@mfissehaye/string-to-drizzle-orm-filters
Version:
144 lines • 6.21 kB
JavaScript
"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