@hyperscale0/hsx 1.0.342 → 1.0.347
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/README.md +1 -1
- package/bases/cards.hsx +0 -1
- package/bases/collections.hsx +0 -2
- package/bases/escrow.hsx +0 -1
- package/bases/financing.hsx +0 -1
- package/bases/insurance.hsx +0 -1
- package/bases/lending.hsx +0 -3
- package/bases/marketplace.hsx +0 -2
- package/bases/money.hsx +0 -1
- package/bases/reporting.hsx +0 -2
- package/bases/savings.hsx +0 -1
- package/bases/wallet.hsx +0 -1
- package/dist/src/ast.d.ts +57 -7
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js.map +1 -1
- package/dist/src/bases-bundle.js +11 -11
- package/dist/src/bases-bundle.js.map +1 -1
- package/dist/src/catalog-program.d.ts +1 -1
- package/dist/src/catalog-program.d.ts.map +1 -1
- package/dist/src/catalog-program.js +0 -13
- package/dist/src/catalog-program.js.map +1 -1
- package/dist/src/checks.d.ts +60 -0
- package/dist/src/checks.d.ts.map +1 -0
- package/dist/src/checks.js +240 -0
- package/dist/src/checks.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +6 -25
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +29 -12
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +414 -316
- package/dist/src/compile.js.map +1 -1
- package/dist/src/diagnostics.d.ts +14 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +31 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/examples-bundle.js +15 -15
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/header-source.d.ts +30 -0
- package/dist/src/header-source.d.ts.map +1 -1
- package/dist/src/header-source.js +120 -9
- package/dist/src/header-source.js.map +1 -1
- package/dist/src/headers.d.ts +9 -18
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +25 -23
- package/dist/src/headers.js.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/keywords.d.ts +2 -4
- package/dist/src/keywords.d.ts.map +1 -1
- package/dist/src/keywords.js +6 -5
- package/dist/src/keywords.js.map +1 -1
- package/dist/src/language.d.ts +3 -4
- package/dist/src/language.d.ts.map +1 -1
- package/dist/src/language.js +123 -60
- package/dist/src/language.js.map +1 -1
- package/dist/src/lex.d.ts +1 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +3 -5
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.d.ts +3 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +234 -22
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts +2 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +16 -15
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/template-guide.d.ts +3 -9
- package/dist/src/template-guide.d.ts.map +1 -1
- package/dist/src/template-guide.js +16 -23
- package/dist/src/template-guide.js.map +1 -1
- package/dist/src/toolchain.d.ts +23 -0
- package/dist/src/toolchain.d.ts.map +1 -0
- package/dist/src/toolchain.js +29 -0
- package/dist/src/toolchain.js.map +1 -0
- package/docs/README.md +69 -28
- package/examples/camera-rental.hsx +0 -1
- package/examples/cards.hsx +0 -1
- package/examples/collections.hsx +0 -2
- package/examples/escrow.hsx +0 -1
- package/examples/financing.hsx +0 -1
- package/examples/insurance.hsx +0 -2
- package/examples/lending.hsx +0 -3
- package/examples/library.hsx +0 -3
- package/examples/marketplace.hsx +0 -2
- package/examples/money.hsx +0 -1
- package/examples/rental-hire.hsx +0 -1
- package/examples/reporting.hsx +0 -2
- package/examples/savings.hsx +0 -1
- package/examples/serviced.hsx +0 -4
- package/examples/wallet.hsx +0 -1
- package/package.json +4 -4
- package/src/ast.ts +56 -3
- package/src/bases-bundle.ts +11 -11
- package/src/catalog-program.ts +0 -13
- package/src/checks.ts +386 -0
- package/src/cli.ts +6 -25
- package/src/compile.ts +583 -438
- package/src/diagnostics.ts +49 -1
- package/src/examples-bundle.ts +15 -15
- package/src/header-source.ts +187 -11
- package/src/headers.ts +31 -32
- package/src/index.ts +10 -4
- package/src/keywords.ts +6 -5
- package/src/language.ts +153 -68
- package/src/lex.ts +3 -5
- package/src/parse.ts +216 -24
- package/src/std-bundle.ts +17 -15
- package/src/template-guide.ts +17 -34
- package/src/toolchain.ts +51 -0
- package/std/booking.hsx +14 -1
- package/std/collections.hsx +12 -2
- package/std/financing.hsx +10 -16
- package/std/money.hsx +2 -3
- package/std/reporting.hsx +10 -1
- package/dist/src/binding-contract.d.ts +0 -22
- package/dist/src/binding-contract.d.ts.map +0 -1
- package/dist/src/binding-contract.js +0 -80
- package/dist/src/binding-contract.js.map +0 -1
- package/dist/src/std-library.d.ts +0 -5
- package/dist/src/std-library.d.ts.map +0 -1
- package/dist/src/std-library.js +0 -5
- package/dist/src/std-library.js.map +0 -1
- package/src/binding-contract.ts +0 -111
- package/src/std-library.ts +0 -7
package/src/parse.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { lex, type Token } from "./lex.ts";
|
|
2
2
|
import type {
|
|
3
3
|
BlockExpr,
|
|
4
|
+
CheckDecl,
|
|
5
|
+
CheckStatement,
|
|
4
6
|
Diagnostic,
|
|
5
7
|
Entry,
|
|
6
8
|
Expr,
|
|
9
|
+
Named,
|
|
7
10
|
Program,
|
|
8
11
|
Span,
|
|
9
12
|
} from "./ast.ts";
|
|
@@ -23,6 +26,7 @@ export function parseProgram(source: string): {
|
|
|
23
26
|
currency: "SAR",
|
|
24
27
|
header: false,
|
|
25
28
|
decls: [],
|
|
29
|
+
checks: [],
|
|
26
30
|
span: { start: 0, end: source.length },
|
|
27
31
|
};
|
|
28
32
|
if (new TextEncoder().encode(source).length > 262144)
|
|
@@ -63,6 +67,17 @@ export function parseProgram(source: string): {
|
|
|
63
67
|
throw error;
|
|
64
68
|
}
|
|
65
69
|
}
|
|
70
|
+
/** One value as a header parameter takes it, such as `10%`; undefined if it is not one. */
|
|
71
|
+
export function parseExpression(source: string): Expr | undefined {
|
|
72
|
+
const result = lex(source);
|
|
73
|
+
if (result.diagnostics.length) return undefined;
|
|
74
|
+
try {
|
|
75
|
+
return new Parser(result.tokens).value();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof ParseFailure) return undefined;
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
66
81
|
class Parser {
|
|
67
82
|
private index = 0;
|
|
68
83
|
private depth = 0;
|
|
@@ -114,6 +129,12 @@ class Parser {
|
|
|
114
129
|
private span(start: number): Span {
|
|
115
130
|
return { start, end: this.tokens[Math.max(0, this.index - 1)]!.span.end };
|
|
116
131
|
}
|
|
132
|
+
value(): Expr {
|
|
133
|
+
const value = this.expression();
|
|
134
|
+
if (this.peek().kind !== "eof")
|
|
135
|
+
this.fail("expected one value", "write one value");
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
117
138
|
program(program: Program): Program {
|
|
118
139
|
if (this.eat("header")) program.header = true;
|
|
119
140
|
else this.expect("program");
|
|
@@ -142,14 +163,6 @@ class Parser {
|
|
|
142
163
|
program.currency = this.identifier();
|
|
143
164
|
continue;
|
|
144
165
|
}
|
|
145
|
-
if (this.eat("use")) {
|
|
146
|
-
program.decls.push({
|
|
147
|
-
kind: "use",
|
|
148
|
-
name: this.identifier(),
|
|
149
|
-
span: this.span(start),
|
|
150
|
-
});
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
166
|
if (this.eat("party")) {
|
|
154
167
|
const name = this.identifier();
|
|
155
168
|
this.expect(":");
|
|
@@ -164,19 +177,20 @@ class Parser {
|
|
|
164
177
|
});
|
|
165
178
|
continue;
|
|
166
179
|
}
|
|
167
|
-
if (this.
|
|
168
|
-
const kind = this.take().text as "expose" | "hide";
|
|
180
|
+
if (this.eat("expose")) {
|
|
169
181
|
const target = this.path();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
this.expect("as");
|
|
183
|
+
const name = this.identifier();
|
|
184
|
+
program.decls.push({
|
|
185
|
+
kind: "expose",
|
|
186
|
+
target,
|
|
187
|
+
name,
|
|
188
|
+
span: this.span(start),
|
|
189
|
+
});
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (this.at("check") && this.tokens[this.index + 1]?.kind === "string") {
|
|
193
|
+
program.checks.push(this.check());
|
|
180
194
|
continue;
|
|
181
195
|
}
|
|
182
196
|
if (this.eat("instrument")) {
|
|
@@ -222,6 +236,133 @@ class Parser {
|
|
|
222
236
|
}
|
|
223
237
|
return program;
|
|
224
238
|
}
|
|
239
|
+
private named(): Named {
|
|
240
|
+
const span = this.peek().span;
|
|
241
|
+
return { name: this.identifier(), span };
|
|
242
|
+
}
|
|
243
|
+
private text(): string {
|
|
244
|
+
if (this.peek().kind !== "string")
|
|
245
|
+
this.fail("expected a quoted name", 'write a name in quotes, as "Alice"');
|
|
246
|
+
return JSON.parse(this.take().text) as string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* `check "Name" { ... }`: customers, steps in order, then `expect`. A step
|
|
250
|
+
* is `clock`, `object`, `close` or `[actor] object.action ... -> state`.
|
|
251
|
+
*/
|
|
252
|
+
private check(): CheckDecl {
|
|
253
|
+
const start = this.take().span.start;
|
|
254
|
+
const name = this.text();
|
|
255
|
+
this.expect("{");
|
|
256
|
+
const statements: CheckStatement[] = [];
|
|
257
|
+
while (!this.eat("}")) {
|
|
258
|
+
this.separators();
|
|
259
|
+
if (this.eat("}")) break;
|
|
260
|
+
if (this.peek().kind === "eof")
|
|
261
|
+
this.fail("unclosed check, expected }", "add }");
|
|
262
|
+
const at = this.peek().span.start;
|
|
263
|
+
const statement = this.checkStatement();
|
|
264
|
+
if (statement.kind !== "customer" && this.eat("balances"))
|
|
265
|
+
statement.balances = this.block();
|
|
266
|
+
statement.span = this.span(at);
|
|
267
|
+
statements.push(statement);
|
|
268
|
+
this.separators();
|
|
269
|
+
}
|
|
270
|
+
return { name, statements, span: this.span(start) };
|
|
271
|
+
}
|
|
272
|
+
private checkStatement(): CheckStatement {
|
|
273
|
+
const span = this.peek().span;
|
|
274
|
+
if (this.eat("customer")) {
|
|
275
|
+
const id = this.named();
|
|
276
|
+
const title = this.text();
|
|
277
|
+
const organization = this.eat("organization");
|
|
278
|
+
const fund = this.eat("funded") ? this.atom() : undefined;
|
|
279
|
+
const binds: Named[] = [];
|
|
280
|
+
while (this.eat("binds")) binds.push(this.named());
|
|
281
|
+
return {
|
|
282
|
+
kind: "customer",
|
|
283
|
+
id,
|
|
284
|
+
title,
|
|
285
|
+
organization,
|
|
286
|
+
...(fund ? { fund } : {}),
|
|
287
|
+
binds,
|
|
288
|
+
span,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (this.eat("clock")) {
|
|
292
|
+
const at = this.atom();
|
|
293
|
+
if (!this.eat("until")) return { kind: "clock", at, span };
|
|
294
|
+
const object = this.named();
|
|
295
|
+
const attachment = this.eat(".") ? this.named() : undefined;
|
|
296
|
+
const recordPath = this.eat("record") ? this.path() : undefined;
|
|
297
|
+
this.expect("is");
|
|
298
|
+
const state = this.named();
|
|
299
|
+
return {
|
|
300
|
+
kind: "clock",
|
|
301
|
+
at,
|
|
302
|
+
until: {
|
|
303
|
+
object,
|
|
304
|
+
...(attachment ? { attachment } : {}),
|
|
305
|
+
...(recordPath ? { recordPath } : {}),
|
|
306
|
+
state,
|
|
307
|
+
},
|
|
308
|
+
span,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (this.eat("object")) {
|
|
312
|
+
const id = this.named();
|
|
313
|
+
this.expect(":");
|
|
314
|
+
const objectKind = this.named();
|
|
315
|
+
if (!this.eat("for"))
|
|
316
|
+
this.fail("an object needs its owner", "write for <customer>");
|
|
317
|
+
const owner = this.named();
|
|
318
|
+
const fields = this.at("{") ? this.block() : undefined;
|
|
319
|
+
return {
|
|
320
|
+
kind: "object",
|
|
321
|
+
id,
|
|
322
|
+
objectKind,
|
|
323
|
+
owner,
|
|
324
|
+
...(fields ? { fields } : {}),
|
|
325
|
+
span,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (this.eat("close"))
|
|
329
|
+
return { kind: "close", customer: this.named(), span };
|
|
330
|
+
if (this.eat("expect")) {
|
|
331
|
+
const statement: Extract<CheckStatement, { kind: "expect" }> = {
|
|
332
|
+
kind: "expect",
|
|
333
|
+
span,
|
|
334
|
+
};
|
|
335
|
+
for (;;) {
|
|
336
|
+
if (this.eat("balances")) statement.balances = this.block();
|
|
337
|
+
else if (this.eat("held")) statement.held = this.atom();
|
|
338
|
+
else if (this.eat("pending")) statement.pending = this.block();
|
|
339
|
+
else break;
|
|
340
|
+
}
|
|
341
|
+
return statement;
|
|
342
|
+
}
|
|
343
|
+
const first = this.named();
|
|
344
|
+
const actor = this.at(".") ? undefined : first;
|
|
345
|
+
const object = actor ? this.named() : first;
|
|
346
|
+
this.expect(".");
|
|
347
|
+
const action = this.named();
|
|
348
|
+
const statement: Extract<CheckStatement, { kind: "action" }> = {
|
|
349
|
+
kind: "action",
|
|
350
|
+
...(actor ? { actor } : {}),
|
|
351
|
+
object,
|
|
352
|
+
action,
|
|
353
|
+
span,
|
|
354
|
+
};
|
|
355
|
+
if (this.at("{")) statement.input = this.block();
|
|
356
|
+
if (this.eat("fields")) statement.fields = this.block();
|
|
357
|
+
if (this.eat("on")) statement.instance = this.named();
|
|
358
|
+
if (this.eat("remember")) statement.remember = this.named();
|
|
359
|
+
this.expect("->");
|
|
360
|
+
if (this.eat("status")) {
|
|
361
|
+
statement.status = this.atom();
|
|
362
|
+
if (this.peek().kind === "string") statement.code = this.text();
|
|
363
|
+
} else statement.state = this.named();
|
|
364
|
+
return statement;
|
|
365
|
+
}
|
|
225
366
|
private entries(end: string): Entry[] {
|
|
226
367
|
const entries: Entry[] = [];
|
|
227
368
|
while (!this.eat(end)) {
|
|
@@ -297,6 +438,11 @@ class Parser {
|
|
|
297
438
|
this.separators();
|
|
298
439
|
continue;
|
|
299
440
|
}
|
|
441
|
+
if (key === "checks" && this.at("{")) {
|
|
442
|
+
entries.push({ key, value: this.checks(), span: this.span(start) });
|
|
443
|
+
this.separators();
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
300
446
|
if (key === "action" && !this.at(":")) key += " " + this.identifier();
|
|
301
447
|
if (key === "economics" && !this.at(":") && !this.at("{"))
|
|
302
448
|
key += " " + this.path();
|
|
@@ -343,8 +489,16 @@ class Parser {
|
|
|
343
489
|
span,
|
|
344
490
|
};
|
|
345
491
|
}
|
|
346
|
-
private operand(): BlockExpr {
|
|
492
|
+
private operand(sums = false): BlockExpr {
|
|
347
493
|
const value = this.atom();
|
|
494
|
+
// A header check may add tunables, as in `first + second <= limit`.
|
|
495
|
+
if (sums && this.at("+")) {
|
|
496
|
+
const items = [value];
|
|
497
|
+
while (this.eat("+")) items.push(this.atom());
|
|
498
|
+
return this.record({
|
|
499
|
+
sum: { kind: "list", items, span: this.span(value.span.start) },
|
|
500
|
+
});
|
|
501
|
+
}
|
|
348
502
|
return this.record({
|
|
349
503
|
[value.kind === "name" &&
|
|
350
504
|
/^(self|input|party|subject)\./.test(value.value)
|
|
@@ -362,7 +516,45 @@ class Parser {
|
|
|
362
516
|
);
|
|
363
517
|
return { kind: "text", value: operator.text, span: operator.span };
|
|
364
518
|
}
|
|
365
|
-
|
|
519
|
+
/**
|
|
520
|
+
* `checks { late_from < early_until { message: "...", fix: "..." } }`. A rule
|
|
521
|
+
* is a comparison, `x is set` or `x is <its type>`, and `when s is c` scopes it.
|
|
522
|
+
*/
|
|
523
|
+
private checks(): BlockExpr {
|
|
524
|
+
const start = this.peek().span.start;
|
|
525
|
+
this.expect("{");
|
|
526
|
+
const entries: Entry[] = [];
|
|
527
|
+
while (!this.eat("}")) {
|
|
528
|
+
this.separators();
|
|
529
|
+
if (this.eat("}")) break;
|
|
530
|
+
if (this.peek().kind === "eof")
|
|
531
|
+
this.fail("unclosed checks, expected }", "add }");
|
|
532
|
+
const at = this.peek().span.start;
|
|
533
|
+
let rule: BlockExpr;
|
|
534
|
+
if (this.tokens[this.index + 1]?.text === "is") {
|
|
535
|
+
const tunable = this.atom();
|
|
536
|
+
this.expect("is");
|
|
537
|
+
rule = this.record({ kind: this.node("is"), tunable, is: this.atom() });
|
|
538
|
+
} else rule = this.requirement(true);
|
|
539
|
+
const values: Record<string, Expr> = {
|
|
540
|
+
rule: { ...rule, span: this.span(at) },
|
|
541
|
+
};
|
|
542
|
+
if (this.eat("when")) {
|
|
543
|
+
const selector = this.atom();
|
|
544
|
+
this.expect("is");
|
|
545
|
+
values.when = this.record({ selector, is: this.atom() });
|
|
546
|
+
}
|
|
547
|
+
values.explain = this.block();
|
|
548
|
+
entries.push({
|
|
549
|
+
key: "check",
|
|
550
|
+
value: { ...this.record(values), span: this.span(at) },
|
|
551
|
+
span: this.span(at),
|
|
552
|
+
});
|
|
553
|
+
this.separators();
|
|
554
|
+
}
|
|
555
|
+
return { kind: "block", entries, span: this.span(start) };
|
|
556
|
+
}
|
|
557
|
+
private requirement(sums = false): BlockExpr {
|
|
366
558
|
if (this.eat("unique")) {
|
|
367
559
|
const namespace = this.atom();
|
|
368
560
|
this.expect("on");
|
|
@@ -416,7 +608,7 @@ class Parser {
|
|
|
416
608
|
if (this.eat("instruction")) values.instruction = this.atom();
|
|
417
609
|
return this.record(values);
|
|
418
610
|
}
|
|
419
|
-
const left = this.operand();
|
|
611
|
+
const left = this.operand(sums);
|
|
420
612
|
if (this.eat("in")) {
|
|
421
613
|
const reference = left.entries.find(
|
|
422
614
|
(entry) => entry.key === "field",
|
|
@@ -437,7 +629,7 @@ class Parser {
|
|
|
437
629
|
kind: this.node("compare"),
|
|
438
630
|
left,
|
|
439
631
|
operator,
|
|
440
|
-
right: this.operand(),
|
|
632
|
+
right: this.operand(sums),
|
|
441
633
|
});
|
|
442
634
|
}
|
|
443
635
|
private move(): BlockExpr {
|