@jmespath-community/jmespath
Version:
Typescript implementation of the JMESPath Community specification
1,477 lines (1,470 loc) • 71.9 kB
JavaScript
#!/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