@hyperscale0/hsx 1.0.342 → 1.0.350
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/bin/hsx.d.ts.map +1 -1
- package/dist/bin/hsx.js.map +1 -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/attachment-economics.d.ts.map +1 -1
- package/dist/src/attachment-economics.js.map +1 -1
- package/dist/src/bases-bundle.d.ts.map +1 -1
- package/dist/src/bases-bundle.js +11 -11
- package/dist/src/bases-bundle.js.map +1 -1
- package/dist/src/bases.d.ts.map +1 -1
- package/dist/src/bases.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/cost.d.ts.map +1 -1
- package/dist/src/cost.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.d.ts.map +1 -1
- package/dist/src/examples-bundle.js +15 -15
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/examples.d.ts.map +1 -1
- package/dist/src/examples.js.map +1 -1
- package/dist/src/format.d.ts.map +1 -1
- package/dist/src/format.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/dist/src/tunables.d.ts.map +1 -1
- package/dist/src/tunables.js.map +1 -1
- 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 +8 -8
- 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/dist/src/compile.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { CompileFailure, didYouMean, fail, failWithCode, } from "./diagnostics.js";
|
|
2
|
-
import { hash as sha256 } from "fast-sha256";
|
|
1
|
+
import { CompileFailure, conflict, didYouMean, fail, failWithCode, } from "./diagnostics.js";
|
|
3
2
|
import { buildUdlCostManifest } from "./cost.js";
|
|
4
|
-
import { validateUdl, resolveField, sameAccount, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, UDL_FORMAT_VERSION, udlObjectFieldSchema, udlFieldValueSchema, udlInstrumentSchema, } from "@hyperscale0/udl";
|
|
5
|
-
import { bindingDependencies, parameterDiagnostics, BindingContractError, } from "./binding-contract.js";
|
|
3
|
+
import { canonicalJson, digest, validateUdl, resolveField, sameAccount, sameFamily, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, UDL_FORMAT_VERSION, udlObjectFieldSchema, udlFieldValueSchema, udlInstrumentSchema, } from "@hyperscale0/udl";
|
|
6
4
|
import { tunableBounds } from "./tunables.js";
|
|
7
|
-
import {
|
|
5
|
+
import { lowerChecks } from "./checks.js";
|
|
6
|
+
import { parseExpression, parseProgram } from "./parse.js";
|
|
8
7
|
import { applyAttachmentEconomics, purposeWarnings, samePartyMoves, unboundEconomicSources, } from "./attachment-economics.js";
|
|
9
|
-
import { parseHeader } from "./header-source.js";
|
|
8
|
+
import { instrumentChecks, parseHeader } from "./header-source.js";
|
|
10
9
|
import { lineColAt, } from "./ast.js";
|
|
11
|
-
import {
|
|
10
|
+
import { defaultToolchain, } from "./toolchain.js";
|
|
12
11
|
function fieldType(row) {
|
|
13
12
|
const type = row.value.kind === "default" ? row.value.type : row.value;
|
|
14
13
|
if (type.kind !== "call" || type.name !== "sensitive")
|
|
@@ -112,25 +111,11 @@ function lowerObjectField(row, resolveExpr = (e) => e) {
|
|
|
112
111
|
fail(row, result.error.message, "use a UDL object field type and its constraints");
|
|
113
112
|
return result.data;
|
|
114
113
|
}
|
|
115
|
-
function canonicalJson(value) {
|
|
116
|
-
if (value === null || typeof value !== "object") {
|
|
117
|
-
return JSON.stringify(value);
|
|
118
|
-
}
|
|
119
|
-
if (Array.isArray(value)) {
|
|
120
|
-
return `[${value.map((item) => (item === undefined ? "null" : canonicalJson(item))).join(",")}]`;
|
|
121
|
-
}
|
|
122
|
-
const record = value;
|
|
123
|
-
const keys = Object.keys(record)
|
|
124
|
-
.filter((k) => record[k] !== undefined)
|
|
125
|
-
.sort();
|
|
126
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(record[k])}`).join(",")}}`;
|
|
127
|
-
}
|
|
128
114
|
const emptyBlock = {
|
|
129
115
|
kind: "block",
|
|
130
116
|
entries: [],
|
|
131
117
|
span: { start: 0, end: 0 },
|
|
132
118
|
};
|
|
133
|
-
const camel = (name) => name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
134
119
|
/** Clock and parent actors run without a caller, so they have no public name. */
|
|
135
120
|
const automatic = (actor) => actor === "clock" ||
|
|
136
121
|
(actor !== null && typeof actor === "object" && "parent" in actor);
|
|
@@ -153,32 +138,52 @@ function namedParties(instrument) {
|
|
|
153
138
|
}
|
|
154
139
|
return names;
|
|
155
140
|
}
|
|
156
|
-
|
|
141
|
+
const MERGE = {
|
|
142
|
+
requires: "concat",
|
|
143
|
+
moves: "concat",
|
|
144
|
+
invariants: "concat",
|
|
145
|
+
invoke: "concat",
|
|
146
|
+
calculate: "concat",
|
|
147
|
+
expose: "concat",
|
|
148
|
+
};
|
|
149
|
+
// The sources of the compile in progress, so a conflict prints line:column.
|
|
150
|
+
let texts = new Map();
|
|
151
|
+
/** An expression with spans, sources and comments dropped. */
|
|
152
|
+
const normal = (value) => Array.isArray(value)
|
|
153
|
+
? value.map(normal)
|
|
154
|
+
: value && typeof value === "object"
|
|
155
|
+
? Object.fromEntries(Object.entries(value).flatMap(([key, item]) => key === "span" || key === "source" || item === undefined
|
|
156
|
+
? []
|
|
157
|
+
: [[key, normal(item)]]))
|
|
158
|
+
: value;
|
|
159
|
+
function given(block, merge = (key) => MERGE[key] ?? "unique") {
|
|
157
160
|
const map = new Map();
|
|
158
161
|
for (const row of block.entries) {
|
|
159
162
|
const previous = map.get(row.key);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"invariants",
|
|
165
|
-
"invoke",
|
|
166
|
-
"calculate",
|
|
167
|
-
"expose",
|
|
168
|
-
].includes(row.key))
|
|
169
|
-
fail(row, `duplicate ${row.key}`, "keep one value for this name");
|
|
163
|
+
const rule = merge(row.key);
|
|
164
|
+
if (!previous)
|
|
165
|
+
map.set(row.key, row);
|
|
166
|
+
else if (rule === "concat") {
|
|
170
167
|
const items = (expr) => expr.kind === "list" ? expr.items : [expr];
|
|
171
168
|
map.set(row.key, {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
...row,
|
|
170
|
+
value: {
|
|
171
|
+
kind: "list",
|
|
172
|
+
items: [...items(previous.value), ...items(row.value)],
|
|
173
|
+
span: row.span,
|
|
174
|
+
},
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
|
-
else
|
|
178
|
-
|
|
177
|
+
else if (rule === "unique" ||
|
|
178
|
+
JSON.stringify(normal(previous.value)) !==
|
|
179
|
+
JSON.stringify(normal(row.value)))
|
|
180
|
+
conflict(texts, `${row.key} is ${rule === "equal" ? "set" : "given"} twice`, previous, row, "keep one", rule === "equal" ? { values: [previous.value, row.value] } : {});
|
|
179
181
|
}
|
|
180
182
|
return map;
|
|
181
183
|
}
|
|
184
|
+
function entries(block) {
|
|
185
|
+
return new Map([...given(block)].map(([key, row]) => [key, row.value]));
|
|
186
|
+
}
|
|
182
187
|
function asBlock(expr) {
|
|
183
188
|
if (!expr)
|
|
184
189
|
return emptyBlock;
|
|
@@ -287,9 +292,21 @@ function literal(expr, expectedType) {
|
|
|
287
292
|
fail(expr, "this value is fixed when the program compiles, so it cannot be runtime", "write a literal value");
|
|
288
293
|
return fail(expr, "expected a literal", "write a typed constant");
|
|
289
294
|
}
|
|
290
|
-
|
|
295
|
+
/** A std declaration's identity: its AST with spans, sources and comments dropped. */
|
|
296
|
+
function declarationDigest(decl) {
|
|
297
|
+
return digest(normal(decl));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* An override compiles the program as if one std term held other text, so a
|
|
301
|
+
* plan can see what that term alone moves.
|
|
302
|
+
*/
|
|
303
|
+
export function compile(source, toolchain = defaultToolchain, override) {
|
|
291
304
|
const parsed = parseProgram(source);
|
|
292
|
-
const sources = new Map([
|
|
305
|
+
const sources = new Map([
|
|
306
|
+
["program", source],
|
|
307
|
+
...(override ? [["override", override.text]] : []),
|
|
308
|
+
]);
|
|
309
|
+
texts = sources;
|
|
293
310
|
const diagnostic = (d, stage) => {
|
|
294
311
|
const text = sources.get(d.source ?? "program") ?? source;
|
|
295
312
|
const end = lineColAt(text, d.span.end);
|
|
@@ -316,8 +333,8 @@ export function compile(source, options = {}) {
|
|
|
316
333
|
const program = parsed.program;
|
|
317
334
|
const bindingDiagnostics = [];
|
|
318
335
|
const adapterTarget = (binding) => {
|
|
319
|
-
const registry =
|
|
320
|
-
if (!
|
|
336
|
+
const registry = toolchain.adapters;
|
|
337
|
+
if (!Object.hasOwn(registry, binding))
|
|
321
338
|
return;
|
|
322
339
|
const target = registry[binding];
|
|
323
340
|
return target &&
|
|
@@ -335,55 +352,56 @@ export function compile(source, options = {}) {
|
|
|
335
352
|
const attachedSelectors = new Map();
|
|
336
353
|
const attachEntries = new Map();
|
|
337
354
|
const declarationSources = new Map();
|
|
355
|
+
// Declaration digest by std path, such as financing.installments.payment.
|
|
356
|
+
const familyDigests = new Map();
|
|
338
357
|
const standardOrigins = new Map();
|
|
339
358
|
const requirementOrigins = new Map();
|
|
340
359
|
// Instruments that lost a std action to an empty all() parent. Their
|
|
341
360
|
// lifecycles drop the states only that action reached.
|
|
342
361
|
const trimmed = new Set();
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
? Array.from(sha256(new TextEncoder().encode(content)), (byte) => byte.toString(16).padStart(2, "0")).join("")
|
|
355
|
-
: undefined;
|
|
362
|
+
// A qualified name such as booking.reservation loads its header the first
|
|
363
|
+
// time the compile reaches it. A header nothing names never loads.
|
|
364
|
+
const loaded = new Set();
|
|
365
|
+
const loadHeader = (name, site) => {
|
|
366
|
+
if (loaded.has(name))
|
|
367
|
+
return true;
|
|
368
|
+
const content = toolchain.std.get(name);
|
|
369
|
+
if (content === undefined)
|
|
370
|
+
return false;
|
|
371
|
+
loaded.add(name);
|
|
372
|
+
sources.set(name, content);
|
|
356
373
|
let header;
|
|
357
374
|
try {
|
|
358
|
-
header = parseHeader(content,
|
|
375
|
+
header = parseHeader(content, name);
|
|
359
376
|
}
|
|
360
377
|
catch (error) {
|
|
361
|
-
if (!(error instanceof CompileFailure))
|
|
378
|
+
if (!(error instanceof CompileFailure) || !site)
|
|
362
379
|
throw error;
|
|
363
380
|
throw new CompileFailure({
|
|
364
381
|
...error.diagnostic,
|
|
365
382
|
related: [
|
|
366
383
|
{
|
|
367
|
-
source: "program",
|
|
368
|
-
span:
|
|
369
|
-
message: `
|
|
384
|
+
source: site.source ?? "program",
|
|
385
|
+
span: site.span,
|
|
386
|
+
message: `Header ${name} is named here`,
|
|
370
387
|
},
|
|
371
388
|
],
|
|
372
389
|
});
|
|
373
390
|
}
|
|
374
391
|
const registerTemplates = (parentDecl, prefix) => {
|
|
375
|
-
|
|
376
|
-
|
|
392
|
+
const first = templates.get(prefix);
|
|
393
|
+
if (first)
|
|
394
|
+
conflict(texts, `instrument ${prefix} is declared twice`, first, parentDecl, "give each instrument a distinct name");
|
|
377
395
|
templates.set(prefix, parentDecl);
|
|
378
|
-
declarationSources.set(parentDecl,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}
|
|
396
|
+
declarationSources.set(parentDecl, name);
|
|
397
|
+
const digest = declarationDigest(parentDecl);
|
|
398
|
+
familyDigests.set(prefix, digest);
|
|
399
|
+
const [, root, ...record] = prefix.split(".");
|
|
400
|
+
standardOrigins.set(parentDecl, {
|
|
401
|
+
key: `${name}.${root}`,
|
|
402
|
+
recordPath: record.join("."),
|
|
403
|
+
digest,
|
|
404
|
+
});
|
|
387
405
|
const recs = entries(asBlock(entries(parentDecl.body).get("records")));
|
|
388
406
|
for (const [recName, recBlock] of recs) {
|
|
389
407
|
const recDecl = {
|
|
@@ -397,10 +415,36 @@ export function compile(source, options = {}) {
|
|
|
397
415
|
}
|
|
398
416
|
};
|
|
399
417
|
for (const decl of header.decls)
|
|
400
|
-
if (decl.kind === "instrument")
|
|
401
|
-
registerTemplates(decl, `${
|
|
402
|
-
|
|
403
|
-
}
|
|
418
|
+
if (decl.kind === "instrument")
|
|
419
|
+
registerTemplates(decl, `${name}.${decl.name}`);
|
|
420
|
+
return true;
|
|
421
|
+
};
|
|
422
|
+
const loadPath = (path, site) => {
|
|
423
|
+
const dot = path.indexOf(".");
|
|
424
|
+
return dot > 0 && loadHeader(path.slice(0, dot), site);
|
|
425
|
+
};
|
|
426
|
+
const templateAt = (path, site) => {
|
|
427
|
+
loadPath(path, site);
|
|
428
|
+
return templates.get(path);
|
|
429
|
+
};
|
|
430
|
+
// The template a qualified name names, or the reason it names none.
|
|
431
|
+
const requireTemplate = (path, site) => {
|
|
432
|
+
const found = templateAt(path, site);
|
|
433
|
+
if (found)
|
|
434
|
+
return found;
|
|
435
|
+
const root = path.split(".")[0];
|
|
436
|
+
const unknown = (noun, name, candidates) => {
|
|
437
|
+
const hint = didYouMean(name, candidates);
|
|
438
|
+
return hint
|
|
439
|
+
? `unknown ${noun} ${name}.${hint}`
|
|
440
|
+
: `unknown ${noun} ${name}`;
|
|
441
|
+
};
|
|
442
|
+
if (path.includes(".") && !toolchain.std.has(root))
|
|
443
|
+
fail(site, unknown("header", root, [...toolchain.std.keys()]), "name a published header");
|
|
444
|
+
return fail(site, unknown("instrument", path, [...templates.keys()].filter((name) => name.startsWith(`${root}.`))), path.includes(".")
|
|
445
|
+
? `choose an instrument header ${root} declares`
|
|
446
|
+
: "choose a declared instrument");
|
|
447
|
+
};
|
|
404
448
|
for (const decl of program.decls)
|
|
405
449
|
if (decl.kind === "instrument")
|
|
406
450
|
templates.set(decl.name, decl);
|
|
@@ -420,18 +464,19 @@ export function compile(source, options = {}) {
|
|
|
420
464
|
const objects = new Map();
|
|
421
465
|
const assignments = new Map();
|
|
422
466
|
const attachmentSubjects = new Map();
|
|
423
|
-
const names = new
|
|
467
|
+
const names = new Map();
|
|
424
468
|
// A record's create turns public only through a program-level expose,
|
|
425
469
|
// which applies after lowering, so creation input needs the list first.
|
|
426
470
|
const exposedCreates = new Set(program.decls.flatMap((decl) => decl.kind === "expose" && decl.target.endsWith(".create")
|
|
427
471
|
? [decl.target.split(".").slice(0, -1).join("_")]
|
|
428
472
|
: []));
|
|
429
473
|
for (const decl of program.decls) {
|
|
430
|
-
if (decl.kind === "expose"
|
|
474
|
+
if (decl.kind === "expose")
|
|
431
475
|
continue;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
476
|
+
const first = names.get(decl.name);
|
|
477
|
+
if (first)
|
|
478
|
+
conflict(texts, `${decl.name} is declared twice`, first, decl, "give this declaration a distinct name");
|
|
479
|
+
names.set(decl.name, decl);
|
|
435
480
|
if (decl.kind === "object") {
|
|
436
481
|
objects.set(decl.name, decl);
|
|
437
482
|
for (const entry of decl.body.entries) {
|
|
@@ -477,51 +522,22 @@ export function compile(source, options = {}) {
|
|
|
477
522
|
return undefined;
|
|
478
523
|
failWithCode(expr, "HSX1001", `invalid family ${rawPath}`, "use module.instrument or module.instrument.record");
|
|
479
524
|
}
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
if (!targetTemplate) {
|
|
525
|
+
loadPath(rawPath, expr);
|
|
526
|
+
const digest = familyDigests.get(rawPath);
|
|
527
|
+
if (!digest) {
|
|
484
528
|
if (!required)
|
|
485
529
|
return undefined;
|
|
486
530
|
failWithCode(expr, "HSX1001", `unknown family declaration ${rawPath}`, "choose a declared standard instrument");
|
|
487
531
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
const topBody = entries(topTemplate.body);
|
|
495
|
-
let revision;
|
|
496
|
-
if (topBody.has("familyRevision")) {
|
|
497
|
-
const val = literal(topBody.get("familyRevision"));
|
|
498
|
-
if (typeof val === "number")
|
|
499
|
-
revision = val;
|
|
500
|
-
}
|
|
501
|
-
let currentBody = topTemplate.body;
|
|
502
|
-
for (const recName of parts.slice(2)) {
|
|
503
|
-
const recs = entries(asBlock(entries(currentBody).get("records")));
|
|
504
|
-
const child = recs.get(recName);
|
|
505
|
-
if (child) {
|
|
506
|
-
currentBody = asBlock(child);
|
|
507
|
-
const cBody = entries(currentBody);
|
|
508
|
-
if (cBody.has("familyRevision")) {
|
|
509
|
-
const val = literal(cBody.get("familyRevision"));
|
|
510
|
-
if (typeof val === "number")
|
|
511
|
-
revision = val;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (revision === undefined) {
|
|
516
|
-
if (!required)
|
|
517
|
-
return undefined;
|
|
518
|
-
failWithCode(expr, "HSX1001", `declaration ${rawPath} has no familyRevision declared`, "declare familyRevision on the standard instrument");
|
|
519
|
-
}
|
|
520
|
-
return { module: moduleName, exportPath, revision };
|
|
532
|
+
return {
|
|
533
|
+
module: parts[0],
|
|
534
|
+
exportPath: parts.slice(1).join("."),
|
|
535
|
+
digest,
|
|
536
|
+
};
|
|
521
537
|
};
|
|
522
538
|
const addInstrument = (decl, id, arguments_, origin, inherited = new Map(), inheritedEnums = new Map(), attachmentInfo, familyDeclaration, standardOrigin,
|
|
523
|
-
/**
|
|
524
|
-
|
|
539
|
+
/** A child record's terms are its parent's. */
|
|
540
|
+
inheritedTerms = new Map()) => {
|
|
525
541
|
const resolveFamilyInstruments = (family, currentInstId, expr) => {
|
|
526
542
|
const found = new Set();
|
|
527
543
|
if (currentInstId && familyDeclaration) {
|
|
@@ -551,7 +567,8 @@ export function compile(source, options = {}) {
|
|
|
551
567
|
if (inst.family &&
|
|
552
568
|
inst.family.module === family.module &&
|
|
553
569
|
inst.family.exportPath === family.exportPath &&
|
|
554
|
-
inst.family
|
|
570
|
+
"digest" in inst.family &&
|
|
571
|
+
inst.family.digest === family.digest) {
|
|
555
572
|
found.add(inst.id);
|
|
556
573
|
}
|
|
557
574
|
}
|
|
@@ -593,6 +610,7 @@ export function compile(source, options = {}) {
|
|
|
593
610
|
};
|
|
594
611
|
const getInstrumentFamily = (targetId) => {
|
|
595
612
|
const existing = document.instruments.find((i) => i.id === targetId);
|
|
613
|
+
// This document is format 2, so every family it holds is a digest.
|
|
596
614
|
if (existing?.family)
|
|
597
615
|
return existing.family;
|
|
598
616
|
if (targetId.startsWith(`${id}_`) && familyDeclaration) {
|
|
@@ -604,7 +622,7 @@ export function compile(source, options = {}) {
|
|
|
604
622
|
}
|
|
605
623
|
for (const [asgnName, asgn] of assignments) {
|
|
606
624
|
if (targetId === asgnName || targetId.startsWith(`${asgnName}_`)) {
|
|
607
|
-
const tmpl =
|
|
625
|
+
const tmpl = templateAt(asgn.target);
|
|
608
626
|
if (!tmpl)
|
|
609
627
|
continue;
|
|
610
628
|
const mod = declarationSources.get(tmpl);
|
|
@@ -630,11 +648,8 @@ export function compile(source, options = {}) {
|
|
|
630
648
|
if (typeof tid !== "string")
|
|
631
649
|
fail(expr, "instrument target needs a name", "name a declared instrument or a list of instruments");
|
|
632
650
|
const fam = getInstrumentFamily(tid);
|
|
633
|
-
if (!fam
|
|
634
|
-
|
|
635
|
-
fam.exportPath !== expectedFamily.exportPath ||
|
|
636
|
-
fam.revision !== expectedFamily.revision) {
|
|
637
|
-
failWithCode(expr, "HSX1001", `target instrument ${tid} family does not match expected family ${expectedFamily.module}.${expectedFamily.exportPath} (revision ${expectedFamily.revision})`, "ensure target instrument matches the declared family");
|
|
651
|
+
if (!sameFamily(fam, expectedFamily)) {
|
|
652
|
+
failWithCode(expr, "HSX1001", `target instrument ${tid} family does not match expected family ${expectedFamily.module}.${expectedFamily.exportPath}`, "ensure target instrument matches the declared family");
|
|
638
653
|
}
|
|
639
654
|
}
|
|
640
655
|
};
|
|
@@ -646,22 +661,22 @@ export function compile(source, options = {}) {
|
|
|
646
661
|
if (type.kind === "call" && type.name === "enum")
|
|
647
662
|
enums.set(parameter.key, type.args.map(text));
|
|
648
663
|
}
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
for (const entry of arguments_.entries) {
|
|
652
|
-
if (supplied.has(entry.key))
|
|
653
|
-
fail(entry, `duplicate tunable ${entry.key}`, "supply each parameter once");
|
|
654
|
-
supplied.set(entry.key, entry.value);
|
|
655
|
-
suppliedEntries.set(entry.key, entry);
|
|
656
|
-
}
|
|
664
|
+
const suppliedEntries = given(arguments_, () => "equal");
|
|
665
|
+
const supplied = new Map([...suppliedEntries].map(([key, entry]) => [key, entry.value]));
|
|
657
666
|
const environment = new Map(inherited);
|
|
658
667
|
// `when every is set` asks whether the author bound a tunable; a
|
|
659
668
|
// default leaves it unset.
|
|
660
|
-
const
|
|
669
|
+
const terms = new Map(inheritedTerms);
|
|
670
|
+
const isSet = (key) => (terms.get(key)?.from ?? "std") !== "std";
|
|
661
671
|
const parameterErrors = [];
|
|
672
|
+
// A std default set back is the header as it was, so its digest too.
|
|
673
|
+
let revertedHeader;
|
|
662
674
|
for (const param of decl.parameters) {
|
|
663
675
|
const type = param.value.kind === "default" ? param.value.type : param.value;
|
|
664
|
-
const
|
|
676
|
+
const overridden = override?.instrument === id && override.name === param.key;
|
|
677
|
+
const fallback = param.value.kind === "default" && !overridden
|
|
678
|
+
? param.value.value
|
|
679
|
+
: undefined;
|
|
665
680
|
const typeName = type.kind === "type" || type.kind === "call" ? type.name : text(type);
|
|
666
681
|
const partyParameter = attachmentInfo && typeName === "party";
|
|
667
682
|
const optional = (type.kind === "type" || type.kind === "call") && !!type.optional;
|
|
@@ -669,17 +684,65 @@ export function compile(source, options = {}) {
|
|
|
669
684
|
// program party that shares its name does not switch it on.
|
|
670
685
|
const byName = partyParameter &&
|
|
671
686
|
!optional &&
|
|
687
|
+
!overridden &&
|
|
672
688
|
(subjectPartyRoles.includes(param.key) ||
|
|
673
689
|
document.parties[param.key])
|
|
674
690
|
? { kind: "name", value: param.key, span: origin }
|
|
675
691
|
: undefined;
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
...
|
|
681
|
-
|
|
682
|
-
|
|
692
|
+
const header = declarationSources.get(decl) ?? "program";
|
|
693
|
+
const forced = !overridden || override.text === "unset"
|
|
694
|
+
? undefined
|
|
695
|
+
: {
|
|
696
|
+
...(parseExpression(override.text) ??
|
|
697
|
+
fail({ span: origin }, `${param.key} cannot hold ${override.text}`, "override with text the term once held")),
|
|
698
|
+
source: "override",
|
|
699
|
+
};
|
|
700
|
+
const written = overridden ? forced : supplied.get(param.key);
|
|
701
|
+
if (overridden && override.from === "std")
|
|
702
|
+
revertedHeader = {
|
|
703
|
+
...decl,
|
|
704
|
+
parameters: decl.parameters.map((entry) => entry !== param
|
|
705
|
+
? entry
|
|
706
|
+
: {
|
|
707
|
+
...entry,
|
|
708
|
+
value: forced
|
|
709
|
+
? {
|
|
710
|
+
kind: "default",
|
|
711
|
+
type,
|
|
712
|
+
value: forced,
|
|
713
|
+
span: entry.span,
|
|
714
|
+
}
|
|
715
|
+
: type,
|
|
716
|
+
}),
|
|
717
|
+
};
|
|
718
|
+
terms.set(param.key, overridden
|
|
719
|
+
? {
|
|
720
|
+
...(forced ? { value: forced } : {}),
|
|
721
|
+
from: override.from,
|
|
722
|
+
span: forced?.span ?? origin,
|
|
723
|
+
source: "override",
|
|
724
|
+
}
|
|
725
|
+
: written
|
|
726
|
+
? {
|
|
727
|
+
value: written,
|
|
728
|
+
from: "program",
|
|
729
|
+
span: written.span,
|
|
730
|
+
source: "program",
|
|
731
|
+
}
|
|
732
|
+
: byName
|
|
733
|
+
? {
|
|
734
|
+
value: byName,
|
|
735
|
+
from: "party",
|
|
736
|
+
span: origin,
|
|
737
|
+
source: "program",
|
|
738
|
+
}
|
|
739
|
+
: {
|
|
740
|
+
...(fallback ? { value: fallback } : {}),
|
|
741
|
+
from: "std",
|
|
742
|
+
span: (fallback ?? param).span,
|
|
743
|
+
source: header,
|
|
744
|
+
});
|
|
745
|
+
const actual = written ?? byName ?? (fallback && { ...fallback, source: header });
|
|
683
746
|
if (!actual) {
|
|
684
747
|
if (optional)
|
|
685
748
|
continue;
|
|
@@ -704,44 +767,22 @@ export function compile(source, options = {}) {
|
|
|
704
767
|
}
|
|
705
768
|
environment.set(param.key, actual);
|
|
706
769
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (!(error instanceof BindingContractError))
|
|
715
|
-
throw error;
|
|
716
|
-
throw new CompileFailure({
|
|
717
|
-
code: "HSX1001",
|
|
718
|
-
message: error.message,
|
|
719
|
-
fix: "Repair the header binding contract.",
|
|
720
|
-
span: error.entry.span,
|
|
721
|
-
source: declarationSources.get(decl) ?? "program",
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
for (const dependency of dependencies) {
|
|
725
|
-
const selected = environment.get(dependency.when);
|
|
726
|
-
const holds = enums.has(dependency.when)
|
|
727
|
-
? selected?.kind === "name" && selected.value === dependency.is
|
|
728
|
-
: tunables.get(dependency.when) === (dependency.is === "set");
|
|
729
|
-
if (holds && !environment.has(dependency.binding)) {
|
|
730
|
-
throw new CompileFailure({
|
|
731
|
-
code: "HSX1001",
|
|
732
|
-
message: dependency.message.replaceAll("{attachment}", attachmentInfo?.attachmentName ?? id),
|
|
733
|
-
fix: dependency.fix,
|
|
734
|
-
span: origin,
|
|
735
|
-
related: [
|
|
736
|
-
{
|
|
737
|
-
source: declarationSources.get(decl) ?? "program",
|
|
738
|
-
span: dependency.span,
|
|
739
|
-
message: "Conditional binding requirement",
|
|
740
|
-
},
|
|
741
|
-
],
|
|
742
|
-
});
|
|
770
|
+
const checks = instrumentChecks(decl);
|
|
771
|
+
const say = (message) => message
|
|
772
|
+
.replaceAll("{attachment}", attachmentInfo?.attachmentName ?? id)
|
|
773
|
+
.replaceAll("{instrument}", assignments.get(id)?.target ?? decl.name);
|
|
774
|
+
const collect = (run) => {
|
|
775
|
+
try {
|
|
776
|
+
run();
|
|
743
777
|
}
|
|
744
|
-
|
|
778
|
+
catch (error) {
|
|
779
|
+
if (!(error instanceof CompileFailure))
|
|
780
|
+
throw error;
|
|
781
|
+
parameterErrors.push(error.diagnostic);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
// Each tunable that failed its type rule, so no check compares it.
|
|
785
|
+
const broken = new Set();
|
|
745
786
|
for (const key of supplied.keys()) {
|
|
746
787
|
if (decl.parameters.some((p) => p.key === key))
|
|
747
788
|
continue;
|
|
@@ -834,7 +875,7 @@ export function compile(source, options = {}) {
|
|
|
834
875
|
const subject = attachmentSubjects.get(assignment.name);
|
|
835
876
|
return `\`${subject}.${assignment.name.slice(subject.length + 1)}\``;
|
|
836
877
|
});
|
|
837
|
-
const target =
|
|
878
|
+
const target = templateAt(type);
|
|
838
879
|
const required = target?.parameters
|
|
839
880
|
.filter((p) => p.value.kind !== "default" &&
|
|
840
881
|
!((p.value.kind === "type" || p.value.kind === "call") &&
|
|
@@ -967,7 +1008,7 @@ export function compile(source, options = {}) {
|
|
|
967
1008
|
fail(v, `${param.key} accepts one reference`, "use one object name");
|
|
968
1009
|
if (!values.length || values.length > 16)
|
|
969
1010
|
fail(v, "reference union needs 1 to 16 objects", "use at most 16 distinct object names");
|
|
970
|
-
const seen = new
|
|
1011
|
+
const seen = new Map();
|
|
971
1012
|
for (const value of values) {
|
|
972
1013
|
if (value.kind !== "name")
|
|
973
1014
|
fail(value, "reference needs an object name", "name a declared object");
|
|
@@ -986,7 +1027,7 @@ export function compile(source, options = {}) {
|
|
|
986
1027
|
if (!value.value.startsWith(`${instance}.`) &&
|
|
987
1028
|
!value.value.startsWith(`${instance}_`))
|
|
988
1029
|
continue;
|
|
989
|
-
const template =
|
|
1030
|
+
const template = templateAt(target);
|
|
990
1031
|
if (!template)
|
|
991
1032
|
continue;
|
|
992
1033
|
const suffix = value.value
|
|
@@ -999,9 +1040,10 @@ export function compile(source, options = {}) {
|
|
|
999
1040
|
if (!targetType ||
|
|
1000
1041
|
(t.kind === "type" && t.target && targetType !== t.target))
|
|
1001
1042
|
fail(value, `${param.key} has the wrong object type`, `use an object of type ${t.kind === "type" ? t.target : "ref"}`);
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1043
|
+
const first = seen.get(value.value);
|
|
1044
|
+
if (first)
|
|
1045
|
+
conflict(texts, `${param.key} lists ${value.value} twice`, first, value, "list each object once");
|
|
1046
|
+
seen.set(value.value, value);
|
|
1005
1047
|
}
|
|
1006
1048
|
// A many-reference tunable is a list even when one object is bound,
|
|
1007
1049
|
// so report datasets and other value positions never see a bare name.
|
|
@@ -1049,11 +1091,8 @@ export function compile(source, options = {}) {
|
|
|
1049
1091
|
: `set \`${param.key}: runtime\``;
|
|
1050
1092
|
fail(v, `\`${param.key}\` cannot read \`${v.value}\` as a tunable. This position accepts a fixed money amount or a runtime amount.`, `For a varying deposit, ${advice} and add \`rename { ${param.key}: ${target} }\`. Use a literal only for a fixed charge.`);
|
|
1051
1093
|
}
|
|
1052
|
-
if (type === "money" && v.kind === "percent")
|
|
1053
|
-
|
|
1054
|
-
fail(v, `\`${assignments.get(id)?.target ?? decl.name}.${param.key}\` accepts ${policy?.accepts ?? "a fixed amount"}. It cannot express ${policy?.percentage ?? "a percentage-based charge"}.`, policy?.fix ??
|
|
1055
|
-
"Use a fixed amount only if that is the intended policy. A percentage charge needs an authored rate calculation; do not approximate it with a cash amount.");
|
|
1056
|
-
}
|
|
1094
|
+
if (type === "money" && v.kind === "percent")
|
|
1095
|
+
fail(v, `\`${assignments.get(id)?.target ?? decl.name}.${param.key}\` accepts a fixed amount. It cannot express a percentage-based charge.`, "Use a fixed amount only if that is the intended policy. A percentage charge needs an authored rate calculation; do not approximate it with a cash amount.");
|
|
1057
1096
|
fail(v, `${param.key} needs ${type}`, `write a ${type} literal`);
|
|
1058
1097
|
}
|
|
1059
1098
|
try {
|
|
@@ -1093,91 +1132,137 @@ export function compile(source, options = {}) {
|
|
|
1093
1132
|
span: party.span,
|
|
1094
1133
|
message: `Declared party ${party.name}`,
|
|
1095
1134
|
});
|
|
1096
|
-
|
|
1135
|
+
// A header's `x is <type>` check explains this tunable's type rule.
|
|
1136
|
+
const typed = checks.find(({ test }) => test.kind === "type" && test.tunable === param.key);
|
|
1137
|
+
broken.add(param.key);
|
|
1138
|
+
parameterErrors.push({
|
|
1139
|
+
...error.diagnostic,
|
|
1140
|
+
...(typed ? { message: say(typed.message), fix: typed.fix } : {}),
|
|
1141
|
+
related,
|
|
1142
|
+
});
|
|
1097
1143
|
}
|
|
1098
1144
|
}
|
|
1099
|
-
if (parameterErrors.length) {
|
|
1100
|
-
bindingDiagnostics.push(...parameterErrors.slice(1));
|
|
1101
|
-
throw new CompileFailure(parameterErrors[0]);
|
|
1102
|
-
}
|
|
1103
1145
|
const body = entries(decl.body);
|
|
1104
1146
|
// A ceiling caps a tunable by the value of an enum tunable, such as the
|
|
1105
1147
|
// longest term per loan kind. An unset tunable takes the ceiling; a
|
|
1106
1148
|
// supplied one may lower it, never raise it.
|
|
1107
|
-
for (const ceiling of asBlock(body.get("ceilings")).entries)
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1149
|
+
for (const ceiling of asBlock(body.get("ceilings")).entries)
|
|
1150
|
+
collect(() => {
|
|
1151
|
+
const table = entries(asBlock(ceiling.value));
|
|
1152
|
+
const by = table.get("by");
|
|
1153
|
+
const selected = by && environment.get(text(by));
|
|
1154
|
+
if (!by || selected?.kind !== "name")
|
|
1155
|
+
fail(ceiling, `ceiling ${ceiling.key} needs by: an enum tunable`, "write by: kind and one maximum per enum value");
|
|
1156
|
+
const maximum = table.get(selected.value);
|
|
1157
|
+
if (maximum?.kind !== "number")
|
|
1158
|
+
fail(ceiling, `ceiling ${ceiling.key} has no maximum for ${selected.value}`, `add ${selected.value}: number to the ceiling`);
|
|
1159
|
+
if (!isSet(ceiling.key)) {
|
|
1160
|
+
const term = terms.get(ceiling.key);
|
|
1161
|
+
if (term)
|
|
1162
|
+
terms.set(ceiling.key, {
|
|
1163
|
+
...term,
|
|
1164
|
+
value: maximum,
|
|
1165
|
+
span: maximum.span,
|
|
1166
|
+
});
|
|
1167
|
+
environment.set(ceiling.key, { ...maximum, span: origin });
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const supplied = environment.get(ceiling.key);
|
|
1171
|
+
if (supplied.kind !== "number" ||
|
|
1172
|
+
BigInt(supplied.value) > BigInt(maximum.value))
|
|
1173
|
+
fail(supplied, `${ceiling.key} for ${selected.value} is at most ${maximum.value}`, `choose ${ceiling.key} of ${maximum.value} or less, or omit it`);
|
|
1174
|
+
});
|
|
1175
|
+
// Every check runs, so one attach reports each broken rule at once.
|
|
1176
|
+
for (const check of checks)
|
|
1177
|
+
collect(() => {
|
|
1178
|
+
const { test, when } = check;
|
|
1179
|
+
if (when) {
|
|
1180
|
+
const selected = environment.get(when.selector);
|
|
1181
|
+
const holds = enums.has(when.selector)
|
|
1182
|
+
? selected?.kind === "name" && selected.value === when.is
|
|
1183
|
+
: terms.has(when.selector) &&
|
|
1184
|
+
isSet(when.selector) === (when.is === "set");
|
|
1185
|
+
if (!holds)
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (test.kind === "type")
|
|
1189
|
+
return;
|
|
1190
|
+
let at;
|
|
1191
|
+
if (test.kind === "set") {
|
|
1192
|
+
if (isSet(test.tunable))
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
const terms = [...test.left, ...test.right];
|
|
1197
|
+
const value = (term) => term.kind === "name" ? environment.get(term.value) : term;
|
|
1198
|
+
// An unset or broken tunable has nothing to compare, and a
|
|
1199
|
+
// runtime value is checked when the agreement is created.
|
|
1200
|
+
if (terms.some((term) => (term.kind === "name" && broken.has(term.value)) ||
|
|
1201
|
+
!value(term) ||
|
|
1202
|
+
(value(term).kind === "name" &&
|
|
1203
|
+
value(term).value === "runtime")))
|
|
1204
|
+
return;
|
|
1205
|
+
const kind = (term) => {
|
|
1206
|
+
const expr = value(term);
|
|
1207
|
+
return expr.kind === "name" && /^P(?:\d|T)/.test(expr.value)
|
|
1208
|
+
? "duration"
|
|
1209
|
+
: expr.kind;
|
|
1210
|
+
};
|
|
1211
|
+
const kinds = new Set(terms.map(kind));
|
|
1212
|
+
if (kinds.size !== 1 ||
|
|
1213
|
+
!["money", "number", "percent", "duration"].includes(kind(terms[0])))
|
|
1214
|
+
fail(check.rule, "check compares values of different types", "compare amounts, integers, percentages or durations of one type");
|
|
1215
|
+
const total = (side) => side.reduce((sum, term) => sum + BigInt(String(literal(value(term)))), 0n);
|
|
1216
|
+
const [a, b] = [total(test.left), total(test.right)];
|
|
1217
|
+
const holds = {
|
|
1218
|
+
"<": a < b,
|
|
1219
|
+
"<=": a <= b,
|
|
1220
|
+
">": a > b,
|
|
1221
|
+
">=": a >= b,
|
|
1222
|
+
"==": a === b,
|
|
1223
|
+
"!=": a !== b,
|
|
1224
|
+
}[test.operator];
|
|
1225
|
+
if (holds)
|
|
1226
|
+
return;
|
|
1227
|
+
at = terms
|
|
1228
|
+
.map((term) => term.kind === "name"
|
|
1229
|
+
? suppliedEntries.get(term.value)
|
|
1230
|
+
: undefined)
|
|
1231
|
+
.find(Boolean);
|
|
1232
|
+
}
|
|
1233
|
+
throw new CompileFailure({
|
|
1234
|
+
code: "HSX1001",
|
|
1235
|
+
message: say(check.message),
|
|
1236
|
+
fix: check.fix,
|
|
1237
|
+
span: at?.value.span ?? origin,
|
|
1238
|
+
related: [
|
|
1239
|
+
{
|
|
1240
|
+
source: check.source ?? "program",
|
|
1241
|
+
span: check.span,
|
|
1242
|
+
message: "Header check",
|
|
1243
|
+
},
|
|
1244
|
+
],
|
|
1245
|
+
});
|
|
1246
|
+
});
|
|
1247
|
+
if (parameterErrors.length) {
|
|
1248
|
+
bindingDiagnostics.push(...parameterErrors.slice(1));
|
|
1249
|
+
throw new CompileFailure(parameterErrors[0]);
|
|
1150
1250
|
}
|
|
1151
1251
|
for (const key of body.keys())
|
|
1152
1252
|
if (![
|
|
1153
|
-
"familyRevision",
|
|
1154
1253
|
"fields",
|
|
1155
1254
|
"lifecycle",
|
|
1156
1255
|
"records",
|
|
1157
1256
|
"summary",
|
|
1158
1257
|
"invariants",
|
|
1159
|
-
"
|
|
1258
|
+
"checks",
|
|
1160
1259
|
"ceilings",
|
|
1161
|
-
"dependencies",
|
|
1162
|
-
"parameterDiagnostics",
|
|
1163
1260
|
"reports",
|
|
1164
1261
|
"revisioned",
|
|
1165
1262
|
"scope",
|
|
1166
1263
|
].includes(key) &&
|
|
1167
1264
|
!key.startsWith("action "))
|
|
1168
1265
|
fail(decl, `unknown instrument clause ${key}`, "use fields, lifecycle, actions, invariants, or records");
|
|
1169
|
-
if (body.has("familyRevision")) {
|
|
1170
|
-
const revExpr = body.get("familyRevision");
|
|
1171
|
-
const revVal = literal(revExpr);
|
|
1172
|
-
if (typeof revVal !== "number" ||
|
|
1173
|
-
!Number.isInteger(revVal) ||
|
|
1174
|
-
revVal <= 0) {
|
|
1175
|
-
fail(revExpr, "familyRevision must be a positive integer", "use a positive integer revision");
|
|
1176
|
-
}
|
|
1177
|
-
if (familyDeclaration) {
|
|
1178
|
-
familyDeclaration.revision = revVal;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
1266
|
const records = entries(asBlock(body.get("records")));
|
|
1182
1267
|
if (!inherited.size)
|
|
1183
1268
|
environment.set("parent", { kind: "name", value: id, span: origin });
|
|
@@ -1354,9 +1439,9 @@ export function compile(source, options = {}) {
|
|
|
1354
1439
|
// optional tunable and a defaulted one both read as unset.
|
|
1355
1440
|
if (relation === "is" &&
|
|
1356
1441
|
(choice === "set" || choice === "unset") &&
|
|
1357
|
-
|
|
1442
|
+
terms.has(tunable) &&
|
|
1358
1443
|
!enums.has(tunable))
|
|
1359
|
-
return
|
|
1444
|
+
return isSet(tunable) === (choice === "set");
|
|
1360
1445
|
const binding = environment.get(tunable);
|
|
1361
1446
|
if (!binding)
|
|
1362
1447
|
fail(entry, `unknown branch tunable ${tunable}`, "name an enum, reference or optional tunable declared by this header");
|
|
@@ -1367,7 +1452,7 @@ export function compile(source, options = {}) {
|
|
|
1367
1452
|
const [root, ...children] = text(bound).split(".");
|
|
1368
1453
|
const assignment = assignments.get(root);
|
|
1369
1454
|
let target = assignment
|
|
1370
|
-
?
|
|
1455
|
+
? templateAt(assignment.target)?.body
|
|
1371
1456
|
: program.decls
|
|
1372
1457
|
.filter((decl) => decl.kind === "instrument")
|
|
1373
1458
|
.find((decl) => decl.name === root)?.body;
|
|
@@ -1775,10 +1860,9 @@ export function compile(source, options = {}) {
|
|
|
1775
1860
|
operation,
|
|
1776
1861
|
requirements: validatedRequirements,
|
|
1777
1862
|
};
|
|
1778
|
-
const digest = sha256(new TextEncoder().encode(canonicalJson(declaration)));
|
|
1779
1863
|
const snapshot = {
|
|
1780
1864
|
...declaration,
|
|
1781
|
-
declarationDigest:
|
|
1865
|
+
declarationDigest: digest(declaration),
|
|
1782
1866
|
};
|
|
1783
1867
|
const adapterRenames = {};
|
|
1784
1868
|
if (attachmentInfo?.renames) {
|
|
@@ -1886,7 +1970,6 @@ export function compile(source, options = {}) {
|
|
|
1886
1970
|
summary: slots.has("summary")
|
|
1887
1971
|
? String(data(slots.get("summary")))
|
|
1888
1972
|
: title(name),
|
|
1889
|
-
publicAction: camel(`${name}_${id}`),
|
|
1890
1973
|
event: `${id}.${name}`,
|
|
1891
1974
|
actor: "caller",
|
|
1892
1975
|
input: lowerFields(asBlock(slots.get("input"))),
|
|
@@ -2315,18 +2398,12 @@ export function compile(source, options = {}) {
|
|
|
2315
2398
|
}
|
|
2316
2399
|
if (moveCalculations.length)
|
|
2317
2400
|
a.calculate = [...(a.calculate ?? []), ...moveCalculations];
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
}
|
|
2324
|
-
else {
|
|
2325
|
-
delete a.publicAction;
|
|
2326
|
-
}
|
|
2401
|
+
// An action is hidden until an expose line names it.
|
|
2402
|
+
if (attachmentInfo?.exposed.has(name)) {
|
|
2403
|
+
if (automatic(a.actor))
|
|
2404
|
+
fail(row, `${name} runs on the clock or its parent, not a caller`, "expose only caller actions");
|
|
2405
|
+
a.publicAction = attachmentInfo.exposed.get(name);
|
|
2327
2406
|
}
|
|
2328
|
-
if (automatic(a.actor))
|
|
2329
|
-
delete a.publicAction;
|
|
2330
2407
|
const checkSubjectPaths = (obj, key = "") => {
|
|
2331
2408
|
if (typeof obj === "string") {
|
|
2332
2409
|
if ([
|
|
@@ -2470,15 +2547,33 @@ export function compile(source, options = {}) {
|
|
|
2470
2547
|
origins.push({
|
|
2471
2548
|
path: `$.instruments[${document.instruments.length}]`,
|
|
2472
2549
|
span: { ...origin, ...lineColAt(source, origin.start) },
|
|
2473
|
-
...(standardOrigin
|
|
2550
|
+
...(standardOrigin
|
|
2551
|
+
? {
|
|
2552
|
+
standardBlock: standardOrigin,
|
|
2553
|
+
terms: [...terms].map(([name, term]) => {
|
|
2554
|
+
const text = sources.get(term.source);
|
|
2555
|
+
return {
|
|
2556
|
+
name,
|
|
2557
|
+
from: term.from,
|
|
2558
|
+
text: !term.value
|
|
2559
|
+
? "unset"
|
|
2560
|
+
: term.from === "party"
|
|
2561
|
+
? name
|
|
2562
|
+
: text.slice(term.span.start, term.span.end),
|
|
2563
|
+
source: term.source,
|
|
2564
|
+
...lineColAt(text, term.span.start),
|
|
2565
|
+
};
|
|
2566
|
+
}),
|
|
2567
|
+
}
|
|
2568
|
+
: {}),
|
|
2474
2569
|
});
|
|
2475
|
-
if (familyDeclaration
|
|
2570
|
+
if (familyDeclaration)
|
|
2476
2571
|
inst.family = {
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2572
|
+
...familyDeclaration,
|
|
2573
|
+
...(revertedHeader
|
|
2574
|
+
? { digest: declarationDigest(revertedHeader) }
|
|
2575
|
+
: {}),
|
|
2480
2576
|
};
|
|
2481
|
-
}
|
|
2482
2577
|
document.instruments.push(inst);
|
|
2483
2578
|
declarations.set(inst, decl);
|
|
2484
2579
|
for (const [key, definition] of records) {
|
|
@@ -2509,13 +2604,7 @@ export function compile(source, options = {}) {
|
|
|
2509
2604
|
};
|
|
2510
2605
|
declarationSources.set(child, declarationSources.get(decl) ?? "program");
|
|
2511
2606
|
const childFamily = familyDeclaration
|
|
2512
|
-
? {
|
|
2513
|
-
module: familyDeclaration.module,
|
|
2514
|
-
exportPath: `${familyDeclaration.exportPath}.${key}`,
|
|
2515
|
-
...(familyDeclaration.revision !== undefined
|
|
2516
|
-
? { revision: familyDeclaration.revision }
|
|
2517
|
-
: {}),
|
|
2518
|
-
}
|
|
2607
|
+
? resolveFamily(`${familyDeclaration.module}.${familyDeclaration.exportPath}.${key}`, { span: origin }, false)
|
|
2519
2608
|
: undefined;
|
|
2520
2609
|
addInstrument(child, `${id}_${key}`, emptyBlock, origin, new Map([
|
|
2521
2610
|
...environment,
|
|
@@ -2527,7 +2616,7 @@ export function compile(source, options = {}) {
|
|
|
2527
2616
|
.filter(Boolean)
|
|
2528
2617
|
.join("."),
|
|
2529
2618
|
}
|
|
2530
|
-
: undefined,
|
|
2619
|
+
: undefined, terms);
|
|
2531
2620
|
}
|
|
2532
2621
|
};
|
|
2533
2622
|
const compileObject = (decl) => {
|
|
@@ -2561,10 +2650,7 @@ export function compile(source, options = {}) {
|
|
|
2561
2650
|
}
|
|
2562
2651
|
const attachmentName = match[1];
|
|
2563
2652
|
const targetTemplate = match[2];
|
|
2564
|
-
const template =
|
|
2565
|
-
if (!template) {
|
|
2566
|
-
fail(entry, `unknown instrument ${targetTemplate}`, `add use ${targetTemplate.split(".")[0]} and choose a declared instrument`);
|
|
2567
|
-
}
|
|
2653
|
+
const template = requireTemplate(targetTemplate, entry);
|
|
2568
2654
|
const instId = `${decl.name}_${attachmentName}`;
|
|
2569
2655
|
const attachmentBlock = asBlock(entry.value);
|
|
2570
2656
|
const renames = new Map();
|
|
@@ -2578,8 +2664,9 @@ export function compile(source, options = {}) {
|
|
|
2578
2664
|
}
|
|
2579
2665
|
else if (row.key === "rename") {
|
|
2580
2666
|
for (const r of asBlock(row.value).entries) {
|
|
2581
|
-
|
|
2582
|
-
|
|
2667
|
+
const first = renameEntries.get(r.key);
|
|
2668
|
+
if (first)
|
|
2669
|
+
conflict(texts, `rename ${r.key} is given twice`, first, r, "rename each subject field once", { values: [first.value, r.value] });
|
|
2583
2670
|
renames.set(r.key, text(r.value));
|
|
2584
2671
|
renameEntries.set(r.key, r);
|
|
2585
2672
|
}
|
|
@@ -2588,9 +2675,12 @@ export function compile(source, options = {}) {
|
|
|
2588
2675
|
if (row.value.kind === "call") {
|
|
2589
2676
|
const actionName = row.value.name;
|
|
2590
2677
|
const publicName = text(row.value.args[0]);
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2678
|
+
const first = exposures.find((exposure) => exposure.instrument === instId &&
|
|
2679
|
+
exposure.action === actionName);
|
|
2680
|
+
if (first)
|
|
2681
|
+
conflict(texts, `${actionName} is exposed twice`, first.entry, row, "expose each action once");
|
|
2682
|
+
if (!template.body.entries.some((entry) => entry.key === `action ${actionName}`))
|
|
2683
|
+
fail(row, `unknown action ${actionName}`, "expose one declared action");
|
|
2594
2684
|
exposed.set(actionName, publicName);
|
|
2595
2685
|
exposures.push({
|
|
2596
2686
|
instrument: instId,
|
|
@@ -2623,7 +2713,7 @@ export function compile(source, options = {}) {
|
|
|
2623
2713
|
attachments,
|
|
2624
2714
|
economics,
|
|
2625
2715
|
template: targetTemplate,
|
|
2626
|
-
}, templateFamily
|
|
2716
|
+
}, templateFamily, standardOrigins.get(template));
|
|
2627
2717
|
}
|
|
2628
2718
|
catch (error) {
|
|
2629
2719
|
if (!(error instanceof CompileFailure))
|
|
@@ -2757,11 +2847,9 @@ export function compile(source, options = {}) {
|
|
|
2757
2847
|
addInstrument(decl, decl.name, emptyBlock, decl.span);
|
|
2758
2848
|
}
|
|
2759
2849
|
if (decl.kind === "assignment") {
|
|
2760
|
-
const template =
|
|
2761
|
-
if (!template)
|
|
2762
|
-
fail(decl, `unknown instrument ${decl.target}`, `add use ${decl.target.split(".")[0]} and choose a declared instrument`);
|
|
2850
|
+
const template = requireTemplate(decl.target, decl);
|
|
2763
2851
|
const templateFamily = resolveFamily(decl.target, decl, false);
|
|
2764
|
-
addInstrument(template, decl.name, decl.body, decl.span, new Map(), new Map(), undefined, templateFamily
|
|
2852
|
+
addInstrument(template, decl.name, decl.body, decl.span, new Map(), new Map(), undefined, templateFamily, standardOrigins.get(template));
|
|
2765
2853
|
}
|
|
2766
2854
|
if (decl.kind === "object") {
|
|
2767
2855
|
compileObject(decl);
|
|
@@ -2787,7 +2875,8 @@ export function compile(source, options = {}) {
|
|
|
2787
2875
|
.map((id) => document.instruments.find((item) => item.id === id)?.family);
|
|
2788
2876
|
const family = families[0];
|
|
2789
2877
|
if (family &&
|
|
2790
|
-
families.every((item) =>
|
|
2878
|
+
families.every((item) => item !== undefined &&
|
|
2879
|
+
canonicalJson(item) === canonicalJson(family)))
|
|
2791
2880
|
selection.family = family;
|
|
2792
2881
|
}
|
|
2793
2882
|
for (const child of Object.values(record))
|
|
@@ -3070,7 +3159,7 @@ export function compile(source, options = {}) {
|
|
|
3070
3159
|
const declaredMembers = (selection) => {
|
|
3071
3160
|
const members = [selection.instrument].flat();
|
|
3072
3161
|
const header = selection.family
|
|
3073
|
-
?
|
|
3162
|
+
? templateAt(`${selection.family.module}.${selection.family.exportPath}`)
|
|
3074
3163
|
: undefined;
|
|
3075
3164
|
if (!header ||
|
|
3076
3165
|
!entries(asBlock(entries(header.body).get("fields"))).has(selection.reference))
|
|
@@ -3305,31 +3394,38 @@ export function compile(source, options = {}) {
|
|
|
3305
3394
|
if (!document.instruments.find((inst) => inst.id === exposure.instrument)
|
|
3306
3395
|
?.actions[exposure.action])
|
|
3307
3396
|
fail(exposure.entry, `action ${exposure.action} is excluded by these bindings`, "expose an action available with these tunables");
|
|
3308
|
-
const changed = new
|
|
3397
|
+
const changed = new Map();
|
|
3309
3398
|
for (const decl of program.decls)
|
|
3310
|
-
if (decl.kind === "
|
|
3399
|
+
if (decl.kind === "expose") {
|
|
3311
3400
|
const parts = decl.target.split(".");
|
|
3312
3401
|
const key = parts.pop();
|
|
3313
3402
|
const id = parts.join("_");
|
|
3314
3403
|
const action = document.instruments.find((i) => i.id === id)?.actions[key ?? ""];
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
if (
|
|
3319
|
-
|
|
3320
|
-
|
|
3404
|
+
const first = changed.get(decl.target);
|
|
3405
|
+
if (first)
|
|
3406
|
+
conflict(texts, `${decl.target} is exposed twice`, first, decl, "expose each action once");
|
|
3407
|
+
if (!action)
|
|
3408
|
+
fail(decl, `unknown action ${decl.target}`, "name one declared object.action");
|
|
3409
|
+
changed.set(decl.target, decl);
|
|
3410
|
+
if (automatic(action.actor))
|
|
3321
3411
|
fail(decl, `${decl.target} runs on the clock or its parent, not a caller`, "expose only caller actions");
|
|
3322
|
-
|
|
3323
|
-
action.publicAction = decl.name;
|
|
3412
|
+
action.publicAction = decl.name;
|
|
3324
3413
|
}
|
|
3414
|
+
// Two actions of one instrument cannot share a public name.
|
|
3415
|
+
const published = new Map();
|
|
3325
3416
|
for (const decl of program.decls) {
|
|
3326
3417
|
if (decl.kind !== "expose")
|
|
3327
3418
|
continue;
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
const
|
|
3331
|
-
|
|
3332
|
-
|
|
3419
|
+
const instrument = decl.target.split(".").slice(0, -1).join("_");
|
|
3420
|
+
const key = `${instrument} ${decl.name}`;
|
|
3421
|
+
const first = published.get(key) ??
|
|
3422
|
+
exposures.find(({ instrument: id, action, entry }) => id === instrument &&
|
|
3423
|
+
`${decl.target}`.split(".").pop() !== action &&
|
|
3424
|
+
entry.value.kind === "call" &&
|
|
3425
|
+
text(entry.value.args[0]) === decl.name)?.entry;
|
|
3426
|
+
if (first)
|
|
3427
|
+
conflict(texts, `public action ${decl.name} is given twice on ${instrument}`, first, decl, "give each exposed action a distinct public name");
|
|
3428
|
+
published.set(key, decl);
|
|
3333
3429
|
}
|
|
3334
3430
|
// Every attachment of an object shares one public action namespace, so
|
|
3335
3431
|
// two attachments that both `expose create` collide.
|
|
@@ -3477,6 +3573,7 @@ export function compile(source, options = {}) {
|
|
|
3477
3573
|
warnings: [],
|
|
3478
3574
|
diagnostics: [...sameParty.errors, ...unbound].map((d) => diagnostic(d, "check")),
|
|
3479
3575
|
};
|
|
3576
|
+
const checks = lowerChecks(program.checks, validated.value, literal);
|
|
3480
3577
|
return {
|
|
3481
3578
|
verdict: "valid",
|
|
3482
3579
|
diagnostics: [],
|
|
@@ -3488,6 +3585,7 @@ export function compile(source, options = {}) {
|
|
|
3488
3585
|
document: validated.value,
|
|
3489
3586
|
costManifest: buildUdlCostManifest(validated.value),
|
|
3490
3587
|
originMap: origins,
|
|
3588
|
+
checks,
|
|
3491
3589
|
},
|
|
3492
3590
|
};
|
|
3493
3591
|
}
|