@hyperscale0/hsx 1.0.0 → 1.0.124
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 +5 -256
- package/LICENSE.md +356 -0
- package/NOTICE.md +8 -0
- package/README.md +44 -44
- package/bin/hsx.ts +0 -7
- package/dist/bin/hsx.d.ts +0 -4
- package/dist/bin/hsx.d.ts.map +1 -1
- package/dist/bin/hsx.js +0 -6
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/ast.d.ts +113 -308
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +7 -71
- package/dist/src/ast.js.map +1 -1
- package/dist/src/attachment-economics.d.ts +5 -0
- package/dist/src/attachment-economics.d.ts.map +1 -0
- package/dist/src/attachment-economics.js +55 -0
- package/dist/src/attachment-economics.js.map +1 -0
- package/dist/src/binding-contract.d.ts +22 -0
- package/dist/src/binding-contract.d.ts.map +1 -0
- package/dist/src/binding-contract.js +74 -0
- package/dist/src/binding-contract.js.map +1 -0
- package/dist/src/cli.d.ts +4 -19
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +89 -276
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +34 -66
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +2949 -117
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +8 -75
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +54 -364
- package/dist/src/cost.js.map +1 -1
- package/dist/src/diagnostics.d.ts +14 -9
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +39 -416
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/examples-bundle.d.ts +3 -0
- package/dist/src/examples-bundle.d.ts.map +1 -0
- package/dist/src/examples-bundle.js +18 -0
- package/dist/src/examples-bundle.js.map +1 -0
- package/dist/src/examples.d.ts +10 -0
- package/dist/src/examples.d.ts.map +1 -0
- package/dist/src/examples.js +2 -0
- package/dist/src/examples.js.map +1 -0
- package/dist/src/format.d.ts +8 -7
- package/dist/src/format.d.ts.map +1 -1
- package/dist/src/format.js +9 -175
- package/dist/src/format.js.map +1 -1
- package/dist/src/header-source.d.ts +3 -0
- package/dist/src/header-source.d.ts.map +1 -0
- package/dist/src/header-source.js +41 -0
- package/dist/src/header-source.js.map +1 -0
- package/dist/src/headers.d.ts +64 -0
- package/dist/src/headers.d.ts.map +1 -0
- package/dist/src/headers.js +167 -0
- package/dist/src/headers.js.map +1 -0
- package/dist/src/index.d.ts +7 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/keywords.d.ts +56 -0
- package/dist/src/keywords.d.ts.map +1 -0
- package/dist/src/keywords.js +133 -0
- package/dist/src/keywords.js.map +1 -0
- package/dist/src/language.d.ts +26 -0
- package/dist/src/language.d.ts.map +1 -0
- package/dist/src/language.js +440 -0
- package/dist/src/language.js.map +1 -0
- package/dist/src/lex.d.ts +13 -28
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +119 -145
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.d.ts +4 -17
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +484 -1036
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +11 -84
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/std-library.d.ts +1 -1
- package/dist/src/std-library.d.ts.map +1 -1
- package/dist/src/std-library.js +1 -5
- package/dist/src/std-library.js.map +1 -1
- package/dist/src/tunables.d.ts +7 -0
- package/dist/src/tunables.d.ts.map +1 -0
- package/dist/src/tunables.js +27 -0
- package/dist/src/tunables.js.map +1 -0
- package/docs/README.md +528 -26
- package/docs/assets/brand/hsx-horizontal-white.svg +1 -0
- package/docs/assets/brand/hsx-horizontal.svg +1 -0
- package/docs/assets/brand/hsx-stacked-white.svg +1 -0
- package/docs/assets/brand/hsx-stacked.svg +1 -0
- package/docs/assets/brand/manifest.json +30 -0
- package/docs/assets/hsx.svg +11 -0
- package/examples/cards.hsx +34 -0
- package/examples/collections.hsx +37 -0
- package/examples/cost-table.json +633 -385
- package/examples/escrow.hsx +24 -0
- package/examples/financing.hsx +32 -0
- package/examples/insurance.hsx +30 -0
- package/examples/lending.hsx +57 -0
- package/examples/library.hsx +15 -0
- package/examples/marketplace.hsx +32 -0
- package/examples/money.hsx +19 -0
- package/examples/rental-deposit.hsx +60 -0
- package/examples/repair-approval.hsx +44 -0
- package/examples/reporting.hsx +28 -0
- package/examples/savings.hsx +21 -0
- package/examples/serviced.hsx +25 -0
- package/examples/wallet.hsx +24 -0
- package/package.json +25 -23
- package/src/ast.ts +83 -440
- package/src/attachment-economics.ts +103 -0
- package/src/binding-contract.ts +104 -0
- package/src/cli.ts +95 -325
- package/src/compile.ts +4063 -209
- package/src/cost.ts +71 -574
- package/src/diagnostics.ts +57 -437
- package/src/examples-bundle.ts +19 -0
- package/src/examples.ts +9 -0
- package/src/format.ts +12 -256
- package/src/header-source.ts +54 -0
- package/src/headers.ts +195 -0
- package/src/index.ts +18 -33
- package/src/keywords.ts +134 -0
- package/src/language.ts +629 -0
- package/src/lex.ts +131 -192
- package/src/parse.ts +489 -1195
- package/src/std-bundle.ts +12 -88
- package/src/std-library.ts +2 -7
- package/src/tunables.ts +36 -0
- package/std/cards.hsx +85 -0
- package/std/collections.hsx +90 -0
- package/std/escrow.hsx +73 -0
- package/std/financing.hsx +683 -0
- package/std/insurance.hsx +89 -0
- package/std/lending.hsx +138 -0
- package/std/marketplace.hsx +123 -0
- package/std/money.hsx +173 -0
- package/std/reporting.hsx +753 -0
- package/std/savings.hsx +68 -0
- package/std/wallet.hsx +57 -0
- package/LICENSE +0 -661
- package/LICENSING.md +0 -52
- package/TRADEMARKS.md +0 -35
- package/dist/src/emit.d.ts +0 -34
- package/dist/src/emit.d.ts.map +0 -1
- package/dist/src/emit.js +0 -137
- package/dist/src/emit.js.map +0 -1
- package/dist/src/entry-overrides.d.ts +0 -58
- package/dist/src/entry-overrides.d.ts.map +0 -1
- package/dist/src/entry-overrides.js +0 -284
- package/dist/src/entry-overrides.js.map +0 -1
- package/dist/src/ir.d.ts +0 -66
- package/dist/src/ir.d.ts.map +0 -1
- package/dist/src/ir.js +0 -15
- package/dist/src/ir.js.map +0 -1
- package/dist/src/limits.d.ts +0 -21
- package/dist/src/limits.d.ts.map +0 -1
- package/dist/src/limits.js +0 -21
- package/dist/src/limits.js.map +0 -1
- package/dist/src/lsp/server.d.ts +0 -32
- package/dist/src/lsp/server.d.ts.map +0 -1
- package/dist/src/lsp/server.js +0 -391
- package/dist/src/lsp/server.js.map +0 -1
- package/dist/src/modules.d.ts +0 -38
- package/dist/src/modules.d.ts.map +0 -1
- package/dist/src/modules.js +0 -368
- package/dist/src/modules.js.map +0 -1
- package/dist/src/typecheck.d.ts +0 -8
- package/dist/src/typecheck.d.ts.map +0 -1
- package/dist/src/typecheck.js +0 -2958
- package/dist/src/typecheck.js.map +0 -1
- package/dist/src/version.d.ts +0 -5
- package/dist/src/version.d.ts.map +0 -1
- package/dist/src/version.js +0 -5
- package/dist/src/version.js.map +0 -1
- package/docs/guide/01-first-program.md +0 -24
- package/docs/guide/02-money.md +0 -25
- package/docs/guide/03-instruments.md +0 -22
- package/docs/guide/04-lifecycles.md +0 -25
- package/docs/guide/05-fees-and-splits.md +0 -45
- package/docs/guide/06-schedules.md +0 -22
- package/docs/guide/07-composition.md +0 -46
- package/docs/guide/08-writing-a-module.md +0 -26
- package/docs/guide/09-cost.md +0 -24
- package/docs/guide/10-diagnostics.md +0 -27
- package/docs/llms-full.txt +0 -1739
- package/docs/llms.txt +0 -45
- package/docs/playground.md +0 -54
- package/docs/reference/cli.md +0 -35
- package/docs/reference/diagnostics.md +0 -619
- package/docs/reference/grammar.md +0 -39
- package/docs/reference/std/advance.md +0 -35
- package/docs/reference/std/cancellable_booking.md +0 -35
- package/docs/reference/std/captured_payment.md +0 -43
- package/docs/reference/std/conditional_disbursement.md +0 -33
- package/docs/reference/std/credit_facility.md +0 -35
- package/docs/reference/std/held_payment.md +0 -64
- package/docs/reference/std/instant_transfer.md +0 -30
- package/docs/reference/std/metered.md +0 -27
- package/docs/reference/std/pooled_split.md +0 -26
- package/docs/reference/std/premium_forward.md +0 -39
- package/docs/reference/std/reconciled_payout.md +0 -33
- package/docs/reference/std/recurring_collection.md +0 -25
- package/docs/reference/std/rotating_pool.md +0 -42
- package/docs/reference/std/scheduled.md +0 -51
- package/docs/reference/std/security_deposit.md +0 -46
- package/docs/reference/std/settlement_batch.md +0 -42
- package/docs/reference/std/swap.md +0 -46
- package/docs/reference/std/threshold_pool.md +0 -47
- package/docs/reference/std/weighted_distribution.md +0 -39
- package/docs/reference/types.md +0 -20
- package/docs/reference/udl-output.md +0 -13
- package/docs/sessions/2026-09-02-two-instruments.hsx +0 -21
- package/docs/sessions/2026-09-02-two-instruments.md +0 -41
- package/examples/01-first-program/README.md +0 -45
- package/examples/01-first-program/tip-jar.hsx +0 -17
- package/examples/02-imports-and-archetypes/README.md +0 -96
- package/examples/02-imports-and-archetypes/photo-booth.hsx +0 -37
- package/examples/03-diagnostics/README.md +0 -68
- package/examples/03-diagnostics/corner-shop-fixed.hsx +0 -22
- package/examples/03-diagnostics/corner-shop.hsx +0 -19
- package/examples/04-complete-product/README.md +0 -84
- package/examples/04-complete-product/study-hall.hsx +0 -67
- package/examples/05-watch-club/README.md +0 -9
- package/examples/05-watch-club/watch-club.hsx +0 -124
- package/examples/README.md +0 -18
- package/examples/advance/README.md +0 -3
- package/examples/advance/advance.hsx +0 -13
- package/examples/advance/advance.udl +0 -339
- package/examples/cancellable_booking/README.md +0 -3
- package/examples/cancellable_booking/cancellable_booking.hsx +0 -14
- package/examples/cancellable_booking/cancellable_booking.udl +0 -391
- package/examples/captured_payment/README.md +0 -3
- package/examples/captured_payment/captured_payment.hsx +0 -21
- package/examples/captured_payment/captured_payment.udl +0 -677
- package/examples/conditional_disbursement/README.md +0 -3
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -17
- package/examples/conditional_disbursement/conditional_disbursement.udl +0 -412
- package/examples/credit_facility/README.md +0 -3
- package/examples/credit_facility/credit_facility.hsx +0 -27
- package/examples/credit_facility/credit_facility.udl +0 -1373
- package/examples/held_payment/README.md +0 -3
- package/examples/held_payment/held_payment.hsx +0 -13
- package/examples/held_payment/held_payment.udl +0 -507
- package/examples/instant_transfer/README.md +0 -3
- package/examples/instant_transfer/instant_transfer.hsx +0 -16
- package/examples/instant_transfer/instant_transfer.udl +0 -364
- package/examples/metered/README.md +0 -3
- package/examples/metered/metered.hsx +0 -13
- package/examples/metered/metered.udl +0 -247
- package/examples/pooled_split/README.md +0 -3
- package/examples/pooled_split/pooled_split.hsx +0 -15
- package/examples/pooled_split/pooled_split.udl +0 -425
- package/examples/premium_forward/README.md +0 -3
- package/examples/premium_forward/premium_forward.hsx +0 -19
- package/examples/premium_forward/premium_forward.udl +0 -594
- package/examples/reconciled_payout/README.md +0 -3
- package/examples/reconciled_payout/reconciled_payout.hsx +0 -13
- package/examples/reconciled_payout/reconciled_payout.udl +0 -303
- package/examples/recurring_collection/README.md +0 -3
- package/examples/recurring_collection/recurring_collection.hsx +0 -21
- package/examples/recurring_collection/recurring_collection.udl +0 -1130
- package/examples/rotating_pool/README.md +0 -3
- package/examples/rotating_pool/rotating_pool.hsx +0 -18
- package/examples/rotating_pool/rotating_pool.udl +0 -4352
- package/examples/scheduled/README.md +0 -3
- package/examples/scheduled/scheduled.hsx +0 -12
- package/examples/scheduled/scheduled.udl +0 -335
- package/examples/security_deposit/README.md +0 -3
- package/examples/security_deposit/security_deposit.hsx +0 -21
- package/examples/security_deposit/security_deposit.udl +0 -293
- package/examples/settlement_batch/README.md +0 -3
- package/examples/settlement_batch/settlement_batch.hsx +0 -19
- package/examples/settlement_batch/settlement_batch.udl +0 -753
- package/examples/swap/README.md +0 -3
- package/examples/swap/swap.hsx +0 -19
- package/examples/swap/swap.udl +0 -876
- package/examples/threshold_pool/README.md +0 -3
- package/examples/threshold_pool/threshold_pool.hsx +0 -16
- package/examples/threshold_pool/threshold_pool.udl +0 -667
- package/examples/weighted_distribution/README.md +0 -3
- package/examples/weighted_distribution/weighted_distribution.hsx +0 -20
- package/examples/weighted_distribution/weighted_distribution.udl +0 -337
- package/skills/hsx/SKILL.md +0 -483
- package/src/emit.ts +0 -201
- package/src/entry-overrides.ts +0 -428
- package/src/ir.ts +0 -99
- package/src/limits.ts +0 -20
- package/src/lsp/server.ts +0 -460
- package/src/modules.ts +0 -446
- package/src/typecheck.ts +0 -3876
- package/src/version.ts +0 -5
- package/std/SEMANTICS.md +0 -130
- package/std/settlements/advance.hsx +0 -177
- package/std/settlements/cancellable_booking.hsx +0 -87
- package/std/settlements/captured_payment.hsx +0 -144
- package/std/settlements/conditional_disbursement.hsx +0 -185
- package/std/settlements/credit_facility.hsx +0 -167
- package/std/settlements/held_payment.hsx +0 -578
- package/std/settlements/index.hsx +0 -3
- package/std/settlements/instant_transfer.hsx +0 -190
- package/std/settlements/metered.hsx +0 -67
- package/std/settlements/pooled_split.hsx +0 -68
- package/std/settlements/premium_forward.hsx +0 -257
- package/std/settlements/reconciled_payout.hsx +0 -77
- package/std/settlements/recurring_collection.hsx +0 -27
- package/std/settlements/rotating_pool.hsx +0 -252
- package/std/settlements/scheduled.hsx +0 -641
- package/std/settlements/security_deposit.hsx +0 -166
- package/std/settlements/settlement_batch.hsx +0 -122
- package/std/settlements/swap.hsx +0 -1114
- package/std/settlements/threshold_pool.hsx +0 -221
- package/std/settlements/weighted_distribution.hsx +0 -141
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Entry, Expr, InstrumentDecl } from "./ast.ts";
|
|
2
|
+
|
|
3
|
+
export class BindingContractError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
readonly entry: Entry,
|
|
6
|
+
message: string,
|
|
7
|
+
) {
|
|
8
|
+
super(message);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function rows(value: Expr): Entry[] {
|
|
13
|
+
if (value.kind === "block") return value.entries;
|
|
14
|
+
throw new BindingContractError(
|
|
15
|
+
{ key: "contract", value, span: value.span },
|
|
16
|
+
"Binding contract needs a block.",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function properties(entry: Entry, allowed: string[]): Map<string, string> {
|
|
20
|
+
const result = new Map<string, string>();
|
|
21
|
+
for (const slot of rows(entry.value)) {
|
|
22
|
+
if (
|
|
23
|
+
!allowed.includes(slot.key) ||
|
|
24
|
+
result.has(slot.key) ||
|
|
25
|
+
(slot.value.kind !== "text" && slot.value.kind !== "name")
|
|
26
|
+
)
|
|
27
|
+
throw new BindingContractError(
|
|
28
|
+
slot,
|
|
29
|
+
"Invalid binding contract property.",
|
|
30
|
+
);
|
|
31
|
+
result.set(slot.key, slot.value.value);
|
|
32
|
+
}
|
|
33
|
+
if (allowed.some((key) => !result.get(key)))
|
|
34
|
+
throw new BindingContractError(
|
|
35
|
+
entry,
|
|
36
|
+
`Binding contract needs ${allowed.join(", ")}.`,
|
|
37
|
+
);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function contractEntries(decl: InstrumentDecl, name: string): Entry[] {
|
|
41
|
+
const blocks = decl.body.entries.filter((entry) => entry.key === name);
|
|
42
|
+
if (blocks.length > 1)
|
|
43
|
+
throw new BindingContractError(blocks[1]!, `Duplicate ${name}.`);
|
|
44
|
+
const entries = blocks[0] ? rows(blocks[0].value) : [];
|
|
45
|
+
const seen = new Set<string>();
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
if (
|
|
48
|
+
seen.has(entry.key) ||
|
|
49
|
+
!decl.parameters.some((parameter) => parameter.key === entry.key)
|
|
50
|
+
)
|
|
51
|
+
throw new BindingContractError(
|
|
52
|
+
entry,
|
|
53
|
+
"Binding contract must name each declared tunable once.",
|
|
54
|
+
);
|
|
55
|
+
seen.add(entry.key);
|
|
56
|
+
}
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Conditional requirements are header data, independent of module names. */
|
|
61
|
+
export function bindingDependencies(decl: InstrumentDecl) {
|
|
62
|
+
return contractEntries(decl, "dependencies").map((entry) => {
|
|
63
|
+
const values = properties(entry, ["selector", "is", "message", "fix"]);
|
|
64
|
+
const selector = decl.parameters.find(
|
|
65
|
+
(parameter) => parameter.key === values.get("selector"),
|
|
66
|
+
);
|
|
67
|
+
const type =
|
|
68
|
+
selector?.value.kind === "default"
|
|
69
|
+
? selector.value.type
|
|
70
|
+
: selector?.value;
|
|
71
|
+
if (
|
|
72
|
+
type?.kind !== "call" ||
|
|
73
|
+
type.name !== "enum" ||
|
|
74
|
+
!type.args.some(
|
|
75
|
+
(arg) => arg.kind === "name" && arg.value === values.get("is"),
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
throw new BindingContractError(
|
|
79
|
+
entry,
|
|
80
|
+
"Dependency must select a declared enum choice.",
|
|
81
|
+
);
|
|
82
|
+
return {
|
|
83
|
+
binding: entry.key,
|
|
84
|
+
when: values.get("selector")!,
|
|
85
|
+
is: values.get("is")!,
|
|
86
|
+
message: values.get("message")!,
|
|
87
|
+
fix: values.get("fix")!,
|
|
88
|
+
span: entry.span,
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A header explains why a typed parameter cannot encode a proposed policy. */
|
|
94
|
+
export function parameterDiagnostics(decl: InstrumentDecl) {
|
|
95
|
+
return contractEntries(decl, "parameterDiagnostics").map((entry) => {
|
|
96
|
+
const values = properties(entry, ["accepts", "percentage", "fix"]);
|
|
97
|
+
return {
|
|
98
|
+
parameter: entry.key,
|
|
99
|
+
accepts: values.get("accepts")!,
|
|
100
|
+
percentage: values.get("percentage")!,
|
|
101
|
+
fix: values.get("fix")!,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -1,344 +1,114 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* compiled artifacts as JSON. Neither reads the environment, neither touches
|
|
6
|
-
* a network, and neither writes outside the path the caller names.
|
|
7
|
-
*
|
|
8
|
-
* Everything here is a pure function of `argv` plus the injected `Io`, so the
|
|
9
|
-
* spec drives the real code paths with an in-memory filesystem instead of
|
|
10
|
-
* asserting on a subprocess's scrollback.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { compile, type CompileResult } from "./compile.ts";
|
|
15
|
-
import type { UdlCostManifest, UdlCostTable } from "./cost.ts";
|
|
16
|
-
import { hsxDiagnostics } from "./diagnostics.ts";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { headerManifest } from "./headers.ts";
|
|
4
|
+
import { compile } from "./compile.ts";
|
|
17
5
|
import { format } from "./format.ts";
|
|
18
|
-
import {
|
|
19
|
-
|
|
6
|
+
import { serializeUdl, UDL_FORMAT_VERSION } from "@hyperscale0/udl";
|
|
7
|
+
const usage =
|
|
8
|
+
"hsx build|format|cost <file> [--out path]\nhsx check <file>\nhsx headers --json";
|
|
9
|
+
const standardLibrary = {
|
|
10
|
+
source: (header: string) => {
|
|
11
|
+
for (const base of ["../std/", "../../std/"]) {
|
|
12
|
+
const file = new URL(`${base}${header}.hsx`, import.meta.url);
|
|
13
|
+
if (existsSync(file)) return readFileSync(file, "utf8");
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
20
18
|
|
|
21
|
-
/** Filesystem and streams, injected so the CLI stays testable. */
|
|
22
19
|
export interface Io {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
readonly stdin?: NodeJS.ReadableStream;
|
|
28
|
-
readonly stdout?: NodeJS.WritableStream;
|
|
20
|
+
out(line: string): void;
|
|
21
|
+
err(line: string): void;
|
|
22
|
+
readFile(path: string): Promise<string>;
|
|
23
|
+
writeFile(path: string, text: string): Promise<void>;
|
|
29
24
|
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Exit codes. `warning` exits 0 because a lint note is not a failure; pass
|
|
33
|
-
* `--strict` to make it one.
|
|
34
|
-
*/
|
|
35
|
-
const OK = 0;
|
|
36
|
-
const REFUSED = 1;
|
|
37
|
-
const USAGE = 2;
|
|
38
|
-
|
|
39
|
-
export const USAGE_TEXT = `hsx ${HSX_VERSION}, the HSX compiler
|
|
40
|
-
|
|
41
|
-
Usage:
|
|
42
|
-
hsx check <file.hsx> [--strict]
|
|
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
|
|
48
|
-
hsx --version
|
|
49
|
-
hsx --help
|
|
50
|
-
|
|
51
|
-
Commands:
|
|
52
|
-
check Compile and report diagnostics. Prints nothing when the program is clean.
|
|
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.
|
|
58
|
-
|
|
59
|
-
Options:
|
|
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.
|
|
62
|
-
--strict Treat warning-severity diagnostics as failures.
|
|
63
|
-
|
|
64
|
-
Exit codes:
|
|
65
|
-
0 the program compiled (verdict valid, or warning without --strict)
|
|
66
|
-
1 the program was refused (verdict invalid, or warning with --strict)
|
|
67
|
-
2 the command line or the input file could not be used`;
|
|
68
|
-
|
|
69
25
|
export async function runCli(argv: readonly string[], io: Io): Promise<number> {
|
|
70
|
-
const [command, ...
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
26
|
+
const [command, file, ...options] = argv;
|
|
27
|
+
if (command === "headers") {
|
|
28
|
+
if (file !== "--json" || options.length) {
|
|
29
|
+
io.err("usage: hsx headers --json");
|
|
30
|
+
return 2;
|
|
31
|
+
}
|
|
32
|
+
io.out(JSON.stringify(headerManifest(standardLibrary), null, 2));
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
if (["--help", "help", "-h"].includes(command ?? "")) {
|
|
36
|
+
io.out(usage);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
if (command === "--version") {
|
|
40
|
+
// src/cli.ts in a checkout, dist/src/cli.js in the published package.
|
|
41
|
+
for (const base of ["../", "../../"]) {
|
|
42
|
+
const manifest = new URL(`${base}package.json`, import.meta.url);
|
|
43
|
+
if (!existsSync(manifest)) continue;
|
|
44
|
+
const { version } = JSON.parse(readFileSync(manifest, "utf8")) as {
|
|
45
|
+
version: string;
|
|
46
|
+
};
|
|
47
|
+
io.out(`${version} (UDL ${UDL_FORMAT_VERSION})`);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
io.err("hsx: package.json not found");
|
|
51
|
+
return 1;
|
|
89
52
|
}
|
|
90
53
|
if (
|
|
91
|
-
command
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
command !== "format"
|
|
54
|
+
!command ||
|
|
55
|
+
!file ||
|
|
56
|
+
!["build", "check", "format", "cost"].includes(command)
|
|
95
57
|
) {
|
|
96
|
-
io.err(`
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return USAGE;
|
|
113
|
-
}
|
|
114
|
-
|
|
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;
|
|
58
|
+
io.err(`usage:\n${usage}`);
|
|
59
|
+
return 2;
|
|
60
|
+
}
|
|
61
|
+
let output: string | undefined;
|
|
62
|
+
for (let i = 0; i < options.length; i++) {
|
|
63
|
+
if (
|
|
64
|
+
command !== "check" &&
|
|
65
|
+
options[i] === "--out" &&
|
|
66
|
+
options[i + 1] &&
|
|
67
|
+
!options[i + 1]!.startsWith("--") &&
|
|
68
|
+
output === undefined
|
|
69
|
+
)
|
|
70
|
+
output = options[++i];
|
|
71
|
+
else {
|
|
72
|
+
io.err(`unknown option ${options[i]}`);
|
|
73
|
+
return 2;
|
|
122
74
|
}
|
|
123
|
-
io.out(result.formatted.trimEnd());
|
|
124
|
-
return OK;
|
|
125
75
|
}
|
|
126
|
-
|
|
127
|
-
let costTable: UdlCostTable;
|
|
128
76
|
try {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const refused =
|
|
138
|
-
result.verdict === "invalid" ||
|
|
139
|
-
(parsed.strict && result.verdict === "warning");
|
|
140
|
-
|
|
141
|
-
if (command === "check") {
|
|
142
|
-
return refused ? REFUSED : OK;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!result.artifacts) return REFUSED;
|
|
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`;
|
|
160
|
-
if (parsed.out === undefined) {
|
|
161
|
-
io.out(json.trimEnd());
|
|
162
|
-
} else {
|
|
163
|
-
try {
|
|
164
|
-
await io.writeFile(parsed.out, json);
|
|
165
|
-
} catch (cause) {
|
|
166
|
-
io.err(`hsx: cannot write ${parsed.out}: ${messageOf(cause)}`);
|
|
167
|
-
return USAGE;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return refused ? REFUSED : OK;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
interface Options {
|
|
174
|
-
readonly file: string;
|
|
175
|
-
readonly json: boolean;
|
|
176
|
-
readonly out?: string;
|
|
177
|
-
readonly strict: boolean;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function parseOptions(
|
|
181
|
-
args: readonly string[],
|
|
182
|
-
command: "build" | "check" | "cost" | "format",
|
|
183
|
-
): Options | { readonly error: string } {
|
|
184
|
-
let file: string | undefined;
|
|
185
|
-
let json = false;
|
|
186
|
-
let out: string | undefined;
|
|
187
|
-
let strict = false;
|
|
188
|
-
|
|
189
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
190
|
-
const argument = args[index] as string;
|
|
191
|
-
if (argument === "--strict") {
|
|
192
|
-
if (command === "format") {
|
|
193
|
-
return { error: "hsx format has no style options" };
|
|
194
|
-
}
|
|
195
|
-
strict = true;
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (argument === "--json") {
|
|
199
|
-
if (command !== "cost") return { error: "--json belongs to hsx cost" };
|
|
200
|
-
json = true;
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (argument === "--out") {
|
|
204
|
-
if (command !== "build" && command !== "cost") {
|
|
205
|
-
return { error: "--out belongs to hsx build or hsx cost" };
|
|
206
|
-
}
|
|
207
|
-
const value = args[index + 1];
|
|
208
|
-
if (value === undefined || value.startsWith("--")) {
|
|
209
|
-
return { error: "--out needs a file path" };
|
|
77
|
+
const source = await io.readFile(file);
|
|
78
|
+
let text: string;
|
|
79
|
+
if (command === "format") {
|
|
80
|
+
const result = format(source);
|
|
81
|
+
if (!result.ok) {
|
|
82
|
+
result.diagnostics.forEach((d) => io.err(`${file}: ${d.message}`));
|
|
83
|
+
return 1;
|
|
210
84
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (file === undefined) return { error: `hsx ${command} needs a file` };
|
|
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;
|
|
85
|
+
text = result.formatted;
|
|
86
|
+
} else {
|
|
87
|
+
const result = compile(source, { standardLibrary });
|
|
88
|
+
for (const d of result.diagnostics)
|
|
89
|
+
io.err(
|
|
90
|
+
`${d.source && d.source !== "program" ? d.source : file}:${d.line}:${d.column}: ${d.code} ${d.message}. ${d.fix}`,
|
|
91
|
+
);
|
|
92
|
+
if (!result.artifacts || result.verdict !== "valid") return 1;
|
|
93
|
+
if (command === "check") return 0;
|
|
94
|
+
text =
|
|
95
|
+
command === "cost"
|
|
96
|
+
? JSON.stringify(result.artifacts.costManifest, null, 2) + "\n"
|
|
97
|
+
: serializeUdl(result.artifacts.document);
|
|
314
98
|
}
|
|
99
|
+
if (output) await io.writeFile(output, text);
|
|
100
|
+
else io.out(text.trimEnd());
|
|
101
|
+
return 0;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
io.err(error instanceof Error ? error.message : String(error));
|
|
104
|
+
return 2;
|
|
315
105
|
}
|
|
316
|
-
throw lastError;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/** `file:line:col: severity [stage] message`, the shape editors already parse. */
|
|
320
|
-
function diagnosticLines(
|
|
321
|
-
file: string,
|
|
322
|
-
result: CompileResult,
|
|
323
|
-
): readonly string[] {
|
|
324
|
-
return result.diagnostics.map(
|
|
325
|
-
(diagnostic) =>
|
|
326
|
-
`${diagnostic.file ?? file}:${diagnostic.line}:${diagnostic.column}: ${diagnostic.severity} [${diagnostic.stage}] ${diagnostic.message}`,
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function messageOf(cause: unknown): string {
|
|
331
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
332
106
|
}
|
|
333
107
|
|
|
334
|
-
if (import.meta.main)
|
|
335
|
-
const { readFile, writeFile } = await import("node:fs/promises");
|
|
108
|
+
if (import.meta.main)
|
|
336
109
|
process.exitCode = await runCli(process.argv.slice(2), {
|
|
337
|
-
|
|
338
|
-
|
|
110
|
+
out: (line) => process.stdout.write(line + "\n"),
|
|
111
|
+
err: (line) => process.stderr.write(line + "\n"),
|
|
339
112
|
readFile: (path) => readFile(path, "utf8"),
|
|
340
|
-
writeFile: (path,
|
|
341
|
-
stdin: process.stdin,
|
|
342
|
-
stdout: process.stdout,
|
|
113
|
+
writeFile: (path, text) => writeFile(path, text, "utf8"),
|
|
343
114
|
});
|
|
344
|
-
}
|