@hyperscale0/hsx 1.0.0-beta.1 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/CHANGELOG.md +41 -9
  2. package/LICENSING.md +3 -3
  3. package/README.md +37 -149
  4. package/TRADEMARKS.md +4 -4
  5. package/dist/src/ast.d.ts +129 -10
  6. package/dist/src/ast.d.ts.map +1 -1
  7. package/dist/src/cli.d.ts +2 -1
  8. package/dist/src/cli.d.ts.map +1 -1
  9. package/dist/src/cli.js +148 -12
  10. package/dist/src/cli.js.map +1 -1
  11. package/dist/src/compile.d.ts +41 -4
  12. package/dist/src/compile.d.ts.map +1 -1
  13. package/dist/src/compile.js +82 -15
  14. package/dist/src/compile.js.map +1 -1
  15. package/dist/src/cost.d.ts +78 -0
  16. package/dist/src/cost.d.ts.map +1 -0
  17. package/dist/src/cost.js +369 -0
  18. package/dist/src/cost.js.map +1 -0
  19. package/dist/src/diagnostics.d.ts +11 -0
  20. package/dist/src/diagnostics.d.ts.map +1 -0
  21. package/dist/src/diagnostics.js +420 -0
  22. package/dist/src/diagnostics.js.map +1 -0
  23. package/dist/src/emit.d.ts +35 -0
  24. package/dist/src/emit.d.ts.map +1 -0
  25. package/dist/src/emit.js +359 -0
  26. package/dist/src/emit.js.map +1 -0
  27. package/dist/src/entry-overrides.d.ts +17 -3
  28. package/dist/src/entry-overrides.d.ts.map +1 -1
  29. package/dist/src/entry-overrides.js +180 -35
  30. package/dist/src/entry-overrides.js.map +1 -1
  31. package/dist/src/format.d.ts +10 -0
  32. package/dist/src/format.d.ts.map +1 -0
  33. package/dist/src/format.js +183 -0
  34. package/dist/src/format.js.map +1 -0
  35. package/dist/src/index.d.ts +10 -4
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +8 -3
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/ir.d.ts +66 -0
  40. package/dist/src/ir.d.ts.map +1 -0
  41. package/dist/src/ir.js +15 -0
  42. package/dist/src/ir.js.map +1 -0
  43. package/dist/src/lex.d.ts +9 -1
  44. package/dist/src/lex.d.ts.map +1 -1
  45. package/dist/src/lex.js +50 -4
  46. package/dist/src/lex.js.map +1 -1
  47. package/dist/src/limits.d.ts +2 -3
  48. package/dist/src/limits.d.ts.map +1 -1
  49. package/dist/src/limits.js +2 -3
  50. package/dist/src/limits.js.map +1 -1
  51. package/dist/src/modules.d.ts +36 -0
  52. package/dist/src/modules.d.ts.map +1 -0
  53. package/dist/src/modules.js +372 -0
  54. package/dist/src/modules.js.map +1 -0
  55. package/dist/src/parse.d.ts.map +1 -1
  56. package/dist/src/parse.js +590 -17
  57. package/dist/src/parse.js.map +1 -1
  58. package/dist/src/typecheck.d.ts +8 -0
  59. package/dist/src/typecheck.d.ts.map +1 -0
  60. package/dist/src/typecheck.js +2958 -0
  61. package/dist/src/typecheck.js.map +1 -0
  62. package/dist/src/version.d.ts +4 -15
  63. package/dist/src/version.d.ts.map +1 -1
  64. package/dist/src/version.js +4 -15
  65. package/dist/src/version.js.map +1 -1
  66. package/docs/README.md +23 -0
  67. package/docs/guide/01-first-program.md +24 -0
  68. package/docs/guide/02-money.md +25 -0
  69. package/docs/guide/03-instruments.md +22 -0
  70. package/docs/guide/04-lifecycles.md +25 -0
  71. package/docs/guide/05-fees-and-splits.md +45 -0
  72. package/docs/guide/06-schedules.md +22 -0
  73. package/docs/guide/07-composition.md +46 -0
  74. package/docs/guide/08-writing-a-module.md +26 -0
  75. package/docs/guide/09-cost.md +24 -0
  76. package/docs/guide/10-diagnostics.md +27 -0
  77. package/docs/llms-full.txt +1738 -0
  78. package/docs/llms.txt +45 -0
  79. package/docs/reference/cli.md +33 -0
  80. package/docs/reference/diagnostics.md +619 -0
  81. package/docs/reference/grammar.md +39 -0
  82. package/docs/reference/std/advance.md +35 -0
  83. package/docs/reference/std/cancellable_booking.md +35 -0
  84. package/docs/reference/std/captured_payment.md +43 -0
  85. package/docs/reference/std/conditional_disbursement.md +33 -0
  86. package/docs/reference/std/credit_facility.md +35 -0
  87. package/docs/reference/std/held_payment.md +64 -0
  88. package/docs/reference/std/instant_transfer.md +30 -0
  89. package/docs/reference/std/metered.md +27 -0
  90. package/docs/reference/std/pooled_split.md +26 -0
  91. package/docs/reference/std/premium_forward.md +39 -0
  92. package/docs/reference/std/reconciled_payout.md +33 -0
  93. package/docs/reference/std/recurring_collection.md +25 -0
  94. package/docs/reference/std/rotating_pool.md +42 -0
  95. package/docs/reference/std/scheduled.md +51 -0
  96. package/docs/reference/std/security_deposit.md +46 -0
  97. package/docs/reference/std/settlement_batch.md +42 -0
  98. package/docs/reference/std/swap.md +46 -0
  99. package/docs/reference/std/threshold_pool.md +47 -0
  100. package/docs/reference/std/weighted_distribution.md +39 -0
  101. package/docs/reference/types.md +20 -0
  102. package/docs/reference/udl-output.md +14 -0
  103. package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
  104. package/docs/sessions/2026-09-02-two-instruments.md +41 -0
  105. package/examples/01-first-program/README.md +45 -0
  106. package/examples/01-first-program/tip-jar.hsx +17 -0
  107. package/examples/02-imports-and-archetypes/README.md +99 -0
  108. package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
  109. package/examples/03-diagnostics/README.md +68 -0
  110. package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
  111. package/examples/03-diagnostics/corner-shop.hsx +19 -0
  112. package/examples/04-complete-product/README.md +87 -0
  113. package/examples/04-complete-product/study-hall.hsx +67 -0
  114. package/examples/05-watch-club/README.md +9 -0
  115. package/examples/05-watch-club/watch-club.hsx +124 -0
  116. package/examples/README.md +18 -0
  117. package/examples/advance/README.md +3 -0
  118. package/examples/advance/advance.hsx +13 -0
  119. package/examples/advance/advance.udl +339 -0
  120. package/examples/cancellable_booking/README.md +3 -0
  121. package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
  122. package/examples/cancellable_booking/cancellable_booking.udl +391 -0
  123. package/examples/captured_payment/README.md +3 -0
  124. package/examples/captured_payment/captured_payment.hsx +21 -0
  125. package/examples/captured_payment/captured_payment.udl +677 -0
  126. package/examples/conditional_disbursement/README.md +3 -0
  127. package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
  128. package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
  129. package/examples/cost-table.json +386 -0
  130. package/examples/credit_facility/README.md +3 -0
  131. package/examples/credit_facility/credit_facility.hsx +27 -0
  132. package/examples/credit_facility/credit_facility.udl +1373 -0
  133. package/examples/held_payment/README.md +3 -0
  134. package/examples/held_payment/held_payment.hsx +13 -0
  135. package/examples/held_payment/held_payment.udl +507 -0
  136. package/examples/instant_transfer/README.md +3 -0
  137. package/examples/instant_transfer/instant_transfer.hsx +16 -0
  138. package/examples/instant_transfer/instant_transfer.udl +364 -0
  139. package/examples/metered/README.md +3 -0
  140. package/examples/metered/metered.hsx +13 -0
  141. package/examples/metered/metered.udl +247 -0
  142. package/examples/pooled_split/README.md +3 -0
  143. package/examples/pooled_split/pooled_split.hsx +15 -0
  144. package/examples/pooled_split/pooled_split.udl +425 -0
  145. package/examples/premium_forward/README.md +3 -0
  146. package/examples/premium_forward/premium_forward.hsx +19 -0
  147. package/examples/premium_forward/premium_forward.udl +594 -0
  148. package/examples/reconciled_payout/README.md +3 -0
  149. package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
  150. package/examples/reconciled_payout/reconciled_payout.udl +303 -0
  151. package/examples/recurring_collection/README.md +3 -0
  152. package/examples/recurring_collection/recurring_collection.hsx +21 -0
  153. package/examples/recurring_collection/recurring_collection.udl +1130 -0
  154. package/examples/rotating_pool/README.md +3 -0
  155. package/examples/rotating_pool/rotating_pool.hsx +18 -0
  156. package/examples/rotating_pool/rotating_pool.udl +4352 -0
  157. package/examples/scheduled/README.md +3 -0
  158. package/examples/scheduled/scheduled.hsx +12 -0
  159. package/examples/scheduled/scheduled.udl +335 -0
  160. package/examples/security_deposit/README.md +3 -0
  161. package/examples/security_deposit/security_deposit.hsx +21 -0
  162. package/examples/security_deposit/security_deposit.udl +293 -0
  163. package/examples/settlement_batch/README.md +3 -0
  164. package/examples/settlement_batch/settlement_batch.hsx +19 -0
  165. package/examples/settlement_batch/settlement_batch.udl +753 -0
  166. package/examples/swap/README.md +3 -0
  167. package/examples/swap/swap.hsx +19 -0
  168. package/examples/swap/swap.udl +876 -0
  169. package/examples/threshold_pool/README.md +3 -0
  170. package/examples/threshold_pool/threshold_pool.hsx +16 -0
  171. package/examples/threshold_pool/threshold_pool.udl +667 -0
  172. package/examples/weighted_distribution/README.md +3 -0
  173. package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
  174. package/examples/weighted_distribution/weighted_distribution.udl +337 -0
  175. package/package.json +19 -6
  176. package/skills/hsx/SKILL.md +483 -0
  177. package/src/ast.ts +163 -9
  178. package/src/cli.ts +164 -12
  179. package/src/compile.ts +160 -20
  180. package/src/cost.ts +579 -0
  181. package/src/diagnostics.ts +442 -0
  182. package/src/emit.ts +447 -0
  183. package/src/entry-overrides.ts +263 -48
  184. package/src/format.ts +261 -0
  185. package/src/index.ts +32 -4
  186. package/src/ir.ts +99 -0
  187. package/src/lex.ts +59 -4
  188. package/src/limits.ts +2 -3
  189. package/src/modules.ts +444 -0
  190. package/src/parse.ts +655 -21
  191. package/src/typecheck.ts +3876 -0
  192. package/src/version.ts +4 -16
  193. package/std/SEMANTICS.md +130 -0
  194. package/std/settlements/advance.hsx +177 -0
  195. package/std/settlements/cancellable_booking.hsx +87 -0
  196. package/std/settlements/captured_payment.hsx +144 -0
  197. package/std/settlements/conditional_disbursement.hsx +185 -0
  198. package/std/settlements/credit_facility.hsx +167 -0
  199. package/std/settlements/held_payment.hsx +578 -0
  200. package/std/settlements/index.hsx +3 -0
  201. package/std/settlements/instant_transfer.hsx +190 -0
  202. package/std/settlements/metered.hsx +67 -0
  203. package/std/settlements/pooled_split.hsx +68 -0
  204. package/std/settlements/premium_forward.hsx +257 -0
  205. package/std/settlements/reconciled_payout.hsx +77 -0
  206. package/std/settlements/recurring_collection.hsx +27 -0
  207. package/std/settlements/rotating_pool.hsx +252 -0
  208. package/std/settlements/scheduled.hsx +641 -0
  209. package/std/settlements/security_deposit.hsx +166 -0
  210. package/std/settlements/settlement_batch.hsx +122 -0
  211. package/std/settlements/swap.hsx +1114 -0
  212. package/std/settlements/threshold_pool.hsx +221 -0
  213. package/std/settlements/weighted_distribution.hsx +141 -0
  214. package/dist/src/archetypes.d.ts +0 -17
  215. package/dist/src/archetypes.d.ts.map +0 -1
  216. package/dist/src/archetypes.js +0 -327
  217. package/dist/src/archetypes.js.map +0 -1
  218. package/dist/src/check.d.ts +0 -12
  219. package/dist/src/check.d.ts.map +0 -1
  220. package/dist/src/check.js +0 -1910
  221. package/dist/src/check.js.map +0 -1
  222. package/dist/src/lower.d.ts +0 -146
  223. package/dist/src/lower.d.ts.map +0 -1
  224. package/dist/src/lower.js +0 -4570
  225. package/dist/src/lower.js.map +0 -1
  226. package/dist/src/model.d.ts +0 -467
  227. package/dist/src/model.d.ts.map +0 -1
  228. package/dist/src/model.js +0 -15
  229. package/dist/src/model.js.map +0 -1
  230. package/spec/hsx-ir.schema.json +0 -920
  231. package/src/archetypes.ts +0 -340
  232. package/src/check.ts +0 -2938
  233. package/src/lower.ts +0 -5416
  234. package/src/model.ts +0 -519
