// config.js - Configuration system for Baba Yaga engine import fs from 'fs'; import path from 'path'; import { ValidationError } from './error.js'; /** * Configuration class for Baba Yaga engine with validation and defaults */ export class BabaYagaConfig { constructor(options = {}) { // Performance settings this.maxRecursionDepth = this.validateNumber(options.maxRecursionDepth, 1000, 1, 100000, 'maxRecursionDepth'); this.maxExecutionTime = this.validateNumber(options.maxExecutionTime, 30000, 100, 300000, 'maxExecutionTime'); this.maxMemoryUsage = this.validateNumber(options.maxMemoryUsage, 100_000_000, 1000000, 1_000_000_000, 'maxMemoryUsage'); // Input validation limits this.maxSourceLength = this.validateNumber(options.maxSourceLength, 10_000_000, 1000, 100_000_000, 'maxSourceLength'); this.maxASTDepth = this.validateNumber(options.maxASTDepth, 1000, 10, 10000, 'maxASTDepth'); this.maxIdentifierLength = this.validateNumber(options.maxIdentifierLength, 255, 1, 1000, 'maxIdentifierLength'); this.maxStringLength = this.validateNumber(options.maxStringLength, 1_000_000, 1000, 10_000_000, 'maxStringLength'); this.maxListLength = this.validateNumber(options.maxListLength, 100_000, 100, 10_000_000, 'maxListLength'); this.maxTableSize = this.validateNumber(options.maxTableSize, 10_000, 10, 1_000_000, 'maxTableSize'); // Feature flags this.enableOptimizations = this.validateBoolean(options.enableOptimizations, false, 'enableOptimizations'); // Disabled by default due to lexer bug this.enableTypeChecking = this.validateBoolean(options.enableTypeChecking, true, 'enableTypeChecking'); this.enableDebugMode = this.validateBoolean(options.enableDebugMode, false, 'enableDebugMode'); this.enableProfiling = this.validateBoolean(options.enableProfiling, false, 'enableProfiling'); this.strictMode = this.validateBoolean(options.strictMode, false, 'strictMode'); // Security settings this.allowUnsafeOperations = this.validateBoolean(options.allowUnsafeOperations, true, 'allowUnsafeOperations'); this.sandboxMode = this.validateBoolean(options.sandboxMode, false, 'sandboxMode'); this.allowedBuiltins = this.validateArray(options.allowedBuiltins, this.getDefaultBuiltins(), 'allowedBuiltins'); this.allowedCharacters = options.allowedCharacters instanceof RegExp ? options.allowedCharacters : /^[\x20-\x7E\s\n\r\t]*$/; // Printable ASCII + whitespace // Error handling this.verboseErrors = this.validateBoolean(options.verboseErrors, true, 'verboseErrors'); this.maxErrorSuggestions = this.validateNumber(options.maxErrorSuggestions, 3, 0, 10, 'maxErrorSuggestions'); this.includeStackTrace = this.validateBoolean(options.includeStackTrace, true, 'includeStackTrace'); // Output settings this.outputFormat = this.validateString(options.outputFormat, 'pretty', ['pretty', 'json', 'minimal'], 'outputFormat'); this.colorOutput = this.validateBoolean(options.colorOutput, true, 'colorOutput'); this.showTimings = this.validateBoolean(options.showTimings, false, 'showTimings'); // Custom extensions this.customBuiltins = this.validateMap(options.customBuiltins, new Map(), 'customBuiltins'); this.plugins = this.validateArray(options.plugins, [], 'plugins'); // Host integration this.hostInterface = options.hostInterface || null; this.ioHandlers = this.validateObject(options.ioHandlers, {}, 'ioHandlers'); // Cache settings this.enableCaching = this.validateBoolean(options.enableCaching, false, 'enableCaching'); this.cacheDirectory = this.validateString(options.cacheDirectory, '.baba-cache', null, 'cacheDirectory'); // Development settings this.repl = this.validateObject(options.repl, { historySize: 1000, multilineMode: true, autoComplete: true, showTypes: false }, 'repl'); // Freeze configuration to prevent accidental modification if (options.freeze !== false) { Object.freeze(this); } } /** * Validate and normalize a number option */ validateNumber(value, defaultValue, min, max, name) { if (value === undefined || value === null) { return defaultValue; } if (typeof value !== 'number' || isNaN(value)) { throw new ValidationError(`Configuration option '${name}' must be a number, got: ${typeof value}`); } if (value < min || value > max) { throw new ValidationError(`Configuration option '${name}' must be between ${min} and ${max}, got: ${value}`); } return value; } /** * Validate and normalize a boolean option */ validateBoolean(value, defaultValue, name) { if (value === undefined || value === null) { return defaultValue; } if (typeof value !== 'boolean') { throw new ValidationError(`Configuration option '${name}' must be a boolean, got: ${typeof value}`); } return value; } /** * Validate and normalize a string option */ validateString(value, defaultValue, allowedValues, name) { if (value === undefined || value === null) { return defaultValue; } if (typeof value !== 'string') { throw new ValidationError(`Configuration option '${name}' must be a string, got: ${typeof value}`); } if (allowedValues && !allowedValues.includes(value)) { throw new ValidationError(`Configuration option '${name}' must be one of: ${allowedValues.join(', ')}, got: ${value}`); } return value; } /** * Validate and normalize an array option */ validateArray(value, defaultValue, name) { if (value === undefined || value === null) { return defaultValue; } if (!Array.isArray(value)) { throw new ValidationError(`Configuration option '${name}' must be an array, got: ${typeof value}`); } return [...value]; // Create a copy } /** * Validate and normalize an object option */ validateObject(value, defaultValue, name) { if (value === undefined || value === null) { return defaultValue; } if (typeof value !== 'object' || Array.isArray(value)) { throw new ValidationError(`Configuration option '${name}' must be an object, got: ${typeof value}`); } return { ...defaultValue, ...value }; // Merge with defaults } /** * Validate and normalize a Map option */ validateMap(value, defaultValue, name) { if (value === undefined || value === null) { return defaultValue; } if (!(value instanceof Map)) { if (typeof value === 'object' && !Array.isArray(value)) { // Convert object to Map return new Map([...defaultValue.entries(), ...Object.entries(value)]); } else { throw new ValidationError(`Configuration option '${name}' must be a Map or object, got: ${typeof value}`); } } return new Map([...defaultValue.entries(), ...value.entries()]); } /** * Get default built-in functions */ getDefaultBuiltins() { return [ // Higher-order functions 'map', 'filter', 'reduce', 'length', // List operations 'append', 'prepend', 'concat', 'update', 'removeAt', 'slice', // Table operations 'set', 'remove', 'merge', 'keys', 'values', // String operations 'str.concat', 'str.split', 'str.join', 'str.length', 'str.substring', 'str.replace', 'str.trim', 'str.upper', 'str.lower', // Math operations 'math.abs', 'math.sign', 'math.floor', 'math.ceil', 'math.round', 'math.trunc', 'math.min', 'math.max', 'math.clamp', 'math.pow', 'math.sqrt', 'math.exp', 'math.log', 'math.sin', 'math.cos', 'math.tan', 'math.asin', 'math.acos', 'math.atan', 'math.atan2', 'math.deg', 'math.rad', 'math.random', 'math.randomInt', // IO operations 'io.out', 'io.in', 'io.emit', 'io.listen', // Introspection 'shape' ]; } /** * Create configuration from JSON file */ static fromFile(configPath) { try { const fullPath = path.resolve(configPath); const configData = fs.readFileSync(fullPath, 'utf8'); const parsed = JSON.parse(configData); return new BabaYagaConfig(parsed); } catch (error) { if (error.code === 'ENOENT') { throw new ValidationError(`Configuration file not found: ${configPath}`); } else if (error instanceof SyntaxError) { throw new ValidationError(`Invalid JSON in configuration file: ${error.message}`); } else { throw new ValidationError(`Failed to load configuration: ${error.message}`); } } } /** * Create configuration from environment variables */ static fromEnvironment(prefix = 'BABA_') { const options = {}; for (const [key, value] of Object.entries(process.env)) { if (key.startsWith(prefix)) { const configKey = key.slice(prefix.length).toLowerCase() .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); // Try to parse as JSON, fall back to string try { options[configKey] = JSON.parse(value); } catch { options[configKey] = value; } } } return new BabaYagaConfig(options); } /** * Create development configuration */ static development() { return new BabaYagaConfig({ enableDebugMode: true, enableProfiling: true, verboseErrors: true, showTimings: true, strictMode: false, maxRecursionDepth: 500, // Lower for development maxExecutionTime: 10000, // 10 seconds repl: { historySize: 10000, multilineMode: true, autoComplete: true, showTypes: true } }); } /** * Create production configuration */ static production() { return new BabaYagaConfig({ enableDebugMode: false, enableProfiling: false, verboseErrors: false, showTimings: false, strictMode: true, enableOptimizations: true, sandboxMode: true, allowUnsafeOperations: false, maxRecursionDepth: 2000, maxExecutionTime: 5000, // 5 seconds enableCaching: true }); } /** * Create testing configuration */ static testing() { return new BabaYagaConfig({ enableDebugMode: true, verboseErrors: true, strictMode: true, maxRecursionDepth: 100, // Low for testing maxExecutionTime: 1000, // 1 second maxSourceLength: 100000, // Smaller for tests includeStackTrace: true }); } /** * Create sandbox configuration for untrusted code */ static sandbox() { return new BabaYagaConfig({ sandboxMode: true, allowUnsafeOperations: false, strictMode: true, maxRecursionDepth: 100, maxExecutionTime: 1000, maxSourceLength: 10000, maxASTDepth: 50, maxListLength: 1000, maxTableSize: 100, allowedBuiltins: [ 'map', 'filter', 'reduce', 'str.length', 'str.substring', 'math.abs', 'math.min', 'math.max', 'math.floor', 'math.ceil' ] }); } /** * Merge with another configuration */ merge(otherConfig) { if (!(otherConfig instanceof BabaYagaConfig)) { otherConfig = new BabaYagaConfig(otherConfig); } const merged = {}; // Copy all properties from both configs for (const key of Object.keys(this)) { merged[key] = otherConfig.hasOwnProperty(key) ? otherConfig[key] : this[key]; } return new BabaYagaConfig({ ...merged, freeze: false }); } /** * Export configuration to JSON */ toJSON() { const config = {}; for (const [key, value] of Object.entries(this)) { if (value instanceof Map) { config[key] = Object.fromEntries(value.entries()); } else if (value instanceof RegExp) { config[key] = value.source; } else { config[key] = value; } } return config; } /** * Save configuration to file */ saveToFile(filePath) { const config = this.toJSON(); const json = JSON.stringify(config, null, 2); try { fs.writeFileSync(filePath, json, 'utf8'); } catch (error) { throw new ValidationError(`Failed to save configuration: ${error.message}`); } } /** * Validate configuration consistency */ validate() { const errors = []; // Check for logical inconsistencies if (this.maxASTDepth > this.maxRecursionDepth * 2) { errors.push('maxASTDepth should not be more than twice maxRecursionDepth'); } if (this.maxExecutionTime < 100) { errors.push('maxExecutionTime should be at least 100ms'); } if (this.sandboxMode && this.allowUnsafeOperations) { errors.push('sandboxMode and allowUnsafeOperations cannot both be true'); } if (this.enableCaching && !this.cacheDirectory) { errors.push('cacheDirectory must be specified when enableCaching is true'); } if (errors.length > 0) { throw new ValidationError(`Configuration validation failed:\n - ${errors.join('\n - ')}`); } return true; } /** * Get a summary of the current configuration */ summary() { return { performance: { maxRecursionDepth: this.maxRecursionDepth, maxExecutionTime: this.maxExecutionTime, maxMemoryUsage: this.maxMemoryUsage, enableOptimizations: this.enableOptimizations }, security: { sandboxMode: this.sandboxMode, allowUnsafeOperations: this.allowUnsafeOperations, strictMode: this.strictMode, allowedBuiltinsCount: this.allowedBuiltins.length }, limits: { maxSourceLength: this.maxSourceLength, maxASTDepth: this.maxASTDepth, maxListLength: this.maxListLength, maxTableSize: this.maxTableSize }, features: { enableDebugMode: this.enableDebugMode, enableProfiling: this.enableProfiling, enableTypeChecking: this.enableTypeChecking, enableCaching: this.enableCaching } }; } }