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.js3129
1 files changed, 2887 insertions, 242 deletions
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
index f91f842..1a0d77e 100644
--- a/js/scripting-lang/lang.js
+++ b/js/scripting-lang/lang.js
@@ -1,320 +1,2965 @@
-// The goal here is less to make anything useful...or even something that works, but to learn what parts an interpreted languages needs to have to function.
+/**
+ * Initializes the standard library in the provided scope.
+ * 
+ * @param {Object} scope - The global scope object to inject functions into
+ * 
+ * @description Injects higher-order functions into the interpreter's global scope.
+ * These functions provide functional programming utilities like map, compose, pipe, etc.
+ * 
+ * @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.
+ * 
+ * @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.
+ */
+function initializeStandardLibrary(scope) {
+    /**
+     * Map: Apply a function to a value
+     * @param {Function} f - Function to apply
+     * @param {*} x - Value to apply function to
+     * @returns {*} Result of applying f to x
+     * @throws {Error} When first argument is not a function
+     */
+    scope.map = function(f, x) { 
+        if (typeof f === 'function') {
+            return f(x);
+        } else {
+            throw new Error('map: first argument must be a function');
+        }
+    };
+    
+    /**
+     * 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
+     */
+    scope.compose = function(f, g, x) { 
+        if (typeof f === 'function' && typeof g === 'function') {
+            if (arguments.length === 3) {
+                return f(g(x));
+            } else {
+                return function(x) {
+                    return f(g(x));
+                };
+            }
+        } else {
+            throw new Error('compose: first two arguments must be functions');
+        }
+    };
+    
+    /**
+     * Curry: Apply a function to arguments (simplified currying)
+     * @param {Function} f - Function to curry
+     * @param {*} x - First argument
+     * @param {*} y - Second argument
+     * @returns {*} Result of applying f to x and y
+     * @throws {Error} When first argument is not a function
+     */
+    scope.curry = function(f, x, y) { 
+        if (typeof f === 'function') {
+            return f(x, y);
+        } else {
+            throw new Error('curry: first argument must be a function');
+        }
+    };
+    
+    /**
+     * Apply: Apply a function to an argument (explicit function application)
+     * @param {Function} f - Function to apply
+     * @param {*} x - Argument to apply function to
+     * @returns {*} Result of applying f to x
+     * @throws {Error} When first argument is not a function
+     */
+    scope.apply = function(f, x) { 
+        if (typeof f === 'function') {
+            return f(x);
+        } else {
+            throw new Error('apply: first argument must be a function');
+        }
+    };
+    
+    /**
+     * 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
+     */
+    scope.pipe = function(f, g, x) { 
+        if (typeof f === 'function' && typeof g === 'function') {
+            if (arguments.length === 3) {
+                return g(f(x));
+            } else {
+                return function(x) {
+                    return g(f(x));
+                };
+            }
+        } else {
+            throw new Error('pipe: first two arguments must be functions');
+        }
+    };
+    
+    /**
+     * Filter: Filter a value based on a predicate
+     * @param {Function} p - Predicate function
+     * @param {*} x - Value to test
+     * @returns {*|0} The value if predicate is true, 0 otherwise
+     * @throws {Error} When first argument is not a function
+     */
+    scope.filter = function(p, x) { 
+        if (typeof p === 'function') {
+            return p(x) ? x : 0;
+        } else {
+            throw new Error('filter: first argument must be a function');
+        }
+    };
+    
+    /**
+     * Reduce: Reduce two values using a binary function
+     * @param {Function} f - Binary function
+     * @param {*} init - Initial value
+     * @param {*} x - Second value
+     * @returns {*} Result of applying f to init and x
+     * @throws {Error} When first argument is not a function
+     */
+    scope.reduce = function(f, init, x) { 
+        if (typeof f === 'function') {
+            return f(init, x);
+        } else {
+            throw new Error('reduce: first argument must be a function');
+        }
+    };
+    
+    /**
+     * Fold: Same as reduce, but more explicit about the folding direction
+     * @param {Function} f - Binary function
+     * @param {*} init - Initial value
+     * @param {*} x - Second value
+     * @returns {*} Result of applying f to init and x
+     * @throws {Error} When first argument is not a function
+     */
+    scope.fold = function(f, init, x) { 
+        if (typeof f === 'function') {
+            return f(init, x);
+        } else {
+            throw new Error('fold: first argument must be a function');
+        }
+    };
+}
 
