@ahsankhanamu/json-transformer 0.1.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 (49) hide show
  1. package/dist/ast.d.ts +259 -0
  2. package/dist/ast.d.ts.map +1 -0
  3. package/dist/ast.js +12 -0
  4. package/dist/ast.js.map +1 -0
  5. package/dist/codegen/base.d.ts +83 -0
  6. package/dist/codegen/base.d.ts.map +1 -0
  7. package/dist/codegen/base.js +494 -0
  8. package/dist/codegen/base.js.map +1 -0
  9. package/dist/codegen/index.d.ts +35 -0
  10. package/dist/codegen/index.d.ts.map +1 -0
  11. package/dist/codegen/index.js +42 -0
  12. package/dist/codegen/index.js.map +1 -0
  13. package/dist/codegen/library.d.ts +42 -0
  14. package/dist/codegen/library.d.ts.map +1 -0
  15. package/dist/codegen/library.js +406 -0
  16. package/dist/codegen/library.js.map +1 -0
  17. package/dist/codegen/native.d.ts +33 -0
  18. package/dist/codegen/native.d.ts.map +1 -0
  19. package/dist/codegen/native.js +452 -0
  20. package/dist/codegen/native.js.map +1 -0
  21. package/dist/codegen.d.ts +13 -0
  22. package/dist/codegen.d.ts.map +1 -0
  23. package/dist/codegen.js +12 -0
  24. package/dist/codegen.js.map +1 -0
  25. package/dist/index.d.ts +257 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +231 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/lexer.d.ts +37 -0
  30. package/dist/lexer.d.ts.map +1 -0
  31. package/dist/lexer.js +499 -0
  32. package/dist/lexer.js.map +1 -0
  33. package/dist/parser.d.ts +63 -0
  34. package/dist/parser.d.ts.map +1 -0
  35. package/dist/parser.js +1045 -0
  36. package/dist/parser.js.map +1 -0
  37. package/dist/playground.d.ts +6 -0
  38. package/dist/playground.d.ts.map +1 -0
  39. package/dist/playground.js +216 -0
  40. package/dist/playground.js.map +1 -0
  41. package/dist/runtime.d.ts +229 -0
  42. package/dist/runtime.d.ts.map +1 -0
  43. package/dist/runtime.js +933 -0
  44. package/dist/runtime.js.map +1 -0
  45. package/dist/tokens.d.ts +80 -0
  46. package/dist/tokens.d.ts.map +1 -0
  47. package/dist/tokens.js +88 -0
  48. package/dist/tokens.js.map +1 -0
  49. package/package.json +30 -0
