@loancrate/json-selector 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/access-guards.d.ts +16 -0
- package/dist/access-guards.d.ts.map +1 -0
- package/dist/{access-internal.d.ts → access-util.d.ts} +1 -1
- package/dist/access-util.d.ts.map +1 -0
- package/dist/access.d.ts +3 -13
- package/dist/access.d.ts.map +1 -1
- package/dist/bind.d.ts +19 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/get.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/json-selector.esm.js +2259 -1998
- package/dist/json-selector.esm.js.map +1 -1
- package/dist/json-selector.umd.js +4583 -4321
- package/dist/json-selector.umd.js.map +1 -1
- package/dist/parser.d.ts +3 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/set.d.ts.map +1 -1
- package/dist/util.d.ts +1 -0
- package/dist/util.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/access-internal.d.ts.map +0 -1
|
@@ -80,6 +80,17 @@ class JsonSelectorRuntimeError extends JsonSelectorError {
|
|
|
80
80
|
*/
|
|
81
81
|
class JsonSelectorTypeError extends JsonSelectorRuntimeError {
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Error thrown when an accessor operation cannot be applied to the given context.
|
|
85
|
+
*/
|
|
86
|
+
class AccessorError extends JsonSelectorRuntimeError {
|
|
87
|
+
constructor(code, path, operation, message) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.code = code;
|
|
90
|
+
this.path = path;
|
|
91
|
+
this.operation = operation;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
83
94
|
/**
|
|
84
95
|
* Error thrown when reading an unbound lexical-scope variable.
|
|
85
96
|
*/
|
|
@@ -220,6 +231,15 @@ function formatRawString(s) {
|
|
|
220
231
|
// eslint-disable-next-line no-control-regex
|
|
221
232
|
return `'${s.replace(/[\0-\x1F]/g, "").replace(/['\\]/g, (c) => `\\${c}`)}'`;
|
|
222
233
|
}
|
|
234
|
+
function describeValueType(value) {
|
|
235
|
+
if (value === null) {
|
|
236
|
+
return "null";
|
|
237
|
+
}
|
|
238
|
+
if (isArray(value)) {
|
|
239
|
+
return "array";
|
|
240
|
+
}
|
|
241
|
+
return typeof value;
|
|
242
|
+
}
|
|
223
243
|
|
|
224
244
|
/** Matches any value regardless of type. */
|
|
225
245
|
const ANY_TYPE = Object.freeze({
|
|
@@ -1688,15 +1708,6 @@ function ensureNumber(value, operand, operator) {
|
|
|
1688
1708
|
}
|
|
1689
1709
|
return value;
|
|
1690
1710
|
}
|
|
1691
|
-
function describeValueType(value) {
|
|
1692
|
-
if (value === null) {
|
|
1693
|
-
return "null";
|
|
1694
|
-
}
|
|
1695
|
-
if (isArray(value)) {
|
|
1696
|
-
return "array";
|
|
1697
|
-
}
|
|
1698
|
-
return typeof value;
|
|
1699
|
-
}
|
|
1700
1711
|
function performArithmetic(lv, rv, operator) {
|
|
1701
1712
|
const l = ensureNumber(lv, "left operand", operator);
|
|
1702
1713
|
const r = ensureNumber(rv, "right operand", operator);
|
|
@@ -1863,49 +1874,6 @@ function compare(lv, rv, operator) {
|
|
|
1863
1874
|
return null;
|
|
1864
1875
|
}
|
|
1865
1876
|
|
|
1866
|
-
function replaceArray(target, source) {
|
|
1867
|
-
target.length = 0;
|
|
1868
|
-
target.push(...source);
|
|
1869
|
-
return target;
|
|
1870
|
-
}
|
|
1871
|
-
function invertedFilter(value, condition, evalCtx) {
|
|
1872
|
-
return value.filter((e) => isFalseOrEmpty(evaluateJsonSelector(condition, e, evalCtx)));
|
|
1873
|
-
}
|
|
1874
|
-
/** Returns the complement of a slice: the elements that would NOT be selected by the given slice parameters. */
|
|
1875
|
-
function invertedSlice(value, start, end, step) {
|
|
1876
|
-
({ start, end, step } = normalizeSlice(value.length, start, end, step));
|
|
1877
|
-
const collected = [];
|
|
1878
|
-
if (step > 0) {
|
|
1879
|
-
if (start >= end) {
|
|
1880
|
-
return value;
|
|
1881
|
-
}
|
|
1882
|
-
let skip = start;
|
|
1883
|
-
for (let i = 0; i < value.length; ++i) {
|
|
1884
|
-
if (i < skip || i >= end) {
|
|
1885
|
-
collected.push(value[i]);
|
|
1886
|
-
}
|
|
1887
|
-
else {
|
|
1888
|
-
skip += step;
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
else {
|
|
1893
|
-
if (start <= end) {
|
|
1894
|
-
return value;
|
|
1895
|
-
}
|
|
1896
|
-
let skip = start;
|
|
1897
|
-
for (let i = value.length - 1; i >= 0; --i) {
|
|
1898
|
-
if (i > skip || i <= end) {
|
|
1899
|
-
collected.push(value[i]);
|
|
1900
|
-
}
|
|
1901
|
-
else {
|
|
1902
|
-
skip += step;
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
return collected;
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
1877
|
const PRECEDENCE_ACCESS = 1;
|
|
1910
1878
|
const PRECEDENCE_NOT = 2;
|
|
1911
1879
|
const PRECEDENCE_MULTIPLY = 3;
|
|
@@ -2129,2108 +2097,2401 @@ function format(selector) {
|
|
|
2129
2097
|
}, undefined);
|
|
2130
2098
|
}
|
|
2131
2099
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2100
|
+
const READ_ONLY_CONSTRUCTS = {
|
|
2101
|
+
not: "not expression",
|
|
2102
|
+
compare: "comparison",
|
|
2103
|
+
arithmetic: "arithmetic expression",
|
|
2104
|
+
unaryArithmetic: "unary arithmetic expression",
|
|
2105
|
+
and: "and expression",
|
|
2106
|
+
or: "or expression",
|
|
2107
|
+
ternary: "ternary expression",
|
|
2108
|
+
functionCall: "function call",
|
|
2109
|
+
expressionRef: "expression reference",
|
|
2110
|
+
variableRef: "variable reference",
|
|
2111
|
+
let: "let expression",
|
|
2112
|
+
multiSelectList: "multi-select list",
|
|
2113
|
+
multiSelectHash: "multi-select hash",
|
|
2114
|
+
literal: "literal",
|
|
2115
|
+
current: "current node reference",
|
|
2116
|
+
root: "root reference",
|
|
2117
|
+
};
|
|
2118
|
+
const MISSING_PARENT_DETAIL = "parent does not exist";
|
|
2119
|
+
function accessorError(code, selector, operation, detail) {
|
|
2120
|
+
const path = formatJsonSelector(selector);
|
|
2121
|
+
return new AccessorError(code, path, operation, `Cannot ${operation} '${path}': ${detail}`);
|
|
2136
2122
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
}
|
|
2144
|
-
set() {
|
|
2145
|
-
// ignored
|
|
2123
|
+
function readOnlyError(selector, operation, construct = READ_ONLY_CONSTRUCTS[selector.type]) {
|
|
2124
|
+
return accessorError("NOT_WRITABLE", selector, operation, `${construct} is read-only`);
|
|
2125
|
+
}
|
|
2126
|
+
function requireObjectContext(value, selector, operation) {
|
|
2127
|
+
if (!isObject(value)) {
|
|
2128
|
+
throw accessorError("TYPE_MISMATCH", selector, operation, `expected object, got ${describeValueType(value)}`);
|
|
2146
2129
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2130
|
+
return value;
|
|
2131
|
+
}
|
|
2132
|
+
function requireObjectParent(value, selector, operation) {
|
|
2133
|
+
if (value == null) {
|
|
2134
|
+
throw accessorError("MISSING_PARENT", selector, operation, MISSING_PARENT_DETAIL);
|
|
2149
2135
|
}
|
|
2136
|
+
return requireObjectContext(value, selector, operation);
|
|
2150
2137
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
this.value = value;
|
|
2138
|
+
function requireArrayParent(value, selector, operation) {
|
|
2139
|
+
if (value == null) {
|
|
2140
|
+
throw accessorError("MISSING_PARENT", selector, operation, MISSING_PARENT_DETAIL);
|
|
2155
2141
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2142
|
+
if (!isArray(value)) {
|
|
2143
|
+
throw accessorError("TYPE_MISMATCH", selector, operation, `expected array, got ${describeValueType(value)}`);
|
|
2158
2144
|
}
|
|
2145
|
+
return value;
|
|
2159
2146
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2147
|
+
function requireArrayOrStringParent(value, selector, operation) {
|
|
2148
|
+
if (value == null) {
|
|
2149
|
+
throw accessorError("MISSING_PARENT", selector, operation, MISSING_PARENT_DETAIL);
|
|
2163
2150
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
2151
|
+
if (!isArray(value) && typeof value !== "string") {
|
|
2152
|
+
throw accessorError("TYPE_MISMATCH", selector, operation, `expected array or string, got ${describeValueType(value)}`);
|
|
2166
2153
|
}
|
|
2154
|
+
return value;
|
|
2167
2155
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2156
|
+
function resolveInBoundsIndex(arr, index, selector, operation) {
|
|
2157
|
+
const resolvedIndex = index < 0 ? arr.length + index : index;
|
|
2158
|
+
if (resolvedIndex < 0 || resolvedIndex >= arr.length) {
|
|
2159
|
+
throw accessorError("INDEX_OUT_OF_BOUNDS", selector, operation, `index ${resolvedIndex} is out of bounds`);
|
|
2171
2160
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2161
|
+
return resolvedIndex;
|
|
2162
|
+
}
|
|
2163
|
+
function requireIdIndex(arr, id, selector, operation) {
|
|
2164
|
+
const index = findIdIndex(arr, id);
|
|
2165
|
+
if (index < 0) {
|
|
2166
|
+
throw accessorError("MISSING_ID", selector, operation, `id '${String(id)}' was not found`);
|
|
2174
2167
|
}
|
|
2168
|
+
return index;
|
|
2175
2169
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2170
|
+
|
|
2171
|
+
function replaceArray(target, source) {
|
|
2172
|
+
target.length = 0;
|
|
2173
|
+
target.push(...source);
|
|
2174
|
+
return target;
|
|
2175
|
+
}
|
|
2176
|
+
function invertedFilter(value, condition, evalCtx) {
|
|
2177
|
+
return value.filter((e) => isFalseOrEmpty(evaluateJsonSelector(condition, e, evalCtx)));
|
|
2178
|
+
}
|
|
2179
|
+
/** Returns the complement of a slice: the elements that would NOT be selected by the given slice parameters. */
|
|
2180
|
+
function invertedSlice(value, start, end, step) {
|
|
2181
|
+
({ start, end, step } = normalizeSlice(value.length, start, end, step));
|
|
2182
|
+
const collected = [];
|
|
2183
|
+
if (step > 0) {
|
|
2184
|
+
if (start >= end) {
|
|
2185
|
+
return value;
|
|
2186
|
+
}
|
|
2187
|
+
let skip = start;
|
|
2188
|
+
for (let i = 0; i < value.length; ++i) {
|
|
2189
|
+
if (i < skip || i >= end) {
|
|
2190
|
+
collected.push(value[i]);
|
|
2191
|
+
}
|
|
2192
|
+
else {
|
|
2193
|
+
skip += step;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2180
2196
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2197
|
+
else {
|
|
2198
|
+
if (start <= end) {
|
|
2199
|
+
return value;
|
|
2200
|
+
}
|
|
2201
|
+
let skip = start;
|
|
2202
|
+
for (let i = value.length - 1; i >= 0; --i) {
|
|
2203
|
+
if (i > skip || i <= end) {
|
|
2204
|
+
collected.push(value[i]);
|
|
2205
|
+
}
|
|
2206
|
+
else {
|
|
2207
|
+
skip += step;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2183
2210
|
}
|
|
2211
|
+
return collected;
|
|
2184
2212
|
}
|
|
2185
|
-
|
|
2186
|
-
|
|
2213
|
+
|
|
2214
|
+
const TOKEN_LIMIT = 42 /* TokenType.ASSIGN */ + 1;
|
|
2215
|
+
const EOF_DESCRIPTION = "end of input";
|
|
2216
|
+
const TOKEN_DESCRIPTIONS = {
|
|
2217
|
+
[0 /* TokenType.EOF */]: EOF_DESCRIPTION,
|
|
2218
|
+
[1 /* TokenType.LPAREN */]: "'('",
|
|
2219
|
+
[2 /* TokenType.RPAREN */]: "')'",
|
|
2220
|
+
[3 /* TokenType.LBRACKET */]: "'['",
|
|
2221
|
+
[4 /* TokenType.RBRACKET */]: "']'",
|
|
2222
|
+
[5 /* TokenType.LBRACE */]: "'{'",
|
|
2223
|
+
[6 /* TokenType.RBRACE */]: "'}'",
|
|
2224
|
+
[7 /* TokenType.DOT */]: "'.'",
|
|
2225
|
+
[8 /* TokenType.COMMA */]: "','",
|
|
2226
|
+
[9 /* TokenType.COLON */]: "':'",
|
|
2227
|
+
[10 /* TokenType.PIPE */]: "'|'",
|
|
2228
|
+
[11 /* TokenType.OR */]: "'||'",
|
|
2229
|
+
[12 /* TokenType.AND */]: "'&&'",
|
|
2230
|
+
[13 /* TokenType.NOT */]: "'!'",
|
|
2231
|
+
[14 /* TokenType.EQ */]: "'=='",
|
|
2232
|
+
[15 /* TokenType.NEQ */]: "'!='",
|
|
2233
|
+
[16 /* TokenType.LT */]: "'<'",
|
|
2234
|
+
[17 /* TokenType.LTE */]: "'<='",
|
|
2235
|
+
[18 /* TokenType.GT */]: "'>'",
|
|
2236
|
+
[19 /* TokenType.GTE */]: "'>='",
|
|
2237
|
+
[20 /* TokenType.PLUS */]: "'+'",
|
|
2238
|
+
[21 /* TokenType.MINUS */]: "'-'",
|
|
2239
|
+
[22 /* TokenType.MULTIPLY */]: "'\u00D7'",
|
|
2240
|
+
[23 /* TokenType.DIVIDE */]: "'/'",
|
|
2241
|
+
[24 /* TokenType.MODULO */]: "'%'",
|
|
2242
|
+
[25 /* TokenType.INT_DIVIDE */]: "'//'",
|
|
2243
|
+
[26 /* TokenType.AT */]: "'@'",
|
|
2244
|
+
[27 /* TokenType.DOLLAR */]: "'$'",
|
|
2245
|
+
[28 /* TokenType.STAR */]: "'*'",
|
|
2246
|
+
[29 /* TokenType.QUESTION */]: "'?'",
|
|
2247
|
+
[30 /* TokenType.AMPERSAND */]: "'&'",
|
|
2248
|
+
[31 /* TokenType.FILTER_BRACKET */]: "'[?'",
|
|
2249
|
+
[32 /* TokenType.FLATTEN_BRACKET */]: "'[]'",
|
|
2250
|
+
[33 /* TokenType.IDENTIFIER */]: "identifier",
|
|
2251
|
+
[34 /* TokenType.QUOTED_STRING */]: "quoted string",
|
|
2252
|
+
[35 /* TokenType.RAW_STRING */]: "raw string",
|
|
2253
|
+
[36 /* TokenType.BACKTICK_LITERAL */]: "backtick literal",
|
|
2254
|
+
[37 /* TokenType.NUMBER */]: "number",
|
|
2255
|
+
[38 /* TokenType.NULL */]: "'null'",
|
|
2256
|
+
[39 /* TokenType.TRUE */]: "'true'",
|
|
2257
|
+
[40 /* TokenType.FALSE */]: "'false'",
|
|
2258
|
+
[41 /* TokenType.VARIABLE */]: "variable",
|
|
2259
|
+
[42 /* TokenType.ASSIGN */]: "'='",
|
|
2260
|
+
};
|
|
2261
|
+
function describeTokenType(type) {
|
|
2187
2262
|
var _a;
|
|
2188
|
-
return
|
|
2189
|
-
functionProvider: (_a = options === null || options === void 0 ? void 0 : options.functionProvider) !== null && _a !== void 0 ? _a : getBuiltinFunctionProvider(),
|
|
2190
|
-
});
|
|
2263
|
+
return (_a = TOKEN_DESCRIPTIONS[type]) !== null && _a !== void 0 ? _a : `unknown token ${type}`;
|
|
2191
2264
|
}
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2265
|
+
|
|
2266
|
+
const KEYWORDS = {
|
|
2267
|
+
null: 38 /* TokenType.NULL */,
|
|
2268
|
+
true: 39 /* TokenType.TRUE */,
|
|
2269
|
+
false: 40 /* TokenType.FALSE */,
|
|
2270
|
+
};
|
|
2271
|
+
// Escape sequence lookup for quoted strings
|
|
2272
|
+
const ESCAPE_CHARS = {
|
|
2273
|
+
'"': '"',
|
|
2274
|
+
"\\": "\\",
|
|
2275
|
+
"/": "/",
|
|
2276
|
+
b: "\b",
|
|
2277
|
+
f: "\f",
|
|
2278
|
+
n: "\n",
|
|
2279
|
+
r: "\r",
|
|
2280
|
+
t: "\t",
|
|
2281
|
+
"`": "`",
|
|
2282
|
+
};
|
|
2283
|
+
/**
|
|
2284
|
+
* Hand-written lexer for JSON Selector expressions providing single token
|
|
2285
|
+
* lookahead.
|
|
2286
|
+
*/
|
|
2287
|
+
class Lexer {
|
|
2288
|
+
constructor(input, options) {
|
|
2289
|
+
var _a;
|
|
2290
|
+
this.pos = 0;
|
|
2291
|
+
this.input = input;
|
|
2292
|
+
this.length = input.length;
|
|
2293
|
+
this.rawStringBackslashEscape = (_a = options === null || options === void 0 ? void 0 : options.rawStringBackslashEscape) !== null && _a !== void 0 ? _a : true;
|
|
2294
|
+
this.current = this.scanNext();
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Peek at current token without consuming.
|
|
2298
|
+
*/
|
|
2299
|
+
peek() {
|
|
2300
|
+
return this.current;
|
|
2301
|
+
}
|
|
2302
|
+
/**
|
|
2303
|
+
* Advance to next token and return it.
|
|
2304
|
+
*/
|
|
2305
|
+
advance() {
|
|
2306
|
+
this.current = this.scanNext();
|
|
2307
|
+
return this.current;
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Consume current token if it matches type, throw otherwise.
|
|
2311
|
+
*/
|
|
2312
|
+
consume(type) {
|
|
2313
|
+
const token = this.peek();
|
|
2314
|
+
if (token.type !== type) {
|
|
2315
|
+
const expected = describeTokenType(type);
|
|
2316
|
+
const actual = token.text || describeTokenType(token.type);
|
|
2317
|
+
throw new UnexpectedTokenError(this.input, token.offset, actual, expected);
|
|
2318
|
+
}
|
|
2319
|
+
this.advance();
|
|
2320
|
+
// Type assertion is safe due to the type check above
|
|
2321
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
2322
|
+
return token;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Try to consume token if it matches type, return null otherwise.
|
|
2326
|
+
*/
|
|
2327
|
+
tryConsume(type) {
|
|
2328
|
+
const token = this.peek();
|
|
2329
|
+
if (token.type !== type) {
|
|
2330
|
+
return null;
|
|
2331
|
+
}
|
|
2332
|
+
this.advance();
|
|
2333
|
+
// Type assertion is safe due to the type check above
|
|
2334
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
2335
|
+
return token;
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Get next token (EOF token at end of input)
|
|
2339
|
+
*/
|
|
2340
|
+
scanNext() {
|
|
2341
|
+
let ch = this.peekCharCode();
|
|
2342
|
+
while (isWhitespace(ch)) {
|
|
2343
|
+
ch = this.advanceCharCode();
|
|
2344
|
+
}
|
|
2345
|
+
const start = this.pos;
|
|
2346
|
+
let singleType = 0;
|
|
2347
|
+
switch (ch) {
|
|
2348
|
+
// End of input
|
|
2349
|
+
case -1:
|
|
2350
|
+
return { type: 0 /* TokenType.EOF */, text: "", offset: this.length };
|
|
2351
|
+
// Multi-character scanner entry points
|
|
2352
|
+
case 91: // [
|
|
2353
|
+
return this.scanBracket(start);
|
|
2354
|
+
case 124: // |
|
|
2355
|
+
return this.scanPipe(start);
|
|
2356
|
+
case 33: // !
|
|
2357
|
+
return this.scanBang(start);
|
|
2358
|
+
case 60: // <
|
|
2359
|
+
return this.scanLessThan(start);
|
|
2360
|
+
case 62: // >
|
|
2361
|
+
return this.scanGreaterThan(start);
|
|
2362
|
+
case 61: // =
|
|
2363
|
+
return this.scanEquals(start);
|
|
2364
|
+
case 38: // &
|
|
2365
|
+
return this.scanAmpersand(start);
|
|
2366
|
+
case 47: // /
|
|
2367
|
+
return this.scanSlash(start);
|
|
2368
|
+
// String/literal openers
|
|
2369
|
+
case 34: // "
|
|
2370
|
+
return this.scanQuotedString(start);
|
|
2371
|
+
case 39: // '
|
|
2372
|
+
return this.scanRawString(start);
|
|
2373
|
+
case 96: // `
|
|
2374
|
+
return this.scanBacktickLiteral(start);
|
|
2375
|
+
// Arithmetic operators (including Unicode aliases)
|
|
2376
|
+
case 43: // +
|
|
2377
|
+
singleType = 20 /* TokenType.PLUS */;
|
|
2378
|
+
break;
|
|
2379
|
+
case 45: // -
|
|
2380
|
+
singleType = 21 /* TokenType.MINUS */;
|
|
2381
|
+
break;
|
|
2382
|
+
case 0x2212: // −
|
|
2383
|
+
singleType = 21 /* TokenType.MINUS */;
|
|
2384
|
+
break;
|
|
2385
|
+
case 42: // *
|
|
2386
|
+
singleType = 28 /* TokenType.STAR */;
|
|
2387
|
+
break;
|
|
2388
|
+
case 0x00d7: // ×
|
|
2389
|
+
singleType = 22 /* TokenType.MULTIPLY */;
|
|
2390
|
+
break;
|
|
2391
|
+
case 37: // %
|
|
2392
|
+
singleType = 24 /* TokenType.MODULO */;
|
|
2393
|
+
break;
|
|
2394
|
+
case 0x00f7: // ÷
|
|
2395
|
+
singleType = 23 /* TokenType.DIVIDE */;
|
|
2396
|
+
break;
|
|
2397
|
+
// Delimiters and punctuation
|
|
2398
|
+
case 40: // (
|
|
2399
|
+
singleType = 1 /* TokenType.LPAREN */;
|
|
2400
|
+
break;
|
|
2401
|
+
case 41: // )
|
|
2402
|
+
singleType = 2 /* TokenType.RPAREN */;
|
|
2403
|
+
break;
|
|
2404
|
+
case 93: // ]
|
|
2405
|
+
singleType = 4 /* TokenType.RBRACKET */;
|
|
2406
|
+
break;
|
|
2407
|
+
case 123: // {
|
|
2408
|
+
singleType = 5 /* TokenType.LBRACE */;
|
|
2409
|
+
break;
|
|
2410
|
+
case 125: // }
|
|
2411
|
+
singleType = 6 /* TokenType.RBRACE */;
|
|
2412
|
+
break;
|
|
2413
|
+
case 46: // .
|
|
2414
|
+
singleType = 7 /* TokenType.DOT */;
|
|
2415
|
+
break;
|
|
2416
|
+
case 44: // ,
|
|
2417
|
+
singleType = 8 /* TokenType.COMMA */;
|
|
2418
|
+
break;
|
|
2419
|
+
case 58: // :
|
|
2420
|
+
singleType = 9 /* TokenType.COLON */;
|
|
2421
|
+
break;
|
|
2422
|
+
// Context and control symbols
|
|
2423
|
+
case 64: // @
|
|
2424
|
+
singleType = 26 /* TokenType.AT */;
|
|
2425
|
+
break;
|
|
2426
|
+
case 36: {
|
|
2427
|
+
// $ or variable reference ($name)
|
|
2428
|
+
const next = this.peekCharCode(this.pos + 1);
|
|
2429
|
+
if (isIdentStart(next)) {
|
|
2430
|
+
return this.scanVariable(start);
|
|
2254
2431
|
}
|
|
2432
|
+
singleType = 27 /* TokenType.DOLLAR */;
|
|
2433
|
+
break;
|
|
2434
|
+
}
|
|
2435
|
+
case 63: // ?
|
|
2436
|
+
singleType = 29 /* TokenType.QUESTION */;
|
|
2437
|
+
break;
|
|
2438
|
+
}
|
|
2439
|
+
if (singleType !== 0) {
|
|
2440
|
+
this.pos++;
|
|
2441
|
+
return {
|
|
2442
|
+
type: singleType,
|
|
2443
|
+
text: this.input[start],
|
|
2444
|
+
offset: start,
|
|
2255
2445
|
};
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
delete(context, rootContext = context) {
|
|
2280
|
-
const arr = base.get(context, rootContext);
|
|
2281
|
-
if (isArray(arr)) {
|
|
2282
|
-
arr.splice(index, 1);
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2446
|
+
}
|
|
2447
|
+
if (isDigit(ch)) {
|
|
2448
|
+
return this.scanNumber(start);
|
|
2449
|
+
}
|
|
2450
|
+
if (isIdentStart(ch)) {
|
|
2451
|
+
return this.scanIdentifier(start);
|
|
2452
|
+
}
|
|
2453
|
+
throw new UnexpectedCharacterError(this.input, start, this.input[start]);
|
|
2454
|
+
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Scan [, [?, or []
|
|
2457
|
+
*/
|
|
2458
|
+
scanBracket(start) {
|
|
2459
|
+
const ch = this.advanceCharCode();
|
|
2460
|
+
if (ch === 63) {
|
|
2461
|
+
// ?
|
|
2462
|
+
this.pos++;
|
|
2463
|
+
return {
|
|
2464
|
+
type: 31 /* TokenType.FILTER_BRACKET */,
|
|
2465
|
+
text: "[?",
|
|
2466
|
+
offset: start,
|
|
2285
2467
|
};
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2468
|
+
}
|
|
2469
|
+
if (ch === 93) {
|
|
2470
|
+
// ]
|
|
2471
|
+
this.pos++;
|
|
2472
|
+
return {
|
|
2473
|
+
type: 32 /* TokenType.FLATTEN_BRACKET */,
|
|
2474
|
+
text: "[]",
|
|
2475
|
+
offset: start,
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
return {
|
|
2479
|
+
type: 3 /* TokenType.LBRACKET */,
|
|
2480
|
+
text: "[",
|
|
2481
|
+
offset: start,
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Scan | or ||
|
|
2486
|
+
*/
|
|
2487
|
+
scanPipe(start) {
|
|
2488
|
+
const ch = this.advanceCharCode(); // consume |
|
|
2489
|
+
if (ch === 124) {
|
|
2490
|
+
// |
|
|
2491
|
+
this.pos++;
|
|
2492
|
+
return { type: 11 /* TokenType.OR */, text: "||", offset: start };
|
|
2493
|
+
}
|
|
2494
|
+
return { type: 10 /* TokenType.PIPE */, text: "|", offset: start };
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Scan ! or !=
|
|
2498
|
+
*/
|
|
2499
|
+
scanBang(start) {
|
|
2500
|
+
const ch = this.advanceCharCode(); // consume !
|
|
2501
|
+
if (ch === 61) {
|
|
2502
|
+
// =
|
|
2503
|
+
this.pos++;
|
|
2504
|
+
return { type: 15 /* TokenType.NEQ */, text: "!=", offset: start };
|
|
2505
|
+
}
|
|
2506
|
+
return { type: 13 /* TokenType.NOT */, text: "!", offset: start };
|
|
2507
|
+
}
|
|
2508
|
+
/**
|
|
2509
|
+
* Scan < or <=
|
|
2510
|
+
*/
|
|
2511
|
+
scanLessThan(start) {
|
|
2512
|
+
const ch = this.advanceCharCode(); // consume <
|
|
2513
|
+
if (ch === 61) {
|
|
2514
|
+
// =
|
|
2515
|
+
this.pos++;
|
|
2516
|
+
return { type: 17 /* TokenType.LTE */, text: "<=", offset: start };
|
|
2517
|
+
}
|
|
2518
|
+
return { type: 16 /* TokenType.LT */, text: "<", offset: start };
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* Scan > or >=
|
|
2522
|
+
*/
|
|
2523
|
+
scanGreaterThan(start) {
|
|
2524
|
+
const ch = this.advanceCharCode(); // consume >
|
|
2525
|
+
if (ch === 61) {
|
|
2526
|
+
// =
|
|
2527
|
+
this.pos++;
|
|
2528
|
+
return { type: 19 /* TokenType.GTE */, text: ">=", offset: start };
|
|
2529
|
+
}
|
|
2530
|
+
return { type: 18 /* TokenType.GT */, text: ">", offset: start };
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* Scan = or ==
|
|
2534
|
+
*/
|
|
2535
|
+
scanEquals(start) {
|
|
2536
|
+
const ch = this.advanceCharCode(); // consume first =
|
|
2537
|
+
if (ch === 61) {
|
|
2538
|
+
// =
|
|
2539
|
+
this.pos++;
|
|
2540
|
+
return { type: 14 /* TokenType.EQ */, text: "==", offset: start };
|
|
2541
|
+
}
|
|
2542
|
+
return { type: 42 /* TokenType.ASSIGN */, text: "=", offset: start };
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Scan & or &&
|
|
2546
|
+
*/
|
|
2547
|
+
scanAmpersand(start) {
|
|
2548
|
+
const ch = this.advanceCharCode(); // consume &
|
|
2549
|
+
if (ch === 38) {
|
|
2550
|
+
// &&
|
|
2551
|
+
this.pos++;
|
|
2552
|
+
return { type: 12 /* TokenType.AND */, text: "&&", offset: start };
|
|
2553
|
+
}
|
|
2554
|
+
// Single & for expression references
|
|
2555
|
+
return { type: 30 /* TokenType.AMPERSAND */, text: "&", offset: start };
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* Scan / or //
|
|
2559
|
+
*/
|
|
2560
|
+
scanSlash(start) {
|
|
2561
|
+
const ch = this.advanceCharCode(); // consume /
|
|
2562
|
+
if (ch === 47) {
|
|
2563
|
+
// /
|
|
2564
|
+
this.pos++;
|
|
2565
|
+
return { type: 25 /* TokenType.INT_DIVIDE */, text: "//", offset: start };
|
|
2566
|
+
}
|
|
2567
|
+
return { type: 23 /* TokenType.DIVIDE */, text: "/", offset: start };
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Scan raw string: '...'
|
|
2571
|
+
* When rawStringBackslashEscape is true (default), both \' and \\ are unescaped.
|
|
2572
|
+
* When false, only \' is unescaped.
|
|
2573
|
+
* Optimized to use slicing for long strings
|
|
2574
|
+
*/
|
|
2575
|
+
scanRawString(start) {
|
|
2576
|
+
this.pos++; // consume opening '
|
|
2577
|
+
const startPos = this.pos;
|
|
2578
|
+
// First pass: find end and check for escapes
|
|
2579
|
+
let hasEscape = false;
|
|
2580
|
+
let endPos = this.pos;
|
|
2581
|
+
while (endPos < this.length) {
|
|
2582
|
+
const ch = this.input.charCodeAt(endPos);
|
|
2583
|
+
if (ch === 39) {
|
|
2584
|
+
// Found closing '
|
|
2585
|
+
break;
|
|
2586
|
+
}
|
|
2587
|
+
if (ch === 92) {
|
|
2588
|
+
// Backslash - potential escape
|
|
2589
|
+
hasEscape = true;
|
|
2590
|
+
endPos += 2; // Skip backslash and next char
|
|
2591
|
+
}
|
|
2592
|
+
else {
|
|
2593
|
+
endPos++;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
if (endPos >= this.length) {
|
|
2597
|
+
throw new UnterminatedTokenError(this.input, start, "raw string", "'");
|
|
2598
|
+
}
|
|
2599
|
+
const text = this.input.slice(start, endPos + 1);
|
|
2600
|
+
let value = this.input.slice(startPos, endPos);
|
|
2601
|
+
if (hasEscape) {
|
|
2602
|
+
value = this.rawStringBackslashEscape
|
|
2603
|
+
? value.replace(/\\([\\'])/g, "$1")
|
|
2604
|
+
: value.replace(/\\'/g, "'");
|
|
2605
|
+
}
|
|
2606
|
+
this.pos = endPos + 1;
|
|
2607
|
+
return { type: 35 /* TokenType.RAW_STRING */, text, value, offset: start };
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Scan backtick literal: `...`
|
|
2611
|
+
* Extracts content between backticks, handles \` escape (JMESPath extension)
|
|
2612
|
+
* Returns raw content for JSON.parse() in parser
|
|
2613
|
+
*/
|
|
2614
|
+
scanBacktickLiteral(start) {
|
|
2615
|
+
this.pos++; // consume opening `
|
|
2616
|
+
const startPos = this.pos;
|
|
2617
|
+
// Find end and handle \` escapes
|
|
2618
|
+
let endPos = this.pos;
|
|
2619
|
+
let hasEscape = false;
|
|
2620
|
+
while (endPos < this.length) {
|
|
2621
|
+
const ch = this.input.charCodeAt(endPos);
|
|
2622
|
+
if (ch === 96) {
|
|
2623
|
+
// A backtick is escaped only when preceded by an odd number of backslashes.
|
|
2624
|
+
let slashCount = 0;
|
|
2625
|
+
for (let i = endPos - 1; i >= startPos; i--) {
|
|
2626
|
+
if (this.input.charCodeAt(i) !== 92) {
|
|
2627
|
+
break;
|
|
2310
2628
|
}
|
|
2629
|
+
slashCount++;
|
|
2311
2630
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
if (index >= 0) {
|
|
2317
|
-
arr.splice(index, 1);
|
|
2318
|
-
}
|
|
2319
|
-
}
|
|
2631
|
+
if (slashCount % 2 === 1) {
|
|
2632
|
+
hasEscape = true;
|
|
2633
|
+
endPos++;
|
|
2634
|
+
continue;
|
|
2320
2635
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2636
|
+
// Found closing `.
|
|
2637
|
+
break;
|
|
2638
|
+
}
|
|
2639
|
+
endPos++;
|
|
2640
|
+
}
|
|
2641
|
+
if (endPos >= this.length) {
|
|
2642
|
+
throw new UnterminatedTokenError(this.input, start, "JSON literal", "`");
|
|
2643
|
+
}
|
|
2644
|
+
const text = this.input.slice(start, endPos + 1);
|
|
2645
|
+
let value = this.input.slice(startPos, endPos);
|
|
2646
|
+
if (hasEscape) {
|
|
2647
|
+
value = value.replace(/\\`/g, "`");
|
|
2648
|
+
}
|
|
2649
|
+
this.pos = endPos + 1;
|
|
2650
|
+
return { type: 36 /* TokenType.BACKTICK_LITERAL */, text, value, offset: start };
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Scan quoted string: "..."
|
|
2654
|
+
* Handles standard JSON escapes plus backtick (\`)
|
|
2655
|
+
*/
|
|
2656
|
+
scanQuotedString(start) {
|
|
2657
|
+
this.pos++; // consume opening "
|
|
2658
|
+
const end = this.scanQuotedStringEnd();
|
|
2659
|
+
const text = this.input.slice(start, end);
|
|
2660
|
+
this.pos = end;
|
|
2661
|
+
// Parse escape sequences (including backtick which JSON.parse doesn't handle)
|
|
2662
|
+
const inner = text.slice(1, -1); // remove quotes
|
|
2663
|
+
let value = "";
|
|
2664
|
+
let i = 0;
|
|
2665
|
+
while (i < inner.length) {
|
|
2666
|
+
if (inner[i] === "\\") {
|
|
2667
|
+
// It's impossible to have a backslash at end due to scanQuotedStringEnd
|
|
2668
|
+
const next = inner[i + 1];
|
|
2669
|
+
if (next === "u") {
|
|
2670
|
+
// Unicode escape: \uXXXX (must have exactly 4 hex digits)
|
|
2671
|
+
const hex = inner.slice(i + 2, i + 6);
|
|
2672
|
+
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
2673
|
+
value += String.fromCharCode(parseInt(hex, 16));
|
|
2674
|
+
i += 6;
|
|
2675
|
+
}
|
|
2676
|
+
else {
|
|
2677
|
+
throw new InvalidTokenError(this.input, start + 1 + i, "unicode escape", "expected 4 hexadecimal digits");
|
|
2678
|
+
}
|
|
2333
2679
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2680
|
+
else if (next in ESCAPE_CHARS) {
|
|
2681
|
+
value += ESCAPE_CHARS[next];
|
|
2682
|
+
i += 2;
|
|
2337
2683
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2684
|
+
else {
|
|
2685
|
+
// Unknown escape, keep both chars
|
|
2686
|
+
value += "\\" + next;
|
|
2687
|
+
i += 2;
|
|
2341
2688
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
return isArray(value);
|
|
2492
|
-
}
|
|
2493
|
-
get(context, rootContext = context) {
|
|
2494
|
-
const arr = base.get(context, rootContext);
|
|
2495
|
-
return flatten(arr);
|
|
2496
|
-
}
|
|
2497
|
-
set(value, context, rootContext = context) {
|
|
2498
|
-
const arr = base.get(context, rootContext);
|
|
2499
|
-
if (isArray(arr)) {
|
|
2500
|
-
replaceArray(arr, asArray(value));
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
delete(context, rootContext = context) {
|
|
2504
|
-
const arr = base.get(context, rootContext);
|
|
2505
|
-
if (isArray(arr)) {
|
|
2506
|
-
arr.length = 0;
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
};
|
|
2510
|
-
return new Accessor();
|
|
2511
|
-
},
|
|
2512
|
-
not(selector) {
|
|
2513
|
-
const { expression } = selector;
|
|
2514
|
-
const base = makeAccessorInternal(expression, options);
|
|
2515
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2516
|
-
constructor() {
|
|
2517
|
-
super(selector);
|
|
2518
|
-
}
|
|
2519
|
-
get(context, rootContext = context) {
|
|
2520
|
-
const value = base.get(context, rootContext);
|
|
2521
|
-
return isFalseOrEmpty(value);
|
|
2522
|
-
}
|
|
2523
|
-
};
|
|
2524
|
-
return new Accessor();
|
|
2525
|
-
},
|
|
2526
|
-
compare(selector) {
|
|
2527
|
-
const { lhs, rhs, operator } = selector;
|
|
2528
|
-
const la = makeAccessorInternal(lhs, options);
|
|
2529
|
-
const ra = makeAccessorInternal(rhs, options);
|
|
2530
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2531
|
-
constructor() {
|
|
2532
|
-
super(selector);
|
|
2533
|
-
}
|
|
2534
|
-
get(context, rootContext = context) {
|
|
2535
|
-
const lv = la.get(context, rootContext);
|
|
2536
|
-
const rv = ra.get(context, rootContext);
|
|
2537
|
-
return compare(lv, rv, operator);
|
|
2538
|
-
}
|
|
2539
|
-
};
|
|
2540
|
-
return new Accessor();
|
|
2541
|
-
},
|
|
2542
|
-
arithmetic(selector) {
|
|
2543
|
-
const la = makeAccessorInternal(selector.lhs, options);
|
|
2544
|
-
const ra = makeAccessorInternal(selector.rhs, options);
|
|
2545
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2546
|
-
constructor() {
|
|
2547
|
-
super(selector);
|
|
2548
|
-
}
|
|
2549
|
-
get(context, rootContext = context) {
|
|
2550
|
-
return performArithmetic(la.get(context, rootContext), ra.get(context, rootContext), selector.operator);
|
|
2551
|
-
}
|
|
2552
|
-
};
|
|
2553
|
-
return new Accessor();
|
|
2554
|
-
},
|
|
2555
|
-
unaryArithmetic(selector) {
|
|
2556
|
-
const base = makeAccessorInternal(selector.expression, options);
|
|
2557
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2558
|
-
constructor() {
|
|
2559
|
-
super(selector);
|
|
2560
|
-
}
|
|
2561
|
-
get(context, rootContext = context) {
|
|
2562
|
-
return performUnaryArithmetic(base.get(context, rootContext), selector.operator);
|
|
2563
|
-
}
|
|
2564
|
-
};
|
|
2565
|
-
return new Accessor();
|
|
2566
|
-
},
|
|
2567
|
-
and(selector) {
|
|
2568
|
-
const { lhs, rhs } = selector;
|
|
2569
|
-
const la = makeAccessorInternal(lhs, options);
|
|
2570
|
-
const ra = makeAccessorInternal(rhs, options);
|
|
2571
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2572
|
-
constructor() {
|
|
2573
|
-
super(selector);
|
|
2574
|
-
}
|
|
2575
|
-
get(context, rootContext = context) {
|
|
2576
|
-
const lv = la.get(context, rootContext);
|
|
2577
|
-
return isFalseOrEmpty(lv) ? lv : ra.get(context, rootContext);
|
|
2578
|
-
}
|
|
2579
|
-
};
|
|
2580
|
-
return new Accessor();
|
|
2581
|
-
},
|
|
2582
|
-
or(selector) {
|
|
2583
|
-
const { lhs, rhs } = selector;
|
|
2584
|
-
const la = makeAccessorInternal(lhs, options);
|
|
2585
|
-
const ra = makeAccessorInternal(rhs, options);
|
|
2586
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2587
|
-
constructor() {
|
|
2588
|
-
super(selector);
|
|
2589
|
-
}
|
|
2590
|
-
get(context, rootContext = context) {
|
|
2591
|
-
const lv = la.get(context, rootContext);
|
|
2592
|
-
return !isFalseOrEmpty(lv) ? lv : ra.get(context, rootContext);
|
|
2593
|
-
}
|
|
2594
|
-
};
|
|
2595
|
-
return new Accessor();
|
|
2596
|
-
},
|
|
2597
|
-
ternary(selector) {
|
|
2598
|
-
const { condition, consequent, alternate } = selector;
|
|
2599
|
-
const ca = makeAccessorInternal(condition, options);
|
|
2600
|
-
const ta = makeAccessorInternal(consequent, options);
|
|
2601
|
-
const aa = makeAccessorInternal(alternate, options);
|
|
2602
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2603
|
-
constructor() {
|
|
2604
|
-
super(selector);
|
|
2605
|
-
}
|
|
2606
|
-
get(context, rootContext = context) {
|
|
2607
|
-
const cv = ca.get(context, rootContext);
|
|
2608
|
-
return isFalseOrEmpty(cv)
|
|
2609
|
-
? aa.get(context, rootContext)
|
|
2610
|
-
: ta.get(context, rootContext);
|
|
2611
|
-
}
|
|
2612
|
-
};
|
|
2613
|
-
return new Accessor();
|
|
2614
|
-
},
|
|
2615
|
-
pipe(selector) {
|
|
2616
|
-
const { lhs, rhs } = selector;
|
|
2617
|
-
const la = makeAccessorInternal(lhs, options);
|
|
2618
|
-
const ra = makeAccessorInternal(rhs, options);
|
|
2619
|
-
const Accessor = class extends BaseAccessor {
|
|
2620
|
-
constructor() {
|
|
2621
|
-
super(selector);
|
|
2622
|
-
}
|
|
2623
|
-
isValidContext(context, rootContext = context) {
|
|
2624
|
-
const lv = la.get(context, rootContext);
|
|
2625
|
-
return ra.isValidContext(lv, rootContext);
|
|
2626
|
-
}
|
|
2627
|
-
get(context, rootContext = context) {
|
|
2628
|
-
const lv = la.get(context, rootContext);
|
|
2629
|
-
return ra.get(lv, rootContext);
|
|
2630
|
-
}
|
|
2631
|
-
set(value, context, rootContext = context) {
|
|
2632
|
-
const lv = la.get(context, rootContext);
|
|
2633
|
-
ra.set(value, lv, rootContext);
|
|
2634
|
-
}
|
|
2635
|
-
delete(context, rootContext = context) {
|
|
2636
|
-
const lv = la.get(context, rootContext);
|
|
2637
|
-
ra.delete(lv, rootContext);
|
|
2638
|
-
}
|
|
2639
|
-
};
|
|
2640
|
-
return new Accessor();
|
|
2641
|
-
},
|
|
2642
|
-
functionCall(selector) {
|
|
2643
|
-
return new EvaluateAccessor(selector, options);
|
|
2644
|
-
},
|
|
2645
|
-
expressionRef(selector) {
|
|
2646
|
-
// Expression references are only meaningful as function arguments
|
|
2647
|
-
return new ConstantAccessor(selector, null);
|
|
2648
|
-
},
|
|
2649
|
-
variableRef(selector) {
|
|
2650
|
-
return new EvaluateAccessor(selector, options);
|
|
2651
|
-
},
|
|
2652
|
-
let(selector) {
|
|
2653
|
-
return new EvaluateAccessor(selector, options);
|
|
2654
|
-
},
|
|
2655
|
-
multiSelectList(selector) {
|
|
2656
|
-
const { expressions } = selector;
|
|
2657
|
-
const accessors = expressions.map((e) => makeAccessorInternal(e, options));
|
|
2658
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2659
|
-
constructor() {
|
|
2660
|
-
super(selector);
|
|
2661
|
-
}
|
|
2662
|
-
get(context, rootContext = context) {
|
|
2663
|
-
if (context == null) {
|
|
2664
|
-
return null;
|
|
2665
|
-
}
|
|
2666
|
-
return accessors.map((a) => a.get(context, rootContext));
|
|
2667
|
-
}
|
|
2668
|
-
};
|
|
2669
|
-
return new Accessor();
|
|
2670
|
-
},
|
|
2671
|
-
multiSelectHash(selector) {
|
|
2672
|
-
const { entries } = selector;
|
|
2673
|
-
const entryAccessors = entries.map(({ key, value }) => ({
|
|
2674
|
-
key,
|
|
2675
|
-
accessor: makeAccessorInternal(value, options),
|
|
2676
|
-
}));
|
|
2677
|
-
const Accessor = class extends ReadOnlyAccessor {
|
|
2678
|
-
constructor() {
|
|
2679
|
-
super(selector);
|
|
2680
|
-
}
|
|
2681
|
-
get(context, rootContext = context) {
|
|
2682
|
-
if (context == null) {
|
|
2683
|
-
return null;
|
|
2684
|
-
}
|
|
2685
|
-
const result = {};
|
|
2686
|
-
for (const { key, accessor } of entryAccessors) {
|
|
2687
|
-
result[key] = accessor.get(context, rootContext);
|
|
2688
|
-
}
|
|
2689
|
-
return result;
|
|
2690
|
-
}
|
|
2691
|
-
};
|
|
2692
|
-
return new Accessor();
|
|
2693
|
-
},
|
|
2694
|
-
}, undefined);
|
|
2689
|
+
}
|
|
2690
|
+
else {
|
|
2691
|
+
value += inner[i];
|
|
2692
|
+
i++;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return { type: 34 /* TokenType.QUOTED_STRING */, text, value, offset: start };
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Find end of quoted string (handles escape sequences)
|
|
2699
|
+
* Rejects unescaped backticks (not valid in JSON strings)
|
|
2700
|
+
*/
|
|
2701
|
+
scanQuotedStringEnd() {
|
|
2702
|
+
let pos = this.pos;
|
|
2703
|
+
while (pos < this.length) {
|
|
2704
|
+
const ch = this.input.charCodeAt(pos);
|
|
2705
|
+
if (ch === 34) {
|
|
2706
|
+
// closing "
|
|
2707
|
+
return pos + 1;
|
|
2708
|
+
}
|
|
2709
|
+
if (ch === 96) {
|
|
2710
|
+
// unescaped backtick - not allowed in quoted strings
|
|
2711
|
+
throw new InvalidTokenError(this.input, pos, "backtick in string", "unescaped backtick");
|
|
2712
|
+
}
|
|
2713
|
+
if (ch === 92) {
|
|
2714
|
+
// backslash (escape)
|
|
2715
|
+
pos += 2; // skip backslash and next char
|
|
2716
|
+
}
|
|
2717
|
+
else {
|
|
2718
|
+
pos++;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
throw new UnterminatedTokenError(this.input, this.pos - 1, "string", '"');
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Scan number: [0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?
|
|
2725
|
+
*/
|
|
2726
|
+
scanNumber(start) {
|
|
2727
|
+
var _a;
|
|
2728
|
+
let ch = this.peekCharCode();
|
|
2729
|
+
// Leading zero or digits
|
|
2730
|
+
if (ch === 48) {
|
|
2731
|
+
ch = this.advanceCharCode();
|
|
2732
|
+
}
|
|
2733
|
+
else {
|
|
2734
|
+
// 1-9 followed by any digits
|
|
2735
|
+
while (isDigit(ch)) {
|
|
2736
|
+
ch = this.advanceCharCode();
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
// Optional decimal part
|
|
2740
|
+
if (ch === 46 && // .
|
|
2741
|
+
this.pos + 1 < this.length &&
|
|
2742
|
+
isDigit(this.input.charCodeAt(this.pos + 1))) {
|
|
2743
|
+
ch = this.advanceCharCode();
|
|
2744
|
+
while (isDigit(ch)) {
|
|
2745
|
+
ch = this.advanceCharCode();
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
// Optional exponent part: e or E
|
|
2749
|
+
if (ch === 101 || ch === 69) {
|
|
2750
|
+
ch = this.advanceCharCode();
|
|
2751
|
+
// Optional sign: + or -
|
|
2752
|
+
if (ch === 43 || ch === 45) {
|
|
2753
|
+
ch = this.advanceCharCode();
|
|
2754
|
+
}
|
|
2755
|
+
// Exponent digits
|
|
2756
|
+
if (!isDigit(ch)) {
|
|
2757
|
+
const digit = (_a = this.input[this.pos]) !== null && _a !== void 0 ? _a : EOF_DESCRIPTION;
|
|
2758
|
+
throw new InvalidTokenError(this.input, start, "number", `invalid exponent: ${digit}`);
|
|
2759
|
+
}
|
|
2760
|
+
while (isDigit(ch)) {
|
|
2761
|
+
ch = this.advanceCharCode();
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
const text = this.input.slice(start, this.pos);
|
|
2765
|
+
return {
|
|
2766
|
+
type: 37 /* TokenType.NUMBER */,
|
|
2767
|
+
text,
|
|
2768
|
+
value: parseFloat(text),
|
|
2769
|
+
offset: start,
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Scan variable: $[a-zA-Z_][a-zA-Z0-9_]*
|
|
2774
|
+
*/
|
|
2775
|
+
scanVariable(start) {
|
|
2776
|
+
// Start is at "$". Consume first variable-name character.
|
|
2777
|
+
let ch = this.advanceCharCode();
|
|
2778
|
+
while (isIdentChar(ch)) {
|
|
2779
|
+
ch = this.advanceCharCode();
|
|
2780
|
+
}
|
|
2781
|
+
const text = this.input.slice(start, this.pos);
|
|
2782
|
+
return {
|
|
2783
|
+
type: 41 /* TokenType.VARIABLE */,
|
|
2784
|
+
text,
|
|
2785
|
+
value: text.slice(1),
|
|
2786
|
+
offset: start,
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Scan identifier or keyword: [a-zA-Z_][a-zA-Z0-9_]*
|
|
2791
|
+
*/
|
|
2792
|
+
scanIdentifier(start) {
|
|
2793
|
+
let ch = this.advanceCharCode();
|
|
2794
|
+
while (isIdentChar(ch)) {
|
|
2795
|
+
ch = this.advanceCharCode();
|
|
2796
|
+
}
|
|
2797
|
+
const text = this.input.slice(start, this.pos);
|
|
2798
|
+
// Check for keywords
|
|
2799
|
+
const keywordType = KEYWORDS[text];
|
|
2800
|
+
if (keywordType !== undefined) {
|
|
2801
|
+
// Return keyword token with appropriate value
|
|
2802
|
+
if (keywordType === 38 /* TokenType.NULL */) {
|
|
2803
|
+
return {
|
|
2804
|
+
type: 38 /* TokenType.NULL */,
|
|
2805
|
+
text: "null",
|
|
2806
|
+
value: null,
|
|
2807
|
+
offset: start,
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
else if (keywordType === 39 /* TokenType.TRUE */) {
|
|
2811
|
+
return {
|
|
2812
|
+
type: 39 /* TokenType.TRUE */,
|
|
2813
|
+
text: "true",
|
|
2814
|
+
value: true,
|
|
2815
|
+
offset: start,
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
else {
|
|
2819
|
+
return {
|
|
2820
|
+
type: 40 /* TokenType.FALSE */,
|
|
2821
|
+
text: "false",
|
|
2822
|
+
value: false,
|
|
2823
|
+
offset: start,
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
return { type: 33 /* TokenType.IDENTIFIER */, text, value: text, offset: start };
|
|
2828
|
+
}
|
|
2829
|
+
peekCharCode(pos = this.pos) {
|
|
2830
|
+
return pos < this.length ? this.input.charCodeAt(pos) : -1;
|
|
2831
|
+
}
|
|
2832
|
+
advanceCharCode() {
|
|
2833
|
+
return this.peekCharCode(++this.pos);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
function isWhitespace(ch) {
|
|
2837
|
+
return ch === 32 || ch === 9 || ch === 10 || ch === 13; // space, tab, newline, carriage return
|
|
2695
2838
|
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
const { selector } = unbound;
|
|
2699
|
-
const valid = unbound.isValidContext(context, rootContext);
|
|
2700
|
-
return {
|
|
2701
|
-
selector,
|
|
2702
|
-
valid,
|
|
2703
|
-
path: formatJsonSelector(selector),
|
|
2704
|
-
get() {
|
|
2705
|
-
return unbound.get(context, rootContext);
|
|
2706
|
-
},
|
|
2707
|
-
set(value) {
|
|
2708
|
-
unbound.set(value, context, rootContext);
|
|
2709
|
-
},
|
|
2710
|
-
delete() {
|
|
2711
|
-
unbound.delete(context, rootContext);
|
|
2712
|
-
},
|
|
2713
|
-
};
|
|
2839
|
+
function isDigit(ch) {
|
|
2840
|
+
return ch >= 48 && ch <= 57; // 0-9
|
|
2714
2841
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2842
|
+
function isIdentStart(ch) {
|
|
2843
|
+
return ((ch >= 65 && ch <= 90) || // A-Z
|
|
2844
|
+
(ch >= 97 && ch <= 122) || // a-z
|
|
2845
|
+
ch === 95 // _
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
function isIdentChar(ch) {
|
|
2849
|
+
return ((ch >= 48 && ch <= 57) || // 0-9
|
|
2850
|
+
(ch >= 65 && ch <= 90) || // A-Z
|
|
2851
|
+
(ch >= 97 && ch <= 122) || // a-z
|
|
2852
|
+
ch === 95 // _
|
|
2853
|
+
);
|
|
2718
2854
|
}
|
|
2719
2855
|
|
|
2856
|
+
// Pre-computed singleton AST nodes
|
|
2857
|
+
const CURRENT_NODE = Object.freeze({
|
|
2858
|
+
type: "current",
|
|
2859
|
+
});
|
|
2860
|
+
const ROOT_NODE = Object.freeze({
|
|
2861
|
+
type: "root",
|
|
2862
|
+
});
|
|
2863
|
+
// Binding power constants (higher = tighter binding)
|
|
2864
|
+
const BP_PIPE = 1;
|
|
2865
|
+
const BP_TERNARY = 2;
|
|
2866
|
+
const BP_OR = 3;
|
|
2867
|
+
const BP_AND = 4;
|
|
2868
|
+
const BP_COMPARE = 5;
|
|
2869
|
+
const BP_ADD = 6;
|
|
2870
|
+
const BP_MUL = 7;
|
|
2871
|
+
const BP_FLATTEN = 9;
|
|
2872
|
+
const BP_FILTER = 21;
|
|
2873
|
+
const BP_DOT = 40;
|
|
2874
|
+
const BP_NOT = 45;
|
|
2875
|
+
const BP_BRACKET = 55;
|
|
2876
|
+
// Projection stop threshold: operators below this terminate projections
|
|
2877
|
+
// Separates terminators (pipe, ternary, or, and, comparisons) from continuators (dot, brackets)
|
|
2878
|
+
const PROJECTION_STOP_BP = 10;
|
|
2879
|
+
// Binding power table (higher = tighter binding)
|
|
2880
|
+
// Use array indexed by TokenType for fast lookup
|
|
2881
|
+
const TOKEN_BP = (() => {
|
|
2882
|
+
const bp = new Array(TOKEN_LIMIT).fill(0);
|
|
2883
|
+
// Terminators (lowest precedence) - already 0
|
|
2884
|
+
// TokenType.RPAREN, RBRACKET, RBRACE, COMMA - all 0
|
|
2885
|
+
// Binary operators (low to high)
|
|
2886
|
+
bp[10 /* TokenType.PIPE */] = BP_PIPE; // |
|
|
2887
|
+
bp[29 /* TokenType.QUESTION */] = BP_TERNARY; // ?:
|
|
2888
|
+
bp[11 /* TokenType.OR */] = BP_OR; // ||
|
|
2889
|
+
bp[12 /* TokenType.AND */] = BP_AND; // &&
|
|
2890
|
+
// Comparison operators (all same precedence - non-associative, cannot chain)
|
|
2891
|
+
bp[14 /* TokenType.EQ */] = BP_COMPARE; // ==
|
|
2892
|
+
bp[15 /* TokenType.NEQ */] = BP_COMPARE; // !=
|
|
2893
|
+
bp[16 /* TokenType.LT */] = BP_COMPARE; // <
|
|
2894
|
+
bp[17 /* TokenType.LTE */] = BP_COMPARE; // <=
|
|
2895
|
+
bp[18 /* TokenType.GT */] = BP_COMPARE; // >
|
|
2896
|
+
bp[19 /* TokenType.GTE */] = BP_COMPARE; // >=
|
|
2897
|
+
bp[20 /* TokenType.PLUS */] = BP_ADD; // +
|
|
2898
|
+
bp[21 /* TokenType.MINUS */] = BP_ADD; // -
|
|
2899
|
+
bp[28 /* TokenType.STAR */] = BP_MUL; // * (multiplication in led context)
|
|
2900
|
+
bp[22 /* TokenType.MULTIPLY */] = BP_MUL; // ×
|
|
2901
|
+
bp[23 /* TokenType.DIVIDE */] = BP_MUL; // /, ÷
|
|
2902
|
+
bp[24 /* TokenType.MODULO */] = BP_MUL; // %
|
|
2903
|
+
bp[25 /* TokenType.INT_DIVIDE */] = BP_MUL; // //
|
|
2904
|
+
// Projection operators
|
|
2905
|
+
// flatten has lower bp (9) than star/filter to allow chaining: foo[*][] or foo[?x][].bar
|
|
2906
|
+
bp[32 /* TokenType.FLATTEN_BRACKET */] = BP_FLATTEN; // []
|
|
2907
|
+
bp[31 /* TokenType.FILTER_BRACKET */] = BP_FILTER; // [?...]
|
|
2908
|
+
// Postfix operators (high precedence)
|
|
2909
|
+
bp[7 /* TokenType.DOT */] = BP_DOT; // .
|
|
2910
|
+
// Prefix operators
|
|
2911
|
+
bp[13 /* TokenType.NOT */] = BP_NOT; // !
|
|
2912
|
+
// Bracket access (highest)
|
|
2913
|
+
bp[3 /* TokenType.LBRACKET */] = BP_BRACKET; // [n], ['id'], [n:], etc.
|
|
2914
|
+
return bp;
|
|
2915
|
+
})();
|
|
2720
2916
|
/**
|
|
2721
|
-
*
|
|
2722
|
-
*
|
|
2917
|
+
* Hand-written parser for JSON Selectors.
|
|
2918
|
+
*
|
|
2919
|
+
* Uses precedence-climbing (Pratt parsing) with binding power (0-55 range) to handle
|
|
2920
|
+
* operator precedence efficiently. The parser uses two main methods:
|
|
2921
|
+
* - nud() for prefix/primary expressions (no left context)
|
|
2922
|
+
* - led() for infix/postfix operators (with left context)
|
|
2723
2923
|
*/
|
|
2724
|
-
class
|
|
2725
|
-
constructor(
|
|
2726
|
-
this.
|
|
2727
|
-
this.
|
|
2924
|
+
class Parser {
|
|
2925
|
+
constructor(input, options) {
|
|
2926
|
+
this.input = input;
|
|
2927
|
+
this.lexer = new Lexer(input, {
|
|
2928
|
+
rawStringBackslashEscape: options === null || options === void 0 ? void 0 : options.rawStringBackslashEscape,
|
|
2929
|
+
});
|
|
2930
|
+
this.strictJsonLiterals = (options === null || options === void 0 ? void 0 : options.strictJsonLiterals) === true;
|
|
2728
2931
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2932
|
+
/**
|
|
2933
|
+
* Main entry point: parse a complete expression
|
|
2934
|
+
*/
|
|
2935
|
+
parse() {
|
|
2936
|
+
const result = this.expression(0);
|
|
2937
|
+
const token = this.lexer.peek();
|
|
2938
|
+
if (token.type !== 0 /* TokenType.EOF */) {
|
|
2939
|
+
throw this.unexpectedToken(token, EOF_DESCRIPTION);
|
|
2940
|
+
}
|
|
2941
|
+
return result;
|
|
2732
2942
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2943
|
+
/**
|
|
2944
|
+
* Core Pratt parser: expression parsing with binding power
|
|
2945
|
+
*
|
|
2946
|
+
* @param rbp Right binding power - controls when to stop parsing
|
|
2947
|
+
* @returns Parsed expression node
|
|
2948
|
+
*/
|
|
2949
|
+
expression(rbp) {
|
|
2950
|
+
return this.expressionFrom(this.nud(), rbp);
|
|
2736
2951
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2952
|
+
/**
|
|
2953
|
+
* Continue Pratt parsing from a given left-hand side node.
|
|
2954
|
+
* Applies led (left denotation) operators while their binding power exceeds rbp.
|
|
2955
|
+
*/
|
|
2956
|
+
expressionFrom(left, rbp) {
|
|
2957
|
+
let current = left;
|
|
2958
|
+
let token = this.lexer.peek();
|
|
2959
|
+
while (token.type !== 0 /* TokenType.EOF */ && rbp < TOKEN_BP[token.type]) {
|
|
2960
|
+
current = this.led(current, token);
|
|
2961
|
+
token = this.lexer.peek();
|
|
2962
|
+
}
|
|
2963
|
+
return current;
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* NUD (Null Denotation): Handle prefix operators and primary expressions
|
|
2967
|
+
* Called when we don't have a left-hand side yet
|
|
2968
|
+
*/
|
|
2969
|
+
nud() {
|
|
2970
|
+
const token = this.lexer.peek();
|
|
2971
|
+
if (token.type === 0 /* TokenType.EOF */) {
|
|
2972
|
+
throw new UnexpectedEndOfInputError(this.input);
|
|
2973
|
+
}
|
|
2974
|
+
switch (token.type) {
|
|
2975
|
+
// Hot path: field names (most common) or function calls
|
|
2976
|
+
case 33 /* TokenType.IDENTIFIER */: {
|
|
2977
|
+
this.lexer.advance();
|
|
2978
|
+
// Check if this is a function call (identifier followed by `(`)
|
|
2979
|
+
if (this.lexer.peek().type === 1 /* TokenType.LPAREN */) {
|
|
2980
|
+
return this.parseFunctionCall(token.value);
|
|
2981
|
+
}
|
|
2982
|
+
// Contextual keyword: let expressions
|
|
2983
|
+
if (token.value === "let" &&
|
|
2984
|
+
this.lexer.peek().type === 41 /* TokenType.VARIABLE */) {
|
|
2985
|
+
return this.parseLetExpression();
|
|
2986
|
+
}
|
|
2987
|
+
return { type: "identifier", id: token.value };
|
|
2988
|
+
}
|
|
2989
|
+
case 34 /* TokenType.QUOTED_STRING */:
|
|
2990
|
+
this.lexer.advance();
|
|
2991
|
+
return { type: "identifier", id: token.value };
|
|
2992
|
+
case 26 /* TokenType.AT */:
|
|
2993
|
+
this.lexer.advance();
|
|
2994
|
+
return { type: "current", explicit: true };
|
|
2995
|
+
case 3 /* TokenType.LBRACKET */:
|
|
2996
|
+
return this.parseBracketExpression();
|
|
2997
|
+
case 32 /* TokenType.FLATTEN_BRACKET */: {
|
|
2998
|
+
// Leading [] applies to @
|
|
2999
|
+
this.lexer.consume(32 /* TokenType.FLATTEN_BRACKET */);
|
|
3000
|
+
const flattenNode = {
|
|
3001
|
+
type: "flatten",
|
|
3002
|
+
expression: CURRENT_NODE,
|
|
3003
|
+
};
|
|
3004
|
+
return this.parseProjectionRHS(flattenNode);
|
|
3005
|
+
}
|
|
3006
|
+
case 31 /* TokenType.FILTER_BRACKET */:
|
|
3007
|
+
return this.parseFilterExpression();
|
|
3008
|
+
case 27 /* TokenType.DOLLAR */:
|
|
3009
|
+
this.lexer.advance();
|
|
3010
|
+
return ROOT_NODE;
|
|
3011
|
+
case 41 /* TokenType.VARIABLE */:
|
|
3012
|
+
this.lexer.advance();
|
|
3013
|
+
return { type: "variableRef", name: token.value };
|
|
3014
|
+
case 35 /* TokenType.RAW_STRING */:
|
|
3015
|
+
case 39 /* TokenType.TRUE */:
|
|
3016
|
+
case 40 /* TokenType.FALSE */:
|
|
3017
|
+
case 38 /* TokenType.NULL */:
|
|
3018
|
+
case 37 /* TokenType.NUMBER */:
|
|
3019
|
+
this.lexer.advance();
|
|
3020
|
+
return { type: "literal", value: token.value };
|
|
3021
|
+
case 36 /* TokenType.BACKTICK_LITERAL */: {
|
|
3022
|
+
this.lexer.advance();
|
|
3023
|
+
const content = trimJsonWhitespace(token.value);
|
|
3024
|
+
try {
|
|
3025
|
+
// Type assertion is safe as JSON.parse returns a JsonValue
|
|
3026
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3027
|
+
const value = JSON.parse(content);
|
|
3028
|
+
return { type: "literal", value, backtickSyntax: true };
|
|
3029
|
+
}
|
|
3030
|
+
catch (_a) {
|
|
3031
|
+
if (this.strictJsonLiterals) {
|
|
3032
|
+
throw new InvalidTokenError(this.input, token.offset, "JSON literal", "expected valid JSON content between backticks");
|
|
3033
|
+
}
|
|
3034
|
+
return {
|
|
3035
|
+
type: "literal",
|
|
3036
|
+
value: content.replace(/\\"/g, '"'),
|
|
3037
|
+
backtickSyntax: true,
|
|
3038
|
+
};
|
|
2743
3039
|
}
|
|
2744
3040
|
}
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3041
|
+
case 13 /* TokenType.NOT */:
|
|
3042
|
+
this.lexer.advance();
|
|
3043
|
+
return {
|
|
3044
|
+
type: "not",
|
|
3045
|
+
expression: this.expression(BP_NOT),
|
|
3046
|
+
};
|
|
3047
|
+
case 20 /* TokenType.PLUS */:
|
|
3048
|
+
this.lexer.advance();
|
|
3049
|
+
return this.foldUnaryArithmeticLiteral("+", this.expression(BP_ADD));
|
|
3050
|
+
case 21 /* TokenType.MINUS */:
|
|
3051
|
+
this.lexer.advance();
|
|
3052
|
+
return this.foldUnaryArithmeticLiteral("-", this.expression(BP_ADD));
|
|
3053
|
+
case 1 /* TokenType.LPAREN */: {
|
|
3054
|
+
this.lexer.advance();
|
|
3055
|
+
const expr = this.expression(0);
|
|
3056
|
+
this.lexer.consume(2 /* TokenType.RPAREN */);
|
|
3057
|
+
return expr;
|
|
3058
|
+
}
|
|
3059
|
+
case 5 /* TokenType.LBRACE */:
|
|
3060
|
+
// Multi-select hash: {a: x, b: y}
|
|
3061
|
+
return this.parseMultiSelectHash();
|
|
3062
|
+
case 28 /* TokenType.STAR */: {
|
|
3063
|
+
// Root object projection: * or *.foo
|
|
3064
|
+
this.lexer.advance();
|
|
3065
|
+
return this.parseObjectProjection(CURRENT_NODE);
|
|
3066
|
+
}
|
|
3067
|
+
case 30 /* TokenType.AMPERSAND */: {
|
|
3068
|
+
// Expression reference: &expr (for sort_by, max_by, etc.)
|
|
3069
|
+
// Use binding power 0 to capture the full expression (up to comma or closing bracket)
|
|
3070
|
+
this.lexer.advance();
|
|
3071
|
+
return {
|
|
3072
|
+
type: "expressionRef",
|
|
3073
|
+
expression: this.expression(0),
|
|
3074
|
+
};
|
|
2757
3075
|
}
|
|
3076
|
+
default:
|
|
3077
|
+
throw this.unexpectedToken(token, "expression");
|
|
2758
3078
|
}
|
|
2759
3079
|
}
|
|
2760
|
-
*keys() {
|
|
2761
|
-
for (const [k] of this.entries()) {
|
|
2762
|
-
yield k;
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
*values() {
|
|
2766
|
-
for (const [, v] of this.entries()) {
|
|
2767
|
-
yield v;
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
[Symbol.iterator]() {
|
|
2771
|
-
return this.entries();
|
|
2772
|
-
}
|
|
2773
|
-
forEach(callbackfn, thisArg) {
|
|
2774
|
-
for (const [k, v] of this.entries()) {
|
|
2775
|
-
callbackfn.call(thisArg, v, k, this);
|
|
2776
|
-
}
|
|
2777
|
-
}
|
|
2778
|
-
}
|
|
2779
|
-
|
|
2780
|
-
/**
|
|
2781
|
-
* Function registry for built-in and custom functions.
|
|
2782
|
-
* Pass `null` as the base provider to omit built-in functions.
|
|
2783
|
-
*/
|
|
2784
|
-
class FunctionRegistry extends FallbackMapView {
|
|
2785
|
-
constructor(baseProvider = getBuiltinFunctionProvider()) {
|
|
2786
|
-
const custom = new Map();
|
|
2787
|
-
super(custom, baseProvider !== null && baseProvider !== void 0 ? baseProvider : undefined);
|
|
2788
|
-
this.custom = custom;
|
|
2789
|
-
}
|
|
2790
|
-
/**
|
|
2791
|
-
* Register a custom function (can override built-ins).
|
|
2792
|
-
*/
|
|
2793
|
-
register(def) {
|
|
2794
|
-
this.custom.set(def.name, def);
|
|
2795
|
-
}
|
|
2796
3080
|
/**
|
|
2797
|
-
*
|
|
3081
|
+
* LED (Left Denotation): Handle infix and postfix operators
|
|
3082
|
+
* Called when we have a left-hand side
|
|
2798
3083
|
*/
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3084
|
+
led(left, token) {
|
|
3085
|
+
switch (token.type) {
|
|
3086
|
+
// Hot path: field access (most common!)
|
|
3087
|
+
case 7 /* TokenType.DOT */: {
|
|
3088
|
+
this.lexer.advance();
|
|
3089
|
+
const nextToken = this.lexer.peek();
|
|
3090
|
+
// Object projection: foo.*
|
|
3091
|
+
if (nextToken.type === 28 /* TokenType.STAR */) {
|
|
3092
|
+
this.lexer.advance();
|
|
3093
|
+
return this.parseObjectProjection(left);
|
|
3094
|
+
}
|
|
3095
|
+
// Multi-select hash: foo.{a: x, b: y}
|
|
3096
|
+
if (nextToken.type === 5 /* TokenType.LBRACE */) {
|
|
3097
|
+
const multiSelectHash = this.parseMultiSelectHash();
|
|
3098
|
+
return {
|
|
3099
|
+
type: "pipe",
|
|
3100
|
+
lhs: left,
|
|
3101
|
+
rhs: multiSelectHash,
|
|
3102
|
+
dotSyntax: true,
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
// Multi-select list: foo.[a, b]
|
|
3106
|
+
if (nextToken.type === 3 /* TokenType.LBRACKET */) {
|
|
3107
|
+
return this.parseDotBracket(left);
|
|
3108
|
+
}
|
|
3109
|
+
// Standard field access or function call
|
|
3110
|
+
const field = this.parseIdentifier();
|
|
3111
|
+
// Check if this is a function call: foo.func(args)
|
|
3112
|
+
// This is equivalent to: foo | func(args)
|
|
3113
|
+
if (this.lexer.peek().type === 1 /* TokenType.LPAREN */) {
|
|
3114
|
+
const funcCall = this.parseFunctionCall(field);
|
|
3115
|
+
return {
|
|
3116
|
+
type: "pipe",
|
|
3117
|
+
lhs: left,
|
|
3118
|
+
rhs: funcCall,
|
|
3119
|
+
dotSyntax: true,
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
return { type: "fieldAccess", expression: left, field };
|
|
3123
|
+
}
|
|
3124
|
+
case 3 /* TokenType.LBRACKET */:
|
|
3125
|
+
return this.parseBracketExpression(left);
|
|
3126
|
+
case 32 /* TokenType.FLATTEN_BRACKET */: {
|
|
3127
|
+
this.lexer.consume(32 /* TokenType.FLATTEN_BRACKET */);
|
|
3128
|
+
const flattenNode = {
|
|
3129
|
+
type: "flatten",
|
|
3130
|
+
expression: left,
|
|
3131
|
+
};
|
|
3132
|
+
return this.parseProjectionRHS(flattenNode);
|
|
3133
|
+
}
|
|
3134
|
+
case 31 /* TokenType.FILTER_BRACKET */:
|
|
3135
|
+
return this.parseFilterExpression(left);
|
|
3136
|
+
case 10 /* TokenType.PIPE */:
|
|
3137
|
+
this.lexer.advance();
|
|
3138
|
+
return {
|
|
3139
|
+
type: "pipe",
|
|
3140
|
+
lhs: left,
|
|
3141
|
+
rhs: this.expression(BP_PIPE),
|
|
3142
|
+
};
|
|
3143
|
+
case 12 /* TokenType.AND */:
|
|
3144
|
+
this.lexer.advance();
|
|
3145
|
+
return {
|
|
3146
|
+
type: "and",
|
|
3147
|
+
lhs: left,
|
|
3148
|
+
rhs: this.expression(BP_AND),
|
|
3149
|
+
};
|
|
3150
|
+
case 11 /* TokenType.OR */:
|
|
3151
|
+
this.lexer.advance();
|
|
3152
|
+
return {
|
|
3153
|
+
type: "or",
|
|
3154
|
+
lhs: left,
|
|
3155
|
+
rhs: this.expression(BP_OR),
|
|
3156
|
+
};
|
|
3157
|
+
case 20 /* TokenType.PLUS */:
|
|
3158
|
+
return this.parseArithmetic(left, "+", BP_ADD);
|
|
3159
|
+
case 21 /* TokenType.MINUS */:
|
|
3160
|
+
return this.parseArithmetic(left, "-", BP_ADD);
|
|
3161
|
+
case 28 /* TokenType.STAR */:
|
|
3162
|
+
case 22 /* TokenType.MULTIPLY */:
|
|
3163
|
+
return this.parseArithmetic(left, "*", BP_MUL);
|
|
3164
|
+
case 23 /* TokenType.DIVIDE */:
|
|
3165
|
+
return this.parseArithmetic(left, "/", BP_MUL);
|
|
3166
|
+
case 24 /* TokenType.MODULO */:
|
|
3167
|
+
return this.parseArithmetic(left, "%", BP_MUL);
|
|
3168
|
+
case 25 /* TokenType.INT_DIVIDE */:
|
|
3169
|
+
return this.parseArithmetic(left, "//", BP_MUL);
|
|
3170
|
+
case 29 /* TokenType.QUESTION */: {
|
|
3171
|
+
this.lexer.advance();
|
|
3172
|
+
const consequent = this.expression(0);
|
|
3173
|
+
this.lexer.consume(9 /* TokenType.COLON */);
|
|
3174
|
+
return {
|
|
3175
|
+
type: "ternary",
|
|
3176
|
+
condition: left,
|
|
3177
|
+
consequent,
|
|
3178
|
+
// NOTE: This differs from jmespath-community/typescript-jmespath, which
|
|
3179
|
+
// parses the false branch with expression(0).
|
|
3180
|
+
//
|
|
3181
|
+
// We intentionally parse with BP_TERNARY - 1 so the current ternary keeps
|
|
3182
|
+
// ownership of lower-precedence operators like pipe. This yields:
|
|
3183
|
+
// a ? b : c | d => (a ? b : c) | d
|
|
3184
|
+
//
|
|
3185
|
+
// This follows JEP-21 exactly:
|
|
3186
|
+
// - "higher precedence than the `|` pipe expression and lower precedence
|
|
3187
|
+
// than the `||` and `&&` logical expressions"
|
|
3188
|
+
// - "Expressions within a ternary conditional expression are evaluated
|
|
3189
|
+
// using a right-associative reading."
|
|
3190
|
+
// Spec: https://github.com/jmespath-community/jmespath.spec/blob/main/jep-0021-ternary-conditionals.md
|
|
3191
|
+
alternate: this.expression(BP_TERNARY - 1),
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
case 14 /* TokenType.EQ */:
|
|
3195
|
+
return this.parseCompare(left, "==");
|
|
3196
|
+
case 15 /* TokenType.NEQ */:
|
|
3197
|
+
return this.parseCompare(left, "!=");
|
|
3198
|
+
case 16 /* TokenType.LT */:
|
|
3199
|
+
return this.parseCompare(left, "<");
|
|
3200
|
+
case 17 /* TokenType.LTE */:
|
|
3201
|
+
return this.parseCompare(left, "<=");
|
|
3202
|
+
case 18 /* TokenType.GT */:
|
|
3203
|
+
return this.parseCompare(left, ">");
|
|
3204
|
+
case 19 /* TokenType.GTE */:
|
|
3205
|
+
return this.parseCompare(left, ">=");
|
|
3206
|
+
default:
|
|
3207
|
+
throw this.unexpectedToken(token);
|
|
3208
|
+
}
|
|
2890
3209
|
}
|
|
2891
3210
|
/**
|
|
2892
|
-
*
|
|
3211
|
+
* Parse lexical scope expression:
|
|
3212
|
+
* let $a = expr, $b = expr in body
|
|
3213
|
+
*
|
|
3214
|
+
* Called after consuming contextual "let".
|
|
2893
3215
|
*/
|
|
2894
|
-
|
|
2895
|
-
|
|
3216
|
+
parseLetExpression() {
|
|
3217
|
+
const bindings = [];
|
|
3218
|
+
do {
|
|
3219
|
+
const variable = this.lexer.consume(41 /* TokenType.VARIABLE */);
|
|
3220
|
+
this.lexer.consume(42 /* TokenType.ASSIGN */);
|
|
3221
|
+
bindings.push({
|
|
3222
|
+
name: variable.value,
|
|
3223
|
+
value: this.expression(0),
|
|
3224
|
+
});
|
|
3225
|
+
} while (this.lexer.tryConsume(8 /* TokenType.COMMA */));
|
|
3226
|
+
const token = this.lexer.peek();
|
|
3227
|
+
if (token.type !== 33 /* TokenType.IDENTIFIER */ || token.value !== "in") {
|
|
3228
|
+
throw this.unexpectedToken(token, "'in'", "after let bindings");
|
|
3229
|
+
}
|
|
3230
|
+
this.lexer.advance();
|
|
3231
|
+
return {
|
|
3232
|
+
type: "let",
|
|
3233
|
+
bindings,
|
|
3234
|
+
expression: this.expression(0),
|
|
3235
|
+
};
|
|
2896
3236
|
}
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
3237
|
+
parseCompare(left, operator) {
|
|
3238
|
+
this.lexer.advance();
|
|
3239
|
+
return {
|
|
3240
|
+
type: "compare",
|
|
3241
|
+
operator,
|
|
3242
|
+
lhs: left,
|
|
3243
|
+
rhs: this.expression(BP_COMPARE),
|
|
3244
|
+
};
|
|
2903
3245
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
throw new UnexpectedTokenError(this.input, token.offset, actual, expected);
|
|
2913
|
-
}
|
|
2914
|
-
this.advance();
|
|
2915
|
-
// Type assertion is safe due to the type check above
|
|
2916
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
2917
|
-
return token;
|
|
3246
|
+
parseArithmetic(left, operator, bp) {
|
|
3247
|
+
this.lexer.advance();
|
|
3248
|
+
return {
|
|
3249
|
+
type: "arithmetic",
|
|
3250
|
+
operator,
|
|
3251
|
+
lhs: left,
|
|
3252
|
+
rhs: this.expression(bp),
|
|
3253
|
+
};
|
|
2918
3254
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
return null;
|
|
3255
|
+
foldUnaryArithmeticLiteral(operator, expression) {
|
|
3256
|
+
if (expression.type === "literal" && typeof expression.value === "number") {
|
|
3257
|
+
const value = operator === "+" ? expression.value : -expression.value;
|
|
3258
|
+
return Object.assign({ type: "literal", value }, (expression.backtickSyntax !== undefined && {
|
|
3259
|
+
backtickSyntax: expression.backtickSyntax,
|
|
3260
|
+
}));
|
|
2926
3261
|
}
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3262
|
+
return {
|
|
3263
|
+
type: "unaryArithmetic",
|
|
3264
|
+
operator,
|
|
3265
|
+
expression,
|
|
3266
|
+
};
|
|
2931
3267
|
}
|
|
2932
3268
|
/**
|
|
2933
|
-
*
|
|
3269
|
+
* Parse bracket expression: foo[0], foo[:], foo[*], foo['id']
|
|
3270
|
+
* Also handles leading brackets (no LHS): [0], [:], [*], ['id']
|
|
3271
|
+
*
|
|
3272
|
+
* When a bracket appears at the start of an expression or after an operator,
|
|
3273
|
+
* it implicitly applies to @ (current context). For example:
|
|
3274
|
+
* - `[0]` means `@[0]`
|
|
3275
|
+
* - `foo | [*]` means `foo | @[*]`
|
|
3276
|
+
*
|
|
3277
|
+
* Note: foo[] (flatten) is handled separately via FLATTEN_BRACKET token in led()
|
|
2934
3278
|
*/
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
case 0x00f7: // ÷
|
|
2990
|
-
singleType = 23 /* TokenType.DIVIDE */;
|
|
2991
|
-
break;
|
|
2992
|
-
// Delimiters and punctuation
|
|
2993
|
-
case 40: // (
|
|
2994
|
-
singleType = 1 /* TokenType.LPAREN */;
|
|
2995
|
-
break;
|
|
2996
|
-
case 41: // )
|
|
2997
|
-
singleType = 2 /* TokenType.RPAREN */;
|
|
2998
|
-
break;
|
|
2999
|
-
case 93: // ]
|
|
3000
|
-
singleType = 4 /* TokenType.RBRACKET */;
|
|
3001
|
-
break;
|
|
3002
|
-
case 123: // {
|
|
3003
|
-
singleType = 5 /* TokenType.LBRACE */;
|
|
3004
|
-
break;
|
|
3005
|
-
case 125: // }
|
|
3006
|
-
singleType = 6 /* TokenType.RBRACE */;
|
|
3007
|
-
break;
|
|
3008
|
-
case 46: // .
|
|
3009
|
-
singleType = 7 /* TokenType.DOT */;
|
|
3010
|
-
break;
|
|
3011
|
-
case 44: // ,
|
|
3012
|
-
singleType = 8 /* TokenType.COMMA */;
|
|
3013
|
-
break;
|
|
3014
|
-
case 58: // :
|
|
3015
|
-
singleType = 9 /* TokenType.COLON */;
|
|
3016
|
-
break;
|
|
3017
|
-
// Context and control symbols
|
|
3018
|
-
case 64: // @
|
|
3019
|
-
singleType = 26 /* TokenType.AT */;
|
|
3020
|
-
break;
|
|
3021
|
-
case 36: {
|
|
3022
|
-
// $ or variable reference ($name)
|
|
3023
|
-
const next = this.peekCharCode(this.pos + 1);
|
|
3024
|
-
if (isIdentStart(next)) {
|
|
3025
|
-
return this.scanVariable(start);
|
|
3279
|
+
parseBracketExpression(left = CURRENT_NODE) {
|
|
3280
|
+
// Must be LBRACKET - consume it and check what follows
|
|
3281
|
+
this.lexer.consume(3 /* TokenType.LBRACKET */);
|
|
3282
|
+
const token = this.lexer.peek();
|
|
3283
|
+
switch (token.type) {
|
|
3284
|
+
case 28 /* TokenType.STAR */: {
|
|
3285
|
+
// At root level, [*...] could be:
|
|
3286
|
+
// - [*] = array projection
|
|
3287
|
+
// - [*.*] = multi-select list containing object projection chain
|
|
3288
|
+
// We need to lookahead to distinguish
|
|
3289
|
+
this.lexer.consume(28 /* TokenType.STAR */);
|
|
3290
|
+
// Check what comes after *
|
|
3291
|
+
const nextToken = this.lexer.peek();
|
|
3292
|
+
if (nextToken.type === 4 /* TokenType.RBRACKET */) {
|
|
3293
|
+
// [*] - array projection
|
|
3294
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3295
|
+
const projectNode = {
|
|
3296
|
+
type: "project",
|
|
3297
|
+
expression: left,
|
|
3298
|
+
projection: CURRENT_NODE,
|
|
3299
|
+
};
|
|
3300
|
+
return this.parseProjectionRHS(projectNode);
|
|
3301
|
+
}
|
|
3302
|
+
// Not just [*], so this is multi-select list starting with * expression
|
|
3303
|
+
// We've already consumed *, so we need to build the expression from there
|
|
3304
|
+
if (left !== CURRENT_NODE) {
|
|
3305
|
+
// foo[*.something] is not valid - multi-select list after expression needs .[
|
|
3306
|
+
throw this.unexpectedToken(nextToken, "']'", "after '[*'");
|
|
3307
|
+
}
|
|
3308
|
+
// Build the rest of the * expression and continue parsing multi-select list
|
|
3309
|
+
const starExpr = this.expressionFrom({
|
|
3310
|
+
type: "objectProject",
|
|
3311
|
+
expression: CURRENT_NODE,
|
|
3312
|
+
projection: CURRENT_NODE,
|
|
3313
|
+
}, 0);
|
|
3314
|
+
return this.parseMultiSelectListFromBracket(starExpr);
|
|
3315
|
+
}
|
|
3316
|
+
case 35 /* TokenType.RAW_STRING */: {
|
|
3317
|
+
// foo['id'] - id access (no projection)
|
|
3318
|
+
const id = this.lexer.consume(35 /* TokenType.RAW_STRING */).value;
|
|
3319
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3320
|
+
return {
|
|
3321
|
+
type: "idAccess",
|
|
3322
|
+
expression: left,
|
|
3323
|
+
id,
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
case 37 /* TokenType.NUMBER */:
|
|
3327
|
+
return this.parseIndexOrSlice(left, this.lexer.consume(37 /* TokenType.NUMBER */).value);
|
|
3328
|
+
case 21 /* TokenType.MINUS */: {
|
|
3329
|
+
this.lexer.consume(21 /* TokenType.MINUS */);
|
|
3330
|
+
const numberToken = this.lexer.tryConsume(37 /* TokenType.NUMBER */);
|
|
3331
|
+
if (numberToken) {
|
|
3332
|
+
return this.parseIndexOrSlice(left, -numberToken.value);
|
|
3026
3333
|
}
|
|
3027
|
-
|
|
3028
|
-
|
|
3334
|
+
// Root multi-select list can still start with unary minus (e.g. [-foo])
|
|
3335
|
+
if (left !== CURRENT_NODE) {
|
|
3336
|
+
const nextToken = this.lexer.peek();
|
|
3337
|
+
throw this.unexpectedToken(nextToken, "number", "after '[-'");
|
|
3338
|
+
}
|
|
3339
|
+
return this.parseMultiSelectListFromBracket(this.foldUnaryArithmeticLiteral("-", this.expression(BP_ADD)));
|
|
3340
|
+
}
|
|
3341
|
+
case 9 /* TokenType.COLON */: {
|
|
3342
|
+
// Slice starting with colon: foo[:n] or foo[:]
|
|
3343
|
+
const sliceNode = this.parseSlice(left);
|
|
3344
|
+
return this.parseProjectionRHS(sliceNode);
|
|
3345
|
+
}
|
|
3346
|
+
default: {
|
|
3347
|
+
// Multi-select list: [expr1, expr2, ...]
|
|
3348
|
+
// Only valid at root level (left === CURRENT_NODE), not after foo[
|
|
3349
|
+
// Requires foo.[a, b] syntax, not foo[a, b]
|
|
3350
|
+
if (left !== CURRENT_NODE) {
|
|
3351
|
+
throw this.unexpectedToken(token, "number, ':', '*', or string", "after '['");
|
|
3352
|
+
}
|
|
3353
|
+
return this.parseMultiSelectListFromBracket(this.expression(0));
|
|
3029
3354
|
}
|
|
3030
|
-
case 63: // ?
|
|
3031
|
-
singleType = 29 /* TokenType.QUESTION */;
|
|
3032
|
-
break;
|
|
3033
|
-
}
|
|
3034
|
-
if (singleType !== 0) {
|
|
3035
|
-
this.pos++;
|
|
3036
|
-
return {
|
|
3037
|
-
type: singleType,
|
|
3038
|
-
text: this.input[start],
|
|
3039
|
-
offset: start,
|
|
3040
|
-
};
|
|
3041
|
-
}
|
|
3042
|
-
if (isDigit(ch)) {
|
|
3043
|
-
return this.scanNumber(start);
|
|
3044
|
-
}
|
|
3045
|
-
if (isIdentStart(ch)) {
|
|
3046
|
-
return this.scanIdentifier(start);
|
|
3047
3355
|
}
|
|
3048
|
-
throw new UnexpectedCharacterError(this.input, start, this.input[start]);
|
|
3049
3356
|
}
|
|
3050
3357
|
/**
|
|
3051
|
-
*
|
|
3358
|
+
* Parse filter expression: foo[?bar]
|
|
3359
|
+
* Also handles leading filter expression (no LHS): [?...]
|
|
3360
|
+
*
|
|
3361
|
+
* When [? appears without a left-hand side, it implicitly applies to @ (current context).
|
|
3362
|
+
* Example: `[?x > 5]` means `@[?x > 5]`
|
|
3052
3363
|
*/
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
};
|
|
3063
|
-
}
|
|
3064
|
-
if (ch === 93) {
|
|
3065
|
-
// ]
|
|
3066
|
-
this.pos++;
|
|
3067
|
-
return {
|
|
3068
|
-
type: 32 /* TokenType.FLATTEN_BRACKET */,
|
|
3069
|
-
text: "[]",
|
|
3070
|
-
offset: start,
|
|
3071
|
-
};
|
|
3072
|
-
}
|
|
3073
|
-
return {
|
|
3074
|
-
type: 3 /* TokenType.LBRACKET */,
|
|
3075
|
-
text: "[",
|
|
3076
|
-
offset: start,
|
|
3364
|
+
parseFilterExpression(left = CURRENT_NODE) {
|
|
3365
|
+
// Lexer emits FILTER_BRACKET token for [?
|
|
3366
|
+
this.lexer.consume(31 /* TokenType.FILTER_BRACKET */);
|
|
3367
|
+
const condition = this.expression(0);
|
|
3368
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3369
|
+
const filterNode = {
|
|
3370
|
+
type: "filter",
|
|
3371
|
+
expression: left,
|
|
3372
|
+
condition,
|
|
3077
3373
|
};
|
|
3374
|
+
return this.parseProjectionRHS(filterNode);
|
|
3078
3375
|
}
|
|
3079
3376
|
/**
|
|
3080
|
-
*
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
*
|
|
3377
|
+
* Parse projection RHS: what comes after [*], [], [?...], or slice
|
|
3378
|
+
*
|
|
3379
|
+
* Key insight: After a projection, subsequent operations may either:
|
|
3380
|
+
* 1. Continue the projection chain (e.g., foo[].bar[].baz)
|
|
3381
|
+
* 2. Terminate the projection
|
|
3382
|
+
*
|
|
3383
|
+
* Uses PROJECTION_STOP_THRESHOLD to distinguish:
|
|
3384
|
+
* - Below threshold: terminators (pipe, ternary, or, and, comparisons, EOF, closing brackets)
|
|
3385
|
+
* - At/above threshold: continuation operators (flatten, filter, star, dot, bracket access)
|
|
3386
|
+
*
|
|
3387
|
+
* This allows proper precedence: `foo[] | bar` pipes the projection result,
|
|
3388
|
+
* while `foo[].bar` continues the projection to select .bar from each element.
|
|
3389
|
+
*
|
|
3390
|
+
* NOTE: Unlike the reference implementation which uses binding power (rbp) to control
|
|
3391
|
+
* projection continuation, we use a fixed threshold. This is because our AST structure is
|
|
3392
|
+
* different: the reference chains projections (project(project(x, y), z)) while we nest
|
|
3393
|
+
* them in the projection field (project(x, project(y, z))). Both approaches produce the same results,
|
|
3394
|
+
* but our nested structure creates clearer semantics for expressions like foo[*].bar[*].
|
|
3093
3395
|
*/
|
|
3094
|
-
|
|
3095
|
-
const
|
|
3096
|
-
if (
|
|
3097
|
-
|
|
3098
|
-
this.pos++;
|
|
3099
|
-
return { type: 15 /* TokenType.NEQ */, text: "!=", offset: start };
|
|
3396
|
+
parseProjectionRHS(projectionNode) {
|
|
3397
|
+
const rhs = this.parseProjectionContinuation();
|
|
3398
|
+
if (rhs === undefined) {
|
|
3399
|
+
return projectionNode;
|
|
3100
3400
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
// =
|
|
3110
|
-
this.pos++;
|
|
3111
|
-
return { type: 17 /* TokenType.LTE */, text: "<=", offset: start };
|
|
3401
|
+
// [*] nodes already have a projection field, so update it directly
|
|
3402
|
+
//
|
|
3403
|
+
// SAFETY: This mutation is safe because projectionNode is freshly created
|
|
3404
|
+
// in parseBracketExpression, parseFilterExpression, or parseSlice,
|
|
3405
|
+
// and has not been shared or returned yet.
|
|
3406
|
+
if (projectionNode.type === "project") {
|
|
3407
|
+
projectionNode.projection = rhs;
|
|
3408
|
+
return projectionNode;
|
|
3112
3409
|
}
|
|
3113
|
-
|
|
3410
|
+
// flatten/filter/slice nodes don't have projection fields, so wrap them
|
|
3411
|
+
return {
|
|
3412
|
+
type: "project",
|
|
3413
|
+
expression: projectionNode,
|
|
3414
|
+
projection: rhs,
|
|
3415
|
+
};
|
|
3114
3416
|
}
|
|
3115
3417
|
/**
|
|
3116
|
-
*
|
|
3418
|
+
* Parse the continuation of a projection: the chain of postfix operators
|
|
3419
|
+
* (dot, bracket, filter) that apply to each projected element.
|
|
3420
|
+
*
|
|
3421
|
+
* Returns undefined if the next token does not continue the projection
|
|
3422
|
+
* (e.g. pipe, comparison, EOF, or closing brackets).
|
|
3117
3423
|
*/
|
|
3118
|
-
|
|
3119
|
-
const
|
|
3120
|
-
if (
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
return
|
|
3424
|
+
parseProjectionContinuation() {
|
|
3425
|
+
const token = this.lexer.peek();
|
|
3426
|
+
if (token.type !== 7 /* TokenType.DOT */ &&
|
|
3427
|
+
token.type !== 3 /* TokenType.LBRACKET */ &&
|
|
3428
|
+
token.type !== 31 /* TokenType.FILTER_BRACKET */) {
|
|
3429
|
+
return undefined;
|
|
3124
3430
|
}
|
|
3125
|
-
return
|
|
3431
|
+
return this.expressionFrom(CURRENT_NODE, PROJECTION_STOP_BP - 1);
|
|
3126
3432
|
}
|
|
3127
3433
|
/**
|
|
3128
|
-
*
|
|
3434
|
+
* Parse slice after [ and optional start have been consumed: [start:end:step]
|
|
3435
|
+
*
|
|
3436
|
+
* Called when pattern is [:...] (start undefined) or [n:...] / [-n:...] (start known).
|
|
3129
3437
|
*/
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3438
|
+
parseSlice(left, start) {
|
|
3439
|
+
// [ and optional start already consumed, current token should be COLON
|
|
3440
|
+
this.lexer.consume(9 /* TokenType.COLON */);
|
|
3441
|
+
const end = this.tryConsumeSignedNumber();
|
|
3442
|
+
let step;
|
|
3443
|
+
if (this.lexer.tryConsume(9 /* TokenType.COLON */)) {
|
|
3444
|
+
step = this.tryConsumeSignedNumber();
|
|
3136
3445
|
}
|
|
3137
|
-
|
|
3446
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3447
|
+
return {
|
|
3448
|
+
type: "slice",
|
|
3449
|
+
expression: left,
|
|
3450
|
+
start,
|
|
3451
|
+
end,
|
|
3452
|
+
step,
|
|
3453
|
+
};
|
|
3138
3454
|
}
|
|
3139
3455
|
/**
|
|
3140
|
-
*
|
|
3456
|
+
* After consuming [ and a number, determine if this is an index or slice.
|
|
3141
3457
|
*/
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
// &&
|
|
3146
|
-
this.pos++;
|
|
3147
|
-
return { type: 12 /* TokenType.AND */, text: "&&", offset: start };
|
|
3458
|
+
parseIndexOrSlice(left, num) {
|
|
3459
|
+
if (this.lexer.peek().type === 9 /* TokenType.COLON */) {
|
|
3460
|
+
return this.parseProjectionRHS(this.parseSlice(left, num));
|
|
3148
3461
|
}
|
|
3149
|
-
|
|
3150
|
-
return { type:
|
|
3462
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3463
|
+
return { type: "indexAccess", expression: left, index: num };
|
|
3151
3464
|
}
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
const ch = this.advanceCharCode(); // consume /
|
|
3157
|
-
if (ch === 47) {
|
|
3158
|
-
// /
|
|
3159
|
-
this.pos++;
|
|
3160
|
-
return { type: 25 /* TokenType.INT_DIVIDE */, text: "//", offset: start };
|
|
3465
|
+
tryConsumeSignedNumber() {
|
|
3466
|
+
const token = this.lexer.peek();
|
|
3467
|
+
if (token.type === 37 /* TokenType.NUMBER */) {
|
|
3468
|
+
return this.lexer.consume(37 /* TokenType.NUMBER */).value;
|
|
3161
3469
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
*/
|
|
3170
|
-
scanRawString(start) {
|
|
3171
|
-
this.pos++; // consume opening '
|
|
3172
|
-
const startPos = this.pos;
|
|
3173
|
-
// First pass: find end and check for escapes
|
|
3174
|
-
let hasEscape = false;
|
|
3175
|
-
let endPos = this.pos;
|
|
3176
|
-
while (endPos < this.length) {
|
|
3177
|
-
const ch = this.input.charCodeAt(endPos);
|
|
3178
|
-
if (ch === 39) {
|
|
3179
|
-
// Found closing '
|
|
3180
|
-
break;
|
|
3181
|
-
}
|
|
3182
|
-
if (ch === 92) {
|
|
3183
|
-
// Backslash - potential escape
|
|
3184
|
-
hasEscape = true;
|
|
3185
|
-
endPos += 2; // Skip backslash and next char
|
|
3186
|
-
}
|
|
3187
|
-
else {
|
|
3188
|
-
endPos++;
|
|
3470
|
+
if (token.type === 20 /* TokenType.PLUS */ || token.type === 21 /* TokenType.MINUS */) {
|
|
3471
|
+
const sign = token.type === 21 /* TokenType.MINUS */ ? -1 : 1;
|
|
3472
|
+
this.lexer.advance();
|
|
3473
|
+
const numberToken = this.lexer.tryConsume(37 /* TokenType.NUMBER */);
|
|
3474
|
+
if (!numberToken) {
|
|
3475
|
+
const nextToken = this.lexer.peek();
|
|
3476
|
+
throw this.unexpectedToken(nextToken, "number");
|
|
3189
3477
|
}
|
|
3478
|
+
return sign * numberToken.value;
|
|
3190
3479
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3480
|
+
return undefined;
|
|
3481
|
+
}
|
|
3482
|
+
parseIdentifier() {
|
|
3483
|
+
const id = this.lexer.tryConsume(33 /* TokenType.IDENTIFIER */);
|
|
3484
|
+
if (id) {
|
|
3485
|
+
return id.value;
|
|
3193
3486
|
}
|
|
3194
|
-
const
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
value = this.rawStringBackslashEscape
|
|
3198
|
-
? value.replace(/\\([\\'])/g, "$1")
|
|
3199
|
-
: value.replace(/\\'/g, "'");
|
|
3487
|
+
const quoted = this.lexer.tryConsume(34 /* TokenType.QUOTED_STRING */);
|
|
3488
|
+
if (quoted) {
|
|
3489
|
+
return quoted.value;
|
|
3200
3490
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3491
|
+
const token = this.lexer.peek();
|
|
3492
|
+
throw this.unexpectedToken(token, "identifier");
|
|
3203
3493
|
}
|
|
3204
3494
|
/**
|
|
3205
|
-
*
|
|
3206
|
-
*
|
|
3207
|
-
* Returns raw content for JSON.parse() in parser
|
|
3495
|
+
* Parse comma-separated expressions and closing bracket into a multi-select list.
|
|
3496
|
+
* Called after the first expression has already been parsed.
|
|
3208
3497
|
*/
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
let endPos = this.pos;
|
|
3214
|
-
let hasEscape = false;
|
|
3215
|
-
while (endPos < this.length) {
|
|
3216
|
-
const ch = this.input.charCodeAt(endPos);
|
|
3217
|
-
if (ch === 96) {
|
|
3218
|
-
// A backtick is escaped only when preceded by an odd number of backslashes.
|
|
3219
|
-
let slashCount = 0;
|
|
3220
|
-
for (let i = endPos - 1; i >= startPos; i--) {
|
|
3221
|
-
if (this.input.charCodeAt(i) !== 92) {
|
|
3222
|
-
break;
|
|
3223
|
-
}
|
|
3224
|
-
slashCount++;
|
|
3225
|
-
}
|
|
3226
|
-
if (slashCount % 2 === 1) {
|
|
3227
|
-
hasEscape = true;
|
|
3228
|
-
endPos++;
|
|
3229
|
-
continue;
|
|
3230
|
-
}
|
|
3231
|
-
// Found closing `.
|
|
3232
|
-
break;
|
|
3233
|
-
}
|
|
3234
|
-
endPos++;
|
|
3235
|
-
}
|
|
3236
|
-
if (endPos >= this.length) {
|
|
3237
|
-
throw new UnterminatedTokenError(this.input, start, "JSON literal", "`");
|
|
3238
|
-
}
|
|
3239
|
-
const text = this.input.slice(start, endPos + 1);
|
|
3240
|
-
let value = this.input.slice(startPos, endPos);
|
|
3241
|
-
if (hasEscape) {
|
|
3242
|
-
value = value.replace(/\\`/g, "`");
|
|
3498
|
+
parseMultiSelectListFromBracket(first) {
|
|
3499
|
+
const expressions = [first];
|
|
3500
|
+
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
3501
|
+
expressions.push(this.expression(0));
|
|
3243
3502
|
}
|
|
3244
|
-
this.
|
|
3245
|
-
return { type:
|
|
3503
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3504
|
+
return { type: "multiSelectList", expressions };
|
|
3246
3505
|
}
|
|
3247
3506
|
/**
|
|
3248
|
-
*
|
|
3249
|
-
*
|
|
3507
|
+
* Parse function call: name(arg1, arg2, ...)
|
|
3508
|
+
* Called after identifier has been consumed and ( has been peeked
|
|
3250
3509
|
*/
|
|
3251
|
-
|
|
3252
|
-
this.
|
|
3253
|
-
const
|
|
3254
|
-
|
|
3255
|
-
this.
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
if (inner[i] === "\\") {
|
|
3262
|
-
// It's impossible to have a backslash at end due to scanQuotedStringEnd
|
|
3263
|
-
const next = inner[i + 1];
|
|
3264
|
-
if (next === "u") {
|
|
3265
|
-
// Unicode escape: \uXXXX (must have exactly 4 hex digits)
|
|
3266
|
-
const hex = inner.slice(i + 2, i + 6);
|
|
3267
|
-
if (/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
3268
|
-
value += String.fromCharCode(parseInt(hex, 16));
|
|
3269
|
-
i += 6;
|
|
3270
|
-
}
|
|
3271
|
-
else {
|
|
3272
|
-
throw new InvalidTokenError(this.input, start + 1 + i, "unicode escape", "expected 4 hexadecimal digits");
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
else if (next in ESCAPE_CHARS) {
|
|
3276
|
-
value += ESCAPE_CHARS[next];
|
|
3277
|
-
i += 2;
|
|
3278
|
-
}
|
|
3279
|
-
else {
|
|
3280
|
-
// Unknown escape, keep both chars
|
|
3281
|
-
value += "\\" + next;
|
|
3282
|
-
i += 2;
|
|
3283
|
-
}
|
|
3284
|
-
}
|
|
3285
|
-
else {
|
|
3286
|
-
value += inner[i];
|
|
3287
|
-
i++;
|
|
3510
|
+
parseFunctionCall(name) {
|
|
3511
|
+
this.lexer.consume(1 /* TokenType.LPAREN */);
|
|
3512
|
+
const args = [];
|
|
3513
|
+
// Handle empty args: func()
|
|
3514
|
+
if (this.lexer.peek().type !== 2 /* TokenType.RPAREN */) {
|
|
3515
|
+
// First argument
|
|
3516
|
+
args.push(this.expression(0));
|
|
3517
|
+
// Additional arguments
|
|
3518
|
+
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
3519
|
+
args.push(this.expression(0));
|
|
3288
3520
|
}
|
|
3289
3521
|
}
|
|
3290
|
-
|
|
3522
|
+
this.lexer.consume(2 /* TokenType.RPAREN */);
|
|
3523
|
+
return {
|
|
3524
|
+
type: "functionCall",
|
|
3525
|
+
name,
|
|
3526
|
+
args,
|
|
3527
|
+
};
|
|
3291
3528
|
}
|
|
3292
3529
|
/**
|
|
3293
|
-
*
|
|
3294
|
-
* Rejects unescaped backticks (not valid in JSON strings)
|
|
3530
|
+
* Parse multi-select hash: {key: expr, key: expr, ...}
|
|
3295
3531
|
*/
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
}
|
|
3304
|
-
if (ch === 96) {
|
|
3305
|
-
// unescaped backtick - not allowed in quoted strings
|
|
3306
|
-
throw new InvalidTokenError(this.input, pos, "backtick in string", "unescaped backtick");
|
|
3307
|
-
}
|
|
3308
|
-
if (ch === 92) {
|
|
3309
|
-
// backslash (escape)
|
|
3310
|
-
pos += 2; // skip backslash and next char
|
|
3311
|
-
}
|
|
3312
|
-
else {
|
|
3313
|
-
pos++;
|
|
3314
|
-
}
|
|
3532
|
+
parseMultiSelectHash() {
|
|
3533
|
+
this.lexer.consume(5 /* TokenType.LBRACE */);
|
|
3534
|
+
const entries = [];
|
|
3535
|
+
// Empty multi-select hash {} is invalid
|
|
3536
|
+
if (this.lexer.peek().type === 6 /* TokenType.RBRACE */) {
|
|
3537
|
+
const token = this.lexer.peek();
|
|
3538
|
+
throw this.unexpectedToken(token, "key-value pair", "in multi-select hash");
|
|
3315
3539
|
}
|
|
3316
|
-
|
|
3540
|
+
// Parse first key-value pair
|
|
3541
|
+
entries.push(this.parseMultiSelectHashEntry());
|
|
3542
|
+
// Parse remaining comma-separated entries
|
|
3543
|
+
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
3544
|
+
entries.push(this.parseMultiSelectHashEntry());
|
|
3545
|
+
}
|
|
3546
|
+
this.lexer.consume(6 /* TokenType.RBRACE */);
|
|
3547
|
+
return {
|
|
3548
|
+
type: "multiSelectHash",
|
|
3549
|
+
entries,
|
|
3550
|
+
};
|
|
3317
3551
|
}
|
|
3318
3552
|
/**
|
|
3319
|
-
*
|
|
3553
|
+
* Parse a single key: value entry in a multi-select hash
|
|
3320
3554
|
*/
|
|
3321
|
-
|
|
3555
|
+
parseMultiSelectHashEntry() {
|
|
3556
|
+
const key = this.parseIdentifier();
|
|
3557
|
+
this.lexer.consume(9 /* TokenType.COLON */);
|
|
3558
|
+
const value = this.expression(0);
|
|
3559
|
+
return { key, value };
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Parse object projection: creates the objectProject node and parses
|
|
3563
|
+
* any continuation operators.
|
|
3564
|
+
*/
|
|
3565
|
+
parseObjectProjection(expression) {
|
|
3322
3566
|
var _a;
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3567
|
+
return {
|
|
3568
|
+
type: "objectProject",
|
|
3569
|
+
expression,
|
|
3570
|
+
projection: (_a = this.parseProjectionContinuation()) !== null && _a !== void 0 ? _a : CURRENT_NODE,
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
/**
|
|
3574
|
+
* Parse dot-bracket: foo.[...] which could be:
|
|
3575
|
+
* - Multi-select list: foo.[a, b]
|
|
3576
|
+
* - Array projection: foo.[*]
|
|
3577
|
+
*
|
|
3578
|
+
* Note: foo.[0], foo.[:], and foo.['id'] are rejected as syntax errors.
|
|
3579
|
+
*/
|
|
3580
|
+
parseDotBracket(left) {
|
|
3581
|
+
this.lexer.consume(3 /* TokenType.LBRACKET */);
|
|
3582
|
+
const token = this.lexer.peek();
|
|
3583
|
+
// After .[, only STAR and expressions are valid
|
|
3584
|
+
// NUMBER, COLON, RAW_STRING are NOT valid (use foo[0], foo[:], foo['id'] instead)
|
|
3585
|
+
if (token.type === 28 /* TokenType.STAR */) {
|
|
3586
|
+
// .[*] is valid as projection
|
|
3587
|
+
this.lexer.advance();
|
|
3588
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3589
|
+
const projectNode = {
|
|
3590
|
+
type: "project",
|
|
3591
|
+
expression: left,
|
|
3592
|
+
projection: CURRENT_NODE,
|
|
3593
|
+
};
|
|
3594
|
+
return this.parseProjectionRHS(projectNode);
|
|
3333
3595
|
}
|
|
3334
|
-
//
|
|
3335
|
-
if (
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
while (isDigit(ch)) {
|
|
3340
|
-
ch = this.advanceCharCode();
|
|
3341
|
-
}
|
|
3596
|
+
// NUMBER, COLON, RAW_STRING after .[ are syntax errors
|
|
3597
|
+
if (token.type === 37 /* TokenType.NUMBER */ ||
|
|
3598
|
+
token.type === 9 /* TokenType.COLON */ ||
|
|
3599
|
+
token.type === 35 /* TokenType.RAW_STRING */) {
|
|
3600
|
+
throw this.unexpectedToken(token, "expression or '*'", "after '.['");
|
|
3342
3601
|
}
|
|
3343
|
-
//
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
const digit = (_a = this.input[this.pos]) !== null && _a !== void 0 ? _a : EOF_DESCRIPTION;
|
|
3353
|
-
throw new InvalidTokenError(this.input, start, "number", `invalid exponent: ${digit}`);
|
|
3354
|
-
}
|
|
3355
|
-
while (isDigit(ch)) {
|
|
3356
|
-
ch = this.advanceCharCode();
|
|
3602
|
+
// Multi-select list: [expr1, expr2, ...]
|
|
3603
|
+
const expressions = [];
|
|
3604
|
+
expressions.push(this.expression(0));
|
|
3605
|
+
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
3606
|
+
const next = this.lexer.peek();
|
|
3607
|
+
if (next.type === 37 /* TokenType.NUMBER */ ||
|
|
3608
|
+
next.type === 9 /* TokenType.COLON */ ||
|
|
3609
|
+
next.type === 35 /* TokenType.RAW_STRING */) {
|
|
3610
|
+
throw this.unexpectedToken(next, "expression", "after '.['");
|
|
3357
3611
|
}
|
|
3612
|
+
expressions.push(this.expression(0));
|
|
3358
3613
|
}
|
|
3359
|
-
|
|
3360
|
-
return {
|
|
3361
|
-
type: 37 /* TokenType.NUMBER */,
|
|
3362
|
-
text,
|
|
3363
|
-
value: parseFloat(text),
|
|
3364
|
-
offset: start,
|
|
3365
|
-
};
|
|
3366
|
-
}
|
|
3367
|
-
/**
|
|
3368
|
-
* Scan variable: $[a-zA-Z_][a-zA-Z0-9_]*
|
|
3369
|
-
*/
|
|
3370
|
-
scanVariable(start) {
|
|
3371
|
-
// Start is at "$". Consume first variable-name character.
|
|
3372
|
-
let ch = this.advanceCharCode();
|
|
3373
|
-
while (isIdentChar(ch)) {
|
|
3374
|
-
ch = this.advanceCharCode();
|
|
3375
|
-
}
|
|
3376
|
-
const text = this.input.slice(start, this.pos);
|
|
3614
|
+
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3377
3615
|
return {
|
|
3378
|
-
type:
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3616
|
+
type: "pipe",
|
|
3617
|
+
lhs: left,
|
|
3618
|
+
rhs: {
|
|
3619
|
+
type: "multiSelectList",
|
|
3620
|
+
expressions,
|
|
3621
|
+
},
|
|
3622
|
+
dotSyntax: true,
|
|
3382
3623
|
};
|
|
3383
3624
|
}
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
*/
|
|
3387
|
-
scanIdentifier(start) {
|
|
3388
|
-
let ch = this.advanceCharCode();
|
|
3389
|
-
while (isIdentChar(ch)) {
|
|
3390
|
-
ch = this.advanceCharCode();
|
|
3391
|
-
}
|
|
3392
|
-
const text = this.input.slice(start, this.pos);
|
|
3393
|
-
// Check for keywords
|
|
3394
|
-
const keywordType = KEYWORDS[text];
|
|
3395
|
-
if (keywordType !== undefined) {
|
|
3396
|
-
// Return keyword token with appropriate value
|
|
3397
|
-
if (keywordType === 38 /* TokenType.NULL */) {
|
|
3398
|
-
return {
|
|
3399
|
-
type: 38 /* TokenType.NULL */,
|
|
3400
|
-
text: "null",
|
|
3401
|
-
value: null,
|
|
3402
|
-
offset: start,
|
|
3403
|
-
};
|
|
3404
|
-
}
|
|
3405
|
-
else if (keywordType === 39 /* TokenType.TRUE */) {
|
|
3406
|
-
return {
|
|
3407
|
-
type: 39 /* TokenType.TRUE */,
|
|
3408
|
-
text: "true",
|
|
3409
|
-
value: true,
|
|
3410
|
-
offset: start,
|
|
3411
|
-
};
|
|
3412
|
-
}
|
|
3413
|
-
else {
|
|
3414
|
-
return {
|
|
3415
|
-
type: 40 /* TokenType.FALSE */,
|
|
3416
|
-
text: "false",
|
|
3417
|
-
value: false,
|
|
3418
|
-
offset: start,
|
|
3419
|
-
};
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
return { type: 33 /* TokenType.IDENTIFIER */, text, value: text, offset: start };
|
|
3625
|
+
unexpectedToken(token, expected, context) {
|
|
3626
|
+
return new UnexpectedTokenError(this.input, token.offset, token.text || EOF_DESCRIPTION, expected, context);
|
|
3423
3627
|
}
|
|
3424
|
-
|
|
3425
|
-
|
|
3628
|
+
}
|
|
3629
|
+
function trimJsonWhitespace(value) {
|
|
3630
|
+
let start = 0;
|
|
3631
|
+
let end = value.length;
|
|
3632
|
+
while (start < end && isWhitespace(value.charCodeAt(start))) {
|
|
3633
|
+
++start;
|
|
3426
3634
|
}
|
|
3427
|
-
|
|
3428
|
-
|
|
3635
|
+
while (end > start && isWhitespace(value.charCodeAt(end - 1))) {
|
|
3636
|
+
--end;
|
|
3429
3637
|
}
|
|
3638
|
+
return value.slice(start, end);
|
|
3430
3639
|
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3640
|
+
|
|
3641
|
+
class BaseAccessor {
|
|
3642
|
+
constructor(selector) {
|
|
3643
|
+
this.selector = selector;
|
|
3644
|
+
}
|
|
3433
3645
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3646
|
+
class ReadOnlyAccessor extends BaseAccessor {
|
|
3647
|
+
constructor(selector) {
|
|
3648
|
+
super(selector);
|
|
3649
|
+
}
|
|
3650
|
+
isValidContext() {
|
|
3651
|
+
return true;
|
|
3652
|
+
}
|
|
3653
|
+
getOrThrow(context, rootContext) {
|
|
3654
|
+
return this.get(context, rootContext);
|
|
3655
|
+
}
|
|
3656
|
+
set() {
|
|
3657
|
+
// ignored
|
|
3658
|
+
}
|
|
3659
|
+
setOrThrow() {
|
|
3660
|
+
throw readOnlyError(this.selector, "set");
|
|
3661
|
+
}
|
|
3662
|
+
delete() {
|
|
3663
|
+
// ignored
|
|
3664
|
+
}
|
|
3665
|
+
deleteOrThrow() {
|
|
3666
|
+
throw readOnlyError(this.selector, "delete");
|
|
3667
|
+
}
|
|
3436
3668
|
}
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
(
|
|
3440
|
-
|
|
3441
|
-
|
|
3669
|
+
class ConstantAccessor extends ReadOnlyAccessor {
|
|
3670
|
+
constructor(selector, value) {
|
|
3671
|
+
super(selector);
|
|
3672
|
+
this.value = value;
|
|
3673
|
+
}
|
|
3674
|
+
get() {
|
|
3675
|
+
return this.value;
|
|
3676
|
+
}
|
|
3442
3677
|
}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
(
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3678
|
+
class ContextAccessor extends ReadOnlyAccessor {
|
|
3679
|
+
constructor() {
|
|
3680
|
+
super(CURRENT_NODE);
|
|
3681
|
+
}
|
|
3682
|
+
get(context) {
|
|
3683
|
+
return context;
|
|
3684
|
+
}
|
|
3449
3685
|
}
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
type: "current",
|
|
3454
|
-
});
|
|
3455
|
-
const ROOT_NODE = Object.freeze({
|
|
3456
|
-
type: "root",
|
|
3457
|
-
});
|
|
3458
|
-
// Binding power constants (higher = tighter binding)
|
|
3459
|
-
const BP_PIPE = 1;
|
|
3460
|
-
const BP_TERNARY = 2;
|
|
3461
|
-
const BP_OR = 3;
|
|
3462
|
-
const BP_AND = 4;
|
|
3463
|
-
const BP_COMPARE = 5;
|
|
3464
|
-
const BP_ADD = 6;
|
|
3465
|
-
const BP_MUL = 7;
|
|
3466
|
-
const BP_FLATTEN = 9;
|
|
3467
|
-
const BP_FILTER = 21;
|
|
3468
|
-
const BP_DOT = 40;
|
|
3469
|
-
const BP_NOT = 45;
|
|
3470
|
-
const BP_BRACKET = 55;
|
|
3471
|
-
// Projection stop threshold: operators below this terminate projections
|
|
3472
|
-
// Separates terminators (pipe, ternary, or, and, comparisons) from continuators (dot, brackets)
|
|
3473
|
-
const PROJECTION_STOP_BP = 10;
|
|
3474
|
-
// Binding power table (higher = tighter binding)
|
|
3475
|
-
// Use array indexed by TokenType for fast lookup
|
|
3476
|
-
const TOKEN_BP = (() => {
|
|
3477
|
-
const bp = new Array(TOKEN_LIMIT).fill(0);
|
|
3478
|
-
// Terminators (lowest precedence) - already 0
|
|
3479
|
-
// TokenType.RPAREN, RBRACKET, RBRACE, COMMA - all 0
|
|
3480
|
-
// Binary operators (low to high)
|
|
3481
|
-
bp[10 /* TokenType.PIPE */] = BP_PIPE; // |
|
|
3482
|
-
bp[29 /* TokenType.QUESTION */] = BP_TERNARY; // ?:
|
|
3483
|
-
bp[11 /* TokenType.OR */] = BP_OR; // ||
|
|
3484
|
-
bp[12 /* TokenType.AND */] = BP_AND; // &&
|
|
3485
|
-
// Comparison operators (all same precedence - non-associative, cannot chain)
|
|
3486
|
-
bp[14 /* TokenType.EQ */] = BP_COMPARE; // ==
|
|
3487
|
-
bp[15 /* TokenType.NEQ */] = BP_COMPARE; // !=
|
|
3488
|
-
bp[16 /* TokenType.LT */] = BP_COMPARE; // <
|
|
3489
|
-
bp[17 /* TokenType.LTE */] = BP_COMPARE; // <=
|
|
3490
|
-
bp[18 /* TokenType.GT */] = BP_COMPARE; // >
|
|
3491
|
-
bp[19 /* TokenType.GTE */] = BP_COMPARE; // >=
|
|
3492
|
-
bp[20 /* TokenType.PLUS */] = BP_ADD; // +
|
|
3493
|
-
bp[21 /* TokenType.MINUS */] = BP_ADD; // -
|
|
3494
|
-
bp[28 /* TokenType.STAR */] = BP_MUL; // * (multiplication in led context)
|
|
3495
|
-
bp[22 /* TokenType.MULTIPLY */] = BP_MUL; // ×
|
|
3496
|
-
bp[23 /* TokenType.DIVIDE */] = BP_MUL; // /, ÷
|
|
3497
|
-
bp[24 /* TokenType.MODULO */] = BP_MUL; // %
|
|
3498
|
-
bp[25 /* TokenType.INT_DIVIDE */] = BP_MUL; // //
|
|
3499
|
-
// Projection operators
|
|
3500
|
-
// flatten has lower bp (9) than star/filter to allow chaining: foo[*][] or foo[?x][].bar
|
|
3501
|
-
bp[32 /* TokenType.FLATTEN_BRACKET */] = BP_FLATTEN; // []
|
|
3502
|
-
bp[31 /* TokenType.FILTER_BRACKET */] = BP_FILTER; // [?...]
|
|
3503
|
-
// Postfix operators (high precedence)
|
|
3504
|
-
bp[7 /* TokenType.DOT */] = BP_DOT; // .
|
|
3505
|
-
// Prefix operators
|
|
3506
|
-
bp[13 /* TokenType.NOT */] = BP_NOT; // !
|
|
3507
|
-
// Bracket access (highest)
|
|
3508
|
-
bp[3 /* TokenType.LBRACKET */] = BP_BRACKET; // [n], ['id'], [n:], etc.
|
|
3509
|
-
return bp;
|
|
3510
|
-
})();
|
|
3511
|
-
/**
|
|
3512
|
-
* Hand-written parser for JSON Selectors.
|
|
3513
|
-
*
|
|
3514
|
-
* Uses precedence-climbing (Pratt parsing) with binding power (0-55 range) to handle
|
|
3515
|
-
* operator precedence efficiently. The parser uses two main methods:
|
|
3516
|
-
* - nud() for prefix/primary expressions (no left context)
|
|
3517
|
-
* - led() for infix/postfix operators (with left context)
|
|
3518
|
-
*/
|
|
3519
|
-
class Parser {
|
|
3520
|
-
constructor(input, options) {
|
|
3521
|
-
this.input = input;
|
|
3522
|
-
this.lexer = new Lexer(input, {
|
|
3523
|
-
rawStringBackslashEscape: options === null || options === void 0 ? void 0 : options.rawStringBackslashEscape,
|
|
3524
|
-
});
|
|
3525
|
-
this.strictJsonLiterals = (options === null || options === void 0 ? void 0 : options.strictJsonLiterals) === true;
|
|
3686
|
+
class RootContextAccessor extends ReadOnlyAccessor {
|
|
3687
|
+
constructor() {
|
|
3688
|
+
super(ROOT_NODE);
|
|
3526
3689
|
}
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
*/
|
|
3530
|
-
parse() {
|
|
3531
|
-
const result = this.expression(0);
|
|
3532
|
-
const token = this.lexer.peek();
|
|
3533
|
-
if (token.type !== 0 /* TokenType.EOF */) {
|
|
3534
|
-
throw this.unexpectedToken(token, EOF_DESCRIPTION);
|
|
3535
|
-
}
|
|
3536
|
-
return result;
|
|
3690
|
+
get(context, rootContext = context) {
|
|
3691
|
+
return rootContext;
|
|
3537
3692
|
}
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
*/
|
|
3544
|
-
expression(rbp) {
|
|
3545
|
-
return this.expressionFrom(this.nud(), rbp);
|
|
3693
|
+
}
|
|
3694
|
+
class EvaluateAccessor extends ReadOnlyAccessor {
|
|
3695
|
+
constructor(selector, options) {
|
|
3696
|
+
super(selector);
|
|
3697
|
+
this.options = options;
|
|
3546
3698
|
}
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
* Applies led (left denotation) operators while their binding power exceeds rbp.
|
|
3550
|
-
*/
|
|
3551
|
-
expressionFrom(left, rbp) {
|
|
3552
|
-
let current = left;
|
|
3553
|
-
let token = this.lexer.peek();
|
|
3554
|
-
while (token.type !== 0 /* TokenType.EOF */ && rbp < TOKEN_BP[token.type]) {
|
|
3555
|
-
current = this.led(current, token);
|
|
3556
|
-
token = this.lexer.peek();
|
|
3557
|
-
}
|
|
3558
|
-
return current;
|
|
3699
|
+
get(context, rootContext = context) {
|
|
3700
|
+
return evaluateJsonSelector(this.selector, context, Object.assign(Object.assign({}, this.options), { rootContext }));
|
|
3559
3701
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3702
|
+
}
|
|
3703
|
+
/** Compiles a selector AST into an {@link UnboundAccessor} that can be repeatedly bound to different contexts. */
|
|
3704
|
+
function makeJsonSelectorAccessor(selector, options) {
|
|
3705
|
+
var _a;
|
|
3706
|
+
return makeAccessorInternal(selector, {
|
|
3707
|
+
functionProvider: (_a = options === null || options === void 0 ? void 0 : options.functionProvider) !== null && _a !== void 0 ? _a : getBuiltinFunctionProvider(),
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
function makeAccessorInternal(selector, options) {
|
|
3711
|
+
return visitJsonSelector(selector, {
|
|
3712
|
+
current() {
|
|
3713
|
+
return new ContextAccessor();
|
|
3714
|
+
},
|
|
3715
|
+
root() {
|
|
3716
|
+
return new RootContextAccessor();
|
|
3717
|
+
},
|
|
3718
|
+
literal(selector) {
|
|
3719
|
+
return new ConstantAccessor(selector, selector.value);
|
|
3720
|
+
},
|
|
3721
|
+
identifier(selector) {
|
|
3722
|
+
const { id } = selector;
|
|
3723
|
+
const Accessor = class extends BaseAccessor {
|
|
3724
|
+
constructor() {
|
|
3725
|
+
super(selector);
|
|
3726
|
+
}
|
|
3727
|
+
isValidContext(context) {
|
|
3728
|
+
return isObject(context);
|
|
3729
|
+
}
|
|
3730
|
+
get(context) {
|
|
3731
|
+
return getField(context, id);
|
|
3732
|
+
}
|
|
3733
|
+
getOrThrow(context) {
|
|
3734
|
+
var _a;
|
|
3735
|
+
const objectContext = requireObjectContext(context, selector, "get");
|
|
3736
|
+
return (_a = objectContext[id]) !== null && _a !== void 0 ? _a : null;
|
|
3737
|
+
}
|
|
3738
|
+
set(value, context) {
|
|
3739
|
+
if (isObject(context)) {
|
|
3740
|
+
context[id] = value;
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
setOrThrow(value, context) {
|
|
3744
|
+
const objectContext = requireObjectContext(context, selector, "set");
|
|
3745
|
+
objectContext[id] = value;
|
|
3746
|
+
}
|
|
3747
|
+
delete(context) {
|
|
3748
|
+
if (isObject(context)) {
|
|
3749
|
+
delete context[id];
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
deleteOrThrow(context) {
|
|
3753
|
+
const objectContext = requireObjectContext(context, selector, "delete");
|
|
3754
|
+
delete objectContext[id];
|
|
3755
|
+
}
|
|
3756
|
+
};
|
|
3757
|
+
return new Accessor();
|
|
3758
|
+
},
|
|
3759
|
+
fieldAccess(selector) {
|
|
3760
|
+
const { expression, field } = selector;
|
|
3761
|
+
const base = makeAccessorInternal(expression, options);
|
|
3762
|
+
const Accessor = class extends BaseAccessor {
|
|
3763
|
+
constructor() {
|
|
3764
|
+
super(selector);
|
|
3765
|
+
}
|
|
3766
|
+
isValidContext(context, rootContext = context) {
|
|
3767
|
+
const value = base.get(context, rootContext);
|
|
3768
|
+
return isObject(value);
|
|
3769
|
+
}
|
|
3770
|
+
get(context, rootContext = context) {
|
|
3771
|
+
const obj = base.get(context, rootContext);
|
|
3772
|
+
return getField(obj, field);
|
|
3773
|
+
}
|
|
3774
|
+
getOrThrow(context, rootContext = context) {
|
|
3775
|
+
var _a;
|
|
3776
|
+
const obj = requireObjectParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
3777
|
+
return (_a = obj[field]) !== null && _a !== void 0 ? _a : null;
|
|
3778
|
+
}
|
|
3779
|
+
set(value, context, rootContext = context) {
|
|
3780
|
+
const obj = base.get(context, rootContext);
|
|
3781
|
+
if (isObject(obj)) {
|
|
3782
|
+
obj[field] = value;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
setOrThrow(value, context, rootContext = context) {
|
|
3786
|
+
const obj = requireObjectParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
3787
|
+
obj[field] = value;
|
|
3788
|
+
}
|
|
3789
|
+
delete(context, rootContext = context) {
|
|
3790
|
+
const obj = base.get(context, rootContext);
|
|
3791
|
+
if (isObject(obj)) {
|
|
3792
|
+
delete obj[field];
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
deleteOrThrow(context, rootContext = context) {
|
|
3796
|
+
const obj = requireObjectParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
3797
|
+
delete obj[field];
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3800
|
+
return new Accessor();
|
|
3801
|
+
},
|
|
3802
|
+
indexAccess(selector) {
|
|
3803
|
+
const { expression, index } = selector;
|
|
3804
|
+
const base = makeAccessorInternal(expression, options);
|
|
3805
|
+
const Accessor = class extends BaseAccessor {
|
|
3806
|
+
constructor() {
|
|
3807
|
+
super(selector);
|
|
3808
|
+
}
|
|
3809
|
+
isValidContext(context, rootContext = context) {
|
|
3810
|
+
const value = base.get(context, rootContext);
|
|
3811
|
+
return isArray(value);
|
|
3812
|
+
}
|
|
3813
|
+
get(context, rootContext = context) {
|
|
3814
|
+
const arr = base.get(context, rootContext);
|
|
3815
|
+
return getIndex(arr, index);
|
|
3816
|
+
}
|
|
3817
|
+
getOrThrow(context, rootContext = context) {
|
|
3818
|
+
var _a;
|
|
3819
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
3820
|
+
return (_a = arr[index < 0 ? arr.length + index : index]) !== null && _a !== void 0 ? _a : null;
|
|
3821
|
+
}
|
|
3822
|
+
set(value, context, rootContext = context) {
|
|
3823
|
+
const arr = base.get(context, rootContext);
|
|
3824
|
+
if (isArray(arr)) {
|
|
3825
|
+
arr[index < 0 ? arr.length + index : index] = value;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
setOrThrow(value, context, rootContext = context) {
|
|
3829
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
3830
|
+
const resolvedIndex = resolveInBoundsIndex(arr, index, selector, "set");
|
|
3831
|
+
arr[resolvedIndex] = value;
|
|
3832
|
+
}
|
|
3833
|
+
delete(context, rootContext = context) {
|
|
3834
|
+
const arr = base.get(context, rootContext);
|
|
3835
|
+
if (isArray(arr)) {
|
|
3836
|
+
arr.splice(index, 1);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
deleteOrThrow(context, rootContext = context) {
|
|
3840
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
3841
|
+
const resolvedIndex = resolveInBoundsIndex(arr, index, selector, "delete");
|
|
3842
|
+
arr.splice(resolvedIndex, 1);
|
|
3843
|
+
}
|
|
3844
|
+
};
|
|
3845
|
+
return new Accessor();
|
|
3846
|
+
},
|
|
3847
|
+
idAccess(selector) {
|
|
3848
|
+
const { expression, id } = selector;
|
|
3849
|
+
const base = makeAccessorInternal(expression, options);
|
|
3850
|
+
const Accessor = class extends BaseAccessor {
|
|
3851
|
+
constructor() {
|
|
3852
|
+
super(selector);
|
|
3853
|
+
}
|
|
3854
|
+
isValidContext(context, rootContext = context) {
|
|
3855
|
+
const value = base.get(context, rootContext);
|
|
3856
|
+
return isArray(value);
|
|
3857
|
+
}
|
|
3858
|
+
get(context, rootContext = context) {
|
|
3859
|
+
const arr = base.get(context, rootContext);
|
|
3860
|
+
return findId(arr, id);
|
|
3861
|
+
}
|
|
3862
|
+
getOrThrow(context, rootContext = context) {
|
|
3863
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
3864
|
+
const index = findIdIndex(arr, id);
|
|
3865
|
+
return index >= 0 ? arr[index] : null;
|
|
3866
|
+
}
|
|
3867
|
+
set(value, context, rootContext = context) {
|
|
3868
|
+
const arr = base.get(context, rootContext);
|
|
3869
|
+
if (isArray(arr)) {
|
|
3870
|
+
const index = findIdIndex(arr, id);
|
|
3871
|
+
if (index >= 0) {
|
|
3872
|
+
arr[index] = value;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
setOrThrow(value, context, rootContext = context) {
|
|
3877
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
3878
|
+
const index = requireIdIndex(arr, id, selector, "set");
|
|
3879
|
+
arr[index] = value;
|
|
3880
|
+
}
|
|
3881
|
+
delete(context, rootContext = context) {
|
|
3882
|
+
const arr = base.get(context, rootContext);
|
|
3883
|
+
if (isArray(arr)) {
|
|
3884
|
+
const index = findIdIndex(arr, id);
|
|
3885
|
+
if (index >= 0) {
|
|
3886
|
+
arr.splice(index, 1);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
deleteOrThrow(context, rootContext = context) {
|
|
3891
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
3892
|
+
const index = requireIdIndex(arr, id, selector, "delete");
|
|
3893
|
+
arr.splice(index, 1);
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
return new Accessor();
|
|
3897
|
+
},
|
|
3898
|
+
project(selector) {
|
|
3899
|
+
const { expression, projection } = selector;
|
|
3900
|
+
const base = makeAccessorInternal(expression, options);
|
|
3901
|
+
const proj = !isIdentityProjection(projection)
|
|
3902
|
+
? makeAccessorInternal(projection, options)
|
|
3903
|
+
: undefined;
|
|
3904
|
+
const Accessor = class extends BaseAccessor {
|
|
3905
|
+
constructor() {
|
|
3906
|
+
super(selector);
|
|
3907
|
+
}
|
|
3908
|
+
isValidContext(context, rootContext = context) {
|
|
3909
|
+
const value = base.get(context, rootContext);
|
|
3910
|
+
return isArray(value);
|
|
3911
|
+
}
|
|
3912
|
+
get(context, rootContext = context) {
|
|
3913
|
+
const arr = base.get(context, rootContext);
|
|
3914
|
+
return project(arr, projection, Object.assign(Object.assign({}, options), { rootContext }));
|
|
3915
|
+
}
|
|
3916
|
+
getOrThrow(context, rootContext = context) {
|
|
3917
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
3918
|
+
return project(arr, projection, Object.assign(Object.assign({}, options), { rootContext }));
|
|
3919
|
+
}
|
|
3920
|
+
set(value, context, rootContext = context) {
|
|
3921
|
+
const arr = base.get(context, rootContext);
|
|
3922
|
+
if (isArray(arr)) {
|
|
3923
|
+
if (proj) {
|
|
3924
|
+
for (const element of arr) {
|
|
3925
|
+
proj.set(value, element, rootContext);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
else {
|
|
3929
|
+
replaceArray(arr, asArray(value));
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
setOrThrow(value, context, rootContext = context) {
|
|
3934
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
3935
|
+
if (proj) {
|
|
3936
|
+
for (const element of arr) {
|
|
3937
|
+
proj.setOrThrow(value, element, rootContext);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
else {
|
|
3941
|
+
replaceArray(arr, asArray(value));
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
delete(context, rootContext = context) {
|
|
3945
|
+
const arr = base.get(context, rootContext);
|
|
3946
|
+
if (isArray(arr)) {
|
|
3947
|
+
if (proj) {
|
|
3948
|
+
for (const element of arr) {
|
|
3949
|
+
proj.delete(element, rootContext);
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
else {
|
|
3953
|
+
arr.length = 0;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
deleteOrThrow(context, rootContext = context) {
|
|
3958
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
3959
|
+
if (proj) {
|
|
3960
|
+
for (const element of arr) {
|
|
3961
|
+
proj.deleteOrThrow(element, rootContext);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
else {
|
|
3965
|
+
arr.length = 0;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
};
|
|
3969
|
+
return new Accessor();
|
|
3970
|
+
},
|
|
3971
|
+
objectProject(selector) {
|
|
3972
|
+
const { expression, projection } = selector;
|
|
3973
|
+
const base = makeAccessorInternal(expression, options);
|
|
3974
|
+
const proj = !isIdentityProjection(projection)
|
|
3975
|
+
? makeAccessorInternal(projection, options)
|
|
3976
|
+
: undefined;
|
|
3977
|
+
const Accessor = class extends BaseAccessor {
|
|
3978
|
+
constructor() {
|
|
3979
|
+
super(selector);
|
|
3980
|
+
}
|
|
3981
|
+
isValidContext(context, rootContext = context) {
|
|
3982
|
+
const value = base.get(context, rootContext);
|
|
3983
|
+
return isObject(value);
|
|
3984
|
+
}
|
|
3985
|
+
get(context, rootContext = context) {
|
|
3986
|
+
const obj = base.get(context, rootContext);
|
|
3987
|
+
return objectProject(obj, projection, Object.assign(Object.assign({}, options), { rootContext }));
|
|
3988
|
+
}
|
|
3989
|
+
getOrThrow(context, rootContext = context) {
|
|
3990
|
+
const obj = requireObjectParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
3991
|
+
return objectProject(obj, projection, Object.assign(Object.assign({}, options), { rootContext }));
|
|
3992
|
+
}
|
|
3993
|
+
set(value, context, rootContext = context) {
|
|
3994
|
+
const obj = base.get(context, rootContext);
|
|
3995
|
+
if (isObject(obj)) {
|
|
3996
|
+
if (proj) {
|
|
3997
|
+
for (const v of Object.values(obj)) {
|
|
3998
|
+
proj.set(value, v, rootContext);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
else {
|
|
4002
|
+
for (const key of Object.keys(obj)) {
|
|
4003
|
+
obj[key] = value;
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
3576
4007
|
}
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
4008
|
+
setOrThrow(value, context, rootContext = context) {
|
|
4009
|
+
const obj = requireObjectParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
4010
|
+
if (proj) {
|
|
4011
|
+
for (const v of Object.values(obj)) {
|
|
4012
|
+
proj.setOrThrow(value, v, rootContext);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
else {
|
|
4016
|
+
for (const key of Object.keys(obj)) {
|
|
4017
|
+
obj[key] = value;
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
3581
4020
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
type: "flatten",
|
|
3597
|
-
expression: CURRENT_NODE,
|
|
3598
|
-
};
|
|
3599
|
-
return this.parseProjectionRHS(flattenNode);
|
|
3600
|
-
}
|
|
3601
|
-
case 31 /* TokenType.FILTER_BRACKET */:
|
|
3602
|
-
return this.parseFilterExpression();
|
|
3603
|
-
case 27 /* TokenType.DOLLAR */:
|
|
3604
|
-
this.lexer.advance();
|
|
3605
|
-
return ROOT_NODE;
|
|
3606
|
-
case 41 /* TokenType.VARIABLE */:
|
|
3607
|
-
this.lexer.advance();
|
|
3608
|
-
return { type: "variableRef", name: token.value };
|
|
3609
|
-
case 35 /* TokenType.RAW_STRING */:
|
|
3610
|
-
case 39 /* TokenType.TRUE */:
|
|
3611
|
-
case 40 /* TokenType.FALSE */:
|
|
3612
|
-
case 38 /* TokenType.NULL */:
|
|
3613
|
-
case 37 /* TokenType.NUMBER */:
|
|
3614
|
-
this.lexer.advance();
|
|
3615
|
-
return { type: "literal", value: token.value };
|
|
3616
|
-
case 36 /* TokenType.BACKTICK_LITERAL */: {
|
|
3617
|
-
this.lexer.advance();
|
|
3618
|
-
const content = trimJsonWhitespace(token.value);
|
|
3619
|
-
try {
|
|
3620
|
-
// Type assertion is safe as JSON.parse returns a JsonValue
|
|
3621
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
3622
|
-
const value = JSON.parse(content);
|
|
3623
|
-
return { type: "literal", value, backtickSyntax: true };
|
|
4021
|
+
delete(context, rootContext = context) {
|
|
4022
|
+
const obj = base.get(context, rootContext);
|
|
4023
|
+
if (isObject(obj)) {
|
|
4024
|
+
if (proj) {
|
|
4025
|
+
for (const v of Object.values(obj)) {
|
|
4026
|
+
proj.delete(v, rootContext);
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
else {
|
|
4030
|
+
for (const key of Object.keys(obj)) {
|
|
4031
|
+
delete obj[key];
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
3624
4035
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
4036
|
+
deleteOrThrow(context, rootContext = context) {
|
|
4037
|
+
const obj = requireObjectParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
4038
|
+
if (proj) {
|
|
4039
|
+
for (const v of Object.values(obj)) {
|
|
4040
|
+
proj.deleteOrThrow(v, rootContext);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
else {
|
|
4044
|
+
for (const key of Object.keys(obj)) {
|
|
4045
|
+
delete obj[key];
|
|
4046
|
+
}
|
|
3628
4047
|
}
|
|
3629
|
-
return {
|
|
3630
|
-
type: "literal",
|
|
3631
|
-
value: content.replace(/\\"/g, '"'),
|
|
3632
|
-
backtickSyntax: true,
|
|
3633
|
-
};
|
|
3634
4048
|
}
|
|
3635
|
-
}
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
4049
|
+
};
|
|
4050
|
+
return new Accessor();
|
|
4051
|
+
},
|
|
4052
|
+
filter(selector) {
|
|
4053
|
+
const { expression, condition } = selector;
|
|
4054
|
+
const base = makeAccessorInternal(expression, options);
|
|
4055
|
+
const Accessor = class extends BaseAccessor {
|
|
4056
|
+
constructor() {
|
|
4057
|
+
super(selector);
|
|
4058
|
+
}
|
|
4059
|
+
isValidContext(context, rootContext = context) {
|
|
4060
|
+
const value = base.get(context, rootContext);
|
|
4061
|
+
return isArray(value);
|
|
4062
|
+
}
|
|
4063
|
+
get(context, rootContext = context) {
|
|
4064
|
+
const arr = base.get(context, rootContext);
|
|
4065
|
+
return filter(arr, condition, Object.assign(Object.assign({}, options), { rootContext }));
|
|
4066
|
+
}
|
|
4067
|
+
getOrThrow(context, rootContext = context) {
|
|
4068
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
4069
|
+
return filter(arr, condition, Object.assign(Object.assign({}, options), { rootContext }));
|
|
4070
|
+
}
|
|
4071
|
+
set(value, context, rootContext = context) {
|
|
4072
|
+
const arr = base.get(context, rootContext);
|
|
4073
|
+
if (isArray(arr)) {
|
|
4074
|
+
replaceArray(arr, invertedFilter(arr, condition, Object.assign(Object.assign({}, options), { rootContext })).concat(asArray(value)));
|
|
4075
|
+
}
|
|
4076
|
+
}
|
|
4077
|
+
setOrThrow(value, context, rootContext = context) {
|
|
4078
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
4079
|
+
replaceArray(arr, invertedFilter(arr, condition, Object.assign(Object.assign({}, options), { rootContext })).concat(asArray(value)));
|
|
4080
|
+
}
|
|
4081
|
+
delete(context, rootContext = context) {
|
|
4082
|
+
const arr = base.get(context, rootContext);
|
|
4083
|
+
if (isArray(arr)) {
|
|
4084
|
+
replaceArray(arr, invertedFilter(arr, condition, Object.assign(Object.assign({}, options), { rootContext })));
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
deleteOrThrow(context, rootContext = context) {
|
|
4088
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
4089
|
+
replaceArray(arr, invertedFilter(arr, condition, Object.assign(Object.assign({}, options), { rootContext })));
|
|
4090
|
+
}
|
|
4091
|
+
};
|
|
4092
|
+
return new Accessor();
|
|
4093
|
+
},
|
|
4094
|
+
slice(selector) {
|
|
4095
|
+
const { expression, start, end, step } = selector;
|
|
4096
|
+
const base = makeAccessorInternal(expression, options);
|
|
4097
|
+
const Accessor = class extends BaseAccessor {
|
|
4098
|
+
constructor() {
|
|
4099
|
+
super(selector);
|
|
4100
|
+
}
|
|
4101
|
+
isValidContext(context, rootContext = context) {
|
|
4102
|
+
const value = base.get(context, rootContext);
|
|
4103
|
+
return isArray(value) || typeof value === "string";
|
|
4104
|
+
}
|
|
4105
|
+
get(context, rootContext = context) {
|
|
4106
|
+
const arr = base.get(context, rootContext);
|
|
4107
|
+
return slice(arr, start, end, step);
|
|
4108
|
+
}
|
|
4109
|
+
getOrThrow(context, rootContext = context) {
|
|
4110
|
+
const value = requireArrayOrStringParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
4111
|
+
return slice(value, start, end, step);
|
|
4112
|
+
}
|
|
4113
|
+
set(value, context, rootContext = context) {
|
|
4114
|
+
const arr = base.get(context, rootContext);
|
|
4115
|
+
if (isArray(arr)) {
|
|
4116
|
+
replaceArray(arr, invertedSlice(arr, start, end, step).concat(asArray(value)));
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
setOrThrow(value, context, rootContext = context) {
|
|
4120
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
4121
|
+
replaceArray(arr, invertedSlice(arr, start, end, step).concat(asArray(value)));
|
|
4122
|
+
}
|
|
4123
|
+
delete(context, rootContext = context) {
|
|
4124
|
+
const arr = base.get(context, rootContext);
|
|
4125
|
+
if (isArray(arr)) {
|
|
4126
|
+
replaceArray(arr, invertedSlice(arr, start, end, step));
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
deleteOrThrow(context, rootContext = context) {
|
|
4130
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
4131
|
+
replaceArray(arr, invertedSlice(arr, start, end, step));
|
|
4132
|
+
}
|
|
4133
|
+
};
|
|
4134
|
+
return new Accessor();
|
|
4135
|
+
},
|
|
4136
|
+
flatten(selector) {
|
|
4137
|
+
const { expression } = selector;
|
|
4138
|
+
const base = makeAccessorInternal(expression, options);
|
|
4139
|
+
const Accessor = class extends BaseAccessor {
|
|
4140
|
+
constructor() {
|
|
4141
|
+
super(selector);
|
|
4142
|
+
}
|
|
4143
|
+
isValidContext(context, rootContext = context) {
|
|
4144
|
+
const value = base.get(context, rootContext);
|
|
4145
|
+
return isArray(value);
|
|
4146
|
+
}
|
|
4147
|
+
get(context, rootContext = context) {
|
|
4148
|
+
const arr = base.get(context, rootContext);
|
|
4149
|
+
return flatten(arr);
|
|
4150
|
+
}
|
|
4151
|
+
getOrThrow(context, rootContext = context) {
|
|
4152
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "get");
|
|
4153
|
+
return flatten(arr);
|
|
4154
|
+
}
|
|
4155
|
+
set(value, context, rootContext = context) {
|
|
4156
|
+
const arr = base.get(context, rootContext);
|
|
4157
|
+
if (isArray(arr)) {
|
|
4158
|
+
replaceArray(arr, asArray(value));
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
setOrThrow(value, context, rootContext = context) {
|
|
4162
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "set");
|
|
4163
|
+
replaceArray(arr, asArray(value));
|
|
4164
|
+
}
|
|
4165
|
+
delete(context, rootContext = context) {
|
|
4166
|
+
const arr = base.get(context, rootContext);
|
|
4167
|
+
if (isArray(arr)) {
|
|
4168
|
+
arr.length = 0;
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
deleteOrThrow(context, rootContext = context) {
|
|
4172
|
+
const arr = requireArrayParent(base.getOrThrow(context, rootContext), selector, "delete");
|
|
4173
|
+
arr.length = 0;
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
return new Accessor();
|
|
4177
|
+
},
|
|
4178
|
+
not(selector) {
|
|
4179
|
+
const { expression } = selector;
|
|
4180
|
+
const base = makeAccessorInternal(expression, options);
|
|
4181
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4182
|
+
constructor() {
|
|
4183
|
+
super(selector);
|
|
4184
|
+
}
|
|
4185
|
+
get(context, rootContext = context) {
|
|
4186
|
+
const value = base.get(context, rootContext);
|
|
4187
|
+
return isFalseOrEmpty(value);
|
|
4188
|
+
}
|
|
4189
|
+
};
|
|
4190
|
+
return new Accessor();
|
|
4191
|
+
},
|
|
4192
|
+
compare(selector) {
|
|
4193
|
+
const { lhs, rhs, operator } = selector;
|
|
4194
|
+
const la = makeAccessorInternal(lhs, options);
|
|
4195
|
+
const ra = makeAccessorInternal(rhs, options);
|
|
4196
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4197
|
+
constructor() {
|
|
4198
|
+
super(selector);
|
|
3689
4199
|
}
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
const
|
|
3693
|
-
return
|
|
3694
|
-
type: "pipe",
|
|
3695
|
-
lhs: left,
|
|
3696
|
-
rhs: multiSelectHash,
|
|
3697
|
-
dotSyntax: true,
|
|
3698
|
-
};
|
|
4200
|
+
get(context, rootContext = context) {
|
|
4201
|
+
const lv = la.get(context, rootContext);
|
|
4202
|
+
const rv = ra.get(context, rootContext);
|
|
4203
|
+
return compare(lv, rv, operator);
|
|
3699
4204
|
}
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
4205
|
+
};
|
|
4206
|
+
return new Accessor();
|
|
4207
|
+
},
|
|
4208
|
+
arithmetic(selector) {
|
|
4209
|
+
const la = makeAccessorInternal(selector.lhs, options);
|
|
4210
|
+
const ra = makeAccessorInternal(selector.rhs, options);
|
|
4211
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4212
|
+
constructor() {
|
|
4213
|
+
super(selector);
|
|
3703
4214
|
}
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
// Check if this is a function call: foo.func(args)
|
|
3707
|
-
// This is equivalent to: foo | func(args)
|
|
3708
|
-
if (this.lexer.peek().type === 1 /* TokenType.LPAREN */) {
|
|
3709
|
-
const funcCall = this.parseFunctionCall(field);
|
|
3710
|
-
return {
|
|
3711
|
-
type: "pipe",
|
|
3712
|
-
lhs: left,
|
|
3713
|
-
rhs: funcCall,
|
|
3714
|
-
dotSyntax: true,
|
|
3715
|
-
};
|
|
4215
|
+
get(context, rootContext = context) {
|
|
4216
|
+
return performArithmetic(la.get(context, rootContext), ra.get(context, rootContext), selector.operator);
|
|
3716
4217
|
}
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
case 21 /* TokenType.MINUS */:
|
|
3755
|
-
return this.parseArithmetic(left, "-", BP_ADD);
|
|
3756
|
-
case 28 /* TokenType.STAR */:
|
|
3757
|
-
case 22 /* TokenType.MULTIPLY */:
|
|
3758
|
-
return this.parseArithmetic(left, "*", BP_MUL);
|
|
3759
|
-
case 23 /* TokenType.DIVIDE */:
|
|
3760
|
-
return this.parseArithmetic(left, "/", BP_MUL);
|
|
3761
|
-
case 24 /* TokenType.MODULO */:
|
|
3762
|
-
return this.parseArithmetic(left, "%", BP_MUL);
|
|
3763
|
-
case 25 /* TokenType.INT_DIVIDE */:
|
|
3764
|
-
return this.parseArithmetic(left, "//", BP_MUL);
|
|
3765
|
-
case 29 /* TokenType.QUESTION */: {
|
|
3766
|
-
this.lexer.advance();
|
|
3767
|
-
const consequent = this.expression(0);
|
|
3768
|
-
this.lexer.consume(9 /* TokenType.COLON */);
|
|
3769
|
-
return {
|
|
3770
|
-
type: "ternary",
|
|
3771
|
-
condition: left,
|
|
3772
|
-
consequent,
|
|
3773
|
-
// NOTE: This differs from jmespath-community/typescript-jmespath, which
|
|
3774
|
-
// parses the false branch with expression(0).
|
|
3775
|
-
//
|
|
3776
|
-
// We intentionally parse with BP_TERNARY - 1 so the current ternary keeps
|
|
3777
|
-
// ownership of lower-precedence operators like pipe. This yields:
|
|
3778
|
-
// a ? b : c | d => (a ? b : c) | d
|
|
3779
|
-
//
|
|
3780
|
-
// This follows JEP-21 exactly:
|
|
3781
|
-
// - "higher precedence than the `|` pipe expression and lower precedence
|
|
3782
|
-
// than the `||` and `&&` logical expressions"
|
|
3783
|
-
// - "Expressions within a ternary conditional expression are evaluated
|
|
3784
|
-
// using a right-associative reading."
|
|
3785
|
-
// Spec: https://github.com/jmespath-community/jmespath.spec/blob/main/jep-0021-ternary-conditionals.md
|
|
3786
|
-
alternate: this.expression(BP_TERNARY - 1),
|
|
3787
|
-
};
|
|
3788
|
-
}
|
|
3789
|
-
case 14 /* TokenType.EQ */:
|
|
3790
|
-
return this.parseCompare(left, "==");
|
|
3791
|
-
case 15 /* TokenType.NEQ */:
|
|
3792
|
-
return this.parseCompare(left, "!=");
|
|
3793
|
-
case 16 /* TokenType.LT */:
|
|
3794
|
-
return this.parseCompare(left, "<");
|
|
3795
|
-
case 17 /* TokenType.LTE */:
|
|
3796
|
-
return this.parseCompare(left, "<=");
|
|
3797
|
-
case 18 /* TokenType.GT */:
|
|
3798
|
-
return this.parseCompare(left, ">");
|
|
3799
|
-
case 19 /* TokenType.GTE */:
|
|
3800
|
-
return this.parseCompare(left, ">=");
|
|
3801
|
-
default:
|
|
3802
|
-
throw this.unexpectedToken(token);
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
|
-
/**
|
|
3806
|
-
* Parse lexical scope expression:
|
|
3807
|
-
* let $a = expr, $b = expr in body
|
|
3808
|
-
*
|
|
3809
|
-
* Called after consuming contextual "let".
|
|
3810
|
-
*/
|
|
3811
|
-
parseLetExpression() {
|
|
3812
|
-
const bindings = [];
|
|
3813
|
-
do {
|
|
3814
|
-
const variable = this.lexer.consume(41 /* TokenType.VARIABLE */);
|
|
3815
|
-
this.lexer.consume(42 /* TokenType.ASSIGN */);
|
|
3816
|
-
bindings.push({
|
|
3817
|
-
name: variable.value,
|
|
3818
|
-
value: this.expression(0),
|
|
3819
|
-
});
|
|
3820
|
-
} while (this.lexer.tryConsume(8 /* TokenType.COMMA */));
|
|
3821
|
-
const token = this.lexer.peek();
|
|
3822
|
-
if (token.type !== 33 /* TokenType.IDENTIFIER */ || token.value !== "in") {
|
|
3823
|
-
throw this.unexpectedToken(token, "'in'", "after let bindings");
|
|
3824
|
-
}
|
|
3825
|
-
this.lexer.advance();
|
|
3826
|
-
return {
|
|
3827
|
-
type: "let",
|
|
3828
|
-
bindings,
|
|
3829
|
-
expression: this.expression(0),
|
|
3830
|
-
};
|
|
3831
|
-
}
|
|
3832
|
-
parseCompare(left, operator) {
|
|
3833
|
-
this.lexer.advance();
|
|
3834
|
-
return {
|
|
3835
|
-
type: "compare",
|
|
3836
|
-
operator,
|
|
3837
|
-
lhs: left,
|
|
3838
|
-
rhs: this.expression(BP_COMPARE),
|
|
3839
|
-
};
|
|
3840
|
-
}
|
|
3841
|
-
parseArithmetic(left, operator, bp) {
|
|
3842
|
-
this.lexer.advance();
|
|
3843
|
-
return {
|
|
3844
|
-
type: "arithmetic",
|
|
3845
|
-
operator,
|
|
3846
|
-
lhs: left,
|
|
3847
|
-
rhs: this.expression(bp),
|
|
3848
|
-
};
|
|
3849
|
-
}
|
|
3850
|
-
foldUnaryArithmeticLiteral(operator, expression) {
|
|
3851
|
-
if (expression.type === "literal" && typeof expression.value === "number") {
|
|
3852
|
-
const value = operator === "+" ? expression.value : -expression.value;
|
|
3853
|
-
return Object.assign({ type: "literal", value }, (expression.backtickSyntax !== undefined && {
|
|
3854
|
-
backtickSyntax: expression.backtickSyntax,
|
|
3855
|
-
}));
|
|
3856
|
-
}
|
|
3857
|
-
return {
|
|
3858
|
-
type: "unaryArithmetic",
|
|
3859
|
-
operator,
|
|
3860
|
-
expression,
|
|
3861
|
-
};
|
|
3862
|
-
}
|
|
3863
|
-
/**
|
|
3864
|
-
* Parse bracket expression: foo[0], foo[:], foo[*], foo['id']
|
|
3865
|
-
* Also handles leading brackets (no LHS): [0], [:], [*], ['id']
|
|
3866
|
-
*
|
|
3867
|
-
* When a bracket appears at the start of an expression or after an operator,
|
|
3868
|
-
* it implicitly applies to @ (current context). For example:
|
|
3869
|
-
* - `[0]` means `@[0]`
|
|
3870
|
-
* - `foo | [*]` means `foo | @[*]`
|
|
3871
|
-
*
|
|
3872
|
-
* Note: foo[] (flatten) is handled separately via FLATTEN_BRACKET token in led()
|
|
3873
|
-
*/
|
|
3874
|
-
parseBracketExpression(left = CURRENT_NODE) {
|
|
3875
|
-
// Must be LBRACKET - consume it and check what follows
|
|
3876
|
-
this.lexer.consume(3 /* TokenType.LBRACKET */);
|
|
3877
|
-
const token = this.lexer.peek();
|
|
3878
|
-
switch (token.type) {
|
|
3879
|
-
case 28 /* TokenType.STAR */: {
|
|
3880
|
-
// At root level, [*...] could be:
|
|
3881
|
-
// - [*] = array projection
|
|
3882
|
-
// - [*.*] = multi-select list containing object projection chain
|
|
3883
|
-
// We need to lookahead to distinguish
|
|
3884
|
-
this.lexer.consume(28 /* TokenType.STAR */);
|
|
3885
|
-
// Check what comes after *
|
|
3886
|
-
const nextToken = this.lexer.peek();
|
|
3887
|
-
if (nextToken.type === 4 /* TokenType.RBRACKET */) {
|
|
3888
|
-
// [*] - array projection
|
|
3889
|
-
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3890
|
-
const projectNode = {
|
|
3891
|
-
type: "project",
|
|
3892
|
-
expression: left,
|
|
3893
|
-
projection: CURRENT_NODE,
|
|
3894
|
-
};
|
|
3895
|
-
return this.parseProjectionRHS(projectNode);
|
|
4218
|
+
};
|
|
4219
|
+
return new Accessor();
|
|
4220
|
+
},
|
|
4221
|
+
unaryArithmetic(selector) {
|
|
4222
|
+
const base = makeAccessorInternal(selector.expression, options);
|
|
4223
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4224
|
+
constructor() {
|
|
4225
|
+
super(selector);
|
|
4226
|
+
}
|
|
4227
|
+
get(context, rootContext = context) {
|
|
4228
|
+
return performUnaryArithmetic(base.get(context, rootContext), selector.operator);
|
|
4229
|
+
}
|
|
4230
|
+
};
|
|
4231
|
+
return new Accessor();
|
|
4232
|
+
},
|
|
4233
|
+
and(selector) {
|
|
4234
|
+
const { lhs, rhs } = selector;
|
|
4235
|
+
const la = makeAccessorInternal(lhs, options);
|
|
4236
|
+
const ra = makeAccessorInternal(rhs, options);
|
|
4237
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4238
|
+
constructor() {
|
|
4239
|
+
super(selector);
|
|
4240
|
+
}
|
|
4241
|
+
get(context, rootContext = context) {
|
|
4242
|
+
const lv = la.get(context, rootContext);
|
|
4243
|
+
return isFalseOrEmpty(lv) ? lv : ra.get(context, rootContext);
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
return new Accessor();
|
|
4247
|
+
},
|
|
4248
|
+
or(selector) {
|
|
4249
|
+
const { lhs, rhs } = selector;
|
|
4250
|
+
const la = makeAccessorInternal(lhs, options);
|
|
4251
|
+
const ra = makeAccessorInternal(rhs, options);
|
|
4252
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4253
|
+
constructor() {
|
|
4254
|
+
super(selector);
|
|
3896
4255
|
}
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
// foo[*.something] is not valid - multi-select list after expression needs .[
|
|
3901
|
-
throw this.unexpectedToken(nextToken, "']'", "after '[*'");
|
|
4256
|
+
get(context, rootContext = context) {
|
|
4257
|
+
const lv = la.get(context, rootContext);
|
|
4258
|
+
return !isFalseOrEmpty(lv) ? lv : ra.get(context, rootContext);
|
|
3902
4259
|
}
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
3915
|
-
return {
|
|
3916
|
-
type: "idAccess",
|
|
3917
|
-
expression: left,
|
|
3918
|
-
id,
|
|
3919
|
-
};
|
|
3920
|
-
}
|
|
3921
|
-
case 37 /* TokenType.NUMBER */:
|
|
3922
|
-
return this.parseIndexOrSlice(left, this.lexer.consume(37 /* TokenType.NUMBER */).value);
|
|
3923
|
-
case 21 /* TokenType.MINUS */: {
|
|
3924
|
-
this.lexer.consume(21 /* TokenType.MINUS */);
|
|
3925
|
-
const numberToken = this.lexer.tryConsume(37 /* TokenType.NUMBER */);
|
|
3926
|
-
if (numberToken) {
|
|
3927
|
-
return this.parseIndexOrSlice(left, -numberToken.value);
|
|
4260
|
+
};
|
|
4261
|
+
return new Accessor();
|
|
4262
|
+
},
|
|
4263
|
+
ternary(selector) {
|
|
4264
|
+
const { condition, consequent, alternate } = selector;
|
|
4265
|
+
const ca = makeAccessorInternal(condition, options);
|
|
4266
|
+
const ta = makeAccessorInternal(consequent, options);
|
|
4267
|
+
const aa = makeAccessorInternal(alternate, options);
|
|
4268
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4269
|
+
constructor() {
|
|
4270
|
+
super(selector);
|
|
3928
4271
|
}
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
4272
|
+
get(context, rootContext = context) {
|
|
4273
|
+
const cv = ca.get(context, rootContext);
|
|
4274
|
+
return isFalseOrEmpty(cv)
|
|
4275
|
+
? aa.get(context, rootContext)
|
|
4276
|
+
: ta.get(context, rootContext);
|
|
3933
4277
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
// Requires foo.[a, b] syntax, not foo[a, b]
|
|
3945
|
-
if (left !== CURRENT_NODE) {
|
|
3946
|
-
throw this.unexpectedToken(token, "number, ':', '*', or string", "after '['");
|
|
4278
|
+
};
|
|
4279
|
+
return new Accessor();
|
|
4280
|
+
},
|
|
4281
|
+
pipe(selector) {
|
|
4282
|
+
const { lhs, rhs } = selector;
|
|
4283
|
+
const la = makeAccessorInternal(lhs, options);
|
|
4284
|
+
const ra = makeAccessorInternal(rhs, options);
|
|
4285
|
+
const Accessor = class extends BaseAccessor {
|
|
4286
|
+
constructor() {
|
|
4287
|
+
super(selector);
|
|
3947
4288
|
}
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4289
|
+
isValidContext(context, rootContext = context) {
|
|
4290
|
+
const lv = la.get(context, rootContext);
|
|
4291
|
+
return ra.isValidContext(lv, rootContext);
|
|
4292
|
+
}
|
|
4293
|
+
get(context, rootContext = context) {
|
|
4294
|
+
const lv = la.get(context, rootContext);
|
|
4295
|
+
return ra.get(lv, rootContext);
|
|
4296
|
+
}
|
|
4297
|
+
getOrThrow(context, rootContext = context) {
|
|
4298
|
+
const lv = la.getOrThrow(context, rootContext);
|
|
4299
|
+
return ra.getOrThrow(lv, rootContext);
|
|
4300
|
+
}
|
|
4301
|
+
set(value, context, rootContext = context) {
|
|
4302
|
+
const lv = la.get(context, rootContext);
|
|
4303
|
+
ra.set(value, lv, rootContext);
|
|
4304
|
+
}
|
|
4305
|
+
setOrThrow(value, context, rootContext = context) {
|
|
4306
|
+
const lv = la.getOrThrow(context, rootContext);
|
|
4307
|
+
ra.setOrThrow(value, lv, rootContext);
|
|
4308
|
+
}
|
|
4309
|
+
delete(context, rootContext = context) {
|
|
4310
|
+
const lv = la.get(context, rootContext);
|
|
4311
|
+
ra.delete(lv, rootContext);
|
|
4312
|
+
}
|
|
4313
|
+
deleteOrThrow(context, rootContext = context) {
|
|
4314
|
+
const lv = la.getOrThrow(context, rootContext);
|
|
4315
|
+
ra.deleteOrThrow(lv, rootContext);
|
|
4316
|
+
}
|
|
4317
|
+
};
|
|
4318
|
+
return new Accessor();
|
|
4319
|
+
},
|
|
4320
|
+
functionCall(selector) {
|
|
4321
|
+
return new EvaluateAccessor(selector, options);
|
|
4322
|
+
},
|
|
4323
|
+
expressionRef(selector) {
|
|
4324
|
+
// Expression references are only meaningful as function arguments
|
|
4325
|
+
return new ConstantAccessor(selector, null);
|
|
4326
|
+
},
|
|
4327
|
+
variableRef(selector) {
|
|
4328
|
+
return new EvaluateAccessor(selector, options);
|
|
4329
|
+
},
|
|
4330
|
+
let(selector) {
|
|
4331
|
+
return new EvaluateAccessor(selector, options);
|
|
4332
|
+
},
|
|
4333
|
+
multiSelectList(selector) {
|
|
4334
|
+
const { expressions } = selector;
|
|
4335
|
+
const accessors = expressions.map((e) => makeAccessorInternal(e, options));
|
|
4336
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4337
|
+
constructor() {
|
|
4338
|
+
super(selector);
|
|
4339
|
+
}
|
|
4340
|
+
get(context, rootContext = context) {
|
|
4341
|
+
if (context == null) {
|
|
4342
|
+
return null;
|
|
4343
|
+
}
|
|
4344
|
+
return accessors.map((a) => a.get(context, rootContext));
|
|
4345
|
+
}
|
|
4346
|
+
};
|
|
4347
|
+
return new Accessor();
|
|
4348
|
+
},
|
|
4349
|
+
multiSelectHash(selector) {
|
|
4350
|
+
const { entries } = selector;
|
|
4351
|
+
const entryAccessors = entries.map(({ key, value }) => ({
|
|
4352
|
+
key,
|
|
4353
|
+
accessor: makeAccessorInternal(value, options),
|
|
4354
|
+
}));
|
|
4355
|
+
const Accessor = class extends ReadOnlyAccessor {
|
|
4356
|
+
constructor() {
|
|
4357
|
+
super(selector);
|
|
4358
|
+
}
|
|
4359
|
+
get(context, rootContext = context) {
|
|
4360
|
+
if (context == null) {
|
|
4361
|
+
return null;
|
|
4362
|
+
}
|
|
4363
|
+
const result = {};
|
|
4364
|
+
for (const { key, accessor } of entryAccessors) {
|
|
4365
|
+
result[key] = accessor.get(context, rootContext);
|
|
4366
|
+
}
|
|
4367
|
+
return result;
|
|
4368
|
+
}
|
|
4369
|
+
};
|
|
4370
|
+
return new Accessor();
|
|
4371
|
+
},
|
|
4372
|
+
}, undefined);
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
/** Binds an {@link UnboundAccessor} to a specific context and root, producing a ready-to-use {@link Accessor}. */
|
|
4376
|
+
function bindJsonSelectorAccessor(unbound, context, rootContext = context) {
|
|
4377
|
+
const { selector } = unbound;
|
|
4378
|
+
const valid = unbound.isValidContext(context, rootContext);
|
|
4379
|
+
return {
|
|
4380
|
+
selector,
|
|
4381
|
+
valid,
|
|
4382
|
+
path: formatJsonSelector(selector),
|
|
4383
|
+
get() {
|
|
4384
|
+
return unbound.get(context, rootContext);
|
|
4385
|
+
},
|
|
4386
|
+
getOrThrow() {
|
|
4387
|
+
return unbound.getOrThrow(context, rootContext);
|
|
4388
|
+
},
|
|
4389
|
+
set(value) {
|
|
4390
|
+
unbound.set(value, context, rootContext);
|
|
4391
|
+
},
|
|
4392
|
+
setOrThrow(value) {
|
|
4393
|
+
unbound.setOrThrow(value, context, rootContext);
|
|
4394
|
+
},
|
|
4395
|
+
delete() {
|
|
4396
|
+
unbound.delete(context, rootContext);
|
|
4397
|
+
},
|
|
4398
|
+
deleteOrThrow() {
|
|
4399
|
+
unbound.deleteOrThrow(context, rootContext);
|
|
4400
|
+
},
|
|
4401
|
+
};
|
|
4402
|
+
}
|
|
4403
|
+
/** One-step convenience: parses a selector into an accessor already bound to the given context. */
|
|
4404
|
+
function accessWithJsonSelector(selector, context, rootContext = context, options) {
|
|
4405
|
+
return bindJsonSelectorAccessor(makeJsonSelectorAccessor(selector, options), context, rootContext);
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
/**
|
|
4409
|
+
* Read-only {@link Map} view that layers a primary map over an optional fallback,
|
|
4410
|
+
* with primary entries taking precedence on key collisions.
|
|
4411
|
+
*/
|
|
4412
|
+
class FallbackMapView {
|
|
4413
|
+
constructor(primary, fallback) {
|
|
4414
|
+
this.primary = primary;
|
|
4415
|
+
this.fallback = fallback;
|
|
4011
4416
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
*
|
|
4016
|
-
* Returns undefined if the next token does not continue the projection
|
|
4017
|
-
* (e.g. pipe, comparison, EOF, or closing brackets).
|
|
4018
|
-
*/
|
|
4019
|
-
parseProjectionContinuation() {
|
|
4020
|
-
const token = this.lexer.peek();
|
|
4021
|
-
if (token.type !== 7 /* TokenType.DOT */ &&
|
|
4022
|
-
token.type !== 3 /* TokenType.LBRACKET */ &&
|
|
4023
|
-
token.type !== 31 /* TokenType.FILTER_BRACKET */) {
|
|
4024
|
-
return undefined;
|
|
4025
|
-
}
|
|
4026
|
-
return this.expressionFrom(CURRENT_NODE, PROJECTION_STOP_BP - 1);
|
|
4417
|
+
get(name) {
|
|
4418
|
+
var _a, _b;
|
|
4419
|
+
return (_a = this.primary.get(name)) !== null && _a !== void 0 ? _a : (_b = this.fallback) === null || _b === void 0 ? void 0 : _b.get(name);
|
|
4027
4420
|
}
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
* Called when pattern is [:...] (start undefined) or [n:...] / [-n:...] (start known).
|
|
4032
|
-
*/
|
|
4033
|
-
parseSlice(left, start) {
|
|
4034
|
-
// [ and optional start already consumed, current token should be COLON
|
|
4035
|
-
this.lexer.consume(9 /* TokenType.COLON */);
|
|
4036
|
-
const end = this.tryConsumeSignedNumber();
|
|
4037
|
-
let step;
|
|
4038
|
-
if (this.lexer.tryConsume(9 /* TokenType.COLON */)) {
|
|
4039
|
-
step = this.tryConsumeSignedNumber();
|
|
4040
|
-
}
|
|
4041
|
-
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
4042
|
-
return {
|
|
4043
|
-
type: "slice",
|
|
4044
|
-
expression: left,
|
|
4045
|
-
start,
|
|
4046
|
-
end,
|
|
4047
|
-
step,
|
|
4048
|
-
};
|
|
4421
|
+
has(name) {
|
|
4422
|
+
var _a;
|
|
4423
|
+
return this.primary.has(name) || ((_a = this.fallback) === null || _a === void 0 ? void 0 : _a.has(name)) === true;
|
|
4049
4424
|
}
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4425
|
+
get size() {
|
|
4426
|
+
let result = this.primary.size;
|
|
4427
|
+
if (this.fallback) {
|
|
4428
|
+
for (const k of this.fallback.keys()) {
|
|
4429
|
+
if (!this.primary.has(k)) {
|
|
4430
|
+
++result;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4056
4433
|
}
|
|
4057
|
-
|
|
4058
|
-
return { type: "indexAccess", expression: left, index: num };
|
|
4434
|
+
return result;
|
|
4059
4435
|
}
|
|
4060
|
-
|
|
4061
|
-
const
|
|
4062
|
-
|
|
4063
|
-
return this.lexer.consume(37 /* TokenType.NUMBER */).value;
|
|
4436
|
+
*entries() {
|
|
4437
|
+
for (const [k, v] of this.primary) {
|
|
4438
|
+
yield [k, v];
|
|
4064
4439
|
}
|
|
4065
|
-
if (
|
|
4066
|
-
const
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
const nextToken = this.lexer.peek();
|
|
4071
|
-
throw this.unexpectedToken(nextToken, "number");
|
|
4440
|
+
if (this.fallback) {
|
|
4441
|
+
for (const [k, v] of this.fallback) {
|
|
4442
|
+
if (!this.primary.has(k)) {
|
|
4443
|
+
yield [k, v];
|
|
4444
|
+
}
|
|
4072
4445
|
}
|
|
4073
|
-
return sign * numberToken.value;
|
|
4074
4446
|
}
|
|
4075
|
-
return undefined;
|
|
4076
4447
|
}
|
|
4077
|
-
|
|
4078
|
-
const
|
|
4079
|
-
|
|
4080
|
-
return id.value;
|
|
4081
|
-
}
|
|
4082
|
-
const quoted = this.lexer.tryConsume(34 /* TokenType.QUOTED_STRING */);
|
|
4083
|
-
if (quoted) {
|
|
4084
|
-
return quoted.value;
|
|
4448
|
+
*keys() {
|
|
4449
|
+
for (const [k] of this.entries()) {
|
|
4450
|
+
yield k;
|
|
4085
4451
|
}
|
|
4086
|
-
const token = this.lexer.peek();
|
|
4087
|
-
throw this.unexpectedToken(token, "identifier");
|
|
4088
4452
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
*/
|
|
4093
|
-
parseMultiSelectListFromBracket(first) {
|
|
4094
|
-
const expressions = [first];
|
|
4095
|
-
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
4096
|
-
expressions.push(this.expression(0));
|
|
4453
|
+
*values() {
|
|
4454
|
+
for (const [, v] of this.entries()) {
|
|
4455
|
+
yield v;
|
|
4097
4456
|
}
|
|
4098
|
-
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
4099
|
-
return { type: "multiSelectList", expressions };
|
|
4100
4457
|
}
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
* Called after identifier has been consumed and ( has been peeked
|
|
4104
|
-
*/
|
|
4105
|
-
parseFunctionCall(name) {
|
|
4106
|
-
this.lexer.consume(1 /* TokenType.LPAREN */);
|
|
4107
|
-
const args = [];
|
|
4108
|
-
// Handle empty args: func()
|
|
4109
|
-
if (this.lexer.peek().type !== 2 /* TokenType.RPAREN */) {
|
|
4110
|
-
// First argument
|
|
4111
|
-
args.push(this.expression(0));
|
|
4112
|
-
// Additional arguments
|
|
4113
|
-
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
4114
|
-
args.push(this.expression(0));
|
|
4115
|
-
}
|
|
4116
|
-
}
|
|
4117
|
-
this.lexer.consume(2 /* TokenType.RPAREN */);
|
|
4118
|
-
return {
|
|
4119
|
-
type: "functionCall",
|
|
4120
|
-
name,
|
|
4121
|
-
args,
|
|
4122
|
-
};
|
|
4458
|
+
[Symbol.iterator]() {
|
|
4459
|
+
return this.entries();
|
|
4123
4460
|
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
parseMultiSelectHash() {
|
|
4128
|
-
this.lexer.consume(5 /* TokenType.LBRACE */);
|
|
4129
|
-
const entries = [];
|
|
4130
|
-
// Empty multi-select hash {} is invalid
|
|
4131
|
-
if (this.lexer.peek().type === 6 /* TokenType.RBRACE */) {
|
|
4132
|
-
const token = this.lexer.peek();
|
|
4133
|
-
throw this.unexpectedToken(token, "key-value pair", "in multi-select hash");
|
|
4134
|
-
}
|
|
4135
|
-
// Parse first key-value pair
|
|
4136
|
-
entries.push(this.parseMultiSelectHashEntry());
|
|
4137
|
-
// Parse remaining comma-separated entries
|
|
4138
|
-
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
4139
|
-
entries.push(this.parseMultiSelectHashEntry());
|
|
4461
|
+
forEach(callbackfn, thisArg) {
|
|
4462
|
+
for (const [k, v] of this.entries()) {
|
|
4463
|
+
callbackfn.call(thisArg, v, k, this);
|
|
4140
4464
|
}
|
|
4141
|
-
this.lexer.consume(6 /* TokenType.RBRACE */);
|
|
4142
|
-
return {
|
|
4143
|
-
type: "multiSelectHash",
|
|
4144
|
-
entries,
|
|
4145
|
-
};
|
|
4146
4465
|
}
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
/**
|
|
4469
|
+
* Function registry for built-in and custom functions.
|
|
4470
|
+
* Pass `null` as the base provider to omit built-in functions.
|
|
4471
|
+
*/
|
|
4472
|
+
class FunctionRegistry extends FallbackMapView {
|
|
4473
|
+
constructor(baseProvider = getBuiltinFunctionProvider()) {
|
|
4474
|
+
const custom = new Map();
|
|
4475
|
+
super(custom, baseProvider !== null && baseProvider !== void 0 ? baseProvider : undefined);
|
|
4476
|
+
this.custom = custom;
|
|
4155
4477
|
}
|
|
4156
4478
|
/**
|
|
4157
|
-
*
|
|
4158
|
-
* any continuation operators.
|
|
4479
|
+
* Register a custom function (can override built-ins).
|
|
4159
4480
|
*/
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
return {
|
|
4163
|
-
type: "objectProject",
|
|
4164
|
-
expression,
|
|
4165
|
-
projection: (_a = this.parseProjectionContinuation()) !== null && _a !== void 0 ? _a : CURRENT_NODE,
|
|
4166
|
-
};
|
|
4481
|
+
register(def) {
|
|
4482
|
+
this.custom.set(def.name, def);
|
|
4167
4483
|
}
|
|
4168
4484
|
/**
|
|
4169
|
-
*
|
|
4170
|
-
* - Multi-select list: foo.[a, b]
|
|
4171
|
-
* - Array projection: foo.[*]
|
|
4172
|
-
*
|
|
4173
|
-
* Note: foo.[0], foo.[:], and foo.['id'] are rejected as syntax errors.
|
|
4485
|
+
* Unregister a custom function.
|
|
4174
4486
|
*/
|
|
4175
|
-
|
|
4176
|
-
this.
|
|
4177
|
-
const token = this.lexer.peek();
|
|
4178
|
-
// After .[, only STAR and expressions are valid
|
|
4179
|
-
// NUMBER, COLON, RAW_STRING are NOT valid (use foo[0], foo[:], foo['id'] instead)
|
|
4180
|
-
if (token.type === 28 /* TokenType.STAR */) {
|
|
4181
|
-
// .[*] is valid as projection
|
|
4182
|
-
this.lexer.advance();
|
|
4183
|
-
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
4184
|
-
const projectNode = {
|
|
4185
|
-
type: "project",
|
|
4186
|
-
expression: left,
|
|
4187
|
-
projection: CURRENT_NODE,
|
|
4188
|
-
};
|
|
4189
|
-
return this.parseProjectionRHS(projectNode);
|
|
4190
|
-
}
|
|
4191
|
-
// NUMBER, COLON, RAW_STRING after .[ are syntax errors
|
|
4192
|
-
if (token.type === 37 /* TokenType.NUMBER */ ||
|
|
4193
|
-
token.type === 9 /* TokenType.COLON */ ||
|
|
4194
|
-
token.type === 35 /* TokenType.RAW_STRING */) {
|
|
4195
|
-
throw this.unexpectedToken(token, "expression or '*'", "after '.['");
|
|
4196
|
-
}
|
|
4197
|
-
// Multi-select list: [expr1, expr2, ...]
|
|
4198
|
-
const expressions = [];
|
|
4199
|
-
expressions.push(this.expression(0));
|
|
4200
|
-
while (this.lexer.tryConsume(8 /* TokenType.COMMA */)) {
|
|
4201
|
-
const next = this.lexer.peek();
|
|
4202
|
-
if (next.type === 37 /* TokenType.NUMBER */ ||
|
|
4203
|
-
next.type === 9 /* TokenType.COLON */ ||
|
|
4204
|
-
next.type === 35 /* TokenType.RAW_STRING */) {
|
|
4205
|
-
throw this.unexpectedToken(next, "expression", "after '.['");
|
|
4206
|
-
}
|
|
4207
|
-
expressions.push(this.expression(0));
|
|
4208
|
-
}
|
|
4209
|
-
this.lexer.consume(4 /* TokenType.RBRACKET */);
|
|
4210
|
-
return {
|
|
4211
|
-
type: "pipe",
|
|
4212
|
-
lhs: left,
|
|
4213
|
-
rhs: {
|
|
4214
|
-
type: "multiSelectList",
|
|
4215
|
-
expressions,
|
|
4216
|
-
},
|
|
4217
|
-
dotSyntax: true,
|
|
4218
|
-
};
|
|
4219
|
-
}
|
|
4220
|
-
unexpectedToken(token, expected, context) {
|
|
4221
|
-
return new UnexpectedTokenError(this.input, token.offset, token.text || EOF_DESCRIPTION, expected, context);
|
|
4487
|
+
unregister(name) {
|
|
4488
|
+
return this.custom.delete(name);
|
|
4222
4489
|
}
|
|
4223
4490
|
}
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
++start;
|
|
4229
|
-
}
|
|
4230
|
-
while (end > start && isWhitespace(value.charCodeAt(end - 1))) {
|
|
4231
|
-
--end;
|
|
4232
|
-
}
|
|
4233
|
-
return value.slice(start, end);
|
|
4491
|
+
|
|
4492
|
+
/** Evaluates a selector against a context and returns the selected value (read-only convenience wrapper). */
|
|
4493
|
+
function getWithJsonSelector(selector, context, options) {
|
|
4494
|
+
return accessWithJsonSelector(selector, context, context, options).get();
|
|
4234
4495
|
}
|
|
4235
4496
|
|
|
4236
4497
|
/** Parses a selector expression string into an AST that can be evaluated, formatted, or compiled into an accessor. */
|
|
@@ -4247,5 +4508,5 @@ function setWithJsonSelector(selector, context, value, options) {
|
|
|
4247
4508
|
return oldValue;
|
|
4248
4509
|
}
|
|
4249
4510
|
|
|
4250
|
-
export { ANY_ARRAY_TYPE, ANY_TYPE, BOOLEAN_TYPE, DivideByZeroError, EXPREF_TYPE, FunctionError, FunctionRegistry, InvalidArgumentError, InvalidArgumentTypeError, InvalidArgumentValueError, InvalidArityError, InvalidTokenError, JsonSelectorError, JsonSelectorRuntimeError, JsonSelectorSyntaxError, JsonSelectorTypeError, NULL_TYPE, NUMBER_ARRAY_TYPE, NUMBER_TYPE, NotANumberError, OBJECT_TYPE, STRING_ARRAY_TYPE, STRING_TYPE, UndefinedVariableError, UnexpectedCharacterError, UnexpectedEndOfInputError, UnexpectedTokenError, UnknownFunctionError, UnterminatedTokenError, accessWithJsonSelector, arg, arrayOf, bindJsonSelectorAccessor, evaluateJsonSelector, formatJsonSelector, formatType, getBuiltinFunctionProvider, getDataType, getDataTypeKind, getExpressionRef, getWithJsonSelector, makeExpressionRef, makeJsonSelectorAccessor, matchesType, optArg, parseJsonSelector, requireInteger, requireNonNegativeInteger, setWithJsonSelector, unionOf, validateArguments, varArg };
|
|
4511
|
+
export { ANY_ARRAY_TYPE, ANY_TYPE, AccessorError, BOOLEAN_TYPE, DivideByZeroError, EXPREF_TYPE, FunctionError, FunctionRegistry, InvalidArgumentError, InvalidArgumentTypeError, InvalidArgumentValueError, InvalidArityError, InvalidTokenError, JsonSelectorError, JsonSelectorRuntimeError, JsonSelectorSyntaxError, JsonSelectorTypeError, NULL_TYPE, NUMBER_ARRAY_TYPE, NUMBER_TYPE, NotANumberError, OBJECT_TYPE, STRING_ARRAY_TYPE, STRING_TYPE, UndefinedVariableError, UnexpectedCharacterError, UnexpectedEndOfInputError, UnexpectedTokenError, UnknownFunctionError, UnterminatedTokenError, accessWithJsonSelector, arg, arrayOf, bindJsonSelectorAccessor, evaluateJsonSelector, formatJsonSelector, formatType, getBuiltinFunctionProvider, getDataType, getDataTypeKind, getExpressionRef, getWithJsonSelector, makeExpressionRef, makeJsonSelectorAccessor, matchesType, optArg, parseJsonSelector, requireInteger, requireNonNegativeInteger, setWithJsonSelector, unionOf, validateArguments, varArg };
|
|
4251
4512
|
//# sourceMappingURL=json-selector.esm.js.map
|