-// Define the types of tokens
+/**
+ * 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
+/**
+ * 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) {
-    const tokens = [];
     let current = 0;
-
+    const tokens = [];
+    
     while (current < input.length) {
         let char = input[current];
-
-        if (/\d/.test(char)) {
-            let value = '';
-            while (/\d/.test(char)) {
-                value += char;
-                char = input[++current];
-            }
-            tokens.push({
-                type: TokenType.NUMBER,
-                value
-            });
-            continue;
-        }
-
-        if (char === '+') {
-            tokens.push({
-                type: TokenType.PLUS
-            });
+        
+        // Skip whitespace
+        if (/\s/.test(char)) {
             current++;
             continue;
         }
-
-        if (/[a-z]/i.test(char)) {
-            let value = '';
-            while (/[a-z]/i.test(char)) {
-                value += char;
-                char = input[++current];
+        
+        // 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++;
+                }
             }
-            tokens.push({
-                type: TokenType.IDENTIFIER,
-                value
-            });
-            continue;
-        }
-
-        if (char === ':') {
-            tokens.push({
-                type: TokenType.ASSIGNMENT
-            });
-            current++;
-            continue;
-        }
-
-        if (char === '=') {
-            tokens.push({
-                type: TokenType.EQUAL
-            });
-            current++;
             continue;
         }
-
-        if (input.slice(current, current + 2) === 'if') {
-            tokens.push({
-                type: TokenType.IF
-            });
-            current += 2;
-            continue;
-        }
-
-        if (input.slice(current, current + 4) === 'else') {
-            tokens.push({
-                type: TokenType.ELSE
-            });
-            current += 4;
-            continue;
-        }
-
-        if (char === '(') {
-            tokens.push({
-                type: TokenType.LEFT_PAREN
-            });
-            current++;
-            continue;
-        }
-
-        if (char === ')') {
-            tokens.push({
-                type: TokenType.RIGHT_PAREN
-            });
-            current++;
+        
+        // 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;
         }
-
-        if (char === '{') {
-            tokens.push({
-                type: TokenType.LEFT_BRACE
-            });
-            current++;
+        
+        // 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;
         }
-
-        if (char === '}') {
-            tokens.push({
-                type: TokenType.RIGHT_BRACE
-            });
-            current++;
+        
+        // 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;
         }
-
-        if (input.slice(current, current + 8) === 'function') {
-            tokens.push({
-                type: TokenType.FUNCTION
-            });
-            current += 8;
-            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;
+            }
         }
-
-        if (char === ';') {
-            tokens.push({ type: TokenType.SEMICOLON });
-            current++;
-            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++;
     }
+    
 
+    
     return tokens;
 }
 
-// Parser
+/**
+ * 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;
-
-    function walk() {
-        if (current >= tokens.length) {
-            return null; // Return null when there are no more tokens
+    let parsingFunctionArgs = false; // Flag to track when we're parsing function arguments
+    
+    // Reset call stack tracker for parser
+    callStackTracker.reset();
+    
+    // Define all parsing functions outside of walk to avoid circular dependencies
+    
+    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();
         }
-
-        let token = tokens[current];
-
-        if (token.type === TokenType.NUMBER) {
-            current++;
-            return {
-                type: 'NumberLiteral',
-                value: token.value,
-            };
+    }
+    
+    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();
         }
-
-        if (token.type === TokenType.PLUS) {
-            current++;
-            return {
-                type: 'PlusExpression',
-                left: walk(),
-                right: walk(),
-            };
+    }
+    
+    function parseArgument() {
+        callStackTracker.push('parseArgument', '');
+        
+        try {
+            const token = tokens[current];
+            if (!token) {
+                throw new Error('Unexpected end of input');
+            }
+            
+            // Parse unary operators
+            if (token.type === TokenType.NOT) {
+                current++;
+                const operand = parseArgument();
+                return { type: 'NotExpression', operand };
+            }
+            
+            if (token.type === TokenType.MINUS) {
+                current++;
+                const operand = parseArgument();
+                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' };
+            } else if (token.type === TokenType.WILDCARD) {
+                current++;
+                return { type: 'WildcardPattern' };
+            } else if (token.type === TokenType.FUNCTION_REF) {
+                current++;
+                if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                    const functionName = tokens[current].value;
+                    current++;
+                    return { type: 'FunctionReference', name: functionName };
+                } else {
+                    throw new Error('Expected function name after @');
+                }
+            } else if (token.type === TokenType.IO_IN) {
+                current++;
+                return { type: 'IOInExpression' };
+            } else if (token.type === TokenType.IO_OUT) {
+                current++;
+                const outputValue = parseLogicalExpression();
+                return { type: 'IOOutExpression', value: outputValue };
+            } else if (token.type === TokenType.IO_ASSERT) {
+                current++;
+                const assertionExpr = parseLogicalExpression();
+                return { type: 'IOAssertExpression', value: assertionExpr };
+            }
+            
+            // Parse identifiers (but NOT as function calls)
+            if (token.type === TokenType.IDENTIFIER) {
+                current++;
+                const identifier = { type: 'Identifier', value: token.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');
+                }
+            }
+            
+            // If we get here, we have an unexpected token
+            throw new Error(`Unexpected token in parseArgument: ${token.type}`);
+        } finally {
+            callStackTracker.pop();
         }
-
-        if (token.type === TokenType.IDENTIFIER) {
-            current++;
+    }
+    
+    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 - parse as expression but skip function call detection
+                    // Create a temporary parsing context that doesn't trigger function call detection
+                    const savedParsingFunctionArgs = parsingFunctionArgs;
+                    parsingFunctionArgs = true; // Temporarily disable function call detection
+                    const arg = parseExpression();
+                    parsingFunctionArgs = savedParsingFunctionArgs; // Restore the flag
+                    args.push(arg);
+                }
+            }
+                
             return {
-                type: 'Identifier',
-                value: token.value,
+                type: 'FunctionCall',
+                name: functionName,
+                args: args
             };
+        } finally {
+            callStackTracker.pop();
         }
-
-        if (token.type === TokenType.ASSIGNMENT) {
-            current++;
-            return {
-                type: 'AssignmentExpression',
-                name: tokens[current - 2].value,
-                value: walk(),
-            };
+    }
+    
+    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();
         }
-
-        if (token.type === TokenType.IF) {
-            current++;
-            let node = {
-                type: 'IfExpression',
-                test: walk(),
-                consequent: walk(),
-                alternate: tokens[current].type === TokenType.ELSE ? (current++, walk()) : null,
-            };
-            return node;
+    }
+    
+    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();
         }
-
-        if (token.type === TokenType.FUNCTION) {
-            current++;
-            let node = {
-                type: 'FunctionDeclaration',
-                name: tokens[current++].value,
-                params: [],
-                body: [],
-            };
-            while (tokens[current].type !== TokenType.RIGHT_PAREN) {
-                node.params.push(tokens[current++].value);
+    }
+    
+    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;
+                }
             }
-            current++; // Skip right paren
-            while (tokens[current].type !== TokenType.RIGHT_BRACE) {
-                node.body.push(walk());
+            
+            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 };
             }
-            current++; // Skip right brace
-            return node;
+            
+            return left;
+        } finally {
+            callStackTracker.pop();
         }
+    }
+    
+    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' };
+            } else if (token.type === TokenType.WILDCARD) {
+                current++;
+                return { type: 'WildcardPattern' };
+            } else if (token.type === TokenType.FUNCTION_REF) {
+                current++;
+                if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                    const functionName = tokens[current].value;
+                    current++;
+                    return { type: 'FunctionReference', name: functionName };
+                } else {
+                    throw new Error('Expected function name after @');
+                }
+            } else if (token.type === TokenType.IO_IN) {
+                current++;
+                return { type: 'IOInExpression' };
+            } else if (token.type === TokenType.IO_OUT) {
+                current++;
+                const outputValue = parseLogicalExpression();
+                return { type: 'IOOutExpression', value: outputValue };
+            } else if (token.type === TokenType.IO_ASSERT) {
+                current++;
+                const assertionExpr = parseLogicalExpression();
+                return { type: 'IOAssertExpression', value: assertionExpr };
+            }
+            
+            // Parse identifiers
+            if (token.type === TokenType.IDENTIFIER) {
+                current++;
+                const identifier = { type: 'Identifier', value: token.value };
+                
+                // Skip function call detection if we're parsing function arguments
+                if (parsingFunctionArgs) {
+                    return identifier;
+                }
+                
+                // Check for function calls
+                if (current < tokens.length && tokens[current].type === TokenType.LEFT_PAREN) {
+                    return parseFunctionCall(identifier.value);
+                }
+                
+                // Check if the next token is an operator - if so, don't treat as function call
+                if (current < tokens.length && 
+                    (tokens[current].type === TokenType.PLUS ||
+                     tokens[current].type === TokenType.MINUS ||
+                     tokens[current].type === TokenType.MULTIPLY ||
+                     tokens[current].type === TokenType.DIVIDE ||
+                     tokens[current].type === TokenType.MODULO ||
+                     tokens[current].type === TokenType.POWER)) {
+                    // This is part of a binary expression, don't treat as function call
+                    return identifier;
+                }
+                
+                // Check for function calls without parentheses (e.g., add 3 4)
+                // Only treat as function call if the next token is a number, string, or left paren
+                // This prevents treating identifiers as function calls when they're actually arguments
+                if (current < tokens.length && 
+                    (tokens[current].type === TokenType.NUMBER ||
+                     tokens[current].type === TokenType.STRING ||
+                     tokens[current].type === TokenType.LEFT_PAREN ||
+                     tokens[current].type === TokenType.FUNCTION_REF)) {
+                    return parseFunctionCall(identifier.value);
+                }
+                
+                // Special case for unary minus: only treat as function call if it's a unary minus
+                if (current < tokens.length && tokens[current].type === TokenType.MINUS) {
+                    // Look ahead to see if this is a unary minus (like -5) or binary minus (like n - 1)
+                    const nextToken = current + 1 < tokens.length ? tokens[current + 1] : null;
+                    if (nextToken && nextToken.type === TokenType.NUMBER) {
+                        // This is a unary minus, treat as function call
+                        return parseFunctionCall(identifier.value);
+                    }
+                    // This is a binary minus, don't treat as function call
+                }
+                
+                // Special case for function calls with identifier arguments (e.g., add x y)
+                // Only treat as function call if the next token is an identifier and not followed by an operator
+                if (!parsingFunctionArgs && current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                    // Look ahead to see if the next token is an identifier followed by an operator
+                    const nextToken = current + 1 < tokens.length ? tokens[current + 1] : null;
+                    const nextNextToken = current + 2 < tokens.length ? tokens[current + 2] : null;
+                    
+                    // Only treat as function call if the next token is an identifier and not followed by an operator
+                    if (nextToken && nextToken.type === TokenType.IDENTIFIER && 
+                        (!nextNextToken || 
+                         (nextNextToken.type !== TokenType.PLUS &&
+                          nextNextToken.type !== TokenType.MINUS &&
+                          nextNextToken.type !== TokenType.MULTIPLY &&
+                          nextNextToken.type !== TokenType.DIVIDE &&
+                          nextNextToken.type !== TokenType.MODULO &&
+                          nextNextToken.type !== TokenType.POWER &&
+                          nextNextToken.type !== TokenType.EQUALS &&
+                          nextNextToken.type !== TokenType.NOT_EQUAL &&
+                          nextNextToken.type !== TokenType.LESS_THAN &&
+                          nextNextToken.type !== TokenType.GREATER_THAN &&
+                          nextNextToken.type !== TokenType.LESS_EQUAL &&
+                          nextNextToken.type !== TokenType.GREATER_EQUAL))) {
+                        if (process.env.DEBUG) {
+                            console.log(`[DEBUG] Creating function call for ${identifier.value} at position ${current}`);
+                        }
+                        return parseFunctionCall(identifier.value);
+                    }
+                }
+                
 
-        if (token.type === TokenType.IDENTIFIER && tokens[current + 1].type === TokenType.LEFT_PAREN) {
-            current++;
-            let node = {
-                type: 'FunctionCall',
-                name: token.value,
-                args: [],
-            };
-            current++; // Skip left paren
-            while (tokens[current].type !== TokenType.RIGHT_PAREN) {
-                node.args.push(walk());
+                
+                // 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;
             }
-            current++; // Skip right paren
-            return node;
-        }
+            
+            // 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 arrow expressions (function definitions)
+            if (token.type === TokenType.ARROW) {
+                current++; // Skip '->'
+                
+                // Parse the function body
+                const body = parseLogicalExpression();
+                
+                return { type: 'ArrowExpression', body };
+            }
+            
 
-        if (token.type === TokenType.SEMICOLON) {
-            current++;
-            return;
-        }
+            
 
-        throw new TypeError(token.type);
+            
+            // If we get here, we have an unexpected token
+            throw new Error(`Unexpected token in parsePrimary: ${token.type}`);
+        } finally {
+            callStackTracker.pop();
+        }
     }
+    
+    function walk() {
+        callStackTracker.push('walk', `position:${current}`);
+        
+        try {
+            
 
-    let ast = {
+            
+
+            
+            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 first
+            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 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 with arrow syntax (x y -> body)
+                    // Look ahead to see if we have parameters followed by ->
+                    const lookAheadTokens = [];
+                    let lookAheadPos = current;
+                    
+                    // Collect tokens until we find -> or hit a terminator
+                    while (lookAheadPos < tokens.length && 
+                           tokens[lookAheadPos].type !== TokenType.ARROW &&
+                           tokens[lookAheadPos].type !== TokenType.SEMICOLON &&
+                           tokens[lookAheadPos].type !== TokenType.ASSIGNMENT) {
+                        lookAheadTokens.push(tokens[lookAheadPos]);
+                        lookAheadPos++;
+                    }
+                    
+                    // If we found ->, this is a function definition with arrow syntax
+                    if (lookAheadPos < tokens.length && tokens[lookAheadPos].type === TokenType.ARROW) {
+                        // Parse parameters (identifiers separated by spaces)
+                        const parameters = [];
+                        let paramIndex = 0;
+                        
+                        while (paramIndex < lookAheadTokens.length) {
+                            if (lookAheadTokens[paramIndex].type === TokenType.IDENTIFIER) {
+                                parameters.push(lookAheadTokens[paramIndex].value);
+                                paramIndex++;
+                            } else {
+                                // Skip non-identifier tokens (spaces, etc.)
+                                paramIndex++;
+                            }
+                        }
+                        
+                        // Skip the parameters and ->
+                        current = lookAheadPos + 1; // Skip the arrow
+                        
+                        // Parse the function body (check if it's a case expression)
+                        let functionBody;
+                        if (current < tokens.length && tokens[current].type === TokenType.CASE) {
+                            // Parse case expression directly
+                            current++; // Skip 'case'
+                            
+                            // Parse the values being matched (can be multiple)
+                            const values = [];
+                            while (current < tokens.length && tokens[current].type !== TokenType.OF) {
+                                if (tokens[current].type === TokenType.IDENTIFIER) {
+                                    values.push({ type: 'Identifier', value: tokens[current].value });
+                                    current++;
+                                } else if (tokens[current].type === TokenType.NUMBER) {
+                                    values.push({ type: 'NumberLiteral', value: tokens[current].value });
+                                    current++;
+                                } else if (tokens[current].type === TokenType.STRING) {
+                                    values.push({ type: 'StringLiteral', value: tokens[current].value });
+                                    current++;
+                                } else {
+                                    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) {
+                                // If we hit an IO operation, we've reached the end of the case expression
+                                if (current < tokens.length && 
+                                    (tokens[current].type === TokenType.IO_IN ||
+                                     tokens[current].type === TokenType.IO_OUT ||
+                                     tokens[current].type === TokenType.IO_ASSERT)) {
+                                    break;
+                                }
+                                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');
+                                }
+                                
+                                // Temporarily disable function call detection when parsing case expression results
+                                const savedParsingFunctionArgs = parsingFunctionArgs;
+                                parsingFunctionArgs = true; // Disable function call detection
+                                const result = parseLogicalExpression();
+                                parsingFunctionArgs = savedParsingFunctionArgs; // Restore the flag
+                                cases.push({ 
+                                    pattern: patterns, 
+                                    result: [result] 
+                                });
+                                
+                                // Skip semicolon if present (but don't stop parsing cases)
+                                if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+                                    current++;
+                                    // If the next token is an identifier followed by assignment, we've reached the end of the case expression
+                                    if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                                        const nextPos = current + 1;
+                                        if (nextPos < tokens.length && tokens[nextPos].type === TokenType.ASSIGNMENT) {
+                                            break; // End of case expression
+                                        }
+                                    }
+                                }
+                            }
+                            
+                            functionBody = {
+                                type: 'CaseExpression',
+                                value: values,
+                                cases,
+                            };
+                        } else {
+                            functionBody = parseLogicalExpression();
+                        }
+                        
+                        return {
+                            type: 'Assignment',
+                            identifier,
+                            value: {
+                                type: 'FunctionDefinition',
+                                parameters,
+                                body: functionBody
+                            }
+                        };
+                    }
+                    
+                    // Check if this is a function definition with 'function' keyword
+                    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 (check if it's a case expression)
+                        let functionBody;
+                        if (current < tokens.length && tokens[current].type === TokenType.CASE) {
+                            // Parse case expression directly
+                            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) {
+                                // If we hit an IO operation, we've reached the end of the case expression
+                                if (current < tokens.length && 
+                                    (tokens[current].type === TokenType.IO_IN ||
+                                     tokens[current].type === TokenType.IO_OUT ||
+                                     tokens[current].type === TokenType.IO_ASSERT)) {
+                                    break;
+                                }
+                                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] 
+                                });
+                                
+                                // Skip semicolon if present (but don't stop parsing cases)
+                                if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+                                    current++;
+                                    // If the next token is an identifier followed by assignment, we've reached the end of the case expression
+                                    if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                                        const nextPos = current + 1;
+                                        if (nextPos < tokens.length && tokens[nextPos].type === TokenType.ASSIGNMENT) {
+                                            break; // End of case expression
+                                        }
+                                    }
+                                }
+                            }
+                            
+                            functionBody = {
+                                type: 'CaseExpression',
+                                value: values,
+                                cases,
+                            };
+                        } else {
+                            functionBody = parseLogicalExpression();
+                        }
+                        
+                        return {
+                            type: 'Assignment',
+                            identifier,
+                            value: {
+                                type: 'FunctionDefinition',
+                                parameters,
+                                body: functionBody
+                            }
+                        };
+                    } else {
+                        // Check if this is a case expression
+                        if (current < tokens.length && tokens[current].type === TokenType.CASE) {
+                            // Parse the case expression directly
+                            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) {
+                                // If we hit an IO operation, we've reached the end of the case expression
+                                if (current < tokens.length && 
+                                    (tokens[current].type === TokenType.IO_IN ||
+                                     tokens[current].type === TokenType.IO_OUT ||
+                                     tokens[current].type === TokenType.IO_ASSERT)) {
+                                    break;
+                                }
+                                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] 
+                                });
+                                
+                                // Skip semicolon if present (but don't stop parsing cases)
+                                if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+                                    current++;
+                                    // If the next token is an identifier followed by assignment, we've reached the end of the case expression
+                                    if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                                        const nextPos = current + 1;
+                                        if (nextPos < tokens.length && tokens[nextPos].type === TokenType.ASSIGNMENT) {
+                                            break; // End of case expression
+                                        }
+                                    }
+                                }
+                            }
+                            
+                            return {
+                                type: 'Assignment',
+                                identifier,
+                                value: {
+                                    type: 'CaseExpression',
+                                    value: values,
+                                    cases,
+                                }
+                            };
+                        } 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();
+        }
+    }
+    
+    const ast = {
         type: 'Program',
-        body: [],
+        body: []
     };
-
+    
+    let lastCurrent = -1;
+    let loopCount = 0;
+    const maxLoops = tokens.length * 2; // Safety limit
+    
     while (current < tokens.length) {
+        // Safety check to prevent infinite loops
+        if (current === lastCurrent) {
+            loopCount++;
+            if (loopCount > 10) { // Allow a few iterations at the same position
+                throw new Error(`Parser stuck at position ${current}, token: ${tokens[current]?.type || 'EOF'}`);
+            }
+        } else {
+            loopCount = 0;
+        }
+        
+        // Safety check for maximum loops
+        if (loopCount > maxLoops) {
+            throw new Error(`Parser exceeded maximum loop count. Last position: ${current}`);
+        }
+        
+        lastCurrent = current;
+        
         const node = walk();
-        if (node !== null) {
+        if (node) {
             ast.body.push(node);
         }
+        
+        // Skip semicolons
+        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+            current++;
+        }
     }
-
+    
     return ast;
 }
 
-// Interpreter
+/**
+ * Interpreter: Walks the AST and evaluates each node.
+ * 
+ * @param {Object} ast - Abstract Syntax Tree to evaluate
+ * @returns {*} The result of evaluating the AST, or a Promise for async operations
+ * @throws {Error} For evaluation errors like division by zero, undefined variables, etc.
+ * 
+ * @description Evaluates an AST by walking through each node and performing the
+ * corresponding operations. Manages scope, handles function calls, and supports
+ * both synchronous and asynchronous operations.
+ * 
+ * @how Uses a global scope for variable storage and function definitions. Each 
+ * function call creates a new scope (using prototypal inheritance) to implement 
+ * lexical scoping. Immutability is enforced by preventing reassignment in the 
+ * global scope.
+ * 
+ * @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), 
+ * localEvalNodeWithScope (for function bodies), and localEvalNode (for internal 
+ * recursion). This separation allows for correct scope handling and easier debugging.
+ */
 function interpreter(ast) {
-    let globalScope = {};
-
+    const globalScope = {};
+    initializeStandardLibrary(globalScope);
+    
+    // Reset call stack tracker at the start of interpretation
+    callStackTracker.reset();
+    
+    /**
+     * Evaluates AST nodes in the global scope.
+     * 
+     * @param {Object} node - AST node to evaluate
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Main evaluation function that handles all node types in the
+     * global scope context.
+     */
     function evalNode(node) {
-        switch (node.type) {
-            case 'NumberLiteral':
-                return parseInt(node.value);
-            case 'PlusExpression':
-                return evalNode(node.left) + evalNode(node.right);
-            case 'AssignmentExpression':
-                globalScope[node.name] = evalNode(node.value);
-                return;
-            case 'Identifier':
-                return globalScope[node.value];
-            case 'IfExpression':
-                return evalNode(node.test) ? evalNode(node.consequent) : node.alternate ? evalNode(node.alternate) : undefined;
-            case 'FunctionDeclaration':
-                globalScope[node.name] = function() {
-                    let localScope = Object.create(globalScope);
-                    for (let i = 0; i < node.params.length; i++) {
-                        localScope[node.params[i]] = arguments[i];
-                    }
-                    let lastResult;
-                    for (let bodyNode of node.body) {
-                        lastResult = evalNode(bodyNode);
-                    }
-                    return lastResult;
-                };
-                return;
-            case 'FunctionCall':
-                if (globalScope[node.name] instanceof Function) {
-                    let args = node.args.map(evalNode);
-                    return globalScope[node.name].apply(null, args);
-                }
-                throw new Error(`Function ${node.name} is not defined`);
-            default:
-                throw new Error(`Unknown node type: ${node.type}`);
+        callStackTracker.push('evalNode', node?.type || 'unknown');
+        
+        try {
+            if (!node) {
+                return undefined;
+            }
+            switch (node.type) {
+                case 'NumberLiteral':
+                    return parseFloat(node.value);
+                case 'StringLiteral':
+                    return node.value;
+                case 'BooleanLiteral':
+                    return node.value;
+                case 'PlusExpression':
+                    return evalNode(node.left) + evalNode(node.right);
+                case 'MinusExpression':
+                    return evalNode(node.left) - evalNode(node.right);
+                case 'MultiplyExpression':
+                    return evalNode(node.left) * evalNode(node.right);
+                case 'DivideExpression':
+                    const divisor = evalNode(node.right);
+                    if (divisor === 0) {
+                        throw new Error('Division by zero');
+                    }
+                    return evalNode(node.left) / evalNode(node.right);
+                case 'ModuloExpression':
+                    return evalNode(node.left) % evalNode(node.right);
+                case 'PowerExpression':
+                    return Math.pow(evalNode(node.left), evalNode(node.right));
+                case 'EqualsExpression':
+                    return evalNode(node.left) === evalNode(node.right);
+                case 'LessThanExpression':
+                    return evalNode(node.left) < evalNode(node.right);
+                case 'GreaterThanExpression':
+                    return evalNode(node.left) > evalNode(node.right);
+                case 'LessEqualExpression':
+                    return evalNode(node.left) <= evalNode(node.right);
+                case 'GreaterEqualExpression':
+                    return evalNode(node.left) >= evalNode(node.right);
+                case 'NotEqualExpression':
+                    return evalNode(node.left) !== evalNode(node.right);
+                case 'AndExpression':
+                    return !!(evalNode(node.left) && evalNode(node.right));
+                case 'OrExpression':
+                    return !!(evalNode(node.left) || evalNode(node.right));
+                case 'XorExpression':
+                    const leftVal = evalNode(node.left);
+                    const rightVal = evalNode(node.right);
+                    return !!((leftVal && !rightVal) || (!leftVal && rightVal));
+                case 'NotExpression':
+                    return !evalNode(node.operand);
+                case 'UnaryMinusExpression':
+                    return -evalNode(node.operand);
+                case 'TableLiteral':
+                    const table = {};
+                    let arrayIndex = 1;
+                    
+                    for (const entry of node.entries) {
+                        if (entry.key === null) {
+                            // Array-like entry: {1, 2, 3}
+                            table[arrayIndex] = evalNode(entry.value);
+                            arrayIndex++;
+                        } else {
+                            // Key-value entry: {name: "Alice", age: 30}
+                            let key;
+                            if (entry.key.type === 'Identifier') {
+                                // Convert identifier keys to strings
+                                key = entry.key.value;
+                            } else {
+                                // For other key types (numbers, strings), evaluate normally
+                                key = evalNode(entry.key);
+                            }
+                            const value = evalNode(entry.value);
+                            table[key] = value;
+                        }
+                    }
+                    
+                    return table;
+                case 'TableAccess':
+                    const tableValue = evalNode(node.table);
+                    let keyValue;
+                    
+                    // Handle different key types
+                    if (node.key.type === 'Identifier') {
+                        // For dot notation, use the identifier name as the key
+                        keyValue = node.key.value;
+                    } else {
+                        // For bracket notation, evaluate the key expression
+                        keyValue = evalNode(node.key);
+                    }
+                    
+                    if (typeof tableValue !== 'object' || tableValue === null) {
+                        throw new Error('Cannot access property of non-table value');
+                    }
+                    
+                    if (tableValue[keyValue] === undefined) {
+                        throw new Error(`Key '${keyValue}' not found in table`);
+                    }
+                    
+                    return tableValue[keyValue];
+                case 'AssignmentExpression':
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    const value = evalNode(node.value);
+                    globalScope[node.name] = value;
+                    return;
+                case 'Assignment':
+                    if (globalScope.hasOwnProperty(node.identifier)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.identifier}`);
+                    }
+                    const assignmentValue = evalNode(node.value);
+                    globalScope[node.identifier] = assignmentValue;
+                    return;
+                case 'Identifier':
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined) {
+                        throw new Error(`Variable ${node.value} is not defined`);
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            let localScope = Object.create(globalScope);
+                            for (let i = 0; i < node.params.length; i++) {
+                                localScope[node.params[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, localScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionDefinition':
+                    // Create a function from the function definition
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.parameters.join(','));
+                        try {
+                            let localScope = Object.create(globalScope);
+                            for (let i = 0; i < node.parameters.length; i++) {
+                                localScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, localScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let funcToCall; // Renamed from 'func' to avoid redeclaration
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        funcToCall = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        funcToCall = globalScope[node.name.value];
+                    } else if (node.name.type === 'TableAccess') {
+                        // Function call from table access (e.g., math.add)
+                        funcToCall = evalNode(node.name);
+                    } else {
+                        throw new Error('Invalid function name in function call');
+                    }
+                    
+                    if (funcToCall instanceof Function) {
+                        let args = node.args.map(evalNode);
+                        return funcToCall(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'CaseExpression':
+                    const values = node.value.map(evalNode);
+                    
+                    for (const caseItem of node.cases) {
+                        const pattern = caseItem.pattern.map(evalNode);
+                        
+                        let matches = true;
+                        for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
+                            const value = values[i];
+                            const patternValue = pattern[i];
+                            
+                            if (patternValue === true) continue;
+                            
+                            if (value !== patternValue) {
+                                matches = false;
+                                break;
+                            }
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(evalNode);
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const readline = require('readline');
+                    const rl = readline.createInterface({
+                        input: process.stdin,
+                        output: process.stdout
+                    });
+                    
+                    return new Promise((resolve) => {
+                        rl.question('', (input) => {
+                            rl.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const outputValue = evalNode(node.value);
+                    console.log(outputValue);
+                    return outputValue;
+                case 'IOAssertExpression':
+                    const assertionValue = evalNode(node.value);
+                    if (!assertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return assertionValue;
+                case 'FunctionReference':
+                    const functionValue = globalScope[node.name];
+                    if (functionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof functionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return functionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return evalNode(node.body);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
         }
     }
 
-    return evalNode(ast.body[0]);
+    /**
+     * Evaluates AST nodes in a local scope with access to parent scope.
+     * 
+     * @param {Object} node - AST node to evaluate
+     * @param {Object} scope - Local scope object (prototypally inherits from global)
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Used for evaluating function bodies and other expressions
+     * that need access to both local and global variables.
+     */
+    const localEvalNodeWithScope = (node, scope) => {
+        callStackTracker.push('localEvalNodeWithScope', node?.type || 'unknown');
+        
+        try {
+            if (!node) {
+                return undefined;
+            }
+            switch (node.type) {
+                case 'NumberLiteral':
+                    return parseFloat(node.value);
+                case 'StringLiteral':
+                    return node.value;
+                case 'BooleanLiteral':
+                    return node.value;
+                case 'PlusExpression':
+                    return localEvalNodeWithScope(node.left, scope) + localEvalNodeWithScope(node.right, scope);
+                case 'MinusExpression':
+                    return localEvalNodeWithScope(node.left, scope) - localEvalNodeWithScope(node.right, scope);
+                case 'MultiplyExpression':
+                    return localEvalNodeWithScope(node.left, scope) * localEvalNodeWithScope(node.right, scope);
+                case 'DivideExpression':
+                    const divisor = localEvalNodeWithScope(node.right, scope);
+                    if (divisor === 0) {
+                        throw new Error('Division by zero');
+                    }
+                    return localEvalNodeWithScope(node.left, scope) / localEvalNodeWithScope(node.right, scope);
+                case 'ModuloExpression':
+                    return localEvalNodeWithScope(node.left, scope) % localEvalNodeWithScope(node.right, scope);
+                case 'PowerExpression':
+                    return Math.pow(localEvalNodeWithScope(node.left, scope), localEvalNodeWithScope(node.right, scope));
+                case 'EqualsExpression':
+                    return localEvalNodeWithScope(node.left, scope) === localEvalNodeWithScope(node.right, scope);
+                case 'LessThanExpression':
+                    return localEvalNodeWithScope(node.left, scope) < localEvalNodeWithScope(node.right, scope);
+                case 'GreaterThanExpression':
+                    return localEvalNodeWithScope(node.left, scope) > localEvalNodeWithScope(node.right, scope);
+                case 'LessEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) <= localEvalNodeWithScope(node.right, scope);
+                case 'GreaterEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) >= localEvalNodeWithScope(node.right, scope);
+                case 'NotEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) !== localEvalNodeWithScope(node.right, scope);
+                case 'AndExpression':
+                    return !!(localEvalNodeWithScope(node.left, scope) && localEvalNodeWithScope(node.right, scope));
+                case 'OrExpression':
+                    return !!(localEvalNodeWithScope(node.left, scope) || localEvalNodeWithScope(node.right, scope));
+                case 'XorExpression':
+                    const leftVal = localEvalNodeWithScope(node.left, scope);
+                    const rightVal = localEvalNodeWithScope(node.right, scope);
+                    return !!((leftVal && !rightVal) || (!leftVal && rightVal));
+                case 'NotExpression':
+                    return !localEvalNodeWithScope(node.operand, scope);
+                case 'UnaryMinusExpression':
+                    return -localEvalNodeWithScope(node.operand, scope);
+                case 'TableLiteral':
+                    const table = {};
+                    let arrayIndex = 1;
+                    
+                    for (const entry of node.entries) {
+                        if (entry.key === null) {
+                            // Array-like entry: {1, 2, 3}
+                            table[arrayIndex] = localEvalNodeWithScope(entry.value, scope);
+                            arrayIndex++;
+                        } else {
+                            // Key-value entry: {name: "Alice", age: 30}
+                            let key;
+                            if (entry.key.type === 'Identifier') {
+                                // Convert identifier keys to strings
+                                key = entry.key.value;
+                            } else {
+                                // For other key types (numbers, strings), evaluate normally
+                                key = localEvalNodeWithScope(entry.key, scope);
+                            }
+                            const value = localEvalNodeWithScope(entry.value, scope);
+                            table[key] = value;
+                        }
+                    }
+                    
+                    return table;
+                case 'TableAccess':
+                    const tableValue = localEvalNodeWithScope(node.table, scope);
+                    let keyValue;
+                    
+                    // Handle different key types
+                    if (node.key.type === 'Identifier') {
+                        // For dot notation, use the identifier name as the key
+                        keyValue = node.key.value;
+                    } else {
+                        // For bracket notation, evaluate the key expression
+                        keyValue = localEvalNodeWithScope(node.key, scope);
+                    }
+                    
+                    if (typeof tableValue !== 'object' || tableValue === null) {
+                        throw new Error('Cannot access property of non-table value');
+                    }
+                    
+                    if (tableValue[keyValue] === undefined) {
+                        throw new Error(`Key '${keyValue}' not found in table`);
+                    }
+                    
+                    return tableValue[keyValue];
+                case 'AssignmentExpression':
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    globalScope[node.name] = localEvalNodeWithScope(node.value, scope);
+                    return;
+                case 'Identifier':
+                    // First check local scope, then global scope
+                    if (scope && scope.hasOwnProperty(node.value)) {
+                        return scope[node.value];
+                    }
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined && node.value) {
+                        return node.value;
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.params.length; i++) {
+                                nestedScope[node.params[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionDefinition':
+                    // Create a function from the function definition
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.parameters.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.parameters.length; i++) {
+                                nestedScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let localFunc;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        localFunc = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        localFunc = globalScope[node.name.value];
+                    } else if (node.name.type === 'TableAccess') {
+                        // Function call from table access (e.g., math.add)
+                        localFunc = localEvalNodeWithScope(node.name, scope);
+                    } else {
+                        throw new Error('Invalid function name in function call');
+                    }
+                    
+                    if (localFunc instanceof Function) {
+                        let args = node.args.map(arg => localEvalNodeWithScope(arg, scope));
+                        return localFunc(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'CaseExpression':
+                    const values = node.value.map(val => localEvalNodeWithScope(val, scope));
+                    
+                    for (const caseItem of node.cases) {
+                        const pattern = caseItem.pattern.map(pat => localEvalNodeWithScope(pat, scope));
+                        
+                        let matches = true;
+                        for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
+                            const value = values[i];
+                            const patternValue = pattern[i];
+                            
+                            if (patternValue === true) continue;
+                            
+                            if (value !== patternValue) {
+                                matches = false;
+                                break;
+                            }
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(res => localEvalNodeWithScope(res, scope));
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const readline = require('readline');
+                    const rl = readline.createInterface({
+                        input: process.stdin,
+                        output: process.stdout
+                    });
+                    
+                    return new Promise((resolve) => {
+                        rl.question('', (input) => {
+                            rl.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const localOutputValue = localEvalNodeWithScope(node.value, scope);
+                    console.log(localOutputValue);
+                    return localOutputValue;
+                case 'IOAssertExpression':
+                    const localAssertionValue = localEvalNodeWithScope(node.value, scope);
+                    if (!localAssertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return localAssertionValue;
+                case 'FunctionReference':
+                    const localFunctionValue = globalScope[node.name];
+                    if (localFunctionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof localFunctionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return localFunctionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return localEvalNodeWithScope(node.body, scope);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
+        }
+    };
+
+    /**
+     * Evaluates AST nodes in the global scope (internal recursion helper).
+     * 
+     * @param {Object} node - AST node to evaluate
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Internal helper function for recursive evaluation that
+     * always uses the global scope. Used to avoid circular dependencies.
+     */
+    const localEvalNode = (node) => {
+        callStackTracker.push('localEvalNode', node?.type || 'unknown');
+        
+        try {
+            if (!node) {
+                return undefined;
+            }
+            switch (node.type) {
+                case 'NumberLiteral':
+                    return parseFloat(node.value);
+                case 'StringLiteral':
+                    return node.value;
+                case 'BooleanLiteral':
+                    return node.value;
+                case 'PlusExpression':
+                    return localEvalNode(node.left) + localEvalNode(node.right);
+                case 'MinusExpression':
+                    return localEvalNode(node.left) - localEvalNode(node.right);
+                case 'MultiplyExpression':
+                    return localEvalNode(node.left) * localEvalNode(node.right);
+                case 'DivideExpression':
+                    const divisor = localEvalNode(node.right);
+                    if (divisor === 0) {
+                        throw new Error('Division by zero');
+                    }
+                    return localEvalNode(node.left) / localEvalNode(node.right);
+                case 'ModuloExpression':
+                    return localEvalNode(node.left) % localEvalNode(node.right);
+                case 'PowerExpression':
+                    return Math.pow(localEvalNode(node.left), localEvalNode(node.right));
+                case 'EqualsExpression':
+                    return localEvalNode(node.left) === localEvalNode(node.right);
+                case 'LessThanExpression':
+                    return localEvalNode(node.left) < localEvalNode(node.right);
+                case 'GreaterThanExpression':
+                    return localEvalNode(node.left) > localEvalNode(node.right);
+                case 'LessEqualExpression':
+                    return localEvalNode(node.left) <= localEvalNode(node.right);
+                case 'GreaterEqualExpression':
+                    return localEvalNode(node.left) >= localEvalNode(node.right);
+                case 'NotEqualExpression':
+                    return localEvalNode(node.left) !== localEvalNode(node.right);
+                case 'AndExpression':
+                    return !!(localEvalNode(node.left) && localEvalNode(node.right));
+                case 'OrExpression':
+                    return !!(localEvalNode(node.left) || localEvalNode(node.right));
+                case 'XorExpression':
+                    const leftVal = localEvalNode(node.left);
+                    const rightVal = localEvalNode(node.right);
+                    return !!((leftVal && !rightVal) || (!leftVal && rightVal));
+                case 'NotExpression':
+                    return !localEvalNode(node.operand);
+                case 'UnaryMinusExpression':
+                    return -localEvalNode(node.operand);
+                case 'TableLiteral':
+                    const table = {};
+                    let arrayIndex = 1;
+                    
+                    for (const entry of node.entries) {
+                        if (entry.key === null) {
+                            // Array-like entry: {1, 2, 3}
+                            table[arrayIndex] = localEvalNode(entry.value);
+                            arrayIndex++;
+                        } else {
+                            // Key-value entry: {name: "Alice", age: 30}
+                            let key;
+                            if (entry.key.type === 'Identifier') {
+                                // Convert identifier keys to strings
+                                key = entry.key.value;
+                            } else {
+                                // For other key types (numbers, strings), evaluate normally
+                                key = localEvalNode(entry.key);
+                            }
+                            const value = localEvalNode(entry.value);
+                            table[key] = value;
+                        }
+                    }
+                    
+                    return table;
+                case 'TableAccess':
+                    const tableValue = localEvalNode(node.table);
+                    let keyValue;
+                    
+                    // Handle different key types
+                    if (node.key.type === 'Identifier') {
+                        // For dot notation, use the identifier name as the key
+                        keyValue = node.key.value;
+                    } else {
+                        // For bracket notation, evaluate the key expression
+                        keyValue = localEvalNode(node.key);
+                    }
+                    
+                    if (typeof tableValue !== 'object' || tableValue === null) {
+                        throw new Error('Cannot access property of non-table value');
+                    }
+                    
+                    if (tableValue[keyValue] === undefined) {
+                        throw new Error(`Key '${keyValue}' not found in table`);
+                    }
+                    
+                    return tableValue[keyValue];
+                case 'AssignmentExpression':
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    globalScope[node.name] = localEvalNode(node.value);
+                    return;
+                case 'Identifier':
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined && node.value) {
+                        return node.value;
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.params.length; i++) {
+                                nestedScope[node.params[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionDefinition':
+                    // Create a function from the function definition
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.parameters.join(','));
+                        try {
+                            let nestedScope = Object.create(globalScope);
+                            for (let i = 0; i < node.parameters.length; i++) {
+                                nestedScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let localFunc;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        localFunc = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        localFunc = globalScope[node.name.value];
+                    } else if (node.name.type === 'TableAccess') {
+                        // Function call from table access (e.g., math.add)
+                        localFunc = localEvalNode(node.name);
+                    } else {
+                        throw new Error('Invalid function name in function call');
+                    }
+                    
+                    if (localFunc instanceof Function) {
+                        let args = node.args.map(localEvalNode);
+                        return localFunc(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'CaseExpression':
+                    const values = node.value.map(localEvalNode);
+                    
+                    for (const caseItem of node.cases) {
+                        const pattern = caseItem.pattern.map(localEvalNode);
+                        
+                        let matches = true;
+                        for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
+                            const value = values[i];
+                            const patternValue = pattern[i];
+                            
+                            if (patternValue === true) continue;
+                            
+                            if (value !== patternValue) {
+                                matches = false;
+                                break;
+                            }
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(localEvalNode);
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const readline = require('readline');
+                    const rl = readline.createInterface({
+                        input: process.stdin,
+                        output: process.stdout
+                    });
+                    
+                    return new Promise((resolve) => {
+                        rl.question('', (input) => {
+                            rl.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const localOutputValue = localEvalNode(node.value);
+                    console.log(localOutputValue);
+                    return localOutputValue;
+                case 'IOAssertExpression':
+                    const localAssertionValue = localEvalNode(node.value);
+                    if (!localAssertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return localAssertionValue;
+                case 'FunctionReference':
+                    const localFunctionValue = globalScope[node.name];
+                    if (localFunctionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof localFunctionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return localFunctionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return localEvalNode(node.body);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
+        }
+    };
+
+    let lastResult;
+    for (let node of ast.body) {
+        if (node) {
+            lastResult = evalNode(node);
+        }
+    }
+    
+    if (lastResult instanceof Promise) {
+        return lastResult.then(result => {
+            return result;
+        });
+    }
+    
+    return lastResult;
+}
+
+/**
+ * Debug logging utility function.
+ * 
+ * @param {string} message - Debug message to log
+ * @param {*} [data=null] - Optional data to log with the message
+ * 
+ * @description Logs debug messages to console when DEBUG environment variable is set.
+ * Provides verbose output during development while remaining silent in production.
+ * 
+ * @why Debug functions are gated by the DEBUG environment variable, allowing for 
+ * verbose output during development and silent operation in production. This 
+ * approach makes it easy to trace execution and diagnose issues without 
+ * cluttering normal output.
+ */
+function debugLog(message, data = null) {
+    if (process.env.DEBUG) {
+        console.log(`[DEBUG] ${message}`);
+        if (data) {
+            console.log(data);
+        }
+    }
 }
 
-// Usage
-// const tokens = lexer('2 + 2');
-// const ast = parser(tokens);
-// const result = interpreter(ast);
-// console.log(result); // 4
+/**
+ * Debug error logging utility function.
+ * 
+ * @param {string} message - Debug error message to log
+ * @param {Error} [error=null] - Optional error object to log
+ * 
+ * @description Logs debug error messages to console when DEBUG environment variable is set.
+ * Provides verbose error output during development while remaining silent in production.
+ * 
+ * @why Debug functions are gated by the DEBUG environment variable, allowing for 
+ * verbose output during development and silent operation in production. This 
+ * approach makes it easy to trace execution and diagnose issues without 
+ * cluttering normal output.
+ */
+function debugError(message, error = null) {
+    if (process.env.DEBUG) {
+        console.error(`[DEBUG ERROR] ${message}`);
+        if (error) {
+            console.error(error);
+        }
+    }
+}
 
-// const tokens2 = lexer('x : 2 + 2');
-// const ast2 = parser(tokens2);
-// const result2 = interpreter(ast2);
-// console.log(result2); 
+/**
+ * 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.
+ */
+const callStackTracker = {
+    stack: [],
+    maxDepth: 0,
+    callCounts: new Map(),
+    
+    /**
+     * Push a function call onto the stack
+     * @param {string} functionName - Name of the function being called
+     * @param {string} context - Context where the call is happening
+     */
+    push: function(functionName, context = '') {
+        const callInfo = { functionName, context, timestamp: Date.now() };
+        this.stack.push(callInfo);
+        
+        // Track maximum depth
+        if (this.stack.length > this.maxDepth) {
+            this.maxDepth = this.stack.length;
+        }
+        
+        // Count function calls
+        const key = `${functionName}${context ? `:${context}` : ''}`;
+        this.callCounts.set(key, (this.callCounts.get(key) || 0) + 1);
+        
+        // Check for potential infinite recursion
+        if (this.stack.length > 1000) {
+            console.error('=== POTENTIAL INFINITE RECURSION DETECTED ===');
+            console.error('Call stack depth:', this.stack.length);
+            console.error('Function call counts:', Object.fromEntries(this.callCounts));
+            console.error('Recent call stack:');
+            this.stack.slice(-10).forEach((call, i) => {
+                console.error(`  ${this.stack.length - 10 + i}: ${call.functionName}${call.context ? ` (${call.context})` : ''}`);
+            });
+            throw new Error(`Potential infinite recursion detected. Call stack depth: ${this.stack.length}`);
+        }
+        
+        if (process.env.DEBUG && this.stack.length % 100 === 0) {
+            console.log(`[DEBUG] Call stack depth: ${this.stack.length}, Max: ${this.maxDepth}`);
+        }
+    },
+    
+    /**
+     * Pop a function call from the stack
+     */
+    pop: function() {
+        return this.stack.pop();
+    },
+    
+    /**
+     * Get current stack depth
+     */
+    getDepth: function() {
+        return this.stack.length;
+    },
+    
+    /**
+     * Get call statistics
+     */
+    getStats: function() {
+        return {
+            currentDepth: this.stack.length,
+            maxDepth: this.maxDepth,
+            callCounts: Object.fromEntries(this.callCounts)
+        };
+    },
+    
+    /**
+     * Reset the tracker
+     */
+    reset: function() {
+        this.stack = [];
+        this.maxDepth = 0;
+        this.callCounts.clear();
+    }
+};
 
-const fs = require('fs');
+/**
+ * Reads a file, tokenizes, parses, and interprets it.
+ * 
+ * @param {string} filePath - Path to the file to execute
+ * @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.
+ * 
+ * @note Supports both synchronous and asynchronous execution, with proper
+ * error handling and process exit codes.
+ */
+function executeFile(filePath) {
+    try {
+        // Validate file extension
+        if (!filePath.endsWith('.txt')) {
+            throw new Error('Only .txt files are supported');
+        }
+        
+        const fs = require('fs');
+        const input = fs.readFileSync(filePath, 'utf8');
+        
+        debugLog('Input:', input);
+        
+        const tokens = lexer(input);
+        debugLog('Tokens:', tokens);
+        
+        const ast = parser(tokens);
+        debugLog('AST:', JSON.stringify(ast, null, 2));
+        
+        const result = interpreter(ast);
+        
+        if (result instanceof Promise) {
+            result.then(finalResult => {
+                if (finalResult !== undefined) {
+                    console.log(finalResult);
+                }
+                // Print call stack statistics after execution
+                const stats = callStackTracker.getStats();
+                console.log('\n=== CALL STACK STATISTICS ===');
+                console.log('Maximum call stack depth:', stats.maxDepth);
+                console.log('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+            }).catch(error => {
+                console.error(`Error executing file: ${error.message}`);
+                // Print call stack statistics on error
+                const stats = callStackTracker.getStats();
+                console.error('\n=== CALL STACK STATISTICS ON ERROR ===');
+                console.error('Maximum call stack depth:', stats.maxDepth);
+                console.error('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+                process.exit(1);
+            });
+        } else {
+            if (result !== undefined) {
+                console.log(result);
+            }
+            // Print call stack statistics after execution
+            const stats = callStackTracker.getStats();
+            console.log('\n=== CALL STACK STATISTICS ===');
+            console.log('Maximum call stack depth:', stats.maxDepth);
+            console.log('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+        }
+    } catch (error) {
+        console.error(`Error executing file: ${error.message}`);
+        // Print call stack statistics on error
+        const stats = callStackTracker.getStats();
+        console.error('\n=== CALL STACK STATISTICS ON ERROR ===');
+        console.error('Maximum call stack depth:', stats.maxDepth);
+        console.error('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+        process.exit(1);
+    }
+}
 
-// Read the input from a file
-const input = fs.readFileSync('input.txt', 'utf-8');
+/**
+ * CLI argument handling and program entry point.
+ * 
+ * @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 
+ * enforces this usage and provides helpful error messages for incorrect invocation.
+ * 
+ * @note Exits with appropriate error codes for different failure scenarios.
+ */
+const args = process.argv.slice(2);
 
-// Usage
-const tokens = lexer(input);
-const ast = parser(tokens);
-const result = interpreter(ast);
-console.log(result);
\ No newline at end of file
+if (args.length === 0) {
+    console.error('Usage: node lang.js <file>');
+    console.error('  Provide a file path to execute');
+    process.exit(1);
+} else if (args.length === 1) {
+    // Execute the file
+    const filePath = args[0];
+    executeFile(filePath);
+} else {
+    // Too many arguments
+    console.error('Usage: node lang.js <file>');
+    console.error('  Provide exactly one file path to execute');
+    process.exit(1);
+}
\ No newline at end of file