@kernlang/core 3.3.9 → 3.4.1

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 (97) hide show
  1. package/dist/capability-matrix.d.ts +15 -0
  2. package/dist/capability-matrix.js +245 -0
  3. package/dist/capability-matrix.js.map +1 -0
  4. package/dist/codegen/body-ts.d.ts +68 -0
  5. package/dist/codegen/body-ts.js +214 -0
  6. package/dist/codegen/body-ts.js.map +1 -0
  7. package/dist/codegen/data-layer.d.ts +1 -1
  8. package/dist/codegen/data-layer.js +59 -23
  9. package/dist/codegen/data-layer.js.map +1 -1
  10. package/dist/codegen/events.js +1 -1
  11. package/dist/codegen/events.js.map +1 -1
  12. package/dist/codegen/functions.js +48 -7
  13. package/dist/codegen/functions.js.map +1 -1
  14. package/dist/codegen/ground-layer.js +10 -6
  15. package/dist/codegen/ground-layer.js.map +1 -1
  16. package/dist/codegen/helpers.d.ts +3 -1
  17. package/dist/codegen/helpers.js +5 -1
  18. package/dist/codegen/helpers.js.map +1 -1
  19. package/dist/codegen/kern-stdlib.d.ts +63 -0
  20. package/dist/codegen/kern-stdlib.js +160 -0
  21. package/dist/codegen/kern-stdlib.js.map +1 -0
  22. package/dist/codegen/machines.js +4 -3
  23. package/dist/codegen/machines.js.map +1 -1
  24. package/dist/codegen/modules.d.ts +1 -0
  25. package/dist/codegen/modules.js +52 -1
  26. package/dist/codegen/modules.js.map +1 -1
  27. package/dist/codegen/screens.js +31 -9
  28. package/dist/codegen/screens.js.map +1 -1
  29. package/dist/codegen/stdlib-preamble.d.ts +58 -0
  30. package/dist/codegen/stdlib-preamble.js +271 -0
  31. package/dist/codegen/stdlib-preamble.js.map +1 -0
  32. package/dist/codegen/type-system.d.ts +113 -1
  33. package/dist/codegen/type-system.js +404 -31
  34. package/dist/codegen/type-system.js.map +1 -1
  35. package/dist/codegen-core.d.ts +2 -2
  36. package/dist/codegen-core.js +65 -10
  37. package/dist/codegen-core.js.map +1 -1
  38. package/dist/codegen-expression.d.ts +11 -0
  39. package/dist/codegen-expression.js +199 -0
  40. package/dist/codegen-expression.js.map +1 -0
  41. package/dist/concepts.d.ts +3 -3
  42. package/dist/config.d.ts +16 -0
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/decompiler.js +575 -4
  46. package/dist/decompiler.js.map +1 -1
  47. package/dist/importer.d.ts +1 -0
  48. package/dist/importer.js +574 -34
  49. package/dist/importer.js.map +1 -1
  50. package/dist/index.d.ts +16 -3
  51. package/dist/index.js +19 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/node-props.d.ts +181 -1
  54. package/dist/node-props.js.map +1 -1
  55. package/dist/parser-core.d.ts +7 -1
  56. package/dist/parser-core.js +33 -7
  57. package/dist/parser-core.js.map +1 -1
  58. package/dist/parser-diagnostics.js +8 -0
  59. package/dist/parser-diagnostics.js.map +1 -1
  60. package/dist/parser-expression.d.ts +22 -0
  61. package/dist/parser-expression.js +774 -0
  62. package/dist/parser-expression.js.map +1 -0
  63. package/dist/parser-keywords.js +16 -0
  64. package/dist/parser-keywords.js.map +1 -1
  65. package/dist/parser-tokenizer.d.ts +5 -3
  66. package/dist/parser-tokenizer.js +97 -16
  67. package/dist/parser-tokenizer.js.map +1 -1
  68. package/dist/parser-validate-effects.d.ts +21 -0
  69. package/dist/parser-validate-effects.js +188 -0
  70. package/dist/parser-validate-effects.js.map +1 -0
  71. package/dist/parser-validate-expressions.d.ts +6 -0
  72. package/dist/parser-validate-expressions.js +41 -0
  73. package/dist/parser-validate-expressions.js.map +1 -0
  74. package/dist/parser-validate-propagation.d.ts +105 -0
  75. package/dist/parser-validate-propagation.js +684 -0
  76. package/dist/parser-validate-propagation.js.map +1 -0
  77. package/dist/parser-validate-union-kind.d.ts +24 -0
  78. package/dist/parser-validate-union-kind.js +97 -0
  79. package/dist/parser-validate-union-kind.js.map +1 -0
  80. package/dist/parser.d.ts +10 -3
  81. package/dist/parser.js +11 -5
  82. package/dist/parser.js.map +1 -1
  83. package/dist/schema.d.ts +1 -1
  84. package/dist/schema.js +562 -30
  85. package/dist/schema.js.map +1 -1
  86. package/dist/semantic-validator.js +24 -13
  87. package/dist/semantic-validator.js.map +1 -1
  88. package/dist/spec.d.ts +5 -2
  89. package/dist/spec.js +36 -2
  90. package/dist/spec.js.map +1 -1
  91. package/dist/types.d.ts +7 -1
  92. package/dist/types.js +7 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/value-ir.d.ts +96 -0
  95. package/dist/value-ir.js +25 -0
  96. package/dist/value-ir.js.map +1 -0
  97. package/package.json +1 -1
