agency-lang 0.0.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 (76) hide show
  1. package/README.md +7 -0
  2. package/dist/backends/baseGenerator.js +194 -0
  3. package/dist/backends/graphGenerator.integration.test.js +119 -0
  4. package/dist/backends/graphGenerator.js +308 -0
  5. package/dist/backends/index.js +3 -0
  6. package/dist/backends/typescriptGenerator/builtins.js +46 -0
  7. package/dist/backends/typescriptGenerator/typeToString.js +36 -0
  8. package/dist/backends/typescriptGenerator/typeToZodSchema.js +54 -0
  9. package/dist/backends/typescriptGenerator.integration.test.js +119 -0
  10. package/dist/backends/typescriptGenerator.js +340 -0
  11. package/dist/backends/typescriptGenerator.test.js +763 -0
  12. package/dist/backends/utils.js +6 -0
  13. package/dist/generate-graph-file.js +25 -0
  14. package/dist/generate-ts-file.js +26 -0
  15. package/dist/index.js +3 -0
  16. package/dist/parser.js +38 -0
  17. package/dist/parser.test.js +306 -0
  18. package/dist/parsers/access.js +19 -0
  19. package/dist/parsers/access.test.js +929 -0
  20. package/dist/parsers/assignment.js +8 -0
  21. package/dist/parsers/body.test.js +106 -0
  22. package/dist/parsers/comment.js +6 -0
  23. package/dist/parsers/comment.test.js +100 -0
  24. package/dist/parsers/dataStructures.js +14 -0
  25. package/dist/parsers/dataStructures.test.js +660 -0
  26. package/dist/parsers/function.js +22 -0
  27. package/dist/parsers/function.test.js +591 -0
  28. package/dist/parsers/functionCall.js +10 -0
  29. package/dist/parsers/functionCall.test.js +119 -0
  30. package/dist/parsers/importStatement.js +3 -0
  31. package/dist/parsers/importStatement.test.js +290 -0
  32. package/dist/parsers/literals.js +12 -0
  33. package/dist/parsers/literals.test.js +639 -0
  34. package/dist/parsers/matchBlock.js +24 -0
  35. package/dist/parsers/matchBlock.test.js +506 -0
  36. package/dist/parsers/parserUtils.js +2 -0
  37. package/dist/parsers/returnStatement.js +8 -0
  38. package/dist/parsers/tools.js +3 -0
  39. package/dist/parsers/tools.test.js +170 -0
  40. package/dist/parsers/typeHints.js +59 -0
  41. package/dist/parsers/typeHints.test.js +2754 -0
  42. package/dist/parsers/utils.js +5 -0
  43. package/dist/parsers/whileLoop.test.js +342 -0
  44. package/dist/templates/backends/graphGenerator/builtinTools.js +36 -0
  45. package/dist/templates/backends/graphGenerator/conditionalEdge.js +10 -0
  46. package/dist/templates/backends/graphGenerator/edge.js +10 -0
  47. package/dist/templates/backends/graphGenerator/goToNode.js +9 -0
  48. package/dist/templates/backends/graphGenerator/graphNode.js +15 -0
  49. package/dist/templates/backends/graphGenerator/imports.js +47 -0
  50. package/dist/templates/backends/graphGenerator/node.js +18 -0
  51. package/dist/templates/backends/graphGenerator/promptNode.js +16 -0
  52. package/dist/templates/backends/graphGenerator/startNode.js +10 -0
  53. package/dist/templates/backends/typescriptGenerator/builtinFunctions/fetch.js +17 -0
  54. package/dist/templates/backends/typescriptGenerator/builtinFunctions/fetchJSON.js +17 -0
  55. package/dist/templates/backends/typescriptGenerator/builtinFunctions/input.js +21 -0
  56. package/dist/templates/backends/typescriptGenerator/builtinFunctions/read.js +13 -0
  57. package/dist/templates/backends/typescriptGenerator/builtinTools.js +36 -0
  58. package/dist/templates/backends/typescriptGenerator/functionDefinition.js +11 -0
  59. package/dist/templates/backends/typescriptGenerator/imports.js +25 -0
  60. package/dist/templates/backends/typescriptGenerator/promptFunction.js +76 -0
  61. package/dist/templates/backends/typescriptGenerator/tool.js +23 -0
  62. package/dist/templates/backends/typescriptGenerator/toolCall.js +35 -0
  63. package/dist/types/access.js +1 -0
  64. package/dist/types/dataStructures.js +1 -0
  65. package/dist/types/function.js +1 -0
  66. package/dist/types/graphNode.js +1 -0
  67. package/dist/types/importStatement.js +1 -0
  68. package/dist/types/literals.js +1 -0
  69. package/dist/types/matchBlock.js +1 -0
  70. package/dist/types/returnStatement.js +1 -0
  71. package/dist/types/tools.js +1 -0
  72. package/dist/types/typeHints.js +1 -0
  73. package/dist/types/whileLoop.js +1 -0
  74. package/dist/types.js +11 -0
  75. package/dist/utils.js +18 -0
  76. package/package.json +52 -0
