UNPKG

@jmespath-community/jmespath

Version:

Typescript implementation of the JMESPath Community specification

1,477 lines (1,470 loc) 71.9 kB
#!/usr/bin/env node import { parseArgs } from 'util'; import * as fs from 'fs'; // package.json var package_default = { name: "@jmespath-community/jmespath", version: "1.3.0"}; // src/utils/index.ts var isObject = (obj) => { return obj !== null && Object.prototype.toString.call(obj) === "[object Object]"; }; var strictDeepEqual = (first, second) => { if (first === second) { return true; } if (typeof first !== typeof second) { return false; } if (Array.isArray(first) && Array.isArray(second)) { if (first.length !== second.length) { return false; } for (let i = 0; i < first.length; i += 1) { if (!strictDeepEqual(first[i], second[i])) { return false; } } return true; } if (isObject(first) && isObject(second)) { const firstEntries = Object.entries(first); const secondKeys = new Set(Object.keys(second)); if (firstEntries.length !== secondKeys.size) { return false; } for (const [key, value] of firstEntries) { if (!strictDeepEqual(value, second[key])) { return false; } secondKeys.delete(key); } return secondKeys.size === 0; } return false; }; var isFalse = (obj) => { if (obj === null || obj === void 0 || obj === false) { return true; } if (typeof obj === "string") { return obj === ""; } if (typeof obj === "object") { if (Array.isArray(obj)) { return obj.length === 0; } if (obj === null) { return true; } return Object.keys(obj).length === 0; } return false; }; var isAlpha = (ch) => { return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_"; }; var isNum = (ch) => { return ch >= "0" && ch <= "9" || ch === "-"; }; var isAlphaNum = (ch) => { return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_"; }; var ensureInteger = (value) => { if (!(typeof value === "number") || Math.floor(value) !== value) { throw new Error("invalid-value: expecting an integer."); } return value; }; var ensurePositiveInteger = (value) => { if (!(typeof value === "number") || value < 0 || Math.floor(value) !== value) { throw new Error("invalid-value: expecting a non-negative integer."); } return value; }; var ensureNumbers = (...operands) => { for (let i = 0; i < operands.length; i++) { if (operands[i] === null || operands[i] === void 0) { throw new Error("not-a-number: undefined"); } if (typeof operands[i] !== "number") { throw new Error("not-a-number"); } } }; var notZero = (n) => { n = +n; if (!n) { throw new Error("not-a-number: divide by zero"); } return n; }; var add = (left, right) => { ensureNumbers(left, right); const result = left + right; return result; }; var sub = (left, right) => { ensureNumbers(left, right); const result = left - right; return result; }; var mul = (left, right) => { ensureNumbers(left, right); const result = left * right; return result; }; var divide = (left, right) => { ensureNumbers(left, right); const result = left / notZero(right); return result; }; var div = (left, right) => { ensureNumbers(left, right); const result = Math.floor(left / notZero(right)); return result; }; var mod = (left, right) => { ensureNumbers(left, right); const result = left % right; return result; }; // src/utils/strings.ts var findFirst = (subject, sub2, start, end) => { if (!subject || !sub2) { return null; } start = Math.max(ensureInteger(start = start || 0), 0); end = Math.min(ensureInteger(end = end || subject.length), subject.length); const offset = subject.slice(start, end).indexOf(sub2); return offset === -1 ? null : offset + start; }; var findLast = (subject, sub2, start, end) => { if (!subject || !sub2) { return null; } start = Math.max(ensureInteger(start = start || 0), 0); end = Math.min(ensureInteger(end = end || subject.length), subject.length); const offset = subject.slice(start, end).lastIndexOf(sub2); const result = offset === -1 ? null : offset + start; return result; }; var lower = (subject) => subject.toLowerCase(); var ensurePadFuncParams = (name, width, padding) => { padding = padding || " "; if (padding.length > 1) { throw new Error(`invalid value, ${name} expects its 'pad' parameter to be a valid string with a single codepoint`); } ensurePositiveInteger(width); return padding; }; var padLeft = (subject, width, padding) => { padding = ensurePadFuncParams("pad_left", width, padding); return subject && subject.padStart(width, padding) || ""; }; var padRight = (subject, width, padding) => { padding = ensurePadFuncParams("pad_right", width, padding); return subject && subject.padEnd(width, padding) || ""; }; var replace = (subject, string, by, count) => { if (count === 0) { return subject; } if (!count) { return subject.split(string).join(by); } ensurePositiveInteger(count); [...Array(count).keys()].map(() => subject = subject.replace(string, by)); return subject; }; var split = (subject, search2, count) => { if (subject.length == 0 && search2.length === 0) { return []; } if (count === null || count === void 0) { return subject.split(search2); } ensurePositiveInteger(count); if (count === 0) { return [subject]; } const split2 = subject.split(search2); return [...split2.slice(0, count), split2.slice(count).join(search2)]; }; var trim = (subject, chars) => { return trimLeft(trimRight(subject, chars), chars); }; var trimLeft = (subject, chars) => { return trimImpl(subject, (list) => new RegExp(`^[${list}]*(.*?)`), chars); }; var trimRight = (subject, chars) => { return trimImpl(subject, (list) => new RegExp(`(.*?)[${list}]*$`), chars); }; var trimImpl = (subject, regExper, chars) => { const pattern = chars ? chars.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&") : "\\s\x85"; return subject.replace(regExper(pattern), "$1"); }; var upper = (subject) => subject.toUpperCase(); // src/Lexer.ts var basicTokens = { "(": "Lparen" /* TOK_LPAREN */, ")": "Rparen" /* TOK_RPAREN */, "*": "Star" /* TOK_STAR */, ",": "Comma" /* TOK_COMMA */, ".": "Dot" /* TOK_DOT */, ":": "Colon" /* TOK_COLON */, "@": "Current" /* TOK_CURRENT */, "]": "Rbracket" /* TOK_RBRACKET */, "{": "Lbrace" /* TOK_LBRACE */, "}": "Rbrace" /* TOK_RBRACE */, "+": "Plus" /* TOK_PLUS */, "%": "Modulo" /* TOK_MODULO */, "?": "Question" /* TOK_QUESTION */, "\u2212": "Minus" /* TOK_MINUS */, "\xD7": "Multiply" /* TOK_MULTIPLY */, "\xF7": "Divide" /* TOK_DIVIDE */ }; var operatorStartToken = { "!": true, "<": true, "=": true, ">": true, "&": true, "|": true, "/": true }; var skipChars = { " ": true, "\n": true, "\r": true, " ": true }; var StreamLexer = class { _current = 0; _enable_legacy_literals = false; tokenize(stream, options) { const tokens = []; this._current = 0; this._enable_legacy_literals = options?.enable_legacy_literals || false; let start; let identifier; let token; while (this._current < stream.length) { if (isAlpha(stream[this._current])) { start = this._current; identifier = this.consumeUnquotedIdentifier(stream); tokens.push({ start, type: "UnquotedIdentifier" /* TOK_UNQUOTEDIDENTIFIER */, value: identifier }); } else if (basicTokens[stream[this._current]] !== void 0) { tokens.push({ start: this._current, type: basicTokens[stream[this._current]], value: stream[this._current] }); this._current += 1; } else if (stream[this._current] === "$") { start = this._current; if (this._current + 1 < stream.length && isAlpha(stream[this._current + 1])) { this._current += 1; identifier = this.consumeUnquotedIdentifier(stream); tokens.push({ start, type: "Variable" /* TOK_VARIABLE */, value: identifier }); } else { tokens.push({ start, type: "Root" /* TOK_ROOT */, value: stream[this._current] }); this._current += 1; } } else if (stream[this._current] === "-") { if (this._current + 1 < stream.length && isNum(stream[this._current + 1])) { const token2 = this.consumeNumber(stream); token2 && tokens.push(token2); } else { const token2 = { start: this._current, type: "Minus" /* TOK_MINUS */, value: "-" }; tokens.push(token2); this._current += 1; } } else if (isNum(stream[this._current])) { token = this.consumeNumber(stream); tokens.push(token); } else if (stream[this._current] === "[") { token = this.consumeLBracket(stream); tokens.push(token); } else if (stream[this._current] === '"') { start = this._current; identifier = this.consumeQuotedIdentifier(stream); tokens.push({ start, type: "QuotedIdentifier" /* TOK_QUOTEDIDENTIFIER */, value: identifier }); } else if (stream[this._current] === `'`) { start = this._current; identifier = this.consumeRawStringLiteral(stream); tokens.push({ start, type: "Literal" /* TOK_LITERAL */, value: identifier }); } else if (stream[this._current] === "`") { start = this._current; const literal = this.consumeLiteral(stream); tokens.push({ start, type: "Literal" /* TOK_LITERAL */, value: literal }); } else if (operatorStartToken[stream[this._current]] !== void 0) { token = this.consumeOperator(stream); token && tokens.push(token); } else if (skipChars[stream[this._current]] !== void 0) { this._current += 1; } else { const error = new Error(`Syntax error: unknown character: ${stream[this._current]}`); error.name = "LexerError"; throw error; } } return tokens; } consumeUnquotedIdentifier(stream) { const start = this._current; this._current += 1; while (this._current < stream.length && isAlphaNum(stream[this._current])) { this._current += 1; } return stream.slice(start, this._current); } consumeQuotedIdentifier(stream) { const start = this._current; this._current += 1; const maxLength = stream.length; while (stream[this._current] !== '"' && this._current < maxLength) { let current = this._current; if (stream[current] === "\\" && (stream[current + 1] === "\\" || stream[current + 1] === '"')) { current += 2; } else { current += 1; } this._current = current; } this._current += 1; const [value, ok] = this.parseJSON(stream.slice(start, this._current)); if (!ok) { const error = new Error(`syntax: unexpected end of JSON input`); error.name = "LexerError"; throw error; } return value; } consumeRawStringLiteral(stream) { const start = this._current; this._current += 1; const maxLength = stream.length; while (stream[this._current] !== `'` && this._current < maxLength) { let current = this._current; if (stream[current] === "\\" && (stream[current + 1] === "\\" || stream[current + 1] === `'`)) { current += 2; } else { current += 1; } this._current = current; } this._current += 1; const literal = stream.slice(start + 1, this._current - 1); return replace(replace(literal, `\\\\`, `\\`), `\\'`, `'`); } consumeNumber(stream) { const start = this._current; this._current += 1; const maxLength = stream.length; while (isNum(stream[this._current]) && this._current < maxLength) { this._current += 1; } const value = parseInt(stream.slice(start, this._current), 10); return { start, value, type: "Number" /* TOK_NUMBER */ }; } consumeLBracket(stream) { const start = this._current; this._current += 1; if (stream[this._current] === "?") { this._current += 1; return { start, type: "Filter" /* TOK_FILTER */, value: "[?" }; } if (stream[this._current] === "]") { this._current += 1; return { start, type: "Flatten" /* TOK_FLATTEN */, value: "[]" }; } return { start, type: "Lbracket" /* TOK_LBRACKET */, value: "[" }; } consumeOrElse(stream, peek, token, orElse) { const start = this._current; this._current += 1; if (this._current < stream.length && stream[this._current] === peek) { this._current += 1; return { start, type: orElse, value: stream.slice(start, this._current) }; } return { start, type: token, value: stream[start] }; } consumeOperator(stream) { const start = this._current; const startingChar = stream[start]; switch (startingChar) { case "!": return this.consumeOrElse(stream, "=", "Not" /* TOK_NOT */, "NE" /* TOK_NE */); case "<": return this.consumeOrElse(stream, "=", "LT" /* TOK_LT */, "LTE" /* TOK_LTE */); case ">": return this.consumeOrElse(stream, "=", "GT" /* TOK_GT */, "GTE" /* TOK_GTE */); case "=": return this.consumeOrElse(stream, "=", "Assign" /* TOK_ASSIGN */, "EQ" /* TOK_EQ */); case "&": return this.consumeOrElse(stream, "&", "Expref" /* TOK_EXPREF */, "And" /* TOK_AND */); case "|": return this.consumeOrElse(stream, "|", "Pipe" /* TOK_PIPE */, "Or" /* TOK_OR */); case "/": return this.consumeOrElse(stream, "/", "Divide" /* TOK_DIVIDE */, "Div" /* TOK_DIV */); } } consumeLiteral(stream) { this._current += 1; const start = this._current; const maxLength = stream.length; while (stream[this._current] !== "`" && this._current < maxLength) { let current = this._current; if (stream[current] === "\\" && (stream[current + 1] === "\\" || stream[current + 1] === "`")) { current += 2; } else { current += 1; } this._current = current; } let literalString = stream.slice(start, this._current).trimStart(); literalString = literalString.replace("\\`", "`"); let literal = null; let ok = false; if (this.looksLikeJSON(literalString)) { [literal, ok] = this.parseJSON(literalString); } if (!ok && this._enable_legacy_literals) { [literal, ok] = this.parseJSON(`"${literalString}"`); } if (!ok) { const error = new Error( `Syntax error: unexpected end of JSON input or invalid format for a JSON literal: ${stream[this._current]}` ); error.name = "LexerError"; throw error; } this._current += 1; return literal; } looksLikeJSON(literalString) { const startingChars = '[{"'; const jsonLiterals = ["true", "false", "null"]; const numberLooking = "-0123456789"; if (literalString === "") { return false; } if (startingChars.includes(literalString[0])) { return true; } if (jsonLiterals.includes(literalString)) { return true; } if (numberLooking.includes(literalString[0])) { const [_, ok] = this.parseJSON(literalString); return ok; } return false; } parseJSON(text) { try { const json = JSON.parse(text); return [json, true]; } catch { return [null, false]; } } }; var Lexer = new StreamLexer(); var Lexer_default = Lexer; // src/Parser.ts var bindingPower = { ["EOF" /* TOK_EOF */]: 0, ["Variable" /* TOK_VARIABLE */]: 0, ["UnquotedIdentifier" /* TOK_UNQUOTEDIDENTIFIER */]: 0, ["QuotedIdentifier" /* TOK_QUOTEDIDENTIFIER */]: 0, ["Rbracket" /* TOK_RBRACKET */]: 0, ["Rparen" /* TOK_RPAREN */]: 0, ["Comma" /* TOK_COMMA */]: 0, ["Rbrace" /* TOK_RBRACE */]: 0, ["Number" /* TOK_NUMBER */]: 0, ["Current" /* TOK_CURRENT */]: 0, ["Expref" /* TOK_EXPREF */]: 0, ["Root" /* TOK_ROOT */]: 0, ["Assign" /* TOK_ASSIGN */]: 1, ["Pipe" /* TOK_PIPE */]: 1, ["Question" /* TOK_QUESTION */]: 2, ["Or" /* TOK_OR */]: 3, ["And" /* TOK_AND */]: 4, ["EQ" /* TOK_EQ */]: 5, ["GT" /* TOK_GT */]: 5, ["LT" /* TOK_LT */]: 5, ["GTE" /* TOK_GTE */]: 5, ["LTE" /* TOK_LTE */]: 5, ["NE" /* TOK_NE */]: 5, ["Minus" /* TOK_MINUS */]: 6, ["Plus" /* TOK_PLUS */]: 6, ["Div" /* TOK_DIV */]: 7, ["Divide" /* TOK_DIVIDE */]: 7, ["Modulo" /* TOK_MODULO */]: 7, ["Multiply" /* TOK_MULTIPLY */]: 7, ["Flatten" /* TOK_FLATTEN */]: 9, ["Star" /* TOK_STAR */]: 20, ["Filter" /* TOK_FILTER */]: 21, ["Dot" /* TOK_DOT */]: 40, ["Not" /* TOK_NOT */]: 45, ["Lbrace" /* TOK_LBRACE */]: 50, ["Lbracket" /* TOK_LBRACKET */]: 55, ["Lparen" /* TOK_LPAREN */]: 60 }; var TokenParser = class _TokenParser { index = 0; tokens = []; parse(expression2, options) { this.loadTokens(expression2, options || { enable_legacy_literals: false }); this.index = 0; const ast = this.expression(0); if (this.lookahead(0) !== "EOF" /* TOK_EOF */) { const token = this.lookaheadToken(0); this.errorToken(token, `Syntax error: unexpected token type: ${token.type}, value: ${token.value}`); } return ast; } loadTokens(expression2, options) { this.tokens = Lexer_default.tokenize(expression2, options); this.tokens.push({ type: "EOF" /* TOK_EOF */, value: "", start: expression2.length }); } expression(rbp) { const leftToken = this.lookaheadToken(0); this.advance(); let left = this.nud(leftToken); let currentTokenType = this.lookahead(0); while (rbp < bindingPower[currentTokenType]) { this.advance(); left = this.led(currentTokenType, left); currentTokenType = this.lookahead(0); } return left; } lookahead(offset) { return this.tokens[this.index + offset].type; } lookaheadToken(offset) { return this.tokens[this.index + offset]; } advance() { this.index += 1; } nud(token) { switch (token.type) { case "Variable" /* TOK_VARIABLE */: return { type: "Variable", name: token.value }; case "Literal" /* TOK_LITERAL */: return { type: "Literal", value: token.value }; case "UnquotedIdentifier" /* TOK_UNQUOTEDIDENTIFIER */: { if (_TokenParser.isKeyword(token, "let") && this.lookahead(0) === "Variable" /* TOK_VARIABLE */) { return this.parseLetExpression(); } else { return { type: "Field", name: token.value }; } } case "QuotedIdentifier" /* TOK_QUOTEDIDENTIFIER */: if (this.lookahead(0) === "Lparen" /* TOK_LPAREN */) { throw new Error("Syntax error: quoted identifier not allowed for function names."); } else { return { type: "Field", name: token.value }; } case "Not" /* TOK_NOT */: { const child = this.expression(bindingPower.Not); return { type: "NotExpression", child }; } case "Minus" /* TOK_MINUS */: { const child = this.expression(bindingPower.Minus); return { type: "Unary", operator: token.type, operand: child }; } case "Plus" /* TOK_PLUS */: { const child = this.expression(bindingPower.Plus); return { type: "Unary", operator: token.type, operand: child }; } case "Star" /* TOK_STAR */: { const left = { type: "Identity" }; return { type: "ValueProjection", left, right: this.parseProjectionRHS(bindingPower.Star) }; } case "Filter" /* TOK_FILTER */: return this.led(token.type, { type: "Identity" }); case "Lbrace" /* TOK_LBRACE */: return this.parseMultiselectHash(); case "Flatten" /* TOK_FLATTEN */: { const left = { type: "Flatten", child: { type: "Identity" } }; const right = this.parseProjectionRHS(bindingPower.Flatten); return { type: "Projection", left, right }; } case "Lbracket" /* TOK_LBRACKET */: { if (this.lookahead(0) === "Number" /* TOK_NUMBER */ || this.lookahead(0) === "Colon" /* TOK_COLON */) { const right = this.parseIndexExpression(); return this.projectIfSlice({ type: "Identity" }, right); } if (this.lookahead(0) === "Star" /* TOK_STAR */ && this.lookahead(1) === "Rbracket" /* TOK_RBRACKET */) { this.advance(); this.advance(); const right = this.parseProjectionRHS(bindingPower.Star); return { left: { type: "Identity" }, right, type: "Projection" }; } return this.parseMultiselectList(); } case "Current" /* TOK_CURRENT */: return { type: "Current" /* TOK_CURRENT */ }; case "Root" /* TOK_ROOT */: return { type: "Root" /* TOK_ROOT */ }; case "Expref" /* TOK_EXPREF */: { const child = this.expression(bindingPower.Expref); return { type: "ExpressionReference", child }; } case "Lparen" /* TOK_LPAREN */: { const expression2 = this.expression(0); this.match("Rparen" /* TOK_RPAREN */); return expression2; } default: this.errorToken(token); } } led(tokenName, left) { switch (tokenName) { case "Question" /* TOK_QUESTION */: { const trueExpr = this.expression(0); this.match("Colon" /* TOK_COLON */); const falseExpr = this.expression(0); return { type: "Ternary", condition: left, trueExpr, falseExpr }; } case "Dot" /* TOK_DOT */: { const rbp = bindingPower.Dot; if (this.lookahead(0) !== "Star" /* TOK_STAR */) { const right2 = this.parseDotRHS(rbp); return { type: "Subexpression", left, right: right2 }; } this.advance(); const right = this.parseProjectionRHS(rbp); return { type: "ValueProjection", left, right }; } case "Pipe" /* TOK_PIPE */: { const right = this.expression(bindingPower.Pipe); return { type: "Pipe", left, right }; } case "Or" /* TOK_OR */: { const right = this.expression(bindingPower.Or); return { type: "OrExpression", left, right }; } case "And" /* TOK_AND */: { const right = this.expression(bindingPower.And); return { type: "AndExpression", left, right }; } case "Lparen" /* TOK_LPAREN */: { if (left.type !== "Field") { throw new Error("Syntax error: expected a Field node"); } const name = left.name; const args2 = this.parseCommaSeparatedExpressionsUntilToken("Rparen" /* TOK_RPAREN */); const node = { name, type: "Function", children: args2 }; return node; } case "Filter" /* TOK_FILTER */: { const condition = this.expression(0); this.match("Rbracket" /* TOK_RBRACKET */); const right = this.lookahead(0) === "Flatten" /* TOK_FLATTEN */ ? { type: "Identity" } : this.parseProjectionRHS(bindingPower.Filter); return { type: "FilterProjection", left, right, condition }; } case "Flatten" /* TOK_FLATTEN */: { const leftNode = { type: "Flatten", child: left }; const right = this.parseProjectionRHS(bindingPower.Flatten); return { type: "Projection", left: leftNode, right }; } case "Assign" /* TOK_ASSIGN */: { const leftNode = left; const right = this.expression(0); return { type: "Binding", variable: leftNode.name, reference: right }; } case "EQ" /* TOK_EQ */: case "NE" /* TOK_NE */: case "GT" /* TOK_GT */: case "GTE" /* TOK_GTE */: case "LT" /* TOK_LT */: case "LTE" /* TOK_LTE */: return this.parseComparator(left, tokenName); case "Plus" /* TOK_PLUS */: case "Minus" /* TOK_MINUS */: case "Multiply" /* TOK_MULTIPLY */: case "Star" /* TOK_STAR */: case "Divide" /* TOK_DIVIDE */: case "Modulo" /* TOK_MODULO */: case "Div" /* TOK_DIV */: return this.parseArithmetic(left, tokenName); case "Lbracket" /* TOK_LBRACKET */: { const token = this.lookaheadToken(0); if (token.type === "Number" /* TOK_NUMBER */ || token.type === "Colon" /* TOK_COLON */) { const right2 = this.parseIndexExpression(); return this.projectIfSlice(left, right2); } this.match("Star" /* TOK_STAR */); this.match("Rbracket" /* TOK_RBRACKET */); const right = this.parseProjectionRHS(bindingPower.Star); return { type: "Projection", left, right }; } default: return this.errorToken(this.lookaheadToken(0)); } } static isKeyword(token, keyword) { return token.type === "UnquotedIdentifier" /* TOK_UNQUOTEDIDENTIFIER */ && token.value === keyword; } match(tokenType) { if (this.lookahead(0) === tokenType) { this.advance(); return; } else { const token = this.lookaheadToken(0); this.errorToken(token, `Syntax error: expected ${tokenType}, got: ${token.type}`); } } errorToken(token, message = "") { const error = new Error(message || `Syntax error: invalid token (${token.type}): "${token.value}"`); error.name = "ParserError"; throw error; } parseIndexExpression() { if (this.lookahead(0) === "Colon" /* TOK_COLON */ || this.lookahead(1) === "Colon" /* TOK_COLON */) { return this.parseSliceExpression(); } const value = Number(this.lookaheadToken(0).value); this.advance(); this.match("Rbracket" /* TOK_RBRACKET */); return { type: "Index", value }; } projectIfSlice(left, right) { const indexExpr = { type: "IndexExpression", left, right }; if (right.type === "Slice") { return { left: indexExpr, right: this.parseProjectionRHS(bindingPower.Star), type: "Projection" }; } return indexExpr; } parseSliceExpression() { const parts = [null, null, null]; let index = 0; let current = this.lookaheadToken(0); while (current.type != "Rbracket" /* TOK_RBRACKET */ && index < 3) { if (current.type === "Colon" /* TOK_COLON */) { index++; if (index === 3) { this.errorToken(this.lookaheadToken(0), "Syntax error, too many colons in slice expression"); } this.advance(); } else if (current.type === "Number" /* TOK_NUMBER */) { const part = this.lookaheadToken(0).value; parts[index] = part; this.advance(); } else { const next = this.lookaheadToken(0); this.errorToken(next, `Syntax error, unexpected token: ${next.value}(${next.type})`); } current = this.lookaheadToken(0); } this.match("Rbracket" /* TOK_RBRACKET */); const [start, stop, step] = parts; return { type: "Slice", start, stop, step }; } parseLetExpression() { const separated = this.parseCommaSeparatedExpressionsUntilKeyword("in"); const expression2 = this.expression(0); const bindings = separated.map((binding) => binding); return { type: "LetExpression", bindings, expression: expression2 }; } parseCommaSeparatedExpressionsUntilKeyword(keyword) { return this.parseCommaSeparatedExpressionsUntil( () => { return _TokenParser.isKeyword(this.lookaheadToken(0), keyword); }, () => { this.advance(); } ); } parseCommaSeparatedExpressionsUntilToken(token) { return this.parseCommaSeparatedExpressionsUntil( () => { return this.lookahead(0) === token; }, () => { return this.match(token); } ); } parseCommaSeparatedExpressionsUntil(isEndToken, matchEndToken) { const args2 = []; let expression2; while (!isEndToken()) { expression2 = this.expression(0); if (this.lookahead(0) === "Comma" /* TOK_COMMA */) { this.match("Comma" /* TOK_COMMA */); } args2.push(expression2); } matchEndToken(); return args2; } parseComparator(left, comparator) { const right = this.expression(bindingPower[comparator]); return { type: "Comparator", name: comparator, left, right }; } parseArithmetic(left, operator) { const right = this.expression(bindingPower[operator]); return { type: "Arithmetic", operator, left, right }; } parseDotRHS(rbp) { const lookahead = this.lookahead(0); const exprTokens = ["UnquotedIdentifier" /* TOK_UNQUOTEDIDENTIFIER */, "QuotedIdentifier" /* TOK_QUOTEDIDENTIFIER */, "Star" /* TOK_STAR */]; if (exprTokens.includes(lookahead)) { return this.expression(rbp); } if (lookahead === "Lbracket" /* TOK_LBRACKET */) { this.match("Lbracket" /* TOK_LBRACKET */); return this.parseMultiselectList(); } if (lookahead === "Lbrace" /* TOK_LBRACE */) { this.match("Lbrace" /* TOK_LBRACE */); return this.parseMultiselectHash(); } const token = this.lookaheadToken(0); this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`); } parseProjectionRHS(rbp) { if (bindingPower[this.lookahead(0)] < 10) { return { type: "Identity" }; } if (this.lookahead(0) === "Lbracket" /* TOK_LBRACKET */) { return this.expression(rbp); } if (this.lookahead(0) === "Filter" /* TOK_FILTER */) { return this.expression(rbp); } if (this.lookahead(0) === "Dot" /* TOK_DOT */) { this.match("Dot" /* TOK_DOT */); return this.parseDotRHS(rbp); } const token = this.lookaheadToken(0); this.errorToken(token, `Syntax error, unexpected token: ${token.value}(${token.type})`); } parseMultiselectList() { const expressions = []; while (this.lookahead(0) !== "Rbracket" /* TOK_RBRACKET */) { const expression2 = this.expression(0); expressions.push(expression2); if (this.lookahead(0) === "Comma" /* TOK_COMMA */) { this.match("Comma" /* TOK_COMMA */); if (this.lookahead(0) === "Rbracket" /* TOK_RBRACKET */) { throw new Error("Syntax error: unexpected token Rbracket"); } } } this.match("Rbracket" /* TOK_RBRACKET */); return { type: "MultiSelectList", children: expressions }; } parseMultiselectHash() { const pairs = []; const identifierTypes = ["UnquotedIdentifier" /* TOK_UNQUOTEDIDENTIFIER */, "QuotedIdentifier" /* TOK_QUOTEDIDENTIFIER */]; let keyToken; let keyName; let value; for (; ; ) { keyToken = this.lookaheadToken(0); if (!identifierTypes.includes(keyToken.type)) { throw new Error(`Syntax error: expecting an identifier token, got: ${keyToken.type}`); } keyName = keyToken.value; this.advance(); this.match("Colon" /* TOK_COLON */); value = this.expression(0); pairs.push({ value, type: "KeyValuePair", name: keyName }); if (this.lookahead(0) === "Comma" /* TOK_COMMA */) { this.match("Comma" /* TOK_COMMA */); } else if (this.lookahead(0) === "Rbrace" /* TOK_RBRACE */) { this.match("Rbrace" /* TOK_RBRACE */); break; } } return { type: "MultiSelectHash", children: pairs }; } }; var Parser = new TokenParser(); var Parser_default = Parser; // src/utils/text.ts var Text = class _Text { _text; constructor(text) { this._text = text; } get string() { return this._text; } get length() { return this.codePoints.length; } compareTo(other) { return _Text.compare(this, new _Text(other)); } static get comparer() { const stringComparer = (left, right) => { return new _Text(left).compareTo(right); }; return stringComparer; } static compare(left, right) { const leftCp = left.codePoints; const rightCp = right.codePoints; for (let index = 0; index < Math.min(leftCp.length, rightCp.length); index++) { if (leftCp[index] === rightCp[index]) { continue; } return leftCp[index] - rightCp[index] > 0 ? 1 : -1; } return leftCp.length - rightCp.length > 0 ? 1 : -1; } reverse() { return String.fromCodePoint(...this.codePoints.reverse()); } get codePoints() { const array = [...this._text].map((s) => s.codePointAt(0)); return array; } }; // src/Runtime.ts var createMathFunction = (mathFn) => ([value]) => mathFn(value); var createStringFunction = (stringFn) => ([subject]) => stringFn(subject); var createObjectFunction = (objFn) => ([obj]) => objFn(obj); var Runtime = class { _interpreter; _functionTable; _customFunctions = /* @__PURE__ */ new Set(); TYPE_NAME_TABLE = Object.freeze({ [0 /* TYPE_NUMBER */]: "number", [1 /* TYPE_ANY */]: "any", [2 /* TYPE_STRING */]: "string", [3 /* TYPE_ARRAY */]: "array", [4 /* TYPE_OBJECT */]: "object", [5 /* TYPE_BOOLEAN */]: "boolean", [6 /* TYPE_EXPREF */]: "expression", [7 /* TYPE_NULL */]: "null", [8 /* TYPE_ARRAY_NUMBER */]: "Array<number>", [10 /* TYPE_ARRAY_OBJECT */]: "Array<object>", [9 /* TYPE_ARRAY_STRING */]: "Array<string>", [11 /* TYPE_ARRAY_ARRAY */]: "Array<Array<any>>" }); constructor(interpreter) { this._interpreter = interpreter; this._functionTable = this.buildFunctionTable(); } buildFunctionTable() { return { // Math functions abs: { _func: createMathFunction(Math.abs), _signature: [{ types: [0 /* TYPE_NUMBER */] }] }, ceil: { _func: createMathFunction(Math.ceil), _signature: [{ types: [0 /* TYPE_NUMBER */] }] }, floor: { _func: createMathFunction(Math.floor), _signature: [{ types: [0 /* TYPE_NUMBER */] }] }, // String functions lower: { _func: createStringFunction(lower), _signature: [{ types: [2 /* TYPE_STRING */] }] }, upper: { _func: createStringFunction(upper), _signature: [{ types: [2 /* TYPE_STRING */] }] }, // Object functions keys: { _func: createObjectFunction(Object.keys), _signature: [{ types: [4 /* TYPE_OBJECT */] }] }, values: { _func: createObjectFunction(Object.values), _signature: [{ types: [4 /* TYPE_OBJECT */] }] }, // Complex functions that need custom implementations avg: { _func: this.functionAvg, _signature: [{ types: [8 /* TYPE_ARRAY_NUMBER */] }] }, contains: { _func: this.functionContains, _signature: [ { types: [2 /* TYPE_STRING */, 3 /* TYPE_ARRAY */] }, { types: [1 /* TYPE_ANY */] } ] }, ends_with: { _func: this.functionEndsWith, _signature: [{ types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }] }, find_first: { _func: this.functionFindFirst, _signature: [ { types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }, { types: [0 /* TYPE_NUMBER */], optional: true }, { types: [0 /* TYPE_NUMBER */], optional: true } ] }, find_last: { _func: this.functionFindLast, _signature: [ { types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }, { types: [0 /* TYPE_NUMBER */], optional: true }, { types: [0 /* TYPE_NUMBER */], optional: true } ] }, from_items: { _func: this.functionFromItems, _signature: [{ types: [11 /* TYPE_ARRAY_ARRAY */] }] }, group_by: { _func: this.functionGroupBy, _signature: [{ types: [3 /* TYPE_ARRAY */] }, { types: [6 /* TYPE_EXPREF */] }] }, items: { _func: this.functionItems, _signature: [{ types: [4 /* TYPE_OBJECT */] }] }, join: { _func: this.functionJoin, _signature: [{ types: [2 /* TYPE_STRING */] }, { types: [9 /* TYPE_ARRAY_STRING */] }] }, length: { _func: this.functionLength, _signature: [{ types: [2 /* TYPE_STRING */, 3 /* TYPE_ARRAY */, 4 /* TYPE_OBJECT */] }] }, map: { _func: this.functionMap, _signature: [{ types: [6 /* TYPE_EXPREF */] }, { types: [3 /* TYPE_ARRAY */] }] }, max: { _func: this.functionMax, _signature: [{ types: [8 /* TYPE_ARRAY_NUMBER */, 9 /* TYPE_ARRAY_STRING */] }] }, max_by: { _func: this.functionMaxBy, _signature: [{ types: [3 /* TYPE_ARRAY */] }, { types: [6 /* TYPE_EXPREF */] }] }, merge: { _func: this.functionMerge, _signature: [{ types: [4 /* TYPE_OBJECT */], variadic: true }] }, min: { _func: this.functionMin, _signature: [{ types: [8 /* TYPE_ARRAY_NUMBER */, 9 /* TYPE_ARRAY_STRING */] }] }, min_by: { _func: this.functionMinBy, _signature: [{ types: [3 /* TYPE_ARRAY */] }, { types: [6 /* TYPE_EXPREF */] }] }, not_null: { _func: this.functionNotNull, _signature: [{ types: [1 /* TYPE_ANY */], variadic: true }] }, pad_left: { _func: this.functionPadLeft, _signature: [ { types: [2 /* TYPE_STRING */] }, { types: [0 /* TYPE_NUMBER */] }, { types: [2 /* TYPE_STRING */], optional: true } ] }, pad_right: { _func: this.functionPadRight, _signature: [ { types: [2 /* TYPE_STRING */] }, { types: [0 /* TYPE_NUMBER */] }, { types: [2 /* TYPE_STRING */], optional: true } ] }, replace: { _func: this.functionReplace, _signature: [ { types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }, { types: [0 /* TYPE_NUMBER */], optional: true } ] }, reverse: { _func: this.functionReverse, _signature: [{ types: [2 /* TYPE_STRING */, 3 /* TYPE_ARRAY */] }] }, sort: { _func: this.functionSort, _signature: [{ types: [9 /* TYPE_ARRAY_STRING */, 8 /* TYPE_ARRAY_NUMBER */] }] }, sort_by: { _func: this.functionSortBy, _signature: [{ types: [3 /* TYPE_ARRAY */] }, { types: [6 /* TYPE_EXPREF */] }] }, split: { _func: this.functionSplit, _signature: [ { types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }, { types: [0 /* TYPE_NUMBER */], optional: true } ] }, starts_with: { _func: this.functionStartsWith, _signature: [{ types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */] }] }, sum: { _func: this.functionSum, _signature: [{ types: [8 /* TYPE_ARRAY_NUMBER */] }] }, to_array: { _func: this.functionToArray, _signature: [{ types: [1 /* TYPE_ANY */] }] }, to_number: { _func: this.functionToNumber, _signature: [{ types: [1 /* TYPE_ANY */] }] }, to_string: { _func: this.functionToString, _signature: [{ types: [1 /* TYPE_ANY */] }] }, trim: { _func: this.functionTrim, _signature: [{ types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */], optional: true }] }, trim_left: { _func: this.functionTrimLeft, _signature: [{ types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */], optional: true }] }, trim_right: { _func: this.functionTrimRight, _signature: [{ types: [2 /* TYPE_STRING */] }, { types: [2 /* TYPE_STRING */], optional: true }] }, type: { _func: this.functionType, _signature: [{ types: [1 /* TYPE_ANY */] }] }, zip: { _func: this.functionZip, _signature: [{ types: [3 /* TYPE_ARRAY */], variadic: true }] } }; } /** * Enhanced registerFunction with backward compatibility and new options * @deprecated Use register() method for enhanced functionality */ registerFunction(name, customFunction, signature, options) { const result = this._registerInternal(name, customFunction, signature, options); if (!result.success) { throw new Error(result.message); } } /** * Internal registration method that bypasses TypeScript type checking */ _registerInternal(name, customFunction, signature, options = {}) { if (!name || typeof name !== "string" || name.trim() === "") { return { success: false, reason: "invalid-name", message: "Function name must be a non-empty string" }; } try { this.validateInputSignatures(name, signature); } catch (error) { return { success: false, reason: "invalid-signature", message: error instanceof Error ? error.message : "Invalid function signature" }; } const { override = false, warn = false } = options; const exists = name in this._functionTable; if (exists && !override) { return { success: false, reason: "already-exists", message: `Function already defined: ${name}(). Use { override: true } to replace it.` }; } if (exists && override && warn) { console.warn(`Warning: Overriding existing function: ${name}()`); } this._functionTable[name] = { _func: customFunction.bind(this), _signature: signature }; this._customFunctions.add(name); const message = exists ? `Function ${name}() overridden successfully` : `Function ${name}() registered successfully`; return { success: true, message }; } /** * Register a new function with enhanced options and type safety */ register(name, customFunction, signature, options = {}) { return this._registerInternal(name, customFunction, signature, options); } /** * Unregister a custom function (built-in functions cannot be unregistered) */ unregister(name) { if (!this._customFunctions.has(name)) { return false; } delete this._functionTable[name]; this._customFunctions.delete(name); return true; } /** * Check if a function is registered */ isRegistered(name) { return name in this._functionTable; } /** * Get list of all registered function names */ getRegistered() { return Object.keys(this._functionTable); } /** * Get list of custom (non-built-in) function names */ getCustomFunctions() { return Array.from(this._customFunctions); } /** * Clear all custom functions (built-in functions remain) */ clearCustomFunctions() { for (const name of this._customFunctions) { delete this._functionTable[name]; } this._customFunctions.clear(); } callFunction(name, resolvedArgs) { const functionEntry = this._functionTable[name]; if (functionEntry === void 0) { throw new Error(`Unknown function: ${name}()`); } this.validateArgs(name, resolvedArgs, functionEntry._signature); return functionEntry._func.call(this, resolvedArgs); } validateInputSignatures(name, signature) { for (let i = 0; i < signature.length; i += 1) { if ("variadic" in signature[i] && i !== signature.length - 1) { throw new Error(`Invalid arity: ${name}() 'variadic' argument ${i + 1} must occur last`); } } } validateArgs(name, args2, signature) { this.validateInputSignatures(name, signature); this.validateArity(name, args2, signature); this.validateTypes(name, args2, signature); } validateArity(name, args2, signature) { const numberOfRequiredArgs = signature.filter((argSignature) => !(argSignature.optional ?? false)).length; const lastArgIsVariadic = signature[signature.length - 1]?.variadic ?? false; const tooFewArgs = args2.length < numberOfRequiredArgs; const tooManyArgs = args2.length > signature.length; if (lastArgIsVariadic && tooFewArgs || !lastArgIsVariadic && (tooFewArgs || tooManyArgs)) { const tooFewModifier = tooFewArgs && (!lastArgIsVariadic && numberOfRequiredArgs > 1 || lastArgIsVariadic) ? "at least " : ""; const pluralized = signature.length > 1; throw new Error( `Invalid arity: ${name}() takes ${tooFewModifier}${numberOfRequiredArgs} argument${pluralized && "s" || ""} but received ${args2.length}` ); } } validateTypes(name, args2, signature) { for (let i = 0; i < signature.length; i += 1) { const currentSpec = signature[i].types; const actualType = this.getTypeName(args2[i]); if (actualType === void 0) { continue; } const typeMatched = currentSpec.some((expectedType) => this.typeMatches(actualType, expectedType, args2[i])); if (!typeMatched) { const expected = currentSpec.map((typeId) => this.TYPE_NAME_TABLE[typeId]).join(" | "); throw new Error( `Invalid type: ${name}() expected argument ${i + 1} to be type (${expected}) but received type ${this.TYPE_NAME_TABLE[actualType]} instead.` ); } } } typeMatches(actual, expected, argValue) { if (expected === 1 /* TYPE_ANY */) { return true; } if (expected === 9 /* TYPE_ARRAY_STRING */ || expected === 8 /* TYPE_ARRAY_NUMBER */ || expected === 10 /* TYPE_ARRAY_OBJECT */ || expected === 11 /* TYPE_ARRAY_ARRAY */ || expected === 3 /* TYPE_ARRAY */) { if (expected === 3 /* TYPE_ARRAY */) { return actual === 3 /* TYPE_ARRAY */; } if (actual === 3 /* TYPE_ARRAY */) { let subtype; if (expected === 8 /* TYPE_ARRAY_NUMBER */) { subtype = 0 /* TYPE_NUMBER */; } else if (expected === 10 /* TYPE_ARRAY_OBJECT */) { subtype = 4 /* TYPE_OBJECT */; } else if (expected === 9 /* TYPE_ARRAY_STRING */) { subtype = 2 /* TYPE_STRING */; } else if (expected === 11 /* TYPE_ARRAY_ARRAY */) { subtype = 3 /* TYPE_ARRAY */; } const array = argValue; for (let i = 0; i < array.length; i += 1) { const typeName = this.getTypeName(array[i]); if (typeName !== void 0 && subtype !== void 0 && !this.typeMatches(typeName, subtype, array[i])) { return false; } } return true; } } else { return actual === expected; } return false; } getTypeName(obj) { if (obj === null) { return 7 /* TYPE_NULL */; } if (typeof obj === "string") { return 2 /* TYPE_STRING */; } if (typeof obj === "number") { return 0 /* TYPE_NUMBER */; } if (typeof obj === "boolean") { return 5 /* TYPE_BOOLEAN */; } if (Array.isArray(obj)) { return 3 /* TYPE_ARRAY */; } if (typeof obj === "object") { if (obj.expref) { return 6 /* TYPE_EXPREF */; } return 4 /* TYPE_OBJECT */; } return; } createKeyFunction(exprefNode, allowedTypes) { const interpreter = this._interpreter; const keyFunc = (x) => { const current = interpreter.visit(exprefNode, x); if (!allowedTypes.includes(this.getTypeName(current))) { const msg = `Invalid type: expected one of (${allowedTypes.map((t) => this.TYPE_NAME_TABLE[t]).join(" | ")}), received ${this.TYPE_NAME_TABLE[this.getTypeName(current)]}`; throw new Error(msg); } return current; }; return keyFunc; } functionAvg = ([inputArray]) => { if (!inputArray || inputArray.length == 0) { return null; } let sum = 0; for (let i = 0; i < inputArray.length; i += 1) { sum += inputArray[i]; } return sum / inputArray.length; }; functionContains = ([ searchable, searchValue ]) => { if (Array.isArray(searchable)) { const array = searchable; return array.includes(searchValue); } if (typeof searchable === "string") { const text = searchable; if (typeof searchValue === "string") { return text.includes(searchValue); } } return null; }; functionEndsWith = (resolvedArgs) => { const [searchStr, suffix] = resolvedArgs; return searchStr.includes(suffix, searchStr.length - suffix.length); }; functionFindFirst = this.createFindFunction(findFirst); functionFindLast = this.createFindFunction(findLast); createFindFunction(findFn) { return (resolvedArgs) => { const subject = resolvedArgs[0]; const search2 = resolvedArgs[1]; const start = resolvedArgs.length > 2 ? resolvedArgs[2] : void 0; const end = resolvedArgs.length > 3 ? resolvedArgs[3] : void 0; return findFn(subject, search2, start, end); }; } functionFromItems = ([array]) => { array.map((pair) => { if (pair.length != 2 || typeof pair[0] !== "string") { throw new Error("invalid value, each array must contain two elements, a pair of string and value"); } }); return Object.fromEntries(array); }; functionGroupBy = ([array, exprefNode]) => { const keyFunction = this.createKeyFunction(exprefNode, [2 /* TYPE_STRING */]); return array.reduce((acc, cur) => { const k = keyFunction(cur ?? {}); const target = acc[k] = acc[k] || []; target.push(cur); return acc; }, {}); }; functionItems = ([inputValue]) => { return Object.entries(inputValue); }; functionJoin = (resolvedArgs) => { const [joinChar, listJoin] = resolvedArgs; return listJoin.join(joinChar); }; functionLength = ([inputValue]) => { if (typeof inputValue === "string") { return new Text(inputValue).length; } if (Array.isArray(inputValue)) { return inputValue.length; } return Object.keys(inputValue).length; }; functionMap = ([exprefNode, elements]) => { if (!this._interpreter) { return []; } const mapped = []; const interpreter = this._interpreter; for (let i = 0; i < elements.length; i += 1) { mapped