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.js2378
1 files changed, 804 insertions, 1574 deletions
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
index 0f827bf..9fa048f 100644
--- a/js/scripting-lang/lang.js
+++ b/js/scripting-lang/lang.js
@@ -1,19 +1,32 @@
+// 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.
  * 
  * @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.
  * 
- * @description Injects higher-order functions into the interpreter's global scope.
- * These functions provide functional programming utilities like map, compose, pipe, etc.
+ * 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)
  * 
- * @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.
+ * 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) {
     /**
@@ -22,6 +35,10 @@ function initializeStandardLibrary(scope) {
      * @param {*} x - Value 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. This enables functional
+     * programming patterns where data transformations are expressed as function
+     * applications rather than imperative operations.
      */
     scope.map = function(f, x) { 
         if (typeof f === 'function') {
@@ -32,25 +49,41 @@ function initializeStandardLibrary(scope) {
     };
     
     /**
-     * Compose: Compose two functions (f ∘ g)(x) = f(g(x))
-     * @param {Function} f - Outer function
-     * @param {Function} g - Inner function  
-     * @param {*} [x] - Optional argument to apply composed function to
-     * @returns {Function|*} Either a composed function or the result of applying it
-     * @throws {Error} When first two arguments are not functions
+     * Compose: Compose functions (f ∘ g)(x) = f(g(x))
+     * @param {Function} f - First 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. When used with partial
+     * application, it enables currying patterns where functions can be built
+     * incrementally. This supports the 'via' operator in the language syntax
+     * and enables powerful function composition chains.
      */
-    scope.compose = function(f, g, x) { 
-        if (typeof f === 'function' && typeof g === 'function') {
-            if (arguments.length === 3) {
-                return f(g(x));
-            } else {
+    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));
+        };
     };
     
     /**
@@ -75,6 +108,12 @@ function initializeStandardLibrary(scope) {
      * @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.
      */
     scope.apply = function(f, x) { 
         if (typeof f === 'function') {
@@ -87,23 +126,39 @@ function initializeStandardLibrary(scope) {
     /**
      * Pipe: Compose functions in left-to-right order (opposite of compose)
      * @param {Function} f - First function
-     * @param {Function} g - Second function
-     * @param {*} [x] - Optional argument to apply piped function to
-     * @returns {Function|*} Either a piped function or the result of applying it
-     * @throws {Error} When first two arguments are not functions
+     * @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. Like compose, it supports partial application
+     * for currying patterns. This enables functional programming patterns where
+     * data flows through a series of transformations in a natural reading order.
      */
-    scope.pipe = function(f, g, x) { 
-        if (typeof f === 'function' && typeof g === 'function') {
-            if (arguments.length === 3) {
-                return g(f(x));
-            } else {
+    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));
+        };
     };
     
     /**
@@ -152,1478 +207,277 @@ function initializeStandardLibrary(scope) {
             throw new Error('fold: first argument must be a function');
         }
     };
-}
-
-/**
- * TokenType enumeration for all supported token types.
- * 
- * @type {Object.<string, string>}
- * 
- * @description A flat object mapping token names to their string representations.
- * This approach allows 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.
- * 
- * @param {string} input - Source code to tokenize
- * @returns {Array.<Object>} Array of token objects with type and value properties
- * @throws {Error} For unterminated strings or unexpected characters
- * 
- * @description Performs lexical analysis by converting source code into a stream of tokens.
- * Handles whitespace, nested comments, numbers (integers and decimals), strings, 
- * identifiers/keywords, and both single- and multi-character operators.
- * 
- * @how Uses a single pass with a while loop and manual character inspection. 
- * Each character is examined to determine the appropriate token type, with 
- * special handling for multi-character tokens and nested constructs.
- * 
- * @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.
- * 
- * @note 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;
-        }
-        
-        // Handle nested comments: /* ... */ with support for /* /* ... */ */
-        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++;
-                }
-            }
-            continue;
-        }
-        
-        // Parse numbers (integers and decimals)
-        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;
-        }
-        
-        // Parse string literals
-        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;
-        }
-        
-        // Parse 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;
-        }
-        
-        // Parse 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 '..':
-                    // Parse IO operations: ..in, ..out, ..assert
-                    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;
-            }
-        }
-        
-        // Parse 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}`);
-        }
-        
-        current++;
-    }
+    // ===== ARITHMETIC COMBINATORS =====
     
