@jarenjs/json 0.9.2

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.
Files changed (57) hide show
  1. package/ARCHITECTURE.md +175 -0
  2. package/LICENSE +21 -0
  3. package/README.md +471 -0
  4. package/dist/types/basic.d.ts +32 -0
  5. package/dist/types/index.d.ts +4 -0
  6. package/dist/types/jslt/dispatch.d.ts +11 -0
  7. package/dist/types/jslt/errors.d.ts +18 -0
  8. package/dist/types/jslt/index.d.ts +53 -0
  9. package/dist/types/jslt/stylesheet.d.ts +8 -0
  10. package/dist/types/jtlt/desugar.d.ts +19 -0
  11. package/dist/types/jtlt/errors.d.ts +18 -0
  12. package/dist/types/jtlt/index.d.ts +57 -0
  13. package/dist/types/jtlt/template.d.ts +8 -0
  14. package/dist/types/jtlt/writer.d.ts +6 -0
  15. package/dist/types/path.d.ts +235 -0
  16. package/dist/types/pointer.d.ts +114 -0
  17. package/dist/types/query/compile.d.ts +21 -0
  18. package/dist/types/query/errors.d.ts +18 -0
  19. package/dist/types/query/index.d.ts +70 -0
  20. package/dist/types/query/normalize.d.ts +68 -0
  21. package/dist/types/query/operators.d.ts +424 -0
  22. package/dist/types/query/runtime.d.ts +93 -0
  23. package/dist/types/segments.d.ts +62 -0
  24. package/dist/types/xquery/index.d.ts +19 -0
  25. package/dist/types/xquery/parse.d.ts +20 -0
  26. package/docs/JSLT-FORMAT.md +861 -0
  27. package/docs/JSLT-PRELUDE.md +159 -0
  28. package/docs/JTLT-FORMAT.md +659 -0
  29. package/docs/QUERY-FORMAT.md +1221 -0
  30. package/docs/XQUERY-FRONTEND.md +321 -0
  31. package/package.json +81 -0
  32. package/schemas/jaren-jslt.draft-07.schema.json +776 -0
  33. package/schemas/jaren-jslt.schema.json +776 -0
  34. package/schemas/jaren-query.draft-07.schema.json +613 -0
  35. package/schemas/jaren-query.schema.json +375 -0
  36. package/src/basic.js +300 -0
  37. package/src/index.js +4 -0
  38. package/src/jslt/dispatch.js +934 -0
  39. package/src/jslt/errors.js +34 -0
  40. package/src/jslt/index.js +121 -0
  41. package/src/jslt/stylesheet.js +234 -0
  42. package/src/jtlt/desugar.js +231 -0
  43. package/src/jtlt/errors.js +34 -0
  44. package/src/jtlt/index.js +155 -0
  45. package/src/jtlt/template.js +130 -0
  46. package/src/jtlt/writer.js +110 -0
  47. package/src/path.js +977 -0
  48. package/src/pointer.js +453 -0
  49. package/src/query/compile.js +817 -0
  50. package/src/query/errors.js +33 -0
  51. package/src/query/index.js +150 -0
  52. package/src/query/normalize.js +1047 -0
  53. package/src/query/operators.js +1253 -0
  54. package/src/query/runtime.js +233 -0
  55. package/src/segments.js +627 -0
  56. package/src/xquery/index.js +35 -0
  57. package/src/xquery/parse.js +1647 -0
