@hyperscale0/hsx 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/src/ast.d.ts +0 -1
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +0 -11
- package/dist/src/ast.js.map +1 -1
- package/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +185 -101
- package/dist/src/compile.js.map +1 -1
- package/dist/src/diagnostics.d.ts +14 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +20 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/header-source.d.ts +3 -0
- package/dist/src/header-source.d.ts.map +1 -0
- package/dist/src/header-source.js +36 -0
- package/dist/src/header-source.js.map +1 -0
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +4 -6
- package/dist/src/headers.js.map +1 -1
- package/dist/src/keywords.d.ts +8 -2
- package/dist/src/keywords.d.ts.map +1 -1
- package/dist/src/keywords.js +13 -5
- package/dist/src/keywords.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 +11 -3
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +22 -5
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.js +1 -1
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/tunables.d.ts.map +1 -1
- package/dist/src/tunables.js +2 -1
- package/dist/src/tunables.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +5 -3
- package/package.json +3 -3
- package/src/ast.ts +0 -10
- package/src/cli.ts +1 -1
- package/src/compile.ts +267 -127
- package/src/diagnostics.ts +28 -0
- package/src/header-source.ts +44 -0
- package/src/headers.ts +4 -6
- package/src/keywords.ts +13 -5
- package/src/lex.ts +13 -5
- package/src/parse.ts +29 -5
- package/src/std-bundle.ts +1 -1
- package/src/tunables.ts +6 -1
- package/src/version.ts +1 -1
- package/std/financing.hsx +2 -2
package/dist/src/compile.js
CHANGED
|
@@ -1,38 +1,23 @@
|
|
|
1
|
+
import { CompileFailure, fail, failWithCode } from "./diagnostics.js";
|
|
1
2
|
import { hash as sha256 } from "fast-sha256";
|
|
2
3
|
import { buildUdlCostManifest } from "./cost.js";
|
|
3
|
-
import { validateUdl, resolveField, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, udlObjectFieldSchema, } from "@hyperscale0/udl";
|
|
4
|
+
import { validateUdl, resolveField, sameObjectField, subjectPartyRoles, RESERVED_OBJECT_NAMES, udlObjectFieldSchema, udlInstrumentSchema, } from "@hyperscale0/udl";
|
|
4
5
|
import { bindingDependencies, parameterDiagnostics, BindingContractError, } from "./binding-contract.js";
|
|
5
6
|
import { tunableBounds } from "./tunables.js";
|
|
6
7
|
import { parseProgram } from "./parse.js";
|
|
8
|
+
import { parseHeader } from "./header-source.js";
|
|
7
9
|
import { lineColAt, } from "./ast.js";
|
|
8
10
|
import { bundledStandardLibrary } from "./std-library.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
function fail(expr, message, fix) {
|
|
17
|
-
return failWithCode(expr, "HSX1001", message, fix);
|
|
18
|
-
}
|
|
19
|
-
function failWithCode(expr, code, message, fix) {
|
|
20
|
-
throw new CompileFailure({
|
|
21
|
-
code,
|
|
22
|
-
message,
|
|
23
|
-
fix,
|
|
24
|
-
span: expr.span,
|
|
25
|
-
...(expr.source ? { source: expr.source } : {}),
|
|
26
|
-
});
|
|
11
|
+
function fieldType(row) {
|
|
12
|
+
const type = row.value.kind === "default" ? row.value.type : row.value;
|
|
13
|
+
if (type.kind !== "call" || type.name !== "sensitive")
|
|
14
|
+
return { type, sensitive: false };
|
|
15
|
+
if (type.args.length !== 1)
|
|
16
|
+
fail(type, "sensitive needs one field type", "write sensitive(text)");
|
|
17
|
+
return { type: type.args[0], sensitive: true };
|
|
27
18
|
}
|
|
28
19
|
function lowerFieldShape(row, resolveExpr = (e) => e) {
|
|
29
|
-
|
|
30
|
-
let isSensitive = false;
|
|
31
|
-
if (rawValue.kind === "call" && rawValue.name === "sensitive") {
|
|
32
|
-
isSensitive = true;
|
|
33
|
-
rawValue = rawValue.args[0];
|
|
34
|
-
}
|
|
35
|
-
const t = rawValue;
|
|
20
|
+
const { type: t, sensitive: isSensitive } = fieldType(row);
|
|
36
21
|
if (t.kind === "block") {
|
|
37
22
|
const b = entries(t);
|
|
38
23
|
if (b.has("family") || b.has("target") || b.has("instrument")) {
|
|
@@ -83,7 +68,12 @@ function lowerFieldShape(row, resolveExpr = (e) => e) {
|
|
|
83
68
|
f.minimum = literal(resolveExpr(t.args[0]));
|
|
84
69
|
f.maximum = literal(resolveExpr(t.args[1]));
|
|
85
70
|
}
|
|
71
|
+
if (t.kind === "call" &&
|
|
72
|
+
!["enum", "text", "integer", "money", "list", "account"].includes(type))
|
|
73
|
+
fail(t, `unknown field constructor ${type}`, "use a field type without arguments");
|
|
86
74
|
if (type === "list" && t.kind === "call") {
|
|
75
|
+
if (t.args.length < 1 || t.args.length > 2)
|
|
76
|
+
fail(t, "list needs an item type and optional bound", "write list(text, 12)");
|
|
87
77
|
const item = t.args[0];
|
|
88
78
|
f.item = item.kind === "type" ? item.name : text(item);
|
|
89
79
|
if (item.kind === "type" && item.name === "ref") {
|
|
@@ -114,7 +104,7 @@ function lowerObjectField(row, resolveExpr = (e) => e) {
|
|
|
114
104
|
f.value =
|
|
115
105
|
f.type === "enum" && constant.kind === "name"
|
|
116
106
|
? constant.value
|
|
117
|
-
: literal(constant);
|
|
107
|
+
: literal(constant, String(f.type));
|
|
118
108
|
}
|
|
119
109
|
const result = udlObjectFieldSchema.safeParse(f);
|
|
120
110
|
if (!result.success)
|
|
@@ -141,7 +131,8 @@ const emptyBlock = {
|
|
|
141
131
|
};
|
|
142
132
|
const camel = (name) => name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
143
133
|
/** Clock and parent actors run without a caller, so they have no public name. */
|
|
144
|
-
const automatic = (actor) => actor === "clock" ||
|
|
134
|
+
const automatic = (actor) => actor === "clock" ||
|
|
135
|
+
(actor !== null && typeof actor === "object" && "parent" in actor);
|
|
145
136
|
const title = (name) => name[0].toUpperCase() + name.slice(1).replaceAll("_", " ");
|
|
146
137
|
function entries(block) {
|
|
147
138
|
const map = new Map();
|
|
@@ -188,7 +179,9 @@ function decimal(raw, scale, expr) {
|
|
|
188
179
|
return (BigInt(whole) * 10n ** BigInt(scale) +
|
|
189
180
|
BigInt(fraction.padEnd(scale, "0") || "0")).toString();
|
|
190
181
|
}
|
|
191
|
-
function literal(expr) {
|
|
182
|
+
function literal(expr, expectedType) {
|
|
183
|
+
if (expr.kind === "text" && expectedType === "duration")
|
|
184
|
+
return literal({ ...expr, kind: "duration" });
|
|
192
185
|
if (expr.kind === "money") {
|
|
193
186
|
const [raw, currency] = expr.value.split(" ");
|
|
194
187
|
if (currency !== "SAR")
|
|
@@ -211,8 +204,7 @@ function literal(expr) {
|
|
|
211
204
|
return n;
|
|
212
205
|
}
|
|
213
206
|
if (expr.kind === "duration" ||
|
|
214
|
-
(expr.kind === "name" && /^P(?:\d|T)/.test(expr.value))
|
|
215
|
-
(expr.kind === "text" && /^P(?:\d|T)/.test(expr.value))) {
|
|
207
|
+
(expr.kind === "name" && /^P(?:\d|T)/.test(expr.value))) {
|
|
216
208
|
const short = /^(\d+)(ms|s|m|h|d|w)$/.exec(expr.value);
|
|
217
209
|
const units = {
|
|
218
210
|
ms: 1,
|
|
@@ -222,7 +214,7 @@ function literal(expr) {
|
|
|
222
214
|
d: 86400000,
|
|
223
215
|
w: 604800000,
|
|
224
216
|
};
|
|
225
|
-
const iso = /^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/.exec(expr.value);
|
|
217
|
+
const iso = /^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?=\d)(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/.exec(expr.value);
|
|
226
218
|
const n = short
|
|
227
219
|
? Number(short[1]) * units[short[2]]
|
|
228
220
|
: iso
|
|
@@ -239,9 +231,10 @@ function literal(expr) {
|
|
|
239
231
|
if (expr.kind === "date") {
|
|
240
232
|
const raw = expr.value.length === 10 ? expr.value + "T00:00:00Z" : expr.value;
|
|
241
233
|
const n = Date.parse(raw);
|
|
234
|
+
const day = Date.parse(raw.slice(0, 10) + "T00:00:00Z");
|
|
242
235
|
if (!Number.isFinite(n) ||
|
|
243
|
-
|
|
244
|
-
|
|
236
|
+
!Number.isFinite(day) ||
|
|
237
|
+
new Date(day).toISOString().slice(0, 10) !== raw.slice(0, 10))
|
|
245
238
|
fail(expr, "invalid date", "write a calendar date or a timestamp with an explicit offset");
|
|
246
239
|
return new Date(n).toISOString();
|
|
247
240
|
}
|
|
@@ -267,6 +260,16 @@ export function compile(source, options = {}) {
|
|
|
267
260
|
};
|
|
268
261
|
const program = parsed.program;
|
|
269
262
|
const bindingDiagnostics = [];
|
|
263
|
+
const adapterTarget = (binding) => {
|
|
264
|
+
const registry = options.adapterRegistry;
|
|
265
|
+
if (!registry || !Object.hasOwn(registry, binding))
|
|
266
|
+
return;
|
|
267
|
+
const target = registry[binding];
|
|
268
|
+
return target &&
|
|
269
|
+
Object.hasOwn(target.adapter.operationMap, target.operation)
|
|
270
|
+
? target
|
|
271
|
+
: undefined;
|
|
272
|
+
};
|
|
270
273
|
try {
|
|
271
274
|
if (program.header)
|
|
272
275
|
fail(program, "compile a program, not a header", 'start with program product_name "Title"');
|
|
@@ -274,7 +277,6 @@ export function compile(source, options = {}) {
|
|
|
274
277
|
fail(program, "this release supports SAR", "write currency SAR or omit currency");
|
|
275
278
|
const templates = new Map();
|
|
276
279
|
const declarationSources = new Map();
|
|
277
|
-
const declarationExportPaths = new Map();
|
|
278
280
|
const requirementOrigins = new Map();
|
|
279
281
|
const used = new Set();
|
|
280
282
|
for (const use of program.decls.filter((d) => d.kind === "use")) {
|
|
@@ -285,15 +287,29 @@ export function compile(source, options = {}) {
|
|
|
285
287
|
if (!content)
|
|
286
288
|
fail(use, `unknown header ${use.name}`, "choose a published header");
|
|
287
289
|
sources.set(use.name, content);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
290
|
+
let header;
|
|
291
|
+
try {
|
|
292
|
+
header = parseHeader(content, use.name);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (!(error instanceof CompileFailure))
|
|
296
|
+
throw error;
|
|
297
|
+
throw new CompileFailure({
|
|
298
|
+
...error.diagnostic,
|
|
299
|
+
related: [
|
|
300
|
+
{
|
|
301
|
+
source: "program",
|
|
302
|
+
span: use.span,
|
|
303
|
+
message: `Imported header ${use.name}`,
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const registerTemplates = (parentDecl, prefix) => {
|
|
309
|
+
if (templates.has(prefix))
|
|
310
|
+
fail(parentDecl, `duplicate instrument ${prefix}`, "give each instrument a distinct name");
|
|
294
311
|
templates.set(prefix, parentDecl);
|
|
295
312
|
declarationSources.set(parentDecl, use.name);
|
|
296
|
-
declarationExportPaths.set(parentDecl, exportPath);
|
|
297
313
|
const recs = entries(asBlock(entries(parentDecl.body).get("records")));
|
|
298
314
|
for (const [recName, recBlock] of recs) {
|
|
299
315
|
const recDecl = {
|
|
@@ -303,12 +319,12 @@ export function compile(source, options = {}) {
|
|
|
303
319
|
body: asBlock(recBlock),
|
|
304
320
|
span: parentDecl.span,
|
|
305
321
|
};
|
|
306
|
-
registerTemplates(recDecl, `${prefix}.${recName}
|
|
322
|
+
registerTemplates(recDecl, `${prefix}.${recName}`);
|
|
307
323
|
}
|
|
308
324
|
};
|
|
309
|
-
for (const decl of header.
|
|
325
|
+
for (const decl of header.decls)
|
|
310
326
|
if (decl.kind === "instrument") {
|
|
311
|
-
registerTemplates(decl, `${use.name}.${decl.name}
|
|
327
|
+
registerTemplates(decl, `${use.name}.${decl.name}`);
|
|
312
328
|
}
|
|
313
329
|
}
|
|
314
330
|
for (const decl of program.decls)
|
|
@@ -324,12 +340,12 @@ export function compile(source, options = {}) {
|
|
|
324
340
|
product: program.name,
|
|
325
341
|
title: program.title,
|
|
326
342
|
currency: "SAR",
|
|
327
|
-
parties: {
|
|
343
|
+
parties: Object.assign(Object.create(null), {
|
|
328
344
|
programOperator: { kind: "business", role: "program_operator" },
|
|
329
345
|
programTax: { kind: "business", role: "tax_payable" },
|
|
330
346
|
programFines: { kind: "business", role: "fine_payable" },
|
|
331
347
|
programCosts: { kind: "business", role: "cost_recovery" },
|
|
332
|
-
},
|
|
348
|
+
}),
|
|
333
349
|
objects: [],
|
|
334
350
|
instruments: [],
|
|
335
351
|
};
|
|
@@ -374,6 +390,7 @@ export function compile(source, options = {}) {
|
|
|
374
390
|
}
|
|
375
391
|
}
|
|
376
392
|
const origins = [];
|
|
393
|
+
const exposures = [];
|
|
377
394
|
const refundBindings = new Map();
|
|
378
395
|
const resolveFamily = (rawPath, expr, required = true) => {
|
|
379
396
|
const parts = rawPath.split(".");
|
|
@@ -502,10 +519,7 @@ export function compile(source, options = {}) {
|
|
|
502
519
|
const sub = resolveChildExportPath(decl, targetId.slice(id.length + 1));
|
|
503
520
|
if (sub) {
|
|
504
521
|
const fullExport = `${familyDeclaration.exportPath}.${sub}`;
|
|
505
|
-
|
|
506
|
-
return resolveFamily(`${familyDeclaration.module}.${fullExport}`, { span: origin }, false);
|
|
507
|
-
}
|
|
508
|
-
catch { }
|
|
522
|
+
return resolveFamily(`${familyDeclaration.module}.${fullExport}`, { span: origin }, false);
|
|
509
523
|
}
|
|
510
524
|
}
|
|
511
525
|
for (const [asgnName, asgn] of assignments) {
|
|
@@ -517,19 +531,13 @@ export function compile(source, options = {}) {
|
|
|
517
531
|
if (!mod || mod === "program")
|
|
518
532
|
continue;
|
|
519
533
|
if (targetId === asgnName) {
|
|
520
|
-
|
|
521
|
-
return resolveFamily(asgn.target, asgn, false);
|
|
522
|
-
}
|
|
523
|
-
catch { }
|
|
534
|
+
return resolveFamily(asgn.target, asgn, false);
|
|
524
535
|
}
|
|
525
536
|
else {
|
|
526
537
|
const sub = resolveChildExportPath(tmpl, targetId.slice(asgnName.length + 1));
|
|
527
538
|
if (sub) {
|
|
528
539
|
const fullTarget = `${asgn.target}.${sub}`;
|
|
529
|
-
|
|
530
|
-
return resolveFamily(fullTarget, asgn, false);
|
|
531
|
-
}
|
|
532
|
-
catch { }
|
|
540
|
+
return resolveFamily(fullTarget, asgn, false);
|
|
533
541
|
}
|
|
534
542
|
}
|
|
535
543
|
}
|
|
@@ -539,6 +547,8 @@ export function compile(source, options = {}) {
|
|
|
539
547
|
const checkTargetFamily = (targetIds, expectedFamily, expr) => {
|
|
540
548
|
const ids = Array.isArray(targetIds) ? targetIds : [targetIds];
|
|
541
549
|
for (const tid of ids) {
|
|
550
|
+
if (typeof tid !== "string")
|
|
551
|
+
fail(expr, "instrument target needs a name", "name a declared instrument or a list of instruments");
|
|
542
552
|
const fam = getInstrumentFamily(tid);
|
|
543
553
|
if (!fam ||
|
|
544
554
|
fam.module !== expectedFamily.module ||
|
|
@@ -668,8 +678,8 @@ export function compile(source, options = {}) {
|
|
|
668
678
|
? entry.value.value === "money"
|
|
669
679
|
: (entry.value.kind === "type" || entry.value.kind === "call") &&
|
|
670
680
|
entry.value.name === "money");
|
|
671
|
-
const target = candidates.length === 1 ? candidates[0].key : "
|
|
672
|
-
fail(suppliedValue, `\`${assignments.get(id)?.target ?? decl.name}\` has no \`${key}\` tunable. Its
|
|
681
|
+
const target = candidates.length === 1 ? candidates[0].key : "yourMoneyField";
|
|
682
|
+
fail(suppliedValue, `\`${assignments.get(id)?.target ?? decl.name}\` has no \`${key}\` tunable. Its actions require the object's \`${field}\` field.`, `Remove \`${key}\`. To use your object's money field, add \`rename { ${field}: ${target} }\`.`);
|
|
673
683
|
}
|
|
674
684
|
fail(suppliedValue, `unknown tunable ${key}`, `choose ${decl.parameters.map((p) => p.key).join(", ")}`);
|
|
675
685
|
}
|
|
@@ -780,9 +790,13 @@ export function compile(source, options = {}) {
|
|
|
780
790
|
continue;
|
|
781
791
|
const t = param.value.kind === "default" ? param.value.type : param.value;
|
|
782
792
|
const type = t.kind === "type" || t.kind === "call" ? t.name : text(t);
|
|
783
|
-
|
|
793
|
+
let v = (supplied.has(param.key) && !attachmentInfo) || type === "enum"
|
|
784
794
|
? actual
|
|
785
795
|
: resolve(actual, new Set(), !!attachmentInfo && type === "party");
|
|
796
|
+
if (type === "duration" &&
|
|
797
|
+
(v.kind === "text" || v.kind === "name") &&
|
|
798
|
+
/^P/.test(v.value))
|
|
799
|
+
v = { ...v, kind: "duration" };
|
|
786
800
|
environment.set(param.key, v);
|
|
787
801
|
if (type === "enum" && t.kind === "call") {
|
|
788
802
|
if (v.kind !== "name" || !t.args.some((a) => text(a) === v.value))
|
|
@@ -821,16 +835,33 @@ export function compile(source, options = {}) {
|
|
|
821
835
|
for (const value of values) {
|
|
822
836
|
if (value.kind !== "name")
|
|
823
837
|
fail(value, "reference needs an object name", "name a declared object");
|
|
824
|
-
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
838
|
+
let targetType = objects.get(value.value)?.name;
|
|
839
|
+
const candidates = [
|
|
840
|
+
...[...assignments.values()].map((assignment) => [assignment.name, assignment.target]),
|
|
841
|
+
...program.decls
|
|
842
|
+
.filter((decl) => decl.kind === "instrument")
|
|
843
|
+
.map((decl) => [decl.name, decl.name]),
|
|
844
|
+
];
|
|
845
|
+
for (const [instance, target] of candidates) {
|
|
846
|
+
if (value.value === instance) {
|
|
847
|
+
targetType = target;
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
if (!value.value.startsWith(`${instance}.`) &&
|
|
851
|
+
!value.value.startsWith(`${instance}_`))
|
|
852
|
+
continue;
|
|
853
|
+
const template = templates.get(target);
|
|
854
|
+
if (!template)
|
|
855
|
+
continue;
|
|
856
|
+
const suffix = value.value
|
|
857
|
+
.slice(instance.length + 1)
|
|
858
|
+
.replaceAll(".", "_");
|
|
859
|
+
const child = resolveChildExportPath(template, suffix);
|
|
860
|
+
if (child)
|
|
861
|
+
targetType = `${target}.${child}`;
|
|
862
|
+
}
|
|
863
|
+
if (!targetType ||
|
|
864
|
+
(t.kind === "type" && t.target && targetType !== t.target))
|
|
834
865
|
fail(value, `${param.key} has the wrong object type`, `use an object of type ${t.kind === "type" ? t.target : "ref"}`);
|
|
835
866
|
if (seen.has(value.value))
|
|
836
867
|
fail(value, "duplicate reference", "list each object once");
|
|
@@ -854,14 +885,20 @@ export function compile(source, options = {}) {
|
|
|
854
885
|
type === "date" ||
|
|
855
886
|
type === "duration" ||
|
|
856
887
|
type === "integer" ||
|
|
857
|
-
type === "text"
|
|
888
|
+
type === "text" ||
|
|
889
|
+
type === "boolean") {
|
|
890
|
+
const bounds = tunableBounds(t);
|
|
858
891
|
if (v.kind === "name" && v.value === "runtime")
|
|
859
892
|
continue;
|
|
860
|
-
const expected = type === "integer"
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
893
|
+
const expected = type === "integer"
|
|
894
|
+
? "number"
|
|
895
|
+
: type === "boolean"
|
|
896
|
+
? "name"
|
|
897
|
+
: type;
|
|
898
|
+
if (v.kind !== expected ||
|
|
899
|
+
(type === "boolean" &&
|
|
900
|
+
v.kind === "name" &&
|
|
901
|
+
!["true", "false"].includes(v.value))) {
|
|
865
902
|
if (type === "money" && v.kind === "name" && attachmentInfo) {
|
|
866
903
|
const target = v.value.replace(/^subject\./, "");
|
|
867
904
|
const fallback = param.value.kind === "default"
|
|
@@ -881,7 +918,6 @@ export function compile(source, options = {}) {
|
|
|
881
918
|
}
|
|
882
919
|
try {
|
|
883
920
|
const value = literal(v);
|
|
884
|
-
const bounds = tunableBounds(t);
|
|
885
921
|
if (bounds &&
|
|
886
922
|
(BigInt(String(value)) < BigInt(bounds.minimum) ||
|
|
887
923
|
BigInt(String(value)) > BigInt(bounds.maximum)))
|
|
@@ -893,6 +929,9 @@ export function compile(source, options = {}) {
|
|
|
893
929
|
throw error;
|
|
894
930
|
}
|
|
895
931
|
}
|
|
932
|
+
else {
|
|
933
|
+
fail(t, `unknown tunable type ${type}`, "use a declared HSX tunable type");
|
|
934
|
+
}
|
|
896
935
|
}
|
|
897
936
|
catch (error) {
|
|
898
937
|
if (!(error instanceof CompileFailure))
|
|
@@ -931,6 +970,12 @@ export function compile(source, options = {}) {
|
|
|
931
970
|
const left = environment.get(constraint.key), right = environment.get(text(rule.args[0]));
|
|
932
971
|
if (!left || !right)
|
|
933
972
|
fail(rule, "constraint needs two declared tunables", "name the compared tunables");
|
|
973
|
+
const numericKind = (expr) => expr.kind === "name" && /^P/.test(expr.value)
|
|
974
|
+
? "duration"
|
|
975
|
+
: expr.kind;
|
|
976
|
+
if (!["money", "number", "percent", "duration"].includes(numericKind(left)) ||
|
|
977
|
+
numericKind(left) !== numericKind(right))
|
|
978
|
+
fail(rule, "constraint needs numeric tunables of the same type", "compare two amounts, integers, percentages or durations");
|
|
934
979
|
const a = BigInt(String(literal(left))), b = BigInt(String(literal(right)));
|
|
935
980
|
if (!(rule.name === "less_than"
|
|
936
981
|
? a < b
|
|
@@ -1009,7 +1054,7 @@ export function compile(source, options = {}) {
|
|
|
1009
1054
|
? se.value.items.map(text)
|
|
1010
1055
|
: [text(se.value)];
|
|
1011
1056
|
return names.some((n) => {
|
|
1012
|
-
const reg =
|
|
1057
|
+
const reg = adapterTarget(n);
|
|
1013
1058
|
if (!reg)
|
|
1014
1059
|
return false;
|
|
1015
1060
|
const op = reg.adapter.operationMap[reg.operation];
|
|
@@ -1119,7 +1164,7 @@ export function compile(source, options = {}) {
|
|
|
1119
1164
|
const lowerFields = (block) => {
|
|
1120
1165
|
const result = [];
|
|
1121
1166
|
for (const row of block.entries) {
|
|
1122
|
-
const
|
|
1167
|
+
const { type: t } = fieldType(row);
|
|
1123
1168
|
const constant = row.value.kind === "default" ? resolve(row.value.value) : undefined;
|
|
1124
1169
|
const f = lowerFieldShape(row, resolve);
|
|
1125
1170
|
const type = f.type;
|
|
@@ -1317,15 +1362,15 @@ export function compile(source, options = {}) {
|
|
|
1317
1362
|
values: [val(constant)],
|
|
1318
1363
|
});
|
|
1319
1364
|
else
|
|
1320
|
-
f.value = literal(constant);
|
|
1365
|
+
f.value = literal(constant, String(type));
|
|
1321
1366
|
}
|
|
1322
1367
|
result.push(f);
|
|
1323
1368
|
}
|
|
1324
1369
|
return result;
|
|
1325
1370
|
};
|
|
1326
1371
|
fields.push(...lowerFields(asBlock(body.get("fields"))));
|
|
1327
|
-
const lifecycle = data(body.get("lifecycle")
|
|
1328
|
-
lifecycle.transitions =
|
|
1372
|
+
const lifecycle = data(asBlock(body.get("lifecycle")));
|
|
1373
|
+
lifecycle.transitions = Object.create(null);
|
|
1329
1374
|
const inst = {
|
|
1330
1375
|
id,
|
|
1331
1376
|
...(attachmentInfo ? { subject: attachmentInfo.subjectKindId } : {}),
|
|
@@ -1336,7 +1381,7 @@ export function compile(source, options = {}) {
|
|
|
1336
1381
|
fields,
|
|
1337
1382
|
calculate: calculations,
|
|
1338
1383
|
lifecycle,
|
|
1339
|
-
actions:
|
|
1384
|
+
actions: Object.create(null),
|
|
1340
1385
|
actionOrder: [],
|
|
1341
1386
|
};
|
|
1342
1387
|
for (const row of decl.body.entries.filter((e) => e.key.startsWith("action "))) {
|
|
@@ -1409,12 +1454,8 @@ export function compile(source, options = {}) {
|
|
|
1409
1454
|
if (!adapterExpr)
|
|
1410
1455
|
fail(boundary, "boundary needs an adapter", "name a bound ADL adapter");
|
|
1411
1456
|
const binding = text(resolve(adapterExpr));
|
|
1412
|
-
const target =
|
|
1413
|
-
|
|
1414
|
-
? options.adapterRegistry[binding]
|
|
1415
|
-
: undefined;
|
|
1416
|
-
if (!target ||
|
|
1417
|
-
!Object.hasOwn(target.adapter.operationMap, target.operation))
|
|
1457
|
+
const target = adapterTarget(binding);
|
|
1458
|
+
if (!target)
|
|
1418
1459
|
fail(boundary, `unknown boundary adapter ${binding}`, "bind the named ADL adapter before compilation");
|
|
1419
1460
|
boundaryBindings.add(binding);
|
|
1420
1461
|
}
|
|
@@ -1458,7 +1499,7 @@ export function compile(source, options = {}) {
|
|
|
1458
1499
|
? entry.value.items.map(adapterName)
|
|
1459
1500
|
: [adapterName(entry.value)];
|
|
1460
1501
|
for (const bindingName of bindingNames) {
|
|
1461
|
-
const target =
|
|
1502
|
+
const target = adapterTarget(bindingName);
|
|
1462
1503
|
if (target) {
|
|
1463
1504
|
const { adapter, operation } = target;
|
|
1464
1505
|
const opBinding = adapter.operationMap[operation];
|
|
@@ -1840,6 +1881,8 @@ export function compile(source, options = {}) {
|
|
|
1840
1881
|
}
|
|
1841
1882
|
if (attachmentInfo && !attachmentInfo.child) {
|
|
1842
1883
|
if (attachmentInfo.exposed.has(name)) {
|
|
1884
|
+
if (automatic(a.actor))
|
|
1885
|
+
fail(row, `${name} runs on the clock or its parent, not a caller`, "expose only caller actions");
|
|
1843
1886
|
a.publicAction = attachmentInfo.exposed.get(name);
|
|
1844
1887
|
}
|
|
1845
1888
|
else {
|
|
@@ -1848,9 +1891,18 @@ export function compile(source, options = {}) {
|
|
|
1848
1891
|
}
|
|
1849
1892
|
if (automatic(a.actor))
|
|
1850
1893
|
delete a.publicAction;
|
|
1851
|
-
const checkSubjectPaths = (obj, span) => {
|
|
1894
|
+
const checkSubjectPaths = (obj, span, key = "") => {
|
|
1852
1895
|
if (typeof obj === "string") {
|
|
1853
|
-
if (
|
|
1896
|
+
if ([
|
|
1897
|
+
"field",
|
|
1898
|
+
"fields",
|
|
1899
|
+
"reference",
|
|
1900
|
+
"anchor",
|
|
1901
|
+
"subject",
|
|
1902
|
+
"at",
|
|
1903
|
+
"instruction",
|
|
1904
|
+
].includes(key) &&
|
|
1905
|
+
obj.startsWith("subject.")) {
|
|
1854
1906
|
const subField = obj.split(".")[1];
|
|
1855
1907
|
const req = a.subject?.requirements.find((r) => r.field.name === subField);
|
|
1856
1908
|
if (!req) {
|
|
@@ -1860,11 +1912,11 @@ export function compile(source, options = {}) {
|
|
|
1860
1912
|
}
|
|
1861
1913
|
else if (Array.isArray(obj)) {
|
|
1862
1914
|
for (const item of obj)
|
|
1863
|
-
checkSubjectPaths(item, span);
|
|
1915
|
+
checkSubjectPaths(item, span, key);
|
|
1864
1916
|
}
|
|
1865
1917
|
else if (obj !== null && typeof obj === "object") {
|
|
1866
|
-
for (const val of Object.
|
|
1867
|
-
checkSubjectPaths(val, span);
|
|
1918
|
+
for (const [key, val] of Object.entries(obj))
|
|
1919
|
+
checkSubjectPaths(val, span, key);
|
|
1868
1920
|
}
|
|
1869
1921
|
};
|
|
1870
1922
|
checkSubjectPaths(a.requires, row.span);
|
|
@@ -1887,6 +1939,17 @@ export function compile(source, options = {}) {
|
|
|
1887
1939
|
for (const key of ["invariants", "reports", "revisioned"])
|
|
1888
1940
|
if (body.has(key))
|
|
1889
1941
|
inst[key] = data(body.get(key));
|
|
1942
|
+
const shape = udlInstrumentSchema.safeParse(inst);
|
|
1943
|
+
if (!shape.success) {
|
|
1944
|
+
const issue = shape.error.issues[0];
|
|
1945
|
+
const actionName = issue.path[0] === "actions" ? String(issue.path[1]) : undefined;
|
|
1946
|
+
const node = decl.body.entries.find((entry) => entry.key ===
|
|
1947
|
+
(actionName ? `action ${actionName}` : String(issue.path[0]))) ?? decl;
|
|
1948
|
+
failWithCode({
|
|
1949
|
+
span: node.span,
|
|
1950
|
+
source: declarationSources.get(decl) ?? "program",
|
|
1951
|
+
}, "UDL1003", `${id}.${issue.path.join(".")}: ${issue.message}`, "use the UDL typed clause shape");
|
|
1952
|
+
}
|
|
1890
1953
|
origins.push({
|
|
1891
1954
|
path: `$.instruments[${document.instruments.length}]`,
|
|
1892
1955
|
span: { ...origin, ...lineColAt(source, origin.start) },
|
|
@@ -1947,7 +2010,6 @@ export function compile(source, options = {}) {
|
|
|
1947
2010
|
fail(decl, `unknown object clause ${key}`, "use fields, columns, or attach");
|
|
1948
2011
|
}
|
|
1949
2012
|
}
|
|
1950
|
-
// 1. Lower authored fields
|
|
1951
2013
|
const authoredFields = [];
|
|
1952
2014
|
const authoredNames = [];
|
|
1953
2015
|
const fieldsBlock = asBlock(body.get("fields"));
|
|
@@ -1959,7 +2021,6 @@ export function compile(source, options = {}) {
|
|
|
1959
2021
|
authoredFields.push(fieldDef);
|
|
1960
2022
|
authoredNames.push(row.key);
|
|
1961
2023
|
}
|
|
1962
|
-
// 2. Process attachments
|
|
1963
2024
|
const attachments = [];
|
|
1964
2025
|
for (const entry of decl.body.entries) {
|
|
1965
2026
|
if (!entry.key.startsWith("attach "))
|
|
@@ -1983,6 +2044,8 @@ export function compile(source, options = {}) {
|
|
|
1983
2044
|
for (const row of attachmentBlock.entries) {
|
|
1984
2045
|
if (row.key === "rename") {
|
|
1985
2046
|
for (const r of asBlock(row.value).entries) {
|
|
2047
|
+
if (renames.has(r.key))
|
|
2048
|
+
fail(r, `duplicate rename ${r.key}`, "rename each subject field once");
|
|
1986
2049
|
renames.set(r.key, text(r.value));
|
|
1987
2050
|
renameEntries.set(r.key, r);
|
|
1988
2051
|
}
|
|
@@ -1991,7 +2054,15 @@ export function compile(source, options = {}) {
|
|
|
1991
2054
|
if (row.value.kind === "call") {
|
|
1992
2055
|
const actionName = row.value.name;
|
|
1993
2056
|
const publicName = text(row.value.args[0]);
|
|
2057
|
+
if (exposed.has(actionName) ||
|
|
2058
|
+
!template.body.entries.some((entry) => entry.key === `action ${actionName}`))
|
|
2059
|
+
fail(row, `unknown or repeated action ${actionName}`, "expose one declared action once");
|
|
1994
2060
|
exposed.set(actionName, publicName);
|
|
2061
|
+
exposures.push({
|
|
2062
|
+
instrument: instId,
|
|
2063
|
+
action: actionName,
|
|
2064
|
+
entry: row,
|
|
2065
|
+
});
|
|
1995
2066
|
}
|
|
1996
2067
|
}
|
|
1997
2068
|
else {
|
|
@@ -2084,7 +2155,6 @@ export function compile(source, options = {}) {
|
|
|
2084
2155
|
}
|
|
2085
2156
|
}
|
|
2086
2157
|
}
|
|
2087
|
-
// 4. Validate columns
|
|
2088
2158
|
const columnsExpr = body.get("columns");
|
|
2089
2159
|
let columns = [];
|
|
2090
2160
|
if (columnsExpr) {
|
|
@@ -2130,7 +2200,8 @@ export function compile(source, options = {}) {
|
|
|
2130
2200
|
// Propagate invoked requirements with the conditions on each invocation path.
|
|
2131
2201
|
let changedInvocations = true;
|
|
2132
2202
|
let invocationIterations = 0;
|
|
2133
|
-
|
|
2203
|
+
const invocationDepth = document.instruments.reduce((total, inst) => total + inst.actionOrder.length, 0);
|
|
2204
|
+
while (changedInvocations && invocationIterations < invocationDepth) {
|
|
2134
2205
|
changedInvocations = false;
|
|
2135
2206
|
invocationIterations++;
|
|
2136
2207
|
for (const inst of document.instruments) {
|
|
@@ -2290,6 +2361,12 @@ export function compile(source, options = {}) {
|
|
|
2290
2361
|
const eliminatedStates = new Map();
|
|
2291
2362
|
// Remove branches excluded by immutable tunables before checking reference states.
|
|
2292
2363
|
for (const inst of document.instruments) {
|
|
2364
|
+
if (!inst.lifecycle.states.includes(inst.lifecycle.initial) ||
|
|
2365
|
+
Object.values(inst.lifecycle.transitions).some((edge) => edge.from.some((state) => !inst.lifecycle.states.includes(state)) ||
|
|
2366
|
+
(edge.to !== "preserve" &&
|
|
2367
|
+
!inst.lifecycle.states.includes(edge.to))))
|
|
2368
|
+
continue;
|
|
2369
|
+
let specialized = false;
|
|
2293
2370
|
for (const [key, action] of Object.entries(inst.actions)) {
|
|
2294
2371
|
const constant = (value) => {
|
|
2295
2372
|
if ("literal" in value)
|
|
@@ -2311,8 +2388,11 @@ export function compile(source, options = {}) {
|
|
|
2311
2388
|
})) {
|
|
2312
2389
|
delete inst.actions[key];
|
|
2313
2390
|
delete inst.lifecycle.transitions[key];
|
|
2391
|
+
specialized = true;
|
|
2314
2392
|
}
|
|
2315
2393
|
}
|
|
2394
|
+
if (!specialized)
|
|
2395
|
+
continue;
|
|
2316
2396
|
const reachable = new Set([inst.lifecycle.initial]);
|
|
2317
2397
|
for (let n = 0; n < inst.lifecycle.states.length; n++)
|
|
2318
2398
|
for (const edge of Object.values(inst.lifecycle.transitions))
|
|
@@ -2342,6 +2422,10 @@ export function compile(source, options = {}) {
|
|
|
2342
2422
|
: field.target).some((id) => eliminatedStates.get(id)?.has(state)));
|
|
2343
2423
|
}
|
|
2344
2424
|
}
|
|
2425
|
+
for (const exposure of exposures)
|
|
2426
|
+
if (!document.instruments.find((inst) => inst.id === exposure.instrument)
|
|
2427
|
+
?.actions[exposure.action])
|
|
2428
|
+
fail(exposure.entry, `action ${exposure.action} is excluded by these bindings`, "expose an action available with these tunables");
|
|
2345
2429
|
const changed = new Set();
|
|
2346
2430
|
for (const decl of program.decls)
|
|
2347
2431
|
if (decl.kind === "hide" || decl.kind === "expose") {
|