@hyperscale0/hsx 1.0.0-beta.1 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -9
- package/LICENSING.md +3 -3
- package/README.md +37 -149
- package/TRADEMARKS.md +4 -4
- package/dist/src/ast.d.ts +129 -10
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/cli.d.ts +2 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +148 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +41 -4
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +82 -15
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +78 -0
- package/dist/src/cost.d.ts.map +1 -0
- package/dist/src/cost.js +369 -0
- package/dist/src/cost.js.map +1 -0
- package/dist/src/diagnostics.d.ts +11 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +420 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/emit.d.ts +35 -0
- package/dist/src/emit.d.ts.map +1 -0
- package/dist/src/emit.js +359 -0
- package/dist/src/emit.js.map +1 -0
- package/dist/src/entry-overrides.d.ts +17 -3
- package/dist/src/entry-overrides.d.ts.map +1 -1
- package/dist/src/entry-overrides.js +180 -35
- package/dist/src/entry-overrides.js.map +1 -1
- package/dist/src/format.d.ts +10 -0
- package/dist/src/format.d.ts.map +1 -0
- package/dist/src/format.js +183 -0
- package/dist/src/format.js.map +1 -0
- package/dist/src/index.d.ts +10 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/ir.d.ts +66 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +15 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/lex.d.ts +9 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +50 -4
- package/dist/src/lex.js.map +1 -1
- package/dist/src/limits.d.ts +2 -3
- package/dist/src/limits.d.ts.map +1 -1
- package/dist/src/limits.js +2 -3
- package/dist/src/limits.js.map +1 -1
- package/dist/src/modules.d.ts +36 -0
- package/dist/src/modules.d.ts.map +1 -0
- package/dist/src/modules.js +372 -0
- package/dist/src/modules.js.map +1 -0
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +590 -17
- package/dist/src/parse.js.map +1 -1
- package/dist/src/typecheck.d.ts +8 -0
- package/dist/src/typecheck.d.ts.map +1 -0
- package/dist/src/typecheck.js +2958 -0
- package/dist/src/typecheck.js.map +1 -0
- package/dist/src/version.d.ts +4 -15
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +4 -15
- package/dist/src/version.js.map +1 -1
- package/docs/README.md +23 -0
- package/docs/guide/01-first-program.md +24 -0
- package/docs/guide/02-money.md +25 -0
- package/docs/guide/03-instruments.md +22 -0
- package/docs/guide/04-lifecycles.md +25 -0
- package/docs/guide/05-fees-and-splits.md +45 -0
- package/docs/guide/06-schedules.md +22 -0
- package/docs/guide/07-composition.md +46 -0
- package/docs/guide/08-writing-a-module.md +26 -0
- package/docs/guide/09-cost.md +24 -0
- package/docs/guide/10-diagnostics.md +27 -0
- package/docs/llms-full.txt +1738 -0
- package/docs/llms.txt +45 -0
- package/docs/reference/cli.md +33 -0
- package/docs/reference/diagnostics.md +619 -0
- package/docs/reference/grammar.md +39 -0
- package/docs/reference/std/advance.md +35 -0
- package/docs/reference/std/cancellable_booking.md +35 -0
- package/docs/reference/std/captured_payment.md +43 -0
- package/docs/reference/std/conditional_disbursement.md +33 -0
- package/docs/reference/std/credit_facility.md +35 -0
- package/docs/reference/std/held_payment.md +64 -0
- package/docs/reference/std/instant_transfer.md +30 -0
- package/docs/reference/std/metered.md +27 -0
- package/docs/reference/std/pooled_split.md +26 -0
- package/docs/reference/std/premium_forward.md +39 -0
- package/docs/reference/std/reconciled_payout.md +33 -0
- package/docs/reference/std/recurring_collection.md +25 -0
- package/docs/reference/std/rotating_pool.md +42 -0
- package/docs/reference/std/scheduled.md +51 -0
- package/docs/reference/std/security_deposit.md +46 -0
- package/docs/reference/std/settlement_batch.md +42 -0
- package/docs/reference/std/swap.md +46 -0
- package/docs/reference/std/threshold_pool.md +47 -0
- package/docs/reference/std/weighted_distribution.md +39 -0
- package/docs/reference/types.md +20 -0
- package/docs/reference/udl-output.md +14 -0
- package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
- package/docs/sessions/2026-09-02-two-instruments.md +41 -0
- package/examples/01-first-program/README.md +45 -0
- package/examples/01-first-program/tip-jar.hsx +17 -0
- package/examples/02-imports-and-archetypes/README.md +99 -0
- package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
- package/examples/03-diagnostics/README.md +68 -0
- package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
- package/examples/03-diagnostics/corner-shop.hsx +19 -0
- package/examples/04-complete-product/README.md +87 -0
- package/examples/04-complete-product/study-hall.hsx +67 -0
- package/examples/05-watch-club/README.md +9 -0
- package/examples/05-watch-club/watch-club.hsx +124 -0
- package/examples/README.md +18 -0
- package/examples/advance/README.md +3 -0
- package/examples/advance/advance.hsx +13 -0
- package/examples/advance/advance.udl +339 -0
- package/examples/cancellable_booking/README.md +3 -0
- package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
- package/examples/cancellable_booking/cancellable_booking.udl +391 -0
- package/examples/captured_payment/README.md +3 -0
- package/examples/captured_payment/captured_payment.hsx +21 -0
- package/examples/captured_payment/captured_payment.udl +677 -0
- package/examples/conditional_disbursement/README.md +3 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
- package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
- package/examples/cost-table.json +386 -0
- package/examples/credit_facility/README.md +3 -0
- package/examples/credit_facility/credit_facility.hsx +27 -0
- package/examples/credit_facility/credit_facility.udl +1373 -0
- package/examples/held_payment/README.md +3 -0
- package/examples/held_payment/held_payment.hsx +13 -0
- package/examples/held_payment/held_payment.udl +507 -0
- package/examples/instant_transfer/README.md +3 -0
- package/examples/instant_transfer/instant_transfer.hsx +16 -0
- package/examples/instant_transfer/instant_transfer.udl +364 -0
- package/examples/metered/README.md +3 -0
- package/examples/metered/metered.hsx +13 -0
- package/examples/metered/metered.udl +247 -0
- package/examples/pooled_split/README.md +3 -0
- package/examples/pooled_split/pooled_split.hsx +15 -0
- package/examples/pooled_split/pooled_split.udl +425 -0
- package/examples/premium_forward/README.md +3 -0
- package/examples/premium_forward/premium_forward.hsx +19 -0
- package/examples/premium_forward/premium_forward.udl +594 -0
- package/examples/reconciled_payout/README.md +3 -0
- package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
- package/examples/reconciled_payout/reconciled_payout.udl +303 -0
- package/examples/recurring_collection/README.md +3 -0
- package/examples/recurring_collection/recurring_collection.hsx +21 -0
- package/examples/recurring_collection/recurring_collection.udl +1130 -0
- package/examples/rotating_pool/README.md +3 -0
- package/examples/rotating_pool/rotating_pool.hsx +18 -0
- package/examples/rotating_pool/rotating_pool.udl +4352 -0
- package/examples/scheduled/README.md +3 -0
- package/examples/scheduled/scheduled.hsx +12 -0
- package/examples/scheduled/scheduled.udl +335 -0
- package/examples/security_deposit/README.md +3 -0
- package/examples/security_deposit/security_deposit.hsx +21 -0
- package/examples/security_deposit/security_deposit.udl +293 -0
- package/examples/settlement_batch/README.md +3 -0
- package/examples/settlement_batch/settlement_batch.hsx +19 -0
- package/examples/settlement_batch/settlement_batch.udl +753 -0
- package/examples/swap/README.md +3 -0
- package/examples/swap/swap.hsx +19 -0
- package/examples/swap/swap.udl +876 -0
- package/examples/threshold_pool/README.md +3 -0
- package/examples/threshold_pool/threshold_pool.hsx +16 -0
- package/examples/threshold_pool/threshold_pool.udl +667 -0
- package/examples/weighted_distribution/README.md +3 -0
- package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
- package/examples/weighted_distribution/weighted_distribution.udl +337 -0
- package/package.json +19 -6
- package/skills/hsx/SKILL.md +483 -0
- package/src/ast.ts +163 -9
- package/src/cli.ts +164 -12
- package/src/compile.ts +160 -20
- package/src/cost.ts +579 -0
- package/src/diagnostics.ts +442 -0
- package/src/emit.ts +447 -0
- package/src/entry-overrides.ts +263 -48
- package/src/format.ts +261 -0
- package/src/index.ts +32 -4
- package/src/ir.ts +99 -0
- package/src/lex.ts +59 -4
- package/src/limits.ts +2 -3
- package/src/modules.ts +444 -0
- package/src/parse.ts +655 -21
- package/src/typecheck.ts +3876 -0
- package/src/version.ts +4 -16
- package/std/SEMANTICS.md +130 -0
- package/std/settlements/advance.hsx +177 -0
- package/std/settlements/cancellable_booking.hsx +87 -0
- package/std/settlements/captured_payment.hsx +144 -0
- package/std/settlements/conditional_disbursement.hsx +185 -0
- package/std/settlements/credit_facility.hsx +167 -0
- package/std/settlements/held_payment.hsx +578 -0
- package/std/settlements/index.hsx +3 -0
- package/std/settlements/instant_transfer.hsx +190 -0
- package/std/settlements/metered.hsx +67 -0
- package/std/settlements/pooled_split.hsx +68 -0
- package/std/settlements/premium_forward.hsx +257 -0
- package/std/settlements/reconciled_payout.hsx +77 -0
- package/std/settlements/recurring_collection.hsx +27 -0
- package/std/settlements/rotating_pool.hsx +252 -0
- package/std/settlements/scheduled.hsx +641 -0
- package/std/settlements/security_deposit.hsx +166 -0
- package/std/settlements/settlement_batch.hsx +122 -0
- package/std/settlements/swap.hsx +1114 -0
- package/std/settlements/threshold_pool.hsx +221 -0
- package/std/settlements/weighted_distribution.hsx +141 -0
- package/dist/src/archetypes.d.ts +0 -17
- package/dist/src/archetypes.d.ts.map +0 -1
- package/dist/src/archetypes.js +0 -327
- package/dist/src/archetypes.js.map +0 -1
- package/dist/src/check.d.ts +0 -12
- package/dist/src/check.d.ts.map +0 -1
- package/dist/src/check.js +0 -1910
- package/dist/src/check.js.map +0 -1
- package/dist/src/lower.d.ts +0 -146
- package/dist/src/lower.d.ts.map +0 -1
- package/dist/src/lower.js +0 -4570
- package/dist/src/lower.js.map +0 -1
- package/dist/src/model.d.ts +0 -467
- package/dist/src/model.d.ts.map +0 -1
- package/dist/src/model.js +0 -15
- package/dist/src/model.js.map +0 -1
- package/spec/hsx-ir.schema.json +0 -920
- package/src/archetypes.ts +0 -340
- package/src/check.ts +0 -2938
- package/src/lower.ts +0 -5416
- package/src/model.ts +0 -519
package/src/parse.ts
CHANGED
|
@@ -7,21 +7,31 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type {
|
|
10
|
+
ApplyExpr,
|
|
10
11
|
AssetDecl,
|
|
11
12
|
BlockExpr,
|
|
13
|
+
ConstDecl,
|
|
12
14
|
Decl,
|
|
13
15
|
Diagnostic,
|
|
14
16
|
Entry,
|
|
15
17
|
Expr,
|
|
18
|
+
ExposeDecl,
|
|
16
19
|
IdentExpr,
|
|
17
20
|
ImportDecl,
|
|
21
|
+
InstrumentApplyDecl,
|
|
22
|
+
InstrumentDecl,
|
|
23
|
+
ModuleDecl,
|
|
24
|
+
Parameter,
|
|
18
25
|
PartyDecl,
|
|
19
26
|
PortDecl,
|
|
20
27
|
Program,
|
|
21
28
|
ProgramDecl,
|
|
22
|
-
SettlementDecl,
|
|
23
29
|
Span,
|
|
24
30
|
StringExpr,
|
|
31
|
+
SubjectDecl,
|
|
32
|
+
TypeDecl,
|
|
33
|
+
TypeParameter,
|
|
34
|
+
UseDecl,
|
|
25
35
|
} from "./ast.ts";
|
|
26
36
|
import { lex, type Token } from "./lex.ts";
|
|
27
37
|
import { HSX_LIMITS } from "./limits.ts";
|
|
@@ -31,6 +41,8 @@ export interface ParseResult {
|
|
|
31
41
|
readonly program: Program;
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
type BlockContext = "general" | "lifecycle";
|
|
45
|
+
|
|
34
46
|
export function parseProgram(source: string): ParseResult {
|
|
35
47
|
// Measured before lexing. The ceiling counts UTF-8 bytes, so the encode is
|
|
36
48
|
// the measurement rather than an extra step: `source.length` counts UTF-16
|
|
@@ -48,6 +60,21 @@ export function parseProgram(source: string): ParseResult {
|
|
|
48
60
|
};
|
|
49
61
|
}
|
|
50
62
|
|
|
63
|
+
const first = source.search(/\S/);
|
|
64
|
+
if (first >= 0 && source[first] === "{") {
|
|
65
|
+
return {
|
|
66
|
+
diagnostics: [
|
|
67
|
+
{
|
|
68
|
+
code: "HSX1014",
|
|
69
|
+
fix: "replace the JSON object with HSX declarations",
|
|
70
|
+
message: "JSON is not HSX",
|
|
71
|
+
span: { end: first + 1, start: first },
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
program: { decls: [], span: { end: source.length, start: 0 } },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
51
78
|
const lexed = lex(source);
|
|
52
79
|
const parser = new Parser(lexed.tokens, [...lexed.diagnostics]);
|
|
53
80
|
const program = parser.parseProgram(source.length);
|
|
@@ -68,6 +95,10 @@ class Parser {
|
|
|
68
95
|
parseProgram(sourceLength: number): Program {
|
|
69
96
|
const decls: Decl[] = [];
|
|
70
97
|
while (!this.at("eof")) {
|
|
98
|
+
if (this.atPunct(";")) {
|
|
99
|
+
this.advance();
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
71
102
|
const decl = this.parseDecl();
|
|
72
103
|
if (decl) {
|
|
73
104
|
decls.push(decl);
|
|
@@ -80,30 +111,47 @@ class Parser {
|
|
|
80
111
|
|
|
81
112
|
// --- Declarations --------------------------------------------------------
|
|
82
113
|
|
|
83
|
-
private parseDecl(): Decl | undefined {
|
|
114
|
+
private parseDecl(exported = false): Decl | undefined {
|
|
84
115
|
const token = this.peek();
|
|
85
116
|
if (token.kind !== "keyword" || token.text === "from") {
|
|
86
117
|
this.error(
|
|
87
118
|
token.span,
|
|
88
119
|
token.kind === "eof"
|
|
89
120
|
? "the program ends unexpectedly"
|
|
90
|
-
: `expected a declaration (program, import, party, asset, settlement, or port), found "${token.text}"`,
|
|
121
|
+
: `expected a declaration (module, program, use, expose, import, party, asset, subject, instrument, settlement, type, const, or port), found "${token.text}"`,
|
|
91
122
|
);
|
|
92
123
|
return undefined;
|
|
93
124
|
}
|
|
94
125
|
switch (token.text) {
|
|
126
|
+
case "export":
|
|
127
|
+
this.advance();
|
|
128
|
+
return this.parseDecl(true);
|
|
129
|
+
case "module":
|
|
130
|
+
return this.parseModuleDecl(exported);
|
|
95
131
|
case "program":
|
|
96
132
|
return this.parseProgramDecl();
|
|
133
|
+
case "use":
|
|
134
|
+
return this.parseUseDecl();
|
|
135
|
+
case "expose":
|
|
136
|
+
return this.parseExposeDecl();
|
|
97
137
|
case "import":
|
|
98
138
|
return this.parseImportDecl();
|
|
99
139
|
case "party":
|
|
100
140
|
return this.parsePartyDecl();
|
|
101
141
|
case "asset":
|
|
102
142
|
return this.parseAssetDecl();
|
|
143
|
+
case "subject":
|
|
144
|
+
return this.parseSubjectDecl(exported);
|
|
145
|
+
case "instrument":
|
|
146
|
+
return this.parseInstrumentDecl(exported);
|
|
103
147
|
case "settlement":
|
|
104
148
|
return this.parseSettlementDecl();
|
|
105
149
|
case "port":
|
|
106
150
|
return this.parsePortDecl();
|
|
151
|
+
case "type":
|
|
152
|
+
return this.parseTypeDecl(exported);
|
|
153
|
+
case "const":
|
|
154
|
+
return this.parseConstDecl(exported);
|
|
107
155
|
default:
|
|
108
156
|
this.error(token.span, `"${token.text}" cannot start a declaration`);
|
|
109
157
|
this.advance();
|
|
@@ -111,6 +159,142 @@ class Parser {
|
|
|
111
159
|
}
|
|
112
160
|
}
|
|
113
161
|
|
|
162
|
+
private parseModuleDecl(exported: boolean): ModuleDecl | undefined {
|
|
163
|
+
const start = this.advance();
|
|
164
|
+
if (exported) {
|
|
165
|
+
this.error(start.span, "a module declaration cannot be exported");
|
|
166
|
+
}
|
|
167
|
+
const name = this.parsePath("the module needs a dotted name");
|
|
168
|
+
return name
|
|
169
|
+
? { kind: "module", name, span: this.spanFrom(start.span) }
|
|
170
|
+
: undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private parseInstrumentDecl(
|
|
174
|
+
exported: boolean,
|
|
175
|
+
): InstrumentDecl | InstrumentApplyDecl | undefined {
|
|
176
|
+
const start = this.advance();
|
|
177
|
+
const name = this.expectIdent("the instrument needs a snake_case name");
|
|
178
|
+
if (!name) return undefined;
|
|
179
|
+
const typeParameters = this.parseTypeParameters();
|
|
180
|
+
if (!typeParameters) return undefined;
|
|
181
|
+
const hasParameterList = this.atPunct("(");
|
|
182
|
+
const parameters = hasParameterList ? this.parseParameters() : [];
|
|
183
|
+
if (!parameters) return undefined;
|
|
184
|
+
if (this.atPunct("=")) {
|
|
185
|
+
this.advance();
|
|
186
|
+
const application = this.parseApplication();
|
|
187
|
+
if (!application) return undefined;
|
|
188
|
+
const metadata = this.atPunct("{") ? this.parseBlock() : undefined;
|
|
189
|
+
return {
|
|
190
|
+
application,
|
|
191
|
+
exported,
|
|
192
|
+
kind: "instrument_apply",
|
|
193
|
+
...(metadata ? { metadata } : {}),
|
|
194
|
+
name,
|
|
195
|
+
span: this.spanFrom(start.span),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (!this.atPunct("{")) {
|
|
199
|
+
this.error(
|
|
200
|
+
this.peek().span,
|
|
201
|
+
`instrument ${name.name} needs a body or an exported instrument application`,
|
|
202
|
+
);
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
const body = this.parseBlock();
|
|
206
|
+
if (!body) return undefined;
|
|
207
|
+
return {
|
|
208
|
+
body,
|
|
209
|
+
exported,
|
|
210
|
+
hasParameterList,
|
|
211
|
+
kind: "instrument",
|
|
212
|
+
name,
|
|
213
|
+
parameters,
|
|
214
|
+
span: this.spanFrom(start.span),
|
|
215
|
+
typeParameters,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private parseTypeDecl(exported: boolean): TypeDecl | undefined {
|
|
220
|
+
const start = this.advance();
|
|
221
|
+
const name = this.expectIdent("the type needs a name");
|
|
222
|
+
if (!name) return undefined;
|
|
223
|
+
if (
|
|
224
|
+
!this.expectPunct("=", `type ${name.name} needs = before its definition`)
|
|
225
|
+
) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
const value = this.parseExpr();
|
|
229
|
+
return value
|
|
230
|
+
? { exported, kind: "type", name, span: this.spanFrom(start.span), value }
|
|
231
|
+
: undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private parseConstDecl(exported: boolean): ConstDecl | undefined {
|
|
235
|
+
const start = this.advance();
|
|
236
|
+
const name = this.expectIdent("the constant needs a name");
|
|
237
|
+
if (!name) return undefined;
|
|
238
|
+
let type: Expr | undefined;
|
|
239
|
+
if (this.atPunct(":")) {
|
|
240
|
+
this.advance();
|
|
241
|
+
type = this.parseExpr();
|
|
242
|
+
if (!type) return undefined;
|
|
243
|
+
}
|
|
244
|
+
if (!this.expectPunct("=", `const ${name.name} needs = before its value`)) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
const value = this.parseExpr();
|
|
248
|
+
return value
|
|
249
|
+
? {
|
|
250
|
+
exported,
|
|
251
|
+
kind: "const",
|
|
252
|
+
name,
|
|
253
|
+
span: this.spanFrom(start.span),
|
|
254
|
+
...(type ? { type } : {}),
|
|
255
|
+
value,
|
|
256
|
+
}
|
|
257
|
+
: undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private parseTypeParameters(): TypeParameter[] | undefined {
|
|
261
|
+
if (!this.atPunct("<")) return [];
|
|
262
|
+
this.advance();
|
|
263
|
+
const parameters: TypeParameter[] = [];
|
|
264
|
+
while (!this.atPunct(">") && !this.at("eof")) {
|
|
265
|
+
const name = this.expectIdent("expected a type parameter");
|
|
266
|
+
if (!name) return undefined;
|
|
267
|
+
parameters.push({ name, span: name.span });
|
|
268
|
+
if (this.atPunct(",")) this.advance();
|
|
269
|
+
}
|
|
270
|
+
return this.expectPunct(">", "the type parameter list never closes")
|
|
271
|
+
? parameters
|
|
272
|
+
: undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private parseParameters(): Parameter[] | undefined {
|
|
276
|
+
this.advance();
|
|
277
|
+
const parameters: Parameter[] = [];
|
|
278
|
+
while (!this.atPunct(")") && !this.at("eof")) {
|
|
279
|
+
const name = this.expectIdent("expected a parameter name");
|
|
280
|
+
if (!name) return undefined;
|
|
281
|
+
if (!this.expectPunct(":", `parameter ${name.name} needs a type`)) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
const type = this.parseExpr();
|
|
285
|
+
if (!type) return undefined;
|
|
286
|
+
parameters.push({
|
|
287
|
+
name,
|
|
288
|
+
span: { start: name.span.start, end: type.span.end },
|
|
289
|
+
type,
|
|
290
|
+
});
|
|
291
|
+
if (this.atPunct(",")) this.advance();
|
|
292
|
+
}
|
|
293
|
+
return this.expectPunct(")", "the parameter list never closes")
|
|
294
|
+
? parameters
|
|
295
|
+
: undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
114
298
|
private parseProgramDecl(): ProgramDecl | undefined {
|
|
115
299
|
const start = this.advance();
|
|
116
300
|
const name = this.expectIdent("the program needs a snake_case name");
|
|
@@ -128,12 +312,62 @@ class Parser {
|
|
|
128
312
|
};
|
|
129
313
|
}
|
|
130
314
|
|
|
315
|
+
private parseUseDecl(): UseDecl | undefined {
|
|
316
|
+
const start = this.advance();
|
|
317
|
+
const instrument = this.expectIdent(
|
|
318
|
+
"use needs a published instrument id, like: use held_settlement",
|
|
319
|
+
);
|
|
320
|
+
return instrument
|
|
321
|
+
? { instrument, kind: "use", span: this.spanFrom(start.span) }
|
|
322
|
+
: undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private parseExposeDecl(): ExposeDecl | undefined {
|
|
326
|
+
const start = this.advance();
|
|
327
|
+
const instrument = this.expectIdent(
|
|
328
|
+
"expose needs an instrument action, like: expose held_settlement.release as releaseFunds",
|
|
329
|
+
);
|
|
330
|
+
if (!instrument) return undefined;
|
|
331
|
+
if (
|
|
332
|
+
!this.expectPunct(
|
|
333
|
+
".",
|
|
334
|
+
"expose separates the instrument and action with .",
|
|
335
|
+
)
|
|
336
|
+
) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const action = this.expectName(
|
|
340
|
+
"expose needs an action after the instrument id",
|
|
341
|
+
);
|
|
342
|
+
if (!action) return undefined;
|
|
343
|
+
if (!this.at("keyword") || this.peek().text !== "as") {
|
|
344
|
+
this.error(
|
|
345
|
+
this.peek().span,
|
|
346
|
+
"expose needs as before its public action name",
|
|
347
|
+
);
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
this.advance();
|
|
351
|
+
const publicName = this.expectIdent(
|
|
352
|
+
"expose needs a camelCase public action name",
|
|
353
|
+
);
|
|
354
|
+
return publicName
|
|
355
|
+
? {
|
|
356
|
+
action,
|
|
357
|
+
instrument,
|
|
358
|
+
kind: "expose",
|
|
359
|
+
publicName,
|
|
360
|
+
span: this.spanFrom(start.span),
|
|
361
|
+
}
|
|
362
|
+
: undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
131
365
|
private parseImportDecl(): ImportDecl | undefined {
|
|
132
366
|
const start = this.advance();
|
|
133
367
|
if (
|
|
134
368
|
!this.expectPunct(
|
|
135
369
|
"{",
|
|
136
|
-
'import lists its names in braces: import { held_payment } from "
|
|
370
|
+
'import lists its names in braces: import { held_payment } from "std/settlements"',
|
|
137
371
|
)
|
|
138
372
|
) {
|
|
139
373
|
return undefined;
|
|
@@ -151,7 +385,7 @@ class Parser {
|
|
|
151
385
|
if (!this.at("keyword") || this.peek().text !== "from") {
|
|
152
386
|
this.error(
|
|
153
387
|
this.peek().span,
|
|
154
|
-
'import needs from: import { held_payment } from "
|
|
388
|
+
'import needs from: import { held_payment } from "std/settlements"',
|
|
155
389
|
);
|
|
156
390
|
return undefined;
|
|
157
391
|
}
|
|
@@ -159,7 +393,7 @@ class Parser {
|
|
|
159
393
|
if (!this.at("string")) {
|
|
160
394
|
this.error(
|
|
161
395
|
this.peek().span,
|
|
162
|
-
'the import source must be a quoted module name like "
|
|
396
|
+
'the import source must be a quoted module name like "std/settlements"',
|
|
163
397
|
);
|
|
164
398
|
return undefined;
|
|
165
399
|
}
|
|
@@ -235,7 +469,24 @@ class Parser {
|
|
|
235
469
|
};
|
|
236
470
|
}
|
|
237
471
|
|
|
238
|
-
private
|
|
472
|
+
private parseSubjectDecl(exported: boolean): SubjectDecl | undefined {
|
|
473
|
+
const start = this.advance();
|
|
474
|
+
if (exported) {
|
|
475
|
+
this.error(start.span, "a subject declaration cannot be exported");
|
|
476
|
+
}
|
|
477
|
+
const name = this.expectIdent("the subject needs a snake_case kind name");
|
|
478
|
+
if (!name) return undefined;
|
|
479
|
+
if (!this.atPunct("{")) {
|
|
480
|
+
this.error(this.peek().span, `subject ${name.name} needs a body`);
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
const body = this.parseBlock();
|
|
484
|
+
return body
|
|
485
|
+
? { body, kind: "subject", name, span: this.spanFrom(start.span) }
|
|
486
|
+
: undefined;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private parseSettlementDecl(): InstrumentApplyDecl | undefined {
|
|
239
490
|
const start = this.advance();
|
|
240
491
|
const name = this.expectIdent(
|
|
241
492
|
"the settlement needs a name, like: settlement sale = held_payment { ... }",
|
|
@@ -263,9 +514,20 @@ class Parser {
|
|
|
263
514
|
const body = this.parseBlock();
|
|
264
515
|
if (!body) return undefined;
|
|
265
516
|
return {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
517
|
+
application: {
|
|
518
|
+
args: body.entries.map((row) => ({
|
|
519
|
+
kind: "binding" as const,
|
|
520
|
+
name: row.key,
|
|
521
|
+
span: row.value.span,
|
|
522
|
+
type: row.value,
|
|
523
|
+
})),
|
|
524
|
+
callee: { kind: "path", parts: [archetype], span: archetype.span },
|
|
525
|
+
kind: "apply",
|
|
526
|
+
span: { start: archetype.span.start, end: body.span.end },
|
|
527
|
+
typeArgs: [],
|
|
528
|
+
},
|
|
529
|
+
exported: false,
|
|
530
|
+
kind: "instrument_apply",
|
|
269
531
|
name,
|
|
270
532
|
span: this.spanFrom(start.span),
|
|
271
533
|
};
|
|
@@ -314,19 +576,23 @@ class Parser {
|
|
|
314
576
|
return true;
|
|
315
577
|
}
|
|
316
578
|
|
|
317
|
-
private parseBlock(): BlockExpr | undefined {
|
|
579
|
+
private parseBlock(context: BlockContext = "general"): BlockExpr | undefined {
|
|
318
580
|
if (!this.enterNesting(this.peek().span)) return undefined;
|
|
319
|
-
const block = this.parseBlockUnguarded();
|
|
581
|
+
const block = this.parseBlockUnguarded(context);
|
|
320
582
|
this.depth -= 1;
|
|
321
583
|
return block;
|
|
322
584
|
}
|
|
323
585
|
|
|
324
|
-
private parseBlockUnguarded(): BlockExpr | undefined {
|
|
586
|
+
private parseBlockUnguarded(context: BlockContext): BlockExpr | undefined {
|
|
325
587
|
const open = this.advance();
|
|
326
588
|
const entries: Entry[] = [];
|
|
327
589
|
while (!this.atPunct("}") && !this.at("eof")) {
|
|
590
|
+
if (this.atPunct(";") || this.atPunct(",")) {
|
|
591
|
+
this.advance();
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
328
594
|
const before = this.index;
|
|
329
|
-
const entry = this.parseEntry();
|
|
595
|
+
const entry = this.parseEntry(context);
|
|
330
596
|
if (!entry) {
|
|
331
597
|
this.recoverInBlock();
|
|
332
598
|
// recoverInBlock bails without consuming when it lands on a depth-0
|
|
@@ -337,7 +603,7 @@ class Parser {
|
|
|
337
603
|
continue;
|
|
338
604
|
}
|
|
339
605
|
entries.push(entry);
|
|
340
|
-
if (this.atPunct(",")) this.advance();
|
|
606
|
+
if (this.atPunct(",") || this.atPunct(";")) this.advance();
|
|
341
607
|
}
|
|
342
608
|
if (!this.expectPunct("}", "this block never closes; add the matching }")) {
|
|
343
609
|
return { entries, kind: "block", span: this.spanFrom(open.span) };
|
|
@@ -345,17 +611,193 @@ class Parser {
|
|
|
345
611
|
return { entries, kind: "block", span: this.spanFrom(open.span) };
|
|
346
612
|
}
|
|
347
613
|
|
|
348
|
-
private parseEntry(): Entry | undefined {
|
|
349
|
-
const
|
|
614
|
+
private parseEntry(context: BlockContext): Entry | undefined {
|
|
615
|
+
const token = this.peek();
|
|
616
|
+
const key =
|
|
617
|
+
token.kind === "string"
|
|
618
|
+
? (() => {
|
|
619
|
+
this.advance();
|
|
620
|
+
return {
|
|
621
|
+
kind: "ident" as const,
|
|
622
|
+
name: token.value,
|
|
623
|
+
quoted: !token.value.includes("["),
|
|
624
|
+
span: token.span,
|
|
625
|
+
};
|
|
626
|
+
})()
|
|
627
|
+
: this.isName(token) && (token.text === "action" || token.text === "on")
|
|
628
|
+
? this.parseIdent()
|
|
629
|
+
: this.parseTemplatedName("expected an entry name here");
|
|
350
630
|
if (!key) return undefined;
|
|
351
631
|
|
|
352
632
|
const qualifiers: IdentExpr[] = [];
|
|
633
|
+
if (key.name === "for") {
|
|
634
|
+
const binding = this.expectIdent("for needs a compile-time binding name");
|
|
635
|
+
const inWord = binding
|
|
636
|
+
? this.expectName("for needs in before its finite bound")
|
|
637
|
+
: undefined;
|
|
638
|
+
if (!binding || inWord?.name !== "in") {
|
|
639
|
+
this.error(
|
|
640
|
+
this.peek().span,
|
|
641
|
+
"write `for item in <finite bound> { ... }`",
|
|
642
|
+
);
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
const bound = this.parseExpr();
|
|
646
|
+
if (!bound || !this.atPunct("{")) {
|
|
647
|
+
this.error(
|
|
648
|
+
this.peek().span,
|
|
649
|
+
"a bounded comprehension needs a body in braces",
|
|
650
|
+
);
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
const value = this.parseBlock(context);
|
|
654
|
+
return value
|
|
655
|
+
? {
|
|
656
|
+
iteration: { binding, bound },
|
|
657
|
+
key,
|
|
658
|
+
qualifiers,
|
|
659
|
+
span: { start: key.span.start, end: value.span.end },
|
|
660
|
+
value,
|
|
661
|
+
}
|
|
662
|
+
: undefined;
|
|
663
|
+
}
|
|
664
|
+
if (
|
|
665
|
+
key.name === "action" &&
|
|
666
|
+
(this.isName(this.peek()) || this.atPunct("["))
|
|
667
|
+
) {
|
|
668
|
+
const action = this.parseTemplatedName("action needs a name");
|
|
669
|
+
if (!action) return undefined;
|
|
670
|
+
qualifiers.push(action);
|
|
671
|
+
if (!this.atPunct("{")) {
|
|
672
|
+
this.error(
|
|
673
|
+
this.peek().span,
|
|
674
|
+
`action ${qualifiers[0]?.name ?? ""} needs a body`,
|
|
675
|
+
);
|
|
676
|
+
return undefined;
|
|
677
|
+
}
|
|
678
|
+
const value = this.parseBlock();
|
|
679
|
+
return value
|
|
680
|
+
? {
|
|
681
|
+
key,
|
|
682
|
+
qualifiers,
|
|
683
|
+
span: { start: key.span.start, end: value.span.end },
|
|
684
|
+
value,
|
|
685
|
+
}
|
|
686
|
+
: undefined;
|
|
687
|
+
}
|
|
688
|
+
if (context === "lifecycle" && key.name === "states") {
|
|
689
|
+
const items: Expr[] = [];
|
|
690
|
+
while (!this.atPunct(";") && !this.atPunct("}") && !this.at("eof")) {
|
|
691
|
+
const state = this.parseTemplatedName("expected a lifecycle state");
|
|
692
|
+
if (!state) return undefined;
|
|
693
|
+
items.push(state);
|
|
694
|
+
}
|
|
695
|
+
const end = items.at(-1)?.span.end ?? key.span.end;
|
|
696
|
+
return {
|
|
697
|
+
key,
|
|
698
|
+
qualifiers,
|
|
699
|
+
span: { start: key.span.start, end },
|
|
700
|
+
value: { items, kind: "list", span: { start: key.span.end, end } },
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (context === "lifecycle" && key.name === "initial") {
|
|
704
|
+
const value = this.parseTemplatedName(
|
|
705
|
+
"initial needs one declared lifecycle state",
|
|
706
|
+
);
|
|
707
|
+
return value
|
|
708
|
+
? {
|
|
709
|
+
key,
|
|
710
|
+
qualifiers,
|
|
711
|
+
span: { start: key.span.start, end: value.span.end },
|
|
712
|
+
value,
|
|
713
|
+
}
|
|
714
|
+
: undefined;
|
|
715
|
+
}
|
|
716
|
+
if (context === "lifecycle" && key.name === "parked") {
|
|
717
|
+
const state = this.parseTemplatedName("parked needs a lifecycle state");
|
|
718
|
+
if (!state) return undefined;
|
|
719
|
+
qualifiers.push(state);
|
|
720
|
+
const reasonWord = this.expectName('parked needs reason "..."');
|
|
721
|
+
if (!reasonWord || reasonWord.name !== "reason" || !this.at("string")) {
|
|
722
|
+
this.error(this.peek().span, 'parked needs reason "..."');
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
const token = this.advance();
|
|
726
|
+
const value: StringExpr = {
|
|
727
|
+
kind: "string",
|
|
728
|
+
span: token.span,
|
|
729
|
+
value: token.value,
|
|
730
|
+
};
|
|
731
|
+
return {
|
|
732
|
+
key,
|
|
733
|
+
qualifiers,
|
|
734
|
+
span: { start: key.span.start, end: value.span.end },
|
|
735
|
+
value,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
if (context === "lifecycle" && key.name === "on") {
|
|
739
|
+
const action = this.parseTemplatedName("on needs an action name");
|
|
740
|
+
if (!action) return undefined;
|
|
741
|
+
qualifiers.push(action);
|
|
742
|
+
if (
|
|
743
|
+
!this.expectPunct(
|
|
744
|
+
":",
|
|
745
|
+
`on ${action.name} needs : before its source states`,
|
|
746
|
+
)
|
|
747
|
+
) {
|
|
748
|
+
return undefined;
|
|
749
|
+
}
|
|
750
|
+
while (!this.atPunct("->") && !this.at("eof")) {
|
|
751
|
+
const state = this.parseTemplatedName(
|
|
752
|
+
"expected a source lifecycle state",
|
|
753
|
+
);
|
|
754
|
+
if (!state) return undefined;
|
|
755
|
+
qualifiers.push(state);
|
|
756
|
+
if (this.atPunct("|")) this.advance();
|
|
757
|
+
}
|
|
758
|
+
if (
|
|
759
|
+
!this.expectPunct(
|
|
760
|
+
"->",
|
|
761
|
+
`on ${action.name} needs -> before its target state`,
|
|
762
|
+
)
|
|
763
|
+
) {
|
|
764
|
+
return undefined;
|
|
765
|
+
}
|
|
766
|
+
const value = this.parseTemplatedName(
|
|
767
|
+
"expected a target lifecycle state",
|
|
768
|
+
);
|
|
769
|
+
return value
|
|
770
|
+
? {
|
|
771
|
+
key,
|
|
772
|
+
qualifiers,
|
|
773
|
+
span: { start: key.span.start, end: value.span.end },
|
|
774
|
+
value,
|
|
775
|
+
}
|
|
776
|
+
: undefined;
|
|
777
|
+
}
|
|
778
|
+
if (key.name === "notify") {
|
|
779
|
+
const role = this.expectName("notify needs a declared role");
|
|
780
|
+
const via = role
|
|
781
|
+
? this.expectName("notify needs via before its channel")
|
|
782
|
+
: undefined;
|
|
783
|
+
const channel = via
|
|
784
|
+
? this.expectName("notify needs a channel")
|
|
785
|
+
: undefined;
|
|
786
|
+
if (!role || via?.name !== "via" || !channel) return undefined;
|
|
787
|
+
qualifiers.push(role, channel);
|
|
788
|
+
return {
|
|
789
|
+
key,
|
|
790
|
+
qualifiers,
|
|
791
|
+
span: { start: key.span.start, end: channel.span.end },
|
|
792
|
+
value: { kind: "boolean", span: channel.span, value: true },
|
|
793
|
+
};
|
|
794
|
+
}
|
|
353
795
|
if (this.atPunct("(")) {
|
|
354
796
|
this.advance();
|
|
355
797
|
while (!this.atPunct(")") && !this.at("eof")) {
|
|
356
|
-
const qualifier = this.
|
|
357
|
-
|
|
358
|
-
|
|
798
|
+
const qualifier = this.at("number")
|
|
799
|
+
? this.parseIdent()
|
|
800
|
+
: this.expectIdent(`expected a qualifier inside ${key.name}( ... )`);
|
|
359
801
|
if (!qualifier) return undefined;
|
|
360
802
|
qualifiers.push(qualifier);
|
|
361
803
|
if (this.atPunct(",")) this.advance();
|
|
@@ -365,11 +807,30 @@ class Parser {
|
|
|
365
807
|
}
|
|
366
808
|
}
|
|
367
809
|
|
|
810
|
+
if (
|
|
811
|
+
(key.name === "requires" || key.name === "computes") &&
|
|
812
|
+
this.isName(this.peek())
|
|
813
|
+
) {
|
|
814
|
+
while (this.isName(this.peek())) qualifiers.push(this.parseIdent());
|
|
815
|
+
}
|
|
816
|
+
|
|
368
817
|
let value: Expr | undefined;
|
|
369
818
|
if (this.atPunct("{")) {
|
|
370
|
-
value = this.parseBlock(
|
|
819
|
+
value = this.parseBlock(
|
|
820
|
+
key.name === "lifecycle" || context === "lifecycle"
|
|
821
|
+
? "lifecycle"
|
|
822
|
+
: "general",
|
|
823
|
+
);
|
|
371
824
|
} else if (this.atPunct(":")) {
|
|
372
825
|
this.advance();
|
|
826
|
+
value = this.atPunct("{")
|
|
827
|
+
? this.parseBlock(
|
|
828
|
+
key.name === "lifecycle" || context === "lifecycle"
|
|
829
|
+
? "lifecycle"
|
|
830
|
+
: "general",
|
|
831
|
+
)
|
|
832
|
+
: this.parseExpr();
|
|
833
|
+
} else if (this.canStartExpr(this.peek())) {
|
|
373
834
|
value = this.parseExpr();
|
|
374
835
|
} else {
|
|
375
836
|
this.error(
|
|
@@ -483,8 +944,41 @@ class Parser {
|
|
|
483
944
|
...(within ? { within } : {}),
|
|
484
945
|
};
|
|
485
946
|
}
|
|
947
|
+
if (token.kind === "keyword") {
|
|
948
|
+
return this.parseIdent();
|
|
949
|
+
}
|
|
950
|
+
if (
|
|
951
|
+
token.kind === "ident" &&
|
|
952
|
+
/^[A-Z]{3}$/.test(token.text) &&
|
|
953
|
+
this.peekAt(1).kind === "number"
|
|
954
|
+
) {
|
|
955
|
+
const currency = this.parseIdent();
|
|
956
|
+
const amount = this.advance();
|
|
957
|
+
return {
|
|
958
|
+
currency,
|
|
959
|
+
kind: "money",
|
|
960
|
+
raw: amount.value,
|
|
961
|
+
span: { start: currency.span.start, end: amount.span.end },
|
|
962
|
+
};
|
|
963
|
+
}
|
|
486
964
|
if (token.kind === "ident") {
|
|
487
965
|
const ident = this.parseIdent();
|
|
966
|
+
if (ident.name === "true" || ident.name === "false") {
|
|
967
|
+
return {
|
|
968
|
+
kind: "boolean",
|
|
969
|
+
span: ident.span,
|
|
970
|
+
value: ident.name === "true",
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
if (ident.name === "decided" && this.atPunct("{")) {
|
|
974
|
+
const body = this.parseBlock();
|
|
975
|
+
if (!body) return undefined;
|
|
976
|
+
return {
|
|
977
|
+
body,
|
|
978
|
+
kind: "decided_amount",
|
|
979
|
+
span: { end: body.span.end, start: ident.span.start },
|
|
980
|
+
};
|
|
981
|
+
}
|
|
488
982
|
if (this.atPunct(".")) {
|
|
489
983
|
this.advance();
|
|
490
984
|
const member = this.expectIdent(
|
|
@@ -498,6 +992,16 @@ class Parser {
|
|
|
498
992
|
span: { end: member.span.end, start: ident.span.start },
|
|
499
993
|
};
|
|
500
994
|
}
|
|
995
|
+
if (this.atPunct("<")) {
|
|
996
|
+
const args = this.parseAngleArguments();
|
|
997
|
+
if (!args) return undefined;
|
|
998
|
+
return {
|
|
999
|
+
args,
|
|
1000
|
+
callee: ident,
|
|
1001
|
+
kind: "type_apply",
|
|
1002
|
+
span: this.spanFrom(ident.span),
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
501
1005
|
if (this.atPunct("(")) {
|
|
502
1006
|
this.advance();
|
|
503
1007
|
const args: Expr[] = [];
|
|
@@ -578,6 +1082,12 @@ class Parser {
|
|
|
578
1082
|
return this.tokens[this.index] as Token;
|
|
579
1083
|
}
|
|
580
1084
|
|
|
1085
|
+
private peekAt(offset: number): Token {
|
|
1086
|
+
return this.tokens[
|
|
1087
|
+
Math.min(this.index + offset, this.tokens.length - 1)
|
|
1088
|
+
] as Token;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
581
1091
|
private previous(): Token {
|
|
582
1092
|
return this.tokens[Math.max(0, this.index - 1)] as Token;
|
|
583
1093
|
}
|
|
@@ -602,6 +1112,130 @@ class Parser {
|
|
|
602
1112
|
return { kind: "ident", name: token.text, span: token.span };
|
|
603
1113
|
}
|
|
604
1114
|
|
|
1115
|
+
private isName(token: Token): boolean {
|
|
1116
|
+
return token.kind === "ident" || token.kind === "keyword";
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private expectName(message: string): IdentExpr | undefined {
|
|
1120
|
+
if (this.isName(this.peek())) return this.parseIdent();
|
|
1121
|
+
this.error(this.peek().span, message);
|
|
1122
|
+
return undefined;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
private parseTemplatedName(message: string): IdentExpr | undefined {
|
|
1126
|
+
if (this.atPunct("[")) {
|
|
1127
|
+
const open = this.advance();
|
|
1128
|
+
const binding = this.expectIdent(
|
|
1129
|
+
"an indexed name needs its loop binding",
|
|
1130
|
+
);
|
|
1131
|
+
if (!binding || !this.expectPunct("]", "an indexed name needs ]")) {
|
|
1132
|
+
return undefined;
|
|
1133
|
+
}
|
|
1134
|
+
const suffix = this.isName(this.peek()) ? this.parseIdent() : undefined;
|
|
1135
|
+
return {
|
|
1136
|
+
kind: "ident",
|
|
1137
|
+
name: `[${binding.name}]${suffix?.name ?? ""}`,
|
|
1138
|
+
span: {
|
|
1139
|
+
start: open.span.start,
|
|
1140
|
+
end: suffix?.span.end ?? this.previous().span.end,
|
|
1141
|
+
},
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
const base = this.expectName(message);
|
|
1145
|
+
if (!base || !this.atPunct("[")) return base;
|
|
1146
|
+
this.advance();
|
|
1147
|
+
const binding = this.expectIdent("an indexed name needs its loop binding");
|
|
1148
|
+
if (!binding || !this.expectPunct("]", "an indexed name needs ]")) {
|
|
1149
|
+
return undefined;
|
|
1150
|
+
}
|
|
1151
|
+
const suffix = this.isName(this.peek()) ? this.parseIdent() : undefined;
|
|
1152
|
+
return {
|
|
1153
|
+
kind: "ident",
|
|
1154
|
+
name: `${base.name}[${binding.name}]${suffix?.name ?? ""}`,
|
|
1155
|
+
span: {
|
|
1156
|
+
start: base.span.start,
|
|
1157
|
+
end: suffix?.span.end ?? this.previous().span.end,
|
|
1158
|
+
},
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
private canStartExpr(token: Token): boolean {
|
|
1163
|
+
return (
|
|
1164
|
+
token.kind === "ident" ||
|
|
1165
|
+
token.kind === "keyword" ||
|
|
1166
|
+
token.kind === "number" ||
|
|
1167
|
+
token.kind === "percent" ||
|
|
1168
|
+
token.kind === "string" ||
|
|
1169
|
+
(token.kind === "punct" && (token.text === "{" || token.text === "["))
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
private parsePath(message: string): import("./ast.ts").PathExpr | undefined {
|
|
1174
|
+
const first = this.expectName(message);
|
|
1175
|
+
if (!first) return undefined;
|
|
1176
|
+
const parts = [first];
|
|
1177
|
+
while (this.atPunct(".")) {
|
|
1178
|
+
this.advance();
|
|
1179
|
+
const part = this.expectName("expected a name after .");
|
|
1180
|
+
if (!part) return undefined;
|
|
1181
|
+
parts.push(part);
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
kind: "path",
|
|
1185
|
+
parts,
|
|
1186
|
+
span: {
|
|
1187
|
+
start: first.span.start,
|
|
1188
|
+
end: parts.at(-1)?.span.end ?? first.span.end,
|
|
1189
|
+
},
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
private parseAngleArguments(): Expr[] | undefined {
|
|
1194
|
+
this.advance();
|
|
1195
|
+
const args: Expr[] = [];
|
|
1196
|
+
while (!this.atPunct(">") && !this.at("eof")) {
|
|
1197
|
+
const arg = this.parseExpr();
|
|
1198
|
+
if (!arg) return undefined;
|
|
1199
|
+
args.push(arg);
|
|
1200
|
+
if (this.atPunct(",")) this.advance();
|
|
1201
|
+
}
|
|
1202
|
+
return this.expectPunct(">", "the type argument list never closes")
|
|
1203
|
+
? args
|
|
1204
|
+
: undefined;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
private parseApplication(): ApplyExpr | undefined {
|
|
1208
|
+
const callee = this.parsePath("expected an instrument function name");
|
|
1209
|
+
if (!callee) return undefined;
|
|
1210
|
+
const typeArgs = this.atPunct("<") ? this.parseAngleArguments() : [];
|
|
1211
|
+
if (!typeArgs) return undefined;
|
|
1212
|
+
if (
|
|
1213
|
+
!this.expectPunct(
|
|
1214
|
+
"(",
|
|
1215
|
+
"an instrument application needs arguments in parentheses",
|
|
1216
|
+
)
|
|
1217
|
+
) {
|
|
1218
|
+
return undefined;
|
|
1219
|
+
}
|
|
1220
|
+
const args: Expr[] = [];
|
|
1221
|
+
while (!this.atPunct(")") && !this.at("eof")) {
|
|
1222
|
+
const arg = this.parseExpr();
|
|
1223
|
+
if (!arg) return undefined;
|
|
1224
|
+
args.push(arg);
|
|
1225
|
+
if (this.atPunct(",")) this.advance();
|
|
1226
|
+
}
|
|
1227
|
+
if (!this.expectPunct(")", "the instrument argument list never closes")) {
|
|
1228
|
+
return undefined;
|
|
1229
|
+
}
|
|
1230
|
+
return {
|
|
1231
|
+
args,
|
|
1232
|
+
callee,
|
|
1233
|
+
kind: "apply",
|
|
1234
|
+
span: { start: callee.span.start, end: this.previous().span.end },
|
|
1235
|
+
typeArgs,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
605
1239
|
private expectIdent(message: string): IdentExpr | undefined {
|
|
606
1240
|
if (this.at("ident")) return this.parseIdent();
|
|
607
1241
|
this.error(this.peek().span, message);
|