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.js427
1 files changed, 341 insertions, 86 deletions
diff --git a/js/scripting-lang/parser.js b/js/scripting-lang/parser.js
index a2bd37a..a5cb45b 100644
--- a/js/scripting-lang/parser.js
+++ b/js/scripting-lang/parser.js
@@ -4,16 +4,47 @@
 
 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.<Object>} tokens - Array of tokens from the lexer
- * @returns {Object} Abstract Syntax Tree with program body
+ * @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 eliminates parsing ambiguity while preserving the original syntax.
+ * 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
@@ -30,15 +61,24 @@ import { TokenType } from './lexer.js';
  * - 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 complex
- * logical expressions. This approach ensures that all operations are consistently
+ * 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 eliminates the need for special operator handling in the
- * interpreter and enables powerful abstractions through the combinator foundation.
+ * 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;
@@ -46,7 +86,7 @@ export function parser(tokens) {
     /**
      * Main parsing function that processes the entire token stream
      * 
-     * @returns {Object} Complete AST with program body
+     * @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.
      * 
@@ -57,12 +97,17 @@ export function parser(tokens) {
      * 
      * The function implements the top-level parsing strategy by processing each
      * statement or expression in sequence. This approach enables the parser to
-     * handle complex 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 any complexity while maintaining
+      * 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 = [];
@@ -80,7 +125,7 @@ export function parser(tokens) {
     /**
      * Main walk function that dispatches to appropriate parsing functions
      * 
-     * @returns {Object|null} Parsed AST node or null for empty statements
+     * @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.
@@ -95,15 +140,19 @@ export function parser(tokens) {
      * 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 complex 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 complex
+      * 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];
@@ -153,7 +202,7 @@ export function parser(tokens) {
     /**
      * Parse assignment statements: identifier : expression;
      * 
-     * @returns {Object} Assignment AST node
+     * @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
@@ -161,6 +210,20 @@ export function parser(tokens) {
      * 
      * 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;
@@ -250,7 +313,7 @@ export function parser(tokens) {
     /**
      * Parse when expressions: when value is pattern then result pattern then result;
      * 
-     * @returns {Object} WhenExpression AST node
+     * @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
@@ -264,9 +327,22 @@ export function parser(tokens) {
      * 
      * 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 (process.env.DEBUG) {
+        if (DEBUG) {
             console.log(`[DEBUG] parseWhenExpression: starting, current token = ${tokens[current].type}`);
         }
         current++; // Skip 'when'
@@ -274,22 +350,17 @@ export function parser(tokens) {
         // Parse the value(s) - can be single value or multiple values
         const values = [];
         while (current < tokens.length && tokens[current].type !== TokenType.IS) {
-            // For when expressions, we want to parse simple identifiers and expressions
-            // but not treat them as function calls
+            // Use parsePrimary to handle all types of expressions including table access and function calls
             let value;
-            if (tokens[current].type === TokenType.IDENTIFIER) {
-                // Single identifier value
-                value = { type: 'Identifier', value: tokens[current].value };
-                current++;
-            } else if (tokens[current].type === TokenType.IO_LISTEN) {
+            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 other types, use normal expression parsing
-                value = parseLogicalExpression();
+                // For all other types, use parsePrimary to handle expressions
+                value = parsePrimary();
             }
             values.push(value);
         }
@@ -302,7 +373,7 @@ export function parser(tokens) {
         const cases = [];
         
         while (current < tokens.length) {
-            if (process.env.DEBUG) {
+            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
@@ -311,7 +382,7 @@ export function parser(tokens) {
             // Parse patterns until we hit THEN
             while (current < tokens.length && tokens[current].type !== TokenType.THEN) {
                 let pattern;
-                if (process.env.DEBUG) {
+                if (DEBUG) {
                     console.log(`[DEBUG] parseWhenExpression: parsing pattern, current token = ${tokens[current].type}, value = ${tokens[current].value || 'N/A'}`);
                 }
                 
@@ -327,8 +398,30 @@ export function parser(tokens) {
                     // Parse as a comparison expression
                     pattern = parseExpression();
                 } else if (tokens[current].type === TokenType.IDENTIFIER) {
-                    pattern = { type: 'Identifier', value: tokens[current].value };
-                    current++;
+                    // 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++;
@@ -347,9 +440,25 @@ export function parser(tokens) {
                 } 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}`);
                 }
@@ -423,7 +532,7 @@ export function parser(tokens) {
                 result: [result]
             });
             
-            if (process.env.DEBUG) {
+            if (DEBUG) {
                 console.log(`[DEBUG] parseWhenExpression: finished case, current token = ${tokens[current].type}, value = ${tokens[current].value || 'N/A'}`);
             }
             
@@ -431,13 +540,13 @@ export function parser(tokens) {
             if (current < tokens.length) {
                 const nextToken = tokens[current];
                 
-                if (process.env.DEBUG) {
+                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 (process.env.DEBUG) {
+                    if (DEBUG) {
                         console.log(`[DEBUG] parseWhenExpression: terminating on SEMICOLON`);
                     }
                     current++;
@@ -446,7 +555,7 @@ export function parser(tokens) {
                 
                 // Stop on assignment (for consecutive assignments)
                 if (nextToken.type === TokenType.ASSIGNMENT) {
-                    if (process.env.DEBUG) {
+                    if (DEBUG) {
                         console.log(`[DEBUG] parseWhenExpression: terminating on ASSIGNMENT`);
                     }
                     break;
@@ -465,7 +574,7 @@ export function parser(tokens) {
                     
                     if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ASSIGNMENT) {
                         // This is the start of a new assignment, terminate the when expression
-                        if (process.env.DEBUG) {
+                        if (DEBUG) {
                             console.log(`[DEBUG] parseWhenExpression: terminating on new assignment starting with ${nextToken.value}`);
                         }
                         break;
@@ -474,7 +583,7 @@ export function parser(tokens) {
                 
                 // Stop on right brace (for when expressions inside table literals)
                 if (nextToken.type === TokenType.RIGHT_BRACE) {
-                    if (process.env.DEBUG) {
+                    if (DEBUG) {
                         console.log(`[DEBUG] parseWhenExpression: terminating on RIGHT_BRACE`);
                     }
                     break;
@@ -482,7 +591,7 @@ export function parser(tokens) {
                 
                 // Stop on comma (for when expressions inside table literals)
                 if (nextToken.type === TokenType.COMMA) {
-                    if (process.env.DEBUG) {
+                    if (DEBUG) {
                         console.log(`[DEBUG] parseWhenExpression: terminating on COMMA`);
                     }
                     break;
@@ -502,7 +611,7 @@ export function parser(tokens) {
     /**
      * Parse function definitions: function (params) : body
      * 
-     * @returns {Object} FunctionDefinition AST node
+     * @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
@@ -513,6 +622,18 @@ export function parser(tokens) {
      * - 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'
@@ -558,10 +679,19 @@ export function parser(tokens) {
     /**
      * Parse IO input operations: ..in
      * 
-     * @returns {Object} IOInExpression AST node
+     * @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
@@ -571,11 +701,20 @@ export function parser(tokens) {
     /**
      * Parse IO output operations: ..out expression
      * 
-     * @returns {Object} IOOutExpression AST node
+     * @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
@@ -595,11 +734,21 @@ export function parser(tokens) {
     /**
      * Parse IO assert operations: ..assert expression
      * 
-     * @returns {Object} IOAssertExpression AST node
+     * @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
@@ -619,9 +768,18 @@ export function parser(tokens) {
     /**
      * Parse IO listen operations: ..listen
      * 
-     * @returns {Object} IOListenExpression AST node
+     * @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
@@ -639,11 +797,20 @@ export function parser(tokens) {
     /**
      * Parse IO emit operations: ..emit expression
      * 
-     * @returns {Object} IOEmitExpression AST node
+     * @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
@@ -663,19 +830,20 @@ export function parser(tokens) {
     /**
      * Parse logical expressions with proper precedence
      * 
-     * @returns {Object} AST node representing the logical expression
+     * @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.
      * 
-     * Operator precedence (lowest to highest):
-     * 1. Logical operators (and, or, xor)
-     * 2. Comparison operators (=, !=, <, >, <=, >=)
-     * 3. Additive operators (+, -)
-     * 4. Multiplicative operators (*, /, %)
-     * 5. Power operator (^)
-     * 6. Unary operators (not, -)
-     * 7. Primary expressions (literals, identifiers, function calls, parentheses)
+     * 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();
@@ -705,7 +873,7 @@ export function parser(tokens) {
     /**
      * Parse comparison expressions
      * 
-     * @returns {Object} AST node representing the comparison expression
+     * @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.
@@ -713,9 +881,19 @@ export function parser(tokens) {
      * 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 (process.env.DEBUG) {
+        if (DEBUG) {
             console.log(`[DEBUG] parseExpression: starting, current token = ${tokens[current].type}`);
         }
         
@@ -731,29 +909,30 @@ export function parser(tokens) {
         }
         
         // Handle unary minus at the beginning of expressions
-        if (current < tokens.length && tokens[current].type === TokenType.MINUS) {
-            if (process.env.DEBUG) {
+        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();
-            return {
+            left = {
                 type: 'FunctionCall',
                 name: 'negate',
                 args: [operand]
             };
+        } else {
+            left = parseTerm();
         }
         
-        let left = parseTerm();
-        
-        if (process.env.DEBUG) {
+        if (DEBUG) {
             console.log(`[DEBUG] parseExpression: after parseTerm, current token = ${tokens[current].type}`);
         }
         
         while (current < tokens.length) {
             const token = tokens[current];
             
-            if (process.env.DEBUG) {
+            if (DEBUG) {
                 console.log(`[DEBUG] parseExpression: while loop, current token = ${token.type}, value = ${token.value || 'N/A'}`);
             }
             
@@ -765,7 +944,7 @@ export function parser(tokens) {
                     name: 'add',
                     args: [left, right]
                 };
-            } else if (token.type === TokenType.MINUS) {
+            } else if (token.type === TokenType.MINUS || token.type === TokenType.BINARY_MINUS) {
                 current++;
                 const right = parseTerm();
                 left = {
@@ -801,13 +980,23 @@ export function parser(tokens) {
     /**
      * Parse multiplication and division expressions
      * 
-     * @returns {Object} AST node representing the multiplicative expression
+     * @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 (process.env.DEBUG) {
+        if (DEBUG) {
             console.log(`[DEBUG] parseTerm: starting, current token = ${tokens[current].type}`);
         }
         let left = parseApplication();
@@ -826,6 +1015,14 @@ export function parser(tokens) {
                           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;
             }
@@ -837,13 +1034,22 @@ export function parser(tokens) {
     /**
      * Parse power expressions and unary operators
      * 
-     * @returns {Object} AST node representing the factor expression
+     * @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 (process.env.DEBUG) {
+        if (DEBUG) {
             console.log(`[DEBUG] parseFactor: starting, current token = ${tokens[current].type}`);
         }
         let left = parsePrimary();
@@ -871,7 +1077,7 @@ export function parser(tokens) {
     /**
      * Parse function composition expressions using the 'via' keyword
      * 
-     * @returns {Object} AST node representing the composition expression
+     * @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))
@@ -895,8 +1101,18 @@ export function parser(tokens) {
      * 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 powerful functional programming patterns
-     * where complex transformations can be built from simple, composable functions.
+      * 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();
@@ -919,15 +1135,24 @@ export function parser(tokens) {
     /**
      * Parse function application (juxtaposition)
      * 
-     * @returns {Object} AST node representing the function application
+     * @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 eliminates the need for parentheses
+     * 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();
@@ -948,7 +1173,7 @@ export function parser(tokens) {
     /**
      * Check if a token is a valid start of a function argument
      * 
-     * @param {Object} token - Token to check
+     * @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
@@ -970,13 +1195,14 @@ export function parser(tokens) {
                token.type === TokenType.FALSE ||
                token.type === TokenType.FUNCTION_REF ||
                token.type === TokenType.FUNCTION_ARG ||
-               token.type === TokenType.NOT;
+               token.type === TokenType.NOT ||
+               token.type === TokenType.UNARY_MINUS;
     }
     
     /**
      * Parse table literals: {key: value, key2: value2} or {value1, value2, value3}
      * 
-     * @returns {Object} TableLiteral AST node
+     * @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.
@@ -987,6 +1213,16 @@ export function parser(tokens) {
      * - 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 '{'
@@ -1177,7 +1413,7 @@ export function parser(tokens) {
     /**
      * Parse function calls: functionName arg1 arg2 ...
      * 
-     * @returns {Object} FunctionCall AST node
+     * @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.
@@ -1185,6 +1421,14 @@ export function parser(tokens) {
      * 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;
@@ -1207,13 +1451,13 @@ export function parser(tokens) {
     /**
      * Parse primary expressions (literals, identifiers, parenthesized expressions)
      * 
-     * @returns {Object} AST node representing the primary expression
+     * @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 sophisticated function call detection by looking
+     * 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.
@@ -1226,6 +1470,16 @@ export function parser(tokens) {
      * - 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];
@@ -1234,7 +1488,7 @@ export function parser(tokens) {
             throw new Error('Unexpected end of input');
         }
         
-        if (process.env.DEBUG) {
+        if (DEBUG) {
             console.log(`[DEBUG] parsePrimary: current token = ${token.type}, value = ${token.value || 'N/A'}`);
         }
         
@@ -1380,9 +1634,9 @@ export function parser(tokens) {
 
             case TokenType.LEFT_PAREN:
                 current++;
-                if (process.env.DEBUG) {
-                    console.log(`[DEBUG] parsePrimary: parsing LEFT_PAREN, current token = ${tokens[current].type}`);
-                }
+                            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');
@@ -1419,6 +1673,7 @@ export function parser(tokens) {
                 };
                 
             case TokenType.MINUS:
+            case TokenType.UNARY_MINUS:
                 // Delegate unary minus to parseExpression for proper precedence
                 return parseExpression();