diff options
Diffstat (limited to 'js/scripting-lang/parser.js')
-rw-r--r-- | js/scripting-lang/parser.js | 427 |
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(); |