-    return tokens;
-}
-
-/**
- * Parser: Converts tokens to an Abstract Syntax Tree (AST).
- * 
- * @param {Array.<Object>} tokens - Array of tokens from the lexer
- * @returns {Object} Abstract Syntax Tree with program body
- * @throws {Error} For parsing errors like unexpected tokens or missing delimiters
- * 
- * @description Implements a recursive descent parser that builds an AST from tokens.
- * Handles all language constructs including expressions, statements, function 
- * definitions, case expressions, table literals, and IO operations.
- * 
- * @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.
- * 
- * @note The parser 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;
+    /**
+     * Add: Add two numbers
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Sum of x and y
+     */
+    scope.add = function(x, y) {
+        return x + y;
+    };
     
-    // Reset call stack tracker for parser
-    callStackTracker.reset();
+    /**
+     * 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;
+    };
     
-    // Define all parsing functions outside of walk to avoid circular dependencies
+    /**
+     * Multiply: Multiply two numbers
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Product of x and y
+     */
+    scope.multiply = function(x, y) {
+        return x * y;
+    };
     
-    function parseLogicalExpression() {
-        callStackTracker.push('parseLogicalExpression', '');
-        
-        try {
-            /**
-             * Parses logical expressions with lowest precedence.
-             * 
-             * @returns {Object} AST node representing the logical expression
-             * 
-             * @description Parses logical operators (and, or, xor) with proper
-             * precedence handling and left associativity.
-             * 
-             * @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;
-        } finally {
-            callStackTracker.pop();
+    /**
+     * 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;
+    };
     
-    function parseExpression() {
-        callStackTracker.push('parseExpression', '');
-        
-        try {
-            /**
-             * Parses expressions with left-associative binary operators.
-             * 
-             * @returns {Object} AST node representing the expression
-             * 
-             * @description Parses addition, subtraction, and comparison operators
-             * with proper precedence and associativity.
-             * 
-             * @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.
-             * 
-             * @note Special case handling for unary minus after function references
-             * to distinguish from binary minus 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;
-        } finally {
-            callStackTracker.pop();
-        }
-    }
+    /**
+     * 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;
+    };
     
-    function parseTerm() {
-        callStackTracker.push('parseTerm', '');
-        
-        try {
-            /**
-             * Parses multiplication, division, and modulo operations.
-             * 
-             * @returns {Object} AST node representing the term
-             * 
-             * @description Parses multiplicative operators with higher precedence
-             * than addition/subtraction.
-             * 
-             * @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;
-        } finally {
-            callStackTracker.pop();
-        }
-    }
+    /**
+     * 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);
+    };
     
-    function parseFactor() {
-        callStackTracker.push('parseFactor', '');
-        
-        try {
-            /**
-             * Parses exponentiation and primary expressions.
-             * 
-             * @returns {Object} AST node representing the factor
-             * 
-             * @description Parses exponentiation with right associativity and
-             * highest precedence among arithmetic operators.
-             * 
-             * @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;
-        } finally {
-            callStackTracker.pop();
-        }
-    }
+    /**
+     * Negate: Negate a number
+     * @param {number} x - Number to negate
+     * @returns {number} Negated value of x
+     */
+    scope.negate = function(x) {
+        return -x;
+    };
     
-    function parsePrimary() {
-        callStackTracker.push('parsePrimary', '');
-        
-        try {
-            /**
-             * Parses literals, identifiers, function definitions, assignments, 
-             * table literals, and parenthesized expressions.
-             * 
-             * @returns {Object} AST node representing the primary expression
-             * @throws {Error} For parsing errors like unexpected tokens
-             * 
-             * @description The core parsing function that handles all atomic and 
-             * context-sensitive constructs in the language.
-             * 
-             * @why This function is the core of the recursive descent parser, handling 
-             * all atomic and context-sensitive constructs. Special care is taken to 
-             * avoid circular dependencies by not calling higher-level parsing functions.
-             */
-            
-            const token = tokens[current];
-            if (!token) {
-                throw new Error('Unexpected end of input');
-            }
-            
-            // Parse unary operators
-            if (token.type === TokenType.NOT) {
-                current++;
-                const operand = parsePrimary();
-                return { type: 'NotExpression', operand };
-            }
-            
-            if (token.type === TokenType.MINUS) {
-                current++;
-                const operand = parsePrimary();
-                return { type: 'UnaryMinusExpression', operand };
-            }
-            
-            // Parse literals
-            if (token.type === TokenType.NUMBER) {
-                current++;
-                return { type: 'NumberLiteral', value: token.value };
-            } else if (token.type === TokenType.STRING) {
-                current++;
-                return { type: 'StringLiteral', value: token.value };
-            } else if (token.type === TokenType.TRUE) {
-                current++;
-                return { type: 'BooleanLiteral', value: true };
-            } else if (token.type === TokenType.FALSE) {
-                current++;
-                return { type: 'BooleanLiteral', value: false };
-            } else if (token.type === TokenType.NULL) {
-                current++;
-                return { type: 'NullLiteral' };
-            }
-            
-            // Parse identifiers
-            if (token.type === TokenType.IDENTIFIER) {
-                current++;
-                const identifier = { type: 'Identifier', value: token.value };
-                
-                // Check for function calls
-                if (current < tokens.length && tokens[current].type === TokenType.LEFT_PAREN) {
-                    return parseFunctionCall(identifier.value);
-                }
-                
-                // Check for table access
-                if (current < tokens.length && tokens[current].type === TokenType.DOT) {
-                    return parseChainedDotAccess(identifier);
-                }
-                
-                // Check for table access with brackets
-                if (current < tokens.length && tokens[current].type === TokenType.LEFT_BRACKET) {
-                    return parseChainedTableAccess(identifier);
-                }
-                
-                return identifier;
-            }
-            
-            // Parse parenthesized expressions
-            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');
-                }
-            }
-            
-            // Parse table literals
-            if (token.type === TokenType.LEFT_BRACE) {
-                current++; // Skip '{'
-                const properties = [];
-                
-                while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_BRACE) {
-                    if (tokens[current].type === TokenType.IDENTIFIER) {
-                        const key = tokens[current].value;
-                        current++;
-                        
-                        if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
-                            current++; // Skip ':'
-                            const value = parseLogicalExpression();
-                            properties.push({ key, value });
-                        } else {
-                            throw new Error('Expected ":" after property name in table literal');
-                        }
-                    } else {
-                        throw new Error('Expected property name in table literal');
-                    }
-                    
-                    // Skip comma if present
-                    if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
-                        current++;
-                    }
-                }
-                
-                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_BRACE) {
-                    current++; // Skip '}'
-                    return { type: 'TableLiteral', properties };
-                } else {
-                    throw new Error('Expected closing brace in table literal');
-                }
-            }
-            
-            // Parse function definitions
-            if (token.type === TokenType.FUNCTION) {
-                current++; // Skip 'function'
-                
-                if (current >= tokens.length || tokens[current].type !== TokenType.LEFT_PAREN) {
-                    throw new Error('Expected "(" after "function"');
-                }
-                current++; // Skip '('
-                
-                const parameters = [];
-                while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_PAREN) {
-                    if (tokens[current].type === TokenType.IDENTIFIER) {
-                        parameters.push(tokens[current].value);
-                        current++;
-                    } else {
-                        throw new Error('Expected parameter name in function definition');
-                    }
-                    
-                    // Skip comma if present
-                    if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
-                        current++;
-                    }
-                }
-                
-                if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
-                    throw new Error('Expected ")" after function parameters');
-                }
-                current++; // Skip ')'
-                
-                if (current >= tokens.length || tokens[current].type !== TokenType.ASSIGNMENT) {
-                    throw new Error('Expected ":" after function parameters');
-                }
-                current++; // Skip ':'
-                
-                // Parse the function body (which could be a case expression or other expression)
-                const functionBody = parseLogicalExpression();
-                
-                return {
-                    type: 'FunctionDefinition',
-                    parameters,
-                    body: functionBody
-                };
-            }
-            
-            // Parse assignments
-            if (token.type === TokenType.IDENTIFIER) {
-                const identifier = token.value;
-                current++;
-                
-                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
-                    current++; // Skip ':'
-                    
-                    // Check if this is a function definition
-                    if (current < tokens.length && tokens[current].type === TokenType.FUNCTION) {
-                        current++; // Skip 'function'
-                        
-                        if (current >= tokens.length || tokens[current].type !== TokenType.LEFT_PAREN) {
-                            throw new Error('Expected "(" after "function"');
-                        }
-                        current++; // Skip '('
-                        
-                        const parameters = [];
-                        while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_PAREN) {
-                            if (tokens[current].type === TokenType.IDENTIFIER) {
-                                parameters.push(tokens[current].value);
-                                current++;
-                            } else {
-                                throw new Error('Expected parameter name in function definition');
-                            }
-                            
-                            // Skip comma if present
-                            if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
-                                current++;
-                            }
-                        }
-                        
-                        if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
-                            throw new Error('Expected ")" after function parameters');
-                        }
-                        current++; // Skip ')'
-                        
-                        if (current >= tokens.length || tokens[current].type !== TokenType.ASSIGNMENT) {
-                            throw new Error('Expected ":" after function parameters');
-                        }
-                        current++; // Skip ':'
-                        
-                        // Parse the function body (which could be a case expression or other expression)
-                        const functionBody = parseLogicalExpression();
-                        
-                        return {
-                            type: 'Assignment',
-                            identifier,
-                            value: {
-                                type: 'FunctionDefinition',
-                                parameters,
-                                body: functionBody
-                            }
-                        };
-                    } else {
-                        // Regular assignment
-                        const value = parseLogicalExpression();
-                        
-                        return {
-                            type: 'Assignment',
-                            identifier,
-                            value
-                        };
-                    }
-                }
-            }
-            
-            // If we get here, we have an unexpected token
-            throw new Error(`Unexpected token in parsePrimary: ${token.type}`);
-        } finally {
-            callStackTracker.pop();
-        }
-    }
+    // ===== COMPARISON COMBINATORS =====
     