@@ -0,0 +1,627 @@
1
+ //#region JSONPath segment engine (package-internal)
2
+ // Runtime segment machinery shared by the JSONPath compiler (path.js) and
3
+ // the query engine (query/). Extracted verbatim from path.js, in two
4
+ // installments: values mode first, then the nodes-mode (normalized paths,
5
+ // RFC 9535 section 2.7) compilers - so consumers beyond path.js (the JSLT
6
+ // dispatcher's positional matching) can run selectors producing
7
+ // (value, normalized-path) pairs. This module is package-internal and is
8
+ // deliberately not listed in the package exports.
9
+
10
+ import { equalsJson } from '@jarenjs/core/object';
11
+ import { countCodePoints, compareCodePoints } from '@jarenjs/core/string';
12
+ import { compileIRegexp } from '@jarenjs/core/text/iregexp';
13
+ import {
14
+ CC_TAB,
15
+ CC_LF,
16
+ CC_CR,
17
+ CC_SPACE,
18
+ CC_SQUOTE,
19
+ CC_BACKSLASH,
20
+ } from '@jarenjs/core/scan';
21
+
22
+ /**
23
+ * Sentinel for the absence of a value ("Nothing" in RFC 9535 terms), as
24
+ * distinct from the JSON value `null`. Re-exported from path.js as
25
+ * `JSONPATH_NOTHING`.
26
+ */
27
+ export const NOTHING = Symbol('JSONPath.Nothing');
28
+
29
+ const hasOwn = Object.hasOwn;
30
+
31
+ /**
32
+ * Returns true when every segment is a child segment with exactly one
33
+ * name or index selector (a "singular query", RFC 9535 section 2.3.5.1).
34
+ * @param {object[]} segments - Parsed query segments
35
+ * @returns {boolean}
36
+ */
37
+ export function isSingularSegments(segments) {
38
+ for (let i = 0; i < segments.length; i++) {
39
+ const seg = segments[i];
40
+ if (seg.descendant || seg.selectors.length !== 1)
41
+ return false;
42
+ const kind = seg.selectors[0].kind;
43
+ if (kind !== 'name' && kind !== 'index')
44
+ return false;
45
+ }
46
+ return true;
47
+ }
48
+
49
+ //#region filter compilation
50
+
51
+ // structural equality per RFC 9535 section 2.3.5.2.2 (equalsJson),
52
+ // lifted over the JSONPath-specific NOTHING sentinel
53
+ function cmpEquals(a, b) {
54
+ if (a === NOTHING || b === NOTHING)
55
+ return a === b;
56
+ return equalsJson(a, b);
57
+ }
58
+
59
+ // numbers by value, strings by Unicode scalar values (RFC 9535
60
+ // section 2.3.5.2.2); other types do not order
61
+ function cmpLess(a, b) {
62
+ if (typeof a === 'number')
63
+ return typeof b === 'number' && a < b;
64
+ if (typeof a === 'string')
65
+ return typeof b === 'string' && compareCodePoints(a, b) < 0;
66
+ return false;
67
+ }
68
+
69
+ function countOwnKeys(obj) {
70
+ let count = 0;
71
+ for (const key in obj) {
72
+ if (hasOwn(obj, key))
73
+ count++;
74
+ }
75
+ return count;
76
+ }
77
+
78
+ /**
79
+ * Compile a singular query into a direct property walk.
80
+ * @returns {(current: any, root: any) => any} getter returning the value or NOTHING
81
+ */
82
+ export function compileSingularGetter(segments, relative) {
83
+ // steps: strings are member names, numbers are array indexes
84
+ const steps = new Array(segments.length);
85
+ for (let i = 0; i < segments.length; i++) {
86
+ const sel = segments[i].selectors[0];
87
+ steps[i] = sel.kind === 'name' ? sel.name : sel.index;
88
+ }
89
+ const slen = steps.length;
90
+ return function singularGetter(current, root) {
91
+ let v = relative ? current : root;
92
+ for (let i = 0; i < slen; i++) {
93
+ const step = steps[i];
94
+ if (typeof step === 'string') {
95
+ if (typeof v !== 'object' || v === null || Array.isArray(v) || !hasOwn(v, step))
96
+ return NOTHING;
97
+ v = v[step];
98
+ }
99
+ else {
100
+ if (!Array.isArray(v))
101
+ return NOTHING;
102
+ const idx = step < 0 ? v.length + step : step;
103
+ if (idx < 0 || idx >= v.length)
104
+ return NOTHING;
105
+ v = v[idx];
106
+ }
107
+ }
108
+ return v;
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Run a chain of compiled value-mode segment functions over a start value.
114
+ * @returns {any[]} the resulting nodelist as an array of values
115
+ */
116
+ export function runSegmentsV(segs, start, root) {
117
+ let vals = [start];
118
+ const slen = segs.length;
119
+ for (let i = 0; i < slen; i++) {
120
+ if (vals.length === 0)
121
+ return vals;
122
+ const out = [];
123
+ segs[i](vals, out, root);
124
+ vals = out;
125
+ }
126
+ return vals;
127
+ }
128
+
129
+ /**
130
+ * Compile an existence test for a filter query; singular queries never
131
+ * materialize nodelists.
132
+ * @returns {(current: any, root: any) => boolean}
133
+ */
134
+ export function compileExists(query) {
135
+ if (isSingularSegments(query.segments)) {
136
+ const getter = compileSingularGetter(query.segments, query.relative);
137
+ return (current, root) => getter(current, root) !== NOTHING;
138
+ }
139
+ const segs = query.segments.map(compileSegmentV);
140
+ const relative = query.relative;
141
+ return (current, root) => runSegmentsV(segs, relative ? current : root, root).length > 0;
142
+ }
143
+
144
+ // getter producing a ValueType result (a JSON value or NOTHING)
145
+ function compileComparable(node) {
146
+ if (node.kind === 'literal') {
147
+ const value = node.value;
148
+ return () => value;
149
+ }
150
+ if (node.kind === 'query')
151
+ return compileSingularGetter(node.query.segments, node.query.relative);
152
+ return compileValueFunction(node);
153
+ }
154
+
155
+ function compileValueFunction(func) {
156
+ switch (func.name) {
157
+ case 'length': {
158
+ const getter = compileComparable(func.args[0]);
159
+ return (current, root) => {
160
+ const v = getter(current, root);
161
+ if (typeof v === 'string')
162
+ return countCodePoints(v);
163
+ if (Array.isArray(v))
164
+ return v.length;
165
+ if (typeof v === 'object' && v !== null)
166
+ return countOwnKeys(v);
167
+ return NOTHING;
168
+ };
169
+ }
170
+ case 'count': {
171
+ const query = func.args[0].query;
172
+ if (isSingularSegments(query.segments)) {
173
+ const getter = compileSingularGetter(query.segments, query.relative);
174
+ return (current, root) => (getter(current, root) === NOTHING ? 0 : 1);
175
+ }
176
+ const segs = query.segments.map(compileSegmentV);
177
+ const relative = query.relative;
178
+ return (current, root) => runSegmentsV(segs, relative ? current : root, root).length;
179
+ }
180
+ case 'value': {
181
+ const query = func.args[0].query;
182
+ if (isSingularSegments(query.segments))
183
+ return compileSingularGetter(query.segments, query.relative);
184
+ const segs = query.segments.map(compileSegmentV);
185
+ const relative = query.relative;
186
+ return (current, root) => {
187
+ const result = runSegmentsV(segs, relative ? current : root, root);
188
+ return result.length === 1 ? result[0] : NOTHING;
189
+ };
190
+ }
191
+ /* c8 ignore next 2 -- guarded by the parser's well-typedness checks */
192
+ default:
193
+ throw new Error(`JSONPath: function '${func.name}' does not return ValueType`);
194
+ }
195
+ }
196
+
197
+ function compileRegexTest(func, fullMatch) {
198
+ const inputGet = compileComparable(func.args[0]);
199
+ const patternArg = func.args[1];
200
+ if (patternArg.kind === 'literal') {
201
+ if (typeof patternArg.value !== 'string')
202
+ return () => false;
203
+ const re = compileIRegexp(patternArg.value, fullMatch);
204
+ if (re === null)
205
+ return () => false;
206
+ return (current, root) => {
207
+ const s = inputGet(current, root);
208
+ return typeof s === 'string' && re.test(s);
209
+ };
210
+ }
211
+ const patternGet = compileComparable(patternArg);
212
+ // monomorphic per-callsite cache: filters usually see one pattern
213
+ let lastPattern = null;
214
+ let lastRegExp = null;
215
+ return (current, root) => {
216
+ const s = inputGet(current, root);
217
+ if (typeof s !== 'string')
218
+ return false;
219
+ const p = patternGet(current, root);
220
+ if (typeof p !== 'string')
221
+ return false;
222
+ if (p !== lastPattern) {
223
+ lastPattern = p;
224
+ lastRegExp = compileIRegexp(p, fullMatch);
225
+ }
226
+ return lastRegExp !== null && lastRegExp.test(s);
227
+ };
228
+ }
229
+
230
+ function compileComparison(expr) {
231
+ const left = compileComparable(expr.left);
232
+ const right = compileComparable(expr.right);
233
+ switch (expr.op) {
234
+ case '==':
235
+ return (c, r) => cmpEquals(left(c, r), right(c, r));
236
+ case '!=':
237
+ return (c, r) => !cmpEquals(left(c, r), right(c, r));
238
+ case '<':
239
+ return (c, r) => cmpLess(left(c, r), right(c, r));
240
+ case '>':
241
+ return (c, r) => cmpLess(right(c, r), left(c, r));
242
+ case '<=':
243
+ return (c, r) => {
244
+ const a = left(c, r);
245
+ const b = right(c, r);
246
+ return cmpLess(a, b) || cmpEquals(a, b);
247
+ };
248
+ default: // '>='
249
+ return (c, r) => {
250
+ const a = left(c, r);
251
+ const b = right(c, r);
252
+ return cmpLess(b, a) || cmpEquals(a, b);
253
+ };
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Compile a filter logical expression to a predicate.
259
+ * @returns {(current: any, root: any) => boolean}
260
+ */
261
+ export function compileLogicalExpr(expr) {
262
+ switch (expr.kind) {
263
+ case 'or': {
264
+ const fns = expr.operands.map(compileLogicalExpr);
265
+ const flen = fns.length;
266
+ return (c, r) => {
267
+ for (let i = 0; i < flen; i++) {
268
+ if (fns[i](c, r))
269
+ return true;
270
+ }
271
+ return false;
272
+ };
273
+ }
274
+ case 'and': {
275
+ const fns = expr.operands.map(compileLogicalExpr);
276
+ const flen = fns.length;
277
+ return (c, r) => {
278
+ for (let i = 0; i < flen; i++) {
279
+ if (!fns[i](c, r))
280
+ return false;
281
+ }
282
+ return true;
283
+ };
284
+ }
285
+ case 'not': {
286
+ const fn = compileLogicalExpr(expr.operand);
287
+ return (c, r) => !fn(c, r);
288
+ }
289
+ case 'exists':
290
+ return compileExists(expr.query);
291
+ case 'ftest':
292
+ return compileRegexTest(expr.func, expr.func.name === 'match');
293
+ default: // 'cmp'
294
+ return compileComparison(expr);
295
+ }
296
+ }
297
+
298
+ //#endregion
299
+
300
+ //#region segment compilation (values mode)
301
+
302
+ // selector-node functions: (value, output, root) => void
303
+
304
+ export function compileSelectorNodeV(sel) {
305
+ switch (sel.kind) {
306
+ case 'name': {
307
+ const name = sel.name;
308
+ return (v, out) => {
309
+ if (typeof v === 'object' && v !== null && !Array.isArray(v) && hasOwn(v, name))
310
+ out.push(v[name]);
311
+ };
312
+ }
313
+ case 'index': {
314
+ const index = sel.index;
315
+ if (index >= 0) {
316
+ return (v, out) => {
317
+ if (Array.isArray(v) && index < v.length)
318
+ out.push(v[index]);
319
+ };
320
+ }
321
+ return (v, out) => {
322
+ if (Array.isArray(v)) {
323
+ const idx = v.length + index;
324
+ if (idx >= 0)
325
+ out.push(v[idx]);
326
+ }
327
+ };
328
+ }
329
+ case 'wildcard':
330
+ return (v, out) => {
331
+ if (Array.isArray(v)) {
332
+ for (let i = 0; i < v.length; i++)
333
+ out.push(v[i]);
334
+ }
335
+ else if (typeof v === 'object' && v !== null) {
336
+ for (const key in v) {
337
+ if (hasOwn(v, key))
338
+ out.push(v[key]);
339
+ }
340
+ }
341
+ };
342
+ case 'slice': {
343
+ const start = sel.start;
344
+ const end = sel.end;
345
+ const step = sel.step === null ? 1 : sel.step;
346
+ if (step === 0)
347
+ return () => { };
348
+ return (v, out) => {
349
+ if (!Array.isArray(v))
350
+ return;
351
+ const len = v.length;
352
+ if (len === 0)
353
+ return;
354
+ // bounds per RFC 9535 section 2.3.4.2.2
355
+ const s = start === null ? (step > 0 ? 0 : len - 1) : (start < 0 ? len + start : start);
356
+ const e = end === null ? (step > 0 ? len : -1) : (end < 0 ? len + end : end);
357
+ if (step > 0) {
358
+ const lower = s < 0 ? 0 : (s > len ? len : s);
359
+ const upper = e < 0 ? 0 : (e > len ? len : e);
360
+ for (let i = lower; i < upper; i += step)
361
+ out.push(v[i]);
362
+ }
363
+ else {
364
+ const upper = s < -1 ? -1 : (s > len - 1 ? len - 1 : s);
365
+ const lower = e < -1 ? -1 : (e > len - 1 ? len - 1 : e);
366
+ for (let i = upper; i > lower; i += step)
367
+ out.push(v[i]);
368
+ }
369
+ };
370
+ }
371
+ default: { // 'filter'
372
+ const pred = compileLogicalExpr(sel.expr);
373
+ return (v, out, root) => {
374
+ if (Array.isArray(v)) {
375
+ for (let i = 0; i < v.length; i++) {
376
+ if (pred(v[i], root))
377
+ out.push(v[i]);
378
+ }
379
+ }
380
+ else if (typeof v === 'object' && v !== null) {
381
+ for (const key in v) {
382
+ if (hasOwn(v, key) && pred(v[key], root))
383
+ out.push(v[key]);
384
+ }
385
+ }
386
+ };
387
+ }
388
+ }
389
+ }
390
+
391
+ export function descendV(v, output, root, apply) {
392
+ apply(v, output, root);
393
+ if (Array.isArray(v)) {
394
+ for (let i = 0; i < v.length; i++)
395
+ descendV(v[i], output, root, apply);
396
+ }
397
+ else if (typeof v === 'object' && v !== null) {
398
+ for (const key in v) {
399
+ if (hasOwn(v, key))
400
+ descendV(v[key], output, root, apply);
401
+ }
402
+ }
403
+ }
404
+
405
+ // segment functions: (input, output, root) => void
406
+ export function compileSegmentV(seg) {
407
+ const fns = seg.selectors.map(compileSelectorNodeV);
408
+ const apply = fns.length === 1
409
+ ? fns[0]
410
+ : (v, out, root) => {
411
+ for (let i = 0; i < fns.length; i++)
412
+ fns[i](v, out, root);
413
+ };
414
+ if (seg.descendant) {
415
+ return (input, output, root) => {
416
+ for (let i = 0; i < input.length; i++)
417
+ descendV(input[i], output, root, apply);
418
+ };
419
+ }
420
+ return (input, output, root) => {
421
+ for (let i = 0; i < input.length; i++)
422
+ apply(input[i], output, root);
423
+ };
424
+ }
425
+
426
+ //#endregion
427
+
428
+ //#region segment compilation (nodes mode, normalized paths)
429
+ // Moved verbatim from path.js: the selector/segment compilers producing
430
+ // (value, normalized-path) pairs per RFC 9535 section 2.7. path.js
431
+ // imports them back for `query.nodes()`/`query.paths()`; nodes mode
432
+ // stays lazily compiled there, so value-only queries never pay for it.
433
+
434
+ // eslint-disable-next-line no-control-regex
435
+ export const RE_NAME_NEEDS_ESCAPE = /['\\\u0000-\u001f]/;
436
+
437
+ /**
438
+ * Escape a member name for use inside a normalized path name selector
439
+ * (RFC 9535 section 2.7).
440
+ */
441
+ export function escapeNormalizedName(name) {
442
+ if (!RE_NAME_NEEDS_ESCAPE.test(name))
443
+ return name;
444
+ let out = '';
445
+ for (let i = 0; i < name.length; i++) {
446
+ const c = name.charCodeAt(i);
447
+ if (c === CC_SQUOTE) out += "\\'";
448
+ else if (c === CC_BACKSLASH) out += '\\\\';
449
+ else if (c === 0x08) out += '\\b';
450
+ else if (c === CC_TAB) out += '\\t';
451
+ else if (c === CC_LF) out += '\\n';
452
+ else if (c === 0x0C) out += '\\f';
453
+ else if (c === CC_CR) out += '\\r';
454
+ else if (c < CC_SPACE) out += '\\u00' + (c < 0x10 ? '0' : '') + c.toString(16);
455
+ else out += name[i];
456
+ }
457
+ return out;
458
+ }
459
+
460
+ export function appendName(path, name) {
461
+ return path + "['" + escapeNormalizedName(name) + "']";
462
+ }
463
+
464
+ // selector-node functions: (value, path, outValues, outPaths, root) => void
465
+
466
+ export function compileSelectorNodeP(sel) {
467
+ switch (sel.kind) {
468
+ case 'name': {
469
+ const name = sel.name;
470
+ const suffix = "['" + escapeNormalizedName(name) + "']";
471
+ return (v, p, outV, outP) => {
472
+ if (typeof v === 'object' && v !== null && !Array.isArray(v) && hasOwn(v, name)) {
473
+ outV.push(v[name]);
474
+ outP.push(p + suffix);
475
+ }
476
+ };
477
+ }
478
+ case 'index': {
479
+ const index = sel.index;
480
+ return (v, p, outV, outP) => {
481
+ if (!Array.isArray(v))
482
+ return;
483
+ const idx = index < 0 ? v.length + index : index;
484
+ if (idx >= 0 && idx < v.length) {
485
+ outV.push(v[idx]);
486
+ outP.push(p + '[' + idx + ']');
487
+ }
488
+ };
489
+ }
490
+ case 'wildcard':
491
+ return (v, p, outV, outP) => {
492
+ if (Array.isArray(v)) {
493
+ for (let i = 0; i < v.length; i++) {
494
+ outV.push(v[i]);
495
+ outP.push(p + '[' + i + ']');
496
+ }
497
+ }
498
+ else if (typeof v === 'object' && v !== null) {
499
+ for (const key in v) {
500
+ if (hasOwn(v, key)) {
501
+ outV.push(v[key]);
502
+ outP.push(appendName(p, key));
503
+ }
504
+ }
505
+ }
506
+ };
507
+ case 'slice': {
508
+ const start = sel.start;
509
+ const end = sel.end;
510
+ const step = sel.step === null ? 1 : sel.step;
511
+ if (step === 0)
512
+ return () => { };
513
+ return (v, p, outV, outP) => {
514
+ if (!Array.isArray(v))
515
+ return;
516
+ const len = v.length;
517
+ if (len === 0)
518
+ return;
519
+ const s = start === null ? (step > 0 ? 0 : len - 1) : (start < 0 ? len + start : start);
520
+ const e = end === null ? (step > 0 ? len : -1) : (end < 0 ? len + end : end);
521
+ if (step > 0) {
522
+ const lower = s < 0 ? 0 : (s > len ? len : s);
523
+ const upper = e < 0 ? 0 : (e > len ? len : e);
524
+ for (let i = lower; i < upper; i += step) {
525
+ outV.push(v[i]);
526
+ outP.push(p + '[' + i + ']');
527
+ }
528
+ }
529
+ else {
530
+ const upper = s < -1 ? -1 : (s > len - 1 ? len - 1 : s);
531
+ const lower = e < -1 ? -1 : (e > len - 1 ? len - 1 : e);
532
+ for (let i = upper; i > lower; i += step) {
533
+ outV.push(v[i]);
534
+ outP.push(p + '[' + i + ']');
535
+ }
536
+ }
537
+ };
538
+ }
539
+ default: { // 'filter'
540
+ const pred = compileLogicalExpr(sel.expr);
541
+ return (v, p, outV, outP, root) => {
542
+ if (Array.isArray(v)) {
543
+ for (let i = 0; i < v.length; i++) {
544
+ if (pred(v[i], root)) {
545
+ outV.push(v[i]);
546
+ outP.push(p + '[' + i + ']');
547
+ }
548
+ }
549
+ }
550
+ else if (typeof v === 'object' && v !== null) {
551
+ for (const key in v) {
552
+ if (hasOwn(v, key) && pred(v[key], root)) {
553
+ outV.push(v[key]);
554
+ outP.push(appendName(p, key));
555
+ }
556
+ }
557
+ }
558
+ };
559
+ }
560
+ }
561
+ }
562
+
563
+ export function descendP(v, p, outV, outP, root, apply) {
564
+ apply(v, p, outV, outP, root);
565
+ if (Array.isArray(v)) {
566
+ for (let i = 0; i < v.length; i++)
567
+ descendP(v[i], p + '[' + i + ']', outV, outP, root, apply);
568
+ }
569
+ else if (typeof v === 'object' && v !== null) {
570
+ for (const key in v) {
571
+ if (hasOwn(v, key))
572
+ descendP(v[key], appendName(p, key), outV, outP, root, apply);
573
+ }
574
+ }
575
+ }
576
+
577
+ // segment functions: (inValues, inPaths, outValues, outPaths, root) => void
578
+ export function compileSegmentP(seg) {
579
+ const fns = seg.selectors.map(compileSelectorNodeP);
580
+ const apply = fns.length === 1
581
+ ? fns[0]
582
+ : (v, p, outV, outP, root) => {
583
+ for (let i = 0; i < fns.length; i++)
584
+ fns[i](v, p, outV, outP, root);
585
+ };
586
+ if (seg.descendant) {
587
+ return (inV, inP, outV, outP, root) => {
588
+ for (let i = 0; i < inV.length; i++)
589
+ descendP(inV[i], inP[i], outV, outP, root, apply);
590
+ };
591
+ }
592
+ return (inV, inP, outV, outP, root) => {
593
+ for (let i = 0; i < inV.length; i++)
594
+ apply(inV[i], inP[i], outV, outP, root);
595
+ };
596
+ }
597
+
598
+ /**
599
+ * Run a chain of compiled nodes-mode segment functions over a start
600
+ * value, threading normalized paths alongside values.
601
+ * @param {Function[]} segs - segment functions from compileSegmentP
602
+ * @param {any} startValue - the value the first segment applies to
603
+ * @param {string} startPath - the base normalized path of startValue
604
+ * (`'$'` for the document root)
605
+ * @param {any} root - the query root (`$` inside embedded filters)
606
+ * @returns {{ vals: any[], paths: string[] }} parallel arrays of the
607
+ * resulting nodelist's values and normalized paths
608
+ */
609
+ export function runSegmentsP(segs, startValue, startPath, root) {
610
+ let vals = [startValue];
611
+ let paths = [startPath];
612
+ const slen = segs.length;
613
+ for (let i = 0; i < slen; i++) {
614
+ if (vals.length === 0)
615
+ break;
616
+ const outV = [];
617
+ const outP = [];
618
+ segs[i](vals, paths, outV, outP, root);
619
+ vals = outV;
620
+ paths = outP;
621
+ }
622
+ return { vals, paths };
623
+ }
624
+
625
+ //#endregion
626
+
627
+ //#endregion
@@ -0,0 +1,35 @@
1
+ //#region XQuery text front-end public API
2
+ // The optional XQuery 3.1 text front-end of the Jaren JSON Query engine:
3
+ // parseXQuery turns text in the supported subset (XQUERY-FRONTEND.md)
4
+ // into a query document, compileXQuery is the parse + compile
5
+ // convenience. The JSON query document is the canonical language
6
+ // (QUERY-FORMAT.md); this module adds a surface syntax, not a second
7
+ // engine.
8
+
9
+ import { compileJsonQuery } from '../query/index.js';
10
+ import { parseXQuery } from './parse.js';
11
+
12
+ export { parseXQuery, XQuerySyntaxError } from './parse.js';
13
+
14
+ /**
15
+ * Parse XQuery text and compile the resulting query document in one call.
16
+ * Returns the engine's compiled query function (see `compileJsonQuery`):
17
+ * `query(data, externals?)` plus the `first`/`exists`/`externals`/`doc`
18
+ * helpers - `doc` holds the emitted query document.
19
+ * @param {string} text - XQuery text in the supported subset
20
+ * @param {object} [options] - passed through to `compileJsonQuery`
21
+ * @returns {function} the compiled query function
22
+ * @throws {XQuerySyntaxError} when the text is invalid or outside the subset
23
+ * @throws {import('../query/errors.js').JsonQueryCompileError} when the
24
+ * emitted document is rejected by the compiler (e.g. duplicate variable
25
+ * bindings, JQ0007)
26
+ * @example
27
+ * const q = compileXQuery('for $b in $doc?store?book?* where $b?price lt 10 return $b?title');
28
+ * q.externals; // ['doc']
29
+ * q(null, { doc: data }); // ['Sayings of the Century', 'Moby Dick']
30
+ */
31
+ export function compileXQuery(text, options) {
32
+ return compileJsonQuery(parseXQuery(text), options);
33
+ }
34
+
35
+ //#endregion