UNPKG

petcarescript

Version:

PetCareScript - A modern, expressive programming language designed for humans

1,399 lines (1,162 loc) 49.8 kB
/** * PetCareScript Parser * Converts tokens into an Abstract Syntax Tree (AST) */ const { TokenType } = require('../lexer/tokens'); const { ProgramNode, VariableDeclarationNode, FunctionDeclarationNode, BlueprintDeclarationNode, BlockStatementNode, ExpressionStatementNode, IfStatementNode, UnlessStatementNode, WhileStatementNode, ForStatementNode, ForEachStatementNode, SwitchStatementNode, CaseClauseNode, ReturnStatementNode, PrintStatementNode, BreakStatementNode, ContinueStatementNode, TryStatementNode, ThrowStatementNode, BinaryExpressionNode, UnaryExpressionNode, CallExpressionNode, MemberExpressionNode, AssignmentExpressionNode, ConditionalExpressionNode, AwaitExpressionNode, LiteralNode, IdentifierNode, ArrayExpressionNode, ObjectExpressionNode, TemplateStringNode, InterfaceDeclarationNode, EnumDeclarationNode, ImportDeclarationNode, ExportDeclarationNode, ModuleDeclarationNode, ServerCallNode, ServerMethodNode, ServerListenNode, ArrowFunctionNode, SpreadElementNode, TestSuiteNode, TestCaseNode, AssertionNode } = require('./ast'); class ParseError extends Error { constructor(message, token) { super(message); this.token = token; } } class Parser { constructor(tokens) { this.tokens = tokens; this.current = 0; this.errors = []; } parse() { const statements = []; while (!this.isAtEnd()) { try { const stmt = this.declaration(); if (stmt) statements.push(stmt); } catch (error) { this.errors.push(error); this.synchronize(); if (this.errors.length > 10) { throw new Error(`Too many parse errors: ${this.errors.map(e => e.message).join(', ')}`); } } } if (this.errors.length > 0) { throw new Error(`Parse errors: ${this.errors.map(e => e.message).join(', ')}`); } return new ProgramNode(statements); } // Helper method to consume identifier or keyword as identifier consumeIdentifier(message) { const validIdentifierTypes = [ TokenType.IDENTIFIER, TokenType.SERVER, TokenType.GET, TokenType.POST, TokenType.PUT, TokenType.DELETE, TokenType.USE, TokenType.LISTEN, TokenType.PROMISE, TokenType.ASYNC, TokenType.JSON, TokenType.CORS, TokenType.LOGGER, TokenType.MIDDLEWARE, TokenType.ROUTE, TokenType.CREATE_SERVER, // Add testing keywords that can be used as identifiers TokenType.DESCRIBE, TokenType.IT, TokenType.EXPECT, TokenType.ASSERT, TokenType.MOCK, TokenType.SPY, TokenType.BEFORE, TokenType.AFTER, TokenType.BEFORE_EACH, TokenType.AFTER_EACH, // Add keywords that can be used as functions/variables TokenType.CONTAINS, TokenType.IN, TokenType.LIKE, TokenType.BETWEEN, // Add logical operators as valid identifiers TokenType.AND, TokenType.OR, TokenType.NOT, TokenType.ALSO, TokenType.EITHER, // Add Promise-related keywords TokenType.RESOLVE, TokenType.REJECT, TokenType.THEN, TokenType.ALL, TokenType.RACE, TokenType.PARALLEL, // Add decorator keywords that can be used as functions TokenType.TIMEOUT, TokenType.RETRY, TokenType.MEMOIZE ]; for (const type of validIdentifierTypes) { if (this.check(type)) { return this.advance(); } } throw new ParseError(`${message} Got: ${this.peek().lexeme} at line ${this.peek().line}`, this.peek()); } declaration() { try { // Testing declarations if (this.match(TokenType.DESCRIBE)) return this.testSuiteDeclaration(); if (this.match(TokenType.IT)) return this.testCaseDeclaration(); // Async functions if (this.match(TokenType.ASYNC)) { if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { return this.asyncFunctionDeclaration(); } // If async is not followed by function, treat as identifier this.current--; return this.statement(); } // Regular declarations if (this.match(TokenType.BLUEPRINT, TokenType.CLASS)) return this.blueprintDeclaration(); if (this.match(TokenType.BUILD, TokenType.FUNCTION)) return this.functionDeclaration(); if (this.match(TokenType.STORE, TokenType.LET, TokenType.VAR, TokenType.CONST)) return this.variableDeclaration(); if (this.match(TokenType.INTERFACE)) return this.interfaceDeclaration(); if (this.match(TokenType.ENUM)) return this.enumDeclaration(); if (this.match(TokenType.MODULE)) return this.moduleDeclaration(); if (this.match(TokenType.IMPORT)) return this.importDeclaration(); if (this.match(TokenType.EXPORT)) return this.exportDeclaration(); return this.statement(); } catch (error) { this.synchronize(); throw error; } } // Testing-related parsing methods testSuiteDeclaration() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'describe'."); const name = this.expression(); this.consume(TokenType.COMMA, "Expected ',' after test suite name."); const body = this.expression(); // This should be a function this.consume(TokenType.RIGHT_PAREN, "Expected ')' after test suite body."); this.consume(TokenType.SEMICOLON, "Expected ';' after test suite declaration."); return new TestSuiteNode(name, body); } testCaseDeclaration() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'it'."); const name = this.expression(); this.consume(TokenType.COMMA, "Expected ',' after test case name."); const body = this.expression(); // This should be a function this.consume(TokenType.RIGHT_PAREN, "Expected ')' after test case body."); this.consume(TokenType.SEMICOLON, "Expected ';' after test case declaration."); return new TestCaseNode(name, body); } asyncFunctionDeclaration() { this.consumeOneOf([TokenType.BUILD, TokenType.FUNCTION], "Expected 'build' or 'function' after 'async'."); const name = this.consumeIdentifier("Expected function name."); this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name."); const parameters = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (parameters.length >= 255) { throw new ParseError("Cannot have more than 255 parameters.", this.peek()); } parameters.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before function body."); const body = this.block(); return new FunctionDeclarationNode(name, parameters, body, true); } interfaceDeclaration() { const name = this.consumeIdentifier("Expected interface name."); let extends_ = null; if (this.match(TokenType.EXTENDS)) { extends_ = this.consumeIdentifier("Expected parent interface name."); } this.consume(TokenType.LEFT_BRACE, "Expected '{' before interface body."); const properties = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { const propName = this.consumeIdentifier("Expected property name."); if (this.match(TokenType.COLON)) { const propType = this.typeAnnotation(); properties.push({ name: propName, type: propType }); } else if (this.match(TokenType.LEFT_PAREN)) { const params = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { params.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters."); this.consume(TokenType.COLON, "Expected ':' after method signature."); const returnType = this.typeAnnotation(); properties.push({ name: propName, type: 'function', params: params, returnType: returnType }); } else { throw new ParseError("Expected ':' or '(' after property name.", this.peek()); } this.match(TokenType.SEMICOLON); } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after interface body."); return new InterfaceDeclarationNode(name, properties, extends_); } enumDeclaration() { const name = this.consumeIdentifier("Expected enum name."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before enum body."); const members = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { const memberName = this.consumeIdentifier("Expected enum member name."); let value = null; if (this.match(TokenType.EQUAL)) { value = this.expression(); } members.push({ name: memberName, value: value }); if (!this.check(TokenType.RIGHT_BRACE)) { this.consume(TokenType.COMMA, "Expected ',' between enum members."); } } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after enum body."); return new EnumDeclarationNode(name, members); } moduleDeclaration() { const name = this.consumeIdentifier("Expected module name."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before module body."); const exports = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { exports.push(this.declaration()); } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after module body."); return new ModuleDeclarationNode(name, exports); } importDeclaration() { const specifiers = []; let isDefault = false; if (this.checkIdentifier()) { specifiers.push(this.advance()); isDefault = true; } else if (this.match(TokenType.LEFT_BRACE)) { do { const name = this.consumeIdentifier("Expected import name."); let alias = name; if (this.match(TokenType.AS)) { alias = this.consumeIdentifier("Expected alias name."); } specifiers.push({ name, alias }); } while (this.match(TokenType.COMMA)); this.consume(TokenType.RIGHT_BRACE, "Expected '}' after import specifiers."); } this.consume(TokenType.FROM, "Expected 'from' after import specifiers."); const source = this.consume(TokenType.STRING, "Expected module path."); this.consume(TokenType.SEMICOLON, "Expected ';' after import statement."); return new ImportDeclarationNode(specifiers, source, isDefault); } exportDeclaration() { if (this.match(TokenType.DEFAULT)) { const declaration = this.declaration(); return new ExportDeclarationNode(declaration, null, null, true); } else if (this.match(TokenType.LEFT_BRACE)) { const specifiers = []; do { const name = this.consumeIdentifier("Expected export name."); let alias = name; if (this.match(TokenType.AS)) { alias = this.consumeIdentifier("Expected alias name."); } specifiers.push({ name, alias }); } while (this.match(TokenType.COMMA)); this.consume(TokenType.RIGHT_BRACE, "Expected '}' after export specifiers."); let source = null; if (this.match(TokenType.FROM)) { source = this.consume(TokenType.STRING, "Expected module path."); } this.consume(TokenType.SEMICOLON, "Expected ';' after export statement."); return new ExportDeclarationNode(null, specifiers, source); } else { const declaration = this.declaration(); return new ExportDeclarationNode(declaration); } } typeAnnotation() { if (this.check(TokenType.LEFT_PAREN)) { this.advance(); const params = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { params.push(this.consumeIdentifier("Expected parameter type.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameter types."); this.consume(TokenType.ARROW, "Expected '=>' in function type."); const returnType = this.consumeOneOf([TokenType.IDENTIFIER, TokenType.VOID], "Expected return type."); return { type: 'function', params, returnType }; } return this.consumeOneOf([TokenType.IDENTIFIER, TokenType.VOID], "Expected type name."); } blueprintDeclaration() { const name = this.consumeIdentifier("Expected blueprint name."); let parentBlueprint = null; if (this.match(TokenType.EXTENDS)) { parentBlueprint = new IdentifierNode(this.consumeIdentifier("Expected parent blueprint name.")); } this.consume(TokenType.LEFT_BRACE, "Expected '{' before blueprint body."); const methods = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { let isAsync = false; if (this.match(TokenType.ASYNC)) { isAsync = true; } if (this.match(TokenType.BUILD, TokenType.FUNCTION)) { // explicit function keyword consumed } const methodName = this.consumeIdentifier("Expected method name."); this.consume(TokenType.LEFT_PAREN, "Expected '(' after method name."); const parameters = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (parameters.length >= 255) { throw new ParseError("Cannot have more than 255 parameters.", this.peek()); } parameters.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before method body."); const body = this.block(); const method = new FunctionDeclarationNode(methodName, parameters, body, isAsync); methods.push(method); } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after blueprint body."); return new BlueprintDeclarationNode(name, parentBlueprint, methods); } functionDeclaration() { const name = this.consumeIdentifier("Expected function name."); this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name."); const parameters = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (parameters.length >= 255) { throw new ParseError("Cannot have more than 255 parameters.", this.peek()); } parameters.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before function body."); const body = this.block(); return new FunctionDeclarationNode(name, parameters, body); } variableDeclaration() { const name = this.consumeIdentifier("Expected variable name."); let initializer = null; if (this.match(TokenType.EQUAL)) { initializer = this.expression(); } this.consume(TokenType.SEMICOLON, "Expected ';' after variable declaration."); return new VariableDeclarationNode(name, initializer); } statement() { if (this.match(TokenType.WHEN, TokenType.IF)) return this.ifStatement(); if (this.match(TokenType.UNLESS)) return this.unlessStatement(); if (this.match(TokenType.SHOW)) return this.printStatement(); if (this.match(TokenType.CONSOLE)) return this.consoleStatement(); if (this.match(TokenType.GIVE, TokenType.RETURN)) return this.returnStatement(); if (this.match(TokenType.REPEAT, TokenType.AGAIN, TokenType.WHILE)) return this.whileStatement(); if (this.match(TokenType.UNTIL)) return this.untilStatement(); if (this.match(TokenType.LOOP, TokenType.FOR)) return this.forStatement(); if (this.match(TokenType.FOREACH)) return this.forEachStatement(); if (this.match(TokenType.SWITCH)) return this.switchStatement(); if (this.match(TokenType.BREAK)) return this.breakStatement(); if (this.match(TokenType.CONTINUE)) return this.continueStatement(); if (this.match(TokenType.ATTEMPT, TokenType.TRY)) return this.tryStatement(); if (this.match(TokenType.THROW)) return this.throwStatement(); if (this.match(TokenType.LEFT_BRACE)) return new BlockStatementNode(this.block()); return this.expressionStatement(); } consoleStatement() { this.consume(TokenType.DOT, "Expected '.' after 'console'."); this.consume(TokenType.LOG, "Expected 'log' after 'console.'."); this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'console.log'."); const value = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after console.log argument."); this.consume(TokenType.SEMICOLON, "Expected ';' after console.log."); return new PrintStatementNode(value); } unlessStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'unless'."); const condition = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after condition."); const thenBranch = this.statement(); let elseBranch = null; if (this.match(TokenType.OTHERWISE, TokenType.ELSE)) { elseBranch = this.statement(); } return new UnlessStatementNode(condition, thenBranch, elseBranch); } forEachStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'foreach'."); const variable = this.consumeIdentifier("Expected variable name."); this.consume(TokenType.IN, "Expected 'in' after variable."); const iterable = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after foreach clauses."); const body = this.statement(); return new ForEachStatementNode(variable, iterable, body); } switchStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'switch'."); const discriminant = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after switch discriminant."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before switch body."); const cases = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { if (this.match(TokenType.CASE)) { const test = this.expression(); this.consume(TokenType.COLON, "Expected ':' after case value."); const consequent = []; while (!this.check(TokenType.CASE) && !this.check(TokenType.DEFAULT) && !this.check(TokenType.RIGHT_BRACE)) { consequent.push(this.statement()); } cases.push(new CaseClauseNode(test, consequent)); } else if (this.match(TokenType.DEFAULT)) { this.consume(TokenType.COLON, "Expected ':' after 'default'."); const consequent = []; while (!this.check(TokenType.CASE) && !this.check(TokenType.RIGHT_BRACE)) { consequent.push(this.statement()); } cases.push(new CaseClauseNode(null, consequent, true)); } else { throw new ParseError("Expected 'case' or 'default' in switch statement.", this.peek()); } } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after switch body."); return new SwitchStatementNode(discriminant, cases); } untilStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'until'."); const condition = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after condition."); const body = this.statement(); return new WhileStatementNode(condition, body, true); } ifStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'when' or 'if'."); const condition = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after condition."); const thenBranch = this.statement(); let elseBranch = null; if (this.match(TokenType.OTHERWISE, TokenType.ELSE)) { elseBranch = this.statement(); } return new IfStatementNode(condition, thenBranch, elseBranch); } printStatement() { const value = this.expression(); this.consume(TokenType.SEMICOLON, "Expected ';' after value."); return new PrintStatementNode(value); } returnStatement() { const keyword = this.previous(); let value = null; if (!this.check(TokenType.SEMICOLON)) { value = this.expression(); } this.consume(TokenType.SEMICOLON, "Expected ';' after return value."); return new ReturnStatementNode(keyword, value); } whileStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after while keyword."); const condition = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after condition."); const body = this.statement(); return new WhileStatementNode(condition, body); } forStatement() { this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'loop' or 'for'."); let initializer; if (this.match(TokenType.SEMICOLON)) { initializer = null; } else if (this.match(TokenType.STORE, TokenType.LET, TokenType.VAR, TokenType.CONST)) { const varName = this.consumeIdentifier("Expected variable name."); let varInitializer = null; if (this.match(TokenType.EQUAL)) { varInitializer = this.expression(); } initializer = new VariableDeclarationNode(varName, varInitializer); } else { const expr = this.expression(); initializer = new ExpressionStatementNode(expr); } this.consume(TokenType.SEMICOLON, "Expected ';' after loop initializer."); let condition = null; if (!this.check(TokenType.SEMICOLON)) { condition = this.expression(); } this.consume(TokenType.SEMICOLON, "Expected ';' after loop condition."); let increment = null; if (!this.check(TokenType.RIGHT_PAREN)) { increment = this.expression(); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after loop clauses."); const body = this.statement(); return new ForStatementNode(initializer, condition, increment, body); } breakStatement() { this.consume(TokenType.SEMICOLON, "Expected ';' after 'break'."); return new BreakStatementNode(); } continueStatement() { this.consume(TokenType.SEMICOLON, "Expected ';' after 'continue'."); return new ContinueStatementNode(); } throwStatement() { const expression = this.expression(); this.consume(TokenType.SEMICOLON, "Expected ';' after throw expression."); return new ThrowStatementNode(expression); } tryStatement() { const tryBlock = this.statement(); let catchBlock = null; let catchVariable = null; if (this.match(TokenType.CATCH)) { if (this.match(TokenType.LEFT_PAREN)) { catchVariable = this.consumeIdentifier("Expected variable name in catch."); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after catch variable."); } catchBlock = this.statement(); } let finallyBlock = null; if (this.match(TokenType.FINALLY)) { finallyBlock = this.statement(); } if (catchBlock === null && finallyBlock === null) { throw new ParseError("Expected 'catch' or 'finally' after 'attempt' or 'try'.", this.peek()); } return new TryStatementNode(tryBlock, catchBlock, catchVariable, finallyBlock); } block() { const statements = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { const stmt = this.declaration(); if (stmt) statements.push(stmt); } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after block."); return statements; } expression() { return this.ternary(); } ternary() { let expr = this.assignment(); if (this.match(TokenType.QUESTION)) { const thenBranch = this.expression(); this.consume(TokenType.COLON, "Expected ':' after '?' in ternary expression."); const elseBranch = this.ternary(); return new ConditionalExpressionNode(expr, thenBranch, elseBranch); } return expr; } assignment() { const expr = this.or(); if (this.match(TokenType.EQUAL)) { const value = this.assignment(); if (expr instanceof IdentifierNode) { return new AssignmentExpressionNode(expr.name, value); } else if (expr instanceof MemberExpressionNode) { return new AssignmentExpressionNode(expr, value); } throw new ParseError("Invalid assignment target.", this.previous()); } return expr; } or() { let expr = this.and(); while (this.match(TokenType.EITHER, TokenType.OR, TokenType.OR_OR)) { const operator = this.previous(); const right = this.and(); expr = new BinaryExpressionNode(expr, operator, right); } return expr; } and() { let expr = this.equality(); while (this.match(TokenType.ALSO, TokenType.AND, TokenType.AND_AND)) { const operator = this.previous(); const right = this.equality(); expr = new BinaryExpressionNode(expr, operator, right); } return expr; } equality() { let expr = this.comparison(); while (this.match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL, TokenType.IS, TokenType.ISNT)) { const operator = this.previous(); const right = this.comparison(); expr = new BinaryExpressionNode(expr, operator, right); } return expr; } comparison() { let expr = this.term(); while (this.match( TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL, TokenType.BETWEEN, TokenType.CONTAINS, TokenType.IN, TokenType.LIKE )) { const operator = this.previous(); // Handle special three-operand 'between' operator if (operator.type === TokenType.BETWEEN) { const middle = this.term(); this.consume(TokenType.AND, "Expected 'and' after first operand in 'between' expression."); const right = this.term(); // Create a compound expression: expr >= middle && expr <= right const leftComparison = new BinaryExpressionNode( expr, { type: TokenType.GREATER_EQUAL, lexeme: '>=' }, middle ); const rightComparison = new BinaryExpressionNode( expr, { type: TokenType.LESS_EQUAL, lexeme: '<=' }, right ); expr = new BinaryExpressionNode( leftComparison, { type: TokenType.AND_AND, lexeme: '&&' }, rightComparison ); } else { const right = this.term(); expr = new BinaryExpressionNode(expr, operator, right); } } return expr; } term() { let expr = this.factor(); while (this.match(TokenType.MINUS, TokenType.PLUS)) { const operator = this.previous(); const right = this.factor(); expr = new BinaryExpressionNode(expr, operator, right); } return expr; } factor() { let expr = this.unary(); while (this.match(TokenType.SLASH, TokenType.STAR, TokenType.PERCENT)) { const operator = this.previous(); const right = this.unary(); expr = new BinaryExpressionNode(expr, operator, right); } return expr; } unary() { if (this.match(TokenType.BANG, TokenType.MINUS, TokenType.NOT)) { const operator = this.previous(); const right = this.unary(); return new UnaryExpressionNode(operator, right); } if (this.match(TokenType.AWAIT)) { const expression = this.unary(); return new AwaitExpressionNode(expression); } return this.call(); } call() { let expr = this.primary(); while (true) { if (this.match(TokenType.LEFT_PAREN)) { expr = this.finishCall(expr); } else if (this.match(TokenType.DOT)) { const name = this.advance(); expr = new MemberExpressionNode(expr, name, false); } else if (this.match(TokenType.LEFT_BRACKET)) { const index = this.expression(); this.consume(TokenType.RIGHT_BRACKET, "Expected ']' after index."); expr = new MemberExpressionNode(expr, index, true); } else { break; } } return expr; } finishCall(callee) { const args = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (args.length >= 255) { throw new ParseError("Cannot have more than 255 arguments.", this.peek()); } // Parse argument - improved to handle all function types args.push(this.parseArgument()); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after arguments."); return new CallExpressionNode(callee, args); } parseArgument() { // Check for async function expressions in arguments if (this.check(TokenType.ASYNC)) { const asyncPos = this.current; this.advance(); // consume 'async' if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { // This is an async function expression this.current = asyncPos; // go back return this.parseAsyncFunction(); } else { // Not an async function, go back and parse as normal expression this.current = asyncPos; return this.expression(); } } else if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { // Regular function expression - check if it has parentheses after const functionPos = this.current; this.advance(); // consume 'build' or 'function' if (this.check(TokenType.LEFT_PAREN)) { // This is a function with parameters this.current = functionPos; // go back return this.parseAnonymousFunction(false); } else { // This might be just an identifier this.current = functionPos; // go back return this.expression(); } } else { // Regular expression return this.expression(); } } parseAsyncFunction() { this.consume(TokenType.ASYNC, "Expected 'async'."); this.consumeOneOf([TokenType.BUILD, TokenType.FUNCTION], "Expected 'build' or 'function' after 'async'."); this.consume(TokenType.LEFT_PAREN, "Expected '(' after function keyword."); const parameters = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (parameters.length >= 255) { throw new ParseError("Cannot have more than 255 parameters.", this.peek()); } parameters.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before function body."); const body = this.block(); // Create an anonymous function node const anonName = { lexeme: '<anonymous>', type: TokenType.IDENTIFIER }; return new FunctionDeclarationNode(anonName, parameters, body, true); } // Helper method to check if current token can be used as identifier checkIdentifier() { const validIdentifierTypes = [ TokenType.IDENTIFIER, TokenType.SERVER, TokenType.GET, TokenType.POST, TokenType.PUT, TokenType.DELETE, TokenType.USE, TokenType.LISTEN, TokenType.PROMISE, TokenType.ASYNC, TokenType.JSON, TokenType.CORS, TokenType.LOGGER, TokenType.MIDDLEWARE, TokenType.ROUTE, TokenType.CREATE_SERVER, // Testing keywords TokenType.DESCRIBE, TokenType.IT, TokenType.EXPECT, TokenType.ASSERT, TokenType.MOCK, TokenType.SPY, TokenType.BEFORE, TokenType.AFTER, TokenType.BEFORE_EACH, TokenType.AFTER_EACH, // Add keywords that can be used as functions TokenType.CONTAINS, TokenType.IN, TokenType.LIKE, TokenType.BETWEEN, // Add logical operators as valid identifiers TokenType.AND, TokenType.OR, TokenType.NOT, TokenType.ALSO, TokenType.EITHER, // Add Promise-related keywords TokenType.RESOLVE, TokenType.REJECT, TokenType.THEN, TokenType.ALL, TokenType.RACE, TokenType.PARALLEL, // Add decorator keywords that can be used as functions TokenType.TIMEOUT, TokenType.RETRY, TokenType.MEMOIZE ]; return validIdentifierTypes.includes(this.peek().type); } primary() { if (this.match(TokenType.YES, TokenType.TRUE)) return new LiteralNode(true); if (this.match(TokenType.NO, TokenType.FALSE)) return new LiteralNode(false); if (this.match(TokenType.EMPTY, TokenType.NULL, TokenType.UNDEFINED)) return new LiteralNode(null); if (this.match(TokenType.NUMBER, TokenType.STRING)) { return new LiteralNode(this.previous().literal); } if (this.match(TokenType.TEMPLATE_STRING)) { return this.templateString(); } if (this.match(TokenType.SELF, TokenType.THIS)) return new IdentifierNode(this.previous()); if (this.match(TokenType.PARENT)) return new IdentifierNode(this.previous()); // Handle async anonymous functions - improved handling if (this.check(TokenType.ASYNC)) { const asyncPos = this.current; this.advance(); // consume 'async' if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { this.current = asyncPos; // go back return this.parseAsyncFunction(); } else { // It's just an identifier named 'async' this.current = asyncPos; return new IdentifierNode(this.advance()); } } // Handle anonymous functions (build/function without name) - IMPROVED if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { const functionPos = this.current; this.advance(); // consume 'build' or 'function' if (this.check(TokenType.LEFT_PAREN)) { // This is definitely a function declaration this.current = functionPos; // go back return this.parseAnonymousFunction(false); } else { // This is just an identifier this.current = functionPos; // go back return new IdentifierNode(this.advance()); } } // Handle identifiers (including keywords that can be identifiers) if (this.checkIdentifier()) { const identifier = this.advance(); return new IdentifierNode(identifier); } if (this.match(TokenType.LEFT_PAREN)) { // Check if this might be an arrow function const parenPos = this.current - 1; // position of LEFT_PAREN // Try to parse as arrow function first if (this.isArrowFunction()) { this.current = parenPos; // go back to LEFT_PAREN return this.parseArrowFunction(); } // Parse as regular grouped expression const expr = this.expression(); this.consume(TokenType.RIGHT_PAREN, "Expected ')' after expression."); return expr; } if (this.match(TokenType.LEFT_BRACKET)) { const elements = []; if (!this.check(TokenType.RIGHT_BRACKET)) { do { elements.push(this.expression()); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_BRACKET, "Expected ']' after array elements."); return new ArrayExpressionNode(elements); } if (this.match(TokenType.LEFT_BRACE)) { return this.parseObject(); } throw new ParseError(`Unexpected token: ${this.peek().lexeme}`, this.peek()); } // Check if current position is an arrow function isArrowFunction() { const currentPos = this.current; let hasParams = false; try { // Skip parameter list if (this.check(TokenType.RIGHT_PAREN)) { // Empty parameter list () this.advance(); } else { // Parse parameters do { if (this.checkIdentifier()) { this.advance(); hasParams = true; } else { // Not valid parameter, not an arrow function this.current = currentPos; return false; } } while (this.match(TokenType.COMMA)); if (!this.match(TokenType.RIGHT_PAREN)) { // No closing paren, not an arrow function this.current = currentPos; return false; } } // Check for arrow const isArrow = this.match(TokenType.ARROW); this.current = currentPos; return isArrow; } catch (error) { this.current = currentPos; return false; } } // Parse arrow function parseArrowFunction() { this.consume(TokenType.LEFT_PAREN, "Expected '(' for arrow function parameters."); const parameters = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (parameters.length >= 255) { throw new ParseError("Cannot have more than 255 parameters.", this.peek()); } parameters.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after arrow function parameters."); this.consume(TokenType.ARROW, "Expected '=>' in arrow function."); // Parse body - can be expression or block let body; if (this.check(TokenType.LEFT_BRACE)) { // Block body this.advance(); // consume '{' body = this.block(); } else { // Expression body - wrap in return statement const expr = this.expression(); body = [new ReturnStatementNode(null, expr)]; } return new ArrowFunctionNode(parameters, body); } parseObject() { const properties = []; while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) { // Handle spread syntax in objects if (this.match(TokenType.DOT_DOT_DOT)) { const spreadExpr = this.expression(); properties.push({ spread: true, value: spreadExpr }); } else { // Parse property key let key; if (this.checkIdentifier()) { key = this.advance(); } else if (this.check(TokenType.STRING)) { key = this.advance(); } else { throw new ParseError("Expected property name.", this.peek()); } this.consume(TokenType.COLON, "Expected ':' after property name."); // CORREÇÃO: Better property value parsing let value; try { value = this.parsePropertyValue(); } catch (error) { // Fallback to expression parsing if property value parsing fails value = this.expression(); } properties.push({ key, value, spread: false }); } // CORREÇÃO: Better comma handling if (this.check(TokenType.RIGHT_BRACE)) { break; // End of object } if (!this.match(TokenType.COMMA)) { // If no comma and not end of object, it's an error if (!this.check(TokenType.RIGHT_BRACE)) { throw new ParseError("Expected ',' or '}' after object property.", this.peek()); } } } this.consume(TokenType.RIGHT_BRACE, "Expected '}' after object properties."); return new ObjectExpressionNode(properties); } parsePropertyValue() { // Check if it's a function property (async build/build/function) if (this.check(TokenType.ASYNC)) { const asyncPos = this.current; this.advance(); // consume 'async' if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { this.current = asyncPos; // go back return this.parseAsyncFunction(); } else { // Not an async function, go back and parse as normal expression this.current = asyncPos; return this.expression(); } } else if (this.check(TokenType.BUILD) || this.check(TokenType.FUNCTION)) { // CORREÇÃO: Parse function property directly return this.parseAnonymousFunction(false); } else { return this.expression(); } } // Parse anonymous functions (functions without names) parseAnonymousFunction(isAsync) { // Consume function keyword this.consumeOneOf([TokenType.BUILD, TokenType.FUNCTION], "Expected 'build' or 'function'."); this.consume(TokenType.LEFT_PAREN, "Expected '(' after function keyword."); const parameters = []; if (!this.check(TokenType.RIGHT_PAREN)) { do { if (parameters.length >= 255) { throw new ParseError("Cannot have more than 255 parameters.", this.peek()); } parameters.push(this.consumeIdentifier("Expected parameter name.")); } while (this.match(TokenType.COMMA)); } this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters."); this.consume(TokenType.LEFT_BRACE, "Expected '{' before function body."); const body = this.block(); // CORREÇÃO: Create proper anonymous function node const anonName = { lexeme: '<anonymous>', type: 'IDENTIFIER' }; return new FunctionDeclarationNode(anonName, parameters, body, isAsync); } templateString() { const quasis = []; const expressions = []; while (this.check(TokenType.TEMPLATE_STRING) || this.check(TokenType.TEMPLATE_EXPRESSION)) { if (this.match(TokenType.TEMPLATE_STRING)) { quasis.push(this.previous().literal); } if (this.match(TokenType.TEMPLATE_EXPRESSION)) { const exprTokens = this.previous().literal; const exprParser = new Parser(exprTokens); const expr = exprParser.expression(); expressions.push(expr); } } this.consume(TokenType.TEMPLATE_END, "Expected end of template string."); return new TemplateStringNode(quasis, expressions); } expressionStatement() { const expr = this.expression(); this.consume(TokenType.SEMICOLON, "Expected ';' after expression."); return new ExpressionStatementNode(expr); } match(...types) { for (const type of types) { if (this.check(type)) { this.advance(); return true; } } return false; } check(type) { if (this.isAtEnd()) return false; return this.peek().type === type; } advance() { if (!this.isAtEnd()) this.current++; return this.previous(); } isAtEnd() { return this.peek().type === TokenType.EOF; } peek() { return this.tokens[this.current]; } peekNext() { if (this.current + 1 >= this.tokens.length) return null; return this.tokens[this.current + 1]; } previous() { return this.tokens[this.current - 1]; } consume(type, message) { if (this.check(type)) return this.advance(); throw new ParseError(`${message} Got: ${this.peek().lexeme} at line ${this.peek().line}`, this.peek()); } consumeOneOf(types, message) { for (const type of types) { if (this.check(type)) return this.advance(); } throw new ParseError(`${message} Got: ${this.peek().lexeme} at line ${this.peek().line}`, this.peek()); } synchronize() { this.advance(); while (!this.isAtEnd()) { if (this.previous().type === TokenType.SEMICOLON) return; switch (this.peek().type) { case TokenType.BLUEPRINT: case TokenType.CLASS: case TokenType.BUILD: case TokenType.FUNCTION: case TokenType.STORE: case TokenType.LET: case TokenType.VAR: case TokenType.CONST: case TokenType.LOOP: case TokenType.FOR: case TokenType.WHEN: case TokenType.IF: case TokenType.REPEAT: case TokenType.WHILE: case TokenType.AGAIN: case TokenType.SHOW: case TokenType.GIVE: case TokenType.RETURN: case TokenType.THROW: case TokenType.DESCRIBE: case TokenType.IT: return; } this.advance(); } } } module.exports = Parser;