rawsql-ts
Version:
[beta]High-performance SQL parser and AST analyzer written in TypeScript. Provides fast parsing and advanced transformation capabilities.
213 lines • 11.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandExpressionParser = void 0;
const Lexeme_1 = require("../models/Lexeme");
const ValueComponent_1 = require("../models/ValueComponent");
const ValueParser_1 = require("./ValueParser");
class CommandExpressionParser {
static parseFromLexeme(lexemes, index) {
let idx = index;
const current = lexemes[idx];
if (current.value === "case") {
// Capture CASE keyword comments before consuming
const caseKeywordComments = current.comments;
const caseKeywordPositionedComments = current.positionedComments;
idx++;
return this.parseCaseExpression(lexemes, idx, caseKeywordComments, caseKeywordPositionedComments);
}
else if (current.value === "case when") {
// Capture CASE WHEN keyword comments before consuming
const caseWhenKeywordComments = current.comments;
const caseWhenKeywordPositionedComments = current.positionedComments;
idx++;
return this.parseCaseWhenExpression(lexemes, idx, caseWhenKeywordComments, caseWhenKeywordPositionedComments);
}
return this.parseModifierUnaryExpression(lexemes, idx);
}
static parseModifierUnaryExpression(lexemes, index) {
let idx = index;
// Check for modifier unary expression
if (idx < lexemes.length && (lexemes[idx].type & Lexeme_1.TokenType.Command)) {
const command = lexemes[idx].value;
idx++;
const result = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx);
return { value: new ValueComponent_1.UnaryExpression(command, result.value), newIndex: result.newIndex };
}
throw new Error(`Invalid modifier unary expression at index ${idx}, Lexeme: ${lexemes[idx].value}`);
}
static parseCaseExpression(lexemes, index, caseKeywordComments, caseKeywordPositionedComments) {
let idx = index;
const condition = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx);
idx = condition.newIndex;
const switchCaseResult = this.parseSwitchCaseArgument(lexemes, idx, []);
idx = switchCaseResult.newIndex;
// Create CASE expression
const result = new ValueComponent_1.CaseExpression(condition.value, switchCaseResult.value);
// Assign CASE keyword comments to the CaseExpression (positioned comments only for unified spec)
if (caseKeywordPositionedComments && caseKeywordPositionedComments.length > 0) {
result.positionedComments = caseKeywordPositionedComments;
}
else if (caseKeywordComments && caseKeywordComments.length > 0) {
// Convert legacy comments to positioned comments for unified spec
result.positionedComments = [CommandExpressionParser.convertLegacyToPositioned(caseKeywordComments, 'before')];
}
return { value: result, newIndex: idx };
}
static parseCaseWhenExpression(lexemes, index, caseWhenKeywordComments, caseWhenKeywordPositionedComments) {
let idx = index;
// Parse the first WHEN clause
const casewhenResult = this.parseCaseConditionValuePair(lexemes, idx);
idx = casewhenResult.newIndex;
// Add the initial WHEN-THEN pair to the list
const caseWhenList = [casewhenResult.value];
// Process remaining WHEN-ELSE-END parts
const switchCaseResult = this.parseSwitchCaseArgument(lexemes, idx, caseWhenList);
idx = switchCaseResult.newIndex;
// Create CASE expression with condition null (uses WHEN conditions instead of a simple CASE)
const result = new ValueComponent_1.CaseExpression(null, switchCaseResult.value);
// Assign CASE WHEN keyword comments to the CaseExpression (positioned comments only for unified spec)
if (caseWhenKeywordPositionedComments && caseWhenKeywordPositionedComments.length > 0) {
result.positionedComments = caseWhenKeywordPositionedComments;
}
else if (caseWhenKeywordComments && caseWhenKeywordComments.length > 0) {
// Convert legacy comments to positioned comments for unified spec
result.positionedComments = [CommandExpressionParser.convertLegacyToPositioned(caseWhenKeywordComments, 'before')];
}
return { value: result, newIndex: idx };
}
// parseSwitchCaseArgument method processes the WHEN, ELSE, and END clauses of a CASE expression.
static parseSwitchCaseArgument(lexemes, index, initialWhenThenList) {
let idx = index;
const whenThenList = [...initialWhenThenList];
// Parse all WHEN clauses
idx = this.parseAdditionalWhenClauses(lexemes, idx, whenThenList);
// Parse optional ELSE clause
const { elseValue, elseComments, newIndex: elseIndex } = this.parseElseClause(lexemes, idx);
idx = elseIndex;
// Parse required END clause
const { endComments, newIndex: endIndex } = this.parseEndClause(lexemes, idx);
idx = endIndex;
if (whenThenList.length === 0) {
throw new Error(`The CASE expression requires at least one WHEN clause (index ${idx})`);
}
// Create SwitchCaseArgument and apply comments directly
const switchCaseArg = new ValueComponent_1.SwitchCaseArgument(whenThenList, elseValue);
this.applySwitchCaseComments(switchCaseArg, elseComments, endComments);
return { value: switchCaseArg, newIndex: idx };
}
// Parse additional WHEN clauses
static parseAdditionalWhenClauses(lexemes, index, whenThenList) {
let idx = index;
while (idx < lexemes.length && this.isCommandWithValue(lexemes[idx], "when")) {
idx++;
const whenResult = this.parseCaseConditionValuePair(lexemes, idx);
idx = whenResult.newIndex;
whenThenList.push(whenResult.value);
}
return idx;
}
// Parse optional ELSE clause
static parseElseClause(lexemes, index) {
let elseValue = null;
let elseComments = null;
let idx = index;
if (idx < lexemes.length && this.isCommandWithValue(lexemes[idx], "else")) {
// Extract comments from ELSE keyword before consuming
elseComments = this.extractKeywordComments(lexemes[idx]);
idx++;
const elseResult = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx);
elseValue = elseResult.value;
idx = elseResult.newIndex;
}
return { elseValue, elseComments, newIndex: idx };
}
// Parse required END clause
static parseEndClause(lexemes, index) {
let idx = index;
let endComments = null;
if (idx < lexemes.length && this.isCommandWithValue(lexemes[idx], "end")) {
// Extract comments from END keyword before consuming
endComments = this.extractKeywordComments(lexemes[idx]);
idx++;
}
else {
throw new Error(`The CASE expression requires 'end' keyword at the end (index ${idx})`);
}
return { endComments, newIndex: idx };
}
// Extract comments from a keyword token
static extractKeywordComments(token) {
return {
legacy: token.comments,
positioned: token.positionedComments
};
}
// Apply comments to SwitchCaseArgument directly (no collection then assignment)
static applySwitchCaseComments(switchCaseArg, elseComments, endComments) {
const allPositionedComments = [];
const allLegacyComments = [];
// Process ELSE comments directly
if ((elseComments === null || elseComments === void 0 ? void 0 : elseComments.positioned) && elseComments.positioned.length > 0) {
allPositionedComments.push(...elseComments.positioned);
}
if ((elseComments === null || elseComments === void 0 ? void 0 : elseComments.legacy) && elseComments.legacy.length > 0) {
allLegacyComments.push(...elseComments.legacy);
}
// Process END comments directly
if ((endComments === null || endComments === void 0 ? void 0 : endComments.positioned) && endComments.positioned.length > 0) {
allPositionedComments.push(...endComments.positioned);
}
if ((endComments === null || endComments === void 0 ? void 0 : endComments.legacy) && endComments.legacy.length > 0) {
allLegacyComments.push(...endComments.legacy);
}
// Apply positioned comments directly, or convert legacy comments
if (allPositionedComments.length > 0) {
switchCaseArg.positionedComments = allPositionedComments;
}
else if (allLegacyComments.length > 0) {
switchCaseArg.positionedComments = [CommandExpressionParser.convertLegacyToPositioned(allLegacyComments, 'after')];
}
}
// Helper method: Check if a lexeme is a Command token with the specified value
static isCommandWithValue(lexeme, value) {
return ((lexeme.type & Lexeme_1.TokenType.Command) !== 0) && lexeme.value === value;
}
static parseCaseConditionValuePair(lexemes, index) {
let idx = index;
const condition = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx);
idx = condition.newIndex;
// Check for the existence of the THEN keyword
if (idx >= lexemes.length || !(lexemes[idx].type & Lexeme_1.TokenType.Command) || lexemes[idx].value !== "then") {
throw new Error(`Expected 'then' after WHEN condition at index ${idx}`);
}
// Capture THEN keyword comments before consuming
const thenKeywordComments = lexemes[idx].comments;
const thenKeywordPositionedComments = lexemes[idx].positionedComments;
idx++; // Skip the THEN keyword
// Parse the value after THEN
const value = ValueParser_1.ValueParser.parseFromLexeme(lexemes, idx);
idx = value.newIndex;
const keyValuePair = new ValueComponent_1.CaseKeyValuePair(condition.value, value.value);
// Store THEN keyword comments on the CaseKeyValuePair
// Store THEN keyword comments - unified spec: positioned comments only
if (thenKeywordPositionedComments && thenKeywordPositionedComments.length > 0) {
keyValuePair.positionedComments = thenKeywordPositionedComments;
}
else if (thenKeywordComments && thenKeywordComments.length > 0) {
// Convert legacy comments to positioned comments for unified spec
keyValuePair.positionedComments = [CommandExpressionParser.convertLegacyToPositioned(thenKeywordComments, 'after')];
}
return { value: keyValuePair, newIndex: idx };
}
/**
* Convert legacy comments to positioned comments format
*/
static convertLegacyToPositioned(legacyComments, position = 'before') {
return {
position,
comments: legacyComments
};
}
}
exports.CommandExpressionParser = CommandExpressionParser;
//# sourceMappingURL=CommandExpressionParser.js.map