about summary refs log tree commit diff stats
path: root/js/scripting-lang/lang.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/scripting-lang/lang.js')
-rw-r--r--js/scripting-lang/lang.js600
1 files changed, 394 insertions, 206 deletions
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
index abe8a7c..070998e 100644
--- a/js/scripting-lang/lang.js
+++ b/js/scripting-lang/lang.js
@@ -1,16 +1,110 @@
+// Baba Yaga
 // Cross-platform scripting language implementation
 // Supports Node.js, Bun, and browser environments
 
 import { lexer, TokenType } from './lexer.js';
 import { parser } from './parser.js';
 
+// Cross-platform environment detection
+const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
+const isBun = typeof process !== 'undefined' && process.versions && process.versions.bun;
+const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
+
+// Cross-platform debug flag
+const DEBUG = (isNode && process.env.DEBUG) || (isBrowser && window.DEBUG) || false;
+
+// Cross-platform IO operations
+const createReadline = () => {
+    if (isNode || isBun) {
+        const readline = require('readline');
+        return readline.createInterface({
+            input: process.stdin,
+            output: process.stdout
+        });
+    } else if (isBrowser) {
+        // Browser fallback - use prompt() for now
+        return {
+            question: (prompt, callback) => {
+                const result = window.prompt(prompt);
+                callback(result);
+            },
+            close: () => {}
+        };
+    } else {
+        // Fallback for other environments
+        return {
+            question: (prompt, callback) => {
+                callback("fallback input");
+            },
+            close: () => {}
+        };
+    }
+};
+
+const createFileSystem = () => {
+    if (isNode || isBun) {
+        return require('fs');
+    } else if (isBrowser) {
+        // Browser fallback - return a mock filesystem
+        return {
+            readFile: (path, encoding, callback) => {
+                callback(new Error('File system not available in browser'));
+            },
+            writeFile: (path, data, callback) => {
+                callback(new Error('File system not available in browser'));
+            }
+        };
+    } else {
+        // Fallback for other environments
+        return {
+            readFile: (path, encoding, callback) => {
+                callback(new Error('File system not available in this environment'));
+            },
+            writeFile: (path, data, callback) => {
+                callback(new Error('File system not available in this environment'));
+            }
+        };
+    }
+};
+
+// Cross-platform console output
+const safeConsoleLog = (message) => {
+    if (typeof console !== 'undefined') {
+        console.log(message);
+    }
+};
+
+const safeConsoleError = (message) => {
+    if (typeof console !== 'undefined') {
+        console.error(message);
+    }
+};
+
+// Cross-platform process exit
+const safeExit = (code) => {
+    if (isNode || isBun) {
+        process.exit(code);
+    } else if (isBrowser) {
+        // In browser, we can't exit, but we can throw an error or redirect
+        throw new Error(`Process would exit with code ${code}`);
+    }
+};
+
+/**
+ * Environment interface for external system integration
+ * 
+ * @typedef {Object} Environment
+ * @property {Function} getCurrentState - Returns the current state from external system
+ * @property {Function} emitValue - Sends a value to the external system
+ */
+
 /**
  * Initializes the standard library in the provided scope.
  * 
  * @param {Object} scope - The global scope object to inject functions into
  * @description Injects higher-order functions and combinator functions into the interpreter's global scope.
  * These functions provide functional programming utilities and implement the combinator foundation
- * that eliminates parsing ambiguity by translating all operations to function calls.
+ * that reduces parsing ambiguity by translating all operations to function calls.
  * 
  * The standard library includes:
  * - Higher-order functions (map, compose, pipe, apply, filter, reduce, fold, curry)
@@ -27,8 +121,18 @@ import { parser } from './parser.js';
  * typed and does not enforce arity or types at parse time. The combinator functions are
  * designed to work seamlessly with the parser's operator translation, providing a consistent
  * and extensible foundation for all language operations.
+ * 
+ * The standard library is the foundation of the combinator-based architecture. Each function
+ * is designed to support partial application, enabling currying patterns and function composition.
+ * This design choice enables functional programming patterns while maintaining
+ * simplicity and consistency across all operations.
+ * 
+ * Error handling is implemented at the function level, with clear error messages that help
+ * users understand what went wrong and how to fix it. This includes type checking for
+ * function arguments and validation of input data.
  */
 function initializeStandardLibrary(scope) {
+
     /**
      * Map: Apply a function to a value or collection
      * @param {Function} f - Function to apply
@@ -42,7 +146,7 @@ function initializeStandardLibrary(scope) {
      * 
      * The function implements APL-inspired element-wise operations for tables:
      * when x is a table, map applies the function to each value while preserving
-     * the table structure and keys. This eliminates the need for explicit loops
+     * the table structure and keys. This reduces the need for explicit loops
      * and enables declarative data transformation patterns.
      * 
      * The function supports partial application: when called with only the function,
@@ -51,10 +155,14 @@ function initializeStandardLibrary(scope) {
      * combinator-based architecture where all operations are function calls.
      * 
      * This design choice aligns with the language's functional foundation and
-     * enables powerful abstractions like `map @double numbers` to transform
+     * enables abstractions like `map @double numbers` to transform
      * every element in a collection without explicit iteration.
+     * 
+     * The function is designed to be polymorphic, working with different data
+     * types including scalars, tables, and arrays. This flexibility enables
+     * consistent data transformation patterns across different data structures.
      */
-    scope.map = function(f, x) { 
+    scope.map = function(f, x) {
         if (typeof f !== 'function') {
             throw new Error('map: first argument must be a function');
         }
@@ -110,7 +218,7 @@ function initializeStandardLibrary(scope) {
      * 
      * Partial application support enables currying patterns where functions can
      * be built incrementally. This is essential for the combinator-based architecture
-     * where complex operations are built from simple, composable functions.
+     * where operations are built from simple, composable functions.
      * 
      * Examples:
      * - compose(double, increment)(5) → double(increment(5)) → double(6) → 12
@@ -205,7 +313,7 @@ function initializeStandardLibrary(scope) {
      * 
      * This function is the core mechanism that enables the parser's juxtaposition
      * detection. When the parser encounters `f x`, it generates `apply(f, x)`,
-     * which this function handles. This design eliminates the need for special
+     * which this function handles. This design reduces the need for special
      * syntax for function calls while maintaining clear precedence rules.
      * 
      * The function supports partial application: when called with only the function,
@@ -252,8 +360,8 @@ function initializeStandardLibrary(scope) {
      * who think in terms of data flow from left to right.
      * 
      * Like compose, it supports partial application for currying patterns.
-     * This enables building complex transformation pipelines incrementally,
-     * which is essential for the combinator-based architecture where complex
+      * This enables building transformation pipelines incrementally,
+ * which is essential for the combinator-based architecture where
      * operations are built from simple, composable functions.
      * 
      * The left-associative design choice makes pipe ideal for data processing
@@ -300,7 +408,7 @@ function initializeStandardLibrary(scope) {
      * The function implements APL-inspired element-wise filtering for tables:
      * when x is a table, filter applies the predicate to each value and returns
      * a new table containing only the key-value pairs where the predicate returns true.
-     * This eliminates the need for explicit loops and enables declarative data
+     * This reduces the need for explicit loops and enables declarative data
      * selection patterns.
      * 
      * The function supports partial application: when called with only the predicate,
@@ -309,7 +417,7 @@ function initializeStandardLibrary(scope) {
      * combinator-based architecture where all operations are function calls.
      * 
      * This design choice aligns with the language's functional foundation and
-     * enables powerful abstractions like `filter @isEven numbers` to select
+     * enables abstractions like `filter @isEven numbers` to select
      * elements from a collection without explicit iteration.
      */
     scope.filter = function(p, x) { 
@@ -363,10 +471,10 @@ function initializeStandardLibrary(scope) {
      * application.
      */
     scope.reduce = function(f, init, x) { 
-        if (process.env.DEBUG) {
-            console.log(`[DEBUG] reduce: f =`, typeof f, f);
-            console.log(`[DEBUG] reduce: init =`, init);
-            console.log(`[DEBUG] reduce: x =`, x);
+        if (DEBUG) {
+            safeConsoleLog(`[DEBUG] reduce: f =`, typeof f, f);
+            safeConsoleLog(`[DEBUG] reduce: init =`, init);
+            safeConsoleLog(`[DEBUG] reduce: x =`, x);
         }
         
         if (typeof f !== 'function') {
@@ -376,10 +484,10 @@ function initializeStandardLibrary(scope) {
         if (init === undefined) {
             // Partial application: return a function that waits for the remaining arguments
             return function(init, x) {
-                if (process.env.DEBUG) {
-                    console.log(`[DEBUG] reduce returned function: f =`, typeof f, f);
-                    console.log(`[DEBUG] reduce returned function: init =`, init);
-                    console.log(`[DEBUG] reduce returned function: x =`, x);
+                if (DEBUG) {
+                    safeConsoleLog(`[DEBUG] reduce returned function: f =`, typeof f, f);
+                    safeConsoleLog(`[DEBUG] reduce returned function: init =`, init);
+                    safeConsoleLog(`[DEBUG] reduce returned function: x =`, x);
                 }
                 if (x === undefined) {
                     // Still partial application
@@ -474,6 +582,12 @@ function initializeStandardLibrary(scope) {
      * operations through the combinator foundation.
      */
     scope.add = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x + y;
+            };
+        }
         return x + y;
     };
     
@@ -484,6 +598,12 @@ function initializeStandardLibrary(scope) {
      * @returns {number} Difference of x and y
      */
     scope.subtract = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x - y;
+            };
+        }
         return x - y;
     };
     
@@ -506,6 +626,12 @@ function initializeStandardLibrary(scope) {
      * operations through the combinator foundation.
      */
     scope.multiply = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x * y;
+            };
+        }
         return x * y;
     };
     
@@ -517,6 +643,15 @@ function initializeStandardLibrary(scope) {
      * @throws {Error} When second argument is zero
      */
     scope.divide = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                if (y === 0) {
+                    throw new Error('Division by zero');
+                }
+                return x / y;
+            };
+        }
         if (y === 0) {
             throw new Error('Division by zero');
         }
@@ -530,6 +665,12 @@ function initializeStandardLibrary(scope) {
      * @returns {number} Remainder of x divided by y
      */
     scope.modulo = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x % y;
+            };
+        }
         return x % y;
     };
     
@@ -540,6 +681,12 @@ function initializeStandardLibrary(scope) {
      * @returns {number} x raised to the power of y
      */
     scope.power = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return Math.pow(x, y);
+            };
+        }
         return Math.pow(x, y);
     };
     
@@ -769,16 +916,16 @@ function initializeStandardLibrary(scope) {
      * - Scalar + Table: Uses map to apply f with the scalar as first argument to each table element
      * - Scalar + Scalar: Falls back to normal function application for backward compatibility
      * 
-     * This design choice enables powerful multi-argument element-wise operations like
+     * This design choice enables multi-argument element-wise operations like
      * `each @add table1 table2` for element-wise addition, while maintaining compatibility
      * with the parser's two-argument apply model. The function is specifically designed
      * for multi-argument operations, distinguishing it from map which is for single-table
      * transformations.
      */
     scope.each = function(f, x) {
-        if (process.env.DEBUG) {
-            console.log(`[DEBUG] each called with: f=${typeof f}, x=${typeof x}`);
-            console.log(`[DEBUG] x value:`, x);
+        if (DEBUG) {
+            safeConsoleLog(`[DEBUG] each called with: f=${typeof f}, x=${typeof x}`);
+            safeConsoleLog(`[DEBUG] x value:`, x);
         }
         
         if (typeof f !== 'function') {
@@ -847,11 +994,11 @@ function initializeStandardLibrary(scope) {
      * All operations in this namespace are designed to work with the language's
      * immutable data philosophy, where data transformations create new structures
      * rather than modifying existing ones. This enables functional programming
-     * patterns and eliminates side effects from table operations.
+     * patterns and reduces side effects from table operations.
      * 
      * The namespace provides both basic table operations (get, set, delete, merge)
      * and higher-order operations (map, filter, reduce) that work element-wise
-     * on table values. This design choice enables powerful data transformation
+     * on table values. This design choice enables data transformation
      * patterns while maintaining the functional programming principles of the language.
      * 
      * Key design principles:
@@ -1168,7 +1315,9 @@ function initializeStandardLibrary(scope) {
 /**
  * Interpreter: Walks the AST and evaluates each node using the combinator foundation.
  * 
- * @param {Object} ast - Abstract Syntax Tree to evaluate
+ * @param {ASTNode} ast - Abstract Syntax Tree to evaluate
+ * @param {Environment} [environment=null] - External environment for IO operations
+ * @param {Object} [initialState={}] - Initial state for the interpreter
  * @returns {*} The result of evaluating the AST, or a Promise for async operations
  * @throws {Error} For evaluation errors like division by zero, undefined variables, etc.
  * 
@@ -1178,7 +1327,7 @@ function initializeStandardLibrary(scope) {
  * 
  * The interpreter implements a combinator-based architecture where all operations
  * are executed through function calls to standard library combinators. This design
- * eliminates parsing ambiguity while preserving intuitive syntax. The parser translates
+ * reduces parsing ambiguity while preserving intuitive syntax. The parser translates
  * all operators (+, -, *, /, etc.) into FunctionCall nodes that reference combinator
  * functions, ensuring consistent semantics across all operations.
  * 
@@ -1188,16 +1337,13 @@ function initializeStandardLibrary(scope) {
  * - Forward Declaration: Recursive functions are supported through placeholder creation
  * - Error Handling: Comprehensive error detection and reporting with call stack tracking
  * - Debug Support: Optional debug mode for development and troubleshooting
+ * - IO Operations: Support for input/output operations through environment interface
  * 
  * The interpreter processes legacy operator expressions (PlusExpression, MinusExpression, etc.)
  * for backward compatibility, but the parser now generates FunctionCall nodes for all operators,
  * which are handled by the standard library combinator functions. This ensures that all
  * operations follow the same execution model and can be extended by adding new combinator
  * functions to the standard library.
- * are translated to function calls to standard library combinators. This eliminates
- * parsing ambiguity while preserving the original syntax. The parser generates
- * FunctionCall nodes for operators (e.g., x + y becomes add(x, y)), and the
- * interpreter executes these calls using the combinator functions in the global scope.
  * 
  * The interpreter uses a global scope for variable storage and function definitions.
  * Each function call creates a new scope (using prototypal inheritance) to implement 
@@ -1214,8 +1360,12 @@ function initializeStandardLibrary(scope) {
  * 
  * The combinator foundation ensures that all operations are executed through
  * function calls, providing a consistent and extensible execution model. This
- * approach enables powerful abstractions and eliminates the need for special
+ * approach enables abstractions and reduces the need for special
  * handling of different operator types in the interpreter.
+ * 
+ * The interpreter supports both synchronous and asynchronous operations. IO operations
+ * like input and output can return Promises, allowing for non-blocking execution
+ * when interacting with external systems or user input.
  */
 function interpreter(ast, environment = null, initialState = {}) {
     const globalScope = { ...initialState };
@@ -1225,10 +1375,10 @@ function interpreter(ast, environment = null, initialState = {}) {
     let ioOperationsPerformed = false;
     
     // Debug: Check if combinators are available
-    if (process.env.DEBUG) {
-        console.log('[DEBUG] Available functions in global scope:', Object.keys(globalScope));
-        console.log('[DEBUG] add function exists:', typeof globalScope.add === 'function');
-        console.log('[DEBUG] subtract function exists:', typeof globalScope.subtract === 'function');
+    if (DEBUG) {
+        safeConsoleLog('[DEBUG] Available functions in global scope:', Object.keys(globalScope));
+        safeConsoleLog('[DEBUG] add function exists:', typeof globalScope.add === 'function');
+        safeConsoleLog('[DEBUG] subtract function exists:', typeof globalScope.subtract === 'function');
     }
     
     // Reset call stack tracker at the start of interpretation
@@ -1237,7 +1387,7 @@ function interpreter(ast, environment = null, initialState = {}) {
     /**
      * Evaluates AST nodes in the global scope using the combinator foundation.
      * 
-     * @param {Object} node - AST node to evaluate
+     * @param {ASTNode} node - AST node to evaluate
      * @returns {*} The result of evaluating the node
      * @throws {Error} For evaluation errors
      * 
@@ -1270,6 +1420,16 @@ function interpreter(ast, environment = null, initialState = {}) {
      * - WhenExpression: Pattern matching with wildcard support
      * - TableLiteral: Creates immutable table structures
      * - TableAccess: Safe property access with error handling
+     * - IO Operations: Handles input/output through environment interface
+     * 
+     * The function maintains call stack tracking for debugging and error reporting.
+     * This enables detailed error messages that include the call chain leading to
+     * the error, making it easier to debug programs.
+     * 
+     * Error handling is comprehensive, with specific error messages for common
+     * issues like undefined variables, type mismatches, and division by zero.
+     * Each error includes context about where the error occurred and what was
+     * expected, helping users quickly identify and fix issues.
      */
     function evalNode(node) {
         callStackTracker.push('evalNode', node?.type || 'unknown');
@@ -1345,8 +1505,8 @@ function interpreter(ast, environment = null, initialState = {}) {
                                 key = evalNode(entry.key);
                             }
                             // Special handling for FunctionDeclaration nodes
-                            if (process.env.DEBUG) {
-                                console.log(`[DEBUG] TableLiteral: entry.value.type = ${entry.value.type}`);
+                            if (DEBUG) {
+                                safeConsoleLog(`[DEBUG] TableLiteral: entry.value.type = ${entry.value.type}`);
                             }
                             if (entry.value.type === 'FunctionDeclaration') {
                                 // Don't evaluate the function body, just create the function
@@ -1542,27 +1702,27 @@ function interpreter(ast, environment = null, initialState = {}) {
                     if (typeof node.name === 'string') {
                         // Regular function call with string name
                         funcToCall = globalScope[node.name];
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] FunctionCall: looking up function '${node.name}' in globalScope, found:`, typeof funcToCall);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: looking up function '${node.name}' in globalScope, found:`, typeof funcToCall);
                         }
                     } else if (node.name.type === 'Identifier') {
                         // Function call with identifier
                         funcToCall = globalScope[node.name.value];
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] FunctionCall: looking up function '${node.name.value}' in globalScope, found:`, typeof funcToCall);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: looking up function '${node.name.value}' in globalScope, found:`, typeof funcToCall);
                         }
                     } else {
                         // Function call from expression (e.g., parenthesized function, higher-order)
                         funcToCall = evalNode(node.name);
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] FunctionCall: evaluated function expression, found:`, typeof funcToCall);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: evaluated function expression, found:`, typeof funcToCall);
                         }
                     }
                     
-                    if (funcToCall instanceof Function) {
+                    if (typeof funcToCall === 'function') {
                         let args = node.args.map(evalNode);
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] FunctionCall: calling function with args:`, args);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: calling function with args:`, args);
                         }
                         return funcToCall(...args);
                     }
@@ -1573,16 +1733,16 @@ function interpreter(ast, environment = null, initialState = {}) {
                         ? node.value.map(evalNode) 
                         : [evalNode(node.value)];
                     
-                    if (process.env.DEBUG) {
-                        console.log(`[DEBUG] WhenExpression: whenValues =`, whenValues);
+                    if (DEBUG) {
+                        safeConsoleLog(`[DEBUG] WhenExpression: whenValues =`, whenValues);
                     }
                     
                     for (const caseItem of node.cases) {
                         // Handle both single patterns and arrays of patterns
                         const patterns = caseItem.pattern.map(evalNode);
                         
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] WhenExpression: patterns =`, patterns);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] WhenExpression: patterns =`, patterns);
                         }
                         
                         // Check if patterns match the values
@@ -1594,14 +1754,14 @@ function interpreter(ast, environment = null, initialState = {}) {
                                 const value = whenValues[i];
                                 const pattern = patterns[i];
                                 
-                                if (process.env.DEBUG) {
-                                    console.log(`[DEBUG] WhenExpression: comparing value ${value} with pattern ${pattern}`);
+                                if (DEBUG) {
+                                    safeConsoleLog(`[DEBUG] WhenExpression: comparing value ${value} with pattern ${pattern}`);
                                 }
                                 
                                 if (pattern === true) { // Wildcard pattern
                                     // Wildcard always matches
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] WhenExpression: wildcard matches`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: wildcard matches`);
                                     }
                                     continue;
                                 } else if (typeof pattern === 'object' && pattern.type === 'FunctionCall') {
@@ -1617,20 +1777,20 @@ function interpreter(ast, environment = null, initialState = {}) {
                                         };
                                     }
                                     const patternResult = evalNode(patternToEvaluate);
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] WhenExpression: boolean pattern result = ${patternResult}`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: boolean pattern result = ${patternResult}`);
                                     }
                                     if (!patternResult) {
                                         matches = false;
-                                        if (process.env.DEBUG) {
-                                            console.log(`[DEBUG] WhenExpression: boolean pattern does not match`);
+                                        if (DEBUG) {
+                                            safeConsoleLog(`[DEBUG] WhenExpression: boolean pattern does not match`);
                                         }
                                         break;
-                                    } else {
-                                        if (process.env.DEBUG) {
-                                            console.log(`[DEBUG] WhenExpression: boolean pattern matches`);
+                                                                            } else {
+                                            if (DEBUG) {
+                                                safeConsoleLog(`[DEBUG] WhenExpression: boolean pattern matches`);
+                                            }
                                         }
-                                    }
                                 } else if (typeof pattern === 'object' && pattern !== null && typeof value === 'object' && value !== null) {
                                     // Table pattern matching - check if all pattern properties exist in value
                                     let tableMatches = true;
@@ -1642,31 +1802,31 @@ function interpreter(ast, environment = null, initialState = {}) {
                                     }
                                     if (!tableMatches) {
                                         matches = false;
-                                        if (process.env.DEBUG) {
-                                            console.log(`[DEBUG] WhenExpression: table pattern does not match`);
+                                        if (DEBUG) {
+                                            safeConsoleLog(`[DEBUG] WhenExpression: table pattern does not match`);
                                         }
                                         break;
-                                    } else {
-                                        if (process.env.DEBUG) {
-                                            console.log(`[DEBUG] WhenExpression: table pattern matches`);
+                                                                            } else {
+                                            if (DEBUG) {
+                                                safeConsoleLog(`[DEBUG] WhenExpression: table pattern matches`);
+                                            }
                                         }
-                                    }
                                 } else if (value !== pattern) {
                                     matches = false;
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] WhenExpression: pattern does not match`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: pattern does not match`);
                                     }
                                     break;
                                 } else {
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] WhenExpression: pattern matches`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: pattern matches`);
                                     }
                                 }
                             }
                         }
                         
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] WhenExpression: case matches = ${matches}`);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] WhenExpression: case matches = ${matches}`);
                         }
                         
                         if (matches) {
@@ -1681,11 +1841,7 @@ function interpreter(ast, environment = null, initialState = {}) {
                 case 'WildcardPattern':
                     return true;
                 case 'IOInExpression':
-                    const readline = require('readline');
-                    const rl = readline.createInterface({
-                        input: process.stdin,
-                        output: process.stdout
-                    });
+                    const rl = createReadline();
                     
                     return new Promise((resolve) => {
                         rl.question('', (input) => {
@@ -1696,7 +1852,7 @@ function interpreter(ast, environment = null, initialState = {}) {
                     });
                 case 'IOOutExpression':
                     const outputValue = evalNode(node.value);
-                    console.log(outputValue);
+                    safeConsoleLog(outputValue);
                     ioOperationsPerformed = true;
                     return outputValue;
                 case 'IOAssertExpression':
@@ -1708,13 +1864,13 @@ function interpreter(ast, environment = null, initialState = {}) {
                 case 'IOListenExpression':
                     // Return current state from environment if available, otherwise placeholder
                     if (environment && typeof environment.getCurrentState === 'function') {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..listen called - returning state from environment');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning state from environment');
                         }
                         return environment.getCurrentState();
                     } else {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..listen called - returning placeholder state');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning placeholder state');
                         }
                         return { status: 'placeholder', message: 'State not available in standalone mode' };
                     }
@@ -1722,19 +1878,19 @@ function interpreter(ast, environment = null, initialState = {}) {
                     const emitValue = evalNode(node.value);
                     // Send value to environment if available, otherwise log to console
                     if (environment && typeof environment.emitValue === 'function') {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..emit called - sending to environment');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..emit called - sending to environment');
                         }
                         environment.emitValue(emitValue);
                     } else {
-                        console.log('[EMIT]', emitValue);
+                        safeConsoleLog('[EMIT]', emitValue);
                     }
                     ioOperationsPerformed = true;
                     return emitValue;
                 case 'FunctionReference':
                     const functionValue = globalScope[node.name];
-                    if (process.env.DEBUG) {
-                        console.log(`[DEBUG] FunctionReference: looking up '${node.name}' in globalScope, found:`, typeof functionValue);
+                    if (DEBUG) {
+                        safeConsoleLog(`[DEBUG] FunctionReference: looking up '${node.name}' in globalScope, found:`, typeof functionValue);
                     }
                     if (functionValue === undefined) {
                         throw new Error(`Function ${node.name} is not defined`);
@@ -1757,7 +1913,7 @@ function interpreter(ast, environment = null, initialState = {}) {
     /**
      * Evaluates AST nodes in a local scope with access to parent scope.
      * 
-     * @param {Object} node - AST node to evaluate
+     * @param {ASTNode} node - AST node to evaluate
      * @param {Object} scope - Local scope object (prototypally inherits from global)
      * @returns {*} The result of evaluating the node
      * @throws {Error} For evaluation errors
@@ -1778,6 +1934,16 @@ function interpreter(ast, environment = null, initialState = {}) {
      * The function prioritizes local scope lookups over global scope lookups, ensuring
      * that function parameters shadow global variables with the same names. This
      * implements proper lexical scoping semantics.
+     * 
+     * The function maintains the same call stack tracking as evalNode, enabling
+     * consistent debugging and error reporting across both global and local evaluation.
+     * This ensures that errors in function bodies can be traced back to their source
+     * with the same level of detail as global errors.
+     * 
+     * Scope management is implemented using JavaScript's prototypal inheritance,
+     * where each local scope is created as an object that inherits from the global
+     * scope. This approach provides efficient variable lookup while maintaining
+     * proper scoping semantics and enabling access to global functions and variables.
      */
     const localEvalNodeWithScope = (node, scope) => {
         callStackTracker.push('localEvalNodeWithScope', node?.type || 'unknown');
@@ -1971,16 +2137,16 @@ function interpreter(ast, environment = null, initialState = {}) {
                         ? node.value.map(val => localEvalNodeWithScope(val, scope)) 
                         : [localEvalNodeWithScope(node.value, scope)];
                     
-                    if (process.env.DEBUG) {
-                        console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: whenValues =`, whenValues);
+                    if (DEBUG) {
+                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: whenValues =`, whenValues);
                     }
                     
                     for (const caseItem of node.cases) {
                         // Handle both single patterns and arrays of patterns
                         const patterns = caseItem.pattern.map(pat => localEvalNodeWithScope(pat, scope));
                         
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: patterns =`, patterns);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: patterns =`, patterns);
                         }
                         
                         // Check if patterns match the values
@@ -1992,14 +2158,14 @@ function interpreter(ast, environment = null, initialState = {}) {
                                 const value = whenValues[i];
                                 const pattern = patterns[i];
                                 
-                                if (process.env.DEBUG) {
-                                    console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: comparing value ${value} with pattern ${pattern}`);
+                                if (DEBUG) {
+                                    safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: comparing value ${value} with pattern ${pattern}`);
                                 }
                                 
                                 if (pattern === true) { // Wildcard pattern
                                     // Wildcard always matches
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: wildcard matches`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: wildcard matches`);
                                     }
                                     continue;
                                 } else if (typeof pattern === 'object' && pattern !== null && typeof value === 'object' && value !== null) {
@@ -2013,31 +2179,31 @@ function interpreter(ast, environment = null, initialState = {}) {
                                     }
                                     if (!tableMatches) {
                                         matches = false;
-                                        if (process.env.DEBUG) {
-                                            console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern does not match`);
+                                        if (DEBUG) {
+                                            safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern does not match`);
                                         }
                                         break;
-                                    } else {
-                                        if (process.env.DEBUG) {
-                                            console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern matches`);
+                                                                            } else {
+                                            if (DEBUG) {
+                                                safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern matches`);
+                                            }
                                         }
-                                    }
                                 } else if (value !== pattern) {
                                     matches = false;
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern does not match`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern does not match`);
                                     }
                                     break;
                                 } else {
-                                    if (process.env.DEBUG) {
-                                        console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern matches`);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern matches`);
                                     }
                                 }
                             }
                         }
                         
