about summary refs log tree commit diff stats
path: root/js/baba-yaga/src/core/config.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/baba-yaga/src/core/config.js')
-rw-r--r--js/baba-yaga/src/core/config.js444
1 files changed, 444 insertions, 0 deletions
diff --git a/js/baba-yaga/src/core/config.js b/js/baba-yaga/src/core/config.js
new file mode 100644
index 0000000..51d9c3f
--- /dev/null
+++ b/js/baba-yaga/src/core/config.js
@@ -0,0 +1,444 @@
+// 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
+      }
+    };
+  }
+}