@hyperscale0/hsx 1.0.0-beta.1 → 1.0.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 (255) hide show
  1. package/CHANGELOG.md +58 -9
  2. package/LICENSING.md +3 -3
  3. package/README.md +39 -149
  4. package/TRADEMARKS.md +4 -4
  5. package/bin/hsx.ts +2 -0
  6. package/dist/bin/hsx.js +2 -0
  7. package/dist/bin/hsx.js.map +1 -1
  8. package/dist/src/ast.d.ts +135 -10
  9. package/dist/src/ast.d.ts.map +1 -1
  10. package/dist/src/ast.js +24 -0
  11. package/dist/src/ast.js.map +1 -1
  12. package/dist/src/cli.d.ts +4 -1
  13. package/dist/src/cli.d.ts.map +1 -1
  14. package/dist/src/cli.js +172 -16
  15. package/dist/src/cli.js.map +1 -1
  16. package/dist/src/compile.d.ts +42 -7
  17. package/dist/src/compile.d.ts.map +1 -1
  18. package/dist/src/compile.js +82 -16
  19. package/dist/src/compile.js.map +1 -1
  20. package/dist/src/cost.d.ts +78 -0
  21. package/dist/src/cost.d.ts.map +1 -0
  22. package/dist/src/cost.js +369 -0
  23. package/dist/src/cost.js.map +1 -0
  24. package/dist/src/diagnostics.d.ts +11 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +420 -0
  27. package/dist/src/diagnostics.js.map +1 -0
  28. package/dist/src/emit.d.ts +34 -0
  29. package/dist/src/emit.d.ts.map +1 -0
  30. package/dist/src/emit.js +137 -0
  31. package/dist/src/emit.js.map +1 -0
  32. package/dist/src/entry-overrides.d.ts +17 -3
  33. package/dist/src/entry-overrides.d.ts.map +1 -1
  34. package/dist/src/entry-overrides.js +180 -35
  35. package/dist/src/entry-overrides.js.map +1 -1
  36. package/dist/src/format.d.ts +10 -0
  37. package/dist/src/format.d.ts.map +1 -0
  38. package/dist/src/format.js +183 -0
  39. package/dist/src/format.js.map +1 -0
  40. package/dist/src/index.d.ts +11 -4
  41. package/dist/src/index.d.ts.map +1 -1
  42. package/dist/src/index.js +10 -3
  43. package/dist/src/index.js.map +1 -1
  44. package/dist/src/ir.d.ts +66 -0
  45. package/dist/src/ir.d.ts.map +1 -0
  46. package/dist/src/ir.js +15 -0
  47. package/dist/src/ir.js.map +1 -0
  48. package/dist/src/lex.d.ts +9 -1
  49. package/dist/src/lex.d.ts.map +1 -1
  50. package/dist/src/lex.js +50 -4
  51. package/dist/src/lex.js.map +1 -1
  52. package/dist/src/limits.d.ts +2 -3
  53. package/dist/src/limits.d.ts.map +1 -1
  54. package/dist/src/limits.js +2 -3
  55. package/dist/src/limits.js.map +1 -1
  56. package/dist/src/lsp/server.d.ts +32 -0
  57. package/dist/src/lsp/server.d.ts.map +1 -0
  58. package/dist/src/lsp/server.js +391 -0
  59. package/dist/src/lsp/server.js.map +1 -0
  60. package/dist/src/modules.d.ts +38 -0
  61. package/dist/src/modules.d.ts.map +1 -0
  62. package/dist/src/modules.js +368 -0
  63. package/dist/src/modules.js.map +1 -0
  64. package/dist/src/parse.d.ts.map +1 -1
  65. package/dist/src/parse.js +590 -17
  66. package/dist/src/parse.js.map +1 -1
  67. package/dist/src/std-bundle.d.ts +2 -0
  68. package/dist/src/std-bundle.d.ts.map +1 -0
  69. package/dist/src/std-bundle.js +88 -0
  70. package/dist/src/std-bundle.js.map +1 -0
  71. package/dist/src/std-library.d.ts +5 -0
  72. package/dist/src/std-library.d.ts.map +1 -0
  73. package/dist/src/std-library.js +9 -0
  74. package/dist/src/std-library.js.map +1 -0
  75. package/dist/src/typecheck.d.ts +8 -0
  76. package/dist/src/typecheck.d.ts.map +1 -0
  77. package/dist/src/typecheck.js +2958 -0
  78. package/dist/src/typecheck.js.map +1 -0
  79. package/dist/src/version.d.ts +4 -15
  80. package/dist/src/version.d.ts.map +1 -1
  81. package/dist/src/version.js +4 -15
  82. package/dist/src/version.js.map +1 -1
  83. package/docs/README.md +26 -0
  84. package/docs/guide/01-first-program.md +24 -0
  85. package/docs/guide/02-money.md +25 -0
  86. package/docs/guide/03-instruments.md +22 -0
  87. package/docs/guide/04-lifecycles.md +25 -0
  88. package/docs/guide/05-fees-and-splits.md +45 -0
  89. package/docs/guide/06-schedules.md +22 -0
  90. package/docs/guide/07-composition.md +46 -0
  91. package/docs/guide/08-writing-a-module.md +26 -0
  92. package/docs/guide/09-cost.md +24 -0
  93. package/docs/guide/10-diagnostics.md +27 -0
  94. package/docs/llms-full.txt +1739 -0
  95. package/docs/llms.txt +45 -0
  96. package/docs/playground.md +54 -0
  97. package/docs/reference/cli.md +35 -0
  98. package/docs/reference/diagnostics.md +619 -0
  99. package/docs/reference/grammar.md +39 -0
  100. package/docs/reference/std/advance.md +35 -0
  101. package/docs/reference/std/cancellable_booking.md +35 -0
  102. package/docs/reference/std/captured_payment.md +43 -0
  103. package/docs/reference/std/conditional_disbursement.md +33 -0
  104. package/docs/reference/std/credit_facility.md +35 -0
  105. package/docs/reference/std/held_payment.md +64 -0
  106. package/docs/reference/std/instant_transfer.md +30 -0
  107. package/docs/reference/std/metered.md +27 -0
  108. package/docs/reference/std/pooled_split.md +26 -0
  109. package/docs/reference/std/premium_forward.md +39 -0
  110. package/docs/reference/std/reconciled_payout.md +33 -0
  111. package/docs/reference/std/recurring_collection.md +25 -0
  112. package/docs/reference/std/rotating_pool.md +42 -0
  113. package/docs/reference/std/scheduled.md +51 -0
  114. package/docs/reference/std/security_deposit.md +46 -0
  115. package/docs/reference/std/settlement_batch.md +42 -0
  116. package/docs/reference/std/swap.md +46 -0
  117. package/docs/reference/std/threshold_pool.md +47 -0
  118. package/docs/reference/std/weighted_distribution.md +39 -0
  119. package/docs/reference/types.md +20 -0
  120. package/docs/reference/udl-output.md +13 -0
  121. package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
  122. package/docs/sessions/2026-09-02-two-instruments.md +41 -0
  123. package/examples/01-first-program/README.md +45 -0
  124. package/examples/01-first-program/tip-jar.hsx +17 -0
  125. package/examples/02-imports-and-archetypes/README.md +96 -0
  126. package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
  127. package/examples/03-diagnostics/README.md +68 -0
  128. package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
  129. package/examples/03-diagnostics/corner-shop.hsx +19 -0
  130. package/examples/04-complete-product/README.md +84 -0
  131. package/examples/04-complete-product/study-hall.hsx +67 -0
  132. package/examples/05-watch-club/README.md +9 -0
  133. package/examples/05-watch-club/watch-club.hsx +124 -0
  134. package/examples/README.md +18 -0
  135. package/examples/advance/README.md +3 -0
  136. package/examples/advance/advance.hsx +13 -0
  137. package/examples/advance/advance.udl +339 -0
  138. package/examples/cancellable_booking/README.md +3 -0
  139. package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
  140. package/examples/cancellable_booking/cancellable_booking.udl +391 -0
  141. package/examples/captured_payment/README.md +3 -0
  142. package/examples/captured_payment/captured_payment.hsx +21 -0
  143. package/examples/captured_payment/captured_payment.udl +677 -0
  144. package/examples/conditional_disbursement/README.md +3 -0
  145. package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
  146. package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
  147. package/examples/cost-table.json +386 -0
  148. package/examples/credit_facility/README.md +3 -0
  149. package/examples/credit_facility/credit_facility.hsx +27 -0
  150. package/examples/credit_facility/credit_facility.udl +1373 -0
  151. package/examples/held_payment/README.md +3 -0
  152. package/examples/held_payment/held_payment.hsx +13 -0
  153. package/examples/held_payment/held_payment.udl +507 -0
  154. package/examples/instant_transfer/README.md +3 -0
  155. package/examples/instant_transfer/instant_transfer.hsx +16 -0
  156. package/examples/instant_transfer/instant_transfer.udl +364 -0
  157. package/examples/metered/README.md +3 -0
  158. package/examples/metered/metered.hsx +13 -0
  159. package/examples/metered/metered.udl +247 -0
  160. package/examples/pooled_split/README.md +3 -0
  161. package/examples/pooled_split/pooled_split.hsx +15 -0
  162. package/examples/pooled_split/pooled_split.udl +425 -0
  163. package/examples/premium_forward/README.md +3 -0
  164. package/examples/premium_forward/premium_forward.hsx +19 -0
  165. package/examples/premium_forward/premium_forward.udl +594 -0
  166. package/examples/reconciled_payout/README.md +3 -0
  167. package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
  168. package/examples/reconciled_payout/reconciled_payout.udl +303 -0
  169. package/examples/recurring_collection/README.md +3 -0
  170. package/examples/recurring_collection/recurring_collection.hsx +21 -0
  171. package/examples/recurring_collection/recurring_collection.udl +1130 -0
  172. package/examples/rotating_pool/README.md +3 -0
  173. package/examples/rotating_pool/rotating_pool.hsx +18 -0
  174. package/examples/rotating_pool/rotating_pool.udl +4352 -0
  175. package/examples/scheduled/README.md +3 -0
  176. package/examples/scheduled/scheduled.hsx +12 -0
  177. package/examples/scheduled/scheduled.udl +335 -0
  178. package/examples/security_deposit/README.md +3 -0
  179. package/examples/security_deposit/security_deposit.hsx +21 -0
  180. package/examples/security_deposit/security_deposit.udl +293 -0
  181. package/examples/settlement_batch/README.md +3 -0
  182. package/examples/settlement_batch/settlement_batch.hsx +19 -0
  183. package/examples/settlement_batch/settlement_batch.udl +753 -0
  184. package/examples/swap/README.md +3 -0
  185. package/examples/swap/swap.hsx +19 -0
  186. package/examples/swap/swap.udl +876 -0
  187. package/examples/threshold_pool/README.md +3 -0
  188. package/examples/threshold_pool/threshold_pool.hsx +16 -0
  189. package/examples/threshold_pool/threshold_pool.udl +667 -0
  190. package/examples/weighted_distribution/README.md +3 -0
  191. package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
  192. package/examples/weighted_distribution/weighted_distribution.udl +337 -0
  193. package/package.json +27 -6
  194. package/skills/hsx/SKILL.md +483 -0
  195. package/src/ast.ts +193 -9
  196. package/src/cli.ts +191 -20
  197. package/src/compile.ts +160 -23
  198. package/src/cost.ts +579 -0
  199. package/src/diagnostics.ts +442 -0
  200. package/src/emit.ts +201 -0
  201. package/src/entry-overrides.ts +263 -48
  202. package/src/format.ts +261 -0
  203. package/src/index.ts +34 -4
  204. package/src/ir.ts +99 -0
  205. package/src/lex.ts +59 -4
  206. package/src/limits.ts +2 -3
  207. package/src/lsp/server.ts +460 -0
  208. package/src/modules.ts +446 -0
  209. package/src/parse.ts +655 -21
  210. package/src/std-bundle.ts +91 -0
  211. package/src/std-library.ts +12 -0
  212. package/src/typecheck.ts +3876 -0
  213. package/src/version.ts +4 -16
  214. package/std/SEMANTICS.md +130 -0
  215. package/std/settlements/advance.hsx +177 -0
  216. package/std/settlements/cancellable_booking.hsx +87 -0
  217. package/std/settlements/captured_payment.hsx +144 -0
  218. package/std/settlements/conditional_disbursement.hsx +185 -0
  219. package/std/settlements/credit_facility.hsx +167 -0
  220. package/std/settlements/held_payment.hsx +578 -0
  221. package/std/settlements/index.hsx +3 -0
  222. package/std/settlements/instant_transfer.hsx +190 -0
  223. package/std/settlements/metered.hsx +67 -0
  224. package/std/settlements/pooled_split.hsx +68 -0
  225. package/std/settlements/premium_forward.hsx +257 -0
  226. package/std/settlements/reconciled_payout.hsx +77 -0
  227. package/std/settlements/recurring_collection.hsx +27 -0
  228. package/std/settlements/rotating_pool.hsx +252 -0
  229. package/std/settlements/scheduled.hsx +641 -0
  230. package/std/settlements/security_deposit.hsx +166 -0
  231. package/std/settlements/settlement_batch.hsx +122 -0
  232. package/std/settlements/swap.hsx +1114 -0
  233. package/std/settlements/threshold_pool.hsx +221 -0
  234. package/std/settlements/weighted_distribution.hsx +141 -0
  235. package/dist/src/archetypes.d.ts +0 -17
  236. package/dist/src/archetypes.d.ts.map +0 -1
  237. package/dist/src/archetypes.js +0 -327
  238. package/dist/src/archetypes.js.map +0 -1
  239. package/dist/src/check.d.ts +0 -12
  240. package/dist/src/check.d.ts.map +0 -1
  241. package/dist/src/check.js +0 -1910
  242. package/dist/src/check.js.map +0 -1
  243. package/dist/src/lower.d.ts +0 -146
  244. package/dist/src/lower.d.ts.map +0 -1
  245. package/dist/src/lower.js +0 -4570
  246. package/dist/src/lower.js.map +0 -1
  247. package/dist/src/model.d.ts +0 -467
  248. package/dist/src/model.d.ts.map +0 -1
  249. package/dist/src/model.js +0 -15
  250. package/dist/src/model.js.map +0 -1
  251. package/spec/hsx-ir.schema.json +0 -920
  252. package/src/archetypes.ts +0 -340
  253. package/src/check.ts +0 -2938
  254. package/src/lower.ts +0 -5416
  255. package/src/model.ts +0 -519
