@grimoirelabs/core 0.3.0 → 0.5.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.
@@ -90,28 +90,20 @@ export class Parser {
90
90
  /** Parse a complete spell file */
91
91
  parseSpellFile() {
92
92
  this.skipNewlines();
93
- // Expect: spell Name
93
+ // Expect: spell Name {
94
94
  this.expect("KEYWORD", "spell");
95
95
  const nameToken = this.expect("IDENTIFIER");
96
96
  const name = nameToken.value;
97
- this.expectNewline();
98
97
  this.skipNewlines();
98
+ this.expect("LBRACE");
99
99
  // Parse sections and triggers
100
100
  const sections = [];
101
101
  const triggers = [];
102
102
  const imports = [];
103
103
  const blocks = [];
104
- // Expect INDENT for spell body
105
- if (!this.check("INDENT")) {
106
- throw new ParseError("Expected indented spell body", {
107
- location: this.current().location,
108
- source: this.source,
109
- });
110
- }
111
- this.advance(); // INDENT
112
- while (!this.check("DEDENT") && !this.check("EOF")) {
104
+ while (!this.check("RBRACE") && !this.check("EOF")) {
113
105
  this.skipNewlines();
114
- if (this.check("DEDENT") || this.check("EOF"))
106
+ if (this.check("RBRACE") || this.check("EOF"))
115
107
  break;
116
108
  // Check for import or block or trigger
117
109
  if (this.check("KEYWORD", "import")) {
@@ -128,10 +120,7 @@ export class Parser {
128
120
  }
129
121
  this.skipNewlines();
130
122
  }
131
- // Consume DEDENT
132
- if (this.check("DEDENT")) {
133
- this.advance();
134
- }
123
+ this.expect("RBRACE");
135
124
  return {
136
125
  kind: "spell",
137
126
  name,
@@ -168,7 +157,7 @@ export class Parser {
168
157
  node.span = this.makeSpan(startToken);
169
158
  return node;
170
159
  }
171
- /** Parse block definition: block name(arg, ...): */
160
+ /** Parse block definition: block name(args) { ... } */
172
161
  parseBlock() {
173
162
  const startToken = this.current();
174
163
  this.expect("KEYWORD", "block");
@@ -185,9 +174,7 @@ export class Parser {
185
174
  }
186
175
  this.expect("RPAREN");
187
176
  }
188
- this.expect("COLON");
189
- this.expectNewline();
190
- const body = this.parseStatementBlock();
177
+ const body = this.parseBraceBlock();
191
178
  const node = { kind: "block", name, params, body };
192
179
  node.span = this.makeSpan(startToken);
193
180
  return node;
@@ -317,7 +304,7 @@ export class Parser {
317
304
  source: this.source,
318
305
  });
319
306
  }
320
- /** Parse assets: [USDC, USDT] or assets block */
307
+ /** Parse assets: [USDC, USDT] or assets: { ... } */
321
308
  parseAssetsSection() {
322
309
  this.expect("KEYWORD", "assets");
323
310
  this.expect("COLON");
@@ -335,76 +322,65 @@ export class Parser {
335
322
  this.expect("RBRACKET");
336
323
  this.expectNewline();
337
324
  }
338
- else {
339
- // Block form
340
- this.expectNewline();
341
- if (this.check("INDENT")) {
342
- this.advance();
343
- while (!this.check("DEDENT") && !this.check("EOF")) {
344
- this.skipNewlines();
345
- if (this.check("DEDENT") || this.check("EOF"))
346
- break;
347
- const symbol = this.expect("IDENTIFIER").value;
348
- const asset = { symbol };
349
- this.expect("COLON");
350
- if (this.check("NEWLINE")) {
351
- this.expectNewline();
352
- if (!this.check("INDENT")) {
353
- throw new ParseError("Expected indented asset block", {
354
- location: this.current().location,
355
- source: this.source,
356
- });
325
+ else if (this.check("LBRACE")) {
326
+ // Block form: assets: { ... }
327
+ this.advance();
328
+ while (!this.check("RBRACE") && !this.check("EOF")) {
329
+ this.skipNewlines();
330
+ if (this.check("RBRACE") || this.check("EOF"))
331
+ break;
332
+ const symbol = this.expect("IDENTIFIER").value;
333
+ const asset = { symbol };
334
+ this.expect("COLON");
335
+ if (this.check("LBRACE")) {
336
+ this.advance();
337
+ while (!this.check("RBRACE") && !this.check("EOF")) {
338
+ this.skipNewlines();
339
+ if (this.check("RBRACE") || this.check("EOF"))
340
+ break;
341
+ const key = this.expect("IDENTIFIER").value;
342
+ this.expect("COLON");
343
+ if (key === "chain") {
344
+ const val = this.expect("NUMBER").value;
345
+ asset.chain = Number.parseInt(val, 10);
346
+ this.expectNewline();
357
347
  }
358
- this.advance();
359
- while (!this.check("DEDENT") && !this.check("EOF")) {
360
- this.skipNewlines();
361
- if (this.check("DEDENT") || this.check("EOF"))
362
- break;
363
- const key = this.expect("IDENTIFIER").value;
364
- this.expect("COLON");
365
- if (key === "chain") {
366
- const val = this.expect("NUMBER").value;
367
- asset.chain = Number.parseInt(val, 10);
368
- this.expectNewline();
369
- }
370
- else if (key === "address") {
371
- if (this.check("ADDRESS")) {
372
- asset.address = this.advance().value;
373
- }
374
- else if (this.check("STRING")) {
375
- asset.address = this.advance().value;
376
- }
377
- else {
378
- throw new ParseError("Expected address value", {
379
- location: this.current().location,
380
- source: this.source,
381
- });
382
- }
383
- this.expectNewline();
348
+ else if (key === "address") {
349
+ if (this.check("ADDRESS")) {
350
+ asset.address = this.advance().value;
384
351
  }
385
- else if (key === "decimals") {
386
- const val = this.expect("NUMBER").value;
387
- asset.decimals = Number.parseInt(val, 10);
388
- this.expectNewline();
352
+ else if (this.check("STRING")) {
353
+ asset.address = this.advance().value;
389
354
  }
390
355
  else {
391
- this.parseExpression();
392
- this.expectNewline();
356
+ throw new ParseError("Expected address value", {
357
+ location: this.current().location,
358
+ source: this.source,
359
+ });
393
360
  }
361
+ this.expectNewline();
362
+ }
363
+ else if (key === "decimals") {
364
+ const val = this.expect("NUMBER").value;
365
+ asset.decimals = Number.parseInt(val, 10);
366
+ this.expectNewline();
367
+ }
368
+ else {
369
+ this.parseExpression();
370
+ this.expectNewline();
394
371
  }
395
- if (this.check("DEDENT"))
396
- this.advance();
397
- }
398
- else {
399
- // Inline asset defaults: assets: SYMBOL: <ignored>
400
- this.parseExpression();
401
- this.expectNewline();
402
372
  }
403
- items.push(asset);
373
+ this.expect("RBRACE");
404
374
  }
405
- if (this.check("DEDENT"))
406
- this.advance();
375
+ else {
376
+ // Inline asset defaults: SYMBOL: <value>
377
+ this.parseExpression();
378
+ this.expectNewline();
379
+ }
380
+ items.push(asset);
381
+ this.skipNewlines();
407
382
  }
383
+ this.expect("RBRACE");
408
384
  }
409
385
  return { kind: "assets", items };
410
386
  }
@@ -412,30 +388,22 @@ export class Parser {
412
388
  parseParamsSection() {
413
389
  this.expect("KEYWORD", "params");
414
390
  this.expect("COLON");
415
- this.expectNewline();
416
391
  const items = [];
417
- if (this.check("INDENT")) {
392
+ if (this.check("LBRACE")) {
418
393
  this.advance();
419
- while (!this.check("DEDENT") && !this.check("EOF")) {
394
+ while (!this.check("RBRACE") && !this.check("EOF")) {
420
395
  this.skipNewlines();
421
- if (this.check("DEDENT"))
396
+ if (this.check("RBRACE"))
422
397
  break;
423
398
  const name = this.expect("IDENTIFIER").value;
424
399
  this.expect("COLON");
425
400
  const item = { name };
426
- // Block form: param:
427
- if (this.check("NEWLINE")) {
428
- this.expectNewline();
429
- if (!this.check("INDENT")) {
430
- throw new ParseError("Expected indented param block", {
431
- location: this.current().location,
432
- source: this.source,
433
- });
434
- }
401
+ // Block form: param: { ... }
402
+ if (this.check("LBRACE")) {
435
403
  this.advance();
436
- while (!this.check("DEDENT") && !this.check("EOF")) {
404
+ while (!this.check("RBRACE") && !this.check("EOF")) {
437
405
  this.skipNewlines();
438
- if (this.check("DEDENT") || this.check("EOF"))
406
+ if (this.check("RBRACE") || this.check("EOF"))
439
407
  break;
440
408
  const keyToken = this.current();
441
409
  if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
@@ -494,8 +462,7 @@ export class Parser {
494
462
  this.expectNewline();
495
463
  }
496
464
  }
497
- if (this.check("DEDENT"))
498
- this.advance();
465
+ this.expect("RBRACE");
499
466
  }
500
467
  else {
501
468
  // Inline form: name: value
@@ -505,8 +472,7 @@ export class Parser {
505
472
  items.push(item);
506
473
  this.skipNewlines();
507
474
  }
508
- if (this.check("DEDENT"))
509
- this.advance();
475
+ this.expect("RBRACE");
510
476
  }
511
477
  return { kind: "params", items };
512
478
  }
@@ -514,13 +480,12 @@ export class Parser {
514
480
  parseLimitsSection() {
515
481
  this.expect("KEYWORD", "limits");
516
482
  this.expect("COLON");
517
- this.expectNewline();
518
483
  const items = [];
519
- if (this.check("INDENT")) {
484
+ if (this.check("LBRACE")) {
520
485
  this.advance();
521
- while (!this.check("DEDENT") && !this.check("EOF")) {
486
+ while (!this.check("RBRACE") && !this.check("EOF")) {
522
487
  this.skipNewlines();
523
- if (this.check("DEDENT"))
488
+ if (this.check("RBRACE"))
524
489
  break;
525
490
  const name = this.expect("IDENTIFIER").value;
526
491
  this.expect("COLON");
@@ -529,8 +494,7 @@ export class Parser {
529
494
  this.expectNewline();
530
495
  this.skipNewlines();
531
496
  }
532
- if (this.check("DEDENT"))
533
- this.advance();
497
+ this.expect("RBRACE");
534
498
  }
535
499
  return { kind: "limits", items };
536
500
  }
@@ -538,13 +502,12 @@ export class Parser {
538
502
  parseVenuesSection() {
539
503
  this.expect("KEYWORD", "venues");
540
504
  this.expect("COLON");
541
- this.expectNewline();
542
505
  const groups = [];
543
- if (this.check("INDENT")) {
506
+ if (this.check("LBRACE")) {
544
507
  this.advance();
545
- while (!this.check("DEDENT") && !this.check("EOF")) {
508
+ while (!this.check("RBRACE") && !this.check("EOF")) {
546
509
  this.skipNewlines();
547
- if (this.check("DEDENT"))
510
+ if (this.check("RBRACE"))
548
511
  break;
549
512
  const name = this.expect("IDENTIFIER").value;
550
513
  this.expect("COLON");
@@ -572,8 +535,7 @@ export class Parser {
572
535
  this.expectNewline();
573
536
  this.skipNewlines();
574
537
  }
575
- if (this.check("DEDENT"))
576
- this.advance();
538
+ this.expect("RBRACE");
577
539
  }
578
540
  return { kind: "venues", groups };
579
541
  }
@@ -581,24 +543,22 @@ export class Parser {
581
543
  parseStateSection() {
582
544
  this.expect("KEYWORD", "state");
583
545
  this.expect("COLON");
584
- this.expectNewline();
585
546
  const persistent = [];
586
547
  const ephemeral = [];
587
- if (this.check("INDENT")) {
548
+ if (this.check("LBRACE")) {
588
549
  this.advance();
589
- while (!this.check("DEDENT") && !this.check("EOF")) {
550
+ while (!this.check("RBRACE") && !this.check("EOF")) {
590
551
  this.skipNewlines();
591
- if (this.check("DEDENT"))
552
+ if (this.check("RBRACE"))
592
553
  break;
593
554
  const scope = this.expect("KEYWORD").value;
594
555
  this.expect("COLON");
595
- this.expectNewline();
596
556
  const items = scope === "persistent" ? persistent : ephemeral;
597
- if (this.check("INDENT")) {
557
+ if (this.check("LBRACE")) {
598
558
  this.advance();
599
- while (!this.check("DEDENT") && !this.check("EOF")) {
559
+ while (!this.check("RBRACE") && !this.check("EOF")) {
600
560
  this.skipNewlines();
601
- if (this.check("DEDENT"))
561
+ if (this.check("RBRACE"))
602
562
  break;
603
563
  const name = this.expect("IDENTIFIER").value;
604
564
  this.expect("COLON");
@@ -607,13 +567,11 @@ export class Parser {
607
567
  this.expectNewline();
608
568
  this.skipNewlines();
609
569
  }
610
- if (this.check("DEDENT"))
611
- this.advance();
570
+ this.expect("RBRACE");
612
571
  }
613
572
  this.skipNewlines();
614
573
  }
615
- if (this.check("DEDENT"))
616
- this.advance();
574
+ this.expect("RBRACE");
617
575
  }
618
576
  return { kind: "state", persistent, ephemeral };
619
577
  }
@@ -622,25 +580,23 @@ export class Parser {
622
580
  const startToken = this.current();
623
581
  this.expect("KEYWORD", "skills");
624
582
  this.expect("COLON");
625
- this.expectNewline();
626
583
  const items = [];
627
- if (this.check("INDENT")) {
584
+ if (this.check("LBRACE")) {
628
585
  this.advance();
629
- while (!this.check("DEDENT") && !this.check("EOF")) {
586
+ while (!this.check("RBRACE") && !this.check("EOF")) {
630
587
  this.skipNewlines();
631
- if (this.check("DEDENT") || this.check("EOF"))
588
+ if (this.check("RBRACE") || this.check("EOF"))
632
589
  break;
633
590
  const name = this.expect("IDENTIFIER").value;
634
591
  this.expect("COLON");
635
- this.expectNewline();
636
592
  let type;
637
593
  const adapters = [];
638
594
  let defaultMaxSlippage;
639
- if (this.check("INDENT")) {
595
+ if (this.check("LBRACE")) {
640
596
  this.advance();
641
- while (!this.check("DEDENT") && !this.check("EOF")) {
597
+ while (!this.check("RBRACE") && !this.check("EOF")) {
642
598
  this.skipNewlines();
643
- if (this.check("DEDENT") || this.check("EOF"))
599
+ if (this.check("RBRACE") || this.check("EOF"))
644
600
  break;
645
601
  const keyToken = this.current();
646
602
  if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
@@ -710,12 +666,11 @@ export class Parser {
710
666
  this.expectNewline();
711
667
  }
712
668
  else if (key === "default_constraints") {
713
- this.expectNewline();
714
- if (this.check("INDENT")) {
669
+ if (this.check("LBRACE")) {
715
670
  this.advance();
716
- while (!this.check("DEDENT") && !this.check("EOF")) {
671
+ while (!this.check("RBRACE") && !this.check("EOF")) {
717
672
  this.skipNewlines();
718
- if (this.check("DEDENT") || this.check("EOF"))
673
+ if (this.check("RBRACE") || this.check("EOF"))
719
674
  break;
720
675
  const dcKey = this.expect("IDENTIFIER").value;
721
676
  this.expect("COLON");
@@ -725,8 +680,7 @@ export class Parser {
725
680
  }
726
681
  this.expectNewline();
727
682
  }
728
- if (this.check("DEDENT"))
729
- this.advance();
683
+ this.expect("RBRACE");
730
684
  }
731
685
  }
732
686
  else {
@@ -735,8 +689,7 @@ export class Parser {
735
689
  this.expectNewline();
736
690
  }
737
691
  }
738
- if (this.check("DEDENT"))
739
- this.advance();
692
+ this.expect("RBRACE");
740
693
  }
741
694
  if (!type) {
742
695
  throw new ParseError(`Skill '${name}' missing type`, {
@@ -752,8 +705,7 @@ export class Parser {
752
705
  });
753
706
  this.skipNewlines();
754
707
  }
755
- if (this.check("DEDENT"))
756
- this.advance();
708
+ this.expect("RBRACE");
757
709
  }
758
710
  const node = { kind: "skills", items };
759
711
  node.span = this.makeSpan(startToken);
@@ -764,26 +716,24 @@ export class Parser {
764
716
  const startToken = this.current();
765
717
  this.expect("KEYWORD", "advisors");
766
718
  this.expect("COLON");
767
- this.expectNewline();
768
719
  const items = [];
769
- if (this.check("INDENT")) {
720
+ if (this.check("LBRACE")) {
770
721
  this.advance();
771
- while (!this.check("DEDENT") && !this.check("EOF")) {
722
+ while (!this.check("RBRACE") && !this.check("EOF")) {
772
723
  this.skipNewlines();
773
- if (this.check("DEDENT") || this.check("EOF"))
724
+ if (this.check("RBRACE") || this.check("EOF"))
774
725
  break;
775
726
  const name = this.expect("IDENTIFIER").value;
776
727
  this.expect("COLON");
777
- this.expectNewline();
778
728
  const advisor = {
779
729
  name,
780
730
  model: "sonnet",
781
731
  };
782
- if (this.check("INDENT")) {
732
+ if (this.check("LBRACE")) {
783
733
  this.advance();
784
- while (!this.check("DEDENT") && !this.check("EOF")) {
734
+ while (!this.check("RBRACE") && !this.check("EOF")) {
785
735
  this.skipNewlines();
786
- if (this.check("DEDENT") || this.check("EOF"))
736
+ if (this.check("RBRACE") || this.check("EOF"))
787
737
  break;
788
738
  const keyToken = this.current();
789
739
  if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
@@ -842,12 +792,11 @@ export class Parser {
842
792
  this.expectNewline();
843
793
  }
844
794
  else if (key === "rate_limit") {
845
- this.expectNewline();
846
- if (this.check("INDENT")) {
795
+ if (this.check("LBRACE")) {
847
796
  this.advance();
848
- while (!this.check("DEDENT") && !this.check("EOF")) {
797
+ while (!this.check("RBRACE") && !this.check("EOF")) {
849
798
  this.skipNewlines();
850
- if (this.check("DEDENT") || this.check("EOF"))
799
+ if (this.check("RBRACE") || this.check("EOF"))
851
800
  break;
852
801
  const rlKey = this.expect("IDENTIFIER").value;
853
802
  this.expect("COLON");
@@ -860,8 +809,7 @@ export class Parser {
860
809
  }
861
810
  this.expectNewline();
862
811
  }
863
- if (this.check("DEDENT"))
864
- this.advance();
812
+ this.expect("RBRACE");
865
813
  }
866
814
  }
867
815
  else {
@@ -870,31 +818,28 @@ export class Parser {
870
818
  this.expectNewline();
871
819
  }
872
820
  }
873
- if (this.check("DEDENT"))
874
- this.advance();
821
+ this.expect("RBRACE");
875
822
  }
876
823
  items.push(advisor);
877
824
  this.skipNewlines();
878
825
  }
879
- if (this.check("DEDENT"))
880
- this.advance();
826
+ this.expect("RBRACE");
881
827
  }
882
828
  const node = { kind: "advisors", items };
883
829
  node.span = this.makeSpan(startToken);
884
830
  return node;
885
831
  }
886
- /** Parse guards section: guards:\n id: expression */
832
+ /** Parse guards section: guards: { id: expression } */
887
833
  parseGuardsSection() {
888
834
  const startToken = this.current();
889
835
  this.expect("KEYWORD", "guards");
890
836
  this.expect("COLON");
891
- this.expectNewline();
892
837
  const items = [];
893
- if (this.check("INDENT")) {
894
- this.advance(); // consume INDENT
895
- while (!this.check("DEDENT") && !this.check("EOF")) {
838
+ if (this.check("LBRACE")) {
839
+ this.advance();
840
+ while (!this.check("RBRACE") && !this.check("EOF")) {
896
841
  this.skipNewlines();
897
- if (this.check("DEDENT") || this.check("EOF"))
842
+ if (this.check("RBRACE") || this.check("EOF"))
898
843
  break;
899
844
  // Each line: id: expression
900
845
  const id = this.current().value;
@@ -933,8 +878,7 @@ export class Parser {
933
878
  });
934
879
  this.skipNewlines();
935
880
  }
936
- if (this.check("DEDENT"))
937
- this.advance();
881
+ this.expect("RBRACE");
938
882
  }
939
883
  const node = { kind: "guards", items };
940
884
  node.span = this.makeSpan(startToken);
@@ -943,13 +887,12 @@ export class Parser {
943
887
  // ===========================================================================
944
888
  // TRIGGER PARSING
945
889
  // ===========================================================================
946
- /** Parse trigger handler: on manual: ... */
890
+ /** Parse trigger handler: on manual: { ... } */
947
891
  parseTriggerHandler() {
948
892
  this.expect("KEYWORD", "on");
949
893
  const triggerType = this.parseTriggerType();
950
894
  this.expect("COLON");
951
- this.expectNewline();
952
- const body = this.parseStatementBlock();
895
+ const body = this.parseBraceBlock();
953
896
  return {
954
897
  kind: "trigger_handler",
955
898
  trigger: triggerType,
@@ -1021,23 +964,19 @@ export class Parser {
1021
964
  // ===========================================================================
1022
965
  // STATEMENT PARSING
1023
966
  // ===========================================================================
1024
- /** Parse an indented block of statements */
1025
- parseStatementBlock() {
967
+ /** Parse a brace-delimited block of statements: { stmt... } */
968
+ parseBraceBlock() {
1026
969
  const statements = [];
1027
- if (!this.check("INDENT")) {
1028
- return statements;
1029
- }
1030
- this.advance(); // INDENT
1031
- while (!this.check("DEDENT") && !this.check("EOF")) {
970
+ this.skipNewlines();
971
+ this.expect("LBRACE");
972
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1032
973
  this.skipNewlines();
1033
- if (this.check("DEDENT") || this.check("EOF"))
974
+ if (this.check("RBRACE") || this.check("EOF"))
1034
975
  break;
1035
976
  statements.push(this.parseStatement());
1036
977
  this.skipNewlines();
1037
978
  }
1038
- if (this.check("DEDENT")) {
1039
- this.advance();
1040
- }
979
+ this.expect("RBRACE");
1041
980
  return statements;
1042
981
  }
1043
982
  /** Parse a single statement */
@@ -1160,15 +1099,15 @@ export class Parser {
1160
1099
  const advisor = this.expect("IDENTIFIER").value;
1161
1100
  this.expect("COLON");
1162
1101
  const prompt = this.expect("STRING").value;
1163
- this.expectNewline();
1164
1102
  let outputSchema;
1165
1103
  let timeout;
1166
1104
  let fallback;
1167
- if (this.check("INDENT")) {
1105
+ this.skipNewlines();
1106
+ if (this.check("LBRACE")) {
1168
1107
  this.advance();
1169
- while (!this.check("DEDENT") && !this.check("EOF")) {
1108
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1170
1109
  this.skipNewlines();
1171
- if (this.check("DEDENT") || this.check("EOF"))
1110
+ if (this.check("RBRACE") || this.check("EOF"))
1172
1111
  break;
1173
1112
  const keyToken = this.current();
1174
1113
  if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
@@ -1194,8 +1133,7 @@ export class Parser {
1194
1133
  this.expectNewline();
1195
1134
  }
1196
1135
  }
1197
- if (this.check("DEDENT"))
1198
- this.advance();
1136
+ this.expect("RBRACE");
1199
1137
  }
1200
1138
  if (!outputSchema || timeout === undefined || fallback === undefined) {
1201
1139
  throw new ParseError("Advise statement requires output, timeout, and fallback", {
@@ -1216,15 +1154,15 @@ export class Parser {
1216
1154
  return node;
1217
1155
  }
1218
1156
  parseOutputSchemaBlock(keyToken) {
1219
- if (!this.check("NEWLINE")) {
1157
+ if (!this.check("LBRACE") && !this.check("NEWLINE")) {
1220
1158
  // Allow inline type: output: boolean
1221
1159
  const inlineType = this.parseSchemaType();
1222
1160
  this.expectNewline();
1223
1161
  return { kind: "advisory_output_schema", type: inlineType };
1224
1162
  }
1225
- this.expectNewline();
1226
- if (!this.check("INDENT")) {
1227
- throw new ParseError("Expected indented output block", {
1163
+ this.skipNewlines();
1164
+ if (!this.check("LBRACE")) {
1165
+ throw new ParseError("Expected '{' for output block", {
1228
1166
  location: keyToken.location,
1229
1167
  source: this.source,
1230
1168
  });
@@ -1233,14 +1171,8 @@ export class Parser {
1233
1171
  return this.parseSchemaObject(keyToken);
1234
1172
  }
1235
1173
  parseSchemaAfterColon(keyToken) {
1236
- if (this.check("NEWLINE")) {
1237
- this.expectNewline();
1238
- if (!this.check("INDENT")) {
1239
- throw new ParseError("Expected indented schema block", {
1240
- location: keyToken.location,
1241
- source: this.source,
1242
- });
1243
- }
1174
+ this.skipNewlines();
1175
+ if (this.check("LBRACE")) {
1244
1176
  this.advance();
1245
1177
  return this.parseSchemaObject(keyToken);
1246
1178
  }
@@ -1258,9 +1190,9 @@ export class Parser {
1258
1190
  let pattern;
1259
1191
  let fields;
1260
1192
  let items;
1261
- while (!this.check("DEDENT") && !this.check("EOF")) {
1193
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1262
1194
  this.skipNewlines();
1263
- if (this.check("DEDENT") || this.check("EOF"))
1195
+ if (this.check("RBRACE") || this.check("EOF"))
1264
1196
  break;
1265
1197
  if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
1266
1198
  throw new ParseError(`Expected output schema field but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
@@ -1311,8 +1243,7 @@ export class Parser {
1311
1243
  this.expectNewline();
1312
1244
  }
1313
1245
  }
1314
- if (this.check("DEDENT"))
1315
- this.advance();
1246
+ this.expect("RBRACE");
1316
1247
  if (!outType) {
1317
1248
  throw new ParseError("Advisory output type is required", {
1318
1249
  location: keyToken.location,
@@ -1333,25 +1264,24 @@ export class Parser {
1333
1264
  };
1334
1265
  }
1335
1266
  parseSchemaFields(keyToken) {
1336
- this.expectNewline();
1337
- if (!this.check("INDENT")) {
1338
- throw new ParseError("Expected indented fields block", {
1267
+ this.skipNewlines();
1268
+ if (!this.check("LBRACE")) {
1269
+ throw new ParseError("Expected '{' for fields block", {
1339
1270
  location: keyToken.location,
1340
1271
  source: this.source,
1341
1272
  });
1342
1273
  }
1343
1274
  this.advance();
1344
1275
  const fields = {};
1345
- while (!this.check("DEDENT") && !this.check("EOF")) {
1276
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1346
1277
  this.skipNewlines();
1347
- if (this.check("DEDENT") || this.check("EOF"))
1278
+ if (this.check("RBRACE") || this.check("EOF"))
1348
1279
  break;
1349
1280
  const fieldName = this.expect("IDENTIFIER").value;
1350
1281
  this.expect("COLON");
1351
1282
  fields[fieldName] = this.parseSchemaAfterColon(keyToken);
1352
1283
  }
1353
- if (this.check("DEDENT"))
1354
- this.advance();
1284
+ this.expect("RBRACE");
1355
1285
  return fields;
1356
1286
  }
1357
1287
  parseSchemaType() {
@@ -1392,9 +1322,7 @@ export class Parser {
1392
1322
  : Number.parseInt(countToken.value, 10),
1393
1323
  literalType: "number",
1394
1324
  };
1395
- this.expect("COLON");
1396
- this.expectNewline();
1397
- const body = this.parseStatementBlock();
1325
+ const body = this.parseBraceBlock();
1398
1326
  const node = { kind: "repeat", count, body };
1399
1327
  node.span = this.makeSpan(startToken);
1400
1328
  return node;
@@ -1411,9 +1339,7 @@ export class Parser {
1411
1339
  const maxToken = this.expect("NUMBER");
1412
1340
  maxIterations = Number.parseFloat(maxToken.value);
1413
1341
  }
1414
- this.expect("COLON");
1415
- this.expectNewline();
1416
- const body = this.parseStatementBlock();
1342
+ const body = this.parseBraceBlock();
1417
1343
  const node = { kind: "until", condition, maxIterations, body };
1418
1344
  node.span = this.makeSpan(startToken);
1419
1345
  return node;
@@ -1422,19 +1348,17 @@ export class Parser {
1422
1348
  parseTryStatement() {
1423
1349
  const startToken = this.current();
1424
1350
  this.expect("KEYWORD", "try");
1425
- this.expect("COLON");
1426
- this.expectNewline();
1427
- const tryBody = this.parseStatementBlock();
1351
+ const tryBody = this.parseBraceBlock();
1352
+ this.skipNewlines();
1428
1353
  const catches = [];
1429
1354
  while (this.check("KEYWORD", "catch")) {
1430
1355
  catches.push(this.parseCatchBlock());
1356
+ this.skipNewlines();
1431
1357
  }
1432
1358
  let finallyBody;
1433
1359
  if (this.check("KEYWORD", "finally")) {
1434
1360
  this.advance();
1435
- this.expect("COLON");
1436
- this.expectNewline();
1437
- finallyBody = this.parseStatementBlock();
1361
+ finallyBody = this.parseBraceBlock();
1438
1362
  }
1439
1363
  const node = { kind: "try", tryBody, catches, finallyBody };
1440
1364
  node.span = this.makeSpan(startToken);
@@ -1451,16 +1375,15 @@ export class Parser {
1451
1375
  else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1452
1376
  error = this.advance().value;
1453
1377
  }
1454
- this.expect("COLON");
1455
- this.expectNewline();
1456
1378
  const body = [];
1457
1379
  let action;
1458
1380
  let retry;
1459
- if (this.check("INDENT")) {
1381
+ this.skipNewlines();
1382
+ if (this.check("LBRACE")) {
1460
1383
  this.advance();
1461
- while (!this.check("DEDENT") && !this.check("EOF")) {
1384
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1462
1385
  this.skipNewlines();
1463
- if (this.check("DEDENT") || this.check("EOF"))
1386
+ if (this.check("RBRACE") || this.check("EOF"))
1464
1387
  break;
1465
1388
  if ((this.check("IDENTIFIER") || this.check("KEYWORD")) &&
1466
1389
  this.current().value === "action") {
@@ -1479,15 +1402,13 @@ export class Parser {
1479
1402
  this.current().value === "retry") {
1480
1403
  this.advance();
1481
1404
  this.expect("COLON");
1482
- this.expectNewline();
1483
1405
  retry = this.parseRetrySpec();
1484
1406
  continue;
1485
1407
  }
1486
1408
  body.push(this.parseStatement());
1487
1409
  this.skipNewlines();
1488
1410
  }
1489
- if (this.check("DEDENT"))
1490
- this.advance();
1411
+ this.expect("RBRACE");
1491
1412
  }
1492
1413
  const node = { kind: "catch", error, action, retry, body };
1493
1414
  node.span = this.makeSpan(startToken);
@@ -1500,11 +1421,12 @@ export class Parser {
1500
1421
  let backoff = "none";
1501
1422
  let backoffBase;
1502
1423
  let maxBackoff;
1503
- if (this.check("INDENT")) {
1424
+ this.skipNewlines();
1425
+ if (this.check("LBRACE")) {
1504
1426
  this.advance();
1505
- while (!this.check("DEDENT") && !this.check("EOF")) {
1427
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1506
1428
  this.skipNewlines();
1507
- if (this.check("DEDENT") || this.check("EOF"))
1429
+ if (this.check("RBRACE") || this.check("EOF"))
1508
1430
  break;
1509
1431
  const key = this.expect("IDENTIFIER").value;
1510
1432
  this.expect("COLON");
@@ -1530,8 +1452,7 @@ export class Parser {
1530
1452
  }
1531
1453
  this.expectNewline();
1532
1454
  }
1533
- if (this.check("DEDENT"))
1534
- this.advance();
1455
+ this.expect("RBRACE");
1535
1456
  }
1536
1457
  const node = {
1537
1458
  kind: "retry",
@@ -1549,8 +1470,8 @@ export class Parser {
1549
1470
  this.expect("KEYWORD", "parallel");
1550
1471
  let join;
1551
1472
  let onFail;
1552
- // Optional header config before colon: join=..., on_fail=...
1553
- while (!this.check("COLON") && !this.check("NEWLINE") && !this.check("EOF")) {
1473
+ // Optional header config before brace: join=..., on_fail=...
1474
+ while (!this.check("LBRACE") && !this.check("NEWLINE") && !this.check("EOF")) {
1554
1475
  if (!(this.check("IDENTIFIER") || this.check("KEYWORD")))
1555
1476
  break;
1556
1477
  const key = this.advance().value;
@@ -1605,25 +1526,20 @@ export class Parser {
1605
1526
  this.parseExpression();
1606
1527
  }
1607
1528
  }
1608
- this.expect("COLON");
1609
- this.expectNewline();
1529
+ this.skipNewlines();
1530
+ this.expect("LBRACE");
1610
1531
  const branches = [];
1611
- if (this.check("INDENT")) {
1612
- this.advance();
1613
- while (!this.check("DEDENT") && !this.check("EOF")) {
1614
- this.skipNewlines();
1615
- if (this.check("DEDENT") || this.check("EOF"))
1616
- break;
1617
- const name = this.expect("IDENTIFIER").value;
1618
- this.expect("COLON");
1619
- this.expectNewline();
1620
- const body = this.parseStatementBlock();
1621
- branches.push({ kind: "parallel_branch", name, body });
1622
- this.skipNewlines();
1623
- }
1624
- if (this.check("DEDENT"))
1625
- this.advance();
1532
+ while (!this.check("RBRACE") && !this.check("EOF")) {
1533
+ this.skipNewlines();
1534
+ if (this.check("RBRACE") || this.check("EOF"))
1535
+ break;
1536
+ const name = this.expect("IDENTIFIER").value;
1537
+ this.expect("COLON");
1538
+ const body = this.parseBraceBlock();
1539
+ branches.push({ kind: "parallel_branch", name, body });
1540
+ this.skipNewlines();
1626
1541
  }
1542
+ this.expect("RBRACE");
1627
1543
  const node = { kind: "parallel", join, onFail, branches };
1628
1544
  node.span = this.makeSpan(startToken);
1629
1545
  return node;
@@ -1661,8 +1577,7 @@ export class Parser {
1661
1577
  }
1662
1578
  }
1663
1579
  this.expect("COLON");
1664
- this.expectNewline();
1665
- const body = this.parseStatementBlock();
1580
+ const body = this.parseBraceBlock();
1666
1581
  if (body.length === 0 &&
1667
1582
  (op === "map" || op === "filter" || op === "reduce" || op === "pmap")) {
1668
1583
  throw new ParseError(`Pipeline stage '${op}' requires a body`, {
@@ -1715,7 +1630,7 @@ export class Parser {
1715
1630
  parseIfStatement() {
1716
1631
  const startToken = this.current();
1717
1632
  this.expect("KEYWORD", "if");
1718
- // Check for advisory condition: if **prompt**:
1633
+ // Check for advisory condition: if **prompt** { ... }
1719
1634
  let condition;
1720
1635
  if (this.check("ADVISORY")) {
1721
1636
  const prompt = this.advance().value;
@@ -1731,26 +1646,22 @@ export class Parser {
1731
1646
  else {
1732
1647
  condition = this.parseExpression();
1733
1648
  }
1734
- this.expect("COLON");
1735
- this.expectNewline();
1736
- const thenBody = this.parseStatementBlock();
1649
+ const thenBody = this.parseBraceBlock();
1737
1650
  const elifs = [];
1738
1651
  let elseBody = [];
1739
1652
  // Check for elif
1653
+ this.skipNewlines();
1740
1654
  while (this.check("KEYWORD", "elif")) {
1741
1655
  this.advance();
1742
1656
  const elifCondition = this.parseExpression();
1743
- this.expect("COLON");
1744
- this.expectNewline();
1745
- const elifBody = this.parseStatementBlock();
1657
+ const elifBody = this.parseBraceBlock();
1746
1658
  elifs.push({ condition: elifCondition, body: elifBody });
1659
+ this.skipNewlines();
1747
1660
  }
1748
1661
  // Check for else
1749
1662
  if (this.check("KEYWORD", "else")) {
1750
1663
  this.advance();
1751
- this.expect("COLON");
1752
- this.expectNewline();
1753
- elseBody = this.parseStatementBlock();
1664
+ elseBody = this.parseBraceBlock();
1754
1665
  }
1755
1666
  const node = { kind: "if", condition, thenBody, elifs, elseBody };
1756
1667
  node.span = this.makeSpan(startToken);
@@ -1763,29 +1674,24 @@ export class Parser {
1763
1674
  const variable = this.expect("IDENTIFIER").value;
1764
1675
  this.expect("KEYWORD", "in");
1765
1676
  const iterable = this.parseExpression();
1766
- this.expect("COLON");
1767
- this.expectNewline();
1768
- const body = this.parseStatementBlock();
1677
+ const body = this.parseBraceBlock();
1769
1678
  const node = { kind: "for", variable, iterable, body };
1770
1679
  node.span = this.makeSpan(startToken);
1771
1680
  return node;
1772
1681
  }
1773
- /** Parse atomic block: atomic: / atomic skip: / atomic halt: / atomic revert: */
1682
+ /** Parse atomic block: atomic { ... } / atomic skip { ... } */
1774
1683
  parseAtomicStatement() {
1775
1684
  const startToken = this.current();
1776
1685
  this.expect("KEYWORD", "atomic");
1777
1686
  let onFailure;
1778
- // Check for failure mode: atomic skip: / atomic halt: / atomic revert:
1779
- // Note: "halt" is a keyword, "skip" and "revert" are identifiers
1687
+ // Check for failure mode: atomic skip { ... } / atomic halt { ... } / atomic revert { ... }
1780
1688
  const failureModes = ["skip", "halt", "revert"];
1781
1689
  if ((this.check("IDENTIFIER") || this.check("KEYWORD")) &&
1782
1690
  failureModes.includes(this.current().value)) {
1783
1691
  onFailure = this.current().value;
1784
1692
  this.advance();
1785
1693
  }
1786
- this.expect("COLON");
1787
- this.expectNewline();
1788
- const body = this.parseStatementBlock();
1694
+ const body = this.parseBraceBlock();
1789
1695
  const node = { kind: "atomic", body, onFailure };
1790
1696
  node.span = this.makeSpan(startToken);
1791
1697
  return node;