jilt 0.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.
@@ -0,0 +1,792 @@
1
+ export class EvaluationError extends Error {
2
+ code;
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.name = 'EvaluationError';
6
+ this.code = code;
7
+ }
8
+ }
9
+ export function normalizeStrict(strict) {
10
+ if (strict === null || strict === undefined)
11
+ return true;
12
+ return strict;
13
+ }
14
+ export function compile(node, options = {}) {
15
+ const strict = normalizeStrict(options.strict);
16
+ const allowFields = options.allowFields ?? true;
17
+ return (obj) => {
18
+ const value = evalExpr(node, { root: obj, current: undefined, strict, allowFields });
19
+ return toBoolean(value, strict);
20
+ };
21
+ }
22
+ export function evaluate(node, obj, options = {}) {
23
+ return compile(node, options)(obj);
24
+ }
25
+ export function evaluateConstant(node, options = {}) {
26
+ const strict = normalizeStrict(options.strict);
27
+ const allowFields = options.allowFields ?? false;
28
+ return evalExpr(node, { root: undefined, current: undefined, strict, allowFields });
29
+ }
30
+ export function toTree(node) {
31
+ switch (node.type) {
32
+ case 'literal':
33
+ return ['literal', node.value];
34
+ case 'field':
35
+ return fieldToTree(node);
36
+ case 'optfield':
37
+ return ['optfield', toTree(node.base), node.field];
38
+ case 'index':
39
+ return ['index', toTree(node.base), toTree(node.index)];
40
+ case 'call':
41
+ return ['call', node.name, ...node.args.map(toTree)];
42
+ case 'compare':
43
+ return [node.op, toTree(node.left), toTree(node.right)];
44
+ case 'and':
45
+ case 'or':
46
+ return [node.type, ...flattenLogical(node.type, node).map(toTree)];
47
+ case 'not':
48
+ return ['not', toTree(node.expr)];
49
+ case 'neg':
50
+ return ['sub', ['literal', 0], toTree(node.expr)];
51
+ case 'binary':
52
+ return [node.op, toTree(node.left), toTree(node.right)];
53
+ case 'if':
54
+ return ['if', toTree(node.cond), toTree(node.thenExpr), toTree(node.elseExpr)];
55
+ case 'coalesce':
56
+ return ['coalesce', toTree(node.left), toTree(node.right)];
57
+ default:
58
+ return assertNever(node);
59
+ }
60
+ }
61
+ function fieldToTree(node) {
62
+ if (node.base === null) {
63
+ return ['field', ...node.parts];
64
+ }
65
+ const baseTree = toTree(node.base);
66
+ return ['field', baseTree, ...node.parts];
67
+ }
68
+ function flattenLogical(kind, node) {
69
+ const items = [];
70
+ const walk = (expr) => {
71
+ if (expr.type === kind) {
72
+ walk(expr.left);
73
+ walk(expr.right);
74
+ }
75
+ else {
76
+ items.push(expr);
77
+ }
78
+ };
79
+ walk(node);
80
+ return items;
81
+ }
82
+ function evalExpr(node, ctx) {
83
+ switch (node.type) {
84
+ case 'literal':
85
+ return node.value;
86
+ case 'field':
87
+ return evalField(node, ctx);
88
+ case 'optfield':
89
+ return evalOptField(node, ctx);
90
+ case 'index':
91
+ return evalIndex(node, ctx);
92
+ case 'call':
93
+ return evalCall(node, ctx);
94
+ case 'compare':
95
+ return evalCompare(node, ctx);
96
+ case 'and':
97
+ return evalLogical(node, ctx, true);
98
+ case 'or':
99
+ return evalLogical(node, ctx, false);
100
+ case 'not':
101
+ return !toBoolean(evalExpr(node.expr, ctx), ctx.strict);
102
+ case 'neg':
103
+ return evalNeg(node, ctx);
104
+ case 'binary':
105
+ return evalBinary(node, ctx);
106
+ case 'if':
107
+ return evalIf(node, ctx);
108
+ case 'coalesce':
109
+ return evalCoalesce(node, ctx);
110
+ default:
111
+ return assertNever(node);
112
+ }
113
+ }
114
+ function evalField(node, ctx) {
115
+ if (!ctx.allowFields) {
116
+ throw new EvaluationError('Field access is not allowed in constant evaluation');
117
+ }
118
+ const base = node.base ? evalExpr(node.base, ctx) : ctx.root;
119
+ return resolvePath(base, node.parts, ctx);
120
+ }
121
+ function evalOptField(node, ctx) {
122
+ if (!ctx.allowFields) {
123
+ throw new EvaluationError('Field access is not allowed in constant evaluation');
124
+ }
125
+ const base = evalExpr(node.base, ctx);
126
+ if (base === null || base === undefined)
127
+ return null;
128
+ if (typeof base !== 'object')
129
+ return null;
130
+ const record = base;
131
+ if (!(node.field in record))
132
+ return null;
133
+ return record[node.field];
134
+ }
135
+ function evalIndex(node, ctx) {
136
+ if (!ctx.allowFields) {
137
+ throw new EvaluationError('Field access is not allowed in constant evaluation');
138
+ }
139
+ const base = evalExpr(node.base, ctx);
140
+ const indexValue = evalExpr(node.index, ctx);
141
+ if (!Array.isArray(base)) {
142
+ return handleTypeError(ctx, 'Index base is not an array');
143
+ }
144
+ if (typeof indexValue !== 'number' || !Number.isFinite(indexValue)) {
145
+ return handleTypeError(ctx, 'Index is not a number');
146
+ }
147
+ const idx = Math.trunc(indexValue);
148
+ if (idx < 0 || idx >= base.length) {
149
+ if (ctx.strict)
150
+ throw new EvaluationError('Index out of bounds');
151
+ return null;
152
+ }
153
+ return base[idx];
154
+ }
155
+ function evalCompare(node, ctx) {
156
+ const left = evalExpr(node.left, ctx);
157
+ const right = evalExpr(node.right, ctx);
158
+ if (node.op === 'eq' || node.op === 'ne') {
159
+ if (!isPrimitive(left) || !isPrimitive(right)) {
160
+ return handleTypeError(ctx, 'Equality requires primitive values');
161
+ }
162
+ const equal = left === right;
163
+ return node.op === 'eq' ? equal : !equal;
164
+ }
165
+ if (typeof left !== 'number' || typeof right !== 'number') {
166
+ return handleTypeError(ctx, 'Numeric comparison requires numbers');
167
+ }
168
+ switch (node.op) {
169
+ case 'lt':
170
+ return left < right;
171
+ case 'le':
172
+ return left <= right;
173
+ case 'gt':
174
+ return left > right;
175
+ case 'ge':
176
+ return left >= right;
177
+ default:
178
+ return assertNever(node.op);
179
+ }
180
+ }
181
+ function evalLogical(node, ctx, isAnd) {
182
+ const left = toBoolean(evalExpr(node.left, ctx), ctx.strict);
183
+ if (isAnd) {
184
+ return left && toBoolean(evalExpr(node.right, ctx), ctx.strict);
185
+ }
186
+ if (left)
187
+ return true;
188
+ return toBoolean(evalExpr(node.right, ctx), ctx.strict);
189
+ }
190
+ function evalBinary(node, ctx) {
191
+ const left = evalExpr(node.left, ctx);
192
+ const right = evalExpr(node.right, ctx);
193
+ if (typeof left !== 'number' || typeof right !== 'number') {
194
+ return handleTypeError(ctx, 'Arithmetic requires numbers');
195
+ }
196
+ switch (node.op) {
197
+ case 'add':
198
+ return left + right;
199
+ case 'sub':
200
+ return left - right;
201
+ case 'mul':
202
+ return left * right;
203
+ case 'div':
204
+ return left / right;
205
+ case 'mod':
206
+ return left % right;
207
+ default:
208
+ return assertNever(node.op);
209
+ }
210
+ }
211
+ function evalNeg(node, ctx) {
212
+ const value = evalExpr(node.expr, ctx);
213
+ if (typeof value !== 'number') {
214
+ return handleTypeError(ctx, 'Unary minus expects number');
215
+ }
216
+ return -value;
217
+ }
218
+ function evalIf(node, ctx) {
219
+ const cond = toBoolean(evalExpr(node.cond, ctx), ctx.strict);
220
+ return cond ? evalExpr(node.thenExpr, ctx) : evalExpr(node.elseExpr, ctx);
221
+ }
222
+ function evalCoalesce(node, ctx) {
223
+ let left;
224
+ try {
225
+ left = evalExpr(node.left, ctx);
226
+ }
227
+ catch (err) {
228
+ if (err instanceof EvaluationError && err.code === 'missing') {
229
+ left = null;
230
+ }
231
+ else {
232
+ throw err;
233
+ }
234
+ }
235
+ if (left === null || left === undefined)
236
+ return evalExpr(node.right, ctx);
237
+ return left;
238
+ }
239
+ function evalCall(node, ctx) {
240
+ switch (node.name) {
241
+ case 'length':
242
+ return lengthFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
243
+ case 'upper':
244
+ return stringUnary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, (s) => s.toUpperCase());
245
+ case 'lower':
246
+ return stringUnary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, (s) => s.toLowerCase());
247
+ case 'trim':
248
+ return stringUnary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, (s) => s.trim());
249
+ case 'contains':
250
+ return containsFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
251
+ case 'startsWith':
252
+ return stringBinary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, (s, p) => s.startsWith(p));
253
+ case 'endsWith':
254
+ return stringBinary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, (s, p) => s.endsWith(p));
255
+ case 'substr':
256
+ return substrFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
257
+ case 'concat':
258
+ return concatFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
259
+ case 'replace':
260
+ return replaceFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
261
+ case 'split':
262
+ return splitFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
263
+ case 'regex':
264
+ return regexFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
265
+ case 'like':
266
+ return likeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
267
+ case 'glob':
268
+ return globFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
269
+ case 'basename':
270
+ return pathUnary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, basename);
271
+ case 'dirname':
272
+ return pathUnary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, dirname);
273
+ case 'ext':
274
+ return pathUnary(node.args.map((arg) => evalExpr(arg, ctx)), ctx, extname);
275
+ case 'first':
276
+ return firstFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
277
+ case 'last':
278
+ return lastFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
279
+ case 'at':
280
+ return atFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
281
+ case 'sum':
282
+ return sumFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
283
+ case 'avg':
284
+ return avgFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
285
+ case 'min':
286
+ return minFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
287
+ case 'max':
288
+ return maxFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
289
+ case 'join':
290
+ return joinFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
291
+ case 'reverse':
292
+ return reverseFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
293
+ case 'sort':
294
+ return sortFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
295
+ case 'unique':
296
+ return uniqueFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
297
+ case 'any':
298
+ return predicateFn(node.args, ctx, 'any');
299
+ case 'all':
300
+ return predicateFn(node.args, ctx, 'all');
301
+ case 'none':
302
+ return predicateFn(node.args, ctx, 'none');
303
+ case 'count':
304
+ return predicateFn(node.args, ctx, 'count');
305
+ case 'filter':
306
+ return predicateFn(node.args, ctx, 'filter');
307
+ case 'map':
308
+ return predicateFn(node.args, ctx, 'map');
309
+ case 'type':
310
+ return typeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
311
+ case 'isString':
312
+ return isTypeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx, 'string');
313
+ case 'isNumber':
314
+ return isTypeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx, 'number');
315
+ case 'isBool':
316
+ return isTypeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx, 'bool');
317
+ case 'isNull':
318
+ return isTypeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx, 'null');
319
+ case 'isArray':
320
+ return isTypeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx, 'array');
321
+ case 'isObject':
322
+ return isTypeFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx, 'object');
323
+ case 'if':
324
+ return ifCallFn(node.args.map((arg) => evalExpr(arg, ctx)), ctx);
325
+ default:
326
+ throw new EvaluationError(`Unknown function '${node.name}'`);
327
+ }
328
+ }
329
+ function lengthFn(args, ctx) {
330
+ if (args.length !== 1)
331
+ return handleArity(ctx, 'length');
332
+ const value = args[0];
333
+ if (typeof value === 'string' || Array.isArray(value))
334
+ return value.length;
335
+ return handleTypeError(ctx, 'length expects string or array');
336
+ }
337
+ function stringUnary(args, ctx, fn) {
338
+ if (args.length !== 1)
339
+ return handleArity(ctx, 'string function');
340
+ const value = args[0];
341
+ if (typeof value !== 'string')
342
+ return handleTypeError(ctx, 'Expected string');
343
+ return fn(value);
344
+ }
345
+ function stringBinary(args, ctx, fn) {
346
+ if (args.length !== 2)
347
+ return handleArity(ctx, 'string predicate');
348
+ const [value, pattern] = args;
349
+ if (typeof value !== 'string' || typeof pattern !== 'string') {
350
+ return handleTypeError(ctx, 'Expected string arguments');
351
+ }
352
+ return fn(value, pattern);
353
+ }
354
+ function containsFn(args, ctx) {
355
+ if (args.length !== 2)
356
+ return handleArity(ctx, 'contains');
357
+ const [container, value] = args;
358
+ if (typeof container === 'string' && typeof value === 'string') {
359
+ return container.includes(value);
360
+ }
361
+ if (Array.isArray(container)) {
362
+ return container.some((item) => item === value);
363
+ }
364
+ return handleTypeError(ctx, 'contains expects string or array');
365
+ }
366
+ function substrFn(args, ctx) {
367
+ if (args.length < 2 || args.length > 3)
368
+ return handleArity(ctx, 'substr');
369
+ const [value, start, len] = args;
370
+ if (typeof value !== 'string')
371
+ return handleTypeError(ctx, 'substr expects string');
372
+ if (typeof start !== 'number')
373
+ return handleTypeError(ctx, 'substr expects numeric start');
374
+ const idx = Math.trunc(start);
375
+ if (len === undefined)
376
+ return value.substring(idx);
377
+ if (typeof len !== 'number')
378
+ return handleTypeError(ctx, 'substr expects numeric length');
379
+ return value.substring(idx, idx + Math.trunc(len));
380
+ }
381
+ function concatFn(args, ctx) {
382
+ if (args.length < 1)
383
+ return handleArity(ctx, 'concat');
384
+ const parts = [];
385
+ for (const item of args) {
386
+ if (typeof item !== 'string')
387
+ return handleTypeError(ctx, 'concat expects strings');
388
+ parts.push(item);
389
+ }
390
+ return parts.join('');
391
+ }
392
+ function replaceFn(args, ctx) {
393
+ if (args.length !== 3)
394
+ return handleArity(ctx, 'replace');
395
+ const [value, oldValue, newValue] = args;
396
+ if (typeof value !== 'string' || typeof oldValue !== 'string' || typeof newValue !== 'string') {
397
+ return handleTypeError(ctx, 'replace expects string arguments');
398
+ }
399
+ return value.replace(oldValue, newValue);
400
+ }
401
+ function splitFn(args, ctx) {
402
+ if (args.length !== 2)
403
+ return handleArity(ctx, 'split');
404
+ const [value, delim] = args;
405
+ if (typeof value !== 'string' || typeof delim !== 'string') {
406
+ return handleTypeError(ctx, 'split expects strings');
407
+ }
408
+ return value.split(delim);
409
+ }
410
+ function regexFn(args, ctx) {
411
+ if (args.length !== 2)
412
+ return handleArity(ctx, 'regex');
413
+ const [value, pattern] = args;
414
+ if (typeof value !== 'string' || typeof pattern !== 'string') {
415
+ return handleTypeError(ctx, 'regex expects strings');
416
+ }
417
+ const re = new RegExp(pattern);
418
+ return re.test(value);
419
+ }
420
+ function likeFn(args, ctx) {
421
+ if (args.length !== 2)
422
+ return handleArity(ctx, 'like');
423
+ const [value, pattern] = args;
424
+ if (typeof value !== 'string' || typeof pattern !== 'string') {
425
+ return handleTypeError(ctx, 'like expects strings');
426
+ }
427
+ return compileLike(pattern).test(value);
428
+ }
429
+ function globFn(args, ctx) {
430
+ if (args.length !== 2)
431
+ return handleArity(ctx, 'glob');
432
+ const [value, pattern] = args;
433
+ if (typeof value !== 'string' || typeof pattern !== 'string') {
434
+ return handleTypeError(ctx, 'glob expects strings');
435
+ }
436
+ return compileGlob(pattern).test(value);
437
+ }
438
+ function pathUnary(args, ctx, fn) {
439
+ if (args.length !== 1)
440
+ return handleArity(ctx, 'path');
441
+ const [value] = args;
442
+ if (typeof value !== 'string')
443
+ return handleTypeError(ctx, 'path expects string');
444
+ return fn(value);
445
+ }
446
+ function firstFn(args, ctx) {
447
+ if (args.length !== 1)
448
+ return handleArity(ctx, 'first');
449
+ const [arr] = args;
450
+ if (!Array.isArray(arr))
451
+ return handleTypeError(ctx, 'first expects array');
452
+ if (arr.length === 0) {
453
+ if (ctx.strict)
454
+ throw new EvaluationError('first on empty array');
455
+ return null;
456
+ }
457
+ return arr[0];
458
+ }
459
+ function lastFn(args, ctx) {
460
+ if (args.length !== 1)
461
+ return handleArity(ctx, 'last');
462
+ const [arr] = args;
463
+ if (!Array.isArray(arr))
464
+ return handleTypeError(ctx, 'last expects array');
465
+ if (arr.length === 0) {
466
+ if (ctx.strict)
467
+ throw new EvaluationError('last on empty array');
468
+ return null;
469
+ }
470
+ return arr[arr.length - 1];
471
+ }
472
+ function atFn(args, ctx) {
473
+ if (args.length !== 2)
474
+ return handleArity(ctx, 'at');
475
+ const [arr, index] = args;
476
+ if (!Array.isArray(arr))
477
+ return handleTypeError(ctx, 'at expects array');
478
+ if (typeof index !== 'number' || !Number.isFinite(index)) {
479
+ return handleTypeError(ctx, 'at expects numeric index');
480
+ }
481
+ const idx = Math.trunc(index);
482
+ if (idx < 0 || idx >= arr.length) {
483
+ if (ctx.strict)
484
+ throw new EvaluationError('at index out of bounds');
485
+ return null;
486
+ }
487
+ return arr[idx];
488
+ }
489
+ function sumFn(args, ctx) {
490
+ if (args.length !== 1)
491
+ return handleArity(ctx, 'sum');
492
+ const [arr] = args;
493
+ if (!Array.isArray(arr))
494
+ return handleTypeError(ctx, 'sum expects array');
495
+ let total = 0;
496
+ for (const item of arr) {
497
+ if (typeof item !== 'number')
498
+ return handleTypeError(ctx, 'sum expects numeric array');
499
+ total += item;
500
+ }
501
+ return total;
502
+ }
503
+ function avgFn(args, ctx) {
504
+ if (args.length !== 1)
505
+ return handleArity(ctx, 'avg');
506
+ const [arr] = args;
507
+ if (!Array.isArray(arr))
508
+ return handleTypeError(ctx, 'avg expects array');
509
+ if (arr.length === 0) {
510
+ if (ctx.strict)
511
+ throw new EvaluationError('avg on empty array');
512
+ return NaN;
513
+ }
514
+ return sumFn([arr], ctx) / arr.length;
515
+ }
516
+ function minFn(args, ctx) {
517
+ if (args.length !== 1)
518
+ return handleArity(ctx, 'min');
519
+ const [arr] = args;
520
+ if (!Array.isArray(arr))
521
+ return handleTypeError(ctx, 'min expects array');
522
+ if (arr.length === 0) {
523
+ if (ctx.strict)
524
+ throw new EvaluationError('min on empty array');
525
+ return NaN;
526
+ }
527
+ let min = Infinity;
528
+ for (const item of arr) {
529
+ if (typeof item !== 'number')
530
+ return handleTypeError(ctx, 'min expects numeric array');
531
+ if (item < min)
532
+ min = item;
533
+ }
534
+ return min;
535
+ }
536
+ function maxFn(args, ctx) {
537
+ if (args.length !== 1)
538
+ return handleArity(ctx, 'max');
539
+ const [arr] = args;
540
+ if (!Array.isArray(arr))
541
+ return handleTypeError(ctx, 'max expects array');
542
+ if (arr.length === 0) {
543
+ if (ctx.strict)
544
+ throw new EvaluationError('max on empty array');
545
+ return NaN;
546
+ }
547
+ let max = -Infinity;
548
+ for (const item of arr) {
549
+ if (typeof item !== 'number')
550
+ return handleTypeError(ctx, 'max expects numeric array');
551
+ if (item > max)
552
+ max = item;
553
+ }
554
+ return max;
555
+ }
556
+ function joinFn(args, ctx) {
557
+ if (args.length !== 2)
558
+ return handleArity(ctx, 'join');
559
+ const [arr, delim] = args;
560
+ if (!Array.isArray(arr))
561
+ return handleTypeError(ctx, 'join expects array');
562
+ if (typeof delim !== 'string')
563
+ return handleTypeError(ctx, 'join expects string delimiter');
564
+ for (const item of arr) {
565
+ if (typeof item !== 'string')
566
+ return handleTypeError(ctx, 'join expects string array');
567
+ }
568
+ return arr.join(delim);
569
+ }
570
+ function reverseFn(args, ctx) {
571
+ if (args.length !== 1)
572
+ return handleArity(ctx, 'reverse');
573
+ const [arr] = args;
574
+ if (!Array.isArray(arr))
575
+ return handleTypeError(ctx, 'reverse expects array');
576
+ return [...arr].reverse();
577
+ }
578
+ function sortFn(args, ctx) {
579
+ if (args.length !== 1)
580
+ return handleArity(ctx, 'sort');
581
+ const [arr] = args;
582
+ if (!Array.isArray(arr))
583
+ return handleTypeError(ctx, 'sort expects array');
584
+ const withIndex = arr.map((value, index) => ({ value, index }));
585
+ if (withIndex.every((item) => typeof item.value === 'number')) {
586
+ withIndex.sort((a, b) => a.value - b.value || a.index - b.index);
587
+ }
588
+ else if (withIndex.every((item) => typeof item.value === 'string')) {
589
+ withIndex.sort((a, b) => a.value.localeCompare(b.value) || a.index - b.index);
590
+ }
591
+ else {
592
+ return handleTypeError(ctx, 'sort expects array of numbers or strings');
593
+ }
594
+ return withIndex.map((item) => item.value);
595
+ }
596
+ function uniqueFn(args, ctx) {
597
+ if (args.length !== 1)
598
+ return handleArity(ctx, 'unique');
599
+ const [arr] = args;
600
+ if (!Array.isArray(arr))
601
+ return handleTypeError(ctx, 'unique expects array');
602
+ const seen = new Set();
603
+ const result = [];
604
+ for (const item of arr) {
605
+ if (!seen.has(item)) {
606
+ seen.add(item);
607
+ result.push(item);
608
+ }
609
+ }
610
+ return result;
611
+ }
612
+ function predicateFn(args, ctx, mode) {
613
+ if (args.length !== 2)
614
+ return handleArity(ctx, mode);
615
+ const [arrExpr, predicate] = args;
616
+ if (!ctx.allowFields) {
617
+ throw new EvaluationError('Array predicates are not allowed in constant evaluation');
618
+ }
619
+ const arr = evalExpr(arrExpr, ctx);
620
+ if (!Array.isArray(arr))
621
+ return handleTypeError(ctx, `${mode} expects array`);
622
+ let count = 0;
623
+ const results = [];
624
+ for (const item of arr) {
625
+ const value = evalExpr(predicate, { ...ctx, current: item });
626
+ if (mode === 'map') {
627
+ results.push(value);
628
+ }
629
+ else {
630
+ const ok = toBoolean(value, ctx.strict);
631
+ if (ok)
632
+ count += 1;
633
+ if (mode === 'any' && ok)
634
+ return true;
635
+ if (mode === 'none' && ok)
636
+ return false;
637
+ }
638
+ }
639
+ if (mode === 'any')
640
+ return false;
641
+ if (mode === 'all')
642
+ return count === arr.length;
643
+ if (mode === 'none')
644
+ return true;
645
+ if (mode === 'count')
646
+ return count;
647
+ if (mode === 'filter') {
648
+ const filtered = [];
649
+ let idx = 0;
650
+ for (const item of arr) {
651
+ const ok = toBoolean(evalExpr(predicate, { ...ctx, current: item }), ctx.strict);
652
+ if (ok)
653
+ filtered.push(item);
654
+ idx += 1;
655
+ }
656
+ return filtered;
657
+ }
658
+ if (mode === 'map')
659
+ return results;
660
+ return assertNever(mode);
661
+ }
662
+ function typeFn(args, ctx) {
663
+ if (args.length !== 1)
664
+ return handleArity(ctx, 'type');
665
+ return typeOf(args[0]);
666
+ }
667
+ function isTypeFn(args, ctx, type) {
668
+ if (args.length !== 1)
669
+ return handleArity(ctx, 'isType');
670
+ return typeOf(args[0]) === type;
671
+ }
672
+ function ifCallFn(args, ctx) {
673
+ if (args.length !== 3)
674
+ return handleArity(ctx, 'if');
675
+ const [cond, thenValue, elseValue] = args;
676
+ return toBoolean(cond, ctx.strict) ? thenValue : elseValue;
677
+ }
678
+ function resolvePath(base, parts, ctx) {
679
+ let current = base;
680
+ for (let i = 0; i < parts.length; i += 1) {
681
+ const part = parts[i];
682
+ if (part === '@' && i === 0) {
683
+ if (ctx.current === undefined)
684
+ return handleTypeError(ctx, 'Current value not available');
685
+ current = ctx.current;
686
+ continue;
687
+ }
688
+ if (current === null || current === undefined || typeof current !== 'object') {
689
+ return handleMissing(ctx, `Missing path '${parts.join('.')}'`);
690
+ }
691
+ const record = current;
692
+ if (!(part in record)) {
693
+ return handleMissing(ctx, `Missing path '${parts.join('.')}'`);
694
+ }
695
+ current = record[part];
696
+ }
697
+ return current;
698
+ }
699
+ function toBoolean(value, strict) {
700
+ if (typeof value === 'boolean')
701
+ return value;
702
+ if (strict)
703
+ throw new EvaluationError('Expected boolean expression');
704
+ return Boolean(value);
705
+ }
706
+ function isPrimitive(value) {
707
+ return (typeof value === 'string' ||
708
+ typeof value === 'number' ||
709
+ typeof value === 'boolean' ||
710
+ value === null);
711
+ }
712
+ function typeOf(value) {
713
+ if (value === null)
714
+ return 'null';
715
+ if (Array.isArray(value))
716
+ return 'array';
717
+ switch (typeof value) {
718
+ case 'string':
719
+ return 'string';
720
+ case 'number':
721
+ return 'number';
722
+ case 'boolean':
723
+ return 'bool';
724
+ case 'object':
725
+ return 'object';
726
+ default:
727
+ return 'object';
728
+ }
729
+ }
730
+ function handleArity(ctx, name) {
731
+ throw new EvaluationError(`Invalid arity for ${name}`, 'arity');
732
+ }
733
+ function handleTypeError(ctx, message) {
734
+ if (ctx.strict)
735
+ throw new EvaluationError(message, 'type');
736
+ return null;
737
+ }
738
+ function handleMissing(ctx, message) {
739
+ if (ctx.strict)
740
+ throw new EvaluationError(message, 'missing');
741
+ return null;
742
+ }
743
+ function basename(path) {
744
+ const idx = path.lastIndexOf('/');
745
+ if (idx === -1)
746
+ return path;
747
+ return path.slice(idx + 1);
748
+ }
749
+ function dirname(path) {
750
+ const idx = path.lastIndexOf('/');
751
+ if (idx === -1)
752
+ return '';
753
+ return path.slice(0, idx);
754
+ }
755
+ function extname(path) {
756
+ const base = basename(path);
757
+ const idx = base.lastIndexOf('.');
758
+ if (idx <= 0)
759
+ return '';
760
+ return base.slice(idx + 1);
761
+ }
762
+ function compileLike(pattern) {
763
+ let regex = '';
764
+ for (const ch of pattern) {
765
+ if (ch === '%')
766
+ regex += '.*';
767
+ else if (ch === '_')
768
+ regex += '.';
769
+ else
770
+ regex += escapeRegex(ch);
771
+ }
772
+ return new RegExp(`^${regex}$`);
773
+ }
774
+ function compileGlob(pattern) {
775
+ let regex = '';
776
+ for (const ch of pattern) {
777
+ if (ch === '*')
778
+ regex += '.*';
779
+ else if (ch === '?')
780
+ regex += '.';
781
+ else
782
+ regex += escapeRegex(ch);
783
+ }
784
+ return new RegExp(`^${regex}$`);
785
+ }
786
+ function escapeRegex(ch) {
787
+ return ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
788
+ }
789
+ function assertNever(value) {
790
+ throw new EvaluationError(`Unexpected value: ${String(value)}`);
791
+ }
792
+ //# sourceMappingURL=compiler.js.map