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.js4285
1 files changed, 2567 insertions, 1718 deletions
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
index b66271c..a541e81 100644
--- a/js/scripting-lang/lang.js
+++ b/js/scripting-lang/lang.js
@@ -1,1873 +1,2456 @@
+// 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';
+
 /**
  * Initializes the standard library in the provided scope.
  * 
- * Why: Injecting the standard library directly into the scope ensures that user code can access these functions as if they were built-in, without special syntax or reserved keywords. This approach also allows for easy extension and testing, as the library is just a set of regular functions in the scope chain.
+ * @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.
+ * 
+ * The standard library includes:
+ * - Higher-order functions (map, compose, pipe, apply, filter, reduce, fold, curry)
+ * - Arithmetic combinators (add, subtract, multiply, divide, modulo, power, negate)
+ * - Comparison combinators (equals, notEquals, lessThan, greaterThan, lessEqual, greaterEqual)
+ * - Logical combinators (logicalAnd, logicalOr, logicalXor, logicalNot)
+ * - Enhanced combinators (identity, constant, flip, on, both, either)
+ * 
+ * This approach ensures that user code can access these functions as if they were built-in,
+ * without special syntax or reserved keywords. The combinator foundation allows the parser
+ * to translate all operators to function calls, eliminating ambiguity while preserving syntax.
  * 
- * How: Each function is added as a property of the scope object. Functions are written to check argument types at runtime, since the language is dynamically typed and does not enforce arity or types at parse time.
+ * Functions are written to check argument types at runtime since the language is dynamically
+ * 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.
  */
 function initializeStandardLibrary(scope) {
-    // Map: Apply a function to each element
+    /**
+     * Map: Apply a function to a value or collection
+     * @param {Function} f - Function to apply
+     * @param {*} x - Value or collection to apply function to
+     * @returns {*} Result of applying f to x
+     * @throws {Error} When first argument is not a function
+     * @description The map function is a fundamental higher-order function that
+     * applies a transformation function to a value or collection. This enables
+     * functional programming patterns where data transformations are expressed
+     * as function applications rather than imperative operations.
+     * 
+     * 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
+     * and enables declarative data transformation patterns.
+     * 
+     * The function supports partial application: when called with only the function,
+     * it returns a new function that waits for the value. This enables currying
+     * patterns and function composition chains, which are essential for the
+     * 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
+     * every element in a collection without explicit iteration.
+     */
     scope.map = function(f, x) { 
-        // Handle function references by calling them if they're functions
-        if (typeof f === 'function') {
-            return f(x);
-        } else {
+        if (typeof f !== 'function') {
             throw new Error('map: first argument must be a function');
         }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.map(f, x);
+            };
+        }
+        
+        // Handle tables (APL-style element-wise operations)
+        if (typeof x === 'object' && x !== null && !Array.isArray(x)) {
+            const result = {};
+            for (const [key, value] of Object.entries(x)) {
+                result[key] = f(value);
+            }
+            return result;
+        }
+        
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.map(f);
+        }
+        
+        // Default: apply to single value
+        return f(x);
     };
     