package/dist/parser.js ADDED
@@ -0,0 +1,1045 @@
1
+ /**
2
+ * Parser - Recursive Descent Parser
3
+ * Produces AST from tokens
4
+ */
5
+ import { TokenType } from './tokens.js';
6
+ import { Lexer } from './lexer.js';
7
+ export class ParseError extends Error {
8
+ token;
9
+ expected;
10
+ constructor(message, token, expected) {
11
+ super(`Parse error at line ${token.line}, column ${token.column}: ${message}`);
12
+ this.token = token;
13
+ this.expected = expected;
14
+ this.name = 'ParseError';
15
+ }
16
+ }
17
+ export class Parser {
18
+ input;
19
+ tokens = [];
20
+ current = 0;
21
+ inPipeObjectContext = false;
22
+ // Stack of arrow function parameter names (for nested arrows)
23
+ // When inside an arrow body, .property resolves to param.property
24
+ arrowParamStack = [];
25
+ constructor(input) {
26
+ this.input = input;
27
+ }
28
+ parse() {
29
+ const lexer = new Lexer(this.input);
30
+ this.tokens = lexer.tokenize();
31
+ this.current = 0;
32
+ const statements = [];
33
+ let expression = null;
34
+ // Parse statements (let/const bindings)
35
+ while (!this.isAtEnd() && (this.check(TokenType.LET) || this.check(TokenType.CONST))) {
36
+ statements.push(this.parseLetBinding());
37
+ }
38
+ // Parse final expression
39
+ if (!this.isAtEnd()) {
40
+ expression = this.parseExpression();
41
+ }
42
+ // Should be at EOF
43
+ if (!this.isAtEnd()) {
44
+ throw new ParseError('Unexpected token after expression', this.peek());
45
+ }
46
+ return {
47
+ type: 'Program',
48
+ statements,
49
+ expression,
50
+ };
51
+ }
52
+ // ===========================================================================
53
+ // STATEMENTS
54
+ // ===========================================================================
55
+ parseLetBinding() {
56
+ const constant = this.check(TokenType.CONST);
57
+ this.advance(); // consume let/const
58
+ const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected variable name');
59
+ this.consume(TokenType.ASSIGN, 'Expected "=" after variable name');
60
+ const value = this.parseExpression();
61
+ this.consume(TokenType.SEMICOLON, 'Expected ";" after variable declaration');
62
+ return {
63
+ type: 'LetBinding',
64
+ name: nameToken.value,
65
+ value,
66
+ constant,
67
+ };
68
+ }
69
+ // ===========================================================================
70
+ // EXPRESSIONS (by precedence, lowest to highest)
71
+ // ===========================================================================
72
+ parseExpression() {
73
+ return this.parsePipe();
74
+ }
75
+ // Pipe: value | transform | transform
76
+ // Also supports jq-style property access: value | .field | .[0] | .method()
77
+ // And pipe-to-object/array construction: value | { id: .id } or value | [.id, .name]
78
+ parsePipe() {
79
+ let left = this.parseTernary();
80
+ while (this.match(TokenType.PIPE)) {
81
+ // Check if next token is DOT - pipe property access syntax (jq-style)
82
+ if (this.check(TokenType.DOT)) {
83
+ const right = this.parsePipePropertyAccess();
84
+ left = { type: 'PipeExpression', left, right };
85
+ }
86
+ else if (this.check(TokenType.LBRACKET)) {
87
+ // Could be index access [0] or array construction [.id, .name]
88
+ const right = this.parsePipeArrayOrIndex();
89
+ left = { type: 'PipeExpression', left, right };
90
+ }
91
+ else if (this.check(TokenType.LBRACE)) {
92
+ // Pipe-to-object construction: value | { .field, newKey: .other }
93
+ const right = this.parsePipeObjectConstruction();
94
+ left = { type: 'PipeExpression', left, right };
95
+ }
96
+ else {
97
+ const right = this.parseTernary();
98
+ left = { type: 'PipeExpression', left, right };
99
+ }
100
+ }
101
+ return left;
102
+ }
103
+ // Determine if | [...] is array construction or index access
104
+ // Array construction: | [.id, .name] or | [id, name] (shorthand) or | [expr, expr, ...]
105
+ // Index access: | [0] or | [expr] (single non-identifier expression)
106
+ parsePipeArrayOrIndex() {
107
+ // Peek at what follows [
108
+ const afterBracket = this.peekNext(); // token after [
109
+ // If [ is followed by DOT, it's array construction with pipe context refs: [.id, .name]
110
+ if (afterBracket.type === TokenType.DOT) {
111
+ return this.parsePipeArrayConstruction();
112
+ }
113
+ // If [ is followed by ], it's empty array construction: []
114
+ if (afterBracket.type === TokenType.RBRACKET) {
115
+ return this.parsePipeArrayConstruction();
116
+ }
117
+ // If [ is followed by SPREAD, it's array construction: [...items]
118
+ if (afterBracket.type === TokenType.SPREAD) {
119
+ return this.parsePipeArrayConstruction();
120
+ }
121
+ // If [ is followed by identifier, check if comma follows (array) or ] follows (index)
122
+ // For now, if followed by identifier, treat as array construction (shorthand)
123
+ if (afterBracket.type === TokenType.IDENTIFIER) {
124
+ return this.parsePipeArrayConstruction();
125
+ }
126
+ // Otherwise (number, string, expression), treat as index access
127
+ return this.parsePipeIndexAccess();
128
+ }
129
+ // Parse pipe-to-object construction: value | { id: .id, name: .name | upper }
130
+ parsePipeObjectConstruction() {
131
+ const savedContext = this.inPipeObjectContext;
132
+ this.inPipeObjectContext = true;
133
+ this.advance(); // consume {
134
+ const result = this.parseObjectLiteral();
135
+ this.inPipeObjectContext = savedContext;
136
+ return result;
137
+ }
138
+ // Parse pipe-to-array construction: value | [.id, .name] or value | [id, name]
139
+ parsePipeArrayConstruction() {
140
+ const savedContext = this.inPipeObjectContext;
141
+ this.inPipeObjectContext = true;
142
+ this.advance(); // consume [
143
+ const result = this.parsePipeArrayLiteral();
144
+ this.inPipeObjectContext = savedContext;
145
+ return result;
146
+ }
147
+ // Parse array literal in pipe context - identifiers become pipe context refs
148
+ parsePipeArrayLiteral() {
149
+ const elements = [];
150
+ while (!this.check(TokenType.RBRACKET) && !this.isAtEnd()) {
151
+ if (this.match(TokenType.SPREAD)) {
152
+ // Spread in pipe array context: [...] spreads pipe value, [...expr] spreads expr
153
+ if (this.check(TokenType.COMMA) || this.check(TokenType.RBRACKET)) {
154
+ // Bare spread: [...] spreads the pipe context
155
+ elements.push({ type: 'SpreadElement', argument: { type: 'PipeContextRef' } });
156
+ }
157
+ else {
158
+ const argument = this.parseExpression();
159
+ elements.push({ type: 'SpreadElement', argument });
160
+ }
161
+ }
162
+ else if (this.match(TokenType.IDENTIFIER)) {
163
+ // Shorthand: identifier in pipe array context becomes .identifier
164
+ const name = this.previous().value;
165
+ elements.push({
166
+ type: 'MemberAccess',
167
+ object: { type: 'PipeContextRef' },
168
+ property: name,
169
+ optional: false,
170
+ });
171
+ }
172
+ else {
173
+ elements.push(this.parseExpression());
174
+ }
175
+ if (!this.check(TokenType.RBRACKET)) {
176
+ this.consume(TokenType.COMMA, 'Expected "," between elements');
177
+ // Allow trailing comma
178
+ if (this.check(TokenType.RBRACKET))
179
+ break;
180
+ }
181
+ }
182
+ this.consume(TokenType.RBRACKET, 'Expected "]" after array literal');
183
+ return { type: 'ArrayLiteral', elements };
184
+ }
185
+ // Parse jq-style pipe property access: .field, .[0], .field.subfield, .method()
186
+ parsePipePropertyAccess() {
187
+ // Start with PipeContextRef as the base
188
+ let expr = { type: 'PipeContextRef' };
189
+ // Must start with DOT
190
+ while (this.check(TokenType.DOT) || this.check(TokenType.LBRACKET)) {
191
+ if (this.match(TokenType.DOT)) {
192
+ // Check for .[index] syntax
193
+ if (this.check(TokenType.LBRACKET)) {
194
+ this.advance(); // consume [
195
+ const index = this.parseExpression();
196
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
197
+ expr = {
198
+ type: 'IndexAccess',
199
+ object: expr,
200
+ index,
201
+ optional: false,
202
+ };
203
+ }
204
+ else {
205
+ // .field or .method()
206
+ const name = this.consume(TokenType.IDENTIFIER, 'Expected property name after "."');
207
+ expr = {
208
+ type: 'MemberAccess',
209
+ object: expr,
210
+ property: name.value,
211
+ optional: false,
212
+ };
213
+ }
214
+ }
215
+ else if (this.match(TokenType.LBRACKET)) {
216
+ // [0] or ["key"] without dot prefix
217
+ const index = this.parseExpression();
218
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
219
+ expr = {
220
+ type: 'IndexAccess',
221
+ object: expr,
222
+ index,
223
+ optional: false,
224
+ };
225
+ }
226
+ // Handle method calls: .method()
227
+ if (this.check(TokenType.LPAREN)) {
228
+ this.advance();
229
+ const args = [];
230
+ if (!this.check(TokenType.RPAREN)) {
231
+ do {
232
+ args.push(this.parseExpression());
233
+ } while (this.match(TokenType.COMMA));
234
+ }
235
+ this.consume(TokenType.RPAREN, 'Expected ")"');
236
+ expr = {
237
+ type: 'CallExpression',
238
+ callee: expr,
239
+ arguments: args,
240
+ };
241
+ }
242
+ }
243
+ return expr;
244
+ }
245
+ // Parse pipe index access: | [0] (without dot)
246
+ parsePipeIndexAccess() {
247
+ let expr = { type: 'PipeContextRef' };
248
+ this.advance(); // consume [
249
+ const index = this.parseExpression();
250
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
251
+ expr = {
252
+ type: 'IndexAccess',
253
+ object: expr,
254
+ index,
255
+ optional: false,
256
+ };
257
+ // Allow chaining after: | [0].field or | [0][1]
258
+ while (this.check(TokenType.DOT) || this.check(TokenType.LBRACKET)) {
259
+ if (this.match(TokenType.DOT)) {
260
+ if (this.check(TokenType.LBRACKET)) {
261
+ this.advance();
262
+ const idx = this.parseExpression();
263
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
264
+ expr = { type: 'IndexAccess', object: expr, index: idx, optional: false };
265
+ }
266
+ else {
267
+ const name = this.consume(TokenType.IDENTIFIER, 'Expected property name after "."');
268
+ expr = {
269
+ type: 'MemberAccess',
270
+ object: expr,
271
+ property: name.value,
272
+ optional: false,
273
+ };
274
+ }
275
+ }
276
+ else if (this.match(TokenType.LBRACKET)) {
277
+ const idx = this.parseExpression();
278
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
279
+ expr = { type: 'IndexAccess', object: expr, index: idx, optional: false };
280
+ }
281
+ // Handle method calls
282
+ if (this.check(TokenType.LPAREN)) {
283
+ this.advance();
284
+ const args = [];
285
+ if (!this.check(TokenType.RPAREN)) {
286
+ do {
287
+ args.push(this.parseExpression());
288
+ } while (this.match(TokenType.COMMA));
289
+ }
290
+ this.consume(TokenType.RPAREN, 'Expected ")"');
291
+ expr = { type: 'CallExpression', callee: expr, arguments: args };
292
+ }
293
+ }
294
+ return expr;
295
+ }
296
+ // Parse .field access inside pipe object context (DOT already consumed)
297
+ // Returns PipeContextRef-based expression: .field, .[0], .field.nested
298
+ parsePipeContextAccess() {
299
+ let expr = { type: 'PipeContextRef' };
300
+ // Check for .[index] syntax
301
+ if (this.check(TokenType.LBRACKET)) {
302
+ this.advance(); // consume [
303
+ const index = this.parseExpression();
304
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
305
+ expr = { type: 'IndexAccess', object: expr, index, optional: false };
306
+ }
307
+ else if (this.check(TokenType.IDENTIFIER)) {
308
+ // .field
309
+ const name = this.advance();
310
+ expr = {
311
+ type: 'MemberAccess',
312
+ object: expr,
313
+ property: name.value,
314
+ optional: false,
315
+ };
316
+ }
317
+ else {
318
+ // Just . by itself - return the pipe context
319
+ return expr;
320
+ }
321
+ // Allow chaining: .field.nested, .field[0], .field.method()
322
+ while (this.check(TokenType.DOT) || this.check(TokenType.LBRACKET)) {
323
+ if (this.match(TokenType.DOT)) {
324
+ if (this.check(TokenType.LBRACKET)) {
325
+ this.advance();
326
+ const idx = this.parseExpression();
327
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
328
+ expr = { type: 'IndexAccess', object: expr, index: idx, optional: false };
329
+ }
330
+ else {
331
+ const name = this.consume(TokenType.IDENTIFIER, 'Expected property name after "."');
332
+ expr = {
333
+ type: 'MemberAccess',
334
+ object: expr,
335
+ property: name.value,
336
+ optional: false,
337
+ };
338
+ }
339
+ }
340
+ else if (this.match(TokenType.LBRACKET)) {
341
+ const idx = this.parseExpression();
342
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
343
+ expr = { type: 'IndexAccess', object: expr, index: idx, optional: false };
344
+ }
345
+ // Handle method calls: .method()
346
+ if (this.check(TokenType.LPAREN)) {
347
+ this.advance();
348
+ const args = [];
349
+ if (!this.check(TokenType.RPAREN)) {
350
+ do {
351
+ args.push(this.parseExpression());
352
+ } while (this.match(TokenType.COMMA));
353
+ }
354
+ this.consume(TokenType.RPAREN, 'Expected ")"');
355
+ expr = { type: 'CallExpression', callee: expr, arguments: args };
356
+ }
357
+ }
358
+ return expr;
359
+ }
360
+ // Parse .field access inside arrow function body (DOT already consumed)
361
+ // Returns param-based expression: .field becomes param.field, .[0] becomes param[0]
362
+ parseArrowContextAccess() {
363
+ const paramName = this.arrowParamStack[this.arrowParamStack.length - 1];
364
+ let expr = { type: 'Identifier', name: paramName };
365
+ // Check for .[index] syntax
366
+ if (this.check(TokenType.LBRACKET)) {
367
+ this.advance(); // consume [
368
+ const index = this.parseExpression();
369
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
370
+ expr = { type: 'IndexAccess', object: expr, index, optional: false };
371
+ }
372
+ else if (this.check(TokenType.IDENTIFIER)) {
373
+ // .field
374
+ const name = this.advance();
375
+ expr = {
376
+ type: 'MemberAccess',
377
+ object: expr,
378
+ property: name.value,
379
+ optional: false,
380
+ };
381
+ }
382
+ else {
383
+ // Just . by itself - return the parameter identifier
384
+ return expr;
385
+ }
386
+ // Allow chaining: .field.nested, .field[0], .field.method()
387
+ while (this.check(TokenType.DOT) || this.check(TokenType.LBRACKET)) {
388
+ if (this.match(TokenType.DOT)) {
389
+ if (this.check(TokenType.LBRACKET)) {
390
+ this.advance();
391
+ const idx = this.parseExpression();
392
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
393
+ expr = { type: 'IndexAccess', object: expr, index: idx, optional: false };
394
+ }
395
+ else {
396
+ const name = this.consume(TokenType.IDENTIFIER, 'Expected property name after "."');
397
+ expr = {
398
+ type: 'MemberAccess',
399
+ object: expr,
400
+ property: name.value,
401
+ optional: false,
402
+ };
403
+ }
404
+ }
405
+ else if (this.match(TokenType.LBRACKET)) {
406
+ const idx = this.parseExpression();
407
+ this.consume(TokenType.RBRACKET, 'Expected "]"');
408
+ expr = { type: 'IndexAccess', object: expr, index: idx, optional: false };
409
+ }
410
+ // Handle method calls: .method()
411
+ if (this.check(TokenType.LPAREN)) {
412
+ this.advance();
413
+ const args = [];
414
+ if (!this.check(TokenType.RPAREN)) {
415
+ do {
416
+ args.push(this.parseExpression());
417
+ } while (this.match(TokenType.COMMA));
418
+ }
419
+ this.consume(TokenType.RPAREN, 'Expected ")"');
420
+ expr = { type: 'CallExpression', callee: expr, arguments: args };
421
+ }
422
+ }
423
+ return expr;
424
+ }
425
+ // Ternary: condition ? then : else
426
+ parseTernary() {
427
+ const test = this.parseLogicalOr();
428
+ if (this.match(TokenType.QUESTION)) {
429
+ const consequent = this.parseExpression();
430
+ this.consume(TokenType.COLON, 'Expected ":" in ternary expression');
431
+ const alternate = this.parseTernary();
432
+ return {
433
+ type: 'TernaryExpression',
434
+ test,
435
+ consequent,
436
+ alternate,
437
+ };
438
+ }
439
+ return test;
440
+ }
441
+ // Logical OR: a || b, a or b
442
+ parseLogicalOr() {
443
+ let left = this.parseLogicalAnd();
444
+ while (this.match(TokenType.OR_OR) || this.match(TokenType.OR)) {
445
+ const _operator = this.previous().value;
446
+ const right = this.parseLogicalAnd();
447
+ left = { type: 'BinaryExpression', operator: '||', left, right };
448
+ }
449
+ return left;
450
+ }
451
+ // Logical AND: a && b, a and b
452
+ parseLogicalAnd() {
453
+ let left = this.parseEquality();
454
+ while (this.match(TokenType.AND_AND) || this.match(TokenType.AND)) {
455
+ const _operator = this.previous().value;
456
+ const right = this.parseEquality();
457
+ left = { type: 'BinaryExpression', operator: '&&', left, right };
458
+ }
459
+ return left;
460
+ }
461
+ // Equality: a == b, a != b
462
+ parseEquality() {
463
+ let left = this.parseComparison();
464
+ while (this.match(TokenType.EQ) ||
465
+ this.match(TokenType.NEQ) ||
466
+ this.match(TokenType.STRICT_EQ) ||
467
+ this.match(TokenType.STRICT_NEQ)) {
468
+ const operator = this.previous().value;
469
+ const right = this.parseComparison();
470
+ left = { type: 'BinaryExpression', operator, left, right };
471
+ }
472
+ return left;
473
+ }
474
+ // Comparison: a < b, a >= b, a in b
475
+ parseComparison() {
476
+ let left = this.parseNullCoalesce();
477
+ while (this.match(TokenType.LT) ||
478
+ this.match(TokenType.GT) ||
479
+ this.match(TokenType.LTE) ||
480
+ this.match(TokenType.GTE) ||
481
+ this.match(TokenType.IN)) {
482
+ const operator = this.previous().value;
483
+ const right = this.parseNullCoalesce();
484
+ left = { type: 'BinaryExpression', operator, left, right };
485
+ }
486
+ return left;
487
+ }
488
+ // Null coalescing: a ?? b
489
+ parseNullCoalesce() {
490
+ let left = this.parseAdditive();
491
+ while (this.match(TokenType.QUESTION_QUESTION)) {
492
+ const right = this.parseAdditive();
493
+ left = { type: 'NullCoalesce', left, right };
494
+ }
495
+ return left;
496
+ }
497
+ // Addition/Subtraction/Concatenation: a + b, a - b, a & b
498
+ parseAdditive() {
499
+ let left = this.parseMultiplicative();
500
+ while (this.match(TokenType.PLUS) ||
501
+ this.match(TokenType.MINUS) ||
502
+ this.match(TokenType.AMPERSAND)) {
503
+ const operator = this.previous().value;
504
+ const right = this.parseMultiplicative();
505
+ left = { type: 'BinaryExpression', operator, left, right };
506
+ }
507
+ return left;
508
+ }
509
+ // Multiplication/Division: a * b, a / b, a % b
510
+ parseMultiplicative() {
511
+ let left = this.parseUnary();
512
+ while (this.match(TokenType.STAR) ||
513
+ this.match(TokenType.SLASH) ||
514
+ this.match(TokenType.PERCENT)) {
515
+ const operator = this.previous().value;
516
+ const right = this.parseUnary();
517
+ left = { type: 'BinaryExpression', operator, left, right };
518
+ }
519
+ return left;
520
+ }
521
+ // Unary: !a, -a, +a, not a
522
+ parseUnary() {
523
+ if (this.match(TokenType.BANG) ||
524
+ this.match(TokenType.MINUS) ||
525
+ this.match(TokenType.PLUS) ||
526
+ this.match(TokenType.NOT)) {
527
+ const operator = this.previous().value;
528
+ const argument = this.parseUnary();
529
+ return { type: 'UnaryExpression', operator, argument, prefix: true };
530
+ }
531
+ return this.parsePostfix();
532
+ }
533
+ // Postfix: member access, index, call, etc.
534
+ parsePostfix() {
535
+ let expr = this.parsePrimary();
536
+ while (true) {
537
+ if (this.match(TokenType.DOT)) {
538
+ // Special case: [*].{ } for array mapping with object construction
539
+ if (expr.type === 'SpreadAccess' && this.match(TokenType.LBRACE)) {
540
+ const objectLiteral = this.parseObjectLiteral();
541
+ expr = { type: 'MapTransform', array: expr.object, template: objectLiteral };
542
+ }
543
+ else {
544
+ const property = this.consume(TokenType.IDENTIFIER, 'Expected property name after "."');
545
+ expr = { type: 'MemberAccess', object: expr, property: property.value, optional: false };
546
+ }
547
+ }
548
+ else if (this.match(TokenType.QUESTION_DOT)) {
549
+ const property = this.consume(TokenType.IDENTIFIER, 'Expected property name after "?."');
550
+ expr = { type: 'MemberAccess', object: expr, property: property.value, optional: true };
551
+ }
552
+ else if (this.match(TokenType.LBRACKET)) {
553
+ expr = this.parseIndexOrSliceOrFilter(expr, false);
554
+ }
555
+ else if (this.match(TokenType.QUESTION_BRACKET)) {
556
+ expr = this.parseIndexOrSliceOrFilter(expr, true);
557
+ }
558
+ else if (this.match(TokenType.LPAREN)) {
559
+ expr = this.parseCallExpression(expr);
560
+ }
561
+ else if (this.match(TokenType.BANG)) {
562
+ // Non-null assertion
563
+ expr = { type: 'NonNullAssertion', expression: expr };
564
+ }
565
+ else if (this.match(TokenType.AS)) {
566
+ const typeAnnotation = this.parseTypeAnnotation();
567
+ expr = { type: 'TypeAssertion', expression: expr, typeAnnotation };
568
+ }
569
+ else {
570
+ break;
571
+ }
572
+ }
573
+ return expr;
574
+ }
575
+ parseIndexOrSliceOrFilter(object, optional) {
576
+ // Check for spread: [*]
577
+ if (this.match(TokenType.STAR)) {
578
+ this.consume(TokenType.RBRACKET, 'Expected "]" after "*"');
579
+ return { type: 'SpreadAccess', object };
580
+ }
581
+ // Check for empty brackets [] - shorthand for [*]
582
+ if (this.match(TokenType.RBRACKET)) {
583
+ return { type: 'SpreadAccess', object };
584
+ }
585
+ // Check for filter: [? predicate] or just [predicate] where predicate is boolean
586
+ if (this.match(TokenType.QUESTION)) {
587
+ const predicate = this.parseExpression();
588
+ this.consume(TokenType.RBRACKET, 'Expected "]" after filter predicate');
589
+ return { type: 'FilterAccess', object, predicate };
590
+ }
591
+ // Check for slice: [start:end]
592
+ let start = null;
593
+ if (!this.check(TokenType.COLON) && !this.check(TokenType.RBRACKET)) {
594
+ start = this.parseExpression();
595
+ }
596
+ if (this.match(TokenType.COLON)) {
597
+ let end = null;
598
+ if (!this.check(TokenType.RBRACKET)) {
599
+ end = this.parseExpression();
600
+ }
601
+ this.consume(TokenType.RBRACKET, 'Expected "]" after slice');
602
+ return { type: 'SliceAccess', object, sliceStart: start, sliceEnd: end };
603
+ }
604
+ // Regular index access
605
+ this.consume(TokenType.RBRACKET, 'Expected "]" after index');
606
+ if (!start) {
607
+ // Safety fallback - shouldn't reach here since [] is handled above
608
+ return { type: 'SpreadAccess', object };
609
+ }
610
+ return { type: 'IndexAccess', object, index: start, optional };
611
+ }
612
+ parseCallExpression(callee) {
613
+ const args = [];
614
+ if (!this.check(TokenType.RPAREN)) {
615
+ do {
616
+ args.push(this.parseExpression());
617
+ } while (this.match(TokenType.COMMA));
618
+ }
619
+ this.consume(TokenType.RPAREN, 'Expected ")" after arguments');
620
+ return { type: 'CallExpression', callee, arguments: args };
621
+ }
622
+ // ===========================================================================
623
+ // PRIMARY EXPRESSIONS
624
+ // ===========================================================================
625
+ parsePrimary() {
626
+ // Literals
627
+ if (this.match(TokenType.NUMBER)) {
628
+ return { type: 'NumberLiteral', value: parseFloat(this.previous().value) };
629
+ }
630
+ if (this.match(TokenType.STRING)) {
631
+ return { type: 'StringLiteral', value: this.previous().value };
632
+ }
633
+ if (this.match(TokenType.TEMPLATE_STRING)) {
634
+ return this.parseTemplateLiteral(this.previous().value);
635
+ }
636
+ if (this.match(TokenType.TRUE)) {
637
+ return { type: 'BooleanLiteral', value: true };
638
+ }
639
+ if (this.match(TokenType.FALSE)) {
640
+ return { type: 'BooleanLiteral', value: false };
641
+ }
642
+ if (this.match(TokenType.NULL)) {
643
+ return { type: 'NullLiteral' };
644
+ }
645
+ if (this.match(TokenType.UNDEFINED)) {
646
+ return { type: 'UndefinedLiteral' };
647
+ }
648
+ // Context access
649
+ if (this.match(TokenType.DOLLAR_DOLLAR)) {
650
+ this.consume(TokenType.DOT, 'Expected "." after "$$"');
651
+ const name = this.consume(TokenType.IDENTIFIER, 'Expected binding name');
652
+ return { type: 'BindingAccess', name: name.value };
653
+ }
654
+ if (this.match(TokenType.DOLLAR)) {
655
+ if (this.match(TokenType.DOT)) {
656
+ if (this.check(TokenType.IDENTIFIER)) {
657
+ const name = this.advance();
658
+ return { type: 'RootAccess', path: name.value };
659
+ }
660
+ }
661
+ return { type: 'RootAccess', path: null };
662
+ }
663
+ if (this.match(TokenType.CARET)) {
664
+ if (this.match(TokenType.DOT)) {
665
+ if (this.check(TokenType.IDENTIFIER)) {
666
+ const name = this.advance();
667
+ return { type: 'ParentAccess', path: name.value };
668
+ }
669
+ }
670
+ return { type: 'ParentAccess', path: null };
671
+ }
672
+ if (this.match(TokenType.DOT)) {
673
+ // In pipe object context, .field refers to pipe value (PipeContextRef)
674
+ if (this.inPipeObjectContext) {
675
+ return this.parsePipeContextAccess();
676
+ }
677
+ // In arrow function body, .field refers to the first parameter
678
+ // e.g., orders.find(o => .price > 10) means o.price > 10
679
+ if (this.arrowParamStack.length > 0) {
680
+ return this.parseArrowContextAccess();
681
+ }
682
+ // Otherwise, .field refers to current input (CurrentAccess)
683
+ if (this.check(TokenType.IDENTIFIER)) {
684
+ const name = this.advance();
685
+ return { type: 'CurrentAccess', path: name.value };
686
+ }
687
+ return { type: 'CurrentAccess', path: null };
688
+ }
689
+ // Object literal
690
+ if (this.match(TokenType.LBRACE)) {
691
+ return this.parseObjectLiteral();
692
+ }
693
+ // Array literal
694
+ if (this.match(TokenType.LBRACKET)) {
695
+ return this.parseArrayLiteral();
696
+ }
697
+ // If expression
698
+ if (this.match(TokenType.IF)) {
699
+ return this.parseIfExpression();
700
+ }
701
+ // Parenthesized expression or arrow function
702
+ if (this.match(TokenType.LPAREN)) {
703
+ return this.parseParenOrArrow();
704
+ }
705
+ // Identifier (or potential arrow function with single param)
706
+ if (this.match(TokenType.IDENTIFIER)) {
707
+ const name = this.previous().value;
708
+ // Check for arrow function: identifier => expr
709
+ if (this.match(TokenType.ARROW)) {
710
+ // Push param to stack so .property inside body resolves to param.property
711
+ this.arrowParamStack.push(name);
712
+ const body = this.parseExpression();
713
+ this.arrowParamStack.pop();
714
+ return {
715
+ type: 'ArrowFunction',
716
+ params: [{ type: 'Parameter', name }],
717
+ body,
718
+ };
719
+ }
720
+ return { type: 'Identifier', name };
721
+ }
722
+ throw new ParseError(`Unexpected token: ${this.peek().value}`, this.peek());
723
+ }
724
+ parseTemplateLiteral(raw) {
725
+ const parts = [];
726
+ let current = '';
727
+ let i = 0;
728
+ while (i < raw.length) {
729
+ if (raw[i] === '$' && raw[i + 1] === '{') {
730
+ if (current) {
731
+ parts.push(current);
732
+ current = '';
733
+ }
734
+ // Find matching closing brace
735
+ let braceDepth = 1;
736
+ const exprStart = i + 2;
737
+ let j = exprStart;
738
+ while (j < raw.length && braceDepth > 0) {
739
+ if (raw[j] === '{')
740
+ braceDepth++;
741
+ if (raw[j] === '}')
742
+ braceDepth--;
743
+ j++;
744
+ }
745
+ const exprSource = raw.slice(exprStart, j - 1);
746
+ const exprParser = new Parser(exprSource);
747
+ const exprProgram = exprParser.parse();
748
+ if (exprProgram.expression) {
749
+ parts.push(exprProgram.expression);
750
+ }
751
+ i = j;
752
+ }
753
+ else {
754
+ current += raw[i];
755
+ i++;
756
+ }
757
+ }
758
+ if (current) {
759
+ parts.push(current);
760
+ }
761
+ return { type: 'TemplateLiteral', parts };
762
+ }
763
+ parseObjectLiteral() {
764
+ const properties = [];
765
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
766
+ // Spread property: ...expr or just ... (spread pipe context)
767
+ if (this.match(TokenType.SPREAD)) {
768
+ // In pipe context, bare ... spreads the pipe value
769
+ if (this.inPipeObjectContext &&
770
+ (this.check(TokenType.COMMA) || this.check(TokenType.RBRACE))) {
771
+ properties.push({ type: 'SpreadProperty', argument: { type: 'PipeContextRef' } });
772
+ }
773
+ else {
774
+ const argument = this.parseExpression();
775
+ properties.push({ type: 'SpreadProperty', argument });
776
+ }
777
+ }
778
+ // Computed property: [expr]: value
779
+ else if (this.match(TokenType.LBRACKET)) {
780
+ const key = this.parseExpression();
781
+ this.consume(TokenType.RBRACKET, 'Expected "]" after computed property key');
782
+ this.consume(TokenType.COLON, 'Expected ":" after computed property key');
783
+ const value = this.parseExpression();
784
+ properties.push({ type: 'ComputedProperty', key, value });
785
+ }
786
+ // String key or identifier
787
+ else {
788
+ let key;
789
+ if (this.match(TokenType.STRING)) {
790
+ key = this.previous().value;
791
+ }
792
+ else if (this.match(TokenType.IDENTIFIER)) {
793
+ key = this.previous().value;
794
+ }
795
+ else {
796
+ throw new ParseError('Expected property name', this.peek());
797
+ }
798
+ // Shorthand: { foo } means { foo: foo } in normal context
799
+ // But in pipe context: { foo } means { foo: .foo } (reference pipe value)
800
+ if (!this.check(TokenType.COLON)) {
801
+ if (this.inPipeObjectContext) {
802
+ // In pipe context, expand shorthand to reference pipe value
803
+ properties.push({
804
+ type: 'StandardProperty',
805
+ key,
806
+ value: {
807
+ type: 'MemberAccess',
808
+ object: { type: 'PipeContextRef' },
809
+ property: key,
810
+ optional: false,
811
+ },
812
+ });
813
+ }
814
+ else {
815
+ properties.push({ type: 'ShorthandProperty', key });
816
+ }
817
+ }
818
+ else {
819
+ this.consume(TokenType.COLON, 'Expected ":" after property key');
820
+ const value = this.parseExpression();
821
+ properties.push({ type: 'StandardProperty', key, value });
822
+ }
823
+ }
824
+ if (!this.check(TokenType.RBRACE)) {
825
+ this.consume(TokenType.COMMA, 'Expected "," between properties');
826
+ // Allow trailing comma
827
+ if (this.check(TokenType.RBRACE))
828
+ break;
829
+ }
830
+ }
831
+ this.consume(TokenType.RBRACE, 'Expected "}" after object literal');
832
+ return { type: 'ObjectLiteral', properties };
833
+ }
834
+ parseArrayLiteral() {
835
+ const elements = [];
836
+ while (!this.check(TokenType.RBRACKET) && !this.isAtEnd()) {
837
+ if (this.match(TokenType.SPREAD)) {
838
+ const argument = this.parseExpression();
839
+ elements.push({ type: 'SpreadElement', argument });
840
+ }
841
+ else {
842
+ elements.push(this.parseExpression());
843
+ }
844
+ if (!this.check(TokenType.RBRACKET)) {
845
+ this.consume(TokenType.COMMA, 'Expected "," between elements');
846
+ // Allow trailing comma
847
+ if (this.check(TokenType.RBRACKET))
848
+ break;
849
+ }
850
+ }
851
+ this.consume(TokenType.RBRACKET, 'Expected "]" after array literal');
852
+ return { type: 'ArrayLiteral', elements };
853
+ }
854
+ parseIfExpression() {
855
+ const conditions = [];
856
+ // First condition
857
+ this.consume(TokenType.LPAREN, 'Expected "(" after "if"');
858
+ const firstTest = this.parseExpression();
859
+ this.consume(TokenType.RPAREN, 'Expected ")" after condition');
860
+ let firstConsequent;
861
+ if (this.match(TokenType.LBRACE)) {
862
+ firstConsequent = this.parseBlockExpression();
863
+ }
864
+ else {
865
+ firstConsequent = this.parseExpression();
866
+ }
867
+ conditions.push({ type: 'ConditionalBranch', test: firstTest, consequent: firstConsequent });
868
+ // else if / else
869
+ let alternate = null;
870
+ while (this.match(TokenType.ELSE)) {
871
+ if (this.match(TokenType.IF)) {
872
+ // else if
873
+ this.consume(TokenType.LPAREN, 'Expected "(" after "else if"');
874
+ const test = this.parseExpression();
875
+ this.consume(TokenType.RPAREN, 'Expected ")" after condition');
876
+ let consequent;
877
+ if (this.match(TokenType.LBRACE)) {
878
+ consequent = this.parseBlockExpression();
879
+ }
880
+ else {
881
+ consequent = this.parseExpression();
882
+ }
883
+ conditions.push({ type: 'ConditionalBranch', test, consequent });
884
+ }
885
+ else {
886
+ // else
887
+ if (this.match(TokenType.LBRACE)) {
888
+ alternate = this.parseBlockExpression();
889
+ }
890
+ else {
891
+ alternate = this.parseExpression();
892
+ }
893
+ break;
894
+ }
895
+ }
896
+ return { type: 'IfExpression', conditions, alternate };
897
+ }
898
+ parseBlockExpression() {
899
+ // For now, just parse a single expression in the block
900
+ const expr = this.parseExpression();
901
+ this.consume(TokenType.RBRACE, 'Expected "}" after block');
902
+ return expr;
903
+ }
904
+ parseParenOrArrow() {
905
+ // Could be: (expr), (), (a, b) => expr
906
+ // Empty parens - must be arrow function with no params
907
+ if (this.match(TokenType.RPAREN)) {
908
+ this.consume(TokenType.ARROW, 'Expected "=>" after "()"');
909
+ // No params, so no implicit property access available
910
+ const body = this.parseExpression();
911
+ return { type: 'ArrowFunction', params: [], body };
912
+ }
913
+ // Parse first expression
914
+ const first = this.parseExpression();
915
+ // Check for comma - indicates arrow function params
916
+ if (this.match(TokenType.COMMA)) {
917
+ const params = [this.exprToParam(first)];
918
+ do {
919
+ const param = this.parseExpression();
920
+ params.push(this.exprToParam(param));
921
+ } while (this.match(TokenType.COMMA));
922
+ this.consume(TokenType.RPAREN, 'Expected ")" after parameters');
923
+ this.consume(TokenType.ARROW, 'Expected "=>" after parameters');
924
+ // Push first param to stack so .property inside body resolves to param.property
925
+ this.arrowParamStack.push(params[0].name);
926
+ const body = this.parseExpression();
927
+ this.arrowParamStack.pop();
928
+ return { type: 'ArrowFunction', params, body };
929
+ }
930
+ this.consume(TokenType.RPAREN, 'Expected ")"');
931
+ // Check for arrow
932
+ if (this.match(TokenType.ARROW)) {
933
+ const params = [this.exprToParam(first)];
934
+ // Push param to stack so .property inside body resolves to param.property
935
+ this.arrowParamStack.push(params[0].name);
936
+ const body = this.parseExpression();
937
+ this.arrowParamStack.pop();
938
+ return { type: 'ArrowFunction', params, body };
939
+ }
940
+ // Just a parenthesized expression
941
+ return first;
942
+ }
943
+ exprToParam(expr) {
944
+ if (expr.type === 'Identifier') {
945
+ return { type: 'Parameter', name: expr.name };
946
+ }
947
+ throw new ParseError('Expected parameter name', this.peek());
948
+ }
949
+ // ===========================================================================
950
+ // TYPE ANNOTATIONS
951
+ // ===========================================================================
952
+ parseTypeAnnotation() {
953
+ const type = this.parsePrimaryType();
954
+ // Union type: string | number
955
+ if (this.check(TokenType.PIPE)) {
956
+ const types = [type];
957
+ while (this.match(TokenType.PIPE)) {
958
+ types.push(this.parsePrimaryType());
959
+ }
960
+ return { type: 'UnionType', types };
961
+ }
962
+ return type;
963
+ }
964
+ parsePrimaryType() {
965
+ if (this.match(TokenType.IDENTIFIER)) {
966
+ const name = this.previous().value;
967
+ // Check for primitive types
968
+ if (['string', 'number', 'boolean', 'null', 'any'].includes(name)) {
969
+ const nonNull = this.match(TokenType.BANG);
970
+ return { type: 'PrimitiveType', name: name, nonNull };
971
+ }
972
+ // Array<T>
973
+ if (name === 'Array' && this.match(TokenType.LT)) {
974
+ const elementType = this.parseTypeAnnotation();
975
+ this.consume(TokenType.GT, 'Expected ">" after array element type');
976
+ return { type: 'ArrayType', elementType };
977
+ }
978
+ // Type reference
979
+ return { type: 'TypeReference', name };
980
+ }
981
+ // Object type: { key: type }
982
+ if (this.match(TokenType.LBRACE)) {
983
+ const properties = [];
984
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
985
+ const key = this.consume(TokenType.IDENTIFIER, 'Expected property name');
986
+ const optional = this.match(TokenType.QUESTION);
987
+ this.consume(TokenType.COLON, 'Expected ":" after property name');
988
+ const valueType = this.parseTypeAnnotation();
989
+ properties.push({ type: 'ObjectTypeProperty', key: key.value, valueType, optional });
990
+ if (!this.check(TokenType.RBRACE)) {
991
+ this.consume(TokenType.COMMA, 'Expected "," between type properties');
992
+ }
993
+ }
994
+ this.consume(TokenType.RBRACE, 'Expected "}" after object type');
995
+ return { type: 'ObjectType', properties };
996
+ }
997
+ throw new ParseError('Expected type annotation', this.peek());
998
+ }
999
+ // ===========================================================================
1000
+ // HELPER METHODS
1001
+ // ===========================================================================
1002
+ isAtEnd() {
1003
+ return this.peek().type === TokenType.EOF;
1004
+ }
1005
+ peek() {
1006
+ return this.tokens[this.current];
1007
+ }
1008
+ peekNext() {
1009
+ if (this.current + 1 >= this.tokens.length) {
1010
+ return this.tokens[this.tokens.length - 1]; // Return EOF
1011
+ }
1012
+ return this.tokens[this.current + 1];
1013
+ }
1014
+ previous() {
1015
+ return this.tokens[this.current - 1];
1016
+ }
1017
+ advance() {
1018
+ if (!this.isAtEnd())
1019
+ this.current++;
1020
+ return this.previous();
1021
+ }
1022
+ check(type) {
1023
+ if (this.isAtEnd())
1024
+ return false;
1025
+ return this.peek().type === type;
1026
+ }
1027
+ match(...types) {
1028
+ for (const type of types) {
1029
+ if (this.check(type)) {
1030
+ this.advance();
1031
+ return true;
1032
+ }
1033
+ }
1034
+ return false;
1035
+ }
1036
+ consume(type, message) {
1037
+ if (this.check(type))
1038
+ return this.advance();
1039
+ throw new ParseError(message, this.peek(), TokenType[type]);
1040
+ }
1041
+ }
1042
+ export function parse(input) {
1043
+ return new Parser(input).parse();
1044
+ }
1045
+ //# sourceMappingURL=parser.js.map