about summary refs log tree commit diff stats
path: root/js/scripting-lang/lang.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/scripting-lang/lang.js')
-rw-r--r--js/scripting-lang/lang.js1027
1 files changed, 965 insertions, 62 deletions
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
index d3bc0b5..157a2a7 100644
--- a/js/scripting-lang/lang.js
+++ b/js/scripting-lang/lang.js
@@ -24,44 +24,114 @@ import { parser } from './parser.js';
  * 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.
+ * typed and does not enforce arity or types at parse time. The combinator functions are
+ * designed to work seamlessly with the parser's operator translation, providing a consistent
+ * and extensible foundation for all language operations.
  */
 function initializeStandardLibrary(scope) {
     /**
-     * Map: Apply a function to a value
+     * Map: Apply a function to a value or collection
      * @param {Function} f - Function to apply
-     * @param {*} x - Value to apply function to
+     * @param {*} x - Value or collection to apply function to
      * @returns {*} Result of applying f to x
      * @throws {Error} When first argument is not a function
+     * @description The map function is a fundamental higher-order function that
+     * applies a transformation function to a value or collection. This enables
+     * functional programming patterns where data transformations are expressed
+     * as function applications rather than imperative operations.
+     * 
+     * The function implements APL-inspired element-wise operations for tables:
+     * when x is a table, map applies the function to each value while preserving
+     * the table structure and keys. This eliminates the need for explicit loops
+     * and enables declarative data transformation patterns.
+     * 
+     * The function supports partial application: when called with only the function,
+     * it returns a new function that waits for the value. This enables currying
+     * patterns and function composition chains, which are essential for the
+     * combinator-based architecture where all operations are function calls.
+     * 
+     * This design choice aligns with the language's functional foundation and
+     * enables powerful abstractions like `map @double numbers` to transform
+     * every element in a collection without explicit iteration.
      */
     scope.map = function(f, x) { 
-        if (typeof f === 'function') {
-            return f(x);
-        } else {
+        if (typeof f !== 'function') {
             throw new Error('map: first argument must be a function');
         }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return scope.map(f, x);
+            };
+        }
+        
+        // Handle tables (APL-style element-wise operations)
+        if (typeof x === 'object' && x !== null && !Array.isArray(x)) {
+            const result = {};
+            for (const [key, value] of Object.entries(x)) {
+                result[key] = f(value);
+            }
+            return result;
+        }
+        
+        // Handle arrays (future enhancement)
+        if (Array.isArray(x)) {
+            return x.map(f);
+        }
+        
+        // Default: apply to single value
+        return f(x);
     };
     
     /**
-     * Compose: Compose two functions (f ∘ g)(x) = f(g(x))
-     * @param {Function} f - Outer function
-     * @param {Function} g - Inner function  
-     * @param {*} [x] - Optional argument to apply composed function to
-     * @returns {Function|*} Either a composed function or the result of applying it
-     * @throws {Error} When first two arguments are not functions
+     * Compose: Compose functions (f ∘ g)(x) = f(g(x))
+     * @param {Function} f - First function
+     * @param {Function} [g] - Second function (optional for partial application)
+     * @returns {Function} Composed function or partially applied function
+     * @throws {Error} When first argument is not a function
+     * @description The compose function is a core functional programming primitive
+     * that combines two functions into a new function. 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.
+     * 
+     * Partial application support enables currying patterns where functions can
+     * be built incrementally. This is essential for the combinator-based architecture
+     * where complex operations are built from simple, composable functions.
+     * 
+     * The right-associative design choice aligns with mathematical conventions
+     * and enables intuitive composition chains that read naturally from right
+     * to left, matching the mathematical notation for function composition.
      */
