@telorun/cel 0.0.0-stage → 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/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 +184 -0
- package/dist/backend-runtime.d.ts.map +1 -0
- package/dist/backend-runtime.js +425 -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 +786 -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 +47 -0
- package/dist/cel-map-value.d.ts.map +1 -0
- package/dist/cel-map-value.js +85 -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 +166 -0
- package/dist/cel-value.d.ts.map +1 -0
- package/dist/cel-value.js +225 -0
- package/dist/check-diagnostic.d.ts +54 -0
- package/dist/check-diagnostic.d.ts.map +1 -0
- package/dist/check-diagnostic.js +66 -0
- package/dist/checker.d.ts +86 -0
- package/dist/checker.d.ts.map +1 -0
- package/dist/checker.js +806 -0
- package/dist/closure-backend.d.ts +21 -0
- package/dist/closure-backend.d.ts.map +1 -0
- package/dist/closure-backend.js +487 -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 +70 -0
- package/dist/duration-value.d.ts.map +1 -0
- package/dist/duration-value.js +149 -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 +291 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +474 -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 +118 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +292 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -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 +133 -0
- package/dist/js-emitter.d.ts.map +1 -0
- package/dist/js-emitter.js +568 -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 +41 -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 +89 -0
- package/dist/member-read.d.ts.map +1 -0
- package/dist/member-read.js +166 -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 +46 -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 +67 -0
- package/dist/runtime-library.d.ts.map +1 -0
- package/dist/runtime-library.js +554 -0
- package/dist/serializer.d.ts +24 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/serializer.js +256 -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 +62 -0
- package/dist/timestamp-value.d.ts.map +1 -0
- package/dist/timestamp-value.js +238 -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 +42 -0
- package/dist/type-expression.d.ts.map +1 -0
- package/dist/type-expression.js +154 -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 +604 -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 +97 -0
- package/src/cel-program.ts +103 -0
- package/src/cel-type.ts +359 -0
- package/src/cel-value.ts +361 -0
- package/src/check-diagnostic.ts +104 -0
- package/src/checker.ts +1045 -0
- package/src/closure-backend.ts +547 -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 +160 -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 +761 -0
- package/src/function-catalog.ts +140 -0
- package/src/function-registry.ts +341 -0
- package/src/index.ts +407 -0
- package/src/integer-arithmetic.ts +64 -0
- package/src/js-emitter.ts +721 -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 +197 -0
- package/src/macro-shape.ts +66 -0
- package/src/member-read.ts +167 -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 +47 -0
- package/src/root-references.ts +126 -0
- package/src/runtime-library.ts +639 -0
- package/src/serializer.ts +262 -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 +310 -0
- package/src/tree-equality.ts +130 -0
- package/src/type-expression.ts +182 -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,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tree back to CEL source.
|
|
3
|
+
*
|
|
4
|
+
* The contract is round-trip: serializing any tree the parser produced yields text
|
|
5
|
+
* that parses to an **equal** tree (`tree-equality.ts`), and a `qcall` writes back
|
|
6
|
+
* as the `Alias.fn(x)` it was read from. That is what makes a tree, rather than the
|
|
7
|
+
* author's text, something a tool may hold and hand back — a rewrite, a quick fix,
|
|
8
|
+
* a stored expression.
|
|
9
|
+
*
|
|
10
|
+
* Parentheses are placed from precedence alone, never kept from the source: the
|
|
11
|
+
* tree records structure, and the source's own parentheses are not structure.
|
|
12
|
+
*
|
|
13
|
+
* It refuses rather than guesses. A tree that cannot be written as CEL — an
|
|
14
|
+
* `unparsed` hole left by error recovery, a `NaN` double, an integer outside its
|
|
15
|
+
* type's range, a name that is not a name — throws, because text that does not
|
|
16
|
+
* parse back would make every later answer about it wrong.
|
|
17
|
+
*/
|
|
18
|
+
import { MAX_INT, MAX_UINT, MIN_INT } from "./lexer.js";
|
|
19
|
+
import { isIdentifierSpelling, isReservedWord } from "./reserved-words.js";
|
|
20
|
+
export class CelSerializeError extends Error {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "CelSerializeError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Binding strength, loosest first; a child is wrapped when it binds more loosely than its slot needs. */
|
|
27
|
+
const PRECEDENCE = {
|
|
28
|
+
conditional: 0,
|
|
29
|
+
or: 1,
|
|
30
|
+
and: 2,
|
|
31
|
+
relation: 3,
|
|
32
|
+
additive: 4,
|
|
33
|
+
multiplicative: 5,
|
|
34
|
+
unary: 6,
|
|
35
|
+
postfix: 7,
|
|
36
|
+
primary: 8,
|
|
37
|
+
};
|
|
38
|
+
const BINARY_PRECEDENCE = {
|
|
39
|
+
"||": PRECEDENCE.or,
|
|
40
|
+
"&&": PRECEDENCE.and,
|
|
41
|
+
"==": PRECEDENCE.relation,
|
|
42
|
+
"!=": PRECEDENCE.relation,
|
|
43
|
+
"<": PRECEDENCE.relation,
|
|
44
|
+
"<=": PRECEDENCE.relation,
|
|
45
|
+
">": PRECEDENCE.relation,
|
|
46
|
+
">=": PRECEDENCE.relation,
|
|
47
|
+
in: PRECEDENCE.relation,
|
|
48
|
+
"+": PRECEDENCE.additive,
|
|
49
|
+
"-": PRECEDENCE.additive,
|
|
50
|
+
"*": PRECEDENCE.multiplicative,
|
|
51
|
+
"/": PRECEDENCE.multiplicative,
|
|
52
|
+
"%": PRECEDENCE.multiplicative,
|
|
53
|
+
};
|
|
54
|
+
function isNegativeNumber(literal) {
|
|
55
|
+
if (literal.type === "int")
|
|
56
|
+
return literal.value < 0n;
|
|
57
|
+
if (literal.type === "double")
|
|
58
|
+
return literal.value < 0 || Object.is(literal.value, -0);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/** A literal written with a leading `-` binds as loosely as a negation does. */
|
|
62
|
+
function precedenceOf(node) {
|
|
63
|
+
switch (node.kind) {
|
|
64
|
+
case "literal":
|
|
65
|
+
return isNegativeNumber(node.literal) ? PRECEDENCE.unary : PRECEDENCE.primary;
|
|
66
|
+
case "ident":
|
|
67
|
+
case "list":
|
|
68
|
+
case "map":
|
|
69
|
+
return PRECEDENCE.primary;
|
|
70
|
+
case "select":
|
|
71
|
+
case "index":
|
|
72
|
+
case "call":
|
|
73
|
+
case "receiverCall":
|
|
74
|
+
case "qcall":
|
|
75
|
+
return PRECEDENCE.postfix;
|
|
76
|
+
case "unary":
|
|
77
|
+
return PRECEDENCE.unary;
|
|
78
|
+
case "binary":
|
|
79
|
+
return BINARY_PRECEDENCE[node.operator];
|
|
80
|
+
case "conditional":
|
|
81
|
+
return PRECEDENCE.conditional;
|
|
82
|
+
case "unparsed":
|
|
83
|
+
throw new CelSerializeError("an unparsed expression has no source to write");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function name(text, what) {
|
|
87
|
+
if (!isIdentifierSpelling(text) || isReservedWord(text)) {
|
|
88
|
+
throw new CelSerializeError(`${JSON.stringify(text)} is not a name, so it cannot be written as a ${what}`);
|
|
89
|
+
}
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
/** A member or a called function may be named by any word, reserved or not. */
|
|
93
|
+
function memberName(text, what) {
|
|
94
|
+
if (!isIdentifierSpelling(text)) {
|
|
95
|
+
throw new CelSerializeError(`${JSON.stringify(text)} is not a name, so it cannot be written as a ${what}`);
|
|
96
|
+
}
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A member name, between backticks where it needs them. A name that is not spelled as an
|
|
101
|
+
* identifier is quoted whether or not it was written that way, because that is the only
|
|
102
|
+
* spelling it has; a backtick inside one has none at all, so it is refused.
|
|
103
|
+
*/
|
|
104
|
+
function fieldName(text, quoted) {
|
|
105
|
+
if (!quoted && isIdentifierSpelling(text))
|
|
106
|
+
return text;
|
|
107
|
+
if (text.includes("`") || text.includes("\n") || text === "") {
|
|
108
|
+
throw new CelSerializeError(`${JSON.stringify(text)} cannot be written as a member name`);
|
|
109
|
+
}
|
|
110
|
+
return `\`${text}\``;
|
|
111
|
+
}
|
|
112
|
+
function writeDouble(value) {
|
|
113
|
+
if (Number.isNaN(value)) {
|
|
114
|
+
throw new CelSerializeError("a double that is not a number cannot be written as a literal");
|
|
115
|
+
}
|
|
116
|
+
if (value === Number.POSITIVE_INFINITY)
|
|
117
|
+
return "1e999";
|
|
118
|
+
if (value === Number.NEGATIVE_INFINITY)
|
|
119
|
+
return "-1e999";
|
|
120
|
+
const sign = value < 0 || Object.is(value, -0) ? "-" : "";
|
|
121
|
+
const text = String(Math.abs(value));
|
|
122
|
+
return `${sign}${/[.e]/.test(text) ? text : `${text}.0`}`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A string literal, always in double quotes.
|
|
126
|
+
*
|
|
127
|
+
* **One spelling, chosen rather than remembered.** The tree records a string's VALUE and not
|
|
128
|
+
* the quote the author typed — the source's own formatting is not structure, which is the
|
|
129
|
+
* same rule that makes the serializer drop redundant parentheses — so a round trip
|
|
130
|
+
* normalizes `'x'` to `"x"`. Both are CEL and the parser reads either.
|
|
131
|
+
*
|
|
132
|
+
* The cost lands on a quick fix, which is written by rewriting the tree and serializing it
|
|
133
|
+
* back **into a YAML scalar**: a double-quoted CEL literal inside a double-quoted scalar has
|
|
134
|
+
* to be escaped, so a repair reads `!cel "a.startsWith(\"x\")"` where the author wrote
|
|
135
|
+
* `'x'`. Choosing the quote here cannot fix that — a single-quoted YAML scalar wants the
|
|
136
|
+
* opposite choice, and this engine does not know which scalar the text will land in. Picking
|
|
137
|
+
* the YAML quoting that needs no escaping is the EDITOR's, which has both halves in hand.
|
|
138
|
+
*/
|
|
139
|
+
function writeString(value) {
|
|
140
|
+
const quote = '"';
|
|
141
|
+
let text = quote;
|
|
142
|
+
for (const unit of value) {
|
|
143
|
+
const code = unit.codePointAt(0);
|
|
144
|
+
if (unit === "\\")
|
|
145
|
+
text += "\\\\";
|
|
146
|
+
else if (unit === quote)
|
|
147
|
+
text += `\\${quote}`;
|
|
148
|
+
else if (unit === "\n")
|
|
149
|
+
text += "\\n";
|
|
150
|
+
else if (unit === "\r")
|
|
151
|
+
text += "\\r";
|
|
152
|
+
else if (unit === "\t")
|
|
153
|
+
text += "\\t";
|
|
154
|
+
else if (code < 0x20 || code === 0x7f)
|
|
155
|
+
text += `\\x${code.toString(16).padStart(2, "0")}`;
|
|
156
|
+
else
|
|
157
|
+
text += unit;
|
|
158
|
+
}
|
|
159
|
+
return `${text}${quote}`;
|
|
160
|
+
}
|
|
161
|
+
function writeBytes(value) {
|
|
162
|
+
let text = 'b"';
|
|
163
|
+
for (const byte of value) {
|
|
164
|
+
if (byte === 0x5c)
|
|
165
|
+
text += "\\\\";
|
|
166
|
+
else if (byte === 0x22)
|
|
167
|
+
text += '\\"';
|
|
168
|
+
else if (byte >= 0x20 && byte <= 0x7e)
|
|
169
|
+
text += String.fromCharCode(byte);
|
|
170
|
+
else
|
|
171
|
+
text += `\\x${byte.toString(16).padStart(2, "0")}`;
|
|
172
|
+
}
|
|
173
|
+
return `${text}"`;
|
|
174
|
+
}
|
|
175
|
+
function writeLiteral(literal) {
|
|
176
|
+
switch (literal.type) {
|
|
177
|
+
case "int":
|
|
178
|
+
if (literal.value < MIN_INT || literal.value > MAX_INT) {
|
|
179
|
+
throw new CelSerializeError(`${literal.value} is outside the range of a 64-bit integer`);
|
|
180
|
+
}
|
|
181
|
+
return `${literal.value}`;
|
|
182
|
+
case "uint":
|
|
183
|
+
if (literal.value < 0n || literal.value > MAX_UINT) {
|
|
184
|
+
throw new CelSerializeError(`${literal.value} is outside the range of an unsigned 64-bit integer`);
|
|
185
|
+
}
|
|
186
|
+
return `${literal.value}u`;
|
|
187
|
+
case "double":
|
|
188
|
+
return writeDouble(literal.value);
|
|
189
|
+
case "string":
|
|
190
|
+
return writeString(literal.value);
|
|
191
|
+
case "bytes":
|
|
192
|
+
return writeBytes(literal.value);
|
|
193
|
+
case "bool":
|
|
194
|
+
return literal.value ? "true" : "false";
|
|
195
|
+
case "null":
|
|
196
|
+
return "null";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Writes `node` for a slot that binds at least as tightly as `needs`. */
|
|
200
|
+
function write(node, needs) {
|
|
201
|
+
const text = writeNode(node);
|
|
202
|
+
return precedenceOf(node) < needs ? `(${text})` : text;
|
|
203
|
+
}
|
|
204
|
+
function writeNode(node) {
|
|
205
|
+
switch (node.kind) {
|
|
206
|
+
case "literal":
|
|
207
|
+
return writeLiteral(node.literal);
|
|
208
|
+
case "ident":
|
|
209
|
+
return `${node.absolute ? "." : ""}${name(node.name, "name")}`;
|
|
210
|
+
case "list":
|
|
211
|
+
return `[${node.elements
|
|
212
|
+
.map((element) => `${element.optional ? "?" : ""}${write(element.value, PRECEDENCE.conditional)}`)
|
|
213
|
+
.join(", ")}]`;
|
|
214
|
+
case "map":
|
|
215
|
+
return `{${node.entries
|
|
216
|
+
.map((entry) => `${entry.optional ? "?" : ""}${write(entry.key, PRECEDENCE.conditional)}: ${write(entry.value, PRECEDENCE.conditional)}`)
|
|
217
|
+
.join(", ")}}`;
|
|
218
|
+
case "select":
|
|
219
|
+
return `${write(node.operand, PRECEDENCE.postfix)}.${node.optional ? "?" : ""}${fieldName(node.field, node.quoted)}`;
|
|
220
|
+
case "index":
|
|
221
|
+
return `${write(node.operand, PRECEDENCE.postfix)}[${node.optional ? "?" : ""}${write(node.index, PRECEDENCE.conditional)}]`;
|
|
222
|
+
case "call":
|
|
223
|
+
return `${name(node.name, "function name")}(${writeArguments(node.args)})`;
|
|
224
|
+
case "receiverCall":
|
|
225
|
+
return `${write(node.receiver, PRECEDENCE.postfix)}.${memberName(node.name, "function name")}(${writeArguments(node.args)})`;
|
|
226
|
+
case "qcall":
|
|
227
|
+
return `${name(node.namespace, "namespace")}.${memberName(node.name, "function name")}(${writeArguments(node.args)})`;
|
|
228
|
+
case "unary":
|
|
229
|
+
return `${node.operator}${writeUnaryOperand(node.operator, node.operand)}`;
|
|
230
|
+
case "binary":
|
|
231
|
+
return `${write(node.left, BINARY_PRECEDENCE[node.operator])} ${node.operator} ${write(node.right, BINARY_PRECEDENCE[node.operator] + 1)}`;
|
|
232
|
+
case "conditional":
|
|
233
|
+
return `${write(node.condition, PRECEDENCE.or)} ? ${write(node.whenTrue, PRECEDENCE.conditional)} : ${write(node.whenFalse, PRECEDENCE.conditional)}`;
|
|
234
|
+
case "unparsed":
|
|
235
|
+
throw new CelSerializeError("an unparsed expression has no source to write");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* A minus directly on a non-negative numeric literal would read back as part of the
|
|
240
|
+
* literal, so it is parenthesized: `-(1)` stays a negation of one.
|
|
241
|
+
*/
|
|
242
|
+
function writeUnaryOperand(operator, operand) {
|
|
243
|
+
if (operator === "-" && operand.kind === "literal" && !isNegativeNumber(operand.literal)) {
|
|
244
|
+
if (operand.literal.type === "int" || operand.literal.type === "double") {
|
|
245
|
+
return `(${writeLiteral(operand.literal)})`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return write(operand, PRECEDENCE.unary);
|
|
249
|
+
}
|
|
250
|
+
function writeArguments(args) {
|
|
251
|
+
return args.map((argument) => write(argument, PRECEDENCE.conditional)).join(", ");
|
|
252
|
+
}
|
|
253
|
+
/** The CEL source of a tree. Throws on a tree that has none. */
|
|
254
|
+
export function serializeTree(root) {
|
|
255
|
+
return write(root, PRECEDENCE.conditional);
|
|
256
|
+
}
|
package/dist/sha256.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256, in this package's own code.
|
|
3
|
+
*
|
|
4
|
+
* A cache key decides which emitted module is loaded, so its hash must be
|
|
5
|
+
* collision-resistant: a collision serves another expression set's code, which is the one
|
|
6
|
+
* failure here that no retry undoes. So it is a cryptographic digest rather than a cheap
|
|
7
|
+
* checksum.
|
|
8
|
+
*
|
|
9
|
+
* It is implemented here rather than taken from a platform because of what this package
|
|
10
|
+
* is allowed to reach. `node:crypto` is a Node built-in, which the browser-safety gate
|
|
11
|
+
* refuses; `crypto.subtle.digest` is **asynchronous**, and emission happens on a path that
|
|
12
|
+
* is synchronous end to end. Sixty lines of arithmetic is the price of keeping both
|
|
13
|
+
* properties, and the digest is held to the published test vectors
|
|
14
|
+
* (`tests/emitted-module.test.ts`).
|
|
15
|
+
*/
|
|
16
|
+
/** The digest of bytes, as 64 lowercase hexadecimal characters. */
|
|
17
|
+
export declare function sha256Hex(input: Uint8Array): string;
|
|
18
|
+
/** The digest of text, read as UTF-8 — what every key and digest in the emitter is over. */
|
|
19
|
+
export declare function sha256OfText(text: string): string;
|
|
20
|
+
//# sourceMappingURL=sha256.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sha256.d.ts","sourceRoot":"","sources":["../src/sha256.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAsBH,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAqEnD;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
package/dist/sha256.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256, in this package's own code.
|
|
3
|
+
*
|
|
4
|
+
* A cache key decides which emitted module is loaded, so its hash must be
|
|
5
|
+
* collision-resistant: a collision serves another expression set's code, which is the one
|
|
6
|
+
* failure here that no retry undoes. So it is a cryptographic digest rather than a cheap
|
|
7
|
+
* checksum.
|
|
8
|
+
*
|
|
9
|
+
* It is implemented here rather than taken from a platform because of what this package
|
|
10
|
+
* is allowed to reach. `node:crypto` is a Node built-in, which the browser-safety gate
|
|
11
|
+
* refuses; `crypto.subtle.digest` is **asynchronous**, and emission happens on a path that
|
|
12
|
+
* is synchronous end to end. Sixty lines of arithmetic is the price of keeping both
|
|
13
|
+
* properties, and the digest is held to the published test vectors
|
|
14
|
+
* (`tests/emitted-module.test.ts`).
|
|
15
|
+
*/
|
|
16
|
+
import { textToBytes } from "./value-text.js";
|
|
17
|
+
/** The first 32 bits of the cube roots of the first 64 primes. */
|
|
18
|
+
const K = new Uint32Array([
|
|
19
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
20
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
21
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
22
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
23
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
24
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
25
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
26
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
27
|
+
]);
|
|
28
|
+
const HEX = "0123456789abcdef";
|
|
29
|
+
function rotateRight(value, by) {
|
|
30
|
+
return (value >>> by) | (value << (32 - by));
|
|
31
|
+
}
|
|
32
|
+
/** The digest of bytes, as 64 lowercase hexadecimal characters. */
|
|
33
|
+
export function sha256Hex(input) {
|
|
34
|
+
const length = input.length;
|
|
35
|
+
// The message, a 0x80 byte, zero padding to 56 mod 64, then the bit length as 64 bits.
|
|
36
|
+
const padded = new Uint8Array(((length + 72) >> 6) << 6);
|
|
37
|
+
padded.set(input);
|
|
38
|
+
padded[length] = 0x80;
|
|
39
|
+
const bits = BigInt(length) * 8n;
|
|
40
|
+
for (let at = 0; at < 8; at += 1) {
|
|
41
|
+
padded[padded.length - 1 - at] = Number((bits >> BigInt(8 * at)) & 0xffn);
|
|
42
|
+
}
|
|
43
|
+
const state = new Uint32Array([
|
|
44
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
45
|
+
]);
|
|
46
|
+
const schedule = new Uint32Array(64);
|
|
47
|
+
for (let block = 0; block < padded.length; block += 64) {
|
|
48
|
+
for (let at = 0; at < 16; at += 1) {
|
|
49
|
+
const from = block + at * 4;
|
|
50
|
+
schedule[at] =
|
|
51
|
+
((padded[from] << 24) | (padded[from + 1] << 16) | (padded[from + 2] << 8) | padded[from + 3]) >>> 0;
|
|
52
|
+
}
|
|
53
|
+
for (let at = 16; at < 64; at += 1) {
|
|
54
|
+
const a = schedule[at - 15];
|
|
55
|
+
const b = schedule[at - 2];
|
|
56
|
+
const s0 = rotateRight(a, 7) ^ rotateRight(a, 18) ^ (a >>> 3);
|
|
57
|
+
const s1 = rotateRight(b, 17) ^ rotateRight(b, 19) ^ (b >>> 10);
|
|
58
|
+
schedule[at] = (schedule[at - 16] + s0 + schedule[at - 7] + s1) >>> 0;
|
|
59
|
+
}
|
|
60
|
+
let a = state[0];
|
|
61
|
+
let b = state[1];
|
|
62
|
+
let c = state[2];
|
|
63
|
+
let d = state[3];
|
|
64
|
+
let e = state[4];
|
|
65
|
+
let f = state[5];
|
|
66
|
+
let g = state[6];
|
|
67
|
+
let h = state[7];
|
|
68
|
+
for (let at = 0; at < 64; at += 1) {
|
|
69
|
+
const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
70
|
+
const choose = (e & f) ^ (~e & g);
|
|
71
|
+
const one = (h + s1 + choose + K[at] + schedule[at]) >>> 0;
|
|
72
|
+
const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
73
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
74
|
+
const two = (s0 + majority) >>> 0;
|
|
75
|
+
h = g;
|
|
76
|
+
g = f;
|
|
77
|
+
f = e;
|
|
78
|
+
e = (d + one) >>> 0;
|
|
79
|
+
d = c;
|
|
80
|
+
c = b;
|
|
81
|
+
b = a;
|
|
82
|
+
a = (one + two) >>> 0;
|
|
83
|
+
}
|
|
84
|
+
state[0] = (state[0] + a) >>> 0;
|
|
85
|
+
state[1] = (state[1] + b) >>> 0;
|
|
86
|
+
state[2] = (state[2] + c) >>> 0;
|
|
87
|
+
state[3] = (state[3] + d) >>> 0;
|
|
88
|
+
state[4] = (state[4] + e) >>> 0;
|
|
89
|
+
state[5] = (state[5] + f) >>> 0;
|
|
90
|
+
state[6] = (state[6] + g) >>> 0;
|
|
91
|
+
state[7] = (state[7] + h) >>> 0;
|
|
92
|
+
}
|
|
93
|
+
let out = "";
|
|
94
|
+
for (const word of state) {
|
|
95
|
+
for (let shift = 28; shift >= 0; shift -= 4)
|
|
96
|
+
out += HEX[(word >>> shift) & 0xf];
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/** The digest of text, read as UTF-8 — what every key and digest in the emitter is over. */
|
|
101
|
+
export function sha256OfText(text) {
|
|
102
|
+
return sha256Hex(textToBytes(text));
|
|
103
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A function's declared shape: `size(string): int`, `string.startsWith(string): bool`.
|
|
3
|
+
*
|
|
4
|
+
* One spelling serves registration, the signature data a port reads, and the
|
|
5
|
+
* definition listing a host prints, so a signature that round-trips through text is
|
|
6
|
+
* the signature it started as.
|
|
7
|
+
*
|
|
8
|
+
* **The dispatch key excludes the return type.** That is what makes "register,
|
|
9
|
+
* override exactly, or remove" one mechanism rather than three: registering
|
|
10
|
+
* `duration(string): Money` replaces `duration(string): google.protobuf.Duration`
|
|
11
|
+
* because both answer the same call, and the engine being replaced refuses exactly
|
|
12
|
+
* this as an overlapping overload — which is why its standard library cannot be
|
|
13
|
+
* changed at all.
|
|
14
|
+
*/
|
|
15
|
+
import type { CelType } from "./cel-type.js";
|
|
16
|
+
import type { CelValue } from "./cel-value.js";
|
|
17
|
+
import type { CelImplementation } from "./runtime-library.js";
|
|
18
|
+
import type { NominalResolver } from "./type-expression.js";
|
|
19
|
+
export type CallForm = "global" | "receiver";
|
|
20
|
+
export interface CelSignature {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly form: CallForm;
|
|
23
|
+
/** The type the call is written on, for a receiver call. */
|
|
24
|
+
readonly receiver?: CelType;
|
|
25
|
+
readonly parameters: readonly CelType[];
|
|
26
|
+
readonly returns: CelType;
|
|
27
|
+
}
|
|
28
|
+
export declare function parseSignature(text: string, resolveNominal?: NominalResolver): CelSignature;
|
|
29
|
+
export declare function formatSignature(signature: CelSignature): string;
|
|
30
|
+
/** What two registrations of the same call share: the name, the form and the parameters. */
|
|
31
|
+
export declare function signatureKey(signature: CelSignature): string;
|
|
32
|
+
/**
|
|
33
|
+
* Checking the arguments a call wrote as LITERALS, where a type cannot say enough.
|
|
34
|
+
*
|
|
35
|
+
* A signature constrains types, so a guard over a VALUE — an unparseable format
|
|
36
|
+
* specifier, a decimal count out of range, an unknown time zone, a pattern the regular
|
|
37
|
+
* expression engine refuses — would otherwise fire only when the expression is evaluated,
|
|
38
|
+
* which puts a defect the source states in plain sight behind a run. A registration that
|
|
39
|
+
* carries one is asked at the call site the checker has just resolved, and a refusal is
|
|
40
|
+
* `CEL_INVALID_ARGUMENT` with the call's own range.
|
|
41
|
+
*
|
|
42
|
+
* `literals[at]` is the value of argument `at` where it was written as a literal — the
|
|
43
|
+
* receiver first, for a call written on a value, so the positions are the ones the
|
|
44
|
+
* implementation sees — and `undefined` where the argument is an expression whose value
|
|
45
|
+
* is not statically known, which a guard must SKIP rather than judge. The answer is the
|
|
46
|
+
* refusal, or nothing where there is none. A guard runs the same code the evaluation
|
|
47
|
+
* runs, so the static and dynamic answers cannot drift into disagreement.
|
|
48
|
+
*/
|
|
49
|
+
export type LiteralArgumentCheck = (literals: readonly (CelValue | undefined)[]) => string | undefined;
|
|
50
|
+
/** What a host declares about a function beyond its types. */
|
|
51
|
+
export interface FunctionMetadata {
|
|
52
|
+
/**
|
|
53
|
+
* What the function does. The standard library's behaviour is looked up by dispatch
|
|
54
|
+
* key instead (`runtime-library.ts`), so a registration carries one only where the
|
|
55
|
+
* host supplies it; a call that resolves to a registration with neither is
|
|
56
|
+
* `unbound_function` at evaluation. The type is not thenable, so an implementation
|
|
57
|
+
* that answers asynchronously does not compile.
|
|
58
|
+
*/
|
|
59
|
+
readonly implementation?: CelImplementation;
|
|
60
|
+
/** A guard over the arguments written as literals, asked where the checker resolves the call. */
|
|
61
|
+
readonly checkArguments?: LiteralArgumentCheck;
|
|
62
|
+
/** Whether two calls with the same arguments answer the same thing. Default true. */
|
|
63
|
+
readonly deterministic?: boolean;
|
|
64
|
+
/** Whether the implementation is supplied by the host rather than the engine. */
|
|
65
|
+
readonly hostBacked?: boolean;
|
|
66
|
+
/** The error codes a call may fail with. */
|
|
67
|
+
readonly throws?: readonly string[];
|
|
68
|
+
readonly description?: string;
|
|
69
|
+
/** Where the registration came from, for a listing a human reads. */
|
|
70
|
+
readonly origin?: string;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../src/signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAID,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,eAAe,GAAG,YAAY,CAkB3F;AAqBD,wBAAgB,eAAe,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAE/D;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAG5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,oBAAoB,GAAG,CACjC,QAAQ,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,EAAE,KACxC,MAAM,GAAG,SAAS,CAAC;AAExB,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,iBAAiB,CAAC;IAC5C,iGAAiG;IACjG,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAC/C,qFAAqF;IACrF,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A function's declared shape: `size(string): int`, `string.startsWith(string): bool`.
|
|
3
|
+
*
|
|
4
|
+
* One spelling serves registration, the signature data a port reads, and the
|
|
5
|
+
* definition listing a host prints, so a signature that round-trips through text is
|
|
6
|
+
* the signature it started as.
|
|
7
|
+
*
|
|
8
|
+
* **The dispatch key excludes the return type.** That is what makes "register,
|
|
9
|
+
* override exactly, or remove" one mechanism rather than three: registering
|
|
10
|
+
* `duration(string): Money` replaces `duration(string): google.protobuf.Duration`
|
|
11
|
+
* because both answer the same call, and the engine being replaced refuses exactly
|
|
12
|
+
* this as an overlapping overload — which is why its standard library cannot be
|
|
13
|
+
* changed at all.
|
|
14
|
+
*/
|
|
15
|
+
import { formatType } from "./cel-type.js";
|
|
16
|
+
import { CelTypeExpressionError, parseTypeExpression } from "./type-expression.js";
|
|
17
|
+
const SIGNATURE = /^(?:(.+)\.)?([A-Za-z_][A-Za-z0-9_]*)\(([^)]*)\)\s*:\s*(.+)$/;
|
|
18
|
+
export function parseSignature(text, resolveNominal) {
|
|
19
|
+
const parts = SIGNATURE.exec(text.trim());
|
|
20
|
+
if (!parts) {
|
|
21
|
+
throw new CelTypeExpressionError(`${JSON.stringify(text)} is not a signature — write 'name(a, b): r' or 'Receiver.name(a): r'`);
|
|
22
|
+
}
|
|
23
|
+
const [, receiverText, name, parameterText, returnText] = parts;
|
|
24
|
+
const parameters = splitParameters(parameterText).map((part) => parseTypeExpression(part, resolveNominal));
|
|
25
|
+
return {
|
|
26
|
+
name: name,
|
|
27
|
+
form: receiverText === undefined ? "global" : "receiver",
|
|
28
|
+
...(receiverText === undefined ? {} : { receiver: parseTypeExpression(receiverText, resolveNominal) }),
|
|
29
|
+
parameters,
|
|
30
|
+
returns: parseTypeExpression(returnText, resolveNominal),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The parameter list, split at its own commas — `map<K, V>` holds one of its own. */
|
|
34
|
+
function splitParameters(text) {
|
|
35
|
+
if (!text.trim())
|
|
36
|
+
return [];
|
|
37
|
+
const parts = [];
|
|
38
|
+
let depth = 0;
|
|
39
|
+
let from = 0;
|
|
40
|
+
for (let at = 0; at < text.length; at += 1) {
|
|
41
|
+
const ch = text[at];
|
|
42
|
+
if (ch === "<")
|
|
43
|
+
depth += 1;
|
|
44
|
+
else if (ch === ">")
|
|
45
|
+
depth -= 1;
|
|
46
|
+
else if (ch === "," && depth === 0) {
|
|
47
|
+
parts.push(text.slice(from, at));
|
|
48
|
+
from = at + 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
parts.push(text.slice(from));
|
|
52
|
+
return parts;
|
|
53
|
+
}
|
|
54
|
+
export function formatSignature(signature) {
|
|
55
|
+
return `${signatureKey(signature)}: ${formatType(signature.returns)}`;
|
|
56
|
+
}
|
|
57
|
+
/** What two registrations of the same call share: the name, the form and the parameters. */
|
|
58
|
+
export function signatureKey(signature) {
|
|
59
|
+
const on = signature.receiver ? `${formatType(signature.receiver)}.` : "";
|
|
60
|
+
return `${on}${signature.name}(${signature.parameters.map((type) => formatType(type)).join(", ")})`;
|
|
61
|
+
}
|