UNPKG

@mfissehaye/string-to-drizzle-orm-filters

Version:
144 lines 6.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parser = exports.ParserError = void 0; const lexer_1 = require("./lexer"); class ParserError extends Error { token; constructor(message, token) { super(message); this.token = token; this.name = 'ParserError'; } } exports.ParserError = ParserError; /** * The Parser class takes a Lexer instance and constructs an Abstraact Syntax Tree (AST) * from the stream of tokens */ class Parser { lexer; lookahead = null; // The next token to be consumed constructor(lexer) { this.lexer = lexer; } parse() { this.lookahead = this.lexer.nextToken(); // Initialize lookahead const expression = this.parseExpression(); if (this.lookahead.type !== lexer_1.TokenType.EOF) { throw new ParserError(`Unexpected token '${this.lookahead.value}' at position ${this.lookahead.position}. Expected end of input.`, this.lookahead); } return { kind: 'Program', expression }; } consume(expectedType, errorMessage) { const token = this.lookahead; if (!token || token.type !== expectedType) { throw new ParserError(errorMessage || `Unexpected token '${token?.value}' (type ${token?.type}). Expected ${expectedType}.`, token); } this.lookahead = this.lexer.nextToken(); // Move to the next token return token; } /** * Checks if the current lookahead matches the given type without consuming it. */ match(type) { return this.lookahead?.type === type; } /** * @Parses a general expression. * Currently, this only delegates to logical expressions. */ parseExpression() { return this.parseLogicalExpression(); } parseLogicalExpression() { let expression = this.parsePrimaryExpression(); while (this.match(lexer_1.TokenType.Identifier) && (this.lookahead?.value === 'and' || this.lookahead?.value === 'or')) { const operatorToken = this.consume(lexer_1.TokenType.Identifier); // const rightExpression = this.parsePrimaryExpression(); // this will be the second argument after a comma throw new ParserError(`Unexpected logical operator '${operatorToken.value}' not in a function call.`, operatorToken); } return expression; } /** * Parses a primary expression, which can be a CallExpression or a parenthesized expression. */ parsePrimaryExpression() { if (this.match(lexer_1.TokenType.LParen)) { this.consume(lexer_1.TokenType.LParen); const expression = this.parseLogicalExpression(); // Recursively parse the expression inside the parentheses this.consume(lexer_1.TokenType.RParen, `Expected ')' to close expression started at position ${expression.kind === 'CallExpression' ? this.lookahead?.position : 'unknown'}.`); return expression; } else if (this.match(lexer_1.TokenType.Identifier)) { return this.parseCallExpression(); } else { throw new ParserError(`Unexpected token '${this.lookahead?.value}' (type ${this.lookahead?.type}). Expected a function call or a '('.`, this.lookahead); } } /** * Parses a function call expression (e.g., `eq("col", "val")`). */ parseCallExpression() { const functionNameToken = this.consume(lexer_1.TokenType.Identifier, `Expected a function name (identifier) but got '${this.lookahead?.value}' (type ${this.lookahead?.type}).`); this.consume(lexer_1.TokenType.LParen, `Expected '(' after function name '${functionNameToken.value}'.`); const args = this.parseArguments(); this.consume(lexer_1.TokenType.RParen, `Expected ')' to close function call '${functionNameToken.value}'.`); return { kind: 'CallExpression', functionName: functionNameToken.value, args, }; } /** * Parses the arguments within a function call. * Arguments can be string literals or nested call expressions. */ parseArguments() { const args = []; // Check for empty arguments (.e.g., `func()`) if (this.match(lexer_1.TokenType.RParen)) { return args; } // Parse the first argument args.push(this.parseArgument()); // Parse subsequent arguments separated by commas while (this.match(lexer_1.TokenType.Comma)) { this.consume(lexer_1.TokenType.Comma); args.push(this.parseArgument()); } return args; } parseArgument() { if (this.match(lexer_1.TokenType.StringLiteral)) { const stringToken = this.consume(lexer_1.TokenType.StringLiteral, `Expected a staring literal but got '${this.lookahead?.value}' (type ${this.lookahead?.type}).`); return { kind: 'StringLiteral', value: stringToken.value, }; } else if (this.match(lexer_1.TokenType.NumberLiteral)) { const numberToken = this.consume(lexer_1.TokenType.NumberLiteral, `Expected a number literal but got '${this.lookahead?.value}' (type ${this.lookahead?.type}).`); // Convert the string value to a number const numericValue = parseFloat(numberToken.value); if (isNaN(numericValue)) { throw new ParserError(`Invalid number literal: '${numberToken.value}'`, numberToken); } return { kind: 'NumberLiteral', value: numericValue, }; } else if (this.match(lexer_1.TokenType.Identifier)) { // Allow nested function calls as arguments (e.g., `and(eq(...), or(...))`) return this.parseCallExpression(); } else { throw new ParserError(`Unexpected token '${this.lookahead?.value}' (type ${this.lookahead?.type}). Expected a string literal or a nested function call as an argument.`, this.lookahead); } } } exports.Parser = Parser; //# sourceMappingURL=parser.js.map