package/src/ast.ts CHANGED
@@ -35,6 +35,36 @@ export function lineColAt(source: string, offset: number): LineCol {
35
35
  return { column: clamped - lineStart + 1, line };
36
36
  }
37
37
 
38
+ const utf8Encoder = new TextEncoder();
39
+
40
+ /**
41
+ * Convert a UTF-8 byte offset to a UTF-16 code-unit offset over a string.
42
+ * Walks Unicode code points, counting UTF-8 bytes and UTF-16 code units.
43
+ * Clamps to [0, source.length].
44
+ */
45
+ export function byteOffsetToCodeUnit(
46
+ source: string,
47
+ byteOffset: number,
48
+ ): number {
49
+ if (byteOffset <= 0) return 0;
50
+ let currentByte = 0;
51
+ let codeUnit = 0;
52
+
53
+ for (const char of source) {
54
+ const charBytes = utf8Encoder.encode(char).length;
55
+ if (currentByte + charBytes > byteOffset) {
56
+ return codeUnit;
57
+ }
58
+ currentByte += charBytes;
59
+ codeUnit += char.length;
60
+ if (currentByte === byteOffset) {
61
+ return codeUnit;
62
+ }
63
+ }
64
+
65
+ return source.length;
66
+ }
67
+
38
68
  /**
39
69
  * One source scanned once, so many offsets resolve without rescanning it.
40
70
  * `lineColAt` walks from offset zero every time, which turns a run of d
@@ -78,6 +108,8 @@ export function lineColIn(index: LineIndex, offset: number): LineCol {
78
108
  export interface IdentExpr {
79
109
  readonly kind: "ident";
80
110
  readonly name: string;
111
+ /** A literal quoted block key keeps its exact spelling during JSON lowering. */
112
+ readonly quoted?: boolean;
81
113
  readonly span: Span;
