@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.
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 +146 -0
  7. package/dist/backend-runtime.d.ts.map +1 -0
  8. package/dist/backend-runtime.js +322 -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 +785 -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 +34 -0
  19. package/dist/cel-map-value.d.ts.map +1 -0
  20. package/dist/cel-map-value.js +74 -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 +159 -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 +53 -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 +77 -0
  34. package/dist/checker.d.ts.map +1 -0
  35. package/dist/checker.js +721 -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 +436 -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 +59 -0
  49. package/dist/duration-value.d.ts.map +1 -0
  50. package/dist/duration-value.js +135 -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 +283 -0
  61. package/dist/environment.d.ts.map +1 -0
  62. package/dist/environment.js +459 -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 +78 -0
  67. package/dist/function-registry.d.ts.map +1 -0
  68. package/dist/function-registry.js +189 -0
  69. package/dist/index.d.ts +91 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +59 -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 +132 -0
  76. package/dist/js-emitter.d.ts.map +1 -0
  77. package/dist/js-emitter.js +562 -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 +33 -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 +52 -0
  94. package/dist/member-read.d.ts.map +1 -0
  95. package/dist/member-read.js +125 -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 +33 -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 +56 -0
  127. package/dist/runtime-library.d.ts.map +1 -0
  128. package/dist/runtime-library.js +545 -0
  129. package/dist/serializer.d.ts +24 -0
  130. package/dist/serializer.d.ts.map +1 -0
  131. package/dist/serializer.js +240 -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 +53 -0
  150. package/dist/timestamp-value.d.ts.map +1 -0
  151. package/dist/timestamp-value.js +223 -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 +26 -0
  156. package/dist/type-expression.d.ts.map +1 -0
  157. package/dist/type-expression.js +134 -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 +454 -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 +86 -0
  174. package/src/cel-program.ts +103 -0
  175. package/src/cel-type.ts +359 -0
  176. package/src/cel-value.ts +353 -0
  177. package/src/check-diagnostic.ts +102 -0
  178. package/src/checker.ts +932 -0
  179. package/src/closure-backend.ts +502 -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 +145 -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 +740 -0
  188. package/src/function-catalog.ts +140 -0
  189. package/src/function-registry.ts +229 -0
  190. package/src/index.ts +386 -0
  191. package/src/integer-arithmetic.ts +64 -0
  192. package/src/js-emitter.ts +713 -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 +191 -0
  197. package/src/macro-shape.ts +66 -0
  198. package/src/member-read.ts +126 -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 +34 -0
  208. package/src/root-references.ts +126 -0
  209. package/src/runtime-library.ts +615 -0
  210. package/src/serializer.ts +246 -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 +294 -0
  219. package/src/tree-equality.ts +130 -0
  220. package/src/type-expression.ts +160 -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