-    function walk() {
-        callStackTracker.push('walk', `position:${current}`);
-        
-        try {
-            function parseChainedDotAccess(tableExpr) {
-                callStackTracker.push('parseChainedDotAccess', '');
-                
-                try {
-                    /**
-                     * Handles chained dot access (e.g., table.key.subkey).
-                     * 
-                     * @param {Object} tableExpr - The table expression to chain access from
-                     * @returns {Object} AST node representing the chained access
-                     * @throws {Error} When expected identifier is missing after dot
-                     * 
-                     * @description Parses dot notation for table access, building a chain
-                     * of TableAccess nodes for nested property access.
-                     * 
-                     * @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');
-                        }
-                    }
-                    
-                    return result;
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-            function parseChainedTableAccess(tableExpr) {
-                callStackTracker.push('parseChainedTableAccess', '');
-                
-                try {
-                    /**
-                     * Handles chained bracket and dot access (e.g., table[0].key).
-                     * 
-                     * @param {Object} tableExpr - The table expression to chain access from
-                     * @returns {Object} AST node representing the chained access
-                     * @throws {Error} When expected closing bracket is missing
-                     * 
-                     * @description Parses both bracket and dot notation for table access,
-                     * supporting mixed access patterns like 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;
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-            function parseFunctionCall(functionName) {
-                callStackTracker.push('parseFunctionCall', '');
-                
-                try {
-                    /**
-                     * Parses function calls with arbitrary argument lists.
-                     * 
-                     * @param {Object|string} functionName - Function name or expression to call
-                     * @returns {Object} AST node representing the function call
-                     * 
-                     * @description Parses function calls by collecting arguments until a 
-                     * clear terminator is found, supporting both curried and regular calls.
-                     * 
-                     * @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.
-                     * 
-                     * @note Special handling for unary minus arguments to distinguish them
-                     * from binary minus operations.
-                     */
-                    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());
-                        }
-                    }
-                        
-                    return {
-                        type: 'FunctionCall',
-                        name: functionName,
-                        args: args
-                    };
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-            function parseLogicalExpression() {
-                callStackTracker.push('parseLogicalExpression', '');
-                
-                try {
-                    /**
-                     * Parses logical expressions with lowest precedence.
-                     * 
-                     * @returns {Object} AST node representing the logical expression
-                     * 
-                     * @description Parses logical operators (and, or, xor) with proper
-                     * precedence handling and left associativity.
-                     * 
-                     * @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;
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-            function parseExpression() {
-                callStackTracker.push('parseExpression', '');
-                
-                try {
-                    /**
-                     * Parses expressions with left-associative binary operators.
-                     * 
-                     * @returns {Object} AST node representing the expression
-                     * 
-                     * @description Parses addition, subtraction, and comparison operators
-                     * with proper precedence and associativity.
-                     * 
-                     * @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.
-                     * 
-                     * @note Special case handling for unary minus after function references
-                     * to distinguish from binary minus 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;
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-            function parseTerm() {
-                callStackTracker.push('parseTerm', '');
-                
-                try {
-                    /**
-                     * Parses multiplication, division, and modulo operations.
-                     * 
-                     * @returns {Object} AST node representing the term
-                     * 
-                     * @description Parses multiplicative operators with higher precedence
-                     * than addition/subtraction.
-                     * 
-                     * @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;
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-            function parseFactor() {
-                callStackTracker.push('parseFactor', '');
-                
-                try {
-                    /**
-                     * Parses exponentiation and primary expressions.
-                     * 
-                     * @returns {Object} AST node representing the factor
-                     * 
-                     * @description Parses exponentiation with right associativity and
-                     * highest precedence among arithmetic operators.
-                     * 
-                     * @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;
-                } finally {
-                    callStackTracker.pop();
-                }
-            }
-            
-
-            
-            // 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'
-                
-                // Parse the values being matched (can be multiple)
-                const values = [];
-                while (current < tokens.length && tokens[current].type !== TokenType.OF) {
-                    const value = parsePrimary();
-                    values.push(value);
-                }
-                
-                // 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(parsePrimary());
-                    }
-                    
-                    // 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] 
-                    });
-                }
-                
-                return {
-                    type: 'CaseExpression',
-                    value: values,
-                    cases,
-                };
-            }
-            
-            // 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'
-                
-                // Parse the values being matched (can be multiple)
-                const values = [];
-                while (current < tokens.length && tokens[current].type !== TokenType.OF) {
-                    const value = parsePrimary();
-                    values.push(value);
-                }
-                
-                // 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');
-                    }
-                    
-                    const result = parseLogicalExpression();
-                    cases.push({ 
-                        pattern: patterns, 
-                        result: [result] 
-                    });
-                }
-                
-                return {
-                    type: 'CaseExpression',
-                    value: values,
-                    cases,
-                };
-            }
-            
-            // Check for assignments (identifier followed by ':')
-            if (tokens[current].type === TokenType.IDENTIFIER) {
-                const identifier = tokens[current].value;
-                current++;
-                
-                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
-                    current++; // Skip ':'
-                    
-                    // Check if this is a function definition
-                    if (current < tokens.length && tokens[current].type === TokenType.FUNCTION) {
-                        current++; // Skip 'function'
-                        
-                        if (current >= tokens.length || tokens[current].type !== TokenType.LEFT_PAREN) {
-                            throw new Error('Expected "(" after "function"');
-                        }
-                        current++; // Skip '('
-                        
-                        const parameters = [];
-                        while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_PAREN) {
-                            if (tokens[current].type === TokenType.IDENTIFIER) {
-                                parameters.push(tokens[current].value);
-                                current++;
-                            } else {
-                                throw new Error('Expected parameter name in function definition');
-                            }
-                            
-                            // Skip comma if present
-                            if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
-                                current++;
-                            }
-                        }
-                        
-                        if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
-                            throw new Error('Expected ")" after function parameters');
-                        }
-                        current++; // Skip ')'
-                        
-                        if (current >= tokens.length || tokens[current].type !== TokenType.ASSIGNMENT) {
-                            throw new Error('Expected ":" after function parameters');
-                        }
-                        current++; // Skip ':'
-                        
-                        // Parse the function body (which could be a case expression or other expression)
-                        const functionBody = parseLogicalExpression();
-                        
-                        return {
-                            type: 'Assignment',
-                            identifier,
-                            value: {
-                                type: 'FunctionDefinition',
-                                parameters,
-                                body: functionBody
-                            }
-                        };
-                    } else {
-                        // Regular assignment
-                        const value = parseLogicalExpression();
-                        
-                        return {
-                            type: 'Assignment',
-                            identifier,
-                            value
-                        };
-                    }
-                }
-                
-                // If it's not an assignment, put the identifier back and continue
-                current--;
-            }
-            
-            // For all other token types (identifiers, numbers, operators, etc.), call parsePrimary
-            // This handles atomic expressions and delegates to the appropriate parsing functions
-            return parsePrimary();
-        } finally {
-            callStackTracker.pop();
-        }
-    }
+    /**
+     * 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;
+    };
     
-    const ast = {
-        type: 'Program',
-        body: []
+    /**
+     * 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;
     };
     
-    let lastCurrent = -1;
-    let loopCount = 0;
-    const maxLoops = tokens.length * 2; // Safety limit
+    /**
+     * 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;
+    };
     
-    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'}`);
-            }
+    /**
+     * 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 {
-            loopCount = 0;
-        }
-        
-        // Safety check for maximum loops
-        if (loopCount > maxLoops) {
-            throw new Error(`Parser exceeded maximum loop count. Last position: ${current}`);
+            return function(y) {
+                return x;
+            };
         }
-        
-        lastCurrent = current;
-        
-        const node = walk();
-        if (node) {
-            ast.body.push(node);
-        }
-        
-        // Skip semicolons
-        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
-            current++;
+    };
+    
+    /**
+     * 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);
+            };
         }
-    }
+    };
     
-    return ast;
+    /**
+     * 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);
+        };
+    };
 }
 
 /**
@@ -1637,23 +491,41 @@ function parser(tokens) {
  * corresponding operations. Manages scope, handles function calls, and supports
  * both synchronous and asynchronous operations.
  * 
- * @how Uses a global scope for variable storage and function definitions. Each 
- * function call creates a new scope (using prototypal inheritance) to implement 
+ * The interpreter implements a combinator-based architecture where all operations
+ * 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.
  * 
- * @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.
- * 
- * @note The interpreter is split into three functions: evalNode (global), 
+ * 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.
+ * 
+ * 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 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 = {};
     initializeStandardLibrary(globalScope);
     
+    // 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();
     
@@ -1665,7 +537,18 @@ function interpreter(ast) {
      * @throws {Error} For evaluation errors
      * 
      * @description Main evaluation function that handles all node types in the
-     * global scope context.
+     * 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 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.
      */
     function evalNode(node) {
         callStackTracker.push('evalNode', node?.type || 'unknown');
@@ -1769,16 +652,58 @@ function interpreter(ast) {
                     
                     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`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = 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.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;
@@ -1804,45 +729,132 @@ function interpreter(ast) {
                             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; // Renamed from 'func' to avoid redeclaration
+                    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];
-                    } else if (node.name.type === 'TableAccess') {
-                        // Function call from table access (e.g., math.add)
-                        funcToCall = evalNode(node.name);
+                        if (process.env.DEBUG) {
+                            console.log(`[DEBUG] FunctionCall: looking up function '${node.name.value}' in globalScope, found:`, typeof funcToCall);
+                        }
                     } else {
-                        throw new Error('Invalid function name in function call');
+                        // 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 (funcToCall instanceof Function) {
                         let args = node.args.map(evalNode);
+                        if (process.env.DEBUG) {
+                            console.log(`[DEBUG] FunctionCall: calling function with args:`, args);
+                        }
                         return funcToCall(...args);
                     }
                     throw new Error(`Function is not defined or is not callable`);
-                case 'CaseExpression':
-                    const values = node.value.map(evalNode);
+                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) {
-                        const pattern = caseItem.pattern.map(evalNode);
+                        // 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;
-                        for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
-                            const value = values[i];
-                            const patternValue = pattern[i];
-                            
-                            if (patternValue === true) continue;
-                            
-                            if (value !== patternValue) {
-                                matches = false;
-                                break;
+                        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 (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) {
@@ -1887,6 +899,9 @@ function interpreter(ast) {
                         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}`);
             }
@@ -1904,7 +919,17 @@ function interpreter(ast) {
      * @throws {Error} For evaluation errors
      * 
      * @description Used for evaluating function bodies and other expressions
-     * that need access to both local and global variables.
+     * 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.
      */
     const localEvalNodeWithScope = (node, scope) => {
         callStackTracker.push('localEvalNodeWithScope', node?.type || 'unknown');
@@ -2008,9 +1033,30 @@ function interpreter(ast) {
                     
                     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`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
+                        
+                        // 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':
@@ -2039,6 +1085,20 @@ function interpreter(ast) {
                             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') {
@@ -2047,11 +1107,9 @@ function interpreter(ast) {
                     } 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');
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        localFunc = localEvalNodeWithScope(node.name, scope);
                     }
                     
                     if (localFunc instanceof Function) {
@@ -2059,25 +1117,61 @@ function interpreter(ast) {
                         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));
+                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);
+                    }
                     
                     for (const caseItem of node.cases) {
-                        const pattern = caseItem.pattern.map(pat => localEvalNodeWithScope(pat, scope));
+                        // 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;
-                        for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
-                            const value = values[i];
-                            const patternValue = pattern[i];
-                            
-                            if (patternValue === true) continue;
-                            
-                            if (value !== patternValue) {
-                                matches = false;
-                                break;
+                        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 (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) {
@@ -2122,6 +1216,9 @@ function interpreter(ast) {
                         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}`);
             }
@@ -2138,7 +1235,17 @@ function interpreter(ast) {
      * @throws {Error} For evaluation errors
      * 
      * @description Internal helper function for recursive evaluation that
-     * always uses the global scope. Used to avoid circular dependencies.
+     * 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.
      */
     const localEvalNode = (node) => {
         callStackTracker.push('localEvalNode', node?.type || 'unknown');
@@ -2242,9 +1349,30 @@ function interpreter(ast) {
                     
                     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`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
+                        
+                        // 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':
@@ -2269,6 +1397,20 @@ function interpreter(ast) {
                             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') {
@@ -2277,11 +1419,9 @@ function interpreter(ast) {
                     } 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');
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        localFunc = localEvalNode(node.name);
                     }
                     
                     if (localFunc instanceof Function) {
@@ -2289,22 +1429,32 @@ function interpreter(ast) {
                         return localFunc(...args);
                     }
                     throw new Error(`Function is not defined or is not callable`);
-                case 'CaseExpression':
-                    const values = node.value.map(localEvalNode);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(localEvalNode) 
+                        : [localEvalNode(node.value)];
                     
                     for (const caseItem of node.cases) {
-                        const pattern = caseItem.pattern.map(localEvalNode);
+                        // Handle both single patterns and arrays of patterns
+                        const patterns = caseItem.pattern.map(localEvalNode);
                         
+                        // Check if patterns match the values
                         let matches = true;
-                        for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
-                            const value = values[i];
-                            const patternValue = pattern[i];
-                            
-                            if (patternValue === true) continue;
-                            
-                            if (value !== patternValue) {
-                                matches = false;
-                                break;
+                        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 (value !== pattern) {
+                                    matches = false;
+                                    break;
+                                }
                             }
                         }
                         
@@ -2352,6 +1502,9 @@ function interpreter(ast) {
                         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}`);
             }
@@ -2385,10 +1538,14 @@ function interpreter(ast) {
  * @description Logs debug messages to console when DEBUG environment variable is set.
  * Provides verbose output during development while remaining silent in production.
  * 
- * @why Debug functions are gated by the DEBUG environment variable, allowing for 
+ * 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.
  */
 function debugLog(message, data = null) {
     if (process.env.DEBUG) {
@@ -2408,7 +1565,7 @@ function debugLog(message, data = null) {
  * @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 
+ * 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.
@@ -2426,7 +1583,18 @@ function debugError(message, error = null) {
  * 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.
+ * 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.
  */
 const callStackTracker = {
     stack: [],
@@ -2504,25 +1672,77 @@ const callStackTracker = {
 };
 
 /**
+ * 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
+ * 
+ * @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.
+ */
+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');
+        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.
  * 
- * @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.
+ * 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
  * 
- * @note Supports both synchronous and asynchronous execution, with proper
- * error handling and process exit codes.
+ * 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.
  */
-function executeFile(filePath) {
+async function executeFile(filePath) {
     try {
-        const fs = require('fs');
-        const input = fs.readFileSync(filePath, 'utf8');
+        // Validate file extension
+        if (!filePath.endsWith('.txt')) {
+            throw new Error('Only .txt files are supported');
+        }
+        
+        const input = await readFile(filePath);
         
         debugLog('Input:', input);
         
@@ -2580,24 +1800,34 @@ function executeFile(filePath) {
  * @description Processes command line arguments and executes the specified file.
  * Provides helpful error messages for incorrect usage.
  * 
- * @why The language is designed for file execution only (no REPL), so the CLI 
+ * 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.
  * 
- * @note Exits with appropriate error codes for different failure scenarios.
+ * 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];
-    executeFile(filePath);
-} else {
-    // Too many arguments
-    console.error('Usage: node lang.js <file>');
-    console.error('  Provide exactly one file path to execute');
+    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
+main().catch(error => {
+    console.error('Fatal error:', error.message);
     process.exit(1);
-}
\ No newline at end of file
+});
\ No newline at end of file