diff options
Diffstat (limited to 'js/scripting-lang/docs/baba-yaga/0.0.1/parser.js.html')
-rw-r--r-- | js/scripting-lang/docs/baba-yaga/0.0.1/parser.js.html | 1769 |
1 files changed, 0 insertions, 1769 deletions
diff --git a/js/scripting-lang/docs/baba-yaga/0.0.1/parser.js.html b/js/scripting-lang/docs/baba-yaga/0.0.1/parser.js.html deleted file mode 100644 index 9858678..0000000 --- a/js/scripting-lang/docs/baba-yaga/0.0.1/parser.js.html +++ /dev/null @@ -1,1769 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <title>parser.js - Documentation</title> - - <script src="scripts/prettify/prettify.js"></script> - <script src="scripts/prettify/lang-css.js"></script> - <!--[if lt IE 9]> - <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> - <![endif]--> - <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"> - <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> - <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> -</head> -<body> - -<input type="checkbox" id="nav-trigger" class="nav-trigger" /> -<label for="nav-trigger" class="navicon-button x"> - <div class="navicon"></div> -</label> - -<label for="nav-trigger" class="overlay"></label> - -<nav> - <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Tutorials</li><li class="nav-item"><a href="tutorial-00_Introduction.html">00_Introduction</a></li><li class="nav-item"><a href="tutorial-01_Function_Calls.html">01_Function_Calls</a></li><li class="nav-item"><a href="tutorial-02_Function_Composition.html">02_Function_Composition</a></li><li class="nav-item"><a href="tutorial-03_Table_Operations.html">03_Table_Operations</a></li><li class="nav-item"><a href="tutorial-04_Currying.html">04_Currying</a></li><li class="nav-item"><a href="tutorial-05_Pattern_Matching.html">05_Pattern_Matching</a></li><li class="nav-item"><a href="tutorial-06_Immutable_Tables.html">06_Immutable_Tables</a></li><li class="nav-item"><a href="tutorial-07_Function_References.html">07_Function_References</a></li><li class="nav-item"><a href="tutorial-08_Combinators.html">08_Combinators</a></li><li class="nav-item"><a href="tutorial-09_Expression_Based.html">09_Expression_Based</a></li><li class="nav-item"><a href="tutorial-10_Tables_Deep_Dive.html">10_Tables_Deep_Dive</a></li><li class="nav-item"><a href="tutorial-11_Standard_Library.html">11_Standard_Library</a></li><li class="nav-item"><a href="tutorial-12_IO_Operations.html">12_IO_Operations</a></li><li class="nav-item"><a href="tutorial-13_Error_Handling.html">13_Error_Handling</a></li><li class="nav-item"><a href="tutorial-14_Advanced_Combinators.html">14_Advanced_Combinators</a></li><li class="nav-item"><a href="tutorial-15_Integration_Patterns.html">15_Integration_Patterns</a></li><li class="nav-item"><a href="tutorial-16_Best_Practices.html">16_Best_Practices</a></li><li class="nav-item"><a href="tutorial-README.html">README</a></li><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-member">M</span><span class="nav-item-name"><a href="global.html#callStackTracker">callStackTracker</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#debugError">debugError</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#debugLog">debugLog</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#executeFile">executeFile</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#initializeStandardLibrary">initializeStandardLibrary</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#interpreter">interpreter</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#lexer">lexer</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#main">main</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#parser">parser</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#readFile">readFile</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#run">run</a></span></li> -</nav> - -<div id="main"> - - <h1 class="page-title">parser.js</h1> - - - - - - - - <section> - <article> - <pre class="prettyprint source linenums"><code>// 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(); -} </code></pre> - </article> - </section> - - - - -</div> - -<br class="clear"> - -<footer> - Generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.4</a> on Tue Jul 29 2025 23:15:00 GMT-0400 (Eastern Daylight Time) using the Minami theme. -</footer> - -<script>prettyPrint();</script> -<script src="scripts/linenumber.js"></script> -</body> -</html> |