package/dist/lexer.js ADDED
@@ -0,0 +1,458 @@
1
+ /**
2
+ * CEL's tokens.
3
+ *
4
+ * The lexer decodes literals as it reads them — a string token carries its text
5
+ * with every escape resolved, a bytes token its bytes, an integer its magnitude as
6
+ * a `bigint` — so nothing downstream re-reads source text to learn a value.
7
+ *
8
+ * It stops at the first thing it cannot read, reporting one diagnostic and ending
9
+ * the token stream there. The parser then builds whatever the tokens before it
10
+ * support, which is what leaves a half-typed expression usable.
11
+ *
12
+ * Three readings are deliberate, each pinned by the conformance vectors:
13
+ *
14
+ * - **Only a single-letter prefix** introduces a string: `r`, `R`, `b`, `B`. `br'x'`
15
+ * is the identifier `br` followed by a string, which no expression admits.
16
+ * - **A raw string still lets a backslash take the next character with it**, keeping
17
+ * both as written, so `r'\''` is a backslash and a quote rather than an unclosed
18
+ * string.
19
+ * - **A bytes literal holds the UTF-8 of its text.** `b'ÿ'` is `0xC3 0xBF`, two bytes,
20
+ * and an escape (`\xff`, `\303`) is the one byte it names. cel-spec's own answer, and
21
+ * the only reading under which `b'ÿ' == b'\303\277'` — a row — holds.
22
+ *
23
+ * A number never begins with `.`: `.5` is a dot and a number, and no expression
24
+ * admits that either.
25
+ */
26
+ import { wordReading } from "./reserved-words.js";
27
+ import { FirstSyntaxDiagnostic } from "./syntax-diagnostic.js";
28
+ export const MAX_INT = 9223372036854775807n;
29
+ export const MIN_INT = -9223372036854775808n;
30
+ export const MAX_UINT = 18446744073709551615n;
31
+ const PUNCTUATION = new Set(["(", ")", "[", "]", "{", "}", ".", ",", ":", "?", "!", "+", "-", "*", "/", "%"]);
32
+ const SIMPLE_ESCAPES = new Map([
33
+ ["\\", 0x5c],
34
+ ["'", 0x27],
35
+ ['"', 0x22],
36
+ ["`", 0x60],
37
+ ["?", 0x3f],
38
+ ["a", 0x07],
39
+ ["b", 0x08],
40
+ ["f", 0x0c],
41
+ ["n", 0x0a],
42
+ ["r", 0x0d],
43
+ ["t", 0x09],
44
+ ["v", 0x0b],
45
+ ]);
46
+ function isDigit(ch) {
47
+ return ch >= "0" && ch <= "9";
48
+ }
49
+ function isHexDigit(ch) {
50
+ return isDigit(ch) || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F");
51
+ }
52
+ function isOctalDigit(ch) {
53
+ return ch >= "0" && ch <= "7";
54
+ }
55
+ function isIdentStart(ch) {
56
+ return ch === "_" || (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
57
+ }
58
+ function isIdentPart(ch) {
59
+ return isIdentStart(ch) || isDigit(ch);
60
+ }
61
+ /**
62
+ * What a word before a quote makes of the literal.
63
+ *
64
+ * cel-spec nests the two markers — `BYTES_LIT: [bB] STRING_LIT` over
65
+ * `STRING_LIT: [rR]? (…)` — so the bytes marker comes first and `rb'…'` is not a
66
+ * literal at all: it is the name `rb` beside a string, which no expression admits.
67
+ */
68
+ function stringPrefix(text) {
69
+ const lower = text.toLowerCase();
70
+ if (lower === "r")
71
+ return { raw: true, bytes: false };
72
+ if (lower === "b")
73
+ return { raw: false, bytes: true };
74
+ if (lower === "br")
75
+ return { raw: true, bytes: true };
76
+ return undefined;
77
+ }
78
+ function isSpace(ch) {
79
+ return ch === " " || ch === "\t" || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
80
+ }
81
+ export class Lexer {
82
+ source;
83
+ diagnostics;
84
+ at = 0;
85
+ constructor(source, diagnostics) {
86
+ this.source = source;
87
+ this.diagnostics = diagnostics;
88
+ }
89
+ /** Every token up to the end of the source, or up to the first unreadable text. */
90
+ tokenize() {
91
+ const tokens = [];
92
+ for (;;) {
93
+ this.skipIgnored();
94
+ if (this.at >= this.source.length) {
95
+ tokens.push(this.eof());
96
+ return tokens;
97
+ }
98
+ const token = this.next();
99
+ if (!token) {
100
+ tokens.push(this.eof());
101
+ return tokens;
102
+ }
103
+ tokens.push(token);
104
+ }
105
+ }
106
+ eof() {
107
+ return { type: "eof", start: this.at, end: this.at, text: "" };
108
+ }
109
+ skipIgnored() {
110
+ while (this.at < this.source.length) {
111
+ const ch = this.source[this.at];
112
+ if (isSpace(ch)) {
113
+ this.at += 1;
114
+ continue;
115
+ }
116
+ if (ch === "/" && this.source[this.at + 1] === "/") {
117
+ while (this.at < this.source.length && this.source[this.at] !== "\n")
118
+ this.at += 1;
119
+ continue;
120
+ }
121
+ return;
122
+ }
123
+ }
124
+ next() {
125
+ const start = this.at;
126
+ const ch = this.source[start];
127
+ if (ch === "'" || ch === '"')
128
+ return this.readQuoted(start, start, false, false);
129
+ if (isIdentStart(ch))
130
+ return this.readWord(start);
131
+ if (isDigit(ch))
132
+ return this.readNumber(start);
133
+ // A double may begin with its point: cel-spec's FLOAT_LIT is `DIGIT* . DIGIT+`.
134
+ if (ch === "." && isDigit(this.source[start + 1] ?? ""))
135
+ return this.readNumber(start);
136
+ if (ch === "`")
137
+ return this.readQuotedIdentifier(start);
138
+ return this.readPunctuation(start, ch);
139
+ }
140
+ readPunctuation(start, ch) {
141
+ const pair = this.source.slice(start, start + 2);
142
+ if (pair === "==" || pair === "!=" || pair === "<=" || pair === ">=" || pair === "&&" || pair === "||") {
143
+ this.at = start + 2;
144
+ return { type: "punct", start, end: this.at, text: pair };
145
+ }
146
+ if (ch === "<" || ch === ">" || PUNCTUATION.has(ch)) {
147
+ this.at = start + 1;
148
+ return { type: "punct", start, end: this.at, text: ch };
149
+ }
150
+ this.diagnostics.report("unexpected_character", `unexpected character ${JSON.stringify(ch)}`, start, start + 1);
151
+ this.at = start;
152
+ return undefined;
153
+ }
154
+ /**
155
+ * A member name between backticks. cel-spec's `ESCAPED_IDENTIFIER` takes no escapes
156
+ * and cannot hold a backtick, so the text between them is the name as written.
157
+ */
158
+ readQuotedIdentifier(start) {
159
+ const close = this.source.indexOf("`", start + 1);
160
+ if (close === -1 || this.source.slice(start + 1, close).includes("\n")) {
161
+ this.diagnostics.report("unterminated_string", "a quoted member name ends at its closing backtick", start, close === -1 ? this.source.length : close);
162
+ return undefined;
163
+ }
164
+ this.at = close + 1;
165
+ return { type: "quotedIdent", start, end: this.at, text: this.source.slice(start + 1, close) };
166
+ }
167
+ /** An identifier, a literal word, or the prefix of a string literal. */
168
+ readWord(start) {
169
+ let at = start;
170
+ while (at < this.source.length && isIdentPart(this.source[at]))
171
+ at += 1;
172
+ const text = this.source.slice(start, at);
173
+ const prefix = stringPrefix(text);
174
+ if (prefix && (this.source[at] === "'" || this.source[at] === '"')) {
175
+ return this.readQuoted(start, at, prefix.raw, prefix.bytes);
176
+ }
177
+ this.at = at;
178
+ const reading = wordReading(text);
179
+ const type = reading === "operator" ? "keyword" : reading === "refused" ? "reserved" : "ident";
180
+ return { type, start, end: at, text };
181
+ }
182
+ readNumber(start) {
183
+ const source = this.source;
184
+ let at = start;
185
+ let kind = "int";
186
+ let digits;
187
+ let radix = 10;
188
+ if (source[at] === "0" && (source[at + 1] === "x" || source[at + 1] === "X")) {
189
+ at += 2;
190
+ const from = at;
191
+ while (at < source.length && isHexDigit(source[at]))
192
+ at += 1;
193
+ if (at === from)
194
+ return this.invalidNumber(start, at);
195
+ digits = source.slice(from, at);
196
+ radix = 16;
197
+ }
198
+ else {
199
+ const from = at;
200
+ while (at < source.length && isDigit(source[at]))
201
+ at += 1;
202
+ if (source[at] === "." && isDigit(source[at + 1] ?? "")) {
203
+ kind = "double";
204
+ at += 1;
205
+ while (at < source.length && isDigit(source[at]))
206
+ at += 1;
207
+ }
208
+ if (source[at] === "e" || source[at] === "E") {
209
+ let exponent = at + 1;
210
+ if (source[exponent] === "+" || source[exponent] === "-")
211
+ exponent += 1;
212
+ if (isDigit(source[exponent] ?? "")) {
213
+ kind = "double";
214
+ at = exponent;
215
+ while (at < source.length && isDigit(source[at]))
216
+ at += 1;
217
+ }
218
+ }
219
+ digits = source.slice(from, at);
220
+ }
221
+ const unsigned = source[at] === "u" || source[at] === "U";
222
+ if (unsigned)
223
+ at += 1;
224
+ if (at < source.length && isIdentPart(source[at]))
225
+ return this.invalidNumber(start, at);
226
+ this.at = at;
227
+ if (kind === "double") {
228
+ if (unsigned)
229
+ return this.invalidNumber(start, at);
230
+ return { type: "double", start, end: at, text: source.slice(start, at), double: Number(digits) };
231
+ }
232
+ const magnitude = radix === 16 ? BigInt(`0x${digits}`) : BigInt(digits);
233
+ if (unsigned) {
234
+ if (magnitude > MAX_UINT) {
235
+ this.diagnostics.report("invalid_unsigned_integer", `${source.slice(start, at)} is outside the range of an unsigned 64-bit integer`, start, at);
236
+ return undefined;
237
+ }
238
+ return { type: "uint", start, end: at, text: source.slice(start, at), int: magnitude };
239
+ }
240
+ if (magnitude > -MIN_INT) {
241
+ this.diagnostics.report("invalid_integer", `${source.slice(start, at)} is outside the range of a 64-bit integer`, start, at);
242
+ return undefined;
243
+ }
244
+ return {
245
+ type: "int",
246
+ start,
247
+ end: at,
248
+ text: source.slice(start, at),
249
+ int: magnitude,
250
+ ...(magnitude === -MIN_INT ? { atIntBoundary: true } : {}),
251
+ };
252
+ }
253
+ invalidNumber(start, at) {
254
+ let end = at;
255
+ while (end < this.source.length && isIdentPart(this.source[end]))
256
+ end += 1;
257
+ this.diagnostics.report("invalid_number", `${this.source.slice(start, end)} is not a number`, start, end);
258
+ return undefined;
259
+ }
260
+ /**
261
+ * A string or bytes literal. `start` is the literal's own start (its prefix, when
262
+ * it has one) and `quoteAt` the opening quote.
263
+ */
264
+ readQuoted(start, quoteAt, raw, bytes) {
265
+ const quote = this.source[quoteAt];
266
+ const triple = this.source.slice(quoteAt, quoteAt + 3) === quote.repeat(3);
267
+ const terminator = triple ? quote.repeat(3) : quote;
268
+ const decoded = raw
269
+ ? this.scanRaw(quoteAt + terminator.length, terminator, triple, bytes)
270
+ : this.scanEscaped(quoteAt + terminator.length, terminator, triple, bytes);
271
+ if (!decoded)
272
+ return undefined;
273
+ this.at = decoded.end;
274
+ const text = this.source.slice(start, decoded.end);
275
+ if (bytes) {
276
+ return { type: "bytes", start, end: decoded.end, text, bytes: Uint8Array.from(decoded.units) };
277
+ }
278
+ return { type: "string", start, end: decoded.end, text, string: textOf(decoded.units) };
279
+ }
280
+ unterminated(start, at) {
281
+ this.diagnostics.report("unterminated_string", "unterminated string", start, at);
282
+ return undefined;
283
+ }
284
+ /** A raw literal: a backslash takes the next character with it, both kept as written. */
285
+ scanRaw(from, terminator, triple, bytes) {
286
+ const source = this.source;
287
+ const units = [];
288
+ let at = from;
289
+ while (at < source.length) {
290
+ if (source.startsWith(terminator, at))
291
+ return { units, end: at + terminator.length };
292
+ const ch = source[at];
293
+ if (!triple && ch === "\n")
294
+ break;
295
+ if (ch === "\\" && at + 1 < source.length) {
296
+ units.push(0x5c);
297
+ at += 1 + this.literalCharacter(at + 1, bytes, units);
298
+ continue;
299
+ }
300
+ at += this.literalCharacter(at, bytes, units);
301
+ }
302
+ return this.unterminated(from - terminator.length, at);
303
+ }
304
+ /**
305
+ * One character of a literal's text, as the literal holds it: a code unit in a string,
306
+ * the character's UTF-8 bytes in a bytes literal. Answers how many code units it read,
307
+ * since a character outside the basic plane is written as a surrogate pair.
308
+ */
309
+ literalCharacter(at, bytes, units) {
310
+ if (!bytes) {
311
+ units.push(this.source.charCodeAt(at));
312
+ return 1;
313
+ }
314
+ const point = this.source.codePointAt(at);
315
+ pushUtf8(units, point);
316
+ return point > 0xffff ? 2 : 1;
317
+ }
318
+ scanEscaped(from, terminator, triple, bytes) {
319
+ const source = this.source;
320
+ const units = [];
321
+ let at = from;
322
+ while (at < source.length) {
323
+ if (source.startsWith(terminator, at))
324
+ return { units, end: at + terminator.length };
325
+ const ch = source[at];
326
+ if (!triple && ch === "\n")
327
+ break;
328
+ if (ch !== "\\") {
329
+ at += this.literalCharacter(at, bytes, units);
330
+ continue;
331
+ }
332
+ const next = this.readEscape(at, bytes, units);
333
+ if (next === undefined)
334
+ return undefined;
335
+ at = next;
336
+ }
337
+ return this.unterminated(from - terminator.length, at);
338
+ }
339
+ /** Decodes one escape sequence onto `units`, answering where it ends. */
340
+ readEscape(at, bytes, units) {
341
+ const source = this.source;
342
+ const ch = source[at + 1];
343
+ if (ch === undefined) {
344
+ this.diagnostics.report("invalid_escape_sequence", "the escape has no character", at, at + 1);
345
+ return undefined;
346
+ }
347
+ const simple = SIMPLE_ESCAPES.get(ch);
348
+ if (simple !== undefined) {
349
+ units.push(simple);
350
+ return at + 2;
351
+ }
352
+ if (ch === "x" || ch === "X")
353
+ return this.readHexEscape(at, units);
354
+ if (ch === "u" || ch === "U") {
355
+ if (bytes) {
356
+ this.diagnostics.report("bytes_unicode_escape", `\\${ch} names text, which a bytes literal cannot hold — write the bytes with \\x`, at, at + 2);
357
+ return undefined;
358
+ }
359
+ return this.readUnicodeEscape(at, ch === "u" ? 4 : 8, units);
360
+ }
361
+ if (isOctalDigit(ch))
362
+ return this.readOctalEscape(at, units);
363
+ this.diagnostics.report("invalid_escape_sequence", `\\${ch} is not an escape sequence`, at, at + 2);
364
+ return undefined;
365
+ }
366
+ readHexEscape(at, units) {
367
+ const digits = this.source.slice(at + 2, at + 4);
368
+ if (digits.length < 2 || !isHexDigit(digits[0]) || !isHexDigit(digits[1])) {
369
+ this.diagnostics.report("invalid_hex_escape", "a \\x escape takes two hexadecimal digits", at, at + 4);
370
+ return undefined;
371
+ }
372
+ units.push(Number.parseInt(digits, 16));
373
+ return at + 4;
374
+ }
375
+ readOctalEscape(at, units) {
376
+ const digits = this.source.slice(at + 1, at + 4);
377
+ if (digits.length < 3 || ![...digits].every(isOctalDigit)) {
378
+ this.diagnostics.report("invalid_octal_escape", "an octal escape takes three octal digits", at, at + 4);
379
+ return undefined;
380
+ }
381
+ const value = Number.parseInt(digits, 8);
382
+ if (value > 0xff) {
383
+ this.diagnostics.report("octal_escape_out_of_range", `\\${digits} is above 255`, at, at + 4);
384
+ return undefined;
385
+ }
386
+ units.push(value);
387
+ return at + 4;
388
+ }
389
+ readUnicodeEscape(at, width, units) {
390
+ const digits = this.source.slice(at + 2, at + 2 + width);
391
+ if (digits.length < width || ![...digits].every(isHexDigit)) {
392
+ this.diagnostics.report("invalid_unicode_escape", `a \\${width === 4 ? "u" : "U"} escape takes ${width} hexadecimal digits`, at, at + 2 + width);
393
+ return undefined;
394
+ }
395
+ const point = Number.parseInt(digits, 16);
396
+ const end = at + 2 + width;
397
+ if (point > 0x10ffff) {
398
+ this.diagnostics.report("invalid_unicode_escape", `U+${digits} is not a code point`, at, end);
399
+ return undefined;
400
+ }
401
+ if (point >= 0xdc00 && point <= 0xdfff) {
402
+ this.diagnostics.report("invalid_unicode_surrogate", `U+${digits} is a trailing surrogate`, at, end);
403
+ return undefined;
404
+ }
405
+ if (point >= 0xd800 && point <= 0xdbff)
406
+ return this.readSurrogatePair(at, end, point, units);
407
+ if (point > 0xffff) {
408
+ units.push(0xd800 + ((point - 0x10000) >> 10), 0xdc00 + ((point - 0x10000) & 0x3ff));
409
+ return end;
410
+ }
411
+ units.push(point);
412
+ return end;
413
+ }
414
+ /** A leading surrogate stands only beside the trailing one that completes it. */
415
+ readSurrogatePair(at, end, lead, units) {
416
+ const follows = this.source.slice(end, end + 6);
417
+ const trail = /^\\u([0-9a-fA-F]{4})$/.exec(follows);
418
+ const point = trail ? Number.parseInt(trail[1], 16) : 0;
419
+ if (!trail || point < 0xdc00 || point > 0xdfff) {
420
+ this.diagnostics.report("invalid_unicode_surrogate", "a leading surrogate must be followed by a trailing surrogate escape", at, end);
421
+ return undefined;
422
+ }
423
+ units.push(lead, point);
424
+ return end + 6;
425
+ }
426
+ }
427
+ /** The UTF-8 bytes of one code point, which is what a bytes literal holds of its text. */
428
+ function pushUtf8(units, point) {
429
+ if (point < 0x80) {
430
+ units.push(point);
431
+ return;
432
+ }
433
+ if (point < 0x800) {
434
+ units.push(0xc0 | (point >> 6), 0x80 | (point & 0x3f));
435
+ return;
436
+ }
437
+ if (point < 0x10000) {
438
+ units.push(0xe0 | (point >> 12), 0x80 | ((point >> 6) & 0x3f), 0x80 | (point & 0x3f));
439
+ return;
440
+ }
441
+ units.push(0xf0 | (point >> 18), 0x80 | ((point >> 12) & 0x3f), 0x80 | ((point >> 6) & 0x3f), 0x80 | (point & 0x3f));
442
+ }
443
+ /** Code units to text, in chunks so that a long literal does not exhaust the stack. */
444
+ function textOf(units) {
445
+ if (units.length <= 4096)
446
+ return String.fromCharCode(...units);
447
+ let text = "";
448
+ for (let at = 0; at < units.length; at += 4096) {
449
+ text += String.fromCharCode(...units.slice(at, at + 4096));
450
+ }
451
+ return text;
452
+ }
453
+ /** Every token of the source, with at most one diagnostic for where it stopped. */
454
+ export function tokenize(source) {
455
+ const diagnostics = new FirstSyntaxDiagnostic();
456
+ const tokens = new Lexer(source, diagnostics).tokenize();
457
+ return { tokens, diagnostics };
458
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Typing the macros — the constructs that are written as calls and are not functions.
3
+ *
4
+ * A macro binds a name, or inspects the shape of its argument, so it cannot be a
5
+ * registered signature: `xs.map(i, i + 1)` has no argument type for `i`, and `has(a.b)`
6
+ * is about whether `b` is there rather than about its value. The parser deliberately
7
+ * leaves each one an ordinary call node (expanding it would make the source unwritable
8
+ * from the tree), so this is where the comprehension appears — in the checker's own
9
+ * lowering, which is also why a macro is **not** a dispatched call in the call listing.
10
+ *
11
+ * Which arguments a macro binds a name over is declared once, in
12
+ * `comprehension-bindings.ts`, and read both here and by the free-variable query. Two
13
+ * tables would be two answers to "what does this macro bind".
14
+ */
15
+ import type { CelType } from "./cel-type.js";
16
+ import type { CelCheckCode } from "./check-diagnostic.js";
17
+ import type { CelNode, SourceRange } from "./syntax-tree.js";
18
+ /** What a macro needs of the checker around it. */
19
+ export interface MacroHost {
20
+ /** The type of a subexpression, in the scope that holds here. */
21
+ typeOf(node: CelNode): CelType;
22
+ /** The type of a subexpression with extra names in scope. */
23
+ typeOfBinding(node: CelNode, bindings: ReadonlyMap<string, CelType>): CelType;
24
+ report(code: CelCheckCode, message: string, range: SourceRange): void;
25
+ }
26
+ /** Whether a call is a macro at all — asked before any overload is looked for. */
27
+ export declare function isMacroCall(node: CelNode): boolean;
28
+ /**
29
+ * The type of a macro call. `isMacroCall` has already said it is one; anything this
30
+ * cannot type reports its own diagnostic and answers `dyn`, so one mistake stays one.
31
+ */
32
+ export declare function checkMacro(node: CelNode, host: MacroHost): CelType;
33
+ //# sourceMappingURL=macro-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macro-check.d.ts","sourceRoot":"","sources":["../src/macro-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE7D,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,iEAAiE;IACjE,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;IAC/B,6DAA6D;IAC7D,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;IAC9E,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;CACvE;AAWD,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAQlD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAalE"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Typing the macros — the constructs that are written as calls and are not functions.
3
+ *
4
+ * A macro binds a name, or inspects the shape of its argument, so it cannot be a
5
+ * registered signature: `xs.map(i, i + 1)` has no argument type for `i`, and `has(a.b)`
6
+ * is about whether `b` is there rather than about its value. The parser deliberately
7
+ * leaves each one an ordinary call node (expanding it would make the source unwritable
8
+ * from the tree), so this is where the comprehension appears — in the checker's own
9
+ * lowering, which is also why a macro is **not** a dispatched call in the call listing.
10
+ *
11
+ * Which arguments a macro binds a name over is declared once, in
12
+ * `comprehension-bindings.ts`, and read both here and by the free-variable query. Two
13
+ * tables would be two answers to "what does this macro bind".
14
+ */
15
+ import { BOOL, DYN, formatType, isDyn, listOf, optionalOf, parameterOf } from "./cel-type.js";
16
+ import { namespaceMacroBinding, receiverMacroBinding } from "./comprehension-bindings.js";
17
+ /** The reserved namespaces a macro is written on, and the macros on each. */
18
+ const NAMESPACE_MACROS = {
19
+ cel: ["bind"],
20
+ optional: ["of", "none", "ofNonZeroValue"],
21
+ };
22
+ /** The optional library's two name-binding members, which no signature can state. */
23
+ const OPTIONAL_BINDING_MACROS = new Set(["optMap", "optFlatMap"]);
24
+ /** Whether a call is a macro at all — asked before any overload is looked for. */
25
+ export function isMacroCall(node) {
26
+ if (node.kind === "call")
27
+ return node.name === "has" && node.args.length === 1;
28
+ if (node.kind !== "receiverCall")
29
+ return false;
30
+ if (receiverMacroBinding(node.name, node.args.length))
31
+ return true;
32
+ const receiver = node.receiver;
33
+ if (receiver.kind !== "ident")
34
+ return false;
35
+ const on = NAMESPACE_MACROS[receiver.name];
36
+ return on !== undefined && on.includes(node.name);
37
+ }
38
+ /**
39
+ * The type of a macro call. `isMacroCall` has already said it is one; anything this
40
+ * cannot type reports its own diagnostic and answers `dyn`, so one mistake stays one.
41
+ */
42
+ export function checkMacro(node, host) {
43
+ if (node.kind === "call")
44
+ return checkHas(node, host);
45
+ if (node.kind !== "receiverCall")
46
+ return DYN;
47
+ const receiver = node.receiver;
48
+ if (receiver.kind === "ident" && NAMESPACE_MACROS[receiver.name]) {
49
+ return receiver.name === "cel"
50
+ ? checkBind(node, host)
51
+ : checkOptionalNamespace(node, host);
52
+ }
53
+ if (OPTIONAL_BINDING_MACROS.has(node.name) && node.args.length === 2) {
54
+ return checkOptionalBinding(node, host);
55
+ }
56
+ return checkComprehension(node, host);
57
+ }
58
+ /**
59
+ * `opt.optMap(v, expr)` and `opt.optFlatMap(v, expr)`: the held value under a name, for
60
+ * one expression. `optMap` wraps what that expression answers; `optFlatMap` takes an
61
+ * optional from it and does not wrap it again.
62
+ */
63
+ function checkOptionalBinding(node, host) {
64
+ const receiver = host.typeOf(node.receiver);
65
+ const name = node.args[0];
66
+ if (name.kind !== "ident")
67
+ return DYN;
68
+ let held = DYN;
69
+ if (receiver.kind === "optional")
70
+ held = receiver.value;
71
+ else if (!isDyn(receiver) && receiver.kind !== "parameter") {
72
+ host.report("CEL_TYPE_ERROR", `${node.name} reads an optional, and ${formatType(receiver)} is not one`, node.receiver.range);
73
+ }
74
+ const result = host.typeOfBinding(node.args[1], new Map([[name.name, held]]));
75
+ if (node.name === "optMap")
76
+ return optionalOf(result);
77
+ if (result.kind === "optional" || isDyn(result) || result.kind === "parameter")
78
+ return result;
79
+ host.report("CEL_TYPE_ERROR", `optFlatMap's expression must answer an optional, and it answers ${formatType(result)}`, node.args[1].range);
80
+ return optionalOf(DYN);
81
+ }
82
+ /** `has(a.b)` — a question about presence. Its shape is already validated. */
83
+ function checkHas(node, host) {
84
+ const argument = node.args[0];
85
+ if (argument.kind === "select")
86
+ host.typeOf(argument);
87
+ return BOOL;
88
+ }
89
+ /** `cel.bind(name, value, body)` — a name for one value, in scope in the body alone. */
90
+ function checkBind(node, host) {
91
+ const binding = namespaceMacroBinding("cel", node.name, node.args.length);
92
+ if (!binding) {
93
+ host.report("CEL_INVALID_ARGUMENT", "cel.bind(name, value, body) takes three arguments: a name, its value, and the expression that reads it", node.range);
94
+ return DYN;
95
+ }
96
+ const name = node.args[binding.variableArgument];
97
+ if (name.kind !== "ident")
98
+ return DYN;
99
+ const value = host.typeOf(node.args[1]);
100
+ return host.typeOfBinding(node.args[2], new Map([[name.name, value]]));
101
+ }
102
+ /** `optional.of(v)`, `optional.ofNonZeroValue(v)` and `optional.none()`. */
103
+ function checkOptionalNamespace(node, host) {
104
+ if (node.name === "none") {
105
+ if (node.args.length !== 0) {
106
+ host.report("CEL_INVALID_ARGUMENT", "optional.none() takes no argument", node.range);
107
+ }
108
+ return optionalOf(parameterOf("T"));
109
+ }
110
+ if (node.args.length !== 1) {
111
+ host.report("CEL_INVALID_ARGUMENT", `optional.${node.name}(value) takes one argument`, node.range);
112
+ return optionalOf(DYN);
113
+ }
114
+ return optionalOf(host.typeOf(node.args[0]));
115
+ }
116
+ /** `all`, `exists`, `exists_one`, `filter` and `map` over a list or a map. */
117
+ function checkComprehension(node, host) {
118
+ const binding = receiverMacroBinding(node.name, node.args.length);
119
+ const receiver = host.typeOf(node.receiver);
120
+ const element = iterationType(receiver, node, host);
121
+ const name = node.args[binding.variableArgument];
122
+ if (name.kind !== "ident")
123
+ return DYN;
124
+ const bindings = new Map([[name.name, element]]);
125
+ const scoped = binding.scopedArguments.map((at) => ({
126
+ node: node.args[at],
127
+ type: host.typeOfBinding(node.args[at], bindings),
128
+ }));
129
+ if (node.name === "all" || node.name === "exists" || node.name === "exists_one") {
130
+ requireBool(scoped[0], node.name, host);
131
+ return BOOL;
132
+ }
133
+ if (node.name === "filter") {
134
+ requireBool(scoped[0], node.name, host);
135
+ return listOf(element);
136
+ }
137
+ // `map` in both arities: the last scoped argument is the transform, and a third
138
+ // argument makes the one before it a filter.
139
+ if (scoped.length === 2)
140
+ requireBool(scoped[0], node.name, host);
141
+ return listOf(scoped.at(-1).type);
142
+ }
143
+ function requireBool(scoped, name, host) {
144
+ if (isDyn(scoped.type) || scoped.type.kind === "parameter")
145
+ return;
146
+ if (scoped.type.kind === "primitive" && scoped.type.name === "bool")
147
+ return;
148
+ host.report("CEL_TYPE_ERROR", `${name}'s test must be bool, and it is ${formatType(scoped.type)}`, scoped.node.range);
149
+ }
150
+ /** What one element of a comprehension's receiver is: a list's element, a map's key. */
151
+ function iterationType(receiver, node, host) {
152
+ if (isDyn(receiver) || receiver.kind === "parameter")
153
+ return DYN;
154
+ if (receiver.kind === "list")
155
+ return receiver.element;
156
+ if (receiver.kind === "map")
157
+ return receiver.key;
158
+ if (receiver.kind === "record")
159
+ return { kind: "primitive", name: "string" };
160
+ host.report("CEL_TYPE_ERROR", `a comprehension reads a list or a map, and ${formatType(receiver)} is neither`, node.kind === "receiverCall" ? node.receiver.range : node.range);
161
+ return DYN;
162
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Whether each macro call is shaped like one at all, judged before any type is read.
3
+ *
4
+ * A macro's own arguments are not values: `xs.map(i, …)` declares the name `i`, and
5
+ * `has(a.b)` asks about the member `b` of a chain of names. Neither is something a type
6
+ * can be wrong about — they are either written in the shape the macro takes, or the call
7
+ * is not that macro. So this is a **structural** pass over the whole tree, and it runs
8
+ * first: a macro written wrongly is reported before anything inside it is typed, because
9
+ * the typing of its body depends on a name the call failed to declare.
10
+ *
11
+ * The walk is **post-order**, so the innermost mistake is the one reported first. Nesting
12
+ * macros is how a generated expression goes wrong, and the inner call is the one a reader
13
+ * has to fix.
14
+ */
15
+ import type { CelCheckCode } from "./check-diagnostic.js";
16
+ import type { CelNode, SourceRange } from "./syntax-tree.js";
17
+ export interface MacroShapeFinding {
18
+ readonly code: CelCheckCode;
19
+ readonly message: string;
20
+ readonly range: SourceRange;
21
+ }
22
+ /** Every macro call whose own arguments are not the shape it takes, innermost first. */
23
+ export declare function macroShapeFindings(root: CelNode): readonly MacroShapeFinding[];
24
+ //# sourceMappingURL=macro-shape.d.ts.map