@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/ast.ts CHANGED
@@ -78,6 +78,8 @@ export function lineColIn(index: LineIndex, offset: number): LineCol {
78
78
  export interface IdentExpr {
79
79
  readonly kind: "ident";
80
80
  readonly name: string;
81
+ /** A literal quoted block key keeps its exact spelling during JSON lowering. */
82
+ readonly quoted?: boolean;
81
83
  readonly span: Span;
82
84
  }
83
85
 
@@ -87,13 +89,51 @@ export interface StringExpr {
87
89
  readonly value: string;
88
90
  }
89
91
 
90
- interface NumberExpr {
92
+ export interface NumberExpr {
91
93
  readonly kind: "number";
92
94
  /** The literal exactly as written, e.g. "99.5". Interpretation is typed later. */
93
95
  readonly raw: string;
94
96
  readonly span: Span;
95
97
  }
96
98
 
99
+ export interface BooleanExpr {
100
+ readonly kind: "boolean";
101
+ readonly span: Span;
102
+ readonly value: boolean;
103
+ }
104
+
105
+ /** A currency-indexed literal. The checker converts it to integer minor units. */
106
+ export interface MoneyExpr {
107
+ readonly currency: IdentExpr;
108
+ readonly kind: "money";
109
+ readonly raw: string;
110
+ readonly span: Span;
111
+ }
112
+
113
+ /** A dotted name used for modules, UDL operations, and field paths. */
114
+ export interface PathExpr {
115
+ readonly kind: "path";
116
+ readonly parts: readonly IdentExpr[];
117
+ readonly span: Span;
118
+ }
119
+
120
+ /** A nominal type application such as `money<SAR>` or `ref<invoice>`. */
121
+ export interface TypeApplyExpr {
122
+ readonly args: readonly Expr[];
123
+ readonly callee: IdentExpr;
124
+ readonly kind: "type_apply";
125
+ readonly span: Span;
126
+ }
127
+
128
+ /** A general instrument or constant application, with optional type arguments. */
129
+ export interface ApplyExpr {
130
+ readonly args: readonly Expr[];
131
+ readonly callee: PathExpr;
132
+ readonly kind: "apply";
133
+ readonly span: Span;
134
+ readonly typeArgs: readonly Expr[];
135
+ }
136
+
97
137
  export interface PercentExpr {
98
138
  /** Exact basis points: 99.5% is 9950. Percents never round. */
99
139
  readonly bps: number;
@@ -110,6 +150,16 @@ export interface CallExpr {
110
150
  readonly span: Span;
111
151
  }
112
152
 
153
+ /**
154
+ * An exit amount chosen by a decision port and bounded by stored money:
155
+ * `decided { field: damageAmount, bound: depositAmount, remainder: return }`.
156
+ */
157
+ export interface DecidedAmountExpr {
158
+ readonly body: BlockExpr;
159
+ readonly kind: "decided_amount";
160
+ readonly span: Span;
161
+ }
162
+
113
163
  /**
114
164
  * A reference to a decision port, optionally bounded (`port dispute within
115
165
  * P14D`) or carrying the date that decides when the port has not
@@ -155,7 +205,7 @@ export interface BlockExpr {
155
205
  * `amount: price: money(SAR)` binds the field name `price` to type
156
206
  * `money(SAR)`: the entry key is `amount`, the value is this binding.
157
207
  */
158
- interface BindingExpr {
208
+ export interface BindingExpr {
159
209
  readonly kind: "binding";
160
210
  readonly name: IdentExpr;
161
211
  readonly span: Span;
@@ -163,16 +213,22 @@ interface BindingExpr {
163
213
  }
164
214
 
165
215
  export type Expr =
216
+ | ApplyExpr
166
217
  | BindingExpr
167
218
  | BlockExpr
219
+ | BooleanExpr
168
220
  | CallExpr
221
+ | DecidedAmountExpr
169
222
  | IdentExpr
170
223
  | ListExpr
224
+ | MoneyExpr
171
225
  | NumberExpr
226
+ | PathExpr
172
227
  | PercentExpr
173
228
  | PortRefExpr
174
229
  | SettlementRefExpr
175
- | StringExpr;
230
+ | StringExpr
231
+ | TypeApplyExpr;
176
232
 
177
233
  // --- Entries ---------------------------------------------------------------
178
234
 
@@ -182,6 +238,11 @@ export type Expr =
182
238
  * and `on_cancel(funded) { ... }` (key + qualifiers + block value).
183
239
  */
184
240
  export interface Entry {
241
+ /** `for item in bound { ... }`, expanded before binding or UDL lowering. */
242
+ readonly iteration?: {
243
+ readonly binding: IdentExpr;
244
+ readonly bound: Expr;
245
+ };
185
246
  readonly key: IdentExpr;
186
247
  readonly qualifiers: readonly IdentExpr[];
187
248
  readonly span: Span;
@@ -198,7 +259,23 @@ export interface ProgramDecl {
198
259
  readonly title?: StringExpr;
199
260
  }
200
261
 
201
- /** `import { held_payment } from "settlement"`. */
262
+ /** `use held_settlement` selects one published catalog instrument. */
263
+ export interface UseDecl {
264
+ readonly instrument: IdentExpr;
265
+ readonly kind: "use";
266
+ readonly span: Span;
267
+ }
268
+
269
+ /** `expose held_settlement.release as releaseFunds` names a public action. */
270
+ export interface ExposeDecl {
271
+ readonly action: IdentExpr;
272
+ readonly instrument: IdentExpr;
273
+ readonly kind: "expose";
274
+ readonly publicName: IdentExpr;
275
+ readonly span: Span;
276
+ }
277
+
278
+ /** `import { held_payment } from "std/settlements"`. */
202
279
  export interface ImportDecl {
203
280
  readonly from: StringExpr;
204
281
  readonly kind: "import";
@@ -224,11 +301,10 @@ export interface AssetDecl {
224
301
  readonly span: Span;
225
302
  }
226
303
 
227
- /** `settlement sale = held_payment { ... }`, an archetype instantiation. */
228
- export interface SettlementDecl {
229
- readonly archetype: IdentExpr;
304
+ /** A full UDL subject-kind declaration. `asset` remains shorthand for this form. */
305
+ export interface SubjectDecl {
230
306
  readonly body: BlockExpr;
231
- readonly kind: "settlement";
307
+ readonly kind: "subject";
232
308
  readonly name: IdentExpr;
233
309
  readonly span: Span;
234
310
  }
@@ -241,13 +317,89 @@ export interface PortDecl {
241
317
  readonly span: Span;
242
318
  }
243
319
 
320
+ /** `module std.settlements` gives a file its importable module name. */
321
+ export interface ModuleDecl {
322
+ readonly kind: "module";
323
+ readonly name: PathExpr;
324
+ readonly span: Span;
325
+ }
326
+
327
+ export interface TypeParameter {
328
+ readonly name: IdentExpr;
329
+ readonly span: Span;
330
+ }
331
+
332
+ export interface Parameter {
333
+ readonly name: IdentExpr;
334
+ readonly span: Span;
335
+ readonly type: Expr;
336
+ }
337
+
338
+ /** The single general instrument definition form. */
339
+ export interface InstrumentDecl {
340
+ readonly body: BlockExpr;
341
+ /** Module-local declarations carried only while this template is bound. */
342
+ readonly declarationScope?: readonly ApplicationScopeDecl[];
343
+ readonly exported: boolean;
344
+ /** True when the source declared a parameter list, including an empty `()`. */
345
+ readonly hasParameterList: boolean;
346
+ readonly kind: "instrument";
347
+ readonly name: IdentExpr;
348
+ readonly parameters: readonly Parameter[];
349
+ readonly span: Span;
350
+ readonly typeParameters: readonly TypeParameter[];
351
+ }
352
+
353
+ /** Instantiation of an exported parameterized instrument. */
354
+ export interface InstrumentApplyDecl {
355
+ readonly application: ApplyExpr;
356
+ /** Module-local declarations carried only while this application is bound. */
357
+ readonly declarationScope?: readonly ApplicationScopeDecl[];
358
+ readonly exported: boolean;
359
+ readonly kind: "instrument_apply";
360
+ readonly metadata?: BlockExpr;
361
+ readonly name: IdentExpr;
362
+ readonly span: Span;
363
+ }
364
+
365
+ export interface TypeDecl {
366
+ readonly exported: boolean;
367
+ readonly kind: "type";
368
+ readonly name: IdentExpr;
369
+ readonly span: Span;
370
+ readonly value: Expr;
371
+ }
372
+
373
+ export interface ConstDecl {
374
+ readonly exported: boolean;
375
+ readonly kind: "const";
376
+ readonly name: IdentExpr;
377
+ readonly span: Span;
378
+ readonly type?: Expr;
379
+ readonly value: Expr;
380
+ }
381
+
382
+ export type ApplicationScopeDecl =
383
+ | ConstDecl
384
+ | InstrumentDecl
385
+ | PartyDecl
386
+ | PortDecl
387
+ | TypeDecl;
388
+
244
389
  export type Decl =
245
390
  | AssetDecl
391
+ | ConstDecl
392
+ | ExposeDecl
246
393
  | ImportDecl
394
+ | InstrumentApplyDecl
395
+ | InstrumentDecl
396
+ | ModuleDecl
247
397
  | PartyDecl
248
398
  | PortDecl
249
399
  | ProgramDecl
250
- | SettlementDecl;
400
+ | SubjectDecl
401
+ | TypeDecl
402
+ | UseDecl;
251
403
 
252
404
  // --- Program ---------------------------------------------------------------
253
405
 
@@ -263,6 +415,8 @@ export interface Program {
263
415
  * it never throws, it returns the best-effort tree plus these.
264
416
  */
265
417
  export interface Diagnostic {
418
+ readonly code?: string;
419
+ readonly fix?: string;
266
420
  readonly message: string;
267
421
  readonly span: Span;
268
422
  }
package/src/cli.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The `hsx` command line: two subcommands over the one compiler entry point.
2
+ * The `hsx` command line over the one compiler entry point and catalog.
3
3
  *
4
4
  * `check` prints diagnostics and says nothing else; `build` writes the
5
5
  * compiled artifacts as JSON. Neither reads the environment, neither touches
@@ -10,8 +10,12 @@
10
10
  * asserting on a subprocess's scrollback.
11
11
  */
12
12
 
13
+ import { fileURLToPath } from "node:url";
13
14
  import { compile, type CompileResult } from "./compile.ts";
14
- import { HSX_IR_VERSION, HSX_VERSION } from "./version.ts";
15
+ import type { UdlCostManifest, UdlCostTable } from "./cost.ts";
16
+ import { hsxDiagnostics } from "./diagnostics.ts";
17
+ import { format } from "./format.ts";
18
+ import { HSX_TARGET_UDL_VERSION, HSX_VERSION } from "./version.ts";
15
19
 
16
20
  /** Filesystem and streams, injected so the CLI stays testable. */
17
21
  export interface Io {
@@ -29,20 +33,27 @@ const OK = 0;
29
33
  const REFUSED = 1;
30
34
  const USAGE = 2;
31
35
 
32
- const USAGE_TEXT = `hsx ${HSX_VERSION}, the HSX compiler
36
+ export const USAGE_TEXT = `hsx ${HSX_VERSION}, the HSX compiler
33
37
 
34
38
  Usage:
35
39
  hsx check <file.hsx> [--strict]
36
40
  hsx build <file.hsx> [--out <file.json>] [--strict]
41
+ hsx cost <file.hsx> [--json] [--out <file.json>] [--strict]
42
+ hsx explain <HSX####>
43
+ hsx format <file.hsx>
37
44
  hsx --version
38
45
  hsx --help
39
46
 
40
47
  Commands:
41
48
  check Compile and report diagnostics. Prints nothing when the program is clean.
42
- build Compile and write the HSX-JSON IR document and Business Frame as JSON.
49
+ build Compile and write canonical UDL and its Business Frame as JSON.
50
+ cost Compile and print the version-pinned cost manifest as a table or JSON.
51
+ explain Print one diagnostic title, fix, and source example.
52
+ format Print the source in the one canonical HSX style.
43
53
 
44
54
  Options:
45
- --out <file> Write build output to this path instead of stdout.
55
+ --json Print the cost manifest as JSON instead of a table.
56
+ --out <file> Write build or cost JSON to this path instead of stdout.
46
57
  --strict Treat warning-severity diagnostics as failures.
47
58
 
48
59
  Exit codes:
@@ -58,10 +69,16 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
58
69
  return command === undefined ? USAGE : OK;
59
70
  }
60
71
  if (command === "--version" || command === "-v") {
61
- io.out(`${HSX_VERSION} (IR version ${HSX_IR_VERSION})`);
72
+ io.out(`${HSX_VERSION} (UDL version ${HSX_TARGET_UDL_VERSION})`);
62
73
  return OK;
63
74
  }
64
- if (command !== "check" && command !== "build") {
75
+ if (command === "explain") return explainDiagnostic(rest, io);
76
+ if (
77
+ command !== "check" &&
78
+ command !== "build" &&
79
+ command !== "cost" &&
80
+ command !== "format"
81
+ ) {
65
82
  io.err(`hsx: unknown command "${command}"`);
66
83
  io.err(USAGE_TEXT);
67
84
  return USAGE;
@@ -81,7 +98,26 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
81
98
  return USAGE;
82
99
  }
83
100
 
84
- const result = compile(source);
101
+ if (command === "format") {
102
+ const result = format(source);
103
+ if (!result.ok) {
104
+ for (const diagnostic of result.diagnostics) {
105
+ io.err(`${parsed.file}:1:1: error [parse] ${diagnostic.message}`);
106
+ }
107
+ return REFUSED;
108
+ }
109
+ io.out(result.formatted.trimEnd());
110
+ return OK;
111
+ }
112
+
113
+ let costTable: UdlCostTable;
114
+ try {
115
+ costTable = await readDefaultCostTable(io);
116
+ } catch (cause) {
117
+ io.err(`hsx: cannot read the packaged cost table: ${messageOf(cause)}`);
118
+ return USAGE;
119
+ }
120
+ const result = compile(source, { costTable });
85
121
  for (const line of diagnosticLines(parsed.file, result)) io.err(line);
86
122
 
87
123
  const refused =
@@ -93,6 +129,19 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
93
129
  }
94
130
 
95
131
  if (!result.artifacts) return REFUSED;
132
+ if (command === "cost") {
133
+ const json = `${JSON.stringify(result.artifacts.costManifest, null, 2)}\n`;
134
+ if (parsed.out !== undefined) {
135
+ const writeCode = await writeOutput(parsed.out, json, io);
136
+ return writeCode === OK && refused ? REFUSED : writeCode;
137
+ }
138
+ io.out(
139
+ parsed.json
140
+ ? json.trimEnd()
141
+ : renderCostTable(result.artifacts.costManifest),
142
+ );
143
+ return refused ? REFUSED : OK;
144
+ }
96
145
  const json = `${JSON.stringify(
97
146
  {
98
147
  document: result.artifacts.document,
@@ -116,26 +165,38 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
116
165
 
117
166
  interface Options {
118
167
  readonly file: string;
168
+ readonly json: boolean;
119
169
  readonly out?: string;
120
170
  readonly strict: boolean;
121
171
  }
122
172
 
123
173
  function parseOptions(
124
174
  args: readonly string[],
125
- command: "build" | "check",
175
+ command: "build" | "check" | "cost" | "format",
126
176
  ): Options | { readonly error: string } {
127
177
  let file: string | undefined;
178
+ let json = false;
128
179
  let out: string | undefined;
129
180
  let strict = false;
130
181
 
131
182
  for (let index = 0; index < args.length; index += 1) {
132
183
  const argument = args[index] as string;
133
184
  if (argument === "--strict") {
185
+ if (command === "format") {
186
+ return { error: "hsx format has no style options" };
187
+ }
134
188
  strict = true;
135
189
  continue;
136
190
  }
191
+ if (argument === "--json") {
192
+ if (command !== "cost") return { error: "--json belongs to hsx cost" };
193
+ json = true;
194
+ continue;
195
+ }
137
196
  if (argument === "--out") {
138
- if (command !== "build") return { error: "--out belongs to hsx build" };
197
+ if (command !== "build" && command !== "cost") {
198
+ return { error: "--out belongs to hsx build or hsx cost" };
199
+ }
139
200
  const value = args[index + 1];
140
201
  if (value === undefined || value.startsWith("--")) {
141
202
  return { error: "--out needs a file path" };
@@ -154,7 +215,98 @@ function parseOptions(
154
215
  }
155
216
 
156
217
  if (file === undefined) return { error: `hsx ${command} needs a file` };
157
- return { file, ...(out === undefined ? {} : { out }), strict };
218
+ return { file, json, ...(out === undefined ? {} : { out }), strict };
219
+ }
220
+
221
+ function explainDiagnostic(args: readonly string[], io: Io): number {
222
+ if (args.length !== 1 || args[0]?.startsWith("-")) {
223
+ io.err("hsx: explain needs one diagnostic code");
224
+ return USAGE;
225
+ }
226
+ const diagnostic = hsxDiagnostics.find(({ code }) => code === args[0]);
227
+ if (!diagnostic) {
228
+ io.err(`hsx: unknown diagnostic code ${args[0]}`);
229
+ return USAGE;
230
+ }
231
+ io.out(
232
+ [
233
+ `${diagnostic.code} ${diagnostic.title}`,
234
+ `Stage: ${diagnostic.stage}`,
235
+ `Fix: ${diagnostic.fix}`,
236
+ diagnostic.example === null
237
+ ? "Example: unavailable from source alone"
238
+ : `Example:\n${diagnostic.example}`,
239
+ ].join("\n"),
240
+ );
241
+ return OK;
242
+ }
243
+
244
+ function renderCostTable(manifest: UdlCostManifest): string {
245
+ const rows = [
246
+ `costTableVersion\t${manifest.costTableVersion}`,
247
+ "instrument.action\teffect\tunit\tcount\ttotal\tpayer",
248
+ ];
249
+ for (const action of manifest.actions) {
250
+ for (const component of action.components) {
251
+ const fixed = BigInt(component.perEventMinor) * BigInt(component.count);
252
+ const unit = [
253
+ `${component.perEventMinor} ${manifest.currency} minor`,
254
+ component.bps > 0 ? `${component.bps} bps` : undefined,
255
+ ]
256
+ .filter((part): part is string => part !== undefined)
257
+ .join(" + ");
258
+ const total =
259
+ component.bps === 0
260
+ ? `${fixed} ${manifest.currency} minor`
261
+ : [
262
+ fixed === 0n ? undefined : `${fixed} ${manifest.currency} minor`,
263
+ `amount-dependent (${component.bps} bps)`,
264
+ ]
265
+ .filter((part): part is string => part !== undefined)
266
+ .join(" + ");
267
+ rows.push(
268
+ [
269
+ `${action.instrument}.${action.action}`,
270
+ component.signature,
271
+ unit,
272
+ component.count,
273
+ total,
274
+ component.payer,
275
+ ].join("\t"),
276
+ );
277
+ }
278
+ }
279
+ return rows.join("\n");
280
+ }
281
+
282
+ async function writeOutput(
283
+ path: string,
284
+ contents: string,
285
+ io: Io,
286
+ ): Promise<number> {
287
+ try {
288
+ await io.writeFile(path, contents);
289
+ } catch (cause) {
290
+ io.err(`hsx: cannot write ${path}: ${messageOf(cause)}`);
291
+ return USAGE;
292
+ }
293
+ return OK;
294
+ }
295
+
296
+ async function readDefaultCostTable(io: Io): Promise<UdlCostTable> {
297
+ const candidates = [
298
+ fileURLToPath(new URL("../examples/cost-table.json", import.meta.url)),
299
+ fileURLToPath(new URL("../../examples/cost-table.json", import.meta.url)),
300
+ ];
301
+ let lastError: unknown;
302
+ for (const path of candidates) {
303
+ try {
304
+ return JSON.parse(await io.readFile(path)) as UdlCostTable;
305
+ } catch (cause) {
306
+ lastError = cause;
307
+ }
308
+ }
309
+ throw lastError;
158
310
  }
159
311
 
160
312
  /** `file:line:col: severity [stage] message`, the shape editors already parse. */
@@ -164,7 +316,7 @@ function diagnosticLines(
164
316
  ): readonly string[] {
165
317
  return result.diagnostics.map(
166
318
  (diagnostic) =>
167
- `${file}:${diagnostic.line}:${diagnostic.column}: ${diagnostic.severity} [${diagnostic.stage}] ${diagnostic.message}`,
319
+ `${diagnostic.file ?? file}:${diagnostic.line}:${diagnostic.column}: ${diagnostic.severity} [${diagnostic.stage}] ${diagnostic.message}`,
168
320
  );
169
321
  }
170
322