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