package/src/parse.ts CHANGED
@@ -7,21 +7,31 @@
7
7
  */
8
8
 
9
9
  import type {
10
+ ApplyExpr,
10
11
  AssetDecl,
11
12
  BlockExpr,
13
+ ConstDecl,
12
14
  Decl,
13
15
  Diagnostic,
14
16
  Entry,
15
17
  Expr,
18
+ ExposeDecl,
16
19
  IdentExpr,
17
20
  ImportDecl,
21
+ InstrumentApplyDecl,
22
+ InstrumentDecl,
23
+ ModuleDecl,
24
+ Parameter,
18
25
  PartyDecl,
19
26
  PortDecl,
20
27
  Program,
21
28
  ProgramDecl,
22
- SettlementDecl,
23
29
  Span,
24
30
  StringExpr,
31
+ SubjectDecl,
32
+ TypeDecl,
33
+ TypeParameter,
34
+ UseDecl,
25
35
  } from "./ast.ts";
26
36
  import { lex, type Token } from "./lex.ts";
27
37
  import { HSX_LIMITS } from "./limits.ts";
@@ -31,6 +41,8 @@ export interface ParseResult {
31
41
  readonly program: Program;
32
42
  }
33
43
 
44
+ type BlockContext = "general" | "lifecycle";
45
+
34
46
  export function parseProgram(source: string): ParseResult {
35
47
  // Measured before lexing. The ceiling counts UTF-8 bytes, so the encode is
36
48
  // the measurement rather than an extra step: `source.length` counts UTF-16
@@ -48,6 +60,21 @@ export function parseProgram(source: string): ParseResult {
48
60
  };
49
61
  }
