@hyperscale0/hsx 1.0.0-rc.1 → 1.0.123
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 -239
- package/LICENSE.md +356 -0
- package/NOTICE.md +8 -0
- package/README.md +45 -43
- package/bin/hsx.ts +0 -5
- package/dist/bin/hsx.d.ts +0 -4
- package/dist/bin/hsx.d.ts.map +1 -1
- package/dist/bin/hsx.js +0 -4
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/ast.d.ts +113 -302
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +7 -47
- 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 -17
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +92 -259
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +34 -68
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +2949 -118
- 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 +8 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -8
- 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 +2 -0
- package/dist/src/std-bundle.d.ts.map +1 -0
- package/dist/src/std-bundle.js +15 -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 +5 -0
- package/dist/src/std-library.js.map +1 -0
- 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 -23
- 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 +27 -17
- package/src/ast.ts +84 -411
- package/src/attachment-economics.ts +103 -0
- package/src/binding-contract.ts +104 -0
- package/src/cli.ts +98 -309
- package/src/compile.ts +4063 -212
- 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 +16 -29
- 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 +15 -0
- package/src/std-library.ts +7 -0
- 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 -35
- package/dist/src/emit.d.ts.map +0 -1
- package/dist/src/emit.js +0 -359
- 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/modules.d.ts +0 -36
- package/dist/src/modules.d.ts.map +0 -1
- package/dist/src/modules.js +0 -372
- 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 -1738
- package/docs/llms.txt +0 -45
- package/docs/reference/cli.md +0 -33
- 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 -14
- 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 -99
- 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 -87
- 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 -447
- package/src/entry-overrides.ts +0 -428
- package/src/ir.ts +0 -99
- package/src/limits.ts +0 -20
- package/src/modules.ts +0 -444
- 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
package/dist/src/parse.js
CHANGED
|
@@ -1,1110 +1,558 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The HSX parser. Recursive descent over the lexer's token stream, total:
|
|
3
|
-
* it always returns a best-effort `Program` plus diagnostics, recovering at
|
|
4
|
-
* the next top-level keyword after an error so one mistake never hides the
|
|
5
|
-
* rest of the file. Diagnostics speak the author's language and point at
|
|
6
|
-
* source spans.
|
|
7
|
-
*/
|
|
8
1
|
import { lex } from "./lex.js";
|
|
9
|
-
|
|
2
|
+
class ParseFailure extends Error {
|
|
3
|
+
diagnostic;
|
|
4
|
+
constructor(diagnostic) {
|
|
5
|
+
super(diagnostic.message);
|
|
6
|
+
this.diagnostic = diagnostic;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
10
9
|
export function parseProgram(source) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const empty = {
|
|
11
|
+
name: "",
|
|
12
|
+
title: "",
|
|
13
|
+
currency: "SAR",
|
|
14
|
+
header: false,
|
|
15
|
+
decls: [],
|
|
16
|
+
span: { start: 0, end: source.length },
|
|
17
|
+
};
|
|
18
|
+
if (new TextEncoder().encode(source).length > 262144)
|
|
16
19
|
return {
|
|
20
|
+
program: empty,
|
|
17
21
|
diagnostics: [
|
|
18
22
|
{
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
code: "HSX1004",
|
|
24
|
+
message: "source exceeds 256 KiB",
|
|
25
|
+
fix: "split the program into headers",
|
|
26
|
+
span: empty.span,
|
|
21
27
|
},
|
|
22
28
|
],
|
|
23
|
-
program: { decls: [], span: { end: 0, start: 0 } },
|
|
24
29
|
};
|
|
25
|
-
|
|
26
|
-
const first = source.search(/\S/);
|
|
27
|
-
if (first >= 0 && source[first] === "{") {
|
|
30
|
+
if (/^\s*[[{]/.test(source))
|
|
28
31
|
return {
|
|
32
|
+
program: empty,
|
|
29
33
|
diagnostics: [
|
|
30
34
|
{
|
|
31
35
|
code: "HSX1014",
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
span: { end:
|
|
36
|
+
message: "JSON is not HSX source",
|
|
37
|
+
fix: 'write program name "Title" followed by HSX declarations',
|
|
38
|
+
span: { start: source.search(/\S/), end: source.search(/\S/) + 1 },
|
|
35
39
|
},
|
|
36
40
|
],
|
|
37
|
-
program: { decls: [], span: { end: source.length, start: 0 } },
|
|
38
41
|
};
|
|
42
|
+
const result = lex(source);
|
|
43
|
+
if (result.diagnostics.length)
|
|
44
|
+
return { program: empty, diagnostics: result.diagnostics };
|
|
45
|
+
try {
|
|
46
|
+
return {
|
|
47
|
+
program: new Parser(result.tokens).program(empty),
|
|
48
|
+
diagnostics: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof ParseFailure)
|
|
53
|
+
return { program: empty, diagnostics: [error.diagnostic] };
|
|
54
|
+
throw error;
|
|
39
55
|
}
|
|
40
|
-
const lexed = lex(source);
|
|
41
|
-
const parser = new Parser(lexed.tokens, [...lexed.diagnostics]);
|
|
42
|
-
const program = parser.parseProgram(source.length);
|
|
43
|
-
return { diagnostics: parser.diagnostics, program };
|
|
44
56
|
}
|
|
45
57
|
class Parser {
|
|
46
|
-
diagnostics;
|
|
47
58
|
tokens;
|
|
48
59
|
index = 0;
|
|
49
60
|
depth = 0;
|
|
50
|
-
constructor(tokens
|
|
61
|
+
constructor(tokens) {
|
|
51
62
|
this.tokens = tokens;
|
|
52
|
-
this.diagnostics = diagnostics;
|
|
53
63
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
while (!this.at("eof")) {
|
|
57
|
-
if (this.atPunct(";")) {
|
|
58
|
-
this.advance();
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const decl = this.parseDecl();
|
|
62
|
-
if (decl) {
|
|
63
|
-
decls.push(decl);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
this.recoverToDecl();
|
|
67
|
-
}
|
|
68
|
-
return { decls, span: { end: sourceLength, start: 0 } };
|
|
64
|
+
peek() {
|
|
65
|
+
return this.tokens[this.index];
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
parseDecl(exported = false) {
|
|
67
|
+
take() {
|
|
72
68
|
const token = this.peek();
|
|
73
|
-
if (token.kind !== "
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
: `expected a declaration (module, program, use, expose, import, party, asset, subject, instrument, settlement, type, const, or port), found "${token.text}"`);
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
switch (token.text) {
|
|
80
|
-
case "export":
|
|
81
|
-
this.advance();
|
|
82
|
-
return this.parseDecl(true);
|
|
83
|
-
case "module":
|
|
84
|
-
return this.parseModuleDecl(exported);
|
|
85
|
-
case "program":
|
|
86
|
-
return this.parseProgramDecl();
|
|
87
|
-
case "use":
|
|
88
|
-
return this.parseUseDecl();
|
|
89
|
-
case "expose":
|
|
90
|
-
return this.parseExposeDecl();
|
|
91
|
-
case "import":
|
|
92
|
-
return this.parseImportDecl();
|
|
93
|
-
case "party":
|
|
94
|
-
return this.parsePartyDecl();
|
|
95
|
-
case "asset":
|
|
96
|
-
return this.parseAssetDecl();
|
|
97
|
-
case "subject":
|
|
98
|
-
return this.parseSubjectDecl(exported);
|
|
99
|
-
case "instrument":
|
|
100
|
-
return this.parseInstrumentDecl(exported);
|
|
101
|
-
case "settlement":
|
|
102
|
-
return this.parseSettlementDecl();
|
|
103
|
-
case "port":
|
|
104
|
-
return this.parsePortDecl();
|
|
105
|
-
case "type":
|
|
106
|
-
return this.parseTypeDecl(exported);
|
|
107
|
-
case "const":
|
|
108
|
-
return this.parseConstDecl(exported);
|
|
109
|
-
default:
|
|
110
|
-
this.error(token.span, `"${token.text}" cannot start a declaration`);
|
|
111
|
-
this.advance();
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
parseModuleDecl(exported) {
|
|
116
|
-
const start = this.advance();
|
|
117
|
-
if (exported) {
|
|
118
|
-
this.error(start.span, "a module declaration cannot be exported");
|
|
119
|
-
}
|
|
120
|
-
const name = this.parsePath("the module needs a dotted name");
|
|
121
|
-
return name
|
|
122
|
-
? { kind: "module", name, span: this.spanFrom(start.span) }
|
|
123
|
-
: undefined;
|
|
69
|
+
if (token.kind !== "eof")
|
|
70
|
+
this.index++;
|
|
71
|
+
return token;
|
|
124
72
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const name = this.expectIdent("the instrument needs a snake_case name");
|
|
128
|
-
if (!name)
|
|
129
|
-
return undefined;
|
|
130
|
-
const typeParameters = this.parseTypeParameters();
|
|
131
|
-
if (!typeParameters)
|
|
132
|
-
return undefined;
|
|
133
|
-
const hasParameterList = this.atPunct("(");
|
|
134
|
-
const parameters = hasParameterList ? this.parseParameters() : [];
|
|
135
|
-
if (!parameters)
|
|
136
|
-
return undefined;
|
|
137
|
-
if (this.atPunct("=")) {
|
|
138
|
-
this.advance();
|
|
139
|
-
const application = this.parseApplication();
|
|
140
|
-
if (!application)
|
|
141
|
-
return undefined;
|
|
142
|
-
const metadata = this.atPunct("{") ? this.parseBlock() : undefined;
|
|
143
|
-
return {
|
|
144
|
-
application,
|
|
145
|
-
exported,
|
|
146
|
-
kind: "instrument_apply",
|
|
147
|
-
...(metadata ? { metadata } : {}),
|
|
148
|
-
name,
|
|
149
|
-
span: this.spanFrom(start.span),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
if (!this.atPunct("{")) {
|
|
153
|
-
this.error(this.peek().span, `instrument ${name.name} needs a body or an exported instrument application`);
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
const body = this.parseBlock();
|
|
157
|
-
if (!body)
|
|
158
|
-
return undefined;
|
|
159
|
-
return {
|
|
160
|
-
body,
|
|
161
|
-
exported,
|
|
162
|
-
hasParameterList,
|
|
163
|
-
kind: "instrument",
|
|
164
|
-
name,
|
|
165
|
-
parameters,
|
|
166
|
-
span: this.spanFrom(start.span),
|
|
167
|
-
typeParameters,
|
|
168
|
-
};
|
|
73
|
+
at(text) {
|
|
74
|
+
return this.peek().text === text;
|
|
169
75
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (!this.expectPunct("=", `type ${name.name} needs = before its definition`)) {
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
const value = this.parseExpr();
|
|
179
|
-
return value
|
|
180
|
-
? { exported, kind: "type", name, span: this.spanFrom(start.span), value }
|
|
181
|
-
: undefined;
|
|
76
|
+
eat(text) {
|
|
77
|
+
if (!this.at(text))
|
|
78
|
+
return false;
|
|
79
|
+
this.take();
|
|
80
|
+
return true;
|
|
182
81
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
82
|
+
fail(message, fix, span = this.peek().span) {
|
|
83
|
+
throw new ParseFailure({
|
|
84
|
+
code: "HSX1000",
|
|
85
|
+
message,
|
|
86
|
+
fix,
|
|
87
|
+
span,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
expect(text) {
|
|
91
|
+
if (!this.eat(text))
|
|
92
|
+
this.fail(`expected ${text}, found ${this.peek().text || "end of file"}`, `write ${text} here`);
|
|
93
|
+
}
|
|
94
|
+
identifier() {
|
|
95
|
+
if (this.peek().kind !== "name")
|
|
96
|
+
this.fail("expected a name", "write a name starting with a letter");
|
|
97
|
+
return this.take().text;
|
|
98
|
+
}
|
|
99
|
+
path() {
|
|
100
|
+
let name = this.identifier();
|
|
101
|
+
while (this.eat("."))
|
|
102
|
+
name += "." + this.identifier();
|
|
103
|
+
return name;
|
|
104
|
+
}
|
|
105
|
+
separators() {
|
|
106
|
+
while (this.eat(",") || this.eat(";")) { }
|
|
107
|
+
}
|
|
108
|
+
span(start) {
|
|
109
|
+
return { start, end: this.tokens[Math.max(0, this.index - 1)].span.end };
|
|
110
|
+
}
|
|
111
|
+
program(program) {
|
|
112
|
+
if (this.eat("header"))
|
|
113
|
+
program.header = true;
|
|
114
|
+
else
|
|
115
|
+
this.expect("program");
|
|
116
|
+
program.name = this.identifier();
|
|
117
|
+
if (!program.header) {
|
|
118
|
+
if (this.peek().kind !== "string")
|
|
119
|
+
this.fail("program needs a title", "write a quoted title after its name");
|
|
120
|
+
program.title = JSON.parse(this.take().text);
|
|
121
|
+
}
|
|
122
|
+
let currencyDeclared = false;
|
|
123
|
+
while (this.peek().kind !== "eof") {
|
|
124
|
+
this.separators();
|
|
125
|
+
if (this.peek().kind === "eof")
|
|
126
|
+
break;
|
|
127
|
+
const start = this.peek().span.start;
|
|
128
|
+
if (this.at("currency")) {
|
|
129
|
+
if (currencyDeclared)
|
|
130
|
+
this.fail("currency is declared twice", "keep one currency declaration");
|
|
131
|
+
this.take();
|
|
132
|
+
currencyDeclared = true;
|
|
133
|
+
program.currency = this.identifier();
|
|
134
|
+
continue;
|
|
207
135
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
while (!this.atPunct(">") && !this.at("eof")) {
|
|
216
|
-
const name = this.expectIdent("expected a type parameter");
|
|
217
|
-
if (!name)
|
|
218
|
-
return undefined;
|
|
219
|
-
parameters.push({ name, span: name.span });
|
|
220
|
-
if (this.atPunct(","))
|
|
221
|
-
this.advance();
|
|
222
|
-
}
|
|
223
|
-
return this.expectPunct(">", "the type parameter list never closes")
|
|
224
|
-
? parameters
|
|
225
|
-
: undefined;
|
|
226
|
-
}
|
|
227
|
-
parseParameters() {
|
|
228
|
-
this.advance();
|
|
229
|
-
const parameters = [];
|
|
230
|
-
while (!this.atPunct(")") && !this.at("eof")) {
|
|
231
|
-
const name = this.expectIdent("expected a parameter name");
|
|
232
|
-
if (!name)
|
|
233
|
-
return undefined;
|
|
234
|
-
if (!this.expectPunct(":", `parameter ${name.name} needs a type`)) {
|
|
235
|
-
return undefined;
|
|
136
|
+
if (this.eat("use")) {
|
|
137
|
+
program.decls.push({
|
|
138
|
+
kind: "use",
|
|
139
|
+
name: this.identifier(),
|
|
140
|
+
span: this.span(start),
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
236
143
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
: undefined;
|
|
251
|
-
}
|
|
252
|
-
parseProgramDecl() {
|
|
253
|
-
const start = this.advance();
|
|
254
|
-
const name = this.expectIdent("the program needs a snake_case name");
|
|
255
|
-
if (!name)
|
|
256
|
-
return undefined;
|
|
257
|
-
let title;
|
|
258
|
-
if (this.at("string")) {
|
|
259
|
-
const token = this.advance();
|
|
260
|
-
title = { kind: "string", span: token.span, value: token.value };
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
kind: "program",
|
|
264
|
-
name,
|
|
265
|
-
span: this.spanFrom(start.span),
|
|
266
|
-
...(title ? { title } : {}),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
parseUseDecl() {
|
|
270
|
-
const start = this.advance();
|
|
271
|
-
const instrument = this.expectIdent("use needs a published instrument id, like: use held_settlement");
|
|
272
|
-
return instrument
|
|
273
|
-
? { instrument, kind: "use", span: this.spanFrom(start.span) }
|
|
274
|
-
: undefined;
|
|
275
|
-
}
|
|
276
|
-
parseExposeDecl() {
|
|
277
|
-
const start = this.advance();
|
|
278
|
-
const instrument = this.expectIdent("expose needs an instrument action, like: expose held_settlement.release as releaseFunds");
|
|
279
|
-
if (!instrument)
|
|
280
|
-
return undefined;
|
|
281
|
-
if (!this.expectPunct(".", "expose separates the instrument and action with .")) {
|
|
282
|
-
return undefined;
|
|
283
|
-
}
|
|
284
|
-
const action = this.expectName("expose needs an action after the instrument id");
|
|
285
|
-
if (!action)
|
|
286
|
-
return undefined;
|
|
287
|
-
if (!this.at("keyword") || this.peek().text !== "as") {
|
|
288
|
-
this.error(this.peek().span, "expose needs as before its public action name");
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
this.advance();
|
|
292
|
-
const publicName = this.expectIdent("expose needs a camelCase public action name");
|
|
293
|
-
return publicName
|
|
294
|
-
? {
|
|
295
|
-
action,
|
|
296
|
-
instrument,
|
|
297
|
-
kind: "expose",
|
|
298
|
-
publicName,
|
|
299
|
-
span: this.spanFrom(start.span),
|
|
144
|
+
if (this.eat("party")) {
|
|
145
|
+
const name = this.identifier();
|
|
146
|
+
this.expect(":");
|
|
147
|
+
const partyKind = this.identifier();
|
|
148
|
+
const role = this.eat("role") ? this.identifier() : undefined;
|
|
149
|
+
program.decls.push({
|
|
150
|
+
kind: "party",
|
|
151
|
+
name,
|
|
152
|
+
partyKind,
|
|
153
|
+
...(role ? { role } : {}),
|
|
154
|
+
span: this.span(start),
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
300
157
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return undefined;
|
|
313
|
-
names.push(name);
|
|
314
|
-
if (this.atPunct(","))
|
|
315
|
-
this.advance();
|
|
316
|
-
}
|
|
317
|
-
if (!this.expectPunct("}", "the import name list never closes")) {
|
|
318
|
-
return undefined;
|
|
319
|
-
}
|
|
320
|
-
if (!this.at("keyword") || this.peek().text !== "from") {
|
|
321
|
-
this.error(this.peek().span, 'import needs from: import { held_payment } from "std/settlements"');
|
|
322
|
-
return undefined;
|
|
323
|
-
}
|
|
324
|
-
this.advance();
|
|
325
|
-
if (!this.at("string")) {
|
|
326
|
-
this.error(this.peek().span, 'the import source must be a quoted module name like "std/settlements"');
|
|
327
|
-
return undefined;
|
|
328
|
-
}
|
|
329
|
-
const sourceToken = this.advance();
|
|
330
|
-
if (names.length === 0) {
|
|
331
|
-
this.error(start.span, "import brings in at least one name");
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
from: {
|
|
335
|
-
kind: "string",
|
|
336
|
-
span: sourceToken.span,
|
|
337
|
-
value: sourceToken.value,
|
|
338
|
-
},
|
|
339
|
-
kind: "import",
|
|
340
|
-
names,
|
|
341
|
-
span: this.spanFrom(start.span),
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
parsePartyDecl() {
|
|
345
|
-
const start = this.advance();
|
|
346
|
-
const name = this.expectIdent("the party needs a name, like: party buyer: person");
|
|
347
|
-
if (!name)
|
|
348
|
-
return undefined;
|
|
349
|
-
if (!this.expectPunct(":", `party ${name.name} needs a kind, like: party ${name.name}: person`)) {
|
|
350
|
-
return undefined;
|
|
351
|
-
}
|
|
352
|
-
const partyKind = this.expectIdent(`expected a kind for party ${name.name}`);
|
|
353
|
-
if (!partyKind)
|
|
354
|
-
return undefined;
|
|
355
|
-
const attrs = this.atPunct("{") ? this.parseBlock() : undefined;
|
|
356
|
-
return {
|
|
357
|
-
...(attrs ? { attrs } : {}),
|
|
358
|
-
kind: "party",
|
|
359
|
-
name,
|
|
360
|
-
partyKind,
|
|
361
|
-
span: this.spanFrom(start.span),
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
parseAssetDecl() {
|
|
365
|
-
const start = this.advance();
|
|
366
|
-
const name = this.expectIdent("the asset needs a name, like: asset vehicle: good");
|
|
367
|
-
if (!name)
|
|
368
|
-
return undefined;
|
|
369
|
-
if (!this.expectPunct(":", `asset ${name.name} needs a kind, like: asset ${name.name}: good`)) {
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
372
|
-
const assetKind = this.expectIdent(`expected a kind for asset ${name.name}`);
|
|
373
|
-
if (!assetKind)
|
|
374
|
-
return undefined;
|
|
375
|
-
const attrs = this.atPunct("{") ? this.parseBlock() : undefined;
|
|
376
|
-
return {
|
|
377
|
-
assetKind,
|
|
378
|
-
...(attrs ? { attrs } : {}),
|
|
379
|
-
kind: "asset",
|
|
380
|
-
name,
|
|
381
|
-
span: this.spanFrom(start.span),
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
parseSubjectDecl(exported) {
|
|
385
|
-
const start = this.advance();
|
|
386
|
-
if (exported) {
|
|
387
|
-
this.error(start.span, "a subject declaration cannot be exported");
|
|
388
|
-
}
|
|
389
|
-
const name = this.expectIdent("the subject needs a snake_case kind name");
|
|
390
|
-
if (!name)
|
|
391
|
-
return undefined;
|
|
392
|
-
if (!this.atPunct("{")) {
|
|
393
|
-
this.error(this.peek().span, `subject ${name.name} needs a body`);
|
|
394
|
-
return undefined;
|
|
395
|
-
}
|
|
396
|
-
const body = this.parseBlock();
|
|
397
|
-
return body
|
|
398
|
-
? { body, kind: "subject", name, span: this.spanFrom(start.span) }
|
|
399
|
-
: undefined;
|
|
400
|
-
}
|
|
401
|
-
parseSettlementDecl() {
|
|
402
|
-
const start = this.advance();
|
|
403
|
-
const name = this.expectIdent("the settlement needs a name, like: settlement sale = held_payment { ... }");
|
|
404
|
-
if (!name)
|
|
405
|
-
return undefined;
|
|
406
|
-
if (!this.expectPunct("=", `settlement ${name.name} instantiates an archetype: settlement ${name.name} = held_payment { ... }`)) {
|
|
407
|
-
return undefined;
|
|
408
|
-
}
|
|
409
|
-
const archetype = this.expectIdent(`expected an archetype name for settlement ${name.name}`);
|
|
410
|
-
if (!archetype)
|
|
411
|
-
return undefined;
|
|
412
|
-
if (!this.atPunct("{")) {
|
|
413
|
-
this.error(this.peek().span, `settlement ${name.name} needs a body in braces`);
|
|
414
|
-
return undefined;
|
|
415
|
-
}
|
|
416
|
-
const body = this.parseBlock();
|
|
417
|
-
if (!body)
|
|
418
|
-
return undefined;
|
|
419
|
-
return {
|
|
420
|
-
application: {
|
|
421
|
-
args: body.entries.map((row) => ({
|
|
422
|
-
kind: "binding",
|
|
423
|
-
name: row.key,
|
|
424
|
-
span: row.value.span,
|
|
425
|
-
type: row.value,
|
|
426
|
-
})),
|
|
427
|
-
callee: { kind: "path", parts: [archetype], span: archetype.span },
|
|
428
|
-
kind: "apply",
|
|
429
|
-
span: { start: archetype.span.start, end: body.span.end },
|
|
430
|
-
typeArgs: [],
|
|
431
|
-
},
|
|
432
|
-
exported: false,
|
|
433
|
-
kind: "instrument_apply",
|
|
434
|
-
name,
|
|
435
|
-
span: this.spanFrom(start.span),
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
parsePortDecl() {
|
|
439
|
-
const start = this.advance();
|
|
440
|
-
const name = this.expectIdent("the port needs a name, like: port confirm_handover { ... }");
|
|
441
|
-
if (!name)
|
|
442
|
-
return undefined;
|
|
443
|
-
if (!this.atPunct("{")) {
|
|
444
|
-
this.error(this.peek().span, `port ${name.name} needs a body in braces`);
|
|
445
|
-
return undefined;
|
|
446
|
-
}
|
|
447
|
-
const body = this.parseBlock();
|
|
448
|
-
if (!body)
|
|
449
|
-
return undefined;
|
|
450
|
-
return { body, kind: "port", name, span: this.spanFrom(start.span) };
|
|
451
|
-
}
|
|
452
|
-
// --- Entries and expressions ---------------------------------------------
|
|
453
|
-
/**
|
|
454
|
-
* Every recursive edge in the grammar passes through `parseExpr` or
|
|
455
|
-
* `parseBlock`: the list, block, call-argument, and binding-type branches of
|
|
456
|
-
* one, and the block to entry to block cycle of the other. Guarding those
|
|
457
|
-
* two entry points is therefore the whole ceiling, and the guard lives in
|
|
458
|
-
* one place instead of five.
|
|
459
|
-
*
|
|
460
|
-
* The budget counts those entries, not source levels, and the two are not
|
|
461
|
-
* the same number: `k { ... }` spends one per level, while `k: { ... }`
|
|
462
|
-
* spends two because the colon branch enters `parseExpr` and `parseExpr`
|
|
463
|
-
* then enters `parseBlock`. So the message names a budget rather than a
|
|
464
|
-
* depth. A builder told they exceeded "64 levels" would count 32 braces and
|
|
465
|
-
* have nothing to act on.
|
|
466
|
-
*/
|
|
467
|
-
enterNesting(span) {
|
|
468
|
-
if (this.depth >= HSX_LIMITS.maxNestingDepth) {
|
|
469
|
-
this.error(span, `this nests past the parser's depth budget of ${HSX_LIMITS.maxNestingDepth}; flatten it`);
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
this.depth += 1;
|
|
473
|
-
return true;
|
|
474
|
-
}
|
|
475
|
-
parseBlock(context = "general") {
|
|
476
|
-
if (!this.enterNesting(this.peek().span))
|
|
477
|
-
return undefined;
|
|
478
|
-
const block = this.parseBlockUnguarded(context);
|
|
479
|
-
this.depth -= 1;
|
|
480
|
-
return block;
|
|
481
|
-
}
|
|
482
|
-
parseBlockUnguarded(context) {
|
|
483
|
-
const open = this.advance();
|
|
484
|
-
const entries = [];
|
|
485
|
-
while (!this.atPunct("}") && !this.at("eof")) {
|
|
486
|
-
if (this.atPunct(";") || this.atPunct(",")) {
|
|
487
|
-
this.advance();
|
|
158
|
+
if (this.at("expose") || this.at("hide")) {
|
|
159
|
+
const kind = this.take().text;
|
|
160
|
+
const target = this.path();
|
|
161
|
+
let name;
|
|
162
|
+
if (kind === "expose") {
|
|
163
|
+
this.expect("as");
|
|
164
|
+
name = this.identifier();
|
|
165
|
+
}
|
|
166
|
+
program.decls.push(kind === "expose"
|
|
167
|
+
? { kind, target, name: name, span: this.span(start) }
|
|
168
|
+
: { kind, target, span: this.span(start) });
|
|
488
169
|
continue;
|
|
489
170
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
this.
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this.
|
|
171
|
+
if (this.eat("instrument")) {
|
|
172
|
+
const name = this.identifier();
|
|
173
|
+
const parameters = this.eat("(") ? this.entries(")") : [];
|
|
174
|
+
const body = this.block();
|
|
175
|
+
program.decls.push({
|
|
176
|
+
kind: "instrument",
|
|
177
|
+
name,
|
|
178
|
+
parameters,
|
|
179
|
+
body,
|
|
180
|
+
span: this.span(start),
|
|
181
|
+
});
|
|
500
182
|
continue;
|
|
501
183
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
184
|
+
if (this.eat("object")) {
|
|
185
|
+
const name = this.identifier();
|
|
186
|
+
const title = this.peek().kind === "string"
|
|
187
|
+
? JSON.parse(this.take().text)
|
|
188
|
+
: name;
|
|
189
|
+
const body = this.block();
|
|
190
|
+
program.decls.push({
|
|
191
|
+
kind: "object",
|
|
192
|
+
name,
|
|
193
|
+
title,
|
|
194
|
+
body,
|
|
195
|
+
span: this.span(start),
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const name = this.identifier();
|
|
200
|
+
this.expect("=");
|
|
201
|
+
const object = this.path();
|
|
202
|
+
const body = this.block();
|
|
203
|
+
program.decls.push({
|
|
204
|
+
kind: "assignment",
|
|
205
|
+
name,
|
|
206
|
+
target: object,
|
|
207
|
+
body,
|
|
208
|
+
span: this.span(start),
|
|
209
|
+
});
|
|
508
210
|
}
|
|
509
|
-
return
|
|
211
|
+
return program;
|
|
510
212
|
}
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
? this.expectName("for needs in before its finite bound")
|
|
533
|
-
: undefined;
|
|
534
|
-
if (!binding || inWord?.name !== "in") {
|
|
535
|
-
this.error(this.peek().span, "write `for item in <finite bound> { ... }`");
|
|
536
|
-
return undefined;
|
|
213
|
+
entries(end) {
|
|
214
|
+
const entries = [];
|
|
215
|
+
while (!this.eat(end)) {
|
|
216
|
+
this.separators();
|
|
217
|
+
if (this.eat(end))
|
|
218
|
+
break;
|
|
219
|
+
if (this.peek().kind === "eof")
|
|
220
|
+
this.fail(`unclosed block, expected ${end}`, `add ${end}`);
|
|
221
|
+
const start = this.peek().span.start;
|
|
222
|
+
if (this.eat("attach")) {
|
|
223
|
+
const name = this.identifier();
|
|
224
|
+
this.expect("=");
|
|
225
|
+
const target = this.path();
|
|
226
|
+
const body = this.block();
|
|
227
|
+
entries.push({
|
|
228
|
+
key: `attach ${name} = ${target}`,
|
|
229
|
+
value: body,
|
|
230
|
+
span: this.span(start),
|
|
231
|
+
});
|
|
232
|
+
this.separators();
|
|
233
|
+
continue;
|
|
537
234
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
235
|
+
if (this.eat("rename")) {
|
|
236
|
+
const body = this.block();
|
|
237
|
+
entries.push({
|
|
238
|
+
key: "rename",
|
|
239
|
+
value: body,
|
|
240
|
+
span: this.span(start),
|
|
241
|
+
});
|
|
242
|
+
this.separators();
|
|
243
|
+
continue;
|
|
542
244
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
qualifiers,
|
|
549
|
-
span: { start: key.span.start, end: value.span.end },
|
|
550
|
-
value,
|
|
245
|
+
if (this.eat("expose")) {
|
|
246
|
+
const action = this.identifier();
|
|
247
|
+
let publicName = action;
|
|
248
|
+
if (this.eat("as")) {
|
|
249
|
+
publicName = this.identifier();
|
|
551
250
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
251
|
+
entries.push({
|
|
252
|
+
key: "expose",
|
|
253
|
+
value: {
|
|
254
|
+
kind: "call",
|
|
255
|
+
name: action,
|
|
256
|
+
args: [{ kind: "name", value: publicName, span: this.span(start) }],
|
|
257
|
+
span: this.span(start),
|
|
258
|
+
},
|
|
259
|
+
span: this.span(start),
|
|
260
|
+
});
|
|
261
|
+
this.separators();
|
|
262
|
+
continue;
|
|
563
263
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
span: { start: key.span.start, end: value.span.end },
|
|
570
|
-
value,
|
|
264
|
+
let key = this.path();
|
|
265
|
+
if (key === "columns" && !this.at(":")) {
|
|
266
|
+
let value;
|
|
267
|
+
if (this.at("[")) {
|
|
268
|
+
value = this.atom();
|
|
571
269
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (context === "lifecycle" && key.name === "states") {
|
|
575
|
-
const items = [];
|
|
576
|
-
while (!this.atPunct(";") && !this.atPunct("}") && !this.at("eof")) {
|
|
577
|
-
const state = this.parseTemplatedName("expected a lifecycle state");
|
|
578
|
-
if (!state)
|
|
579
|
-
return undefined;
|
|
580
|
-
items.push(state);
|
|
581
|
-
}
|
|
582
|
-
const end = items.at(-1)?.span.end ?? key.span.end;
|
|
583
|
-
return {
|
|
584
|
-
key,
|
|
585
|
-
qualifiers,
|
|
586
|
-
span: { start: key.span.start, end },
|
|
587
|
-
value: { items, kind: "list", span: { start: key.span.end, end } },
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
if (context === "lifecycle" && key.name === "initial") {
|
|
591
|
-
const value = this.parseTemplatedName("initial needs one declared lifecycle state");
|
|
592
|
-
return value
|
|
593
|
-
? {
|
|
594
|
-
key,
|
|
595
|
-
qualifiers,
|
|
596
|
-
span: { start: key.span.start, end: value.span.end },
|
|
597
|
-
value,
|
|
270
|
+
else if (this.at("{")) {
|
|
271
|
+
value = this.block();
|
|
598
272
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return undefined;
|
|
610
|
-
}
|
|
611
|
-
const token = this.advance();
|
|
612
|
-
const value = {
|
|
613
|
-
kind: "string",
|
|
614
|
-
span: token.span,
|
|
615
|
-
value: token.value,
|
|
616
|
-
};
|
|
617
|
-
return {
|
|
618
|
-
key,
|
|
619
|
-
qualifiers,
|
|
620
|
-
span: { start: key.span.start, end: value.span.end },
|
|
621
|
-
value,
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
if (context === "lifecycle" && key.name === "on") {
|
|
625
|
-
const action = this.parseTemplatedName("on needs an action name");
|
|
626
|
-
if (!action)
|
|
627
|
-
return undefined;
|
|
628
|
-
qualifiers.push(action);
|
|
629
|
-
if (!this.expectPunct(":", `on ${action.name} needs : before its source states`)) {
|
|
630
|
-
return undefined;
|
|
631
|
-
}
|
|
632
|
-
while (!this.atPunct("->") && !this.at("eof")) {
|
|
633
|
-
const state = this.parseTemplatedName("expected a source lifecycle state");
|
|
634
|
-
if (!state)
|
|
635
|
-
return undefined;
|
|
636
|
-
qualifiers.push(state);
|
|
637
|
-
if (this.atPunct("|"))
|
|
638
|
-
this.advance();
|
|
273
|
+
else {
|
|
274
|
+
value = { kind: "list", items: [], span: this.span(start) };
|
|
275
|
+
}
|
|
276
|
+
entries.push({
|
|
277
|
+
key: "columns",
|
|
278
|
+
value,
|
|
279
|
+
span: this.span(start),
|
|
280
|
+
});
|
|
281
|
+
this.separators();
|
|
282
|
+
continue;
|
|
639
283
|
}
|
|
640
|
-
if (!this.
|
|
641
|
-
|
|
284
|
+
if (key === "action" && !this.at(":"))
|
|
285
|
+
key += " " + this.identifier();
|
|
286
|
+
if (key === "economics" && !this.at(":") && !this.at("{"))
|
|
287
|
+
key += " " + this.path();
|
|
288
|
+
if (key === "when") {
|
|
289
|
+
const tunable = this.identifier();
|
|
290
|
+
const relation = this.eat("has") ? "has" : "is";
|
|
291
|
+
if (relation === "is")
|
|
292
|
+
this.expect("is");
|
|
293
|
+
const choice = this.identifier();
|
|
294
|
+
entries.push({
|
|
295
|
+
key: `when ${tunable} ${relation} ${choice}`,
|
|
296
|
+
value: this.block(),
|
|
297
|
+
span: this.span(start),
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
642
300
|
}
|
|
643
|
-
const
|
|
644
|
-
|
|
301
|
+
const clause = ["requires", "invariants", "moves"].includes(key);
|
|
302
|
+
const value = clause && !this.at(":") && !this.at("{")
|
|
645
303
|
? {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
span:
|
|
649
|
-
value,
|
|
304
|
+
kind: "list",
|
|
305
|
+
items: [key === "moves" ? this.move() : this.requirement()],
|
|
306
|
+
span: this.span(start),
|
|
650
307
|
}
|
|
651
|
-
:
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
? this.expectName("notify needs via before its channel")
|
|
657
|
-
: undefined;
|
|
658
|
-
const channel = via
|
|
659
|
-
? this.expectName("notify needs a channel")
|
|
660
|
-
: undefined;
|
|
661
|
-
if (!role || via?.name !== "via" || !channel)
|
|
662
|
-
return undefined;
|
|
663
|
-
qualifiers.push(role, channel);
|
|
664
|
-
return {
|
|
665
|
-
key,
|
|
666
|
-
qualifiers,
|
|
667
|
-
span: { start: key.span.start, end: channel.span.end },
|
|
668
|
-
value: { kind: "boolean", span: channel.span, value: true },
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
if (this.atPunct("(")) {
|
|
672
|
-
this.advance();
|
|
673
|
-
while (!this.atPunct(")") && !this.at("eof")) {
|
|
674
|
-
const qualifier = this.at("number")
|
|
675
|
-
? this.parseIdent()
|
|
676
|
-
: this.expectIdent(`expected a qualifier inside ${key.name}( ... )`);
|
|
677
|
-
if (!qualifier)
|
|
678
|
-
return undefined;
|
|
679
|
-
qualifiers.push(qualifier);
|
|
680
|
-
if (this.atPunct(","))
|
|
681
|
-
this.advance();
|
|
682
|
-
}
|
|
683
|
-
if (!this.expectPunct(")", `${key.name}( ... ) never closes`)) {
|
|
684
|
-
return undefined;
|
|
685
|
-
}
|
|
308
|
+
: this.at("{")
|
|
309
|
+
? this.block()
|
|
310
|
+
: (this.expect(":"), this.expression());
|
|
311
|
+
entries.push({ key, value, span: this.span(start) });
|
|
312
|
+
this.separators();
|
|
686
313
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
value = this.parseBlock(key.name === "lifecycle" || context === "lifecycle"
|
|
695
|
-
? "lifecycle"
|
|
696
|
-
: "general");
|
|
697
|
-
}
|
|
698
|
-
else if (this.atPunct(":")) {
|
|
699
|
-
this.advance();
|
|
700
|
-
value = this.atPunct("{")
|
|
701
|
-
? this.parseBlock(key.name === "lifecycle" || context === "lifecycle"
|
|
702
|
-
? "lifecycle"
|
|
703
|
-
: "general")
|
|
704
|
-
: this.parseExpr();
|
|
705
|
-
}
|
|
706
|
-
else if (this.canStartExpr(this.peek())) {
|
|
707
|
-
value = this.parseExpr();
|
|
708
|
-
}
|
|
709
|
-
else {
|
|
710
|
-
this.error(this.peek().span, `entry ${key.name} needs a value: either "${key.name}: <value>" or "${key.name} { ... }"`);
|
|
711
|
-
return undefined;
|
|
712
|
-
}
|
|
713
|
-
if (!value)
|
|
714
|
-
return undefined;
|
|
314
|
+
return entries;
|
|
315
|
+
}
|
|
316
|
+
node(value) {
|
|
317
|
+
return { kind: "text", value, span: this.peek().span };
|
|
318
|
+
}
|
|
319
|
+
record(values) {
|
|
320
|
+
const span = this.peek().span;
|
|
715
321
|
return {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
322
|
+
kind: "block",
|
|
323
|
+
entries: Object.entries(values).map(([key, value]) => ({
|
|
324
|
+
key,
|
|
325
|
+
value,
|
|
326
|
+
span: value.span,
|
|
327
|
+
})),
|
|
328
|
+
span,
|
|
720
329
|
};
|
|
721
330
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
331
|
+
operand() {
|
|
332
|
+
const value = this.atom();
|
|
333
|
+
return this.record({
|
|
334
|
+
[value.kind === "name" &&
|
|
335
|
+
/^(self|input|party|subject)\./.test(value.value)
|
|
336
|
+
? "field"
|
|
337
|
+
: "literal"]: value,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
operator() {
|
|
341
|
+
const operator = this.take();
|
|
342
|
+
if (!["==", "!=", "<", "<=", ">", ">="].includes(operator.text))
|
|
343
|
+
this.fail("expected a comparison", "write ==, !=, <, <=, >, or >=", operator.span);
|
|
344
|
+
return { kind: "text", value: operator.text, span: operator.span };
|
|
345
|
+
}
|
|
346
|
+
requirement() {
|
|
347
|
+
if (this.eat("unique")) {
|
|
348
|
+
const namespace = this.atom();
|
|
349
|
+
this.expect("on");
|
|
350
|
+
return this.record({
|
|
351
|
+
kind: this.node("unique"),
|
|
352
|
+
namespace,
|
|
353
|
+
fields: this.atom(),
|
|
354
|
+
});
|
|
734
355
|
}
|
|
735
|
-
if (
|
|
736
|
-
this.
|
|
737
|
-
|
|
356
|
+
if (this.at("count") || this.at("sum")) {
|
|
357
|
+
const measure = this.eat("count")
|
|
358
|
+
? this.node("count")
|
|
359
|
+
: (this.expect("sum"), this.record({ sum: this.atom() }));
|
|
360
|
+
this.expect("of");
|
|
361
|
+
const selection = this.block();
|
|
362
|
+
const operator = this.operator();
|
|
363
|
+
const value = this.operand();
|
|
364
|
+
return this.record({
|
|
365
|
+
kind: this.node("aggregate"),
|
|
366
|
+
selection,
|
|
367
|
+
measure,
|
|
368
|
+
operator,
|
|
369
|
+
value,
|
|
370
|
+
});
|
|
738
371
|
}
|
|
739
|
-
if (
|
|
740
|
-
this.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
return {
|
|
372
|
+
if (this.eat("hours")) {
|
|
373
|
+
const at = this.atom();
|
|
374
|
+
this.expect("between");
|
|
375
|
+
const start = this.atom();
|
|
376
|
+
this.expect("and");
|
|
377
|
+
const end = this.atom();
|
|
378
|
+
this.expect("timezone");
|
|
379
|
+
return this.record({
|
|
380
|
+
kind: this.node("hours"),
|
|
381
|
+
at,
|
|
382
|
+
start,
|
|
383
|
+
end,
|
|
384
|
+
timezone: this.atom(),
|
|
385
|
+
});
|
|
747
386
|
}
|
|
748
|
-
if (this.
|
|
749
|
-
const
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
this.advance();
|
|
758
|
-
}
|
|
759
|
-
if (!this.expectPunct("]", "this list never closes; add the matching ]")) {
|
|
760
|
-
return undefined;
|
|
387
|
+
if (this.eat("evidence")) {
|
|
388
|
+
const subject = this.atom();
|
|
389
|
+
const values = {
|
|
390
|
+
kind: this.node("evidence"),
|
|
391
|
+
subject,
|
|
392
|
+
};
|
|
393
|
+
for (const key of ["family", "check", "result", "maxAge"]) {
|
|
394
|
+
this.expect(key);
|
|
395
|
+
values[key] = this.atom();
|
|
761
396
|
}
|
|
762
|
-
|
|
397
|
+
if (this.eat("instruction"))
|
|
398
|
+
values.instruction = this.atom();
|
|
399
|
+
return this.record(values);
|
|
400
|
+
}
|
|
401
|
+
const left = this.operand();
|
|
402
|
+
if (this.eat("in")) {
|
|
403
|
+
const reference = left.entries.find((entry) => entry.key === "field")?.value;
|
|
404
|
+
if (!reference)
|
|
405
|
+
this.fail("state requirement needs a reference", "write requires self.record in [state]");
|
|
406
|
+
return this.record({
|
|
407
|
+
kind: this.node("state"),
|
|
408
|
+
reference,
|
|
409
|
+
states: this.atom(),
|
|
410
|
+
});
|
|
763
411
|
}
|
|
764
|
-
|
|
765
|
-
|
|
412
|
+
const operator = this.operator();
|
|
413
|
+
return this.record({
|
|
414
|
+
kind: this.node("compare"),
|
|
415
|
+
left,
|
|
416
|
+
operator,
|
|
417
|
+
right: this.operand(),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
move() {
|
|
421
|
+
const values = {};
|
|
422
|
+
if (this.at("post") || this.at("void")) {
|
|
423
|
+
values.operation = this.node("internal_transfer." + this.take().text);
|
|
424
|
+
values.transfer = this.atom();
|
|
766
425
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (this.
|
|
774
|
-
this.
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
779
|
-
let deadline;
|
|
780
|
-
if (this.atPunct("|")) {
|
|
781
|
-
this.advance();
|
|
782
|
-
if (!this.at("ident") || this.peek().text !== "at") {
|
|
783
|
-
this.error(this.peek().span, "after | comes the date the platform decides on, like: | at(releaseDueAt)");
|
|
784
|
-
return undefined;
|
|
785
|
-
}
|
|
786
|
-
this.advance();
|
|
787
|
-
if (!this.expectPunct("(", "at names its date field in parentheses: at(releaseDueAt)")) {
|
|
788
|
-
return undefined;
|
|
789
|
-
}
|
|
790
|
-
deadline = this.expectIdent("expected a stored date field inside at( ... ), like at(releaseDueAt)");
|
|
791
|
-
if (!deadline)
|
|
792
|
-
return undefined;
|
|
793
|
-
if (!this.expectPunct(")", "at( ... ) never closes"))
|
|
794
|
-
return undefined;
|
|
426
|
+
else {
|
|
427
|
+
if (this.eat("reserve"))
|
|
428
|
+
values.operation = this.node("internal_transfer.reserve");
|
|
429
|
+
values.amount = this.atom();
|
|
430
|
+
this.expect("from");
|
|
431
|
+
values.from = this.atom();
|
|
432
|
+
if (this.eat("to"))
|
|
433
|
+
values.to = this.atom();
|
|
434
|
+
else {
|
|
435
|
+
this.expect("shares");
|
|
436
|
+
values.shares = this.atom();
|
|
795
437
|
}
|
|
796
|
-
return {
|
|
797
|
-
...(deadline ? { deadline } : {}),
|
|
798
|
-
kind: "port_ref",
|
|
799
|
-
name,
|
|
800
|
-
span: this.spanFrom(start.span),
|
|
801
|
-
...(within ? { within } : {}),
|
|
802
|
-
};
|
|
803
438
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
439
|
+
for (const key of ["fee", "capture", "key"])
|
|
440
|
+
if (this.eat(key))
|
|
441
|
+
values[key] = this.atom();
|
|
442
|
+
if (this.eat("economics"))
|
|
443
|
+
values.economics = this.block();
|
|
444
|
+
if (this.eat("boundary"))
|
|
445
|
+
values.boundary = this.record({ adapter: this.atom() });
|
|
446
|
+
return this.record(values);
|
|
447
|
+
}
|
|
448
|
+
block() {
|
|
449
|
+
const start = this.peek().span.start;
|
|
450
|
+
this.expect("{");
|
|
451
|
+
if (++this.depth > 32)
|
|
452
|
+
this.fail("block nesting exceeds 32", "reduce nested blocks");
|
|
453
|
+
const entries = this.entries("}");
|
|
454
|
+
this.depth--;
|
|
455
|
+
return { kind: "block", entries, span: this.span(start) };
|
|
456
|
+
}
|
|
457
|
+
expression() {
|
|
458
|
+
if (++this.depth > 32)
|
|
459
|
+
this.fail("expression nesting exceeds 32", "reduce nested values");
|
|
460
|
+
const start = this.peek().span.start;
|
|
461
|
+
let value = this.atom();
|
|
462
|
+
if (this.eat("cap"))
|
|
463
|
+
value = {
|
|
464
|
+
kind: "capped",
|
|
465
|
+
rate: value,
|
|
466
|
+
cap: this.atom(),
|
|
467
|
+
span: this.span(start),
|
|
817
468
|
};
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
this.
|
|
840
|
-
const member = this.expectIdent(`expected the exit after ${ident.name}., like ${ident.name}.release`);
|
|
841
|
-
if (!member)
|
|
842
|
-
return undefined;
|
|
843
|
-
return {
|
|
844
|
-
kind: "settlement_ref",
|
|
845
|
-
member,
|
|
846
|
-
owner: ident,
|
|
847
|
-
span: { end: member.span.end, start: ident.span.start },
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
if (this.atPunct("<")) {
|
|
851
|
-
const args = this.parseAngleArguments();
|
|
852
|
-
if (!args)
|
|
853
|
-
return undefined;
|
|
854
|
-
return {
|
|
855
|
-
args,
|
|
856
|
-
callee: ident,
|
|
857
|
-
kind: "type_apply",
|
|
858
|
-
span: this.spanFrom(ident.span),
|
|
859
|
-
};
|
|
469
|
+
if (this.eat("="))
|
|
470
|
+
value = {
|
|
471
|
+
kind: "default",
|
|
472
|
+
type: value,
|
|
473
|
+
value: this.expression(),
|
|
474
|
+
span: this.span(start),
|
|
475
|
+
};
|
|
476
|
+
this.depth--;
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
atom() {
|
|
480
|
+
const token = this.peek();
|
|
481
|
+
const start = token.span.start;
|
|
482
|
+
if (this.at("{"))
|
|
483
|
+
return this.block();
|
|
484
|
+
if (this.eat("[")) {
|
|
485
|
+
const items = [];
|
|
486
|
+
while (!this.eat("]")) {
|
|
487
|
+
if (this.peek().kind === "eof")
|
|
488
|
+
this.fail("unclosed list", "add ]");
|
|
489
|
+
items.push(this.expression());
|
|
490
|
+
this.separators();
|
|
860
491
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
if (!arg)
|
|
867
|
-
return undefined;
|
|
868
|
-
args.push(arg);
|
|
869
|
-
if (this.atPunct(","))
|
|
870
|
-
this.advance();
|
|
871
|
-
}
|
|
872
|
-
if (!this.expectPunct(")", `${ident.name}( ... ) never closes`)) {
|
|
873
|
-
return undefined;
|
|
874
|
-
}
|
|
492
|
+
return { kind: "list", items, span: this.span(start) };
|
|
493
|
+
}
|
|
494
|
+
if (token.kind === "number") {
|
|
495
|
+
this.take();
|
|
496
|
+
if (token.text.endsWith("%"))
|
|
875
497
|
return {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
span: this.spanFrom(ident.span),
|
|
498
|
+
kind: "percent",
|
|
499
|
+
value: token.text.slice(0, -1),
|
|
500
|
+
span: token.span,
|
|
880
501
|
};
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
if (!type)
|
|
886
|
-
return undefined;
|
|
502
|
+
if (/[a-z]$/.test(token.text))
|
|
503
|
+
return { kind: "duration", value: token.text, span: token.span };
|
|
504
|
+
if (/^[A-Z]{3}$/.test(this.peek().text)) {
|
|
505
|
+
const currency = this.take().text;
|
|
887
506
|
return {
|
|
888
|
-
kind: "
|
|
889
|
-
|
|
890
|
-
span:
|
|
891
|
-
type,
|
|
507
|
+
kind: "money",
|
|
508
|
+
value: token.text + " " + currency,
|
|
509
|
+
span: this.span(start),
|
|
892
510
|
};
|
|
893
511
|
}
|
|
894
|
-
return
|
|
895
|
-
}
|
|
896
|
-
this.error(token.span, token.kind === "eof"
|
|
897
|
-
? "the program ends where a value was expected"
|
|
898
|
-
: `"${token.text}" is not a value HSX understands here`);
|
|
899
|
-
return undefined;
|
|
900
|
-
}
|
|
901
|
-
// --- Recovery ------------------------------------------------------------
|
|
902
|
-
recoverToDecl() {
|
|
903
|
-
while (!this.at("eof")) {
|
|
904
|
-
const token = this.peek();
|
|
905
|
-
if (token.kind === "keyword" && token.text !== "from")
|
|
906
|
-
return;
|
|
907
|
-
this.advance();
|
|
512
|
+
return { kind: "number", value: token.text, span: token.span };
|
|
908
513
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (token.text === "}" || token.text === "]" || token.text === ")") {
|
|
919
|
-
if (depth === 0)
|
|
920
|
-
return;
|
|
921
|
-
depth -= 1;
|
|
922
|
-
}
|
|
923
|
-
if (token.text === "," && depth === 0) {
|
|
924
|
-
this.advance();
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
if (token.kind === "keyword" && depth === 0)
|
|
929
|
-
return;
|
|
930
|
-
this.advance();
|
|
514
|
+
if (token.kind === "string" || token.kind === "date") {
|
|
515
|
+
this.take();
|
|
516
|
+
return {
|
|
517
|
+
kind: token.kind === "string" ? "text" : "date",
|
|
518
|
+
value: token.kind === "string"
|
|
519
|
+
? JSON.parse(token.text)
|
|
520
|
+
: token.text,
|
|
521
|
+
span: token.span,
|
|
522
|
+
};
|
|
931
523
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
return this.tokens[Math.min(this.index + offset, this.tokens.length - 1)];
|
|
939
|
-
}
|
|
940
|
-
previous() {
|
|
941
|
-
return this.tokens[Math.max(0, this.index - 1)];
|
|
942
|
-
}
|
|
943
|
-
advance() {
|
|
944
|
-
const token = this.peek();
|
|
945
|
-
if (token.kind !== "eof")
|
|
946
|
-
this.index += 1;
|
|
947
|
-
return token;
|
|
948
|
-
}
|
|
949
|
-
at(kind) {
|
|
950
|
-
return this.peek().kind === kind;
|
|
951
|
-
}
|
|
952
|
-
atPunct(glyph) {
|
|
953
|
-
const token = this.peek();
|
|
954
|
-
return token.kind === "punct" && token.text === glyph;
|
|
955
|
-
}
|
|
956
|
-
parseIdent() {
|
|
957
|
-
const token = this.advance();
|
|
958
|
-
return { kind: "ident", name: token.text, span: token.span };
|
|
959
|
-
}
|
|
960
|
-
isName(token) {
|
|
961
|
-
return token.kind === "ident" || token.kind === "keyword";
|
|
962
|
-
}
|
|
963
|
-
expectName(message) {
|
|
964
|
-
if (this.isName(this.peek()))
|
|
965
|
-
return this.parseIdent();
|
|
966
|
-
this.error(this.peek().span, message);
|
|
967
|
-
return undefined;
|
|
968
|
-
}
|
|
969
|
-
parseTemplatedName(message) {
|
|
970
|
-
if (this.atPunct("[")) {
|
|
971
|
-
const open = this.advance();
|
|
972
|
-
const binding = this.expectIdent("an indexed name needs its loop binding");
|
|
973
|
-
if (!binding || !this.expectPunct("]", "an indexed name needs ]")) {
|
|
974
|
-
return undefined;
|
|
524
|
+
const name = this.path();
|
|
525
|
+
if (this.eat("(")) {
|
|
526
|
+
const args = [];
|
|
527
|
+
while (!this.eat(")")) {
|
|
528
|
+
args.push(this.expression());
|
|
529
|
+
this.separators();
|
|
975
530
|
}
|
|
976
|
-
|
|
531
|
+
return { kind: "call", name, args, span: this.span(start) };
|
|
532
|
+
}
|
|
533
|
+
let target;
|
|
534
|
+
let owner;
|
|
535
|
+
if (["ref", "list"].includes(name) && this.eat("<")) {
|
|
536
|
+
target = this.path();
|
|
537
|
+
this.expect(">");
|
|
538
|
+
}
|
|
539
|
+
const many = name === "ref" && this.eat("[");
|
|
540
|
+
if (many)
|
|
541
|
+
this.expect("]");
|
|
542
|
+
if (this.eat("of"))
|
|
543
|
+
owner = this.path();
|
|
544
|
+
const optional = this.eat("?");
|
|
545
|
+
if (target || owner || optional || many)
|
|
977
546
|
return {
|
|
978
|
-
kind: "
|
|
979
|
-
name
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
},
|
|
547
|
+
kind: "type",
|
|
548
|
+
name,
|
|
549
|
+
...(target ? { target } : {}),
|
|
550
|
+
...(owner ? { owner } : {}),
|
|
551
|
+
optional,
|
|
552
|
+
...(many ? { many: true } : {}),
|
|
553
|
+
span: this.span(start),
|
|
984
554
|
};
|
|
985
|
-
}
|
|
986
|
-
const base = this.expectName(message);
|
|
987
|
-
if (!base || !this.atPunct("["))
|
|
988
|
-
return base;
|
|
989
|
-
this.advance();
|
|
990
|
-
const binding = this.expectIdent("an indexed name needs its loop binding");
|
|
991
|
-
if (!binding || !this.expectPunct("]", "an indexed name needs ]")) {
|
|
992
|
-
return undefined;
|
|
993
|
-
}
|
|
994
|
-
const suffix = this.isName(this.peek()) ? this.parseIdent() : undefined;
|
|
995
|
-
return {
|
|
996
|
-
kind: "ident",
|
|
997
|
-
name: `${base.name}[${binding.name}]${suffix?.name ?? ""}`,
|
|
998
|
-
span: {
|
|
999
|
-
start: base.span.start,
|
|
1000
|
-
end: suffix?.span.end ?? this.previous().span.end,
|
|
1001
|
-
},
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
canStartExpr(token) {
|
|
1005
|
-
return (token.kind === "ident" ||
|
|
1006
|
-
token.kind === "keyword" ||
|
|
1007
|
-
token.kind === "number" ||
|
|
1008
|
-
token.kind === "percent" ||
|
|
1009
|
-
token.kind === "string" ||
|
|
1010
|
-
(token.kind === "punct" && (token.text === "{" || token.text === "[")));
|
|
1011
|
-
}
|
|
1012
|
-
parsePath(message) {
|
|
1013
|
-
const first = this.expectName(message);
|
|
1014
|
-
if (!first)
|
|
1015
|
-
return undefined;
|
|
1016
|
-
const parts = [first];
|
|
1017
|
-
while (this.atPunct(".")) {
|
|
1018
|
-
this.advance();
|
|
1019
|
-
const part = this.expectName("expected a name after .");
|
|
1020
|
-
if (!part)
|
|
1021
|
-
return undefined;
|
|
1022
|
-
parts.push(part);
|
|
1023
|
-
}
|
|
1024
|
-
return {
|
|
1025
|
-
kind: "path",
|
|
1026
|
-
parts,
|
|
1027
|
-
span: {
|
|
1028
|
-
start: first.span.start,
|
|
1029
|
-
end: parts.at(-1)?.span.end ?? first.span.end,
|
|
1030
|
-
},
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
parseAngleArguments() {
|
|
1034
|
-
this.advance();
|
|
1035
|
-
const args = [];
|
|
1036
|
-
while (!this.atPunct(">") && !this.at("eof")) {
|
|
1037
|
-
const arg = this.parseExpr();
|
|
1038
|
-
if (!arg)
|
|
1039
|
-
return undefined;
|
|
1040
|
-
args.push(arg);
|
|
1041
|
-
if (this.atPunct(","))
|
|
1042
|
-
this.advance();
|
|
1043
|
-
}
|
|
1044
|
-
return this.expectPunct(">", "the type argument list never closes")
|
|
1045
|
-
? args
|
|
1046
|
-
: undefined;
|
|
555
|
+
return { kind: "name", value: name, span: this.span(start) };
|
|
1047
556
|
}
|
|
1048
|
-
parseApplication() {
|
|
1049
|
-
const callee = this.parsePath("expected an instrument function name");
|
|
1050
|
-
if (!callee)
|
|
1051
|
-
return undefined;
|
|
1052
|
-
const typeArgs = this.atPunct("<") ? this.parseAngleArguments() : [];
|
|
1053
|
-
if (!typeArgs)
|
|
1054
|
-
return undefined;
|
|
1055
|
-
if (!this.expectPunct("(", "an instrument application needs arguments in parentheses")) {
|
|
1056
|
-
return undefined;
|
|
1057
|
-
}
|
|
1058
|
-
const args = [];
|
|
1059
|
-
while (!this.atPunct(")") && !this.at("eof")) {
|
|
1060
|
-
const arg = this.parseExpr();
|
|
1061
|
-
if (!arg)
|
|
1062
|
-
return undefined;
|
|
1063
|
-
args.push(arg);
|
|
1064
|
-
if (this.atPunct(","))
|
|
1065
|
-
this.advance();
|
|
1066
|
-
}
|
|
1067
|
-
if (!this.expectPunct(")", "the instrument argument list never closes")) {
|
|
1068
|
-
return undefined;
|
|
1069
|
-
}
|
|
1070
|
-
return {
|
|
1071
|
-
args,
|
|
1072
|
-
callee,
|
|
1073
|
-
kind: "apply",
|
|
1074
|
-
span: { start: callee.span.start, end: this.previous().span.end },
|
|
1075
|
-
typeArgs,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
expectIdent(message) {
|
|
1079
|
-
if (this.at("ident"))
|
|
1080
|
-
return this.parseIdent();
|
|
1081
|
-
this.error(this.peek().span, message);
|
|
1082
|
-
return undefined;
|
|
1083
|
-
}
|
|
1084
|
-
expectPunct(glyph, message) {
|
|
1085
|
-
if (this.atPunct(glyph)) {
|
|
1086
|
-
this.advance();
|
|
1087
|
-
return true;
|
|
1088
|
-
}
|
|
1089
|
-
this.error(this.peek().span, message);
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
spanFrom(start) {
|
|
1093
|
-
return { end: this.previous().span.end, start: start.start };
|
|
1094
|
-
}
|
|
1095
|
-
error(span, message) {
|
|
1096
|
-
this.diagnostics.push({ message, span });
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Convert a percent literal's raw digits to exact basis points, or refuse
|
|
1101
|
-
* when the literal is finer than a basis point. "99.5" -> 9950; "0.05" -> 5.
|
|
1102
|
-
*/
|
|
1103
|
-
export function percentToBps(raw) {
|
|
1104
|
-
const [whole = "0", fraction = ""] = raw.split(".");
|
|
1105
|
-
if (fraction.length > 2)
|
|
1106
|
-
return undefined;
|
|
1107
|
-
const scaled = fraction.padEnd(2, "0");
|
|
1108
|
-
return Number(whole) * 100 + Number(scaled === "" ? "0" : scaled);
|
|
1109
557
|
}
|
|
1110
558
|
//# sourceMappingURL=parse.js.map
|