@@ -0,0 +1,929 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { dotPropertyParser, indexAccessParser, dotFunctionCallParser, accessExpressionParser, } from "./access.js";
3
+ describe("access expression parsers", () => {
4
+ describe("dotPropertyParser", () => {
5
+ const testCases = [
6
+ // Happy path - variable name with property
7
+ {
8
+ input: "obj.foo",
9
+ expected: {
10
+ success: true,
11
+ result: {
12
+ type: "dotProperty",
13
+ object: { type: "variableName", value: "obj" },
14
+ propertyName: "foo",
15
+ },
16
+ },
17
+ },
18
+ {
19
+ input: "response.status",
20
+ expected: {
21
+ success: true,
22
+ result: {
23
+ type: "dotProperty",
24
+ object: { type: "variableName", value: "response" },
25
+ propertyName: "status",
26
+ },
27
+ },
28
+ },
29
+ {
30
+ input: "user.name",
31
+ expected: {
32
+ success: true,
33
+ result: {
34
+ type: "dotProperty",
35
+ object: { type: "variableName", value: "user" },
36
+ propertyName: "name",
37
+ },
38
+ },
39
+ },
40
+ // Edge cases - single character names
41
+ {
42
+ input: "x.y",
43
+ expected: {
44
+ success: true,
45
+ result: {
46
+ type: "dotProperty",
47
+ object: { type: "variableName", value: "x" },
48
+ propertyName: "y",
49
+ },
50
+ },
51
+ },
52
+ {
53
+ input: "a.b",
54
+ expected: {
55
+ success: true,
56
+ result: {
57
+ type: "dotProperty",
58
+ object: { type: "variableName", value: "a" },
59
+ propertyName: "b",
60
+ },
61
+ },
62
+ },
63
+ // Property names with numbers
64
+ {
65
+ input: "obj.prop123",
66
+ expected: {
67
+ success: true,
68
+ result: {
69
+ type: "dotProperty",
70
+ object: { type: "variableName", value: "obj" },
71
+ propertyName: "prop123",
72
+ },
73
+ },
74
+ },
75
+ {
76
+ input: "data.field2",
77
+ expected: {
78
+ success: true,
79
+ result: {
80
+ type: "dotProperty",
81
+ object: { type: "variableName", value: "data" },
82
+ propertyName: "field2",
83
+ },
84
+ },
85
+ },
86
+ // Note: Function calls as objects like "fetch().body" are not currently supported
87
+ // by this parser due to the order of parsers in the or() combinator on line 24.
88
+ // The literalParser matches the function name as a variableName before
89
+ // functionCallParser gets a chance to parse the full function call.
90
+ // This parser still uses or(literalParser, functionCallParser) instead of
91
+ // or(functionCallParser, literalParser).
92
+ // Note: Number literals with property access like "42.toString" are not supported
93
+ // because the dot is consumed as part of the number (decimal point).
94
+ // String literal as object
95
+ {
96
+ input: '"hello".length',
97
+ expected: {
98
+ success: true,
99
+ result: {
100
+ type: "dotProperty",
101
+ object: { type: "string", value: "hello" },
102
+ propertyName: "length",
103
+ },
104
+ },
105
+ },
106
+ // Failure cases
107
+ { input: "obj.", expected: { success: false } },
108
+ { input: ".property", expected: { success: false } },
109
+ { input: "obj", expected: { success: false } },
110
+ { input: "", expected: { success: false } },
111
+ { input: ".", expected: { success: false } },
112
+ // Note: "obj.123" actually parses successfully because alphanum includes digits
113
+ { input: "obj..prop", expected: { success: false } },
114
+ ];
115
+ testCases.forEach(({ input, expected }) => {
116
+ if (expected.success) {
117
+ it(`should parse "${input}" successfully`, () => {
118
+ const result = dotPropertyParser(input);
119
+ expect(result.success).toBe(true);
120
+ if (result.success) {
121
+ expect(result.result).toEqual(expected.result);
122
+ }
123
+ });
124
+ }
125
+ else {
126
+ it(`should fail to parse "${input}"`, () => {
127
+ const result = dotPropertyParser(input);
128
+ expect(result.success).toBe(false);
129
+ });
130
+ }
131
+ });
132
+ });
133
+ describe("indexAccessParser", () => {
134
+ const testCases = [
135
+ // Happy path - variable with number index
136
+ {
137
+ input: "arr[0]",
138
+ expected: {
139
+ success: true,
140
+ result: {
141
+ type: "indexAccess",
142
+ array: { type: "variableName", value: "arr" },
143
+ index: { type: "number", value: "0" },
144
+ },
145
+ },
146
+ },
147
+ {
148
+ input: "items[5]",
149
+ expected: {
150
+ success: true,
151
+ result: {
152
+ type: "indexAccess",
153
+ array: { type: "variableName", value: "items" },
154
+ index: { type: "number", value: "5" },
155
+ },
156
+ },
157
+ },
158
+ {
159
+ input: "data[42]",
160
+ expected: {
161
+ success: true,
162
+ result: {
163
+ type: "indexAccess",
164
+ array: { type: "variableName", value: "data" },
165
+ index: { type: "number", value: "42" },
166
+ },
167
+ },
168
+ },
169
+ // Variable as index
170
+ {
171
+ input: "arr[i]",
172
+ expected: {
173
+ success: true,
174
+ result: {
175
+ type: "indexAccess",
176
+ array: { type: "variableName", value: "arr" },
177
+ index: { type: "variableName", value: "i" },
178
+ },
179
+ },
180
+ },
181
+ {
182
+ input: "items[index]",
183
+ expected: {
184
+ success: true,
185
+ result: {
186
+ type: "indexAccess",
187
+ array: { type: "variableName", value: "items" },
188
+ index: { type: "variableName", value: "index" },
189
+ },
190
+ },
191
+ },
192
+ // String as index (for object/map access)
193
+ {
194
+ input: 'obj["key"]',
195
+ expected: {
196
+ success: true,
197
+ result: {
198
+ type: "indexAccess",
199
+ array: { type: "variableName", value: "obj" },
200
+ index: { type: "string", value: "key" },
201
+ },
202
+ },
203
+ },
204
+ {
205
+ input: 'data["field"]',
206
+ expected: {
207
+ success: true,
208
+ result: {
209
+ type: "indexAccess",
210
+ array: { type: "variableName", value: "data" },
211
+ index: { type: "string", value: "field" },
212
+ },
213
+ },
214
+ },
215
+ // Function call as array
216
+ {
217
+ input: "getData()[0]",
218
+ expected: {
219
+ success: true,
220
+ result: {
221
+ type: "indexAccess",
222
+ array: {
223
+ type: "functionCall",
224
+ functionName: "getData",
225
+ arguments: [],
226
+ },
227
+ index: { type: "number", value: "0" },
228
+ },
229
+ },
230
+ },
231
+ {
232
+ input: "fetchItems()[5]",
233
+ expected: {
234
+ success: true,
235
+ result: {
236
+ type: "indexAccess",
237
+ array: {
238
+ type: "functionCall",
239
+ functionName: "fetchItems",
240
+ arguments: [],
241
+ },
242
+ index: { type: "number", value: "5" },
243
+ },
244
+ },
245
+ },
246
+ // Function call as index
247
+ {
248
+ input: "arr[getIndex()]",
249
+ expected: {
250
+ success: true,
251
+ result: {
252
+ type: "indexAccess",
253
+ array: { type: "variableName", value: "arr" },
254
+ index: {
255
+ type: "functionCall",
256
+ functionName: "getIndex",
257
+ arguments: [],
258
+ },
259
+ },
260
+ },
261
+ },
262
+ {
263
+ input: "items[calculatePosition()]",
264
+ expected: {
265
+ success: true,
266
+ result: {
267
+ type: "indexAccess",
268
+ array: { type: "variableName", value: "items" },
269
+ index: {
270
+ type: "functionCall",
271
+ functionName: "calculatePosition",
272
+ arguments: [],
273
+ },
274
+ },
275
+ },
276
+ },
277
+ // Function call with arguments as array
278
+ {
279
+ input: 'getUsers("active")[0]',
280
+ expected: {
281
+ success: true,
282
+ result: {
283
+ type: "indexAccess",
284
+ array: {
285
+ type: "functionCall",
286
+ functionName: "getUsers",
287
+ arguments: [{ type: "string", value: "active" }],
288
+ },
289
+ index: { type: "number", value: "0" },
290
+ },
291
+ },
292
+ },
293
+ // Edge cases - negative index
294
+ {
295
+ input: "arr[-1]",
296
+ expected: {
297
+ success: true,
298
+ result: {
299
+ type: "indexAccess",
300
+ array: { type: "variableName", value: "arr" },
301
+ index: { type: "number", value: "-1" },
302
+ },
303
+ },
304
+ },
305
+ // Edge cases - single character names
306
+ {
307
+ input: "a[0]",
308
+ expected: {
309
+ success: true,
310
+ result: {
311
+ type: "indexAccess",
312
+ array: { type: "variableName", value: "a" },
313
+ index: { type: "number", value: "0" },
314
+ },
315
+ },
316
+ },
317
+ {
318
+ input: "x[i]",
319
+ expected: {
320
+ success: true,
321
+ result: {
322
+ type: "indexAccess",
323
+ array: { type: "variableName", value: "x" },
324
+ index: { type: "variableName", value: "i" },
325
+ },
326
+ },
327
+ },
328
+ // Failure cases
329
+ { input: "arr[]", expected: { success: false } },
330
+ { input: "arr[", expected: { success: false } },
331
+ { input: "arr]", expected: { success: false } },
332
+ { input: "[0]", expected: { success: false } },
333
+ { input: "arr[0", expected: { success: false } },
334
+ { input: "", expected: { success: false } },
335
+ { input: "[]", expected: { success: false } },
336
+ { input: "[", expected: { success: false } },
337
+ { input: "]", expected: { success: false } },
338
+ ];
339
+ testCases.forEach(({ input, expected }) => {
340
+ if (expected.success) {
341
+ it(`should parse "${input}" successfully`, () => {
342
+ const result = indexAccessParser(input);
343
+ expect(result.success).toBe(true);
344
+ if (result.success) {
345
+ expect(result.result).toEqual(expected.result);
346
+ }
347
+ });
348
+ }
349
+ else {
350
+ it(`should fail to parse "${input}"`, () => {
351
+ const result = indexAccessParser(input);
352
+ expect(result.success).toBe(false);
353
+ });
354
+ }
355
+ });
356
+ });
357
+ describe("dotFunctionCallParser", () => {
358
+ const testCases = [
359
+ // Happy path - variable with method call
360
+ {
361
+ input: "story.json()",
362
+ expected: {
363
+ success: true,
364
+ result: {
365
+ type: "dotFunctionCall",
366
+ object: { type: "variableName", value: "story" },
367
+ functionCall: {
368
+ type: "functionCall",
369
+ functionName: "json",
370
+ arguments: [],
371
+ },
372
+ },
373
+ },
374
+ },
375
+ {
376
+ input: "response.text()",
377
+ expected: {
378
+ success: true,
379
+ result: {
380
+ type: "dotFunctionCall",
381
+ object: { type: "variableName", value: "response" },
382
+ functionCall: {
383
+ type: "functionCall",
384
+ functionName: "text",
385
+ arguments: [],
386
+ },
387
+ },
388
+ },
389
+ },
390
+ {
391
+ input: "obj.getData()",
392
+ expected: {
393
+ success: true,
394
+ result: {
395
+ type: "dotFunctionCall",
396
+ object: { type: "variableName", value: "obj" },
397
+ functionCall: {
398
+ type: "functionCall",
399
+ functionName: "getData",
400
+ arguments: [],
401
+ },
402
+ },
403
+ },
404
+ },
405
+ // Method call with arguments
406
+ {
407
+ input: "obj.method(42)",
408
+ expected: {
409
+ success: true,
410
+ result: {
411
+ type: "dotFunctionCall",
412
+ object: { type: "variableName", value: "obj" },
413
+ functionCall: {
414
+ type: "functionCall",
415
+ functionName: "method",
416
+ arguments: [{ type: "number", value: "42" }],
417
+ },
418
+ },
419
+ },
420
+ },
421
+ {
422
+ input: 'str.split(",")',
423
+ expected: {
424
+ success: true,
425
+ result: {
426
+ type: "dotFunctionCall",
427
+ object: { type: "variableName", value: "str" },
428
+ functionCall: {
429
+ type: "functionCall",
430
+ functionName: "split",
431
+ arguments: [{ type: "string", value: "," }],
432
+ },
433
+ },
434
+ },
435
+ },
436
+ {
437
+ input: "arr.slice(0)",
438
+ expected: {
439
+ success: true,
440
+ result: {
441
+ type: "dotFunctionCall",
442
+ object: { type: "variableName", value: "arr" },
443
+ functionCall: {
444
+ type: "functionCall",
445
+ functionName: "slice",
446
+ arguments: [{ type: "number", value: "0" }],
447
+ },
448
+ },
449
+ },
450
+ },
451
+ // Multiple arguments
452
+ {
453
+ input: "obj.calculate(1, 2)",
454
+ expected: {
455
+ success: true,
456
+ result: {
457
+ type: "dotFunctionCall",
458
+ object: { type: "variableName", value: "obj" },
459
+ functionCall: {
460
+ type: "functionCall",
461
+ functionName: "calculate",
462
+ arguments: [
463
+ { type: "number", value: "1" },
464
+ { type: "number", value: "2" },
465
+ ],
466
+ },
467
+ },
468
+ },
469
+ },
470
+ {
471
+ input: 'str.replace("old", "new")',
472
+ expected: {
473
+ success: true,
474
+ result: {
475
+ type: "dotFunctionCall",
476
+ object: { type: "variableName", value: "str" },
477
+ functionCall: {
478
+ type: "functionCall",
479
+ functionName: "replace",
480
+ arguments: [
481
+ { type: "string", value: "old" },
482
+ { type: "string", value: "new" },
483
+ ],
484
+ },
485
+ },
486
+ },
487
+ },
488
+ // Variable as argument
489
+ {
490
+ input: "obj.process(data)",
491
+ expected: {
492
+ success: true,
493
+ result: {
494
+ type: "dotFunctionCall",
495
+ object: { type: "variableName", value: "obj" },
496
+ functionCall: {
497
+ type: "functionCall",
498
+ functionName: "process",
499
+ arguments: [{ type: "variableName", value: "data" }],
500
+ },
501
+ },
502
+ },
503
+ },
504
+ // Chained function calls
505
+ {
506
+ input: "fetch().json()",
507
+ expected: {
508
+ success: true,
509
+ result: {
510
+ type: "dotFunctionCall",
511
+ object: {
512
+ type: "functionCall",
513
+ functionName: "fetch",
514
+ arguments: [],
515
+ },
516
+ functionCall: {
517
+ type: "functionCall",
518
+ functionName: "json",
519
+ arguments: [],
520
+ },
521
+ },
522
+ },
523
+ },
524
+ {
525
+ input: "getData().process()",
526
+ expected: {
527
+ success: true,
528
+ result: {
529
+ type: "dotFunctionCall",
530
+ object: {
531
+ type: "functionCall",
532
+ functionName: "getData",
533
+ arguments: [],
534
+ },
535
+ functionCall: {
536
+ type: "functionCall",
537
+ functionName: "process",
538
+ arguments: [],
539
+ },
540
+ },
541
+ },
542
+ },
543
+ {
544
+ input: "getResponse().text()",
545
+ expected: {
546
+ success: true,
547
+ result: {
548
+ type: "dotFunctionCall",
549
+ object: {
550
+ type: "functionCall",
551
+ functionName: "getResponse",
552
+ arguments: [],
553
+ },
554
+ functionCall: {
555
+ type: "functionCall",
556
+ functionName: "text",
557
+ arguments: [],
558
+ },
559
+ },
560
+ },
561
+ },
562
+ // Chained function calls with arguments
563
+ {
564
+ input: 'fetch("url").json()',
565
+ expected: {
566
+ success: true,
567
+ result: {
568
+ type: "dotFunctionCall",
569
+ object: {
570
+ type: "functionCall",
571
+ functionName: "fetch",
572
+ arguments: [{ type: "string", value: "url" }],
573
+ },
574
+ functionCall: {
575
+ type: "functionCall",
576
+ functionName: "json",
577
+ arguments: [],
578
+ },
579
+ },
580
+ },
581
+ },
582
+ {
583
+ input: "getUser(42).getName()",
584
+ expected: {
585
+ success: true,
586
+ result: {
587
+ type: "dotFunctionCall",
588
+ object: {
589
+ type: "functionCall",
590
+ functionName: "getUser",
591
+ arguments: [{ type: "number", value: "42" }],
592
+ },
593
+ functionCall: {
594
+ type: "functionCall",
595
+ functionName: "getName",
596
+ arguments: [],
597
+ },
598
+ },
599
+ },
600
+ },
601
+ // Edge cases - single character names
602
+ {
603
+ input: "x.f()",
604
+ expected: {
605
+ success: true,
606
+ result: {
607
+ type: "dotFunctionCall",
608
+ object: { type: "variableName", value: "x" },
609
+ functionCall: {
610
+ type: "functionCall",
611
+ functionName: "f",
612
+ arguments: [],
613
+ },
614
+ },
615
+ },
616
+ },
617
+ // String literal as object
618
+ {
619
+ input: '"hello".toUpperCase()',
620
+ expected: {
621
+ success: true,
622
+ result: {
623
+ type: "dotFunctionCall",
624
+ object: { type: "string", value: "hello" },
625
+ functionCall: {
626
+ type: "functionCall",
627
+ functionName: "toUpperCase",
628
+ arguments: [],
629
+ },
630
+ },
631
+ },
632
+ },
633
+ // Failure cases
634
+ { input: "obj.method", expected: { success: false } }, // missing ()
635
+ { input: "obj.()", expected: { success: false } }, // missing method name
636
+ { input: ".method()", expected: { success: false } }, // missing object
637
+ { input: "obj.", expected: { success: false } },
638
+ { input: "method()", expected: { success: false } }, // no dot
639
+ { input: "", expected: { success: false } },
640
+ { input: "obj.method(", expected: { success: false } }, // unclosed paren
641
+ { input: "obj.method)", expected: { success: false } }, // no opening paren
642
+ ];
643
+ testCases.forEach(({ input, expected }) => {
644
+ if (expected.success) {
645
+ it(`should parse "${input}" successfully`, () => {
646
+ const result = dotFunctionCallParser(input);
647
+ expect(result.success).toBe(true);
648
+ if (result.success) {
649
+ expect(result.result).toEqual(expected.result);
650
+ }
651
+ });
652
+ }
653
+ else {
654
+ it(`should fail to parse "${input}"`, () => {
655
+ const result = dotFunctionCallParser(input);
656
+ expect(result.success).toBe(false);
657
+ });
658
+ }
659
+ });
660
+ });
661
+ describe("accessExpressionParser", () => {
662
+ const testCases = [
663
+ // Dot property access
664
+ {
665
+ input: "obj.foo",
666
+ expected: {
667
+ success: true,
668
+ result: {
669
+ type: "accessExpression",
670
+ expression: {
671
+ type: "dotProperty",
672
+ object: { type: "variableName", value: "obj" },
673
+ propertyName: "foo",
674
+ },
675
+ },
676
+ },
677
+ },
678
+ {
679
+ input: "response.status",
680
+ expected: {
681
+ success: true,
682
+ result: {
683
+ type: "accessExpression",
684
+ expression: {
685
+ type: "dotProperty",
686
+ object: { type: "variableName", value: "response" },
687
+ propertyName: "status",
688
+ },
689
+ },
690
+ },
691
+ },
692
+ // Index access
693
+ {
694
+ input: "arr[0]",
695
+ expected: {
696
+ success: true,
697
+ result: {
698
+ type: "accessExpression",
699
+ expression: {
700
+ type: "indexAccess",
701
+ array: { type: "variableName", value: "arr" },
702
+ index: { type: "number", value: "0" },
703
+ },
704
+ },
705
+ },
706
+ },
707
+ {
708
+ input: "items[i]",
709
+ expected: {
710
+ success: true,
711
+ result: {
712
+ type: "accessExpression",
713
+ expression: {
714
+ type: "indexAccess",
715
+ array: { type: "variableName", value: "items" },
716
+ index: { type: "variableName", value: "i" },
717
+ },
718
+ },
719
+ },
720
+ },
721
+ {
722
+ input: 'data["key"]',
723
+ expected: {
724
+ success: true,
725
+ result: {
726
+ type: "accessExpression",
727
+ expression: {
728
+ type: "indexAccess",
729
+ array: { type: "variableName", value: "data" },
730
+ index: { type: "string", value: "key" },
731
+ },
732
+ },
733
+ },
734
+ },
735
+ // Dot function call
736
+ {
737
+ input: "story.json()",
738
+ expected: {
739
+ success: true,
740
+ result: {
741
+ type: "accessExpression",
742
+ expression: {
743
+ type: "dotFunctionCall",
744
+ object: { type: "variableName", value: "story" },
745
+ functionCall: {
746
+ type: "functionCall",
747
+ functionName: "json",
748
+ arguments: [],
749
+ },
750
+ },
751
+ },
752
+ },
753
+ },
754
+ {
755
+ input: "response.text()",
756
+ expected: {
757
+ success: true,
758
+ result: {
759
+ type: "accessExpression",
760
+ expression: {
761
+ type: "dotFunctionCall",
762
+ object: { type: "variableName", value: "response" },
763
+ functionCall: {
764
+ type: "functionCall",
765
+ functionName: "text",
766
+ arguments: [],
767
+ },
768
+ },
769
+ },
770
+ },
771
+ },
772
+ {
773
+ input: "obj.method(42)",
774
+ expected: {
775
+ success: true,
776
+ result: {
777
+ type: "accessExpression",
778
+ expression: {
779
+ type: "dotFunctionCall",
780
+ object: { type: "variableName", value: "obj" },
781
+ functionCall: {
782
+ type: "functionCall",
783
+ functionName: "method",
784
+ arguments: [{ type: "number", value: "42" }],
785
+ },
786
+ },
787
+ },
788
+ },
789
+ },
790
+ // Chained function calls
791
+ {
792
+ input: "fetch().json()",
793
+ expected: {
794
+ success: true,
795
+ result: {
796
+ type: "accessExpression",
797
+ expression: {
798
+ type: "dotFunctionCall",
799
+ object: {
800
+ type: "functionCall",
801
+ functionName: "fetch",
802
+ arguments: [],
803
+ },
804
+ functionCall: {
805
+ type: "functionCall",
806
+ functionName: "json",
807
+ arguments: [],
808
+ },
809
+ },
810
+ },
811
+ },
812
+ },
813
+ {
814
+ input: "getData().process()",
815
+ expected: {
816
+ success: true,
817
+ result: {
818
+ type: "accessExpression",
819
+ expression: {
820
+ type: "dotFunctionCall",
821
+ object: {
822
+ type: "functionCall",
823
+ functionName: "getData",
824
+ arguments: [],
825
+ },
826
+ functionCall: {
827
+ type: "functionCall",
828
+ functionName: "process",
829
+ arguments: [],
830
+ },
831
+ },
832
+ },
833
+ },
834
+ },
835
+ // Function call with index access
836
+ {
837
+ input: "getData()[0]",
838
+ expected: {
839
+ success: true,
840
+ result: {
841
+ type: "accessExpression",
842
+ expression: {
843
+ type: "indexAccess",
844
+ array: {
845
+ type: "functionCall",
846
+ functionName: "getData",
847
+ arguments: [],
848
+ },
849
+ index: { type: "number", value: "0" },
850
+ },
851
+ },
852
+ },
853
+ },
854
+ // Edge cases
855
+ {
856
+ input: "x.y",
857
+ expected: {
858
+ success: true,
859
+ result: {
860
+ type: "accessExpression",
861
+ expression: {
862
+ type: "dotProperty",
863
+ object: { type: "variableName", value: "x" },
864
+ propertyName: "y",
865
+ },
866
+ },
867
+ },
868
+ },
869
+ {
870
+ input: "a[0]",
871
+ expected: {
872
+ success: true,
873
+ result: {
874
+ type: "accessExpression",
875
+ expression: {
876
+ type: "indexAccess",
877
+ array: { type: "variableName", value: "a" },
878
+ index: { type: "number", value: "0" },
879
+ },
880
+ },
881
+ },
882
+ },
883
+ {
884
+ input: "x.f()",
885
+ expected: {
886
+ success: true,
887
+ result: {
888
+ type: "accessExpression",
889
+ expression: {
890
+ type: "dotFunctionCall",
891
+ object: { type: "variableName", value: "x" },
892
+ functionCall: {
893
+ type: "functionCall",
894
+ functionName: "f",
895
+ arguments: [],
896
+ },
897
+ },
898
+ },
899
+ },
900
+ },
901
+ // Failure cases
902
+ { input: "", expected: { success: false } },
903
+ { input: "obj", expected: { success: false } }, // just a variable, not an access expression
904
+ { input: "42", expected: { success: false } }, // just a number
905
+ { input: '"string"', expected: { success: false } }, // just a string
906
+ { input: "func()", expected: { success: false } }, // just a function call
907
+ { input: "obj.", expected: { success: false } },
908
+ { input: ".property", expected: { success: false } },
909
+ { input: "[0]", expected: { success: false } },
910
+ ];
911
+ testCases.forEach(({ input, expected }) => {
912
+ if (expected.success) {
913
+ it(`should parse "${input}" successfully`, () => {
914
+ const result = accessExpressionParser(input);
915
+ expect(result.success).toBe(true);
916
+ if (result.success) {
917
+ expect(result.result).toEqual(expected.result);
918
+ }
919
+ });
920
+ }
921
+ else {
922
+ it(`should fail to parse "${input}"`, () => {
923
+ const result = accessExpressionParser(input);
924
+ expect(result.success).toBe(false);
925
+ });
926
+ }
927
+ });
928
+ });
929
+ });