@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.
Files changed (223) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +281 -2
  3. package/dist/activation.d.ts +27 -0
  4. package/dist/activation.d.ts.map +1 -0
  5. package/dist/activation.js +24 -0
  6. package/dist/backend-runtime.d.ts +184 -0
  7. package/dist/backend-runtime.d.ts.map +1 -0
  8. package/dist/backend-runtime.js +425 -0
  9. package/dist/bounded-cache.d.ts +21 -0
  10. package/dist/bounded-cache.d.ts.map +1 -0
  11. package/dist/bounded-cache.js +42 -0
  12. package/dist/catalog-runtime.d.ts +59 -0
  13. package/dist/catalog-runtime.d.ts.map +1 -0
  14. package/dist/catalog-runtime.js +786 -0
  15. package/dist/cel-expression.d.ts +32 -0
  16. package/dist/cel-expression.d.ts.map +1 -0
  17. package/dist/cel-expression.js +29 -0
  18. package/dist/cel-map-value.d.ts +47 -0
  19. package/dist/cel-map-value.d.ts.map +1 -0
  20. package/dist/cel-map-value.js +85 -0
  21. package/dist/cel-program.d.ts +44 -0
  22. package/dist/cel-program.d.ts.map +1 -0
  23. package/dist/cel-program.js +72 -0
  24. package/dist/cel-type.d.ts +131 -0
  25. package/dist/cel-type.d.ts.map +1 -0
  26. package/dist/cel-type.js +293 -0
  27. package/dist/cel-value.d.ts +166 -0
  28. package/dist/cel-value.d.ts.map +1 -0
  29. package/dist/cel-value.js +225 -0
  30. package/dist/check-diagnostic.d.ts +54 -0
  31. package/dist/check-diagnostic.d.ts.map +1 -0
  32. package/dist/check-diagnostic.js +66 -0
  33. package/dist/checker.d.ts +86 -0
  34. package/dist/checker.d.ts.map +1 -0
  35. package/dist/checker.js +806 -0
  36. package/dist/closure-backend.d.ts +21 -0
  37. package/dist/closure-backend.d.ts.map +1 -0
  38. package/dist/closure-backend.js +487 -0
  39. package/dist/comprehension-bindings.d.ts +33 -0
  40. package/dist/comprehension-bindings.d.ts.map +1 -0
  41. package/dist/comprehension-bindings.js +50 -0
  42. package/dist/comprehension-runtime.d.ts +44 -0
  43. package/dist/comprehension-runtime.d.ts.map +1 -0
  44. package/dist/comprehension-runtime.js +137 -0
  45. package/dist/declared-chain.d.ts +35 -0
  46. package/dist/declared-chain.d.ts.map +1 -0
  47. package/dist/declared-chain.js +36 -0
  48. package/dist/duration-value.d.ts +70 -0
  49. package/dist/duration-value.d.ts.map +1 -0
  50. package/dist/duration-value.js +149 -0
  51. package/dist/emitted-module.d.ts +207 -0
  52. package/dist/emitted-module.d.ts.map +1 -0
  53. package/dist/emitted-module.js +359 -0
  54. package/dist/engine-version.d.ts +3 -0
  55. package/dist/engine-version.d.ts.map +1 -0
  56. package/dist/engine-version.js +8 -0
  57. package/dist/environment-digest.d.ts +44 -0
  58. package/dist/environment-digest.d.ts.map +1 -0
  59. package/dist/environment-digest.js +98 -0
  60. package/dist/environment.d.ts +291 -0
  61. package/dist/environment.d.ts.map +1 -0
  62. package/dist/environment.js +474 -0
  63. package/dist/function-catalog.d.ts +66 -0
  64. package/dist/function-catalog.d.ts.map +1 -0
  65. package/dist/function-catalog.js +77 -0
  66. package/dist/function-registry.d.ts +118 -0
  67. package/dist/function-registry.d.ts.map +1 -0
  68. package/dist/function-registry.js +292 -0
  69. package/dist/index.d.ts +93 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +65 -0
  72. package/dist/integer-arithmetic.d.ts +27 -0
  73. package/dist/integer-arithmetic.d.ts.map +1 -0
  74. package/dist/integer-arithmetic.js +58 -0
  75. package/dist/js-emitter.d.ts +133 -0
  76. package/dist/js-emitter.d.ts.map +1 -0
  77. package/dist/js-emitter.js +568 -0
  78. package/dist/json-schema-type.d.ts +182 -0
  79. package/dist/json-schema-type.d.ts.map +1 -0
  80. package/dist/json-schema-type.js +487 -0
  81. package/dist/json-text-scan.d.ts +28 -0
  82. package/dist/json-text-scan.d.ts.map +1 -0
  83. package/dist/json-text-scan.js +159 -0
  84. package/dist/lexer.d.ts +103 -0
  85. package/dist/lexer.d.ts.map +1 -0
  86. package/dist/lexer.js +458 -0
  87. package/dist/macro-check.d.ts +41 -0
  88. package/dist/macro-check.d.ts.map +1 -0
  89. package/dist/macro-check.js +162 -0
  90. package/dist/macro-shape.d.ts +24 -0
  91. package/dist/macro-shape.d.ts.map +1 -0
  92. package/dist/macro-shape.js +55 -0
  93. package/dist/member-read.d.ts +89 -0
  94. package/dist/member-read.d.ts.map +1 -0
  95. package/dist/member-read.js +166 -0
  96. package/dist/namespace-resolution.d.ts +35 -0
  97. package/dist/namespace-resolution.d.ts.map +1 -0
  98. package/dist/namespace-resolution.js +160 -0
  99. package/dist/nominal-type.d.ts +63 -0
  100. package/dist/nominal-type.d.ts.map +1 -0
  101. package/dist/nominal-type.js +98 -0
  102. package/dist/nullable-access.d.ts +38 -0
  103. package/dist/nullable-access.d.ts.map +1 -0
  104. package/dist/nullable-access.js +93 -0
  105. package/dist/parse-limits.d.ts +26 -0
  106. package/dist/parse-limits.d.ts.map +1 -0
  107. package/dist/parse-limits.js +21 -0
  108. package/dist/parser.d.ts +48 -0
  109. package/dist/parser.d.ts.map +1 -0
  110. package/dist/parser.js +503 -0
  111. package/dist/qualified-calls.d.ts +22 -0
  112. package/dist/qualified-calls.d.ts.map +1 -0
  113. package/dist/qualified-calls.js +27 -0
  114. package/dist/regular-expression.d.ts +48 -0
  115. package/dist/regular-expression.d.ts.map +1 -0
  116. package/dist/regular-expression.js +77 -0
  117. package/dist/reserved-words.d.ts +52 -0
  118. package/dist/reserved-words.d.ts.map +1 -0
  119. package/dist/reserved-words.js +77 -0
  120. package/dist/resolved-call.d.ts +46 -0
  121. package/dist/resolved-call.d.ts.map +1 -0
  122. package/dist/resolved-call.js +15 -0
  123. package/dist/root-references.d.ts +23 -0
  124. package/dist/root-references.d.ts.map +1 -0
  125. package/dist/root-references.js +120 -0
  126. package/dist/runtime-library.d.ts +67 -0
  127. package/dist/runtime-library.d.ts.map +1 -0
  128. package/dist/runtime-library.js +554 -0
  129. package/dist/serializer.d.ts +24 -0
  130. package/dist/serializer.d.ts.map +1 -0
  131. package/dist/serializer.js +256 -0
  132. package/dist/sha256.d.ts +20 -0
  133. package/dist/sha256.d.ts.map +1 -0
  134. package/dist/sha256.js +103 -0
  135. package/dist/signature.d.ts +72 -0
  136. package/dist/signature.d.ts.map +1 -0
  137. package/dist/signature.js +61 -0
  138. package/dist/signatures/function-catalog.json +788 -0
  139. package/dist/signatures/standard-library.json +229 -0
  140. package/dist/standard-library.d.ts +41 -0
  141. package/dist/standard-library.d.ts.map +1 -0
  142. package/dist/standard-library.js +85 -0
  143. package/dist/syntax-diagnostic.d.ts +61 -0
  144. package/dist/syntax-diagnostic.d.ts.map +1 -0
  145. package/dist/syntax-diagnostic.js +28 -0
  146. package/dist/syntax-tree.d.ts +160 -0
  147. package/dist/syntax-tree.d.ts.map +1 -0
  148. package/dist/syntax-tree.js +58 -0
  149. package/dist/timestamp-value.d.ts +62 -0
  150. package/dist/timestamp-value.d.ts.map +1 -0
  151. package/dist/timestamp-value.js +238 -0
  152. package/dist/tree-equality.d.ts +15 -0
  153. package/dist/tree-equality.d.ts.map +1 -0
  154. package/dist/tree-equality.js +105 -0
  155. package/dist/type-expression.d.ts +42 -0
  156. package/dist/type-expression.d.ts.map +1 -0
  157. package/dist/type-expression.js +154 -0
  158. package/dist/value-equality.d.ts +38 -0
  159. package/dist/value-equality.d.ts.map +1 -0
  160. package/dist/value-equality.js +196 -0
  161. package/dist/value-text.d.ts +25 -0
  162. package/dist/value-text.d.ts.map +1 -0
  163. package/dist/value-text.js +44 -0
  164. package/dist/zoned-calendar.d.ts +61 -0
  165. package/dist/zoned-calendar.d.ts.map +1 -0
  166. package/dist/zoned-calendar.js +143 -0
  167. package/package.json +56 -3
  168. package/src/activation.ts +32 -0
  169. package/src/backend-runtime.ts +604 -0
  170. package/src/bounded-cache.ts +45 -0
  171. package/src/catalog-runtime.ts +921 -0
  172. package/src/cel-expression.ts +53 -0
  173. package/src/cel-map-value.ts +97 -0
  174. package/src/cel-program.ts +103 -0
  175. package/src/cel-type.ts +359 -0
  176. package/src/cel-value.ts +361 -0
  177. package/src/check-diagnostic.ts +104 -0
  178. package/src/checker.ts +1045 -0
  179. package/src/closure-backend.ts +547 -0
  180. package/src/comprehension-bindings.ts +66 -0
  181. package/src/comprehension-runtime.ts +157 -0
  182. package/src/declared-chain.ts +45 -0
  183. package/src/duration-value.ts +160 -0
  184. package/src/emitted-module.ts +494 -0
  185. package/src/engine-version.ts +9 -0
  186. package/src/environment-digest.ts +111 -0
  187. package/src/environment.ts +761 -0
  188. package/src/function-catalog.ts +140 -0
  189. package/src/function-registry.ts +341 -0
  190. package/src/index.ts +407 -0
  191. package/src/integer-arithmetic.ts +64 -0
  192. package/src/js-emitter.ts +721 -0
  193. package/src/json-schema-type.ts +664 -0
  194. package/src/json-text-scan.ts +163 -0
  195. package/src/lexer.ts +562 -0
  196. package/src/macro-check.ts +197 -0
  197. package/src/macro-shape.ts +66 -0
  198. package/src/member-read.ts +167 -0
  199. package/src/namespace-resolution.ts +167 -0
  200. package/src/nominal-type.ts +149 -0
  201. package/src/nullable-access.ts +95 -0
  202. package/src/parse-limits.ts +36 -0
  203. package/src/parser.ts +554 -0
  204. package/src/qualified-calls.ts +39 -0
  205. package/src/regular-expression.ts +101 -0
  206. package/src/reserved-words.ts +94 -0
  207. package/src/resolved-call.ts +47 -0
  208. package/src/root-references.ts +126 -0
  209. package/src/runtime-library.ts +639 -0
  210. package/src/serializer.ts +262 -0
  211. package/src/sha256.ts +112 -0
  212. package/src/signature.ts +127 -0
  213. package/src/signatures/function-catalog.json +788 -0
  214. package/src/signatures/standard-library.json +235 -0
  215. package/src/standard-library.ts +149 -0
  216. package/src/syntax-diagnostic.ts +72 -0
  217. package/src/syntax-tree.ts +229 -0
  218. package/src/timestamp-value.ts +310 -0
  219. package/src/tree-equality.ts +130 -0
  220. package/src/type-expression.ts +182 -0
  221. package/src/value-equality.ts +201 -0
  222. package/src/value-text.ts +45 -0
  223. package/src/zoned-calendar.ts +178 -0
