@mark1russell7/coeval 0.4.0 → 0.4.2
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 +4 -3
- package/build/index.d.ts +597 -331
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1195 -41
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +231 -0
- package/src/errors.ts +6 -1
- package/src/evaluate.ts +96 -11
- package/src/formula.ts +10 -8
- package/src/generate.ts +367 -0
- package/src/index.ts +6 -0
- package/src/reference.ts +571 -0
- package/src/static.ts +39 -7
- package/src/stdlib.ts +6 -2
- package/src/world.ts +24 -0
package/build/index.js
CHANGED
|
@@ -282,7 +282,11 @@ var Failure = class extends Error {
|
|
|
282
282
|
* @returns `true` for a stack overflow.
|
|
283
283
|
*/
|
|
284
284
|
function isStackOverflow(error) {
|
|
285
|
-
|
|
285
|
+
try {
|
|
286
|
+
return error instanceof RangeError && /call stack/i.test(error.message);
|
|
287
|
+
} catch {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
286
290
|
}
|
|
287
291
|
/**
|
|
288
292
|
* This function changes an exception into an error value. A `Failure` gives its error value. A stack overflow
|
|
@@ -1281,6 +1285,52 @@ function narrow(parent, next) {
|
|
|
1281
1285
|
caps
|
|
1282
1286
|
};
|
|
1283
1287
|
}
|
|
1288
|
+
/** No capabilities. One list serves each masked part. */
|
|
1289
|
+
const NO_CAPS = Object.freeze([]);
|
|
1290
|
+
/** No capability rules. One list serves each part that no rule narrows. */
|
|
1291
|
+
const NO_RULES = Object.freeze([]);
|
|
1292
|
+
/**
|
|
1293
|
+
* This function gives the capabilities that two lists both permit. An effect is permitted by the result when each
|
|
1294
|
+
* list permits it. When the list `b` permits each capability of `a`, the result is the list `a` itself.
|
|
1295
|
+
*
|
|
1296
|
+
* @param a - The first list, for example the capabilities of a document.
|
|
1297
|
+
* @param b - The second list, for example the list of a rule `only`.
|
|
1298
|
+
* @returns The capabilities that both lists permit.
|
|
1299
|
+
*/
|
|
1300
|
+
function intersectCaps(a, b) {
|
|
1301
|
+
if (a.every((cap) => permits(b, cap))) return a;
|
|
1302
|
+
const out = [];
|
|
1303
|
+
for (const cap of b) if (permits(a, cap)) out.push(cap);
|
|
1304
|
+
for (const cap of a) if (permits(b, cap) && !permits(out, cap)) out.push(cap);
|
|
1305
|
+
return out;
|
|
1306
|
+
}
|
|
1307
|
+
/** This function gives the capability rules of a kind that cover a part and change its capabilities. */
|
|
1308
|
+
function rulesFor(rules, path) {
|
|
1309
|
+
if (rules === void 0 || rules.length === 0) return NO_RULES;
|
|
1310
|
+
const out = rules.filter((rule) => rule.rule !== "same" && (rule.in === void 0 || rule.in.some((pattern) => pathMatches(path, pattern))));
|
|
1311
|
+
return out.length === 0 ? NO_RULES : out;
|
|
1312
|
+
}
|
|
1313
|
+
/** This function applies the rules that cover a part to the capabilities of its document. */
|
|
1314
|
+
function capsUnder(rules, caps) {
|
|
1315
|
+
let out = caps;
|
|
1316
|
+
for (const rule of rules) {
|
|
1317
|
+
if (rule.rule === "mask") return NO_CAPS;
|
|
1318
|
+
if (rule.rule === "only") out = intersectCaps(out, rule.caps);
|
|
1319
|
+
}
|
|
1320
|
+
return out;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* This function gives the capabilities that a kind gives one of its parts, by the capability rules of its contract.
|
|
1324
|
+
* The kernel and the static kit both use it, thus a run and a check narrow the same way.
|
|
1325
|
+
*
|
|
1326
|
+
* @param rules - The capability rules of the kind (`Kind.caps`).
|
|
1327
|
+
* @param path - The path of the part in the value of the document.
|
|
1328
|
+
* @param caps - The capabilities of the document.
|
|
1329
|
+
* @returns The capabilities of the part: the same list or a smaller one.
|
|
1330
|
+
*/
|
|
1331
|
+
function partCaps(rules, path, caps) {
|
|
1332
|
+
return capsUnder(rulesFor(rules, path), caps);
|
|
1333
|
+
}
|
|
1284
1334
|
function selfLabel(self) {
|
|
1285
1335
|
try {
|
|
1286
1336
|
if (isHostThing(self)) return self.label;
|
|
@@ -1497,7 +1547,7 @@ function holderNode() {
|
|
|
1497
1547
|
};
|
|
1498
1548
|
}
|
|
1499
1549
|
/** This function drives steps that cannot wait. */
|
|
1500
|
-
function drive(steps) {
|
|
1550
|
+
function drive$1(steps) {
|
|
1501
1551
|
const step = steps.next();
|
|
1502
1552
|
if (step.done === true) return step.value;
|
|
1503
1553
|
step.value.catch(() => void 0);
|
|
@@ -1540,7 +1590,10 @@ function isCompiledRunner(runner) {
|
|
|
1540
1590
|
return typeof runner === "object" && runner !== null && typeof runner.compileRun === "function";
|
|
1541
1591
|
}
|
|
1542
1592
|
function failing(c, meta, place, code, reason) {
|
|
1543
|
-
|
|
1593
|
+
return failingWith(c, meta, fail(code, reason, { place }));
|
|
1594
|
+
}
|
|
1595
|
+
/** This function gives the code of a document that fails with one result at each evaluation. */
|
|
1596
|
+
function failingWith(c, meta, result) {
|
|
1544
1597
|
const plain = () => result;
|
|
1545
1598
|
return finish(c, meta, plain, function* () {
|
|
1546
1599
|
return result;
|
|
@@ -1676,7 +1729,9 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1676
1729
|
}
|
|
1677
1730
|
const parts = split.parts.map((slot) => {
|
|
1678
1731
|
const item = slot.item;
|
|
1679
|
-
const
|
|
1732
|
+
const own = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1, slot.address === true);
|
|
1733
|
+
const rules = rulesFor(kind?.caps, slot.path);
|
|
1734
|
+
const compiled = rules.length === 0 ? own : gated(rules, own);
|
|
1680
1735
|
const header = typeof item?.name === "string" && isEnvelope(item) ? item.name : void 0;
|
|
1681
1736
|
return header === void 0 ? {
|
|
1682
1737
|
key: slot.key,
|
|
@@ -1704,10 +1759,22 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1704
1759
|
...address ? { address: true } : {}
|
|
1705
1760
|
};
|
|
1706
1761
|
if (runner !== void 0) {
|
|
1707
|
-
|
|
1762
|
+
let inner;
|
|
1763
|
+
try {
|
|
1764
|
+
inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
return failingWith(c, meta, placed(thrown(error, `the runner ${name}`), place));
|
|
1767
|
+
}
|
|
1768
|
+
const run = placedRun(inner, place, name);
|
|
1708
1769
|
return finish(c, meta, runnerCode(run, place, kindText), run);
|
|
1709
1770
|
}
|
|
1710
|
-
|
|
1771
|
+
let inner;
|
|
1772
|
+
try {
|
|
1773
|
+
inner = isCompiledEvaluator(evaluator) ? evaluator.compile(input) : interpret(evaluator, input, split.fill);
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
|
|
1776
|
+
}
|
|
1777
|
+
const code = placedCode(inner, place, name);
|
|
1711
1778
|
return finish(c, meta, code, runOf(code));
|
|
1712
1779
|
}
|
|
1713
1780
|
/** This function gives the code of a document: the code of its evaluator, with the place of each error and of each exception. */
|
|
@@ -1720,6 +1787,25 @@ function placedCode(inner, place, name) {
|
|
|
1720
1787
|
}
|
|
1721
1788
|
};
|
|
1722
1789
|
}
|
|
1790
|
+
/**
|
|
1791
|
+
* This function gives the code of a part that capability rules cover. The code narrows the context of the part by
|
|
1792
|
+
* the rules before the part starts.
|
|
1793
|
+
*/
|
|
1794
|
+
function gated(rules, part) {
|
|
1795
|
+
const inner = part.code;
|
|
1796
|
+
const steps = part.run;
|
|
1797
|
+
const under = (ctx) => {
|
|
1798
|
+
const caps = capsUnder(rules, ctx.caps);
|
|
1799
|
+
return caps === ctx.caps || caps.length === 0 && ctx.caps.length === 0 ? ctx : {
|
|
1800
|
+
...ctx,
|
|
1801
|
+
caps
|
|
1802
|
+
};
|
|
1803
|
+
};
|
|
1804
|
+
return {
|
|
1805
|
+
code: (ctx, rt) => inner(under(ctx), rt),
|
|
1806
|
+
run: (ctx, rt) => steps(under(ctx), rt)
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1723
1809
|
/** This function gives the steps of a runner, with the place of each error and of each exception. */
|
|
1724
1810
|
function placedRun(inner, place, name) {
|
|
1725
1811
|
return function* (ctx, rt) {
|
|
@@ -1730,9 +1816,12 @@ function placedRun(inner, place, name) {
|
|
|
1730
1816
|
}
|
|
1731
1817
|
};
|
|
1732
1818
|
}
|
|
1733
|
-
/**
|
|
1819
|
+
/**
|
|
1820
|
+
* This function gives the code of a runner in an evaluation: its steps, which cannot wait. A step that waits gives
|
|
1821
|
+
* `#CALC!` at the place of the runner, where the error started (RUN.WAIT).
|
|
1822
|
+
*/
|
|
1734
1823
|
function runnerCode(run, place, kindText) {
|
|
1735
|
-
return (ctx, rt) => rt.running ? fail("#CALC!", `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`, { place }) : drive(run(ctx, rt));
|
|
1824
|
+
return (ctx, rt) => rt.running ? fail("#CALC!", `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`, { place }) : placed(drive$1(run(ctx, rt)), place);
|
|
1736
1825
|
}
|
|
1737
1826
|
/** This function gives the steps of a run of an evaluator. The code runs at the first step, and it does not wait. */
|
|
1738
1827
|
function runOf(code) {
|
|
@@ -1824,7 +1913,7 @@ function prepare(machine, doc, options = {}) {
|
|
|
1824
1913
|
try {
|
|
1825
1914
|
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
1826
1915
|
} catch (error) {
|
|
1827
|
-
const result = thrown(error, "the compilation");
|
|
1916
|
+
const result = placed(thrown(error, "the compilation"), []);
|
|
1828
1917
|
root = {
|
|
1829
1918
|
code: () => result,
|
|
1830
1919
|
run: function* () {
|
|
@@ -1885,7 +1974,7 @@ function planner(machine, options = {}) {
|
|
|
1885
1974
|
return plan;
|
|
1886
1975
|
};
|
|
1887
1976
|
}
|
|
1888
|
-
function record(s, result) {
|
|
1977
|
+
function record$1(s, result) {
|
|
1889
1978
|
const root = s.holder?.children[0];
|
|
1890
1979
|
if (root === void 0) throw new Error("The evaluation made no trace.");
|
|
1891
1980
|
return {
|
|
@@ -1914,7 +2003,7 @@ function evaluate(machine, doc, context = makeContext(), declaredType) {
|
|
|
1914
2003
|
...declaredType === void 0 ? {} : { declaredType }
|
|
1915
2004
|
});
|
|
1916
2005
|
const s = new Session(machine, false, true, void 0, void 0, void 0);
|
|
1917
|
-
return record(s, plan.root.code(rootContext(machine, context), s));
|
|
2006
|
+
return record$1(s, plan.root.code(rootContext(machine, context), s));
|
|
1918
2007
|
}
|
|
1919
2008
|
/**
|
|
1920
2009
|
* This function runs a document: an evaluation in which the host does each effect. A runner waits for its
|
|
@@ -1933,7 +2022,7 @@ async function run(machine, doc, context = makeContext(), options = {}) {
|
|
|
1933
2022
|
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType }
|
|
1934
2023
|
});
|
|
1935
2024
|
const s = new Session(machine, true, true, options.perform, options.signal, void 0);
|
|
1936
|
-
return record(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
|
|
2025
|
+
return record$1(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
|
|
1937
2026
|
}
|
|
1938
2027
|
/**
|
|
1939
2028
|
* This function finds a node of a trace by its path.
|
|
@@ -2146,6 +2235,7 @@ const STD_WORLD = {
|
|
|
2146
2235
|
view: "view.op"
|
|
2147
2236
|
},
|
|
2148
2237
|
parts: { shape: "list" },
|
|
2238
|
+
caps: [{ rule: "mask" }],
|
|
2149
2239
|
summary: "It evaluates its part without capabilities, thus an effect in it fails."
|
|
2150
2240
|
},
|
|
2151
2241
|
log: {
|
|
@@ -2158,6 +2248,7 @@ const STD_WORLD = {
|
|
|
2158
2248
|
of: "text"
|
|
2159
2249
|
},
|
|
2160
2250
|
side: "run",
|
|
2251
|
+
effects: ["log"],
|
|
2161
2252
|
summary: "It writes the result of its part to the log. This is an effect."
|
|
2162
2253
|
},
|
|
2163
2254
|
write: {
|
|
@@ -2170,6 +2261,7 @@ const STD_WORLD = {
|
|
|
2170
2261
|
fields: ["value"]
|
|
2171
2262
|
},
|
|
2172
2263
|
side: "run",
|
|
2264
|
+
effects: ["write"],
|
|
2173
2265
|
summary: "It writes the result of its part to the store, at its key. This is an effect."
|
|
2174
2266
|
}
|
|
2175
2267
|
},
|
|
@@ -2347,13 +2439,10 @@ const STD_RUNNERS = {
|
|
|
2347
2439
|
}
|
|
2348
2440
|
return ok(last);
|
|
2349
2441
|
},
|
|
2350
|
-
"control.pure": function* ({ parts
|
|
2442
|
+
"control.pure": function* ({ parts }) {
|
|
2351
2443
|
const only = parts[0];
|
|
2352
2444
|
if (only === void 0) return fail("#VALUE!", "pure needs one part.");
|
|
2353
|
-
return yield* only.evaluate(
|
|
2354
|
-
...context,
|
|
2355
|
-
caps: []
|
|
2356
|
-
});
|
|
2445
|
+
return yield* only.evaluate();
|
|
2357
2446
|
},
|
|
2358
2447
|
"control.if": function* ({ parts }) {
|
|
2359
2448
|
const test = parts.find((part) => part.key === "test");
|
|
@@ -2878,9 +2967,10 @@ var Visit = class {
|
|
|
2878
2967
|
kind;
|
|
2879
2968
|
typePath;
|
|
2880
2969
|
address;
|
|
2970
|
+
caps;
|
|
2881
2971
|
#place;
|
|
2882
2972
|
#at;
|
|
2883
|
-
constructor(place, at, doc, declared, depth, names, later, slot, parent, kind, typePath, address) {
|
|
2973
|
+
constructor(place, at, doc, declared, depth, names, later, slot, parent, kind, typePath, address, caps) {
|
|
2884
2974
|
this.doc = doc;
|
|
2885
2975
|
this.declared = declared;
|
|
2886
2976
|
this.depth = depth;
|
|
@@ -2891,6 +2981,7 @@ var Visit = class {
|
|
|
2891
2981
|
this.kind = kind;
|
|
2892
2982
|
this.typePath = typePath;
|
|
2893
2983
|
this.address = address;
|
|
2984
|
+
this.caps = caps;
|
|
2894
2985
|
this.#place = place;
|
|
2895
2986
|
this.#at = at;
|
|
2896
2987
|
}
|
|
@@ -2903,14 +2994,15 @@ var Visit = class {
|
|
|
2903
2994
|
};
|
|
2904
2995
|
/**
|
|
2905
2996
|
* This function visits each document of a document: the root and each part at each depth, by the contracts of the
|
|
2906
|
-
* kinds. A part of a kind that the world does not have is not visited.
|
|
2997
|
+
* kinds. A part of a kind that the world does not have is not visited. With capabilities in the options, the walk
|
|
2998
|
+
* narrows them for each part by the rules of its kind, as a run does. The walk uses no stack of the host for the
|
|
2907
2999
|
* depth, thus a deep document cannot overflow it. The time and the memory of a walk are linear in the size of the
|
|
2908
3000
|
* document. A visit makes the keys of its place and of its path only when a reader asks for them.
|
|
2909
3001
|
*
|
|
2910
3002
|
* @param world - The world of the kinds.
|
|
2911
3003
|
* @param doc - The document, as JSON.
|
|
2912
3004
|
* @param visit - The function that gets each document. It gives `"skip"` to not visit the parts of a document.
|
|
2913
|
-
* @param options - The type of a bare root value, the names
|
|
3005
|
+
* @param options - The type of a bare root value, the names, the capabilities and a function for the problems.
|
|
2914
3006
|
*/
|
|
2915
3007
|
function walk(world, doc, visit, options = {}) {
|
|
2916
3008
|
const stack = [{
|
|
@@ -2923,7 +3015,8 @@ function walk(world, doc, visit, options = {}) {
|
|
|
2923
3015
|
later: NO_NAMES,
|
|
2924
3016
|
address: false,
|
|
2925
3017
|
slot: [],
|
|
2926
|
-
parent: void 0
|
|
3018
|
+
parent: void 0,
|
|
3019
|
+
caps: options.caps
|
|
2927
3020
|
}];
|
|
2928
3021
|
while (stack.length > 0) {
|
|
2929
3022
|
const item = stack.pop();
|
|
@@ -2943,7 +3036,7 @@ function walk(world, doc, visit, options = {}) {
|
|
|
2943
3036
|
};
|
|
2944
3037
|
const found = lookupKind(world, doc.type);
|
|
2945
3038
|
const kind = found.ok ? found.kind : void 0;
|
|
2946
|
-
const here = new Visit(item.place, item.at, doc, envelope === void 0, item.depth, item.names, item.later, item.slot, item.parent, kind, found.ok ? found.path : void 0, item.address ? true : void 0);
|
|
3039
|
+
const here = new Visit(item.place, item.at, doc, envelope === void 0, item.depth, item.names, item.later, item.slot, item.parent, kind, found.ok ? found.path : void 0, item.address ? true : void 0, item.caps);
|
|
2947
3040
|
if (visit(here) === "skip" || kind === void 0) continue;
|
|
2948
3041
|
const split = splitParts(kind.parts, doc.value);
|
|
2949
3042
|
const base = envelope === void 0 ? item.at : {
|
|
@@ -2985,7 +3078,8 @@ function walk(world, doc, visit, options = {}) {
|
|
|
2985
3078
|
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden]),
|
|
2986
3079
|
address: slot.address === true,
|
|
2987
3080
|
slot: slot.path,
|
|
2988
|
-
parent: here
|
|
3081
|
+
parent: here,
|
|
3082
|
+
caps: item.caps === void 0 || kind.caps === void 0 ? item.caps : partCaps(kind.caps, slot.path, item.caps)
|
|
2989
3083
|
});
|
|
2990
3084
|
}
|
|
2991
3085
|
}
|
|
@@ -3073,6 +3167,15 @@ function mapRefs(world, doc, change, options = {}) {
|
|
|
3073
3167
|
return out;
|
|
3074
3168
|
}
|
|
3075
3169
|
/**
|
|
3170
|
+
* This function gives the reason of a `#CAP!`. When a kind around the document narrowed the capabilities, the
|
|
3171
|
+
* reason names the nearest such kind.
|
|
3172
|
+
*/
|
|
3173
|
+
function capReason(v, kind) {
|
|
3174
|
+
const text = kind.join(".");
|
|
3175
|
+
for (let up = v.parent; up !== void 0; up = up.parent) if (up.caps !== void 0 && permits(up.caps, kind)) return `The capabilities do not permit the effect ${text} here. The kind ${typeName(up.doc.type)} around it narrows them for its parts.`;
|
|
3176
|
+
return `The capabilities do not permit the effect ${text}.`;
|
|
3177
|
+
}
|
|
3178
|
+
/**
|
|
3076
3179
|
* This function checks a document before an evaluation, by the contracts of its kinds. It finds the problems
|
|
3077
3180
|
* that an evaluation finds when it reaches them:
|
|
3078
3181
|
*
|
|
@@ -3083,7 +3186,7 @@ function mapRefs(world, doc, change, options = {}) {
|
|
|
3083
3186
|
* - `#DEPTH!`: too much nesting.
|
|
3084
3187
|
* - `#REF!` and `#STEP?`: a bad path.
|
|
3085
3188
|
* - `#NAME?`: a name that nothing binds.
|
|
3086
|
-
* - `#CAP!`: an effect that the capabilities do not permit.
|
|
3189
|
+
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
3087
3190
|
*
|
|
3088
3191
|
* @param machine - The world and the code of the evaluators.
|
|
3089
3192
|
* @param doc - The document, as JSON.
|
|
@@ -3124,12 +3227,13 @@ function check(machine, doc, options = {}) {
|
|
|
3124
3227
|
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`,
|
|
3125
3228
|
at: v.at
|
|
3126
3229
|
});
|
|
3127
|
-
|
|
3230
|
+
const caps = v.caps;
|
|
3231
|
+
if (caps !== void 0 && v.kind?.effects !== void 0) {
|
|
3128
3232
|
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
3129
|
-
for (const kind of kinds) if (!permits(
|
|
3233
|
+
for (const kind of kinds) if (!permits(caps, kind)) issues.push({
|
|
3130
3234
|
code: "#CAP!",
|
|
3131
3235
|
place: v.place,
|
|
3132
|
-
reason:
|
|
3236
|
+
reason: capReason(v, kind),
|
|
3133
3237
|
at: v.at
|
|
3134
3238
|
});
|
|
3135
3239
|
}
|
|
@@ -3187,6 +3291,7 @@ function check(machine, doc, options = {}) {
|
|
|
3187
3291
|
}, {
|
|
3188
3292
|
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
|
|
3189
3293
|
...Array.isArray(options.names) ? { names: options.names } : {},
|
|
3294
|
+
...options.caps === void 0 ? {} : { caps: options.caps },
|
|
3190
3295
|
problem: (problem) => issues.push({
|
|
3191
3296
|
code: problem.code,
|
|
3192
3297
|
place: problem.place,
|
|
@@ -3207,7 +3312,7 @@ var Held = class {
|
|
|
3207
3312
|
this.error = error;
|
|
3208
3313
|
}
|
|
3209
3314
|
};
|
|
3210
|
-
const CLOSURE = Symbol.for("coeval.closure");
|
|
3315
|
+
const CLOSURE$2 = Symbol.for("coeval.closure");
|
|
3211
3316
|
/**
|
|
3212
3317
|
* This function tells if a value is a function that the construct `fn` made.
|
|
3213
3318
|
*
|
|
@@ -3215,7 +3320,7 @@ const CLOSURE = Symbol.for("coeval.closure");
|
|
|
3215
3320
|
* @returns `true` for a closure.
|
|
3216
3321
|
*/
|
|
3217
3322
|
function isClosure(value) {
|
|
3218
|
-
return typeof value === "function" && value[CLOSURE] !== void 0;
|
|
3323
|
+
return typeof value === "function" && value[CLOSURE$2] !== void 0;
|
|
3219
3324
|
}
|
|
3220
3325
|
/** The standard extensions: the values that JSON cannot write. */
|
|
3221
3326
|
const STD_EXT = {
|
|
@@ -3448,18 +3553,19 @@ function formulaEvaluators(profile = {}) {
|
|
|
3448
3553
|
if (missing !== void 0 && missing(reached)) return fail("#N/A", "The path reaches no value.");
|
|
3449
3554
|
return ok(reached);
|
|
3450
3555
|
};
|
|
3451
|
-
const
|
|
3556
|
+
const held = (ctx) => {
|
|
3557
|
+
if (head === void 0 || !Object.hasOwn(ctx.names, head)) return void 0;
|
|
3558
|
+
const bound = ctx.names[head];
|
|
3559
|
+
return bound instanceof Held ? {
|
|
3560
|
+
ok: false,
|
|
3561
|
+
error: bound.error
|
|
3562
|
+
} : void 0;
|
|
3563
|
+
};
|
|
3564
|
+
if (parts.length === 0) return (ctx, rt) => held(ctx) ?? give(rt.resolve(ctx, path));
|
|
3452
3565
|
return (ctx, rt) => {
|
|
3453
|
-
if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
|
|
3454
|
-
const bound = ctx.names[head];
|
|
3455
|
-
if (bound instanceof Held) return {
|
|
3456
|
-
ok: false,
|
|
3457
|
-
error: bound.error
|
|
3458
|
-
};
|
|
3459
|
-
}
|
|
3460
|
-
if (!computed) return give(rt.resolve(ctx, path));
|
|
3461
3566
|
const steps = valuesOf(parts, ctx, rt);
|
|
3462
|
-
|
|
3567
|
+
if (!Array.isArray(steps)) return steps;
|
|
3568
|
+
return held(ctx) ?? give(rt.resolve(ctx, fill(steps)));
|
|
3463
3569
|
};
|
|
3464
3570
|
}),
|
|
3465
3571
|
"formula.step": compiled(({ doc, parts, fill }) => {
|
|
@@ -3543,7 +3649,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3543
3649
|
if (!result.ok) throw new Failure(result.error);
|
|
3544
3650
|
return result.value;
|
|
3545
3651
|
};
|
|
3546
|
-
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
3652
|
+
Object.defineProperty(closure, CLOSURE$2, { value: { params: names } });
|
|
3547
3653
|
return ok(closure);
|
|
3548
3654
|
};
|
|
3549
3655
|
}),
|
|
@@ -3712,6 +3818,1054 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
3712
3818
|
};
|
|
3713
3819
|
}
|
|
3714
3820
|
//#endregion
|
|
3715
|
-
|
|
3821
|
+
//#region src/reference.ts
|
|
3822
|
+
function own(table, name) {
|
|
3823
|
+
return table !== void 0 && Object.hasOwn(table, name) ? table[name] : void 0;
|
|
3824
|
+
}
|
|
3825
|
+
/** This function finds the code of an evaluator name. A runner wins over an evaluator of the same name. */
|
|
3826
|
+
function choose(env, name) {
|
|
3827
|
+
const runner = own(env.machine.runners, name);
|
|
3828
|
+
if (runner !== void 0) {
|
|
3829
|
+
const defined = own(env.definitions.runners, name);
|
|
3830
|
+
if (defined !== void 0) return { runner: defined };
|
|
3831
|
+
return typeof runner === "function" ? { runner } : { gap: `The reference has no definition of the compiled runner ${name}.` };
|
|
3832
|
+
}
|
|
3833
|
+
const evaluator = own(env.machine.evaluators, name);
|
|
3834
|
+
if (evaluator === void 0) return void 0;
|
|
3835
|
+
const defined = own(env.definitions.evaluators, name);
|
|
3836
|
+
if (defined !== void 0) return { evaluator: defined };
|
|
3837
|
+
return typeof evaluator === "function" ? { evaluator } : { gap: `The reference has no definition of the compiled evaluator ${name}.` };
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* This function reads one document by the rules, in their order. First come an envelope or a declared type
|
|
3841
|
+
* (EVAL.ERRORS) and the limit of the depth (PLAN.DEPTH). Then come the ladder (LADDER.*), an evaluator and the shape
|
|
3842
|
+
* of the parts (SHAPE.*).
|
|
3843
|
+
*/
|
|
3844
|
+
function examine(env, at) {
|
|
3845
|
+
const { place, declared } = at;
|
|
3846
|
+
const envelope = isEnvelope(at.value) ? at.value : void 0;
|
|
3847
|
+
if (envelope === void 0 && declared === void 0) return fail("#PART?", "The value has no envelope, and its position declares no type.", { place });
|
|
3848
|
+
if (at.depth > env.maxDepth) return fail("#DEPTH!", `The nesting is deeper than ${env.maxDepth} documents.`, { place });
|
|
3849
|
+
const doc = envelope ?? {
|
|
3850
|
+
type: declared,
|
|
3851
|
+
value: at.value
|
|
3852
|
+
};
|
|
3853
|
+
const kindText = typeName(doc.type);
|
|
3854
|
+
const world = env.machine.world;
|
|
3855
|
+
const answer = ask(world, envelope ?? {}, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
3856
|
+
if (answer.error === "unknown-kind") return fail("#KIND?", `The world has no kind ${kindText}.`, { place });
|
|
3857
|
+
const name = answer.evaluator;
|
|
3858
|
+
const chosen = name === void 0 ? void 0 : choose(env, name);
|
|
3859
|
+
if (name === void 0 || chosen === void 0) return fail("#NOEVAL", `No rung gives an evaluator for ${kindText}.`, { place });
|
|
3860
|
+
const found = lookupKind(world, doc.type);
|
|
3861
|
+
const kind = found.ok ? found.kind : void 0;
|
|
3862
|
+
const split = splitParts(kind?.parts, doc.value);
|
|
3863
|
+
if (!split.ok) {
|
|
3864
|
+
const where = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
3865
|
+
return fail("#VALUE!", `${split.reason}${where} The kind is ${kindText}.`, { place });
|
|
3866
|
+
}
|
|
3867
|
+
return {
|
|
3868
|
+
doc,
|
|
3869
|
+
kindText,
|
|
3870
|
+
typePath: found.ok ? found.path : [],
|
|
3871
|
+
kind,
|
|
3872
|
+
name,
|
|
3873
|
+
chosen,
|
|
3874
|
+
split
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
/** This function gives the capabilities that the rules of a kind give one part (D14, CAP.CONTRACT, CAP.ONLY). */
|
|
3878
|
+
function capsOfPart(rules, path, caps) {
|
|
3879
|
+
let out = caps;
|
|
3880
|
+
for (const rule of rules ?? []) {
|
|
3881
|
+
if (rule.in !== void 0 && !rule.in.some((pattern) => pathMatches(path, pattern))) continue;
|
|
3882
|
+
if (rule.rule === "mask") return [];
|
|
3883
|
+
if (rule.rule === "only") out = intersectCaps(out, rule.caps);
|
|
3884
|
+
}
|
|
3885
|
+
return out;
|
|
3886
|
+
}
|
|
3887
|
+
/**
|
|
3888
|
+
* This function gives the context of a part. A context that the evaluator gives keeps only the capabilities of the
|
|
3889
|
+
* parent (CAP.NARROW). Then the rules of the kind narrow it (CAP.CONTRACT).
|
|
3890
|
+
*/
|
|
3891
|
+
function contextOfPart(seen, slot, parent, given) {
|
|
3892
|
+
const base = given === void 0 || given === parent ? parent : {
|
|
3893
|
+
...given,
|
|
3894
|
+
caps: given.caps.filter((cap) => permits(parent.caps, cap))
|
|
3895
|
+
};
|
|
3896
|
+
const caps = capsOfPart(seen.kind?.caps, slot.path, base.caps);
|
|
3897
|
+
return caps === base.caps ? base : {
|
|
3898
|
+
...base,
|
|
3899
|
+
caps
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
function headerOf(item) {
|
|
3903
|
+
return isEnvelope(item) && typeof item.name === "string" ? item.name : void 0;
|
|
3904
|
+
}
|
|
3905
|
+
function partAt(seen, at, slot, given) {
|
|
3906
|
+
return {
|
|
3907
|
+
value: slot.item,
|
|
3908
|
+
place: at.place.concat(slot.path),
|
|
3909
|
+
declared: slot.of,
|
|
3910
|
+
depth: at.depth + 1,
|
|
3911
|
+
address: slot.address === true,
|
|
3912
|
+
ctx: contextOfPart(seen, slot, at.ctx, given)
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
/** This function resolves a path with the full algorithm of `resolve`, not with its short path (DEREF.QUICK). */
|
|
3916
|
+
function resolveFully(machine, ctx, path, options) {
|
|
3917
|
+
const found = resolve(machine, ctx, path, { hops: true });
|
|
3918
|
+
return options?.hops === true ? found : {
|
|
3919
|
+
...found,
|
|
3920
|
+
hops: []
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
/** This function records an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). */
|
|
3924
|
+
function record(env, ctx, kind, payload, place) {
|
|
3925
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
3926
|
+
const text = keys.join(".");
|
|
3927
|
+
const machine = env.machine;
|
|
3928
|
+
if (!(machine.permits === void 0 ? permits(ctx.caps, keys) : machine.permits(ctx.caps, keys, ctx))) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
3929
|
+
env.effects.push({
|
|
3930
|
+
index: env.effects.length,
|
|
3931
|
+
kind: text,
|
|
3932
|
+
path: keys,
|
|
3933
|
+
payload,
|
|
3934
|
+
node: -1,
|
|
3935
|
+
at: 0,
|
|
3936
|
+
place,
|
|
3937
|
+
context: ctx
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
/** This function evaluates a document. Each error gets the place of the document where it started (K5). */
|
|
3941
|
+
function evaluateAt(env, at) {
|
|
3942
|
+
const seen = examine(env, at);
|
|
3943
|
+
if (!("doc" in seen)) return seen;
|
|
3944
|
+
return evaluateSeen(env, seen, at);
|
|
3945
|
+
}
|
|
3946
|
+
function evaluateSeen(env, seen, at) {
|
|
3947
|
+
const { chosen } = seen;
|
|
3948
|
+
const { place } = at;
|
|
3949
|
+
if ("gap" in chosen) return fail("#NOEVAL", chosen.gap, { place });
|
|
3950
|
+
if ("runner" in chosen) return drive(runSeen(env, seen, at, chosen.runner), place);
|
|
3951
|
+
const input = evalInput(env, seen, at);
|
|
3952
|
+
try {
|
|
3953
|
+
return placed(chosen.evaluator(input), place);
|
|
3954
|
+
} catch (error) {
|
|
3955
|
+
return placed(thrown(error, `the evaluator ${seen.name}`), place);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
/** This function gives the steps of a document for a runner. Nothing happens before the first step. */
|
|
3959
|
+
function* stepsAt(env, at) {
|
|
3960
|
+
const seen = examine(env, at);
|
|
3961
|
+
if (!("doc" in seen)) return seen;
|
|
3962
|
+
if ("runner" in seen.chosen) return yield* runSeen(env, seen, at, seen.chosen.runner);
|
|
3963
|
+
return evaluateSeen(env, seen, at);
|
|
3964
|
+
}
|
|
3965
|
+
function* runSeen(env, seen, at, runner) {
|
|
3966
|
+
try {
|
|
3967
|
+
return placed(yield* runner(runInput(env, seen, at)), at.place);
|
|
3968
|
+
} catch (error) {
|
|
3969
|
+
return placed(thrown(error, `the runner ${seen.name}`), at.place);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
/** This function drives steps that cannot wait. A step that waits gives `#CALC!` at the document (RUN.WAIT). */
|
|
3973
|
+
function drive(steps, place) {
|
|
3974
|
+
const step = steps.next();
|
|
3975
|
+
if (step.done === true) return step.value;
|
|
3976
|
+
step.value.catch(() => void 0);
|
|
3977
|
+
const waited = fail("#CALC!", "A part waited, and its parent cannot wait.", { place });
|
|
3978
|
+
steps.return(waited);
|
|
3979
|
+
return waited;
|
|
3980
|
+
}
|
|
3981
|
+
function evalInput(env, seen, at) {
|
|
3982
|
+
const { split, doc } = seen;
|
|
3983
|
+
const parts = split.parts.map((slot) => {
|
|
3984
|
+
const name = headerOf(slot.item);
|
|
3985
|
+
return {
|
|
3986
|
+
key: slot.key,
|
|
3987
|
+
path: slot.path,
|
|
3988
|
+
...name === void 0 ? {} : { name },
|
|
3989
|
+
evaluate: (given) => evaluateAt(env, partAt(seen, at, slot, given))
|
|
3990
|
+
};
|
|
3991
|
+
});
|
|
3992
|
+
const fill = (items) => split.parts.length === 0 ? doc.value : split.fill(items);
|
|
3993
|
+
return {
|
|
3994
|
+
kind: seen.kindText,
|
|
3995
|
+
typePath: seen.typePath,
|
|
3996
|
+
doc,
|
|
3997
|
+
value: fill(parts),
|
|
3998
|
+
data: split.data,
|
|
3999
|
+
parts,
|
|
4000
|
+
context: at.ctx,
|
|
4001
|
+
place: at.place,
|
|
4002
|
+
...at.address ? { address: true } : {},
|
|
4003
|
+
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
4004
|
+
effect: (kind, payload) => record(env, at.ctx, kind, payload, at.place) ?? ok(payload),
|
|
4005
|
+
fill
|
|
4006
|
+
};
|
|
4007
|
+
}
|
|
4008
|
+
function runInput(env, seen, at) {
|
|
4009
|
+
const { split, doc } = seen;
|
|
4010
|
+
const parts = split.parts.map((slot) => {
|
|
4011
|
+
const name = headerOf(slot.item);
|
|
4012
|
+
return {
|
|
4013
|
+
key: slot.key,
|
|
4014
|
+
path: slot.path,
|
|
4015
|
+
...name === void 0 ? {} : { name },
|
|
4016
|
+
evaluate: (given) => stepsAt(env, partAt(seen, at, slot, given))
|
|
4017
|
+
};
|
|
4018
|
+
});
|
|
4019
|
+
const fill = (items) => split.parts.length === 0 ? doc.value : split.fill(items);
|
|
4020
|
+
return {
|
|
4021
|
+
kind: seen.kindText,
|
|
4022
|
+
typePath: seen.typePath,
|
|
4023
|
+
doc,
|
|
4024
|
+
value: fill(parts),
|
|
4025
|
+
data: split.data,
|
|
4026
|
+
parts,
|
|
4027
|
+
context: at.ctx,
|
|
4028
|
+
place: at.place,
|
|
4029
|
+
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
4030
|
+
effect: function* (kind, payload) {
|
|
4031
|
+
return record(env, at.ctx, kind, payload, at.place) ?? ok(payload);
|
|
4032
|
+
},
|
|
4033
|
+
all: (list, options) => together(list, options),
|
|
4034
|
+
fill
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
/**
|
|
4038
|
+
* This function evaluates parts for `all` (RUN.ALL). The parts start in their order, up to the limit, and each part
|
|
4039
|
+
* goes on until it ends or waits. In an evaluation, a part that waits does not end, thus it keeps its place in the
|
|
4040
|
+
* limit. With `failFast`, the first failure stops the parts that wait, and the later parts do not start
|
|
4041
|
+
* (RUN.FAIL-FAST). When no part failed and a part waits, the steps wait, and the drive gives `#CALC!`.
|
|
4042
|
+
*/
|
|
4043
|
+
function* together(list, options = {}) {
|
|
4044
|
+
const limit = Math.max(1, options.concurrency ?? list.length);
|
|
4045
|
+
const results = list.map(() => void 0);
|
|
4046
|
+
const waiting = [];
|
|
4047
|
+
let failed = false;
|
|
4048
|
+
for (let index = 0; index < list.length && !failed && waiting.length < limit; index += 1) {
|
|
4049
|
+
let result;
|
|
4050
|
+
try {
|
|
4051
|
+
const steps = list[index].evaluate();
|
|
4052
|
+
const step = steps.next();
|
|
4053
|
+
if (step.done === true) result = step.value;
|
|
4054
|
+
else waiting.push({
|
|
4055
|
+
steps,
|
|
4056
|
+
promise: step.value
|
|
4057
|
+
});
|
|
4058
|
+
} catch (error) {
|
|
4059
|
+
result = thrown(error, "a part");
|
|
4060
|
+
}
|
|
4061
|
+
results[index] = result;
|
|
4062
|
+
if (result !== void 0 && options.failFast === true && !result.ok) failed = true;
|
|
4063
|
+
}
|
|
4064
|
+
if (failed) {
|
|
4065
|
+
const stopped = fail("#ABORT", "The part stopped: a part before it failed, and the parts run with failFast.");
|
|
4066
|
+
for (const item of waiting) try {
|
|
4067
|
+
item.steps.return(stopped);
|
|
4068
|
+
} catch {}
|
|
4069
|
+
return results.map((result) => result ?? stopped);
|
|
4070
|
+
}
|
|
4071
|
+
if (waiting.length > 0) yield Promise.race(waiting.map((item) => item.promise));
|
|
4072
|
+
return results;
|
|
4073
|
+
}
|
|
4074
|
+
/**
|
|
4075
|
+
* This function evaluates a document by the rules, without a compilation. It is the reference of the plans: a plan,
|
|
4076
|
+
* a traced plan and the second tier of a language must give its results. It covers evaluations, not runs: an effect
|
|
4077
|
+
* gives its payload, and a part that waits gives `#CALC!`.
|
|
4078
|
+
*
|
|
4079
|
+
* @param machine - The world and the code of the evaluators.
|
|
4080
|
+
* @param doc - The document.
|
|
4081
|
+
* @param context - The names and the capabilities at the root.
|
|
4082
|
+
* @param options - The type of a bare root value, the definitions of compiled code, and a list for the effects.
|
|
4083
|
+
* @returns The result.
|
|
4084
|
+
*/
|
|
4085
|
+
function reference(machine, doc, context = makeContext(), options = {}) {
|
|
4086
|
+
const env = {
|
|
4087
|
+
machine,
|
|
4088
|
+
definitions: options.definitions ?? {},
|
|
4089
|
+
effects: options.effects ?? [],
|
|
4090
|
+
maxDepth: machine.maxDepth ?? 1e3
|
|
4091
|
+
};
|
|
4092
|
+
const ctx = context.world === null ? {
|
|
4093
|
+
...context,
|
|
4094
|
+
world: worldThing(machine.world)
|
|
4095
|
+
} : context;
|
|
4096
|
+
try {
|
|
4097
|
+
return evaluateAt(env, {
|
|
4098
|
+
value: doc,
|
|
4099
|
+
place: [],
|
|
4100
|
+
declared: options.declaredType,
|
|
4101
|
+
depth: 0,
|
|
4102
|
+
address: false,
|
|
4103
|
+
ctx
|
|
4104
|
+
});
|
|
4105
|
+
} catch (error) {
|
|
4106
|
+
return placed(thrown(error, "the reference"), []);
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
const CLOSURE$1 = Symbol.for("coeval.closure");
|
|
4110
|
+
/** This function sets an own field, also the field `__proto__` (SHAPE.PROTO). */
|
|
4111
|
+
function setField(target, key, value) {
|
|
4112
|
+
Object.defineProperty(target, key, {
|
|
4113
|
+
value,
|
|
4114
|
+
enumerable: true,
|
|
4115
|
+
writable: true,
|
|
4116
|
+
configurable: true
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
function partNamed(parts, key) {
|
|
4120
|
+
return parts.find((part) => part.path.length === 1 && part.path[0] === key);
|
|
4121
|
+
}
|
|
4122
|
+
function partsUnder(parts, key) {
|
|
4123
|
+
return parts.filter((part) => part.path.length === 2 && part.path[0] === key);
|
|
4124
|
+
}
|
|
4125
|
+
/**
|
|
4126
|
+
* This function gives the definitions of the formula kit for one profile. Each construct is a function evaluator in
|
|
4127
|
+
* the most direct style. It shares no code with the compilers of `formulaEvaluators`, thus the two readings of the
|
|
4128
|
+
* rules can disagree, and a test finds it.
|
|
4129
|
+
*
|
|
4130
|
+
* @param profile - The operations and the rules of the language.
|
|
4131
|
+
* @returns The definitions, by evaluator name.
|
|
4132
|
+
*/
|
|
4133
|
+
function formulaDefinitions(profile = {}) {
|
|
4134
|
+
const truthy = profile.truthy ?? ((value) => typeof value === "boolean" ? value : void 0);
|
|
4135
|
+
const opOf = (name) => typeof profile.ops === "function" ? profile.ops(name) : own(profile.ops, name);
|
|
4136
|
+
const noTruth = (what) => fail("#VALUE!", `The ${what} must give a truth value.`);
|
|
4137
|
+
/** FORMULA.LIST, FORMULA.OPS and FORMULA.COMBINE: the values of parts in order, or the error. */
|
|
4138
|
+
const valuesOf = (parts, skip) => {
|
|
4139
|
+
const values = [];
|
|
4140
|
+
const errors = [];
|
|
4141
|
+
for (const part of parts) {
|
|
4142
|
+
const result = part.evaluate();
|
|
4143
|
+
if (result.ok) values.push(result.value);
|
|
4144
|
+
else if (skip?.(result.error) === true) values.push(SKIP);
|
|
4145
|
+
else if (profile.combine === void 0) return result;
|
|
4146
|
+
else errors.push(result.error);
|
|
4147
|
+
}
|
|
4148
|
+
if (errors.length === 0 || profile.combine === void 0) return { values };
|
|
4149
|
+
return {
|
|
4150
|
+
ok: false,
|
|
4151
|
+
error: errors.length === 1 ? errors[0] : profile.combine(errors)
|
|
4152
|
+
};
|
|
4153
|
+
};
|
|
4154
|
+
const lit = ({ doc }) => ok(doc.value);
|
|
4155
|
+
const ref = ({ doc, parts, fill, address, context, resolve: walk }) => {
|
|
4156
|
+
const path = doc.value;
|
|
4157
|
+
if (!Array.isArray(path) || path.length === 0) return fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
|
|
4158
|
+
let steps = path;
|
|
4159
|
+
if (parts.length > 0) {
|
|
4160
|
+
const all = valuesOfStrict(parts);
|
|
4161
|
+
if (!("values" in all)) return all;
|
|
4162
|
+
steps = fill(all.values);
|
|
4163
|
+
}
|
|
4164
|
+
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
|
|
4165
|
+
const bound = head === void 0 ? void 0 : own(context.names, head);
|
|
4166
|
+
if (bound instanceof Held) return {
|
|
4167
|
+
ok: false,
|
|
4168
|
+
error: bound.error
|
|
4169
|
+
};
|
|
4170
|
+
const found = walk(steps);
|
|
4171
|
+
if (!found.ok) return fail(found.code, found.reason);
|
|
4172
|
+
if (address === true) return ok(found.thing);
|
|
4173
|
+
const reached = valueOfThing(found.thing);
|
|
4174
|
+
if (reached instanceof Held) return {
|
|
4175
|
+
ok: false,
|
|
4176
|
+
error: reached.error
|
|
4177
|
+
};
|
|
4178
|
+
if (profile.missing?.(reached) === true) return fail("#N/A", "The path reaches no value.");
|
|
4179
|
+
return ok(reached);
|
|
4180
|
+
};
|
|
4181
|
+
const step = ({ doc, parts, fill }) => {
|
|
4182
|
+
if (parts.length === 0) return ok(doc);
|
|
4183
|
+
const all = valuesOfStrict(parts);
|
|
4184
|
+
return "values" in all ? ok({
|
|
4185
|
+
...doc,
|
|
4186
|
+
value: fill(all.values)
|
|
4187
|
+
}) : all;
|
|
4188
|
+
};
|
|
4189
|
+
const record = ({ parts }) => {
|
|
4190
|
+
const all = valuesOf(parts, profile.omit);
|
|
4191
|
+
if (!("values" in all)) return all;
|
|
4192
|
+
const out = {};
|
|
4193
|
+
parts.forEach((part, index) => {
|
|
4194
|
+
const value = all.values[index];
|
|
4195
|
+
if (value !== SKIP) setField(out, String(part.path[0]), value);
|
|
4196
|
+
});
|
|
4197
|
+
return ok(out);
|
|
4198
|
+
};
|
|
4199
|
+
const list = ({ parts }) => {
|
|
4200
|
+
const all = valuesOf(parts);
|
|
4201
|
+
return "values" in all ? ok([...all.values]) : all;
|
|
4202
|
+
};
|
|
4203
|
+
const letIn = ({ parts, context }) => {
|
|
4204
|
+
const names = {};
|
|
4205
|
+
for (const key of Object.keys(context.names)) setField(names, key, context.names[key]);
|
|
4206
|
+
for (const binding of partsUnder(parts, "bind")) {
|
|
4207
|
+
const result = binding.evaluate();
|
|
4208
|
+
setField(names, String(binding.path[1]), result.ok ? result.value : new Held(result.error));
|
|
4209
|
+
}
|
|
4210
|
+
return partNamed(parts, "body").evaluate({
|
|
4211
|
+
...context,
|
|
4212
|
+
names
|
|
4213
|
+
});
|
|
4214
|
+
};
|
|
4215
|
+
const fn = ({ data, parts, context }) => {
|
|
4216
|
+
const params = isRecord(data) ? data["params"] : void 0;
|
|
4217
|
+
if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) return fail("#VALUE!", "The params of fn are a list of different texts.");
|
|
4218
|
+
const names = params;
|
|
4219
|
+
const body = partNamed(parts, "body");
|
|
4220
|
+
const closure = (...args) => {
|
|
4221
|
+
const scope = {};
|
|
4222
|
+
for (const key of Object.keys(context.names)) setField(scope, key, context.names[key]);
|
|
4223
|
+
names.forEach((name, index) => setField(scope, name, args[index]));
|
|
4224
|
+
const result = body.evaluate({
|
|
4225
|
+
...context,
|
|
4226
|
+
names: scope
|
|
4227
|
+
});
|
|
4228
|
+
if (!result.ok) throw new Failure(result.error);
|
|
4229
|
+
return result.value;
|
|
4230
|
+
};
|
|
4231
|
+
Object.defineProperty(closure, CLOSURE$1, { value: { params: names } });
|
|
4232
|
+
return ok(closure);
|
|
4233
|
+
};
|
|
4234
|
+
const call = ({ parts }) => {
|
|
4235
|
+
const target = partNamed(parts, "fn").evaluate();
|
|
4236
|
+
if (!target.ok) return target;
|
|
4237
|
+
if (typeof target.value !== "function") return fail("#VALUE!", "The fn of call must give a function.");
|
|
4238
|
+
const all = valuesOfStrict(partsUnder(parts, "args"));
|
|
4239
|
+
if (!("values" in all)) return all;
|
|
4240
|
+
try {
|
|
4241
|
+
return ok(target.value(...all.values));
|
|
4242
|
+
} catch (error) {
|
|
4243
|
+
return thrown(error, "the function of call");
|
|
4244
|
+
}
|
|
4245
|
+
};
|
|
4246
|
+
const choice = ({ parts }) => {
|
|
4247
|
+
const test = partNamed(parts, "test").evaluate();
|
|
4248
|
+
if (!test.ok) return test;
|
|
4249
|
+
const truth = truthy(test.value);
|
|
4250
|
+
if (truth === void 0) return noTruth("test of if");
|
|
4251
|
+
if (truth) return partNamed(parts, "then").evaluate();
|
|
4252
|
+
const other = partNamed(parts, "else");
|
|
4253
|
+
if (other !== void 0) return other.evaluate();
|
|
4254
|
+
return ok(Object.hasOwn(profile, "otherwise") ? profile.otherwise : null);
|
|
4255
|
+
};
|
|
4256
|
+
/** FORMULA.LOGIC: `and` stops at the first false term, and `or` at the first true term. */
|
|
4257
|
+
const logic = (stopAt, what) => ({ parts }) => {
|
|
4258
|
+
let last = ok(!stopAt);
|
|
4259
|
+
for (const part of parts) {
|
|
4260
|
+
last = part.evaluate();
|
|
4261
|
+
if (!last.ok) return last;
|
|
4262
|
+
const truth = truthy(last.value);
|
|
4263
|
+
if (truth === void 0) return noTruth(what);
|
|
4264
|
+
if (truth === stopAt) return profile.logic === "value" ? last : ok(stopAt);
|
|
4265
|
+
}
|
|
4266
|
+
return profile.logic === "value" ? last : ok(!stopAt);
|
|
4267
|
+
};
|
|
4268
|
+
const op = ({ typePath, parts }) => {
|
|
4269
|
+
const name = typePath.slice(1).join(".");
|
|
4270
|
+
const lazy = own(profile.lazy, name);
|
|
4271
|
+
if (lazy !== void 0) try {
|
|
4272
|
+
return lazy(parts.map((part) => () => part.evaluate()));
|
|
4273
|
+
} catch (error) {
|
|
4274
|
+
return thrown(error, `the operation ${name}`);
|
|
4275
|
+
}
|
|
4276
|
+
const apply = opOf(name);
|
|
4277
|
+
if (apply === void 0) return fail("#NAME?", `The language has no operation ${name}.`);
|
|
4278
|
+
const all = valuesOf(parts);
|
|
4279
|
+
if (!("values" in all)) return all;
|
|
4280
|
+
try {
|
|
4281
|
+
const out = apply(...all.values);
|
|
4282
|
+
return profile.check?.(out, name) ?? ok(out);
|
|
4283
|
+
} catch (error) {
|
|
4284
|
+
return thrown(error, `the operation ${name}`);
|
|
4285
|
+
}
|
|
4286
|
+
};
|
|
4287
|
+
const ext = ({ typePath, doc, context }) => {
|
|
4288
|
+
const name = typePath.slice(1).join(".");
|
|
4289
|
+
const handler = own(profile.ext, name) ?? own(STD_EXT, name);
|
|
4290
|
+
if (handler === void 0) return fail("#NAME?", `The language has no extension ${name}.`);
|
|
4291
|
+
try {
|
|
4292
|
+
return handler(doc.value, context);
|
|
4293
|
+
} catch (error) {
|
|
4294
|
+
return thrown(error, `the extension ${name}`);
|
|
4295
|
+
}
|
|
4296
|
+
};
|
|
4297
|
+
return { evaluators: {
|
|
4298
|
+
"formula.lit": lit,
|
|
4299
|
+
"formula.ref": ref,
|
|
4300
|
+
"formula.step": step,
|
|
4301
|
+
"formula.record": record,
|
|
4302
|
+
"formula.list": list,
|
|
4303
|
+
"formula.let": letIn,
|
|
4304
|
+
"formula.fn": fn,
|
|
4305
|
+
"formula.call": call,
|
|
4306
|
+
"formula.if": choice,
|
|
4307
|
+
"formula.and": logic(false, "term of and"),
|
|
4308
|
+
"formula.or": logic(true, "term of or"),
|
|
4309
|
+
"formula.op": op,
|
|
4310
|
+
"formula.ext": ext
|
|
4311
|
+
} };
|
|
4312
|
+
}
|
|
4313
|
+
/** The mark of a field that the profile leaves out of a record (FORMULA.RECORD). */
|
|
4314
|
+
const SKIP = Symbol("skip");
|
|
4315
|
+
/** The values of parts in order. The first error stops them, also with `combine` (FORMULA.STEPS, FORMULA.FN). */
|
|
4316
|
+
function valuesOfStrict(parts) {
|
|
4317
|
+
const values = [];
|
|
4318
|
+
for (const part of parts) {
|
|
4319
|
+
const result = part.evaluate();
|
|
4320
|
+
if (!result.ok) return result;
|
|
4321
|
+
values.push(result.value);
|
|
4322
|
+
}
|
|
4323
|
+
return { values };
|
|
4324
|
+
}
|
|
4325
|
+
//#endregion
|
|
4326
|
+
//#region src/agree.ts
|
|
4327
|
+
/**
|
|
4328
|
+
* This function tells if two values are the same for `agree`. Lists and plain records compare item by item and field
|
|
4329
|
+
* by field, and the order of the fields does not matter. Each other leaf compares with `Object.is`, thus `-0` is not
|
|
4330
|
+
* `0`, and `NaN` is `NaN`. Two closures of `fn` are the same when they have the same parameters. Two `Held` values
|
|
4331
|
+
* are the same when their errors have the same code and place. Identity is not compared.
|
|
4332
|
+
*
|
|
4333
|
+
* @param a - The first value.
|
|
4334
|
+
* @param b - The second value.
|
|
4335
|
+
* @param equal - The rule of the caller for other values. It answers first, and `undefined` gives the rules above.
|
|
4336
|
+
* @returns `true` when the values are the same.
|
|
4337
|
+
*/
|
|
4338
|
+
function sameValue(a, b, equal) {
|
|
4339
|
+
const stack = [[a, b]];
|
|
4340
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4341
|
+
while (stack.length > 0) {
|
|
4342
|
+
const [x, y] = stack.pop();
|
|
4343
|
+
if (Object.is(x, y)) continue;
|
|
4344
|
+
const told = equal?.(x, y);
|
|
4345
|
+
if (told !== void 0) {
|
|
4346
|
+
if (told) continue;
|
|
4347
|
+
return false;
|
|
4348
|
+
}
|
|
4349
|
+
if (typeof x !== "object" && typeof x !== "function") return false;
|
|
4350
|
+
if (typeof y !== "object" && typeof y !== "function") return false;
|
|
4351
|
+
if (x === null || y === null) return false;
|
|
4352
|
+
const pairs = seen.get(x) ?? /* @__PURE__ */ new Set();
|
|
4353
|
+
if (pairs.has(y)) continue;
|
|
4354
|
+
pairs.add(y);
|
|
4355
|
+
seen.set(x, pairs);
|
|
4356
|
+
if (isClosure(x) || isClosure(y)) {
|
|
4357
|
+
if (!(isClosure(x) && isClosure(y))) return false;
|
|
4358
|
+
stack.push([closureParams(x), closureParams(y)]);
|
|
4359
|
+
continue;
|
|
4360
|
+
}
|
|
4361
|
+
if (x instanceof Held || y instanceof Held) {
|
|
4362
|
+
if (!(x instanceof Held && y instanceof Held) || errorDiffers(x.error, y.error, false) !== void 0) return false;
|
|
4363
|
+
continue;
|
|
4364
|
+
}
|
|
4365
|
+
if (Array.isArray(x) || Array.isArray(y)) {
|
|
4366
|
+
if (!Array.isArray(x) || !Array.isArray(y) || x.length !== y.length) return false;
|
|
4367
|
+
for (let index = 0; index < x.length; index += 1) stack.push([x[index], y[index]]);
|
|
4368
|
+
continue;
|
|
4369
|
+
}
|
|
4370
|
+
if (!isRecord(x) || !isRecord(y)) return false;
|
|
4371
|
+
const keys = Object.keys(x);
|
|
4372
|
+
if (keys.length !== Object.keys(y).length) return false;
|
|
4373
|
+
for (const key of keys) {
|
|
4374
|
+
if (!Object.hasOwn(y, key)) return false;
|
|
4375
|
+
stack.push([x[key], y[key]]);
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
return true;
|
|
4379
|
+
}
|
|
4380
|
+
const CLOSURE = Symbol.for("coeval.closure");
|
|
4381
|
+
function closureParams(fn) {
|
|
4382
|
+
return fn[CLOSURE]?.params;
|
|
4383
|
+
}
|
|
4384
|
+
function codeAndPlace(error) {
|
|
4385
|
+
return error.place === void 0 ? error.code : `${error.code} at [${error.place.join(", ")}]`;
|
|
4386
|
+
}
|
|
4387
|
+
/** This function gives the difference of two errors, or `undefined` when they agree. */
|
|
4388
|
+
function errorDiffers(a, b, strict) {
|
|
4389
|
+
if (a.code !== b.code) return `The plan gives ${codeAndPlace(a)}, and the tier gives ${codeAndPlace(b)}.`;
|
|
4390
|
+
if (strict && (a.place === void 0 || b.place === void 0)) return `An error ${a.code} has no place.`;
|
|
4391
|
+
if (a.place !== void 0 && b.place !== void 0 && !docEquals(a.place, b.place)) return `The error ${a.code} has the place [${a.place.join(", ")}] in the plan, and [${b.place.join(", ")}] in the tier.`;
|
|
4392
|
+
if (strict && a.reason !== b.reason) return `The error ${a.code} has another reason in the tier: "${b.reason}" for "${a.reason}".`;
|
|
4393
|
+
}
|
|
4394
|
+
/** A host stack overflow is the one permitted difference (VALUE.DEPTH): the tiers use different numbers of frames. */
|
|
4395
|
+
function overflowed(result) {
|
|
4396
|
+
return !result.ok && result.error.code === "#DEPTH!" && isStackOverflow(result.error.cause);
|
|
4397
|
+
}
|
|
4398
|
+
/** This function gives the difference of two results, or `undefined` when they agree. */
|
|
4399
|
+
function resultDiffers(a, b, options) {
|
|
4400
|
+
if (a.ok && b.ok) return sameValue(a.value, b.value, options.equal) ? void 0 : "The plan and the tier give different values.";
|
|
4401
|
+
if (!a.ok && !b.ok) return errorDiffers(a.error, b.error, options.strict === true);
|
|
4402
|
+
return a.ok ? `The plan gives a value, and the tier gives ${codeAndPlace(b.error)}.` : `The plan gives ${codeAndPlace(a.error)}, and the tier gives a value.`;
|
|
4403
|
+
}
|
|
4404
|
+
/** This function gives the difference of two lists of effects, or `undefined` when they agree (EFFECT.ORDER). */
|
|
4405
|
+
function effectsDiffer(a, b, options) {
|
|
4406
|
+
if (a.length !== b.length) return `The plan causes ${a.length} effects, and the tier causes ${b.length}.`;
|
|
4407
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
4408
|
+
const x = a[index];
|
|
4409
|
+
const y = b[index];
|
|
4410
|
+
if (x.kind !== y.kind || !docEquals(x.place, y.place) || !sameValue(x.payload, y.payload, options.equal)) return `The effect ${index} differs: ${x.kind} at [${x.place.join(", ")}] in the plan, and ${y.kind} at [${y.place.join(", ")}] in the tier.`;
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
/**
|
|
4414
|
+
* This function evaluates a document with a plan and with other tiers, and compares the results (PLAN.AGREES). Two
|
|
4415
|
+
* results agree when they have the same value by `sameValue`, or errors with the same code, and the same place when
|
|
4416
|
+
* both have one. The effects must have the same kinds, places and payloads, in the same order. A language gives its
|
|
4417
|
+
* own tier with the option `other`.
|
|
4418
|
+
*
|
|
4419
|
+
* @param machine - The world and the code of the evaluators.
|
|
4420
|
+
* @param doc - The document.
|
|
4421
|
+
* @param context - The names and the capabilities at the root.
|
|
4422
|
+
* @param options - The tiers, the definitions for the reference, the rules of the comparison and the second tier.
|
|
4423
|
+
* @returns The result of the plan, or the first tier that disagrees and the reason.
|
|
4424
|
+
*/
|
|
4425
|
+
function agree(machine, doc, context = makeContext(), options = {}) {
|
|
4426
|
+
const declared = options.declaredType === void 0 ? {} : { declaredType: options.declaredType };
|
|
4427
|
+
const observe = options.observe;
|
|
4428
|
+
observe?.();
|
|
4429
|
+
const plan = prepare(machine, doc, declared);
|
|
4430
|
+
const effects = [];
|
|
4431
|
+
const result = plan.evaluate(context, { effects });
|
|
4432
|
+
const saw = observe?.();
|
|
4433
|
+
const differs = (other, otherEffects) => {
|
|
4434
|
+
if (overflowed(result) || overflowed(other)) return void 0;
|
|
4435
|
+
const why = resultDiffers(result, other, options) ?? (otherEffects === void 0 ? void 0 : effectsDiffer(effects, otherEffects, options));
|
|
4436
|
+
if (why !== void 0 || observe === void 0) return why;
|
|
4437
|
+
return sameValue(saw, observe(), options.equal) ? void 0 : "The host saw other calls in the tier.";
|
|
4438
|
+
};
|
|
4439
|
+
for (const tier of options.tiers ?? [
|
|
4440
|
+
"reference",
|
|
4441
|
+
"traced",
|
|
4442
|
+
"again"
|
|
4443
|
+
]) {
|
|
4444
|
+
let other;
|
|
4445
|
+
let otherEffects = [];
|
|
4446
|
+
if (tier === "reference") other = reference(machine, doc, context, {
|
|
4447
|
+
...declared,
|
|
4448
|
+
effects: otherEffects,
|
|
4449
|
+
...options.definitions === void 0 ? {} : { definitions: options.definitions }
|
|
4450
|
+
});
|
|
4451
|
+
else if (tier === "traced") {
|
|
4452
|
+
const traced = evaluate(machine, doc, context, options.declaredType);
|
|
4453
|
+
other = traced.result;
|
|
4454
|
+
otherEffects = [...traced.effects];
|
|
4455
|
+
} else other = plan.evaluate(context, { effects: otherEffects });
|
|
4456
|
+
const why = differs(other, otherEffects);
|
|
4457
|
+
if (why !== void 0) return {
|
|
4458
|
+
ok: false,
|
|
4459
|
+
tier,
|
|
4460
|
+
plan: result,
|
|
4461
|
+
other,
|
|
4462
|
+
reason: why
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
if (options.other !== void 0) {
|
|
4466
|
+
let given;
|
|
4467
|
+
try {
|
|
4468
|
+
given = options.other(doc, context);
|
|
4469
|
+
} catch (error) {
|
|
4470
|
+
given = thrown(error, "the tier");
|
|
4471
|
+
}
|
|
4472
|
+
const other = "result" in given ? given.result : given;
|
|
4473
|
+
const why = differs(other, "result" in given ? given.effects : void 0);
|
|
4474
|
+
if (why !== void 0) return {
|
|
4475
|
+
ok: false,
|
|
4476
|
+
tier: "other",
|
|
4477
|
+
plan: result,
|
|
4478
|
+
other,
|
|
4479
|
+
reason: why
|
|
4480
|
+
};
|
|
4481
|
+
}
|
|
4482
|
+
return {
|
|
4483
|
+
ok: true,
|
|
4484
|
+
result,
|
|
4485
|
+
effects
|
|
4486
|
+
};
|
|
4487
|
+
}
|
|
4488
|
+
//#endregion
|
|
4489
|
+
//#region src/generate.ts
|
|
4490
|
+
/**
|
|
4491
|
+
* This function makes a source of random numbers with a seed (the algorithm mulberry32). It needs no package.
|
|
4492
|
+
*
|
|
4493
|
+
* @param seed - The seed: a whole number.
|
|
4494
|
+
* @returns The source.
|
|
4495
|
+
*/
|
|
4496
|
+
function random(seed) {
|
|
4497
|
+
let state = seed >>> 0;
|
|
4498
|
+
const next = () => {
|
|
4499
|
+
state = state + 1831565813 >>> 0;
|
|
4500
|
+
let t = state;
|
|
4501
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
4502
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
4503
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
4504
|
+
};
|
|
4505
|
+
const int = (below) => Math.floor(next() * below);
|
|
4506
|
+
return {
|
|
4507
|
+
next,
|
|
4508
|
+
int,
|
|
4509
|
+
chance: (p) => next() < p,
|
|
4510
|
+
pick: (items) => items[int(items.length)]
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* This function lists the types of a world: each kind and each alias, also in a namespace. A namespace with a
|
|
4515
|
+
* fallback gives no types, because each key names a kind.
|
|
4516
|
+
*
|
|
4517
|
+
* @param world - The world.
|
|
4518
|
+
* @returns The keys of each type.
|
|
4519
|
+
*/
|
|
4520
|
+
function typesOf(world) {
|
|
4521
|
+
const out = [];
|
|
4522
|
+
const visit = (entries, prefix) => {
|
|
4523
|
+
for (const key of Object.keys(entries)) {
|
|
4524
|
+
const entry = entries[key];
|
|
4525
|
+
if (isKind(entry) || isAlias(entry)) out.push([...prefix, key]);
|
|
4526
|
+
else if (isNamespace(entry)) visit(entry.kinds, [...prefix, key]);
|
|
4527
|
+
}
|
|
4528
|
+
};
|
|
4529
|
+
visit(world.kinds, []);
|
|
4530
|
+
return out;
|
|
4531
|
+
}
|
|
4532
|
+
const NAMES = [
|
|
4533
|
+
"a",
|
|
4534
|
+
"b",
|
|
4535
|
+
"x",
|
|
4536
|
+
"n",
|
|
4537
|
+
"last",
|
|
4538
|
+
"__proto__"
|
|
4539
|
+
];
|
|
4540
|
+
const FIELDS = [
|
|
4541
|
+
"a",
|
|
4542
|
+
"b",
|
|
4543
|
+
"c",
|
|
4544
|
+
"1",
|
|
4545
|
+
"__proto__"
|
|
4546
|
+
];
|
|
4547
|
+
const KEYS = [
|
|
4548
|
+
"a",
|
|
4549
|
+
"b",
|
|
4550
|
+
"x",
|
|
4551
|
+
0,
|
|
4552
|
+
1,
|
|
4553
|
+
"0",
|
|
4554
|
+
"length",
|
|
4555
|
+
"type",
|
|
4556
|
+
"value",
|
|
4557
|
+
"parent"
|
|
4558
|
+
];
|
|
4559
|
+
/** This function tells if a kind can be a leaf: it needs no part. */
|
|
4560
|
+
function leafKind(kind) {
|
|
4561
|
+
const shape = kind?.parts;
|
|
4562
|
+
return shape === void 0 || shape.shape === "none" || shape.shape === "list" || shape.shape === "map" || shape.shape === "text" || shape.shape === "number" || shape.shape === "one";
|
|
4563
|
+
}
|
|
4564
|
+
/**
|
|
4565
|
+
* This function builds a random document for a world from the contracts of its kinds. It reads the shapes of the
|
|
4566
|
+
* parts, the declared types, the permitted kinds and the names that the scope rules bind. Some choices are malformed
|
|
4567
|
+
* on purpose. Examples are an unknown kind, a value without its shape, a bare value without a type and a name that
|
|
4568
|
+
* nothing binds. The depth and the size have limits.
|
|
4569
|
+
*
|
|
4570
|
+
* @param world - The world.
|
|
4571
|
+
* @param options - The seed, the types, the limits and the data of the leaves.
|
|
4572
|
+
* @returns The document, as JSON.
|
|
4573
|
+
*/
|
|
4574
|
+
function generate(world, options) {
|
|
4575
|
+
const rng = random(options.seed);
|
|
4576
|
+
const types = options.types ?? typesOf(world);
|
|
4577
|
+
const maxDepth = options.maxDepth ?? 4;
|
|
4578
|
+
const maxSize = options.maxSize ?? 24;
|
|
4579
|
+
const malformed = options.malformed ?? .05;
|
|
4580
|
+
const keys = options.keys ?? KEYS;
|
|
4581
|
+
const stepTypes = types.filter((type) => {
|
|
4582
|
+
const found = lookupKind(world, type);
|
|
4583
|
+
return found.ok && Object.hasOwn(found.kind.verbs, "step");
|
|
4584
|
+
});
|
|
4585
|
+
let size = 0;
|
|
4586
|
+
const kindOf = (type) => {
|
|
4587
|
+
const found = lookupKind(world, type);
|
|
4588
|
+
return found.ok ? found.kind : void 0;
|
|
4589
|
+
};
|
|
4590
|
+
const anyJson = (depth = 0) => {
|
|
4591
|
+
const roll = rng.int(depth > 1 ? 6 : 8);
|
|
4592
|
+
if (roll === 0) return null;
|
|
4593
|
+
if (roll === 1) return rng.chance(.5);
|
|
4594
|
+
if (roll === 2) return rng.pick([
|
|
4595
|
+
0,
|
|
4596
|
+
1,
|
|
4597
|
+
2,
|
|
4598
|
+
-1,
|
|
4599
|
+
.5,
|
|
4600
|
+
1e21
|
|
4601
|
+
]);
|
|
4602
|
+
if (roll === 3) return rng.pick([
|
|
4603
|
+
"",
|
|
4604
|
+
"a",
|
|
4605
|
+
"text",
|
|
4606
|
+
"0"
|
|
4607
|
+
]);
|
|
4608
|
+
if (roll === 4) return rng.int(10);
|
|
4609
|
+
if (roll === 5) return rng.pick(NAMES);
|
|
4610
|
+
if (roll === 6) return Array.from({ length: rng.int(3) }, () => anyJson(depth + 1));
|
|
4611
|
+
const out = {};
|
|
4612
|
+
for (let index = rng.int(3); index > 0; index -= 1) Object.defineProperty(out, rng.pick(FIELDS), {
|
|
4613
|
+
value: anyJson(depth + 1),
|
|
4614
|
+
enumerable: true,
|
|
4615
|
+
writable: true,
|
|
4616
|
+
configurable: true
|
|
4617
|
+
});
|
|
4618
|
+
return out;
|
|
4619
|
+
};
|
|
4620
|
+
const dataOf = (type) => options.data?.(type, rng) ?? anyJson();
|
|
4621
|
+
const renderType = (type) => {
|
|
4622
|
+
const roll = rng.next();
|
|
4623
|
+
if (roll < .06) return {
|
|
4624
|
+
type: "ref",
|
|
4625
|
+
value: ["world", ...type]
|
|
4626
|
+
};
|
|
4627
|
+
if (type.length === 1 && roll < .8) return type[0];
|
|
4628
|
+
return [...type];
|
|
4629
|
+
};
|
|
4630
|
+
const permitted = (type, kinds) => kinds === void 0 || kinds.some((kind) => kind.length <= type.length && kind.every((key, index) => String(key) === String(type[index])));
|
|
4631
|
+
const pickName = (names) => names.length === 0 || rng.chance(malformed) ? rng.pick(["unbound", "zz"]) : rng.pick(names);
|
|
4632
|
+
const someNames = () => {
|
|
4633
|
+
const out = /* @__PURE__ */ new Set();
|
|
4634
|
+
for (let index = 1 + rng.int(2); index > 0; index -= 1) out.add(rng.pick(NAMES));
|
|
4635
|
+
return [...out];
|
|
4636
|
+
};
|
|
4637
|
+
const envelope = (type, value, header) => {
|
|
4638
|
+
const out = {
|
|
4639
|
+
type: renderType(type),
|
|
4640
|
+
value
|
|
4641
|
+
};
|
|
4642
|
+
if (header !== void 0) out["name"] = header;
|
|
4643
|
+
if (options.own !== void 0 && options.own.length > 0 && rng.chance(.03)) out["own"] = { eval: rng.pick(options.own) };
|
|
4644
|
+
return out;
|
|
4645
|
+
};
|
|
4646
|
+
/** A path from a root (D10). It uses the names in scope, and sometimes a step document. */
|
|
4647
|
+
const pathFor = (pos, refKind) => {
|
|
4648
|
+
if (rng.chance(malformed)) return rng.pick([
|
|
4649
|
+
"names",
|
|
4650
|
+
[],
|
|
4651
|
+
{ names: "a" },
|
|
4652
|
+
["nowhere", "a"]
|
|
4653
|
+
]);
|
|
4654
|
+
const root = pos.names.length > 0 && rng.chance(.75) ? "names" : rng.pick(ROOTS.filter((name) => name !== "names"));
|
|
4655
|
+
const path = [root];
|
|
4656
|
+
if (root === "names") path.push(pickName(pos.names));
|
|
4657
|
+
if (root === "world") path.push(String(rng.pick(types)[0]));
|
|
4658
|
+
for (let index = rng.int(3); index > 0; index -= 1) if (stepTypes.length > 0 && rng.chance(.2)) {
|
|
4659
|
+
const type = rng.pick(stepTypes);
|
|
4660
|
+
const kind = kindOf(type);
|
|
4661
|
+
const value = refKind?.parts?.shape === "deep" && kind?.parts !== void 0 ? valueOf(kind.parts, [], {
|
|
4662
|
+
...pos,
|
|
4663
|
+
depth: pos.depth + 1
|
|
4664
|
+
}, emptyScope()) : dataOf(type);
|
|
4665
|
+
path.push({
|
|
4666
|
+
type: renderType(type),
|
|
4667
|
+
value
|
|
4668
|
+
});
|
|
4669
|
+
} else path.push(rng.pick(keys));
|
|
4670
|
+
return path;
|
|
4671
|
+
};
|
|
4672
|
+
const emptyScope = () => ({
|
|
4673
|
+
rules: [],
|
|
4674
|
+
names: [],
|
|
4675
|
+
covered: [],
|
|
4676
|
+
sources: /* @__PURE__ */ new Map()
|
|
4677
|
+
});
|
|
4678
|
+
/** This function decides the names of the scope rules before the parts, so that each part knows what it sees. */
|
|
4679
|
+
const scopeOf = (kind) => {
|
|
4680
|
+
const rules = kind.scope ?? [];
|
|
4681
|
+
const scope = {
|
|
4682
|
+
rules,
|
|
4683
|
+
names: [],
|
|
4684
|
+
covered: rules.map(() => 0),
|
|
4685
|
+
sources: /* @__PURE__ */ new Map()
|
|
4686
|
+
};
|
|
4687
|
+
for (const rule of rules) {
|
|
4688
|
+
const source = rule.names;
|
|
4689
|
+
let names = [];
|
|
4690
|
+
if ("fixed" in source) names = [...source.fixed];
|
|
4691
|
+
else if ("keysAt" in source) {
|
|
4692
|
+
names = someNames();
|
|
4693
|
+
scope.sources.set(source.keysAt.join("."), { keys: names });
|
|
4694
|
+
} else if ("listAt" in source) {
|
|
4695
|
+
names = someNames();
|
|
4696
|
+
scope.sources.set(source.listAt.join("."), { list: rng.chance(malformed) ? [...names, ...names] : names });
|
|
4697
|
+
} else if ("textAt" in source) {
|
|
4698
|
+
names = [rng.pick(NAMES)];
|
|
4699
|
+
scope.sources.set(source.textAt.join("."), { text: names[0] });
|
|
4700
|
+
}
|
|
4701
|
+
scope.names.push(names);
|
|
4702
|
+
}
|
|
4703
|
+
return scope;
|
|
4704
|
+
};
|
|
4705
|
+
/** The names that a part sees from the scope rules of its parent. A rule `sequential` shows only earlier names. */
|
|
4706
|
+
const seenBy = (scope, path) => {
|
|
4707
|
+
const names = [];
|
|
4708
|
+
let headers = false;
|
|
4709
|
+
scope.rules.forEach((rule, index) => {
|
|
4710
|
+
if (!rule.in.some((pattern) => pathMatches(path, pattern))) return;
|
|
4711
|
+
const ruleNames = scope.names[index] ?? [];
|
|
4712
|
+
if ("headers" in rule.names) headers = true;
|
|
4713
|
+
if (rule.sequential !== true || "headers" in rule.names || (scope.covered[index] ?? 0) > 0) names.push(...ruleNames);
|
|
4714
|
+
});
|
|
4715
|
+
return {
|
|
4716
|
+
names,
|
|
4717
|
+
headers
|
|
4718
|
+
};
|
|
4719
|
+
};
|
|
4720
|
+
/** This function records the part at a path, and the header name that it got, for the rules that cover it. */
|
|
4721
|
+
const saw = (scope, path, part) => {
|
|
4722
|
+
const header = isEnvelope(part) && typeof part.name === "string" ? part.name : void 0;
|
|
4723
|
+
scope.rules.forEach((rule, index) => {
|
|
4724
|
+
if (!rule.in.some((pattern) => pathMatches(path, pattern))) return;
|
|
4725
|
+
scope.covered[index] = (scope.covered[index] ?? 0) + 1;
|
|
4726
|
+
if ("headers" in rule.names && header !== void 0) scope.names[index]?.push(header);
|
|
4727
|
+
});
|
|
4728
|
+
};
|
|
4729
|
+
const partAt = (pos, scope, path, spec, parentType) => {
|
|
4730
|
+
const seen = seenBy(scope, path);
|
|
4731
|
+
const header = seen.headers && rng.chance(.5) ? rng.pick(NAMES) : void 0;
|
|
4732
|
+
const part = docAt({
|
|
4733
|
+
declared: spec.of,
|
|
4734
|
+
kinds: "kinds" in spec ? spec.kinds : void 0,
|
|
4735
|
+
names: seen.names.length === 0 ? pos.names : [...pos.names, ...seen.names],
|
|
4736
|
+
depth: pos.depth + 1,
|
|
4737
|
+
hint: parentType === void 0 ? void 0 : options.hints?.(parentType, path)
|
|
4738
|
+
}, header);
|
|
4739
|
+
saw(scope, path, part);
|
|
4740
|
+
return part;
|
|
4741
|
+
};
|
|
4742
|
+
/** This function builds a value of a shape (SHAPE.*). A part is a document at the next depth. */
|
|
4743
|
+
const valueOf = (shape, path, pos, scope, type) => {
|
|
4744
|
+
const source = scope.sources.get(path.join("."));
|
|
4745
|
+
if (source !== void 0 && "list" in source) return [...source.list];
|
|
4746
|
+
if (source !== void 0 && "text" in source) return source.text;
|
|
4747
|
+
const term = (of) => of === void 0 ? { shape: "term" } : {
|
|
4748
|
+
shape: "term",
|
|
4749
|
+
of
|
|
4750
|
+
};
|
|
4751
|
+
const small = () => pos.depth >= maxDepth ? 0 : size >= maxSize ? rng.int(2) : rng.int(4);
|
|
4752
|
+
if (shape === void 0 || shape.shape === "none") return path.length === 0 && type !== void 0 ? dataOf(type) : anyJson();
|
|
4753
|
+
switch (shape.shape) {
|
|
4754
|
+
case "term": return partAt(pos, scope, path, shape, type);
|
|
4755
|
+
case "list": {
|
|
4756
|
+
const items = Array.from({ length: small() }, (_, index) => valueOf(shape.item ?? term(shape.of), [...path, index], pos, scope, type));
|
|
4757
|
+
if (shape.distinct !== true || rng.chance(malformed)) return items;
|
|
4758
|
+
return items.filter((item, index) => !items.slice(0, index).some((before) => docEquals(before, item)));
|
|
4759
|
+
}
|
|
4760
|
+
case "map": {
|
|
4761
|
+
const out = {};
|
|
4762
|
+
const names = source !== void 0 && "keys" in source ? source.keys : Array.from({ length: small() }, () => rng.pick(FIELDS));
|
|
4763
|
+
for (const key of names) Object.defineProperty(out, key, {
|
|
4764
|
+
value: valueOf(shape.item ?? term(shape.of), [...path, key], pos, scope, type),
|
|
4765
|
+
enumerable: true,
|
|
4766
|
+
writable: true,
|
|
4767
|
+
configurable: true
|
|
4768
|
+
});
|
|
4769
|
+
return out;
|
|
4770
|
+
}
|
|
4771
|
+
case "record": {
|
|
4772
|
+
const named = Array.isArray(shape.fields) ? Object.fromEntries(shape.fields.map((name) => [name, term(shape.of)])) : shape.fields;
|
|
4773
|
+
const fieldKeys = /* @__PURE__ */ new Set([...shape.required ?? [], ...Object.keys(named).filter(() => rng.chance(.75))]);
|
|
4774
|
+
if (shape.closed !== true && shape.rest === void 0 && rng.chance(.15)) fieldKeys.add("extra");
|
|
4775
|
+
const ordered = rng.chance(.2) ? [...fieldKeys].reverse() : [...fieldKeys];
|
|
4776
|
+
const out = {};
|
|
4777
|
+
for (const key of ordered) {
|
|
4778
|
+
if (rng.chance(malformed / 2) && (shape.required ?? []).includes(key)) continue;
|
|
4779
|
+
const fieldShape = Object.hasOwn(named, key) ? named[key] : shape.rest;
|
|
4780
|
+
Object.defineProperty(out, key, {
|
|
4781
|
+
value: valueOf(fieldShape, [...path, key], pos, scope, type),
|
|
4782
|
+
enumerable: true,
|
|
4783
|
+
writable: true,
|
|
4784
|
+
configurable: true
|
|
4785
|
+
});
|
|
4786
|
+
}
|
|
4787
|
+
if (shape.closed === true && rng.chance(malformed)) out["stray"] = 1;
|
|
4788
|
+
return out;
|
|
4789
|
+
}
|
|
4790
|
+
case "deep": return rng.chance(.5) ? [partAt(pos, scope, [...path, 0], term(shape.of), type), anyJson()] : {
|
|
4791
|
+
at: partAt(pos, scope, [...path, "at"], term(shape.of), type),
|
|
4792
|
+
data: anyJson()
|
|
4793
|
+
};
|
|
4794
|
+
case "text": return rng.chance(malformed) ? 1 : rng.pick([
|
|
4795
|
+
"",
|
|
4796
|
+
"a",
|
|
4797
|
+
"label"
|
|
4798
|
+
]);
|
|
4799
|
+
case "number": {
|
|
4800
|
+
if (rng.chance(malformed)) return "1";
|
|
4801
|
+
const value = (shape.min ?? -2) + rng.int(5) + (shape.whole === true ? 0 : rng.pick([0, .5]));
|
|
4802
|
+
return shape.max !== void 0 && value > shape.max ? shape.max : value;
|
|
4803
|
+
}
|
|
4804
|
+
case "one": return rng.chance(malformed) ? "other" : rng.pick(shape.values);
|
|
4805
|
+
}
|
|
4806
|
+
};
|
|
4807
|
+
/** A malformed document: an unknown kind, a bad shape, a bare value without a type, or a bad reference. */
|
|
4808
|
+
const brokenAt = (pos) => {
|
|
4809
|
+
const roll = rng.int(5);
|
|
4810
|
+
if (roll === 0) return {
|
|
4811
|
+
type: rng.pick(["nothing", ["no", "such"]]),
|
|
4812
|
+
value: 1
|
|
4813
|
+
};
|
|
4814
|
+
if (roll === 1) return pos.declared === void 0 ? rng.pick([
|
|
4815
|
+
7,
|
|
4816
|
+
"bare",
|
|
4817
|
+
[1]
|
|
4818
|
+
]) : {
|
|
4819
|
+
type: "ref",
|
|
4820
|
+
value: ["names", "unbound"]
|
|
4821
|
+
};
|
|
4822
|
+
if (roll === 2) {
|
|
4823
|
+
const shaped = types.filter((type) => kindOf(type)?.parts !== void 0);
|
|
4824
|
+
if (shaped.length > 0) return envelope(rng.pick(shaped), rng.pick([
|
|
4825
|
+
7,
|
|
4826
|
+
"x",
|
|
4827
|
+
null
|
|
4828
|
+
]));
|
|
4829
|
+
}
|
|
4830
|
+
if (roll === 3) return {
|
|
4831
|
+
type: "ref",
|
|
4832
|
+
value: rng.pick([
|
|
4833
|
+
["names", "unbound"],
|
|
4834
|
+
["nowhere"],
|
|
4835
|
+
"names"
|
|
4836
|
+
])
|
|
4837
|
+
};
|
|
4838
|
+
return {
|
|
4839
|
+
type: rng.pick(types),
|
|
4840
|
+
value: anyJson()
|
|
4841
|
+
};
|
|
4842
|
+
};
|
|
4843
|
+
const docAt = (pos, header) => {
|
|
4844
|
+
size += 1;
|
|
4845
|
+
const leaf = pos.depth >= maxDepth || size >= maxSize;
|
|
4846
|
+
if (rng.chance(malformed)) return brokenAt(pos);
|
|
4847
|
+
if (pos.declared !== void 0 && rng.chance(leaf ? .7 : .3)) return dataOf([pos.declared]);
|
|
4848
|
+
let candidates = types.filter((type) => permitted(type, pos.kinds) && (!leaf || leafKind(kindOf(type))));
|
|
4849
|
+
if (pos.hint !== void 0 && rng.chance(.6)) {
|
|
4850
|
+
const hinted = candidates.filter((type) => pos.hint?.some((hint) => hint.length === type.length && hint.every((key, index) => key === type[index])));
|
|
4851
|
+
if (hinted.length > 0) candidates = hinted;
|
|
4852
|
+
}
|
|
4853
|
+
if (candidates.length === 0) candidates = [...types];
|
|
4854
|
+
const type = rng.pick(candidates);
|
|
4855
|
+
const kind = kindOf(type);
|
|
4856
|
+
if (type.length === 1 && type[0] === "ref") return envelope(type, pathFor(pos, kind), header);
|
|
4857
|
+
const value = kind === void 0 ? dataOf(type) : valueOf(kind.parts, [], pos, scopeOf(kind), type);
|
|
4858
|
+
return envelope(type, value, header);
|
|
4859
|
+
};
|
|
4860
|
+
return docAt({
|
|
4861
|
+
declared: options.declaredType,
|
|
4862
|
+
kinds: void 0,
|
|
4863
|
+
names: options.names ?? [],
|
|
4864
|
+
depth: 0,
|
|
4865
|
+
hint: void 0
|
|
4866
|
+
});
|
|
4867
|
+
}
|
|
4868
|
+
//#endregion
|
|
4869
|
+
export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaDefinitions, formulaEvaluators, formulaKinds, formulaMachine, fromJson, generate, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, narrow, nearestWalker, nodeAt, ok, partCaps, pathMatches, permits, placed, planner, prepare, random, ref, reference, refsOf, resolve, run, sameValue, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, typesOf, valueOfThing, walk, worldThing };
|
|
3716
4870
|
|
|
3717
4871
|
//# sourceMappingURL=index.js.map
|