parser.js

// 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();
}