50
62
 
63
+ const first = source.search(/\S/);
64
+ if (first >= 0 && source[first] === "{") {
65
+ return {
66
+ diagnostics: [
67
+ {
68
+ code: "HSX1014",
69
+ fix: "replace the JSON object with HSX declarations",
70
+ message: "JSON is not HSX",
71
+ span: { end: first + 1, start: first },
72
+ },
73
+ ],
74
+ program: { decls: [], span: { end: source.length, start: 0 } },
75
+ };
76
+ }
77
+
51
78
  const lexed = lex(source);
52
79
  const parser = new Parser(lexed.tokens, [...lexed.diagnostics]);
53
80
  const program = parser.parseProgram(source.length);
@@ -68,6 +95,10 @@ class Parser {
68
95
  parseProgram(sourceLength: number): Program {
69
96
  const decls: Decl[] = [];
70
97
  while (!this.at("eof")) {
98
+ if (this.atPunct(";")) {
99
+ this.advance();
100
+ continue;
101
+ }
71
102
  const decl = this.parseDecl();
72
103
  if (decl) {
73
104
  decls.push(decl);
@@ -80,30 +111,47 @@ class Parser {
80
111
 
81
112
  // --- Declarations --------------------------------------------------------
82
113
 
83
- private parseDecl(): Decl | undefined {
114
+ private parseDecl(exported = false): Decl | undefined {
84
115
  const token = this.peek();
85
116
  if (token.kind !== "keyword" || token.text === "from") {
86
117
  this.error(
87
118
  token.span,
88
119
  token.kind === "eof"
89
120
  ? "the program ends unexpectedly"
90
- : `expected a declaration (program, import, party, asset, settlement, or port), found "${token.text}"`,
121
+ : `expected a declaration (module, program, use, expose, import, party, asset, subject, instrument, settlement, type, const, or port), found "${token.text}"`,
91
122
  );
92
123
  return undefined;
93
124
  }
94
125
  switch (token.text) {
126
+ case "export":
127
+ this.advance();
128
+ return this.parseDecl(true);
129
+ case "module":
130
+ return this.parseModuleDecl(exported);
95
131
  case "program":
96
132
  return this.parseProgramDecl();
133
+ case "use":
134
+ return this.parseUseDecl();
135
+ case "expose":
136
+ return this.parseExposeDecl();
97
137
  case "import":
98
138
  return this.parseImportDecl();
99
139
  case "party":
100
140
  return this.parsePartyDecl();
101
141
  case "asset":
102
142
  return this.parseAssetDecl();
143
+ case "subject":
144
+ return this.parseSubjectDecl(exported);
145
+ case "instrument":
146
+ return this.parseInstrumentDecl(exported);
103
147
  case "settlement":
104
148
  return this.parseSettlementDecl();
105
149
  case "port":
106
150
  return this.parsePortDecl();
151
+ case "type":
152
+ return this.parseTypeDecl(exported);
153
+ case "const":
154
+ return this.parseConstDecl(exported);
107
155
  default:
108
156
  this.error(token.span, `"${token.text}" cannot start a declaration`);
109
157
  this.advance();
@@ -111,6 +159,142 @@ class Parser {
111
159
  }
112
160
  }
113
161
 
162
+ private parseModuleDecl(exported: boolean): ModuleDecl | undefined {
163
+ const start = this.advance();
164
+ if (exported) {
165
+ this.error(start.span, "a module declaration cannot be exported");
166
+ }
167
+ const name = this.parsePath("the module needs a dotted name");
168
+ return name
169
+ ? { kind: "module", name, span: this.spanFrom(start.span) }
170
+ : undefined;
171
+ }
172
+
173
+ private parseInstrumentDecl(
174
+ exported: boolean,
175
+ ): InstrumentDecl | InstrumentApplyDecl | undefined {
176
+ const start = this.advance();
177
+ const name = this.expectIdent("the instrument needs a snake_case name");
178
+ if (!name) return undefined;
179
+ const typeParameters = this.parseTypeParameters();
180
+ if (!typeParameters) return undefined;
181
+ const hasParameterList = this.atPunct("(");
182
+ const parameters = hasParameterList ? this.parseParameters() : [];
183
+ if (!parameters) return undefined;
184
+ if (this.atPunct("=")) {
185
+ this.advance();
186
+ const application = this.parseApplication();
187
+ if (!application) return undefined;
188
+ const metadata = this.atPunct("{") ? this.parseBlock() : undefined;
189
+ return {
190
+ application,
191
+ exported,
192
+ kind: "instrument_apply",
193
+ ...(metadata ? { metadata } : {}),
194
+ name,
195
+ span: this.spanFrom(start.span),
196
+ };
197
+ }
198
+ if (!this.atPunct("{")) {
199
+ this.error(
200
+ this.peek().span,
201
+ `instrument ${name.name} needs a body or an exported instrument application`,
202
+ );
203
+ return undefined;
204
+ }
205
+ const body = this.parseBlock();
206
+ if (!body) return undefined;
207
+ return {
208
+ body,
209
+ exported,
210
+ hasParameterList,
211
+ kind: "instrument",
212
+ name,
213
+ parameters,
214
+ span: this.spanFrom(start.span),
215
+ typeParameters,
216
+ };
217
+ }
218
+
219
+ private parseTypeDecl(exported: boolean): TypeDecl | undefined {
220
+ const start = this.advance();
221
+ const name = this.expectIdent("the type needs a name");
222
+ if (!name) return undefined;
223
+ if (
224
+ !this.expectPunct("=", `type ${name.name} needs = before its definition`)
225
+ ) {
226
+ return undefined;
227
+ }
228
+ const value = this.parseExpr();
229
+ return value
230
+ ? { exported, kind: "type", name, span: this.spanFrom(start.span), value }
231
+ : undefined;
232
+ }
233
+
234
+ private parseConstDecl(exported: boolean): ConstDecl | undefined {
235
+ const start = this.advance();
236
+ const name = this.expectIdent("the constant needs a name");
237
+ if (!name) return undefined;
238
+ let type: Expr | undefined;
239
+ if (this.atPunct(":")) {
240
+ this.advance();
241
+ type = this.parseExpr();
242
+ if (!type) return undefined;
243
+ }
244
+ if (!this.expectPunct("=", `const ${name.name} needs = before its value`)) {
245
+ return undefined;
246
+ }
247
+ const value = this.parseExpr();
248
+ return value
249
+ ? {
250
+ exported,
251
+ kind: "const",
252
+ name,
253
+ span: this.spanFrom(start.span),
254
+ ...(type ? { type } : {}),
255
+ value,
256
+ }
257
+ : undefined;
258
+ }
259
+
260
+ private parseTypeParameters(): TypeParameter[] | undefined {
261
+ if (!this.atPunct("<")) return [];
262
+ this.advance();
263
+ const parameters: TypeParameter[] = [];
264
+ while (!this.atPunct(">") && !this.at("eof")) {
265
+ const name = this.expectIdent("expected a type parameter");
266
+ if (!name) return undefined;
267
+ parameters.push({ name, span: name.span });
268
+ if (this.atPunct(",")) this.advance();
269
+ }
270
+ return this.expectPunct(">", "the type parameter list never closes")
271
+ ? parameters
272
+ : undefined;
273
+ }
274
+
275
+ private parseParameters(): Parameter[] | undefined {
276
+ this.advance();
277
+ const parameters: Parameter[] = [];
278
+ while (!this.atPunct(")") && !this.at("eof")) {
279
+ const name = this.expectIdent("expected a parameter name");
280
+ if (!name) return undefined;
281
+ if (!this.expectPunct(":", `parameter ${name.name} needs a type`)) {
282
+ return undefined;
283
+ }
284
+ const type = this.parseExpr();
285
+ if (!type) return undefined;
286
+ parameters.push({
287
+ name,
288
+ span: { start: name.span.start, end: type.span.end },
289
+ type,
290
+ });
291
+ if (this.atPunct(",")) this.advance();
292
+ }
293
+ return this.expectPunct(")", "the parameter list never closes")
294
+ ? parameters
295
+ : undefined;
296
+ }
297
+
114
298
  private parseProgramDecl(): ProgramDecl | undefined {
115
299
  const start = this.advance();
116
300
  const name = this.expectIdent("the program needs a snake_case name");
@@ -128,12 +312,62 @@ class Parser {
128
312
  };
129
313
  }
130
314
 
315
+ private parseUseDecl(): UseDecl | undefined {
316
+ const start = this.advance();
317
+ const instrument = this.expectIdent(
318
+ "use needs a published instrument id, like: use held_settlement",
319
+ );
320
+ return instrument
321
+ ? { instrument, kind: "use", span: this.spanFrom(start.span) }
322
+ : undefined;
323
+ }
324
+
325
+ private parseExposeDecl(): ExposeDecl | undefined {
326
+ const start = this.advance();
327
+ const instrument = this.expectIdent(
328
+ "expose needs an instrument action, like: expose held_settlement.release as releaseFunds",
329
+ );
330
+ if (!instrument) return undefined;
331
+ if (
332
+ !this.expectPunct(
333
+ ".",
334
+ "expose separates the instrument and action with .",
335
+ )
336
+ ) {
337
+ return undefined;
338
+ }
339
+ const action = this.expectName(
340
+ "expose needs an action after the instrument id",
341
+ );
342
+ if (!action) return undefined;
343
+ if (!this.at("keyword") || this.peek().text !== "as") {
344
+ this.error(
345
+ this.peek().span,
346
+ "expose needs as before its public action name",
347
+ );
348
+ return undefined;
349
+ }
350
+ this.advance();
351
+ const publicName = this.expectIdent(
352
+ "expose needs a camelCase public action name",
353
+ );
354
+ return publicName
355
+ ? {
356
+ action,
357
+ instrument,
358
+ kind: "expose",
359
+ publicName,
360
+ span: this.spanFrom(start.span),
361
+ }
362
+ : undefined;
363
+ }
364
+
131
365
  private parseImportDecl(): ImportDecl | undefined {
132
366
  const start = this.advance();
133
367
  if (
134
368
  !this.expectPunct(
135
369
  "{",
136
- 'import lists its names in braces: import { held_payment } from "settlement"',
370
+ 'import lists its names in braces: import { held_payment } from "std/settlements"',
137
371
  )
138
372
  ) {
139
373
  return undefined;
@@ -151,7 +385,7 @@ class Parser {
151
385
  if (!this.at("keyword") || this.peek().text !== "from") {
152
386
  this.error(
153
387
  this.peek().span,
154
- 'import needs from: import { held_payment } from "settlement"',
388
+ 'import needs from: import { held_payment } from "std/settlements"',
155
389
  );
156
390
  return undefined;
157
391
  }
@@ -159,7 +393,7 @@ class Parser {
159
393
  if (!this.at("string")) {
160
394
  this.error(
161
395
  this.peek().span,
162
- 'the import source must be a quoted module name like "settlement"',
396
+ 'the import source must be a quoted module name like "std/settlements"',
163
397
  );
164
398
  return undefined;
165
399
  }
@@ -235,7 +469,24 @@ class Parser {
235
469
  };
236
470
  }
237
471
 
238
- private parseSettlementDecl(): SettlementDecl | undefined {
472
+ private parseSubjectDecl(exported: boolean): SubjectDecl | undefined {
473
+ const start = this.advance();
474
+ if (exported) {
475
+ this.error(start.span, "a subject declaration cannot be exported");
476
+ }
477
+ const name = this.expectIdent("the subject needs a snake_case kind name");
478
+ if (!name) return undefined;
479
+ if (!this.atPunct("{")) {
480
+ this.error(this.peek().span, `subject ${name.name} needs a body`);
481
+ return undefined;
482
+ }
483
+ const body = this.parseBlock();
484
+ return body
485
+ ? { body, kind: "subject", name, span: this.spanFrom(start.span) }
486
+ : undefined;
487
+ }
488
+
489
+ private parseSettlementDecl(): InstrumentApplyDecl | undefined {
239
490
  const start = this.advance();
240
491
  const name = this.expectIdent(
241
492
  "the settlement needs a name, like: settlement sale = held_payment { ... }",
@@ -263,9 +514,20 @@ class Parser {
263
514
  const body = this.parseBlock();
264
515
  if (!body) return undefined;
265
516
  return {
266
- archetype,
267
- body,
268
- kind: "settlement",
517
+ application: {
518
+ args: body.entries.map((row) => ({
519
+ kind: "binding" as const,
520
+ name: row.key,
521
+ span: row.value.span,
522
+ type: row.value,
523
+ })),
524
+ callee: { kind: "path", parts: [archetype], span: archetype.span },
525
+ kind: "apply",
526
+ span: { start: archetype.span.start, end: body.span.end },
527
+ typeArgs: [],
528
+ },
529
+ exported: false,
530
+ kind: "instrument_apply",
269
531
  name,
270
532
  span: this.spanFrom(start.span),
271
533
  };
@@ -314,19 +576,23 @@ class Parser {
314
576
  return true;
315
577
  }
316
578
 
317
- private parseBlock(): BlockExpr | undefined {
579
+ private parseBlock(context: BlockContext = "general"): BlockExpr | undefined {
318
580
  if (!this.enterNesting(this.peek().span)) return undefined;
319
- const block = this.parseBlockUnguarded();
581
+ const block = this.parseBlockUnguarded(context);
320
582
  this.depth -= 1;
321
583
  return block;
322
584
  }
323
585
 
324
- private parseBlockUnguarded(): BlockExpr | undefined {
586
+ private parseBlockUnguarded(context: BlockContext): BlockExpr | undefined {
325
587
  const open = this.advance();
326
588
  const entries: Entry[] = [];
327
589
  while (!this.atPunct("}") && !this.at("eof")) {
590
+ if (this.atPunct(";") || this.atPunct(",")) {
591
+ this.advance();
592
+ continue;
593
+ }
328
594
  const before = this.index;
329
- const entry = this.parseEntry();
595
+ const entry = this.parseEntry(context);
330
596
  if (!entry) {
331
597
  this.recoverInBlock();
332
598
  // recoverInBlock bails without consuming when it lands on a depth-0
@@ -337,7 +603,7 @@ class Parser {
337
603
  continue;
338
604
  }
339
605
  entries.push(entry);
340
- if (this.atPunct(",")) this.advance();
606
+ if (this.atPunct(",") || this.atPunct(";")) this.advance();
341
607
  }
342
608
  if (!this.expectPunct("}", "this block never closes; add the matching }")) {
343
609
  return { entries, kind: "block", span: this.spanFrom(open.span) };
@@ -345,17 +611,193 @@ class Parser {
345
611
  return { entries, kind: "block", span: this.spanFrom(open.span) };
346
612
  }
347
613
 
348
- private parseEntry(): Entry | undefined {
349
- const key = this.expectIdent("expected an entry name here");
614
+ private parseEntry(context: BlockContext): Entry | undefined {
615
+ const token = this.peek();
616
+ const key =
617
+ token.kind === "string"
618
+ ? (() => {
619
+ this.advance();
620
+ return {
621
+ kind: "ident" as const,
622
+ name: token.value,
623
+ quoted: !token.value.includes("["),
624
+ span: token.span,
625
+ };
626
+ })()
627
+ : this.isName(token) && (token.text === "action" || token.text === "on")
628
+ ? this.parseIdent()
629
+ : this.parseTemplatedName("expected an entry name here");
350
630
  if (!key) return undefined;
351
631
 
352
632
  const qualifiers: IdentExpr[] = [];
633
+ if (key.name === "for") {
634
+ const binding = this.expectIdent("for needs a compile-time binding name");
635
+ const inWord = binding
636
+ ? this.expectName("for needs in before its finite bound")
637
+ : undefined;
638
+ if (!binding || inWord?.name !== "in") {
639
+ this.error(
640
+ this.peek().span,
641
+ "write `for item in <finite bound> { ... }`",
642
+ );
643
+ return undefined;
644
+ }
645
+ const bound = this.parseExpr();
646
+ if (!bound || !this.atPunct("{")) {
647
+ this.error(
648
+ this.peek().span,
649
+ "a bounded comprehension needs a body in braces",
650
+ );
651
+ return undefined;
652
+ }
653
+ const value = this.parseBlock(context);
654
+ return value
655
+ ? {
656
+ iteration: { binding, bound },
657
+ key,
658
+ qualifiers,
659
+ span: { start: key.span.start, end: value.span.end },
660
+ value,
661
+ }
662
+ : undefined;
663
+ }
664
+ if (
665
+ key.name === "action" &&
666
+ (this.isName(this.peek()) || this.atPunct("["))
667
+ ) {
668
+ const action = this.parseTemplatedName("action needs a name");
669
+ if (!action) return undefined;
670
+ qualifiers.push(action);
671
+ if (!this.atPunct("{")) {
672
+ this.error(
673
+ this.peek().span,
674
+ `action ${qualifiers[0]?.name ?? ""} needs a body`,
675
+ );
676
+ return undefined;
677
+ }
678
+ const value = this.parseBlock();
679
+ return value
680
+ ? {
681
+ key,
682
+ qualifiers,
683
+ span: { start: key.span.start, end: value.span.end },
684
+ value,
685
+ }
686
+ : undefined;
687
+ }
688
+ if (context === "lifecycle" && key.name === "states") {
689
+ const items: Expr[] = [];
690
+ while (!this.atPunct(";") && !this.atPunct("}") && !this.at("eof")) {
691
+ const state = this.parseTemplatedName("expected a lifecycle state");
692
+ if (!state) return undefined;
693
+ items.push(state);
694
+ }
695
+ const end = items.at(-1)?.span.end ?? key.span.end;
696
+ return {
697
+ key,
698
+ qualifiers,
699
+ span: { start: key.span.start, end },
700
+ value: { items, kind: "list", span: { start: key.span.end, end } },
701
+ };
702
+ }
703
+ if (context === "lifecycle" && key.name === "initial") {
704
+ const value = this.parseTemplatedName(
705
+ "initial needs one declared lifecycle state",
706
+ );
707
+ return value
708
+ ? {
709
+ key,
710
+ qualifiers,
711
+ span: { start: key.span.start, end: value.span.end },
712
+ value,
713
+ }
714
+ : undefined;
715
+ }
716
+ if (context === "lifecycle" && key.name === "parked") {
717
+ const state = this.parseTemplatedName("parked needs a lifecycle state");
718
+ if (!state) return undefined;
719
+ qualifiers.push(state);
720
+ const reasonWord = this.expectName('parked needs reason "..."');
721
+ if (!reasonWord || reasonWord.name !== "reason" || !this.at("string")) {
722
+ this.error(this.peek().span, 'parked needs reason "..."');
723
+ return undefined;
724
+ }
725
+ const token = this.advance();
726
+ const value: StringExpr = {
727
+ kind: "string",
728
+ span: token.span,
729
+ value: token.value,
730
+ };
731
+ return {
732
+ key,
733
+ qualifiers,
734
+ span: { start: key.span.start, end: value.span.end },
735
+ value,
736
+ };
737
+ }
738
+ if (context === "lifecycle" && key.name === "on") {
739
+ const action = this.parseTemplatedName("on needs an action name");
740
+ if (!action) return undefined;
741
+ qualifiers.push(action);
742
+ if (
743
+ !this.expectPunct(
744
+ ":",
745
+ `on ${action.name} needs : before its source states`,
746
+ )
747
+ ) {
748
+ return undefined;
749
+ }
750
+ while (!this.atPunct("->") && !this.at("eof")) {
751
+ const state = this.parseTemplatedName(
752
+ "expected a source lifecycle state",
753
+ );
754
+ if (!state) return undefined;
755
+ qualifiers.push(state);
756
+ if (this.atPunct("|")) this.advance();
757
+ }
758
+ if (
759
+ !this.expectPunct(
760
+ "->",
761
+ `on ${action.name} needs -> before its target state`,
762
+ )
763
+ ) {
764
+ return undefined;
765
+ }
766
+ const value = this.parseTemplatedName(
767
+ "expected a target lifecycle state",
768
+ );
769
+ return value
770
+ ? {
771
+ key,
772
+ qualifiers,
773
+ span: { start: key.span.start, end: value.span.end },
774
+ value,
775
+ }
776
+ : undefined;
777
+ }
778
+ if (key.name === "notify") {
779
+ const role = this.expectName("notify needs a declared role");
780
+ const via = role
781
+ ? this.expectName("notify needs via before its channel")
782
+ : undefined;
783
+ const channel = via
784
+ ? this.expectName("notify needs a channel")
785
+ : undefined;
786
+ if (!role || via?.name !== "via" || !channel) return undefined;
787
+ qualifiers.push(role, channel);
788
+ return {
789
+ key,
790
+ qualifiers,
791
+ span: { start: key.span.start, end: channel.span.end },
792
+ value: { kind: "boolean", span: channel.span, value: true },
793
+ };
794
+ }
353
795
  if (this.atPunct("(")) {
354
796
  this.advance();
355
797
  while (!this.atPunct(")") && !this.at("eof")) {
356
- const qualifier = this.expectIdent(
357
- `expected a qualifier inside ${key.name}( ... )`,
358
- );
798
+ const qualifier = this.at("number")
799
+ ? this.parseIdent()
800
+ : this.expectIdent(`expected a qualifier inside ${key.name}( ... )`);
359
801
  if (!qualifier) return undefined;
360
802
  qualifiers.push(qualifier);
361
803
  if (this.atPunct(",")) this.advance();
@@ -365,11 +807,30 @@ class Parser {
365
807
  }
366
808
  }
367
809
 
810
+ if (
811
+ (key.name === "requires" || key.name === "computes") &&
812
+ this.isName(this.peek())
813
+ ) {
814
+ while (this.isName(this.peek())) qualifiers.push(this.parseIdent());
815
+ }
816
+
368
817
  let value: Expr | undefined;
369
818
  if (this.atPunct("{")) {
370
- value = this.parseBlock();
819
+ value = this.parseBlock(
820
+ key.name === "lifecycle" || context === "lifecycle"
821
+ ? "lifecycle"
822
+ : "general",
823
+ );
371
824
  } else if (this.atPunct(":")) {
372
825
  this.advance();
826
+ value = this.atPunct("{")
827
+ ? this.parseBlock(
828
+ key.name === "lifecycle" || context === "lifecycle"
829
+ ? "lifecycle"
830
+ : "general",
831
+ )
832
+ : this.parseExpr();
833
+ } else if (this.canStartExpr(this.peek())) {
373
834
  value = this.parseExpr();
374
835
  } else {
375
836
  this.error(
@@ -483,8 +944,41 @@ class Parser {
483
944
  ...(within ? { within } : {}),
484
945
  };
485
946
  }
947
+ if (token.kind === "keyword") {
948
+ return this.parseIdent();
949
+ }
950
+ if (
951
+ token.kind === "ident" &&
952
+ /^[A-Z]{3}$/.test(token.text) &&
953
+ this.peekAt(1).kind === "number"
954
+ ) {
955
+ const currency = this.parseIdent();
956
+ const amount = this.advance();
957
+ return {
958
+ currency,
959
+ kind: "money",
960
+ raw: amount.value,
961
+ span: { start: currency.span.start, end: amount.span.end },
962
+ };
963
+ }
486
964
  if (token.kind === "ident") {
487
965
  const ident = this.parseIdent();
966
+ if (ident.name === "true" || ident.name === "false") {
967
+ return {
968
+ kind: "boolean",
969
+ span: ident.span,
970
+ value: ident.name === "true",
971
+ };
972
+ }
973
+ if (ident.name === "decided" && this.atPunct("{")) {
974
+ const body = this.parseBlock();
975
+ if (!body) return undefined;
976
+ return {
977
+ body,
978
+ kind: "decided_amount",
979
+ span: { end: body.span.end, start: ident.span.start },
980
+ };
981
+ }
488
982
  if (this.atPunct(".")) {
489
983
  this.advance();
490
984
  const member = this.expectIdent(
@@ -498,6 +992,16 @@ class Parser {
498
992
  span: { end: member.span.end, start: ident.span.start },
499
993
  };
500
994
  }
995
+ if (this.atPunct("<")) {
996
+ const args = this.parseAngleArguments();
997
+ if (!args) return undefined;
998
+ return {
999
+ args,
1000
+ callee: ident,
1001
+ kind: "type_apply",
1002
+ span: this.spanFrom(ident.span),
1003
+ };
1004
+ }
501
1005
  if (this.atPunct("(")) {
502
1006
  this.advance();
503
1007
  const args: Expr[] = [];
@@ -578,6 +1082,12 @@ class Parser {
578
1082
  return this.tokens[this.index] as Token;
579
1083
  }
580
1084
 
1085
+ private peekAt(offset: number): Token {
1086
+ return this.tokens[
1087
+ Math.min(this.index + offset, this.tokens.length - 1)
1088
+ ] as Token;
1089
+ }
1090
+
581
1091
  private previous(): Token {
582
1092
  return this.tokens[Math.max(0, this.index - 1)] as Token;
583
1093
  }
@@ -602,6 +1112,130 @@ class Parser {
602
1112
  return { kind: "ident", name: token.text, span: token.span };
603
1113
  }
604
1114
 
1115
+ private isName(token: Token): boolean {
1116
+ return token.kind === "ident" || token.kind === "keyword";
1117
+ }
1118
+
1119
+ private expectName(message: string): IdentExpr | undefined {
1120
+ if (this.isName(this.peek())) return this.parseIdent();
1121
+ this.error(this.peek().span, message);
1122
+ return undefined;
1123
+ }
1124
+
1125
+ private parseTemplatedName(message: string): IdentExpr | undefined {
1126
+ if (this.atPunct("[")) {
1127
+ const open = this.advance();
1128
+ const binding = this.expectIdent(
1129
+ "an indexed name needs its loop binding",
1130
+ );
1131
+ if (!binding || !this.expectPunct("]", "an indexed name needs ]")) {
1132
+ return undefined;
1133
+ }
1134
+ const suffix = this.isName(this.peek()) ? this.parseIdent() : undefined;
1135
+ return {
1136
+ kind: "ident",
1137
+ name: `[${binding.name}]${suffix?.name ?? ""}`,
1138
+ span: {
1139
+ start: open.span.start,
1140
+ end: suffix?.span.end ?? this.previous().span.end,
1141
+ },
1142
+ };
1143
+ }
1144
+ const base = this.expectName(message);
1145
+ if (!base || !this.atPunct("[")) return base;
1146
+ this.advance();
1147
+ const binding = this.expectIdent("an indexed name needs its loop binding");
1148
+ if (!binding || !this.expectPunct("]", "an indexed name needs ]")) {
1149
+ return undefined;
1150
+ }
1151
+ const suffix = this.isName(this.peek()) ? this.parseIdent() : undefined;
1152
+ return {
1153
+ kind: "ident",
1154
+ name: `${base.name}[${binding.name}]${suffix?.name ?? ""}`,
1155
+ span: {
1156
+ start: base.span.start,
1157
+ end: suffix?.span.end ?? this.previous().span.end,
1158
+ },
1159
+ };
1160
+ }
1161
+
1162
+ private canStartExpr(token: Token): boolean {
1163
+ return (
1164
+ token.kind === "ident" ||
1165
+ token.kind === "keyword" ||
1166
+ token.kind === "number" ||
1167
+ token.kind === "percent" ||
1168
+ token.kind === "string" ||
1169
+ (token.kind === "punct" && (token.text === "{" || token.text === "["))
1170
+ );
1171
+ }
1172
+
1173
+ private parsePath(message: string): import("./ast.ts").PathExpr | undefined {
1174
+ const first = this.expectName(message);
1175
+ if (!first) return undefined;
1176
+ const parts = [first];
1177
+ while (this.atPunct(".")) {
1178
+ this.advance();
1179
+ const part = this.expectName("expected a name after .");
1180
+ if (!part) return undefined;
1181
+ parts.push(part);
1182
+ }
1183
+ return {
1184
+ kind: "path",
1185
+ parts,
1186
+ span: {
1187
+ start: first.span.start,
1188
+ end: parts.at(-1)?.span.end ?? first.span.end,
1189
+ },
1190
+ };
1191
+ }
1192
+
1193
+ private parseAngleArguments(): Expr[] | undefined {
1194
+ this.advance();
1195
+ const args: Expr[] = [];
1196
+ while (!this.atPunct(">") && !this.at("eof")) {
1197
+ const arg = this.parseExpr();
1198
+ if (!arg) return undefined;
1199
+ args.push(arg);
1200
+ if (this.atPunct(",")) this.advance();
1201
+ }
1202
+ return this.expectPunct(">", "the type argument list never closes")
1203
+ ? args
1204
+ : undefined;
1205
+ }
1206
+
1207
+ private parseApplication(): ApplyExpr | undefined {
1208
+ const callee = this.parsePath("expected an instrument function name");
1209
+ if (!callee) return undefined;
1210
+ const typeArgs = this.atPunct("<") ? this.parseAngleArguments() : [];
1211
+ if (!typeArgs) return undefined;
1212
+ if (
1213
+ !this.expectPunct(
1214
+ "(",
1215
+ "an instrument application needs arguments in parentheses",
1216
+ )
1217
+ ) {
1218
+ return undefined;
1219
+ }
1220
+ const args: Expr[] = [];
1221
+ while (!this.atPunct(")") && !this.at("eof")) {
1222
+ const arg = this.parseExpr();
1223
+ if (!arg) return undefined;
1224
+ args.push(arg);
1225
+ if (this.atPunct(",")) this.advance();
1226
+ }
1227
+ if (!this.expectPunct(")", "the instrument argument list never closes")) {
1228
+ return undefined;
1229
+ }
1230
+ return {
1231
+ args,
1232
+ callee,
1233
+ kind: "apply",
1234
+ span: { start: callee.span.start, end: this.previous().span.end },
1235
+ typeArgs,
1236
+ };
1237
+ }
1238
+
605
1239
  private expectIdent(message: string): IdentExpr | undefined {
606
1240
  if (this.at("ident")) return this.parseIdent();
607
1241
  this.error(this.peek().span, message);