// engine.js - Main Baba Yaga engine with improved error handling and configuration import { createLexer } from './lexer.js'; import { createParser } from './parser.js'; import { createInterpreter } from './interpreter.js'; import { BabaYagaConfig } from '../core/config.js'; import { InputValidator, SecurityValidator } from '../core/validation.js'; import { BabaError } from '../core/error.js'; /** * Main Baba Yaga engine class */ export class BabaYagaEngine { constructor(config = new BabaYagaConfig()) { this.config = config; this.validator = config.sandboxMode ? new SecurityValidator(config) : new InputValidator(config); // Performance tracking this.stats = { totalExecutions: 0, totalTime: 0, averageTime: 0, errors: 0 }; } /** * Execute Baba Yaga source code */ async execute(source, options = {}) { const startTime = performance.now(); try { // Validate input this.validator.validateSourceCode(source, options.filename || ''); // Lexical analysis const lexer = createLexer(source); const tokens = lexer.allTokens(); if (this.config.enableDebugMode) { console.log('[DEBUG] Tokens:', tokens.length); } // Parsing const parser = createParser(tokens, this.config.enableDebugMode, source); const ast = parser.parse(); // Validate AST this.validator.validateAST(ast, source); if (this.config.enableDebugMode) { console.log('[DEBUG] AST depth:', this.getASTDepth(ast)); } // Interpretation const host = this.createHostInterface(source, options); const interpreter = createInterpreter(ast, host); // Set up execution timeout let timeoutId; const executionPromise = new Promise((resolve, reject) => { try { const result = interpreter.interpret(); resolve(result); } catch (error) { reject(error); } }); const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new BabaError( `Execution timeout after ${this.config.maxExecutionTime}ms`, null, source, ['Reduce recursion depth', 'Optimize algorithm complexity', 'Increase maxExecutionTime'] )); }, this.config.maxExecutionTime); }); const result = await Promise.race([executionPromise, timeoutPromise]); clearTimeout(timeoutId); // Update statistics const executionTime = performance.now() - startTime; this.updateStats(executionTime, false); if (this.config.showTimings) { console.log(`[TIMING] Execution completed in ${executionTime.toFixed(2)}ms`); } return { result, executionTime, success: true }; } catch (error) { const executionTime = performance.now() - startTime; this.updateStats(executionTime, true); // Format error for display if (error instanceof BabaError) { const formattedError = this.config.verboseErrors ? error.formatError() : error.message; return { error: formattedError, errorType: error.name, executionTime, success: false, suggestions: error.suggestions }; } else { // Unexpected error console.error('[INTERNAL ERROR]', error); return { error: 'Internal error occurred', errorType: 'InternalError', executionTime, success: false, suggestions: ['Report this as a bug', 'Check for malformed input'] }; } } } /** * Create host interface for interpreter */ createHostInterface(source, options) { return { source, scope: options.scope || new Map(), io: { out: (...args) => { if (options.onOutput) { options.onOutput(...args); } else { console.log(...args); } }, in: () => { if (options.onInput) { return options.onInput(); } else { throw new BabaError('Input not available in this context'); } }, emit: (event) => { if (options.onEvent) { options.onEvent(event); } }, addListener: (topic, handler) => { if (options.onAddListener) { return options.onAddListener(topic, handler); } return () => {}; // No-op unsubscribe }, debug: this.config.enableDebugMode ? console.log : () => {}, ...this.config.ioHandlers } }; } /** * Get AST depth for validation */ getASTDepth(node, depth = 0) { if (!node || typeof node !== 'object') { return depth; } let maxDepth = depth; // Check common AST node children const childFields = ['body', 'left', 'right', 'operand', 'callee', 'arguments', 'elements', 'discriminants', 'cases']; for (const field of childFields) { const child = node[field]; if (child) { if (Array.isArray(child)) { for (const item of child) { maxDepth = Math.max(maxDepth, this.getASTDepth(item, depth + 1)); } } else { maxDepth = Math.max(maxDepth, this.getASTDepth(child, depth + 1)); } } } return maxDepth; } /** * Update execution statistics */ updateStats(executionTime, isError) { this.stats.totalExecutions++; this.stats.totalTime += executionTime; this.stats.averageTime = this.stats.totalTime / this.stats.totalExecutions; if (isError) { this.stats.errors++; } } /** * Get engine statistics */ getStats() { return { ...this.stats, errorRate: this.stats.totalExecutions > 0 ? this.stats.errors / this.stats.totalExecutions : 0 }; } /** * Reset statistics */ resetStats() { this.stats = { totalExecutions: 0, totalTime: 0, averageTime: 0, errors: 0 }; } /** * Validate configuration */ validateConfig() { return this.config.validate(); } /** * Update configuration */ updateConfig(newConfig) { if (newConfig instanceof BabaYagaConfig) { this.config = newConfig; } else { this.config = this.config.merge(newConfig); } // Update validator if security mode changed this.validator = this.config.sandboxMode ? new SecurityValidator(this.config) : new InputValidator(this.config); } } /** * Convenience function for quick execution */ export async function execute(source, config = new BabaYagaConfig()) { const engine = new BabaYagaEngine(config); return engine.execute(source); } /** * Create engine with preset configurations */ export function createEngine(preset = 'default') { let config; switch (preset) { case 'development': config = BabaYagaConfig.development(); break; case 'production': config = BabaYagaConfig.production(); break; case 'testing': config = BabaYagaConfig.testing(); break; case 'sandbox': config = BabaYagaConfig.sandbox(); break; default: config = new BabaYagaConfig(); } return new BabaYagaEngine(config); }