-                        if (process.env.DEBUG) {
-                            console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: case matches = ${matches}`);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: case matches = ${matches}`);
                         }
                         
                         if (matches) {
@@ -2052,22 +2218,18 @@ function interpreter(ast, environment = null, initialState = {}) {
                 case 'WildcardPattern':
                     return true;
                 case 'IOInExpression':
-                    const readline = require('readline');
-                    const rl = readline.createInterface({
-                        input: process.stdin,
-                        output: process.stdout
-                    });
+                    const rl2 = createReadline();
                     
                     return new Promise((resolve) => {
-                        rl.question('', (input) => {
-                            rl.close();
+                        rl2.question('', (input) => {
+                            rl2.close();
                             const num = parseInt(input);
                             resolve(isNaN(num) ? input : num);
                         });
                     });
                 case 'IOOutExpression':
                     const localOutputValue = localEvalNodeWithScope(node.value, scope);
-                    console.log(localOutputValue);
+                    safeConsoleLog(localOutputValue);
                     ioOperationsPerformed = true;
                     return localOutputValue;
                 case 'IOAssertExpression':
@@ -2079,13 +2241,13 @@ function interpreter(ast, environment = null, initialState = {}) {
                 case 'IOListenExpression':
                     // Return current state from environment if available, otherwise placeholder
                     if (environment && typeof environment.getCurrentState === 'function') {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..listen called - returning state from environment');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning state from environment');
                         }
                         return environment.getCurrentState();
                     } else {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..listen called - returning placeholder state');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning placeholder state');
                         }
                         return { status: 'placeholder', message: 'State not available in standalone mode' };
                     }
@@ -2093,12 +2255,12 @@ function interpreter(ast, environment = null, initialState = {}) {
                     const localEmitValue = localEvalNodeWithScope(node.value, scope);
                     // Send value to environment if available, otherwise log to console
                     if (environment && typeof environment.emitValue === 'function') {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..emit called - sending to environment');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..emit called - sending to environment');
                         }
                         environment.emitValue(localEmitValue);
                     } else {
-                        console.log('[EMIT]', localEmitValue);
+                        safeConsoleLog('[EMIT]', localEmitValue);
                     }
                     ioOperationsPerformed = true;
                     return localEmitValue;
