@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
@@ -1,19 +1,35 @@
1
1
  /**
2
2
  * Data-driven replacement of existing settlement entries. This is not syntax:
3
3
  * callers select a slot that already exists in the parsed program, replace its
4
- * literal value, then run the ordinary checker and lowerer again.
4
+ * literal value, then run the ordinary general compiler again.
5
5
  */
6
6
 
7
- import type { BlockExpr, Entry, Expr, Program, SettlementDecl } from "./ast.ts";
7
+ import type {
8
+ BlockExpr,
9
+ Entry,
10
+ Expr,
11
+ InstrumentApplyDecl,
12
+ Program,
13
+ } from "./ast.ts";
8
14
 
9
15
  export type ProgramEntryOverrideValue =
10
16
  | { readonly kind: "integer"; readonly value: number }
11
- | { readonly kind: "basis_points"; readonly value: number };
17
+ | { readonly kind: "basis_points"; readonly value: number }
18
+ | {
19
+ readonly currency: string;
20
+ readonly kind: "money";
21
+ readonly value: number;
22
+ }
23
+ | { readonly kind: "duration"; readonly value: string };
12
24
 
13
- export interface ProgramEntryOverrideBounds {
14
- readonly max: number;
15
- readonly min: number;
16
- }
25
+ export type ProgramEntryOverrideBounds =
26
+ | { readonly max: number; readonly min: number }
27
+ | {
28
+ readonly currency: string;
29
+ readonly max: number;
30
+ readonly min: number;
31
+ }
32
+ | { readonly max: string; readonly min: string };
17
33
 