@@ -0,0 +1,774 @@
1
+ /** Expression-mode tokenizer + recursive-descent parser producing ValueIR.
2
+ * Supports: identifiers, literals (number/string/true/false/null/undefined/none),
3
+ * member access (. and ?.), call (() and ?.()), spread (...), logical ?? || &&,
4
+ * parenthesized grouping, template literals with ${...} interpolation,
5
+ * `await` prefix, propagation `?` postfix on call/await-call.
6
+ *
7
+ * `none` is a KERN-side alias for `null` — both produce nullLit. Per native-handler
8
+ * spec, `none` is the canonical empty-value form in `lang=kern` bodies; `null` is
9
+ * retained for legacy/round-trip compatibility.
10
+ *
11
+ * Intentionally NOT yet supported: arithmetic, comparisons, ternary, indexing,
12
+ * bitwise, assignment. Those land in a later slice. */
13
+ const KEYWORDS = {
14
+ null: 'kwNull',
15
+ none: 'kwNull',
16
+ undefined: 'kwUndef',
17
+ true: 'kwTrue',
18
+ false: 'kwFalse',
19
+ await: 'kwAwait',
20
+ // Slice 4c+4d review fix (Codex P2): `new` is prefix-position-only.
21
+ // Tokenizing it as `kwNew` globally broke `obj.new` and `{ new: 1 }`
22
+ // for property/key names. Now an `ident` token; parseUnary checks
23
+ // `value === 'new'` to recognize the prefix-position usage.
24
+ };
25
+ function isDigit(ch) {
26
+ return ch >= '0' && ch <= '9';
27
+ }
28
+ function isIdentStart(ch) {
29
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch === '_' || ch === '$';
30
+ }
31
+ function isIdentChar(ch) {
32
+ return isIdentStart(ch) || isDigit(ch);
33
+ }
34
+ function isHexDigit(ch) {
35
+ return isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
36
+ }
37
+ function consumeDigitsStrict(input, start, isValid) {
38
+ let i = start;
39
+ let started = false;
40
+ let lastWasUnderscore = false;
41
+ while (i < input.length) {
42
+ const c = input[i];
43
+ if (isValid(c)) {
44
+ lastWasUnderscore = false;
45
+ started = true;
46
+ i++;
47
+ }
48
+ else if (c === '_' && started && !lastWasUnderscore && i + 1 < input.length && isValid(input[i + 1])) {
49
+ lastWasUnderscore = true;
50
+ i++;
51
+ }
52
+ else {
53
+ break;
54
+ }
55
+ }
56
+ return i;
57
+ }
58
+ function consumeNumber(input, start) {
59
+ let i = start;
60
+ const ch = input[i];
61
+ if (ch === '0' && i + 1 < input.length) {
62
+ const next = input[i + 1];
63
+ let validator = null;
64
+ if (next === 'x' || next === 'X')
65
+ validator = isHexDigit;
66
+ else if (next === 'b' || next === 'B')
67
+ validator = (c) => c === '0' || c === '1';
68
+ else if (next === 'o' || next === 'O')
69
+ validator = (c) => c >= '0' && c <= '7';
70
+ if (validator) {
71
+ const after = consumeDigitsStrict(input, i + 2, validator);
72
+ if (after === i + 2)
73
+ return start;
74
+ i = after;
75
+ if (i < input.length && input[i] === 'n')
76
+ i++;
77
+ return i;
78
+ }
79
+ }
80
+ const hasInt = isDigit(ch);
81
+ let j = hasInt ? consumeDigitsStrict(input, i, isDigit) : i;
82
+ let hasFrac = false;
83
+ if (j < input.length && input[j] === '.' && j + 1 < input.length && isDigit(input[j + 1])) {
84
+ j++;
85
+ j = consumeDigitsStrict(input, j, isDigit);
86
+ hasFrac = true;
87
+ }
88
+ if (!hasInt && !hasFrac)
89
+ return start;
90
+ if (!hasFrac && j < input.length && input[j] === 'n') {
91
+ j++;
92
+ }
93
+ else if (hasFrac && j < input.length && input[j] === 'n') {
94
+ throw new Error(`BigInt literal cannot have a fractional part at column ${start + 1}`);
95
+ }
96
+ return j;
97
+ }
98
+ function consumeString(input, start) {
99
+ const quote = input[start];
100
+ let i = start + 1;
101
+ let value = '';
102
+ while (i < input.length && input[i] !== quote) {
103
+ if (input[i] === '\\' && i + 1 < input.length) {
104
+ const next = input[i + 1];
105
+ if (next === quote) {
106
+ value += quote;
107
+ i += 2;
108
+ }
109
+ else if (next === '\\') {
110
+ value += '\\';
111
+ i += 2;
112
+ }
113
+ else if (next === 'n') {
114
+ value += '\n';
115
+ i += 2;
116
+ }
117
+ else if (next === 't') {
118
+ value += '\t';
119
+ i += 2;
120
+ }
121
+ else {
122
+ value += input[i];
123
+ i++;
124
+ }
125
+ }
126
+ else {
127
+ value += input[i];
128
+ i++;
129
+ }
130
+ }
131
+ if (i >= input.length)
132
+ throw new Error(`Unclosed string starting at column ${start + 1}`);
133
+ return { end: i + 1, value };
134
+ }
135
+ /** Tokenize an expression source. Stops at end of input. */
136
+ export function tokenizeExpression(input) {
137
+ const tokens = [];
138
+ let i = 0;
139
+ while (i < input.length) {
140
+ const ch = input[i];
141
+ if (ch === ' ' || ch === '\t' || ch === '\n') {
142
+ i++;
143
+ continue;
144
+ }
145
+ if (ch === '`') {
146
+ const start = i;
147
+ i = scanTemplateEnd(input, i + 1);
148
+ tokens.push({ kind: 'tmplStart', value: '`', pos: start });
149
+ continue;
150
+ }
151
+ if (ch === '?' && input[i + 1] === '.') {
152
+ tokens.push({ kind: 'optDot', value: '?.', pos: i });
153
+ i += 2;
154
+ continue;
155
+ }
156
+ if (ch === '?' && input[i + 1] === '?') {
157
+ tokens.push({ kind: 'nullish', value: '??', pos: i });
158
+ i += 2;
159
+ continue;
160
+ }
161
+ if (ch === '?') {
162
+ tokens.push({ kind: 'qmark', value: '?', pos: i });
163
+ i++;
164
+ continue;
165
+ }
166
+ // Slice 2c — equality / strict-equality / negation. Multi-char first.
167
+ if (ch === '=' && input[i + 1] === '=' && input[i + 2] === '=') {
168
+ tokens.push({ kind: 'strictEq', value: '===', pos: i });
169
+ i += 3;
170
+ continue;
171
+ }
172
+ if (ch === '=' && input[i + 1] === '=') {
173
+ tokens.push({ kind: 'eq', value: '==', pos: i });
174
+ i += 2;
175
+ continue;
176
+ }
177
+ if (ch === '!' && input[i + 1] === '=' && input[i + 2] === '=') {
178
+ tokens.push({ kind: 'strictNeq', value: '!==', pos: i });
179
+ i += 3;
180
+ continue;
181
+ }
182
+ if (ch === '!' && input[i + 1] === '=') {
183
+ tokens.push({ kind: 'neq', value: '!=', pos: i });
184
+ i += 2;
185
+ continue;
186
+ }
187
+ if (ch === '!') {
188
+ tokens.push({ kind: 'bang', value: '!', pos: i });
189
+ i++;
190
+ continue;
191
+ }
192
+ // Slice 2c — relational. Multi-char first so `<=` / `>=` win over bare `<` / `>`.
193
+ if (ch === '<' && input[i + 1] === '=') {
194
+ tokens.push({ kind: 'lte', value: '<=', pos: i });
195
+ i += 2;
196
+ continue;
197
+ }
198
+ if (ch === '<') {
199
+ tokens.push({ kind: 'lt', value: '<', pos: i });
200
+ i++;
201
+ continue;
202
+ }
203
+ if (ch === '>' && input[i + 1] === '=') {
204
+ tokens.push({ kind: 'gte', value: '>=', pos: i });
205
+ i += 2;
206
+ continue;
207
+ }
208
+ if (ch === '>') {
209
+ tokens.push({ kind: 'gt', value: '>', pos: i });
210
+ i++;
211
+ continue;
212
+ }
213
+ // Slice 2c — arithmetic. `-` could be sign of a number, but the number
214
+ // tokenizer below handles only unsigned literals; unary minus is a parser
215
+ // concern (see parseUnary), so keep `-` as its own token here.
216
+ if (ch === '+') {
217
+ tokens.push({ kind: 'plus', value: '+', pos: i });
218
+ i++;
219
+ continue;
220
+ }
221
+ if (ch === '-') {
222
+ tokens.push({ kind: 'minus', value: '-', pos: i });
223
+ i++;
224
+ continue;
225
+ }
226
+ if (ch === '*') {
227
+ tokens.push({ kind: 'star', value: '*', pos: i });
228
+ i++;
229
+ continue;
230
+ }
231
+ if (ch === '/') {
232
+ tokens.push({ kind: 'slash', value: '/', pos: i });
233
+ i++;
234
+ continue;
235
+ }
236
+ if (ch === '%') {
237
+ tokens.push({ kind: 'percent', value: '%', pos: i });
238
+ i++;
239
+ continue;
240
+ }
241
+ if (ch === '|' && input[i + 1] === '|') {
242
+ tokens.push({ kind: 'or', value: '||', pos: i });
243
+ i += 2;
244
+ continue;
245
+ }
246
+ if (ch === '&' && input[i + 1] === '&') {
247
+ tokens.push({ kind: 'and', value: '&&', pos: i });
248
+ i += 2;
249
+ continue;
250
+ }
251
+ if (ch === '.' && input[i + 1] === '.' && input[i + 2] === '.') {
252
+ tokens.push({ kind: 'spread', value: '...', pos: i });
253
+ i += 3;
254
+ continue;
255
+ }
256
+ // Number must be checked BEFORE bare-dot so leading-dot floats (.5) lex as num
257
+ if (isDigit(ch) || (ch === '.' && i + 1 < input.length && isDigit(input[i + 1]))) {
258
+ const end = consumeNumber(input, i);
259
+ if (end > i) {
260
+ tokens.push({ kind: 'num', value: input.slice(i, end), pos: i });
261
+ i = end;
262
+ continue;
263
+ }
264
+ }
265
+ if (ch === '.') {
266
+ tokens.push({ kind: 'dot', value: '.', pos: i });
267
+ i++;
268
+ continue;
269
+ }
270
+ if (ch === '(') {
271
+ tokens.push({ kind: 'lparen', value: '(', pos: i });
272
+ i++;
273
+ continue;
274
+ }
275
+ if (ch === ')') {
276
+ tokens.push({ kind: 'rparen', value: ')', pos: i });
277
+ i++;
278
+ continue;
279
+ }
280
+ if (ch === '{') {
281
+ tokens.push({ kind: 'lbrace', value: '{', pos: i });
282
+ i++;
283
+ continue;
284
+ }
285
+ if (ch === '}') {
286
+ tokens.push({ kind: 'rbrace', value: '}', pos: i });
287
+ i++;
288
+ continue;
289
+ }
290
+ if (ch === '[') {
291
+ tokens.push({ kind: 'lbracket', value: '[', pos: i });
292
+ i++;
293
+ continue;
294
+ }
295
+ if (ch === ']') {
296
+ tokens.push({ kind: 'rbracket', value: ']', pos: i });
297
+ i++;
298
+ continue;
299
+ }
300
+ if (ch === ':') {
301
+ tokens.push({ kind: 'colon', value: ':', pos: i });
302
+ i++;
303
+ continue;
304
+ }
305
+ if (ch === ',') {
306
+ tokens.push({ kind: 'comma', value: ',', pos: i });
307
+ i++;
308
+ continue;
309
+ }
310
+ if (ch === '"' || ch === "'") {
311
+ const { end, value } = consumeString(input, i);
312
+ tokens.push({ kind: 'str', value, pos: i });
313
+ // Preserve raw form for codegen quote-style preservation
314
+ tokens[tokens.length - 1].quote = ch;
315
+ i = end;
316
+ continue;
317
+ }
318
+ if (isIdentStart(ch)) {
319
+ const start = i;
320
+ while (i < input.length && isIdentChar(input[i]))
321
+ i++;
322
+ const word = input.slice(start, i);
323
+ const kw = KEYWORDS[word];
324
+ if (kw) {
325
+ tokens.push({ kind: kw, value: word, pos: start });
326
+ }
327
+ else {
328
+ tokens.push({ kind: 'ident', value: word, pos: start });
329
+ }
330
+ continue;
331
+ }
332
+ throw new Error(`Unexpected character '${ch}' at column ${i + 1}`);
333
+ }
334
+ tokens.push({ kind: 'eof', value: '', pos: i });
335
+ return tokens;
336
+ }
337
+ // ── Parser ───────────────────────────────────────────────────────────────
338
+ class Parser {
339
+ tokens;
340
+ input;
341
+ i = 0;
342
+ constructor(tokens, input) {
343
+ this.tokens = tokens;
344
+ this.input = input;
345
+ }
346
+ peek(offset = 0) {
347
+ return this.tokens[this.i + offset];
348
+ }
349
+ advance() {
350
+ return this.tokens[this.i++];
351
+ }
352
+ expect(kind) {
353
+ const t = this.peek();
354
+ if (t.kind !== kind)
355
+ throw new Error(`Expected ${kind}, got ${t.kind} ('${t.value}') at column ${t.pos + 1}`);
356
+ return this.advance();
357
+ }
358
+ parse() {
359
+ const result = this.parseNullish();
360
+ if (this.peek().kind !== 'eof') {
361
+ const t = this.peek();
362
+ throw new Error(`Unexpected token ${t.kind} ('${t.value}') at column ${t.pos + 1}`);
363
+ }
364
+ return result;
365
+ }
366
+ parseNullish() {
367
+ let left = this.parseOr();
368
+ while (this.peek().kind === 'nullish') {
369
+ this.advance();
370
+ const right = this.parseOr();
371
+ left = { kind: 'binary', op: '??', left, right };
372
+ }
373
+ return left;
374
+ }
375
+ parseOr() {
376
+ let left = this.parseAnd();
377
+ while (this.peek().kind === 'or') {
378
+ this.advance();
379
+ const right = this.parseAnd();
380
+ left = { kind: 'binary', op: '||', left, right };
381
+ }
382
+ return left;
383
+ }
384
+ parseAnd() {
385
+ let left = this.parseEquality();
386
+ while (this.peek().kind === 'and') {
387
+ this.advance();
388
+ const right = this.parseEquality();
389
+ left = { kind: 'binary', op: '&&', left, right };
390
+ }
391
+ return left;
392
+ }
393
+ // Slice 2c — equality (==, !=, ===, !==), left-associative.
394
+ parseEquality() {
395
+ let left = this.parseRelational();
396
+ while (true) {
397
+ const k = this.peek().kind;
398
+ if (k !== 'eq' && k !== 'neq' && k !== 'strictEq' && k !== 'strictNeq')
399
+ break;
400
+ const op = this.advance().value;
401
+ const right = this.parseRelational();
402
+ left = { kind: 'binary', op, left, right };
403
+ }
404
+ return left;
405
+ }
406
+ // Slice 2c — relational (<, <=, >, >=), left-associative.
407
+ parseRelational() {
408
+ let left = this.parseAdditive();
409
+ while (true) {
410
+ const k = this.peek().kind;
411
+ if (k !== 'lt' && k !== 'lte' && k !== 'gt' && k !== 'gte')
412
+ break;
413
+ const op = this.advance().value;
414
+ const right = this.parseAdditive();
415
+ left = { kind: 'binary', op, left, right };
416
+ }
417
+ return left;
418
+ }
419
+ // Slice 2c — additive (+, -), left-associative.
420
+ parseAdditive() {
421
+ let left = this.parseMultiplicative();
422
+ while (true) {
423
+ const k = this.peek().kind;
424
+ if (k !== 'plus' && k !== 'minus')
425
+ break;
426
+ const op = this.advance().value;
427
+ const right = this.parseMultiplicative();
428
+ left = { kind: 'binary', op, left, right };
429
+ }
430
+ return left;
431
+ }
432
+ // Slice 2c — multiplicative (*, /, %), left-associative.
433
+ parseMultiplicative() {
434
+ let left = this.parseUnary();
435
+ while (true) {
436
+ const k = this.peek().kind;
437
+ if (k !== 'star' && k !== 'slash' && k !== 'percent')
438
+ break;
439
+ const op = this.advance().value;
440
+ const right = this.parseUnary();
441
+ left = { kind: 'binary', op, left, right };
442
+ }
443
+ return left;
444
+ }
445
+ parseUnary() {
446
+ if (this.peek().kind === 'spread') {
447
+ this.advance();
448
+ return { kind: 'spread', argument: this.parseUnary() };
449
+ }
450
+ if (this.peek().kind === 'bang') {
451
+ this.advance();
452
+ return { kind: 'unary', op: '!', argument: this.parseUnary() };
453
+ }
454
+ if (this.peek().kind === 'minus') {
455
+ this.advance();
456
+ return { kind: 'unary', op: '-', argument: this.parseUnary() };
457
+ }
458
+ if (this.peek().kind === 'kwAwait') {
459
+ this.advance();
460
+ // Use parseCall (not parsePostfix) so the trailing `?` stays available
461
+ // for the outer await + propagation composition. With parsePostfix the
462
+ // `?` would bind to the call alone, producing `await(propagate(call()))`
463
+ // instead of the semantically-correct `propagate(await(call()))`.
464
+ const argument = this.parseCall();
465
+ const awaited = { kind: 'await', argument };
466
+ if (this.peek().kind === 'qmark') {
467
+ this.advance();
468
+ return { kind: 'propagate', argument: awaited, op: '?' };
469
+ }
470
+ return awaited;
471
+ }
472
+ // Slice 4c+4d review fix (Codex P2) — match `new` only in prefix
473
+ // position so `obj.new` and `{ new: 1 }` keep working as identifier
474
+ // / property-name uses.
475
+ if (this.peek().kind === 'ident' && this.peek().value === 'new') {
476
+ this.advance();
477
+ const argument = this.parseCall();
478
+ return { kind: 'new', argument };
479
+ }
480
+ return this.parsePostfix();
481
+ }
482
+ parsePostfix() {
483
+ const node = this.parseCall();
484
+ if (this.peek().kind === 'qmark') {
485
+ this.advance();
486
+ return { kind: 'propagate', argument: node, op: '?' };
487
+ }
488
+ return node;
489
+ }
490
+ parseCall() {
491
+ let node = this.parsePrimary();
492
+ while (true) {
493
+ const t = this.peek();
494
+ if (t.kind === 'dot') {
495
+ this.advance();
496
+ const name = this.expect('ident');
497
+ node = { kind: 'member', object: node, property: name.value, optional: false };
498
+ }
499
+ else if (t.kind === 'optDot') {
500
+ this.advance();
501
+ const next = this.peek();
502
+ if (next.kind === 'lparen') {
503
+ this.advance();
504
+ const args = this.parseArgs();
505
+ this.expect('rparen');
506
+ node = { kind: 'call', callee: node, args, optional: true };
507
+ }
508
+ else {
509
+ const name = this.expect('ident');
510
+ node = { kind: 'member', object: node, property: name.value, optional: true };
511
+ }
512
+ }
513
+ else if (t.kind === 'lparen') {
514
+ this.advance();
515
+ const args = this.parseArgs();
516
+ this.expect('rparen');
517
+ node = { kind: 'call', callee: node, args, optional: false };
518
+ }
519
+ else {
520
+ break;
521
+ }
522
+ }
523
+ return node;
524
+ }
525
+ parseArgs() {
526
+ const args = [];
527
+ if (this.peek().kind === 'rparen')
528
+ return args;
529
+ args.push(this.parseNullish());
530
+ while (this.peek().kind === 'comma') {
531
+ this.advance();
532
+ args.push(this.parseNullish());
533
+ }
534
+ return args;
535
+ }
536
+ parsePrimary() {
537
+ const t = this.peek();
538
+ switch (t.kind) {
539
+ case 'ident':
540
+ this.advance();
541
+ return { kind: 'ident', name: t.value };
542
+ case 'num': {
543
+ this.advance();
544
+ const raw = t.value;
545
+ const isBig = raw.endsWith('n');
546
+ const numStr = isBig ? raw.slice(0, -1).replace(/_/g, '') : raw.replace(/_/g, '');
547
+ const value = isBig ? 0 : Number(numStr);
548
+ return isBig ? { kind: 'numLit', value, bigint: true, raw } : { kind: 'numLit', value, raw };
549
+ }
550
+ case 'str': {
551
+ this.advance();
552
+ const quote = (t.quote ?? '"');
553
+ return { kind: 'strLit', value: t.value, quote };
554
+ }
555
+ case 'kwTrue':
556
+ this.advance();
557
+ return { kind: 'boolLit', value: true };
558
+ case 'kwFalse':
559
+ this.advance();
560
+ return { kind: 'boolLit', value: false };
561
+ case 'kwNull':
562
+ this.advance();
563
+ return { kind: 'nullLit' };
564
+ case 'kwUndef':
565
+ this.advance();
566
+ return { kind: 'undefLit' };
567
+ case 'lparen': {
568
+ this.advance();
569
+ const inner = this.parseNullish();
570
+ this.expect('rparen');
571
+ return inner;
572
+ }
573
+ case 'lbrace':
574
+ this.advance();
575
+ return this.parseObjectLiteral();
576
+ case 'lbracket':
577
+ this.advance();
578
+ return this.parseArrayLiteral();
579
+ case 'tmplStart':
580
+ this.advance();
581
+ return this.parseTemplate(t.pos);
582
+ default:
583
+ throw new Error(`Unexpected token ${t.kind} ('${t.value}') at column ${t.pos + 1}`);
584
+ }
585
+ }
586
+ // Slice 2d — object literal: `{ key: value, "str-key": value }`. Computed
587
+ // keys (`[expr]:`) defer to slice 3.
588
+ parseObjectLiteral() {
589
+ const entries = [];
590
+ if (this.peek().kind === 'rbrace') {
591
+ this.advance();
592
+ return { kind: 'objectLit', entries };
593
+ }
594
+ while (true) {
595
+ const keyTok = this.peek();
596
+ if (keyTok.kind === 'spread') {
597
+ this.advance();
598
+ const argument = this.parseNullish();
599
+ entries.push({ kind: 'spread', argument });
600
+ }
601
+ else {
602
+ let key;
603
+ if (keyTok.kind === 'ident') {
604
+ key = keyTok.value;
605
+ this.advance();
606
+ }
607
+ else if (keyTok.kind === 'str') {
608
+ key = keyTok.value;
609
+ this.advance();
610
+ }
611
+ else {
612
+ throw new Error(`Object literal key must be an identifier, string, or spread at column ${keyTok.pos + 1}`);
613
+ }
614
+ this.expect('colon');
615
+ const value = this.parseNullish();
616
+ entries.push({ key, value });
617
+ }
618
+ if (this.peek().kind === 'comma') {
619
+ this.advance();
620
+ // Trailing comma allowed.
621
+ if (this.peek().kind === 'rbrace')
622
+ break;
623
+ continue;
624
+ }
625
+ break;
626
+ }
627
+ this.expect('rbrace');
628
+ return { kind: 'objectLit', entries };
629
+ }
630
+ // Slice 2d — array literal: `[a, b, c]`.
631
+ parseArrayLiteral() {
632
+ const items = [];
633
+ if (this.peek().kind === 'rbracket') {
634
+ this.advance();
635
+ return { kind: 'arrayLit', items };
636
+ }
637
+ while (true) {
638
+ items.push(this.parseNullish());
639
+ if (this.peek().kind === 'comma') {
640
+ this.advance();
641
+ if (this.peek().kind === 'rbracket')
642
+ break;
643
+ continue;
644
+ }
645
+ break;
646
+ }
647
+ this.expect('rbracket');
648
+ return { kind: 'arrayLit', items };
649
+ }
650
+ parseTemplate(startPos) {
651
+ // After consuming opening backtick, scan source from token's source position + 1
652
+ // We don't have nice token-stream coverage of template guts (the tokenizer treated
653
+ // ` as just a marker), so re-scan the raw input.
654
+ const quasis = [];
655
+ const expressions = [];
656
+ let pos = startPos + 1;
657
+ let buf = '';
658
+ while (pos < this.input.length) {
659
+ const ch = this.input[pos];
660
+ if (ch === '`') {
661
+ quasis.push(buf);
662
+ // Re-sync the parent tokenizer by setting `i` past this template.
663
+ // Find the corresponding eof or token at this pos.
664
+ this.resyncAfter(pos + 1);
665
+ return { kind: 'tmplLit', quasis, expressions };
666
+ }
667
+ if (ch === '\\' && pos + 1 < this.input.length) {
668
+ const next = this.input[pos + 1];
669
+ if (next === '`') {
670
+ buf += '`';
671
+ pos += 2;
672
+ continue;
673
+ }
674
+ if (next === '\\') {
675
+ buf += '\\';
676
+ pos += 2;
677
+ continue;
678
+ }
679
+ if (next === '$') {
680
+ buf += '$';
681
+ pos += 2;
682
+ continue;
683
+ }
684
+ if (next === 'n') {
685
+ buf += '\n';
686
+ pos += 2;
687
+ continue;
688
+ }
689
+ if (next === 't') {
690
+ buf += '\t';
691
+ pos += 2;
692
+ continue;
693
+ }
694
+ buf += ch;
695
+ pos++;
696
+ continue;
697
+ }
698
+ if (ch === '$' && this.input[pos + 1] === '{') {
699
+ quasis.push(buf);
700
+ buf = '';
701
+ pos += 2;
702
+ const exprEnd = findMatchingBrace(this.input, pos);
703
+ const exprSrc = this.input.slice(pos, exprEnd);
704
+ const innerTokens = tokenizeExpression(exprSrc);
705
+ const innerParser = new Parser(innerTokens, exprSrc);
706
+ expressions.push(innerParser.parse());
707
+ pos = exprEnd + 1;
708
+ continue;
709
+ }
710
+ buf += ch;
711
+ pos++;
712
+ }
713
+ throw new Error(`Unclosed template literal starting at column ${startPos + 1}`);
714
+ }
715
+ resyncAfter(pos) {
716
+ // Drop any tokens whose pos < `pos` from being re-consumed; jump past them.
717
+ while (this.i < this.tokens.length && this.tokens[this.i].pos < pos)
718
+ this.i++;
719
+ }
720
+ }
721
+ function scanTemplateEnd(input, start) {
722
+ let i = start;
723
+ while (i < input.length) {
724
+ const ch = input[i];
725
+ if (ch === '\\' && i + 1 < input.length) {
726
+ i += 2;
727
+ continue;
728
+ }
729
+ if (ch === '`')
730
+ return i + 1;
731
+ if (ch === '$' && input[i + 1] === '{') {
732
+ i = findMatchingBrace(input, i + 2) + 1;
733
+ continue;
734
+ }
735
+ i++;
736
+ }
737
+ throw new Error(`Unclosed template literal starting at column ${start}`);
738
+ }
739
+ function findMatchingBrace(input, start) {
740
+ let depth = 1;
741
+ let i = start;
742
+ while (i < input.length) {
743
+ const ch = input[i];
744
+ if (ch === '{')
745
+ depth++;
746
+ else if (ch === '}') {
747
+ depth--;
748
+ if (depth === 0)
749
+ return i;
750
+ }
751
+ else if (ch === '`') {
752
+ i = scanTemplateEnd(input, i + 1);
753
+ continue;
754
+ }
755
+ else if (ch === '"' || ch === "'") {
756
+ let j = i + 1;
757
+ while (j < input.length && input[j] !== ch) {
758
+ if (input[j] === '\\')
759
+ j += 2;
760
+ else
761
+ j++;
762
+ }
763
+ i = j;
764
+ }
765
+ i++;
766
+ }
767
+ throw new Error(`Unclosed \${...} substitution starting at column ${start + 1}`);
768
+ }
769
+ export function parseExpression(input) {
770
+ const tokens = tokenizeExpression(input);
771
+ const parser = new Parser(tokens, input);
772
+ return parser.parse();
773
+ }
774
+ //# sourceMappingURL=parser-expression.js.map