@grimoirelabs/core 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 (205) hide show
  1. package/dist/builders/expressions.d.ts +33 -0
  2. package/dist/builders/expressions.d.ts.map +1 -0
  3. package/dist/builders/expressions.js +57 -0
  4. package/dist/builders/expressions.js.map +1 -0
  5. package/dist/builders/index.d.ts +44 -0
  6. package/dist/builders/index.d.ts.map +1 -0
  7. package/dist/builders/index.js +32 -0
  8. package/dist/builders/index.js.map +1 -0
  9. package/dist/builders/spell-builder.d.ts +124 -0
  10. package/dist/builders/spell-builder.d.ts.map +1 -0
  11. package/dist/builders/spell-builder.js +299 -0
  12. package/dist/builders/spell-builder.js.map +1 -0
  13. package/dist/builders/step-builder.d.ts +212 -0
  14. package/dist/builders/step-builder.d.ts.map +1 -0
  15. package/dist/builders/step-builder.js +499 -0
  16. package/dist/builders/step-builder.js.map +1 -0
  17. package/dist/compiler/expression-parser.d.ts +14 -0
  18. package/dist/compiler/expression-parser.d.ts.map +1 -0
  19. package/dist/compiler/expression-parser.js +460 -0
  20. package/dist/compiler/expression-parser.js.map +1 -0
  21. package/dist/compiler/grimoire/ast.d.ts +450 -0
  22. package/dist/compiler/grimoire/ast.d.ts.map +1 -0
  23. package/dist/compiler/grimoire/ast.js +19 -0
  24. package/dist/compiler/grimoire/ast.js.map +1 -0
  25. package/dist/compiler/grimoire/errors.d.ts +65 -0
  26. package/dist/compiler/grimoire/errors.d.ts.map +1 -0
  27. package/dist/compiler/grimoire/errors.js +86 -0
  28. package/dist/compiler/grimoire/errors.js.map +1 -0
  29. package/dist/compiler/grimoire/index.d.ts +24 -0
  30. package/dist/compiler/grimoire/index.d.ts.map +1 -0
  31. package/dist/compiler/grimoire/index.js +63 -0
  32. package/dist/compiler/grimoire/index.js.map +1 -0
  33. package/dist/compiler/grimoire/parser.d.ts +135 -0
  34. package/dist/compiler/grimoire/parser.d.ts.map +1 -0
  35. package/dist/compiler/grimoire/parser.js +2148 -0
  36. package/dist/compiler/grimoire/parser.js.map +1 -0
  37. package/dist/compiler/grimoire/tokenizer.d.ts +59 -0
  38. package/dist/compiler/grimoire/tokenizer.d.ts.map +1 -0
  39. package/dist/compiler/grimoire/tokenizer.js +509 -0
  40. package/dist/compiler/grimoire/tokenizer.js.map +1 -0
  41. package/dist/compiler/grimoire/transformer.d.ts +71 -0
  42. package/dist/compiler/grimoire/transformer.d.ts.map +1 -0
  43. package/dist/compiler/grimoire/transformer.js +1011 -0
  44. package/dist/compiler/grimoire/transformer.js.map +1 -0
  45. package/dist/compiler/index.d.ts +45 -0
  46. package/dist/compiler/index.d.ts.map +1 -0
  47. package/dist/compiler/index.js +97 -0
  48. package/dist/compiler/index.js.map +1 -0
  49. package/dist/compiler/ir-generator.d.ts +16 -0
  50. package/dist/compiler/ir-generator.d.ts.map +1 -0
  51. package/dist/compiler/ir-generator.js +997 -0
  52. package/dist/compiler/ir-generator.js.map +1 -0
  53. package/dist/compiler/validator.d.ts +15 -0
  54. package/dist/compiler/validator.d.ts.map +1 -0
  55. package/dist/compiler/validator.js +401 -0
  56. package/dist/compiler/validator.js.map +1 -0
  57. package/dist/index.d.ts +16 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +17 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/runtime/circuit-breaker.d.ts +59 -0
  62. package/dist/runtime/circuit-breaker.d.ts.map +1 -0
  63. package/dist/runtime/circuit-breaker.js +155 -0
  64. package/dist/runtime/circuit-breaker.js.map +1 -0
  65. package/dist/runtime/context.d.ts +92 -0
  66. package/dist/runtime/context.d.ts.map +1 -0
  67. package/dist/runtime/context.js +219 -0
  68. package/dist/runtime/context.js.map +1 -0
  69. package/dist/runtime/error-classifier.d.ts +16 -0
  70. package/dist/runtime/error-classifier.d.ts.map +1 -0
  71. package/dist/runtime/error-classifier.js +38 -0
  72. package/dist/runtime/error-classifier.js.map +1 -0
  73. package/dist/runtime/expression-evaluator.d.ts +36 -0
  74. package/dist/runtime/expression-evaluator.d.ts.map +1 -0
  75. package/dist/runtime/expression-evaluator.js +391 -0
  76. package/dist/runtime/expression-evaluator.js.map +1 -0
  77. package/dist/runtime/index.d.ts +12 -0
  78. package/dist/runtime/index.d.ts.map +1 -0
  79. package/dist/runtime/index.js +11 -0
  80. package/dist/runtime/index.js.map +1 -0
  81. package/dist/runtime/interpreter.d.ts +59 -0
  82. package/dist/runtime/interpreter.d.ts.map +1 -0
  83. package/dist/runtime/interpreter.js +414 -0
  84. package/dist/runtime/interpreter.js.map +1 -0
  85. package/dist/runtime/skills/registry.d.ts +11 -0
  86. package/dist/runtime/skills/registry.d.ts.map +1 -0
  87. package/dist/runtime/skills/registry.js +73 -0
  88. package/dist/runtime/skills/registry.js.map +1 -0
  89. package/dist/runtime/sqlite-state-store.d.ts +28 -0
  90. package/dist/runtime/sqlite-state-store.d.ts.map +1 -0
  91. package/dist/runtime/sqlite-state-store.js +180 -0
  92. package/dist/runtime/sqlite-state-store.js.map +1 -0
  93. package/dist/runtime/state-store.d.ts +52 -0
  94. package/dist/runtime/state-store.d.ts.map +1 -0
  95. package/dist/runtime/state-store.js +32 -0
  96. package/dist/runtime/state-store.js.map +1 -0
  97. package/dist/runtime/steps/action.d.ts +17 -0
  98. package/dist/runtime/steps/action.d.ts.map +1 -0
  99. package/dist/runtime/steps/action.js +430 -0
  100. package/dist/runtime/steps/action.js.map +1 -0
  101. package/dist/runtime/steps/advisory.d.ts +28 -0
  102. package/dist/runtime/steps/advisory.d.ts.map +1 -0
  103. package/dist/runtime/steps/advisory.js +209 -0
  104. package/dist/runtime/steps/advisory.js.map +1 -0
  105. package/dist/runtime/steps/compute.d.ts +9 -0
  106. package/dist/runtime/steps/compute.d.ts.map +1 -0
  107. package/dist/runtime/steps/compute.js +74 -0
  108. package/dist/runtime/steps/compute.js.map +1 -0
  109. package/dist/runtime/steps/conditional.d.ts +14 -0
  110. package/dist/runtime/steps/conditional.d.ts.map +1 -0
  111. package/dist/runtime/steps/conditional.js +58 -0
  112. package/dist/runtime/steps/conditional.js.map +1 -0
  113. package/dist/runtime/steps/emit.d.ts +9 -0
  114. package/dist/runtime/steps/emit.d.ts.map +1 -0
  115. package/dist/runtime/steps/emit.js +70 -0
  116. package/dist/runtime/steps/emit.js.map +1 -0
  117. package/dist/runtime/steps/halt.d.ts +9 -0
  118. package/dist/runtime/steps/halt.d.ts.map +1 -0
  119. package/dist/runtime/steps/halt.js +19 -0
  120. package/dist/runtime/steps/halt.js.map +1 -0
  121. package/dist/runtime/steps/loop.d.ts +14 -0
  122. package/dist/runtime/steps/loop.d.ts.map +1 -0
  123. package/dist/runtime/steps/loop.js +109 -0
  124. package/dist/runtime/steps/loop.js.map +1 -0
  125. package/dist/runtime/steps/parallel.d.ts +9 -0
  126. package/dist/runtime/steps/parallel.d.ts.map +1 -0
  127. package/dist/runtime/steps/parallel.js +87 -0
  128. package/dist/runtime/steps/parallel.js.map +1 -0
  129. package/dist/runtime/steps/pipeline.d.ts +9 -0
  130. package/dist/runtime/steps/pipeline.d.ts.map +1 -0
  131. package/dist/runtime/steps/pipeline.js +125 -0
  132. package/dist/runtime/steps/pipeline.js.map +1 -0
  133. package/dist/runtime/steps/try.d.ts +13 -0
  134. package/dist/runtime/steps/try.d.ts.map +1 -0
  135. package/dist/runtime/steps/try.js +222 -0
  136. package/dist/runtime/steps/try.js.map +1 -0
  137. package/dist/runtime/steps/wait.d.ts +9 -0
  138. package/dist/runtime/steps/wait.d.ts.map +1 -0
  139. package/dist/runtime/steps/wait.js +38 -0
  140. package/dist/runtime/steps/wait.js.map +1 -0
  141. package/dist/types/actions.d.ts +162 -0
  142. package/dist/types/actions.d.ts.map +1 -0
  143. package/dist/types/actions.js +5 -0
  144. package/dist/types/actions.js.map +1 -0
  145. package/dist/types/execution.d.ts +276 -0
  146. package/dist/types/execution.d.ts.map +1 -0
  147. package/dist/types/execution.js +5 -0
  148. package/dist/types/execution.js.map +1 -0
  149. package/dist/types/expressions.d.ts +100 -0
  150. package/dist/types/expressions.d.ts.map +1 -0
  151. package/dist/types/expressions.js +48 -0
  152. package/dist/types/expressions.js.map +1 -0
  153. package/dist/types/index.d.ts +14 -0
  154. package/dist/types/index.d.ts.map +1 -0
  155. package/dist/types/index.js +5 -0
  156. package/dist/types/index.js.map +1 -0
  157. package/dist/types/ir.d.ts +187 -0
  158. package/dist/types/ir.d.ts.map +1 -0
  159. package/dist/types/ir.js +5 -0
  160. package/dist/types/ir.js.map +1 -0
  161. package/dist/types/policy.d.ts +123 -0
  162. package/dist/types/policy.d.ts.map +1 -0
  163. package/dist/types/policy.js +5 -0
  164. package/dist/types/policy.js.map +1 -0
  165. package/dist/types/primitives.d.ts +76 -0
  166. package/dist/types/primitives.d.ts.map +1 -0
  167. package/dist/types/primitives.js +10 -0
  168. package/dist/types/primitives.js.map +1 -0
  169. package/dist/types/steps.d.ts +226 -0
  170. package/dist/types/steps.d.ts.map +1 -0
  171. package/dist/types/steps.js +5 -0
  172. package/dist/types/steps.js.map +1 -0
  173. package/dist/venues/index.d.ts +6 -0
  174. package/dist/venues/index.d.ts.map +1 -0
  175. package/dist/venues/index.js +26 -0
  176. package/dist/venues/index.js.map +1 -0
  177. package/dist/venues/types.d.ts +40 -0
  178. package/dist/venues/types.d.ts.map +1 -0
  179. package/dist/venues/types.js +5 -0
  180. package/dist/venues/types.js.map +1 -0
  181. package/dist/wallet/executor.d.ts +109 -0
  182. package/dist/wallet/executor.d.ts.map +1 -0
  183. package/dist/wallet/executor.js +354 -0
  184. package/dist/wallet/executor.js.map +1 -0
  185. package/dist/wallet/index.d.ts +14 -0
  186. package/dist/wallet/index.d.ts.map +1 -0
  187. package/dist/wallet/index.js +13 -0
  188. package/dist/wallet/index.js.map +1 -0
  189. package/dist/wallet/keystore.d.ts +44 -0
  190. package/dist/wallet/keystore.d.ts.map +1 -0
  191. package/dist/wallet/keystore.js +296 -0
  192. package/dist/wallet/keystore.js.map +1 -0
  193. package/dist/wallet/provider.d.ts +111 -0
  194. package/dist/wallet/provider.d.ts.map +1 -0
  195. package/dist/wallet/provider.js +309 -0
  196. package/dist/wallet/provider.js.map +1 -0
  197. package/dist/wallet/tx-builder.d.ts +85 -0
  198. package/dist/wallet/tx-builder.d.ts.map +1 -0
  199. package/dist/wallet/tx-builder.js +290 -0
  200. package/dist/wallet/tx-builder.js.map +1 -0
  201. package/dist/wallet/types.d.ts +116 -0
  202. package/dist/wallet/types.d.ts.map +1 -0
  203. package/dist/wallet/types.js +86 -0
  204. package/dist/wallet/types.js.map +1 -0
  205. package/package.json +39 -0