18
34
  export interface ProgramEntryOverride {
19
35
  readonly bounds: ProgramEntryOverrideBounds;
@@ -52,49 +68,151 @@ function percentRaw(bps: number): string {
52
68
  : `${whole}.${String(fraction).padStart(2, "0").replace(/0$/, "")}`;
53
69
  }
54
70
 
71
+ function durationMagnitude(
72
+ value: string,
73
+ ): { readonly amount: number; readonly family: "days" | "months" } | undefined {
74
+ const match = /^P([1-9][0-9]{0,3})([DWMY])$/.exec(value);
75
+ if (!match) return undefined;
76
+ const amount = Number(match[1]);
77
+ switch (match[2]) {
78
+ case "D":
79
+ return { amount, family: "days" };
80
+ case "W":
81
+ return { amount: amount * 7, family: "days" };
82
+ case "M":
83
+ return { amount, family: "months" };
84
+ case "Y":
85
+ return { amount: amount * 12, family: "months" };
86
+ default:
87
+ return undefined;
88
+ }
89
+ }
90
+
55
91
  function replacementExpr(
56
92
  current: Expr,
57
93
  value: ProgramEntryOverrideValue,
58
- ): Expr {
94
+ ): { readonly expr?: Expr; readonly issue?: string } {
59
95
  if (value.kind === "integer") {
60
- return { kind: "number", raw: String(value.value), span: current.span };
96
+ return current.kind === "number"
97
+ ? {
98
+ expr: {
99
+ kind: "number",
100
+ raw: String(value.value),
101
+ span: current.span,
102
+ },
103
+ }
104
+ : { issue: "target is not an integer literal" };
105
+ }
106
+ if (value.kind === "basis_points") {
107
+ return current.kind === "percent"
108
+ ? {
109
+ expr: {
110
+ bps: value.value,
111
+ kind: "percent",
112
+ raw: percentRaw(value.value),
113
+ span: current.span,
114
+ },
115
+ }
116
+ : { issue: "target is not a percentage literal" };
117
+ }
118
+ if (value.kind === "duration") {
119
+ return current.kind === "ident" && durationMagnitude(current.name)
120
+ ? {
121
+ expr: { kind: "ident", name: value.value, span: current.span },
122
+ }
123
+ : { issue: "target is not an ISO-8601 duration literal" };
124
+ }
125
+ if (
126
+ current.kind !== "binding" ||
127
+ current.type.kind !== "call" ||
128
+ current.type.callee.name !== "money" ||
129
+ current.type.args.length !== 2 ||
130
+ current.type.args[0]?.kind !== "ident" ||
131
+ current.type.args[1]?.kind !== "number"
132
+ ) {
133
+ return { issue: "target is not a fixed money literal" };
134
+ }
135
+ if (current.type.args[0].name !== value.currency) {
136
+ return {
137
+ issue: `target currency ${current.type.args[0].name} does not match ${value.currency}`,
138
+ };
61
139
  }
62
140
  return {
63
- bps: value.value,
64
- kind: "percent",
65
- raw: percentRaw(value.value),
66
- span: current.span,
141
+ expr: {
142
+ ...current,
143
+ type: {
144
+ ...current.type,
145
+ args: [
146
+ current.type.args[0],
147
+ {
148
+ kind: "number",
149
+ raw: String(value.value),
150
+ span: current.type.args[1].span,
151
+ },
152
+ ],
153
+ },
154
+ },
67
155
  };
68
156
  }
69
157
 
70
- function replaceEntry(
158
+ function selectedEntry(
71
159
  block: BlockExpr,
72
160
  path: readonly string[],
73
- value: ProgramEntryOverrideValue,
74
- ): { readonly block?: BlockExpr; readonly issue?: string } {
161
+ ):
162
+ | {
163
+ readonly match: { readonly entry: Entry; readonly index: number };
164
+ readonly tail: readonly string[];
165
+ }
166
+ | { readonly issue: string } {
75
167
  const [head, ...tail] = path;
76
168
  const matches = block.entries
77
169
  .map((entry, index) => ({ entry, index }))
78
170
  .filter(({ entry }) => entry.key.name === head);
79
171
  if (matches.length === 0) return { issue: `entry ${head} does not exist` };
80
- if (matches.length > 1) return { issue: `entry ${head} is ambiguous` };
172
+ if (matches.length === 1) return { match: matches[0]!, tail };
173
+ const occurrence = Number(tail[0]);
174
+ if (!Number.isInteger(occurrence) || occurrence < 0) {
175
+ return { issue: `entry ${head} is ambiguous` };
176
+ }
177
+ const match = matches[occurrence];
178
+ return match
179
+ ? { match, tail: tail.slice(1) }
180
+ : { issue: `entry ${head} occurrence ${occurrence} does not exist` };
181
+ }
81
182
 
82
- const match = matches[0] as { readonly entry: Entry; readonly index: number };
183
+ function replaceEntry(
184
+ block: BlockExpr,
185
+ path: readonly string[],
186
+ value: ProgramEntryOverrideValue,
187
+ ): { readonly block?: BlockExpr; readonly issue?: string } {
188
+ const selected = selectedEntry(block, path);
189
+ if (!("match" in selected)) return selected;
190
+ const { match, tail } = selected;
83
191
  let nextEntry: Entry;
84
192
  if (tail.length === 0) {
85
- if (
86
- match.entry.value.kind !== "number" &&
87
- match.entry.value.kind !== "percent"
88
- ) {
89
- return { issue: `entry ${head} is not a numeric literal` };
193
+ const replacement = replacementExpr(match.entry.value, value);
194
+ if (!replacement.expr) return replacement;
195
+ nextEntry = {
196
+ ...match.entry,
197
+ value: replacement.expr,
198
+ };
199
+ } else if (
200
+ tail.length === 1 &&
201
+ tail[0] === "within" &&
202
+ match.entry.value.kind === "port_ref" &&
203
+ match.entry.value.within
204
+ ) {
205
+ const replacement = replacementExpr(match.entry.value.within, value);
206
+ if (!replacement.expr || replacement.expr.kind !== "ident") {
207
+ return replacement;
90
208
  }
91
209
  nextEntry = {
92
210
  ...match.entry,
93
- value: replacementExpr(match.entry.value, value),
211
+ value: { ...match.entry.value, within: replacement.expr },
94
212
  };
95
213
  } else {
96
214
  if (match.entry.value.kind !== "block") {
97
- return { issue: `entry ${head} is not a block` };
215
+ return { issue: `entry ${match.entry.key.name} is not a block` };
98
216
  }
99
217
  const nested = replaceEntry(match.entry.value, tail, value);
100
218
  if (!nested.block) return nested;
@@ -114,17 +232,80 @@ function replaceEntry(
114
232
  function settlementByName(
115
233
  program: Program,
116
234
  name: string,
117
- ): SettlementDecl | undefined {
235
+ ): InstrumentApplyDecl | undefined {
118
236
  return program.decls.find(
119
- (decl): decl is SettlementDecl =>
120
- decl.kind === "settlement" && decl.name.name === name,
237
+ (decl): decl is InstrumentApplyDecl =>
238
+ decl.kind === "instrument_apply" && decl.name.name === name,
121
239
  );
122
240
  }
123
241
 
242
+ function applicationBody(settlement: InstrumentApplyDecl): BlockExpr {
243
+ return {
244
+ entries: settlement.application.args.flatMap((argument) =>
245
+ argument.kind === "binding"
246
+ ? [
247
+ {
248
+ key: argument.name,
249
+ qualifiers: [],
250
+ span: argument.span,
251
+ value: argument.type,
252
+ },
253
+ ]
254
+ : [],
255
+ ),
256
+ kind: "block",
257
+ span: settlement.application.span,
258
+ };
259
+ }
260
+
124
261
  function validateOverride(
125
262
  override: ProgramEntryOverride,
126
263
  ): ProgramEntryOverrideIssue | undefined {
127
264
  const { max, min } = override.bounds;
265
+ if (override.value.kind === "duration") {
266
+ if (typeof min !== "string" || typeof max !== "string") {
267
+ return issue(
268
+ override,
269
+ "invalid_bounds",
270
+ "duration bounds must be ISO-8601 durations",
271
+ );
272
+ }
273
+ const minDuration = durationMagnitude(min);
274
+ const maxDuration = durationMagnitude(max);
275
+ if (
276
+ !minDuration ||
277
+ !maxDuration ||
278
+ minDuration.family !== maxDuration.family ||
279
+ minDuration.amount > maxDuration.amount
280
+ ) {
281
+ return issue(
282
+ override,
283
+ "invalid_bounds",
284
+ `duration bounds ${min}..${max} must be valid, comparable, and ordered`,
285
+ );
286
+ }
287
+ const value = durationMagnitude(override.value.value);
288
+ if (
289
+ !value ||
290
+ value.family !== minDuration.family ||
291
+ value.amount < minDuration.amount ||
292
+ value.amount > maxDuration.amount
293
+ ) {
294
+ return issue(
295
+ override,
296
+ "invalid_value",
297
+ `override value ${override.value.value} is outside ${min}..${max}`,
298
+ );
299
+ }
300
+ return undefined;
301
+ }
302
+ if (typeof min !== "number" || typeof max !== "number") {
303
+ return issue(
304
+ override,
305
+ "invalid_bounds",
306
+ "numeric bounds must be finite nonnegative integers",
307
+ );
308
+ }
128
309
  if (
129
310
  !Number.isFinite(min) ||
130
311
  !Number.isInteger(min) ||
@@ -133,33 +314,54 @@ function validateOverride(
133
314
  !Number.isInteger(max) ||
134
315
  max < min
135
316
  ) {
136
- return {
137
- code: "invalid_bounds",
138
- message: `override bounds ${min}..${max} must be finite nonnegative integers with min at most max`,
139
- path: override.path,
140
- settlement: override.settlement,
141
- };
317
+ return issue(
318
+ override,
319
+ "invalid_bounds",
320
+ `override bounds ${min}..${max} must be finite nonnegative integers with min at most max`,
321
+ );
142
322
  }
143
323
 
144
324
  const { value } = override.value;
145
325
  if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
146
- return {
147
- code: "invalid_value",
148
- message: `override value ${value} must be a finite nonnegative integer`,
149
- path: override.path,
150
- settlement: override.settlement,
151
- };
326
+ return issue(
327
+ override,
328
+ "invalid_value",
329
+ `override value ${value} must be a finite nonnegative integer`,
330
+ );
152
331
  }
153
332
  if (value < min || value > max) {
154
- return {
155
- code: "invalid_value",
156
- message: `override value ${value} is outside ${min}..${max}`,
157
- path: override.path,
158
- settlement: override.settlement,
159
- };
333
+ return issue(
334
+ override,
335
+ "invalid_value",
336
+ `override value ${value} is outside ${min}..${max}`,
337
+ );
338
+ }
339
+ if (override.value.kind === "money") {
340
+ const boundsCurrency =
341
+ "currency" in override.bounds ? override.bounds.currency : undefined;
342
+ if (!boundsCurrency || boundsCurrency !== override.value.currency) {
343
+ return issue(
344
+ override,
345
+ "invalid_bounds",
346
+ `money bounds must use currency ${override.value.currency}`,
347
+ );
348
+ }
160
349
  }
161
350
  }
162
351
 
352
+ function issue(
353
+ override: ProgramEntryOverride,
354
+ code: ProgramEntryOverrideIssue["code"],
355
+ message: string,
356
+ ): ProgramEntryOverrideIssue {
357
+ return {
358
+ code,
359
+ message,
360
+ path: override.path,
361
+ settlement: override.settlement,
362
+ };
363
+ }
364
+
163
365
  /**
164
366
  * Applies every override atomically. A missing or non-literal slot returns
165
367
  * issues and no program, so callers never lower a partially configured shape.
@@ -187,7 +389,7 @@ export function overrideProgramEntries(
187
389
  continue;
188
390
  }
189
391
  const replaced = replaceEntry(
190
- settlement.body,
392
+ applicationBody(settlement),
191
393
  override.path,
192
394
  override.value,
193
395
  );
@@ -203,7 +405,20 @@ export function overrideProgramEntries(
203
405
  current = {
204
406
  ...current,
205
407
  decls: current.decls.map((decl) =>
206
- decl === settlement ? { ...settlement, body: replaced.block! } : decl,
408
+ decl === settlement
409
+ ? {
410
+ ...settlement,
411
+ application: {
412
+ ...settlement.application,
413
+ args: replaced.block!.entries.map((entry) => ({
414
+ kind: "binding" as const,
415
+ name: entry.key,
416
+ span: entry.span,
417
+ type: entry.value,
418
+ })),
419
+ },
420
+ }
421
+ : decl,
207
422
  ),
208
423
  };
209
424
  }
package/src/format.ts ADDED
@@ -0,0 +1,261 @@
1
+ import type { BlockExpr, Decl, Diagnostic, Entry, Expr, Span } from "./ast.ts";
2
+ import { lex, type CommentTrivia } from "./lex.ts";
3
+ import { parseProgram } from "./parse.ts";
4
+
5
+ export type FormatResult =
6
+ | { readonly formatted: string; readonly ok: true }
7
+ | { readonly diagnostics: readonly Diagnostic[]; readonly ok: false };
8
+
9
+ export function format(source: string): FormatResult {
10
+ const parsed = parseProgram(source);
11
+ if (parsed.diagnostics.length > 0) {
12
+ return { diagnostics: parsed.diagnostics, ok: false };
13
+ }
14
+
15
+ const context: FormatContext = { comments: lex(source).comments, source };
16
+ return {
17
+ formatted: `${renderSequence(
18
+ parsed.program.decls,
19
+ parsed.program.span,
20
+ 0,
21
+ (decl) => renderDecl(decl, context),
22
+ context,
23
+ )}\n`,
24
+ ok: true,
25
+ };
26
+ }
27
+
28
+ interface FormatContext {
29
+ readonly comments: readonly CommentTrivia[];
30
+ readonly source: string;
31
+ }
32
+
33
+ function renderDecl(decl: Decl, context: FormatContext): string {
34
+ switch (decl.kind) {
35
+ case "module":
36
+ return `module ${renderExpr(decl.name, 0, context)};`;
37
+ case "program":
38
+ return `program ${decl.name.name}${decl.title ? ` ${renderExpr(decl.title, 0, context)}` : ""};`;
39
+ case "use":
40
+ return `use ${decl.instrument.name};`;
41
+ case "expose":
42
+ return `expose ${decl.instrument.name}.${decl.action.name} as ${decl.publicName.name};`;
43
+ case "import":
44
+ return `import { ${decl.names.map((name) => name.name).join(", ")} } from ${renderExpr(decl.from, 0, context)};`;
45
+ case "party":
46
+ return renderBlockDecl(
47
+ `party ${decl.name.name}: ${decl.partyKind.name}`,
48
+ decl.attrs,
49
+ context,
50
+ );
51
+ case "asset":
52
+ return renderBlockDecl(
53
+ `asset ${decl.name.name}: ${decl.assetKind.name}`,
54
+ decl.attrs,
55
+ context,
56
+ );
57
+ case "subject":
58
+ return `subject ${decl.name.name} ${renderBlock(decl.body, 0, context)}`;
59
+ case "port":
60
+ return `port ${decl.name.name} ${renderBlock(decl.body, 0, context)}`;
61
+ case "instrument": {
62
+ const exported = decl.exported ? "export " : "";
63
+ const typeParameters =
64
+ decl.typeParameters.length > 0
65
+ ? `<${decl.typeParameters.map((parameter) => parameter.name.name).join(", ")}>`
66
+ : "";
67
+ const parameters = decl.hasParameterList
68
+ ? `(${decl.parameters
69
+ .map(
70
+ (parameter) =>
71
+ `${parameter.name.name}: ${renderExpr(parameter.type, 0)}`,
72
+ )
73
+ .join(", ")})`
74
+ : "";
75
+ return `${exported}instrument ${decl.name.name}${typeParameters}${parameters} ${renderBlock(decl.body, 0, context)}`;
76
+ }
77
+ case "instrument_apply":
78
+ return `${decl.exported ? "export " : ""}instrument ${decl.name.name} = ${renderExpr(decl.application, 0, context)}${decl.metadata ? ` ${renderBlock(decl.metadata, 0, context)}` : ";"}`;
79
+ case "type":
80
+ return `${decl.exported ? "export " : ""}type ${decl.name.name} = ${renderExpr(decl.value, 0, context)};`;
81
+ case "const": {
82
+ const type = decl.type ? `: ${renderExpr(decl.type, 0, context)}` : "";
83
+ return `${decl.exported ? "export " : ""}const ${decl.name.name}${type} = ${renderExpr(decl.value, 0, context)};`;
84
+ }
85
+ }
86
+ }
87
+
88
+ function renderBlockDecl(
89
+ header: string,
90
+ body: BlockExpr | undefined,
91
+ context: FormatContext,
92
+ ): string {
93
+ return body ? `${header} ${renderBlock(body, 0, context)}` : `${header};`;
94
+ }
95
+
96
+ function renderBlock(
97
+ block: BlockExpr,
98
+ indent: number,
99
+ context: FormatContext,
100
+ ): string {
101
+ const rows = renderSequence(
102
+ block.entries,
103
+ block.span,
104
+ indent + 1,
105
+ (entry) =>
106
+ `${spaces(indent + 1)}${renderEntry(entry, indent + 1, context)}`,
107
+ context,
108
+ );
109
+ if (rows.length === 0) return "{}";
110
+ return `{\n${rows}\n${spaces(indent)}}`;
111
+ }
112
+
113
+ function renderEntry(
114
+ entry: Entry,
115
+ indent: number,
116
+ context: FormatContext,
117
+ ): string {
118
+ const key = entry.key.name;
119
+ const identifierKey =
120
+ /^[A-Za-z][A-Za-z0-9_]*(?:\[[A-Za-z][A-Za-z0-9_]*\][A-Za-z0-9_]*)?$/.test(
121
+ key,
122
+ );
123
+ const renderedKey =
124
+ !entry.key.quoted && identifierKey ? key : JSON.stringify(key);
125
+ if (entry.iteration && entry.value.kind === "block") {
126
+ return `for ${entry.iteration.binding.name} in ${renderExpr(entry.iteration.bound, indent, context)} ${renderBlock(entry.value, indent, context)}`;
127
+ }
128
+ if (key === "action" && entry.value.kind === "block") {
129
+ return `action ${entry.qualifiers[0]?.name ?? ""} ${renderBlock(entry.value, indent, context)}`;
130
+ }
131
+ if (key === "states" && entry.value.kind === "list") {
132
+ return `states ${entry.value.items.map((item) => renderExpr(item, indent, context)).join(" ")};`;
133
+ }
134
+ if (key === "initial") {
135
+ return `initial ${renderExpr(entry.value, indent, context)};`;
136
+ }
137
+ if (key === "parked") {
138
+ return `parked ${entry.qualifiers[0]?.name ?? ""} reason ${renderExpr(entry.value, indent, context)};`;
139
+ }
140
+ if (key === "on") {
141
+ const [action, ...states] = entry.qualifiers;
142
+ return `on ${action?.name ?? ""}: ${states.map((state) => state.name).join(" | ")} -> ${renderExpr(entry.value, indent, context)};`;
143
+ }
144
+ if (key === "notify") {
145
+ return `notify ${entry.qualifiers[0]?.name ?? ""} via ${entry.qualifiers[1]?.name ?? ""};`;
146
+ }
147
+
148
+ const qualifier = renderQualifier(key, entry);
149
+ if (entry.value.kind === "block") {
150
+ return `${renderedKey}${qualifier} ${renderBlock(entry.value, indent, context)}`;
151
+ }
152
+ return `${renderedKey}${qualifier}: ${renderExpr(entry.value, indent, context)};`;
153
+ }
154
+
155
+ function renderQualifier(key: string, entry: Entry): string {
156
+ if (entry.qualifiers.length === 0) return "";
157
+ const names = entry.qualifiers.map((qualifier) => qualifier.name);
158
+ return key === "requires" || key === "computes"
159
+ ? ` ${names.join(" ")}`
160
+ : `(${names.join(", ")})`;
161
+ }
162
+
163
+ function renderExpr(
164
+ expr: Expr,
165
+ indent: number,
166
+ context?: FormatContext,
167
+ ): string {
168
+ switch (expr.kind) {
169
+ case "ident":
170
+ return expr.name;
171
+ case "string":
172
+ return `"${expr.value}"`;
173
+ case "number":
174
+ return expr.raw;
175
+ case "boolean":
176
+ return String(expr.value);
177
+ case "money":
178
+ return `${expr.currency.name} ${expr.raw}`;
179
+ case "percent":
180
+ return `${expr.raw}%`;
181
+ case "path":
182
+ return expr.parts.map((part) => part.name).join(".");
183
+ case "settlement_ref":
184
+ return `${expr.owner.name}.${expr.member.name}`;
185
+ case "type_apply":
186
+ return `${expr.callee.name}<${expr.args.map((arg) => renderExpr(arg, indent, context)).join(", ")}>`;
187
+ case "call":
188
+ return `${expr.callee.name}(${expr.args.map((arg) => renderExpr(arg, indent, context)).join(", ")})`;
189
+ case "apply": {
190
+ const typeArgs =
191
+ expr.typeArgs.length > 0
192
+ ? `<${expr.typeArgs.map((arg) => renderExpr(arg, indent, context)).join(", ")}>`
193
+ : "";
194
+ return `${renderExpr(expr.callee, indent, context)}${typeArgs}(${expr.args
195
+ .map((arg) => renderExpr(arg, indent, context))
196
+ .join(", ")})`;
197
+ }
198
+ case "binding":
199
+ return `${expr.name.name}: ${renderExpr(expr.type, indent, context)}`;
200
+ case "list":
201
+ return `[${expr.items.map((item) => renderExpr(item, indent, context)).join(", ")}]`;
202
+ case "block":
203
+ return renderBlock(expr, indent, context as FormatContext);
204
+ case "decided_amount":
205
+ return `decided ${renderBlock(expr.body, indent, context as FormatContext)}`;
206
+ case "port_ref": {
207
+ const within = expr.within ? ` within ${expr.within.name}` : "";
208
+ const deadline = expr.deadline ? ` | at(${expr.deadline.name})` : "";
209
+ return `port ${expr.name.name}${within}${deadline}`;
210
+ }
211
+ }
212
+ }
213
+
214
+ function renderSequence<T extends { readonly span: Span }>(
215
+ children: readonly T[],
216
+ container: Span,
217
+ indent: number,
218
+ render: (child: T) => string,
219
+ context: FormatContext,
220
+ ): string {
221
+ const rows: string[] = [];
222
+ let cursor = container.start;
223
+ for (const child of children) {
224
+ appendComments(rows, cursor, child.span.start, indent, context);
225
+ rows.push(render(child));
226
+ cursor = child.span.end;
227
+ }
228
+ appendComments(rows, cursor, container.end, indent, context);
229
+ return rows.join("\n");
230
+ }
231
+
232
+ function appendComments(
233
+ rows: string[],
234
+ start: number,
235
+ end: number,
236
+ indent: number,
237
+ context: FormatContext,
238
+ ): void {
239
+ for (const comment of context.comments) {
240
+ if (comment.span.start < start || comment.span.end > end) continue;
241
+ const trailing =
242
+ rows.length > 0 &&
243
+ !context.source.slice(start, comment.span.start).includes("\n");
244
+ if (trailing) {
245
+ const last = rows.pop() as string;
246
+ const newline = last.lastIndexOf("\n");
247
+ rows.push(
248
+ newline < 0
249
+ ? `${last} ${comment.text}`
250
+ : `${last.slice(0, newline + 1)}${last.slice(newline + 1)} ${comment.text}`,
251
+ );
252
+ } else {
253
+ rows.push(`${spaces(indent)}${comment.text}`);
254
+ }
255
+ start = comment.span.end;
256
+ }
257
+ }
258
+
259
+ function spaces(indent: number): string {
260
+ return " ".repeat(indent);
261
+ }
package/src/index.ts CHANGED
@@ -1,11 +1,39 @@
1
- export { checkProgram } from "./check.ts";
2
- export { compile } from "./compile.ts";
1
+ export {
2
+ compile,
3
+ type CompileDiagnostic,
4
+ type CompileOptions,
5
+ type CompileOriginMapEntry,
6
+ type CompileResult,
7
+ } from "./compile.ts";
8
+ export {
9
+ buildUdlCostManifest,
10
+ computeUdlFixedCost,
11
+ evaluateUdlCostManifest,
12
+ type HsxEffectKind,
13
+ type UdlCostManifest,
14
+ type UdlCostTable,
15
+ } from "./cost.ts";
16
+ export {
17
+ hsxDiagnostics,
18
+ type HsxDiagnosticCatalogEntry,
19
+ } from "./diagnostics.ts";
20
+ export type { HsxCompilerHost, ModuleSource } from "./modules.ts";
21
+ export { format, type FormatResult } from "./format.ts";
22
+ export { KEYWORDS } from "./lex.ts";
3
23
  export {
4
24
  overrideProgramEntries,
5
25
  type ProgramEntryOverride,
6
26
  type ProgramEntryOverrideIssue,
7
27
  type ProgramEntryOverrideValue,
8
28
  } from "./entry-overrides.ts";
9
- export { lowerProgram, MONEY_EVENT_BUDGET } from "./lower.ts";
29
+ export { type OriginMapEntry } from "./emit.ts";
30
+ export { lowerGeneralProgram } from "./emit.ts";
10
31
  export { parseProgram } from "./parse.ts";
11
- export type { Program } from "./ast.ts";
32
+ export { checkGeneralProgram, type GeneralCheckOptions } from "./typecheck.ts";
33
+ export type {
34
+ BlockExpr,
35
+ ExposeDecl,
36
+ Program,
37
+ ProgramDecl,
38
+ UseDecl,
39
+ } from "./ast.ts";