@@ -2383,22 +2545,18 @@ function interpreter(ast, environment = null, initialState = {}) {
                 case 'WildcardPattern':
                     return true;
                 case 'IOInExpression':
-                    const readline = require('readline');
-                    const rl = readline.createInterface({
-                        input: process.stdin,
-                        output: process.stdout
-                    });
+                    const rl3 = createReadline();
                     
                     return new Promise((resolve) => {
-                        rl.question('', (input) => {
-                            rl.close();
+                        rl3.question('', (input) => {
+                            rl3.close();
                             const num = parseInt(input);
                             resolve(isNaN(num) ? input : num);
                         });
                     });
                 case 'IOOutExpression':
                     const localOutputValue = localEvalNode(node.value);
-                    console.log(localOutputValue);
+                    safeConsoleLog(localOutputValue);
                     ioOperationsPerformed = true;
                     return localOutputValue;
                 case 'IOAssertExpression':
@@ -2410,13 +2568,13 @@ function interpreter(ast, environment = null, initialState = {}) {
                 case 'IOListenExpression':
                     // Return current state from environment if available, otherwise placeholder
                     if (environment && typeof environment.getCurrentState === 'function') {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..listen called - returning state from environment');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning state from environment');
                         }
                         return environment.getCurrentState();
                     } else {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..listen called - returning placeholder state');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning placeholder state');
                         }
                         return { status: 'placeholder', message: 'State not available in standalone mode' };
                     }
