about summary refs log tree commit diff stats
path: root/js/scripting-lang/docs/baba-yaga/0.0.1/lang.js.html
diff options
context:
space:
mode:
Diffstat (limited to 'js/scripting-lang/docs/baba-yaga/0.0.1/lang.js.html')
-rw-r--r--js/scripting-lang/docs/baba-yaga/0.0.1/lang.js.html3074
1 files changed, 3074 insertions, 0 deletions
diff --git a/js/scripting-lang/docs/baba-yaga/0.0.1/lang.js.html b/js/scripting-lang/docs/baba-yaga/0.0.1/lang.js.html
new file mode 100644
index 0000000..27fe6d6
--- /dev/null
+++ b/js/scripting-lang/docs/baba-yaga/0.0.1/lang.js.html
@@ -0,0 +1,3074 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <title>lang.js - Documentation</title>
+
+    <script src="scripts/prettify/prettify.js"></script>
+    <script src="scripts/prettify/lang-css.js"></script>
+    <!--[if lt IE 9]>
+      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+    <![endif]-->
+    <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
+    <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
+    <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
+</head>
+<body>
+
+<input type="checkbox" id="nav-trigger" class="nav-trigger" />
+<label for="nav-trigger" class="navicon-button x">
+  <div class="navicon"></div>
+</label>
+
+<label for="nav-trigger" class="overlay"></label>
+
+<nav>
+    <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Tutorials</li><li class="nav-item"><a href="tutorial-00_Introduction.html">00_Introduction</a></li><li class="nav-item"><a href="tutorial-01_Function_Calls.html">01_Function_Calls</a></li><li class="nav-item"><a href="tutorial-02_Function_Composition.html">02_Function_Composition</a></li><li class="nav-item"><a href="tutorial-03_Table_Operations.html">03_Table_Operations</a></li><li class="nav-item"><a href="tutorial-04_Currying.html">04_Currying</a></li><li class="nav-item"><a href="tutorial-05_Pattern_Matching.html">05_Pattern_Matching</a></li><li class="nav-item"><a href="tutorial-06_Immutable_Tables.html">06_Immutable_Tables</a></li><li class="nav-item"><a href="tutorial-07_Function_References.html">07_Function_References</a></li><li class="nav-item"><a href="tutorial-08_Combinators.html">08_Combinators</a></li><li class="nav-item"><a href="tutorial-09_Expression_Based.html">09_Expression_Based</a></li><li class="nav-item"><a href="tutorial-10_Tables_Deep_Dive.html">10_Tables_Deep_Dive</a></li><li class="nav-item"><a href="tutorial-11_Standard_Library.html">11_Standard_Library</a></li><li class="nav-item"><a href="tutorial-12_IO_Operations.html">12_IO_Operations</a></li><li class="nav-item"><a href="tutorial-13_Error_Handling.html">13_Error_Handling</a></li><li class="nav-item"><a href="tutorial-14_Advanced_Combinators.html">14_Advanced_Combinators</a></li><li class="nav-item"><a href="tutorial-15_Integration_Patterns.html">15_Integration_Patterns</a></li><li class="nav-item"><a href="tutorial-16_Best_Practices.html">16_Best_Practices</a></li><li class="nav-item"><a href="tutorial-README.html">README</a></li><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-member">M</span><span class="nav-item-name"><a href="global.html#callStackTracker">callStackTracker</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#debugError">debugError</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#debugLog">debugLog</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#executeFile">executeFile</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#initializeStandardLibrary">initializeStandardLibrary</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#interpreter">interpreter</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#lexer">lexer</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#main">main</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#parser">parser</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#readFile">readFile</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#run">run</a></span></li>
+</nav>
+
+<div id="main">
+    
+    <h1 class="page-title">lang.js</h1>
+    
+
+    
+
+
+
+    
+    <section>
+        <article>
+            <pre class="prettyprint source linenums"><code>// Baba Yaga
+// Cross-platform scripting language implementation
+// Supports Node.js, Bun, and browser environments
+
+import { lexer, TokenType } from './lexer.js';
+import { parser } from './parser.js';
+
+// Cross-platform environment detection
+const isNode = typeof process !== 'undefined' &amp;&amp; process.versions &amp;&amp; process.versions.node;
+const isBun = typeof process !== 'undefined' &amp;&amp; process.versions &amp;&amp; process.versions.bun;
+const isBrowser = typeof window !== 'undefined' &amp;&amp; typeof document !== 'undefined';
+
+// Cross-platform debug flag
+const DEBUG = (isNode &amp;&amp; process.env.DEBUG) || (isBrowser &amp;&amp; window.DEBUG) || false;
+
+// Cross-platform IO operations
+const createReadline = () => {
+    if (isNode || isBun) {
+        const readline = require('readline');
+        return readline.createInterface({
+            input: process.stdin,
+            output: process.stdout
+        });
+    } else if (isBrowser) {
+        // Browser fallback - use prompt() for now
+        return {
+            question: (prompt, callback) => {
+                const result = window.prompt(prompt);
+                callback(result);
+            },
+            close: () => {}
+        };
+    } else {
+        // Fallback for other environments
+        return {
+            question: (prompt, callback) => {
+                callback("fallback input");
+            },
+            close: () => {}
+        };
+    }
+};
+
+const createFileSystem = () => {
+    if (isNode || isBun) {
+        return require('fs');
+    } else if (isBrowser) {
+        // Browser fallback - return a mock filesystem
+        return {
+            readFile: (path, encoding, callback) => {
+                callback(new Error('File system not available in browser'));
+            },
+            writeFile: (path, data, callback) => {
+                callback(new Error('File system not available in browser'));
+            }
+        };
+    } else {
+        // Fallback for other environments
+        return {
+            readFile: (path, encoding, callback) => {
+                callback(new Error('File system not available in this environment'));
+            },
+            writeFile: (path, data, callback) => {
+                callback(new Error('File system not available in this environment'));
+            }
+        };
+    }
+};
+
+// Cross-platform console output
+const safeConsoleLog = (message) => {
+    if (typeof console !== 'undefined') {
+        console.log(message);
+    }
+};
+
+const safeConsoleError = (message) => {
+    if (typeof console !== 'undefined') {
+        console.error(message);
+    }
+};
+
+// Cross-platform process exit
+const safeExit = (code) => {
+    if (isNode || isBun) {
+        process.exit(code);
+    } else if (isBrowser) {
+        // In browser, we can't exit, but we can throw an error or redirect
+        throw new Error(`Process would exit with code ${code}`);
+    }
+};
+
+/**
+ * Environment interface for external system integration
+ * 
+ * @typedef {Object} Environment
+ * @property {Function} getCurrentState - Returns the current state from external system
+ * @property {Function} emitValue - Sends a value to the external system
+ */
+
+/**
+ * Initializes the standard library in the provided scope.
+ * 
+ * @param {Object} scope - The global scope object to inject functions into
+ * @description Injects higher-order functions and combinator functions into the interpreter's global scope.
+ * These functions provide functional programming utilities and implement the combinator foundation
+ * that reduces parsing ambiguity by translating all operations to function calls.
+ * 
+ * The standard library includes:
+ * - Higher-order functions (map, compose, pipe, apply, filter, reduce, fold, curry)
+ * - Arithmetic combinators (add, subtract, multiply, divide, modulo, power, negate)
+ * - Comparison combinators (equals, notEquals, lessThan, greaterThan, lessEqual, greaterEqual)
+ * - Logical combinators (logicalAnd, logicalOr, logicalXor, logicalNot)
+ * - Enhanced combinators (identity, constant, flip, on, both, either)
+ * 
+ * This approach ensures that user code can access these functions as if they were built-in,
+ * without special syntax or reserved keywords. The combinator foundation allows the parser
+ * to translate all operators to function calls, eliminating ambiguity while preserving syntax.
+ * 
+ * Functions are written to check argument types at runtime since the language is dynamically
+ * typed and does not enforce arity or types at parse time. The combinator functions are
+ * designed to work seamlessly with the parser's operator translation, providing a consistent
+ * and extensible foundation for all language operations.
+ * 
+ * The standard library is the foundation of the combinator-based architecture. Each function
+ * is designed to support partial application, enabling currying patterns and function composition.
+ * This design choice enables functional programming patterns while maintaining
+ * simplicity and consistency across all operations.
+ * 
+ * Error handling is implemented at the function level, with clear error messages that help
+ * users understand what went wrong and how to fix it. This includes type checking for
+ * function arguments and validation of input data.
+ */
+function initializeStandardLibrary(scope) {
+
+    /**
+     * Map: Apply a function to a value or collection
+     * @param {Function} f - Function to apply
+     * @param {*} x - Value or collection to apply function to
+     * @returns {*} Result of applying f to x
+     * @throws {Error} When first argument is not a function
+     * @description The map function is a fundamental higher-order function that
+     * applies a transformation function to a value or collection. This enables
+     * functional programming patterns where data transformations are expressed
+     * as function applications rather than imperative operations.
+     * 
+     * The function implements APL-inspired element-wise operations for tables:
+     * when x is a table, map applies the function to each value while preserving
+     * the table structure and keys. This reduces the need for explicit loops
+     * and enables declarative data transformation patterns.
+     * 
+     * The function supports partial application: when called with only the function,
+     * it returns a new function that waits for the value. This enables currying
+     * patterns and function composition chains, which are essential for the
+     * combinator-based architecture where all operations are function calls.
+     * 
+     * This design choice aligns with the language's functional foundation and
+     * enables abstractions like `map @double numbers` to transform
+     * every element in a collection without explicit iteration.
+     * 
+     * The function is designed to be polymorphic, working with different data
+     * types including scalars, tables, and arrays. This flexibility enables
+     * consistent data transformation patterns across different data structures.
+     */
+    scope.map = function(f, x) {
+        if (typeof f !== 'function') {
+            throw new Error('map: first argument must be a function');
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.map(f, x);
+            };
+        }
+        
+        // Handle tables (APL-style element-wise operations)
+        if (typeof x === 'object' &amp;&amp; x !== null &amp;&amp; !Array.isArray(x)) {
+            const result = {};
+            for (const [key, value] of Object.entries(x)) {
+                result[key] = f(value);
+            }
+            return result;
+        }
+        
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.map(f);
+        }
+        
+        // Default: apply to single value
+        return f(x);
+    };
+    
+    /**
+     * Compose: Combine two functions into a new function (function composition)
+     * @param {Function} f - First function (outer function)
+     * @param {Function} [g] - Second function (optional for partial application)
+     * @returns {Function} Composed function or partially applied function
+     * @throws {Error} When first argument is not a function
+     * @description The compose function is a core functional programming primitive
+     * that combines two functions into a new function. This is the foundation
+     * for the 'via' operator in the language syntax, enabling natural function
+     * composition chains like `f via g via h`.
+     * 
+     * The function implements right-associative composition, meaning that
+     * compose(f, compose(g, h)) creates a function that applies h, then g, then f.
+     * This matches mathematical function composition notation (f ∘ g ∘ h) and
+     * enables natural reading of composition chains from right to left.
+     * 
+     * The 'via' operator translates to compose calls:
+     * - f via g → compose(f, g)
+     * - f via g via h → compose(f, compose(g, h))
+     * - f via g via h via i → compose(f, compose(g, compose(h, i)))
+     * 
+     * This right-associative behavior means that composition chains read naturally
+     * from right to left, matching mathematical notation where (f ∘ g ∘ h)(x) = f(g(h(x))).
+     * 
+     * Partial application support enables currying patterns where functions can
+     * be built incrementally. This is essential for the combinator-based architecture
+     * where operations are built from simple, composable functions.
+     * 
+     * Examples:
+     * - compose(double, increment)(5) → double(increment(5)) → double(6) → 12
+     * - compose(increment, double)(5) → increment(double(5)) → increment(10) → 11
+     * - double via increment 5 → compose(double, increment)(5) → 12
+     * - increment via double via square 3 → compose(increment, compose(double, square))(3) → 19
+     */
+    scope.compose = function(f, g) {
+        if (typeof f !== 'function') {
+            throw new Error(`compose: first argument must be a function, got ${typeof f}`);
+        }
+        
+        if (g === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(g) {
+                if (typeof g !== 'function') {
+                    throw new Error(`compose: second argument must be a function, got ${typeof g}`);
+                }
+                return function(x) {
+                    return f(g(x));
+                };
+            };
+        }
+        
+        if (typeof g !== 'function') {
+            throw new Error(`compose: second argument must be a function, got ${typeof g}`);
+        }
+        
+        return function(x) {
+            return f(g(x));
+        };
+    };
+    
+    /**
+     * Curry: Apply a function to arguments (simplified currying)
+     * @param {Function} f - Function to curry
+     * @param {*} x - First argument
+     * @param {*} y - Second argument
+     * @returns {*} Result of applying f to x and y
+     * @throws {Error} When first argument is not a function
+     * @description The curry function provides a simplified currying mechanism
+     * that allows functions to be applied to arguments incrementally. When called
+     * with fewer arguments than the function expects, it returns a new function
+     * that waits for the remaining arguments.
+     * 
+     * This function is designed to work with the parser's one-by-one argument
+     * application system, where multi-argument function calls are translated to
+     * nested apply calls. The nested partial application checks ensure that
+     * functions return partially applied functions until all arguments are received.
+     */
+    scope.curry = function(f, x, y) { 
+        if (typeof f !== 'function') {
+            throw new Error('curry: first argument must be a function');
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the remaining arguments
+            return function(x, y) {
+                if (y === undefined) {
+                    // Still partial application
+                    return function(y) {
+                        return f(x, y);
+                    };
+                }
+                return f(x, y);
+            };
+        }
+        
+        if (y === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(y) {
+                return f(x, y);
+            };
+        }
+        
+        // Full application: apply the function to all arguments
+        return f(x, y);
+    };
+    
+    /**
+     * Apply: Apply a function to an argument (explicit function application)
+     * @param {Function} f - Function to apply
+     * @param {*} x - Argument to apply function to
+     * @returns {*} Result of applying f to x
+     * @throws {Error} When first argument is not a function
+     * @description The apply function is the fundamental mechanism for function
+     * application in the language. It enables the juxtaposition-based function
+     * application syntax (f x) by providing an explicit function application
+     * primitive. This function is called by the parser whenever function
+     * application is detected, ensuring consistent semantics across all
+     * function calls.
+     * 
+     * This function is the core mechanism that enables the parser's juxtaposition
+     * detection. When the parser encounters `f x`, it generates `apply(f, x)`,
+     * which this function handles. This design reduces the need for special
+     * syntax for function calls while maintaining clear precedence rules.
+     * 
+     * The function supports partial application: when called with only the function,
+     * it returns a new function that waits for the argument. This enables the
+     * parser to build function application chains incrementally, supporting
+     * both immediate evaluation and deferred execution patterns.
+     * 
+     * This partial application support is essential for the parser's left-associative
+     * function application model, where `f g x` becomes `apply(apply(f, g), x)`.
+     * The nested partial application ensures that each step returns a function
+     * until all arguments are provided.
+     */
+    scope.apply = function(f, x) { 
+        if (typeof f !== 'function') {
+            throw new Error('apply: first argument must be a function');
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return f(x);
+            };
+        }
+        
+        // Full application: apply the function to the argument
+        return f(x);
+    };
+    
+    /**
+     * Pipe: Compose functions in left-to-right order (opposite of compose)
+     * @param {Function} f - First function
+     * @param {Function} [g] - Second function (optional for partial application)
+     * @returns {Function} Function that applies the functions in left-to-right order
+     * @throws {Error} When first argument is not a function
+     * @description The pipe function provides an alternative to compose that
+     * applies functions in left-to-right order, which is often more intuitive
+     * for data processing pipelines. This enables functional programming patterns
+     * where data flows through a series of transformations in a natural reading order.
+     * 
+     * The function implements left-associative composition, meaning that
+     * pipe(f, pipe(g, h)) creates a function that applies f, then g, then h.
+     * This is the opposite of compose and matches the natural reading order
+     * for data transformation pipelines, making it intuitive for programmers
+     * who think in terms of data flow from left to right.
+     * 
+     * Like compose, it supports partial application for currying patterns.
+      * This enables building transformation pipelines incrementally,
+ * which is essential for the combinator-based architecture where
+     * operations are built from simple, composable functions.
+     * 
+     * The left-associative design choice makes pipe ideal for data processing
+     * workflows where each step transforms the data and passes it to the next
+     * step, creating a natural pipeline that reads like a sequence of operations.
+     */
+    scope.pipe = function(f, g) {
+        if (typeof f !== 'function') {
+            throw new Error(`pipe: first argument must be a function, got ${typeof f}`);
+        }
+        
+        if (g === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(g) {
+                if (typeof g !== 'function') {
+                    throw new Error(`pipe: second argument must be a function, got ${typeof g}`);
+                }
+                return function(x) {
+                    return g(f(x));
+                };
+            };
+        }
+        
+        if (typeof g !== 'function') {
+            throw new Error(`pipe: second argument must be a function, got ${typeof g}`);
+        }
+        
+        return function(x) {
+            return g(f(x));
+        };
+    };
+    
+    /**
+     * Filter: Filter a value or collection based on a predicate
+     * @param {Function} p - Predicate function
+     * @param {*} x - Value or collection to test
+     * @returns {*|0} The value if predicate is true, filtered collection for tables, 0 otherwise
+     * @throws {Error} When first argument is not a function
+     * @description The filter function applies a predicate to a value or collection,
+     * returning the value if the predicate is true, or a filtered collection for tables.
+     * This enables functional programming patterns where data selection is expressed
+     * as predicate application rather than imperative filtering loops.
+     * 
+     * The function implements APL-inspired element-wise filtering for tables:
+     * when x is a table, filter applies the predicate to each value and returns
+     * a new table containing only the key-value pairs where the predicate returns true.
+     * This reduces the need for explicit loops and enables declarative data
+     * selection patterns.
+     * 
+     * The function supports partial application: when called with only the predicate,
+     * it returns a new function that waits for the value. This enables currying
+     * patterns and function composition chains, which are essential for the
+     * combinator-based architecture where all operations are function calls.
+     * 
+     * This design choice aligns with the language's functional foundation and
+     * enables abstractions like `filter @isEven numbers` to select
+     * elements from a collection without explicit iteration.
+     */
+    scope.filter = function(p, x) { 
+        if (typeof p !== 'function') {
+            throw new Error('filter: first argument must be a function');
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.filter(p, x);
+            };
+        }
+        
+        // Handle tables (APL-style element-wise filtering)
+        if (typeof x === 'object' &amp;&amp; x !== null &amp;&amp; !Array.isArray(x)) {
+            const result = {};
+            for (const [key, value] of Object.entries(x)) {
+                if (p(value)) {
+                    result[key] = value;
+                }
+            }
+            return result;
+        }
+        
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.filter(p);
+        }
+        
+        // Default: apply predicate to single value
+        return p(x) ? x : 0;
+    };
+    
+    /**
+     * Reduce: Reduce two values using a binary function
+     * @param {Function} f - Binary function
+     * @param {*} init - Initial value
+     * @param {*} x - Second value
+     * @returns {*} Result of applying f to init and x
+     * @throws {Error} When first argument is not a function
+     * @description The reduce function applies a binary function to an initial value
+     * and a second value, returning the result. This is a simplified version of
+     * traditional reduce that works with pairs of values rather than collections.
+     * 
+     * The function supports partial application with nested checks to handle the
+     * parser's one-by-one argument application system. When called with only the
+     * function, it returns a function that waits for the initial value. When called
+     * with the function and initial value, it returns a function that waits for
+     * the second value. This enables currying patterns and incremental function
+     * application.
+     */
+    scope.reduce = function(f, init, x) { 
+        if (DEBUG) {
+            safeConsoleLog(`[DEBUG] reduce: f =`, typeof f, f);
+            safeConsoleLog(`[DEBUG] reduce: init =`, init);
+            safeConsoleLog(`[DEBUG] reduce: x =`, x);
+        }
+        
+        if (typeof f !== 'function') {
+            throw new Error('reduce: first argument must be a function');
+        }
+        
+        if (init === undefined) {
+            // Partial application: return a function that waits for the remaining arguments
+            return function(init, x) {
+                if (DEBUG) {
+                    safeConsoleLog(`[DEBUG] reduce returned function: f =`, typeof f, f);
+                    safeConsoleLog(`[DEBUG] reduce returned function: init =`, init);
+                    safeConsoleLog(`[DEBUG] reduce returned function: x =`, x);
+                }
+                if (x === undefined) {
+                    // Still partial application
+                    return function(x) {
+                        return scope.reduce(f, init, x);
+                    };
+                }
+                return scope.reduce(f, init, x);
+            };
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(x) {
+                return scope.reduce(f, init, x);
+            };
+        }
+        
+        // Handle tables (reduce all values in the table)
+        if (typeof x === 'object' &amp;&amp; x !== null &amp;&amp; !Array.isArray(x)) {
+            let result = init;
+            for (const [key, value] of Object.entries(x)) {
+                result = f(result, value, key);
+            }
+            return result;
+        }
+        
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.reduce(f, init);
+        }
+        
+        // Default: apply the function to init and x (original behavior)
+        return f(init, x);
+    };
+    
+    /**
+     * Fold: Same as reduce, but more explicit about the folding direction
+     * @param {Function} f - Binary function
+     * @param {*} init - Initial value
+     * @param {*} x - Second value
+     * @returns {*} Result of applying f to init and x
+     * @throws {Error} When first argument is not a function
+     */
+    scope.fold = function(f, init, x) { 
+        if (typeof f !== 'function') {
+            throw new Error('fold: first argument must be a function');
+        }
+        
+        if (init === undefined) {
+            // Partial application: return a function that waits for the remaining arguments
+            return function(init, x) {
+                if (x === undefined) {
+                    // Still partial application
+                    return function(x) {
+                        return f(init, x);
+                    };
+                }
+                return f(init, x);
+            };
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(x) {
+                return f(init, x);
+            };
+        }
+        
+        // Full application: apply the function to all arguments
+        return f(init, x);
+    };
+    
+    // ===== ARITHMETIC COMBINATORS =====
+    
+    /**
+     * Add: Add two numbers
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Sum of x and y
+     * @description The add function is a fundamental arithmetic combinator that
+     * implements addition. This function is called by the parser when the '+'
+     * operator is encountered, translating `x + y` into `add(x, y)`.
+     * 
+     * As a combinator function, add supports partial application and can be used
+     * in function composition chains. This enables patterns like `map @add 10`
+     * to add 10 to every element in a collection, or `each @add table1 table2`
+     * for element-wise addition of corresponding table elements.
+     * 
+     * The function is designed to work seamlessly with the parser's operator
+     * translation system, providing consistent semantics for all arithmetic
+     * operations through the combinator foundation.
+     */
+    scope.add = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x + y;
+            };
+        }
+        return x + y;
+    };
+    
+    /**
+     * Subtract: Subtract second number from first
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Difference of x and y
+     */
+    scope.subtract = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x - y;
+            };
+        }
+        return x - y;
+    };
+    
+    /**
+     * Multiply: Multiply two numbers
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Product of x and y
+     * @description The multiply function is a fundamental arithmetic combinator that
+     * implements multiplication. This function is called by the parser when the '*'
+     * operator is encountered, translating `x * y` into `multiply(x, y)`.
+     * 
+     * As a combinator function, multiply supports partial application and can be used
+     * in function composition chains. This enables patterns like `map @multiply 2`
+     * to double every element in a collection, or `each @multiply table1 table2`
+     * for element-wise multiplication of corresponding table elements.
+     * 
+     * The function is designed to work seamlessly with the parser's operator
+     * translation system, providing consistent semantics for all arithmetic
+     * operations through the combinator foundation.
+     */
+    scope.multiply = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x * y;
+            };
+        }
+        return x * y;
+    };
+    
+    /**
+     * Divide: Divide first number by second
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Quotient of x and y
+     * @throws {Error} When second argument is zero
+     */
+    scope.divide = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                if (y === 0) {
+                    throw new Error('Division by zero');
+                }
+                return x / y;
+            };
+        }
+        if (y === 0) {
+            throw new Error('Division by zero');
+        }
+        return x / y;
+    };
+    
+    /**
+     * Modulo: Get remainder of division
+     * @param {number} x - First number
+     * @param {number} y - Second number
+     * @returns {number} Remainder of x divided by y
+     */
+    scope.modulo = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return x % y;
+            };
+        }
+        return x % y;
+    };
+    
+    /**
+     * Power: Raise first number to power of second
+     * @param {number} x - Base number
+     * @param {number} y - Exponent
+     * @returns {number} x raised to the power of y
+     */
+    scope.power = function(x, y) {
+        if (y === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(y) {
+                return Math.pow(x, y);
+            };
+        }
+        return Math.pow(x, y);
+    };
+    
+    /**
+     * Negate: Negate a number
+     * @param {number} x - Number to negate
+     * @returns {number} Negated value of x
+     */
+    scope.negate = function(x) {
+        return -x;
+    };
+    
+    // ===== COMPARISON COMBINATORS =====
+    
+    /**
+     * Equals: Check if two values are equal
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x equals y
+     */
+    scope.equals = function(x, y) {
+        return x === y;
+    };
+    
+    /**
+     * NotEquals: Check if two values are not equal
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x does not equal y
+     */
+    scope.notEquals = function(x, y) {
+        return x !== y;
+    };
+    
+    /**
+     * LessThan: Check if first value is less than second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x &lt; y
+     */
+    scope.lessThan = function(x, y) {
+        return x &lt; y;
+    };
+    
+    /**
+     * GreaterThan: Check if first value is greater than second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x > y
+     */
+    scope.greaterThan = function(x, y) {
+        return x > y;
+    };
+    
+    /**
+     * LessEqual: Check if first value is less than or equal to second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x &lt;= y
+     */
+    scope.lessEqual = function(x, y) {
+        return x &lt;= y;
+    };
+    
+    /**
+     * GreaterEqual: Check if first value is greater than or equal to second
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if x >= y
+     */
+    scope.greaterEqual = function(x, y) {
+        return x >= y;
+    };
+    
+    // ===== LOGICAL COMBINATORS =====
+    
+    /**
+     * LogicalAnd: Logical AND of two values
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if both x and y are truthy
+     */
+    scope.logicalAnd = function(x, y) {
+        return !!(x &amp;&amp; y);
+    };
+    
+    /**
+     * LogicalOr: Logical OR of two values
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if either x or y is truthy
+     */
+    scope.logicalOr = function(x, y) {
+        return !!(x || y);
+    };
+    
+    /**
+     * LogicalXor: Logical XOR of two values
+     * @param {*} x - First value
+     * @param {*} y - Second value
+     * @returns {boolean} True if exactly one of x or y is truthy
+     */
+    scope.logicalXor = function(x, y) {
+        return !!((x &amp;&amp; !y) || (!x &amp;&amp; y));
+    };
+    
+    /**
+     * LogicalNot: Logical NOT of a value
+     * @param {*} x - Value to negate
+     * @returns {boolean} True if x is falsy, false if x is truthy
+     */
+    scope.logicalNot = function(x) {
+        return !x;
+    };
+    
+    // ===== ASSIGNMENT COMBINATOR =====
+    
+    /**
+     * Assign: Assign a value to a variable name
+     * @param {string} name - Variable name
+     * @param {*} value - Value to assign
+     * @returns {*} The assigned value
+     * @throws {Error} When trying to reassign an immutable variable
+     * @note This function needs access to the global scope, so it will be
+     *       set up during interpreter initialization
+     */
+    // Note: assign will be set up in the interpreter with access to globalScope
+    
+    // ===== ENHANCED HIGHER-ORDER COMBINATORS =====
+    
+    /**
+     * Identity: Return the input unchanged
+     * @param {*} x - Any value
+     * @returns {*} The same value
+     */
+    scope.identity = function(x) {
+        return x;
+    };
+    
+    /**
+     * Constant: Create a function that always returns the same value
+     * @param {*} x - Value to return
+     * @param {*} [y] - Optional second argument (ignored)
+     * @returns {*} The value x, or a function if only one argument provided
+     */
+    scope.constant = function(x, y) {
+        if (arguments.length === 2) {
+            return x;
+        } else {
+            return function(y) {
+                return x;
+            };
+        }
+    };
+    
+    /**
+     * Flip: Flip the order of arguments for a binary function
+     * @param {Function} f - Binary function
+     * @param {*} [x] - Optional first argument
+     * @param {*} [y] - Optional second argument
+     * @returns {Function|*} Function with flipped argument order, or result if arguments provided
+     */
+    scope.flip = function(f, x, y) {
+        if (arguments.length === 3) {
+            return f(y, x);
+        } else {
+            return function(x, y) {
+                return f(y, x);
+            };
+        }
+    };
+    
+    /**
+     * On: Apply a function to the results of another function
+     * @param {Function} f - Outer function
+     * @param {Function} g - Inner function
+     * @returns {Function} Function that applies f to the results of g
+     */
+    scope.on = function(f, g) {
+        return function(x, y) {
+            return f(g(x), g(y));
+        };
+    };
+    
+    /**
+     * Both: Check if both predicates are true
+     * @param {Function} f - First predicate
+     * @param {Function} g - Second predicate
+     * @returns {Function} Function that returns true if both predicates are true
+     */
+    scope.both = function(f, g) {
+        return function(x) {
+            return f(x) &amp;&amp; g(x);
+        };
+    };
+    
+    /**
+     * Either: Check if either predicate is true
+     * @param {Function} f - First predicate
+     * @param {Function} g - Second predicate
+     * @returns {Function} Function that returns true if either predicate is true
+     */
+    scope.either = function(f, g) {
+        return function(x) {
+            return f(x) || g(x);
+        };
+    };
+    
+    /**
+     * Each: Multi-argument element-wise operations for tables and scalars
+     * @param {Function} f - Function to apply element-wise
+     * @param {*} x - First argument (table or scalar)
+     * @returns {Function|*} Function for partial application or result of element-wise application
+     * @throws {Error} When first argument is not a function
+     * @description The each combinator provides APL-inspired element-wise operations
+     * for multi-argument functions over table structures. This is the primary mechanism
+     * for combining multiple tables or tables with scalars in element-wise fashion.
+     * 
+     * The function is designed for multi-argument operations and aligns with the parser's
+     * apply mechanism. When x is a table, each returns a function that waits for the
+     * second argument (y), enabling the parser to build `apply(apply(each, f), x)` chains
+     * that resolve to element-wise operations when y is provided.
+     * 
+     * Key behaviors:
+     * - Table + Scalar: Applies f to each element of the table with the scalar as second argument
+     * - Table + Table: Applies f to corresponding elements from both tables
+     * - Scalar + Table: Uses map to apply f with the scalar as first argument to each table element
+     * - Scalar + Scalar: Falls back to normal function application for backward compatibility
+     * 
+     * This design choice enables multi-argument element-wise operations like
+     * `each @add table1 table2` for element-wise addition, while maintaining compatibility
+     * with the parser's two-argument apply model. The function is specifically designed
+     * for multi-argument operations, distinguishing it from map which is for single-table
+     * transformations.
+     */
+    scope.each = function(f, x) {
+        if (DEBUG) {
+            safeConsoleLog(`[DEBUG] each called with: f=${typeof f}, x=${typeof x}`);
+            safeConsoleLog(`[DEBUG] x value:`, x);
+        }
+        
+        if (typeof f !== 'function') {
+            throw new Error('each: first argument must be a function, got ' + typeof f);
+        }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.each(f, x);
+            };
+        }
+        
+        // Check if x is a table
+        const isXTable = typeof x === 'object' &amp;&amp; x !== null &amp;&amp; !Array.isArray(x);
+        
+        if (isXTable) {
+            // x is a table - always return a function that can handle the second argument
+            return function(y) {
+                // Check if y is a table
+                const isYTable = typeof y === 'object' &amp;&amp; y !== null &amp;&amp; !Array.isArray(y);
+                
+                if (!isYTable) {
+                    // x is a table, y is not a table - apply function to each element of x with y as second argument
+                    const result = {};
+                    for (const [key, value] of Object.entries(x)) {
+                        result[key] = f(value, y);
+                    }
+                    return result;
+                }
+                
+                // Both x and y are tables - they should have the same keys
+                const result = {};
+                for (const [key, value] of Object.entries(x)) {
+                    if (y.hasOwnProperty(key)) {
+                        result[key] = f(value, y[key]);
+                    }
+                }
+                return result;
+            };
+        }
+        
+        // x is not a table, return a function that waits for the second argument
+        return function(y) {
+            // Check if y is a table
+            const isYTable = typeof y === 'object' &amp;&amp; y !== null &amp;&amp; !Array.isArray(y);
+            
+            if (!isYTable) {
+                // No tables, apply normally (backward compatibility)
+                return f(x, y);
+            }
+            
+            // x is not a table, y is a table - use map
+            return scope.map(function(val) { return f(x, val); }, y);
+        };
+    };
+    
+    // ===== TABLE OPERATIONS NAMESPACE (t.) =====
+    
+    /**
+     * Table operations namespace (t.)
+     * @description Provides immutable table operations that always return new tables,
+     * never modifying the original. This namespace implements APL-inspired element-wise
+     * operations and functional table manipulation patterns.
+     * 
+     * All operations in this namespace are designed to work with the language's
+     * immutable data philosophy, where data transformations create new structures
+     * rather than modifying existing ones. This enables functional programming
+     * patterns and reduces side effects from table operations.
+     * 
+     * The namespace provides both basic table operations (get, set, delete, merge)
+     * and higher-order operations (map, filter, reduce) that work element-wise
+     * on table values. This design choice enables data transformation
+     * patterns while maintaining the functional programming principles of the language.
+     * 
+     * Key design principles:
+     * - Immutability: All operations return new tables, never modify originals
+     * - Element-wise operations: Functions operate on table values, not structure
+     * - Partial application: All functions support currying patterns
+     * - Functional consistency: Operations work with the combinator foundation
+     */
+    scope.t = {
+        /**
+         * Map: Apply a function to each value in a table
+         * @param {Function} f - Function to apply
+         * @param {Object} table - Table to map over
+         * @returns {Object} New table with transformed values
+         * @throws {Error} When first argument is not a function or second is not a table
+         */
+        map: function(f, table) {
+            if (typeof f !== 'function') {
+                throw new Error('t.map: first argument must be a function');
+            }
+            
+            if (table === undefined) {
+                // Partial application: return a function that waits for the table
+                return function(table) {
+                    return scope.t.map(f, table);
+                };
+            }
+            
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.map: second argument must be a table');
+            }
+            
+            const result = {};
+            for (const [key, value] of Object.entries(table)) {
+                result[key] = f(value);
+            }
+            return result;
+        },
+        
+        /**
+         * Filter: Filter table values based on a predicate
+         * @param {Function} p - Predicate function
+         * @param {Object} table - Table to filter
+         * @returns {Object} New table with only values that pass the predicate
+         * @throws {Error} When first argument is not a function or second is not a table
+         */
+        filter: function(p, table) {
+            if (typeof p !== 'function') {
+                throw new Error('t.filter: first argument must be a function');
+            }
+            
+            if (table === undefined) {
+                // Partial application: return a function that waits for the table
+                return function(table) {
+                    return scope.t.filter(p, table);
+                };
+            }
+            
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.filter: second argument must be a table');
+            }
+            
+            const result = {};
+            for (const [key, value] of Object.entries(table)) {
+                if (p(value)) {
+                    result[key] = value;
+                }
+            }
+            return result;
+        },
+        
+        /**
+         * Reduce: Reduce all values in a table using a binary function
+         * @param {Function} f - Binary function
+         * @param {*} init - Initial value
+         * @param {Object} table - Table to reduce
+         * @returns {*} Result of reducing all values
+         * @throws {Error} When first argument is not a function or third is not a table
+         */
+        reduce: function(f, init, table) {
+            if (typeof f !== 'function') {
+                throw new Error('t.reduce: first argument must be a function');
+            }
+            
+            if (init === undefined) {
+                // Partial application: return a function that waits for the remaining arguments
+                return function(init, table) {
+                    if (table === undefined) {
+                        // Still partial application
+                        return function(table) {
+                            return scope.t.reduce(f, init, table);
+                        };
+                    }
+                    return scope.t.reduce(f, init, table);
+                };
+            }
+            
+            if (table === undefined) {
+                // Partial application: return a function that waits for the table
+                return function(table) {
+                    return scope.t.reduce(f, init, table);
+                };
+            }
+            
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.reduce: third argument must be a table');
+            }
+            
+            let result = init;
+            for (const [key, value] of Object.entries(table)) {
+                result = f(result, value, key);
+            }
+            return result;
+        },
+        
+        /**
+         * Set: Immutably set a key-value pair in a table
+         * @param {Object} table - Table to modify
+         * @param {*} key - Key to set
+         * @param {*} value - Value to set
+         * @returns {Object} New table with the key-value pair set
+         * @throws {Error} When first argument is not a table
+         */
+        set: function(table, key, value) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.set: first argument must be a table');
+            }
+            
+            if (key === undefined) {
+                // Partial application: return a function that waits for the remaining arguments
+                return function(key, value) {
+                    if (value === undefined) {
+                        // Still partial application
+                        return function(value) {
+                            return scope.t.set(table, key, value);
+                        };
+                    }
+                    return scope.t.set(table, key, value);
+                };
+            }
+            
+            if (value === undefined) {
+                // Partial application: return a function that waits for the value
+                return function(value) {
+                    return scope.t.set(table, key, value);
+                };
+            }
+            
+            return { ...table, [key]: value };
+        },
+        
+        /**
+         * Delete: Immutably delete a key from a table
+         * @param {Object} table - Table to modify
+         * @param {*} key - Key to delete
+         * @returns {Object} New table without the specified key
+         * @throws {Error} When first argument is not a table
+         */
+        delete: function(table, key) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.delete: first argument must be a table');
+            }
+            
+            if (key === undefined) {
+                // Partial application: return a function that waits for the key
+                return function(key) {
+                    return scope.t.delete(table, key);
+                };
+            }
+            
+            const result = { ...table };
+            delete result[key];
+            return result;
+        },
+        
+        /**
+         * Merge: Immutably merge two tables
+         * @param {Object} table1 - First table
+         * @param {Object} table2 - Second table (values override table1)
+         * @returns {Object} New merged table
+         * @throws {Error} When either argument is not a table
+         */
+        merge: function(table1, table2) {
+            if (typeof table1 !== 'object' || table1 === null) {
+                throw new Error('t.merge: first argument must be a table');
+            }
+            
+            if (table2 === undefined) {
+                // Partial application: return a function that waits for the second table
+                return function(table2) {
+                    return scope.t.merge(table1, table2);
+                };
+            }
+            
+            if (typeof table2 !== 'object' || table2 === null) {
+                throw new Error('t.merge: second argument must be a table');
+            }
+            
+            return { ...table1, ...table2 };
+        },
+        
+        /**
+         * Pairs: Get all key-value pairs from a table
+         * @param {Object} table - Table to get pairs from
+         * @returns {Array} Array of [key, value] pairs
+         * @throws {Error} When argument is not a table
+         */
+        pairs: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.pairs: argument must be a table');
+            }
+            return Object.entries(table);
+        },
+        
+        /**
+         * Keys: Get all keys from a table
+         * @param {Object} table - Table to get keys from
+         * @returns {Array} Array of keys
+         * @throws {Error} When argument is not a table
+         */
+        keys: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.keys: argument must be a table');
+            }
+            return Object.keys(table);
+        },
+        
+        /**
+         * Values: Get all values from a table
+         * @param {Object} table - Table to get values from
+         * @returns {Array} Array of values
+         * @throws {Error} When argument is not a table
+         */
+        values: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.values: argument must be a table');
+            }
+            return Object.values(table);
+        },
+        
+        /**
+         * Length: Get the number of key-value pairs in a table
+         * @param {Object} table - Table to measure
+         * @returns {number} Number of key-value pairs
+         * @throws {Error} When argument is not a table
+         */
+        length: function(table) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.length: argument must be a table');
+            }
+            return Object.keys(table).length;
+        },
+        
+        /**
+         * Has: Check if a table has a specific key
+         * @param {Object} table - Table to check
+         * @param {*} key - Key to check for
+         * @returns {boolean} True if key exists, false otherwise
+         * @throws {Error} When first argument is not a table
+         */
+        has: function(table, key) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.has: first argument must be a table');
+            }
+            
+            if (key === undefined) {
+                // Partial application: return a function that waits for the key
+                return function(key) {
+                    return scope.t.has(table, key);
+                };
+            }
+            
+            return table.hasOwnProperty(key);
+        },
+        
+        /**
+         * Get: Safely get a value from a table with optional default
+         * @param {Object} table - Table to get from
+         * @param {*} key - Key to get
+         * @param {*} defaultValue - Default value if key doesn't exist
+         * @returns {*} Value at key or default value
+         * @throws {Error} When first argument is not a table
+         */
+        get: function(table, key, defaultValue) {
+            if (typeof table !== 'object' || table === null) {
+                throw new Error('t.get: first argument must be a table');
+            }
+            
+            if (key === undefined) {
+                // Partial application: return a function that waits for the remaining arguments
+                return function(key, defaultValue) {
+                    if (defaultValue === undefined) {
+                        // Still partial application
+                        return function(defaultValue) {
+                            return scope.t.get(table, key, defaultValue);
+                        };
+                    }
+                    return scope.t.get(table, key, defaultValue);
+                };
+            }
+            
+            if (defaultValue === undefined) {
+                // Partial application: return a function that waits for the default value
+                return function(defaultValue) {
+                    return scope.t.get(table, key, defaultValue);
+                };
+            }
+            
+            return table.hasOwnProperty(key) ? table[key] : defaultValue;
+        }
+    };
+}
+
+/**
+ * Interpreter: Walks the AST and evaluates each node using the combinator foundation.
+ * 
+ * @param {ASTNode} ast - Abstract Syntax Tree to evaluate
+ * @param {Environment} [environment=null] - External environment for IO operations
+ * @param {Object} [initialState={}] - Initial state for the interpreter
+ * @returns {*} The result of evaluating the AST, or a Promise for async operations
+ * @throws {Error} For evaluation errors like division by zero, undefined variables, etc.
+ * 
+ * @description Evaluates an AST by walking through each node and performing the
+ * corresponding operations. Manages scope, handles function calls, and supports
+ * both synchronous and asynchronous operations.
+ * 
+ * The interpreter implements a combinator-based architecture where all operations
+ * are executed through function calls to standard library combinators. This design
+ * reduces parsing ambiguity while preserving intuitive syntax. The parser translates
+ * all operators (+, -, *, /, etc.) into FunctionCall nodes that reference combinator
+ * functions, ensuring consistent semantics across all operations.
+ * 
+ * Key architectural features:
+ * - Combinator Foundation: All operations are function calls to standard library combinators
+ * - Scope Management: Prototypal inheritance for variable lookup and function definitions
+ * - Forward Declaration: Recursive functions are supported through placeholder creation
+ * - Error Handling: Comprehensive error detection and reporting with call stack tracking
+ * - Debug Support: Optional debug mode for development and troubleshooting
+ * - IO Operations: Support for input/output operations through environment interface
+ * 
+ * The interpreter processes legacy operator expressions (PlusExpression, MinusExpression, etc.)
+ * for backward compatibility, but the parser now generates FunctionCall nodes for all operators,
+ * which are handled by the standard library combinator functions. This ensures that all
+ * operations follow the same execution model and can be extended by adding new combinator
+ * functions to the standard library.
+ * 
+ * The interpreter uses a global scope for variable storage and function definitions.
+ * Each function call creates a new scope (using prototypal inheritance) to implement 
+ * lexical scoping. Immutability is enforced by preventing reassignment in the 
+ * global scope.
+ * 
+ * The interpreter is split into three functions: evalNode (global), 
+ * localEvalNodeWithScope (for function bodies), and localEvalNode (for internal 
+ * recursion). This separation allows for correct scope handling and easier debugging.
+ * 
+ * Recursive function support is implemented using a forward declaration pattern:
+ * a placeholder function is created in the global scope before evaluation, allowing
+ * the function body to reference itself during evaluation.
+ * 
+ * The combinator foundation ensures that all operations are executed through
+ * function calls, providing a consistent and extensible execution model. This
+ * approach enables abstractions and reduces the need for special
+ * handling of different operator types in the interpreter.
+ * 
+ * The interpreter supports both synchronous and asynchronous operations. IO operations
+ * like input and output can return Promises, allowing for non-blocking execution
+ * when interacting with external systems or user input.
+ */
+function interpreter(ast, environment = null, initialState = {}) {
+    const globalScope = { ...initialState };
+    initializeStandardLibrary(globalScope);
+    
+    // Track whether any IO operations have been performed
+    let ioOperationsPerformed = false;
+    
+    // Debug: Check if combinators are available
+    if (DEBUG) {
+        safeConsoleLog('[DEBUG] Available functions in global scope:', Object.keys(globalScope));
+        safeConsoleLog('[DEBUG] add function exists:', typeof globalScope.add === 'function');
+        safeConsoleLog('[DEBUG] subtract function exists:', typeof globalScope.subtract === 'function');
+    }
+    
+    // Reset call stack tracker at the start of interpretation
+    callStackTracker.reset();
+    
+    /**
+     * Evaluates AST nodes in the global scope using the combinator foundation.
+     * 
+     * @param {ASTNode} node - AST node to evaluate
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Main evaluation function that handles all node types in the
+     * global scope context. This function processes the core language constructs
+     * and delegates to combinator functions for all operations.
+     * 
+     * The function implements the forward declaration pattern for recursive functions:
+     * when a function assignment is detected, a placeholder is created in the global
+     * scope before evaluation, allowing the function body to reference itself.
+     * This pattern enables natural recursive function definitions without requiring
+     * special syntax or pre-declaration.
+     * 
+     * This function is the primary entry point for AST evaluation and handles
+     * all the core language constructs including literals, operators (translated
+     * to combinator calls), function definitions, and control structures. It
+     * ensures that all operations are executed through the combinator foundation,
+     * providing consistent semantics across the language.
+     * 
+     * The function processes legacy operator expressions (PlusExpression, MinusExpression, etc.)
+     * for backward compatibility, but the parser now generates FunctionCall nodes for
+     * all operators, which are handled by the standard library combinator functions.
+     * This design ensures that all operations follow the same execution model and
+     * can be extended by adding new combinator functions to the standard library.
+     * 
+     * Key evaluation patterns:
+     * - Literals: Direct value return
+     * - FunctionCall: Delegates to standard library combinator functions
+     * - Assignment: Creates variables in global scope with forward declaration support
+     * - WhenExpression: Pattern matching with wildcard support
+     * - TableLiteral: Creates immutable table structures
+     * - TableAccess: Safe property access with error handling
+     * - IO Operations: Handles input/output through environment interface
+     * 
+     * The function maintains call stack tracking for debugging and error reporting.
+     * This enables detailed error messages that include the call chain leading to
+     * the error, making it easier to debug programs.
+     * 
+     * Error handling is comprehensive, with specific error messages for common
+     * issues like undefined variables, type mismatches, and division by zero.
+     * Each error includes context about where the error occurred and what was
+     * expected, helping users quickly identify and fix issues.
+     */
+    function evalNode(node) {
+        callStackTracker.push('evalNode', node?.type || 'unknown');
+        
+        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) &lt; evalNode(node.right);
+                case 'GreaterThanExpression':
+                    return evalNode(node.left) > evalNode(node.right);
+                case 'LessEqualExpression':
+                    return evalNode(node.left) &lt;= 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) &amp;&amp; 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 &amp;&amp; !rightVal) || (!leftVal &amp;&amp; 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);
+                            }
+                            // Special handling for FunctionDeclaration nodes
+                            if (DEBUG) {
+                                safeConsoleLog(`[DEBUG] TableLiteral: entry.value.type = ${entry.value.type}`);
+                            }
+                            if (entry.value.type === 'FunctionDeclaration') {
+                                // Don't evaluate the function body, just create the function
+                                const func = function(...args) {
+                                    callStackTracker.push('FunctionCall', entry.value.params.join(','));
+                                    try {
+                                        // If we have fewer arguments than parameters, return a curried function
+                                        if (args.length &lt; entry.value.params.length) {
+                                            return function(...moreArgs) {
+                                                const allArgs = [...args, ...moreArgs];
+                                                if (allArgs.length &lt; entry.value.params.length) {
+                                                    // Still not enough arguments, curry again
+                                                    return function(...evenMoreArgs) {
+                                                        const finalArgs = [...allArgs, ...evenMoreArgs];
+                                                        let localScope = Object.create(globalScope);
+                                                        for (let i = 0; i &lt; entry.value.params.length; i++) {
+                                                            localScope[entry.value.params[i]] = finalArgs[i];
+                                                        }
+                                                        return localEvalNodeWithScope(entry.value.body, localScope);
+                                                    };
+                                                } else {
+                                                    // We have enough arguments now
+                                                    let localScope = Object.create(globalScope);
+                                                    for (let i = 0; i &lt; entry.value.params.length; i++) {
+                                                        localScope[entry.value.params[i]] = allArgs[i];
+                                                    }
+                                                    return localEvalNodeWithScope(entry.value.body, localScope);
+                                                }
+                                            };
+                                        } else {
+                                            // We have enough arguments, evaluate the function
+                                            let localScope = Object.create(globalScope);
+                                            for (let i = 0; i &lt; entry.value.params.length; i++) {
+                                                localScope[entry.value.params[i]] = args[i];
+                                            }
+                                            return localEvalNodeWithScope(entry.value.body, localScope);
+                                        }
+                                    } finally {
+                                        callStackTracker.pop();
+                                    }
+                                };
+                                table[key] = func;
+                            } else {
+                                const value = evalNode(entry.value);
+                                table[key] = value;
+                            }
+                        }
+                    }
+                    
+                    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':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.name} is not yet fully defined`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
+                        
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = evalNode(node.value);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.name] = actualFunction;
+                        return;
+                    }
+                    
+                    const value = evalNode(node.value);
+                    globalScope[node.name] = value;
+                    return;
+                case 'Assignment':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.identifier)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.identifier}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.identifier} is not yet fully defined`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.identifier] = placeholder;
+                        
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = evalNode(node.value);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.identifier] = actualFunction;
+                        return;
+                    }
+                    
+                    const assignmentValue = evalNode(node.value);
+                    globalScope[node.identifier] = assignmentValue;
+                    return;
+                case 'Identifier':
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined) {
+                        throw new Error(`Variable ${node.value} is not defined`);
+                    }
+                    return identifierValue;
+                case 'FunctionDeclaration':
+                    // For anonymous functions, the name comes from the assignment
+                    // The function itself doesn't have a name, so we just return
+                    // The assignment will handle storing it in the global scope
+                    return function(...args) {
+                        callStackTracker.push('FunctionCall', node.params.join(','));
+                        try {
+                            // If we have fewer arguments than parameters, return a curried function
+                            if (args.length &lt; node.params.length) {
+                                return function(...moreArgs) {
+                                    const allArgs = [...args, ...moreArgs];
+                                    if (allArgs.length &lt; node.params.length) {
+                                        // Still not enough arguments, curry again
+                                        return function(...evenMoreArgs) {
+                                            const finalArgs = [...allArgs, ...evenMoreArgs];
+                                            let localScope = Object.create(globalScope);
+                                            for (let i = 0; i &lt; node.params.length; i++) {
+                                                localScope[node.params[i]] = finalArgs[i];
+                                            }
+                                            return localEvalNodeWithScope(node.body, localScope);
+                                        };
+                                    } else {
+                                        // We have enough arguments now
+                                        let localScope = Object.create(globalScope);
+                                        for (let i = 0; i &lt; node.params.length; i++) {
+                                            localScope[node.params[i]] = allArgs[i];
+                                        }
+                                        return localEvalNodeWithScope(node.body, localScope);
+                                    }
+                                };
+                            } else {
+                                // We have enough arguments, evaluate the function
+                                let localScope = Object.create(globalScope);
+                                for (let i = 0; i &lt; 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 &lt; node.parameters.length; i++) {
+                                localScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, localScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let funcToCall;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        funcToCall = globalScope[node.name];
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: looking up function '${node.name}' in globalScope, found:`, typeof funcToCall);
+                        }
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        funcToCall = globalScope[node.name.value];
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: looking up function '${node.name.value}' in globalScope, found:`, typeof funcToCall);
+                        }
+                    } else {
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        funcToCall = evalNode(node.name);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: evaluated function expression, found:`, typeof funcToCall);
+                        }
+                    }
+                    
+                    if (typeof funcToCall === 'function') {
+                        let args = node.args.map(evalNode);
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] FunctionCall: calling function with args:`, args);
+                        }
+                        return funcToCall(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(evalNode) 
+                        : [evalNode(node.value)];
+                    
+                    if (DEBUG) {
+                        safeConsoleLog(`[DEBUG] WhenExpression: whenValues =`, whenValues);
+                    }
+                    
+                    for (const caseItem of node.cases) {
+                        // Handle both single patterns and arrays of patterns
+                        const patterns = caseItem.pattern.map(evalNode);
+                        
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] WhenExpression: patterns =`, patterns);
+                        }
+                        
+                        // Check if patterns match the values
+                        let matches = true;
+                        if (whenValues.length !== patterns.length) {
+                            matches = false;
+                        } else {
+                            for (let i = 0; i &lt; whenValues.length; i++) {
+                                const value = whenValues[i];
+                                const pattern = patterns[i];
+                                
+                                if (DEBUG) {
+                                    safeConsoleLog(`[DEBUG] WhenExpression: comparing value ${value} with pattern ${pattern}`);
+                                }
+                                
+                                if (pattern === true) { // Wildcard pattern
+                                    // Wildcard always matches
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: wildcard matches`);
+                                    }
+                                    continue;
+                                } else if (typeof pattern === 'object' &amp;&amp; pattern.type === 'FunctionCall') {
+                                    // This is a boolean expression pattern (e.g., x &lt; 0)
+                                    // We need to substitute the current value for the pattern variable
+                                    // For now, let's assume the pattern variable is the first identifier in the function call
+                                    let patternToEvaluate = pattern;
+                                    if (pattern.args &amp;&amp; pattern.args.length > 0 &amp;&amp; pattern.args[0].type === 'Identifier') {
+                                        // Create a copy of the pattern with the current value substituted
+                                        patternToEvaluate = {
+                                            ...pattern,
+                                            args: [value, ...pattern.args.slice(1)]
+                                        };
+                                    }
+                                    const patternResult = evalNode(patternToEvaluate);
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: boolean pattern result = ${patternResult}`);
+                                    }
+                                    if (!patternResult) {
+                                        matches = false;
+                                        if (DEBUG) {
+                                            safeConsoleLog(`[DEBUG] WhenExpression: boolean pattern does not match`);
+                                        }
+                                        break;
+                                                                            } else {
+                                            if (DEBUG) {
+                                                safeConsoleLog(`[DEBUG] WhenExpression: boolean pattern matches`);
+                                            }
+                                        }
+                                } else if (typeof pattern === 'object' &amp;&amp; pattern !== null &amp;&amp; typeof value === 'object' &amp;&amp; value !== null) {
+                                    // Table pattern matching - check if all pattern properties exist in value
+                                    let tableMatches = true;
+                                    for (const key in pattern) {
+                                        if (pattern.hasOwnProperty(key) &amp;&amp; (!value.hasOwnProperty(key) || value[key] !== pattern[key])) {
+                                            tableMatches = false;
+                                            break;
+                                        }
+                                    }
+                                    if (!tableMatches) {
+                                        matches = false;
+                                        if (DEBUG) {
+                                            safeConsoleLog(`[DEBUG] WhenExpression: table pattern does not match`);
+                                        }
+                                        break;
+                                                                            } else {
+                                            if (DEBUG) {
+                                                safeConsoleLog(`[DEBUG] WhenExpression: table pattern matches`);
+                                            }
+                                        }
+                                } else if (value !== pattern) {
+                                    matches = false;
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: pattern does not match`);
+                                    }
+                                    break;
+                                } else {
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] WhenExpression: pattern matches`);
+                                    }
+                                }
+                            }
+                        }
+                        
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] WhenExpression: case matches = ${matches}`);
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(evalNode);
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const rl = createReadline();
+                    
+                    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);
+                    safeConsoleLog(outputValue);
+                    ioOperationsPerformed = true;
+                    return outputValue;
+                case 'IOAssertExpression':
+                    const assertionValue = evalNode(node.value);
+                    if (!assertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return assertionValue;
+                case 'IOListenExpression':
+                    // Return current state from environment if available, otherwise placeholder
+                    if (environment &amp;&amp; typeof environment.getCurrentState === 'function') {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning state from environment');
+                        }
+                        return environment.getCurrentState();
+                    } else {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning placeholder state');
+                        }
+                        return { status: 'placeholder', message: 'State not available in standalone mode' };
+                    }
+                case 'IOEmitExpression':
+                    const emitValue = evalNode(node.value);
+                    // Send value to environment if available, otherwise log to console
+                    if (environment &amp;&amp; typeof environment.emitValue === 'function') {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..emit called - sending to environment');
+                        }
+                        environment.emitValue(emitValue);
+                    } else {
+                        safeConsoleLog('[EMIT]', emitValue);
+                    }
+                    ioOperationsPerformed = true;
+                    return emitValue;
+                case 'FunctionReference':
+                    const functionValue = globalScope[node.name];
+                    if (DEBUG) {
+                        safeConsoleLog(`[DEBUG] FunctionReference: looking up '${node.name}' in globalScope, found:`, typeof functionValue);
+                    }
+                    if (functionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof functionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return functionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return evalNode(node.body);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
+        }
+    }
+
+    /**
+     * Evaluates AST nodes in a local scope with access to parent scope.
+     * 
+     * @param {ASTNode} node - AST node to evaluate
+     * @param {Object} scope - Local scope object (prototypally inherits from global)
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Used for evaluating function bodies and other expressions
+     * that need access to both local and global variables. This function implements
+     * lexical scoping by creating a local scope that prototypally inherits from
+     * the global scope, allowing access to both local parameters and global functions.
+     * 
+     * The function handles the same node types as evalNode but uses the local scope
+     * for variable lookups. It also implements the forward declaration pattern for
+     * recursive functions, ensuring that function definitions can reference themselves
+     * during evaluation.
+     * 
+     * This separation of global and local evaluation allows for proper scope management
+     * and prevents variable name conflicts between function parameters and global variables.
+     * 
+     * The function prioritizes local scope lookups over global scope lookups, ensuring
+     * that function parameters shadow global variables with the same names. This
+     * implements proper lexical scoping semantics.
+     * 
+     * The function maintains the same call stack tracking as evalNode, enabling
+     * consistent debugging and error reporting across both global and local evaluation.
+     * This ensures that errors in function bodies can be traced back to their source
+     * with the same level of detail as global errors.
+     * 
+     * Scope management is implemented using JavaScript's prototypal inheritance,
+     * where each local scope is created as an object that inherits from the global
+     * scope. This approach provides efficient variable lookup while maintaining
+     * proper scoping semantics and enabling access to global functions and variables.
+     */
+    const localEvalNodeWithScope = (node, scope) => {
+        callStackTracker.push('localEvalNodeWithScope', node?.type || 'unknown');
+        
+        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) &lt; localEvalNodeWithScope(node.right, scope);
+                case 'GreaterThanExpression':
+                    return localEvalNodeWithScope(node.left, scope) > localEvalNodeWithScope(node.right, scope);
+                case 'LessEqualExpression':
+                    return localEvalNodeWithScope(node.left, scope) &lt;= 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) &amp;&amp; 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 &amp;&amp; !rightVal) || (!leftVal &amp;&amp; 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':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.name} is not yet fully defined`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
+                        
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = localEvalNodeWithScope(node.value, scope);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.name] = actualFunction;
+                        return;
+                    }
+                    
+                    globalScope[node.name] = localEvalNodeWithScope(node.value, scope);
+                    return;
+                case 'Identifier':
+                    // First check local scope, then global scope
+                    if (scope &amp;&amp; scope.hasOwnProperty(node.value)) {
+                        return scope[node.value];
+                    }
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined &amp;&amp; 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 &lt; 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 &lt; node.parameters.length; i++) {
+                                nestedScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let localFunc;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        localFunc = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        localFunc = globalScope[node.name.value];
+                    } else {
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        localFunc = localEvalNodeWithScope(node.name, scope);
+                    }
+                    
+                    if (localFunc instanceof Function) {
+                        let args = node.args.map(arg => localEvalNodeWithScope(arg, scope));
+                        return localFunc(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(val => localEvalNodeWithScope(val, scope)) 
+                        : [localEvalNodeWithScope(node.value, scope)];
+                    
+                    if (DEBUG) {
+                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: whenValues =`, whenValues);
+                    }
+                    
+                    for (const caseItem of node.cases) {
+                        // Handle both single patterns and arrays of patterns
+                        const patterns = caseItem.pattern.map(pat => localEvalNodeWithScope(pat, scope));
+                        
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: patterns =`, patterns);
+                        }
+                        
+                        // Check if patterns match the values
+                        let matches = true;
+                        if (whenValues.length !== patterns.length) {
+                            matches = false;
+                        } else {
+                            for (let i = 0; i &lt; whenValues.length; i++) {
+                                const value = whenValues[i];
+                                const pattern = patterns[i];
+                                
+                                if (DEBUG) {
+                                    safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: comparing value ${value} with pattern ${pattern}`);
+                                }
+                                
+                                if (pattern === true) { // Wildcard pattern
+                                    // Wildcard always matches
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: wildcard matches`);
+                                    }
+                                    continue;
+                                } else if (typeof pattern === 'object' &amp;&amp; pattern !== null &amp;&amp; typeof value === 'object' &amp;&amp; value !== null) {
+                                    // Table pattern matching - check if all pattern properties exist in value
+                                    let tableMatches = true;
+                                    for (const key in pattern) {
+                                        if (pattern.hasOwnProperty(key) &amp;&amp; (!value.hasOwnProperty(key) || value[key] !== pattern[key])) {
+                                            tableMatches = false;
+                                            break;
+                                        }
+                                    }
+                                    if (!tableMatches) {
+                                        matches = false;
+                                        if (DEBUG) {
+                                            safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern does not match`);
+                                        }
+                                        break;
+                                                                            } else {
+                                            if (DEBUG) {
+                                                safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: table pattern matches`);
+                                            }
+                                        }
+                                } else if (value !== pattern) {
+                                    matches = false;
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern does not match`);
+                                    }
+                                    break;
+                                } else {
+                                    if (DEBUG) {
+                                        safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: pattern matches`);
+                                    }
+                                }
+                            }
+                        }
+                        
+                        if (DEBUG) {
+                            safeConsoleLog(`[DEBUG] localEvalNodeWithScope WhenExpression: case matches = ${matches}`);
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(res => localEvalNodeWithScope(res, scope));
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const rl2 = createReadline();
+                    
+                    return new Promise((resolve) => {
+                        rl2.question('', (input) => {
+                            rl2.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const localOutputValue = localEvalNodeWithScope(node.value, scope);
+                    safeConsoleLog(localOutputValue);
+                    ioOperationsPerformed = true;
+                    return localOutputValue;
+                case 'IOAssertExpression':
+                    const localAssertionValue = localEvalNodeWithScope(node.value, scope);
+                    if (!localAssertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return localAssertionValue;
+                case 'IOListenExpression':
+                    // Return current state from environment if available, otherwise placeholder
+                    if (environment &amp;&amp; typeof environment.getCurrentState === 'function') {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning state from environment');
+                        }
+                        return environment.getCurrentState();
+                    } else {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning placeholder state');
+                        }
+                        return { status: 'placeholder', message: 'State not available in standalone mode' };
+                    }
+                case 'IOEmitExpression':
+                    const localEmitValue = localEvalNodeWithScope(node.value, scope);
+                    // Send value to environment if available, otherwise log to console
+                    if (environment &amp;&amp; typeof environment.emitValue === 'function') {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..emit called - sending to environment');
+                        }
+                        environment.emitValue(localEmitValue);
+                    } else {
+                        safeConsoleLog('[EMIT]', localEmitValue);
+                    }
+                    ioOperationsPerformed = true;
+                    return localEmitValue;
+                case 'FunctionReference':
+                    const localFunctionValue = globalScope[node.name];
+                    if (localFunctionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof localFunctionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return localFunctionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return localEvalNodeWithScope(node.body, scope);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
+        }
+    };
+
+    /**
+     * Evaluates AST nodes in the global scope (internal recursion helper).
+     * 
+     * @param {Object} node - AST node to evaluate
+     * @returns {*} The result of evaluating the node
+     * @throws {Error} For evaluation errors
+     * 
+     * @description Internal helper function for recursive evaluation that
+     * always uses the global scope. This function is used to avoid circular
+     * dependencies and infinite recursion when evaluating nested expressions
+     * that need access to the global scope.
+     * 
+     * This function duplicates the logic of evalNode but without the scope
+     * parameter, ensuring that all variable lookups go through the global scope.
+     * It's primarily used for evaluating function bodies and other expressions
+     * that need to be isolated from local scope contexts.
+     * 
+     * The function also implements the forward declaration pattern for recursive
+     * functions, maintaining consistency with the other evaluation functions.
+     * 
+     * This function is essential for preventing scope pollution when evaluating
+     * nested expressions that should not inherit local scope variables, ensuring
+     * that global functions and variables are always accessible regardless of
+     * the current evaluation context.
+     */
+    const localEvalNode = (node) => {
+        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) &lt; localEvalNode(node.right);
+                case 'GreaterThanExpression':
+                    return localEvalNode(node.left) > localEvalNode(node.right);
+                case 'LessEqualExpression':
+                    return localEvalNode(node.left) &lt;= 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) &amp;&amp; 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 &amp;&amp; !rightVal) || (!leftVal &amp;&amp; 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':
+                    // Prevent reassignment of standard library functions
+                    if (globalScope.hasOwnProperty(node.name)) {
+                        throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                    }
+                    
+                    // Check if this is a function assignment for potential recursion
+                    if (node.value.type === 'FunctionDefinition' || node.value.type === 'FunctionDeclaration') {
+                        // Create a placeholder function that will be replaced
+                        let placeholder = function(...args) {
+                            // This should never be called, but if it is, it means we have a bug
+                            throw new Error(`Function ${node.name} is not yet fully defined`);
+                        };
+                        
+                        // Store the placeholder in global scope
+                        globalScope[node.name] = placeholder;
+                        
+                        // Now evaluate the function definition with access to the placeholder
+                        const actualFunction = localEvalNode(node.value);
+                        
+                        // Replace the placeholder with the actual function
+                        globalScope[node.name] = actualFunction;
+                        return;
+                    }
+                    
+                    globalScope[node.name] = localEvalNode(node.value);
+                    return;
+                case 'Identifier':
+                    const identifierValue = globalScope[node.value];
+                    if (identifierValue === undefined &amp;&amp; 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 &lt; 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 &lt; node.parameters.length; i++) {
+                                nestedScope[node.parameters[i]] = args[i];
+                            }
+                            return localEvalNodeWithScope(node.body, nestedScope);
+                        } finally {
+                            callStackTracker.pop();
+                        }
+                    };
+                case 'FunctionCall':
+                    let localFunc;
+                    if (typeof node.name === 'string') {
+                        // Regular function call with string name
+                        localFunc = globalScope[node.name];
+                    } else if (node.name.type === 'Identifier') {
+                        // Function call with identifier
+                        localFunc = globalScope[node.name.value];
+                    } else {
+                        // Function call from expression (e.g., parenthesized function, higher-order)
+                        localFunc = localEvalNode(node.name);
+                    }
+                    
+                    if (localFunc instanceof Function) {
+                        let args = node.args.map(localEvalNode);
+                        return localFunc(...args);
+                    }
+                    throw new Error(`Function is not defined or is not callable`);
+                case 'WhenExpression':
+                    // Handle both single values and arrays of values
+                    const whenValues = Array.isArray(node.value) 
+                        ? node.value.map(localEvalNode) 
+                        : [localEvalNode(node.value)];
+                    
+                    for (const caseItem of node.cases) {
+                        // Handle both single patterns and arrays of patterns
+                        const patterns = caseItem.pattern.map(localEvalNode);
+                        
+                        // Check if patterns match the values
+                        let matches = true;
+                        if (whenValues.length !== patterns.length) {
+                            matches = false;
+                        } else {
+                            for (let i = 0; i &lt; whenValues.length; i++) {
+                                const value = whenValues[i];
+                                const pattern = patterns[i];
+                                
+                                if (pattern === true) { // Wildcard pattern
+                                    // Wildcard always matches
+                                    continue;
+                                } else if (typeof pattern === 'object' &amp;&amp; pattern !== null &amp;&amp; typeof value === 'object' &amp;&amp; value !== null) {
+                                    // Table pattern matching - check if all pattern properties exist in value
+                                    let tableMatches = true;
+                                    for (const key in pattern) {
+                                        if (pattern.hasOwnProperty(key) &amp;&amp; (!value.hasOwnProperty(key) || value[key] !== pattern[key])) {
+                                            tableMatches = false;
+                                            break;
+                                        }
+                                    }
+                                    if (!tableMatches) {
+                                        matches = false;
+                                        break;
+                                    }
+                                } else if (value !== pattern) {
+                                    matches = false;
+                                    break;
+                                }
+                            }
+                        }
+                        
+                        if (matches) {
+                            const results = caseItem.result.map(localEvalNode);
+                            if (results.length === 1) {
+                                return results[0];
+                            }
+                            return results.join(' ');
+                        }
+                    }
+                    throw new Error('No matching pattern found');
+                case 'WildcardPattern':
+                    return true;
+                case 'IOInExpression':
+                    const rl3 = createReadline();
+                    
+                    return new Promise((resolve) => {
+                        rl3.question('', (input) => {
+                            rl3.close();
+                            const num = parseInt(input);
+                            resolve(isNaN(num) ? input : num);
+                        });
+                    });
+                case 'IOOutExpression':
+                    const localOutputValue = localEvalNode(node.value);
+                    safeConsoleLog(localOutputValue);
+                    ioOperationsPerformed = true;
+                    return localOutputValue;
+                case 'IOAssertExpression':
+                    const localAssertionValue = localEvalNode(node.value);
+                    if (!localAssertionValue) {
+                        throw new Error('Assertion failed');
+                    }
+                    return localAssertionValue;
+                case 'IOListenExpression':
+                    // Return current state from environment if available, otherwise placeholder
+                    if (environment &amp;&amp; typeof environment.getCurrentState === 'function') {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning state from environment');
+                        }
+                        return environment.getCurrentState();
+                    } else {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..listen called - returning placeholder state');
+                        }
+                        return { status: 'placeholder', message: 'State not available in standalone mode' };
+                    }
+                case 'IOEmitExpression':
+                    const localEmitValue = localEvalNode(node.value);
+                    // Send value to environment if available, otherwise log to console
+                    if (environment &amp;&amp; typeof environment.emitValue === 'function') {
+                        if (DEBUG) {
+                            safeConsoleLog('[DEBUG] ..emit called - sending to environment');
+                        }
+                        environment.emitValue(localEmitValue);
+                    } else {
+                        safeConsoleLog('[EMIT]', localEmitValue);
+                    }
+                    ioOperationsPerformed = true;
+                    return localEmitValue;
+                case 'FunctionReference':
+                    const localFunctionValue = globalScope[node.name];
+                    if (localFunctionValue === undefined) {
+                        throw new Error(`Function ${node.name} is not defined`);
+                    }
+                    if (typeof localFunctionValue !== 'function') {
+                        throw new Error(`${node.name} is not a function`);
+                    }
+                    return localFunctionValue;
+                case 'ArrowExpression':
+                    // Arrow expressions are function bodies that should be evaluated
+                    return localEvalNode(node.body);
+                default:
+                    throw new Error(`Unknown node type: ${node.type}`);
+            }
+        } finally {
+            callStackTracker.pop();
+        }
+    };
+
+    let lastResult;
+    for (let node of ast.body) {
+        if (node) {
+            lastResult = evalNode(node);
+        }
+    }
+    
+    if (lastResult instanceof Promise) {
+        return lastResult.then(result => {
+            return { result: globalScope, ioOperationsPerformed };
+        });
+    }
+    
+    return { result: globalScope, ioOperationsPerformed };
+}
+
+/**
+ * Run script with environment support for harness integration
+ * 
+ * @param {string} scriptContent - The script content to execute
+ * @param {Object} [initialState={}] - Initial state for the interpreter
+ * @param {Environment} [environment=null] - Environment for IO operations
+ * @returns {*} The result of executing the script
+ * @throws {Error} For parsing or evaluation errors
+ * 
+ * @description Parses and executes a script using the combinator-based language.
+ * This function orchestrates the entire execution pipeline from source code
+ * to final result.
+ * 
+ * The function performs the following steps:
+ * 1. Tokenize the source code using the lexer
+ * 2. Parse the tokens into an AST using the parser
+ * 3. Evaluate the AST using the interpreter
+ * 4. Return the final result
+ * 
+ * This is the primary interface for executing scripts in the language.
+ * It handles the parsing and evaluation pipeline,
+ * providing a simple interface for users to run their code.
+ * 
+ * The function supports both synchronous and asynchronous execution. When
+ * the script contains IO operations that return Promises, the function
+ * will return a Promise that resolves to the final result. This enables
+ * non-blocking execution for interactive programs.
+ * 
+ * Error handling is comprehensive, with errors from any stage of the
+ * pipeline (lexing, parsing, or evaluation) being caught and re-thrown
+ * with appropriate context. This ensures that users get meaningful
+ * error messages that help them identify and fix issues in their code.
+ * 
+ * The function is designed to be stateless, with each call creating
+ * a fresh interpreter instance. This ensures that scripts don't interfere
+ * with each other and enables safe concurrent execution of multiple scripts.
+ */
+function run(scriptContent, initialState = {}, environment = null) {
+    // Parse the script
+    const tokens = lexer(scriptContent);
+    const ast = parser(tokens);
+    
+    // Run the interpreter with environment and initial state
+    const result = interpreter(ast, environment, initialState);
+    
+    // Return the result
+    return result.result;
+}
+
+/**
+ * Debug logging utility function.
+ * 
+ * @param {string} message - Debug message to log
+ * @param {*} [data=null] - Optional data to log with the message
+ * 
+ * @description Logs debug messages to console when DEBUG environment variable is set.
+ * Provides verbose output during development while remaining silent in production.
+ * 
+ * Debug functions are gated by the DEBUG environment variable, allowing for 
+ * verbose output during development and silent operation in production. This 
+ * approach makes it easy to trace execution and diagnose issues without 
+ * cluttering normal output.
+ * 
+ * This function is essential for debugging the combinator-based architecture,
+ * allowing developers to trace how operators are translated to function calls
+ * and how the interpreter executes these calls through the standard library.
+ * 
+ * The function is designed to be lightweight and safe to call frequently,
+ * making it suitable for tracing execution flow through nested
+ * expressions and function applications.
+ */
+function debugLog(message, data = null) {
+    if (DEBUG) {
+        safeConsoleLog(`[DEBUG] ${message}`);
+        if (data) {
+            safeConsoleLog(data);
+        }
+    }
+}
+
+/**
+ * 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.
+ * 
+ * Debug functions are gated by the DEBUG environment variable, allowing for 
+ * verbose output during development and silent operation in production. This 
+ * approach makes it easy to trace execution and diagnose issues without 
+ * cluttering normal output.
+ * 
+ * This function is particularly useful for debugging parsing and evaluation errors,
+ * providing detailed context about where and why errors occur in the language
+ * execution pipeline.
+ */
+function debugError(message, error = null) {
+    if (DEBUG) {
+        safeConsoleError(`[DEBUG ERROR] ${message}`);
+        if (error) {
+            safeConsoleError(error);
+        }
+    }
+}
+
+/**
+ * Call stack tracking for debugging recursion issues.
+ * 
+ * @description Tracks function calls to help identify infinite recursion
+ * and deep call stacks that cause stack overflow errors. This is essential
+ * for debugging the interpreter's recursive evaluation of AST nodes.
+ * 
+ * The tracker maintains a stack of function calls with timestamps and context
+ * information, counts function calls to identify hot paths, and detects
+ * potential infinite recursion by monitoring stack depth.
+ * 
+ * This tool is particularly important for the combinator-based architecture
+ * where function calls are the primary execution mechanism, and
+ * nested expressions can lead to deep call stacks. The tracker helps identify
+ * when the combinator translation creates unexpectedly deep call chains,
+ * enabling optimization of the function composition and application patterns.
+ * 
+ * The tracker provides detailed statistics about function call patterns,
+ * helping developers understand the execution characteristics of their code
+ * and identify potential performance bottlenecks in the combinator evaluation.
+ */
+const callStackTracker = {
+    stack: [],
+    maxDepth: 0,
+    callCounts: new Map(),
+    
+    /**
+     * Push a function call onto the stack
+     * @param {string} functionName - Name of the function being called
+     * @param {string} context - Context where the call is happening
+     */
+    push: function(functionName, context = '') {
+        const callInfo = { functionName, context, timestamp: Date.now() };
+        this.stack.push(callInfo);
+        
+        // Track maximum depth
+        if (this.stack.length > this.maxDepth) {
+            this.maxDepth = this.stack.length;
+        }
+        
+        // Count function calls
+        const key = `${functionName}${context ? `:${context}` : ''}`;
+        this.callCounts.set(key, (this.callCounts.get(key) || 0) + 1);
+        
+        // Check for potential infinite recursion
+        if (this.stack.length > 1000) {
+            console.error('=== POTENTIAL INFINITE RECURSION DETECTED ===');
+            console.error('Call stack depth:', this.stack.length);
+            console.error('Function call counts:', Object.fromEntries(this.callCounts));
+            console.error('Recent call stack:');
+            this.stack.slice(-10).forEach((call, i) => {
+                console.error(`  ${this.stack.length - 10 + i}: ${call.functionName}${call.context ? ` (${call.context})` : ''}`);
+            });
+            throw new Error(`Potential infinite recursion detected. Call stack depth: ${this.stack.length}`);
+        }
+        
+        if (DEBUG &amp;&amp; this.stack.length % 100 === 0) {
+            safeConsoleLog(`[DEBUG] Call stack depth: ${this.stack.length}, Max: ${this.maxDepth}`);
+        }
+    },
+    
+    /**
+     * Pop a function call from the stack
+     */
+    pop: function() {
+        return this.stack.pop();
+    },
+    
+    /**
+     * Get current stack depth
+     */
+    getDepth: function() {
+        return this.stack.length;
+    },
+    
+    /**
+     * Get call statistics
+     */
+    getStats: function() {
+        return {
+            currentDepth: this.stack.length,
+            maxDepth: this.maxDepth,
+            callCounts: Object.fromEntries(this.callCounts)
+        };
+    },
+    
+    /**
+     * Reset the tracker
+     */
+    reset: function() {
+        this.stack = [];
+        this.maxDepth = 0;
+        this.callCounts.clear();
+    }
+};
+
+/**
+ * Cross-platform file I/O utility
+ * 
+ * @param {string} filePath - Path to the file to read
+ * @returns {Promise&lt;string>} File contents as a string
+ * @throws {Error} For file reading errors
+ * 
+ * @description Handles file reading across different platforms (Node.js, Bun, browser)
+ * with appropriate fallbacks for each environment. This function is essential for
+ * the language's file execution model where scripts are loaded from .txt files.
+ * 
+ * The function prioritizes ES modules compatibility by using dynamic import,
+ * but falls back to require for older Node.js versions. Browser environments
+ * are not supported for file I/O operations.
+ * 
+ * This cross-platform approach ensures the language can run in various JavaScript
+ * environments while maintaining consistent behavior. The file reading capability
+ * enables the language to execute scripts from files, supporting the development
+ * workflow where tests and examples are stored as .txt files.
+ */
+async function readFile(filePath) {
+    // Use cross-platform filesystem
+    const fs = createFileSystem();
+    
+    return new Promise((resolve, reject) => {
+        fs.readFile(filePath, 'utf8', (error, data) => {
+            if (error) {
+                reject(error);
+            } else {
+                resolve(data);
+            }
+        });
+    });
+}
+
+/**
+ * Reads a file, tokenizes, parses, and interprets it.
+ * 
+ * @param {string} filePath - Path to the file to execute
+ * @returns {Promise&lt;*>} The result of executing the file
+ * @throws {Error} For file reading, parsing, or execution errors
+ * 
+ * @description Main entry point for file execution. Handles the complete language
+ * pipeline: file reading, lexical analysis, parsing, and interpretation.
+ * 
+ * This function orchestrates the entire language execution process:
+ * 1. Reads the source file using cross-platform I/O utilities
+ * 2. Tokenizes the source code using the lexer
+ * 3. Parses tokens into an AST using the combinator-based parser
+ * 4. Interprets the AST using the combinator-based interpreter
+ * 
+ * The function provides comprehensive error handling and debug output at each
+ * stage for transparency and troubleshooting. It also manages the call stack
+ * tracker to provide execution statistics and detect potential issues.
+ * 
+ * Supports both synchronous and asynchronous execution, with proper
+ * error handling and process exit codes. This function demonstrates the
+ * complete combinator-based architecture in action, showing how source code
+ * is transformed through each stage of the language pipeline.
+ * 
+ * The function enforces the .txt file extension requirement and provides
+ * detailed error reporting with call stack statistics to help developers
+ * understand execution behavior and diagnose issues.
+ */
+async function executeFile(filePath) {
+    try {
+        // Validate file extension
+        if (!filePath.endsWith('.txt') &amp;&amp; !filePath.endsWith('.baba')) {
+            throw new Error('Only .txt and .baba files are supported');
+        }
+        
+        const input = await readFile(filePath);
+        
+        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 => {
+                // Only output result if debug mode is enabled (no automatic final result output)
+                if (finalResult.result !== undefined &amp;&amp; DEBUG) {
+                    safeConsoleLog(finalResult.result);
+                }
+                // Print call stack statistics only in debug mode
+                if (DEBUG) {
+                    const stats = callStackTracker.getStats();
+                    safeConsoleLog('\n=== CALL STACK STATISTICS ===');
+                    safeConsoleLog('Maximum call stack depth:', stats.maxDepth);
+                    safeConsoleLog('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+                }
+            }).catch(error => {
+                safeConsoleError(`Error executing file: ${error.message}`);
+                // Print call stack statistics on error only in debug mode
+                if (DEBUG) {
+                    const stats = callStackTracker.getStats();
+                    safeConsoleError('\n=== CALL STACK STATISTICS ON ERROR ===');
+                    safeConsoleError('Maximum call stack depth:', stats.maxDepth);
+                    safeConsoleError('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+                }
+                safeExit(1);
+            });
+        } else {
+            // Only output result if debug mode is enabled (no automatic final result output)
+            if (result.result !== undefined &amp;&amp; DEBUG) {
+                safeConsoleLog(result.result);
+            }
+            // Print call stack statistics only in debug mode
+            if (DEBUG) {
+                const stats = callStackTracker.getStats();
+                safeConsoleLog('\n=== CALL STACK STATISTICS ===');
+                safeConsoleLog('Maximum call stack depth:', stats.maxDepth);
+                safeConsoleLog('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+            }
+        }
+    } catch (error) {
+        safeConsoleError(`Error executing file: ${error.message}`);
+        // Print call stack statistics on error only in debug mode
+        if (DEBUG) {
+            const stats = callStackTracker.getStats();
+            safeConsoleError('\n=== CALL STACK STATISTICS ON ERROR ===');
+            safeConsoleError('Maximum call stack depth:', stats.maxDepth);
+            safeConsoleError('Function call counts:', JSON.stringify(stats.callCounts, null, 2));
+        }
+        safeExit(1);
+    }
+}
+
+/**
+ * CLI argument handling and program entry point.
+ * 
+ * @description Processes command line arguments and executes the specified file.
+ * Provides helpful error messages for incorrect usage.
+ * 
+ * The language is designed for file execution only (no REPL), so the CLI 
+ * enforces this usage and provides helpful error messages for incorrect invocation.
+ * The function validates that exactly one file path is provided and that the
+ * file has the correct .txt extension.
+ * 
+ * Exits with appropriate error codes for different failure scenarios.
+ */
+async function main() {
+    // Only run main function in Node.js/Bun environments
+    if (!isNode &amp;&amp; !isBun) {
+        return; // Skip in browser environment
+    }
+    
+    const args = process.argv.slice(2);
+
+    if (args.length === 0) {
+        safeConsoleError('Usage: node lang.js &lt;file>');
+        safeConsoleError('  Provide a file path to execute');
+        safeExit(1);
+    } else if (args.length === 1) {
+        // Execute the file
+        const filePath = args[0];
+        await executeFile(filePath);
+    } else {
+        // Too many arguments
+        safeConsoleError('Usage: node lang.js &lt;file>');
+        safeConsoleError('  Provide exactly one file path to execute');
+        safeExit(1);
+    }
+}
+
+// Start the program only if this file is run directly in Node.js/Bun
+if ((isNode || isBun) &amp;&amp; process.argv[1] &amp;&amp; process.argv[1].endsWith('lang.js')) {
+    main().catch(error => {
+        safeConsoleError('Fatal error:', error.message);
+        safeExit(1);
+    });
+}
+
+// Export functions for harness integration
+export { run, interpreter, lexer, parser };
+
+
+</code></pre>
+        </article>
+    </section>
+
+
+
+
+</div>
+
+<br class="clear">
+
+<footer>
+    Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.4</a> on Tue Jul 29 2025 23:15:00 GMT-0400 (Eastern Daylight Time) using the Minami theme.
+</footer>
+
+<script>prettyPrint();</script>
+<script src="scripts/linenumber.js"></script>
+</body>
+</html>