@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.
- package/CHANGELOG.md +58 -9
- package/LICENSING.md +3 -3
- package/README.md +39 -149
- package/TRADEMARKS.md +4 -4
- package/bin/hsx.ts +2 -0
- package/dist/bin/hsx.js +2 -0
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/ast.d.ts +135 -10
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +24 -0
- package/dist/src/ast.js.map +1 -1
- package/dist/src/cli.d.ts +4 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +172 -16
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +42 -7
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +82 -16
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +78 -0
- package/dist/src/cost.d.ts.map +1 -0
- package/dist/src/cost.js +369 -0
- package/dist/src/cost.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +420 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/emit.d.ts +34 -0
- package/dist/src/emit.d.ts.map +1 -0
- package/dist/src/emit.js +137 -0
- package/dist/src/emit.js.map +1 -0
- package/dist/src/entry-overrides.d.ts +17 -3
- package/dist/src/entry-overrides.d.ts.map +1 -1
- package/dist/src/entry-overrides.js +180 -35
- package/dist/src/entry-overrides.js.map +1 -1
- package/dist/src/format.d.ts +10 -0
- package/dist/src/format.d.ts.map +1 -0
- package/dist/src/format.js +183 -0
- package/dist/src/format.js.map +1 -0
- package/dist/src/index.d.ts +11 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/ir.d.ts +66 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +15 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/lex.d.ts +9 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +50 -4
- package/dist/src/lex.js.map +1 -1
- package/dist/src/limits.d.ts +2 -3
- package/dist/src/limits.d.ts.map +1 -1
- package/dist/src/limits.js +2 -3
- package/dist/src/limits.js.map +1 -1
- package/dist/src/lsp/server.d.ts +32 -0
- package/dist/src/lsp/server.d.ts.map +1 -0
- package/dist/src/lsp/server.js +391 -0
- package/dist/src/lsp/server.js.map +1 -0
- package/dist/src/modules.d.ts +38 -0
- package/dist/src/modules.d.ts.map +1 -0
- package/dist/src/modules.js +368 -0
- package/dist/src/modules.js.map +1 -0
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +590 -17
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts +2 -0
- package/dist/src/std-bundle.d.ts.map +1 -0
- package/dist/src/std-bundle.js +88 -0
- package/dist/src/std-bundle.js.map +1 -0
- package/dist/src/std-library.d.ts +5 -0
- package/dist/src/std-library.d.ts.map +1 -0
- package/dist/src/std-library.js +9 -0
- package/dist/src/std-library.js.map +1 -0
- package/dist/src/typecheck.d.ts +8 -0
- package/dist/src/typecheck.d.ts.map +1 -0
- package/dist/src/typecheck.js +2958 -0
- package/dist/src/typecheck.js.map +1 -0
- package/dist/src/version.d.ts +4 -15
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +4 -15
- package/dist/src/version.js.map +1 -1
- package/docs/README.md +26 -0
- package/docs/guide/01-first-program.md +24 -0
- package/docs/guide/02-money.md +25 -0
- package/docs/guide/03-instruments.md +22 -0
- package/docs/guide/04-lifecycles.md +25 -0
- package/docs/guide/05-fees-and-splits.md +45 -0
- package/docs/guide/06-schedules.md +22 -0
- package/docs/guide/07-composition.md +46 -0
- package/docs/guide/08-writing-a-module.md +26 -0
- package/docs/guide/09-cost.md +24 -0
- package/docs/guide/10-diagnostics.md +27 -0
- package/docs/llms-full.txt +1739 -0
- package/docs/llms.txt +45 -0
- package/docs/playground.md +54 -0
- package/docs/reference/cli.md +35 -0
- package/docs/reference/diagnostics.md +619 -0
- package/docs/reference/grammar.md +39 -0
- package/docs/reference/std/advance.md +35 -0
- package/docs/reference/std/cancellable_booking.md +35 -0
- package/docs/reference/std/captured_payment.md +43 -0
- package/docs/reference/std/conditional_disbursement.md +33 -0
- package/docs/reference/std/credit_facility.md +35 -0
- package/docs/reference/std/held_payment.md +64 -0
- package/docs/reference/std/instant_transfer.md +30 -0
- package/docs/reference/std/metered.md +27 -0
- package/docs/reference/std/pooled_split.md +26 -0
- package/docs/reference/std/premium_forward.md +39 -0
- package/docs/reference/std/reconciled_payout.md +33 -0
- package/docs/reference/std/recurring_collection.md +25 -0
- package/docs/reference/std/rotating_pool.md +42 -0
- package/docs/reference/std/scheduled.md +51 -0
- package/docs/reference/std/security_deposit.md +46 -0
- package/docs/reference/std/settlement_batch.md +42 -0
- package/docs/reference/std/swap.md +46 -0
- package/docs/reference/std/threshold_pool.md +47 -0
- package/docs/reference/std/weighted_distribution.md +39 -0
- package/docs/reference/types.md +20 -0
- package/docs/reference/udl-output.md +13 -0
- package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
- package/docs/sessions/2026-09-02-two-instruments.md +41 -0
- package/examples/01-first-program/README.md +45 -0
- package/examples/01-first-program/tip-jar.hsx +17 -0
- package/examples/02-imports-and-archetypes/README.md +96 -0
- package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
- package/examples/03-diagnostics/README.md +68 -0
- package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
- package/examples/03-diagnostics/corner-shop.hsx +19 -0
- package/examples/04-complete-product/README.md +84 -0
- package/examples/04-complete-product/study-hall.hsx +67 -0
- package/examples/05-watch-club/README.md +9 -0
- package/examples/05-watch-club/watch-club.hsx +124 -0
- package/examples/README.md +18 -0
- package/examples/advance/README.md +3 -0
- package/examples/advance/advance.hsx +13 -0
- package/examples/advance/advance.udl +339 -0
- package/examples/cancellable_booking/README.md +3 -0
- package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
- package/examples/cancellable_booking/cancellable_booking.udl +391 -0
- package/examples/captured_payment/README.md +3 -0
- package/examples/captured_payment/captured_payment.hsx +21 -0
- package/examples/captured_payment/captured_payment.udl +677 -0
- package/examples/conditional_disbursement/README.md +3 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
- package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
- package/examples/cost-table.json +386 -0
- package/examples/credit_facility/README.md +3 -0
- package/examples/credit_facility/credit_facility.hsx +27 -0
- package/examples/credit_facility/credit_facility.udl +1373 -0
- package/examples/held_payment/README.md +3 -0
- package/examples/held_payment/held_payment.hsx +13 -0
- package/examples/held_payment/held_payment.udl +507 -0
- package/examples/instant_transfer/README.md +3 -0
- package/examples/instant_transfer/instant_transfer.hsx +16 -0
- package/examples/instant_transfer/instant_transfer.udl +364 -0
- package/examples/metered/README.md +3 -0
- package/examples/metered/metered.hsx +13 -0
- package/examples/metered/metered.udl +247 -0
- package/examples/pooled_split/README.md +3 -0
- package/examples/pooled_split/pooled_split.hsx +15 -0
- package/examples/pooled_split/pooled_split.udl +425 -0
- package/examples/premium_forward/README.md +3 -0
- package/examples/premium_forward/premium_forward.hsx +19 -0
- package/examples/premium_forward/premium_forward.udl +594 -0
- package/examples/reconciled_payout/README.md +3 -0
- package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
- package/examples/reconciled_payout/reconciled_payout.udl +303 -0
- package/examples/recurring_collection/README.md +3 -0
- package/examples/recurring_collection/recurring_collection.hsx +21 -0
- package/examples/recurring_collection/recurring_collection.udl +1130 -0
- package/examples/rotating_pool/README.md +3 -0
- package/examples/rotating_pool/rotating_pool.hsx +18 -0
- package/examples/rotating_pool/rotating_pool.udl +4352 -0
- package/examples/scheduled/README.md +3 -0
- package/examples/scheduled/scheduled.hsx +12 -0
- package/examples/scheduled/scheduled.udl +335 -0
- package/examples/security_deposit/README.md +3 -0
- package/examples/security_deposit/security_deposit.hsx +21 -0
- package/examples/security_deposit/security_deposit.udl +293 -0
- package/examples/settlement_batch/README.md +3 -0
- package/examples/settlement_batch/settlement_batch.hsx +19 -0
- package/examples/settlement_batch/settlement_batch.udl +753 -0
- package/examples/swap/README.md +3 -0
- package/examples/swap/swap.hsx +19 -0
- package/examples/swap/swap.udl +876 -0
- package/examples/threshold_pool/README.md +3 -0
- package/examples/threshold_pool/threshold_pool.hsx +16 -0
- package/examples/threshold_pool/threshold_pool.udl +667 -0
- package/examples/weighted_distribution/README.md +3 -0
- package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
- package/examples/weighted_distribution/weighted_distribution.udl +337 -0
- package/package.json +27 -6
- package/skills/hsx/SKILL.md +483 -0
- package/src/ast.ts +193 -9
- package/src/cli.ts +191 -20
- package/src/compile.ts +160 -23
- package/src/cost.ts +579 -0
- package/src/diagnostics.ts +442 -0
- package/src/emit.ts +201 -0
- package/src/entry-overrides.ts +263 -48
- package/src/format.ts +261 -0
- package/src/index.ts +34 -4
- package/src/ir.ts +99 -0
- package/src/lex.ts +59 -4
- package/src/limits.ts +2 -3
- package/src/lsp/server.ts +460 -0
- package/src/modules.ts +446 -0
- package/src/parse.ts +655 -21
- package/src/std-bundle.ts +91 -0
- package/src/std-library.ts +12 -0
- package/src/typecheck.ts +3876 -0
- package/src/version.ts +4 -16
- package/std/SEMANTICS.md +130 -0
- package/std/settlements/advance.hsx +177 -0
- package/std/settlements/cancellable_booking.hsx +87 -0
- package/std/settlements/captured_payment.hsx +144 -0
- package/std/settlements/conditional_disbursement.hsx +185 -0
- package/std/settlements/credit_facility.hsx +167 -0
- package/std/settlements/held_payment.hsx +578 -0
- package/std/settlements/index.hsx +3 -0
- package/std/settlements/instant_transfer.hsx +190 -0
- package/std/settlements/metered.hsx +67 -0
- package/std/settlements/pooled_split.hsx +68 -0
- package/std/settlements/premium_forward.hsx +257 -0
- package/std/settlements/reconciled_payout.hsx +77 -0
- package/std/settlements/recurring_collection.hsx +27 -0
- package/std/settlements/rotating_pool.hsx +252 -0
- package/std/settlements/scheduled.hsx +641 -0
- package/std/settlements/security_deposit.hsx +166 -0
- package/std/settlements/settlement_batch.hsx +122 -0
- package/std/settlements/swap.hsx +1114 -0
- package/std/settlements/threshold_pool.hsx +221 -0
- package/std/settlements/weighted_distribution.hsx +141 -0
- package/dist/src/archetypes.d.ts +0 -17
- package/dist/src/archetypes.d.ts.map +0 -1
- package/dist/src/archetypes.js +0 -327
- package/dist/src/archetypes.js.map +0 -1
- package/dist/src/check.d.ts +0 -12
- package/dist/src/check.d.ts.map +0 -1
- package/dist/src/check.js +0 -1910
- package/dist/src/check.js.map +0 -1
- package/dist/src/lower.d.ts +0 -146
- package/dist/src/lower.d.ts.map +0 -1
- package/dist/src/lower.js +0 -4570
- package/dist/src/lower.js.map +0 -1
- package/dist/src/model.d.ts +0 -467
- package/dist/src/model.d.ts.map +0 -1
- package/dist/src/model.js +0 -15
- package/dist/src/model.js.map +0 -1
- package/spec/hsx-ir.schema.json +0 -920
- package/src/archetypes.ts +0 -340
- package/src/check.ts +0 -2938
- package/src/lower.ts +0 -5416
- 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
|
-
/** `
|
|
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
|
-
/**
|
|
228
|
-
export interface
|
|
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: "
|
|
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
|
-
|
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
--
|
|
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} (
|
|
77
|
+
io.out(`${HSX_VERSION} (UDL version ${HSX_TARGET_UDL_VERSION})`);
|
|
62
78
|
return OK;
|
|
63
79
|
}
|
|
64
|
-
if (command
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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"
|
|
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
|
+
}
|