82
114
  }
83
115
 
@@ -87,13 +119,51 @@ export interface StringExpr {
87
119
  readonly value: string;
88
120
  }
89
121
 
90
- interface NumberExpr {
122
+ export interface NumberExpr {
91
123
  readonly kind: "number";
92
124
  /** The literal exactly as written, e.g. "99.5". Interpretation is typed later. */
93
125
  readonly raw: string;
94
126
  readonly span: Span;
95
127
  }
96
128
 
129
+ export interface BooleanExpr {
130
+ readonly kind: "boolean";
131
+ readonly span: Span;
132
+ readonly value: boolean;
133
+ }
134
+
135
+ /** A currency-indexed literal. The checker converts it to integer minor units. */
136
+ export interface MoneyExpr {
137
+ readonly currency: IdentExpr;
138
+ readonly kind: "money";
139
+ readonly raw: string;
140
+ readonly span: Span;
141
+ }
142
+
143
+ /** A dotted name used for modules, UDL operations, and field paths. */
144
+ export interface PathExpr {
145
+ readonly kind: "path";
146
+ readonly parts: readonly IdentExpr[];
147
+ readonly span: Span;
148
+ }
149
+
150
+ /** A nominal type application such as `money<SAR>` or `ref<invoice>`. */
151
+ export interface TypeApplyExpr {
152
+ readonly args: readonly Expr[];
153
+ readonly callee: IdentExpr;
154
+ readonly kind: "type_apply";
155
+ readonly span: Span;
156
+ }
157
+
158
+ /** A general instrument or constant application, with optional type arguments. */
159
+ export interface ApplyExpr {
160
+ readonly args: readonly Expr[];
161
+ readonly callee: PathExpr;
162
+ readonly kind: "apply";
163
+ readonly span: Span;
164
+ readonly typeArgs: readonly Expr[];
165
+ }
166
+
97
167
  export interface PercentExpr {
98
168
  /** Exact basis points: 99.5% is 9950. Percents never round. */
99
169
  readonly bps: number;
@@ -110,6 +180,16 @@ export interface CallExpr {
110
180
  readonly span: Span;
111
181
  }
112
182
 
183
+ /**
184
+ * An exit amount chosen by a decision port and bounded by stored money:
185
+ * `decided { field: damageAmount, bound: depositAmount, remainder: return }`.
186
+ */
187
+ export interface DecidedAmountExpr {
188
+ readonly body: BlockExpr;
189
+ readonly kind: "decided_amount";
190
+ readonly span: Span;
191
+ }
192
+
113
193
  /**
114
194
  * A reference to a decision port, optionally bounded (`port dispute within
115
195
  * P14D`) or carrying the date that decides when the port has not
@@ -155,7 +235,7 @@ export interface BlockExpr {
155
235
  * `amount: price: money(SAR)` binds the field name `price` to type
156
236
  * `money(SAR)`: the entry key is `amount`, the value is this binding.
157
237
  */
158
- interface BindingExpr {
238
+ export interface BindingExpr {
159
239
  readonly kind: "binding";
160
240
  readonly name: IdentExpr;
161
241
  readonly span: Span;
@@ -163,16 +243,22 @@ interface BindingExpr {
163
243
  }
164
244
 
165
245
  export type Expr =
246
+ | ApplyExpr
166
247
  | BindingExpr
167
248
  | BlockExpr
249
+ | BooleanExpr
168
250
  | CallExpr
251
+ | DecidedAmountExpr
169
252
  | IdentExpr
170
253
  | ListExpr
254
+ | MoneyExpr
171
255
  | NumberExpr
256
+ | PathExpr
172
257
  | PercentExpr
173
258
  | PortRefExpr
174
259
  | SettlementRefExpr
175
- | StringExpr;
260
+ | StringExpr
261
+ | TypeApplyExpr;
176
262
 
177
263
  // --- Entries ---------------------------------------------------------------
178
264
 
@@ -182,6 +268,11 @@ export type Expr =
182
268
  * and `on_cancel(funded) { ... }` (key + qualifiers + block value).
183
269
  */
184
270
  export interface Entry {
271
+ /** `for item in bound { ... }`, expanded before binding or UDL lowering. */
272
+ readonly iteration?: {
273
+ readonly binding: IdentExpr;
274
+ readonly bound: Expr;
275
+ };
185
276
  readonly key: IdentExpr;
186
277
  readonly qualifiers: readonly IdentExpr[];
187
278
  readonly span: Span;
@@ -198,7 +289,23 @@ export interface ProgramDecl {
198
289
  readonly title?: StringExpr;
199
290
  }
200
291
 
201
- /** `import { held_payment } from "settlement"`. */
292
+ /** `use held_settlement` selects one published catalog instrument. */
293
+ export interface UseDecl {
294
+ readonly instrument: IdentExpr;
295
+ readonly kind: "use";
296
+ readonly span: Span;
297
+ }
298
+
299
+ /** `expose held_settlement.release as releaseFunds` names a public action. */
300
+ export interface ExposeDecl {
301
+ readonly action: IdentExpr;
302
+ readonly instrument: IdentExpr;
303
+ readonly kind: "expose";
304
+ readonly publicName: IdentExpr;
305
+ readonly span: Span;
306
+ }
307
+
308
+ /** `import { held_payment } from "std/settlements"`. */
202
309
  export interface ImportDecl {
203
310
  readonly from: StringExpr;
204
311
  readonly kind: "import";
@@ -224,11 +331,10 @@ export interface AssetDecl {
224
331
  readonly span: Span;
225
332
  }
226
333
 
227
- /** `settlement sale = held_payment { ... }`, an archetype instantiation. */
228
- export interface SettlementDecl {
229
- readonly archetype: IdentExpr;
334
+ /** A full UDL subject-kind declaration. `asset` remains shorthand for this form. */
335
+ export interface SubjectDecl {
230
336
  readonly body: BlockExpr;
231
- readonly kind: "settlement";
337
+ readonly kind: "subject";
232
338
  readonly name: IdentExpr;
233
339
  readonly span: Span;
234
340
  }
@@ -241,13 +347,89 @@ export interface PortDecl {
241
347
  readonly span: Span;
242
348
  }
243
349
 
350
+ /** `module std.settlements` gives a file its importable module name. */
351
+ export interface ModuleDecl {
352
+ readonly kind: "module";
353
+ readonly name: PathExpr;
354
+ readonly span: Span;
355
+ }
356
+
357
+ export interface TypeParameter {
358
+ readonly name: IdentExpr;
359
+ readonly span: Span;
360
+ }
361
+
362
+ export interface Parameter {
363
+ readonly name: IdentExpr;
364
+ readonly span: Span;
365
+ readonly type: Expr;
366
+ }
367
+
368
+ /** The single general instrument definition form. */
369
+ export interface InstrumentDecl {
370
+ readonly body: BlockExpr;
371
+ /** Module-local declarations carried only while this template is bound. */
372
+ readonly declarationScope?: readonly ApplicationScopeDecl[];
373
+ readonly exported: boolean;
374
+ /** True when the source declared a parameter list, including an empty `()`. */
375
+ readonly hasParameterList: boolean;
376
+ readonly kind: "instrument";
377
+ readonly name: IdentExpr;
378
+ readonly parameters: readonly Parameter[];
379
+ readonly span: Span;
380
+ readonly typeParameters: readonly TypeParameter[];
381
+ }
382
+
383
+ /** Instantiation of an exported parameterized instrument. */
384
+ export interface InstrumentApplyDecl {
385
+ readonly application: ApplyExpr;
386
+ /** Module-local declarations carried only while this application is bound. */
387
+ readonly declarationScope?: readonly ApplicationScopeDecl[];
388
+ readonly exported: boolean;
389
+ readonly kind: "instrument_apply";
390
+ readonly metadata?: BlockExpr;
391
+ readonly name: IdentExpr;
392
+ readonly span: Span;
393
+ }
394
+
395
+ export interface TypeDecl {
396
+ readonly exported: boolean;
397
+ readonly kind: "type";
398
+ readonly name: IdentExpr;
399
+ readonly span: Span;
400
+ readonly value: Expr;
401
+ }
402
+
403
+ export interface ConstDecl {
404
+ readonly exported: boolean;
405
+ readonly kind: "const";
406
+ readonly name: IdentExpr;
407
+ readonly span: Span;
408
+ readonly type?: Expr;
409
+ readonly value: Expr;
410
+ }
411
+
412
+ export type ApplicationScopeDecl =
413
+ | ConstDecl
414
+ | InstrumentDecl
415
+ | PartyDecl
416
+ | PortDecl
417
+ | TypeDecl;
418
+
244
419
  export type Decl =
245
420
  | AssetDecl
421
+ | ConstDecl
422
+ | ExposeDecl
246
423
  | ImportDecl
424
+ | InstrumentApplyDecl
425
+ | InstrumentDecl
426
+ | ModuleDecl
247
427
  | PartyDecl
248
428
  | PortDecl
249
429
  | ProgramDecl
250
- | SettlementDecl;
430
+ | SubjectDecl
431
+ | TypeDecl
432
+ | UseDecl;
251
433
 
252
434
  // --- Program ---------------------------------------------------------------
253
435
 
@@ -263,6 +445,8 @@ export interface Program {
263
445
  * it never throws, it returns the best-effort tree plus these.
264
446
  */
265
447
  export interface Diagnostic {
448
+ readonly code?: string;
449
+ readonly fix?: string;
266
450
  readonly message: string;
267
451
  readonly span: Span;
268
452
  }
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,13 @@
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 { startLspServer } from "./lsp/server.ts";
19
+ import { HSX_TARGET_UDL_VERSION, HSX_VERSION } from "./version.ts";
15
20
 
16
21
  /** Filesystem and streams, injected so the CLI stays testable. */
17
22
  export interface Io {
@@ -19,6 +24,8 @@ export interface Io {
19
24
  readonly out: (line: string) => void;
20
25
  readonly readFile: (path: string) => Promise<string>;
21
26
  readonly writeFile: (path: string, contents: string) => Promise<void>;
27
+ readonly stdin?: NodeJS.ReadableStream;
28
+ readonly stdout?: NodeJS.WritableStream;
22
29
  }
23
30
 
24
31
  /**
@@ -29,20 +36,29 @@ const OK = 0;
29
36
  const REFUSED = 1;
30
37
  const USAGE = 2;
31
38
 
32
- const USAGE_TEXT = `hsx ${HSX_VERSION}, the HSX compiler
39
+ export const USAGE_TEXT = `hsx ${HSX_VERSION}, the HSX compiler
33
40
 
34
41
  Usage:
35
42
  hsx check <file.hsx> [--strict]
36
43
  hsx build <file.hsx> [--out <file.json>] [--strict]
44
+ hsx cost <file.hsx> [--json] [--out <file.json>] [--strict]
45
+ hsx explain <HSX####>
46
+ hsx format <file.hsx>
47
+ hsx lsp
37
48
  hsx --version
38
49
  hsx --help
39
50
 
40
51
  Commands:
41
52
  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.
53
+ build Compile and write canonical UDL as JSON.
54
+ cost Compile and print the version-pinned cost manifest as a table or JSON.
55
+ explain Print one diagnostic title, fix, and source example.
56
+ format Print the source in the one canonical HSX style.
57
+ lsp Run the language server over stdin and stdout.
43
58
 
44
59
  Options:
45
- --out <file> Write build output to this path instead of stdout.
60
+ --json Print the cost manifest as JSON instead of a table.
61
+ --out <file> Write build or cost JSON to this path instead of stdout.
46
62
  --strict Treat warning-severity diagnostics as failures.
47
63
 
48
64
  Exit codes:
@@ -58,10 +74,25 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
58
74
  return command === undefined ? USAGE : OK;
59
75
  }
60
76
  if (command === "--version" || command === "-v") {
61
- io.out(`${HSX_VERSION} (IR version ${HSX_IR_VERSION})`);
77
+ io.out(`${HSX_VERSION} (UDL version ${HSX_TARGET_UDL_VERSION})`);
62
78
  return OK;
63
79
  }
64
- if (command !== "check" && command !== "build") {
80
+ if (command === "explain") return explainDiagnostic(rest, io);
81
+ if (command === "lsp") {
82
+ const input = io.stdin ?? process.stdin;
83
+ const output = io.stdout ?? process.stdout;
84
+ return new Promise<number>((resolve) => {
85
+ startLspServer(input, output, {
86
+ onExit: resolve,
87
+ });
88
+ });
89
+ }
90
+ if (
91
+ command !== "check" &&
92
+ command !== "build" &&
93
+ command !== "cost" &&
94
+ command !== "format"
95
+ ) {
65
96
  io.err(`hsx: unknown command "${command}"`);
66
97
  io.err(USAGE_TEXT);
67
98
  return USAGE;
@@ -81,7 +112,26 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
81
112
  return USAGE;
82
113
  }
83
114
 
84
- const result = compile(source);
115
+ if (command === "format") {
116
+ const result = format(source);
117
+ if (!result.ok) {
118
+ for (const diagnostic of result.diagnostics) {
119
+ io.err(`${parsed.file}:1:1: error [parse] ${diagnostic.message}`);
120
+ }
121
+ return REFUSED;
122
+ }
123
+ io.out(result.formatted.trimEnd());
124
+ return OK;
125
+ }
126
+
127
+ let costTable: UdlCostTable;
128
+ try {
129
+ costTable = await readDefaultCostTable(io);
130
+ } catch (cause) {
131
+ io.err(`hsx: cannot read the packaged cost table: ${messageOf(cause)}`);
132
+ return USAGE;
133
+ }
134
+ const result = compile(source, { costTable });
85
135
  for (const line of diagnosticLines(parsed.file, result)) io.err(line);
86
136
 
87
137
  const refused =
@@ -93,14 +143,20 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
93
143
  }
94
144
 
95
145
  if (!result.artifacts) return REFUSED;
96
- const json = `${JSON.stringify(
97
- {
98
- document: result.artifacts.document,
99
- frame: result.artifacts.frame,
100
- },
101
- null,
102
- 2,
103
- )}\n`;
146
+ if (command === "cost") {
147
+ const json = `${JSON.stringify(result.artifacts.costManifest, null, 2)}\n`;
148
+ if (parsed.out !== undefined) {
149
+ const writeCode = await writeOutput(parsed.out, json, io);
150
+ return writeCode === OK && refused ? REFUSED : writeCode;
151
+ }
152
+ io.out(
153
+ parsed.json
154
+ ? json.trimEnd()
155
+ : renderCostTable(result.artifacts.costManifest),
156
+ );
157
+ return refused ? REFUSED : OK;
158
+ }
159
+ const json = `${JSON.stringify(result.artifacts.document, null, 2)}\n`;
104
160
  if (parsed.out === undefined) {
105
161
  io.out(json.trimEnd());
106
162
  } else {
@@ -116,26 +172,38 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
116
172
 
117
173
  interface Options {
118
174
  readonly file: string;
175
+ readonly json: boolean;
119
176
  readonly out?: string;
120
177
  readonly strict: boolean;
121
178
  }
122
179
 
123
180
  function parseOptions(
124
181
  args: readonly string[],
125
- command: "build" | "check",
182
+ command: "build" | "check" | "cost" | "format",
126
183
  ): Options | { readonly error: string } {
127
184
  let file: string | undefined;
185
+ let json = false;
128
186
  let out: string | undefined;
129
187
  let strict = false;
130
188
 
131
189
  for (let index = 0; index < args.length; index += 1) {
132
190
  const argument = args[index] as string;
133
191
  if (argument === "--strict") {
192
+ if (command === "format") {
193
+ return { error: "hsx format has no style options" };
194
+ }
134
195
  strict = true;
135
196
  continue;
136
197
  }
198
+ if (argument === "--json") {
199
+ if (command !== "cost") return { error: "--json belongs to hsx cost" };
200
+ json = true;
201
+ continue;
202
+ }
137
203
  if (argument === "--out") {
138
- if (command !== "build") return { error: "--out belongs to hsx build" };
204
+ if (command !== "build" && command !== "cost") {
205
+ return { error: "--out belongs to hsx build or hsx cost" };
206
+ }
139
207
  const value = args[index + 1];
140
208
  if (value === undefined || value.startsWith("--")) {
141
209
  return { error: "--out needs a file path" };
@@ -154,7 +222,98 @@ function parseOptions(
154
222
  }
155
223
 
156
224
  if (file === undefined) return { error: `hsx ${command} needs a file` };
157
- return { file, ...(out === undefined ? {} : { out }), strict };
225
+ return { file, json, ...(out === undefined ? {} : { out }), strict };
226
+ }
227
+
228
+ function explainDiagnostic(args: readonly string[], io: Io): number {
229
+ if (args.length !== 1 || args[0]?.startsWith("-")) {
230
+ io.err("hsx: explain needs one diagnostic code");
231
+ return USAGE;
232
+ }
233
+ const diagnostic = hsxDiagnostics.find(({ code }) => code === args[0]);
234
+ if (!diagnostic) {
235
+ io.err(`hsx: unknown diagnostic code ${args[0]}`);
236
+ return USAGE;
237
+ }
238
+ io.out(
239
+ [
240
+ `${diagnostic.code} ${diagnostic.title}`,
241
+ `Stage: ${diagnostic.stage}`,
242
+ `Fix: ${diagnostic.fix}`,
243
+ diagnostic.example === null
244
+ ? "Example: unavailable from source alone"
245
+ : `Example:\n${diagnostic.example}`,
246
+ ].join("\n"),
247
+ );
248
+ return OK;
249
+ }
250
+
251
+ function renderCostTable(manifest: UdlCostManifest): string {
252
+ const rows = [
253
+ `costTableVersion\t${manifest.costTableVersion}`,
254
+ "instrument.action\teffect\tunit\tcount\ttotal\tpayer",
255
+ ];
256
+ for (const action of manifest.actions) {
257
+ for (const component of action.components) {
258
+ const fixed = BigInt(component.perEventMinor) * BigInt(component.count);
259
+ const unit = [
260
+ `${component.perEventMinor} ${manifest.currency} minor`,
261
+ component.bps > 0 ? `${component.bps} bps` : undefined,
262
+ ]
263
+ .filter((part): part is string => part !== undefined)
264
+ .join(" + ");
265
+ const total =
266
+ component.bps === 0
267
+ ? `${fixed} ${manifest.currency} minor`
268
+ : [
269
+ fixed === 0n ? undefined : `${fixed} ${manifest.currency} minor`,
270
+ `amount-dependent (${component.bps} bps)`,
271
+ ]
272
+ .filter((part): part is string => part !== undefined)
273
+ .join(" + ");
274
+ rows.push(
275
+ [
276
+ `${action.instrument}.${action.action}`,
277
+ component.signature,
278
+ unit,
279
+ component.count,
280
+ total,
281
+ component.payer,
282
+ ].join("\t"),
283
+ );
284
+ }
285
+ }
286
+ return rows.join("\n");
287
+ }
288
+
289
+ async function writeOutput(
290
+ path: string,
291
+ contents: string,
292
+ io: Io,
293
+ ): Promise<number> {
294
+ try {
295
+ await io.writeFile(path, contents);
296
+ } catch (cause) {
297
+ io.err(`hsx: cannot write ${path}: ${messageOf(cause)}`);
298
+ return USAGE;
299
+ }
300
+ return OK;
301
+ }
302
+
303
+ async function readDefaultCostTable(io: Io): Promise<UdlCostTable> {
304
+ const candidates = [
305
+ fileURLToPath(new URL("../examples/cost-table.json", import.meta.url)),
306
+ fileURLToPath(new URL("../../examples/cost-table.json", import.meta.url)),
307
+ ];
308
+ let lastError: unknown;
309
+ for (const path of candidates) {
310
+ try {
311
+ return JSON.parse(await io.readFile(path)) as UdlCostTable;
312
+ } catch (cause) {
313
+ lastError = cause;
314
+ }
315
+ }
316
+ throw lastError;
158
317
  }
159
318
 
160
319
  /** `file:line:col: severity [stage] message`, the shape editors already parse. */
@@ -164,10 +323,22 @@ function diagnosticLines(
164
323
  ): readonly string[] {
165
324
  return result.diagnostics.map(
166
325
  (diagnostic) =>
167
- `${file}:${diagnostic.line}:${diagnostic.column}: ${diagnostic.severity} [${diagnostic.stage}] ${diagnostic.message}`,
326
+ `${diagnostic.file ?? file}:${diagnostic.line}:${diagnostic.column}: ${diagnostic.severity} [${diagnostic.stage}] ${diagnostic.message}`,
168
327
  );
169
328
  }
170
329
 
171
330
  function messageOf(cause: unknown): string {
172
331
  return cause instanceof Error ? cause.message : String(cause);
173
332
  }
333
+
334
+ if (import.meta.main) {
335
+ const { readFile, writeFile } = await import("node:fs/promises");
336
+ process.exitCode = await runCli(process.argv.slice(2), {
337
+ err: (line) => process.stderr.write(`${line}\n`),
338
+ out: (line) => process.stdout.write(`${line}\n`),
339
+ readFile: (path) => readFile(path, "utf8"),
340
+ writeFile: (path, contents) => writeFile(path, contents, "utf8"),
341
+ stdin: process.stdin,
342
+ stdout: process.stdout,
343
+ });
344
+ }