@telorun/cel 0.0.0-stage → 0.107.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/LICENSE +17 -0
- package/README.md +281 -2
- package/dist/activation.d.ts +27 -0
- package/dist/activation.d.ts.map +1 -0
- package/dist/activation.js +24 -0
- package/dist/backend-runtime.d.ts +146 -0
- package/dist/backend-runtime.d.ts.map +1 -0
- package/dist/backend-runtime.js +322 -0
- package/dist/bounded-cache.d.ts +21 -0
- package/dist/bounded-cache.d.ts.map +1 -0
- package/dist/bounded-cache.js +42 -0
- package/dist/catalog-runtime.d.ts +59 -0
- package/dist/catalog-runtime.d.ts.map +1 -0
- package/dist/catalog-runtime.js +785 -0
- package/dist/cel-expression.d.ts +32 -0
- package/dist/cel-expression.d.ts.map +1 -0
- package/dist/cel-expression.js +29 -0
- package/dist/cel-map-value.d.ts +34 -0
- package/dist/cel-map-value.d.ts.map +1 -0
- package/dist/cel-map-value.js +74 -0
- package/dist/cel-program.d.ts +44 -0
- package/dist/cel-program.d.ts.map +1 -0
- package/dist/cel-program.js +72 -0
- package/dist/cel-type.d.ts +131 -0
- package/dist/cel-type.d.ts.map +1 -0
- package/dist/cel-type.js +293 -0
- package/dist/cel-value.d.ts +159 -0
- package/dist/cel-value.d.ts.map +1 -0
- package/dist/cel-value.js +225 -0
- package/dist/check-diagnostic.d.ts +53 -0
- package/dist/check-diagnostic.d.ts.map +1 -0
- package/dist/check-diagnostic.js +66 -0
- package/dist/checker.d.ts +77 -0
- package/dist/checker.d.ts.map +1 -0
- package/dist/checker.js +721 -0
- package/dist/closure-backend.d.ts +21 -0
- package/dist/closure-backend.d.ts.map +1 -0
- package/dist/closure-backend.js +436 -0
- package/dist/comprehension-bindings.d.ts +33 -0
- package/dist/comprehension-bindings.d.ts.map +1 -0
- package/dist/comprehension-bindings.js +50 -0
- package/dist/comprehension-runtime.d.ts +44 -0
- package/dist/comprehension-runtime.d.ts.map +1 -0
- package/dist/comprehension-runtime.js +137 -0
- package/dist/declared-chain.d.ts +35 -0
- package/dist/declared-chain.d.ts.map +1 -0
- package/dist/declared-chain.js +36 -0
- package/dist/duration-value.d.ts +59 -0
- package/dist/duration-value.d.ts.map +1 -0
- package/dist/duration-value.js +135 -0
- package/dist/emitted-module.d.ts +207 -0
- package/dist/emitted-module.d.ts.map +1 -0
- package/dist/emitted-module.js +359 -0
- package/dist/engine-version.d.ts +3 -0
- package/dist/engine-version.d.ts.map +1 -0
- package/dist/engine-version.js +8 -0
- package/dist/environment-digest.d.ts +44 -0
- package/dist/environment-digest.d.ts.map +1 -0
- package/dist/environment-digest.js +98 -0
- package/dist/environment.d.ts +283 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +459 -0
- package/dist/function-catalog.d.ts +66 -0
- package/dist/function-catalog.d.ts.map +1 -0
- package/dist/function-catalog.js +77 -0
- package/dist/function-registry.d.ts +78 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +189 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/integer-arithmetic.d.ts +27 -0
- package/dist/integer-arithmetic.d.ts.map +1 -0
- package/dist/integer-arithmetic.js +58 -0
- package/dist/js-emitter.d.ts +132 -0
- package/dist/js-emitter.d.ts.map +1 -0
- package/dist/js-emitter.js +562 -0
- package/dist/json-schema-type.d.ts +182 -0
- package/dist/json-schema-type.d.ts.map +1 -0
- package/dist/json-schema-type.js +487 -0
- package/dist/json-text-scan.d.ts +28 -0
- package/dist/json-text-scan.d.ts.map +1 -0
- package/dist/json-text-scan.js +159 -0
- package/dist/lexer.d.ts +103 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +458 -0
- package/dist/macro-check.d.ts +33 -0
- package/dist/macro-check.d.ts.map +1 -0
- package/dist/macro-check.js +162 -0
- package/dist/macro-shape.d.ts +24 -0
- package/dist/macro-shape.d.ts.map +1 -0
- package/dist/macro-shape.js +55 -0
- package/dist/member-read.d.ts +52 -0
- package/dist/member-read.d.ts.map +1 -0
- package/dist/member-read.js +125 -0
- package/dist/namespace-resolution.d.ts +35 -0
- package/dist/namespace-resolution.d.ts.map +1 -0
- package/dist/namespace-resolution.js +160 -0
- package/dist/nominal-type.d.ts +63 -0
- package/dist/nominal-type.d.ts.map +1 -0
- package/dist/nominal-type.js +98 -0
- package/dist/nullable-access.d.ts +38 -0
- package/dist/nullable-access.d.ts.map +1 -0
- package/dist/nullable-access.js +93 -0
- package/dist/parse-limits.d.ts +26 -0
- package/dist/parse-limits.d.ts.map +1 -0
- package/dist/parse-limits.js +21 -0
- package/dist/parser.d.ts +48 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +503 -0
- package/dist/qualified-calls.d.ts +22 -0
- package/dist/qualified-calls.d.ts.map +1 -0
- package/dist/qualified-calls.js +27 -0
- package/dist/regular-expression.d.ts +48 -0
- package/dist/regular-expression.d.ts.map +1 -0
- package/dist/regular-expression.js +77 -0
- package/dist/reserved-words.d.ts +52 -0
- package/dist/reserved-words.d.ts.map +1 -0
- package/dist/reserved-words.js +77 -0
- package/dist/resolved-call.d.ts +33 -0
- package/dist/resolved-call.d.ts.map +1 -0
- package/dist/resolved-call.js +15 -0
- package/dist/root-references.d.ts +23 -0
- package/dist/root-references.d.ts.map +1 -0
- package/dist/root-references.js +120 -0
- package/dist/runtime-library.d.ts +56 -0
- package/dist/runtime-library.d.ts.map +1 -0
- package/dist/runtime-library.js +545 -0
- package/dist/serializer.d.ts +24 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +240 -0
- package/dist/sha256.d.ts +20 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +103 -0
- package/dist/signature.d.ts +72 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +61 -0
- package/dist/signatures/function-catalog.json +788 -0
- package/dist/signatures/standard-library.json +229 -0
- package/dist/standard-library.d.ts +41 -0
- package/dist/standard-library.d.ts.map +1 -0
- package/dist/standard-library.js +85 -0
- package/dist/syntax-diagnostic.d.ts +61 -0
- package/dist/syntax-diagnostic.d.ts.map +1 -0
- package/dist/syntax-diagnostic.js +28 -0
- package/dist/syntax-tree.d.ts +160 -0
- package/dist/syntax-tree.d.ts.map +1 -0
- package/dist/syntax-tree.js +58 -0
- package/dist/timestamp-value.d.ts +53 -0
- package/dist/timestamp-value.d.ts.map +1 -0
- package/dist/timestamp-value.js +223 -0
- package/dist/tree-equality.d.ts +15 -0
- package/dist/tree-equality.d.ts.map +1 -0
- package/dist/tree-equality.js +105 -0
- package/dist/type-expression.d.ts +26 -0
- package/dist/type-expression.d.ts.map +1 -0
- package/dist/type-expression.js +134 -0
- package/dist/value-equality.d.ts +38 -0
- package/dist/value-equality.d.ts.map +1 -0
- package/dist/value-equality.js +196 -0
- package/dist/value-text.d.ts +25 -0
- package/dist/value-text.d.ts.map +1 -0
- package/dist/value-text.js +44 -0
- package/dist/zoned-calendar.d.ts +61 -0
- package/dist/zoned-calendar.d.ts.map +1 -0
- package/dist/zoned-calendar.js +143 -0
- package/package.json +56 -3
- package/src/activation.ts +32 -0
- package/src/backend-runtime.ts +454 -0
- package/src/bounded-cache.ts +45 -0
- package/src/catalog-runtime.ts +921 -0
- package/src/cel-expression.ts +53 -0
- package/src/cel-map-value.ts +86 -0
- package/src/cel-program.ts +103 -0
- package/src/cel-type.ts +359 -0
- package/src/cel-value.ts +353 -0
- package/src/check-diagnostic.ts +102 -0
- package/src/checker.ts +932 -0
- package/src/closure-backend.ts +502 -0
- package/src/comprehension-bindings.ts +66 -0
- package/src/comprehension-runtime.ts +157 -0
- package/src/declared-chain.ts +45 -0
- package/src/duration-value.ts +145 -0
- package/src/emitted-module.ts +494 -0
- package/src/engine-version.ts +9 -0
- package/src/environment-digest.ts +111 -0
- package/src/environment.ts +740 -0
- package/src/function-catalog.ts +140 -0
- package/src/function-registry.ts +229 -0
- package/src/index.ts +386 -0
- package/src/integer-arithmetic.ts +64 -0
- package/src/js-emitter.ts +713 -0
- package/src/json-schema-type.ts +664 -0
- package/src/json-text-scan.ts +163 -0
- package/src/lexer.ts +562 -0
- package/src/macro-check.ts +191 -0
- package/src/macro-shape.ts +66 -0
- package/src/member-read.ts +126 -0
- package/src/namespace-resolution.ts +167 -0
- package/src/nominal-type.ts +149 -0
- package/src/nullable-access.ts +95 -0
- package/src/parse-limits.ts +36 -0
- package/src/parser.ts +554 -0
- package/src/qualified-calls.ts +39 -0
- package/src/regular-expression.ts +101 -0
- package/src/reserved-words.ts +94 -0
- package/src/resolved-call.ts +34 -0
- package/src/root-references.ts +126 -0
- package/src/runtime-library.ts +615 -0
- package/src/serializer.ts +246 -0
- package/src/sha256.ts +112 -0
- package/src/signature.ts +127 -0
- package/src/signatures/function-catalog.json +788 -0
- package/src/signatures/standard-library.json +235 -0
- package/src/standard-library.ts +149 -0
- package/src/syntax-diagnostic.ts +72 -0
- package/src/syntax-tree.ts +229 -0
- package/src/timestamp-value.ts +294 -0
- package/src/tree-equality.ts +130 -0
- package/src/type-expression.ts +160 -0
- package/src/value-equality.ts +201 -0
- package/src/value-text.ts +45 -0
- package/src/zoned-calendar.ts +178 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical CEL syntax tree.
|
|
3
|
+
*
|
|
4
|
+
* Every node carries `range`, the half-open `[start, end)` span of UTF-16 code
|
|
5
|
+
* units it covers in the source it was parsed from, so a diagnostic, a hover or a
|
|
6
|
+
* rename can point at the text the author wrote. A tree built by hand carries
|
|
7
|
+
* ranges too — the serializer and the queries read them, and a node with none
|
|
8
|
+
* would make either answer silently wrong.
|
|
9
|
+
*
|
|
10
|
+
* Literals hold their decoded value in a tagged form rather than as a bare
|
|
11
|
+
* JavaScript value: CEL's `int` and `uint` are both arbitrary-magnitude integers,
|
|
12
|
+
* `1` and `1u` are different expressions, and a backend must be able to tell them
|
|
13
|
+
* apart without consulting a runtime value class.
|
|
14
|
+
*
|
|
15
|
+
* `qcall` is the one node the parser never produces — see `namespace-resolution.ts`.
|
|
16
|
+
*/
|
|
17
|
+
/** Every child node, in source order. The single reader of a node's shape for traversal. */
|
|
18
|
+
export function childNodes(node) {
|
|
19
|
+
switch (node.kind) {
|
|
20
|
+
case "literal":
|
|
21
|
+
case "ident":
|
|
22
|
+
case "unparsed":
|
|
23
|
+
return [];
|
|
24
|
+
case "list":
|
|
25
|
+
return node.elements.map((element) => element.value);
|
|
26
|
+
case "map":
|
|
27
|
+
return node.entries.flatMap((entry) => [entry.key, entry.value]);
|
|
28
|
+
case "select":
|
|
29
|
+
return [node.operand];
|
|
30
|
+
case "index":
|
|
31
|
+
return [node.operand, node.index];
|
|
32
|
+
case "call":
|
|
33
|
+
return node.args;
|
|
34
|
+
case "receiverCall":
|
|
35
|
+
return [node.receiver, ...node.args];
|
|
36
|
+
case "qcall":
|
|
37
|
+
return node.args;
|
|
38
|
+
case "unary":
|
|
39
|
+
return [node.operand];
|
|
40
|
+
case "binary":
|
|
41
|
+
return [node.left, node.right];
|
|
42
|
+
case "conditional":
|
|
43
|
+
return [node.condition, node.whenTrue, node.whenFalse];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Depth-first pre-order walk over the whole tree. */
|
|
47
|
+
export function* walkTree(root) {
|
|
48
|
+
yield root;
|
|
49
|
+
for (const child of childNodes(root))
|
|
50
|
+
yield* walkTree(child);
|
|
51
|
+
}
|
|
52
|
+
/** Whether any node of the tree is an unparsed hole. */
|
|
53
|
+
export function hasUnparsed(root) {
|
|
54
|
+
for (const node of walkTree(root))
|
|
55
|
+
if (node.kind === "unparsed")
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instants: reading one, writing one, and reading a field of one in a time zone.
|
|
3
|
+
*
|
|
4
|
+
* A timestamp is whole seconds plus nanoseconds in `[0, 1e9)`, because cel-spec's own
|
|
5
|
+
* rows carry nanosecond instants (`string(timestamp('9999-12-31T23:59:59.999999999Z'))`)
|
|
6
|
+
* and no host date type holds them. The range is cel-spec's: `0001-01-01T00:00:00Z`
|
|
7
|
+
* through `9999-12-31T23:59:59.999999999Z`, and an instant outside it is an error
|
|
8
|
+
* wherever it would be built — which is exactly the answer the vectors call
|
|
9
|
+
* domain-keeping where the engine being replaced returns a date beyond year 9999.
|
|
10
|
+
*
|
|
11
|
+
* **A zone is named, never computed here.** A getter takes an IANA name or a fixed
|
|
12
|
+
* `±HH:MM` offset; the rules behind a name are the host's data, which no release of this
|
|
13
|
+
* engine fixes, so the only thing this file decides is how a name is applied.
|
|
14
|
+
*/
|
|
15
|
+
import { type CelError, type CelTimestamp } from "./cel-value.js";
|
|
16
|
+
import type { SourceRange } from "./syntax-tree.js";
|
|
17
|
+
/** `0001-01-01T00:00:00Z` and `9999-12-31T23:59:59Z`, in seconds since the epoch. */
|
|
18
|
+
export declare const MIN_TIMESTAMP_SECONDS = -62135596800n;
|
|
19
|
+
export declare const MAX_TIMESTAMP_SECONDS = 253402300799n;
|
|
20
|
+
/** An instant from seconds and nanoseconds of any sign, normalized, or the range error. */
|
|
21
|
+
export declare function celTimestamp(seconds: bigint, nanos?: bigint | number, range?: SourceRange): CelTimestamp | CelError;
|
|
22
|
+
/** The instant's nanoseconds since the epoch — what arithmetic and ordering compare. */
|
|
23
|
+
export declare function timestampNanos(value: CelTimestamp): bigint;
|
|
24
|
+
/** An instant from RFC 3339 text, with any offset and any fractional precision. */
|
|
25
|
+
export declare function parseTimestamp(text: string, range?: SourceRange): CelTimestamp | CelError;
|
|
26
|
+
/**
|
|
27
|
+
* RFC 3339 in UTC, with the fraction written to the precision it carries and omitted
|
|
28
|
+
* when the instant is whole — cel-spec's own spelling (`2009-02-13T23:31:30Z`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatTimestamp(value: CelTimestamp): string;
|
|
31
|
+
export interface CivilFields {
|
|
32
|
+
readonly year: number;
|
|
33
|
+
readonly month: number;
|
|
34
|
+
readonly day: number;
|
|
35
|
+
readonly hour: number;
|
|
36
|
+
readonly minute: number;
|
|
37
|
+
readonly second: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The civil fields of an instant in a zone. A zone is an IANA name, resolved by the
|
|
41
|
+
* host's own tz database, or a fixed `±HH:MM` offset, which is arithmetic; an unsigned
|
|
42
|
+
* offset is read as ahead of UTC, as cel-spec's own rows read it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function zonedFields(value: CelTimestamp, zone: string | undefined, range?: SourceRange): CivilFields | CelError;
|
|
45
|
+
/** Which field of an instant a getter answers. The names are cel-spec's. */
|
|
46
|
+
export type TimestampField = "getFullYear" | "getMonth" | "getDayOfYear" | "getDayOfMonth" | "getDate" | "getDayOfWeek" | "getHours" | "getMinutes" | "getSeconds" | "getMilliseconds";
|
|
47
|
+
/**
|
|
48
|
+
* A field of an instant. `getMonth`, `getDayOfMonth` and `getDayOfYear` are **zero
|
|
49
|
+
* based** and `getDate` is one based, which is cel-spec's own split; `getDayOfWeek`
|
|
50
|
+
* counts from Sunday.
|
|
51
|
+
*/
|
|
52
|
+
export declare function timestampField(value: CelTimestamp, field: TimestampField, zone?: string, range?: SourceRange): bigint | CelError;
|
|
53
|
+
//# sourceMappingURL=timestamp-value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamp-value.d.ts","sourceRoot":"","sources":["../src/timestamp-value.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AACnD,eAAO,MAAM,qBAAqB,gBAAgB,CAAC;AAInD,2FAA2F;AAC3F,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAM,GAAG,MAAU,EAC1B,KAAK,CAAC,EAAE,WAAW,GAClB,YAAY,GAAG,QAAQ,CAYzB;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAE1D;AAKD,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,YAAY,GAAG,QAAQ,CAgCzF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAK3D;AAaD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,CAAC,EAAE,WAAW,GAClB,WAAW,GAAG,QAAQ,CAYxB;AAsCD,4EAA4E;AAC5E,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,UAAU,GACV,cAAc,GACd,eAAe,GACf,SAAS,GACT,cAAc,GACd,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,cAAc,EACrB,IAAI,CAAC,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,WAAW,GAClB,MAAM,GAAG,QAAQ,CA0BnB"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instants: reading one, writing one, and reading a field of one in a time zone.
|
|
3
|
+
*
|
|
4
|
+
* A timestamp is whole seconds plus nanoseconds in `[0, 1e9)`, because cel-spec's own
|
|
5
|
+
* rows carry nanosecond instants (`string(timestamp('9999-12-31T23:59:59.999999999Z'))`)
|
|
6
|
+
* and no host date type holds them. The range is cel-spec's: `0001-01-01T00:00:00Z`
|
|
7
|
+
* through `9999-12-31T23:59:59.999999999Z`, and an instant outside it is an error
|
|
8
|
+
* wherever it would be built — which is exactly the answer the vectors call
|
|
9
|
+
* domain-keeping where the engine being replaced returns a date beyond year 9999.
|
|
10
|
+
*
|
|
11
|
+
* **A zone is named, never computed here.** A getter takes an IANA name or a fixed
|
|
12
|
+
* `±HH:MM` offset; the rules behind a name are the host's data, which no release of this
|
|
13
|
+
* engine fixes, so the only thing this file decides is how a name is applied.
|
|
14
|
+
*/
|
|
15
|
+
import { celError, CEL_VALUE_TYPE, isCelError, } from "./cel-value.js";
|
|
16
|
+
/** `0001-01-01T00:00:00Z` and `9999-12-31T23:59:59Z`, in seconds since the epoch. */
|
|
17
|
+
export const MIN_TIMESTAMP_SECONDS = -62135596800n;
|
|
18
|
+
export const MAX_TIMESTAMP_SECONDS = 253402300799n;
|
|
19
|
+
const NANOS_PER_SECOND = 1000000000n;
|
|
20
|
+
/** An instant from seconds and nanoseconds of any sign, normalized, or the range error. */
|
|
21
|
+
export function celTimestamp(seconds, nanos = 0, range) {
|
|
22
|
+
const total = BigInt(nanos);
|
|
23
|
+
let whole = seconds + total / NANOS_PER_SECOND;
|
|
24
|
+
let rest = total % NANOS_PER_SECOND;
|
|
25
|
+
if (rest < 0n) {
|
|
26
|
+
whole -= 1n;
|
|
27
|
+
rest += NANOS_PER_SECOND;
|
|
28
|
+
}
|
|
29
|
+
if (whole < MIN_TIMESTAMP_SECONDS || whole > MAX_TIMESTAMP_SECONDS) {
|
|
30
|
+
return celError("invalid_conversion", "timestamp out of range", range);
|
|
31
|
+
}
|
|
32
|
+
return { [CEL_VALUE_TYPE]: "google.protobuf.Timestamp", seconds: whole, nanos: Number(rest) };
|
|
33
|
+
}
|
|
34
|
+
/** The instant's nanoseconds since the epoch — what arithmetic and ordering compare. */
|
|
35
|
+
export function timestampNanos(value) {
|
|
36
|
+
return value.seconds * NANOS_PER_SECOND + BigInt(value.nanos);
|
|
37
|
+
}
|
|
38
|
+
const RFC_3339 = /^(\d{4})-(\d{2})-(\d{2})[Tt ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(?:[Zz]|([+-])(\d{2}):(\d{2}))$/;
|
|
39
|
+
/** An instant from RFC 3339 text, with any offset and any fractional precision. */
|
|
40
|
+
export function parseTimestamp(text, range) {
|
|
41
|
+
const parts = RFC_3339.exec(text);
|
|
42
|
+
if (!parts) {
|
|
43
|
+
return celError("invalid_conversion", `${JSON.stringify(text)} is not an RFC 3339 instant`, range);
|
|
44
|
+
}
|
|
45
|
+
const [, year, month, day, hour, minute, second, fraction, sign, offsetHours, offsetMinutes] = parts;
|
|
46
|
+
const fields = {
|
|
47
|
+
year: Number(year),
|
|
48
|
+
month: Number(month),
|
|
49
|
+
day: Number(day),
|
|
50
|
+
hour: Number(hour),
|
|
51
|
+
minute: Number(minute),
|
|
52
|
+
second: Number(second),
|
|
53
|
+
};
|
|
54
|
+
if (fields.month < 1 ||
|
|
55
|
+
fields.month > 12 ||
|
|
56
|
+
fields.day < 1 ||
|
|
57
|
+
fields.day > daysInMonth(fields.year, fields.month) ||
|
|
58
|
+
fields.hour > 23 ||
|
|
59
|
+
fields.minute > 59 ||
|
|
60
|
+
fields.second > 59) {
|
|
61
|
+
return celError("invalid_conversion", `${JSON.stringify(text)} is not an instant`, range);
|
|
62
|
+
}
|
|
63
|
+
let seconds = secondsFromFields(fields);
|
|
64
|
+
if (sign) {
|
|
65
|
+
const offset = BigInt(Number(offsetHours) * 3600 + Number(offsetMinutes) * 60);
|
|
66
|
+
seconds += sign === "-" ? offset : -offset;
|
|
67
|
+
}
|
|
68
|
+
const nanos = fraction ? Number(`${fraction}000000000`.slice(0, 9)) : 0;
|
|
69
|
+
return celTimestamp(seconds, nanos, range);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* RFC 3339 in UTC, with the fraction written to the precision it carries and omitted
|
|
73
|
+
* when the instant is whole — cel-spec's own spelling (`2009-02-13T23:31:30Z`).
|
|
74
|
+
*/
|
|
75
|
+
export function formatTimestamp(value) {
|
|
76
|
+
const fields = utcFields(value.seconds);
|
|
77
|
+
const date = `${pad(fields.year, 4)}-${pad(fields.month, 2)}-${pad(fields.day, 2)}`;
|
|
78
|
+
const time = `${pad(fields.hour, 2)}:${pad(fields.minute, 2)}:${pad(fields.second, 2)}`;
|
|
79
|
+
return `${date}T${time}${fractionText(value.nanos)}Z`;
|
|
80
|
+
}
|
|
81
|
+
function fractionText(nanos) {
|
|
82
|
+
if (nanos === 0)
|
|
83
|
+
return "";
|
|
84
|
+
return `.${pad(nanos, 9).replace(/0+$/, "")}`;
|
|
85
|
+
}
|
|
86
|
+
function pad(value, width) {
|
|
87
|
+
return String(value).padStart(width, "0");
|
|
88
|
+
}
|
|
89
|
+
function isLeapYear(year) {
|
|
90
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
91
|
+
}
|
|
92
|
+
const MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
93
|
+
function daysInMonth(year, month) {
|
|
94
|
+
return month === 2 && isLeapYear(year) ? 29 : MONTH_DAYS[month - 1];
|
|
95
|
+
}
|
|
96
|
+
/** Days from the epoch to a civil date, by Howard Hinnant's `days_from_civil`. */
|
|
97
|
+
function daysFromCivil(year, month, day) {
|
|
98
|
+
const shifted = month <= 2 ? year - 1 : year;
|
|
99
|
+
const era = Math.floor(shifted / 400);
|
|
100
|
+
const yearOfEra = shifted - era * 400;
|
|
101
|
+
const dayOfYear = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
|
|
102
|
+
const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
|
|
103
|
+
return era * 146097 + dayOfEra - 719468;
|
|
104
|
+
}
|
|
105
|
+
/** The civil date a day count names, the inverse of `daysFromCivil`. */
|
|
106
|
+
function civilFromDays(days) {
|
|
107
|
+
const shifted = days + 719468;
|
|
108
|
+
const era = Math.floor(shifted / 146097);
|
|
109
|
+
const dayOfEra = shifted - era * 146097;
|
|
110
|
+
const yearOfEra = Math.floor((dayOfEra - Math.floor(dayOfEra / 1460) + Math.floor(dayOfEra / 36524) - Math.floor(dayOfEra / 146096)) / 365);
|
|
111
|
+
const year = yearOfEra + era * 400;
|
|
112
|
+
const dayOfYear = dayOfEra - (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
|
|
113
|
+
const monthPrime = Math.floor((5 * dayOfYear + 2) / 153);
|
|
114
|
+
const day = dayOfYear - Math.floor((153 * monthPrime + 2) / 5) + 1;
|
|
115
|
+
const month = monthPrime + (monthPrime < 10 ? 3 : -9);
|
|
116
|
+
return { year: month <= 2 ? year + 1 : year, month, day };
|
|
117
|
+
}
|
|
118
|
+
function secondsFromFields(fields) {
|
|
119
|
+
const days = daysFromCivil(fields.year, fields.month, fields.day);
|
|
120
|
+
return BigInt(days) * 86400n + BigInt(fields.hour * 3600 + fields.minute * 60 + fields.second);
|
|
121
|
+
}
|
|
122
|
+
function utcFields(seconds) {
|
|
123
|
+
const days = Number(floorDiv(seconds, 86400n));
|
|
124
|
+
const rest = Number(seconds - BigInt(days) * 86400n);
|
|
125
|
+
const date = civilFromDays(days);
|
|
126
|
+
return {
|
|
127
|
+
...date,
|
|
128
|
+
hour: Math.floor(rest / 3600),
|
|
129
|
+
minute: Math.floor((rest % 3600) / 60),
|
|
130
|
+
second: rest % 60,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function floorDiv(value, by) {
|
|
134
|
+
const quotient = value / by;
|
|
135
|
+
return value % by < 0n ? quotient - 1n : quotient;
|
|
136
|
+
}
|
|
137
|
+
// --- zones -----------------------------------------------------------------
|
|
138
|
+
const FIXED_OFFSET = /^([+-]?)(\d{2}):(\d{2})$/;
|
|
139
|
+
/**
|
|
140
|
+
* The civil fields of an instant in a zone. A zone is an IANA name, resolved by the
|
|
141
|
+
* host's own tz database, or a fixed `±HH:MM` offset, which is arithmetic; an unsigned
|
|
142
|
+
* offset is read as ahead of UTC, as cel-spec's own rows read it.
|
|
143
|
+
*/
|
|
144
|
+
export function zonedFields(value, zone, range) {
|
|
145
|
+
if (zone === undefined || zone === "UTC")
|
|
146
|
+
return utcFields(value.seconds);
|
|
147
|
+
const fixed = FIXED_OFFSET.exec(zone);
|
|
148
|
+
if (fixed) {
|
|
149
|
+
const [, sign, hours, minutes] = fixed;
|
|
150
|
+
if (Number(hours) > 23 || Number(minutes) > 59) {
|
|
151
|
+
return celError("invalid_argument", `${JSON.stringify(zone)} is not a time zone`, range);
|
|
152
|
+
}
|
|
153
|
+
const offset = BigInt(Number(hours) * 3600 + Number(minutes) * 60);
|
|
154
|
+
return utcFields(value.seconds + (sign === "-" ? -offset : offset));
|
|
155
|
+
}
|
|
156
|
+
return namedZoneFields(value, zone, range);
|
|
157
|
+
}
|
|
158
|
+
/** The host's answer for an IANA zone. Its rules are the host's data, not this engine's. */
|
|
159
|
+
function namedZoneFields(value, zone, range) {
|
|
160
|
+
let parts;
|
|
161
|
+
try {
|
|
162
|
+
const format = new Intl.DateTimeFormat("en-US", {
|
|
163
|
+
timeZone: zone,
|
|
164
|
+
year: "numeric",
|
|
165
|
+
month: "2-digit",
|
|
166
|
+
day: "2-digit",
|
|
167
|
+
hour: "2-digit",
|
|
168
|
+
minute: "2-digit",
|
|
169
|
+
second: "2-digit",
|
|
170
|
+
hourCycle: "h23",
|
|
171
|
+
era: "short",
|
|
172
|
+
});
|
|
173
|
+
parts = format.formatToParts(new Date(Number(value.seconds) * 1000));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return celError("invalid_argument", `${JSON.stringify(zone)} is not a time zone`, range);
|
|
177
|
+
}
|
|
178
|
+
const read = (type) => Number(parts.find((part) => part.type === type)?.value ?? Number.NaN);
|
|
179
|
+
const era = parts.find((part) => part.type === "era")?.value;
|
|
180
|
+
const year = read("year");
|
|
181
|
+
return {
|
|
182
|
+
year: era === "BC" ? 1 - year : year,
|
|
183
|
+
month: read("month"),
|
|
184
|
+
day: read("day"),
|
|
185
|
+
hour: read("hour"),
|
|
186
|
+
minute: read("minute"),
|
|
187
|
+
second: read("second"),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* A field of an instant. `getMonth`, `getDayOfMonth` and `getDayOfYear` are **zero
|
|
192
|
+
* based** and `getDate` is one based, which is cel-spec's own split; `getDayOfWeek`
|
|
193
|
+
* counts from Sunday.
|
|
194
|
+
*/
|
|
195
|
+
export function timestampField(value, field, zone, range) {
|
|
196
|
+
if (field === "getMilliseconds")
|
|
197
|
+
return BigInt(Math.floor(value.nanos / 1_000_000));
|
|
198
|
+
const civil = zonedFields(value, zone, range);
|
|
199
|
+
if (isCelError(civil))
|
|
200
|
+
return civil;
|
|
201
|
+
switch (field) {
|
|
202
|
+
case "getFullYear":
|
|
203
|
+
return BigInt(civil.year);
|
|
204
|
+
case "getMonth":
|
|
205
|
+
return BigInt(civil.month - 1);
|
|
206
|
+
case "getDayOfMonth":
|
|
207
|
+
return BigInt(civil.day - 1);
|
|
208
|
+
case "getDate":
|
|
209
|
+
return BigInt(civil.day);
|
|
210
|
+
case "getDayOfYear":
|
|
211
|
+
return BigInt(daysFromCivil(civil.year, civil.month, civil.day) - daysFromCivil(civil.year, 1, 1));
|
|
212
|
+
case "getDayOfWeek": {
|
|
213
|
+
const days = daysFromCivil(civil.year, civil.month, civil.day);
|
|
214
|
+
return BigInt(((days % 7) + 11) % 7);
|
|
215
|
+
}
|
|
216
|
+
case "getHours":
|
|
217
|
+
return BigInt(civil.hour);
|
|
218
|
+
case "getMinutes":
|
|
219
|
+
return BigInt(civil.minute);
|
|
220
|
+
case "getSeconds":
|
|
221
|
+
return BigInt(civil.second);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether two trees are the same expression.
|
|
3
|
+
*
|
|
4
|
+
* Ranges are **not** compared: two trees of the same expression written with
|
|
5
|
+
* different spacing or parentheses hold different offsets, and the round-trip
|
|
6
|
+
* contract (`serializer.ts`) is exactly that serializing and re-parsing gives back
|
|
7
|
+
* the same expression — not the same text.
|
|
8
|
+
*
|
|
9
|
+
* Numbers compare by identity rather than by `==`, so `-0.0` is not `0.0` and a
|
|
10
|
+
* `NaN` equals itself: both are distinct literals a round-trip must preserve.
|
|
11
|
+
*/
|
|
12
|
+
import type { CelNode } from "./syntax-tree.js";
|
|
13
|
+
/** Whether the two trees are the same expression, their ranges aside. */
|
|
14
|
+
export declare function treesEqual(left: CelNode, right: CelNode): boolean;
|
|
15
|
+
//# sourceMappingURL=tree-equality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-equality.d.ts","sourceRoot":"","sources":["../src/tree-equality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAc,OAAO,EAAE,MAAM,kBAAkB,CAAC;AA0B5D,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA0FjE"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether two trees are the same expression.
|
|
3
|
+
*
|
|
4
|
+
* Ranges are **not** compared: two trees of the same expression written with
|
|
5
|
+
* different spacing or parentheses hold different offsets, and the round-trip
|
|
6
|
+
* contract (`serializer.ts`) is exactly that serializing and re-parsing gives back
|
|
7
|
+
* the same expression — not the same text.
|
|
8
|
+
*
|
|
9
|
+
* Numbers compare by identity rather than by `==`, so `-0.0` is not `0.0` and a
|
|
10
|
+
* `NaN` equals itself: both are distinct literals a round-trip must preserve.
|
|
11
|
+
*/
|
|
12
|
+
function literalsEqual(left, right) {
|
|
13
|
+
if (left.type !== right.type)
|
|
14
|
+
return false;
|
|
15
|
+
switch (left.type) {
|
|
16
|
+
case "null":
|
|
17
|
+
return true;
|
|
18
|
+
case "int":
|
|
19
|
+
case "uint":
|
|
20
|
+
return left.value === right.value;
|
|
21
|
+
case "double":
|
|
22
|
+
return Object.is(left.value, right.value);
|
|
23
|
+
case "bool":
|
|
24
|
+
case "string":
|
|
25
|
+
return left.value === right.value;
|
|
26
|
+
case "bytes": {
|
|
27
|
+
const other = right.value;
|
|
28
|
+
return left.value.length === other.length && left.value.every((byte, at) => byte === other[at]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function listsEqual(left, right) {
|
|
33
|
+
return left.length === right.length && left.every((node, at) => treesEqual(node, right[at]));
|
|
34
|
+
}
|
|
35
|
+
/** Whether the two trees are the same expression, their ranges aside. */
|
|
36
|
+
export function treesEqual(left, right) {
|
|
37
|
+
if (left.kind !== right.kind)
|
|
38
|
+
return false;
|
|
39
|
+
switch (left.kind) {
|
|
40
|
+
case "literal":
|
|
41
|
+
return literalsEqual(left.literal, right.literal);
|
|
42
|
+
case "ident": {
|
|
43
|
+
const other = right;
|
|
44
|
+
// Absolute changes what the name resolves against, so it is part of the expression;
|
|
45
|
+
// whether a member was written between backticks is not.
|
|
46
|
+
return left.name === other.name && left.absolute === other.absolute;
|
|
47
|
+
}
|
|
48
|
+
case "unparsed":
|
|
49
|
+
return true;
|
|
50
|
+
case "list": {
|
|
51
|
+
const other = right.elements;
|
|
52
|
+
return (left.elements.length === other.length &&
|
|
53
|
+
left.elements.every((element, at) => element.optional === other[at].optional && treesEqual(element.value, other[at].value)));
|
|
54
|
+
}
|
|
55
|
+
case "map": {
|
|
56
|
+
const other = right.entries;
|
|
57
|
+
return (left.entries.length === other.length &&
|
|
58
|
+
left.entries.every((entry, at) => entry.optional === other[at].optional &&
|
|
59
|
+
treesEqual(entry.key, other[at].key) &&
|
|
60
|
+
treesEqual(entry.value, other[at].value)));
|
|
61
|
+
}
|
|
62
|
+
case "select": {
|
|
63
|
+
const other = right;
|
|
64
|
+
return (left.field === other.field &&
|
|
65
|
+
left.optional === other.optional &&
|
|
66
|
+
treesEqual(left.operand, other.operand));
|
|
67
|
+
}
|
|
68
|
+
case "index": {
|
|
69
|
+
const other = right;
|
|
70
|
+
return (left.optional === other.optional &&
|
|
71
|
+
treesEqual(left.operand, other.operand) &&
|
|
72
|
+
treesEqual(left.index, other.index));
|
|
73
|
+
}
|
|
74
|
+
case "call": {
|
|
75
|
+
const other = right;
|
|
76
|
+
return left.name === other.name && listsEqual(left.args, other.args);
|
|
77
|
+
}
|
|
78
|
+
case "receiverCall": {
|
|
79
|
+
const other = right;
|
|
80
|
+
return (left.name === other.name &&
|
|
81
|
+
treesEqual(left.receiver, other.receiver) &&
|
|
82
|
+
listsEqual(left.args, other.args));
|
|
83
|
+
}
|
|
84
|
+
case "qcall": {
|
|
85
|
+
const other = right;
|
|
86
|
+
return (left.namespace === other.namespace && left.name === other.name && listsEqual(left.args, other.args));
|
|
87
|
+
}
|
|
88
|
+
case "unary": {
|
|
89
|
+
const other = right;
|
|
90
|
+
return left.operator === other.operator && treesEqual(left.operand, other.operand);
|
|
91
|
+
}
|
|
92
|
+
case "binary": {
|
|
93
|
+
const other = right;
|
|
94
|
+
return (left.operator === other.operator &&
|
|
95
|
+
treesEqual(left.left, other.left) &&
|
|
96
|
+
treesEqual(left.right, other.right));
|
|
97
|
+
}
|
|
98
|
+
case "conditional": {
|
|
99
|
+
const other = right;
|
|
100
|
+
return (treesEqual(left.condition, other.condition) &&
|
|
101
|
+
treesEqual(left.whenTrue, other.whenTrue) &&
|
|
102
|
+
treesEqual(left.whenFalse, other.whenFalse));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types as text: `list<int>`, `map<string, int>`, `optional<T>`, `Money<string>`.
|
|
3
|
+
*
|
|
4
|
+
* A host declares a variable's or a signature's type as a string, and the signature
|
|
5
|
+
* data a port reads is text, so one grammar reads both. It is deliberately the same
|
|
6
|
+
* spelling `formatType` writes, so a type that round-trips through text is the type
|
|
7
|
+
* it started as — which is what lets the definition listing and the signature data
|
|
8
|
+
* be compared without a second parser.
|
|
9
|
+
*
|
|
10
|
+
* A bare capital letter is a type **parameter**, which is how a signature says "the
|
|
11
|
+
* same type here and there". A name the host registered as a nominal type resolves
|
|
12
|
+
* to it; any other name is refused rather than guessed, because a misspelled type in
|
|
13
|
+
* a signature would otherwise register a function nothing can call.
|
|
14
|
+
*/
|
|
15
|
+
import type { CelType } from "./cel-type.js";
|
|
16
|
+
export declare class CelTypeExpressionError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** What a name that is neither a primitive nor a parameter resolves to. */
|
|
20
|
+
export type NominalResolver = (name: string, args: readonly CelType[]) => CelType | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Reads one type expression. `resolveNominal` is consulted for every name the
|
|
23
|
+
* language itself does not define.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseTypeExpression(text: string, resolveNominal?: NominalResolver): CelType;
|
|
26
|
+
//# sourceMappingURL=type-expression.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-expression.d.ts","sourceRoot":"","sources":["../src/type-expression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,eAAe,CAAC;AAoB5D,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAmBD,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,KAAK,OAAO,GAAG,SAAS,CAAC;AAE9F;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,eAAe,GAAG,OAAO,CAK3F"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types as text: `list<int>`, `map<string, int>`, `optional<T>`, `Money<string>`.
|
|
3
|
+
*
|
|
4
|
+
* A host declares a variable's or a signature's type as a string, and the signature
|
|
5
|
+
* data a port reads is text, so one grammar reads both. It is deliberately the same
|
|
6
|
+
* spelling `formatType` writes, so a type that round-trips through text is the type
|
|
7
|
+
* it started as — which is what lets the definition listing and the signature data
|
|
8
|
+
* be compared without a second parser.
|
|
9
|
+
*
|
|
10
|
+
* A bare capital letter is a type **parameter**, which is how a signature says "the
|
|
11
|
+
* same type here and there". A name the host registered as a nominal type resolves
|
|
12
|
+
* to it; any other name is refused rather than guessed, because a misspelled type in
|
|
13
|
+
* a signature would otherwise register a function nothing can call.
|
|
14
|
+
*/
|
|
15
|
+
import { BOOL, BYTES, DOUBLE, DYN, DURATION, INT, listOf, mapOf, NULL, optionalOf, parameterOf, STRING, TIMESTAMP, TYPE, UINT, WELL_KNOWN_TYPE_NAMES, } from "./cel-type.js";
|
|
16
|
+
export class CelTypeExpressionError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "CelTypeExpressionError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const PRIMITIVES = {
|
|
23
|
+
dyn: DYN,
|
|
24
|
+
int: INT,
|
|
25
|
+
uint: UINT,
|
|
26
|
+
double: DOUBLE,
|
|
27
|
+
bool: BOOL,
|
|
28
|
+
string: STRING,
|
|
29
|
+
bytes: BYTES,
|
|
30
|
+
null: NULL,
|
|
31
|
+
null_type: NULL,
|
|
32
|
+
type: TYPE,
|
|
33
|
+
timestamp: TIMESTAMP,
|
|
34
|
+
duration: DURATION,
|
|
35
|
+
};
|
|
36
|
+
const PARAMETER = /^[A-Z]$/;
|
|
37
|
+
/**
|
|
38
|
+
* Reads one type expression. `resolveNominal` is consulted for every name the
|
|
39
|
+
* language itself does not define.
|
|
40
|
+
*/
|
|
41
|
+
export function parseTypeExpression(text, resolveNominal) {
|
|
42
|
+
const reader = new TypeReader(text, resolveNominal);
|
|
43
|
+
const type = reader.read();
|
|
44
|
+
reader.expectEnd();
|
|
45
|
+
return type;
|
|
46
|
+
}
|
|
47
|
+
class TypeReader {
|
|
48
|
+
text;
|
|
49
|
+
resolveNominal;
|
|
50
|
+
at = 0;
|
|
51
|
+
constructor(text, resolveNominal) {
|
|
52
|
+
this.text = text;
|
|
53
|
+
this.resolveNominal = resolveNominal;
|
|
54
|
+
}
|
|
55
|
+
read() {
|
|
56
|
+
const name = this.readName();
|
|
57
|
+
const args = this.readArguments();
|
|
58
|
+
return this.resolve(name, args);
|
|
59
|
+
}
|
|
60
|
+
expectEnd() {
|
|
61
|
+
this.skipSpace();
|
|
62
|
+
if (this.at !== this.text.length) {
|
|
63
|
+
throw new CelTypeExpressionError(`${JSON.stringify(this.text)} is not one type expression`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Reads the next type of a list, stopping at `,` or `>`. */
|
|
67
|
+
readArgument() {
|
|
68
|
+
return this.read();
|
|
69
|
+
}
|
|
70
|
+
skipSpace() {
|
|
71
|
+
while (this.text[this.at] === " ")
|
|
72
|
+
this.at += 1;
|
|
73
|
+
}
|
|
74
|
+
readName() {
|
|
75
|
+
this.skipSpace();
|
|
76
|
+
const from = this.at;
|
|
77
|
+
while (this.at < this.text.length && /[A-Za-z0-9_.]/.test(this.text[this.at]))
|
|
78
|
+
this.at += 1;
|
|
79
|
+
if (this.at === from) {
|
|
80
|
+
throw new CelTypeExpressionError(`${JSON.stringify(this.text)} names no type at offset ${from}`);
|
|
81
|
+
}
|
|
82
|
+
return this.text.slice(from, this.at);
|
|
83
|
+
}
|
|
84
|
+
readArguments() {
|
|
85
|
+
this.skipSpace();
|
|
86
|
+
if (this.text[this.at] !== "<")
|
|
87
|
+
return [];
|
|
88
|
+
this.at += 1;
|
|
89
|
+
const args = [this.readArgument()];
|
|
90
|
+
for (;;) {
|
|
91
|
+
this.skipSpace();
|
|
92
|
+
const next = this.text[this.at];
|
|
93
|
+
if (next === ",") {
|
|
94
|
+
this.at += 1;
|
|
95
|
+
args.push(this.readArgument());
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (next === ">") {
|
|
99
|
+
this.at += 1;
|
|
100
|
+
return args;
|
|
101
|
+
}
|
|
102
|
+
throw new CelTypeExpressionError(`${JSON.stringify(this.text)} has no closing '>' for its type arguments`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
resolve(name, args) {
|
|
106
|
+
const wellKnown = WELL_KNOWN_TYPE_NAMES[name];
|
|
107
|
+
if (wellKnown)
|
|
108
|
+
return this.noArguments(name, args, PRIMITIVES[wellKnown]);
|
|
109
|
+
if (name === "list")
|
|
110
|
+
return args.length === 1 ? listOf(args[0]) : listOf(DYN);
|
|
111
|
+
if (name === "map")
|
|
112
|
+
return args.length === 2 ? mapOf(args[0], args[1]) : mapOf(DYN, DYN);
|
|
113
|
+
if (name === "optional") {
|
|
114
|
+
if (args.length !== 1) {
|
|
115
|
+
throw new CelTypeExpressionError("optional takes exactly one type argument");
|
|
116
|
+
}
|
|
117
|
+
return optionalOf(args[0]);
|
|
118
|
+
}
|
|
119
|
+
const primitive = PRIMITIVES[name];
|
|
120
|
+
if (primitive)
|
|
121
|
+
return this.noArguments(name, args, primitive);
|
|
122
|
+
if (PARAMETER.test(name))
|
|
123
|
+
return this.noArguments(name, args, parameterOf(name));
|
|
124
|
+
const nominal = this.resolveNominal?.(name, args);
|
|
125
|
+
if (nominal)
|
|
126
|
+
return nominal;
|
|
127
|
+
throw new CelTypeExpressionError(`no type is registered under the name ${JSON.stringify(name)}`);
|
|
128
|
+
}
|
|
129
|
+
noArguments(name, args, type) {
|
|
130
|
+
if (args.length > 0)
|
|
131
|
+
throw new CelTypeExpressionError(`${name} takes no type arguments`);
|
|
132
|
+
return type;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equality and ordering over values — **one comparison, serving both.**
|
|
3
|
+
*
|
|
4
|
+
* `<`, `<=`, `>`, `>=`, cross-numeric equality, `in` over a list and a list index all read
|
|
5
|
+
* the same rule, because two readers of "is this the same value" is how a container and an
|
|
6
|
+
* operator come to disagree about one expression.
|
|
7
|
+
*
|
|
8
|
+
* **Across the numeric types it CONVERTS.** The checker is strict — `1.0 == 1` is a type
|
|
9
|
+
* error — but `dyn(1.0) == 1` checks, so the runtime is reached with two numeric types in
|
|
10
|
+
* hand and cel-spec's answer is `true`. Where the double lies outside the integer type's
|
|
11
|
+
* range the sign decides; otherwise the integer becomes a double and two doubles are
|
|
12
|
+
* compared, **lossily**, so `dyn(9223372036854775807) < 9223372036854775808.0` is `false`.
|
|
13
|
+
* That is cel-spec's own rule: its corpus comments the case ("the conversion of the int to
|
|
14
|
+
* double is lossy") and names the test `not_lt_dyn_int_big_lossy_double`. `int(double)`
|
|
15
|
+
* refuses a double at or beyond **either** int64 extreme for the same reason.
|
|
16
|
+
*
|
|
17
|
+
* An exact comparison is defensible alone and indefensible as a cross-engine contract: a
|
|
18
|
+
* second engine built on a conformant library would answer the other way on a comparison
|
|
19
|
+
* that can decide an authorization or a retry bound, silently, at a level no static check
|
|
20
|
+
* reaches.
|
|
21
|
+
*
|
|
22
|
+
* **A map's key identity is NOT this comparison** and does not convert: a map is keyed by
|
|
23
|
+
* the canonical text of its key, so `{1u: 1.0}[?1.0]` reads the entry and `[?3.1]` names
|
|
24
|
+
* none.
|
|
25
|
+
*
|
|
26
|
+
* Equality across unrelated types is `false`, not an error — two values of different
|
|
27
|
+
* types are not equal, and asking is not a mistake. Ordering across unrelated types has
|
|
28
|
+
* no answer, so the caller reports no overload.
|
|
29
|
+
*/
|
|
30
|
+
import type { CelValue } from "./cel-value.js";
|
|
31
|
+
/**
|
|
32
|
+
* How two values order: `-1`, `0`, `1`, or nothing when they do not order at all —
|
|
33
|
+
* unrelated types, or a NaN, which orders with nothing including itself.
|
|
34
|
+
*/
|
|
35
|
+
export declare function celCompare(left: CelValue, right: CelValue): number | undefined;
|
|
36
|
+
/** CEL equality: by value, across the numeric types, and `false` across unrelated ones. */
|
|
37
|
+
export declare function celEqual(left: CelValue, right: CelValue): boolean;
|
|
38
|
+
//# sourceMappingURL=value-equality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-equality.d.ts","sourceRoot":"","sources":["../src/value-equality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA8E/C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAgB9E;AAED,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAgCjE"}
|