-    // Compose: Compose two functions (f ∘ g)(x) = f(g(x))
-    scope.compose = function(f, g, x) { 
-        if (typeof f === 'function' && typeof g === 'function') {
-            if (arguments.length === 3) {
-                // compose f g x = f(g(x))
-                return f(g(x));
-            } else {
-                // compose f g = function that takes x and returns f(g(x))
+    /**
+     * Compose: Combine two functions into a new function (function composition)
+     * @param {Function} f - First function (outer function)
+     * @param {Function} [g] - Second function (optional for partial application)
+     * @returns {Function} Composed function or partially applied function
+     * @throws {Error} When first argument is not a function
+     * @description The compose function is a core functional programming primitive
+     * that combines two functions into a new function. This is the foundation
+     * for the 'via' operator in the language syntax, enabling natural function
+     * composition chains like `f via g via h`.
+     * 
+     * The function implements right-associative composition, meaning that
+     * compose(f, compose(g, h)) creates a function that applies h, then g, then f.
+     * This matches mathematical function composition notation (f ∘ g ∘ h) and
+     * enables natural reading of composition chains from right to left.
+     * 
+     * The 'via' operator translates to compose calls:
+     * - f via g → compose(f, g)
+     * - f via g via h → compose(f, compose(g, h))
+     * - f via g via h via i → compose(f, compose(g, compose(h, i)))
+     * 
+     * This right-associative behavior means that composition chains read naturally
+     * from right to left, matching mathematical notation where (f ∘ g ∘ h)(x) = f(g(h(x))).
+     * 
+     * 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.
+     * 
+     * Examples:
+     * - compose(double, increment)(5) → double(increment(5)) → double(6) → 12
+     * - compose(increment, double)(5) → increment(double(5)) → increment(10) → 11
+     * - double via increment 5 → compose(double, increment)(5) → 12
+     * - increment via double via square 3 → compose(increment, compose(double, square))(3) → 19
+     */
+    scope.compose = function(f, g) {
+        if (typeof f !== 'function') {
+            throw new Error(`compose: first argument must be a function, got ${typeof f}`);
+        }
+        
+        if (g === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(g) {
+                if (typeof g !== 'function') {
+                    throw new Error(`compose: second argument must be a function, got ${typeof g}`);
+                }
                 return function(x) {
                     return f(g(x));
                 };
-            }
-        } else {
-            throw new Error('compose: first two arguments must be functions');
+            };
+        }
+        
+        if (typeof g !== 'function') {
+            throw new Error(`compose: second argument must be a function, got ${typeof g}`);
         }
+        
+        return function(x) {
+            return f(g(x));
+        };
     };
     
-    // Curry: Convert a function that takes multiple arguments into a series of functions
-    // Since our language already uses curried functions by default, this is mostly for explicit currying
+    /**
+     * Curry: Apply a function to arguments (simplified currying)
+     * @param {Function} f - Function to curry
+     * @param {*} x - First argument
+     * @param {*} y - Second argument
+     * @returns {*} Result of applying f to x and y
+     * @throws {Error} When first argument is not a function
+     * @description The curry function provides a simplified currying mechanism
+     * that allows functions to be applied to arguments incrementally. When called
+     * with fewer arguments than the function expects, it returns a new function
+     * that waits for the remaining arguments.
+     * 
+     * This function is designed to work with the parser's one-by-one argument
+     * application system, where multi-argument function calls are translated to
+     * nested apply calls. The nested partial application checks ensure that
+     * functions return partially applied functions until all arguments are received.
+     */
     scope.curry = function(f, x, y) { 
-        if (typeof f === 'function') {
-            return f(x, y);
-        } else {
+        if (typeof f !== 'function') {
             throw new Error('curry: first argument must be a function');
         }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the remaining arguments
+            return function(x, y) {
+                if (y === undefined) {
+                    // Still partial application
+                    return function(y) {
+                        return f(x, y);
+                    };
+                }
+                return f(x, y);
+            };
+        }
+        
+        if (y === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(y) {
+                return f(x, y);
+            };
+        }
+        
+        // Full application: apply the function to all arguments
+        return f(x, y);
     };
     
-    // Apply: Apply a function to an argument (same as function call, but more explicit)
+    /**
+     * Apply: Apply a function to an argument (explicit function application)
+     * @param {Function} f - Function to apply
+     * @param {*} x - Argument to apply function to
+     * @returns {*} Result of applying f to x
+     * @throws {Error} When first argument is not a function
+     * @description The apply function is the fundamental mechanism for function
+     * application in the language. It enables the juxtaposition-based function
+     * application syntax (f x) by providing an explicit function application
+     * primitive. This function is called by the parser whenever function
+     * application is detected, ensuring consistent semantics across all
+     * function calls.
+     * 
+     * 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
+     * syntax for function calls while maintaining clear precedence rules.
+     * 
+     * The function supports partial application: when called with only the function,
+     * it returns a new function that waits for the argument. This enables the
+     * parser to build function application chains incrementally, supporting
+     * both immediate evaluation and deferred execution patterns.
+     * 
+     * This partial application support is essential for the parser's left-associative
+     * function application model, where `f g x` becomes `apply(apply(f, g), x)`.
+     * The nested partial application ensures that each step returns a function
+     * until all arguments are provided.
+     */
     scope.apply = function(f, x) { 
-        if (typeof f === 'function') {
-            return f(x);
-        } else {
+        if (typeof f !== 'function') {
             throw new Error('apply: first argument must be a function');
         }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return f(x);
+            };
+        }
+        
+        // Full application: apply the function to the argument
+        return f(x);
     };
     
-    // Pipe: Compose functions in left-to-right order (opposite of compose)
-    // pipe f g x = g f x
-    scope.pipe = function(f, g, x) { 
-        if (typeof f === 'function' && typeof g === 'function') {
-            if (arguments.length === 3) {
-                // pipe f g x = g(f(x))
-                return g(f(x));
-            } else {
-                // pipe f g = function that takes x and returns g(f(x))
+    /**
+     * Pipe: Compose functions in left-to-right order (opposite of compose)
+     * @param {Function} f - First function
+     * @param {Function} [g] - Second function (optional for partial application)
+     * @returns {Function} Function that applies the functions in left-to-right order
+     * @throws {Error} When first argument is not a function
+     * @description The pipe function provides an alternative to compose that
+     * applies functions in left-to-right order, which is often more intuitive
+     * for data processing pipelines. This enables functional programming patterns
+     * where data flows through a series of transformations in a natural reading order.
+     * 
+     * The function implements left-associative composition, meaning that
+     * pipe(f, pipe(g, h)) creates a function that applies f, then g, then h.
+     * This is the opposite of compose and matches the natural reading order
+     * for data transformation pipelines, making it intuitive for programmers
+     * 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
+     * operations are built from simple, composable functions.
+     * 
+     * The left-associative design choice makes pipe ideal for data processing
+     * workflows where each step transforms the data and passes it to the next
+     * step, creating a natural pipeline that reads like a sequence of operations.
+     */
+    scope.pipe = function(f, g) {
+        if (typeof f !== 'function') {
+            throw new Error(`pipe: first argument must be a function, got ${typeof f}`);
+        }
+        
+        if (g === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(g) {
+                if (typeof g !== 'function') {
+                    throw new Error(`pipe: second argument must be a function, got ${typeof g}`);
+                }
                 return function(x) {
                     return g(f(x));
                 };
-            }
-        } else {
-            throw new Error('pipe: first two arguments must be functions');
+            };
+        }
+        
+        if (typeof g !== 'function') {
+            throw new Error(`pipe: second argument must be a function, got ${typeof g}`);
         }
+        
+        return function(x) {
+            return g(f(x));
+        };
     };
     
-    // Filter: Filter based on a predicate
-    // For now, we'll implement it as a higher-order function
+    /**
+     * Filter: Filter a value or collection based on a predicate
+     * @param {Function} p - Predicate function
+     * @param {*} x - Value or collection to test
+     * @returns {*|0} The value if predicate is true, filtered collection for tables, 0 otherwise
+     * @throws {Error} When first argument is not a function
+     * @description The filter function applies a predicate to a value or collection,
+     * returning the value if the predicate is true, or a filtered collection for tables.
+     * This enables functional programming patterns where data selection is expressed
+     * as predicate application rather than imperative filtering loops.
+     * 
+     * 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
+     * selection patterns.
+     * 
+     * The function supports partial application: when called with only the predicate,
+     * it returns a new function that waits for the value. This enables currying
+     * patterns and function composition chains, which are essential for the
+     * 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
+     * elements from a collection without explicit iteration.
+     */
     scope.filter = function(p, x) { 
-        if (typeof p === 'function') {
-            return p(x) ? x : 0;
-        } else {
+        if (typeof p !== 'function') {
             throw new Error('filter: first argument must be a function');
         }
-    };
-    
-    // Reduce: Reduce to a single value using a binary function
-    // For now, we'll implement it as a higher-order function
-    scope.reduce = function(f, init, x) { 
-        if (typeof f === 'function') {
-            return f(init, x);
-        } else {
-            throw new Error('reduce: first argument must be a function');
-        }
-    };
-    
-    // Fold: Same as reduce, but more explicit about the folding direction
-    scope.fold = function(f, init, x) { 
-        if (typeof f === 'function') {
-            return f(init, x);
-        } else {
-            throw new Error('fold: first argument must be a function');
-        }
-    };
-}
-
-/**
- * TokenType is a flat object, not an enum, to allow for fast string comparisons and easy extensibility.
- * 
- * Why: Using a flat object avoids the need for import/export or enum boilerplate, and makes it easy to add new token types as the language evolves.
- */
-const TokenType = {
-    NUMBER: 'NUMBER',
-    PLUS: 'PLUS',
-    MINUS: 'MINUS',
-    MULTIPLY: 'MULTIPLY',
-    DIVIDE: 'DIVIDE',
-    IDENTIFIER: 'IDENTIFIER',
-    ASSIGNMENT: 'ASSIGNMENT',
-    ARROW: 'ARROW',
-    CASE: 'CASE',
-    OF: 'OF',
-    WILDCARD: 'WILDCARD',
-    FUNCTION: 'FUNCTION',
-    LEFT_PAREN: 'LEFT_PAREN',
-    RIGHT_PAREN: 'RIGHT_PAREN',
-    LEFT_BRACE: 'LEFT_BRACE',
-    RIGHT_BRACE: 'RIGHT_BRACE',
-    LEFT_BRACKET: 'LEFT_BRACKET',
-    RIGHT_BRACKET: 'RIGHT_BRACKET',
-    SEMICOLON: 'SEMICOLON',
-    COMMA: 'COMMA',
-    DOT: 'DOT',
-    STRING: 'STRING',
-    TRUE: 'TRUE',
-    FALSE: 'FALSE',
-    AND: 'AND',
-    OR: 'OR',
-    XOR: 'XOR',
-    NOT: 'NOT',
-    EQUALS: 'EQUALS',
-    LESS_THAN: 'LESS_THAN',
-    GREATER_THAN: 'GREATER_THAN',
-    LESS_EQUAL: 'LESS_EQUAL',
-    GREATER_EQUAL: 'GREATER_EQUAL',
-    NOT_EQUAL: 'NOT_EQUAL',
-    MODULO: 'MODULO',
-    POWER: 'POWER',
-    IO_IN: 'IO_IN',
-    IO_OUT: 'IO_OUT',
-    IO_ASSERT: 'IO_ASSERT',
-    FUNCTION_REF: 'FUNCTION_REF'
-};
-
-/**
- * Lexer: Converts source code to tokens.
- * 
- * How: Uses a single pass with a while loop and manual character inspection. Handles whitespace, comments (with nesting), numbers (including decimals), strings, identifiers/keywords, and both single- and multi-character operators.
- * 
- * Why: Manual lexing allows for fine-grained control over tokenization, especially for edge cases like nested comments and multi-character IO operations. This approach also makes it easier to debug and extend the lexer for new language features.
- * 
- * Notably, IO operations (..in, ..out, ..assert) are recognized as multi-character tokens to avoid ambiguity with the dot operator. Decimal numbers are parsed as a single token to support floating point arithmetic.
- */
-function lexer(input) {
-    let current = 0;
-    const tokens = [];
-    
-    while (current < input.length) {
-        let char = input[current];
         
-        // Skip whitespace
-        if (/\s/.test(char)) {
-            current++;
-            continue;
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.filter(p, x);
+            };
         }
         
-        // Skip comments
-        if (char === '/' && input[current + 1] === '*') {
-            let commentDepth = 1;
-            current += 2; // Skip /*
-            
-            while (current < input.length && commentDepth > 0) {
-                if (input[current] === '/' && input[current + 1] === '*') {
-                    commentDepth++;
-                    current += 2;
-                } else if (input[current] === '*' && input[current + 1] === '/') {
-                    commentDepth--;
-                    current += 2;
-                } else {
-                    current++;
+        // Handle tables (APL-style element-wise filtering)
+        if (typeof x === 'object' && x !== null && !Array.isArray(x)) {
+            const result = {};
+            for (const [key, value] of Object.entries(x)) {
+                if (p(value)) {
+                    result[key] = value;
                 }
             }
-            continue;
+            return result;
         }
         
-        // Numbers
-        if (/[0-9]/.test(char)) {
-            let value = '';
-            while (current < input.length && /[0-9]/.test(input[current])) {
-                value += input[current];
-                current++;
-            }
-            
-            // Check for decimal point
-            if (current < input.length && input[current] === '.') {
-                value += input[current];
-                current++;
-                
-                // Parse decimal part
-                while (current < input.length && /[0-9]/.test(input[current])) {
-                    value += input[current];
-                    current++;
-                }
-                
-                tokens.push({
-                    type: TokenType.NUMBER,
-                    value: parseFloat(value)
-                });
-            } else {
-                tokens.push({
-                    type: TokenType.NUMBER,
-                    value: parseInt(value)
-                });
-            }
-            continue;
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.filter(p);
         }
         
-        // Strings
-        if (char === '"') {
-            let value = '';
-            current++; // Skip opening quote
-            
-            while (current < input.length && input[current] !== '"') {
-                value += input[current];
-                current++;
-            }
-            
-            if (current < input.length) {
-                current++; // Skip closing quote
-                tokens.push({
-                    type: TokenType.STRING,
-                    value: value
-                });
-            } else {
-                throw new Error('Unterminated string');
-            }
-            continue;
+        // Default: apply predicate to single value
+        return p(x) ? x : 0;
+    };
+    
+    /**
+     * Reduce: Reduce two values using a binary function
+     * @param {Function} f - Binary function
+     * @param {*} init - Initial value
+     * @param {*} x - Second value
+     * @returns {*} Result of applying f to init and x
+     * @throws {Error} When first argument is not a function
+     * @description The reduce function applies a binary function to an initial value
+     * and a second value, returning the result. This is a simplified version of
+     * traditional reduce that works with pairs of values rather than collections.
+     * 
+     * The function supports partial application with nested checks to handle the
+     * parser's one-by-one argument application system. When called with only the
+     * function, it returns a function that waits for the initial value. When called
+     * with the function and initial value, it returns a function that waits for
+     * the second value. This enables currying patterns and incremental function
+     * 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);
         }
         
-        // Identifiers and keywords
-        if (/[a-zA-Z_]/.test(char)) {
-            let value = '';
-            while (current < input.length && /[a-zA-Z0-9_]/.test(input[current])) {
-                value += input[current];
-                current++;
-            }
-            
-            // Check for keywords
-            switch (value) {
-                case 'case':
-                    tokens.push({ type: TokenType.CASE });
-                    break;
-                case 'of':
-                    tokens.push({ type: TokenType.OF });
-                    break;
-                case 'function':
-                    tokens.push({ type: TokenType.FUNCTION });
-                    break;
-                case 'true':
-                    tokens.push({ type: TokenType.TRUE });
-                    break;
-                case 'false':
-                    tokens.push({ type: TokenType.FALSE });
-                    break;
-                case 'and':
-                    tokens.push({ type: TokenType.AND });
-                    break;
-                case 'or':
-                    tokens.push({ type: TokenType.OR });
-                    break;
-                case 'xor':
-                    tokens.push({ type: TokenType.XOR });
-                    break;
-                case 'not':
-                    tokens.push({ type: TokenType.NOT });
-                    break;
-                case '_':
-                    tokens.push({ type: TokenType.WILDCARD });
-                    break;
-                default:
-                    tokens.push({
-                        type: TokenType.IDENTIFIER,
-                        value: value
-                    });
-            }
-            continue;
+        if (typeof f !== 'function') {
+            throw new Error('reduce: first argument must be a function');
         }
         
-        // Two-character operators
-        if (current + 1 < input.length) {
-            const twoChar = char + input[current + 1];
-            switch (twoChar) {
-                case '->':
-                    tokens.push({ type: TokenType.ARROW });
-                    current += 2;
-                    continue;
-                case '==':
-                    tokens.push({ type: TokenType.EQUALS });
-                    current += 2;
-                    continue;
-                case '!=':
-                    tokens.push({ type: TokenType.NOT_EQUAL });
-                    current += 2;
-                    continue;
-                case '<=':
-                    tokens.push({ type: TokenType.LESS_EQUAL });
-                    current += 2;
-                    continue;
-                case '>=':
-                    tokens.push({ type: TokenType.GREATER_EQUAL });
-                    current += 2;
-                    continue;
-                case '..':
-                    // Check for IO operations
-                    if (current + 2 < input.length) {
-                        const ioChar = input[current + 2];
-                        switch (ioChar) {
-                            case 'i':
-                                if (current + 3 < input.length && input[current + 3] === 'n') {
-                                    tokens.push({ type: TokenType.IO_IN });
-                                    current += 4;
-                                    continue;
-                                }
-                                break;
-                            case 'o':
-                                if (current + 3 < input.length && input[current + 3] === 'u') {
-                                    if (current + 4 < input.length && input[current + 4] === 't') {
-                                        tokens.push({ type: TokenType.IO_OUT });
-                                        current += 5;
-                                        continue;
-                                    }
-                                }
-                                break;
-                            case 'a':
-                                if (current + 3 < input.length && input[current + 3] === 's') {
-                                    if (current + 4 < input.length && input[current + 4] === 's') {
-                                        if (current + 5 < input.length && input[current + 5] === 'e') {
-                                            if (current + 6 < input.length && input[current + 6] === 'r') {
-                                                if (current + 7 < input.length && input[current + 7] === 't') {
-                                                    tokens.push({ type: TokenType.IO_ASSERT });
-                                                    current += 8;
-                                                    continue;
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                                break;
-                        }
-                    }
-                    // If we get here, it's not a complete IO operation, so skip the '..'
-                    current += 2;
-                    continue;
-            }
+        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 (x === undefined) {
+                    // Still partial application
+                    return function(x) {
+                        return scope.reduce(f, init, x);
+                    };
+                }
+                return scope.reduce(f, init, x);
+            };
         }
         
-        // Single character operators
-        switch (char) {
-            case '+':
-                tokens.push({ type: TokenType.PLUS });
-                break;
-            case '-':
-                tokens.push({ type: TokenType.MINUS });
-                break;
-            case '*':
-                tokens.push({ type: TokenType.MULTIPLY });
-                break;
-            case '/':
-                tokens.push({ type: TokenType.DIVIDE });
-                break;
-            case '%':
-                tokens.push({ type: TokenType.MODULO });
-                break;
-            case '^':
-                tokens.push({ type: TokenType.POWER });
-                break;
-            case ':':
-                tokens.push({ type: TokenType.ASSIGNMENT });
-                break;
-            case '(':
-                tokens.push({ type: TokenType.LEFT_PAREN });
-                break;
-            case ')':
-                tokens.push({ type: TokenType.RIGHT_PAREN });
-                break;
-            case '{':
-                tokens.push({ type: TokenType.LEFT_BRACE });
-                break;
-            case '}':
-                tokens.push({ type: TokenType.RIGHT_BRACE });
-                break;
-            case '[':
-                tokens.push({ type: TokenType.LEFT_BRACKET });
-                break;
-            case ']':
-                tokens.push({ type: TokenType.RIGHT_BRACKET });
-                break;
-            case ';':
-                tokens.push({ type: TokenType.SEMICOLON });
-                break;
-            case ',':
-                tokens.push({ type: TokenType.COMMA });
-                break;
-            case '.':
-                tokens.push({ type: TokenType.DOT });
-                break;
-            case '@':
-                tokens.push({ type: TokenType.FUNCTION_REF });
-                break;
-            case '_':
-                tokens.push({ type: TokenType.WILDCARD });
-                break;
-            case '=':
-                tokens.push({ type: TokenType.EQUALS });
-                break;
-            case '<':
-                tokens.push({ type: TokenType.LESS_THAN });
-                break;
-            case '>':
-                tokens.push({ type: TokenType.GREATER_THAN });
-                break;
-            default:
-                throw new Error(`Unexpected character: ${char}`);
+        if (x === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(x) {
+                return scope.reduce(f, init, x);
+            };
         }
         
-        current++;
-    }
-    
-    return tokens;
-}
-
-/**
- * Parser: Converts tokens to an Abstract Syntax Tree (AST).
- * 
- * How: Implements a recursive descent parser, with separate functions for each precedence level (expression, term, factor, primary). Handles chained table access, function calls, and complex constructs like case expressions and function definitions.
- * 
- * Why: Recursive descent is chosen for its clarity and flexibility, especially for a language with many context-sensitive constructs (e.g., case expressions, function definitions, chained access). The parser is structured to minimize circular dependencies and infinite recursion, with careful placement of IO and case expression parsing.
- * 
- * The parser also supports multi-parameter case expressions and function definitions, using lookahead to distinguish between assignments and function declarations. Table literals are parsed with support for both array-like and key-value entries, inspired by Lua.
- */
-function parser(tokens) {
-    let current = 0;
-    
-    function walk() {
-        function parseChainedDotAccess(tableExpr) {
-            /**
-             * Handles chained dot access (e.g., table.key.subkey).
-             * 
-             * Why: Chained access is parsed iteratively rather than recursively to avoid deep call stacks and to allow for easy extension (e.g., supporting method calls in the future).
-             */
-            let result = tableExpr;
-            
-            while (current < tokens.length && tokens[current].type === TokenType.DOT) {
-                current++; // Skip the dot
-                
-                if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
-                    const key = {
-                        type: 'Identifier',
-                        value: tokens[current].value
-                    };
-                    current++;
-                    
-                    result = {
-                        type: 'TableAccess',
-                        table: result,
-                        key: key
-                    };
-                } else {
-                    throw new Error('Expected identifier after dot');
-                }
+        // Handle tables (reduce all values in the table)
+        if (typeof x === 'object' && x !== null && !Array.isArray(x)) {
+            let result = init;
+            for (const [key, value] of Object.entries(x)) {
+                result = f(result, value, key);
             }
-            
             return result;
         }
         
-        function parseChainedTableAccess(tableExpr) {
-            /**
-             * Handles chained bracket and dot access (e.g., table[0].key).
-             * 
-             * Why: This function allows for flexible access patterns, supporting both array and object semantics. Chaining is handled by checking for further access tokens after each access.
-             */
-            if (current < tokens.length && tokens[current].type === TokenType.LEFT_BRACKET) {
-                current++; // Skip '['
-                const keyExpr = walk();
-                
-                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_BRACKET) {
-                    current++; // Skip ']'
-                    
-                    const access = {
-                        type: 'TableAccess',
-                        table: tableExpr,
-                        key: keyExpr
-                    };
-                    
-                    // Check for chained access
-                    if (current < tokens.length && tokens[current].type === TokenType.DOT) {
-                        return parseChainedDotAccess(access);
-                    }
-                    
-                    // Check if this is a function call
-                    if (current < tokens.length && 
-                        (tokens[current].type === TokenType.IDENTIFIER || 
-                         tokens[current].type === TokenType.NUMBER ||
-                         tokens[current].type === TokenType.STRING ||
-                         tokens[current].type === TokenType.LEFT_PAREN)) {
-                        return parseFunctionCall(access);
-                    }
-                    
-                    return access;
-                } else {
-                    throw new Error('Expected closing bracket');
-                }
-            }
-            
-            // Check for dot access
-            if (current < tokens.length && tokens[current].type === TokenType.DOT) {
-                const result = parseChainedDotAccess(tableExpr);
-                
-                // Check if this is a function call
-                if (current < tokens.length && 
-                    (tokens[current].type === TokenType.IDENTIFIER || 
-                     tokens[current].type === TokenType.NUMBER ||
-                     tokens[current].type === TokenType.STRING ||
-                     tokens[current].type === TokenType.LEFT_PAREN)) {
-                    return parseFunctionCall(result);
-                }
-                
-                return result;
-            }
-            
-            return tableExpr;
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.reduce(f, init);
         }
         
-        function detectAmbiguousFunctionCalls() {
-            // This is a placeholder for future ambiguous function call detection
-            // For now, we'll assume the parser handles function calls correctly
+        // Default: apply the function to init and x (original behavior)
+        return f(init, x);
+    };
+    
+    /**
+     * Fold: Same as reduce, but more explicit about the folding direction
+     * @param {Function} f - Binary function
+     * @param {*} init - Initial value
+     * @param {*} x - Second value
+     * @returns {*} Result of applying f to init and x
+     * @throws {Error} When first argument is not a function
+     */
+    scope.fold = function(f, init, x) { 
+        if (typeof f !== 'function') {
+            throw new Error('fold: first argument must be a function');
         }
         
-                function parseFunctionCall(functionName) {
-            /**
-             * Parses function calls with arbitrary argument lists.
-             * 
-             * Why: Arguments are parsed until a clear terminator is found, allowing for flexible function call syntax. This approach supports both curried and regular function calls, and allows for future extension to variadic functions.
-             */
-            const args = [];
-            
-            // Parse arguments until we hit a semicolon or other terminator
-            while (current < tokens.length && 
-                   tokens[current].type !== TokenType.SEMICOLON &&
-                   tokens[current].type !== TokenType.RIGHT_PAREN &&
-                   tokens[current].type !== TokenType.RIGHT_BRACE &&
-                   tokens[current].type !== TokenType.COMMA &&
-                   tokens[current].type !== TokenType.AND &&
-                   tokens[current].type !== TokenType.OR &&
-                   tokens[current].type !== TokenType.XOR) {
-                
-                // Special handling for unary minus as argument
-                if (tokens[current].type === TokenType.MINUS) {
-                    // This is a unary minus, parse it as a new argument
-                    current++; // Skip the minus
-                    if (current < tokens.length && tokens[current].type === TokenType.NUMBER) {
-                        args.push({
-                            type: 'UnaryMinusExpression',
-                            operand: {
-                                type: 'NumberLiteral',
-                                value: tokens[current].value
-                            }
-                        });
-                        current++; // Skip the number
-                    } else {
-                        // More complex unary minus expression
-                        args.push({
-                            type: 'UnaryMinusExpression',
-                            operand: parsePrimary()
-                        });
-                    }
-                } else {
-                    // Regular argument parsing - use parseExpression to avoid circular dependency
-                    args.push(parseExpression());
+        if (init === undefined) {
+            // Partial application: return a function that waits for the remaining arguments
+            return function(init, x) {
+                if (x === undefined) {
+                    // Still partial application
+                    return function(x) {
+                        return f(init, x);
+                    };
                 }
-            }
-                
-            return {
-                type: 'FunctionCall',
-                name: functionName,
-                args: args
+                return f(init, x);
             };
         }
         
-        function parseLogicalExpression() {
-            /**
-             * Parses logical expressions with lowest precedence.
-             * 
-             * Why: Logical operators should have lower precedence than arithmetic and comparison operators
-             * to ensure proper grouping of expressions like "isEven 10 and isPositive 5".
-             */
-            let left = parseExpression();
-            
-            while (current < tokens.length && 
-                   (tokens[current].type === TokenType.AND ||
-                    tokens[current].type === TokenType.OR ||
-                    tokens[current].type === TokenType.XOR)) {
-                
-                const operator = tokens[current].type;
-                current++;
-                const right = parseExpression();
-                
-                switch (operator) {
-                    case TokenType.AND:
-                        left = { type: 'AndExpression', left, right };
-                        break;
-                    case TokenType.OR:
-                        left = { type: 'OrExpression', left, right };
-                        break;
-                    case TokenType.XOR:
-                        left = { type: 'XorExpression', left, right };
-                        break;
-                }
-            }
-            
-            return left;
+        if (x === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(x) {
+                return f(init, x);
+            };
         }
         
-        function parseExpression() {
-            /**
-             * Parses expressions with left-associative binary operators.
-             * 
-             * Why: Operator precedence is handled by splitting parsing into multiple functions (expression, term, factor, primary). This structure avoids ambiguity and ensures correct grouping of operations.
-             */
-            let left = parseTerm();
-            
-            while (current < tokens.length && 
-                   (tokens[current].type === TokenType.PLUS || 
-                    tokens[current].type === TokenType.MINUS ||
-                    tokens[current].type === TokenType.EQUALS ||
-                    tokens[current].type === TokenType.NOT_EQUAL ||
-                    tokens[current].type === TokenType.LESS_THAN ||
-                    tokens[current].type === TokenType.GREATER_THAN ||
-                    tokens[current].type === TokenType.LESS_EQUAL ||
-                    tokens[current].type === TokenType.GREATER_EQUAL)) {
-                
-                const operator = tokens[current].type;
-                
-                // Special case: Don't treat MINUS as binary operator if left is a FunctionReference
-                // This handles cases like "filter @isPositive -3" where -3 should be a separate argument
-                if (operator === TokenType.MINUS && left.type === 'FunctionReference') {
-                    // This is likely a function call with unary minus argument, not a binary operation
-                    // Return the left side and let the caller handle it
-                    return left;
-                }
-                
-
-                
-
-                
-
-                
-                current++;
-                const right = parseTerm();
-                
-                switch (operator) {
-                    case TokenType.PLUS:
-                        left = { type: 'PlusExpression', left, right };
-                        break;
-                    case TokenType.MINUS:
-                        left = { type: 'MinusExpression', left, right };
-                        break;
-                    case TokenType.EQUALS:
-                        left = { type: 'EqualsExpression', left, right };
-                        break;
-                    case TokenType.NOT_EQUAL:
-                        left = { type: 'NotEqualExpression', left, right };
-                        break;
-                    case TokenType.LESS_THAN:
-                        left = { type: 'LessThanExpression', left, right };
-                        break;
-                    case TokenType.GREATER_THAN:
-                        left = { type: 'GreaterThanExpression', left, right };
-                        break;
-                    case TokenType.LESS_EQUAL:
-                        left = { type: 'LessEqualExpression', left, right };
-                        break;
-                    case TokenType.GREATER_EQUAL:
-                        left = { type: 'GreaterEqualExpression', left, right };
-                        break;
-                }
-            }
-            
-            return left;
+        // Full application: apply the function to all arguments
+        return f(init, x);
+    };
+    
+    // ===== ARITHMETIC COMBINATORS =====
+    
+    /**
+     * Add: Add two numbers
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Sum of x and y
+     * @description The add function is a fundamental arithmetic combinator that
+     * implements addition. This function is called by the parser when the '+'
+     * operator is encountered, translating `x + y` into `add(x, y)`.
+     * 
+     * As a combinator function, add supports partial application and can be used
+     * in function composition chains. This enables patterns like `map @add 10`
+     * to add 10 to every element in a collection, or `each @add table1 table2`
+     * for element-wise addition of corresponding table elements.
+     * 
+     * The function is designed to work seamlessly with the parser's operator
+     * translation system, providing consistent semantics for all arithmetic
+     * operations through the combinator foundation.
+     */
+    scope.add = function(x, y) {
+        return x + y;
+    };
+    
+    /**
+     * Subtract: Subtract second number from first
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Difference of x and y
+     */
+    scope.subtract = function(x, y) {
+        return x - y;
+    };
+    
+    /**
+     * Multiply: Multiply two numbers
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Product of x and y
+     * @description The multiply function is a fundamental arithmetic combinator that
+     * implements multiplication. This function is called by the parser when the '*'
+     * operator is encountered, translating `x * y` into `multiply(x, y)`.
+     * 
+     * As a combinator function, multiply supports partial application and can be used
+     * in function composition chains. This enables patterns like `map @multiply 2`
+     * to double every element in a collection, or `each @multiply table1 table2`
+     * for element-wise multiplication of corresponding table elements.
+     * 
+     * The function is designed to work seamlessly with the parser's operator
+     * translation system, providing consistent semantics for all arithmetic
+     * operations through the combinator foundation.
+     */
+    scope.multiply = function(x, y) {
+        return x * y;
+    };
+    
+    /**
+     * Divide: Divide first number by second
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Quotient of x and y
+     * @throws {Error} When second argument is zero
+     */
+    scope.divide = function(x, y) {
+        if (y === 0) {
+            throw new Error('Division by zero');
+        }
+        return x / y;
+    };
+    
+    /**
+     * Modulo: Get remainder of division
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Remainder of x divided by y
+     */
+    scope.modulo = function(x, y) {
+        return x % y;
+    };
+    
+    /**
+     * Power: Raise first number to power of second
+     * @param {number} x - Base number
+     * @param {number} y - Exponent
+     * @returns {number} x raised to the power of y
+     */
+    scope.power = function(x, y) {
+        return Math.pow(x, y);
+    };
+    
+    /**
+     * Negate: Negate a number
+     * @param {number} x - Number to negate
+     * @returns {number} Negated value of x
+     */
+    scope.negate = function(x) {
+        return -x;
+    };
+    
+    // ===== COMPARISON COMBINATORS =====
+    
+    /**
+     * Equals: Check if two values are equal
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x equals y
+     */
+    scope.equals = function(x, y) {
+        return x === y;
+    };
+    
+    /**
+     * NotEquals: Check if two values are not equal
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x does not equal y
+     */
+    scope.notEquals = function(x, y) {
+        return x !== y;
+    };
+    
+    /**
+     * LessThan: Check if first value is less than second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x < y
+     */
+    scope.lessThan = function(x, y) {
+        return x < y;
+    };
+    
+    /**
+     * GreaterThan: Check if first value is greater than second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x > y
+     */
+    scope.greaterThan = function(x, y) {
+        return x > y;
+    };
+    
+    /**
+     * LessEqual: Check if first value is less than or equal to second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x <= y
+     */
+    scope.lessEqual = function(x, y) {
+        return x <= y;
+    };
+    
+    /**
+     * GreaterEqual: Check if first value is greater than or equal to second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x >= y
+     */
+    scope.greaterEqual = function(x, y) {
+        return x >= y;
+    };
+    
+    // ===== LOGICAL COMBINATORS =====
+    
+    /**
+     * LogicalAnd: Logical AND of two values
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if both x and y are truthy
+     */
+    scope.logicalAnd = function(x, y) {
+        return !!(x && y);
+    };
+    
+    /**
+     * LogicalOr: Logical OR of two values
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if either x or y is truthy
+     */
+    scope.logicalOr = function(x, y) {
+        return !!(x || y);
+    };
+    
+    /**
+     * LogicalXor: Logical XOR of two values
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if exactly one of x or y is truthy
+     */
+    scope.logicalXor = function(x, y) {
+        return !!((x && !y) || (!x && y));
+    };
+    
+    /**
+     * LogicalNot: Logical NOT of a value
+     * @param {*} x - Value to negate
+     * @returns {boolean} True if x is falsy, false if x is truthy
+     */
+    scope.logicalNot = function(x) {
+        return !x;
+    };
+    
+    // ===== ASSIGNMENT COMBINATOR =====
+    
+    /**
+     * Assign: Assign a value to a variable name
+     * @param {string} name - Variable name
+     * @param {*} value - Value to assign
+     * @returns {*} The assigned value
+     * @throws {Error} When trying to reassign an immutable variable
+     * @note This function needs access to the global scope, so it will be
+     *       set up during interpreter initialization
+     */
+    // Note: assign will be set up in the interpreter with access to globalScope
+    
+    // ===== ENHANCED HIGHER-ORDER COMBINATORS =====
+    
+    /**
+     * Identity: Return the input unchanged
+     * @param {*} x - Any value
+     * @returns {*} The same value
+     */
+    scope.identity = function(x) {
+        return x;
+    };
+    
+    /**
+     * Constant: Create a function that always returns the same value
+     * @param {*} x - Value to return
+     * @param {*} [y] - Optional second argument (ignored)
+     * @returns {*} The value x, or a function if only one argument provided
+     */
+    scope.constant = function(x, y) {
+        if (arguments.length === 2) {
+            return x;
+        } else {
+            return function(y) {
+                return x;
+            };
+        }
+    };
+    
+    /**
+     * Flip: Flip the order of arguments for a binary function
+     * @param {Function} f - Binary function
+     * @param {*} [x] - Optional first argument
+     * @param {*} [y] - Optional second argument
+     * @returns {Function|*} Function with flipped argument order, or result if arguments provided
+     */
+    scope.flip = function(f, x, y) {
+        if (arguments.length === 3) {
+            return f(y, x);
+        } else {
+            return function(x, y) {
+                return f(y, x);
+            };
+        }
+    };
+    
+    /**
+     * On: Apply a function to the results of another function
+     * @param {Function} f - Outer function
+     * @param {Function} g - Inner function
+     * @returns {Function} Function that applies f to the results of g
+     */
+    scope.on = function(f, g) {
+        return function(x, y) {
+            return f(g(x), g(y));
+        };
+    };
+    
+    /**
+     * Both: Check if both predicates are true
+     * @param {Function} f - First predicate
+     * @param {Function} g - Second predicate
+     * @returns {Function} Function that returns true if both predicates are true
+     */
+    scope.both = function(f, g) {
+        return function(x) {
+            return f(x) && g(x);
+        };
+    };
+    
+    /**
+     * Either: Check if either predicate is true
+     * @param {Function} f - First predicate
+     * @param {Function} g - Second predicate
+     * @returns {Function} Function that returns true if either predicate is true
+     */
+    scope.either = function(f, g) {
+        return function(x) {
+            return f(x) || g(x);
+        };
+    };
+    
+    /**
+     * Each: Multi-argument element-wise operations for tables and scalars
+     * @param {Function} f - Function to apply element-wise
+     * @param {*} x - First argument (table or scalar)
+     * @returns {Function|*} Function for partial application or result of element-wise application
+     * @throws {Error} When first argument is not a function
+     * @description The each combinator provides APL-inspired element-wise operations
+     * for multi-argument functions over table structures. This is the primary mechanism
+     * for combining multiple tables or tables with scalars in element-wise fashion.
+     * 
+     * The function is designed for multi-argument operations and aligns with the parser's
+     * apply mechanism. When x is a table, each returns a function that waits for the
+     * second argument (y), enabling the parser to build `apply(apply(each, f), x)` chains
+     * that resolve to element-wise operations when y is provided.
+     * 
+     * Key behaviors:
+     * - Table + Scalar: Applies f to each element of the table with the scalar as second argument
+     * - Table + Table: Applies f to corresponding elements from both tables
+     * - 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
+     * `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);
         }
         
-        function parseTerm() {
-            /**
-             * Parses multiplication, division, and modulo operations.
-             * 
-             * Why: By handling these operators at a separate precedence level, the parser ensures that multiplication/division bind tighter than addition/subtraction, matching standard arithmetic rules.
-             */
-            let left = parseFactor();
-            
-            while (current < tokens.length && 
-                   (tokens[current].type === TokenType.MULTIPLY || 
-                    tokens[current].type === TokenType.DIVIDE ||
-                    tokens[current].type === TokenType.MODULO)) {
-                
-                const operator = tokens[current].type;
-                current++;
-                const right = parseFactor();
-                
-                switch (operator) {
-                    case TokenType.MULTIPLY:
-                        left = { type: 'MultiplyExpression', left, right };
-                        break;
-                    case TokenType.DIVIDE:
-                        left = { type: 'DivideExpression', left, right };
-                        break;
-                    case TokenType.MODULO:
-                        left = { type: 'ModuloExpression', left, right };
-                        break;
-                }
-            }
-            
-            return left;
+        if (typeof f !== 'function') {
+            throw new Error('each: first argument must be a function, got ' + typeof f);
         }
         
-        function parseFactor() {
-            /**
-             * Parses exponentiation and primary expressions.
-             * 
-             * Why: Exponentiation is right-associative and binds tighter than multiplication/division, so it is handled at the factor level. This also allows for future extension to other high-precedence operators.
-             */
-            let left = parsePrimary();
-            
-            while (current < tokens.length && tokens[current].type === TokenType.POWER) {
-                current++;
-                const right = parsePrimary();
-                left = { type: 'PowerExpression', left, right };
-            }
-            
-            return left;
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.each(f, x);
+            };
         }
         
-        function parsePrimary() {
-            /**
-             * Parses literals, identifiers, function definitions, assignments, table literals, case expressions, IO operations, and parenthesized expressions.
-             * 
-             * Why: This function is the core of the recursive descent parser, handling all atomic and context-sensitive constructs. Special care is taken to distinguish between assignments and function definitions using lookahead, and to parse multi-parameter case expressions and function calls.
-             * 
-             * The parser avoids circular recursion by handling IO and case expressions at the top level, and by using explicit checks for each construct.
-             */
-            const token = tokens[current];
-            
-            if (token.type === TokenType.CASE) {
-                current++; // Skip 'case'
+        // Check if x is a table
+        const isXTable = typeof x === 'object' && x !== null && !Array.isArray(x);
+        
+        if (isXTable) {
+            // x is a table - always return a function that can handle the second argument
+            return function(y) {
+                // Check if y is a table
+                const isYTable = typeof y === 'object' && y !== null && !Array.isArray(y);
                 
-                // Parse the values being matched (can be multiple)
-                const values = [];
-                while (current < tokens.length && tokens[current].type !== TokenType.OF) {
-                    // Parse simple expressions (identifiers, numbers, etc.)
-                    if (tokens[current].type === TokenType.IDENTIFIER) {
-                        values.push({
-                            type: 'Identifier',
-                            value: tokens[current].value
-                        });
-                        current++;
-                    } else if (tokens[current].type === TokenType.NUMBER) {
-                        values.push({
-                            type: 'NumberLiteral',
-                            value: tokens[current].value
-                        });
-                        current++;
-                    } else if (tokens[current].type === TokenType.STRING) {
-                        values.push({
-                            type: 'StringLiteral',
-                            value: tokens[current].value
-                        });
-                        current++;
-                    } else {
-                        // For more complex expressions, fall back to parseLogicalExpression
-                        const value = parseLogicalExpression();
-                        values.push(value);
+                if (!isYTable) {
+                    // x is a table, y is not a table - apply function to each element of x with y as second argument
+                    const result = {};
+                    for (const [key, value] of Object.entries(x)) {
+                        result[key] = f(value, y);
                     }
+                    return result;
                 }
                 
-                // Expect 'of'
-                if (current >= tokens.length || tokens[current].type !== TokenType.OF) {
-                    throw new Error('Expected "of" after "case"');
-                }
-                current++; // Skip 'of'
-                
-                const cases = [];
-                
-                // Parse cases until we hit a semicolon or end
-                while (current < tokens.length && tokens[current].type !== TokenType.SEMICOLON) {
-                    const patterns = [];
-                    while (current < tokens.length && 
-                           tokens[current].type !== TokenType.ASSIGNMENT && 
-                           tokens[current].type !== TokenType.SEMICOLON) {
-                        patterns.push(parseLogicalExpression());
-                    }
-                    
-                    // Expect ':' after pattern
-                    if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
-                        current++; // Skip ':'
-                    } else {
-                        throw new Error('Expected ":" after pattern in case expression');
+                // Both x and y are tables - they should have the same keys
+                const result = {};
+                for (const [key, value] of Object.entries(x)) {
+                    if (y.hasOwnProperty(key)) {
+                        result[key] = f(value, y[key]);
                     }
-                    
-                    const result = parseLogicalExpression();
-                    cases.push({ 
-                        pattern: patterns, 
-                        result: [result] 
-                    });
                 }
-                
-                return {
-                    type: 'CaseExpression',
-                    value: values,
-                    cases,
-                };
-            }
+                return result;
+            };
+        }
+        
+        // x is not a table, return a function that waits for the second argument
+        return function(y) {
+            // Check if y is a table
+            const isYTable = typeof y === 'object' && y !== null && !Array.isArray(y);
             
-            if (token.type === TokenType.NOT) {
-                current++;
-                const operand = parsePrimary();
-                return { type: 'NotExpression', operand };
+            if (!isYTable) {
+                // No tables, apply normally (backward compatibility)
+                return f(x, y);
             }
             
-            if (token.type === TokenType.MINUS) {
-                current++;
-                const operand = parsePrimary();
-                return { type: 'UnaryMinusExpression', operand };
+            // x is not a table, y is a table - use map
+            return scope.map(function(val) { return f(x, val); }, y);
+        };
+    };
+    
+    // ===== TABLE OPERATIONS NAMESPACE (t.) =====
+    
+    /**
+     * Table operations namespace (t.)
+     * @description Provides immutable table operations that always return new tables,
+     * never modifying the original. This namespace implements APL-inspired element-wise
+     * operations and functional table manipulation patterns.
+     * 
+     * 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.
+     * 
+     * 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
+     * patterns while maintaining the functional programming principles of the language.
+     * 
+     * Key design principles:
+     * - Immutability: All operations return new tables, never modify originals
+     * - Element-wise operations: Functions operate on table values, not structure
+     * - Partial application: All functions support currying patterns
+     * - Functional consistency: Operations work with the combinator foundation
+     */
+    scope.t = {
+        /**
+         * Map: Apply a function to each value in a table
+         * @param {Function} f - Function to apply
+         * @param {Object} table - Table to map over
+         * @returns {Object} New table with transformed values
+         * @throws {Error} When first argument is not a function or second is not a table
+         */
+        map: function(f, table) {
+            if (typeof f !== 'function') {
+                throw new Error('t.map: first argument must be a function');
             }
             
-            if (token.type === TokenType.NUMBER) {
-                current++;
-                return {
-                    type: 'NumberLiteral',
-                    value: token.value
+            if (table === undefined) {
+                // Partial application: return a function that waits for the table
+                return function(table) {
+                    return scope.t.map(f, table);
                 };
             }
             
-            if (token.type === TokenType.STRING) {
-                current++;
-                return {
-                    type: 'StringLiteral',
-                    value: token.value
-                };
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.map: second argument must be a table');
             }
             
-            if (token.type === TokenType.TRUE) {
-                current++;
-                return {
-                    type: 'BooleanLiteral',
-                    value: true
-                };
+            const result = {};
+            for (const [key, value] of Object.entries(table)) {
+                result[key] = f(value);
+            }
+            return result;
+        },
+        
+        /**
+         * Filter: Filter table values based on a predicate
+         * @param {Function} p - Predicate function
+         * @param {Object} table - Table to filter
+         * @returns {Object} New table with only values that pass the predicate
+         * @throws {Error} When first argument is not a function or second is not a table
+         */
+        filter: function(p, table) {
+            if (typeof p !== 'function') {
+                throw new Error('t.filter: first argument must be a function');
             }
             
-            if (token.type === TokenType.FALSE) {
-                current++;
-                return {
-                    type: 'BooleanLiteral',
-                    value: false
+            if (table === undefined) {
+                // Partial application: return a function that waits for the table
+                return function(table) {
+                    return scope.t.filter(p, table);
                 };
             }
             
-            if (token.type === TokenType.LEFT_PAREN) {
-                current++; // Skip '('
-                const parenthesizedExpr = parseLogicalExpression();
-                
-                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_PAREN) {
-                    current++; // Skip ')'
-                    return parenthesizedExpr;
-                } else {
-                    throw new Error('Expected closing parenthesis');
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.filter: second argument must be a table');
+            }
+            
+            const result = {};
+            for (const [key, value] of Object.entries(table)) {
+                if (p(value)) {
+                    result[key] = value;
                 }
             }
+            return result;
+        },
+        
+        /**
+         * Reduce: Reduce all values in a table using a binary function
+         * @param {Function} f - Binary function
+         * @param {*} init - Initial value
+         * @param {Object} table - Table to reduce
+         * @returns {*} Result of reducing all values
+         * @throws {Error} When first argument is not a function or third is not a table
+         */
+        reduce: function(f, init, table) {
+            if (typeof f !== 'function') {
+                throw new Error('t.reduce: first argument must be a function');
+            }
             
-            if (token.type === TokenType.IDENTIFIER) {
-                const identifier = {
-                    type: 'Identifier',
-                    value: token.value
-                };
-                current++;
-                
-                // Check if this is an assignment
-                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
-                    current++; // Skip ':'
-                    
-                    // Check if this is a function definition
-                    let isFunction = false;
-                    let params = [];
-                    
-                    // Look ahead to see if this is a function definition
-                    let lookAhead = current;
-                    while (lookAhead < tokens.length && 
-                           tokens[lookAhead].type !== TokenType.ARROW && 
-                           tokens[lookAhead].type !== TokenType.SEMICOLON) {
-                        if (tokens[lookAhead].type === TokenType.IDENTIFIER) {
-                            params.push(tokens[lookAhead].value);
-                        }
-                        lookAhead++;
-                    }
-                    
-                    if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ARROW) {
-                        isFunction = true;
-                    }
-                    
-                    if (isFunction) {
-                        // Clear params array and parse function parameters
-                        params = [];
-                        while (current < tokens.length && tokens[current].type !== TokenType.ARROW) {
-                            if (tokens[current].type === TokenType.IDENTIFIER) {
-                                params.push(tokens[current].value);
-                            }
-                            current++;
-                        }
-                        
-                        current++; // Skip '->'
-                        
-                        // Parse the function body (which could be a case expression or other expression)
-                        const functionBody = parseLogicalExpression();
-                        
-                        return {
-                            type: 'AssignmentExpression',
-                            name: identifier.value,
-                            value: {
-                                type: 'FunctionDeclaration',
-                                name: null, // Anonymous function
-                                params,
-                                body: functionBody,
-                            }
-                        };
-                    } else {
-                        // Regular assignment
-                        const value = parseLogicalExpression();
-                        return {
-                            type: 'AssignmentExpression',
-                            name: identifier.value,
-                            value: value
+            if (init === undefined) {
+                // Partial application: return a function that waits for the remaining arguments
+                return function(init, table) {
+                    if (table === undefined) {
+                        // Still partial application
+                        return function(table) {
+                            return scope.t.reduce(f, init, table);
                         };
                     }
-                }
-                
-                // Check if this is table access
-                if (current < tokens.length && 
-                    (tokens[current].type === TokenType.LEFT_BRACKET ||
-                     tokens[current].type === TokenType.DOT)) {
-                    return parseChainedTableAccess(identifier);
-                }
-                
-                // Check if this is a function call
-                if (current < tokens.length && 
-                    (tokens[current].type === TokenType.IDENTIFIER || 
-                     tokens[current].type === TokenType.NUMBER ||
-                     tokens[current].type === TokenType.STRING ||
-                     tokens[current].type === TokenType.LEFT_PAREN ||
-                     tokens[current].type === TokenType.FUNCTION_REF)) {
-                    return parseFunctionCall(identifier);
-                }
-                
-                // Special case: Check for function call with unary minus argument
-                // This handles cases like "isPositive -3" where -3 should be a separate argument
-                // Only trigger if the identifier looks like a function name (contains letters)
-                if (current < tokens.length && tokens[current].type === TokenType.MINUS) {
-                    // Look ahead to see if we have MINUS NUMBER
-                    if (current + 1 < tokens.length && tokens[current + 1].type === TokenType.NUMBER) {
-                        // Check if the identifier looks like a function name (not a simple variable like 'n')
-                        if (identifier.value.length > 1 && /[a-zA-Z]/.test(identifier.value)) {
-                            return parseFunctionCall(identifier);
-                        }
-                    }
-                }
-                
-
-                
-
-                
-                return identifier;
+                    return scope.t.reduce(f, init, table);
+                };
             }
             
-            if (token.type === TokenType.FUNCTION_REF) {
-                current++; // Skip '@'
-                if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
-                    const funcName = tokens[current].value;
-                    current++;
-                    return {
-                        type: 'FunctionReference',
-                        name: funcName
-                    };
-                } else {
-                    throw new Error('Expected function name after @');
-                }
+            if (table === undefined) {
+                // Partial application: return a function that waits for the table
+                return function(table) {
+                    return scope.t.reduce(f, init, table);
+                };
             }
-
-                        if (token.type === TokenType.WILDCARD) {
-                current++; // Skip '_'
-                return { type: 'WildcardPattern' };
+            
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.reduce: third argument must be a table');
             }
-
-
             
-
+            let result = init;
+            for (const [key, value] of Object.entries(table)) {
+                result = f(result, value, key);
+            }
+            return result;
+        },
+        
+        /**
+         * Set: Immutably set a key-value pair in a table
+         * @param {Object} table - Table to modify
+         * @param {*} key - Key to set
+         * @param {*} value - Value to set
+         * @returns {Object} New table with the key-value pair set
+         * @throws {Error} When first argument is not a table
+         */
+        set: function(table, key, value) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.set: first argument must be a table');
+            }
             
-            // If we get here, it's an operator token that should be handled by parseExpression
-            // But we need to handle it here to avoid circular dependency
-            if (token.type === TokenType.LEFT_BRACE) {
-
-                current++; // Skip '{'
-                const entries = [];
-                let arrayIndex = 1;
-                
-                while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_BRACE) {
-                    // Skip leading commas
-                    if (tokens[current].type === TokenType.COMMA) {
-                        current++;
-                        continue;
-                    }
-                    
-                    let key = null;
-                    let value;
-                    
-                    // Check if this is a key-value pair or just a value
-                    
-                                        // Check if this is a key-value pair or just a value
-                    if (current + 1 < tokens.length && tokens[current + 1].type === TokenType.ASSIGNMENT) {
-                        // This is a key-value pair: key: value
-                        if (tokens[current].type === TokenType.IDENTIFIER) {
-                            key = {
-                                type: 'Identifier',
-                                value: tokens[current].value
-                            };
-                            current++; // Skip the key
-                        } else if (tokens[current].type === TokenType.NUMBER) {
-                            key = {
-                                type: 'NumberLiteral',
-                                value: tokens[current].value,
-                            };
-                            current++; // Skip the key
-                        } else if (tokens[current].type === TokenType.STRING) {
-                            key = {
-                                type: 'StringLiteral',
-                                value: tokens[current].value,
-                            };
-                            current++; // Skip the key
-                        } else if (tokens[current].type === TokenType.TRUE) {
-                            key = {
-                                type: 'BooleanLiteral',
-                                value: true,
-                            };
-                            current++; // Skip the key
-                        } else if (tokens[current].type === TokenType.FALSE) {
-                            key = {
-                                type: 'BooleanLiteral',
-                                value: false,
-                            };
-                            current++; // Skip the key
-
-                        } else {
-                            throw new Error('Invalid key type in table literal');
-                        }
-                        
-                        current++; // Skip ':'
-                        value = parseExpression();
-                    } else {
-                        // This is just a value (array-like entry)
-                        value = parseExpression();
-                    }
-                    
-                    entries.push({ key, value });
-
-                    
-                    // Skip trailing commas
-                    if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
-                        current++;
+            if (key === undefined) {
+                // Partial application: return a function that waits for the remaining arguments
+                return function(key, value) {
+                    if (value === undefined) {
+                        // Still partial application
+                        return function(value) {
+                            return scope.t.set(table, key, value);
+                        };
                     }
-                }
-                
-                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_BRACE) {
-                    current++; // Skip '}'
-                    return {
-                        type: 'TableLiteral',
-                        entries: entries
-                    };
-                } else {
-                    throw new Error('Expected closing brace');
-                }
+                    return scope.t.set(table, key, value);
+                };
             }
             
-            // If we get here, it's an operator token that should be handled by parseExpression
-            // But we need to handle it here to avoid circular dependency
-            if (token.type === TokenType.PLUS ||
-                token.type === TokenType.MINUS ||
-                token.type === TokenType.MULTIPLY ||
-                token.type === TokenType.DIVIDE ||
-                token.type === TokenType.MODULO ||
-                token.type === TokenType.POWER ||
-                token.type === TokenType.EQUALS ||
-                token.type === TokenType.NOT_EQUAL ||
-                token.type === TokenType.LESS_THAN ||
-                token.type === TokenType.GREATER_THAN ||
-                token.type === TokenType.LESS_EQUAL ||
-                token.type === TokenType.GREATER_EQUAL ||
-                token.type === TokenType.AND ||
-                token.type === TokenType.OR ||
-                token.type === TokenType.XOR) {
-                // Reset current to parse the expression properly
-                return parseExpression();
+            if (value === undefined) {
+                // Partial application: return a function that waits for the value
+                return function(value) {
+                    return scope.t.set(table, key, value);
+                };
             }
             
-            // If we get here, we have an unexpected token
-
-            throw new Error(`Unexpected token in parsePrimary: ${token.type}`);
-        }
+            return { ...table, [key]: value };
+        },
         
-        // Check for IO operations before calling parsePrimary
-        if (tokens[current].type === TokenType.IO_IN) {
-            current++;
-            return { type: 'IOInExpression' };
-        } else if (tokens[current].type === TokenType.IO_OUT) {
-            current++;
-            const outputValue = parseLogicalExpression();
-            return { type: 'IOOutExpression', value: outputValue };
-        } else if (tokens[current].type === TokenType.IO_ASSERT) {
-            current++;
-            const assertionExpr = parseLogicalExpression();
-            return { type: 'IOAssertExpression', value: assertionExpr };
-        }
-        
-        // Check for case expressions at top level
-        if (tokens[current].type === TokenType.CASE) {
-            current++; // Skip 'case'
+        /**
+         * Delete: Immutably delete a key from a table
+         * @param {Object} table - Table to modify
+         * @param {*} key - Key to delete
+         * @returns {Object} New table without the specified key
+         * @throws {Error} When first argument is not a table
+         */
+        delete: function(table, key) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.delete: first argument must be a table');
+            }
             
-            // Parse the values being matched (can be multiple)
-            const values = [];
-            while (current < tokens.length && tokens[current].type !== TokenType.OF) {
-                const value = parseLogicalExpression();
-                values.push(value);
+            if (key === undefined) {
+                // Partial application: return a function that waits for the key
+                return function(key) {
+                    return scope.t.delete(table, key);
+                };
             }
             
-            // Expect 'of'
-            if (current >= tokens.length || tokens[current].type !== TokenType.OF) {
-                throw new Error('Expected "of" after "case"');
+            const result = { ...table };
+            delete result[key];
+            return result;
+        },
+        
+        /**
+         * Merge: Immutably merge two tables
+         * @param {Object} table1 - First table
+         * @param {Object} table2 - Second table (values override table1)
+         * @returns {Object} New merged table
+         * @throws {Error} When either argument is not a table
+         */
+        merge: function(table1, table2) {
+            if (typeof table1 !== 'object' || table1 === null) {
+                throw new Error('t.merge: first argument must be a table');
             }
-            current++; // Skip 'of'
             
-            const cases = [];
+            if (table2 === undefined) {
+                // Partial application: return a function that waits for the second table
+                return function(table2) {
+                    return scope.t.merge(table1, table2);
+                };
+            }
             
-            // Parse cases until we hit a semicolon or end
-            while (current < tokens.length && tokens[current].type !== TokenType.SEMICOLON) {
-                const patterns = [];
-                while (current < tokens.length && 
-                       tokens[current].type !== TokenType.ASSIGNMENT && 
-                       tokens[current].type !== TokenType.SEMICOLON) {
-                    patterns.push(parseLogicalExpression());
-                }
-                
-                // Expect ':' after pattern
-                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
-                    current++; // Skip ':'
-                } else {
-                    throw new Error('Expected ":" after pattern in case expression');
-                }
-                
-                const result = parseLogicalExpression();
-                cases.push({ 
-                    pattern: patterns, 
-                    result: [result] 
-                });
+            if (typeof table2 !== 'object' || table2 === null) {
+                throw new Error('t.merge: second argument must be a table');
             }
             
-            return {
-                type: 'CaseExpression',
-                value: values,
-                cases,
-            };
-        }
+            return { ...table1, ...table2 };
+        },
         
-        // Simple wrapper that calls parseLogicalExpression for all token types
-        return parseLogicalExpression();
-    }
-    
-    const ast = {
-        type: 'Program',
-        body: []
-    };
-    
-    let lastCurrent = -1;
-    let loopCount = 0;
-    const maxLoops = tokens.length * 2; // Safety limit
-    
-    while (current < tokens.length) {
-        // Safety check to prevent infinite loops
-        if (current === lastCurrent) {
-            loopCount++;
-            if (loopCount > 10) { // Allow a few iterations at the same position
-                throw new Error(`Parser stuck at position ${current}, token: ${tokens[current]?.type || 'EOF'}`);
+        /**
+         * Pairs: Get all key-value pairs from a table
+         * @param {Object} table - Table to get pairs from
+         * @returns {Array} Array of [key, value] pairs
+         * @throws {Error} When argument is not a table
+         */
+        pairs: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.pairs: argument must be a table');
             }
-        } else {
-            loopCount = 0;
-        }
+            return Object.entries(table);
+        },
         
-        // Safety check for maximum loops
-        if (loopCount > maxLoops) {
-            throw new Error(`Parser exceeded maximum loop count. Last position: ${current}`);
-        }
+        /**
+         * Keys: Get all keys from a table
+         * @param {Object} table - Table to get keys from
+         * @returns {Array} Array of keys
+         * @throws {Error} When argument is not a table
+         */
+        keys: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.keys: argument must be a table');
+            }
+            return Object.keys(table);
+        },
         
-        lastCurrent = current;
+        /**
+         * Values: Get all values from a table
+         * @param {Object} table - Table to get values from
+         * @returns {Array} Array of values
+         * @throws {Error} When argument is not a table
+         */
+        values: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.values: argument must be a table');
+            }
+            return Object.values(table);
+        },
         
-        const node = walk();
-        if (node) {
-            ast.body.push(node);
-        }
+        /**
+         * Length: Get the number of key-value pairs in a table
+         * @param {Object} table - Table to measure
+         * @returns {number} Number of key-value pairs
+         * @throws {Error} When argument is not a table
+         */
+        length: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.length: argument must be a table');
+            }
+            return Object.keys(table).length;
+        },
         
-        // Skip semicolons
-        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
-            current++;
+        /**
+         * Has: Check if a table has a specific key
+         * @param {Object} table - Table to check
+         * @param {*} key - Key to check for
+         * @returns {boolean} True if key exists, false otherwise
+         * @throws {Error} When first argument is not a table
+         */
+        has: function(table, key) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.has: first argument must be a table');
+            }
+            
+            if (key === undefined) {
+                // Partial application: return a function that waits for the key
+                return function(key) {
+                    return scope.t.has(table, key);
+                };
+            }
+            
+            return table.hasOwnProperty(key);
+        },
+        
+        /**
+         * Get: Safely get a value from a table with optional default
+         * @param {Object} table - Table to get from
+         * @param {*} key - Key to get
+         * @param {*} defaultValue - Default value if key doesn't exist
+         * @returns {*} Value at key or default value
+         * @throws {Error} When first argument is not a table
+         */
+        get: function(table, key, defaultValue) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.get: first argument must be a table');
+            }
+            
+            if (key === undefined) {
+                // Partial application: return a function that waits for the remaining arguments
+                return function(key, defaultValue) {
+                    if (defaultValue === undefined) {
+                        // Still partial application
+                        return function(defaultValue) {
+                            return scope.t.get(table, key, defaultValue);
+                        };
+                    }
+                    return scope.t.get(table, key, defaultValue);
+                };
+            }
+            
+            if (defaultValue === undefined) {
+                // Partial application: return a function that waits for the default value
+                return function(defaultValue) {
+                    return scope.t.get(table, key, defaultValue);
+                };
+            }
+            
+            return table.hasOwnProperty(key) ? table[key] : defaultValue;
         }
-    }
-    
-    return ast;
+    };
 }
 
 /**
- * Interpreter: Walks the AST and evaluates each node.
+ * Interpreter: Walks the AST and evaluates each node using the combinator foundation.
+ * 
+ * @param {Object} ast - Abstract Syntax Tree to evaluate
+ * @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.
+ * 
+ * @description Evaluates an AST by walking through each node and performing the
+ * corresponding operations. Manages scope, handles function calls, and supports
+ * both synchronous and asynchronous operations.
+ * 
+ * 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
+ * all operators (+, -, *, /, etc.) into FunctionCall nodes that reference combinator
+ * functions, ensuring consistent semantics across all operations.
+ * 
+ * Key architectural features:
+ * - Combinator Foundation: All operations are function calls to standard library combinators
+ * - Scope Management: Prototypal inheritance for variable lookup and function definitions
+ * - 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
+ * 
+ * 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 
+ * lexical scoping. Immutability is enforced by preventing reassignment in the 
+ * global scope.
  * 
- * How: Uses a global scope for variable storage and function definitions. Each function call creates a new scope (using prototypal inheritance) to implement lexical scoping. Immutability is enforced by preventing reassignment in the global scope.
+ * The interpreter is split into three functions: evalNode (global), 
+ * localEvalNodeWithScope (for function bodies), and localEvalNode (for internal 
+ * recursion). This separation allows for correct scope handling and easier debugging.
  * 
- * Why: This approach allows for first-class functions, closures, and lexical scoping, while keeping the implementation simple. The interpreter supports both synchronous and asynchronous IO operations, returning Promises when necessary.
+ * Recursive function support is implemented using a forward declaration pattern:
+ * a placeholder function is created in the global scope before evaluation, allowing
+ * the function body to reference itself during evaluation.
  * 
- * The interpreter is split into three functions: evalNode (global), localEvalNodeWithScope (for function bodies), and localEvalNode (for internal recursion). This separation allows for correct scope handling and easier debugging.
+ * 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
+ * handling of different operator types in the interpreter.
  */
-function interpreter(ast) {
-    const globalScope = {};
+function interpreter(ast, environment = null, initialState = {}) {
+    const globalScope = { ...initialState };
     initializeStandardLibrary(globalScope);
     
+    // Track whether any IO operations have been performed
+    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');
+    }
+    
+    // Reset call stack tracker at the start of interpretation
+    callStackTracker.reset();
+    
+    /**
+     * Evaluates AST nodes in the global scope using the combinator foundation.
+     * 
+     * @param {Object} node - AST node to evaluate
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Main evaluation function that handles all node types in the
+     * global scope context. This function processes the core language constructs
+     * and delegates to combinator functions for all operations.
+     * 
+     * The function implements the forward declaration pattern for recursive functions:
+     * when a function assignment is detected, a placeholder is created in the global
+     * scope before evaluation, allowing the function body to reference itself.
+     * This pattern enables natural recursive function definitions without requiring
+     * special syntax or pre-declaration.
+     * 
+     * This function is the primary entry point for AST evaluation and handles
+     * all the core language constructs including literals, operators (translated
+     * to combinator calls), function definitions, and control structures. It
+     * ensures that all operations are executed through the combinator foundation,
+     * providing consistent semantics across the language.
+     * 
+     * The function 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 design ensures that all operations follow the same execution model and
+     * can be extended by adding new combinator functions to the standard library.
+     * 
+     * Key evaluation patterns:
+     * - Literals: Direct value return
+     * - FunctionCall: Delegates to standard library combinator functions
+     * - Assignment: Creates variables in global scope with forward declaration support
+     * - WhenExpression: Pattern matching with wildcard support
+     * - TableLiteral: Creates immutable table structures
+     * - TableAccess: Safe property access with error handling
+     */
     function evalNode(node) {
-        if (!node) {
-            return undefined;
-        }
-        switch (node.type) {
-            case 'NumberLiteral':
-                return parseFloat(node.value);
-            case 'StringLiteral':
-                return node.value;
-            case 'BooleanLiteral':
-                return node.value;
-            case 'PlusExpression':
-                return evalNode(node.left) + evalNode(node.right);
-            case 'MinusExpression':
-                return evalNode(node.left) - evalNode(node.right);
-            case 'MultiplyExpression':
-                return evalNode(node.left) * evalNode(node.right);
-            case 'DivideExpression':
-                const divisor = evalNode(node.right);
-                if (divisor === 0) {
-                    throw new Error('Division by zero');
-                }
-                return evalNode(node.left) / evalNode(node.right);
-            case 'ModuloExpression':
-                return evalNode(node.left) % evalNode(node.right);
-            case 'PowerExpression':
-                return Math.pow(evalNode(node.left), evalNode(node.right));
-            case 'EqualsExpression':
-                return evalNode(node.left) === evalNode(node.right);
-            case 'LessThanExpression':
-                return evalNode(node.left) < evalNode(node.right);
-            case 'GreaterThanExpression':
-                return evalNode(node.left) > evalNode(node.right);
-            case 'LessEqualExpression':
-                return evalNode(node.left) <= evalNode(node.right);
-            case 'GreaterEqualExpression':
-                return evalNode(node.left) >= evalNode(node.right);
-            case 'NotEqualExpression':
-                return evalNode(node.left) !== evalNode(node.right);
-            case 'AndExpression':
-                return !!(evalNode(node.left) && evalNode(node.right));
-            case 'OrExpression':
-                return !!(evalNode(node.left) || evalNode(node.right));
-            case 'XorExpression':
-                const leftVal = evalNode(node.left);
-                const rightVal = evalNode(node.right);
-                return !!((leftVal && !rightVal) || (!leftVal && rightVal));
-            case 'NotExpression':
-                return !evalNode(node.operand);
-            case 'UnaryMinusExpression':
-                return -evalNode(node.operand);
-            case 'TableLiteral':
-                const table = {};
-                let arrayIndex = 1;
-                
-                for (const entry of node.entries) {
-                    if (entry.key === null) {
-                        // Array-like entry: {1, 2, 3}
-                        table[arrayIndex] = evalNode(entry.value);
-                        arrayIndex++;
-                    } else {
-                        // Key-value entry: {name: "Alice", age: 30}
-                        let key;
-                        if (entry.key.type === 'Identifier') {
-                            // Convert identifier keys to strings
-                            key = entry.key.value;
+        callStackTracker.push('evalNode', node?.type || 'unknown');
+        
+        try {
+            if (!node) {
+                return undefined;
+            }
+            switch (node.type) {
+                case 'NumberLiteral':
+                    return parseFloat(node.value);
+                case 'StringLiteral':
+                    return node.value;
+                case 'BooleanLiteral':
+                    return node.value;
+                case 'PlusExpression':
+                    return evalNode(node.left) + evalNode(node.right);
+                case 'MinusExpression':
+                    return evalNode(node.left) - evalNode(node.right);
+                case 'MultiplyExpression':
+                    return evalNode(node.left) * evalNode(node.right);
+                case 'DivideExpression':
+                    const divisor = evalNode(node.right);
+                    if (divisor === 0) {
+                        throw new Error('Division by zero');
+                    }
+                    return evalNode(node.left) / evalNode(node.right);
+                case 'ModuloExpression':
+                    return evalNode(node.left) % evalNode(node.right);
+                case 'PowerExpression':
+                    return Math.pow(evalNode(node.left), evalNode(node.right));
+                case 'EqualsExpression':
+                    return evalNode(node.left) === evalNode(node.right);
+                case 'LessThanExpression':
+                    return evalNode(node.left) < evalNode(node.right);
+                case 'GreaterThanExpression':
+                    return evalNode(node.left) > evalNode(node.right);
+                case 'LessEqualExpression':
+                    return evalNode(node.left) <= evalNode(node.right);
+                case 'GreaterEqualExpression':
+                    return evalNode(node.left) >= evalNode(node.right);
+                case 'NotEqualExpression':
+                    return evalNode(node.left) !== evalNode(node.right);
+                case 'AndExpression':
+                    return !!(evalNode(node.left) && evalNode(node.right));
+                case 'OrExpression':
+                    return !!(evalNode(node.left) || evalNode(node.right));
+                case 'XorExpression':
+                    const leftVal = evalNode(node.left);
+                    const rightVal = evalNode(node.right);
+                    return !!((leftVal && !rightVal) || (!leftVal && rightVal));
+                case 'NotExpression':
+                    return !evalNode(node.operand);
+                case 'UnaryMinusExpression':
+                    return -evalNode(node.operand);
+                case 'TableLiteral':
+                    const table = {};
+                    let arrayIndex = 1;
+                    
+                    for (const entry of node.entries) {
+                        if (entry.key === null) {
+                            // Array-like entry: {1, 2, 3}
+                            table[arrayIndex] = evalNode(entry.value);
+                            arrayIndex++;
                         } else {
-                            // For other key types (numbers, strings), evaluate normally
-                            key = evalNode(entry.key);
+                            // Key-value entry: {name: "Alice", age: 30}
+                            let key;
+                            if (entry.key.type === 'Identifier') {
+                                // Convert identifier keys to strings
+                                key = entry.key.value;
+                            } else {
+                                // For other key types (numbers, strings), evaluate normally
+                                key = evalNode(entry.key);
+                            }
+                            // Special handling for FunctionDeclaration nodes
+                            if (process.env.DEBUG) {
+                                console.log(`[DEBUG] TableLiteral: entry.value.type = ${entry.value.type}`);
+                            }
+                            if (entry.value.type === 'FunctionDeclaration') {
+                                // Don't evaluate the function body, just create the function
+                                const func = function(...args) {
+                                    callStackTracker.push('FunctionCall', entry.value.params.join(','));
+                                    try {
+                                        // If we have fewer arguments than parameters, return a curried function
+                                        if (args.length < entry.value.params.length) {
+                                            return function(...moreArgs) {
+                                                const allArgs = [...args, ...moreArgs];
+                                                if (allArgs.length < entry.value.params.length) {
+                                                    // Still not enough arguments, curry again
+                                                    return function(...evenMoreArgs) {
+                                                        const finalArgs = [...allArgs, ...evenMoreArgs];
+                                                        let localScope = Object.create(globalScope);
+                                                        for (let i = 0; i < entry.value.params.length; i++) {
+                                                            localScope[entry.value.params[i]] = finalArgs[i];
+                                                        }
+                                                        return localEvalNodeWithScope(entry.value.body, localScope);
+                                                    };
+                                                } else {
+                                                    // We have enough arguments now
+                                                    let localScope = Object.create(globalScope);
+                                                    for (let i = 0; i < entry.value.params.length; i++) {
+                                                        localScope[entry.value.params[i]] = allArgs[i];
+                                                    }
+                                                    return localEvalNodeWithScope(entry.value.body, localScope);
+                                                }
+                                            };
+                                        } else {
+                                            // We have enough arguments, evaluate the function
+                                            let localScope = Object.create(globalScope);
+                                            for (let i = 0; i < entry.value.params.length; i++) {
+                                                localScope[entry.value.params[i]] = args[i];
+                                            }
+                                            return localEvalNodeWithScope(entry.value.body, localScope);
+                                        }
+                                    } finally {
+                                        callStackTracker.pop();
+                                    }
+                                };
+                                table[key] = func;
+                            } else {
+                                const value = evalNode(entry.value);
+                                table[key] = value;
+                            }
                         }
-                        const value = evalNode(entry.value);
-                        table[key] = value;
                     }
-                }
-                
-                return table;
-            case 'TableAccess':
-                const tableValue = evalNode(node.table);
-                let keyValue;
-                
-                // Handle different key types
-                if (node.key.type === 'Identifier') {
-                    // For dot notation, use the identifier name as the key
-                    keyValue = node.key.value;
-                } else {
-                    // For bracket notation, evaluate the key expression
-                    keyValue = evalNode(node.key);
-                }
-                
-                if (typeof tableValue !== 'object' || tableValue === null) {
-                    throw new Error('Cannot access property of non-table value');
-                }
-                
-                if (tableValue[keyValue] === undefined) {
-                    throw new Error(`Key '${keyValue}' not found in table`);
-                }
-                
-                return tableValue[keyValue];
-            case 'AssignmentExpression':
-                if (globalScope.hasOwnProperty(node.name)) {
-                    throw new Error(`Cannot reassign immutable variable: ${node.name}`);
-                }
-                const value = evalNode(node.value);
-                globalScope[node.name] = value;
-                return;
-            case 'Identifier':
-                const identifierValue = globalScope[node.value];
-                if (identifierValue === undefined) {
-                    throw new Error(`Variable ${node.value} is not defined`);
-                }
-                return identifierValue;
-            case 'FunctionDeclaration':
-                // For anonymous functions, the name comes from the assignment
-                // The function itself doesn't have a name, so we just return
-                // The assignment will handle storing it in the global scope
-                return function(...args) {
-                    let localScope = Object.create(globalScope);
-                    for (let i = 0; i < node.params.length; i++) {
-                        localScope[node.params[i]] = args[i];
+                    
+                    return table;
+                case 'TableAccess':
+                    const tableValue = evalNode(node.table);
+                    let keyValue;
+                    
+                    // Handle different key types
+                    if (node.key.type === 'Identifier') {
+                        // For dot notation, use the identifier name as the key
+                        keyValue = node.key.value;
+                    } else {
+                        // For bracket notation, evaluate the key expression
+                        keyValue = evalNode(node.key);
                     }
-                    return localEvalNodeWithScope(node.body, localScope);
-                };
-            case 'FunctionCall':
-                let funcToCall; // Renamed from 'func' to avoid redeclaration
-                if (typeof node.name === 'string') {
-                    // Regular function call with string name
-                    funcToCall = globalScope[node.name];
-                } else if (node.name.type === 'Identifier') {
-                    // Function call with identifier
-                    funcToCall = globalScope[node.name.value];
-                } else if (node.name.type === 'TableAccess') {
-                    // Function call from table access (e.g., math.add)
-                    funcToCall = evalNode(node.name);
-                } else {
-                    throw new Error('Invalid function name in function call');
-                }
-                
-                if (funcToCall instanceof Function) {
-                    let args = node.args.map(evalNode);
-                    return funcToCall(...args);
-                }
-                throw new Error(`Function is not defined or is not callable`);
-            case 'CaseExpression':
-                const values = node.value.map(evalNode);
-                
-                for (const caseItem of node.cases) {
-                    const pattern = caseItem.pattern.map(evalNode);
                     
-                    let matches = true;
-                    for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
-                        const value = values[i];
-                        const patternValue = pattern[i];
+                    if (typeof tableValue !== 'object' || tableValue === null) {
+                        throw new Error('Cannot access property of non-table value');
+                    }
+                    
+                    if (tableValue[keyValue] === undefined) {
+                        throw new Error(`Key '${keyValue}' not found in table`);
+                    }
+                    
+                    return tableValue[keyValue];
+                case 'AssignmentExpression':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.name} is not yet fully defined`);
+                        };
                         
-                        if (patternValue === true) continue;
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
                         
-                        if (value !== patternValue) {
-                            matches = false;
-                            break;
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = evalNode(node.value);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.name] = actualFunction;
+                        return;
+                    }
+                    
+                    const value = evalNode(node.value);
+                    globalScope[node.name] = value;
+                    return;
+                case 'Assignment':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.identifier)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.identifier}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.identifier} is not yet fully defined`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.identifier] = placeholder;
+                        
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = evalNode(node.value);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.identifier] = actualFunction;
+                        return;
+                    }
+                    
+                    const assignmentValue = evalNode(node.value);
+                    globalScope[node.identifier] = assignmentValue;
+                    return;
+                case 'Identifier':
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined) {
+                        throw new Error(`Variable ${node.value} is not defined`);
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            // If we have fewer arguments than parameters, return a curried function
+                            if (args.length < node.params.length) {
+                                return function(...moreArgs) {
+                                    const allArgs = [...args, ...moreArgs];
+                                    if (allArgs.length < node.params.length) {
+                                        // Still not enough arguments, curry again
+                                        return function(...evenMoreArgs) {
+                                            const finalArgs = [...allArgs, ...evenMoreArgs];
+                                            let localScope = Object.create(globalScope);
+                                            for (let i = 0; i < node.params.length; i++) {
+                                                localScope[node.params[i]] = finalArgs[i];
+                                            }
+                                            return localEvalNodeWithScope(node.body, localScope);
+                                        };
+                                    } else {
+                                        // We have enough arguments now
+                                        let localScope = Object.create(globalScope);
+                                        for (let i = 0; i < node.params.length; i++) {
+                                            localScope[node.params[i]] = allArgs[i];
+                                        }
+                                        return localEvalNodeWithScope(node.body, localScope);
+                                    }
+                                };
+                            } else {
+                                // We have enough arguments, evaluate the function
+                                let localScope = Object.create(globalScope);
+                                for (let i = 0; i < node.params.length; i++) {
+                                    localScope[node.params[i]] = args[i];
+                                }
+                                return localEvalNodeWithScope(node.body, localScope);
+                            }
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionDefinition':
+                    // Create a function from the function definition
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.parameters.join(','));
+                        try {
+                            let localScope = Object.create(globalScope);
+                            for (let i = 0; i < node.parameters.length; i++) {
+                                localScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, localScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let funcToCall;
+                    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);
+                        }
+                    } 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);
+                        }
+                    } 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 (matches) {
-                        const results = caseItem.result.map(evalNode);
-                        if (results.length === 1) {
-                            return results[0];
+                    if (funcToCall instanceof Function) {
+                        let args = node.args.map(evalNode);
+                        if (process.env.DEBUG) {
+                            console.log(`[DEBUG] FunctionCall: calling function with args:`, args);
                         }
-                        return results.join(' ');
+                        return funcToCall(...args);
                     }
-                }
-                throw new Error('No matching pattern found');
-            case 'WildcardPattern':
-                return true;
-            case 'IOInExpression':
-                const readline = require('readline');
-                const rl = readline.createInterface({
-                    input: process.stdin,
-                    output: process.stdout
-                });
-                
-                return new Promise((resolve) => {
-                    rl.question('', (input) => {
-                        rl.close();
-                        const num = parseInt(input);
-                        resolve(isNaN(num) ? input : num);
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(evalNode) 
+                        : [evalNode(node.value)];
+                    
+                    if (process.env.DEBUG) {
+                        console.log(`[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);
+                        }
+                        
+                        // Check if patterns match the values
+                        let matches = true;
+                        if (whenValues.length !== patterns.length) {
+                            matches = false;
+                        } else {
+                            for (let i = 0; i < whenValues.length; i++) {
+                                const value = whenValues[i];
+                                const pattern = patterns[i];
+                                
+                                if (process.env.DEBUG) {
+                                    console.log(`[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`);
+                                    }
+                                    continue;
+                                } else if (typeof pattern === 'object' && pattern.type === 'FunctionCall') {
+                                    // This is a boolean expression pattern (e.g., x < 0)
+                                    // We need to substitute the current value for the pattern variable
+                                    // For now, let's assume the pattern variable is the first identifier in the function call
+                                    let patternToEvaluate = pattern;
+                                    if (pattern.args && pattern.args.length > 0 && pattern.args[0].type === 'Identifier') {
+                                        // Create a copy of the pattern with the current value substituted
+                                        patternToEvaluate = {
+                                            ...pattern,
+                                            args: [value, ...pattern.args.slice(1)]
+                                        };
+                                    }
+                                    const patternResult = evalNode(patternToEvaluate);
+                                    if (process.env.DEBUG) {
+                                        console.log(`[DEBUG] WhenExpression: boolean pattern result = ${patternResult}`);
+                                    }
+                                    if (!patternResult) {
+                                        matches = false;
+                                        if (process.env.DEBUG) {
+                                            console.log(`[DEBUG] WhenExpression: boolean pattern does not match`);
+                                        }
+                                        break;
+                                    } else {
+                                        if (process.env.DEBUG) {
+                                            console.log(`[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;
+                                    for (const key in pattern) {
+                                        if (pattern.hasOwnProperty(key) && (!value.hasOwnProperty(key) || value[key] !== pattern[key])) {
+                                            tableMatches = false;
+                                            break;
+                                        }
+                                    }
+                                    if (!tableMatches) {
+                                        matches = false;
+                                        if (process.env.DEBUG) {
+                                            console.log(`[DEBUG] WhenExpression: table pattern does not match`);
+                                        }
+                                        break;
+                                    } else {
+                                        if (process.env.DEBUG) {
+                                            console.log(`[DEBUG] WhenExpression: table pattern matches`);
+                                        }
+                                    }
+                                } else if (value !== pattern) {
+                                    matches = false;
+                                    if (process.env.DEBUG) {
+                                        console.log(`[DEBUG] WhenExpression: pattern does not match`);
+                                    }
+                                    break;
+                                } else {
+                                    if (process.env.DEBUG) {
+                                        console.log(`[DEBUG] WhenExpression: pattern matches`);
+                                    }
+                                }
+                            }
+                        }
+                        
+                        if (process.env.DEBUG) {
+                            console.log(`[DEBUG] WhenExpression: case matches = ${matches}`);
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(evalNode);
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const readline = require('readline');
+                    const rl = readline.createInterface({
+                        input: process.stdin,
+                        output: process.stdout
                     });
-                });
-            case 'IOOutExpression':
-                const outputValue = evalNode(node.value);
-                console.log(outputValue);
-                return outputValue;
-            case 'IOAssertExpression':
-                const assertionValue = evalNode(node.value);
-                if (!assertionValue) {
-                    throw new Error('Assertion failed');
-                }
-                return assertionValue;
-            case 'FunctionReference':
-                const functionValue = globalScope[node.name];
-                if (functionValue === undefined) {
-                    throw new Error(`Function ${node.name} is not defined`);
-                }
-                if (typeof functionValue !== 'function') {
-                    throw new Error(`${node.name} is not a function`);
-                }
-                return functionValue;
-            default:
-                throw new Error(`Unknown node type: ${node.type}`);
+                    
+                    return new Promise((resolve) => {
+                        rl.question('', (input) => {
+                            rl.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const outputValue = evalNode(node.value);
+                    console.log(outputValue);
+                    ioOperationsPerformed = true;
+                    return outputValue;
+                case 'IOAssertExpression':
+                    const assertionValue = evalNode(node.value);
+                    if (!assertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return assertionValue;
+                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');
+                        }
+                        return environment.getCurrentState();
+                    } else {
+                        if (process.env.DEBUG) {
+                            console.log('[DEBUG] ..listen called - returning placeholder state');
+                        }
+                        return { status: 'placeholder', message: 'State not available in standalone mode' };
+                    }
+                case 'IOEmitExpression':
+                    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');
+                        }
+                        environment.emitValue(emitValue);
+                    } else {
+                        console.log('[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 (functionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof functionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return functionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return evalNode(node.body);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
         }
     }
 
+    /**
+     * Evaluates AST nodes in a local scope with access to parent scope.
+     * 
+     * @param {Object} 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
+     * 
+     * @description Used for evaluating function bodies and other expressions
+     * that need access to both local and global variables. This function implements
+     * lexical scoping by creating a local scope that prototypally inherits from
+     * the global scope, allowing access to both local parameters and global functions.
+     * 
+     * The function handles the same node types as evalNode but uses the local scope
+     * for variable lookups. It also implements the forward declaration pattern for
+     * recursive functions, ensuring that function definitions can reference themselves
+     * during evaluation.
+     * 
+     * This separation of global and local evaluation allows for proper scope management
+     * and prevents variable name conflicts between function parameters and global variables.
+     * 
+     * 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.
+     */
     const localEvalNodeWithScope = (node, scope) => {
-        if (!node) {
-            return undefined;
-        }
-        switch (node.type) {
-            case 'NumberLiteral':
-                return parseFloat(node.value);
-            case 'StringLiteral':
-                return node.value;
-            case 'BooleanLiteral':
-                return node.value;
-            case 'PlusExpression':
-                return localEvalNodeWithScope(node.left, scope) + localEvalNodeWithScope(node.right, scope);
-            case 'MinusExpression':
-                return localEvalNodeWithScope(node.left, scope) - localEvalNodeWithScope(node.right, scope);
-            case 'MultiplyExpression':
-                return localEvalNodeWithScope(node.left, scope) * localEvalNodeWithScope(node.right, scope);
-            case 'DivideExpression':
-                const divisor = localEvalNodeWithScope(node.right, scope);
-                if (divisor === 0) {
-                    throw new Error('Division by zero');
-                }
-                return localEvalNodeWithScope(node.left, scope) / localEvalNodeWithScope(node.right, scope);
-            case 'ModuloExpression':
-                return localEvalNodeWithScope(node.left, scope) % localEvalNodeWithScope(node.right, scope);
-            case 'PowerExpression':
-                return Math.pow(localEvalNodeWithScope(node.left, scope), localEvalNodeWithScope(node.right, scope));
-            case 'EqualsExpression':
-                return localEvalNodeWithScope(node.left, scope) === localEvalNodeWithScope(node.right, scope);
-            case 'LessThanExpression':
-                return localEvalNodeWithScope(node.left, scope) < localEvalNodeWithScope(node.right, scope);
-            case 'GreaterThanExpression':
-                return localEvalNodeWithScope(node.left, scope) > localEvalNodeWithScope(node.right, scope);
-            case 'LessEqualExpression':
-                return localEvalNodeWithScope(node.left, scope) <= localEvalNodeWithScope(node.right, scope);
-            case 'GreaterEqualExpression':
-                return localEvalNodeWithScope(node.left, scope) >= localEvalNodeWithScope(node.right, scope);
-            case 'NotEqualExpression':
-                return localEvalNodeWithScope(node.left, scope) !== localEvalNodeWithScope(node.right, scope);
-            case 'AndExpression':
-                return !!(localEvalNodeWithScope(node.left, scope) && localEvalNodeWithScope(node.right, scope));
-            case 'OrExpression':
-                return !!(localEvalNodeWithScope(node.left, scope) || localEvalNodeWithScope(node.right, scope));
-            case 'XorExpression':
-                const leftVal = localEvalNodeWithScope(node.left, scope);
-                const rightVal = localEvalNodeWithScope(node.right, scope);
-                return !!((leftVal && !rightVal) || (!leftVal && rightVal));
-            case 'NotExpression':
-                return !localEvalNodeWithScope(node.operand, scope);
-            case 'UnaryMinusExpression':
-                return -localEvalNodeWithScope(node.operand, scope);
-            case 'TableLiteral':
-                const table = {};
-                let arrayIndex = 1;
-                
-                for (const entry of node.entries) {
-                    if (entry.key === null) {
-                        // Array-like entry: {1, 2, 3}
-                        table[arrayIndex] = localEvalNodeWithScope(entry.value, scope);
-                        arrayIndex++;
-                    } else {
-                        // Key-value entry: {name: "Alice", age: 30}
-                        let key;
-                        if (entry.key.type === 'Identifier') {
-                            // Convert identifier keys to strings
-                            key = entry.key.value;
+        callStackTracker.push('localEvalNodeWithScope', node?.type || 'unknown');
+        
+        try {
+            if (!node) {
+                return undefined;
+            }
+            switch (node.type) {
+                case 'NumberLiteral':
+                    return parseFloat(node.value);
+                case 'StringLiteral':
+                    return node.value;
+                case 'BooleanLiteral':
+                    return node.value;
+                case 'PlusExpression':
+                    return localEvalNodeWithScope(node.left, scope) + localEvalNodeWithScope(node.right, scope);
+                case 'MinusExpression':
+                    return localEvalNodeWithScope(node.left, scope) - localEvalNodeWithScope(node.right, scope);
+                case 'MultiplyExpression':
+                    return localEvalNodeWithScope(node.left, scope) * localEvalNodeWithScope(node.right, scope);
+                case 'DivideExpression':
+                    const divisor = localEvalNodeWithScope(node.right, scope);
+                    if (divisor === 0) {
+                        throw new Error('Division by zero');
+                    }
+                    return localEvalNodeWithScope(node.left, scope) / localEvalNodeWithScope(node.right, scope);
+                case 'ModuloExpression':
+                    return localEvalNodeWithScope(node.left, scope) % localEvalNodeWithScope(node.right, scope);
+                case 'PowerExpression':
+                    return Math.pow(localEvalNodeWithScope(node.left, scope), localEvalNodeWithScope(node.right, scope));
+                case 'EqualsExpression':
+                    return localEvalNodeWithScope(node.left, scope) === localEvalNodeWithScope(node.right, scope);
+                case 'LessThanExpression':
+                    return localEvalNodeWithScope(node.left, scope) < localEvalNodeWithScope(node.right, scope);
+                case 'GreaterThanExpression':
+                    return localEvalNodeWithScope(node.left, scope) > localEvalNodeWithScope(node.right, scope);
+                case 'LessEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) <= localEvalNodeWithScope(node.right, scope);
+                case 'GreaterEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) >= localEvalNodeWithScope(node.right, scope);
+                case 'NotEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) !== localEvalNodeWithScope(node.right, scope);
+                case 'AndExpression':
+                    return !!(localEvalNodeWithScope(node.left, scope) && localEvalNodeWithScope(node.right, scope));
+                case 'OrExpression':
+                    return !!(localEvalNodeWithScope(node.left, scope) || localEvalNodeWithScope(node.right, scope));
+                case 'XorExpression':
+                    const leftVal = localEvalNodeWithScope(node.left, scope);
+                    const rightVal = localEvalNodeWithScope(node.right, scope);
+                    return !!((leftVal && !rightVal) || (!leftVal && rightVal));
+                case 'NotExpression':
+                    return !localEvalNodeWithScope(node.operand, scope);
+                case 'UnaryMinusExpression':
+                    return -localEvalNodeWithScope(node.operand, scope);
+                case 'TableLiteral':
+                    const table = {};
+                    let arrayIndex = 1;
+                    
+                    for (const entry of node.entries) {
+                        if (entry.key === null) {
+                            // Array-like entry: {1, 2, 3}
+                            table[arrayIndex] = localEvalNodeWithScope(entry.value, scope);
+                            arrayIndex++;
                         } else {
-                            // For other key types (numbers, strings), evaluate normally
-                            key = localEvalNodeWithScope(entry.key, scope);
+                            // Key-value entry: {name: "Alice", age: 30}
+                            let key;
+                            if (entry.key.type === 'Identifier') {
+                                // Convert identifier keys to strings
+                                key = entry.key.value;
+                            } else {
+                                // For other key types (numbers, strings), evaluate normally
+                                key = localEvalNodeWithScope(entry.key, scope);
+                            }
+                            const value = localEvalNodeWithScope(entry.value, scope);
+                            table[key] = value;
                         }
-                        const value = localEvalNodeWithScope(entry.value, scope);
-                        table[key] = value;
                     }
-                }
-                
-                return table;
-            case 'TableAccess':
-                const tableValue = localEvalNodeWithScope(node.table, scope);
-                let keyValue;
-                
-                // Handle different key types
-                if (node.key.type === 'Identifier') {
-                    // For dot notation, use the identifier name as the key
-                    keyValue = node.key.value;
-                } else {
-                    // For bracket notation, evaluate the key expression
-                    keyValue = localEvalNodeWithScope(node.key, scope);
-                }
-                
-                if (typeof tableValue !== 'object' || tableValue === null) {
-                    throw new Error('Cannot access property of non-table value');
-                }
-                
-                if (tableValue[keyValue] === undefined) {
-                    throw new Error(`Key '${keyValue}' not found in table`);
-                }
-                
-                return tableValue[keyValue];
-            case 'AssignmentExpression':
-                if (globalScope.hasOwnProperty(node.name)) {
-                    throw new Error(`Cannot reassign immutable variable: ${node.name}`);
-                }
-                globalScope[node.name] = localEvalNodeWithScope(node.value, scope);
-                return;
-            case 'Identifier':
-                // First check local scope, then global scope
-                if (scope && scope.hasOwnProperty(node.value)) {
-                    return scope[node.value];
-                }
-                const identifierValue = globalScope[node.value];
-                if (identifierValue === undefined && node.value) {
-                    return node.value;
-                }
-                return identifierValue;
-            case 'FunctionDeclaration':
-                // For anonymous functions, the name comes from the assignment
-                // The function itself doesn't have a name, so we just return
-                // The assignment will handle storing it in the global scope
-                return function(...args) {
-                    let nestedScope = Object.create(globalScope);
-                    for (let i = 0; i < node.params.length; i++) {
-                        nestedScope[node.params[i]] = args[i];
+                    
+                    return table;
+                case 'TableAccess':
+                    const tableValue = localEvalNodeWithScope(node.table, scope);
+                    let keyValue;
+                    
+                    // Handle different key types
+                    if (node.key.type === 'Identifier') {
+                        // For dot notation, use the identifier name as the key
+                        keyValue = node.key.value;
+                    } else {
+                        // For bracket notation, evaluate the key expression
+                        keyValue = localEvalNodeWithScope(node.key, scope);
+                    }
+                    
+                    if (typeof tableValue !== 'object' || tableValue === null) {
+                        throw new Error('Cannot access property of non-table value');
                     }
-                    return localEvalNodeWithScope(node.body, nestedScope);
-                };
-            case 'FunctionCall':
-                let localFunc;
-                if (typeof node.name === 'string') {
-                    // Regular function call with string name
-                    localFunc = globalScope[node.name];
-                } else if (node.name.type === 'Identifier') {
-                    // Function call with identifier
-                    localFunc = globalScope[node.name.value];
-                } else if (node.name.type === 'TableAccess') {
-                    // Function call from table access (e.g., math.add)
-                    localFunc = localEvalNodeWithScope(node.name, scope);
-                } else {
-                    throw new Error('Invalid function name in function call');
-                }
-                
-                if (localFunc instanceof Function) {
-                    let args = node.args.map(arg => localEvalNodeWithScope(arg, scope));
-                    return localFunc(...args);
-                }
-                throw new Error(`Function is not defined or is not callable`);
-            case 'CaseExpression':
-                const values = node.value.map(val => localEvalNodeWithScope(val, scope));
-                
-                for (const caseItem of node.cases) {
-                    const pattern = caseItem.pattern.map(pat => localEvalNodeWithScope(pat, scope));
                     
-                    let matches = true;
-                    for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
-                        const value = values[i];
-                        const patternValue = pattern[i];
+                    if (tableValue[keyValue] === undefined) {
+                        throw new Error(`Key '${keyValue}' not found in table`);
+                    }
+                    
+                    return tableValue[keyValue];
+                case 'AssignmentExpression':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.name} is not yet fully defined`);
+                        };
                         
-                        if (patternValue === true) continue;
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
                         
-                        if (value !== patternValue) {
-                            matches = false;
-                            break;
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = localEvalNodeWithScope(node.value, scope);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.name] = actualFunction;
+                        return;
+                    }
+                    
+                    globalScope[node.name] = localEvalNodeWithScope(node.value, scope);
+                    return;
+                case 'Identifier':
+                    // First check local scope, then global scope
+                    if (scope && scope.hasOwnProperty(node.value)) {
+                        return scope[node.value];
+                    }
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined && node.value) {
+                        return node.value;
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.params.length; i++) {
+                                nestedScope[node.params[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
                         }
+                    };
+                case 'FunctionDefinition':
+                    // Create a function from the function definition
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.parameters.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.parameters.length; i++) {
+                                nestedScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let localFunc;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        localFunc = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        localFunc = globalScope[node.name.value];
+                    } else {
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        localFunc = localEvalNodeWithScope(node.name, scope);
+                    }
+                    
+                    if (localFunc instanceof Function) {
+                        let args = node.args.map(arg => localEvalNodeWithScope(arg, scope));
+                        return localFunc(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(val => localEvalNodeWithScope(val, scope)) 
+                        : [localEvalNodeWithScope(node.value, scope)];
+                    
+                    if (process.env.DEBUG) {
+                        console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: whenValues =`, whenValues);
                     }
                     
-                    if (matches) {
-                        const results = caseItem.result.map(res => localEvalNodeWithScope(res, scope));
-                        if (results.length === 1) {
-                            return results[0];
+                    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);
+                        }
+                        
+                        // Check if patterns match the values
+                        let matches = true;
+                        if (whenValues.length !== patterns.length) {
+                            matches = false;
+                        } else {
+                            for (let i = 0; i < whenValues.length; i++) {
+                                const value = whenValues[i];
+                                const pattern = patterns[i];
+                                
+                                if (process.env.DEBUG) {
+                                    console.log(`[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`);
+                                    }
+                                    continue;
+                                } 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;
+                                    for (const key in pattern) {
+                                        if (pattern.hasOwnProperty(key) && (!value.hasOwnProperty(key) || value[key] !== pattern[key])) {
+                                            tableMatches = false;
+                                            break;
+                                        }
+                                    }
+                                    if (!tableMatches) {
+                                        matches = false;
+                                        if (process.env.DEBUG) {
+                                            console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern does not match`);
+                                        }
+                                        break;
+                                    } else {
+                                        if (process.env.DEBUG) {
+                                            console.log(`[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`);
+                                    }
+                                    break;
+                                } else {
+                                    if (process.env.DEBUG) {
+                                        console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern matches`);
+                                    }
+                                }
+                            }
+                        }
+                        
+                        if (process.env.DEBUG) {
+                            console.log(`[DEBUG] localEvalNodeWithScope WhenExpression: case matches = ${matches}`);
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(res => localEvalNodeWithScope(res, scope));
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
                         }
-                        return results.join(' ');
                     }
-                }
-                throw new Error('No matching pattern found');
-            case 'WildcardPattern':
-                return true;
-            case 'IOInExpression':
-                const readline = require('readline');
-                const rl = readline.createInterface({
-                    input: process.stdin,
-                    output: process.stdout
-                });
-                
-                return new Promise((resolve) => {
-                    rl.question('', (input) => {
-                        rl.close();
-                        const num = parseInt(input);
-                        resolve(isNaN(num) ? input : num);
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const readline = require('readline');
+                    const rl = readline.createInterface({
+                        input: process.stdin,
+                        output: process.stdout
                     });
-                });
-            case 'IOOutExpression':
-                const localOutputValue = localEvalNodeWithScope(node.value, scope);
-                console.log(localOutputValue);
-                return localOutputValue;
-            case 'IOAssertExpression':
-                const localAssertionValue = localEvalNodeWithScope(node.value, scope);
-                if (!localAssertionValue) {
-                    throw new Error('Assertion failed');
-                }
-                return localAssertionValue;
-            case 'FunctionReference':
-                const localFunctionValue = globalScope[node.name];
-                if (localFunctionValue === undefined) {
-                    throw new Error(`Function ${node.name} is not defined`);
-                }
-                if (typeof localFunctionValue !== 'function') {
-                    throw new Error(`${node.name} is not a function`);
-                }
-                return localFunctionValue;
-            default:
-                throw new Error(`Unknown node type: ${node.type}`);
+                    
+                    return new Promise((resolve) => {
+                        rl.question('', (input) => {
+                            rl.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const localOutputValue = localEvalNodeWithScope(node.value, scope);
+                    console.log(localOutputValue);
+                    ioOperationsPerformed = true;
+                    return localOutputValue;
+                case 'IOAssertExpression':
+                    const localAssertionValue = localEvalNodeWithScope(node.value, scope);
+                    if (!localAssertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return localAssertionValue;
+                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');
+                        }
+                        return environment.getCurrentState();
+                    } else {
+                        if (process.env.DEBUG) {
+                            console.log('[DEBUG] ..listen called - returning placeholder state');
+                        }
+                        return { status: 'placeholder', message: 'State not available in standalone mode' };
+                    }
+                case 'IOEmitExpression':
+                    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');
+                        }
+                        environment.emitValue(localEmitValue);
+                    } else {
+                        console.log('[EMIT]', localEmitValue);
+                    }
+                    ioOperationsPerformed = true;
+                    return localEmitValue;
+                case 'FunctionReference':
+                    const localFunctionValue = globalScope[node.name];
+                    if (localFunctionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof localFunctionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return localFunctionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return localEvalNodeWithScope(node.body, scope);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
         }
     };
 
+    /**
+     * Evaluates AST nodes in the global scope (internal recursion helper).
+     * 
+     * @param {Object} node - AST node to evaluate
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Internal helper function for recursive evaluation that
+     * always uses the global scope. This function is used to avoid circular
+     * dependencies and infinite recursion when evaluating nested expressions
+     * that need access to the global scope.
+     * 
+     * This function duplicates the logic of evalNode but without the scope
+     * parameter, ensuring that all variable lookups go through the global scope.
+     * It's primarily used for evaluating function bodies and other expressions
+     * that need to be isolated from local scope contexts.
+     * 
+     * The function also implements the forward declaration pattern for recursive
+     * functions, maintaining consistency with the other evaluation functions.
+     * 
+     * This function is essential for preventing scope pollution when evaluating
+     * nested expressions that should not inherit local scope variables, ensuring
+     * that global functions and variables are always accessible regardless of
+     * the current evaluation context.
+     */
     const localEvalNode = (node) => {
-        if (!node) {
-            return undefined;
-        }
-        switch (node.type) {
-            case 'NumberLiteral':
-                return parseFloat(node.value);
-            case 'StringLiteral':
-                return node.value;
-            case 'BooleanLiteral':
-                return node.value;
-            case 'PlusExpression':
-                return localEvalNode(node.left) + localEvalNode(node.right);
-            case 'MinusExpression':
-                return localEvalNode(node.left) - localEvalNode(node.right);
-            case 'MultiplyExpression':
-                return localEvalNode(node.left) * localEvalNode(node.right);
-            case 'DivideExpression':
-                const divisor = localEvalNode(node.right);
-                if (divisor === 0) {
-                    throw new Error('Division by zero');
-                }
-                return localEvalNode(node.left) / localEvalNode(node.right);
-            case 'ModuloExpression':
-                return localEvalNode(node.left) % localEvalNode(node.right);
-            case 'PowerExpression':
-                return Math.pow(localEvalNode(node.left), localEvalNode(node.right));
-            case 'EqualsExpression':
-                return localEvalNode(node.left) === localEvalNode(node.right);
-            case 'LessThanExpression':
-                return localEvalNode(node.left) < localEvalNode(node.right);
-            case 'GreaterThanExpression':
-                return localEvalNode(node.left) > localEvalNode(node.right);
-            case 'LessEqualExpression':
-                return localEvalNode(node.left) <= localEvalNode(node.right);
-            case 'GreaterEqualExpression':
-                return localEvalNode(node.left) >= localEvalNode(node.right);
-            case 'NotEqualExpression':
-                return localEvalNode(node.left) !== localEvalNode(node.right);
-            case 'AndExpression':
-                return !!(localEvalNode(node.left) && localEvalNode(node.right));
-            case 'OrExpression':
-                return !!(localEvalNode(node.left) || localEvalNode(node.right));
-            case 'XorExpression':
-                const leftVal = localEvalNode(node.left);
-                const rightVal = localEvalNode(node.right);
-                return !!((leftVal && !rightVal) || (!leftVal && rightVal));
-            case 'NotExpression':
-                return !localEvalNode(node.operand);
-            case 'UnaryMinusExpression':
-                return -localEvalNode(node.operand);
-            case 'TableLiteral':
-                const table = {};
-                let arrayIndex = 1;
-                
-                for (const entry of node.entries) {
-                    if (entry.key === null) {
-                        // Array-like entry: {1, 2, 3}
-                        table[arrayIndex] = localEvalNode(entry.value);
-                        arrayIndex++;
-                    } else {
-                        // Key-value entry: {name: "Alice", age: 30}
-                        let key;
-                        if (entry.key.type === 'Identifier') {
-                            // Convert identifier keys to strings
-                            key = entry.key.value;
+        callStackTracker.push('localEvalNode', node?.type || 'unknown');
+        
+        try {
+            if (!node) {
+                return undefined;
+            }
+            switch (node.type) {
+                case 'NumberLiteral':
+                    return parseFloat(node.value);
+                case 'StringLiteral':
+                    return node.value;
+                case 'BooleanLiteral':
+                    return node.value;
+                case 'PlusExpression':
+                    return localEvalNode(node.left) + localEvalNode(node.right);
+                case 'MinusExpression':
+                    return localEvalNode(node.left) - localEvalNode(node.right);
+                case 'MultiplyExpression':
+                    return localEvalNode(node.left) * localEvalNode(node.right);
+                case 'DivideExpression':
+                    const divisor = localEvalNode(node.right);
+                    if (divisor === 0) {
+                        throw new Error('Division by zero');
+                    }
+                    return localEvalNode(node.left) / localEvalNode(node.right);
+                case 'ModuloExpression':
+                    return localEvalNode(node.left) % localEvalNode(node.right);
+                case 'PowerExpression':
+                    return Math.pow(localEvalNode(node.left), localEvalNode(node.right));
+                case 'EqualsExpression':
+                    return localEvalNode(node.left) === localEvalNode(node.right);
+                case 'LessThanExpression':
+                    return localEvalNode(node.left) < localEvalNode(node.right);
+                case 'GreaterThanExpression':
+                    return localEvalNode(node.left) > localEvalNode(node.right);
+                case 'LessEqualExpression':
+                    return localEvalNode(node.left) <= localEvalNode(node.right);
+                case 'GreaterEqualExpression':
+                    return localEvalNode(node.left) >= localEvalNode(node.right);
+                case 'NotEqualExpression':
+                    return localEvalNode(node.left) !== localEvalNode(node.right);
+                case 'AndExpression':
+                    return !!(localEvalNode(node.left) && localEvalNode(node.right));
+                case 'OrExpression':
+                    return !!(localEvalNode(node.left) || localEvalNode(node.right));
+                case 'XorExpression':
+                    const leftVal = localEvalNode(node.left);
+                    const rightVal = localEvalNode(node.right);
+                    return !!((leftVal && !rightVal) || (!leftVal && rightVal));
+                case 'NotExpression':
+                    return !localEvalNode(node.operand);
+                case 'UnaryMinusExpression':
+                    return -localEvalNode(node.operand);
+                case 'TableLiteral':
+                    const table = {};
+                    let arrayIndex = 1;
+                    
+                    for (const entry of node.entries) {
+                        if (entry.key === null) {
+                            // Array-like entry: {1, 2, 3}
+                            table[arrayIndex] = localEvalNode(entry.value);
+                            arrayIndex++;
                         } else {
-                            // For other key types (numbers, strings), evaluate normally
-                            key = localEvalNode(entry.key);
+                            // Key-value entry: {name: "Alice", age: 30}
+                            let key;
+                            if (entry.key.type === 'Identifier') {
+                                // Convert identifier keys to strings
+                                key = entry.key.value;
+                            } else {
+                                // For other key types (numbers, strings), evaluate normally
+                                key = localEvalNode(entry.key);
+                            }
+                            const value = localEvalNode(entry.value);
+                            table[key] = value;
                         }
-                        const value = localEvalNode(entry.value);
-                        table[key] = value;
                     }
-                }
-                
-                return table;
-            case 'TableAccess':
-                const tableValue = localEvalNode(node.table);
-                let keyValue;
-                
-                // Handle different key types
-                if (node.key.type === 'Identifier') {
-                    // For dot notation, use the identifier name as the key
-                    keyValue = node.key.value;
-                } else {
-                    // For bracket notation, evaluate the key expression
-                    keyValue = localEvalNode(node.key);
-                }
-                
-                if (typeof tableValue !== 'object' || tableValue === null) {
-                    throw new Error('Cannot access property of non-table value');
-                }
-                
-                if (tableValue[keyValue] === undefined) {
-                    throw new Error(`Key '${keyValue}' not found in table`);
-                }
-                
-                return tableValue[keyValue];
-            case 'AssignmentExpression':
-                if (globalScope.hasOwnProperty(node.name)) {
-                    throw new Error(`Cannot reassign immutable variable: ${node.name}`);
-                }
-                globalScope[node.name] = localEvalNode(node.value);
-                return;
-            case 'Identifier':
-                const identifierValue = globalScope[node.value];
-                if (identifierValue === undefined && node.value) {
-                    return node.value;
-                }
-                return identifierValue;
-            case 'FunctionDeclaration':
-                // For anonymous functions, the name comes from the assignment
-                // The function itself doesn't have a name, so we just return
-                // The assignment will handle storing it in the global scope
-                return function(...args) {
-                    let nestedScope = Object.create(globalScope);
-                    for (let i = 0; i < node.params.length; i++) {
-                        nestedScope[node.params[i]] = args[i];
+                    
+                    return table;
+                case 'TableAccess':
+                    const tableValue = localEvalNode(node.table);
+                    let keyValue;
+                    
+                    // Handle different key types
+                    if (node.key.type === 'Identifier') {
+                        // For dot notation, use the identifier name as the key
+                        keyValue = node.key.value;
+                    } else {
+                        // For bracket notation, evaluate the key expression
+                        keyValue = localEvalNode(node.key);
+                    }
+                    
+                    if (typeof tableValue !== 'object' || tableValue === null) {
+                        throw new Error('Cannot access property of non-table value');
                     }
-                    return localEvalNodeWithScope(node.body, nestedScope);
-                };
-            case 'FunctionCall':
-                let localFunc;
-                if (typeof node.name === 'string') {
-                    // Regular function call with string name
-                    localFunc = globalScope[node.name];
-                } else if (node.name.type === 'Identifier') {
-                    // Function call with identifier
-                    localFunc = globalScope[node.name.value];
-                } else if (node.name.type === 'TableAccess') {
-                    // Function call from table access (e.g., math.add)
-                    localFunc = localEvalNode(node.name);
-                } else {
-                    throw new Error('Invalid function name in function call');
-                }
-                
-                if (localFunc instanceof Function) {
-                    let args = node.args.map(localEvalNode);
-                    return localFunc(...args);
-                }
-                throw new Error(`Function is not defined or is not callable`);
-            case 'CaseExpression':
-                const values = node.value.map(localEvalNode);
-                
-                for (const caseItem of node.cases) {
-                    const pattern = caseItem.pattern.map(localEvalNode);
                     
-                    let matches = true;
-                    for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
-                        const value = values[i];
-                        const patternValue = pattern[i];
+                    if (tableValue[keyValue] === undefined) {
+                        throw new Error(`Key '${keyValue}' not found in table`);
+                    }
+                    
+                    return tableValue[keyValue];
+                case 'AssignmentExpression':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.name} is not yet fully defined`);
+                        };
                         
-                        if (patternValue === true) continue;
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
                         
-                        if (value !== patternValue) {
-                            matches = false;
-                            break;
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = localEvalNode(node.value);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.name] = actualFunction;
+                        return;
+                    }
+                    
+                    globalScope[node.name] = localEvalNode(node.value);
+                    return;
+                case 'Identifier':
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined && node.value) {
+                        return node.value;
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.params.length; i++) {
+                                nestedScope[node.params[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionDefinition':
+                    // Create a function from the function definition
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.parameters.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.parameters.length; i++) {
+                                nestedScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
                         }
+                    };
+                case 'FunctionCall':
+                    let localFunc;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        localFunc = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        localFunc = globalScope[node.name.value];
+                    } else {
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        localFunc = localEvalNode(node.name);
+                    }
+                    
+                    if (localFunc instanceof Function) {
+                        let args = node.args.map(localEvalNode);
+                        return localFunc(...args);
                     }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(localEvalNode) 
+                        : [localEvalNode(node.value)];
                     
-                    if (matches) {
-                        const results = caseItem.result.map(localEvalNode);
-                        if (results.length === 1) {
-                            return results[0];
+                    for (const caseItem of node.cases) {
+                        // Handle both single patterns and arrays of patterns
+                        const patterns = caseItem.pattern.map(localEvalNode);
+                        
+                        // Check if patterns match the values
+                        let matches = true;
+                        if (whenValues.length !== patterns.length) {
+                            matches = false;
+                        } else {
+                            for (let i = 0; i < whenValues.length; i++) {
+                                const value = whenValues[i];
+                                const pattern = patterns[i];
+                                
+                                if (pattern === true) { // Wildcard pattern
+                                    // Wildcard always matches
+                                    continue;
+                                } 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;
+                                    for (const key in pattern) {
+                                        if (pattern.hasOwnProperty(key) && (!value.hasOwnProperty(key) || value[key] !== pattern[key])) {
+                                            tableMatches = false;
+                                            break;
+                                        }
+                                    }
+                                    if (!tableMatches) {
+                                        matches = false;
+                                        break;
+                                    }
+                                } else if (value !== pattern) {
+                                    matches = false;
+                                    break;
+                                }
+                            }
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(localEvalNode);
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
                         }
-                        return results.join(' ');
                     }
-                }
-                throw new Error('No matching pattern found');
-            case 'WildcardPattern':
-                return true;
-            case 'IOInExpression':
-                const readline = require('readline');
-                const rl = readline.createInterface({
-                    input: process.stdin,
-                    output: process.stdout
-                });
-                
-                return new Promise((resolve) => {
-                    rl.question('', (input) => {
-                        rl.close();
-                        const num = parseInt(input);
-                        resolve(isNaN(num) ? input : num);
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const readline = require('readline');
+                    const rl = readline.createInterface({
+                        input: process.stdin,
+                        output: process.stdout
                     });
-                });
-            case 'IOOutExpression':
-                const localOutputValue = localEvalNode(node.value);
-                console.log(localOutputValue);
-                return localOutputValue;
-            case 'IOAssertExpression':
-                const localAssertionValue = localEvalNode(node.value);
-                if (!localAssertionValue) {
-                    throw new Error('Assertion failed');
-                }
-                return localAssertionValue;
-            case 'FunctionReference':
-                const localFunctionValue = globalScope[node.name];
-                if (localFunctionValue === undefined) {
-                    throw new Error(`Function ${node.name} is not defined`);
-                }
-                if (typeof localFunctionValue !== 'function') {
-                    throw new Error(`${node.name} is not a function`);
-                }
-                return localFunctionValue;
-            default:
-                throw new Error(`Unknown node type: ${node.type}`);
+                    
+                    return new Promise((resolve) => {
+                        rl.question('', (input) => {
+                            rl.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const localOutputValue = localEvalNode(node.value);
+                    console.log(localOutputValue);
+                    ioOperationsPerformed = true;
+                    return localOutputValue;
+                case 'IOAssertExpression':
+                    const localAssertionValue = localEvalNode(node.value);
+                    if (!localAssertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return localAssertionValue;
+                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');
+                        }
+                        return environment.getCurrentState();
+                    } else {
+                        if (process.env.DEBUG) {
+                            console.log('[DEBUG] ..listen called - returning placeholder state');
+                        }
+                        return { status: 'placeholder', message: 'State not available in standalone mode' };
+                    }
+                case 'IOEmitExpression':
+                    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');
+                        }
+                        environment.emitValue(localEmitValue);
+                    } else {
+                        console.log('[EMIT]', localEmitValue);
+                    }
+                    ioOperationsPerformed = true;
+                    return localEmitValue;
+                case 'FunctionReference':
+                    const localFunctionValue = globalScope[node.name];
+                    if (localFunctionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof localFunctionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return localFunctionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return localEvalNode(node.body);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
         }
     };
 
@@ -1880,17 +2463,54 @@ function interpreter(ast) {
     
     if (lastResult instanceof Promise) {
         return lastResult.then(result => {
-            return result;
+            return { result: globalScope, ioOperationsPerformed };
         });
     }
     
-    return lastResult;
+    return { result: globalScope, ioOperationsPerformed };
 }
 
 /**
- * Debug logging and error reporting.
+ * Run script with environment support for harness integration
  * 
- * Why: Debug functions are gated by the DEBUG environment variable, allowing for verbose output during development and silent operation in production. This approach makes it easy to trace execution and diagnose issues without cluttering normal output.
+ * @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
+ */
+function run(scriptContent, initialState = {}, environment = null) {
+    // Parse the script
+    const tokens = lexer(scriptContent);
+    const ast = parser(tokens);
+    
+    // Run the interpreter with environment and initial state
+    const result = interpreter(ast, environment, initialState);
+    
+    // Return the result
+    return result.result;
+}
+
+/**
+ * Debug logging utility function.
+ * 
+ * @param {string} message - Debug message to log
+ * @param {*} [data=null] - Optional data to log with the message
+ * 
+ * @description Logs debug messages to console when DEBUG environment variable is set.
+ * Provides verbose output during development while remaining silent in production.
+ * 
+ * Debug functions are gated by the DEBUG environment variable, allowing for 
+ * verbose output during development and silent operation in production. This 
+ * approach makes it easy to trace execution and diagnose issues without 
+ * cluttering normal output.
+ * 
+ * This function is essential for debugging the combinator-based architecture,
+ * allowing developers to trace how operators are translated to function calls
+ * 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
+ * expressions and function applications.
  */
 function debugLog(message, data = null) {
     if (process.env.DEBUG) {
@@ -1902,9 +2522,22 @@ function debugLog(message, data = null) {
 }
 
 /**
- * Debug logging and error reporting.
+ * Debug error logging utility function.
+ * 
+ * @param {string} message - Debug error message to log
+ * @param {Error} [error=null] - Optional error object to log
+ * 
+ * @description Logs debug error messages to console when DEBUG environment variable is set.
+ * Provides verbose error output during development while remaining silent in production.
  * 
- * Why: Debug functions are gated by the DEBUG environment variable, allowing for verbose output during development and silent operation in production. This approach makes it easy to trace execution and diagnose issues without cluttering normal output.
+ * Debug functions are gated by the DEBUG environment variable, allowing for 
+ * verbose output during development and silent operation in production. This 
+ * approach makes it easy to trace execution and diagnose issues without 
+ * cluttering normal output.
+ * 
+ * This function is particularly useful for debugging parsing and evaluation errors,
+ * providing detailed context about where and why errors occur in the language
+ * execution pipeline.
  */
 function debugError(message, error = null) {
     if (process.env.DEBUG) {
@@ -1916,14 +2549,177 @@ function debugError(message, error = null) {
 }
 
 /**
- * Reads a file, tokenizes, parses, and interprets it.
+ * Call stack tracking for debugging recursion issues.
+ * 
+ * @description Tracks function calls to help identify infinite recursion
+ * and deep call stacks that cause stack overflow errors. This is essential
+ * for debugging the interpreter's recursive evaluation of AST nodes.
+ * 
+ * The tracker maintains a stack of function calls with timestamps and context
+ * information, counts function calls to identify hot paths, and detects
+ * 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
+ * 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.
+ * 
+ * The tracker provides detailed statistics about function call patterns,
+ * helping developers understand the execution characteristics of their code
+ * and identify potential performance bottlenecks in the combinator evaluation.
+ */
+const callStackTracker = {
+    stack: [],
+    maxDepth: 0,
+    callCounts: new Map(),
+    
+    /**
+     * Push a function call onto the stack
+     * @param {string} functionName - Name of the function being called
+     * @param {string} context - Context where the call is happening
+     */
+    push: function(functionName, context = '') {
+        const callInfo = { functionName, context, timestamp: Date.now() };
+        this.stack.push(callInfo);
+        
+        // Track maximum depth
+        if (this.stack.length > this.maxDepth) {
+            this.maxDepth = this.stack.length;
+        }
+        
+        // Count function calls
+        const key = `${functionName}${context ? `:${context}` : ''}`;
+        this.callCounts.set(key, (this.callCounts.get(key) || 0) + 1);
+        
+        // Check for potential infinite recursion
+        if (this.stack.length > 1000) {
+            console.error('=== POTENTIAL INFINITE RECURSION DETECTED ===');
+            console.error('Call stack depth:', this.stack.length);
+            console.error('Function call counts:', Object.fromEntries(this.callCounts));
+            console.error('Recent call stack:');
+            this.stack.slice(-10).forEach((call, i) => {
+                console.error(`  ${this.stack.length - 10 + i}: ${call.functionName}${call.context ? ` (${call.context})` : ''}`);
+            });
+            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}`);
+        }
+    },
+    
+    /**
+     * Pop a function call from the stack
+     */
+    pop: function() {
+        return this.stack.pop();
+    },
+    
+    /**
+     * Get current stack depth
+     */
+    getDepth: function() {
+        return this.stack.length;
+    },
+    
+    /**
+     * Get call statistics
+     */
+    getStats: function() {
+        return {
+            currentDepth: this.stack.length,
+            maxDepth: this.maxDepth,
+            callCounts: Object.fromEntries(this.callCounts)
+        };
+    },
+    
+    /**
+     * Reset the tracker
+     */
+    reset: function() {
+        this.stack = [];
+        this.maxDepth = 0;
+        this.callCounts.clear();
+    }
+};
+
+/**
+ * Cross-platform file I/O utility
+ * 
+ * @param {string} filePath - Path to the file to read
+ * @returns {Promise<string>} File contents as a string
+ * @throws {Error} For file reading errors
  * 
- * Why: This function is the entry point for file execution, handling all stages of the language pipeline. Debug output is provided at each stage for transparency and troubleshooting.
+ * @description Handles file reading across different platforms (Node.js, Bun, browser)
+ * with appropriate fallbacks for each environment. This function is essential for
+ * the language's file execution model where scripts are loaded from .txt files.
+ * 
+ * The function prioritizes ES modules compatibility by using dynamic import,
+ * but falls back to require for older Node.js versions. Browser environments
+ * are not supported for file I/O operations.
+ * 
+ * This cross-platform approach ensures the language can run in various JavaScript
+ * environments while maintaining consistent behavior. The file reading capability
+ * enables the language to execute scripts from files, supporting the development
+ * workflow where tests and examples are stored as .txt files.
  */
-function executeFile(filePath) {
+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');
+    }
+    
+    // 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');
-        const input = fs.readFileSync(filePath, 'utf8');
+        return fs.readFileSync(filePath, 'utf8');
+    }
+}
+
+/**
+ * Reads a file, tokenizes, parses, and interprets it.
+ * 
+ * @param {string} filePath - Path to the file to execute
+ * @returns {Promise<*>} The result of executing the file
+ * @throws {Error} For file reading, parsing, or execution errors
+ * 
+ * @description Main entry point for file execution. Handles the complete language
+ * pipeline: file reading, lexical analysis, parsing, and interpretation.
+ * 
+ * This function orchestrates the entire language execution process:
+ * 1. Reads the source file using cross-platform I/O utilities
+ * 2. Tokenizes the source code using the lexer
+ * 3. Parses tokens into an AST using the combinator-based parser
+ * 4. Interprets the AST using the combinator-based interpreter
+ * 
+ * The function provides comprehensive error handling and debug output at each
+ * stage for transparency and troubleshooting. It also manages the call stack
+ * tracker to provide execution statistics and detect potential issues.
+ * 
+ * Supports both synchronous and asynchronous execution, with proper
+ * error handling and process exit codes. This function demonstrates the
+ * complete combinator-based architecture in action, showing how source code
+ * is transformed through each stage of the language pipeline.
+ * 
+ * The function enforces the .txt file extension requirement and provides
+ * detailed error reporting with call stack statistics to help developers
+ * understand execution behavior and diagnose issues.
+ */
+async function executeFile(filePath) {
+    try {
+        // Validate file extension
+        if (!filePath.endsWith('.txt') && !filePath.endsWith('.baba')) {
+            throw new Error('Only .txt and .baba files are supported');
+        }
+        
+        const input = await readFile(filePath);
         
         debugLog('Input:', input);
         
@@ -1937,42 +2733,95 @@ function executeFile(filePath) {
         
         if (result instanceof Promise) {
             result.then(finalResult => {
-                if (finalResult !== undefined) {
-                    console.log(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);
+                }
+                // Print call stack statistics only in debug mode
+                if (process.env.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));
                 }
             }).catch(error => {
                 console.error(`Error executing file: ${error.message}`);
+                // Print call stack statistics on error only in debug mode
+                if (process.env.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));
+                }
                 process.exit(1);
             });
         } else {
-            if (result !== undefined) {
-                console.log(result);
+            // Only output result if debug mode is enabled (no automatic final result output)
+            if (result.result !== undefined && process.env.DEBUG) {
+                console.log(result.result);
+            }
+            // Print call stack statistics only in debug mode
+            if (process.env.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));
             }
         }
     } catch (error) {
         console.error(`Error executing file: ${error.message}`);
+        // Print call stack statistics on error only in debug mode
+        if (process.env.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));
+        }
         process.exit(1);
     }
 }
 
 /**
- * CLI argument handling.
+ * CLI argument handling and program entry point.
  * 
- * Why: The language is designed for file execution only (no REPL), so the CLI enforces this usage and provides helpful error messages for incorrect invocation.
+ * @description Processes command line arguments and executes the specified file.
+ * Provides helpful error messages for incorrect usage.
+ * 
+ * The language is designed for file execution only (no REPL), so the CLI 
+ * enforces this usage and provides helpful error messages for incorrect invocation.
+ * The function validates that exactly one file path is provided and that the
+ * file has the correct .txt extension.
+ * 
+ * Exits with appropriate error codes for different failure scenarios.
  */
-const args = process.argv.slice(2);
+async function main() {
+    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);
+    } 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);
+    }
+}
+
+// Start the program only if this file is run directly
+if (process.argv[1] && process.argv[1].endsWith('lang.js')) {
+    main().catch(error => {
+        console.error('Fatal error:', error.message);
+        process.exit(1);
+    });
+}
+
+// Export functions for harness integration
+export { run, interpreter, lexer, parser };
+
 
-if (args.length === 0) {
-    console.error('Usage: node lang.js <file>');
-    console.error('  Provide a file path to execute');
-    process.exit(1);
-} else if (args.length === 1) {
-    // Execute the file
-    const filePath = args[0];
-    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);
-}
\ No newline at end of file