@@ -0,0 +1,262 @@
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
+
19
+ import { MAX_INT, MAX_UINT, MIN_INT } from "./lexer.js";
20
+ import { isIdentifierSpelling, isReservedWord } from "./reserved-words.js";
21
+ import type { CelLiteral, CelNode } from "./syntax-tree.js";
22
+
23
+ export class CelSerializeError extends Error {
24
+ constructor(message: string) {
25
+ super(message);
26
+ this.name = "CelSerializeError";
27
+ }
28
+ }
29
+
30
+ /** Binding strength, loosest first; a child is wrapped when it binds more loosely than its slot needs. */
31
+ const PRECEDENCE = {
32
+ conditional: 0,
33
+ or: 1,
34
+ and: 2,
35
+ relation: 3,
36
+ additive: 4,
37
+ multiplicative: 5,
38
+ unary: 6,
39
+ postfix: 7,
40
+ primary: 8,
41
+ } as const;
42
+
43
+ const BINARY_PRECEDENCE: Record<string, number> = {
44
+ "||": PRECEDENCE.or,
45
+ "&&": PRECEDENCE.and,
46
+ "==": PRECEDENCE.relation,
47
+ "!=": PRECEDENCE.relation,
48
+ "<": PRECEDENCE.relation,
49
+ "<=": PRECEDENCE.relation,
50
+ ">": PRECEDENCE.relation,
51
+ ">=": PRECEDENCE.relation,
52
+ in: PRECEDENCE.relation,
53
+ "+": PRECEDENCE.additive,
54
+ "-": PRECEDENCE.additive,
55
+ "*": PRECEDENCE.multiplicative,
56
+ "/": PRECEDENCE.multiplicative,
57
+ "%": PRECEDENCE.multiplicative,
58
+ };
59
+
60
+ function isNegativeNumber(literal: CelLiteral): boolean {
61
+ if (literal.type === "int") return literal.value < 0n;
62
+ if (literal.type === "double") return literal.value < 0 || Object.is(literal.value, -0);
63
+ return false;
64
+ }
65
+
66
+ /** A literal written with a leading `-` binds as loosely as a negation does. */
67
+ function precedenceOf(node: CelNode): number {
68
+ switch (node.kind) {
69
+ case "literal":
70
+ return isNegativeNumber(node.literal) ? PRECEDENCE.unary : PRECEDENCE.primary;
71
+ case "ident":
72
+ case "list":
73
+ case "map":
74
+ return PRECEDENCE.primary;
75
+ case "select":
76
+ case "index":
77
+ case "call":
78
+ case "receiverCall":
79
+ case "qcall":
80
+ return PRECEDENCE.postfix;
81
+ case "unary":
82
+ return PRECEDENCE.unary;
83
+ case "binary":
84
+ return BINARY_PRECEDENCE[node.operator]!;
85
+ case "conditional":
86
+ return PRECEDENCE.conditional;
87
+ case "unparsed":
88
+ throw new CelSerializeError("an unparsed expression has no source to write");
89
+ }
90
+ }
91
+
92
+ function name(text: string, what: string): string {
93
+ if (!isIdentifierSpelling(text) || isReservedWord(text)) {
94
+ throw new CelSerializeError(`${JSON.stringify(text)} is not a name, so it cannot be written as a ${what}`);
95
+ }
96
+ return text;
97
+ }
98
+
99
+ /** A member or a called function may be named by any word, reserved or not. */
100
+ function memberName(text: string, what: string): string {
101
+ if (!isIdentifierSpelling(text)) {
102
+ throw new CelSerializeError(`${JSON.stringify(text)} is not a name, so it cannot be written as a ${what}`);
103
+ }
104
+ return text;
105
+ }
106
+
107
+ /**
108
+ * A member name, between backticks where it needs them. A name that is not spelled as an
109
+ * identifier is quoted whether or not it was written that way, because that is the only
110
+ * spelling it has; a backtick inside one has none at all, so it is refused.
111
+ */
112
+ function fieldName(text: string, quoted: boolean): string {
113
+ if (!quoted && isIdentifierSpelling(text)) return text;
114
+ if (text.includes("`") || text.includes("\n") || text === "") {
115
+ throw new CelSerializeError(`${JSON.stringify(text)} cannot be written as a member name`);
116
+ }
117
+ return `\`${text}\``;
118
+ }
119
+
120
+ function writeDouble(value: number): string {
121
+ if (Number.isNaN(value)) {
122
+ throw new CelSerializeError("a double that is not a number cannot be written as a literal");
123
+ }
124
+ if (value === Number.POSITIVE_INFINITY) return "1e999";
125
+ if (value === Number.NEGATIVE_INFINITY) return "-1e999";
126
+ const sign = value < 0 || Object.is(value, -0) ? "-" : "";
127
+ const text = String(Math.abs(value));
128
+ return `${sign}${/[.e]/.test(text) ? text : `${text}.0`}`;
129
+ }
130
+
131
+ /**
132
+ * A string literal, always in double quotes.
133
+ *
134
+ * **One spelling, chosen rather than remembered.** The tree records a string's VALUE and not
135
+ * the quote the author typed — the source's own formatting is not structure, which is the
136
+ * same rule that makes the serializer drop redundant parentheses — so a round trip
137
+ * normalizes `'x'` to `"x"`. Both are CEL and the parser reads either.
138
+ *
139
+ * The cost lands on a quick fix, which is written by rewriting the tree and serializing it
140
+ * back **into a YAML scalar**: a double-quoted CEL literal inside a double-quoted scalar has
141
+ * to be escaped, so a repair reads `!cel "a.startsWith(\"x\")"` where the author wrote
142
+ * `'x'`. Choosing the quote here cannot fix that — a single-quoted YAML scalar wants the
143
+ * opposite choice, and this engine does not know which scalar the text will land in. Picking
144
+ * the YAML quoting that needs no escaping is the EDITOR's, which has both halves in hand.
145
+ */
146
+ function writeString(value: string): string {
147
+ const quote = '"';
148
+ let text = quote;
149
+ for (const unit of value) {
150
+ const code = unit.codePointAt(0)!;
151
+ if (unit === "\\") text += "\\\\";
152
+ else if (unit === quote) text += `\\${quote}`;
153
+ else if (unit === "\n") text += "\\n";
154
+ else if (unit === "\r") text += "\\r";
155
+ else if (unit === "\t") text += "\\t";
156
+ else if (code < 0x20 || code === 0x7f) text += `\\x${code.toString(16).padStart(2, "0")}`;
157
+ else text += unit;
158
+ }
159
+ return `${text}${quote}`;
160
+ }
161
+
162
+ function writeBytes(value: Uint8Array): string {
163
+ let text = 'b"';
164
+ for (const byte of value) {
165
+ if (byte === 0x5c) text += "\\\\";
166
+ else if (byte === 0x22) text += '\\"';
167
+ else if (byte >= 0x20 && byte <= 0x7e) text += String.fromCharCode(byte);
168
+ else text += `\\x${byte.toString(16).padStart(2, "0")}`;
169
+ }
170
+ return `${text}"`;
171
+ }
172
+
173
+ function writeLiteral(literal: CelLiteral): string {
174
+ switch (literal.type) {
175
+ case "int":
176
+ if (literal.value < MIN_INT || literal.value > MAX_INT) {
177
+ throw new CelSerializeError(`${literal.value} is outside the range of a 64-bit integer`);
178
+ }
179
+ return `${literal.value}`;
180
+ case "uint":
181
+ if (literal.value < 0n || literal.value > MAX_UINT) {
182
+ throw new CelSerializeError(`${literal.value} is outside the range of an unsigned 64-bit integer`);
183
+ }
184
+ return `${literal.value}u`;
185
+ case "double":
186
+ return writeDouble(literal.value);
187
+ case "string":
188
+ return writeString(literal.value);
189
+ case "bytes":
190
+ return writeBytes(literal.value);
191
+ case "bool":
192
+ return literal.value ? "true" : "false";
193
+ case "null":
194
+ return "null";
195
+ }
196
+ }
197
+
198
+ /** Writes `node` for a slot that binds at least as tightly as `needs`. */
199
+ function write(node: CelNode, needs: number): string {
200
+ const text = writeNode(node);
201
+ return precedenceOf(node) < needs ? `(${text})` : text;
202
+ }
203
+
204
+ function writeNode(node: CelNode): string {
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(
217
+ (entry) =>
218
+ `${entry.optional ? "?" : ""}${write(entry.key, PRECEDENCE.conditional)}: ${write(entry.value, PRECEDENCE.conditional)}`,
219
+ )
220
+ .join(", ")}}`;
221
+ case "select":
222
+ return `${write(node.operand, PRECEDENCE.postfix)}.${node.optional ? "?" : ""}${fieldName(node.field, node.quoted)}`;
223
+ case "index":
224
+ return `${write(node.operand, PRECEDENCE.postfix)}[${node.optional ? "?" : ""}${write(node.index, PRECEDENCE.conditional)}]`;
225
+ case "call":
226
+ return `${name(node.name, "function name")}(${writeArguments(node.args)})`;
227
+ case "receiverCall":
228
+ return `${write(node.receiver, PRECEDENCE.postfix)}.${memberName(node.name, "function name")}(${writeArguments(node.args)})`;
229
+ case "qcall":
230
+ return `${name(node.namespace, "namespace")}.${memberName(node.name, "function name")}(${writeArguments(node.args)})`;
231
+ case "unary":
232
+ return `${node.operator}${writeUnaryOperand(node.operator, node.operand)}`;
233
+ case "binary":
234
+ return `${write(node.left, BINARY_PRECEDENCE[node.operator]!)} ${node.operator} ${write(node.right, BINARY_PRECEDENCE[node.operator]! + 1)}`;
235
+ case "conditional":
236
+ return `${write(node.condition, PRECEDENCE.or)} ? ${write(node.whenTrue, PRECEDENCE.conditional)} : ${write(node.whenFalse, PRECEDENCE.conditional)}`;
237
+ case "unparsed":
238
+ throw new CelSerializeError("an unparsed expression has no source to write");
239
+ }
240
+ }
241
+
242
+ /**
243
+ * A minus directly on a non-negative numeric literal would read back as part of the
244
+ * literal, so it is parenthesized: `-(1)` stays a negation of one.
245
+ */
246
+ function writeUnaryOperand(operator: string, operand: CelNode): string {
247
+ if (operator === "-" && operand.kind === "literal" && !isNegativeNumber(operand.literal)) {
248
+ if (operand.literal.type === "int" || operand.literal.type === "double") {
249
+ return `(${writeLiteral(operand.literal)})`;
250
+ }
251
+ }
252
+ return write(operand, PRECEDENCE.unary);
253
+ }
254
+
255
+ function writeArguments(args: readonly CelNode[]): string {
256
+ return args.map((argument) => write(argument, PRECEDENCE.conditional)).join(", ");
257
+ }
258
+
259
+ /** The CEL source of a tree. Throws on a tree that has none. */
260
+ export function serializeTree(root: CelNode): string {
261
+ return write(root, PRECEDENCE.conditional);
262
+ }
package/src/sha256.ts ADDED
@@ -0,0 +1,112 @@
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
+
17
+ import { textToBytes } from "./value-text.js";
18
+
19
+ /** The first 32 bits of the cube roots of the first 64 primes. */
20
+ const K = new Uint32Array([
21
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
22
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
23
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
24
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
25
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
26
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
27
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
28
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
29
+ ]);
30
+
31
+ const HEX = "0123456789abcdef";
32
+
33
+ function rotateRight(value: number, by: number): number {
34
+ return (value >>> by) | (value << (32 - by));
35
+ }
36
+
37
+ /** The digest of bytes, as 64 lowercase hexadecimal characters. */
38
+ export function sha256Hex(input: Uint8Array): string {
39
+ const length = input.length;
40
+ // The message, a 0x80 byte, zero padding to 56 mod 64, then the bit length as 64 bits.
41
+ const padded = new Uint8Array(((length + 72) >> 6) << 6);
42
+ padded.set(input);
43
+ padded[length] = 0x80;
44
+ const bits = BigInt(length) * 8n;
45
+ for (let at = 0; at < 8; at += 1) {
46
+ padded[padded.length - 1 - at] = Number((bits >> BigInt(8 * at)) & 0xffn);
47
+ }
48
+
49
+ const state = new Uint32Array([
50
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
51
+ ]);
52
+ const schedule = new Uint32Array(64);
53
+
54
+ for (let block = 0; block < padded.length; block += 64) {
55
+ for (let at = 0; at < 16; at += 1) {
56
+ const from = block + at * 4;
57
+ schedule[at] =
58
+ ((padded[from]! << 24) | (padded[from + 1]! << 16) | (padded[from + 2]! << 8) | padded[from + 3]!) >>> 0;
59
+ }
60
+ for (let at = 16; at < 64; at += 1) {
61
+ const a = schedule[at - 15]!;
62
+ const b = schedule[at - 2]!;
63
+ const s0 = rotateRight(a, 7) ^ rotateRight(a, 18) ^ (a >>> 3);
64
+ const s1 = rotateRight(b, 17) ^ rotateRight(b, 19) ^ (b >>> 10);
65
+ schedule[at] = (schedule[at - 16]! + s0 + schedule[at - 7]! + s1) >>> 0;
66
+ }
67
+
68
+ let a = state[0]!;
69
+ let b = state[1]!;
70
+ let c = state[2]!;
71
+ let d = state[3]!;
72
+ let e = state[4]!;
73
+ let f = state[5]!;
74
+ let g = state[6]!;
75
+ let h = state[7]!;
76
+ for (let at = 0; at < 64; at += 1) {
77
+ const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
78
+ const choose = (e & f) ^ (~e & g);
79
+ const one = (h + s1 + choose + K[at]! + schedule[at]!) >>> 0;
80
+ const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
81
+ const majority = (a & b) ^ (a & c) ^ (b & c);
82
+ const two = (s0 + majority) >>> 0;
83
+ h = g;
84
+ g = f;
85
+ f = e;
86
+ e = (d + one) >>> 0;
87
+ d = c;
88
+ c = b;
89
+ b = a;
90
+ a = (one + two) >>> 0;
91
+ }
92
+ state[0] = (state[0]! + a) >>> 0;
93
+ state[1] = (state[1]! + b) >>> 0;
94
+ state[2] = (state[2]! + c) >>> 0;
95
+ state[3] = (state[3]! + d) >>> 0;
96
+ state[4] = (state[4]! + e) >>> 0;
97
+ state[5] = (state[5]! + f) >>> 0;
98
+ state[6] = (state[6]! + g) >>> 0;
99
+ state[7] = (state[7]! + h) >>> 0;
100
+ }
101
+
102
+ let out = "";
103
+ for (const word of state) {
104
+ for (let shift = 28; shift >= 0; shift -= 4) out += HEX[(word >>> shift) & 0xf];
105
+ }
106
+ return out;
107
+ }
108
+
109
+ /** The digest of text, read as UTF-8 — what every key and digest in the emitter is over. */
110
+ export function sha256OfText(text: string): string {
111
+ return sha256Hex(textToBytes(text));
112
+ }
@@ -0,0 +1,127 @@
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
+
16
+ import type { CelType } from "./cel-type.js";
17
+ import type { CelValue } from "./cel-value.js";
18
+ import { formatType } from "./cel-type.js";
19
+ import type { CelImplementation } from "./runtime-library.js";
20
+ import type { NominalResolver } from "./type-expression.js";
21
+ import { CelTypeExpressionError, parseTypeExpression } from "./type-expression.js";
22
+
23
+ export type CallForm = "global" | "receiver";
24
+
25
+ export interface CelSignature {
26
+ readonly name: string;
27
+ readonly form: CallForm;
28
+ /** The type the call is written on, for a receiver call. */
29
+ readonly receiver?: CelType;
30
+ readonly parameters: readonly CelType[];
31
+ readonly returns: CelType;
32
+ }
33
+
34
+ const SIGNATURE = /^(?:(.+)\.)?([A-Za-z_][A-Za-z0-9_]*)\(([^)]*)\)\s*:\s*(.+)$/;
35
+
36
+ export function parseSignature(text: string, resolveNominal?: NominalResolver): CelSignature {
37
+ const parts = SIGNATURE.exec(text.trim());
38
+ if (!parts) {
39
+ throw new CelTypeExpressionError(
40
+ `${JSON.stringify(text)} is not a signature — write 'name(a, b): r' or 'Receiver.name(a): r'`,
41
+ );
42
+ }
43
+ const [, receiverText, name, parameterText, returnText] = parts;
44
+ const parameters = splitParameters(parameterText!).map((part) =>
45
+ parseTypeExpression(part, resolveNominal),
46
+ );
47
+ return {
48
+ name: name!,
49
+ form: receiverText === undefined ? "global" : "receiver",
50
+ ...(receiverText === undefined ? {} : { receiver: parseTypeExpression(receiverText, resolveNominal) }),
51
+ parameters,
52
+ returns: parseTypeExpression(returnText!, resolveNominal),
53
+ };
54
+ }
55
+
56
+ /** The parameter list, split at its own commas — `map<K, V>` holds one of its own. */
57
+ function splitParameters(text: string): string[] {
58
+ if (!text.trim()) return [];
59
+ const parts: string[] = [];
60
+ let depth = 0;
61
+ let from = 0;
62
+ for (let at = 0; at < text.length; at += 1) {
63
+ const ch = text[at];
64
+ if (ch === "<") depth += 1;
65
+ else if (ch === ">") depth -= 1;
66
+ else if (ch === "," && depth === 0) {
67
+ parts.push(text.slice(from, at));
68
+ from = at + 1;
69
+ }
70
+ }
71
+ parts.push(text.slice(from));
72
+ return parts;
73
+ }
74
+
75
+ export function formatSignature(signature: CelSignature): string {
76
+ return `${signatureKey(signature)}: ${formatType(signature.returns)}`;
77
+ }
78
+
79
+ /** What two registrations of the same call share: the name, the form and the parameters. */
80
+ export function signatureKey(signature: CelSignature): string {
81
+ const on = signature.receiver ? `${formatType(signature.receiver)}.` : "";
82
+ return `${on}${signature.name}(${signature.parameters.map((type) => formatType(type)).join(", ")})`;
83
+ }
84
+
85
+ /**
86
+ * Checking the arguments a call wrote as LITERALS, where a type cannot say enough.
87
+ *
88
+ * A signature constrains types, so a guard over a VALUE — an unparseable format
89
+ * specifier, a decimal count out of range, an unknown time zone, a pattern the regular
90
+ * expression engine refuses — would otherwise fire only when the expression is evaluated,
91
+ * which puts a defect the source states in plain sight behind a run. A registration that
92
+ * carries one is asked at the call site the checker has just resolved, and a refusal is
93
+ * `CEL_INVALID_ARGUMENT` with the call's own range.
94
+ *
95
+ * `literals[at]` is the value of argument `at` where it was written as a literal — the
96
+ * receiver first, for a call written on a value, so the positions are the ones the
97
+ * implementation sees — and `undefined` where the argument is an expression whose value
98
+ * is not statically known, which a guard must SKIP rather than judge. The answer is the
99
+ * refusal, or nothing where there is none. A guard runs the same code the evaluation
100
+ * runs, so the static and dynamic answers cannot drift into disagreement.
101
+ */
102
+ export type LiteralArgumentCheck = (
103
+ literals: readonly (CelValue | undefined)[],
104
+ ) => string | undefined;
105
+
106
+ /** What a host declares about a function beyond its types. */
107
+ export interface FunctionMetadata {
108
+ /**
109
+ * What the function does. The standard library's behaviour is looked up by dispatch
110
+ * key instead (`runtime-library.ts`), so a registration carries one only where the
111
+ * host supplies it; a call that resolves to a registration with neither is
112
+ * `unbound_function` at evaluation. The type is not thenable, so an implementation
113
+ * that answers asynchronously does not compile.
114
+ */
115
+ readonly implementation?: CelImplementation;
116
+ /** A guard over the arguments written as literals, asked where the checker resolves the call. */
117
+ readonly checkArguments?: LiteralArgumentCheck;
118
+ /** Whether two calls with the same arguments answer the same thing. Default true. */
119
+ readonly deterministic?: boolean;
120
+ /** Whether the implementation is supplied by the host rather than the engine. */
121
+ readonly hostBacked?: boolean;
122
+ /** The error codes a call may fail with. */
123
+ readonly throws?: readonly string[];
124
+ readonly description?: string;
125
+ /** Where the registration came from, for a listing a human reads. */
126
+ readonly origin?: string;
127
+ }