@@ -2424,12 +2582,12 @@ function interpreter(ast, environment = null, initialState = {}) {
                     const localEmitValue = localEvalNode(node.value);
                     // Send value to environment if available, otherwise log to console
                     if (environment && typeof environment.emitValue === 'function') {
-                        if (process.env.DEBUG) {
-                            console.log('[DEBUG] ..emit called - sending to environment');
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..emit called - sending to environment');
                         }
                         environment.emitValue(localEmitValue);
                     } else {
-                        console.log('[EMIT]', localEmitValue);
+                        safeConsoleLog('[EMIT]', localEmitValue);
                     }
                     ioOperationsPerformed = true;
                     return localEmitValue;
@@ -2472,10 +2630,39 @@ function interpreter(ast, environment = null, initialState = {}) {
 /**
  * Run script with environment support for harness integration
  * 
- * @param {string} scriptContent - Script content to execute
- * @param {Object} initialState - Initial state for the script
- * @param {Object} environment - Script environment for IO operations
- * @returns {Object} Script execution result
+ * @param {string} scriptContent - The script content to execute
+ * @param {Object} [initialState={}] - Initial state for the interpreter
+ * @param {Environment} [environment=null] - Environment for IO operations
+ * @returns {*} The result of executing the script
+ * @throws {Error} For parsing or evaluation errors
+ * 
+ * @description Parses and executes a script using the combinator-based language.
+ * This function orchestrates the entire execution pipeline from source code
+ * to final result.
+ * 
+ * The function performs the following steps:
+ * 1. Tokenize the source code using the lexer
+ * 2. Parse the tokens into an AST using the parser
+ * 3. Evaluate the AST using the interpreter
+ * 4. Return the final result
+ * 
+ * This is the primary interface for executing scripts in the language.
+ * It handles the parsing and evaluation pipeline,
+ * providing a simple interface for users to run their code.
+ * 
+ * The function supports both synchronous and asynchronous execution. When
+ * the script contains IO operations that return Promises, the function
+ * will return a Promise that resolves to the final result. This enables
+ * non-blocking execution for interactive programs.
+ * 
+ * Error handling is comprehensive, with errors from any stage of the
+ * pipeline (lexing, parsing, or evaluation) being caught and re-thrown
+ * with appropriate context. This ensures that users get meaningful
+ * error messages that help them identify and fix issues in their code.
+ * 
+ * The function is designed to be stateless, with each call creating
+ * a fresh interpreter instance. This ensures that scripts don't interfere
+ * with each other and enables safe concurrent execution of multiple scripts.
  */
 function run(scriptContent, initialState = {}, environment = null) {
     // Parse the script
@@ -2508,14 +2695,14 @@ function run(scriptContent, initialState = {}, environment = null) {
  * and how the interpreter executes these calls through the standard library.
  * 
  * The function is designed to be lightweight and safe to call frequently,
- * making it suitable for tracing execution flow through complex nested
+ * making it suitable for tracing execution flow through nested
  * expressions and function applications.
  */
 function debugLog(message, data = null) {
-    if (process.env.DEBUG) {
-        console.log(`[DEBUG] ${message}`);
+    if (DEBUG) {
+        safeConsoleLog(`[DEBUG] ${message}`);
         if (data) {
-            console.log(data);
+            safeConsoleLog(data);
         }
     }
 }
@@ -2539,10 +2726,10 @@ function debugLog(message, data = null) {
  * execution pipeline.
  */
 function debugError(message, error = null) {
-    if (process.env.DEBUG) {
-        console.error(`[DEBUG ERROR] ${message}`);
+    if (DEBUG) {
+        safeConsoleError(`[DEBUG ERROR] ${message}`);
         if (error) {
-            console.error(error);
+            safeConsoleError(error);
         }
     }
 }
@@ -2559,7 +2746,7 @@ function debugError(message, error = null) {
  * potential infinite recursion by monitoring stack depth.
  * 
  * This tool is particularly important for the combinator-based architecture
- * where function calls are the primary execution mechanism, and complex
+ * where function calls are the primary execution mechanism, and
  * nested expressions can lead to deep call stacks. The tracker helps identify
  * when the combinator translation creates unexpectedly deep call chains,
  * enabling optimization of the function composition and application patterns.
@@ -2603,8 +2790,8 @@ const callStackTracker = {
             throw new Error(`Potential infinite recursion detected. Call stack depth: ${this.stack.length}`);
         }
         
-        if (process.env.DEBUG && this.stack.length % 100 === 0) {
-            console.log(`[DEBUG] Call stack depth: ${this.stack.length}, Max: ${this.maxDepth}`);
+        if (DEBUG && this.stack.length % 100 === 0) {
+            safeConsoleLog(`[DEBUG] Call stack depth: ${this.stack.length}, Max: ${this.maxDepth}`);
         }
     },
     
@@ -2664,22 +2851,18 @@ const callStackTracker = {
  * workflow where tests and examples are stored as .txt files.
  */
 async function readFile(filePath) {
-    // Check if we're in a browser environment
-    if (typeof window !== 'undefined') {
-        // Browser environment - would need to implement file input or fetch
-        throw new Error('File I/O not supported in browser environment');
-    }
+    // Use cross-platform filesystem
+    const fs = createFileSystem();
     
-    // Node.js or Bun environment
-    try {
-        // Try dynamic import for ES modules compatibility
-        const fs = await import('fs');
-        return fs.readFileSync(filePath, 'utf8');
-    } catch (error) {
-        // Fallback to require for older Node.js versions
-        const fs = require('fs');
-        return fs.readFileSync(filePath, 'utf8');
-    }
+    return new Promise((resolve, reject) => {
+        fs.readFile(filePath, 'utf8', (error, data) => {
+            if (error) {
+                reject(error);
+            } else {
+                resolve(data);
+            }
+        });
+    });
 }
 
 /**
@@ -2714,8 +2897,8 @@ async function readFile(filePath) {
 async function executeFile(filePath) {
     try {
         // Validate file extension
-        if (!filePath.endsWith('.txt')) {
-            throw new Error('Only .txt files are supported');
+        if (!filePath.endsWith('.txt') && !filePath.endsWith('.baba')) {
+            throw new Error('Only .txt and .baba files are supported');
         }
         
         const input = await readFile(filePath);
@@ -2733,50 +2916,50 @@ async function executeFile(filePath) {
         if (result instanceof Promise) {
             result.then(finalResult => {
                 // Only output result if debug mode is enabled (no automatic final result output)
-                if (finalResult.result !== undefined && process.env.DEBUG) {
-                    console.log(finalResult.result);
+                if (finalResult.result !== undefined && DEBUG) {
+                    safeConsoleLog(finalResult.result);
                 }
                 // Print call stack statistics only in debug mode
-                if (process.env.DEBUG) {
+                if (DEBUG) {
                     const stats = callStackTracker.getStats();
-                    console.log('\n=== CALL STACK STATISTICS ===');
-                    console.log('Maximum call stack depth:', stats.maxDepth);
-                    console.log('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+                    safeConsoleLog('\n=== CALL STACK STATISTICS ===');
+                    safeConsoleLog('Maximum call stack depth:', stats.maxDepth);
+                    safeConsoleLog('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
                 }
             }).catch(error => {
-                console.error(`Error executing file: ${error.message}`);
+                safeConsoleError(`Error executing file: ${error.message}`);
                 // Print call stack statistics on error only in debug mode
-                if (process.env.DEBUG) {
+                if (DEBUG) {
                     const stats = callStackTracker.getStats();
-                    console.error('\n=== CALL STACK STATISTICS ON ERROR ===');
-                    console.error('Maximum call stack depth:', stats.maxDepth);
-                    console.error('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+                    safeConsoleError('\n=== CALL STACK STATISTICS ON ERROR ===');
+                    safeConsoleError('Maximum call stack depth:', stats.maxDepth);
+                    safeConsoleError('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
                 }
-                process.exit(1);
+                safeExit(1);
             });
         } else {
             // Only output result if debug mode is enabled (no automatic final result output)
-            if (result.result !== undefined && process.env.DEBUG) {
-                console.log(result.result);
+            if (result.result !== undefined && DEBUG) {
+                safeConsoleLog(result.result);
             }
             // Print call stack statistics only in debug mode
-            if (process.env.DEBUG) {
+            if (DEBUG) {
                 const stats = callStackTracker.getStats();
-                console.log('\n=== CALL STACK STATISTICS ===');
-                console.log('Maximum call stack depth:', stats.maxDepth);
-                console.log('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+                safeConsoleLog('\n=== CALL STACK STATISTICS ===');
+                safeConsoleLog('Maximum call stack depth:', stats.maxDepth);
+                safeConsoleLog('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
             }
         }
     } catch (error) {
-        console.error(`Error executing file: ${error.message}`);
+        safeConsoleError(`Error executing file: ${error.message}`);
         // Print call stack statistics on error only in debug mode
-        if (process.env.DEBUG) {
+        if (DEBUG) {
             const stats = callStackTracker.getStats();
-            console.error('\n=== CALL STACK STATISTICS ON ERROR ===');
-            console.error('Maximum call stack depth:', stats.maxDepth);
-            console.error('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+            safeConsoleError('\n=== CALL STACK STATISTICS ON ERROR ===');
+            safeConsoleError('Maximum call stack depth:', stats.maxDepth);
+            safeConsoleError('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
         }
-        process.exit(1);
+        safeExit(1);
     }
 }
 
@@ -2794,29 +2977,34 @@ async function executeFile(filePath) {
  * Exits with appropriate error codes for different failure scenarios.
  */
 async function main() {
+    // Only run main function in Node.js/Bun environments
+    if (!isNode && !isBun) {
+        return; // Skip in browser environment
+    }
+    
     const args = process.argv.slice(2);
 
     if (args.length === 0) {
-        console.error('Usage: node lang.js <file>');
-        console.error('  Provide a file path to execute');
-        process.exit(1);
+        safeConsoleError('Usage: node lang.js <file>');
+        safeConsoleError('  Provide a file path to execute');
+        safeExit(1);
     } else if (args.length === 1) {
         // Execute the file
         const filePath = args[0];
         await executeFile(filePath);
     } else {
         // Too many arguments
-        console.error('Usage: node lang.js <file>');
-        console.error('  Provide exactly one file path to execute');
-        process.exit(1);
+        safeConsoleError('Usage: node lang.js <file>');
+        safeConsoleError('  Provide exactly one file path to execute');
+        safeExit(1);
     }
 }
 
-// Start the program only if this file is run directly
-if (process.argv[1] && process.argv[1].endsWith('lang.js')) {
+// Start the program only if this file is run directly in Node.js/Bun
+if ((isNode || isBun) && process.argv[1] && process.argv[1].endsWith('lang.js')) {
     main().catch(error => {
-        console.error('Fatal error:', error.message);
-        process.exit(1);
+        safeConsoleError('Fatal error:', error.message);
+        safeExit(1);
     });
 }