rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
175 lines • 8.36 kB
JavaScript
import { TokenType } from "../models/Lexeme";
import { FunctionCall, BinaryExpression, TypeValue, CastExpression, BetweenExpression, RawString } from "../models/ValueComponent";
import { OverExpressionParser } from "./OverExpressionParser";
import { ValueParser } from "./ValueParser";
import { FullNameParser } from "./FullNameParser";
export class FunctionExpressionParser {
static parseFromLexeme(lexemes, index) {
let idx = index;
const current = lexemes[idx];
if (current.value === "substring" || current.value === "overlay") {
return this.parseKeywordFunction(lexemes, idx, [
{ key: "from", required: false },
{ key: "for", required: false }
]);
}
else if (current.value === "cast") {
return this.parseKeywordFunction(lexemes, idx, [
{ key: "as", required: true }
]);
}
else if (current.value === "trim") {
return this.parseKeywordFunction(lexemes, idx, [
{ key: "from", required: false }
]);
}
return this.parseFunctionCall(lexemes, idx);
}
static tryParseBinaryExpression(lexemes, index, left, allowAndOperator = true) {
let idx = index;
// If the next element is an operator, process it as a binary expression
if (idx < lexemes.length && (lexemes[idx].type & TokenType.Operator)) {
if (!allowAndOperator && lexemes[idx].value === "and") {
// Handle special case for "and" operator
return null;
}
const operator = lexemes[idx].value;
idx++;
// between
if (operator === "between") {
return this.parseBetweenExpression(lexemes, idx, left, false);
}
else if (operator === "not between") {
return this.parseBetweenExpression(lexemes, idx, left, true);
}
// ::
if (operator === "::") {
const typeValue = this.parseTypeValue(lexemes, idx);
idx = typeValue.newIndex;
const exp = new CastExpression(left, typeValue.value);
return { value: exp, newIndex: idx };
}
// Get the right-hand side value
const rightResult = ValueParser.parseFromLexeme(lexemes, idx);
idx = rightResult.newIndex;
// Create binary expression
const value = new BinaryExpression(left, operator, rightResult.value);
return { value, newIndex: idx };
}
return null;
}
static parseBetweenExpression(lexemes, index, value, negated) {
let idx = index;
const lower = ValueParser.parseFromLexeme(lexemes, idx, false);
idx = lower.newIndex;
if (idx < lexemes.length && (lexemes[idx].type & TokenType.Operator) && lexemes[idx].value !== "and") {
throw new Error(`Expected 'and' after 'between' at index ${idx}`);
}
idx++;
const upper = ValueParser.parseFromLexeme(lexemes, idx);
idx = upper.newIndex;
const result = new BetweenExpression(value, lower.value, upper.value, negated);
return { value: result, newIndex: idx };
}
static parseFunctionCall(lexemes, index) {
let idx = index;
// Parse namespaced function name (e.g., myschema.myfunc, dbo.util.myfunc)
// Use FullNameParser to get namespaces and function name
const fullNameResult = FullNameParser.parseFromLexeme(lexemes, idx);
const namespaces = fullNameResult.namespaces;
const name = fullNameResult.name;
idx = fullNameResult.newIndex;
if (idx < lexemes.length && (lexemes[idx].type & TokenType.OpenParen)) {
// General argument parsing
const arg = ValueParser.parseArgument(TokenType.OpenParen, TokenType.CloseParen, lexemes, idx);
idx = arg.newIndex;
if (idx < lexemes.length && lexemes[idx].value === "over") {
const over = OverExpressionParser.parseFromLexeme(lexemes, idx);
idx = over.newIndex;
const value = new FunctionCall(namespaces, name.name, arg.value, over.value);
return { value, newIndex: idx };
}
else {
const value = new FunctionCall(namespaces, name.name, arg.value, null);
return { value, newIndex: idx };
}
}
else {
throw new Error(`Expected opening parenthesis after function name '${name.name}' at index ${idx}`);
}
}
static parseKeywordFunction(lexemes, index, keywords) {
let idx = index;
// Parse function name and namespaces at the beginning for consistent usage
const fullNameResult = FullNameParser.parseFromLexeme(lexemes, idx);
const namespaces = fullNameResult.namespaces;
const name = fullNameResult.name;
idx = fullNameResult.newIndex;
if (idx < lexemes.length && (lexemes[idx].type & TokenType.OpenParen)) {
idx++;
const input = ValueParser.parseFromLexeme(lexemes, idx);
let arg = input.value;
idx = input.newIndex;
// Delegate to the standard function parser if parsing by comma
if (idx < lexemes.length && (lexemes[idx].type & TokenType.Comma)) {
return this.parseFunctionCall(lexemes, index);
}
// Check for required/optional keywords in function arguments
for (const { key, required } of keywords) {
if (idx < lexemes.length && (lexemes[idx].type & TokenType.Command) && lexemes[idx].value === key) {
idx++;
if (idx < lexemes.length && (lexemes[idx].type & TokenType.Type)) {
const typeValue = this.parseTypeValue(lexemes, idx);
arg = new BinaryExpression(arg, key, typeValue.value);
idx = typeValue.newIndex;
}
else {
const right = ValueParser.parseFromLexeme(lexemes, idx);
arg = new BinaryExpression(arg, key, right.value);
idx = right.newIndex;
}
}
else if (required) {
throw new Error(`Keyword '${key}' is required at index ${idx}`);
}
}
if (idx < lexemes.length && (lexemes[idx].type & TokenType.CloseParen)) {
idx++;
// Use the previously parsed namespaces and function name for consistency
if (idx < lexemes.length && lexemes[idx].value === "over") {
idx++;
const over = OverExpressionParser.parseFromLexeme(lexemes, idx);
idx = over.newIndex;
const value = new FunctionCall(namespaces, name.name, arg, over.value);
return { value, newIndex: idx };
}
else {
const value = new FunctionCall(namespaces, name.name, arg, null);
return { value, newIndex: idx };
}
}
else {
throw new Error(`Missing closing parenthesis for function '${name.name}' at index ${idx}`);
}
}
else {
throw new Error(`Missing opening parenthesis for function '${name.name}' at index ${idx}`);
}
}
static parseTypeValue(lexemes, index) {
let idx = index;
const { namespaces, name, newIndex } = FullNameParser.parseFromLexeme(lexemes, idx);
idx = newIndex;
if (idx < lexemes.length && (lexemes[idx].type & TokenType.OpenParen)) {
const arg = ValueParser.parseArgument(TokenType.OpenParen, TokenType.CloseParen, lexemes, idx);
idx = arg.newIndex;
const value = new TypeValue(namespaces, new RawString(name.name), arg.value);
return { value, newIndex: idx };
}
else {
const value = new TypeValue(namespaces, new RawString(name.name));
return { value, newIndex: idx };
}
}
}
//# sourceMappingURL=FunctionExpressionParser.js.map