@@ -0,0 +1,2148 @@
1
+ /**
2
+ * Recursive descent parser for Grimoire syntax
3
+ */
4
+ import { ParseError } from "./errors.js";
5
+ import { tokenize } from "./tokenizer.js";
6
+ // =============================================================================
7
+ // PARSER CLASS
8
+ // =============================================================================
9
+ export class Parser {
10
+ tokens;
11
+ source;
12
+ pos = 0;
13
+ constructor(tokens, source) {
14
+ this.tokens = tokens;
15
+ this.source = source;
16
+ }
17
+ // ===========================================================================
18
+ // UTILITIES
19
+ // ===========================================================================
20
+ /** Get current token */
21
+ current() {
22
+ return (this.tokens[this.pos] ?? {
23
+ type: "EOF",
24
+ value: "",
25
+ location: { line: 0, column: 0, offset: 0 },
26
+ });
27
+ }
28
+ /** Peek at token at offset */
29
+ peek(offset = 1) {
30
+ return (this.tokens[this.pos + offset] ?? {
31
+ type: "EOF",
32
+ value: "",
33
+ location: { line: 0, column: 0, offset: 0 },
34
+ });
35
+ }
36
+ /** Check if current token matches */
37
+ check(type, value) {
38
+ const token = this.current();
39
+ if (token.type !== type)
40
+ return false;
41
+ if (value !== undefined && token.value !== value)
42
+ return false;
43
+ return true;
44
+ }
45
+ /** Advance and return previous token */
46
+ advance() {
47
+ const token = this.current();
48
+ if (token.type !== "EOF") {
49
+ this.pos++;
50
+ }
51
+ return token;
52
+ }
53
+ /** Expect and consume a specific token */
54
+ expect(type, value) {
55
+ const token = this.current();
56
+ if (token.type !== type) {
57
+ throw new ParseError(`Expected ${type}${value ? ` '${value}'` : ""} but got ${token.type} '${token.value}'`, { location: token.location, source: this.source });
58
+ }
59
+ if (value !== undefined && token.value !== value) {
60
+ throw new ParseError(`Expected '${value}' but got '${token.value}'`, {
61
+ location: token.location,
62
+ source: this.source,
63
+ });
64
+ }
65
+ return this.advance();
66
+ }
67
+ /** Skip newlines */
68
+ skipNewlines() {
69
+ while (this.check("NEWLINE")) {
70
+ this.advance();
71
+ }
72
+ }
73
+ /** Consume a newline (required) */
74
+ expectNewline() {
75
+ if (!this.check("NEWLINE") && !this.check("EOF")) {
76
+ throw new ParseError(`Expected newline but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
77
+ }
78
+ if (this.check("NEWLINE")) {
79
+ this.advance();
80
+ }
81
+ }
82
+ /** Create a source span from a start location to the current token's location */
83
+ makeSpan(startToken) {
84
+ const end = this.current().location;
85
+ return { start: startToken.location, end };
86
+ }
87
+ // ===========================================================================
88
+ // TOP-LEVEL PARSING
89
+ // ===========================================================================
90
+ /** Parse a complete spell file */
91
+ parseSpellFile() {
92
+ this.skipNewlines();
93
+ // Expect: spell Name
94
+ this.expect("KEYWORD", "spell");
95
+ const nameToken = this.expect("IDENTIFIER");
96
+ const name = nameToken.value;
97
+ this.expectNewline();
98
+ this.skipNewlines();
99
+ // Parse sections and triggers
100
+ const sections = [];
101
+ const triggers = [];
102
+ const imports = [];
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")) {
113
+ this.skipNewlines();
114
+ if (this.check("DEDENT") || this.check("EOF"))
115
+ break;
116
+ // Check for import or block or trigger
117
+ if (this.check("KEYWORD", "import")) {
118
+ imports.push(this.parseImport());
119
+ }
120
+ else if (this.check("KEYWORD", "block")) {
121
+ blocks.push(this.parseBlock());
122
+ }
123
+ else if (this.check("KEYWORD", "on")) {
124
+ triggers.push(this.parseTriggerHandler());
125
+ }
126
+ else {
127
+ sections.push(this.parseSection());
128
+ }
129
+ this.skipNewlines();
130
+ }
131
+ // Consume DEDENT
132
+ if (this.check("DEDENT")) {
133
+ this.advance();
134
+ }
135
+ return {
136
+ kind: "spell",
137
+ name,
138
+ sections,
139
+ triggers,
140
+ imports,
141
+ blocks,
142
+ };
143
+ }
144
+ // ===========================================================================
145
+ // SECTION PARSING
146
+ // ===========================================================================
147
+ /** Parse import declaration: import "path" */
148
+ parseImport() {
149
+ const startToken = this.current();
150
+ this.expect("KEYWORD", "import");
151
+ const pathToken = this.expect("STRING");
152
+ let alias;
153
+ if (this.check("KEYWORD", "as") ||
154
+ (this.check("IDENTIFIER") && this.current().value === "as")) {
155
+ this.advance();
156
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
157
+ alias = this.advance().value;
158
+ }
159
+ else {
160
+ throw new ParseError("Expected alias name after 'as'", {
161
+ location: this.current().location,
162
+ source: this.source,
163
+ });
164
+ }
165
+ }
166
+ this.expectNewline();
167
+ const node = { kind: "import", path: pathToken.value, alias };
168
+ node.span = this.makeSpan(startToken);
169
+ return node;
170
+ }
171
+ /** Parse block definition: block name(arg, ...): */
172
+ parseBlock() {
173
+ const startToken = this.current();
174
+ this.expect("KEYWORD", "block");
175
+ const name = this.expect("IDENTIFIER").value;
176
+ const params = [];
177
+ if (this.check("LPAREN")) {
178
+ this.advance();
179
+ while (!this.check("RPAREN")) {
180
+ const param = this.expect("IDENTIFIER").value;
181
+ params.push(param);
182
+ if (this.check("COMMA")) {
183
+ this.advance();
184
+ }
185
+ }
186
+ this.expect("RPAREN");
187
+ }
188
+ this.expect("COLON");
189
+ this.expectNewline();
190
+ const body = this.parseStatementBlock();
191
+ const node = { kind: "block", name, params, body };
192
+ node.span = this.makeSpan(startToken);
193
+ return node;
194
+ }
195
+ /** Parse a section (version, assets, params, etc.) */
196
+ parseSection() {
197
+ const token = this.current();
198
+ if (token.type === "KEYWORD") {
199
+ switch (token.value) {
200
+ case "version":
201
+ return this.parseVersionSection();
202
+ case "description":
203
+ return this.parseDescriptionSection();
204
+ case "assets":
205
+ return this.parseAssetsSection();
206
+ case "params":
207
+ return this.parseParamsSection();
208
+ case "limits":
209
+ return this.parseLimitsSection();
210
+ case "venues":
211
+ return this.parseVenuesSection();
212
+ case "state":
213
+ return this.parseStateSection();
214
+ case "skills":
215
+ return this.parseSkillsSection();
216
+ case "advisors":
217
+ return this.parseAdvisorsSection();
218
+ case "guards":
219
+ return this.parseGuardsSection();
220
+ }
221
+ }
222
+ // Could be a simple key: value
223
+ if (token.type === "IDENTIFIER") {
224
+ const key = token.value;
225
+ this.advance();
226
+ this.expect("COLON");
227
+ // Handle known identifiers as sections
228
+ if (key === "version") {
229
+ const value = this.parseSimpleValue();
230
+ this.expectNewline();
231
+ return { kind: "version", value: String(value) };
232
+ }
233
+ }
234
+ throw new ParseError(`Unexpected token in section: ${token.type} '${token.value}'`, {
235
+ location: token.location,
236
+ source: this.source,
237
+ });
238
+ }
239
+ /** Parse version: "1.0.0" */
240
+ parseVersionSection() {
241
+ this.expect("KEYWORD", "version");
242
+ this.expect("COLON");
243
+ const value = this.parseSimpleValue();
244
+ this.expectNewline();
245
+ return { kind: "version", value: String(value) };
246
+ }
247
+ /** Parse description: "..." */
248
+ parseDescriptionSection() {
249
+ this.expect("KEYWORD", "description");
250
+ this.expect("COLON");
251
+ const value = this.parseSimpleValue();
252
+ this.expectNewline();
253
+ return { kind: "description", value: String(value) };
254
+ }
255
+ /** Parse simple value (string, number, boolean) */
256
+ parseSimpleValue() {
257
+ const token = this.current();
258
+ if (token.type === "STRING") {
259
+ this.advance();
260
+ return token.value;
261
+ }
262
+ if (token.type === "NUMBER") {
263
+ this.advance();
264
+ return Number.parseFloat(token.value);
265
+ }
266
+ if (token.type === "BOOLEAN") {
267
+ this.advance();
268
+ return token.value === "true";
269
+ }
270
+ throw new ParseError(`Expected value but got ${token.type}`, {
271
+ location: token.location,
272
+ source: this.source,
273
+ });
274
+ }
275
+ /** Parse list of strings/identifiers (e.g., [a, "b"]) */
276
+ parseStringList() {
277
+ const items = [];
278
+ if (this.check("LBRACKET")) {
279
+ this.advance();
280
+ while (!this.check("RBRACKET")) {
281
+ if (this.check("STRING")) {
282
+ items.push(this.advance().value);
283
+ }
284
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
285
+ items.push(this.advance().value);
286
+ }
287
+ else if (this.check("VENUE_REF")) {
288
+ items.push(this.advance().value);
289
+ }
290
+ else {
291
+ throw new ParseError("Expected list item", {
292
+ location: this.current().location,
293
+ source: this.source,
294
+ });
295
+ }
296
+ if (this.check("COMMA"))
297
+ this.advance();
298
+ }
299
+ this.expect("RBRACKET");
300
+ return items;
301
+ }
302
+ // Single item
303
+ if (this.check("STRING")) {
304
+ items.push(this.advance().value);
305
+ return items;
306
+ }
307
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
308
+ items.push(this.advance().value);
309
+ return items;
310
+ }
311
+ if (this.check("VENUE_REF")) {
312
+ items.push(this.advance().value);
313
+ return items;
314
+ }
315
+ throw new ParseError("Expected list", {
316
+ location: this.current().location,
317
+ source: this.source,
318
+ });
319
+ }
320
+ /** Parse assets: [USDC, USDT] or assets block */
321
+ parseAssetsSection() {
322
+ this.expect("KEYWORD", "assets");
323
+ this.expect("COLON");
324
+ const items = [];
325
+ // Inline array: [USDC, USDT, DAI]
326
+ if (this.check("LBRACKET")) {
327
+ this.advance();
328
+ while (!this.check("RBRACKET")) {
329
+ const symbol = this.expect("IDENTIFIER").value;
330
+ items.push({ symbol });
331
+ if (this.check("COMMA")) {
332
+ this.advance();
333
+ }
334
+ }
335
+ this.expect("RBRACKET");
336
+ this.expectNewline();
337
+ }
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
+ });
357
+ }
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();
384
+ }
385
+ else if (key === "decimals") {
386
+ const val = this.expect("NUMBER").value;
387
+ asset.decimals = Number.parseInt(val, 10);
388
+ this.expectNewline();
389
+ }
390
+ else {
391
+ this.parseExpression();
392
+ this.expectNewline();
393
+ }
394
+ }
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
+ }
403
+ items.push(asset);
404
+ }
405
+ if (this.check("DEDENT"))
406
+ this.advance();
407
+ }
408
+ }
409
+ return { kind: "assets", items };
410
+ }
411
+ /** Parse params section */
412
+ parseParamsSection() {
413
+ this.expect("KEYWORD", "params");
414
+ this.expect("COLON");
415
+ this.expectNewline();
416
+ const items = [];
417
+ if (this.check("INDENT")) {
418
+ this.advance();
419
+ while (!this.check("DEDENT") && !this.check("EOF")) {
420
+ this.skipNewlines();
421
+ if (this.check("DEDENT"))
422
+ break;
423
+ const name = this.expect("IDENTIFIER").value;
424
+ this.expect("COLON");
425
+ 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
+ }
435
+ this.advance();
436
+ while (!this.check("DEDENT") && !this.check("EOF")) {
437
+ this.skipNewlines();
438
+ if (this.check("DEDENT") || this.check("EOF"))
439
+ break;
440
+ const keyToken = this.current();
441
+ if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
442
+ throw new ParseError(`Expected param field but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
443
+ }
444
+ const key = keyToken.value;
445
+ this.advance();
446
+ this.expect("COLON");
447
+ if (key === "type") {
448
+ if (this.check("STRING")) {
449
+ item.type = this.advance().value;
450
+ }
451
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
452
+ item.type = this.advance().value;
453
+ }
454
+ else {
455
+ throw new ParseError("Expected param type value", {
456
+ location: keyToken.location,
457
+ source: this.source,
458
+ });
459
+ }
460
+ this.expectNewline();
461
+ }
462
+ else if (key === "asset") {
463
+ if (this.check("STRING")) {
464
+ item.asset = this.advance().value;
465
+ }
466
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
467
+ item.asset = this.advance().value;
468
+ }
469
+ else {
470
+ throw new ParseError("Expected asset identifier", {
471
+ location: keyToken.location,
472
+ source: this.source,
473
+ });
474
+ }
475
+ this.expectNewline();
476
+ }
477
+ else if (key === "default") {
478
+ item.value = this.parseExpression();
479
+ this.expectNewline();
480
+ }
481
+ else if (key === "min") {
482
+ const val = this.expect("NUMBER").value;
483
+ item.min = Number.parseFloat(val);
484
+ this.expectNewline();
485
+ }
486
+ else if (key === "max") {
487
+ const val = this.expect("NUMBER").value;
488
+ item.max = Number.parseFloat(val);
489
+ this.expectNewline();
490
+ }
491
+ else {
492
+ // Unknown field - parse and ignore
493
+ this.parseExpression();
494
+ this.expectNewline();
495
+ }
496
+ }
497
+ if (this.check("DEDENT"))
498
+ this.advance();
499
+ }
500
+ else {
501
+ // Inline form: name: value
502
+ item.value = this.parseExpression();
503
+ this.expectNewline();
504
+ }
505
+ items.push(item);
506
+ this.skipNewlines();
507
+ }
508
+ if (this.check("DEDENT"))
509
+ this.advance();
510
+ }
511
+ return { kind: "params", items };
512
+ }
513
+ /** Parse limits section */
514
+ parseLimitsSection() {
515
+ this.expect("KEYWORD", "limits");
516
+ this.expect("COLON");
517
+ this.expectNewline();
518
+ const items = [];
519
+ if (this.check("INDENT")) {
520
+ this.advance();
521
+ while (!this.check("DEDENT") && !this.check("EOF")) {
522
+ this.skipNewlines();
523
+ if (this.check("DEDENT"))
524
+ break;
525
+ const name = this.expect("IDENTIFIER").value;
526
+ this.expect("COLON");
527
+ const value = this.parseExpression();
528
+ items.push({ name, value });
529
+ this.expectNewline();
530
+ this.skipNewlines();
531
+ }
532
+ if (this.check("DEDENT"))
533
+ this.advance();
534
+ }
535
+ return { kind: "limits", items };
536
+ }
537
+ /** Parse venues section */
538
+ parseVenuesSection() {
539
+ this.expect("KEYWORD", "venues");
540
+ this.expect("COLON");
541
+ this.expectNewline();
542
+ const groups = [];
543
+ if (this.check("INDENT")) {
544
+ this.advance();
545
+ while (!this.check("DEDENT") && !this.check("EOF")) {
546
+ this.skipNewlines();
547
+ if (this.check("DEDENT"))
548
+ break;
549
+ const name = this.expect("IDENTIFIER").value;
550
+ this.expect("COLON");
551
+ const venues = [];
552
+ // Single venue: swap: @uniswap_v3
553
+ if (this.check("VENUE_REF")) {
554
+ const venueToken = this.advance();
555
+ venues.push({ kind: "venue_ref", name: venueToken.value });
556
+ }
557
+ // Array of venues: lending: [@aave_v3, @morpho]
558
+ else if (this.check("LBRACKET")) {
559
+ this.advance();
560
+ while (!this.check("RBRACKET")) {
561
+ if (this.check("VENUE_REF")) {
562
+ const venueToken = this.advance();
563
+ venues.push({ kind: "venue_ref", name: venueToken.value });
564
+ }
565
+ if (this.check("COMMA")) {
566
+ this.advance();
567
+ }
568
+ }
569
+ this.expect("RBRACKET");
570
+ }
571
+ groups.push({ name, venues });
572
+ this.expectNewline();
573
+ this.skipNewlines();
574
+ }
575
+ if (this.check("DEDENT"))
576
+ this.advance();
577
+ }
578
+ return { kind: "venues", groups };
579
+ }
580
+ /** Parse state section */
581
+ parseStateSection() {
582
+ this.expect("KEYWORD", "state");
583
+ this.expect("COLON");
584
+ this.expectNewline();
585
+ const persistent = [];
586
+ const ephemeral = [];
587
+ if (this.check("INDENT")) {
588
+ this.advance();
589
+ while (!this.check("DEDENT") && !this.check("EOF")) {
590
+ this.skipNewlines();
591
+ if (this.check("DEDENT"))
592
+ break;
593
+ const scope = this.expect("KEYWORD").value;
594
+ this.expect("COLON");
595
+ this.expectNewline();
596
+ const items = scope === "persistent" ? persistent : ephemeral;
597
+ if (this.check("INDENT")) {
598
+ this.advance();
599
+ while (!this.check("DEDENT") && !this.check("EOF")) {
600
+ this.skipNewlines();
601
+ if (this.check("DEDENT"))
602
+ break;
603
+ const name = this.expect("IDENTIFIER").value;
604
+ this.expect("COLON");
605
+ const initialValue = this.parseExpression();
606
+ items.push({ name, initialValue });
607
+ this.expectNewline();
608
+ this.skipNewlines();
609
+ }
610
+ if (this.check("DEDENT"))
611
+ this.advance();
612
+ }
613
+ this.skipNewlines();
614
+ }
615
+ if (this.check("DEDENT"))
616
+ this.advance();
617
+ }
618
+ return { kind: "state", persistent, ephemeral };
619
+ }
620
+ /** Parse skills section */
621
+ parseSkillsSection() {
622
+ const startToken = this.current();
623
+ this.expect("KEYWORD", "skills");
624
+ this.expect("COLON");
625
+ this.expectNewline();
626
+ const items = [];
627
+ if (this.check("INDENT")) {
628
+ this.advance();
629
+ while (!this.check("DEDENT") && !this.check("EOF")) {
630
+ this.skipNewlines();
631
+ if (this.check("DEDENT") || this.check("EOF"))
632
+ break;
633
+ const name = this.expect("IDENTIFIER").value;
634
+ this.expect("COLON");
635
+ this.expectNewline();
636
+ let type;
637
+ const adapters = [];
638
+ let defaultMaxSlippage;
639
+ if (this.check("INDENT")) {
640
+ this.advance();
641
+ while (!this.check("DEDENT") && !this.check("EOF")) {
642
+ this.skipNewlines();
643
+ if (this.check("DEDENT") || this.check("EOF"))
644
+ break;
645
+ const keyToken = this.current();
646
+ if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
647
+ throw new ParseError(`Expected skill field but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
648
+ }
649
+ const key = keyToken.value;
650
+ this.advance();
651
+ this.expect("COLON");
652
+ if (key === "type") {
653
+ // Accept identifier or string
654
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
655
+ type = this.advance().value;
656
+ }
657
+ else if (this.check("STRING")) {
658
+ type = this.advance().value;
659
+ }
660
+ else {
661
+ throw new ParseError("Expected skill type value", {
662
+ location: this.current().location,
663
+ source: this.source,
664
+ });
665
+ }
666
+ this.expectNewline();
667
+ }
668
+ else if (key === "adapters") {
669
+ if (this.check("LBRACKET")) {
670
+ this.advance();
671
+ while (!this.check("RBRACKET")) {
672
+ if (this.check("VENUE_REF")) {
673
+ adapters.push(this.advance().value);
674
+ }
675
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
676
+ adapters.push(this.advance().value);
677
+ }
678
+ else if (this.check("STRING")) {
679
+ adapters.push(this.advance().value);
680
+ }
681
+ else {
682
+ throw new ParseError("Expected adapter name", {
683
+ location: this.current().location,
684
+ source: this.source,
685
+ });
686
+ }
687
+ if (this.check("COMMA"))
688
+ this.advance();
689
+ }
690
+ this.expect("RBRACKET");
691
+ }
692
+ else {
693
+ // Single adapter
694
+ if (this.check("VENUE_REF")) {
695
+ adapters.push(this.advance().value);
696
+ }
697
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
698
+ adapters.push(this.advance().value);
699
+ }
700
+ else if (this.check("STRING")) {
701
+ adapters.push(this.advance().value);
702
+ }
703
+ else {
704
+ throw new ParseError("Expected adapter name", {
705
+ location: this.current().location,
706
+ source: this.source,
707
+ });
708
+ }
709
+ }
710
+ this.expectNewline();
711
+ }
712
+ else if (key === "default_constraints") {
713
+ this.expectNewline();
714
+ if (this.check("INDENT")) {
715
+ this.advance();
716
+ while (!this.check("DEDENT") && !this.check("EOF")) {
717
+ this.skipNewlines();
718
+ if (this.check("DEDENT") || this.check("EOF"))
719
+ break;
720
+ const dcKey = this.expect("IDENTIFIER").value;
721
+ this.expect("COLON");
722
+ const value = this.parseExpression();
723
+ if (dcKey === "max_slippage") {
724
+ defaultMaxSlippage = value;
725
+ }
726
+ this.expectNewline();
727
+ }
728
+ if (this.check("DEDENT"))
729
+ this.advance();
730
+ }
731
+ }
732
+ else {
733
+ // Unknown field, parse and ignore
734
+ this.parseExpression();
735
+ this.expectNewline();
736
+ }
737
+ }
738
+ if (this.check("DEDENT"))
739
+ this.advance();
740
+ }
741
+ if (!type) {
742
+ throw new ParseError(`Skill '${name}' missing type`, {
743
+ location: this.current().location,
744
+ source: this.source,
745
+ });
746
+ }
747
+ items.push({
748
+ name,
749
+ type,
750
+ adapters,
751
+ defaultConstraints: defaultMaxSlippage ? { maxSlippage: defaultMaxSlippage } : undefined,
752
+ });
753
+ this.skipNewlines();
754
+ }
755
+ if (this.check("DEDENT"))
756
+ this.advance();
757
+ }
758
+ const node = { kind: "skills", items };
759
+ node.span = this.makeSpan(startToken);
760
+ return node;
761
+ }
762
+ /** Parse advisors section */
763
+ parseAdvisorsSection() {
764
+ const startToken = this.current();
765
+ this.expect("KEYWORD", "advisors");
766
+ this.expect("COLON");
767
+ this.expectNewline();
768
+ const items = [];
769
+ if (this.check("INDENT")) {
770
+ this.advance();
771
+ while (!this.check("DEDENT") && !this.check("EOF")) {
772
+ this.skipNewlines();
773
+ if (this.check("DEDENT") || this.check("EOF"))
774
+ break;
775
+ const name = this.expect("IDENTIFIER").value;
776
+ this.expect("COLON");
777
+ this.expectNewline();
778
+ const advisor = {
779
+ name,
780
+ model: "sonnet",
781
+ };
782
+ if (this.check("INDENT")) {
783
+ this.advance();
784
+ while (!this.check("DEDENT") && !this.check("EOF")) {
785
+ this.skipNewlines();
786
+ if (this.check("DEDENT") || this.check("EOF"))
787
+ break;
788
+ const keyToken = this.current();
789
+ if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
790
+ throw new ParseError(`Expected advisor field but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
791
+ }
792
+ const key = keyToken.value;
793
+ this.advance();
794
+ this.expect("COLON");
795
+ if (key === "model") {
796
+ if (this.check("STRING")) {
797
+ advisor.model = this.advance().value;
798
+ }
799
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
800
+ advisor.model = this.advance().value;
801
+ }
802
+ else {
803
+ throw new ParseError("Expected model value", {
804
+ location: this.current().location,
805
+ source: this.source,
806
+ });
807
+ }
808
+ this.expectNewline();
809
+ }
810
+ else if (key === "system" || key === "system_prompt") {
811
+ const value = this.expect("STRING").value;
812
+ advisor.systemPrompt = value;
813
+ this.expectNewline();
814
+ }
815
+ else if (key === "skills") {
816
+ advisor.skills = this.parseStringList();
817
+ this.expectNewline();
818
+ }
819
+ else if (key === "allowed_tools") {
820
+ advisor.allowedTools = this.parseStringList();
821
+ this.expectNewline();
822
+ }
823
+ else if (key === "mcp") {
824
+ advisor.mcp = this.parseStringList();
825
+ this.expectNewline();
826
+ }
827
+ else if (key === "timeout") {
828
+ const value = this.expect("NUMBER").value;
829
+ advisor.timeout = Number.parseFloat(value);
830
+ this.expectNewline();
831
+ }
832
+ else if (key === "fallback") {
833
+ if (this.check("BOOLEAN")) {
834
+ advisor.fallback = this.advance().value === "true";
835
+ }
836
+ else {
837
+ throw new ParseError("Expected boolean fallback", {
838
+ location: this.current().location,
839
+ source: this.source,
840
+ });
841
+ }
842
+ this.expectNewline();
843
+ }
844
+ else if (key === "rate_limit") {
845
+ this.expectNewline();
846
+ if (this.check("INDENT")) {
847
+ this.advance();
848
+ while (!this.check("DEDENT") && !this.check("EOF")) {
849
+ this.skipNewlines();
850
+ if (this.check("DEDENT") || this.check("EOF"))
851
+ break;
852
+ const rlKey = this.expect("IDENTIFIER").value;
853
+ this.expect("COLON");
854
+ const value = this.expect("NUMBER").value;
855
+ if (rlKey === "max_per_run") {
856
+ advisor.maxPerRun = Number.parseFloat(value);
857
+ }
858
+ else if (rlKey === "max_per_hour") {
859
+ advisor.maxPerHour = Number.parseFloat(value);
860
+ }
861
+ this.expectNewline();
862
+ }
863
+ if (this.check("DEDENT"))
864
+ this.advance();
865
+ }
866
+ }
867
+ else {
868
+ // Unknown field - parse and ignore
869
+ this.parseExpression();
870
+ this.expectNewline();
871
+ }
872
+ }
873
+ if (this.check("DEDENT"))
874
+ this.advance();
875
+ }
876
+ items.push(advisor);
877
+ this.skipNewlines();
878
+ }
879
+ if (this.check("DEDENT"))
880
+ this.advance();
881
+ }
882
+ const node = { kind: "advisors", items };
883
+ node.span = this.makeSpan(startToken);
884
+ return node;
885
+ }
886
+ /** Parse guards section: guards:\n id: expression */
887
+ parseGuardsSection() {
888
+ const startToken = this.current();
889
+ this.expect("KEYWORD", "guards");
890
+ this.expect("COLON");
891
+ this.expectNewline();
892
+ const items = [];
893
+ if (this.check("INDENT")) {
894
+ this.advance(); // consume INDENT
895
+ while (!this.check("DEDENT") && !this.check("EOF")) {
896
+ this.skipNewlines();
897
+ if (this.check("DEDENT") || this.check("EOF"))
898
+ break;
899
+ // Each line: id: expression
900
+ const id = this.current().value;
901
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
902
+ this.advance();
903
+ }
904
+ else {
905
+ throw new ParseError(`Expected guard identifier but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
906
+ }
907
+ this.expect("COLON");
908
+ const check = this.parseExpression();
909
+ let severity = "halt";
910
+ let message;
911
+ let fallback;
912
+ if (this.check("KEYWORD", "with")) {
913
+ const meta = this.parseConstraintClause();
914
+ for (const { key, value } of meta.constraints) {
915
+ if (key === "severity" && value.kind === "literal") {
916
+ severity = String(value.value);
917
+ }
918
+ else if (key === "message" && value.kind === "literal") {
919
+ message = String(value.value);
920
+ }
921
+ else if (key === "fallback" && value.kind === "literal") {
922
+ fallback = Boolean(value.value);
923
+ }
924
+ }
925
+ }
926
+ this.expectNewline();
927
+ items.push({
928
+ id,
929
+ check,
930
+ severity,
931
+ message,
932
+ fallback,
933
+ });
934
+ this.skipNewlines();
935
+ }
936
+ if (this.check("DEDENT"))
937
+ this.advance();
938
+ }
939
+ const node = { kind: "guards", items };
940
+ node.span = this.makeSpan(startToken);
941
+ return node;
942
+ }
943
+ // ===========================================================================
944
+ // TRIGGER PARSING
945
+ // ===========================================================================
946
+ /** Parse trigger handler: on manual: ... */
947
+ parseTriggerHandler() {
948
+ this.expect("KEYWORD", "on");
949
+ const triggerType = this.parseTriggerType();
950
+ this.expect("COLON");
951
+ this.expectNewline();
952
+ const body = this.parseStatementBlock();
953
+ return {
954
+ kind: "trigger_handler",
955
+ trigger: triggerType,
956
+ body,
957
+ };
958
+ }
959
+ /** Parse trigger type */
960
+ parseTriggerType() {
961
+ const token = this.current();
962
+ if (token.type === "KEYWORD" || token.type === "IDENTIFIER") {
963
+ switch (token.value) {
964
+ case "manual":
965
+ this.advance();
966
+ return { kind: "manual" };
967
+ case "hourly":
968
+ this.advance();
969
+ return { kind: "hourly" };
970
+ case "daily":
971
+ this.advance();
972
+ return { kind: "daily" };
973
+ case "condition": {
974
+ this.advance();
975
+ const expression = this.parseExpression();
976
+ let pollInterval;
977
+ if (this.check("KEYWORD", "every") ||
978
+ (this.check("IDENTIFIER") && this.current().value === "every")) {
979
+ this.advance();
980
+ const durationToken = this.expect("NUMBER");
981
+ pollInterval = Number.parseFloat(durationToken.value);
982
+ }
983
+ return { kind: "condition", expression, pollInterval };
984
+ }
985
+ case "event": {
986
+ this.advance();
987
+ let eventName = "";
988
+ if (this.check("STRING")) {
989
+ eventName = this.advance().value;
990
+ }
991
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
992
+ eventName = this.advance().value;
993
+ }
994
+ else {
995
+ throw new ParseError("Expected event name", {
996
+ location: this.current().location,
997
+ source: this.source,
998
+ });
999
+ }
1000
+ let filter;
1001
+ if (this.check("KEYWORD", "where") ||
1002
+ (this.check("IDENTIFIER") && this.current().value === "where")) {
1003
+ this.advance();
1004
+ filter = this.parseExpression();
1005
+ }
1006
+ return { kind: "event", event: eventName, filter };
1007
+ }
1008
+ }
1009
+ }
1010
+ // Could be a cron expression or condition
1011
+ if (token.type === "STRING") {
1012
+ const cron = token.value;
1013
+ this.advance();
1014
+ return { kind: "schedule", cron };
1015
+ }
1016
+ throw new ParseError(`Expected trigger type but got ${token.type} '${token.value}'`, {
1017
+ location: token.location,
1018
+ source: this.source,
1019
+ });
1020
+ }
1021
+ // ===========================================================================
1022
+ // STATEMENT PARSING
1023
+ // ===========================================================================
1024
+ /** Parse an indented block of statements */
1025
+ parseStatementBlock() {
1026
+ const statements = [];
1027
+ if (!this.check("INDENT")) {
1028
+ return statements;
1029
+ }
1030
+ this.advance(); // INDENT
1031
+ while (!this.check("DEDENT") && !this.check("EOF")) {
1032
+ this.skipNewlines();
1033
+ if (this.check("DEDENT") || this.check("EOF"))
1034
+ break;
1035
+ statements.push(this.parseStatement());
1036
+ this.skipNewlines();
1037
+ }
1038
+ if (this.check("DEDENT")) {
1039
+ this.advance();
1040
+ }
1041
+ return statements;
1042
+ }
1043
+ /** Parse a single statement */
1044
+ parseStatement() {
1045
+ const token = this.current();
1046
+ // Keywords
1047
+ if (token.type === "KEYWORD") {
1048
+ switch (token.value) {
1049
+ case "if":
1050
+ return this.parseIfStatement();
1051
+ case "for":
1052
+ return this.parseForStatement();
1053
+ case "repeat":
1054
+ return this.parseRepeatStatement();
1055
+ case "loop":
1056
+ return this.parseUntilStatement();
1057
+ case "try":
1058
+ return this.parseTryStatement();
1059
+ case "parallel":
1060
+ return this.parseParallelStatement();
1061
+ case "do":
1062
+ return this.parseDoStatement();
1063
+ case "atomic":
1064
+ return this.parseAtomicStatement();
1065
+ case "emit":
1066
+ return this.parseEmitStatement();
1067
+ case "halt":
1068
+ return this.parseHaltStatement();
1069
+ case "wait":
1070
+ return this.parseWaitStatement();
1071
+ case "pass": {
1072
+ const startToken = this.current();
1073
+ this.advance();
1074
+ this.expectNewline();
1075
+ const node = { kind: "pass" };
1076
+ node.span = this.makeSpan(startToken);
1077
+ return node;
1078
+ }
1079
+ }
1080
+ }
1081
+ // Assignment or expression statement
1082
+ // Check for: identifier = ...
1083
+ if (token.type === "IDENTIFIER") {
1084
+ // Look ahead for assignment
1085
+ if (this.peek().type === "ASSIGN") {
1086
+ return this.parseAssignment();
1087
+ }
1088
+ }
1089
+ // Expression statement (method call, etc.)
1090
+ const startToken = this.current();
1091
+ const expr = this.parseExpression();
1092
+ // Pipeline expression statement
1093
+ if (this.check("OPERATOR") && this.current().value === "|") {
1094
+ const node = this.parsePipeline(expr, undefined, startToken);
1095
+ return node;
1096
+ }
1097
+ // If it's a method call, convert to statement
1098
+ if (expr.kind === "call") {
1099
+ const callExpr = expr;
1100
+ if (callExpr.callee.kind === "property_access") {
1101
+ const prop = callExpr.callee;
1102
+ const node = {
1103
+ kind: "method_call",
1104
+ object: prop.object,
1105
+ method: prop.property,
1106
+ args: callExpr.args,
1107
+ };
1108
+ // Check for using clause: ... using skill
1109
+ if (this.check("KEYWORD") && this.current().value === "using") {
1110
+ node.skill = this.parseUsingClause();
1111
+ }
1112
+ // Check for constraint clause: ... with key=value
1113
+ if (this.check("KEYWORD") && this.current().value === "with") {
1114
+ node.constraints = this.parseConstraintClause();
1115
+ }
1116
+ this.expectNewline();
1117
+ node.span = this.makeSpan(startToken);
1118
+ return node;
1119
+ }
1120
+ }
1121
+ this.expectNewline();
1122
+ // Treat as assignment with expression
1123
+ throw new ParseError("Unexpected expression statement", {
1124
+ location: token.location,
1125
+ source: this.source,
1126
+ });
1127
+ }
1128
+ /** Parse assignment: x = expr [with key=value, ...] */
1129
+ parseAssignment() {
1130
+ const startToken = this.current();
1131
+ const target = this.expect("IDENTIFIER").value;
1132
+ this.expect("ASSIGN");
1133
+ // Special case: advise statement
1134
+ if (this.check("KEYWORD", "advise")) {
1135
+ const node = this.parseAdviseStatement(target, startToken);
1136
+ return node;
1137
+ }
1138
+ const value = this.parseExpression();
1139
+ // Pipeline assignment
1140
+ if (this.check("OPERATOR") && this.current().value === "|") {
1141
+ const node = this.parsePipeline(value, target, startToken);
1142
+ return node;
1143
+ }
1144
+ let skill;
1145
+ if (this.check("KEYWORD") && this.current().value === "using") {
1146
+ skill = this.parseUsingClause();
1147
+ }
1148
+ let constraints;
1149
+ if (this.check("KEYWORD") && this.current().value === "with") {
1150
+ constraints = this.parseConstraintClause();
1151
+ }
1152
+ this.expectNewline();
1153
+ const node = { kind: "assignment", target, value, constraints, skill };
1154
+ node.span = this.makeSpan(startToken);
1155
+ return node;
1156
+ }
1157
+ /** Parse advise statement (assignment form) */
1158
+ parseAdviseStatement(target, startToken) {
1159
+ this.expect("KEYWORD", "advise");
1160
+ const advisor = this.expect("IDENTIFIER").value;
1161
+ this.expect("COLON");
1162
+ const prompt = this.expect("STRING").value;
1163
+ this.expectNewline();
1164
+ let outputSchema;
1165
+ let timeout;
1166
+ let fallback;
1167
+ if (this.check("INDENT")) {
1168
+ this.advance();
1169
+ while (!this.check("DEDENT") && !this.check("EOF")) {
1170
+ this.skipNewlines();
1171
+ if (this.check("DEDENT") || this.check("EOF"))
1172
+ break;
1173
+ const keyToken = this.current();
1174
+ if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
1175
+ throw new ParseError(`Expected advise field but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
1176
+ }
1177
+ const key = keyToken.value;
1178
+ this.advance();
1179
+ this.expect("COLON");
1180
+ if (key === "output") {
1181
+ outputSchema = this.parseOutputSchemaBlock(keyToken);
1182
+ }
1183
+ else if (key === "timeout") {
1184
+ const value = this.expect("NUMBER").value;
1185
+ timeout = Number.parseFloat(value);
1186
+ this.expectNewline();
1187
+ }
1188
+ else if (key === "fallback") {
1189
+ fallback = this.parseExpression();
1190
+ this.expectNewline();
1191
+ }
1192
+ else {
1193
+ this.parseExpression();
1194
+ this.expectNewline();
1195
+ }
1196
+ }
1197
+ if (this.check("DEDENT"))
1198
+ this.advance();
1199
+ }
1200
+ if (!outputSchema || timeout === undefined || fallback === undefined) {
1201
+ throw new ParseError("Advise statement requires output, timeout, and fallback", {
1202
+ location: startToken.location,
1203
+ source: this.source,
1204
+ });
1205
+ }
1206
+ const node = {
1207
+ kind: "advise",
1208
+ target,
1209
+ advisor,
1210
+ prompt,
1211
+ outputSchema,
1212
+ timeout,
1213
+ fallback,
1214
+ };
1215
+ node.span = this.makeSpan(startToken);
1216
+ return node;
1217
+ }
1218
+ parseOutputSchemaBlock(keyToken) {
1219
+ if (!this.check("NEWLINE")) {
1220
+ // Allow inline type: output: boolean
1221
+ const inlineType = this.parseSchemaType();
1222
+ this.expectNewline();
1223
+ return { kind: "advisory_output_schema", type: inlineType };
1224
+ }
1225
+ this.expectNewline();
1226
+ if (!this.check("INDENT")) {
1227
+ throw new ParseError("Expected indented output block", {
1228
+ location: keyToken.location,
1229
+ source: this.source,
1230
+ });
1231
+ }
1232
+ this.advance();
1233
+ return this.parseSchemaObject(keyToken);
1234
+ }
1235
+ 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
+ }
1244
+ this.advance();
1245
+ return this.parseSchemaObject(keyToken);
1246
+ }
1247
+ const inlineType = this.parseSchemaType();
1248
+ this.expectNewline();
1249
+ return { kind: "advisory_output_schema", type: inlineType };
1250
+ }
1251
+ parseSchemaObject(keyToken) {
1252
+ let outType;
1253
+ let values;
1254
+ let min;
1255
+ let max;
1256
+ let minLength;
1257
+ let maxLength;
1258
+ let pattern;
1259
+ let fields;
1260
+ let items;
1261
+ while (!this.check("DEDENT") && !this.check("EOF")) {
1262
+ this.skipNewlines();
1263
+ if (this.check("DEDENT") || this.check("EOF"))
1264
+ break;
1265
+ if (!(this.check("IDENTIFIER") || this.check("KEYWORD"))) {
1266
+ throw new ParseError(`Expected output schema field but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
1267
+ }
1268
+ const outKey = this.current().value;
1269
+ this.advance();
1270
+ this.expect("COLON");
1271
+ if (outKey === "type") {
1272
+ outType = this.parseSchemaType();
1273
+ this.expectNewline();
1274
+ }
1275
+ else if (outKey === "values") {
1276
+ values = this.parseStringList();
1277
+ this.expectNewline();
1278
+ }
1279
+ else if (outKey === "min") {
1280
+ const val = this.expect("NUMBER").value;
1281
+ min = Number.parseFloat(val);
1282
+ this.expectNewline();
1283
+ }
1284
+ else if (outKey === "max") {
1285
+ const val = this.expect("NUMBER").value;
1286
+ max = Number.parseFloat(val);
1287
+ this.expectNewline();
1288
+ }
1289
+ else if (outKey === "min_length") {
1290
+ const val = this.expect("NUMBER").value;
1291
+ minLength = Number.parseFloat(val);
1292
+ this.expectNewline();
1293
+ }
1294
+ else if (outKey === "max_length") {
1295
+ const val = this.expect("NUMBER").value;
1296
+ maxLength = Number.parseFloat(val);
1297
+ this.expectNewline();
1298
+ }
1299
+ else if (outKey === "pattern") {
1300
+ pattern = this.expect("STRING").value;
1301
+ this.expectNewline();
1302
+ }
1303
+ else if (outKey === "fields") {
1304
+ fields = this.parseSchemaFields(keyToken);
1305
+ }
1306
+ else if (outKey === "items") {
1307
+ items = this.parseSchemaAfterColon(keyToken);
1308
+ }
1309
+ else {
1310
+ this.parseExpression();
1311
+ this.expectNewline();
1312
+ }
1313
+ }
1314
+ if (this.check("DEDENT"))
1315
+ this.advance();
1316
+ if (!outType) {
1317
+ throw new ParseError("Advisory output type is required", {
1318
+ location: keyToken.location,
1319
+ source: this.source,
1320
+ });
1321
+ }
1322
+ return {
1323
+ kind: "advisory_output_schema",
1324
+ type: outType,
1325
+ values,
1326
+ min,
1327
+ max,
1328
+ minLength,
1329
+ maxLength,
1330
+ pattern,
1331
+ fields,
1332
+ items,
1333
+ };
1334
+ }
1335
+ parseSchemaFields(keyToken) {
1336
+ this.expectNewline();
1337
+ if (!this.check("INDENT")) {
1338
+ throw new ParseError("Expected indented fields block", {
1339
+ location: keyToken.location,
1340
+ source: this.source,
1341
+ });
1342
+ }
1343
+ this.advance();
1344
+ const fields = {};
1345
+ while (!this.check("DEDENT") && !this.check("EOF")) {
1346
+ this.skipNewlines();
1347
+ if (this.check("DEDENT") || this.check("EOF"))
1348
+ break;
1349
+ const fieldName = this.expect("IDENTIFIER").value;
1350
+ this.expect("COLON");
1351
+ fields[fieldName] = this.parseSchemaAfterColon(keyToken);
1352
+ }
1353
+ if (this.check("DEDENT"))
1354
+ this.advance();
1355
+ return fields;
1356
+ }
1357
+ parseSchemaType() {
1358
+ if (this.check("STRING")) {
1359
+ return this.advance().value;
1360
+ }
1361
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1362
+ return this.advance().value;
1363
+ }
1364
+ throw new ParseError("Expected output schema type", {
1365
+ location: this.current().location,
1366
+ source: this.source,
1367
+ });
1368
+ }
1369
+ /** Parse using clause: using skill_name */
1370
+ parseUsingClause() {
1371
+ this.expect("KEYWORD", "using");
1372
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1373
+ return this.advance().value;
1374
+ }
1375
+ if (this.check("STRING")) {
1376
+ return this.advance().value;
1377
+ }
1378
+ throw new ParseError("Expected skill name after 'using'", {
1379
+ location: this.current().location,
1380
+ source: this.source,
1381
+ });
1382
+ }
1383
+ /** Parse repeat statement */
1384
+ parseRepeatStatement() {
1385
+ const startToken = this.current();
1386
+ this.expect("KEYWORD", "repeat");
1387
+ const countToken = this.expect("NUMBER");
1388
+ const count = {
1389
+ kind: "literal",
1390
+ value: countToken.value.includes(".")
1391
+ ? Number.parseFloat(countToken.value)
1392
+ : Number.parseInt(countToken.value, 10),
1393
+ literalType: "number",
1394
+ };
1395
+ this.expect("COLON");
1396
+ this.expectNewline();
1397
+ const body = this.parseStatementBlock();
1398
+ const node = { kind: "repeat", count, body };
1399
+ node.span = this.makeSpan(startToken);
1400
+ return node;
1401
+ }
1402
+ /** Parse loop-until statement */
1403
+ parseUntilStatement() {
1404
+ const startToken = this.current();
1405
+ this.expect("KEYWORD", "loop");
1406
+ this.expect("KEYWORD", "until");
1407
+ const condition = this.parseExpression();
1408
+ let maxIterations;
1409
+ if (this.check("KEYWORD", "max")) {
1410
+ this.advance();
1411
+ const maxToken = this.expect("NUMBER");
1412
+ maxIterations = Number.parseFloat(maxToken.value);
1413
+ }
1414
+ this.expect("COLON");
1415
+ this.expectNewline();
1416
+ const body = this.parseStatementBlock();
1417
+ const node = { kind: "until", condition, maxIterations, body };
1418
+ node.span = this.makeSpan(startToken);
1419
+ return node;
1420
+ }
1421
+ /** Parse try/catch/finally */
1422
+ parseTryStatement() {
1423
+ const startToken = this.current();
1424
+ this.expect("KEYWORD", "try");
1425
+ this.expect("COLON");
1426
+ this.expectNewline();
1427
+ const tryBody = this.parseStatementBlock();
1428
+ const catches = [];
1429
+ while (this.check("KEYWORD", "catch")) {
1430
+ catches.push(this.parseCatchBlock());
1431
+ }
1432
+ let finallyBody;
1433
+ if (this.check("KEYWORD", "finally")) {
1434
+ this.advance();
1435
+ this.expect("COLON");
1436
+ this.expectNewline();
1437
+ finallyBody = this.parseStatementBlock();
1438
+ }
1439
+ const node = { kind: "try", tryBody, catches, finallyBody };
1440
+ node.span = this.makeSpan(startToken);
1441
+ return node;
1442
+ }
1443
+ /** Parse catch block */
1444
+ parseCatchBlock() {
1445
+ const startToken = this.current();
1446
+ this.expect("KEYWORD", "catch");
1447
+ let error = "*";
1448
+ if (this.check("OPERATOR") && this.current().value === "*") {
1449
+ this.advance();
1450
+ }
1451
+ else if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1452
+ error = this.advance().value;
1453
+ }
1454
+ this.expect("COLON");
1455
+ this.expectNewline();
1456
+ const body = [];
1457
+ let action;
1458
+ let retry;
1459
+ if (this.check("INDENT")) {
1460
+ this.advance();
1461
+ while (!this.check("DEDENT") && !this.check("EOF")) {
1462
+ this.skipNewlines();
1463
+ if (this.check("DEDENT") || this.check("EOF"))
1464
+ break;
1465
+ if ((this.check("IDENTIFIER") || this.check("KEYWORD")) &&
1466
+ this.current().value === "action") {
1467
+ this.advance();
1468
+ this.expect("COLON");
1469
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1470
+ action = this.advance().value;
1471
+ }
1472
+ else if (this.check("STRING")) {
1473
+ action = this.advance().value;
1474
+ }
1475
+ this.expectNewline();
1476
+ continue;
1477
+ }
1478
+ if ((this.check("IDENTIFIER") || this.check("KEYWORD")) &&
1479
+ this.current().value === "retry") {
1480
+ this.advance();
1481
+ this.expect("COLON");
1482
+ this.expectNewline();
1483
+ retry = this.parseRetrySpec();
1484
+ continue;
1485
+ }
1486
+ body.push(this.parseStatement());
1487
+ this.skipNewlines();
1488
+ }
1489
+ if (this.check("DEDENT"))
1490
+ this.advance();
1491
+ }
1492
+ const node = { kind: "catch", error, action, retry, body };
1493
+ node.span = this.makeSpan(startToken);
1494
+ return node;
1495
+ }
1496
+ /** Parse retry spec block */
1497
+ parseRetrySpec() {
1498
+ const startToken = this.current();
1499
+ let maxAttempts = 3;
1500
+ let backoff = "none";
1501
+ let backoffBase;
1502
+ let maxBackoff;
1503
+ if (this.check("INDENT")) {
1504
+ this.advance();
1505
+ while (!this.check("DEDENT") && !this.check("EOF")) {
1506
+ this.skipNewlines();
1507
+ if (this.check("DEDENT") || this.check("EOF"))
1508
+ break;
1509
+ const key = this.expect("IDENTIFIER").value;
1510
+ this.expect("COLON");
1511
+ if (key === "max_attempts") {
1512
+ maxAttempts = Number.parseFloat(this.expect("NUMBER").value);
1513
+ }
1514
+ else if (key === "backoff") {
1515
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1516
+ backoff = this.advance().value;
1517
+ }
1518
+ else if (this.check("STRING")) {
1519
+ backoff = this.advance().value;
1520
+ }
1521
+ }
1522
+ else if (key === "backoff_base") {
1523
+ backoffBase = Number.parseFloat(this.expect("NUMBER").value);
1524
+ }
1525
+ else if (key === "max_backoff") {
1526
+ maxBackoff = Number.parseFloat(this.expect("NUMBER").value);
1527
+ }
1528
+ else {
1529
+ this.parseExpression();
1530
+ }
1531
+ this.expectNewline();
1532
+ }
1533
+ if (this.check("DEDENT"))
1534
+ this.advance();
1535
+ }
1536
+ const node = {
1537
+ kind: "retry",
1538
+ maxAttempts,
1539
+ backoff,
1540
+ backoffBase,
1541
+ maxBackoff,
1542
+ };
1543
+ node.span = this.makeSpan(startToken);
1544
+ return node;
1545
+ }
1546
+ /** Parse parallel statement */
1547
+ parseParallelStatement() {
1548
+ const startToken = this.current();
1549
+ this.expect("KEYWORD", "parallel");
1550
+ let join;
1551
+ let onFail;
1552
+ // Optional header config before colon: join=..., on_fail=...
1553
+ while (!this.check("COLON") && !this.check("NEWLINE") && !this.check("EOF")) {
1554
+ if (!(this.check("IDENTIFIER") || this.check("KEYWORD")))
1555
+ break;
1556
+ const key = this.advance().value;
1557
+ if (!this.check("ASSIGN"))
1558
+ break;
1559
+ this.advance();
1560
+ if (key === "join") {
1561
+ let joinType = "all";
1562
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1563
+ joinType = this.advance().value;
1564
+ }
1565
+ else if (this.check("STRING")) {
1566
+ joinType = this.advance().value;
1567
+ }
1568
+ join = {
1569
+ kind: "parallel_join",
1570
+ type: joinType,
1571
+ };
1572
+ }
1573
+ else if (key === "on_fail") {
1574
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1575
+ onFail = this.advance().value;
1576
+ }
1577
+ else if (this.check("STRING")) {
1578
+ onFail = this.advance().value;
1579
+ }
1580
+ }
1581
+ else if (key === "metric") {
1582
+ if (!join) {
1583
+ join = { kind: "parallel_join", type: "best" };
1584
+ }
1585
+ join.metric = this.parseExpression();
1586
+ }
1587
+ else if (key === "order") {
1588
+ if (!join) {
1589
+ join = { kind: "parallel_join", type: "best" };
1590
+ }
1591
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1592
+ join.order = this.advance().value;
1593
+ }
1594
+ }
1595
+ else if (key === "count") {
1596
+ if (!join) {
1597
+ join = { kind: "parallel_join", type: "any" };
1598
+ }
1599
+ if (this.check("NUMBER")) {
1600
+ join.count = Number.parseFloat(this.advance().value);
1601
+ }
1602
+ }
1603
+ else {
1604
+ // Unknown header option, skip value
1605
+ this.parseExpression();
1606
+ }
1607
+ }
1608
+ this.expect("COLON");
1609
+ this.expectNewline();
1610
+ 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();
1626
+ }
1627
+ const node = { kind: "parallel", join, onFail, branches };
1628
+ node.span = this.makeSpan(startToken);
1629
+ return node;
1630
+ }
1631
+ /** Parse pipeline after source expression */
1632
+ parsePipeline(source, outputBinding, startToken) {
1633
+ const stages = [];
1634
+ while (this.check("OPERATOR") && this.current().value === "|") {
1635
+ this.advance(); // consume |
1636
+ const opToken = this.expect("IDENTIFIER");
1637
+ const op = opToken.value;
1638
+ let initial;
1639
+ let count;
1640
+ let order;
1641
+ let by;
1642
+ // Optional args
1643
+ if (op === "reduce" && this.check("LPAREN")) {
1644
+ this.advance();
1645
+ initial = this.parseExpression();
1646
+ this.expect("RPAREN");
1647
+ }
1648
+ else if ((op === "take" || op === "skip") && this.check("NUMBER")) {
1649
+ count = Number.parseFloat(this.advance().value);
1650
+ }
1651
+ else if (op === "sort") {
1652
+ if (this.check("IDENTIFIER") && this.current().value === "by") {
1653
+ this.advance();
1654
+ by = this.parseExpression();
1655
+ }
1656
+ if (this.check("IDENTIFIER") && this.current().value === "order") {
1657
+ this.advance();
1658
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1659
+ order = this.advance().value;
1660
+ }
1661
+ }
1662
+ }
1663
+ this.expect("COLON");
1664
+ this.expectNewline();
1665
+ const body = this.parseStatementBlock();
1666
+ if (body.length === 0 &&
1667
+ (op === "map" || op === "filter" || op === "reduce" || op === "pmap")) {
1668
+ throw new ParseError(`Pipeline stage '${op}' requires a body`, {
1669
+ location: opToken.location,
1670
+ source: this.source,
1671
+ });
1672
+ }
1673
+ const stageStep = (body[0] ?? { kind: "pass" });
1674
+ stages.push({
1675
+ kind: "pipeline_stage",
1676
+ op,
1677
+ step: stageStep,
1678
+ initial,
1679
+ count,
1680
+ order,
1681
+ by,
1682
+ });
1683
+ }
1684
+ const node = { kind: "pipeline", source, stages, outputBinding };
1685
+ node.span = this.makeSpan(startToken);
1686
+ return node;
1687
+ }
1688
+ /** Parse do statement (block invocation) */
1689
+ parseDoStatement() {
1690
+ const startToken = this.current();
1691
+ this.expect("KEYWORD", "do");
1692
+ const nameParts = [];
1693
+ nameParts.push(this.expect("IDENTIFIER").value);
1694
+ while (this.check("DOT")) {
1695
+ this.advance();
1696
+ nameParts.push(this.expect("IDENTIFIER").value);
1697
+ }
1698
+ const name = nameParts.join(".");
1699
+ const args = [];
1700
+ if (this.check("LPAREN")) {
1701
+ this.advance();
1702
+ while (!this.check("RPAREN")) {
1703
+ args.push(this.parseExpression());
1704
+ if (this.check("COMMA"))
1705
+ this.advance();
1706
+ }
1707
+ this.expect("RPAREN");
1708
+ }
1709
+ this.expectNewline();
1710
+ const node = { kind: "do", name, args };
1711
+ node.span = this.makeSpan(startToken);
1712
+ return node;
1713
+ }
1714
+ /** Parse if statement */
1715
+ parseIfStatement() {
1716
+ const startToken = this.current();
1717
+ this.expect("KEYWORD", "if");
1718
+ // Check for advisory condition: if **prompt**:
1719
+ let condition;
1720
+ if (this.check("ADVISORY")) {
1721
+ const prompt = this.advance().value;
1722
+ let advisor;
1723
+ if (this.check("KEYWORD", "via")) {
1724
+ this.advance();
1725
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1726
+ advisor = this.advance().value;
1727
+ }
1728
+ }
1729
+ condition = { kind: "advisory_expr", prompt, advisor };
1730
+ }
1731
+ else {
1732
+ condition = this.parseExpression();
1733
+ }
1734
+ this.expect("COLON");
1735
+ this.expectNewline();
1736
+ const thenBody = this.parseStatementBlock();
1737
+ const elifs = [];
1738
+ let elseBody = [];
1739
+ // Check for elif
1740
+ while (this.check("KEYWORD", "elif")) {
1741
+ this.advance();
1742
+ const elifCondition = this.parseExpression();
1743
+ this.expect("COLON");
1744
+ this.expectNewline();
1745
+ const elifBody = this.parseStatementBlock();
1746
+ elifs.push({ condition: elifCondition, body: elifBody });
1747
+ }
1748
+ // Check for else
1749
+ if (this.check("KEYWORD", "else")) {
1750
+ this.advance();
1751
+ this.expect("COLON");
1752
+ this.expectNewline();
1753
+ elseBody = this.parseStatementBlock();
1754
+ }
1755
+ const node = { kind: "if", condition, thenBody, elifs, elseBody };
1756
+ node.span = this.makeSpan(startToken);
1757
+ return node;
1758
+ }
1759
+ /** Parse for loop */
1760
+ parseForStatement() {
1761
+ const startToken = this.current();
1762
+ this.expect("KEYWORD", "for");
1763
+ const variable = this.expect("IDENTIFIER").value;
1764
+ this.expect("KEYWORD", "in");
1765
+ const iterable = this.parseExpression();
1766
+ this.expect("COLON");
1767
+ this.expectNewline();
1768
+ const body = this.parseStatementBlock();
1769
+ const node = { kind: "for", variable, iterable, body };
1770
+ node.span = this.makeSpan(startToken);
1771
+ return node;
1772
+ }
1773
+ /** Parse atomic block: atomic: / atomic skip: / atomic halt: / atomic revert: */
1774
+ parseAtomicStatement() {
1775
+ const startToken = this.current();
1776
+ this.expect("KEYWORD", "atomic");
1777
+ let onFailure;
1778
+ // Check for failure mode: atomic skip: / atomic halt: / atomic revert:
1779
+ // Note: "halt" is a keyword, "skip" and "revert" are identifiers
1780
+ const failureModes = ["skip", "halt", "revert"];
1781
+ if ((this.check("IDENTIFIER") || this.check("KEYWORD")) &&
1782
+ failureModes.includes(this.current().value)) {
1783
+ onFailure = this.current().value;
1784
+ this.advance();
1785
+ }
1786
+ this.expect("COLON");
1787
+ this.expectNewline();
1788
+ const body = this.parseStatementBlock();
1789
+ const node = { kind: "atomic", body, onFailure };
1790
+ node.span = this.makeSpan(startToken);
1791
+ return node;
1792
+ }
1793
+ /** Parse emit statement: emit event_name(key=value) */
1794
+ parseEmitStatement() {
1795
+ const startToken = this.current();
1796
+ this.expect("KEYWORD", "emit");
1797
+ const event = this.expect("IDENTIFIER").value;
1798
+ const data = [];
1799
+ if (this.check("LPAREN")) {
1800
+ this.advance();
1801
+ while (!this.check("RPAREN")) {
1802
+ let key;
1803
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
1804
+ key = this.advance().value;
1805
+ }
1806
+ else {
1807
+ throw new ParseError(`Expected identifier in emit but got ${this.current().type} '${this.current().value}'`, { location: this.current().location, source: this.source });
1808
+ }
1809
+ this.expect("ASSIGN");
1810
+ const value = this.parseExpression();
1811
+ data.push({ key, value });
1812
+ if (this.check("COMMA")) {
1813
+ this.advance();
1814
+ }
1815
+ }
1816
+ this.expect("RPAREN");
1817
+ }
1818
+ this.expectNewline();
1819
+ const node = { kind: "emit", event, data };
1820
+ node.span = this.makeSpan(startToken);
1821
+ return node;
1822
+ }
1823
+ /** Parse halt statement */
1824
+ parseHaltStatement() {
1825
+ const startToken = this.current();
1826
+ this.expect("KEYWORD", "halt");
1827
+ let reason = "halted";
1828
+ if (this.check("STRING")) {
1829
+ reason = this.advance().value;
1830
+ }
1831
+ this.expectNewline();
1832
+ const node = { kind: "halt", reason };
1833
+ node.span = this.makeSpan(startToken);
1834
+ return node;
1835
+ }
1836
+ /** Parse wait statement */
1837
+ parseWaitStatement() {
1838
+ const startToken = this.current();
1839
+ this.expect("KEYWORD", "wait");
1840
+ const durationToken = this.expect("NUMBER");
1841
+ const duration = Number.parseFloat(durationToken.value);
1842
+ this.expectNewline();
1843
+ const node = { kind: "wait", duration };
1844
+ node.span = this.makeSpan(startToken);
1845
+ return node;
1846
+ }
1847
+ /** Parse constraint clause: with key=value, key=value, ... */
1848
+ parseConstraintClause() {
1849
+ const startToken = this.current();
1850
+ this.expect("KEYWORD", "with");
1851
+ const constraints = [];
1852
+ do {
1853
+ const key = this.expect("IDENTIFIER").value;
1854
+ this.expect("ASSIGN");
1855
+ const value = this.parseExpression();
1856
+ constraints.push({ key, value });
1857
+ } while (this.check("COMMA") &&
1858
+ (() => {
1859
+ this.advance();
1860
+ return true;
1861
+ })());
1862
+ const node = { kind: "constraint_clause", constraints };
1863
+ node.span = this.makeSpan(startToken);
1864
+ return node;
1865
+ }
1866
+ // ===========================================================================
1867
+ // EXPRESSION PARSING
1868
+ // ===========================================================================
1869
+ /** Parse an expression */
1870
+ parseExpression() {
1871
+ return this.parseTernary();
1872
+ }
1873
+ /** Parse ternary: cond ? then : else */
1874
+ parseTernary() {
1875
+ const condition = this.parseOr();
1876
+ if (this.check("QUESTION")) {
1877
+ this.advance();
1878
+ const thenExpr = this.parseTernary();
1879
+ this.expect("COLON");
1880
+ const elseExpr = this.parseTernary();
1881
+ return {
1882
+ kind: "ternary",
1883
+ condition,
1884
+ thenExpr,
1885
+ elseExpr,
1886
+ };
1887
+ }
1888
+ return condition;
1889
+ }
1890
+ /** Parse or: a or b */
1891
+ parseOr() {
1892
+ let left = this.parseAnd();
1893
+ while (this.check("KEYWORD", "or")) {
1894
+ this.advance();
1895
+ const right = this.parseAnd();
1896
+ left = { kind: "binary", op: "or", left, right };
1897
+ }
1898
+ return left;
1899
+ }
1900
+ /** Parse and: a and b */
1901
+ parseAnd() {
1902
+ let left = this.parseEquality();
1903
+ while (this.check("KEYWORD", "and")) {
1904
+ this.advance();
1905
+ const right = this.parseEquality();
1906
+ left = { kind: "binary", op: "and", left, right };
1907
+ }
1908
+ return left;
1909
+ }
1910
+ /** Parse equality: == != */
1911
+ parseEquality() {
1912
+ let left = this.parseComparison();
1913
+ while (this.check("OPERATOR") &&
1914
+ (this.current().value === "==" || this.current().value === "!=")) {
1915
+ const op = this.advance().value;
1916
+ const right = this.parseComparison();
1917
+ left = { kind: "binary", op, left, right };
1918
+ }
1919
+ return left;
1920
+ }
1921
+ /** Parse comparison: < > <= >= */
1922
+ parseComparison() {
1923
+ let left = this.parseAdditive();
1924
+ while (this.check("OPERATOR") && ["<", ">", "<=", ">="].includes(this.current().value)) {
1925
+ const op = this.advance().value;
1926
+ const right = this.parseAdditive();
1927
+ left = { kind: "binary", op, left, right };
1928
+ }
1929
+ return left;
1930
+ }
1931
+ /** Parse additive: + - */
1932
+ parseAdditive() {
1933
+ let left = this.parseMultiplicative();
1934
+ while (this.check("OPERATOR") &&
1935
+ (this.current().value === "+" || this.current().value === "-")) {
1936
+ const op = this.advance().value;
1937
+ const right = this.parseMultiplicative();
1938
+ left = { kind: "binary", op, left, right };
1939
+ }
1940
+ return left;
1941
+ }
1942
+ /** Parse multiplicative: * / % */
1943
+ parseMultiplicative() {
1944
+ let left = this.parseUnary();
1945
+ while (this.check("OPERATOR") && ["*", "/", "%"].includes(this.current().value)) {
1946
+ const op = this.advance().value;
1947
+ const right = this.parseUnary();
1948
+ left = { kind: "binary", op, left, right };
1949
+ }
1950
+ return left;
1951
+ }
1952
+ /** Parse unary: not - */
1953
+ parseUnary() {
1954
+ if (this.check("KEYWORD", "not")) {
1955
+ this.advance();
1956
+ const arg = this.parseUnary();
1957
+ return { kind: "unary", op: "not", arg };
1958
+ }
1959
+ if (this.check("OPERATOR", "-")) {
1960
+ this.advance();
1961
+ const arg = this.parseUnary();
1962
+ return { kind: "unary", op: "-", arg };
1963
+ }
1964
+ return this.parsePostfix();
1965
+ }
1966
+ /** Parse postfix: . [] () */
1967
+ parsePostfix() {
1968
+ let expr = this.parsePrimary();
1969
+ while (true) {
1970
+ if (this.check("DOT")) {
1971
+ this.advance();
1972
+ const property = this.expect("IDENTIFIER").value;
1973
+ expr = { kind: "property_access", object: expr, property };
1974
+ }
1975
+ else if (this.check("LBRACKET")) {
1976
+ this.advance();
1977
+ const index = this.parseExpression();
1978
+ this.expect("RBRACKET");
1979
+ expr = { kind: "array_access", array: expr, index };
1980
+ }
1981
+ else if (this.check("LPAREN")) {
1982
+ this.advance();
1983
+ const args = [];
1984
+ const kwargs = [];
1985
+ while (!this.check("RPAREN")) {
1986
+ // Check for kwarg: key=value
1987
+ if (this.check("IDENTIFIER") && this.peek().type === "ASSIGN") {
1988
+ const key = this.advance().value;
1989
+ this.expect("ASSIGN");
1990
+ const value = this.parseExpression();
1991
+ kwargs.push({ key, value });
1992
+ }
1993
+ else {
1994
+ args.push(this.parseExpression());
1995
+ }
1996
+ if (this.check("COMMA")) {
1997
+ this.advance();
1998
+ }
1999
+ }
2000
+ this.expect("RPAREN");
2001
+ expr = {
2002
+ kind: "call",
2003
+ callee: expr,
2004
+ args,
2005
+ kwargs: kwargs.length > 0 ? kwargs : undefined,
2006
+ };
2007
+ }
2008
+ else {
2009
+ break;
2010
+ }
2011
+ }
2012
+ return expr;
2013
+ }
2014
+ /** Parse primary expression */
2015
+ parsePrimary() {
2016
+ const token = this.current();
2017
+ // Number
2018
+ if (token.type === "NUMBER") {
2019
+ this.advance();
2020
+ const value = token.value.includes(".")
2021
+ ? Number.parseFloat(token.value)
2022
+ : Number.parseInt(token.value, 10);
2023
+ if (this.check("IDENTIFIER")) {
2024
+ const unit = this.advance().value;
2025
+ return { kind: "unit_literal", value, unit };
2026
+ }
2027
+ return { kind: "literal", value, literalType: "number" };
2028
+ }
2029
+ // String
2030
+ if (token.type === "STRING") {
2031
+ this.advance();
2032
+ return { kind: "literal", value: token.value, literalType: "string" };
2033
+ }
2034
+ // Boolean
2035
+ if (token.type === "BOOLEAN") {
2036
+ this.advance();
2037
+ return {
2038
+ kind: "literal",
2039
+ value: token.value === "true",
2040
+ literalType: "boolean",
2041
+ };
2042
+ }
2043
+ // Address
2044
+ if (token.type === "ADDRESS") {
2045
+ this.advance();
2046
+ return { kind: "literal", value: token.value, literalType: "address" };
2047
+ }
2048
+ // Percentage
2049
+ if (token.type === "PERCENTAGE") {
2050
+ this.advance();
2051
+ return { kind: "percentage", value: Number.parseFloat(token.value) };
2052
+ }
2053
+ // Venue reference: @name
2054
+ if (token.type === "VENUE_REF") {
2055
+ this.advance();
2056
+ return { kind: "venue_ref_expr", name: token.value };
2057
+ }
2058
+ // Advisory: **text**
2059
+ if (token.type === "ADVISORY") {
2060
+ this.advance();
2061
+ let advisor;
2062
+ if (this.check("KEYWORD", "via")) {
2063
+ this.advance();
2064
+ if (this.check("IDENTIFIER") || this.check("KEYWORD")) {
2065
+ advisor = this.advance().value;
2066
+ }
2067
+ }
2068
+ return { kind: "advisory_expr", prompt: token.value, advisor };
2069
+ }
2070
+ // Array literal: [a, b, c]
2071
+ if (token.type === "LBRACKET") {
2072
+ this.advance();
2073
+ const elements = [];
2074
+ while (!this.check("RBRACKET")) {
2075
+ elements.push(this.parseExpression());
2076
+ if (this.check("COMMA")) {
2077
+ this.advance();
2078
+ }
2079
+ }
2080
+ this.expect("RBRACKET");
2081
+ return { kind: "array_literal", elements };
2082
+ }
2083
+ // Object literal: {key: value}
2084
+ if (token.type === "LBRACE") {
2085
+ this.advance();
2086
+ const entries = [];
2087
+ while (!this.check("RBRACE")) {
2088
+ const key = this.expect("IDENTIFIER").value;
2089
+ this.expect("COLON");
2090
+ const value = this.parseExpression();
2091
+ entries.push({ key, value });
2092
+ if (this.check("COMMA")) {
2093
+ this.advance();
2094
+ }
2095
+ }
2096
+ this.expect("RBRACE");
2097
+ return { kind: "object_literal", entries };
2098
+ }
2099
+ // Parenthesized expression
2100
+ if (token.type === "LPAREN") {
2101
+ this.advance();
2102
+ const expr = this.parseExpression();
2103
+ this.expect("RPAREN");
2104
+ return expr;
2105
+ }
2106
+ // Identifier or keyword used as identifier in expression context
2107
+ // Many keywords can be used as identifiers when they appear in expressions
2108
+ const keywordsAsIdentifiers = new Set([
2109
+ "max",
2110
+ "assets",
2111
+ "params",
2112
+ "limits",
2113
+ "state",
2114
+ "venues",
2115
+ "lending",
2116
+ "swap",
2117
+ "persistent",
2118
+ "ephemeral",
2119
+ "version",
2120
+ "description",
2121
+ "skills",
2122
+ "advisors",
2123
+ "guards",
2124
+ "block",
2125
+ ]);
2126
+ if (token.type === "IDENTIFIER" ||
2127
+ (token.type === "KEYWORD" && keywordsAsIdentifiers.has(token.value))) {
2128
+ this.advance();
2129
+ return { kind: "identifier", name: token.value };
2130
+ }
2131
+ throw new ParseError(`Unexpected token in expression: ${token.type} '${token.value}'`, {
2132
+ location: token.location,
2133
+ source: this.source,
2134
+ });
2135
+ }
2136
+ }
2137
+ // =============================================================================
2138
+ // PUBLIC API
2139
+ // =============================================================================
2140
+ /**
2141
+ * Parse Grimoire source code into AST
2142
+ */
2143
+ export function parse(source) {
2144
+ const tokens = tokenize(source);
2145
+ const parser = new Parser(tokens, source);
2146
+ return parser.parseSpellFile();
2147
+ }
2148
+ //# sourceMappingURL=parser.js.map