-    scope.compose = function(f, g, x) { 
-        if (typeof f === 'function' && typeof g === 'function') {
-            if (arguments.length === 3) {
-                return f(g(x));
-            } else {
+    scope.compose = function(f, g) {
+        if (typeof f !== 'function') {
+            throw new Error(`compose: first argument must be a function, got ${typeof f}`);
+        }
+        
+        if (g === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(g) {
+                if (typeof g !== 'function') {
+                    throw new Error(`compose: second argument must be a function, got ${typeof g}`);
+                }
                 return function(x) {
                     return f(g(x));
                 };
-            }
-        } else {
-            throw new Error('compose: first two arguments must be functions');
+            };
+        }
+        
+        if (typeof g !== 'function') {
+            throw new Error(`compose: second argument must be a function, got ${typeof g}`);
         }
+        
+        return function(x) {
+            return f(g(x));
+        };
     };
     
     /**
@@ -71,13 +141,43 @@ function initializeStandardLibrary(scope) {
      * @param {*} y - Second argument
      * @returns {*} Result of applying f to x and y
      * @throws {Error} When first argument is not a function
+     * @description The curry function provides a simplified currying mechanism
+     * that allows functions to be applied to arguments incrementally. When called
+     * with fewer arguments than the function expects, it returns a new function
+     * that waits for the remaining arguments.
+     * 
+     * This function is designed to work with the parser's one-by-one argument
+     * application system, where multi-argument function calls are translated to
+     * nested apply calls. The nested partial application checks ensure that
+     * functions return partially applied functions until all arguments are received.
      */
     scope.curry = function(f, x, y) { 
-        if (typeof f === 'function') {
-            return f(x, y);
-        } else {
+        if (typeof f !== 'function') {
             throw new Error('curry: first argument must be a function');
         }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the remaining arguments
+            return function(x, y) {
+                if (y === undefined) {
+                    // Still partial application
+                    return function(y) {
+                        return f(x, y);
+                    };
+                }
+                return f(x, y);
+            };
+        }
+        
+        if (y === undefined) {
+            // Partial application: return a function that waits for the last argument
+            return function(y) {
+                return f(x, y);
+            };
+        }
+        
+        // Full application: apply the function to all arguments
+        return f(x, y);
     };
     
     /**
@@ -86,50 +186,152 @@ function initializeStandardLibrary(scope) {
      * @param {*} x - Argument to apply function to
      * @returns {*} Result of applying f to x
      * @throws {Error} When first argument is not a function
+     * @description The apply function is the fundamental mechanism for function
+     * application in the language. It enables the juxtaposition-based function
+     * application syntax (f x) by providing an explicit function application
+     * primitive. This function is called by the parser whenever function
+     * application is detected, ensuring consistent semantics across all
+     * function calls.
+     * 
+     * This function is the core mechanism that enables the parser's juxtaposition
+     * detection. When the parser encounters `f x`, it generates `apply(f, x)`,
+     * which this function handles. This design eliminates the need for special
+     * syntax for function calls while maintaining clear precedence rules.
+     * 
+     * The function supports partial application: when called with only the function,
+     * it returns a new function that waits for the argument. This enables the
+     * parser to build function application chains incrementally, supporting
+     * both immediate evaluation and deferred execution patterns.
+     * 
+     * This partial application support is essential for the parser's left-associative
+     * function application model, where `f g x` becomes `apply(apply(f, g), x)`.
+     * The nested partial application ensures that each step returns a function
+     * until all arguments are provided.
      */
     scope.apply = function(f, x) { 
-        if (typeof f === 'function') {
-            return f(x);
-        } else {
+        if (typeof f !== 'function') {
             throw new Error('apply: first argument must be a function');
         }
+        
+        if (x === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(x) {
+                return f(x);
+            };
+        }
+        
+        // Full application: apply the function to the argument
+        return f(x);
     };
     
     /**
      * Pipe: Compose functions in left-to-right order (opposite of compose)
      * @param {Function} f - First function
-     * @param {Function} g - Second function
-     * @param {*} [x] - Optional argument to apply piped function to
-     * @returns {Function|*} Either a piped function or the result of applying it
-     * @throws {Error} When first two arguments are not functions
+     * @param {Function} [g] - Second function (optional for partial application)
+     * @returns {Function} Function that applies the functions in left-to-right order
+     * @throws {Error} When first argument is not a function
+     * @description The pipe function provides an alternative to compose that
+     * applies functions in left-to-right order, which is often more intuitive
+     * for data processing pipelines. This enables functional programming patterns
+     * where data flows through a series of transformations in a natural reading order.
+     * 
+     * The function implements left-associative composition, meaning that
+     * pipe(f, pipe(g, h)) creates a function that applies f, then g, then h.
+     * This is the opposite of compose and matches the natural reading order
+     * for data transformation pipelines, making it intuitive for programmers
+     * who think in terms of data flow from left to right.
+     * 
+     * Like compose, it supports partial application for currying patterns.
+     * This enables building complex transformation pipelines incrementally,
+     * which is essential for the combinator-based architecture where complex
+     * operations are built from simple, composable functions.
+     * 
+     * The left-associative design choice makes pipe ideal for data processing
+     * workflows where each step transforms the data and passes it to the next
+     * step, creating a natural pipeline that reads like a sequence of operations.
      */
-    scope.pipe = function(f, g, x) { 
-        if (typeof f === 'function' && typeof g === 'function') {
-            if (arguments.length === 3) {
-                return g(f(x));
-            } else {
+    scope.pipe = function(f, g) {
+        if (typeof f !== 'function') {
+            throw new Error(`pipe: first argument must be a function, got ${typeof f}`);
+        }
+        
+        if (g === undefined) {
+            // Partial application: return a function that waits for the second argument
+            return function(g) {
+                if (typeof g !== 'function') {
+                    throw new Error(`pipe: second argument must be a function, got ${typeof g}`);
+                }
                 return function(x) {
                     return g(f(x));
                 };
-            }
-        } else {
-            throw new Error('pipe: first two arguments must be functions');
+            };
         }
+        
+        if (typeof g !== 'function') {
+            throw new Error(`pipe: second argument must be a function, got ${typeof g}`);
+        }
+        
+        return function(x) {
+            return g(f(x));
+        };
     };
     
     /**
-     * Filter: Filter a value based on a predicate
+     * Filter: Filter a value or collection based on a predicate
      * @param {Function} p - Predicate function
-     * @param {*} x - Value to test
-     * @returns {*|0} The value if predicate is true, 0 otherwise
+     * @param {*} x - Value or collection to test
+     * @returns {*|0} The value if predicate is true, filtered collection for tables, 0 otherwise
      * @throws {Error} When first argument is not a function
+     * @description The filter function applies a predicate to a value or collection,
+     * returning the value if the predicate is true, or a filtered collection for tables.
+     * This enables functional programming patterns where data selection is expressed
+     * as predicate application rather than imperative filtering loops.
+     * 
+     * The function implements APL-inspired element-wise filtering for tables:
+     * when x is a table, filter applies the predicate to each value and returns
+     * a new table containing only the key-value pairs where the predicate returns true.
+     * This eliminates the need for explicit loops and enables declarative data
+     * selection patterns.
+     * 
+     * The function supports partial application: when called with only the predicate,
+     * it returns a new function that waits for the value. This enables currying
+     * patterns and function composition chains, which are essential for the
+     * combinator-based architecture where all operations are function calls.
+     * 
+     * This design choice aligns with the language's functional foundation and
+     * enables powerful abstractions like `filter @isEven numbers` to select
+     * elements from a collection without explicit iteration.
      */
     scope.filter = function(p, x) { 
-        if (typeof p === 'function') {
-            return p(x) ? x : 0;
-        } else {
+        if (typeof p !== 'function') {
             throw new Error('filter: first argument must be a function');
         }
+        
+        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' && x !== null && !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;
     };
     
     /**
@@ -139,13 +341,69 @@ function initializeStandardLibrary(scope) {
      * @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 (typeof f === 'function') {
-            return f(init, x);
-        } else {
+        if (process.env.DEBUG) {
+            console.log(`[DEBUG] reduce: f =`, typeof f, f);
+            console.log(`[DEBUG] reduce: init =`, init);
+            console.log(`[DEBUG] reduce: x =`, x);
+        }
+        
+        if (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 (process.env.DEBUG) {
+                    console.log(`[DEBUG] reduce returned function: f =`, typeof f, f);
+                    console.log(`[DEBUG] reduce returned function: init =`, init);
+                    console.log(`[DEBUG] reduce returned function: x =`, x);
+                }
+                if (x === undefined) {
+                    // Still partial application
+                    return function(x) {
+                        return scope.reduce(f, init, x);
+                    };
+                }
+                return scope.reduce(f, init, x);
+            };
+        }
+        
+        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' && x !== null && !Array.isArray(x)) {
+            let result = init;
+            for (const [key, value] of Object.entries(x)) {
+                result = f(result, value);
+            }
+            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);
     };
     
     /**
@@ -157,11 +415,32 @@ function initializeStandardLibrary(scope) {
      * @throws {Error} When first argument is not a function
      */
     scope.fold = function(f, init, x) { 
-        if (typeof f === 'function') {
-            return f(init, x);
-        } else {
+        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 =====
@@ -171,6 +450,18 @@ function initializeStandardLibrary(scope) {
      * @param {number} x - First number
      * @param {number} y - Second number
      * @returns {number} Sum of x and y
+     * @description The add function is a fundamental arithmetic combinator that
+     * implements addition. This function is called by the parser when the '+'
+     * operator is encountered, translating `x + y` into `add(x, y)`.
+     * 
+     * As a combinator function, add supports partial application and can be used
+     * in function composition chains. This enables patterns like `map @add 10`
+     * to add 10 to every element in a collection, or `each @add table1 table2`
+     * for element-wise addition of corresponding table elements.
+     * 
+     * The function is designed to work seamlessly with the parser's operator
+     * translation system, providing consistent semantics for all arithmetic
+     * operations through the combinator foundation.
      */
     scope.add = function(x, y) {
         return x + y;
@@ -191,6 +482,18 @@ function initializeStandardLibrary(scope) {
      * @param {number} x - First number
      * @param {number} y - Second number
      * @returns {number} Product of x and y
+     * @description The multiply function is a fundamental arithmetic combinator that
+     * implements multiplication. This function is called by the parser when the '*'
+     * operator is encountered, translating `x * y` into `multiply(x, y)`.
+     * 
+     * As a combinator function, multiply supports partial application and can be used
+     * in function composition chains. This enables patterns like `map @multiply 2`
+     * to double every element in a collection, or `each @multiply table1 table2`
+     * for element-wise multiplication of corresponding table elements.
+     * 
+     * The function is designed to work seamlessly with the parser's operator
+     * translation system, providing consistent semantics for all arithmetic
+     * operations through the combinator foundation.
      */
     scope.multiply = function(x, y) {
         return x * y;
@@ -434,10 +737,426 @@ function initializeStandardLibrary(scope) {
             return f(x) || g(x);
         };
     };
+    
+    /**
+     * Each: Multi-argument element-wise operations for tables and scalars
+     * @param {Function} f - Function to apply element-wise
+     * @param {*} x - First argument (table or scalar)
+     * @returns {Function|*} Function for partial application or result of element-wise application
+     * @throws {Error} When first argument is not a function
+     * @description The each combinator provides APL-inspired element-wise operations
+     * for multi-argument functions over table structures. This is the primary mechanism
+     * for combining multiple tables or tables with scalars in element-wise fashion.
+     * 
+     * The function is designed for multi-argument operations and aligns with the parser's
+     * apply mechanism. When x is a table, each returns a function that waits for the
+     * second argument (y), enabling the parser to build `apply(apply(each, f), x)` chains
+     * that resolve to element-wise operations when y is provided.
+     * 
+     * Key behaviors:
+     * - Table + Scalar: Applies f to each element of the table with the scalar as second argument
+     * - Table + Table: Applies f to corresponding elements from both tables
+     * - Scalar + Table: Uses map to apply f with the scalar as first argument to each table element
+     * - Scalar + Scalar: Falls back to normal function application for backward compatibility
+     * 
+     * This design choice enables powerful multi-argument element-wise operations like
+     * `each @add table1 table2` for element-wise addition, while maintaining compatibility
+     * with the parser's two-argument apply model. The function is specifically designed
+     * for multi-argument operations, distinguishing it from map which is for single-table
+     * transformations.
+     */
+    scope.each = function(f, x) {
+        if (process.env.DEBUG) {
+            console.log(`[DEBUG] each called with: f=${typeof f}, x=${typeof x}`);
+            console.log(`[DEBUG] x value:`, x);
+        }
+        
+        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' && x !== null && !Array.isArray(x);
+        
+        if (isXTable) {
+            // x is a table - always return a function that can handle the second argument
+            return function(y) {
+                // Check if y is a table
+                const isYTable = typeof y === 'object' && y !== null && !Array.isArray(y);
+                
+                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' && y !== null && !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 eliminates side effects from table operations.
+     * 
+     * The namespace provides both basic table operations (get, set, delete, merge)
+     * and higher-order operations (map, filter, reduce) that work element-wise
+     * on table values. This design choice enables powerful data transformation
+     * patterns while maintaining the functional programming principles of the language.
+     * 
+     * Key design principles:
+     * - Immutability: All operations return new tables, never modify originals
+     * - Element-wise operations: Functions operate on table values, not structure
+     * - Partial application: All functions support currying patterns
+     * - Functional consistency: Operations work with the combinator foundation
+     */
+    scope.t = {
+        /**
+         * Map: Apply a function to each value in a table
+         * @param {Function} f - Function to apply
+         * @param {Object} table - Table to map over
+         * @returns {Object} New table with transformed values
+         * @throws {Error} When first argument is not a function or second is not a table
+         */
+        map: function(f, table) {
+            if (typeof f !== 'function') {
+                throw new Error('t.map: first argument must be a function');
+            }
+            
+            if (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.
+ * Interpreter: Walks the AST and evaluates each node using the combinator foundation.
  * 
  * @param {Object} ast - Abstract Syntax Tree to evaluate
  * @returns {*} The result of evaluating the AST, or a Promise for async operations
@@ -448,6 +1167,23 @@ function initializeStandardLibrary(scope) {
  * both synchronous and asynchronous operations.
  * 
  * The interpreter implements a combinator-based architecture where all operations
+ * are executed through function calls to standard library combinators. This design
+ * eliminates parsing ambiguity while preserving intuitive syntax. The parser translates
+ * all operators (+, -, *, /, etc.) into FunctionCall nodes that reference combinator
+ * functions, ensuring consistent semantics across all operations.
+ * 
+ * Key architectural features:
+ * - Combinator Foundation: All operations are function calls to standard library combinators
+ * - Scope Management: Prototypal inheritance for variable lookup and function definitions
+ * - Forward Declaration: Recursive functions are supported through placeholder creation
+ * - Error Handling: Comprehensive error detection and reporting with call stack tracking
+ * - Debug Support: Optional debug mode for development and troubleshooting
+ * 
+ * The interpreter processes legacy operator expressions (PlusExpression, MinusExpression, etc.)
+ * for backward compatibility, but the parser now generates FunctionCall nodes for all operators,
+ * which are handled by the standard library combinator functions. This ensures that all
+ * operations follow the same execution model and can be extended by adding new combinator
+ * functions to the standard library.
  * are translated to function calls to standard library combinators. This eliminates
  * parsing ambiguity while preserving the original syntax. The parser generates
  * FunctionCall nodes for operators (e.g., x + y becomes add(x, y)), and the
@@ -465,6 +1201,11 @@ function initializeStandardLibrary(scope) {
  * Recursive function support is implemented using a forward declaration pattern:
  * a placeholder function is created in the global scope before evaluation, allowing
  * the function body to reference itself during evaluation.
+ * 
+ * The combinator foundation ensures that all operations are executed through
+ * function calls, providing a consistent and extensible execution model. This
+ * approach enables powerful abstractions and eliminates the need for special
+ * handling of different operator types in the interpreter.
  */
 function interpreter(ast) {
     const globalScope = {};
@@ -481,7 +1222,7 @@ function interpreter(ast) {
     callStackTracker.reset();
     
     /**
-     * Evaluates AST nodes in the global scope.
+     * Evaluates AST nodes in the global scope using the combinator foundation.
      * 
      * @param {Object} node - AST node to evaluate
      * @returns {*} The result of evaluating the node
@@ -494,6 +1235,28 @@ function interpreter(ast) {
      * The function implements the forward declaration pattern for recursive functions:
      * when a function assignment is detected, a placeholder is created in the global
      * scope before evaluation, allowing the function body to reference itself.
+     * This pattern enables natural recursive function definitions without requiring
+     * special syntax or pre-declaration.
+     * 
+     * This function is the primary entry point for AST evaluation and handles
+     * all the core language constructs including literals, operators (translated
+     * to combinator calls), function definitions, and control structures. It
+     * ensures that all operations are executed through the combinator foundation,
+     * providing consistent semantics across the language.
+     * 
+     * The function processes legacy operator expressions (PlusExpression, MinusExpression, etc.)
+     * for backward compatibility, but the parser now generates FunctionCall nodes for
+     * all operators, which are handled by the standard library combinator functions.
+     * This design ensures that all operations follow the same execution model and
+     * can be extended by adding new combinator functions to the standard library.
+     * 
+     * Key evaluation patterns:
+     * - Literals: Direct value return
+     * - FunctionCall: Delegates to standard library combinator functions
+     * - Assignment: Creates variables in global scope with forward declaration support
+     * - WhenExpression: Pattern matching with wildcard support
+     * - TableLiteral: Creates immutable table structures
+     * - TableAccess: Safe property access with error handling
      */
     function evalNode(node) {
         callStackTracker.push('evalNode', node?.type || 'unknown');
@@ -568,8 +1331,55 @@ function interpreter(ast) {
                                 // For other key types (numbers, strings), evaluate normally
                                 key = evalNode(entry.key);
                             }
-                            const value = evalNode(entry.value);
-                            table[key] = value;
+                            // Special handling for FunctionDeclaration nodes
+                            if (process.env.DEBUG) {
+                                console.log(`[DEBUG] TableLiteral: entry.value.type = ${entry.value.type}`);
+                            }
+                            if (entry.value.type === 'FunctionDeclaration') {
+                                // Don't evaluate the function body, just create the function
+                                const func = function(...args) {
+                                    callStackTracker.push('FunctionCall', entry.value.params.join(','));
+                                    try {
+                                        // If we have fewer arguments than parameters, return a curried function
+                                        if (args.length < entry.value.params.length) {
+                                            return function(...moreArgs) {
+                                                const allArgs = [...args, ...moreArgs];
+                                                if (allArgs.length < entry.value.params.length) {
+                                                    // Still not enough arguments, curry again
+                                                    return function(...evenMoreArgs) {
+                                                        const finalArgs = [...allArgs, ...evenMoreArgs];
+                                                        let localScope = Object.create(globalScope);
+                                                        for (let i = 0; i < entry.value.params.length; i++) {
+                                                            localScope[entry.value.params[i]] = finalArgs[i];
+                                                        }
+                                                        return localEvalNodeWithScope(entry.value.body, localScope);
+                                                    };
+                                                } else {
+                                                    // We have enough arguments now
+                                                    let localScope = Object.create(globalScope);
+                                                    for (let i = 0; i < entry.value.params.length; i++) {
+                                                        localScope[entry.value.params[i]] = allArgs[i];
+                                                    }
+                                                    return localEvalNodeWithScope(entry.value.body, localScope);
+                                                }
+                                            };
+                                        } else {
+                                            // We have enough arguments, evaluate the function
+                                            let localScope = Object.create(globalScope);
+                                            for (let i = 0; i < entry.value.params.length; i++) {
+                                                localScope[entry.value.params[i]] = args[i];
+                                            }
+                                            return localEvalNodeWithScope(entry.value.body, localScope);
+                                        }
+                                    } finally {
+                                        callStackTracker.pop();
+                                    }
+                                };
+                                table[key] = func;
+                            } else {
+                                const value = evalNode(entry.value);
+                                table[key] = value;
+                            }
                         }
                     }
                     
@@ -665,11 +1475,37 @@ function interpreter(ast) {
                     return function(...args) {
                         callStackTracker.push('FunctionCall', node.params.join(','));
                         try {
-                            let localScope = Object.create(globalScope);
-                            for (let i = 0; i < node.params.length; i++) {
-                                localScope[node.params[i]] = args[i];
+                            // If we have fewer arguments than parameters, return a curried function
+                            if (args.length < node.params.length) {
+                                return function(...moreArgs) {
+                                    const allArgs = [...args, ...moreArgs];
+                                    if (allArgs.length < node.params.length) {
+                                        // Still not enough arguments, curry again
+                                        return function(...evenMoreArgs) {
+                                            const finalArgs = [...allArgs, ...evenMoreArgs];
+                                            let localScope = Object.create(globalScope);
+                                            for (let i = 0; i < node.params.length; i++) {
+                                                localScope[node.params[i]] = finalArgs[i];
+                                            }
+                                            return localEvalNodeWithScope(node.body, localScope);
+                                        };
+                                    } else {
+                                        // We have enough arguments now
+                                        let localScope = Object.create(globalScope);
+                                        for (let i = 0; i < node.params.length; i++) {
+                                            localScope[node.params[i]] = allArgs[i];
+                                        }
+                                        return localEvalNodeWithScope(node.body, localScope);
+                                    }
+                                };
+                            } else {
+                                // We have enough arguments, evaluate the function
+                                let localScope = Object.create(globalScope);
+                                for (let i = 0; i < node.params.length; i++) {
+                                    localScope[node.params[i]] = args[i];
+                                }
+                                return localEvalNodeWithScope(node.body, localScope);
                             }
-                            return localEvalNodeWithScope(node.body, localScope);
                         } finally {
                             callStackTracker.pop();
                         }
@@ -755,6 +1591,33 @@ function interpreter(ast) {
                                         console.log(`[DEBUG] WhenExpression: wildcard matches`);
                                     }
                                     continue;
+                                } else if (typeof pattern === 'object' && pattern.type === 'FunctionCall') {
+                                    // This is a boolean expression pattern (e.g., x < 0)
+                                    // We need to substitute the current value for the pattern variable
+                                    // For now, let's assume the pattern variable is the first identifier in the function call
+                                    let patternToEvaluate = pattern;
+                                    if (pattern.args && pattern.args.length > 0 && pattern.args[0].type === 'Identifier') {
+                                        // Create a copy of the pattern with the current value substituted
+                                        patternToEvaluate = {
+                                            ...pattern,
+                                            args: [value, ...pattern.args.slice(1)]
+                                        };
+                                    }
+                                    const patternResult = evalNode(patternToEvaluate);
+                                    if (process.env.DEBUG) {
+                                        console.log(`[DEBUG] WhenExpression: boolean pattern result = ${patternResult}`);
+                                    }
+                                    if (!patternResult) {
+                                        matches = false;
+                                        if (process.env.DEBUG) {
+                                            console.log(`[DEBUG] WhenExpression: boolean pattern does not match`);
+                                        }
+                                        break;
+                                    } else {
+                                        if (process.env.DEBUG) {
+                                            console.log(`[DEBUG] WhenExpression: boolean pattern matches`);
+                                        }
+                                    }
                                 } else if (value !== pattern) {
                                     matches = false;
                                     if (process.env.DEBUG) {
@@ -810,6 +1673,9 @@ function interpreter(ast) {
                     return assertionValue;
                 case 'FunctionReference':
                     const functionValue = globalScope[node.name];
+                    if (process.env.DEBUG) {
+                        console.log(`[DEBUG] FunctionReference: looking up '${node.name}' in globalScope, found:`, typeof functionValue);
+                    }
                     if (functionValue === undefined) {
                         throw new Error(`Function ${node.name} is not defined`);
                     }
@@ -848,6 +1714,10 @@ function interpreter(ast) {
      * 
      * This separation of global and local evaluation allows for proper scope management
      * and prevents variable name conflicts between function parameters and global variables.
+     * 
+     * The function prioritizes local scope lookups over global scope lookups, ensuring
+     * that function parameters shadow global variables with the same names. This
+     * implements proper lexical scoping semantics.
      */
     const localEvalNodeWithScope = (node, scope) => {
         callStackTracker.push('localEvalNodeWithScope', node?.type || 'unknown');
@@ -1164,6 +2034,11 @@ function interpreter(ast) {
      * 
      * 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');
@@ -1460,6 +2335,14 @@ function interpreter(ast) {
  * verbose output during development and silent operation in production. This 
  * approach makes it easy to trace execution and diagnose issues without 
  * cluttering normal output.
+ * 
+ * This function is essential for debugging the combinator-based architecture,
+ * allowing developers to trace how operators are translated to function calls
+ * and how the interpreter executes these calls through the standard library.
+ * 
+ * The function is designed to be lightweight and safe to call frequently,
+ * making it suitable for tracing execution flow through complex nested
+ * expressions and function applications.
  */
 function debugLog(message, data = null) {
     if (process.env.DEBUG) {
@@ -1483,6 +2366,10 @@ function debugLog(message, data = null) {
  * verbose output during development and silent operation in production. This 
  * approach makes it easy to trace execution and diagnose issues without 
  * cluttering normal output.
+ * 
+ * This function is particularly useful for debugging parsing and evaluation errors,
+ * providing detailed context about where and why errors occur in the language
+ * execution pipeline.
  */
 function debugError(message, error = null) {
     if (process.env.DEBUG) {
@@ -1506,7 +2393,13 @@ function debugError(message, error = null) {
  * 
  * This tool is particularly important for the combinator-based architecture
  * where function calls are the primary execution mechanism, and complex
- * nested expressions can lead to deep call stacks.
+ * 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: [],
@@ -1599,7 +2492,9 @@ const callStackTracker = {
  * 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.
+ * environments while maintaining consistent behavior. The file reading capability
+ * enables the language to execute scripts from files, supporting the development
+ * workflow where tests and examples are stored as .txt files.
  */
 async function readFile(filePath) {
     // Check if we're in a browser environment
@@ -1641,7 +2536,13 @@ async function readFile(filePath) {
  * tracker to provide execution statistics and detect potential issues.
  * 
  * Supports both synchronous and asynchronous execution, with proper
- * error handling and process exit codes.
+ * 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 {
@@ -1738,4 +2639,6 @@ async function main() {
 main().catch(error => {
     console.error('Fatal error:', error.message);
     process.exit(1);
-});
\ No newline at end of file
+});
+
+