about summary refs log tree commit diff stats
path: root/js/scripting-lang/parser.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/scripting-lang/parser.js')
-rw-r--r--js/scripting-lang/parser.js1710
1 files changed, 1710 insertions, 0 deletions
diff --git a/js/scripting-lang/parser.js b/js/scripting-lang/parser.js
new file mode 100644
index 0000000..a5cb45b
--- /dev/null
+++ b/js/scripting-lang/parser.js
@@ -0,0 +1,1710 @@
+// Parser for the scripting language
+// Exports: parser(tokens)
+// Converts tokens to an Abstract Syntax Tree (AST)
+
+import { TokenType } from './lexer.js';
+
+// Cross-platform environment detection
+const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
+const isBun = typeof process !== 'undefined' && process.versions && process.versions.bun;
+const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
+
+// Cross-platform debug flag
+const DEBUG = (isNode && process.env.DEBUG) || (isBrowser && window.DEBUG) || false;
+
+/**
+ * AST node types for the language
+ * 
+ * @typedef {Object} ASTNode
+ * @property {string} type - The node type identifier
+ * @property {*} [value] - Node value (for literals)
+ * @property {string} [name] - Identifier name (for identifiers)
+ * @property {Array.<ASTNode>} [body] - Program or function body
+ * @property {Array.<ASTNode>} [args] - Function call arguments
+ * @property {Array.<string>} [params] - Function parameters
+ * @property {Array.<string>} [parameters] - Function parameters (alternative)
+ * @property {ASTNode} [left] - Left operand (for binary expressions)
+ * @property {ASTNode} [right] - Right operand (for binary expressions)
+ * @property {ASTNode} [operand] - Operand (for unary expressions)
+ * @property {ASTNode} [table] - Table expression (for table access)
+ * @property {ASTNode} [key] - Key expression (for table access)
+ * @property {Array.<Object>} [entries] - Table entries (for table literals)
+ * @property {Array.<ASTNode>} [cases] - When expression cases
+ * @property {Array.<ASTNode>} [pattern] - Pattern matching patterns
+ * @property {Array.<ASTNode>} [result] - Pattern matching results
+ * @property {ASTNode} [value] - When expression value
+ */
+
+/**
+ * Parser: Converts tokens to an Abstract Syntax Tree (AST) using combinator-based architecture.
+ * 
+ * @param {Array.<Token>} tokens - Array of tokens from the lexer
+ * @returns {ASTNode} Abstract Syntax Tree with program body
+ * @throws {Error} For parsing errors like unexpected tokens or missing delimiters
+ * 
+ * @description The parser implements a combinator-based architecture where all
+ * operators are translated to function calls to standard library combinators.
+ * This reduces parsing ambiguity while preserving the original syntax.
+ * 
+ * The parser uses a recursive descent approach with proper operator precedence
+ * handling. Each operator expression (e.g., x + y) is translated to a FunctionCall
+ * node (e.g., add(x, y)) that will be executed by the interpreter using the
+ * corresponding combinator function.
+ * 
+ * Key architectural decisions:
+ * - All operators become FunctionCall nodes to eliminate ambiguity
+ * - Operator precedence is handled through recursive parsing functions
+ * - Function calls are detected by looking for identifiers followed by expressions
+ * - When expressions and case patterns are parsed with special handling
+ * - Table literals and access are parsed as structured data
+ * - Function composition uses 'via' keyword with right-associative precedence
+ * - Function application uses juxtaposition with left-associative precedence
+ * 
+ * The parser maintains a current token index and advances through the token
+ * stream, building the AST bottom-up from primary expressions to logical
+ * expressions. This approach ensures that all operations are consistently
+ * represented as function calls, enabling the interpreter to use the combinator
+ * foundation for execution.
+ * 
+ * This design choice reduces the need for special operator handling in the
+ * interpreter and enables abstractions through the combinator foundation.
+ * All operations become function calls, providing a consistent and extensible
+ * execution model that can be enhanced by adding new combinator functions.
+ * 
+ * The parser implements a top-down recursive descent strategy where each
+ * parsing function handles a specific precedence level. This approach ensures
+ * that operator precedence is correctly enforced while maintaining clear
+ * separation of concerns for different language constructs.
+ * 
+ * Error handling is designed to provide meaningful feedback by including
+ * context about what was expected and what was found. This enables users
+ * to quickly identify and fix parsing errors in their code.
+ */
+export function parser(tokens) {
+    let current = 0;
+    
+    /**
+     * Main parsing function that processes the entire token stream
+     * 
+     * @returns {ASTNode} Complete AST with program body
+     * @description Iterates through all tokens, parsing each statement or expression
+     * and building the program body. Handles empty programs gracefully.
+     * 
+     * This function orchestrates the parsing process by repeatedly calling walk()
+     * until all tokens are consumed. It ensures that the final AST contains all
+     * statements and expressions in the correct order, ready for interpretation
+     * by the combinator-based interpreter.
+     * 
+     * The function implements the top-level parsing strategy by processing each
+     * statement or expression in sequence. This approach enables the parser to
+      * handle programs with multiple statements while maintaining the
+ * combinator-based architecture where all operations become function calls.
+ * 
+ * Each call to walk() processes one complete statement or expression, ensuring
+ * that the parser can handle programs of various sizes while maintaining
+     * clear separation between different language constructs.
+     * 
+     * The function returns a Program node that contains all parsed statements
+     * and expressions in the order they appeared in the source code. This
+     * structure enables the interpreter to execute statements sequentially
+     * while maintaining proper scope and state management.
+     */
+    function parse() {
+        const body = [];
+        
+        while (current < tokens.length) {
+            const node = walk();
+            if (node) {
+                body.push(node);
+            }
+        }
+        
+        return { type: 'Program', body };
+    }
+    
+    /**
+     * Main walk function that dispatches to appropriate parsing functions
+     * 
+     * @returns {ASTNode|null} Parsed AST node or null for empty statements
+     * @description Determines the type of construct at the current position
+     * and delegates to the appropriate parsing function. The order of checks
+     * determines parsing precedence for top-level constructs.
+     * 
+     * Parsing order:
+     * 1. IO operations (highest precedence for top-level constructs)
+     * 2. Assignments (identifier followed by assignment token)
+     * 3. When expressions (pattern matching)
+     * 4. Function definitions (explicit function declarations)
+     * 5. Logical expressions (default case for all other expressions)
+     * 
+     * This function implements the top-level parsing strategy by checking for
+     * specific token patterns that indicate different language constructs.
+     * The order of checks is crucial for correct parsing precedence and
+      * ensures that expressions are properly decomposed into their
+ * constituent parts for combinator translation.
+ * 
+ * The function uses a pattern-matching approach to identify language constructs
+ * based on token sequences. This design enables the parser to handle various
+     * syntax while maintaining clear separation between different constructs.
+     * Each parsing function is responsible for handling its specific syntax
+     * and translating it into appropriate AST nodes for the combinator-based
+     * interpreter.
+     * 
+     * The function returns null for empty statements or whitespace, allowing
+     * the parser to gracefully handle programs with empty lines or comments
+     * without affecting the AST structure.
+     */
+    function walk() {
+        const token = tokens[current];
+        
+        if (!token) return null;
+        
+        // Handle IO operations first
+        if (token.type === TokenType.IO_IN) {
+            return parseIOIn();
+        }
+        if (token.type === TokenType.IO_OUT) {
+            return parseIOOut();
+        }
+        if (token.type === TokenType.IO_ASSERT) {
+            return parseIOAssert();
+        }
+        if (token.type === TokenType.IO_LISTEN) {
+            return parseIOListen();
+        }
+        if (token.type === TokenType.IO_EMIT) {
+            return parseIOEmit();
+        }
+        
+        // Handle assignments
+        if (token.type === TokenType.IDENTIFIER && 
+            current + 1 < tokens.length && 
+            tokens[current + 1].type === TokenType.ASSIGNMENT) {
+            return parseAssignment();
+        }
+        
+        // Handle when expressions
+        if (token.type === TokenType.WHEN) {
+            return parseWhenExpression();
+        }
+        
+        // Handle function definitions
+        if (token.type === TokenType.FUNCTION) {
+            return parseFunctionDefinition();
+        }
+        
+
+        
+        // For all other expressions, parse as logical expressions
+        return parseLogicalExpression();
+    }
+    
+    /**
+     * Parse assignment statements: identifier : expression;
+     * 
+     * @returns {ASTNode} Assignment AST node
+     * @throws {Error} For malformed assignments or missing semicolons
+     * @description Parses variable assignments and function definitions.
+     * Supports both simple assignments (x : 42) and arrow function definitions
+     * (f : x y -> x + y). Also handles when expressions as assignment values.
+     * 
+     * The function uses lookahead to distinguish between different assignment
+     * types and parses the value according to the detected type.
+     * 
+     * Assignment parsing is crucial for the language's variable binding system.
+     * The function supports multiple assignment patterns to provide flexibility
+     * while maintaining clear syntax. This includes traditional variable
+     * assignments, function definitions using arrow syntax, and when expressions
+     * that can be assigned to variables.
+     * 
+     * The function implements forward declaration support for recursive functions
+     * by allowing function definitions to reference themselves during parsing.
+     * This enables natural recursive function definitions without requiring
+     * special syntax or pre-declaration.
+     * 
+     * Error handling includes checks for missing semicolons and malformed
+     * assignment syntax, providing clear feedback to help users fix syntax errors.
+     */
+    function parseAssignment() {
+        const identifier = tokens[current].value;
+        current++; // Skip identifier
+        current++; // Skip assignment token (:)
+        
+        // Check if the value is a when expression
+        if (tokens[current].type === TokenType.WHEN) {
+            const value = parseWhenExpression();
+            
+            // Expect semicolon
+            if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+                current++;
+            }
+            
+            return {
+                type: 'Assignment',
+                identifier,
+                value
+            };
+        } else {
+            // Check if this is an arrow function: param1 param2 -> body
+            const params = [];
+            let isArrowFunction = false;
+            
+            // Look ahead to see if this is an arrow function
+            let lookAhead = current;
+            while (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.IDENTIFIER) {
+                lookAhead++;
+            }
+            
+            if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ARROW) {
+                // This is an arrow function
+                isArrowFunction = true;
+                
+                // Parse parameters
+                while (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                    params.push(tokens[current].value);
+                    current++;
+                }
+                
+                if (current >= tokens.length || tokens[current].type !== TokenType.ARROW) {
+                    throw new Error('Expected "->" after parameters in arrow function');
+                }
+                current++; // Skip '->'
+                
+                // Check if the body is a when expression
+                let body;
+                if (tokens[current].type === TokenType.WHEN) {
+                    body = parseWhenExpression();
+                } else {
+                    body = parseLogicalExpression();
+                }
+                
+                // Expect semicolon
+                if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+                    current++;
+                }
+                
+                return {
+                    type: 'Assignment',
+                    identifier,
+                    value: {
+                        type: 'FunctionDeclaration',
+                        params,
+                        body
+                    }
+                };
+            } else {
+                // Parse the value as an expression (function calls will be handled by expression parsing)
+                const value = parseLogicalExpression();
+                
+                // Expect semicolon
+                if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+                    current++;
+                }
+                
+                return {
+                    type: 'Assignment',
+                    identifier,
+                    value
+                };
+            }
+        }
+    }
+    
+    /**
+     * Parse when expressions: when value is pattern then result pattern then result;
+     * 
+     * @returns {ASTNode} WhenExpression AST node
+     * @throws {Error} For malformed when expressions
+     * @description Parses pattern matching expressions with support for single
+     * and multiple values/patterns. The when expression is the primary pattern
+     * matching construct in the language.
+     * 
+     * Supports:
+     * - Single value patterns: when x is 42 then "correct" _ then "wrong"
+     * - Multiple value patterns: when x y is 0 0 then "both zero" _ _ then "not both"
+     * - Wildcard patterns: _ (matches any value)
+     * - Function references: @functionName
+     * 
+     * The function parses values, patterns, and results, building a structured
+     * AST that the interpreter can efficiently evaluate.
+     * 
+     * When expression parsing is essential for pattern matching and conditional
+     * execution. It allows for flexible conditional logic where
+     * a single value or multiple values can be matched against a set of patterns,
+     * and the result of the match determines the next action.
+     * 
+     * The function implements a recursive descent parser that handles nested
+     * patterns and results. It correctly identifies the 'when' keyword,
+     * parses the value(s), and then iterates through cases, parsing patterns
+     * and results. The 'then' keyword is used to separate patterns from results.
+     * 
+     * Error handling includes checks for missing 'is' after value, malformed
+     * patterns, and unexpected tokens during pattern parsing.
+     */
+    function parseWhenExpression() {
+        if (DEBUG) {
+            console.log(`[DEBUG] parseWhenExpression: starting, current token = ${tokens[current].type}`);
+        }
+        current++; // Skip 'when'
+        
+        // Parse the value(s) - can be single value or multiple values
+        const values = [];
+        while (current < tokens.length && tokens[current].type !== TokenType.IS) {
+            // Use parsePrimary to handle all types of expressions including table access and function calls
+            let value;
+            if (tokens[current].type === TokenType.IO_LISTEN) {
+                // Handle IO listen in when expressions
+                value = parseIOListen();
+            } else if (tokens[current].type === TokenType.IO_EMIT) {
+                // Handle IO emit in when expressions
+                value = parseIOEmit();
+            } else {
+                // For all other types, use parsePrimary to handle expressions
+                value = parsePrimary();
+            }
+            values.push(value);
+        }
+        
+        if (current >= tokens.length || tokens[current].type !== TokenType.IS) {
+            throw new Error('Expected "is" after value in when expression');
+        }
+        current++; // Skip 'is'
+        
+        const cases = [];
+        
+        while (current < tokens.length) {
+            if (DEBUG) {
+                console.log(`[DEBUG] parseWhenExpression: starting new case, current token = ${tokens[current].type}, value = ${tokens[current].value || 'N/A'}`);
+            }
+            // Parse pattern(s) - can be single pattern or multiple patterns
+            const patterns = [];
+            
+            // Parse patterns until we hit THEN
+            while (current < tokens.length && tokens[current].type !== TokenType.THEN) {
+                let pattern;
+                if (DEBUG) {
+                    console.log(`[DEBUG] parseWhenExpression: parsing pattern, current token = ${tokens[current].type}, value = ${tokens[current].value || 'N/A'}`);
+                }
+                
+                // Check if this is a comparison expression (starts with identifier followed by comparison operator)
+                if (tokens[current].type === TokenType.IDENTIFIER && 
+                    current + 1 < tokens.length &&
+                    (tokens[current + 1].type === TokenType.LESS_THAN ||
+                     tokens[current + 1].type === TokenType.GREATER_THAN ||
+                     tokens[current + 1].type === TokenType.LESS_EQUAL ||
+                     tokens[current + 1].type === TokenType.GREATER_EQUAL ||
+                     tokens[current + 1].type === TokenType.EQUALS ||
+                     tokens[current + 1].type === TokenType.NOT_EQUAL)) {
+                    // Parse as a comparison expression
+                    pattern = parseExpression();
+                } else if (tokens[current].type === TokenType.IDENTIFIER) {
+                    // Check if this is a function call (identifier followed by arguments)
+                    if (current + 1 < tokens.length && isValidArgumentStart(tokens[current + 1])) {
+                        // Parse as a function call, but stop at THEN or semicolon
+                        const functionName = tokens[current].value;
+                        current++; // Skip function name
+                        
+                        // Parse arguments until we hit THEN, semicolon, or end of tokens
+                        const args = [];
+                        while (current < tokens.length && 
+                               tokens[current].type !== TokenType.THEN &&
+                               tokens[current].type !== TokenType.SEMICOLON) {
+                            const arg = parseLogicalExpression();
+                            args.push(arg);
+                        }
+                        
+                        pattern = {
+                            type: 'FunctionCall',
+                            name: functionName,
+                            args
+                        };
+                    } else {
+                        pattern = { type: 'Identifier', value: tokens[current].value };
+                        current++;
+                    }
+                } else if (tokens[current].type === TokenType.NUMBER) {
+                    pattern = { type: 'NumberLiteral', value: tokens[current].value };
+                    current++;
+                } else if (tokens[current].type === TokenType.STRING) {
+                    pattern = { type: 'StringLiteral', value: tokens[current].value };
+                    current++;
+                } else if (tokens[current].type === TokenType.WILDCARD) {
+                    pattern = { type: 'WildcardPattern' };
+                    current++;
+                } else if (tokens[current].type === TokenType.FUNCTION_REF) {
+                    pattern = { type: 'FunctionReference', name: tokens[current].name };
+                    current++;
+                } else if (tokens[current].type === TokenType.TRUE) {
+                    pattern = { type: 'BooleanLiteral', value: true };
+                    current++;
+                } else if (tokens[current].type === TokenType.FALSE) {
+                    pattern = { type: 'BooleanLiteral', value: false };
+                    current++;
+                } else if (tokens[current].type === TokenType.MINUS || tokens[current].type === TokenType.UNARY_MINUS) {
+                    // Handle negative numbers in patterns
+                    current++; // Skip minus token
+                    if (current >= tokens.length || tokens[current].type !== TokenType.NUMBER) {
+                        throw new Error('Expected number after minus in pattern');
+                    }
+                    pattern = { type: 'NumberLiteral', value: -tokens[current].value };
+                    current++;
+                } else if (tokens[current].type === TokenType.LEFT_BRACE) {
+                    // Handle table literals in patterns
+                    pattern = parseTableLiteral();
+                } else if (tokens[current].type === TokenType.LEFT_PAREN) {
+                    // Handle parenthesized expressions in patterns
+                    current++; // Skip '('
+                    pattern = parseLogicalExpression();
+                    if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
+                        throw new Error('Expected ")" after parenthesized expression in pattern');
+                    }
+                    current++; // Skip ')'
+                } else {
+                    throw new Error(`Expected pattern (identifier, number, string, wildcard, function reference, boolean, or comparison) in when expression, got ${tokens[current].type}`);
+                }
+                patterns.push(pattern);
+                
+                // If we have multiple patterns, we need to handle them correctly
+                // Check if the next token is a valid pattern start (not THEN)
+                if (current < tokens.length && 
+                    tokens[current].type !== TokenType.THEN &&
+                    tokens[current].type !== TokenType.SEMICOLON) {
+                    // Continue parsing more patterns
+                    continue;
+                }
+            }
+            
+            if (current >= tokens.length || tokens[current].type !== TokenType.THEN) {
+                throw new Error('Expected "then" after pattern in when expression');
+            }
+            current++; // Skip 'then'
+            
+            // Parse result - be careful not to parse beyond the result
+            let result;
+            
+            // Check if the next token after THEN is a pattern start
+            if (current < tokens.length) {
+                const nextToken = tokens[current];
+                if (nextToken.type === TokenType.IDENTIFIER ||
+                    nextToken.type === TokenType.NUMBER ||
+                    nextToken.type === TokenType.STRING ||
+                    nextToken.type === TokenType.WILDCARD ||
+                    nextToken.type === TokenType.FUNCTION_REF) {
+                    // Look ahead to see if this is actually a pattern
+                    let lookAhead = current;
+                    while (lookAhead < tokens.length && 
+                           tokens[lookAhead].type !== TokenType.THEN &&
+                           tokens[lookAhead].type !== TokenType.SEMICOLON) {
+                        lookAhead++;
+                    }
+                    
+                    if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.THEN) {
+                        // This is a pattern start, so the result is just the current token
+                        if (nextToken.type === TokenType.IDENTIFIER) {
+                            result = { type: 'Identifier', value: nextToken.value };
+                        } else if (nextToken.type === TokenType.NUMBER) {
+                            result = { type: 'NumberLiteral', value: nextToken.value };
+                        } else if (nextToken.type === TokenType.STRING) {
+                            result = { type: 'StringLiteral', value: nextToken.value };
+                        } else if (nextToken.type === TokenType.WILDCARD) {
+                            result = { type: 'WildcardPattern' };
+                        } else if (nextToken.type === TokenType.FUNCTION_REF) {
+                            result = { type: 'FunctionReference', name: nextToken.name };
+                        }
+                        current++; // Consume the token
+                    } else {
+                        // This is part of the result, parse normally
+                        result = parseLogicalExpression();
+                    }
+                } else if (nextToken.type === TokenType.WHEN) {
+                    // This is a nested when expression, parse it directly
+                    result = parseWhenExpression();
+                } else {
+                    // Not a pattern start, parse normally
+                    result = parseLogicalExpression();
+                }
+            } else {
+                result = parseLogicalExpression();
+            }
+            
+            cases.push({
+                pattern: patterns,
+                result: [result]
+            });
+            
+            if (DEBUG) {
+                console.log(`[DEBUG] parseWhenExpression: finished case, current token = ${tokens[current].type}, value = ${tokens[current].value || 'N/A'}`);
+            }
+            
+            // Enhanced termination logic for when expressions
+            if (current < tokens.length) {
+                const nextToken = tokens[current];
+                
+                if (DEBUG) {
+                    console.log(`[DEBUG] parseWhenExpression: checking termination, nextToken = ${nextToken.type}, value = ${nextToken.value || 'N/A'}`);
+                }
+                
+                // Stop on semicolon
+                if (nextToken.type === TokenType.SEMICOLON) {
+                    if (DEBUG) {
+                        console.log(`[DEBUG] parseWhenExpression: terminating on SEMICOLON`);
+                    }
+                    current++;
+                    break;
+                }
+                
+                // Stop on assignment (for consecutive assignments)
+                if (nextToken.type === TokenType.ASSIGNMENT) {
+                    if (DEBUG) {
+                        console.log(`[DEBUG] parseWhenExpression: terminating on ASSIGNMENT`);
+                    }
+                    break;
+                }
+                
+                // Stop on identifier that starts a new assignment
+                if (nextToken.type === TokenType.IDENTIFIER) {
+                    // Look ahead to see if this is the start of a new assignment
+                    let lookAhead = current;
+                    while (lookAhead < tokens.length && 
+                           tokens[lookAhead].type !== TokenType.ASSIGNMENT &&
+                           tokens[lookAhead].type !== TokenType.SEMICOLON &&
+                           tokens[lookAhead].type !== TokenType.THEN) {
+                        lookAhead++;
+                    }
+                    
+                    if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ASSIGNMENT) {
+                        // This is the start of a new assignment, terminate the when expression
+                        if (DEBUG) {
+                            console.log(`[DEBUG] parseWhenExpression: terminating on new assignment starting with ${nextToken.value}`);
+                        }
+                        break;
+                    }
+                }
+                
+                // Stop on right brace (for when expressions inside table literals)
+                if (nextToken.type === TokenType.RIGHT_BRACE) {
+                    if (DEBUG) {
+                        console.log(`[DEBUG] parseWhenExpression: terminating on RIGHT_BRACE`);
+                    }
+                    break;
+                }
+                
+                // Stop on comma (for when expressions inside table literals)
+                if (nextToken.type === TokenType.COMMA) {
+                    if (DEBUG) {
+                        console.log(`[DEBUG] parseWhenExpression: terminating on COMMA`);
+                    }
+                    break;
+                }
+            }
+        }
+        
+        return {
+            type: 'WhenExpression',
+            value: values.length === 1 ? values[0] : values,
+            cases
+        };
+    }
+    
+
+
+    /**
+     * Parse function definitions: function (params) : body
+     * 
+     * @returns {ASTNode} FunctionDefinition AST node
+     * @throws {Error} For malformed function definitions
+     * @description Parses explicit function declarations with parameter lists
+     * and function bodies. This is the traditional function definition syntax
+     * as opposed to arrow functions.
+     * 
+     * The function expects:
+     * - function keyword
+     * - Parenthesized parameter list
+     * - Assignment token (:)
+     * - Function body expression
+     * 
+     * Function definition parsing is fundamental to the language's ability to
+     * define reusable functions. It supports traditional function declarations
+     * with explicit parameter lists and function bodies.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * 'function' keyword, parameter parsing, and the assignment token.
+     * It then recursively parses the function body, which can be any valid
+     * expression.
+     * 
+     * Error handling includes checks for missing '(' after function keyword,
+     * missing ')' after function parameters, and missing ':' after parameters.
+     */
+    function parseFunctionDefinition() {
+        current++; // Skip 'function'
+        
+        if (current >= tokens.length || tokens[current].type !== TokenType.LEFT_PAREN) {
+            throw new Error('Expected "(" after function keyword');
+        }
+        current++; // Skip '('
+        
+        const parameters = [];
+        while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_PAREN) {
+            if (tokens[current].type === TokenType.IDENTIFIER) {
+                parameters.push(tokens[current].value);
+                current++;
+                
+                if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
+                    current++; // Skip comma
+                }
+            } else {
+                throw new Error('Expected parameter name in function definition');
+            }
+        }
+        
+        if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
+            throw new Error('Expected ")" after function parameters');
+        }
+        current++; // Skip ')'
+        
+        if (current >= tokens.length || tokens[current].type !== TokenType.ASSIGNMENT) {
+            throw new Error('Expected ":" after function parameters');
+        }
+        current++; // Skip ':'
+        
+        const body = parseLogicalExpression();
+        
+        return {
+            type: 'FunctionDefinition',
+            parameters,
+            body
+        };
+    }
+    
+    /**
+     * Parse IO input operations: ..in
+     * 
+     * @returns {ASTNode} IOInExpression AST node
+     * @description Parses input operations that read from standard input.
+     * The operation is represented as a simple AST node that the interpreter
+     * will handle by prompting for user input.
+     * 
+     * IO input parsing is crucial for interactive programs that require
+     * user interaction. It allows for simple and direct input operations
+     * that read values from the standard input stream.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * '..in' keyword and expects a semicolon after the operation.
+     * 
+     * Error handling includes checks for missing semicolon after input operation.
+     */
+    function parseIOIn() {
+        current++; // Skip IO_IN token
+        return { type: 'IOInExpression' };
+    }
+    
+    /**
+     * Parse IO output operations: ..out expression
+     * 
+     * @returns {ASTNode} IOOutExpression AST node
+     * @throws {Error} For malformed output expressions
+     * @description Parses output operations that write to standard output.
+     * The expression to output is parsed as a logical expression and will
+     * be evaluated by the interpreter before being printed.
+     * 
+     * IO output parsing is essential for programs that need to display
+     * information to the user. It allows for expressions to be evaluated
+     * and their results to be printed to the standard output stream.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * '..out' keyword and expects a semicolon after the expression.
+     * 
+     * Error handling includes checks for missing semicolon after output expression.
+     */
+    function parseIOOut() {
+        current++; // Skip IO_OUT token
+        const value = parseLogicalExpression();
+        
+        // Expect semicolon
+        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+            current++;
+        }
+        
+        return {
+            type: 'IOOutExpression',
+            value
+        };
+    }
+    
+    /**
+     * Parse IO assert operations: ..assert expression
+     * 
+     * @returns {ASTNode} IOAssertExpression AST node
+     * @throws {Error} For malformed assert expressions
+     * @description Parses assertion operations that verify conditions.
+     * The expression is parsed as a logical expression and will be evaluated
+     * by the interpreter. If the result is falsy, an assertion error is thrown.
+     * 
+     * IO assert parsing is important for programs that need to perform
+     * runtime checks or assertions. It allows for expressions to be evaluated
+     * and their boolean results to be used for conditional execution or
+     * error reporting.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * '..assert' keyword and expects a semicolon after the expression.
+     * 
+     * Error handling includes checks for missing semicolon after assert expression.
+     */
+    function parseIOAssert() {
+        current++; // Skip IO_ASSERT token
+        const value = parseLogicalExpression();
+        
+        // Expect semicolon
+        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+            current++;
+        }
+        
+        return {
+            type: 'IOAssertExpression',
+            value
+        };
+    }
+
+    /**
+     * Parse IO listen operations: ..listen
+     * 
+     * @returns {ASTNode} IOListenExpression AST node
+     * @description Parses listen operations that retrieve current state.
+     * Returns the current state from the external system without any parameters.
+     * 
+     * IO listen parsing is useful for programs that need to query the
+     * current state of an external system or environment. It allows for
+     * simple retrieval of state without requiring any input parameters.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * '..listen' keyword and expects a semicolon after the operation.
+     * 
+     * Error handling includes checks for missing semicolon after listen operation.
+     */
+    function parseIOListen() {
+        current++; // Skip IO_LISTEN token
+        
+        // Expect semicolon
+        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+            current++;
+        }
+        
+        return {
+            type: 'IOListenExpression'
+        };
+    }
+
+    /**
+     * Parse IO emit operations: ..emit expression
+     * 
+     * @returns {ASTNode} IOEmitExpression AST node
+     * @throws {Error} For malformed emit expressions
+     * @description Parses emit operations that send values to external system.
+     * The expression is parsed as a logical expression and will be evaluated
+     * by the interpreter before being sent to the external system.
+     * 
+     * IO emit parsing is essential for programs that need to interact with
+     * external systems or environments. It allows for expressions to be
+     * evaluated and their results to be sent to the external system.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * '..emit' keyword and expects a semicolon after the expression.
+     * 
+     * Error handling includes checks for missing semicolon after emit expression.
+     */
+    function parseIOEmit() {
+        current++; // Skip IO_EMIT token
+        const value = parseLogicalExpression();
+        
+        // Expect semicolon
+        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+            current++;
+        }
+        
+        return {
+            type: 'IOEmitExpression',
+            value
+        };
+    }
+    
+    /**
+     * Parse logical expressions with proper precedence
+     * 
+     * @returns {ASTNode} AST node representing the logical expression
+     * @description Parses logical expressions (and, or, xor) with the lowest
+     * precedence. All logical operators are translated to FunctionCall nodes
+     * using the corresponding combinator functions.
+     * 
+     * Logical expression parsing is the foundation for conditional logic
+     * in the language. It handles the lowest precedence operators (and, or, xor)
+     * and translates them to combinator function calls.
+     * 
+     * The function implements a recursive descent parser that handles
+     * operator precedence by repeatedly calling itself with the right operand
+     * until no more operators of the same precedence are found.
+     * 
+     * Error handling includes checks for missing operators or operands.
+     */
+    function parseLogicalExpression() {
+        let left = parseExpression();
+        
+        while (current < tokens.length) {
+            const token = tokens[current];
+            
+            if (token.type === TokenType.AND || 
+                token.type === TokenType.OR || 
+                token.type === TokenType.XOR) {
+                current++;
+                const right = parseExpression();
+                left = {
+                    type: 'FunctionCall',
+                    name: token.type === TokenType.AND ? 'logicalAnd' :
+                          token.type === TokenType.OR ? 'logicalOr' : 'logicalXor',
+                    args: [left, right]
+                };
+            } else {
+                break;
+            }
+        }
+        
+        return left;
+    }
+    
+    /**
+     * Parse comparison expressions
+     * 
+     * @returns {ASTNode} AST node representing the comparison expression
+     * @description Parses comparison expressions (=, !=, <, >, <=, >=) and
+     * additive expressions (+, -). All operators are translated to FunctionCall
+     * nodes using the corresponding combinator functions.
+     * 
+     * This function implements the core of the combinator-based architecture
+     * by translating operator expressions to function calls that will be
+     * executed by the interpreter using standard library combinators.
+     * 
+     * Comparison expression parsing is crucial for conditional logic
+     * and arithmetic operations. It handles equality, inequality,
+     * comparison operators, and additive operators.
+     * 
+     * The function implements a recursive descent parser that handles
+     * operator precedence by repeatedly calling itself with the right operand
+     * until no more operators of the same precedence are found.
+     * 
+     * Error handling includes checks for missing operators or operands.
+     */
+    function parseExpression() {
+        if (DEBUG) {
+            console.log(`[DEBUG] parseExpression: starting, current token = ${tokens[current].type}`);
+        }
+        
+        // Handle IO operations in expressions
+        if (current < tokens.length) {
+            const token = tokens[current];
+            if (token.type === TokenType.IO_LISTEN) {
+                return parseIOListen();
+            }
+            if (token.type === TokenType.IO_EMIT) {
+                return parseIOEmit();
+            }
+        }
+        
+        // Handle unary minus at the beginning of expressions
+        let left;
+        if (current < tokens.length && (tokens[current].type === TokenType.MINUS || tokens[current].type === TokenType.UNARY_MINUS)) {
+            if (DEBUG) {
+                console.log(`[DEBUG] parseExpression: handling unary minus`);
+            }
+            current++;
+            const operand = parseTerm();
+            left = {
+                type: 'FunctionCall',
+                name: 'negate',
+                args: [operand]
+            };
+        } else {
+            left = parseTerm();
+        }
+        
+        if (DEBUG) {
+            console.log(`[DEBUG] parseExpression: after parseTerm, current token = ${tokens[current].type}`);
+        }
+        
+        while (current < tokens.length) {
+            const token = tokens[current];
+            
+            if (DEBUG) {
+                console.log(`[DEBUG] parseExpression: while loop, current token = ${token.type}, value = ${token.value || 'N/A'}`);
+            }
+            
+            if (token.type === TokenType.PLUS) {
+                current++;
+                const right = parseTerm();
+                left = {
+                    type: 'FunctionCall',
+                    name: 'add',
+                    args: [left, right]
+                };
+            } else if (token.type === TokenType.MINUS || token.type === TokenType.BINARY_MINUS) {
+                current++;
+                const right = parseTerm();
+                left = {
+                    type: 'FunctionCall',
+                    name: 'subtract',
+                    args: [left, right]
+                };
+            } else if (token.type === TokenType.EQUALS || 
+                       token.type === TokenType.NOT_EQUAL ||
+                       token.type === TokenType.LESS_THAN ||
+                       token.type === TokenType.GREATER_THAN ||
+                       token.type === TokenType.LESS_EQUAL ||
+                       token.type === TokenType.GREATER_EQUAL) {
+                current++;
+                const right = parseTerm();
+                left = {
+                    type: 'FunctionCall',
+                    name: token.type === TokenType.EQUALS ? 'equals' :
+                          token.type === TokenType.NOT_EQUAL ? 'notEquals' :
+                          token.type === TokenType.LESS_THAN ? 'lessThan' :
+                          token.type === TokenType.GREATER_THAN ? 'greaterThan' :
+                          token.type === TokenType.LESS_EQUAL ? 'lessEqual' : 'greaterEqual',
+                    args: [left, right]
+                };
+            } else {
+                break;
+            }
+        }
+        
+        return left;
+    }
+    
+    /**
+     * Parse multiplication and division expressions
+     * 
+     * @returns {ASTNode} AST node representing the multiplicative expression
+     * @description Parses multiplicative expressions (*, /, %) with higher
+     * precedence than additive expressions. All operators are translated to
+     * FunctionCall nodes using the corresponding combinator functions.
+     * 
+     * Multiplicative expression parsing is crucial for arithmetic operations
+     * and mathematical calculations. It handles multiplication, division,
+     * and modulo operations.
+     * 
+     * The function implements a recursive descent parser that handles
+     * operator precedence by repeatedly calling itself with the right operand
+     * until no more operators of the same precedence are found.
+     * 
+     * Error handling includes checks for missing operators or operands.
+     */
+    function parseTerm() {
+        if (DEBUG) {
+            console.log(`[DEBUG] parseTerm: starting, current token = ${tokens[current].type}`);
+        }
+        let left = parseApplication();
+        
+        while (current < tokens.length) {
+            const token = tokens[current];
+            
+            if (token.type === TokenType.MULTIPLY || 
+                token.type === TokenType.DIVIDE || 
+                token.type === TokenType.MODULO) {
+                current++;
+                const right = parseFactor();
+                left = {
+                    type: 'FunctionCall',
+                    name: token.type === TokenType.MULTIPLY ? 'multiply' :
+                          token.type === TokenType.DIVIDE ? 'divide' : 'modulo',
+                    args: [left, right]
+                };
+            } else if (token.type === TokenType.MINUS) {
+                current++;
+                const right = parseFactor();
+                left = {
+                    type: 'FunctionCall',
+                    name: 'subtract',
+                    args: [left, right]
+                };
+            } else {
+                break;
+            }
+        }
+        
+        return left;
+    }
+    
+    /**
+     * Parse power expressions and unary operators
+     * 
+     * @returns {ASTNode} AST node representing the factor expression
+     * @description Parses power expressions (^) and unary operators (not, -)
+     * with the highest precedence among operators. All operators are translated
+     * to FunctionCall nodes using the corresponding combinator functions.
+     * 
+     * Factor expression parsing is crucial for exponentiation and unary
+     * operators. It handles power expressions and unary operators (not, -).
+     * 
+     * The function implements a recursive descent parser that handles
+     * operator precedence by repeatedly calling itself with the right operand
+     * until no more operators of the same precedence are found.
+     * 
+     * Error handling includes checks for missing operators or operands.
+     */
+    function parseFactor() {
+        if (DEBUG) {
+            console.log(`[DEBUG] parseFactor: starting, current token = ${tokens[current].type}`);
+        }
+        let left = parsePrimary();
+        
+        // Parse power expressions (existing logic)
+        while (current < tokens.length) {
+            const token = tokens[current];
+            
+            if (token.type === TokenType.POWER) {
+                current++;
+                const right = parsePrimary();
+                left = {
+                    type: 'FunctionCall',
+                    name: 'power',
+                    args: [left, right]
+                };
+            } else {
+                break;
+            }
+        }
+        
+        return left;
+    }
+    
+    /**
+     * Parse function composition expressions using the 'via' keyword
+     * 
+     * @returns {ASTNode} AST node representing the composition expression
+     * @throws {Error} For malformed composition expressions
+     * @description Parses function composition using the 'via' keyword
+     * with right-associative precedence: f via g via h = compose(f, compose(g, h))
+     * 
+     * The 'via' operator provides natural function composition syntax that reads
+     * from right to left, matching mathematical function composition notation.
+     * 
+     * Precedence and associativity:
+     * - 'via' has higher precedence than function application (juxtaposition)
+     * - 'via' is right-associative: f via g via h = compose(f, compose(g, h))
+     * - This means: f via g via h(x) = compose(f, compose(g, h))(x) = f(g(h(x)))
+     * 
+     * Translation examples:
+     * - 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)))
+     * 
+     * The right-associative design choice enables natural reading of composition
+     * chains that matches mathematical notation where (f ∘ g ∘ h)(x) = f(g(h(x))).
+     * 
+     * Function composition is a fundamental feature that allows functions to be
+     * combined naturally. The right-associative precedence means that composition
+     * chains are built from right to left, which matches mathematical function
+      * composition notation. This enables functional programming patterns
+ * where transformations can be built from simple, composable functions.
+     * 
+     * Composition parsing is essential for functional programming patterns
+     * where functions are composed together. It handles the 'via' keyword
+     * and recursively composes functions from right to left.
+     * 
+     * The function implements a recursive descent parser that handles the
+     * 'via' keyword and recursively composes functions.
+     * 
+     * Error handling includes checks for missing 'via' keyword or malformed
+     * composition chains.
+     */
+    function parseComposition() {
+        let left = parseFactor();
+        
+        // Parse right-associative composition: f via g via h = compose(f, compose(g, h))
+        while (current < tokens.length && tokens[current].type === TokenType.COMPOSE) {
+            current++; // Skip 'via'
+            const right = parseFactor();
+            
+            left = {
+                type: 'FunctionCall',
+                name: 'compose',
+                args: [left, right]
+            };
+        }
+        
+        return left;
+    }
+    
+    /**
+     * Parse function application (juxtaposition)
+     * 
+     * @returns {ASTNode} AST node representing the function application
+     * @description Parses function application using juxtaposition (f x)
+     * with left-associative precedence: f g x = apply(apply(f, g), x)
+     * 
+     * Function application using juxtaposition is the primary mechanism for
+     * calling functions in the language. The left-associative precedence means
+     * that application chains are built from left to right, which is intuitive
+     * for most programmers. This approach reduces the need for parentheses
+     * in many cases while maintaining clear precedence rules.
+     * 
+     * Function application parsing is essential for calling functions in
+     * the language. It handles juxtaposition of function and argument expressions.
+     * 
+     * The function implements a recursive descent parser that handles
+     * left-associative function application. It repeatedly calls itself
+     * with the right operand until no more function applications are found.
+     * 
+     * Error handling includes checks for missing function or argument expressions.
+     */
+    function parseApplication() {
+        let left = parseComposition();
+        
+        // Parse left-associative function application: f g x = apply(apply(f, g), x)
+        while (current < tokens.length && isValidArgumentStart(tokens[current])) {
+            const arg = parseComposition(); // Parse the argument as a composition expression
+            left = {
+                type: 'FunctionCall',
+                name: 'apply',
+                args: [left, arg]
+            };
+        }
+        
+        return left;
+    }
+    
+    /**
+     * Check if a token is a valid start of a function argument
+     * 
+     * @param {Token} token - Token to check
+     * @returns {boolean} True if the token can start a function argument
+     * @description Determines if a token can be the start of a function argument.
+     * This is used to detect function application (juxtaposition) where function
+     * application binds tighter than infix operators.
+     * 
+     * This function is crucial for the juxtaposition-based function application
+     * system. It determines when the parser should treat an expression as a
+     * function argument rather than as part of an infix operator expression.
+     * The tokens that can start arguments are carefully chosen to ensure that
+     * function application has the correct precedence relative to operators.
+     */
+    function isValidArgumentStart(token) {
+        return token.type === TokenType.IDENTIFIER ||
+               token.type === TokenType.NUMBER ||
+               token.type === TokenType.STRING ||
+               token.type === TokenType.LEFT_PAREN ||
+               token.type === TokenType.LEFT_BRACE ||
+               token.type === TokenType.TRUE ||
+               token.type === TokenType.FALSE ||
+               token.type === TokenType.FUNCTION_REF ||
+               token.type === TokenType.FUNCTION_ARG ||
+               token.type === TokenType.NOT ||
+               token.type === TokenType.UNARY_MINUS;
+    }
+    
+    /**
+     * Parse table literals: {key: value, key2: value2} or {value1, value2, value3}
+     * 
+     * @returns {ASTNode} TableLiteral AST node
+     * @throws {Error} For malformed table literals
+     * @description Parses table literals with support for both key-value pairs
+     * and array-like entries. Tables are the primary data structure in the language.
+     * 
+     * Supports:
+     * - Key-value pairs: {name: "Alice", age: 30}
+     * - Array-like entries: {1, 2, 3}
+     * - Mixed entries: {1, 2, name: "Alice", 3}
+     * 
+     * Array-like entries are automatically assigned numeric keys starting from 1.
+     * 
+     * Table literal parsing is essential for defining and accessing
+     * key-value or array-like data structures. It handles curly braces,
+     * keys, and values.
+     * 
+     * The function implements a recursive descent parser that handles
+     * nested structures and supports both key-value and array-like entries.
+     * 
+     * Error handling includes checks for missing braces, malformed keys,
+     * and unexpected tokens.
+     */
+    function parseTableLiteral() {
+        current++; // Skip '{'
+        
+        const entries = [];
+        
+        while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_BRACE) {
+            // Check if this is a key-value pair or just a value
+            let key = null;
+            let value;
+            
+            // Parse the first element
+            if (tokens[current].type === TokenType.IDENTIFIER) {
+                // Could be a key or a value
+                const identifier = tokens[current].value;
+                current++;
+                
+                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
+                    // This is a key-value pair: key : value
+                    key = { type: 'Identifier', value: identifier };
+                    current++; // Skip ':'
+                    
+                    // Check if the value is an arrow function
+                    let isArrowFunction = false;
+                    let lookAhead = current;
+                    
+                    // Look ahead to see if this is an arrow function
+                    while (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.IDENTIFIER) {
+                        lookAhead++;
+                    }
+                    
+                    if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ARROW) {
+                        // This is an arrow function
+                        isArrowFunction = true;
+                        
+                        // Parse parameters
+                        const params = [];
+                        while (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                            params.push(tokens[current].value);
+                            current++;
+                        }
+                        
+                        if (current >= tokens.length || tokens[current].type !== TokenType.ARROW) {
+                            throw new Error('Expected "->" after parameters in arrow function');
+                        }
+                        current++; // Skip '->'
+                        
+                        // Check if the body is a when expression
+                        let body;
+                        if (tokens[current].type === TokenType.WHEN) {
+                            body = parseWhenExpression();
+                        } else {
+                            body = parseLogicalExpression();
+                        }
+                        
+                        value = {
+                            type: 'FunctionDeclaration',
+                            params,
+                            body
+                        };
+                    } else {
+                        // This is a regular value
+                        value = parseLogicalExpression();
+                    }
+                } else {
+                    // This is just a value (array-like entry)
+                    value = { type: 'Identifier', value: identifier };
+                }
+            } else if (tokens[current].type === TokenType.NUMBER) {
+                // Could be a numeric key or a value
+                const number = tokens[current].value;
+                current++;
+                
+                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
+                    // This is a key-value pair: number : value
+                    key = { type: 'NumberLiteral', value: number };
+                    current++; // Skip ':'
+                    value = parseLogicalExpression();
+                } else {
+                    // This is just a value (array-like entry)
+                    value = { type: 'NumberLiteral', value: number };
+                }
+            } else if (tokens[current].type === TokenType.TRUE) {
+                // Could be a boolean key or a value
+                current++;
+                
+                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
+                    // This is a key-value pair: true : value
+                    key = { type: 'BooleanLiteral', value: true };
+                    current++; // Skip ':'
+                    value = parseLogicalExpression();
+                } else {
+                    // This is just a value (array-like entry)
+                    value = { type: 'BooleanLiteral', value: true };
+                }
+            } else if (tokens[current].type === TokenType.FALSE) {
+                // Could be a boolean key or a value
+                current++;
+                
+                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
+                    // This is a key-value pair: false : value
+                    key = { type: 'BooleanLiteral', value: false };
+                    current++; // Skip ':'
+                    value = parseLogicalExpression();
+                } else {
+                    // This is just a value (array-like entry)
+                    value = { type: 'BooleanLiteral', value: false };
+                }
+            } else if (tokens[current].type === TokenType.LEFT_PAREN) {
+                // This could be a computed key or a value
+                const expression = parseLogicalExpression();
+                
+                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
+                    // This is a key-value pair: (expression) : value
+                    key = expression;
+                    current++; // Skip ':'
+                    value = parseLogicalExpression();
+                } else {
+                    // This is just a value (array-like entry)
+                    value = expression;
+                }
+            } else {
+                // Check if this is an arrow function: param1 param2 -> body
+                let isArrowFunction = false;
+                let lookAhead = current;
+                
+                // Look ahead to see if this is an arrow function
+                while (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.IDENTIFIER) {
+                    lookAhead++;
+                }
+                
+                if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ARROW) {
+                    // This is an arrow function
+                    isArrowFunction = true;
+                    
+                    // Parse parameters
+                    const params = [];
+                    while (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                        params.push(tokens[current].value);
+                        current++;
+                    }
+                    
+                    if (current >= tokens.length || tokens[current].type !== TokenType.ARROW) {
+                        throw new Error('Expected "->" after parameters in arrow function');
+                    }
+                    current++; // Skip '->'
+                    
+                    // Check if the body is a when expression
+                    let body;
+                    if (tokens[current].type === TokenType.WHEN) {
+                        body = parseWhenExpression();
+                    } else {
+                        body = parseLogicalExpression();
+                    }
+                    
+                    value = {
+                        type: 'FunctionDeclaration',
+                        params,
+                        body
+                    };
+                } else {
+                    // This is a regular value (array-like entry)
+                    value = parseLogicalExpression();
+                }
+            }
+            
+            entries.push({ key, value });
+            
+            // Skip comma if present
+            if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
+                current++;
+            }
+        }
+        
+        if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_BRACE) {
+            throw new Error('Expected "}" after table literal');
+        }
+        current++; // Skip '}'
+        
+        return {
+            type: 'TableLiteral',
+            entries
+        };
+    }
+    
+
+    
+    /**
+     * Parse function calls: functionName arg1 arg2 ...
+     * 
+     * @returns {ASTNode} FunctionCall AST node
+     * @description Parses function calls with multiple arguments. This function
+     * is used by parsePrimary to detect when an identifier is followed by
+     * expressions that should be treated as function arguments.
+     * 
+     * Function calls are detected by the presence of an identifier followed
+     * by expressions that are not operators. The parser uses lookahead to
+     * determine if an identifier should be treated as a function call.
+     * 
+     * Function call parsing is essential for calling functions in the language.
+     * It handles the juxtaposition of function names and their arguments.
+     * 
+     * The function implements a recursive descent parser that handles
+     * the function name, followed by a parenthesized list of arguments.
+     * 
+     * Error handling includes checks for missing function name or arguments.
+     */
+    function parseFunctionCall() {
+        const functionName = tokens[current].value;
+        current++; // Skip function name
+        
+        // Parse arguments until we hit a semicolon or end of tokens
+        const args = [];
+        while (current < tokens.length && tokens[current].type !== TokenType.SEMICOLON) {
+            const arg = parseLogicalExpression();
+            args.push(arg);
+        }
+        
+        return {
+            type: 'FunctionCall',
+            name: functionName,
+            args
+        };
+    }
+    
+    /**
+     * Parse primary expressions (literals, identifiers, parenthesized expressions)
+     * 
+     * @returns {ASTNode} AST node representing the primary expression
+     * @throws {Error} For unexpected tokens or malformed expressions
+     * @description Parses the highest precedence expressions including literals,
+     * identifiers, function calls, table access, and parenthesized expressions.
+     * This is the foundation of the expression parsing hierarchy.
+     * 
+     * The function implements function call detection by looking
+     * for identifiers followed by expressions that could be arguments. This
+     * approach allows the language to support both traditional function calls
+     * and the ML-style function application syntax.
+     * 
+     * Supports:
+     * - Literals: numbers, strings, booleans
+     * - Identifiers: variables and function names
+     * - Function calls: f(x, y) or f x y
+     * - Table access: table[key] or table.property
+     * - Parenthesized expressions: (x + y)
+     * - Unary operators: not x, -x
+     * - Function references: @functionName
+     * 
+     * Primary expression parsing is the foundation of all other expression
+     * parsing. It handles literals, identifiers, function calls, table access,
+     * parenthesized expressions, and unary operators.
+     * 
+     * The function implements a recursive descent parser that handles
+     * each specific type of primary expression.
+     * 
+     * Error handling includes checks for missing literals, malformed
+     * identifiers, and unexpected tokens.
+     */
+    function parsePrimary() {
+        const token = tokens[current];
+        
+        if (!token) {
+            throw new Error('Unexpected end of input');
+        }
+        
+        if (DEBUG) {
+            console.log(`[DEBUG] parsePrimary: current token = ${token.type}, value = ${token.value || 'N/A'}`);
+        }
+        
+        switch (token.type) {
+            case TokenType.NUMBER:
+                current++;
+                return { type: 'NumberLiteral', value: token.value };
+                
+            case TokenType.STRING:
+                current++;
+                return { type: 'StringLiteral', value: token.value };
+                
+            case TokenType.TRUE:
+                current++;
+                return { type: 'BooleanLiteral', value: true };
+                
+            case TokenType.FALSE:
+                current++;
+                return { type: 'BooleanLiteral', value: false };
+                
+            case TokenType.WHEN:
+                return parseWhenExpression();
+                
+
+                
+            case TokenType.IDENTIFIER:
+                const identifierValue = token.value;
+                current++;
+                
+                // Check for table access: identifier[key] or identifier.property
+                if (current < tokens.length && tokens[current].type === TokenType.LEFT_BRACKET) {
+                    current++; // Skip '['
+                    const keyExpression = parseLogicalExpression();
+                    
+                    if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_BRACKET) {
+                        throw new Error('Expected "]" after table key');
+                    }
+                    current++; // Skip ']'
+                    
+                    let tableNode = {
+                        type: 'TableAccess',
+                        table: { type: 'Identifier', value: identifierValue },
+                        key: keyExpression
+                    };
+                    
+                    // Check for chained access: table[key].property or table[key][key2]
+                    while (current < tokens.length && (tokens[current].type === TokenType.DOT || tokens[current].type === TokenType.LEFT_BRACKET)) {
+                        if (tokens[current].type === TokenType.DOT) {
+                            current++; // Skip '.'
+                            
+                            if (current >= tokens.length || tokens[current].type !== TokenType.IDENTIFIER) {
+                                throw new Error('Expected identifier after "." in table access');
+                            }
+                            
+                            const propertyName = tokens[current].value;
+                            current++; // Skip property name
+                            
+                            tableNode = {
+                                type: 'TableAccess',
+                                table: tableNode,
+                                key: { type: 'Identifier', value: propertyName }
+                            };
+                        } else if (tokens[current].type === TokenType.LEFT_BRACKET) {
+                            current++; // Skip '['
+                            const keyExpression2 = parseLogicalExpression();
+                            
+                            if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_BRACKET) {
+                                throw new Error('Expected "]" after table key');
+                            }
+                            current++; // Skip ']'
+                            
+                            tableNode = {
+                                type: 'TableAccess',
+                                table: tableNode,
+                                key: keyExpression2
+                            };
+                        }
+                    }
+                    
+                    return tableNode;
+                } else if (current < tokens.length && tokens[current].type === TokenType.DOT) {
+                    current++; // Skip '.'
+                    
+                    if (current >= tokens.length || tokens[current].type !== TokenType.IDENTIFIER) {
+                        throw new Error('Expected identifier after "." in table access');
+                    }
+                    
+                    const propertyName = tokens[current].value;
+                    current++; // Skip property name
+                    
+                    let tableNode = {
+                        type: 'TableAccess',
+                        table: { type: 'Identifier', value: identifierValue },
+                        key: { type: 'Identifier', value: propertyName }
+                    };
+                    
+                    // Check for chained access: table.property[key] or table.property.property2
+                    while (current < tokens.length && (tokens[current].type === TokenType.DOT || tokens[current].type === TokenType.LEFT_BRACKET)) {
+                        if (tokens[current].type === TokenType.DOT) {
+                            current++; // Skip '.'
+                            
+                            if (current >= tokens.length || tokens[current].type !== TokenType.IDENTIFIER) {
+                                throw new Error('Expected identifier after "." in table access');
+                            }
+                            
+                            const propertyName2 = tokens[current].value;
+                            current++; // Skip property name
+                            
+                            tableNode = {
+                                type: 'TableAccess',
+                                table: tableNode,
+                                key: { type: 'Identifier', value: propertyName2 }
+                            };
+                        } else if (tokens[current].type === TokenType.LEFT_BRACKET) {
+                            current++; // Skip '['
+                            const keyExpression = parseLogicalExpression();
+                            
+                            if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_BRACKET) {
+                                throw new Error('Expected "]" after table key');
+                            }
+                            current++; // Skip ']'
+                            
+                            tableNode = {
+                                type: 'TableAccess',
+                                table: tableNode,
+                                key: keyExpression
+                            };
+                        }
+                    }
+                    
+                    return tableNode;
+                }
+                
+                // Parenthesized expressions after identifiers are handled by parseApplication
+                // to support function calls like f(x)
+                if (current < tokens.length && tokens[current].type === TokenType.LEFT_PAREN) {
+                    // Don't handle this here, let parseApplication handle it
+                    // This ensures that f(x) is parsed as apply(f, x) not just x
+                }
+                
+                // Juxtaposition function calls are now handled in parseFactor() with proper precedence
+                return { type: 'Identifier', value: identifierValue };
+
+            case TokenType.LEFT_PAREN:
+                current++;
+                            if (DEBUG) {
+                console.log(`[DEBUG] parsePrimary: parsing LEFT_PAREN, current token = ${tokens[current].type}`);
+            }
+                const expression = parseLogicalExpression();
+                if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
+                    throw new Error('Expected ")" after expression');
+                }
+                current++;
+                
+                // Check if this is just a simple identifier in parentheses
+                if (expression.type === 'Identifier') {
+                    return { 
+                        type: 'FunctionCall', 
+                        name: 'identity', 
+                        args: [expression] 
+                    };
+                }
+                
+                return expression;
+
+            case TokenType.WILDCARD:
+                current++;
+                return { type: 'WildcardPattern' };
+                
+            case TokenType.LEFT_BRACE:
+                return parseTableLiteral();
+                
+
+                
+                                                case TokenType.NOT:
+                current++;
+                const operand = parsePrimary();
+                return { 
+                    type: 'FunctionCall',
+                    name: 'logicalNot',
+                    args: [operand]
+                };
+                
+            case TokenType.MINUS:
+            case TokenType.UNARY_MINUS:
+                // Delegate unary minus to parseExpression for proper precedence
+                return parseExpression();
+                
+            case TokenType.ARROW:
+                current++;
+                const arrowBody = parseLogicalExpression();
+                return { type: 'ArrowExpression', body: arrowBody };
+                
+            case TokenType.FUNCTION_REF:
+                const functionRef = { type: 'FunctionReference', name: tokens[current].name };
+                current++;
+                return functionRef;
+                
+            case TokenType.FUNCTION_ARG:
+                // @(expression) - parse the parenthesized expression as a function argument
+                current++; // Skip FUNCTION_ARG token
+                if (current >= tokens.length || tokens[current].type !== TokenType.LEFT_PAREN) {
+                    throw new Error('Expected "(" after @');
+                }
+                current++; // Skip '('
+                const argExpression = parseLogicalExpression();
+                if (current >= tokens.length || tokens[current].type !== TokenType.RIGHT_PAREN) {
+                    throw new Error('Expected ")" after function argument expression');
+                }
+                current++; // Skip ')'
+                return argExpression;
+                
+            default:
+                throw new Error(`Unexpected token in parsePrimary: ${token.type}`);
+        }
+    }
+    
+    return parse();
+} 
\ No newline at end of file