@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/src/compile.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CompileFailure,
|
|
3
|
+
conflict,
|
|
3
4
|
didYouMean,
|
|
4
5
|
fail,
|
|
5
6
|
failWithCode,
|
|
7
|
+
type Site,
|
|
6
8
|
} from "./diagnostics.ts";
|
|
7
|
-
import { hash as sha256 } from "fast-sha256";
|
|
8
9
|
import { buildUdlCostManifest, type UdlCostManifest } from "./cost.ts";
|
|
9
10
|
import {
|
|
11
|
+
canonicalJson,
|
|
12
|
+
digest,
|
|
10
13
|
validateUdl,
|
|
11
14
|
resolveField,
|
|
12
15
|
sameAccount,
|
|
16
|
+
sameFamily,
|
|
13
17
|
sameObjectField,
|
|
14
18
|
subjectPartyRoles,
|
|
15
19
|
RESERVED_OBJECT_NAMES,
|
|
@@ -33,20 +37,16 @@ import {
|
|
|
33
37
|
type UdlSubjectRequirement,
|
|
34
38
|
type UdlValue,
|
|
35
39
|
} from "@hyperscale0/udl";
|
|
36
|
-
import {
|
|
37
|
-
bindingDependencies,
|
|
38
|
-
parameterDiagnostics,
|
|
39
|
-
BindingContractError,
|
|
40
|
-
} from "./binding-contract.ts";
|
|
41
40
|
import { tunableBounds } from "./tunables.ts";
|
|
42
|
-
import {
|
|
41
|
+
import { lowerChecks, type CompiledCheck } from "./checks.ts";
|
|
42
|
+
import { parseExpression, parseProgram } from "./parse.ts";
|
|
43
43
|
import {
|
|
44
44
|
applyAttachmentEconomics,
|
|
45
45
|
purposeWarnings,
|
|
46
46
|
samePartyMoves,
|
|
47
47
|
unboundEconomicSources,
|
|
48
48
|
} from "./attachment-economics.ts";
|
|
49
|
-
import { parseHeader } from "./header-source.ts";
|
|
49
|
+
import { instrumentChecks, parseHeader } from "./header-source.ts";
|
|
50
50
|
import {
|
|
51
51
|
lineColAt,
|
|
52
52
|
type AssignmentDecl,
|
|
@@ -58,9 +58,11 @@ import {
|
|
|
58
58
|
type ObjectDecl,
|
|
59
59
|
type Span,
|
|
60
60
|
} from "./ast.ts";
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
import {
|
|
62
|
+
defaultToolchain,
|
|
63
|
+
type AdapterBindingTarget,
|
|
64
|
+
type Toolchain,
|
|
65
|
+
} from "./toolchain.ts";
|
|
64
66
|
|
|
65
67
|
export interface CompileDiagnostic extends Diagnostic {
|
|
66
68
|
line: number;
|
|
@@ -71,18 +73,42 @@ export interface CompileDiagnostic extends Diagnostic {
|
|
|
71
73
|
severity: "error";
|
|
72
74
|
stage: "parse" | "check" | "lower";
|
|
73
75
|
}
|
|
76
|
+
/** A std header parameter's value in force on one instrument, and who set it. */
|
|
77
|
+
export interface CompileTerm {
|
|
78
|
+
name: string;
|
|
79
|
+
/** program: the author bound it. party: a party of the same name bound it. std: the header's default. */
|
|
80
|
+
from: "program" | "party" | "std";
|
|
81
|
+
/** The value as written, or "unset" for an optional parameter nothing set. */
|
|
82
|
+
text: string;
|
|
83
|
+
/** "program", or the header that holds the default. */
|
|
84
|
+
source: string;
|
|
85
|
+
line: number;
|
|
86
|
+
column: number;
|
|
87
|
+
}
|
|
88
|
+
/** One std term on one instrument, set as another compile had it. */
|
|
89
|
+
export type CompileTermOverride = Pick<
|
|
90
|
+
CompileTerm,
|
|
91
|
+
"name" | "from" | "text"
|
|
92
|
+
> & {
|
|
93
|
+
instrument: string;
|
|
94
|
+
};
|
|
74
95
|
export interface CompileOriginMapEntry {
|
|
75
96
|
path: string;
|
|
76
97
|
span: Span & { line: number; column: number };
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
operation: string;
|
|
98
|
+
/** The std declaration an instrument lowers from, by its declaration digest. */
|
|
99
|
+
standardBlock?: { key: string; recordPath: string; digest: string };
|
|
100
|
+
/** One row per header parameter of a std instrument. */
|
|
101
|
+
terms?: CompileTerm[];
|
|
82
102
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
/**
|
|
104
|
+
* A parameter's value in force where it was written. An optional parameter
|
|
105
|
+
* nothing set has no value; a std term is unset for `when x is set`.
|
|
106
|
+
*/
|
|
107
|
+
interface Term {
|
|
108
|
+
value?: Expr;
|
|
109
|
+
from: CompileTerm["from"];
|
|
110
|
+
span: Span;
|
|
111
|
+
source: string;
|
|
86
112
|
}
|
|
87
113
|
export interface CompileWarning extends Omit<CompileDiagnostic, "severity"> {
|
|
88
114
|
severity: "warning";
|
|
@@ -96,6 +122,8 @@ export interface CompileResult {
|
|
|
96
122
|
document: UdlDocument;
|
|
97
123
|
costManifest: UdlCostManifest;
|
|
98
124
|
originMap: CompileOriginMapEntry[];
|
|
125
|
+
/** `check` blocks, beside the document and never inside it. */
|
|
126
|
+
checks: CompiledCheck[];
|
|
99
127
|
};
|
|
100
128
|
}
|
|
101
129
|
function fieldType(row: Entry): { type: Expr; sensitive: boolean } {
|
|
@@ -228,27 +256,11 @@ function lowerObjectField(
|
|
|
228
256
|
);
|
|
229
257
|
return result.data;
|
|
230
258
|
}
|
|
231
|
-
function canonicalJson(value: unknown): string {
|
|
232
|
-
if (value === null || typeof value !== "object") {
|
|
233
|
-
return JSON.stringify(value);
|
|
234
|
-
}
|
|
235
|
-
if (Array.isArray(value)) {
|
|
236
|
-
return `[${value.map((item) => (item === undefined ? "null" : canonicalJson(item))).join(",")}]`;
|
|
237
|
-
}
|
|
238
|
-
const record = value as Record<string, unknown>;
|
|
239
|
-
const keys = Object.keys(record)
|
|
240
|
-
.filter((k) => record[k] !== undefined)
|
|
241
|
-
.sort();
|
|
242
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(record[k])}`).join(",")}}`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
259
|
const emptyBlock: BlockExpr = {
|
|
246
260
|
kind: "block",
|
|
247
261
|
entries: [],
|
|
248
262
|
span: { start: 0, end: 0 },
|
|
249
263
|
};
|
|
250
|
-
const camel = (name: string) =>
|
|
251
|
-
name.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
252
264
|
/** Clock and parent actors run without a caller, so they have no public name. */
|
|
253
265
|
const automatic = (actor: UdlAction["actor"]) =>
|
|
254
266
|
actor === "clock" ||
|
|
@@ -273,33 +285,74 @@ function namedParties(instrument: UdlInstrument): string[] {
|
|
|
273
285
|
}
|
|
274
286
|
return names;
|
|
275
287
|
}
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
/**
|
|
289
|
+
* How a block merges a key given twice: `concat` joins the values, `equal`
|
|
290
|
+
* keeps one value given twice alike, and `unique` refuses a second. A key not
|
|
291
|
+
* listed is unique; tunable arguments are equal.
|
|
292
|
+
*/
|
|
293
|
+
type Merge = "unique" | "concat" | "equal";
|
|
294
|
+
const MERGE: Readonly<Record<string, Merge>> = {
|
|
295
|
+
requires: "concat",
|
|
296
|
+
moves: "concat",
|
|
297
|
+
invariants: "concat",
|
|
298
|
+
invoke: "concat",
|
|
299
|
+
calculate: "concat",
|
|
300
|
+
expose: "concat",
|
|
301
|
+
};
|
|
302
|
+
// The sources of the compile in progress, so a conflict prints line:column.
|
|
303
|
+
let texts: ReadonlyMap<string, string> = new Map();
|
|
304
|
+
/** An expression with spans, sources and comments dropped. */
|
|
305
|
+
const normal = (value: unknown): unknown =>
|
|
306
|
+
Array.isArray(value)
|
|
307
|
+
? value.map(normal)
|
|
308
|
+
: value && typeof value === "object"
|
|
309
|
+
? Object.fromEntries(
|
|
310
|
+
Object.entries(value).flatMap(([key, item]) =>
|
|
311
|
+
key === "span" || key === "source" || item === undefined
|
|
312
|
+
? []
|
|
313
|
+
: [[key, normal(item)]],
|
|
314
|
+
),
|
|
315
|
+
)
|
|
316
|
+
: value;
|
|
317
|
+
function given(
|
|
318
|
+
block: BlockExpr,
|
|
319
|
+
merge = (key: string): Merge => MERGE[key] ?? "unique",
|
|
320
|
+
): Map<string, Entry> {
|
|
321
|
+
const map = new Map<string, Entry>();
|
|
278
322
|
for (const row of block.entries) {
|
|
279
323
|
const previous = map.get(row.key);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
"requires",
|
|
284
|
-
"moves",
|
|
285
|
-
"invariants",
|
|
286
|
-
"invoke",
|
|
287
|
-
"calculate",
|
|
288
|
-
"expose",
|
|
289
|
-
].includes(row.key)
|
|
290
|
-
)
|
|
291
|
-
fail(row, `duplicate ${row.key}`, "keep one value for this name");
|
|
324
|
+
const rule = merge(row.key);
|
|
325
|
+
if (!previous) map.set(row.key, row);
|
|
326
|
+
else if (rule === "concat") {
|
|
292
327
|
const items = (expr: Expr) =>
|
|
293
328
|
expr.kind === "list" ? expr.items : [expr];
|
|
294
329
|
map.set(row.key, {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
330
|
+
...row,
|
|
331
|
+
value: {
|
|
332
|
+
kind: "list",
|
|
333
|
+
items: [...items(previous.value), ...items(row.value)],
|
|
334
|
+
span: row.span,
|
|
335
|
+
},
|
|
298
336
|
});
|
|
299
|
-
} else
|
|
337
|
+
} else if (
|
|
338
|
+
rule === "unique" ||
|
|
339
|
+
JSON.stringify(normal(previous.value)) !==
|
|
340
|
+
JSON.stringify(normal(row.value))
|
|
341
|
+
)
|
|
342
|
+
conflict(
|
|
343
|
+
texts,
|
|
344
|
+
`${row.key} is ${rule === "equal" ? "set" : "given"} twice`,
|
|
345
|
+
previous,
|
|
346
|
+
row,
|
|
347
|
+
"keep one",
|
|
348
|
+
rule === "equal" ? { values: [previous.value, row.value] } : {},
|
|
349
|
+
);
|
|
300
350
|
}
|
|
301
351
|
return map;
|
|
302
352
|
}
|
|
353
|
+
function entries(block: BlockExpr): Map<string, Expr> {
|
|
354
|
+
return new Map([...given(block)].map(([key, row]) => [key, row.value]));
|
|
355
|
+
}
|
|
303
356
|
function asBlock(expr: Expr | undefined): BlockExpr {
|
|
304
357
|
if (!expr) return emptyBlock;
|
|
305
358
|
if (expr.kind !== "block")
|
|
@@ -456,12 +509,26 @@ function literal(expr: Expr, expectedType?: string): string | number | boolean {
|
|
|
456
509
|
return fail(expr, "expected a literal", "write a typed constant");
|
|
457
510
|
}
|
|
458
511
|
|
|
512
|
+
/** A std declaration's identity: its AST with spans, sources and comments dropped. */
|
|
513
|
+
function declarationDigest(decl: InstrumentDecl): string {
|
|
514
|
+
return digest(normal(decl));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* An override compiles the program as if one std term held other text, so a
|
|
519
|
+
* plan can see what that term alone moves.
|
|
520
|
+
*/
|
|
459
521
|
export function compile(
|
|
460
522
|
source: string,
|
|
461
|
-
|
|
523
|
+
toolchain: Toolchain = defaultToolchain,
|
|
524
|
+
override?: CompileTermOverride,
|
|
462
525
|
): CompileResult {
|
|
463
526
|
const parsed = parseProgram(source);
|
|
464
|
-
const sources = new Map([
|
|
527
|
+
const sources = new Map([
|
|
528
|
+
["program", source],
|
|
529
|
+
...(override ? [["override", override.text] as const] : []),
|
|
530
|
+
]);
|
|
531
|
+
texts = sources;
|
|
465
532
|
const diagnostic = (
|
|
466
533
|
d: Diagnostic,
|
|
467
534
|
stage: CompileDiagnostic["stage"],
|
|
@@ -498,8 +565,8 @@ export function compile(
|
|
|
498
565
|
const program = parsed.program;
|
|
499
566
|
const bindingDiagnostics: Diagnostic[] = [];
|
|
500
567
|
const adapterTarget = (binding: string): AdapterBindingTarget | undefined => {
|
|
501
|
-
const registry =
|
|
502
|
-
if (!
|
|
568
|
+
const registry = toolchain.adapters;
|
|
569
|
+
if (!Object.hasOwn(registry, binding)) return;
|
|
503
570
|
const target = registry[binding];
|
|
504
571
|
return target &&
|
|
505
572
|
Object.hasOwn(target.adapter.operationMap, target.operation)
|
|
@@ -530,6 +597,8 @@ export function compile(
|
|
|
530
597
|
{ entry: Entry; name: string; template: InstrumentDecl }
|
|
531
598
|
>();
|
|
532
599
|
const declarationSources = new Map<InstrumentDecl, string>();
|
|
600
|
+
// Declaration digest by std path, such as financing.installments.payment.
|
|
601
|
+
const familyDigests = new Map<string, string>();
|
|
533
602
|
const standardOrigins = new Map<
|
|
534
603
|
InstrumentDecl,
|
|
535
604
|
NonNullable<CompileOriginMapEntry["standardBlock"]>
|
|
@@ -541,40 +610,27 @@ export function compile(
|
|
|
541
610
|
// Instruments that lost a std action to an empty all() parent. Their
|
|
542
611
|
// lifecycles drop the states only that action reached.
|
|
543
612
|
const trimmed = new Set<string>();
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const content = (
|
|
554
|
-
options.standardLibrary ?? bundledStandardLibrary
|
|
555
|
-
).source(use.name);
|
|
556
|
-
if (!content)
|
|
557
|
-
fail(use, `unknown header ${use.name}`, "choose a published header");
|
|
558
|
-
sources.set(use.name, content);
|
|
559
|
-
const bundled = bundledStandardLibrary.source(use.name);
|
|
560
|
-
const headerDigest =
|
|
561
|
-
bundled === content
|
|
562
|
-
? Array.from(sha256(new TextEncoder().encode(content)), (byte) =>
|
|
563
|
-
byte.toString(16).padStart(2, "0"),
|
|
564
|
-
).join("")
|
|
565
|
-
: undefined;
|
|
613
|
+
// A qualified name such as booking.reservation loads its header the first
|
|
614
|
+
// time the compile reaches it. A header nothing names never loads.
|
|
615
|
+
const loaded = new Set<string>();
|
|
616
|
+
const loadHeader = (name: string, site?: Site) => {
|
|
617
|
+
if (loaded.has(name)) return true;
|
|
618
|
+
const content = toolchain.std.get(name);
|
|
619
|
+
if (content === undefined) return false;
|
|
620
|
+
loaded.add(name);
|
|
621
|
+
sources.set(name, content);
|
|
566
622
|
let header;
|
|
567
623
|
try {
|
|
568
|
-
header = parseHeader(content,
|
|
624
|
+
header = parseHeader(content, name);
|
|
569
625
|
} catch (error) {
|
|
570
|
-
if (!(error instanceof CompileFailure)) throw error;
|
|
626
|
+
if (!(error instanceof CompileFailure) || !site) throw error;
|
|
571
627
|
throw new CompileFailure({
|
|
572
628
|
...error.diagnostic,
|
|
573
629
|
related: [
|
|
574
630
|
{
|
|
575
|
-
source: "program",
|
|
576
|
-
span:
|
|
577
|
-
message: `
|
|
631
|
+
source: site.source ?? "program",
|
|
632
|
+
span: site.span,
|
|
633
|
+
message: `Header ${name} is named here`,
|
|
578
634
|
},
|
|
579
635
|
],
|
|
580
636
|
});
|
|
@@ -583,22 +639,25 @@ export function compile(
|
|
|
583
639
|
parentDecl: InstrumentDecl,
|
|
584
640
|
prefix: string,
|
|
585
641
|
) => {
|
|
586
|
-
|
|
587
|
-
|
|
642
|
+
const first = templates.get(prefix);
|
|
643
|
+
if (first)
|
|
644
|
+
conflict(
|
|
645
|
+
texts,
|
|
646
|
+
`instrument ${prefix} is declared twice`,
|
|
647
|
+
first,
|
|
588
648
|
parentDecl,
|
|
589
|
-
`duplicate instrument ${prefix}`,
|
|
590
649
|
"give each instrument a distinct name",
|
|
591
650
|
);
|
|
592
651
|
templates.set(prefix, parentDecl);
|
|
593
|
-
declarationSources.set(parentDecl,
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
652
|
+
declarationSources.set(parentDecl, name);
|
|
653
|
+
const digest = declarationDigest(parentDecl);
|
|
654
|
+
familyDigests.set(prefix, digest);
|
|
655
|
+
const [, root, ...record] = prefix.split(".");
|
|
656
|
+
standardOrigins.set(parentDecl, {
|
|
657
|
+
key: `${name}.${root}`,
|
|
658
|
+
recordPath: record.join("."),
|
|
659
|
+
digest,
|
|
660
|
+
});
|
|
602
661
|
const recs = entries(asBlock(entries(parentDecl.body).get("records")));
|
|
603
662
|
for (const [recName, recBlock] of recs) {
|
|
604
663
|
const recDecl: InstrumentDecl = {
|
|
@@ -612,10 +671,47 @@ export function compile(
|
|
|
612
671
|
}
|
|
613
672
|
};
|
|
614
673
|
for (const decl of header.decls)
|
|
615
|
-
if (decl.kind === "instrument")
|
|
616
|
-
registerTemplates(decl, `${
|
|
617
|
-
|
|
618
|
-
}
|
|
674
|
+
if (decl.kind === "instrument")
|
|
675
|
+
registerTemplates(decl, `${name}.${decl.name}`);
|
|
676
|
+
return true;
|
|
677
|
+
};
|
|
678
|
+
const loadPath = (path: string, site?: Site) => {
|
|
679
|
+
const dot = path.indexOf(".");
|
|
680
|
+
return dot > 0 && loadHeader(path.slice(0, dot), site);
|
|
681
|
+
};
|
|
682
|
+
const templateAt = (path: string, site?: Site) => {
|
|
683
|
+
loadPath(path, site);
|
|
684
|
+
return templates.get(path);
|
|
685
|
+
};
|
|
686
|
+
// The template a qualified name names, or the reason it names none.
|
|
687
|
+
const requireTemplate = (path: string, site: Site) => {
|
|
688
|
+
const found = templateAt(path, site);
|
|
689
|
+
if (found) return found;
|
|
690
|
+
const root = path.split(".")[0]!;
|
|
691
|
+
const unknown = (noun: string, name: string, candidates: string[]) => {
|
|
692
|
+
const hint = didYouMean(name, candidates);
|
|
693
|
+
return hint
|
|
694
|
+
? `unknown ${noun} ${name}.${hint}`
|
|
695
|
+
: `unknown ${noun} ${name}`;
|
|
696
|
+
};
|
|
697
|
+
if (path.includes(".") && !toolchain.std.has(root))
|
|
698
|
+
fail(
|
|
699
|
+
site,
|
|
700
|
+
unknown("header", root, [...toolchain.std.keys()]),
|
|
701
|
+
"name a published header",
|
|
702
|
+
);
|
|
703
|
+
return fail(
|
|
704
|
+
site,
|
|
705
|
+
unknown(
|
|
706
|
+
"instrument",
|
|
707
|
+
path,
|
|
708
|
+
[...templates.keys()].filter((name) => name.startsWith(`${root}.`)),
|
|
709
|
+
),
|
|
710
|
+
path.includes(".")
|
|
711
|
+
? `choose an instrument header ${root} declares`
|
|
712
|
+
: "choose a declared instrument",
|
|
713
|
+
);
|
|
714
|
+
};
|
|
619
715
|
for (const decl of program.decls)
|
|
620
716
|
if (decl.kind === "instrument") templates.set(decl.name, decl);
|
|
621
717
|
const document: UdlDocument = {
|
|
@@ -634,7 +730,7 @@ export function compile(
|
|
|
634
730
|
const objects = new Map<string, ObjectDecl>();
|
|
635
731
|
const assignments = new Map<string, AssignmentDecl>();
|
|
636
732
|
const attachmentSubjects = new Map<string, string>();
|
|
637
|
-
const names = new
|
|
733
|
+
const names = new Map<string, Site>();
|
|
638
734
|
// A record's create turns public only through a program-level expose,
|
|
639
735
|
// which applies after lowering, so creation input needs the list first.
|
|
640
736
|
const exposedCreates = new Set(
|
|
@@ -645,15 +741,17 @@ export function compile(
|
|
|
645
741
|
),
|
|
646
742
|
);
|
|
647
743
|
for (const decl of program.decls) {
|
|
648
|
-
if (decl.kind === "expose"
|
|
649
|
-
|
|
650
|
-
if (
|
|
651
|
-
|
|
744
|
+
if (decl.kind === "expose") continue;
|
|
745
|
+
const first = names.get(decl.name);
|
|
746
|
+
if (first)
|
|
747
|
+
conflict(
|
|
748
|
+
texts,
|
|
749
|
+
`${decl.name} is declared twice`,
|
|
750
|
+
first,
|
|
652
751
|
decl,
|
|
653
|
-
`duplicate declaration ${decl.name}`,
|
|
654
752
|
"give this declaration a distinct name",
|
|
655
753
|
);
|
|
656
|
-
names.
|
|
754
|
+
names.set(decl.name, decl);
|
|
657
755
|
if (decl.kind === "object") {
|
|
658
756
|
objects.set(decl.name, decl);
|
|
659
757
|
for (const entry of decl.body.entries) {
|
|
@@ -718,10 +816,9 @@ export function compile(
|
|
|
718
816
|
"use module.instrument or module.instrument.record",
|
|
719
817
|
);
|
|
720
818
|
}
|
|
721
|
-
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
if (!targetTemplate) {
|
|
819
|
+
loadPath(rawPath, expr);
|
|
820
|
+
const digest = familyDigests.get(rawPath);
|
|
821
|
+
if (!digest) {
|
|
725
822
|
if (!required) return undefined;
|
|
726
823
|
failWithCode(
|
|
727
824
|
expr,
|
|
@@ -730,45 +827,11 @@ export function compile(
|
|
|
730
827
|
"choose a declared standard instrument",
|
|
731
828
|
);
|
|
732
829
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
"HSX1001",
|
|
739
|
-
`unknown family declaration ${moduleName}.${parts[1]!}`,
|
|
740
|
-
"choose a declared standard instrument",
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
const topBody = entries(topTemplate.body);
|
|
744
|
-
let revision: number | undefined;
|
|
745
|
-
if (topBody.has("familyRevision")) {
|
|
746
|
-
const val = literal(topBody.get("familyRevision")!);
|
|
747
|
-
if (typeof val === "number") revision = val;
|
|
748
|
-
}
|
|
749
|
-
let currentBody = topTemplate.body;
|
|
750
|
-
for (const recName of parts.slice(2)) {
|
|
751
|
-
const recs = entries(asBlock(entries(currentBody).get("records")));
|
|
752
|
-
const child = recs.get(recName);
|
|
753
|
-
if (child) {
|
|
754
|
-
currentBody = asBlock(child);
|
|
755
|
-
const cBody = entries(currentBody);
|
|
756
|
-
if (cBody.has("familyRevision")) {
|
|
757
|
-
const val = literal(cBody.get("familyRevision")!);
|
|
758
|
-
if (typeof val === "number") revision = val;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
if (revision === undefined) {
|
|
763
|
-
if (!required) return undefined;
|
|
764
|
-
failWithCode(
|
|
765
|
-
expr,
|
|
766
|
-
"HSX1001",
|
|
767
|
-
`declaration ${rawPath} has no familyRevision declared`,
|
|
768
|
-
"declare familyRevision on the standard instrument",
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
return { module: moduleName, exportPath, revision };
|
|
830
|
+
return {
|
|
831
|
+
module: parts[0]!,
|
|
832
|
+
exportPath: parts.slice(1).join("."),
|
|
833
|
+
digest,
|
|
834
|
+
};
|
|
772
835
|
};
|
|
773
836
|
const addInstrument = (
|
|
774
837
|
decl: InstrumentDecl,
|
|
@@ -792,14 +855,10 @@ export function compile(
|
|
|
792
855
|
/** The template as the founder wrote it, such as money.schedule. */
|
|
793
856
|
template?: string;
|
|
794
857
|
},
|
|
795
|
-
familyDeclaration?:
|
|
796
|
-
module: string;
|
|
797
|
-
exportPath: string;
|
|
798
|
-
revision?: number | undefined;
|
|
799
|
-
},
|
|
858
|
+
familyDeclaration?: UdlFamily,
|
|
800
859
|
standardOrigin?: NonNullable<CompileOriginMapEntry["standardBlock"]>,
|
|
801
|
-
/**
|
|
802
|
-
|
|
860
|
+
/** A child record's terms are its parent's. */
|
|
861
|
+
inheritedTerms = new Map<string, Term>(),
|
|
803
862
|
) => {
|
|
804
863
|
const resolveFamilyInstruments = (
|
|
805
864
|
family: UdlFamily,
|
|
@@ -836,7 +895,8 @@ export function compile(
|
|
|
836
895
|
inst.family &&
|
|
837
896
|
inst.family.module === family.module &&
|
|
838
897
|
inst.family.exportPath === family.exportPath &&
|
|
839
|
-
inst.family
|
|
898
|
+
"digest" in inst.family &&
|
|
899
|
+
inst.family.digest === family.digest
|
|
840
900
|
) {
|
|
841
901
|
found.add(inst.id);
|
|
842
902
|
}
|
|
@@ -900,7 +960,8 @@ export function compile(
|
|
|
900
960
|
|
|
901
961
|
const getInstrumentFamily = (targetId: string): UdlFamily | undefined => {
|
|
902
962
|
const existing = document.instruments.find((i) => i.id === targetId);
|
|
903
|
-
|
|
963
|
+
// This document is format 2, so every family it holds is a digest.
|
|
964
|
+
if (existing?.family) return existing.family as UdlFamily;
|
|
904
965
|
|
|
905
966
|
if (targetId.startsWith(`${id}_`) && familyDeclaration) {
|
|
906
967
|
const sub = resolveChildExportPath(
|
|
@@ -919,7 +980,7 @@ export function compile(
|
|
|
919
980
|
|
|
920
981
|
for (const [asgnName, asgn] of assignments) {
|
|
921
982
|
if (targetId === asgnName || targetId.startsWith(`${asgnName}_`)) {
|
|
922
|
-
const tmpl =
|
|
983
|
+
const tmpl = templateAt(asgn.target);
|
|
923
984
|
if (!tmpl) continue;
|
|
924
985
|
const mod = declarationSources.get(tmpl);
|
|
925
986
|
if (!mod || mod === "program") continue;
|
|
@@ -954,16 +1015,11 @@ export function compile(
|
|
|
954
1015
|
"name a declared instrument or a list of instruments",
|
|
955
1016
|
);
|
|
956
1017
|
const fam = getInstrumentFamily(tid);
|
|
957
|
-
if (
|
|
958
|
-
!fam ||
|
|
959
|
-
fam.module !== expectedFamily.module ||
|
|
960
|
-
fam.exportPath !== expectedFamily.exportPath ||
|
|
961
|
-
fam.revision !== expectedFamily.revision
|
|
962
|
-
) {
|
|
1018
|
+
if (!sameFamily(fam, expectedFamily)) {
|
|
963
1019
|
failWithCode(
|
|
964
1020
|
expr,
|
|
965
1021
|
"HSX1001",
|
|
966
|
-
`target instrument ${tid} family does not match expected family ${expectedFamily.module}.${expectedFamily.exportPath}
|
|
1022
|
+
`target instrument ${tid} family does not match expected family ${expectedFamily.module}.${expectedFamily.exportPath}`,
|
|
967
1023
|
"ensure target instrument matches the declared family",
|
|
968
1024
|
);
|
|
969
1025
|
}
|
|
@@ -979,28 +1035,27 @@ export function compile(
|
|
|
979
1035
|
if (type.kind === "call" && type.name === "enum")
|
|
980
1036
|
enums.set(parameter.key, type.args.map(text));
|
|
981
1037
|
}
|
|
982
|
-
const
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
fail(
|
|
987
|
-
entry,
|
|
988
|
-
`duplicate tunable ${entry.key}`,
|
|
989
|
-
"supply each parameter once",
|
|
990
|
-
);
|
|
991
|
-
supplied.set(entry.key, entry.value);
|
|
992
|
-
suppliedEntries.set(entry.key, entry);
|
|
993
|
-
}
|
|
1038
|
+
const suppliedEntries = given(arguments_, () => "equal");
|
|
1039
|
+
const supplied = new Map(
|
|
1040
|
+
[...suppliedEntries].map(([key, entry]) => [key, entry.value]),
|
|
1041
|
+
);
|
|
994
1042
|
const environment = new Map<string, Expr>(inherited);
|
|
995
1043
|
// `when every is set` asks whether the author bound a tunable; a
|
|
996
1044
|
// default leaves it unset.
|
|
997
|
-
const
|
|
1045
|
+
const terms = new Map(inheritedTerms);
|
|
1046
|
+
const isSet = (key: string) => (terms.get(key)?.from ?? "std") !== "std";
|
|
998
1047
|
const parameterErrors: Diagnostic[] = [];
|
|
1048
|
+
// A std default set back is the header as it was, so its digest too.
|
|
1049
|
+
let revertedHeader: InstrumentDecl | undefined;
|
|
999
1050
|
for (const param of decl.parameters) {
|
|
1000
1051
|
const type =
|
|
1001
1052
|
param.value.kind === "default" ? param.value.type : param.value;
|
|
1053
|
+
const overridden =
|
|
1054
|
+
override?.instrument === id && override.name === param.key;
|
|
1002
1055
|
const fallback =
|
|
1003
|
-
param.value.kind === "default"
|
|
1056
|
+
param.value.kind === "default" && !overridden
|
|
1057
|
+
? param.value.value
|
|
1058
|
+
: undefined;
|
|
1004
1059
|
const typeName =
|
|
1005
1060
|
type.kind === "type" || type.kind === "call" ? type.name : text(type);
|
|
1006
1061
|
const partyParameter = attachmentInfo && typeName === "party";
|
|
@@ -1011,18 +1066,76 @@ export function compile(
|
|
|
1011
1066
|
const byName: Expr | undefined =
|
|
1012
1067
|
partyParameter &&
|
|
1013
1068
|
!optional &&
|
|
1069
|
+
!overridden &&
|
|
1014
1070
|
(subjectPartyRoles.includes(param.key as SubjectPartyRole) ||
|
|
1015
1071
|
document.parties[param.key])
|
|
1016
1072
|
? { kind: "name", value: param.key, span: origin }
|
|
1017
1073
|
: undefined;
|
|
1018
|
-
|
|
1074
|
+
const header = declarationSources.get(decl) ?? "program";
|
|
1075
|
+
const forced =
|
|
1076
|
+
!overridden || override.text === "unset"
|
|
1077
|
+
? undefined
|
|
1078
|
+
: {
|
|
1079
|
+
...(parseExpression(override.text) ??
|
|
1080
|
+
fail(
|
|
1081
|
+
{ span: origin },
|
|
1082
|
+
`${param.key} cannot hold ${override.text}`,
|
|
1083
|
+
"override with text the term once held",
|
|
1084
|
+
)),
|
|
1085
|
+
source: "override",
|
|
1086
|
+
};
|
|
1087
|
+
const written = overridden ? forced : supplied.get(param.key);
|
|
1088
|
+
if (overridden && override.from === "std")
|
|
1089
|
+
revertedHeader = {
|
|
1090
|
+
...decl,
|
|
1091
|
+
parameters: decl.parameters.map((entry) =>
|
|
1092
|
+
entry !== param
|
|
1093
|
+
? entry
|
|
1094
|
+
: {
|
|
1095
|
+
...entry,
|
|
1096
|
+
value: forced
|
|
1097
|
+
? {
|
|
1098
|
+
kind: "default",
|
|
1099
|
+
type,
|
|
1100
|
+
value: forced,
|
|
1101
|
+
span: entry.span,
|
|
1102
|
+
}
|
|
1103
|
+
: type,
|
|
1104
|
+
},
|
|
1105
|
+
),
|
|
1106
|
+
};
|
|
1107
|
+
terms.set(
|
|
1108
|
+
param.key,
|
|
1109
|
+
overridden
|
|
1110
|
+
? {
|
|
1111
|
+
...(forced ? { value: forced } : {}),
|
|
1112
|
+
from: override.from,
|
|
1113
|
+
span: forced?.span ?? origin,
|
|
1114
|
+
source: "override",
|
|
1115
|
+
}
|
|
1116
|
+
: written
|
|
1117
|
+
? {
|
|
1118
|
+
value: written,
|
|
1119
|
+
from: "program",
|
|
1120
|
+
span: written.span,
|
|
1121
|
+
source: "program",
|
|
1122
|
+
}
|
|
1123
|
+
: byName
|
|
1124
|
+
? {
|
|
1125
|
+
value: byName,
|
|
1126
|
+
from: "party",
|
|
1127
|
+
span: origin,
|
|
1128
|
+
source: "program",
|
|
1129
|
+
}
|
|
1130
|
+
: {
|
|
1131
|
+
...(fallback ? { value: fallback } : {}),
|
|
1132
|
+
from: "std",
|
|
1133
|
+
span: (fallback ?? param).span,
|
|
1134
|
+
source: header,
|
|
1135
|
+
},
|
|
1136
|
+
);
|
|
1019
1137
|
const actual =
|
|
1020
|
-
|
|
1021
|
-
byName ??
|
|
1022
|
-
(fallback && {
|
|
1023
|
-
...fallback,
|
|
1024
|
-
source: declarationSources.get(decl) ?? "program",
|
|
1025
|
-
});
|
|
1138
|
+
written ?? byName ?? (fallback && { ...fallback, source: header });
|
|
1026
1139
|
if (!actual) {
|
|
1027
1140
|
if (optional) continue;
|
|
1028
1141
|
parameterErrors.push({
|
|
@@ -1046,45 +1159,21 @@ export function compile(
|
|
|
1046
1159
|
}
|
|
1047
1160
|
environment.set(param.key, actual);
|
|
1048
1161
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
span: error.entry.span,
|
|
1061
|
-
source: declarationSources.get(decl) ?? "program",
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
for (const dependency of dependencies) {
|
|
1065
|
-
const selected = environment.get(dependency.when);
|
|
1066
|
-
const holds = enums.has(dependency.when)
|
|
1067
|
-
? selected?.kind === "name" && selected.value === dependency.is
|
|
1068
|
-
: tunables.get(dependency.when) === (dependency.is === "set");
|
|
1069
|
-
if (holds && !environment.has(dependency.binding)) {
|
|
1070
|
-
throw new CompileFailure({
|
|
1071
|
-
code: "HSX1001",
|
|
1072
|
-
message: dependency.message.replaceAll(
|
|
1073
|
-
"{attachment}",
|
|
1074
|
-
attachmentInfo?.attachmentName ?? id,
|
|
1075
|
-
),
|
|
1076
|
-
fix: dependency.fix,
|
|
1077
|
-
span: origin,
|
|
1078
|
-
related: [
|
|
1079
|
-
{
|
|
1080
|
-
source: declarationSources.get(decl) ?? "program",
|
|
1081
|
-
span: dependency.span,
|
|
1082
|
-
message: "Conditional binding requirement",
|
|
1083
|
-
},
|
|
1084
|
-
],
|
|
1085
|
-
});
|
|
1162
|
+
const checks = instrumentChecks(decl);
|
|
1163
|
+
const say = (message: string) =>
|
|
1164
|
+
message
|
|
1165
|
+
.replaceAll("{attachment}", attachmentInfo?.attachmentName ?? id)
|
|
1166
|
+
.replaceAll("{instrument}", assignments.get(id)?.target ?? decl.name);
|
|
1167
|
+
const collect = (run: () => void) => {
|
|
1168
|
+
try {
|
|
1169
|
+
run();
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
if (!(error instanceof CompileFailure)) throw error;
|
|
1172
|
+
parameterErrors.push(error.diagnostic);
|
|
1086
1173
|
}
|
|
1087
|
-
}
|
|
1174
|
+
};
|
|
1175
|
+
// Each tunable that failed its type rule, so no check compares it.
|
|
1176
|
+
const broken = new Set<string>();
|
|
1088
1177
|
for (const key of supplied.keys()) {
|
|
1089
1178
|
if (decl.parameters.some((p) => p.key === key)) continue;
|
|
1090
1179
|
const suppliedValue = supplied.get(key)!;
|
|
@@ -1241,7 +1330,7 @@ export function compile(
|
|
|
1241
1330
|
const subject = attachmentSubjects.get(assignment.name)!;
|
|
1242
1331
|
return `\`${subject}.${assignment.name.slice(subject.length + 1)}\``;
|
|
1243
1332
|
});
|
|
1244
|
-
const target =
|
|
1333
|
+
const target = templateAt(type);
|
|
1245
1334
|
const required =
|
|
1246
1335
|
target?.parameters
|
|
1247
1336
|
.filter(
|
|
@@ -1429,7 +1518,7 @@ export function compile(
|
|
|
1429
1518
|
"reference union needs 1 to 16 objects",
|
|
1430
1519
|
"use at most 16 distinct object names",
|
|
1431
1520
|
);
|
|
1432
|
-
const seen = new
|
|
1521
|
+
const seen = new Map<string, Expr>();
|
|
1433
1522
|
for (const value of values) {
|
|
1434
1523
|
if (value.kind !== "name")
|
|
1435
1524
|
fail(
|
|
@@ -1456,7 +1545,7 @@ export function compile(
|
|
|
1456
1545
|
!value.value.startsWith(`${instance}_`)
|
|
1457
1546
|
)
|
|
1458
1547
|
continue;
|
|
1459
|
-
const template =
|
|
1548
|
+
const template = templateAt(target);
|
|
1460
1549
|
if (!template) continue;
|
|
1461
1550
|
const suffix = value.value
|
|
1462
1551
|
.slice(instance.length + 1)
|
|
@@ -1473,9 +1562,16 @@ export function compile(
|
|
|
1473
1562
|
`${param.key} has the wrong object type`,
|
|
1474
1563
|
`use an object of type ${t.kind === "type" ? t.target : "ref"}`,
|
|
1475
1564
|
);
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1565
|
+
const first = seen.get(value.value);
|
|
1566
|
+
if (first)
|
|
1567
|
+
conflict(
|
|
1568
|
+
texts,
|
|
1569
|
+
`${param.key} lists ${value.value} twice`,
|
|
1570
|
+
first,
|
|
1571
|
+
value,
|
|
1572
|
+
"list each object once",
|
|
1573
|
+
);
|
|
1574
|
+
seen.set(value.value, value);
|
|
1479
1575
|
}
|
|
1480
1576
|
// A many-reference tunable is a list even when one object is bound,
|
|
1481
1577
|
// so report datasets and other value positions never see a bare name.
|
|
@@ -1538,17 +1634,12 @@ export function compile(
|
|
|
1538
1634
|
`For a varying deposit, ${advice} and add \`rename { ${param.key}: ${target} }\`. Use a literal only for a fixed charge.`,
|
|
1539
1635
|
);
|
|
1540
1636
|
}
|
|
1541
|
-
if (type === "money" && v.kind === "percent")
|
|
1542
|
-
const policy = policyDiagnostics.find(
|
|
1543
|
-
(policy) => policy.parameter === param.key,
|
|
1544
|
-
);
|
|
1637
|
+
if (type === "money" && v.kind === "percent")
|
|
1545
1638
|
fail(
|
|
1546
1639
|
v,
|
|
1547
|
-
`\`${assignments.get(id)?.target ?? decl.name}.${param.key}\` accepts
|
|
1548
|
-
policy
|
|
1549
|
-
"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.",
|
|
1640
|
+
`\`${assignments.get(id)?.target ?? decl.name}.${param.key}\` accepts a fixed amount. It cannot express a percentage-based charge.`,
|
|
1641
|
+
"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.",
|
|
1550
1642
|
);
|
|
1551
|
-
}
|
|
1552
1643
|
fail(v, `${param.key} needs ${type}`, `write a ${type} literal`);
|
|
1553
1644
|
}
|
|
1554
1645
|
try {
|
|
@@ -1602,119 +1693,163 @@ export function compile(
|
|
|
1602
1693
|
span: party.span,
|
|
1603
1694
|
message: `Declared party ${party.name}`,
|
|
1604
1695
|
});
|
|
1605
|
-
|
|
1696
|
+
// A header's `x is <type>` check explains this tunable's type rule.
|
|
1697
|
+
const typed = checks.find(
|
|
1698
|
+
({ test }) => test.kind === "type" && test.tunable === param.key,
|
|
1699
|
+
);
|
|
1700
|
+
broken.add(param.key);
|
|
1701
|
+
parameterErrors.push({
|
|
1702
|
+
...error.diagnostic,
|
|
1703
|
+
...(typed ? { message: say(typed.message), fix: typed.fix } : {}),
|
|
1704
|
+
related,
|
|
1705
|
+
});
|
|
1606
1706
|
}
|
|
1607
1707
|
}
|
|
1608
|
-
if (parameterErrors.length) {
|
|
1609
|
-
bindingDiagnostics.push(...parameterErrors.slice(1));
|
|
1610
|
-
throw new CompileFailure(parameterErrors[0]!);
|
|
1611
|
-
}
|
|
1612
1708
|
const body = entries(decl.body);
|
|
1613
1709
|
// A ceiling caps a tunable by the value of an enum tunable, such as the
|
|
1614
1710
|
// longest term per loan kind. An unset tunable takes the ceiling; a
|
|
1615
1711
|
// supplied one may lower it, never raise it.
|
|
1616
|
-
for (const ceiling of asBlock(body.get("ceilings")).entries)
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
const rule = constraint.value;
|
|
1650
|
-
if (
|
|
1651
|
-
rule.kind !== "call" ||
|
|
1652
|
-
rule.args.length !== 1 ||
|
|
1653
|
-
!["less_than", "at_most", "greater_than"].includes(rule.name)
|
|
1654
|
-
)
|
|
1655
|
-
fail(
|
|
1656
|
-
rule,
|
|
1657
|
-
"invalid tunable constraint",
|
|
1658
|
-
"use less_than(name), at_most(name), or greater_than(name)",
|
|
1659
|
-
);
|
|
1660
|
-
const left = environment.get(constraint.key),
|
|
1661
|
-
right = environment.get(text(rule.args[0]!));
|
|
1662
|
-
if (!left || !right)
|
|
1663
|
-
fail(
|
|
1664
|
-
rule,
|
|
1665
|
-
"constraint needs two declared tunables",
|
|
1666
|
-
"name the compared tunables",
|
|
1667
|
-
);
|
|
1668
|
-
// A runtime value is checked when the agreement is created.
|
|
1669
|
-
if (
|
|
1670
|
-
[left, right].some(
|
|
1671
|
-
(expr) => expr.kind === "name" && expr.value === "runtime",
|
|
1712
|
+
for (const ceiling of asBlock(body.get("ceilings")).entries)
|
|
1713
|
+
collect(() => {
|
|
1714
|
+
const table = entries(asBlock(ceiling.value));
|
|
1715
|
+
const by = table.get("by");
|
|
1716
|
+
const selected = by && environment.get(text(by));
|
|
1717
|
+
if (!by || selected?.kind !== "name")
|
|
1718
|
+
fail(
|
|
1719
|
+
ceiling,
|
|
1720
|
+
`ceiling ${ceiling.key} needs by: an enum tunable`,
|
|
1721
|
+
"write by: kind and one maximum per enum value",
|
|
1722
|
+
);
|
|
1723
|
+
const maximum = table.get(selected.value);
|
|
1724
|
+
if (maximum?.kind !== "number")
|
|
1725
|
+
fail(
|
|
1726
|
+
ceiling,
|
|
1727
|
+
`ceiling ${ceiling.key} has no maximum for ${selected.value}`,
|
|
1728
|
+
`add ${selected.value}: number to the ceiling`,
|
|
1729
|
+
);
|
|
1730
|
+
if (!isSet(ceiling.key)) {
|
|
1731
|
+
const term = terms.get(ceiling.key);
|
|
1732
|
+
if (term)
|
|
1733
|
+
terms.set(ceiling.key, {
|
|
1734
|
+
...term,
|
|
1735
|
+
value: maximum,
|
|
1736
|
+
span: maximum.span,
|
|
1737
|
+
});
|
|
1738
|
+
environment.set(ceiling.key, { ...maximum, span: origin });
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
const supplied = environment.get(ceiling.key)!;
|
|
1742
|
+
if (
|
|
1743
|
+
supplied.kind !== "number" ||
|
|
1744
|
+
BigInt(supplied.value) > BigInt(maximum.value)
|
|
1672
1745
|
)
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1746
|
+
fail(
|
|
1747
|
+
supplied,
|
|
1748
|
+
`${ceiling.key} for ${selected.value} is at most ${maximum.value}`,
|
|
1749
|
+
`choose ${ceiling.key} of ${maximum.value} or less, or omit it`,
|
|
1750
|
+
);
|
|
1751
|
+
});
|
|
1752
|
+
// Every check runs, so one attach reports each broken rule at once.
|
|
1753
|
+
for (const check of checks)
|
|
1754
|
+
collect(() => {
|
|
1755
|
+
const { test, when } = check;
|
|
1756
|
+
if (when) {
|
|
1757
|
+
const selected = environment.get(when.selector);
|
|
1758
|
+
const holds = enums.has(when.selector)
|
|
1759
|
+
? selected?.kind === "name" && selected.value === when.is
|
|
1760
|
+
: terms.has(when.selector) &&
|
|
1761
|
+
isSet(when.selector) === (when.is === "set");
|
|
1762
|
+
if (!holds) return;
|
|
1763
|
+
}
|
|
1764
|
+
if (test.kind === "type") return;
|
|
1765
|
+
let at: Entry | undefined;
|
|
1766
|
+
if (test.kind === "set") {
|
|
1767
|
+
if (isSet(test.tunable)) return;
|
|
1768
|
+
} else {
|
|
1769
|
+
const terms = [...test.left, ...test.right];
|
|
1770
|
+
const value = (term: Expr) =>
|
|
1771
|
+
term.kind === "name" ? environment.get(term.value) : term;
|
|
1772
|
+
// An unset or broken tunable has nothing to compare, and a
|
|
1773
|
+
// runtime value is checked when the agreement is created.
|
|
1774
|
+
if (
|
|
1775
|
+
terms.some(
|
|
1776
|
+
(term) =>
|
|
1777
|
+
(term.kind === "name" && broken.has(term.value)) ||
|
|
1778
|
+
!value(term) ||
|
|
1779
|
+
(value(term)!.kind === "name" &&
|
|
1780
|
+
(value(term) as { value: string }).value === "runtime"),
|
|
1781
|
+
)
|
|
1782
|
+
)
|
|
1783
|
+
return;
|
|
1784
|
+
const kind = (term: Expr) => {
|
|
1785
|
+
const expr = value(term)!;
|
|
1786
|
+
return expr.kind === "name" && /^P(?:\d|T)/.test(expr.value)
|
|
1787
|
+
? "duration"
|
|
1788
|
+
: expr.kind;
|
|
1789
|
+
};
|
|
1790
|
+
const kinds = new Set(terms.map(kind));
|
|
1791
|
+
if (
|
|
1792
|
+
kinds.size !== 1 ||
|
|
1793
|
+
!["money", "number", "percent", "duration"].includes(
|
|
1794
|
+
kind(terms[0]!),
|
|
1795
|
+
)
|
|
1796
|
+
)
|
|
1797
|
+
fail(
|
|
1798
|
+
check.rule,
|
|
1799
|
+
"check compares values of different types",
|
|
1800
|
+
"compare amounts, integers, percentages or durations of one type",
|
|
1801
|
+
);
|
|
1802
|
+
const total = (side: Expr[]) =>
|
|
1803
|
+
side.reduce(
|
|
1804
|
+
(sum, term) => sum + BigInt(String(literal(value(term)!))),
|
|
1805
|
+
0n,
|
|
1806
|
+
);
|
|
1807
|
+
const [a, b] = [total(test.left), total(test.right)];
|
|
1808
|
+
const holds = {
|
|
1809
|
+
"<": a < b,
|
|
1810
|
+
"<=": a <= b,
|
|
1811
|
+
">": a > b,
|
|
1812
|
+
">=": a >= b,
|
|
1813
|
+
"==": a === b,
|
|
1814
|
+
"!=": a !== b,
|
|
1815
|
+
}[test.operator];
|
|
1816
|
+
if (holds) return;
|
|
1817
|
+
at = terms
|
|
1818
|
+
.map((term) =>
|
|
1819
|
+
term.kind === "name"
|
|
1820
|
+
? suppliedEntries.get(term.value)
|
|
1821
|
+
: undefined,
|
|
1822
|
+
)
|
|
1823
|
+
.find(Boolean);
|
|
1824
|
+
}
|
|
1825
|
+
throw new CompileFailure({
|
|
1826
|
+
code: "HSX1001",
|
|
1827
|
+
message: say(check.message),
|
|
1828
|
+
fix: check.fix,
|
|
1829
|
+
span: at?.value.span ?? origin,
|
|
1830
|
+
related: [
|
|
1831
|
+
{
|
|
1832
|
+
source: check.source ?? "program",
|
|
1833
|
+
span: check.span,
|
|
1834
|
+
message: "Header check",
|
|
1835
|
+
},
|
|
1836
|
+
],
|
|
1837
|
+
});
|
|
1838
|
+
});
|
|
1839
|
+
if (parameterErrors.length) {
|
|
1840
|
+
bindingDiagnostics.push(...parameterErrors.slice(1));
|
|
1841
|
+
throw new CompileFailure(parameterErrors[0]!);
|
|
1704
1842
|
}
|
|
1705
1843
|
for (const key of body.keys())
|
|
1706
1844
|
if (
|
|
1707
1845
|
![
|
|
1708
|
-
"familyRevision",
|
|
1709
1846
|
"fields",
|
|
1710
1847
|
"lifecycle",
|
|
1711
1848
|
"records",
|
|
1712
1849
|
"summary",
|
|
1713
1850
|
"invariants",
|
|
1714
|
-
"
|
|
1851
|
+
"checks",
|
|
1715
1852
|
"ceilings",
|
|
1716
|
-
"dependencies",
|
|
1717
|
-
"parameterDiagnostics",
|
|
1718
1853
|
"reports",
|
|
1719
1854
|
"revisioned",
|
|
1720
1855
|
"scope",
|
|
@@ -1726,24 +1861,6 @@ export function compile(
|
|
|
1726
1861
|
`unknown instrument clause ${key}`,
|
|
1727
1862
|
"use fields, lifecycle, actions, invariants, or records",
|
|
1728
1863
|
);
|
|
1729
|
-
if (body.has("familyRevision")) {
|
|
1730
|
-
const revExpr = body.get("familyRevision")!;
|
|
1731
|
-
const revVal = literal(revExpr);
|
|
1732
|
-
if (
|
|
1733
|
-
typeof revVal !== "number" ||
|
|
1734
|
-
!Number.isInteger(revVal) ||
|
|
1735
|
-
revVal <= 0
|
|
1736
|
-
) {
|
|
1737
|
-
fail(
|
|
1738
|
-
revExpr,
|
|
1739
|
-
"familyRevision must be a positive integer",
|
|
1740
|
-
"use a positive integer revision",
|
|
1741
|
-
);
|
|
1742
|
-
}
|
|
1743
|
-
if (familyDeclaration) {
|
|
1744
|
-
familyDeclaration.revision = revVal;
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
1864
|
const records = entries(asBlock(body.get("records")));
|
|
1748
1865
|
if (!inherited.size)
|
|
1749
1866
|
environment.set("parent", { kind: "name", value: id, span: origin });
|
|
@@ -1962,10 +2079,10 @@ export function compile(
|
|
|
1962
2079
|
if (
|
|
1963
2080
|
relation === "is" &&
|
|
1964
2081
|
(choice === "set" || choice === "unset") &&
|
|
1965
|
-
|
|
2082
|
+
terms.has(tunable!) &&
|
|
1966
2083
|
!enums.has(tunable!)
|
|
1967
2084
|
)
|
|
1968
|
-
return
|
|
2085
|
+
return isSet(tunable!) === (choice === "set");
|
|
1969
2086
|
const binding = environment.get(tunable!);
|
|
1970
2087
|
if (!binding)
|
|
1971
2088
|
fail(
|
|
@@ -1980,7 +2097,7 @@ export function compile(
|
|
|
1980
2097
|
const [root, ...children] = text(bound).split(".");
|
|
1981
2098
|
const assignment = assignments.get(root!);
|
|
1982
2099
|
let target = assignment
|
|
1983
|
-
?
|
|
2100
|
+
? templateAt(assignment.target)?.body
|
|
1984
2101
|
: program.decls
|
|
1985
2102
|
.filter(
|
|
1986
2103
|
(decl): decl is InstrumentDecl => decl.kind === "instrument",
|
|
@@ -2468,14 +2585,9 @@ export function compile(
|
|
|
2468
2585
|
operation,
|
|
2469
2586
|
requirements: validatedRequirements,
|
|
2470
2587
|
};
|
|
2471
|
-
const digest = sha256(
|
|
2472
|
-
new TextEncoder().encode(canonicalJson(declaration)),
|
|
2473
|
-
);
|
|
2474
2588
|
const snapshot: UdlAdapterSubjectSnapshot = {
|
|
2475
2589
|
...declaration,
|
|
2476
|
-
declarationDigest:
|
|
2477
|
-
byte.toString(16).padStart(2, "0"),
|
|
2478
|
-
).join(""),
|
|
2590
|
+
declarationDigest: digest(declaration),
|
|
2479
2591
|
};
|
|
2480
2592
|
const adapterRenames: Record<string, string> = {};
|
|
2481
2593
|
if (attachmentInfo?.renames) {
|
|
@@ -2606,7 +2718,6 @@ export function compile(
|
|
|
2606
2718
|
summary: slots.has("summary")
|
|
2607
2719
|
? String(data(slots.get("summary")!))
|
|
2608
2720
|
: title(name),
|
|
2609
|
-
publicAction: camel(`${name}_${id}`),
|
|
2610
2721
|
event: `${id}.${name}`,
|
|
2611
2722
|
actor: "caller",
|
|
2612
2723
|
input: lowerFields(asBlock(slots.get("input"))),
|
|
@@ -3234,20 +3345,16 @@ export function compile(
|
|
|
3234
3345
|
}
|
|
3235
3346
|
if (moveCalculations.length)
|
|
3236
3347
|
a.calculate = [...(a.calculate ?? []), ...moveCalculations];
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
} else {
|
|
3247
|
-
delete a.publicAction;
|
|
3248
|
-
}
|
|
3348
|
+
// An action is hidden until an expose line names it.
|
|
3349
|
+
if (attachmentInfo?.exposed.has(name)) {
|
|
3350
|
+
if (automatic(a.actor))
|
|
3351
|
+
fail(
|
|
3352
|
+
row,
|
|
3353
|
+
`${name} runs on the clock or its parent, not a caller`,
|
|
3354
|
+
"expose only caller actions",
|
|
3355
|
+
);
|
|
3356
|
+
a.publicAction = attachmentInfo.exposed.get(name)!;
|
|
3249
3357
|
}
|
|
3250
|
-
if (automatic(a.actor)) delete a.publicAction;
|
|
3251
3358
|
const checkSubjectPaths = (obj: unknown, key = "") => {
|
|
3252
3359
|
if (typeof obj === "string") {
|
|
3253
3360
|
if (
|
|
@@ -3434,15 +3541,33 @@ export function compile(
|
|
|
3434
3541
|
origins.push({
|
|
3435
3542
|
path: `$.instruments[${document.instruments.length}]`,
|
|
3436
3543
|
span: { ...origin, ...lineColAt(source, origin.start) },
|
|
3437
|
-
...(standardOrigin
|
|
3544
|
+
...(standardOrigin
|
|
3545
|
+
? {
|
|
3546
|
+
standardBlock: standardOrigin,
|
|
3547
|
+
terms: [...terms].map(([name, term]) => {
|
|
3548
|
+
const text = sources.get(term.source)!;
|
|
3549
|
+
return {
|
|
3550
|
+
name,
|
|
3551
|
+
from: term.from,
|
|
3552
|
+
text: !term.value
|
|
3553
|
+
? "unset"
|
|
3554
|
+
: term.from === "party"
|
|
3555
|
+
? name
|
|
3556
|
+
: text.slice(term.span.start, term.span.end),
|
|
3557
|
+
source: term.source,
|
|
3558
|
+
...lineColAt(text, term.span.start),
|
|
3559
|
+
};
|
|
3560
|
+
}),
|
|
3561
|
+
}
|
|
3562
|
+
: {}),
|
|
3438
3563
|
});
|
|
3439
|
-
if (familyDeclaration
|
|
3564
|
+
if (familyDeclaration)
|
|
3440
3565
|
inst.family = {
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3566
|
+
...familyDeclaration,
|
|
3567
|
+
...(revertedHeader
|
|
3568
|
+
? { digest: declarationDigest(revertedHeader) }
|
|
3569
|
+
: {}),
|
|
3444
3570
|
};
|
|
3445
|
-
}
|
|
3446
3571
|
document.instruments.push(inst);
|
|
3447
3572
|
declarations.set(inst, decl);
|
|
3448
3573
|
for (const [key, definition] of records) {
|
|
@@ -3476,13 +3601,11 @@ export function compile(
|
|
|
3476
3601
|
declarationSources.get(decl) ?? "program",
|
|
3477
3602
|
);
|
|
3478
3603
|
const childFamily = familyDeclaration
|
|
3479
|
-
?
|
|
3480
|
-
module
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
: {}),
|
|
3485
|
-
}
|
|
3604
|
+
? resolveFamily(
|
|
3605
|
+
`${familyDeclaration.module}.${familyDeclaration.exportPath}.${key}`,
|
|
3606
|
+
{ span: origin },
|
|
3607
|
+
false,
|
|
3608
|
+
)
|
|
3486
3609
|
: undefined;
|
|
3487
3610
|
addInstrument(
|
|
3488
3611
|
child,
|
|
@@ -3504,7 +3627,7 @@ export function compile(
|
|
|
3504
3627
|
.join("."),
|
|
3505
3628
|
}
|
|
3506
3629
|
: undefined,
|
|
3507
|
-
|
|
3630
|
+
terms,
|
|
3508
3631
|
);
|
|
3509
3632
|
}
|
|
3510
3633
|
};
|
|
@@ -3554,14 +3677,7 @@ export function compile(
|
|
|
3554
3677
|
}
|
|
3555
3678
|
const attachmentName = match[1]!;
|
|
3556
3679
|
const targetTemplate = match[2]!;
|
|
3557
|
-
const template =
|
|
3558
|
-
if (!template) {
|
|
3559
|
-
fail(
|
|
3560
|
-
entry,
|
|
3561
|
-
`unknown instrument ${targetTemplate}`,
|
|
3562
|
-
`add use ${targetTemplate.split(".")[0]} and choose a declared instrument`,
|
|
3563
|
-
);
|
|
3564
|
-
}
|
|
3680
|
+
const template = requireTemplate(targetTemplate, entry);
|
|
3565
3681
|
const instId = `${decl.name}_${attachmentName}`;
|
|
3566
3682
|
|
|
3567
3683
|
const attachmentBlock = asBlock(entry.value);
|
|
@@ -3576,11 +3692,15 @@ export function compile(
|
|
|
3576
3692
|
economics.push(row);
|
|
3577
3693
|
} else if (row.key === "rename") {
|
|
3578
3694
|
for (const r of asBlock(row.value).entries) {
|
|
3579
|
-
|
|
3580
|
-
|
|
3695
|
+
const first = renameEntries.get(r.key);
|
|
3696
|
+
if (first)
|
|
3697
|
+
conflict(
|
|
3698
|
+
texts,
|
|
3699
|
+
`rename ${r.key} is given twice`,
|
|
3700
|
+
first,
|
|
3581
3701
|
r,
|
|
3582
|
-
`duplicate rename ${r.key}`,
|
|
3583
3702
|
"rename each subject field once",
|
|
3703
|
+
{ values: [first.value, r.value] },
|
|
3584
3704
|
);
|
|
3585
3705
|
renames.set(r.key, text(r.value));
|
|
3586
3706
|
renameEntries.set(r.key, r);
|
|
@@ -3589,16 +3709,28 @@ export function compile(
|
|
|
3589
3709
|
if (row.value.kind === "call") {
|
|
3590
3710
|
const actionName = row.value.name;
|
|
3591
3711
|
const publicName = text(row.value.args[0]!);
|
|
3712
|
+
const first = exposures.find(
|
|
3713
|
+
(exposure) =>
|
|
3714
|
+
exposure.instrument === instId &&
|
|
3715
|
+
exposure.action === actionName,
|
|
3716
|
+
);
|
|
3717
|
+
if (first)
|
|
3718
|
+
conflict(
|
|
3719
|
+
texts,
|
|
3720
|
+
`${actionName} is exposed twice`,
|
|
3721
|
+
first.entry,
|
|
3722
|
+
row,
|
|
3723
|
+
"expose each action once",
|
|
3724
|
+
);
|
|
3592
3725
|
if (
|
|
3593
|
-
exposed.has(actionName) ||
|
|
3594
3726
|
!template.body.entries.some(
|
|
3595
3727
|
(entry) => entry.key === `action ${actionName}`,
|
|
3596
3728
|
)
|
|
3597
3729
|
)
|
|
3598
3730
|
fail(
|
|
3599
3731
|
row,
|
|
3600
|
-
`unknown
|
|
3601
|
-
"expose one declared action
|
|
3732
|
+
`unknown action ${actionName}`,
|
|
3733
|
+
"expose one declared action",
|
|
3602
3734
|
);
|
|
3603
3735
|
exposed.set(actionName, publicName);
|
|
3604
3736
|
exposures.push({
|
|
@@ -3642,7 +3774,7 @@ export function compile(
|
|
|
3642
3774
|
economics,
|
|
3643
3775
|
template: targetTemplate,
|
|
3644
3776
|
},
|
|
3645
|
-
templateFamily
|
|
3777
|
+
templateFamily,
|
|
3646
3778
|
standardOrigins.get(template),
|
|
3647
3779
|
);
|
|
3648
3780
|
} catch (error) {
|
|
@@ -3828,13 +3960,7 @@ export function compile(
|
|
|
3828
3960
|
addInstrument(decl, decl.name, emptyBlock, decl.span);
|
|
3829
3961
|
}
|
|
3830
3962
|
if (decl.kind === "assignment") {
|
|
3831
|
-
const template =
|
|
3832
|
-
if (!template)
|
|
3833
|
-
fail(
|
|
3834
|
-
decl,
|
|
3835
|
-
`unknown instrument ${decl.target}`,
|
|
3836
|
-
`add use ${decl.target.split(".")[0]} and choose a declared instrument`,
|
|
3837
|
-
);
|
|
3963
|
+
const template = requireTemplate(decl.target, decl);
|
|
3838
3964
|
const templateFamily = resolveFamily(decl.target, decl, false);
|
|
3839
3965
|
addInstrument(
|
|
3840
3966
|
template,
|
|
@@ -3844,7 +3970,7 @@ export function compile(
|
|
|
3844
3970
|
new Map(),
|
|
3845
3971
|
new Map(),
|
|
3846
3972
|
undefined,
|
|
3847
|
-
templateFamily
|
|
3973
|
+
templateFamily,
|
|
3848
3974
|
standardOrigins.get(template),
|
|
3849
3975
|
);
|
|
3850
3976
|
}
|
|
@@ -3877,10 +4003,12 @@ export function compile(
|
|
|
3877
4003
|
if (
|
|
3878
4004
|
family &&
|
|
3879
4005
|
families.every(
|
|
3880
|
-
(item) =>
|
|
4006
|
+
(item) =>
|
|
4007
|
+
item !== undefined &&
|
|
4008
|
+
canonicalJson(item) === canonicalJson(family),
|
|
3881
4009
|
)
|
|
3882
4010
|
)
|
|
3883
|
-
selection.family = family;
|
|
4011
|
+
selection.family = family as UdlFamily;
|
|
3884
4012
|
}
|
|
3885
4013
|
for (const child of Object.values(record)) retainSelectionFamilies(child);
|
|
3886
4014
|
};
|
|
@@ -4251,7 +4379,7 @@ export function compile(
|
|
|
4251
4379
|
const declaredMembers = (selection: UdlSelection) => {
|
|
4252
4380
|
const members = [selection.instrument].flat();
|
|
4253
4381
|
const header = selection.family
|
|
4254
|
-
?
|
|
4382
|
+
? templateAt(
|
|
4255
4383
|
`${selection.family.module}.${selection.family.exportPath}`,
|
|
4256
4384
|
)
|
|
4257
4385
|
: undefined;
|
|
@@ -4580,48 +4708,63 @@ export function compile(
|
|
|
4580
4708
|
`action ${exposure.action} is excluded by these bindings`,
|
|
4581
4709
|
"expose an action available with these tunables",
|
|
4582
4710
|
);
|
|
4583
|
-
const changed = new
|
|
4711
|
+
const changed = new Map<string, Site>();
|
|
4584
4712
|
for (const decl of program.decls)
|
|
4585
|
-
if (decl.kind === "
|
|
4713
|
+
if (decl.kind === "expose") {
|
|
4586
4714
|
const parts = decl.target.split(".");
|
|
4587
4715
|
const key = parts.pop();
|
|
4588
4716
|
const id = parts.join("_");
|
|
4589
4717
|
const action = document.instruments.find((i) => i.id === id)?.actions[
|
|
4590
4718
|
key ?? ""
|
|
4591
4719
|
];
|
|
4592
|
-
|
|
4720
|
+
const first = changed.get(decl.target);
|
|
4721
|
+
if (first)
|
|
4722
|
+
conflict(
|
|
4723
|
+
texts,
|
|
4724
|
+
`${decl.target} is exposed twice`,
|
|
4725
|
+
first,
|
|
4726
|
+
decl,
|
|
4727
|
+
"expose each action once",
|
|
4728
|
+
);
|
|
4729
|
+
if (!action)
|
|
4593
4730
|
fail(
|
|
4594
4731
|
decl,
|
|
4595
|
-
`unknown
|
|
4596
|
-
"name one declared object.action
|
|
4732
|
+
`unknown action ${decl.target}`,
|
|
4733
|
+
"name one declared object.action",
|
|
4597
4734
|
);
|
|
4598
|
-
changed.
|
|
4599
|
-
if (
|
|
4600
|
-
else if (automatic(action!.actor))
|
|
4735
|
+
changed.set(decl.target, decl);
|
|
4736
|
+
if (automatic(action.actor))
|
|
4601
4737
|
fail(
|
|
4602
4738
|
decl,
|
|
4603
4739
|
`${decl.target} runs on the clock or its parent, not a caller`,
|
|
4604
4740
|
"expose only caller actions",
|
|
4605
4741
|
);
|
|
4606
|
-
|
|
4742
|
+
action.publicAction = decl.name;
|
|
4607
4743
|
}
|
|
4744
|
+
// Two actions of one instrument cannot share a public name.
|
|
4745
|
+
const published = new Map<string, Site>();
|
|
4608
4746
|
for (const decl of program.decls) {
|
|
4609
4747
|
if (decl.kind !== "expose") continue;
|
|
4610
|
-
const
|
|
4611
|
-
|
|
4612
|
-
const
|
|
4613
|
-
(
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4748
|
+
const instrument = decl.target.split(".").slice(0, -1).join("_");
|
|
4749
|
+
const key = `${instrument} ${decl.name}`;
|
|
4750
|
+
const first =
|
|
4751
|
+
published.get(key) ??
|
|
4752
|
+
exposures.find(
|
|
4753
|
+
({ instrument: id, action, entry }) =>
|
|
4754
|
+
id === instrument &&
|
|
4755
|
+
`${decl.target}`.split(".").pop() !== action &&
|
|
4756
|
+
entry.value.kind === "call" &&
|
|
4757
|
+
text(entry.value.args[0]!) === decl.name,
|
|
4758
|
+
)?.entry;
|
|
4759
|
+
if (first)
|
|
4760
|
+
conflict(
|
|
4761
|
+
texts,
|
|
4762
|
+
`public action ${decl.name} is given twice on ${instrument}`,
|
|
4763
|
+
first,
|
|
4621
4764
|
decl,
|
|
4622
|
-
`public action ${decl.name} is used more than once on ${parts.join(".")}`,
|
|
4623
4765
|
"give each exposed action a distinct public name",
|
|
4624
4766
|
);
|
|
4767
|
+
published.set(key, decl);
|
|
4625
4768
|
}
|
|
4626
4769
|
// Every attachment of an object shares one public action namespace, so
|
|
4627
4770
|
// two attachments that both `expose create` collide.
|
|
@@ -4836,6 +4979,7 @@ export function compile(
|
|
|
4836
4979
|
diagnostic(d, "check"),
|
|
4837
4980
|
),
|
|
4838
4981
|
};
|
|
4982
|
+
const checks = lowerChecks(program.checks, validated.value, literal);
|
|
4839
4983
|
return {
|
|
4840
4984
|
verdict: "valid",
|
|
4841
4985
|
diagnostics: [],
|
|
@@ -4847,6 +4991,7 @@ export function compile(
|
|
|
4847
4991
|
document: validated.value,
|
|
4848
4992
|
costManifest: buildUdlCostManifest(validated.value),
|
|
4849
4993
|
originMap: origins,
|
|
4994
|
+
checks,
|
|
4850
4995
|
},
|
|
4851
4996
|
};
|
|
4852
4997
|
} catch (error) {
|