@telorun/cel 0.107.0 → 0.108.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/dist/backend-runtime.d.ts +46 -8
- package/dist/backend-runtime.d.ts.map +1 -1
- package/dist/backend-runtime.js +133 -30
- package/dist/catalog-runtime.d.ts.map +1 -1
- package/dist/catalog-runtime.js +110 -109
- package/dist/cel-map-value.d.ts +20 -7
- package/dist/cel-map-value.d.ts.map +1 -1
- package/dist/cel-map-value.js +29 -18
- package/dist/cel-value.d.ts +10 -3
- package/dist/cel-value.d.ts.map +1 -1
- package/dist/check-diagnostic.d.ts +3 -2
- package/dist/check-diagnostic.d.ts.map +1 -1
- package/dist/checker.d.ts +9 -0
- package/dist/checker.d.ts.map +1 -1
- package/dist/checker.js +98 -13
- package/dist/closure-backend.js +66 -15
- package/dist/duration-value.d.ts +11 -0
- package/dist/duration-value.d.ts.map +1 -1
- package/dist/duration-value.js +17 -3
- package/dist/emitted-module.d.ts +1 -1
- package/dist/emitted-module.js +1 -1
- package/dist/engine-version.d.ts +1 -1
- package/dist/engine-version.js +1 -1
- package/dist/environment.d.ts +8 -0
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +20 -5
- package/dist/function-registry.d.ts +40 -0
- package/dist/function-registry.d.ts.map +1 -1
- package/dist/function-registry.js +105 -2
- package/dist/index.d.ts +10 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/js-emitter.d.ts +3 -2
- package/dist/js-emitter.d.ts.map +1 -1
- package/dist/js-emitter.js +11 -5
- package/dist/macro-check.d.ts +8 -0
- package/dist/macro-check.d.ts.map +1 -1
- package/dist/macro-check.js +1 -1
- package/dist/member-read.d.ts +42 -5
- package/dist/member-read.d.ts.map +1 -1
- package/dist/member-read.js +47 -6
- package/dist/resolved-call.d.ts +13 -0
- package/dist/resolved-call.d.ts.map +1 -1
- package/dist/runtime-library.d.ts +13 -2
- package/dist/runtime-library.d.ts.map +1 -1
- package/dist/runtime-library.js +134 -125
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +20 -4
- package/dist/timestamp-value.d.ts +9 -0
- package/dist/timestamp-value.d.ts.map +1 -1
- package/dist/timestamp-value.js +15 -0
- package/dist/type-expression.d.ts +18 -2
- package/dist/type-expression.d.ts.map +1 -1
- package/dist/type-expression.js +23 -3
- package/dist/value-equality.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/backend-runtime.ts +180 -30
- package/src/catalog-runtime.ts +133 -133
- package/src/cel-map-value.ts +31 -20
- package/src/cel-value.ts +11 -3
- package/src/check-diagnostic.ts +4 -2
- package/src/checker.ts +123 -10
- package/src/closure-backend.ts +59 -14
- package/src/duration-value.ts +18 -3
- package/src/emitted-module.ts +1 -1
- package/src/engine-version.ts +1 -1
- package/src/environment.ts +26 -5
- package/src/function-registry.ts +114 -2
- package/src/index.ts +24 -3
- package/src/js-emitter.ts +13 -5
- package/src/macro-check.ts +7 -1
- package/src/member-read.ts +48 -7
- package/src/resolved-call.ts +13 -0
- package/src/runtime-library.ts +167 -143
- package/src/serializer.ts +19 -3
- package/src/timestamp-value.ts +16 -0
- package/src/type-expression.ts +25 -3
- package/src/value-equality.ts +3 -3
package/src/backend-runtime.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
import type { CelError, CelOptional, CelValue } from "./cel-value.js";
|
|
40
40
|
import {
|
|
41
41
|
asyncValueRefused,
|
|
42
|
+
CEL_VALUE_TYPE,
|
|
42
43
|
celError,
|
|
43
44
|
celNone,
|
|
44
45
|
celSome,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
isCelOptional,
|
|
48
49
|
} from "./cel-value.js";
|
|
49
50
|
import type { FunctionRegistry, Resolution, ResolutionFailure } from "./function-registry.js";
|
|
50
|
-
import { celHas, celLookup, celRead,
|
|
51
|
+
import { celHas, celLookup, celRead, lookupAbsence, lookupError } from "./member-read.js";
|
|
51
52
|
import type { CelCallContext, CelImplementation } from "./runtime-library.js";
|
|
52
53
|
import { implementationOf } from "./runtime-library.js";
|
|
53
54
|
import type { CallForm } from "./signature.js";
|
|
@@ -65,7 +66,19 @@ export class CelCompileError extends Error {
|
|
|
65
66
|
export const CALL_SITE_CACHE_CAPACITY = 16;
|
|
66
67
|
|
|
67
68
|
/** What a namespaced call is dispatched through, when something bound it. */
|
|
68
|
-
|
|
69
|
+
/**
|
|
70
|
+
* A function bound under a namespace. Its arguments arrive as **one array**, where a
|
|
71
|
+
* registered overload's arrive positionally: a namespace declaration may withhold its
|
|
72
|
+
* parameter list entirely, so the count is the host's and not the dispatch key's. That is
|
|
73
|
+
* the whole rule — positional where the key fixes the arity, one array where the author
|
|
74
|
+
* decides it.
|
|
75
|
+
*/
|
|
76
|
+
export type NamespaceImplementation = (args: readonly CelValue[], ctx: CelCallContext) => CelValue;
|
|
77
|
+
|
|
78
|
+
export type NamespaceDispatch = (
|
|
79
|
+
namespace: string,
|
|
80
|
+
name: string,
|
|
81
|
+
) => NamespaceImplementation | undefined;
|
|
69
82
|
|
|
70
83
|
export interface EvaluationFrame {
|
|
71
84
|
readonly activation: CelActivation;
|
|
@@ -211,9 +224,13 @@ export function plainMemberChain(
|
|
|
211
224
|
/**
|
|
212
225
|
* A member read in every form. Reading **through an optional** answers an optional
|
|
213
226
|
* whichever form the read is written in, which is what lets a chain over a value that
|
|
214
|
-
* may be absent stay one expression: an absent one propagates as absent, a key the
|
|
215
|
-
* value does not have is absent too
|
|
216
|
-
*
|
|
227
|
+
* may be absent stay one expression: an absent one propagates as absent, and a key the
|
|
228
|
+
* held value does not have is absent too.
|
|
229
|
+
*
|
|
230
|
+
* Which refusals are absence and which are a mistake is `lookupAbsence`'s, and it turns
|
|
231
|
+
* on the FORM the read is written in rather than on what the operand turned out to be: a
|
|
232
|
+
* presence-shaped read over a value that holds no members is absent, while the ordinary
|
|
233
|
+
* read of a member of such a value is the mistake it is outside an optional too.
|
|
217
234
|
*/
|
|
218
235
|
export function readThrough(
|
|
219
236
|
container: CelValue,
|
|
@@ -221,31 +238,61 @@ export function readThrough(
|
|
|
221
238
|
optionalForm: boolean,
|
|
222
239
|
range: SourceRange,
|
|
223
240
|
): CelValue {
|
|
241
|
+
// The shape nearly every read has — a record's own string-keyed entry — decided by asking
|
|
242
|
+
// what the container is ONCE. The general path below is three total steps, so it reads the
|
|
243
|
+
// type key twice and the prototype again; this asks each question once and falls through
|
|
244
|
+
// to the one seam for every other container, every other key and every refusal.
|
|
245
|
+
if (!optionalForm && typeof key === "string" && typeof container === "object" && container !== null) {
|
|
246
|
+
if ((container as { [CEL_VALUE_TYPE]?: unknown })[CEL_VALUE_TYPE] === undefined) {
|
|
247
|
+
const prototype = Object.getPrototypeOf(container) as object | null;
|
|
248
|
+
if (
|
|
249
|
+
(prototype === Object.prototype || prototype === null) &&
|
|
250
|
+
Object.prototype.hasOwnProperty.call(container, key)
|
|
251
|
+
) {
|
|
252
|
+
const held = (container as Record<string, CelValue>)[key] as CelValue;
|
|
253
|
+
// The thenable door, inline: `readHostValue` is this test and nothing else.
|
|
254
|
+
if (typeof held !== "object" || held === null || typeof (held as { then?: unknown }).then !== "function") {
|
|
255
|
+
return held;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
224
260
|
if (isCelOptional(container)) {
|
|
225
261
|
if (!container.present) return celNone();
|
|
226
|
-
return optionalRead(container.held as CelValue, key, range);
|
|
262
|
+
return optionalRead(container.held as CelValue, key, optionalForm, range);
|
|
227
263
|
}
|
|
228
|
-
if (optionalForm) return optionalRead(container, key, range);
|
|
264
|
+
if (optionalForm) return optionalRead(container, key, true, range);
|
|
229
265
|
// **A member read is a door a host value comes through**, and the value it answers is
|
|
230
266
|
// whatever the host put inside its own object: `readHostValue` is what refuses a thenable
|
|
231
267
|
// there, rather than the aggregate, the operator or the exit that happens to see it next.
|
|
232
268
|
return readHostValue(celRead(container, key, range), range);
|
|
233
269
|
}
|
|
234
270
|
|
|
235
|
-
|
|
271
|
+
/**
|
|
272
|
+
* A read that answers an optional. `presence` is whether the read was WRITTEN in a
|
|
273
|
+
* presence-shaped form (`.?`, `[?]`), which is what decides whether a value holding no
|
|
274
|
+
* members is absence or a mistake.
|
|
275
|
+
*/
|
|
276
|
+
function optionalRead(
|
|
277
|
+
container: CelValue,
|
|
278
|
+
key: CelValue,
|
|
279
|
+
presence: boolean,
|
|
280
|
+
range: SourceRange,
|
|
281
|
+
): CelValue {
|
|
236
282
|
const found = celLookup(container, key);
|
|
237
283
|
if (typeof found !== "symbol") {
|
|
238
284
|
const held = readHostValue(found, range);
|
|
239
285
|
// A value that must be awaited is refused rather than carried as a present optional.
|
|
240
286
|
return isCelError(held) ? held : celSome(held);
|
|
241
287
|
}
|
|
242
|
-
if (found
|
|
288
|
+
if (lookupAbsence(found, presence)) return celNone();
|
|
243
289
|
return lookupError(found, key, range);
|
|
244
290
|
}
|
|
245
291
|
|
|
246
292
|
/**
|
|
247
|
-
* `has(a.b)` — presence, which a missing key
|
|
248
|
-
* An absent optional has no members, and a
|
|
293
|
+
* `has(a.b)` — presence, which a missing key and a value that holds no members both
|
|
294
|
+
* answer `false` for rather than erroring. An absent optional has no members, and a
|
|
295
|
+
* present one is asked about what it holds.
|
|
249
296
|
*/
|
|
250
297
|
export function hasMember(container: CelValue, field: CelValue, range: SourceRange): CelValue {
|
|
251
298
|
if (isCelOptional(container)) {
|
|
@@ -318,7 +365,12 @@ function equalityFallback(name: string): CelImplementation | null {
|
|
|
318
365
|
export class CallSite {
|
|
319
366
|
private readonly resolved = new BoundedCache<string, Dispatch | null>(CALL_SITE_CACHE_CAPACITY);
|
|
320
367
|
private readonly context: CelCallContext;
|
|
321
|
-
|
|
368
|
+
/** The arity the last resolution was made under; `-1` until there is one. */
|
|
369
|
+
private count = -1;
|
|
370
|
+
private t0: string | undefined;
|
|
371
|
+
private t1: string | undefined;
|
|
372
|
+
private t2: string | undefined;
|
|
373
|
+
private t3: string | undefined;
|
|
322
374
|
private lastDispatch: Dispatch | null = null;
|
|
323
375
|
|
|
324
376
|
constructor(
|
|
@@ -331,22 +383,111 @@ export class CallSite {
|
|
|
331
383
|
this.context = { range };
|
|
332
384
|
}
|
|
333
385
|
|
|
386
|
+
call0(): CelValue {
|
|
387
|
+
if (this.count === 0) return this.answer(this.lastDispatch);
|
|
388
|
+
return this.resolve(0, undefined, undefined, undefined, undefined);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
call1(a: CelValue): CelValue {
|
|
392
|
+
if (this.count === 1 && celTypeNameOf(a) === this.t0) return this.answer(this.lastDispatch, a);
|
|
393
|
+
return this.resolve(1, a, undefined, undefined, undefined);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
call2(a: CelValue, b: CelValue): CelValue {
|
|
397
|
+
if (this.count === 2 && celTypeNameOf(a) === this.t0 && celTypeNameOf(b) === this.t1) {
|
|
398
|
+
return this.answer(this.lastDispatch, a, b);
|
|
399
|
+
}
|
|
400
|
+
return this.resolve(2, a, b, undefined, undefined);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
call3(a: CelValue, b: CelValue, c: CelValue): CelValue {
|
|
404
|
+
if (
|
|
405
|
+
this.count === 3 &&
|
|
406
|
+
celTypeNameOf(a) === this.t0 &&
|
|
407
|
+
celTypeNameOf(b) === this.t1 &&
|
|
408
|
+
celTypeNameOf(c) === this.t2
|
|
409
|
+
) {
|
|
410
|
+
return this.answer(this.lastDispatch, a, b, c);
|
|
411
|
+
}
|
|
412
|
+
return this.resolve(3, a, b, c, undefined);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
call4(a: CelValue, b: CelValue, c: CelValue, d: CelValue): CelValue {
|
|
416
|
+
if (
|
|
417
|
+
this.count === 4 &&
|
|
418
|
+
celTypeNameOf(a) === this.t0 &&
|
|
419
|
+
celTypeNameOf(b) === this.t1 &&
|
|
420
|
+
celTypeNameOf(c) === this.t2 &&
|
|
421
|
+
celTypeNameOf(d) === this.t3
|
|
422
|
+
) {
|
|
423
|
+
return this.answer(this.lastDispatch, a, b, c, d);
|
|
424
|
+
}
|
|
425
|
+
return this.resolve(4, a, b, c, d);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* The array form, for a caller that holds its arguments as one — a call written WIDER than
|
|
430
|
+
* any overload can be (`'42'.replace('2', '1', 1, false)` is five values, which is a row),
|
|
431
|
+
* the conformance and identity gates, and a host comparing a site cold against warm. It
|
|
432
|
+
* routes to the same entry points, so there is one dispatch path and not two.
|
|
433
|
+
*/
|
|
334
434
|
call(values: readonly CelValue[]): CelValue {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
435
|
+
switch (values.length) {
|
|
436
|
+
case 0:
|
|
437
|
+
return this.call0();
|
|
438
|
+
case 1:
|
|
439
|
+
return this.call1(values[0]!);
|
|
440
|
+
case 2:
|
|
441
|
+
return this.call2(values[0]!, values[1]!);
|
|
442
|
+
case 3:
|
|
443
|
+
return this.call3(values[0]!, values[1]!, values[2]!);
|
|
444
|
+
case 4:
|
|
445
|
+
return this.call4(values[0]!, values[1]!, values[2]!, values[3]!);
|
|
446
|
+
default:
|
|
447
|
+
return this.wide(values);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* A call written with more values than the widest signature may declare. **The arity is
|
|
453
|
+
* the SOURCE's, not the dispatch key's** — anyone may write a call of any width — so this
|
|
454
|
+
* is reachable and answers the ordinary refusal, naming the types it was handed. Nothing
|
|
455
|
+
* resolves here, because a signature that wide is refused where it is registered; the
|
|
456
|
+
* resolution still runs, so one place decides what a call that resolves to nothing says.
|
|
457
|
+
*/
|
|
458
|
+
private wide(values: readonly CelValue[]): CelValue {
|
|
459
|
+
const names: string[] = new Array<string>(values.length);
|
|
460
|
+
for (let at = 0; at < values.length; at += 1) {
|
|
461
|
+
const held = celTypeNameOf(values[at]);
|
|
462
|
+
if (held === undefined) {
|
|
463
|
+
const named = readHostValue(values[at], this.range);
|
|
464
|
+
if (isCelError(named)) return named;
|
|
465
|
+
return celError(
|
|
466
|
+
"no_matching_overload",
|
|
467
|
+
`${this.name} was handed a value of no CEL type`,
|
|
468
|
+
this.range,
|
|
469
|
+
);
|
|
344
470
|
}
|
|
345
|
-
|
|
471
|
+
names[at] = held;
|
|
346
472
|
}
|
|
473
|
+
return celError(
|
|
474
|
+
"no_matching_overload",
|
|
475
|
+
`no overload of ${JSON.stringify(this.name)} takes (${names.join(", ")})`,
|
|
476
|
+
this.range,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private resolve(
|
|
481
|
+
count: number,
|
|
482
|
+
a: CelValue | undefined,
|
|
483
|
+
b: CelValue | undefined,
|
|
484
|
+
c: CelValue | undefined,
|
|
485
|
+
d: CelValue | undefined,
|
|
486
|
+
): CelValue {
|
|
347
487
|
const names: string[] = new Array<string>(count);
|
|
348
488
|
for (let at = 0; at < count; at += 1) {
|
|
349
|
-
const
|
|
489
|
+
const value = at === 0 ? a : at === 1 ? b : at === 2 ? c : d;
|
|
490
|
+
const held = celTypeNameOf(value);
|
|
350
491
|
if (held === undefined) {
|
|
351
492
|
// A value of no CEL type. A **thenable** is one, and a thenable NESTED inside a host
|
|
352
493
|
// value arrives here rather than through the activation read, because a member read
|
|
@@ -354,7 +495,7 @@ export class CallSite {
|
|
|
354
495
|
// shape, so this is the door a host actually uses. `readHostValue` names it for what
|
|
355
496
|
// it is; anything else is the overload failure it was going to be. The cost is on the
|
|
356
497
|
// slow path only: dispatch was about to fail either way.
|
|
357
|
-
const named = readHostValue(
|
|
498
|
+
const named = readHostValue(value, this.range);
|
|
358
499
|
if (isCelError(named)) return named;
|
|
359
500
|
return celError(
|
|
360
501
|
"no_matching_overload",
|
|
@@ -367,7 +508,9 @@ export class CallSite {
|
|
|
367
508
|
const key = names.join(",");
|
|
368
509
|
let dispatch = this.resolved.get(key);
|
|
369
510
|
if (dispatch === undefined) {
|
|
370
|
-
const types =
|
|
511
|
+
const types = names.map((unused, at) =>
|
|
512
|
+
runtimeType((at === 0 ? a : at === 1 ? b : at === 2 ? c : d) as CelValue, this.nominalArity),
|
|
513
|
+
);
|
|
371
514
|
const resolution = this.registry.resolve(
|
|
372
515
|
this.name,
|
|
373
516
|
this.form,
|
|
@@ -377,24 +520,31 @@ export class CallSite {
|
|
|
377
520
|
dispatch = dispatchOf(this.name, resolution);
|
|
378
521
|
this.resolved.set(key, dispatch);
|
|
379
522
|
}
|
|
380
|
-
this.
|
|
523
|
+
this.count = count;
|
|
524
|
+
this.t0 = names[0];
|
|
525
|
+
this.t1 = names[1];
|
|
526
|
+
this.t2 = names[2];
|
|
527
|
+
this.t3 = names[3];
|
|
381
528
|
this.lastDispatch = dispatch;
|
|
382
|
-
return this.answer(dispatch,
|
|
529
|
+
return this.answer(dispatch, a, b, c, d);
|
|
383
530
|
}
|
|
384
531
|
|
|
385
532
|
private answer(
|
|
386
533
|
dispatch: Dispatch | null,
|
|
387
|
-
|
|
388
|
-
|
|
534
|
+
a?: CelValue,
|
|
535
|
+
b?: CelValue,
|
|
536
|
+
c?: CelValue,
|
|
537
|
+
d?: CelValue,
|
|
389
538
|
): CelValue {
|
|
390
539
|
if (!dispatch) {
|
|
540
|
+
const names = [this.t0, this.t1, this.t2, this.t3].slice(0, Math.max(this.count, 0));
|
|
391
541
|
return celError(
|
|
392
542
|
"no_matching_overload",
|
|
393
543
|
`no overload of ${JSON.stringify(this.name)} takes (${names.join(", ")})`,
|
|
394
544
|
this.range,
|
|
395
545
|
);
|
|
396
546
|
}
|
|
397
|
-
const value = dispatch.implementation(
|
|
547
|
+
const value = dispatch.implementation(this.context, a, b, c, d);
|
|
398
548
|
return dispatch.foreign ? readHostValue(value, this.range) : value;
|
|
399
549
|
}
|
|
400
550
|
}
|