@hyperscale0/hsx 5.7.0 → 5.8.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 +18 -0
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +278 -15
- package/dist/src/compile.js.map +1 -1
- package/dist/src/diagnostics.d.ts +2 -0
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +23 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/std-bundle.js +2 -2
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/headers.md +1 -1
- package/package.json +2 -2
- package/src/compile.ts +363 -27
- package/src/diagnostics.ts +34 -0
- package/src/std-bundle.ts +2 -2
- package/src/version.ts +1 -1
- package/std/financing.hsx +3 -1
- package/std/marketplace.hsx +3 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
# HSX 5.8.0
|
|
2
|
+
|
|
3
|
+
The checker names misspelled fields, parties and states at the misspelled word,
|
|
4
|
+
with the nearest declared name: "`amont` is not a field or party of `deposit`.
|
|
5
|
+
Did you mean `amount`?" This covers moves, actor parties, and field paths in
|
|
6
|
+
`requires`, `unique ... on`, `set`, `calculate:`, `invoke`, `invariants` and the
|
|
7
|
+
`at:` of `due` and `deadline`. The check runs once on the lowered program, so
|
|
8
|
+
it also names a wrong segment deep in a path ("`balanse` is not a field of
|
|
9
|
+
`self.held`"), `input.x` and `party.x` names, invoke instruments, actions and
|
|
10
|
+
inputs, selection instruments, `where` fields and references, `ref<...>`
|
|
11
|
+
targets and the list of an `at` calculation. Swapped adjacent letters count as
|
|
12
|
+
one edit when picking the suggestion. A state typo in `from`, `to` or `initial` is one error
|
|
13
|
+
instead of an invalid transition plus every unreachable state. A program
|
|
14
|
+
instrument attached before its declaration reports each mistake once.
|
|
15
|
+
|
|
16
|
+
A `ref<T>[]` binding accepts a list of sibling attachments, so `on: [plan]`
|
|
17
|
+
compiles to the same program as `on: plan`.
|
|
18
|
+
|
|
1
19
|
# HSX 5.7.0
|
|
2
20
|
|
|
3
21
|
Follows UDL 4.9.0. Object `entryActions` admits enrolled customers through named
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAeL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAqTD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAwnHf"}
|
package/dist/src/compile.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompileFailure, fail, failWithCode } from "./diagnostics.js";
|
|
1
|
+
import { CompileFailure, didYouMean, fail, failWithCode, } from "./diagnostics.js";
|
|
2
2
|
import { hash as sha256 } from "fast-sha256";
|
|
3
3
|
import { buildUdlCostManifest } from "./cost.js";
|
|
4
4
|
import { validateUdl, resolveField, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, udlObjectFieldSchema, udlInstrumentSchema, } from "@hyperscale0/udl";
|
|
@@ -253,6 +253,11 @@ export function compile(source, options = {}) {
|
|
|
253
253
|
severity: "error",
|
|
254
254
|
stage,
|
|
255
255
|
});
|
|
256
|
+
// A program instrument lowers once standalone and once per attachment, so
|
|
257
|
+
// one authored mistake can fail each copy at the same place.
|
|
258
|
+
const distinct = (list) => list.filter((d, index) => list.findIndex((other) => other.source === d.source &&
|
|
259
|
+
other.span.start === d.span.start &&
|
|
260
|
+
other.message === d.message) === index);
|
|
256
261
|
if (parsed.diagnostics.length)
|
|
257
262
|
return {
|
|
258
263
|
verdict: "invalid",
|
|
@@ -403,6 +408,10 @@ export function compile(source, options = {}) {
|
|
|
403
408
|
}
|
|
404
409
|
}
|
|
405
410
|
const origins = [];
|
|
411
|
+
// Lowered objects lose their spans, so each keeps the expression that
|
|
412
|
+
// produced it for the name check after lowering.
|
|
413
|
+
const authored = new WeakMap();
|
|
414
|
+
const declarations = new WeakMap();
|
|
406
415
|
const exposures = [];
|
|
407
416
|
const refundBindings = new Map();
|
|
408
417
|
const resolveFamily = (rawPath, expr, required = true) => {
|
|
@@ -807,7 +816,8 @@ export function compile(source, options = {}) {
|
|
|
807
816
|
continue;
|
|
808
817
|
const t = param.value.kind === "default" ? param.value.type : param.value;
|
|
809
818
|
const type = t.kind === "type" || t.kind === "call" ? t.name : text(t);
|
|
810
|
-
|
|
819
|
+
const asWritten = (supplied.has(param.key) && !attachmentInfo) || type === "enum";
|
|
820
|
+
let v = asWritten
|
|
811
821
|
? actual
|
|
812
822
|
: resolve(actual, new Set(), !!attachmentInfo && type === "party");
|
|
813
823
|
if (type === "duration" &&
|
|
@@ -843,7 +853,15 @@ export function compile(source, options = {}) {
|
|
|
843
853
|
: { party: v.value };
|
|
844
854
|
}
|
|
845
855
|
else if (type === "ref") {
|
|
846
|
-
|
|
856
|
+
// Listed objects resolve like a single binding, so `on: [plan]`
|
|
857
|
+
// names the sibling attachment that `on: plan` names.
|
|
858
|
+
const values = v.kind !== "list"
|
|
859
|
+
? [v]
|
|
860
|
+
: asWritten
|
|
861
|
+
? v.items
|
|
862
|
+
: v.items.map((item) => resolve(item));
|
|
863
|
+
if (v.kind === "list")
|
|
864
|
+
environment.set(param.key, { ...v, items: values });
|
|
847
865
|
if (v.kind === "list" && (t.kind !== "type" || !t.many))
|
|
848
866
|
fail(v, `${param.key} accepts one reference`, "use one object name");
|
|
849
867
|
if (!values.length || values.length > 16)
|
|
@@ -1093,9 +1111,11 @@ export function compile(source, options = {}) {
|
|
|
1093
1111
|
};
|
|
1094
1112
|
const val = (expr) => {
|
|
1095
1113
|
const v = resolve(expr);
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1114
|
+
if (v.kind !== "name" || ["true", "false"].includes(v.value))
|
|
1115
|
+
return { literal: literal(v) };
|
|
1116
|
+
const value = { field: path(v) };
|
|
1117
|
+
authored.set(value, expr);
|
|
1118
|
+
return value;
|
|
1099
1119
|
};
|
|
1100
1120
|
const data = (expr) => {
|
|
1101
1121
|
if (expr.kind === "call" &&
|
|
@@ -1139,6 +1159,7 @@ export function compile(source, options = {}) {
|
|
|
1139
1159
|
continue;
|
|
1140
1160
|
result[k] = data(v);
|
|
1141
1161
|
}
|
|
1162
|
+
authored.set(result, value);
|
|
1142
1163
|
return result;
|
|
1143
1164
|
}
|
|
1144
1165
|
const result = Object.fromEntries([...rawEntries].map(([key, value]) => [key, data(value)]));
|
|
@@ -1160,10 +1181,14 @@ export function compile(source, options = {}) {
|
|
|
1160
1181
|
};
|
|
1161
1182
|
return null;
|
|
1162
1183
|
}
|
|
1184
|
+
authored.set(result, value);
|
|
1163
1185
|
return result;
|
|
1164
1186
|
}
|
|
1165
|
-
if (value.kind === "list")
|
|
1166
|
-
|
|
1187
|
+
if (value.kind === "list") {
|
|
1188
|
+
const items = value.items.map(data).filter((item) => item !== null);
|
|
1189
|
+
authored.set(items, value);
|
|
1190
|
+
return items;
|
|
1191
|
+
}
|
|
1167
1192
|
if (value.kind === "call" &&
|
|
1168
1193
|
value.name === "child" &&
|
|
1169
1194
|
value.args.length === 2) {
|
|
@@ -1370,6 +1395,7 @@ export function compile(source, options = {}) {
|
|
|
1370
1395
|
});
|
|
1371
1396
|
else
|
|
1372
1397
|
fail(constant, `unknown calculation ${constant.name}`, "use rate, sum, minimum, subtract, multiply, divide, before or after");
|
|
1398
|
+
authored.set(calculations.at(-1), constant);
|
|
1373
1399
|
}
|
|
1374
1400
|
else if (type === "enum" && constant.kind === "name")
|
|
1375
1401
|
f.value = constant.value;
|
|
@@ -1382,12 +1408,25 @@ export function compile(source, options = {}) {
|
|
|
1382
1408
|
else
|
|
1383
1409
|
f.value = literal(constant, String(type));
|
|
1384
1410
|
}
|
|
1411
|
+
authored.set(f, row.value);
|
|
1385
1412
|
result.push(f);
|
|
1386
1413
|
}
|
|
1387
1414
|
return result;
|
|
1388
1415
|
};
|
|
1389
1416
|
fields.push(...lowerFields(asBlock(body.get("fields"))));
|
|
1390
|
-
const
|
|
1417
|
+
const lifecycleBlock = asBlock(body.get("lifecycle"));
|
|
1418
|
+
const lifecycle = data(lifecycleBlock);
|
|
1419
|
+
const states = Array.isArray(lifecycle.states) ? lifecycle.states : [];
|
|
1420
|
+
const checkState = (expr, state) => {
|
|
1421
|
+
if (typeof state !== "string" || !states.length)
|
|
1422
|
+
return;
|
|
1423
|
+
if (states.includes(state))
|
|
1424
|
+
return;
|
|
1425
|
+
fail(expr, `\`${state}\` is not a state of \`${decl.name}\`.${didYouMean(state, states)}`, `Use one of ${states.join(", ")}, or add \`${state}\` to \`lifecycle.states\`.`);
|
|
1426
|
+
};
|
|
1427
|
+
const initialExpr = entries(lifecycleBlock).get("initial");
|
|
1428
|
+
if (initialExpr)
|
|
1429
|
+
checkState(initialExpr, lifecycle.initial);
|
|
1391
1430
|
lifecycle.transitions = Object.create(null);
|
|
1392
1431
|
const inst = {
|
|
1393
1432
|
id,
|
|
@@ -1656,6 +1695,7 @@ export function compile(source, options = {}) {
|
|
|
1656
1695
|
moves: [],
|
|
1657
1696
|
...(actionSubject ? { subject: actionSubject } : {}),
|
|
1658
1697
|
};
|
|
1698
|
+
authored.set(a, row.value);
|
|
1659
1699
|
for (const binding of boundaryBindings)
|
|
1660
1700
|
if (!a.subject?.adapters.find((entry) => entry.binding === binding)
|
|
1661
1701
|
?.snapshot)
|
|
@@ -1667,10 +1707,13 @@ export function compile(source, options = {}) {
|
|
|
1667
1707
|
const to = slots.get("to");
|
|
1668
1708
|
if (!from || !to)
|
|
1669
1709
|
fail(row, `action ${name} needs from and to`, "declare its source and destination states");
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1710
|
+
const sources = from.kind === "list" ? from.items : [from];
|
|
1711
|
+
const fromStates = sources.map((e) => String(data(e)));
|
|
1712
|
+
const toState = String(data(to));
|
|
1713
|
+
sources.forEach((e, index) => checkState(e, fromStates[index]));
|
|
1714
|
+
if (toState !== "preserve")
|
|
1715
|
+
checkState(to, toState);
|
|
1716
|
+
lifecycle.transitions[name] = { from: fromStates, to: toState };
|
|
1674
1717
|
}
|
|
1675
1718
|
for (const [key, expr] of slots) {
|
|
1676
1719
|
if (["from", "to", "input", "summary", "subject"].includes(key))
|
|
@@ -1678,6 +1721,7 @@ export function compile(source, options = {}) {
|
|
|
1678
1721
|
if (key === "moves") {
|
|
1679
1722
|
const moves = expr.kind === "list" ? expr.items : [expr];
|
|
1680
1723
|
for (const [index, move] of moves.entries()) {
|
|
1724
|
+
const lowered = a.moves.length;
|
|
1681
1725
|
const parts = entries(asBlock(move));
|
|
1682
1726
|
const op = parts.has("operation")
|
|
1683
1727
|
? String(data(parts.get("operation")))
|
|
@@ -1831,6 +1875,8 @@ export function compile(source, options = {}) {
|
|
|
1831
1875
|
}
|
|
1832
1876
|
if (total !== 10000)
|
|
1833
1877
|
fail(move, "split percentages must sum to 100%", "give the recipients exactly 100% together");
|
|
1878
|
+
for (const item of a.moves.slice(lowered))
|
|
1879
|
+
authored.set(item, move);
|
|
1834
1880
|
continue;
|
|
1835
1881
|
}
|
|
1836
1882
|
if (!amount || !from || !to)
|
|
@@ -1972,6 +2018,8 @@ export function compile(source, options = {}) {
|
|
|
1972
2018
|
});
|
|
1973
2019
|
else
|
|
1974
2020
|
fail(move, `unknown transfer instruction ${op}`, "choose create, reserve, post, or void from internal_transfer");
|
|
2021
|
+
for (const item of a.moves.slice(lowered))
|
|
2022
|
+
authored.set(item, move);
|
|
1975
2023
|
}
|
|
1976
2024
|
}
|
|
1977
2025
|
else
|
|
@@ -2090,6 +2138,7 @@ export function compile(source, options = {}) {
|
|
|
2090
2138
|
};
|
|
2091
2139
|
}
|
|
2092
2140
|
document.instruments.push(inst);
|
|
2141
|
+
declarations.set(inst, decl);
|
|
2093
2142
|
for (const [key, definition] of records) {
|
|
2094
2143
|
const childAttachment = attachmentInfo
|
|
2095
2144
|
? {
|
|
@@ -2337,7 +2386,7 @@ export function compile(source, options = {}) {
|
|
|
2337
2386
|
if (bindingDiagnostics.length)
|
|
2338
2387
|
return {
|
|
2339
2388
|
verdict: "invalid",
|
|
2340
|
-
diagnostics: bindingDiagnostics
|
|
2389
|
+
diagnostics: distinct(bindingDiagnostics)
|
|
2341
2390
|
.sort((a, b) => a.span.start - b.span.start)
|
|
2342
2391
|
.map((d) => diagnostic(d, "check")),
|
|
2343
2392
|
};
|
|
@@ -2521,6 +2570,220 @@ export function compile(source, options = {}) {
|
|
|
2521
2570
|
failWithCode(objects.get(kind.id), "subject_field_unknown", `column ${column} is unknown on ${kind.id}`, "name an authored field or attached requirement");
|
|
2522
2571
|
}
|
|
2523
2572
|
}
|
|
2573
|
+
const byId = new Map(document.instruments.map((inst) => [inst.id, inst]));
|
|
2574
|
+
const site = (owner, key, fallback) => {
|
|
2575
|
+
const expr = authored.get(owner);
|
|
2576
|
+
if (expr?.kind === "block")
|
|
2577
|
+
return expr.entries.find((entry) => entry.key === key)?.value ?? expr;
|
|
2578
|
+
if (expr?.kind === "list" && Array.isArray(owner))
|
|
2579
|
+
return expr.items.length === owner.length
|
|
2580
|
+
? expr.items[Number(key)]
|
|
2581
|
+
: expr;
|
|
2582
|
+
return expr ?? fallback;
|
|
2583
|
+
};
|
|
2584
|
+
const keySite = (owner, key, fallback) => {
|
|
2585
|
+
const expr = authored.get(owner);
|
|
2586
|
+
const entry = expr?.kind === "block"
|
|
2587
|
+
? expr.entries.find((item) => item.key === key)
|
|
2588
|
+
: undefined;
|
|
2589
|
+
if (!entry)
|
|
2590
|
+
return fallback;
|
|
2591
|
+
return {
|
|
2592
|
+
...entry,
|
|
2593
|
+
span: { start: entry.span.start, end: entry.span.start + key.length },
|
|
2594
|
+
};
|
|
2595
|
+
};
|
|
2596
|
+
const builtins = (inst) => [
|
|
2597
|
+
"id",
|
|
2598
|
+
"now",
|
|
2599
|
+
"createdAt",
|
|
2600
|
+
"productRevision",
|
|
2601
|
+
"status",
|
|
2602
|
+
...(inst.subject ? ["subject"] : []),
|
|
2603
|
+
];
|
|
2604
|
+
const parties = (inst) => [
|
|
2605
|
+
...Object.keys(document.parties),
|
|
2606
|
+
...(inst.subject ? subjectPartyRoles : []),
|
|
2607
|
+
];
|
|
2608
|
+
const members = (field) => {
|
|
2609
|
+
if (field.type === "account")
|
|
2610
|
+
return ["balance", "reserved"];
|
|
2611
|
+
if (field.type !== "ref")
|
|
2612
|
+
return [];
|
|
2613
|
+
const [id] = [field.target].flat();
|
|
2614
|
+
const target = field.targetKind === "object"
|
|
2615
|
+
? document.objects.find((object) => object.id === id)
|
|
2616
|
+
: byId.get(id);
|
|
2617
|
+
if (!target)
|
|
2618
|
+
return [];
|
|
2619
|
+
return [
|
|
2620
|
+
...target.fields.map((item) => item.name),
|
|
2621
|
+
...("lifecycle" in target ? builtins(target) : []),
|
|
2622
|
+
];
|
|
2623
|
+
};
|
|
2624
|
+
const checkParty = (name, at, inst) => {
|
|
2625
|
+
const names = parties(inst);
|
|
2626
|
+
if (!names.includes(name))
|
|
2627
|
+
fail(at, `\`${name}\` is not a declared party.${didYouMean(name, names)}`, `Declare it with \`party ${name}: business\`, or name a declared party (${names.join(", ")}).`);
|
|
2628
|
+
};
|
|
2629
|
+
const checkInput = (name, at, label, input) => {
|
|
2630
|
+
const names = input.map((field) => field.name);
|
|
2631
|
+
if (!names.includes(name))
|
|
2632
|
+
fail(at, `\`${name}\` is not an input of \`${label}\`.${didYouMean(name, names)}`, `Declare \`${name}\` in the input of \`${label}\`${names.length ? `, or use one of ${names.join(", ")}` : ""}.`);
|
|
2633
|
+
};
|
|
2634
|
+
const checkTarget = (id, at, objects = false) => {
|
|
2635
|
+
const ids = [
|
|
2636
|
+
...byId.keys(),
|
|
2637
|
+
...(objects ? document.objects.map((object) => object.id) : []),
|
|
2638
|
+
];
|
|
2639
|
+
const noun = objects ? "instrument or object" : "instrument";
|
|
2640
|
+
if (!ids.includes(id))
|
|
2641
|
+
fail(at, `\`${id}\` is not a declared ${noun}.${didYouMean(id, ids)}`, `Name a declared ${noun}.`);
|
|
2642
|
+
};
|
|
2643
|
+
const checkPath = (path, at, scope) => {
|
|
2644
|
+
const { inst, action } = scope;
|
|
2645
|
+
const parts = path.split(".");
|
|
2646
|
+
const found = (count) => resolveField(document, inst, parts.slice(0, count).join("."), action?.input, action);
|
|
2647
|
+
// Subject paths keep their own check against the action's requirements.
|
|
2648
|
+
if (found(parts.length) || parts[0] === "subject")
|
|
2649
|
+
return;
|
|
2650
|
+
if (parts[0] === "party" && parts[1]) {
|
|
2651
|
+
checkParty(parts[1], at, inst);
|
|
2652
|
+
if (!found(2))
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
if (!["self", "input", "party"].includes(parts[0]))
|
|
2656
|
+
parts.unshift("self");
|
|
2657
|
+
let count = 2;
|
|
2658
|
+
while (count <= parts.length && found(count))
|
|
2659
|
+
count++;
|
|
2660
|
+
if (count > parts.length)
|
|
2661
|
+
return;
|
|
2662
|
+
const name = parts[count - 1];
|
|
2663
|
+
if (count > 2) {
|
|
2664
|
+
const prefix = parts.slice(0, count - 1).join(".");
|
|
2665
|
+
const names = members(found(count - 1));
|
|
2666
|
+
fail(at, `\`${name}\` is not a field of \`${prefix}\`.${didYouMean(name, names)}`, names.length
|
|
2667
|
+
? `Use one of ${names.join(", ")}.`
|
|
2668
|
+
: `\`${prefix}\` has no fields.`);
|
|
2669
|
+
}
|
|
2670
|
+
if (parts[0] === "input")
|
|
2671
|
+
return checkInput(name, at, scope.label, action?.input ?? []);
|
|
2672
|
+
const declared = declarations.get(inst).name;
|
|
2673
|
+
const fields = inst.fields.map((field) => field.name);
|
|
2674
|
+
fail(at, `\`${name}\` is not a field or party of \`${declared}\`.${didYouMean(name, [...fields, ...builtins(inst), ...parties(inst)])}`, `Use a field declared in \`${declared}\` (${fields.join(", ")}) or a declared party (${parties(inst).join(", ")}).`);
|
|
2675
|
+
};
|
|
2676
|
+
const scopeOf = (inst) => ({
|
|
2677
|
+
inst,
|
|
2678
|
+
label: declarations.get(inst).name,
|
|
2679
|
+
});
|
|
2680
|
+
const checkSelection = (selection, at, scope) => {
|
|
2681
|
+
walk(selection.anchor, "anchor", site(selection, "anchor", at), scope);
|
|
2682
|
+
walk(selection.where, "where", site(selection, "where", at), scope);
|
|
2683
|
+
for (const id of [selection.instrument].flat()) {
|
|
2684
|
+
checkTarget(id, site(selection, "instrument", at));
|
|
2685
|
+
const target = scopeOf(byId.get(id));
|
|
2686
|
+
const member = (key, where) => checkPath(key.startsWith("self.") ? key : `self.${key}`, where, target);
|
|
2687
|
+
for (const key of Object.keys(selection.where ?? {}))
|
|
2688
|
+
member(key, keySite(selection.where, key, at));
|
|
2689
|
+
member(selection.reference, site(selection, "reference", at));
|
|
2690
|
+
selection.order?.forEach((key, index) => member(key, site(selection.order, index, at)));
|
|
2691
|
+
if (selection.window)
|
|
2692
|
+
member(selection.window.field, site(selection.window, "field", at));
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
const checkCall = (call, at, scope) => {
|
|
2696
|
+
const bind = "range" in call ? call.range?.bind : undefined;
|
|
2697
|
+
for (const [key, value] of Object.entries(call))
|
|
2698
|
+
if (key !== "input")
|
|
2699
|
+
walk(value, key, site(call, key, at), scope);
|
|
2700
|
+
for (const [key, value] of Object.entries(call.input))
|
|
2701
|
+
if (!("field" in value) || value.field !== bind)
|
|
2702
|
+
walk(value, key, site(call.input, key, at), scope);
|
|
2703
|
+
if ("instrument" in call)
|
|
2704
|
+
checkTarget(call.instrument, site(call, "instrument", at));
|
|
2705
|
+
const reference = "reference" in call
|
|
2706
|
+
? resolveField(document, scope.inst, call.reference, scope.action?.input, scope.action)
|
|
2707
|
+
: undefined;
|
|
2708
|
+
const ids = "instrument" in call
|
|
2709
|
+
? [call.instrument]
|
|
2710
|
+
: "selection" in call
|
|
2711
|
+
? [call.selection.instrument].flat()
|
|
2712
|
+
: reference?.type === "ref" && reference.targetKind === "instrument"
|
|
2713
|
+
? [reference.target].flat()
|
|
2714
|
+
: [];
|
|
2715
|
+
for (const id of ids) {
|
|
2716
|
+
const target = byId.get(id);
|
|
2717
|
+
if (!target)
|
|
2718
|
+
continue;
|
|
2719
|
+
const action = target.actions[call.action];
|
|
2720
|
+
if (!action)
|
|
2721
|
+
fail(site(call, "action", at), `\`${call.action}\` is not an action of \`${id}\`.${didYouMean(call.action, target.actionOrder)}`, `Use one of ${target.actionOrder.join(", ")}.`);
|
|
2722
|
+
for (const key of Object.keys(call.input))
|
|
2723
|
+
checkInput(key, keySite(call.input, key, at), `${id}.${call.action}`, action.input);
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2726
|
+
const pathKeys = [
|
|
2727
|
+
"field",
|
|
2728
|
+
"reference",
|
|
2729
|
+
"anchor",
|
|
2730
|
+
"at",
|
|
2731
|
+
"list",
|
|
2732
|
+
"from",
|
|
2733
|
+
"to",
|
|
2734
|
+
"transfer",
|
|
2735
|
+
"subject",
|
|
2736
|
+
"instruction",
|
|
2737
|
+
"installment",
|
|
2738
|
+
"recipient",
|
|
2739
|
+
"dueAt",
|
|
2740
|
+
"fields",
|
|
2741
|
+
];
|
|
2742
|
+
const walk = (value, key, at, scope) => {
|
|
2743
|
+
if (typeof value === "string") {
|
|
2744
|
+
if (key === "party")
|
|
2745
|
+
checkParty(value, at, scope.inst);
|
|
2746
|
+
if (key === "target")
|
|
2747
|
+
checkPath(`self.${value}`, at, scope);
|
|
2748
|
+
if (pathKeys.includes(key))
|
|
2749
|
+
checkPath(value, at, scope);
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
if (!value || typeof value !== "object")
|
|
2753
|
+
return;
|
|
2754
|
+
if (Array.isArray(value))
|
|
2755
|
+
return value.forEach((item, index) => walk(item, key, site(value, index, at), scope));
|
|
2756
|
+
if (key === "selection")
|
|
2757
|
+
return checkSelection(value, at, scope);
|
|
2758
|
+
if (key === "invoke")
|
|
2759
|
+
return checkCall(value, at, scope);
|
|
2760
|
+
if (key === "set")
|
|
2761
|
+
for (const name of Object.keys(value))
|
|
2762
|
+
checkPath(`self.${name}`, keySite(value, name, at), scope);
|
|
2763
|
+
for (const [child, item] of Object.entries(value))
|
|
2764
|
+
walk(item, child, site(value, child, at), scope);
|
|
2765
|
+
};
|
|
2766
|
+
for (const inst of document.instruments) {
|
|
2767
|
+
const decl = declarations.get(inst);
|
|
2768
|
+
const scope = scopeOf(inst);
|
|
2769
|
+
for (const field of [
|
|
2770
|
+
...inst.fields,
|
|
2771
|
+
...Object.values(inst.actions).flatMap((action) => action.input),
|
|
2772
|
+
])
|
|
2773
|
+
if ("target" in field && field.target !== undefined)
|
|
2774
|
+
for (const id of [field.target].flat())
|
|
2775
|
+
checkTarget(id, site(field, "target", decl), true);
|
|
2776
|
+
walk(inst.calculate, "calculate", decl, scope);
|
|
2777
|
+
walk(inst.invariants, "invariants", decl, scope);
|
|
2778
|
+
for (const [name, action] of Object.entries(inst.actions))
|
|
2779
|
+
for (const [key, value] of Object.entries(action))
|
|
2780
|
+
if (key !== "input" && key !== "subject")
|
|
2781
|
+
walk(value, key, site(action, key, decl), {
|
|
2782
|
+
inst,
|
|
2783
|
+
label: `${decl.name}.${name}`,
|
|
2784
|
+
action,
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2524
2787
|
const eliminatedStates = new Map();
|
|
2525
2788
|
// Remove branches excluded by immutable tunables before checking reference states.
|
|
2526
2789
|
for (const inst of document.instruments) {
|
|
@@ -2702,7 +2965,7 @@ export function compile(source, options = {}) {
|
|
|
2702
2965
|
if (error instanceof CompileFailure)
|
|
2703
2966
|
return {
|
|
2704
2967
|
verdict: "invalid",
|
|
2705
|
-
diagnostics: [...bindingDiagnostics, error.diagnostic].map((d) => diagnostic(d, "check")),
|
|
2968
|
+
diagnostics: distinct([...bindingDiagnostics, error.diagnostic]).map((d) => diagnostic(d, "check")),
|
|
2706
2969
|
};
|
|
2707
2970
|
throw error;
|
|
2708
2971
|
}
|