@mark1russell7/coeval 0.4.1 → 0.4.3
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 +5 -3
- package/build/index.d.ts +288 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1291 -86
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +237 -0
- package/src/doc.ts +26 -8
- package/src/errors.ts +6 -1
- package/src/evaluate.ts +51 -28
- package/src/formula.ts +55 -11
- package/src/generate.ts +388 -0
- package/src/index.ts +5 -1
- package/src/parts.ts +59 -3
- package/src/reference.ts +566 -0
- package/src/static.ts +17 -16
package/build/index.js
CHANGED
|
@@ -175,9 +175,17 @@ function fromJson(value) {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
|
-
* This function tells if two
|
|
179
|
-
* the same items in the same order, and the same fields. The order of the fields
|
|
180
|
-
*
|
|
178
|
+
* This function tells if two values are equal. Plain JSON compares by its structure. Equal values have the same
|
|
179
|
+
* texts, numbers and truth values, the same items in the same order, and the same fields. The order of the fields
|
|
180
|
+
* does not matter. Each leaf compares with `Object.is`, thus `-0` is not `0`, and `NaN` is `NaN`.
|
|
181
|
+
*
|
|
182
|
+
* Only lists and plain records (`isRecord`) compare by their items and fields. Each other object compares by
|
|
183
|
+
* identity: a date, a map, a function and an instance of a class are equal only to themselves. Thus an early cutoff
|
|
184
|
+
* with this function does not keep a stale host value.
|
|
185
|
+
*
|
|
186
|
+
* The function does not use the stack for the depth, thus a deep document cannot overflow it. A cycle of lists or
|
|
187
|
+
* records does not make it loop. `sameValue` of `agree` uses the same rules, and it adds rules for closures and for
|
|
188
|
+
* a rule of the caller.
|
|
181
189
|
*
|
|
182
190
|
* @param a - The first value.
|
|
183
191
|
* @param b - The second value.
|
|
@@ -185,15 +193,23 @@ function fromJson(value) {
|
|
|
185
193
|
*/
|
|
186
194
|
function docEquals(a, b) {
|
|
187
195
|
const stack = [[a, b]];
|
|
196
|
+
let seen;
|
|
188
197
|
while (stack.length > 0) {
|
|
189
198
|
const [x, y] = stack.pop();
|
|
190
199
|
if (Object.is(x, y)) continue;
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
if (
|
|
200
|
+
const list = Array.isArray(x);
|
|
201
|
+
if (list !== Array.isArray(y)) return false;
|
|
202
|
+
if (!list && (!isRecord(x) || !isRecord(y))) return false;
|
|
203
|
+
seen ??= /* @__PURE__ */ new Map();
|
|
204
|
+
const pairs = seen.get(x);
|
|
205
|
+
if (pairs?.has(y) === true) continue;
|
|
206
|
+
if (pairs === void 0) seen.set(x, /* @__PURE__ */ new Set([y]));
|
|
207
|
+
else pairs.add(y);
|
|
208
|
+
if (list) {
|
|
209
|
+
const xs = x;
|
|
194
210
|
const ys = y;
|
|
195
|
-
if (
|
|
196
|
-
for (let index = 0; index <
|
|
211
|
+
if (xs.length !== ys.length) return false;
|
|
212
|
+
for (let index = 0; index < xs.length; index += 1) stack.push([xs[index], ys[index]]);
|
|
197
213
|
continue;
|
|
198
214
|
}
|
|
199
215
|
const xr = x;
|
|
@@ -282,7 +298,11 @@ var Failure = class extends Error {
|
|
|
282
298
|
* @returns `true` for a stack overflow.
|
|
283
299
|
*/
|
|
284
300
|
function isStackOverflow(error) {
|
|
285
|
-
|
|
301
|
+
try {
|
|
302
|
+
return error instanceof RangeError && /call stack/i.test(error.message);
|
|
303
|
+
} catch {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
286
306
|
}
|
|
287
307
|
/**
|
|
288
308
|
* This function changes an exception into an error value. A `Failure` gives its error value. A stack overflow
|
|
@@ -1036,6 +1056,10 @@ const NO_SLOTS = Object.freeze([]);
|
|
|
1036
1056
|
function hasSlots(template) {
|
|
1037
1057
|
return template.t !== "data";
|
|
1038
1058
|
}
|
|
1059
|
+
/** This function tells if a shape holds only data: no value of the shape can hold a part. */
|
|
1060
|
+
function isData(shape) {
|
|
1061
|
+
return shape.shape === "none" || shape.shape === "text" || shape.shape === "number" || shape.shape === "one";
|
|
1062
|
+
}
|
|
1039
1063
|
/** This function gives the reason why a value is not data of a shape, or `undefined` when it is. */
|
|
1040
1064
|
function dataFault(spec, item) {
|
|
1041
1065
|
if (spec.shape === "text") return typeof item === "string" ? void 0 : "The value must be a text.";
|
|
@@ -1137,9 +1161,13 @@ function splitParts(shape, value) {
|
|
|
1137
1161
|
shape: "term",
|
|
1138
1162
|
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1139
1163
|
};
|
|
1140
|
-
|
|
1164
|
+
const items = list.map((child, index) => walk(each, child, [...path, index]));
|
|
1165
|
+
return isData(each) ? {
|
|
1166
|
+
t: "data",
|
|
1167
|
+
value: item
|
|
1168
|
+
} : {
|
|
1141
1169
|
t: "list",
|
|
1142
|
-
items
|
|
1170
|
+
items
|
|
1143
1171
|
};
|
|
1144
1172
|
}
|
|
1145
1173
|
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
@@ -1207,6 +1235,54 @@ function splitParts(shape, value) {
|
|
|
1207
1235
|
};
|
|
1208
1236
|
}
|
|
1209
1237
|
/**
|
|
1238
|
+
* This function reads one document by the contracts of the world. It finds the kind of the type. A bare value gets
|
|
1239
|
+
* the type that its position declares. Then the function splits the value by the shape of the kind. The plan, the
|
|
1240
|
+
* reference interpreter and the static kit read each document with this function. Thus they see the same parts.
|
|
1241
|
+
*
|
|
1242
|
+
* @param world - The world of the kinds.
|
|
1243
|
+
* @param value - The document, or a bare value.
|
|
1244
|
+
* @param declaredType - The type that the position of a bare value declares.
|
|
1245
|
+
* @returns The document, its kind and its parts, or the reason why the value is not a document.
|
|
1246
|
+
*/
|
|
1247
|
+
function partsOf(world, value, declaredType) {
|
|
1248
|
+
const envelope = isEnvelope(value) ? value : void 0;
|
|
1249
|
+
if (envelope === void 0 && declaredType === void 0) return {
|
|
1250
|
+
ok: false,
|
|
1251
|
+
code: "#PART?",
|
|
1252
|
+
reason: "The value has no envelope, and its position declares no type."
|
|
1253
|
+
};
|
|
1254
|
+
const doc = envelope ?? {
|
|
1255
|
+
type: declaredType,
|
|
1256
|
+
value
|
|
1257
|
+
};
|
|
1258
|
+
const found = lookupKind(world, doc.type);
|
|
1259
|
+
const kind = found.ok ? found.kind : void 0;
|
|
1260
|
+
const split = splitParts(kind?.parts, doc.value);
|
|
1261
|
+
const typePath = found.ok ? found.path : void 0;
|
|
1262
|
+
if (split.ok) return {
|
|
1263
|
+
ok: true,
|
|
1264
|
+
doc,
|
|
1265
|
+
declared: envelope === void 0,
|
|
1266
|
+
kind,
|
|
1267
|
+
typePath,
|
|
1268
|
+
split
|
|
1269
|
+
};
|
|
1270
|
+
const where = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1271
|
+
const reason = `${split.reason}${where} The kind is ${typeName(doc.type)}.`;
|
|
1272
|
+
return {
|
|
1273
|
+
ok: true,
|
|
1274
|
+
doc,
|
|
1275
|
+
declared: envelope === void 0,
|
|
1276
|
+
kind,
|
|
1277
|
+
typePath,
|
|
1278
|
+
split: {
|
|
1279
|
+
ok: false,
|
|
1280
|
+
reason,
|
|
1281
|
+
at: split.at
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1210
1286
|
* This function tells if the path of a part matches a pattern of a scope rule. The key `"*"` of a pattern matches
|
|
1211
1287
|
* each key.
|
|
1212
1288
|
*
|
|
@@ -1543,7 +1619,7 @@ function holderNode() {
|
|
|
1543
1619
|
};
|
|
1544
1620
|
}
|
|
1545
1621
|
/** This function drives steps that cannot wait. */
|
|
1546
|
-
function drive(steps) {
|
|
1622
|
+
function drive$1(steps) {
|
|
1547
1623
|
const step = steps.next();
|
|
1548
1624
|
if (step.done === true) return step.value;
|
|
1549
1625
|
step.value.catch(() => void 0);
|
|
@@ -1586,7 +1662,10 @@ function isCompiledRunner(runner) {
|
|
|
1586
1662
|
return typeof runner === "object" && runner !== null && typeof runner.compileRun === "function";
|
|
1587
1663
|
}
|
|
1588
1664
|
function failing(c, meta, place, code, reason) {
|
|
1589
|
-
|
|
1665
|
+
return failingWith(c, meta, fail(code, reason, { place }));
|
|
1666
|
+
}
|
|
1667
|
+
/** This function gives the code of a document that fails with one result at each evaluation. */
|
|
1668
|
+
function failingWith(c, meta, result) {
|
|
1590
1669
|
const plain = () => result;
|
|
1591
1670
|
return finish(c, meta, plain, function* () {
|
|
1592
1671
|
return result;
|
|
@@ -1648,15 +1727,15 @@ function finish(c, meta, code, run) {
|
|
|
1648
1727
|
* This function finds the evaluator of the verb `eval` without the record of the rungs, for a compilation without a
|
|
1649
1728
|
* trace. It gives the same evaluator as `ask`: the own entry, then the kind, then the default of the world.
|
|
1650
1729
|
*/
|
|
1651
|
-
function evaluatorOf(world, envelope,
|
|
1730
|
+
function evaluatorOf(world, envelope, kind) {
|
|
1652
1731
|
const own = envelope?.own;
|
|
1653
1732
|
const ownName = own !== void 0 && Object.hasOwn(own, "eval") ? own["eval"] : void 0;
|
|
1654
1733
|
if (ownName !== void 0) return {
|
|
1655
1734
|
name: ownName,
|
|
1656
1735
|
unknown: false
|
|
1657
1736
|
};
|
|
1658
|
-
if (
|
|
1659
|
-
const verbs =
|
|
1737
|
+
if (kind === void 0) return { unknown: true };
|
|
1738
|
+
const verbs = kind.verbs;
|
|
1660
1739
|
const name = Object.hasOwn(verbs, "eval") ? verbs["eval"] : Object.hasOwn(world.defaults, "eval") ? world.defaults["eval"] : void 0;
|
|
1661
1740
|
return name === void 0 ? { unknown: false } : {
|
|
1662
1741
|
name,
|
|
@@ -1675,23 +1754,28 @@ const NO_META = Object.freeze({
|
|
|
1675
1754
|
side: "derive"
|
|
1676
1755
|
});
|
|
1677
1756
|
function compileNode(c, value, place, declared, depth, address = false) {
|
|
1678
|
-
const
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1757
|
+
const world = c.machine.world;
|
|
1758
|
+
const read = partsOf(world, value, declared);
|
|
1759
|
+
if (!read.ok) return failing(c, c.traced ? {
|
|
1760
|
+
path: place.map(String),
|
|
1761
|
+
doc: {
|
|
1762
|
+
type: "?",
|
|
1763
|
+
value
|
|
1764
|
+
},
|
|
1765
|
+
declared: true,
|
|
1766
|
+
answer: NO_ANSWER,
|
|
1767
|
+
side: "derive"
|
|
1768
|
+
} : NO_META, place, read.code, read.reason);
|
|
1769
|
+
const { doc, kind, split } = read;
|
|
1770
|
+
const envelope = read.declared ? void 0 : doc;
|
|
1683
1771
|
const meta0 = c.traced ? {
|
|
1684
1772
|
path: place.map(String),
|
|
1685
1773
|
doc,
|
|
1686
|
-
declared:
|
|
1774
|
+
declared: read.declared,
|
|
1687
1775
|
answer: NO_ANSWER,
|
|
1688
1776
|
side: "derive"
|
|
1689
1777
|
} : NO_META;
|
|
1690
|
-
if (envelope === void 0 && declared === void 0) return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
|
|
1691
1778
|
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
1692
|
-
const world = c.machine.world;
|
|
1693
|
-
const found = lookupKind(world, doc.type);
|
|
1694
|
-
const kind = found.ok ? found.kind : void 0;
|
|
1695
1779
|
let meta = meta0;
|
|
1696
1780
|
let name;
|
|
1697
1781
|
let unknown;
|
|
@@ -1705,7 +1789,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1705
1789
|
name = answer.evaluator;
|
|
1706
1790
|
unknown = answer.error === "unknown-kind";
|
|
1707
1791
|
} else {
|
|
1708
|
-
const quick = evaluatorOf(world, envelope,
|
|
1792
|
+
const quick = evaluatorOf(world, envelope, kind);
|
|
1709
1793
|
name = quick.name;
|
|
1710
1794
|
unknown = quick.unknown;
|
|
1711
1795
|
}
|
|
@@ -1715,11 +1799,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1715
1799
|
const runner = name !== void 0 && Object.hasOwn(runners, name) ? runners[name] : void 0;
|
|
1716
1800
|
const evaluator = name !== void 0 && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : void 0;
|
|
1717
1801
|
if (name === void 0 || runner === void 0 && evaluator === void 0) return failing(c, meta, place, "#NOEVAL", `No rung gives an evaluator for ${kindText}.`);
|
|
1718
|
-
|
|
1719
|
-
if (!split.ok) {
|
|
1720
|
-
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1721
|
-
return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
|
|
1722
|
-
}
|
|
1802
|
+
if (!split.ok) return failing(c, meta, place, "#VALUE!", split.reason);
|
|
1723
1803
|
const parts = split.parts.map((slot) => {
|
|
1724
1804
|
const item = slot.item;
|
|
1725
1805
|
const own = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1, slot.address === true);
|
|
@@ -1741,7 +1821,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1741
1821
|
});
|
|
1742
1822
|
const input = {
|
|
1743
1823
|
kind: kindText,
|
|
1744
|
-
typePath:
|
|
1824
|
+
typePath: read.typePath ?? [],
|
|
1745
1825
|
doc,
|
|
1746
1826
|
value: split.parts.length === 0 ? doc.value : split.fill(parts),
|
|
1747
1827
|
data: split.data,
|
|
@@ -1752,10 +1832,22 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1752
1832
|
...address ? { address: true } : {}
|
|
1753
1833
|
};
|
|
1754
1834
|
if (runner !== void 0) {
|
|
1755
|
-
|
|
1835
|
+
let inner;
|
|
1836
|
+
try {
|
|
1837
|
+
inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
|
|
1838
|
+
} catch (error) {
|
|
1839
|
+
return failingWith(c, meta, placed(thrown(error, `the runner ${name}`), place));
|
|
1840
|
+
}
|
|
1841
|
+
const run = placedRun(inner, place, name);
|
|
1756
1842
|
return finish(c, meta, runnerCode(run, place, kindText), run);
|
|
1757
1843
|
}
|
|
1758
|
-
|
|
1844
|
+
let inner;
|
|
1845
|
+
try {
|
|
1846
|
+
inner = isCompiledEvaluator(evaluator) ? evaluator.compile(input) : interpret(evaluator, input, split.fill);
|
|
1847
|
+
} catch (error) {
|
|
1848
|
+
return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
|
|
1849
|
+
}
|
|
1850
|
+
const code = placedCode(inner, place, name);
|
|
1759
1851
|
return finish(c, meta, code, runOf(code));
|
|
1760
1852
|
}
|
|
1761
1853
|
/** This function gives the code of a document: the code of its evaluator, with the place of each error and of each exception. */
|
|
@@ -1797,9 +1889,12 @@ function placedRun(inner, place, name) {
|
|
|
1797
1889
|
}
|
|
1798
1890
|
};
|
|
1799
1891
|
}
|
|
1800
|
-
/**
|
|
1892
|
+
/**
|
|
1893
|
+
* This function gives the code of a runner in an evaluation: its steps, which cannot wait. A step that waits gives
|
|
1894
|
+
* `#CALC!` at the place of the runner, where the error started (RUN.WAIT).
|
|
1895
|
+
*/
|
|
1801
1896
|
function runnerCode(run, place, kindText) {
|
|
1802
|
-
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));
|
|
1897
|
+
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);
|
|
1803
1898
|
}
|
|
1804
1899
|
/** This function gives the steps of a run of an evaluator. The code runs at the first step, and it does not wait. */
|
|
1805
1900
|
function runOf(code) {
|
|
@@ -1891,7 +1986,7 @@ function prepare(machine, doc, options = {}) {
|
|
|
1891
1986
|
try {
|
|
1892
1987
|
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
1893
1988
|
} catch (error) {
|
|
1894
|
-
const result = thrown(error, "the compilation");
|
|
1989
|
+
const result = placed(thrown(error, "the compilation"), []);
|
|
1895
1990
|
root = {
|
|
1896
1991
|
code: () => result,
|
|
1897
1992
|
run: function* () {
|
|
@@ -1952,7 +2047,7 @@ function planner(machine, options = {}) {
|
|
|
1952
2047
|
return plan;
|
|
1953
2048
|
};
|
|
1954
2049
|
}
|
|
1955
|
-
function record(s, result) {
|
|
2050
|
+
function record$1(s, result) {
|
|
1956
2051
|
const root = s.holder?.children[0];
|
|
1957
2052
|
if (root === void 0) throw new Error("The evaluation made no trace.");
|
|
1958
2053
|
return {
|
|
@@ -1981,7 +2076,7 @@ function evaluate(machine, doc, context = makeContext(), declaredType) {
|
|
|
1981
2076
|
...declaredType === void 0 ? {} : { declaredType }
|
|
1982
2077
|
});
|
|
1983
2078
|
const s = new Session(machine, false, true, void 0, void 0, void 0);
|
|
1984
|
-
return record(s, plan.root.code(rootContext(machine, context), s));
|
|
2079
|
+
return record$1(s, plan.root.code(rootContext(machine, context), s));
|
|
1985
2080
|
}
|
|
1986
2081
|
/**
|
|
1987
2082
|
* This function runs a document: an evaluation in which the host does each effect. A runner waits for its
|
|
@@ -2000,7 +2095,7 @@ async function run(machine, doc, context = makeContext(), options = {}) {
|
|
|
2000
2095
|
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType }
|
|
2001
2096
|
});
|
|
2002
2097
|
const s = new Session(machine, true, true, options.perform, options.signal, void 0);
|
|
2003
|
-
return record(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
|
|
2098
|
+
return record$1(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
|
|
2004
2099
|
}
|
|
2005
2100
|
/**
|
|
2006
2101
|
* This function finds a node of a trace by its path.
|
|
@@ -2998,40 +3093,32 @@ function walk(world, doc, visit, options = {}) {
|
|
|
2998
3093
|
}];
|
|
2999
3094
|
while (stack.length > 0) {
|
|
3000
3095
|
const item = stack.pop();
|
|
3001
|
-
const
|
|
3002
|
-
if (
|
|
3096
|
+
const read = partsOf(world, item.value, item.declared);
|
|
3097
|
+
if (!read.ok) {
|
|
3003
3098
|
options.problem?.({
|
|
3004
3099
|
place: keysOf(item.place),
|
|
3005
3100
|
at: keysOf(item.at),
|
|
3006
|
-
code:
|
|
3007
|
-
reason:
|
|
3101
|
+
code: read.code,
|
|
3102
|
+
reason: read.reason
|
|
3008
3103
|
});
|
|
3009
3104
|
continue;
|
|
3010
3105
|
}
|
|
3011
|
-
const doc
|
|
3012
|
-
|
|
3013
|
-
value: item.value
|
|
3014
|
-
};
|
|
3015
|
-
const found = lookupKind(world, doc.type);
|
|
3016
|
-
const kind = found.ok ? found.kind : void 0;
|
|
3017
|
-
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);
|
|
3106
|
+
const { doc, kind, split } = read;
|
|
3107
|
+
const here = new Visit(item.place, item.at, doc, read.declared, item.depth, item.names, item.later, item.slot, item.parent, kind, read.typePath, item.address ? true : void 0, item.caps);
|
|
3018
3108
|
if (visit(here) === "skip" || kind === void 0) continue;
|
|
3019
|
-
const
|
|
3020
|
-
const base = envelope === void 0 ? item.at : {
|
|
3109
|
+
const base = read.declared ? item.at : {
|
|
3021
3110
|
up: item.at,
|
|
3022
3111
|
keys: ["value"]
|
|
3023
3112
|
};
|
|
3024
3113
|
if (!split.ok) {
|
|
3025
|
-
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
3026
|
-
const where = keysOf({
|
|
3027
|
-
up: base,
|
|
3028
|
-
keys: split.at
|
|
3029
|
-
});
|
|
3030
3114
|
options.problem?.({
|
|
3031
3115
|
place: keysOf(item.place),
|
|
3032
|
-
at:
|
|
3116
|
+
at: keysOf({
|
|
3117
|
+
up: base,
|
|
3118
|
+
keys: split.at
|
|
3119
|
+
}),
|
|
3033
3120
|
code: "#VALUE!",
|
|
3034
|
-
reason:
|
|
3121
|
+
reason: split.reason
|
|
3035
3122
|
});
|
|
3036
3123
|
continue;
|
|
3037
3124
|
}
|
|
@@ -3163,7 +3250,8 @@ function capReason(v, kind) {
|
|
|
3163
3250
|
* - `#PART?`: a value without a type.
|
|
3164
3251
|
* - `#DEPTH!`: too much nesting.
|
|
3165
3252
|
* - `#REF!` and `#STEP?`: a bad path.
|
|
3166
|
-
* - `#NAME?`: a name that nothing binds.
|
|
3253
|
+
* - `#NAME?`: a name that nothing binds. With the hook `known` of the machine, also a name that the language does not
|
|
3254
|
+
* have, for example an operation.
|
|
3167
3255
|
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
3168
3256
|
*
|
|
3169
3257
|
* @param machine - The world and the code of the evaluators.
|
|
@@ -3205,6 +3293,15 @@ function check(machine, doc, options = {}) {
|
|
|
3205
3293
|
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`,
|
|
3206
3294
|
at: v.at
|
|
3207
3295
|
});
|
|
3296
|
+
else if (machine.known !== void 0) {
|
|
3297
|
+
const known = machine.known(v.typePath ?? [], name);
|
|
3298
|
+
if (known !== true) issues.push({
|
|
3299
|
+
code: "#NAME?",
|
|
3300
|
+
place: v.place,
|
|
3301
|
+
reason: known,
|
|
3302
|
+
at: v.at
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3208
3305
|
const caps = v.caps;
|
|
3209
3306
|
if (caps !== void 0 && v.kind?.effects !== void 0) {
|
|
3210
3307
|
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
@@ -3237,7 +3334,7 @@ function check(machine, doc, options = {}) {
|
|
|
3237
3334
|
reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
|
|
3238
3335
|
at: stepAt(0)
|
|
3239
3336
|
});
|
|
3240
|
-
return
|
|
3337
|
+
return;
|
|
3241
3338
|
}
|
|
3242
3339
|
if (!roots.includes(root)) issues.push({
|
|
3243
3340
|
code: "#REF!",
|
|
@@ -3290,7 +3387,7 @@ var Held = class {
|
|
|
3290
3387
|
this.error = error;
|
|
3291
3388
|
}
|
|
3292
3389
|
};
|
|
3293
|
-
const CLOSURE = Symbol.for("coeval.closure");
|
|
3390
|
+
const CLOSURE$2 = Symbol.for("coeval.closure");
|
|
3294
3391
|
/**
|
|
3295
3392
|
* This function tells if a value is a function that the construct `fn` made.
|
|
3296
3393
|
*
|
|
@@ -3298,7 +3395,7 @@ const CLOSURE = Symbol.for("coeval.closure");
|
|
|
3298
3395
|
* @returns `true` for a closure.
|
|
3299
3396
|
*/
|
|
3300
3397
|
function isClosure(value) {
|
|
3301
|
-
return typeof value === "function" && value[CLOSURE] !== void 0;
|
|
3398
|
+
return typeof value === "function" && value[CLOSURE$2] !== void 0;
|
|
3302
3399
|
}
|
|
3303
3400
|
/** The standard extensions: the values that JSON cannot write. */
|
|
3304
3401
|
const STD_EXT = {
|
|
@@ -3344,7 +3441,7 @@ function valuesOf(parts, ctx, rt) {
|
|
|
3344
3441
|
}
|
|
3345
3442
|
return values;
|
|
3346
3443
|
}
|
|
3347
|
-
function partsOf(value) {
|
|
3444
|
+
function partsOf$1(value) {
|
|
3348
3445
|
return isRecord(value) ? value : {};
|
|
3349
3446
|
}
|
|
3350
3447
|
const LIT_SUMMARY = "A literal: its value is the result.";
|
|
@@ -3408,7 +3505,14 @@ function formulaKinds() {
|
|
|
3408
3505
|
verbs: { eval: "formula.fn" },
|
|
3409
3506
|
parts: {
|
|
3410
3507
|
shape: "record",
|
|
3411
|
-
fields: {
|
|
3508
|
+
fields: {
|
|
3509
|
+
params: {
|
|
3510
|
+
shape: "list",
|
|
3511
|
+
item: { shape: "text" },
|
|
3512
|
+
distinct: true
|
|
3513
|
+
},
|
|
3514
|
+
body: term
|
|
3515
|
+
},
|
|
3412
3516
|
required: ["params", "body"]
|
|
3413
3517
|
},
|
|
3414
3518
|
scope: [{
|
|
@@ -3531,18 +3635,19 @@ function formulaEvaluators(profile = {}) {
|
|
|
3531
3635
|
if (missing !== void 0 && missing(reached)) return fail("#N/A", "The path reaches no value.");
|
|
3532
3636
|
return ok(reached);
|
|
3533
3637
|
};
|
|
3534
|
-
const
|
|
3638
|
+
const held = (ctx) => {
|
|
3639
|
+
if (head === void 0 || !Object.hasOwn(ctx.names, head)) return void 0;
|
|
3640
|
+
const bound = ctx.names[head];
|
|
3641
|
+
return bound instanceof Held ? {
|
|
3642
|
+
ok: false,
|
|
3643
|
+
error: bound.error
|
|
3644
|
+
} : void 0;
|
|
3645
|
+
};
|
|
3646
|
+
if (parts.length === 0) return (ctx, rt) => held(ctx) ?? give(rt.resolve(ctx, path));
|
|
3535
3647
|
return (ctx, rt) => {
|
|
3536
|
-
if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
|
|
3537
|
-
const bound = ctx.names[head];
|
|
3538
|
-
if (bound instanceof Held) return {
|
|
3539
|
-
ok: false,
|
|
3540
|
-
error: bound.error
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
if (!computed) return give(rt.resolve(ctx, path));
|
|
3544
3648
|
const steps = valuesOf(parts, ctx, rt);
|
|
3545
|
-
|
|
3649
|
+
if (!Array.isArray(steps)) return steps;
|
|
3650
|
+
return held(ctx) ?? give(rt.resolve(ctx, fill(steps)));
|
|
3546
3651
|
};
|
|
3547
3652
|
}),
|
|
3548
3653
|
"formula.step": compiled(({ doc, parts, fill }) => {
|
|
@@ -3559,7 +3664,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3559
3664
|
};
|
|
3560
3665
|
}),
|
|
3561
3666
|
"formula.record": compiled(({ value }) => {
|
|
3562
|
-
const fields = partsOf(value);
|
|
3667
|
+
const fields = partsOf$1(value);
|
|
3563
3668
|
const keys = Object.keys(fields);
|
|
3564
3669
|
const codes = keys.map((key) => fields[key].code);
|
|
3565
3670
|
if (combine !== void 0) {
|
|
@@ -3592,8 +3697,8 @@ function formulaEvaluators(profile = {}) {
|
|
|
3592
3697
|
return Array.isArray(values) ? ok(values) : values;
|
|
3593
3698
|
}),
|
|
3594
3699
|
"formula.let": compiled(({ value }) => {
|
|
3595
|
-
const record = partsOf(value);
|
|
3596
|
-
const bindings = Object.entries(partsOf(record["bind"]));
|
|
3700
|
+
const record = partsOf$1(value);
|
|
3701
|
+
const bindings = Object.entries(partsOf$1(record["bind"]));
|
|
3597
3702
|
const body = record["body"];
|
|
3598
3703
|
return (ctx, rt) => {
|
|
3599
3704
|
const names = { ...ctx.names };
|
|
@@ -3614,7 +3719,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3614
3719
|
return () => bad;
|
|
3615
3720
|
}
|
|
3616
3721
|
const names = params;
|
|
3617
|
-
const body = partsOf(value)["body"];
|
|
3722
|
+
const body = partsOf$1(value)["body"];
|
|
3618
3723
|
return (ctx, rt) => {
|
|
3619
3724
|
const closure = (...args) => {
|
|
3620
3725
|
const scope = { ...ctx.names };
|
|
@@ -3626,12 +3731,12 @@ function formulaEvaluators(profile = {}) {
|
|
|
3626
3731
|
if (!result.ok) throw new Failure(result.error);
|
|
3627
3732
|
return result.value;
|
|
3628
3733
|
};
|
|
3629
|
-
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
3734
|
+
Object.defineProperty(closure, CLOSURE$2, { value: { params: names } });
|
|
3630
3735
|
return ok(closure);
|
|
3631
3736
|
};
|
|
3632
3737
|
}),
|
|
3633
3738
|
"formula.call": compiled(({ value }) => {
|
|
3634
|
-
const record = partsOf(value);
|
|
3739
|
+
const record = partsOf$1(value);
|
|
3635
3740
|
const fnPart = record["fn"];
|
|
3636
3741
|
const args = Array.isArray(record["args"]) ? record["args"] : [];
|
|
3637
3742
|
return (ctx, rt) => {
|
|
@@ -3652,7 +3757,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3652
3757
|
};
|
|
3653
3758
|
}),
|
|
3654
3759
|
"formula.if": compiled(({ value }) => {
|
|
3655
|
-
const record = partsOf(value);
|
|
3760
|
+
const record = partsOf$1(value);
|
|
3656
3761
|
const test = record["test"];
|
|
3657
3762
|
const then = record["then"];
|
|
3658
3763
|
const other = record["else"];
|
|
@@ -3771,14 +3876,52 @@ function formulaEvaluators(profile = {}) {
|
|
|
3771
3876
|
};
|
|
3772
3877
|
}
|
|
3773
3878
|
/**
|
|
3879
|
+
* This function makes the hook `known` of the formula kit for one profile (STATIC.KNOWN). The evaluator
|
|
3880
|
+
* `formula.op` knows an operation of `lazy` or of `ops`. A profile whose `ops` is a function answers through a call
|
|
3881
|
+
* of that function. A function that throws counts as an answer, because the run then gives `#CALC!`. The evaluator
|
|
3882
|
+
* `formula.ext` knows an extension of `ext` and each standard extension. The hook knows each name of the other
|
|
3883
|
+
* evaluators.
|
|
3884
|
+
*
|
|
3885
|
+
* @param profile - The operations and the extensions of the language.
|
|
3886
|
+
* @returns The hook: `true`, or the reason of the `#NAME?` that the evaluator gives in a run.
|
|
3887
|
+
*/
|
|
3888
|
+
function formulaKnown(profile = {}) {
|
|
3889
|
+
const hasOp = (name) => {
|
|
3890
|
+
if (profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name)) return true;
|
|
3891
|
+
const ops = profile.ops;
|
|
3892
|
+
if (ops === void 0) return false;
|
|
3893
|
+
if (typeof ops !== "function") return Object.hasOwn(ops, name);
|
|
3894
|
+
try {
|
|
3895
|
+
return ops(name) !== void 0;
|
|
3896
|
+
} catch {
|
|
3897
|
+
return true;
|
|
3898
|
+
}
|
|
3899
|
+
};
|
|
3900
|
+
const hasExt = (name) => profile.ext !== void 0 && Object.hasOwn(profile.ext, name) || Object.hasOwn(STD_EXT, name);
|
|
3901
|
+
return (path, evaluator) => {
|
|
3902
|
+
if (evaluator !== "formula.op" && evaluator !== "formula.ext") return true;
|
|
3903
|
+
const name = path.slice(1).join(".");
|
|
3904
|
+
if (evaluator === "formula.op") return hasOp(name) ? true : `The language has no operation ${name}.`;
|
|
3905
|
+
return hasExt(name) ? true : `The language has no extension ${name}.`;
|
|
3906
|
+
};
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3774
3909
|
* This function makes a machine for a formula language. The machine has the kinds and the evaluators of the
|
|
3775
|
-
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds.
|
|
3910
|
+
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds. Its hook `known` comes
|
|
3911
|
+
* from the profile (`formulaKnown`). A hook `known` of the language answers each name that the kit knows.
|
|
3776
3912
|
*
|
|
3777
3913
|
* @param profile - The operations and the rules of the language.
|
|
3778
3914
|
* @param more - The parts of a machine that the language adds. Its kinds win over the kinds of the kit.
|
|
3779
3915
|
* @returns The machine.
|
|
3780
3916
|
*/
|
|
3781
3917
|
function formulaMachine(profile = {}, more = {}) {
|
|
3918
|
+
const kit = formulaKnown(profile);
|
|
3919
|
+
const replaced = (name) => more.evaluators !== void 0 && Object.hasOwn(more.evaluators, name) || more.runners !== void 0 && Object.hasOwn(more.runners, name);
|
|
3920
|
+
const theirs = more.known;
|
|
3921
|
+
const known = (path, evaluator) => {
|
|
3922
|
+
const answer = replaced(evaluator) ? true : kit(path, evaluator);
|
|
3923
|
+
return answer === true && theirs !== void 0 ? theirs(path, evaluator) : answer;
|
|
3924
|
+
};
|
|
3782
3925
|
return {
|
|
3783
3926
|
...more,
|
|
3784
3927
|
world: {
|
|
@@ -3791,10 +3934,1072 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
3791
3934
|
evaluators: {
|
|
3792
3935
|
...formulaEvaluators(profile),
|
|
3793
3936
|
...more.evaluators
|
|
3937
|
+
},
|
|
3938
|
+
known
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
//#endregion
|
|
3942
|
+
//#region src/reference.ts
|
|
3943
|
+
function own(table, name) {
|
|
3944
|
+
return table !== void 0 && Object.hasOwn(table, name) ? table[name] : void 0;
|
|
3945
|
+
}
|
|
3946
|
+
/** This function finds the code of an evaluator name. A runner wins over an evaluator of the same name. */
|
|
3947
|
+
function choose(env, name) {
|
|
3948
|
+
const runner = own(env.machine.runners, name);
|
|
3949
|
+
if (runner !== void 0) {
|
|
3950
|
+
const defined = own(env.definitions.runners, name);
|
|
3951
|
+
if (defined !== void 0) return { runner: defined };
|
|
3952
|
+
return typeof runner === "function" ? { runner } : { gap: `The reference has no definition of the compiled runner ${name}.` };
|
|
3953
|
+
}
|
|
3954
|
+
const evaluator = own(env.machine.evaluators, name);
|
|
3955
|
+
if (evaluator === void 0) return void 0;
|
|
3956
|
+
const defined = own(env.definitions.evaluators, name);
|
|
3957
|
+
if (defined !== void 0) return { evaluator: defined };
|
|
3958
|
+
return typeof evaluator === "function" ? { evaluator } : { gap: `The reference has no definition of the compiled evaluator ${name}.` };
|
|
3959
|
+
}
|
|
3960
|
+
/**
|
|
3961
|
+
* This function reads one document by the rules, in their order. First come an envelope or a declared type
|
|
3962
|
+
* (EVAL.ERRORS) and the limit of the depth (PLAN.DEPTH). Then come the ladder (LADDER.*), an evaluator and the shape
|
|
3963
|
+
* of the parts (SHAPE.*).
|
|
3964
|
+
*/
|
|
3965
|
+
function examine(env, at) {
|
|
3966
|
+
const { place, declared } = at;
|
|
3967
|
+
const world = env.machine.world;
|
|
3968
|
+
const read = partsOf(world, at.value, declared);
|
|
3969
|
+
if (!read.ok) return fail(read.code, read.reason, { place });
|
|
3970
|
+
if (at.depth > env.maxDepth) return fail("#DEPTH!", `The nesting is deeper than ${env.maxDepth} documents.`, { place });
|
|
3971
|
+
const { doc, kind, split } = read;
|
|
3972
|
+
const kindText = typeName(doc.type);
|
|
3973
|
+
const answer = ask(world, read.declared ? {} : doc, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
3974
|
+
if (answer.error === "unknown-kind") return fail("#KIND?", `The world has no kind ${kindText}.`, { place });
|
|
3975
|
+
const name = answer.evaluator;
|
|
3976
|
+
const chosen = name === void 0 ? void 0 : choose(env, name);
|
|
3977
|
+
if (name === void 0 || chosen === void 0) return fail("#NOEVAL", `No rung gives an evaluator for ${kindText}.`, { place });
|
|
3978
|
+
if (!split.ok) return fail("#VALUE!", split.reason, { place });
|
|
3979
|
+
return {
|
|
3980
|
+
doc,
|
|
3981
|
+
kindText,
|
|
3982
|
+
typePath: read.typePath ?? [],
|
|
3983
|
+
kind,
|
|
3984
|
+
name,
|
|
3985
|
+
chosen,
|
|
3986
|
+
split
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
/** This function gives the capabilities that the rules of a kind give one part (D14, CAP.CONTRACT, CAP.ONLY). */
|
|
3990
|
+
function capsOfPart(rules, path, caps) {
|
|
3991
|
+
let out = caps;
|
|
3992
|
+
for (const rule of rules ?? []) {
|
|
3993
|
+
if (rule.in !== void 0 && !rule.in.some((pattern) => pathMatches(path, pattern))) continue;
|
|
3994
|
+
if (rule.rule === "mask") return [];
|
|
3995
|
+
if (rule.rule === "only") out = intersectCaps(out, rule.caps);
|
|
3996
|
+
}
|
|
3997
|
+
return out;
|
|
3998
|
+
}
|
|
3999
|
+
/**
|
|
4000
|
+
* This function gives the context of a part. A context that the evaluator gives keeps only the capabilities of the
|
|
4001
|
+
* parent (CAP.NARROW). Then the rules of the kind narrow it (CAP.CONTRACT).
|
|
4002
|
+
*/
|
|
4003
|
+
function contextOfPart(seen, slot, parent, given) {
|
|
4004
|
+
const base = given === void 0 || given === parent ? parent : {
|
|
4005
|
+
...given,
|
|
4006
|
+
caps: given.caps.filter((cap) => permits(parent.caps, cap))
|
|
4007
|
+
};
|
|
4008
|
+
const caps = capsOfPart(seen.kind?.caps, slot.path, base.caps);
|
|
4009
|
+
return caps === base.caps ? base : {
|
|
4010
|
+
...base,
|
|
4011
|
+
caps
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
function headerOf(item) {
|
|
4015
|
+
return isEnvelope(item) && typeof item.name === "string" ? item.name : void 0;
|
|
4016
|
+
}
|
|
4017
|
+
function partAt(seen, at, slot, given) {
|
|
4018
|
+
return {
|
|
4019
|
+
value: slot.item,
|
|
4020
|
+
place: at.place.concat(slot.path),
|
|
4021
|
+
declared: slot.of,
|
|
4022
|
+
depth: at.depth + 1,
|
|
4023
|
+
address: slot.address === true,
|
|
4024
|
+
ctx: contextOfPart(seen, slot, at.ctx, given)
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
/** This function resolves a path with the full algorithm of `resolve`, not with its short path (DEREF.QUICK). */
|
|
4028
|
+
function resolveFully(machine, ctx, path, options) {
|
|
4029
|
+
const found = resolve(machine, ctx, path, { hops: true });
|
|
4030
|
+
return options?.hops === true ? found : {
|
|
4031
|
+
...found,
|
|
4032
|
+
hops: []
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
/** This function records an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). */
|
|
4036
|
+
function record(env, ctx, kind, payload, place) {
|
|
4037
|
+
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
4038
|
+
const text = keys.join(".");
|
|
4039
|
+
const machine = env.machine;
|
|
4040
|
+
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 });
|
|
4041
|
+
env.effects.push({
|
|
4042
|
+
index: env.effects.length,
|
|
4043
|
+
kind: text,
|
|
4044
|
+
path: keys,
|
|
4045
|
+
payload,
|
|
4046
|
+
node: -1,
|
|
4047
|
+
at: 0,
|
|
4048
|
+
place,
|
|
4049
|
+
context: ctx
|
|
4050
|
+
});
|
|
4051
|
+
}
|
|
4052
|
+
/** This function evaluates a document. Each error gets the place of the document where it started (K5). */
|
|
4053
|
+
function evaluateAt(env, at) {
|
|
4054
|
+
const seen = examine(env, at);
|
|
4055
|
+
if (!("doc" in seen)) return seen;
|
|
4056
|
+
return evaluateSeen(env, seen, at);
|
|
4057
|
+
}
|
|
4058
|
+
function evaluateSeen(env, seen, at) {
|
|
4059
|
+
const { chosen } = seen;
|
|
4060
|
+
const { place } = at;
|
|
4061
|
+
if ("gap" in chosen) return fail("#NOEVAL", chosen.gap, { place });
|
|
4062
|
+
if ("runner" in chosen) return drive(runSeen(env, seen, at, chosen.runner), place);
|
|
4063
|
+
const input = evalInput(env, seen, at);
|
|
4064
|
+
try {
|
|
4065
|
+
return placed(chosen.evaluator(input), place);
|
|
4066
|
+
} catch (error) {
|
|
4067
|
+
return placed(thrown(error, `the evaluator ${seen.name}`), place);
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
/** This function gives the steps of a document for a runner. Nothing happens before the first step. */
|
|
4071
|
+
function* stepsAt(env, at) {
|
|
4072
|
+
const seen = examine(env, at);
|
|
4073
|
+
if (!("doc" in seen)) return seen;
|
|
4074
|
+
if ("runner" in seen.chosen) return yield* runSeen(env, seen, at, seen.chosen.runner);
|
|
4075
|
+
return evaluateSeen(env, seen, at);
|
|
4076
|
+
}
|
|
4077
|
+
function* runSeen(env, seen, at, runner) {
|
|
4078
|
+
try {
|
|
4079
|
+
return placed(yield* runner(runInput(env, seen, at)), at.place);
|
|
4080
|
+
} catch (error) {
|
|
4081
|
+
return placed(thrown(error, `the runner ${seen.name}`), at.place);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
/** This function drives steps that cannot wait. A step that waits gives `#CALC!` at the document (RUN.WAIT). */
|
|
4085
|
+
function drive(steps, place) {
|
|
4086
|
+
const step = steps.next();
|
|
4087
|
+
if (step.done === true) return step.value;
|
|
4088
|
+
step.value.catch(() => void 0);
|
|
4089
|
+
const waited = fail("#CALC!", "A part waited, and its parent cannot wait.", { place });
|
|
4090
|
+
steps.return(waited);
|
|
4091
|
+
return waited;
|
|
4092
|
+
}
|
|
4093
|
+
function evalInput(env, seen, at) {
|
|
4094
|
+
const { split, doc } = seen;
|
|
4095
|
+
const parts = split.parts.map((slot) => {
|
|
4096
|
+
const name = headerOf(slot.item);
|
|
4097
|
+
return {
|
|
4098
|
+
key: slot.key,
|
|
4099
|
+
path: slot.path,
|
|
4100
|
+
...name === void 0 ? {} : { name },
|
|
4101
|
+
evaluate: (given) => evaluateAt(env, partAt(seen, at, slot, given))
|
|
4102
|
+
};
|
|
4103
|
+
});
|
|
4104
|
+
const fill = (items) => split.parts.length === 0 ? doc.value : split.fill(items);
|
|
4105
|
+
return {
|
|
4106
|
+
kind: seen.kindText,
|
|
4107
|
+
typePath: seen.typePath,
|
|
4108
|
+
doc,
|
|
4109
|
+
value: fill(parts),
|
|
4110
|
+
data: split.data,
|
|
4111
|
+
parts,
|
|
4112
|
+
context: at.ctx,
|
|
4113
|
+
place: at.place,
|
|
4114
|
+
...at.address ? { address: true } : {},
|
|
4115
|
+
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
4116
|
+
effect: (kind, payload) => record(env, at.ctx, kind, payload, at.place) ?? ok(payload),
|
|
4117
|
+
fill
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
function runInput(env, seen, at) {
|
|
4121
|
+
const { split, doc } = seen;
|
|
4122
|
+
const parts = split.parts.map((slot) => {
|
|
4123
|
+
const name = headerOf(slot.item);
|
|
4124
|
+
return {
|
|
4125
|
+
key: slot.key,
|
|
4126
|
+
path: slot.path,
|
|
4127
|
+
...name === void 0 ? {} : { name },
|
|
4128
|
+
evaluate: (given) => stepsAt(env, partAt(seen, at, slot, given))
|
|
4129
|
+
};
|
|
4130
|
+
});
|
|
4131
|
+
const fill = (items) => split.parts.length === 0 ? doc.value : split.fill(items);
|
|
4132
|
+
return {
|
|
4133
|
+
kind: seen.kindText,
|
|
4134
|
+
typePath: seen.typePath,
|
|
4135
|
+
doc,
|
|
4136
|
+
value: fill(parts),
|
|
4137
|
+
data: split.data,
|
|
4138
|
+
parts,
|
|
4139
|
+
context: at.ctx,
|
|
4140
|
+
place: at.place,
|
|
4141
|
+
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
4142
|
+
effect: function* (kind, payload) {
|
|
4143
|
+
return record(env, at.ctx, kind, payload, at.place) ?? ok(payload);
|
|
4144
|
+
},
|
|
4145
|
+
all: (list, options) => together(list, options),
|
|
4146
|
+
fill
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* This function evaluates parts for `all` (RUN.ALL). The parts start in their order, up to the limit, and each part
|
|
4151
|
+
* goes on until it ends or waits. In an evaluation, a part that waits does not end, thus it keeps its place in the
|
|
4152
|
+
* limit. With `failFast`, the first failure stops the parts that wait, and the later parts do not start
|
|
4153
|
+
* (RUN.FAIL-FAST). When no part failed and a part waits, the steps wait, and the drive gives `#CALC!`.
|
|
4154
|
+
*/
|
|
4155
|
+
function* together(list, options = {}) {
|
|
4156
|
+
const limit = Math.max(1, options.concurrency ?? list.length);
|
|
4157
|
+
const results = list.map(() => void 0);
|
|
4158
|
+
const waiting = [];
|
|
4159
|
+
let failed = false;
|
|
4160
|
+
for (let index = 0; index < list.length && !failed && waiting.length < limit; index += 1) {
|
|
4161
|
+
let result;
|
|
4162
|
+
try {
|
|
4163
|
+
const steps = list[index].evaluate();
|
|
4164
|
+
const step = steps.next();
|
|
4165
|
+
if (step.done === true) result = step.value;
|
|
4166
|
+
else waiting.push({
|
|
4167
|
+
steps,
|
|
4168
|
+
promise: step.value
|
|
4169
|
+
});
|
|
4170
|
+
} catch (error) {
|
|
4171
|
+
result = thrown(error, "a part");
|
|
3794
4172
|
}
|
|
4173
|
+
results[index] = result;
|
|
4174
|
+
if (result !== void 0 && options.failFast === true && !result.ok) failed = true;
|
|
4175
|
+
}
|
|
4176
|
+
if (failed) {
|
|
4177
|
+
const stopped = fail("#ABORT", "The part stopped: a part before it failed, and the parts run with failFast.");
|
|
4178
|
+
for (const item of waiting) try {
|
|
4179
|
+
item.steps.return(stopped);
|
|
4180
|
+
} catch {}
|
|
4181
|
+
return results.map((result) => result ?? stopped);
|
|
4182
|
+
}
|
|
4183
|
+
if (waiting.length > 0) yield Promise.race(waiting.map((item) => item.promise));
|
|
4184
|
+
return results;
|
|
4185
|
+
}
|
|
4186
|
+
/**
|
|
4187
|
+
* This function evaluates a document by the rules, without a compilation. It is the reference of the plans: a plan,
|
|
4188
|
+
* a traced plan and the second tier of a language must give its results. It covers evaluations, not runs: an effect
|
|
4189
|
+
* gives its payload, and a part that waits gives `#CALC!`.
|
|
4190
|
+
*
|
|
4191
|
+
* @param machine - The world and the code of the evaluators.
|
|
4192
|
+
* @param doc - The document.
|
|
4193
|
+
* @param context - The names and the capabilities at the root.
|
|
4194
|
+
* @param options - The type of a bare root value, the definitions of compiled code, and a list for the effects.
|
|
4195
|
+
* @returns The result.
|
|
4196
|
+
*/
|
|
4197
|
+
function reference(machine, doc, context = makeContext(), options = {}) {
|
|
4198
|
+
const env = {
|
|
4199
|
+
machine,
|
|
4200
|
+
definitions: options.definitions ?? {},
|
|
4201
|
+
effects: options.effects ?? [],
|
|
4202
|
+
maxDepth: machine.maxDepth ?? 1e3
|
|
3795
4203
|
};
|
|
4204
|
+
const ctx = context.world === null ? {
|
|
4205
|
+
...context,
|
|
4206
|
+
world: worldThing(machine.world)
|
|
4207
|
+
} : context;
|
|
4208
|
+
try {
|
|
4209
|
+
return evaluateAt(env, {
|
|
4210
|
+
value: doc,
|
|
4211
|
+
place: [],
|
|
4212
|
+
declared: options.declaredType,
|
|
4213
|
+
depth: 0,
|
|
4214
|
+
address: false,
|
|
4215
|
+
ctx
|
|
4216
|
+
});
|
|
4217
|
+
} catch (error) {
|
|
4218
|
+
return placed(thrown(error, "the reference"), []);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
const CLOSURE$1 = Symbol.for("coeval.closure");
|
|
4222
|
+
/** This function sets an own field, also the field `__proto__` (SHAPE.PROTO). */
|
|
4223
|
+
function setField(target, key, value) {
|
|
4224
|
+
Object.defineProperty(target, key, {
|
|
4225
|
+
value,
|
|
4226
|
+
enumerable: true,
|
|
4227
|
+
writable: true,
|
|
4228
|
+
configurable: true
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
function partNamed(parts, key) {
|
|
4232
|
+
return parts.find((part) => part.path.length === 1 && part.path[0] === key);
|
|
4233
|
+
}
|
|
4234
|
+
function partsUnder(parts, key) {
|
|
4235
|
+
return parts.filter((part) => part.path.length === 2 && part.path[0] === key);
|
|
4236
|
+
}
|
|
4237
|
+
/**
|
|
4238
|
+
* This function gives the definitions of the formula kit for one profile. Each construct is a function evaluator in
|
|
4239
|
+
* the most direct style. It shares no code with the compilers of `formulaEvaluators`, thus the two readings of the
|
|
4240
|
+
* rules can disagree, and a test finds it.
|
|
4241
|
+
*
|
|
4242
|
+
* @param profile - The operations and the rules of the language.
|
|
4243
|
+
* @returns The definitions, by evaluator name.
|
|
4244
|
+
*/
|
|
4245
|
+
function formulaDefinitions(profile = {}) {
|
|
4246
|
+
const truthy = profile.truthy ?? ((value) => typeof value === "boolean" ? value : void 0);
|
|
4247
|
+
const opOf = (name) => typeof profile.ops === "function" ? profile.ops(name) : own(profile.ops, name);
|
|
4248
|
+
const noTruth = (what) => fail("#VALUE!", `The ${what} must give a truth value.`);
|
|
4249
|
+
/** FORMULA.LIST, FORMULA.OPS and FORMULA.COMBINE: the values of parts in order, or the error. */
|
|
4250
|
+
const valuesOf = (parts, skip) => {
|
|
4251
|
+
const values = [];
|
|
4252
|
+
const errors = [];
|
|
4253
|
+
for (const part of parts) {
|
|
4254
|
+
const result = part.evaluate();
|
|
4255
|
+
if (result.ok) values.push(result.value);
|
|
4256
|
+
else if (skip?.(result.error) === true) values.push(SKIP);
|
|
4257
|
+
else if (profile.combine === void 0) return result;
|
|
4258
|
+
else errors.push(result.error);
|
|
4259
|
+
}
|
|
4260
|
+
if (errors.length === 0 || profile.combine === void 0) return { values };
|
|
4261
|
+
return {
|
|
4262
|
+
ok: false,
|
|
4263
|
+
error: errors.length === 1 ? errors[0] : profile.combine(errors)
|
|
4264
|
+
};
|
|
4265
|
+
};
|
|
4266
|
+
const lit = ({ doc }) => ok(doc.value);
|
|
4267
|
+
const ref = ({ doc, parts, fill, address, context, resolve: walk }) => {
|
|
4268
|
+
const path = doc.value;
|
|
4269
|
+
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\"].");
|
|
4270
|
+
let steps = path;
|
|
4271
|
+
if (parts.length > 0) {
|
|
4272
|
+
const all = valuesOfStrict(parts);
|
|
4273
|
+
if (!("values" in all)) return all;
|
|
4274
|
+
steps = fill(all.values);
|
|
4275
|
+
}
|
|
4276
|
+
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
|
|
4277
|
+
const bound = head === void 0 ? void 0 : own(context.names, head);
|
|
4278
|
+
if (bound instanceof Held) return {
|
|
4279
|
+
ok: false,
|
|
4280
|
+
error: bound.error
|
|
4281
|
+
};
|
|
4282
|
+
const found = walk(steps);
|
|
4283
|
+
if (!found.ok) return fail(found.code, found.reason);
|
|
4284
|
+
if (address === true) return ok(found.thing);
|
|
4285
|
+
const reached = valueOfThing(found.thing);
|
|
4286
|
+
if (reached instanceof Held) return {
|
|
4287
|
+
ok: false,
|
|
4288
|
+
error: reached.error
|
|
4289
|
+
};
|
|
4290
|
+
if (profile.missing?.(reached) === true) return fail("#N/A", "The path reaches no value.");
|
|
4291
|
+
return ok(reached);
|
|
4292
|
+
};
|
|
4293
|
+
const step = ({ doc, parts, fill }) => {
|
|
4294
|
+
if (parts.length === 0) return ok(doc);
|
|
4295
|
+
const all = valuesOfStrict(parts);
|
|
4296
|
+
return "values" in all ? ok({
|
|
4297
|
+
...doc,
|
|
4298
|
+
value: fill(all.values)
|
|
4299
|
+
}) : all;
|
|
4300
|
+
};
|
|
4301
|
+
const record = ({ parts }) => {
|
|
4302
|
+
const all = valuesOf(parts, profile.omit);
|
|
4303
|
+
if (!("values" in all)) return all;
|
|
4304
|
+
const out = {};
|
|
4305
|
+
parts.forEach((part, index) => {
|
|
4306
|
+
const value = all.values[index];
|
|
4307
|
+
if (value !== SKIP) setField(out, String(part.path[0]), value);
|
|
4308
|
+
});
|
|
4309
|
+
return ok(out);
|
|
4310
|
+
};
|
|
4311
|
+
const list = ({ parts }) => {
|
|
4312
|
+
const all = valuesOf(parts);
|
|
4313
|
+
return "values" in all ? ok([...all.values]) : all;
|
|
4314
|
+
};
|
|
4315
|
+
const letIn = ({ parts, context }) => {
|
|
4316
|
+
const names = {};
|
|
4317
|
+
for (const key of Object.keys(context.names)) setField(names, key, context.names[key]);
|
|
4318
|
+
for (const binding of partsUnder(parts, "bind")) {
|
|
4319
|
+
const result = binding.evaluate();
|
|
4320
|
+
setField(names, String(binding.path[1]), result.ok ? result.value : new Held(result.error));
|
|
4321
|
+
}
|
|
4322
|
+
return partNamed(parts, "body").evaluate({
|
|
4323
|
+
...context,
|
|
4324
|
+
names
|
|
4325
|
+
});
|
|
4326
|
+
};
|
|
4327
|
+
const fn = ({ data, parts, context }) => {
|
|
4328
|
+
const params = isRecord(data) ? data["params"] : void 0;
|
|
4329
|
+
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.");
|
|
4330
|
+
const names = params;
|
|
4331
|
+
const body = partNamed(parts, "body");
|
|
4332
|
+
const closure = (...args) => {
|
|
4333
|
+
const scope = {};
|
|
4334
|
+
for (const key of Object.keys(context.names)) setField(scope, key, context.names[key]);
|
|
4335
|
+
names.forEach((name, index) => setField(scope, name, args[index]));
|
|
4336
|
+
const result = body.evaluate({
|
|
4337
|
+
...context,
|
|
4338
|
+
names: scope
|
|
4339
|
+
});
|
|
4340
|
+
if (!result.ok) throw new Failure(result.error);
|
|
4341
|
+
return result.value;
|
|
4342
|
+
};
|
|
4343
|
+
Object.defineProperty(closure, CLOSURE$1, { value: { params: names } });
|
|
4344
|
+
return ok(closure);
|
|
4345
|
+
};
|
|
4346
|
+
const call = ({ parts }) => {
|
|
4347
|
+
const target = partNamed(parts, "fn").evaluate();
|
|
4348
|
+
if (!target.ok) return target;
|
|
4349
|
+
if (typeof target.value !== "function") return fail("#VALUE!", "The fn of call must give a function.");
|
|
4350
|
+
const all = valuesOfStrict(partsUnder(parts, "args"));
|
|
4351
|
+
if (!("values" in all)) return all;
|
|
4352
|
+
try {
|
|
4353
|
+
return ok(target.value(...all.values));
|
|
4354
|
+
} catch (error) {
|
|
4355
|
+
return thrown(error, "the function of call");
|
|
4356
|
+
}
|
|
4357
|
+
};
|
|
4358
|
+
const choice = ({ parts }) => {
|
|
4359
|
+
const test = partNamed(parts, "test").evaluate();
|
|
4360
|
+
if (!test.ok) return test;
|
|
4361
|
+
const truth = truthy(test.value);
|
|
4362
|
+
if (truth === void 0) return noTruth("test of if");
|
|
4363
|
+
if (truth) return partNamed(parts, "then").evaluate();
|
|
4364
|
+
const other = partNamed(parts, "else");
|
|
4365
|
+
if (other !== void 0) return other.evaluate();
|
|
4366
|
+
return ok(Object.hasOwn(profile, "otherwise") ? profile.otherwise : null);
|
|
4367
|
+
};
|
|
4368
|
+
/** FORMULA.LOGIC: `and` stops at the first false term, and `or` at the first true term. */
|
|
4369
|
+
const logic = (stopAt, what) => ({ parts }) => {
|
|
4370
|
+
let last = ok(!stopAt);
|
|
4371
|
+
for (const part of parts) {
|
|
4372
|
+
last = part.evaluate();
|
|
4373
|
+
if (!last.ok) return last;
|
|
4374
|
+
const truth = truthy(last.value);
|
|
4375
|
+
if (truth === void 0) return noTruth(what);
|
|
4376
|
+
if (truth === stopAt) return profile.logic === "value" ? last : ok(stopAt);
|
|
4377
|
+
}
|
|
4378
|
+
return profile.logic === "value" ? last : ok(!stopAt);
|
|
4379
|
+
};
|
|
4380
|
+
const op = ({ typePath, parts }) => {
|
|
4381
|
+
const name = typePath.slice(1).join(".");
|
|
4382
|
+
const lazy = own(profile.lazy, name);
|
|
4383
|
+
if (lazy !== void 0) try {
|
|
4384
|
+
return lazy(parts.map((part) => () => part.evaluate()));
|
|
4385
|
+
} catch (error) {
|
|
4386
|
+
return thrown(error, `the operation ${name}`);
|
|
4387
|
+
}
|
|
4388
|
+
const apply = opOf(name);
|
|
4389
|
+
if (apply === void 0) return fail("#NAME?", `The language has no operation ${name}.`);
|
|
4390
|
+
const all = valuesOf(parts);
|
|
4391
|
+
if (!("values" in all)) return all;
|
|
4392
|
+
try {
|
|
4393
|
+
const out = apply(...all.values);
|
|
4394
|
+
return profile.check?.(out, name) ?? ok(out);
|
|
4395
|
+
} catch (error) {
|
|
4396
|
+
return thrown(error, `the operation ${name}`);
|
|
4397
|
+
}
|
|
4398
|
+
};
|
|
4399
|
+
const ext = ({ typePath, doc, context }) => {
|
|
4400
|
+
const name = typePath.slice(1).join(".");
|
|
4401
|
+
const handler = own(profile.ext, name) ?? own(STD_EXT, name);
|
|
4402
|
+
if (handler === void 0) return fail("#NAME?", `The language has no extension ${name}.`);
|
|
4403
|
+
try {
|
|
4404
|
+
return handler(doc.value, context);
|
|
4405
|
+
} catch (error) {
|
|
4406
|
+
return thrown(error, `the extension ${name}`);
|
|
4407
|
+
}
|
|
4408
|
+
};
|
|
4409
|
+
return { evaluators: {
|
|
4410
|
+
"formula.lit": lit,
|
|
4411
|
+
"formula.ref": ref,
|
|
4412
|
+
"formula.step": step,
|
|
4413
|
+
"formula.record": record,
|
|
4414
|
+
"formula.list": list,
|
|
4415
|
+
"formula.let": letIn,
|
|
4416
|
+
"formula.fn": fn,
|
|
4417
|
+
"formula.call": call,
|
|
4418
|
+
"formula.if": choice,
|
|
4419
|
+
"formula.and": logic(false, "term of and"),
|
|
4420
|
+
"formula.or": logic(true, "term of or"),
|
|
4421
|
+
"formula.op": op,
|
|
4422
|
+
"formula.ext": ext
|
|
4423
|
+
} };
|
|
4424
|
+
}
|
|
4425
|
+
/** The mark of a field that the profile leaves out of a record (FORMULA.RECORD). */
|
|
4426
|
+
const SKIP = Symbol("skip");
|
|
4427
|
+
/** The values of parts in order. The first error stops them, also with `combine` (FORMULA.STEPS, FORMULA.FN). */
|
|
4428
|
+
function valuesOfStrict(parts) {
|
|
4429
|
+
const values = [];
|
|
4430
|
+
for (const part of parts) {
|
|
4431
|
+
const result = part.evaluate();
|
|
4432
|
+
if (!result.ok) return result;
|
|
4433
|
+
values.push(result.value);
|
|
4434
|
+
}
|
|
4435
|
+
return { values };
|
|
4436
|
+
}
|
|
4437
|
+
//#endregion
|
|
4438
|
+
//#region src/agree.ts
|
|
4439
|
+
/**
|
|
4440
|
+
* This function tells if two values are the same for `agree`. Lists and plain records compare item by item and field
|
|
4441
|
+
* by field, and the order of the fields does not matter. Each other leaf compares with `Object.is`, thus `-0` is not
|
|
4442
|
+
* `0`, and `NaN` is `NaN`. The identity of a list or of a plain record is not compared.
|
|
4443
|
+
*
|
|
4444
|
+
* Two closures of `fn` are the same when they have the same parameters. Two `Held` values are the same when their
|
|
4445
|
+
* errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
|
|
4446
|
+
* tells otherwise.
|
|
4447
|
+
*
|
|
4448
|
+
* `docEquals` uses the same rules for lists, plain records and the other objects. It has no rules for closures, for
|
|
4449
|
+
* `Held` values and for a rule of the caller. Thus two closures are equal there only when they are the same function.
|
|
4450
|
+
*
|
|
4451
|
+
* @param a - The first value.
|
|
4452
|
+
* @param b - The second value.
|
|
4453
|
+
* @param equal - The rule of the caller for other values. It answers first, and `undefined` gives the rules above.
|
|
4454
|
+
* @returns `true` when the values are the same.
|
|
4455
|
+
*/
|
|
4456
|
+
function sameValue(a, b, equal) {
|
|
4457
|
+
const stack = [[a, b]];
|
|
4458
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4459
|
+
while (stack.length > 0) {
|
|
4460
|
+
const [x, y] = stack.pop();
|
|
4461
|
+
if (Object.is(x, y)) continue;
|
|
4462
|
+
const told = equal?.(x, y);
|
|
4463
|
+
if (told !== void 0) {
|
|
4464
|
+
if (told) continue;
|
|
4465
|
+
return false;
|
|
4466
|
+
}
|
|
4467
|
+
if (typeof x !== "object" && typeof x !== "function") return false;
|
|
4468
|
+
if (typeof y !== "object" && typeof y !== "function") return false;
|
|
4469
|
+
if (x === null || y === null) return false;
|
|
4470
|
+
const pairs = seen.get(x) ?? /* @__PURE__ */ new Set();
|
|
4471
|
+
if (pairs.has(y)) continue;
|
|
4472
|
+
pairs.add(y);
|
|
4473
|
+
seen.set(x, pairs);
|
|
4474
|
+
if (isClosure(x) || isClosure(y)) {
|
|
4475
|
+
if (!(isClosure(x) && isClosure(y))) return false;
|
|
4476
|
+
stack.push([closureParams(x), closureParams(y)]);
|
|
4477
|
+
continue;
|
|
4478
|
+
}
|
|
4479
|
+
if (x instanceof Held || y instanceof Held) {
|
|
4480
|
+
if (!(x instanceof Held && y instanceof Held) || errorDiffers(x.error, y.error, false) !== void 0) return false;
|
|
4481
|
+
continue;
|
|
4482
|
+
}
|
|
4483
|
+
if (Array.isArray(x) || Array.isArray(y)) {
|
|
4484
|
+
if (!Array.isArray(x) || !Array.isArray(y) || x.length !== y.length) return false;
|
|
4485
|
+
for (let index = 0; index < x.length; index += 1) stack.push([x[index], y[index]]);
|
|
4486
|
+
continue;
|
|
4487
|
+
}
|
|
4488
|
+
if (!isRecord(x) || !isRecord(y)) return false;
|
|
4489
|
+
const keys = Object.keys(x);
|
|
4490
|
+
if (keys.length !== Object.keys(y).length) return false;
|
|
4491
|
+
for (const key of keys) {
|
|
4492
|
+
if (!Object.hasOwn(y, key)) return false;
|
|
4493
|
+
stack.push([x[key], y[key]]);
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
return true;
|
|
4497
|
+
}
|
|
4498
|
+
const CLOSURE = Symbol.for("coeval.closure");
|
|
4499
|
+
function closureParams(fn) {
|
|
4500
|
+
return fn[CLOSURE]?.params;
|
|
4501
|
+
}
|
|
4502
|
+
function codeAndPlace(error) {
|
|
4503
|
+
return error.place === void 0 ? error.code : `${error.code} at [${error.place.join(", ")}]`;
|
|
4504
|
+
}
|
|
4505
|
+
/** This function gives the difference of two errors, or `undefined` when they agree. */
|
|
4506
|
+
function errorDiffers(a, b, strict) {
|
|
4507
|
+
if (a.code !== b.code) return `The plan gives ${codeAndPlace(a)}, and the tier gives ${codeAndPlace(b)}.`;
|
|
4508
|
+
if (strict && (a.place === void 0 || b.place === void 0)) return `An error ${a.code} has no place.`;
|
|
4509
|
+
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.`;
|
|
4510
|
+
if (strict && a.reason !== b.reason) return `The error ${a.code} has another reason in the tier: "${b.reason}" for "${a.reason}".`;
|
|
4511
|
+
}
|
|
4512
|
+
/** A host stack overflow is the one permitted difference (VALUE.DEPTH): the tiers use different numbers of frames. */
|
|
4513
|
+
function overflowed(result) {
|
|
4514
|
+
return !result.ok && result.error.code === "#DEPTH!" && isStackOverflow(result.error.cause);
|
|
4515
|
+
}
|
|
4516
|
+
/** This function gives the difference of two results, or `undefined` when they agree. */
|
|
4517
|
+
function resultDiffers(a, b, options) {
|
|
4518
|
+
if (a.ok && b.ok) return sameValue(a.value, b.value, options.equal) ? void 0 : "The plan and the tier give different values.";
|
|
4519
|
+
if (!a.ok && !b.ok) return errorDiffers(a.error, b.error, options.strict === true);
|
|
4520
|
+
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.`;
|
|
4521
|
+
}
|
|
4522
|
+
/** This function gives the difference of two lists of effects, or `undefined` when they agree (EFFECT.ORDER). */
|
|
4523
|
+
function effectsDiffer(a, b, options) {
|
|
4524
|
+
if (a.length !== b.length) return `The plan causes ${a.length} effects, and the tier causes ${b.length}.`;
|
|
4525
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
4526
|
+
const x = a[index];
|
|
4527
|
+
const y = b[index];
|
|
4528
|
+
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.`;
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
/**
|
|
4532
|
+
* This function evaluates a document with a plan and with other tiers, and compares the results (PLAN.AGREES). Two
|
|
4533
|
+
* results agree when they have the same value by `sameValue`, or errors with the same code, and the same place when
|
|
4534
|
+
* both have one. The effects must have the same kinds, places and payloads, in the same order. A language gives its
|
|
4535
|
+
* own tier with the option `other`.
|
|
4536
|
+
*
|
|
4537
|
+
* @param machine - The world and the code of the evaluators.
|
|
4538
|
+
* @param doc - The document.
|
|
4539
|
+
* @param context - The names and the capabilities at the root.
|
|
4540
|
+
* @param options - The tiers, the definitions for the reference, the rules of the comparison and the second tier.
|
|
4541
|
+
* @returns The result of the plan, or the first tier that disagrees and the reason.
|
|
4542
|
+
*/
|
|
4543
|
+
function agree(machine, doc, context = makeContext(), options = {}) {
|
|
4544
|
+
const declared = options.declaredType === void 0 ? {} : { declaredType: options.declaredType };
|
|
4545
|
+
const observe = options.observe;
|
|
4546
|
+
observe?.();
|
|
4547
|
+
const plan = prepare(machine, doc, declared);
|
|
4548
|
+
const effects = [];
|
|
4549
|
+
const result = plan.evaluate(context, { effects });
|
|
4550
|
+
const saw = observe?.();
|
|
4551
|
+
const differs = (other, otherEffects) => {
|
|
4552
|
+
if (overflowed(result) || overflowed(other)) return void 0;
|
|
4553
|
+
const why = resultDiffers(result, other, options) ?? (otherEffects === void 0 ? void 0 : effectsDiffer(effects, otherEffects, options));
|
|
4554
|
+
if (why !== void 0 || observe === void 0) return why;
|
|
4555
|
+
return sameValue(saw, observe(), options.equal) ? void 0 : "The host saw other calls in the tier.";
|
|
4556
|
+
};
|
|
4557
|
+
for (const tier of options.tiers ?? [
|
|
4558
|
+
"reference",
|
|
4559
|
+
"traced",
|
|
4560
|
+
"again"
|
|
4561
|
+
]) {
|
|
4562
|
+
let other;
|
|
4563
|
+
let otherEffects = [];
|
|
4564
|
+
if (tier === "reference") other = reference(machine, doc, context, {
|
|
4565
|
+
...declared,
|
|
4566
|
+
effects: otherEffects,
|
|
4567
|
+
...options.definitions === void 0 ? {} : { definitions: options.definitions }
|
|
4568
|
+
});
|
|
4569
|
+
else if (tier === "traced") {
|
|
4570
|
+
const traced = evaluate(machine, doc, context, options.declaredType);
|
|
4571
|
+
other = traced.result;
|
|
4572
|
+
otherEffects = [...traced.effects];
|
|
4573
|
+
} else other = plan.evaluate(context, { effects: otherEffects });
|
|
4574
|
+
const why = differs(other, otherEffects);
|
|
4575
|
+
if (why !== void 0) return {
|
|
4576
|
+
ok: false,
|
|
4577
|
+
tier,
|
|
4578
|
+
plan: result,
|
|
4579
|
+
other,
|
|
4580
|
+
reason: why
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
if (options.other !== void 0) {
|
|
4584
|
+
let given;
|
|
4585
|
+
try {
|
|
4586
|
+
given = options.other(doc, context);
|
|
4587
|
+
} catch (error) {
|
|
4588
|
+
given = thrown(error, "the tier");
|
|
4589
|
+
}
|
|
4590
|
+
const other = "result" in given ? given.result : given;
|
|
4591
|
+
const why = differs(other, "result" in given ? given.effects : void 0);
|
|
4592
|
+
if (why !== void 0) return {
|
|
4593
|
+
ok: false,
|
|
4594
|
+
tier: "other",
|
|
4595
|
+
plan: result,
|
|
4596
|
+
other,
|
|
4597
|
+
reason: why
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
return {
|
|
4601
|
+
ok: true,
|
|
4602
|
+
result,
|
|
4603
|
+
effects
|
|
4604
|
+
};
|
|
4605
|
+
}
|
|
4606
|
+
//#endregion
|
|
4607
|
+
//#region src/generate.ts
|
|
4608
|
+
/**
|
|
4609
|
+
* This function makes a source of random numbers with a seed (the algorithm mulberry32). It needs no package.
|
|
4610
|
+
*
|
|
4611
|
+
* @param seed - The seed: a whole number.
|
|
4612
|
+
* @returns The source.
|
|
4613
|
+
*/
|
|
4614
|
+
function random(seed) {
|
|
4615
|
+
let state = seed >>> 0;
|
|
4616
|
+
const next = () => {
|
|
4617
|
+
state = state + 1831565813 >>> 0;
|
|
4618
|
+
let t = state;
|
|
4619
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
4620
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
4621
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
4622
|
+
};
|
|
4623
|
+
const int = (below) => Math.floor(next() * below);
|
|
4624
|
+
return {
|
|
4625
|
+
next,
|
|
4626
|
+
int,
|
|
4627
|
+
chance: (p) => next() < p,
|
|
4628
|
+
pick: (items) => items[int(items.length)]
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
/**
|
|
4632
|
+
* This function lists the types of a world: each kind and each alias, also in a namespace. A namespace with a
|
|
4633
|
+
* fallback gives no types, because each key names a kind.
|
|
4634
|
+
*
|
|
4635
|
+
* @param world - The world.
|
|
4636
|
+
* @returns The keys of each type.
|
|
4637
|
+
*/
|
|
4638
|
+
function typesOf(world) {
|
|
4639
|
+
const out = [];
|
|
4640
|
+
const visit = (entries, prefix) => {
|
|
4641
|
+
for (const key of Object.keys(entries)) {
|
|
4642
|
+
const entry = entries[key];
|
|
4643
|
+
if (isKind(entry) || isAlias(entry)) out.push([...prefix, key]);
|
|
4644
|
+
else if (isNamespace(entry)) visit(entry.kinds, [...prefix, key]);
|
|
4645
|
+
}
|
|
4646
|
+
};
|
|
4647
|
+
visit(world.kinds, []);
|
|
4648
|
+
return out;
|
|
4649
|
+
}
|
|
4650
|
+
const NAMES = [
|
|
4651
|
+
"a",
|
|
4652
|
+
"b",
|
|
4653
|
+
"x",
|
|
4654
|
+
"n",
|
|
4655
|
+
"last",
|
|
4656
|
+
"__proto__"
|
|
4657
|
+
];
|
|
4658
|
+
const FIELDS = [
|
|
4659
|
+
"a",
|
|
4660
|
+
"b",
|
|
4661
|
+
"c",
|
|
4662
|
+
"1",
|
|
4663
|
+
"__proto__"
|
|
4664
|
+
];
|
|
4665
|
+
const KEYS = [
|
|
4666
|
+
"a",
|
|
4667
|
+
"b",
|
|
4668
|
+
"x",
|
|
4669
|
+
0,
|
|
4670
|
+
1,
|
|
4671
|
+
"0",
|
|
4672
|
+
"length",
|
|
4673
|
+
"type",
|
|
4674
|
+
"value",
|
|
4675
|
+
"parent"
|
|
4676
|
+
];
|
|
4677
|
+
/** This function tells if a kind can be a leaf: it needs no part. */
|
|
4678
|
+
function leafKind(kind) {
|
|
4679
|
+
const shape = kind?.parts;
|
|
4680
|
+
return shape === void 0 || shape.shape === "none" || shape.shape === "list" || shape.shape === "map" || shape.shape === "text" || shape.shape === "number" || shape.shape === "one";
|
|
4681
|
+
}
|
|
4682
|
+
/**
|
|
4683
|
+
* This function builds a random document for a world from the contracts of its kinds. It reads the shapes of the
|
|
4684
|
+
* parts, the declared types, the permitted kinds and the names that the scope rules bind. Some choices are malformed
|
|
4685
|
+
* on purpose, for example an unknown kind or an unknown name under a namespace with a fallback. Other examples are a
|
|
4686
|
+
* value without its shape, a bare value without a type and a name that nothing binds. The depth and the size have
|
|
4687
|
+
* limits.
|
|
4688
|
+
*
|
|
4689
|
+
* @param world - The world.
|
|
4690
|
+
* @param options - The seed, the types, the limits and the data of the leaves.
|
|
4691
|
+
* @returns The document, as JSON.
|
|
4692
|
+
*/
|
|
4693
|
+
function generate(world, options) {
|
|
4694
|
+
const rng = random(options.seed);
|
|
4695
|
+
const types = options.types ?? typesOf(world);
|
|
4696
|
+
const maxDepth = options.maxDepth ?? 4;
|
|
4697
|
+
const maxSize = options.maxSize ?? 24;
|
|
4698
|
+
const malformed = options.malformed ?? .05;
|
|
4699
|
+
const keys = options.keys ?? KEYS;
|
|
4700
|
+
const stepTypes = types.filter((type) => {
|
|
4701
|
+
const found = lookupKind(world, type);
|
|
4702
|
+
return found.ok && Object.hasOwn(found.kind.verbs, "step");
|
|
4703
|
+
});
|
|
4704
|
+
const unknownNames = Object.keys(world.kinds).filter((key) => {
|
|
4705
|
+
const entry = world.kinds[key];
|
|
4706
|
+
return isNamespace(entry) && entry.fallback !== void 0;
|
|
4707
|
+
}).map((key) => [key, "nope"]);
|
|
4708
|
+
let size = 0;
|
|
4709
|
+
const kindOf = (type) => {
|
|
4710
|
+
const found = lookupKind(world, type);
|
|
4711
|
+
return found.ok ? found.kind : void 0;
|
|
4712
|
+
};
|
|
4713
|
+
const anyJson = (depth = 0) => {
|
|
4714
|
+
const roll = rng.int(depth > 1 ? 6 : 8);
|
|
4715
|
+
if (roll === 0) return null;
|
|
4716
|
+
if (roll === 1) return rng.chance(.5);
|
|
4717
|
+
if (roll === 2) return rng.pick([
|
|
4718
|
+
0,
|
|
4719
|
+
1,
|
|
4720
|
+
2,
|
|
4721
|
+
-1,
|
|
4722
|
+
.5,
|
|
4723
|
+
1e21
|
|
4724
|
+
]);
|
|
4725
|
+
if (roll === 3) return rng.pick([
|
|
4726
|
+
"",
|
|
4727
|
+
"a",
|
|
4728
|
+
"text",
|
|
4729
|
+
"0"
|
|
4730
|
+
]);
|
|
4731
|
+
if (roll === 4) return rng.int(10);
|
|
4732
|
+
if (roll === 5) return rng.pick(NAMES);
|
|
4733
|
+
if (roll === 6) return Array.from({ length: rng.int(3) }, () => anyJson(depth + 1));
|
|
4734
|
+
const out = {};
|
|
4735
|
+
for (let index = rng.int(3); index > 0; index -= 1) Object.defineProperty(out, rng.pick(FIELDS), {
|
|
4736
|
+
value: anyJson(depth + 1),
|
|
4737
|
+
enumerable: true,
|
|
4738
|
+
writable: true,
|
|
4739
|
+
configurable: true
|
|
4740
|
+
});
|
|
4741
|
+
return out;
|
|
4742
|
+
};
|
|
4743
|
+
const dataOf = (type) => options.data?.(type, rng) ?? anyJson();
|
|
4744
|
+
const renderType = (type) => {
|
|
4745
|
+
const roll = rng.next();
|
|
4746
|
+
if (roll < .06) return {
|
|
4747
|
+
type: "ref",
|
|
4748
|
+
value: ["world", ...type]
|
|
4749
|
+
};
|
|
4750
|
+
if (type.length === 1 && roll < .8) return type[0];
|
|
4751
|
+
return [...type];
|
|
4752
|
+
};
|
|
4753
|
+
const permitted = (type, kinds) => kinds === void 0 || kinds.some((kind) => kind.length <= type.length && kind.every((key, index) => String(key) === String(type[index])));
|
|
4754
|
+
const pickName = (names) => names.length === 0 || rng.chance(malformed) ? rng.pick(["unbound", "zz"]) : rng.pick(names);
|
|
4755
|
+
const someNames = () => {
|
|
4756
|
+
const out = /* @__PURE__ */ new Set();
|
|
4757
|
+
for (let index = 1 + rng.int(2); index > 0; index -= 1) out.add(rng.pick(NAMES));
|
|
4758
|
+
return [...out];
|
|
4759
|
+
};
|
|
4760
|
+
const envelope = (type, value, header) => {
|
|
4761
|
+
const out = {
|
|
4762
|
+
type: renderType(type),
|
|
4763
|
+
value
|
|
4764
|
+
};
|
|
4765
|
+
if (header !== void 0) out["name"] = header;
|
|
4766
|
+
if (options.own !== void 0 && options.own.length > 0 && rng.chance(.03)) out["own"] = { eval: rng.pick(options.own) };
|
|
4767
|
+
return out;
|
|
4768
|
+
};
|
|
4769
|
+
/** A path from a root (D10). It uses the names in scope, and sometimes a step document. */
|
|
4770
|
+
const pathFor = (pos, refKind) => {
|
|
4771
|
+
if (rng.chance(malformed)) return rng.pick([
|
|
4772
|
+
"names",
|
|
4773
|
+
[],
|
|
4774
|
+
{ names: "a" },
|
|
4775
|
+
["nowhere", "a"]
|
|
4776
|
+
]);
|
|
4777
|
+
const root = pos.names.length > 0 && rng.chance(.75) ? "names" : rng.pick(ROOTS.filter((name) => name !== "names"));
|
|
4778
|
+
const path = [root];
|
|
4779
|
+
if (root === "names") path.push(pickName(pos.names));
|
|
4780
|
+
if (root === "world") path.push(String(rng.pick(types)[0]));
|
|
4781
|
+
for (let index = rng.int(3); index > 0; index -= 1) if (stepTypes.length > 0 && rng.chance(.2)) {
|
|
4782
|
+
const type = rng.pick(stepTypes);
|
|
4783
|
+
const kind = kindOf(type);
|
|
4784
|
+
const value = refKind?.parts?.shape === "deep" && kind?.parts !== void 0 ? valueOf(kind.parts, [], {
|
|
4785
|
+
...pos,
|
|
4786
|
+
depth: pos.depth + 1
|
|
4787
|
+
}, emptyScope()) : dataOf(type);
|
|
4788
|
+
path.push({
|
|
4789
|
+
type: renderType(type),
|
|
4790
|
+
value
|
|
4791
|
+
});
|
|
4792
|
+
} else path.push(rng.pick(keys));
|
|
4793
|
+
return path;
|
|
4794
|
+
};
|
|
4795
|
+
const emptyScope = () => ({
|
|
4796
|
+
rules: [],
|
|
4797
|
+
names: [],
|
|
4798
|
+
covered: [],
|
|
4799
|
+
sources: /* @__PURE__ */ new Map()
|
|
4800
|
+
});
|
|
4801
|
+
/** This function decides the names of the scope rules before the parts, so that each part knows what it sees. */
|
|
4802
|
+
const scopeOf = (kind) => {
|
|
4803
|
+
const rules = kind.scope ?? [];
|
|
4804
|
+
const scope = {
|
|
4805
|
+
rules,
|
|
4806
|
+
names: [],
|
|
4807
|
+
covered: rules.map(() => 0),
|
|
4808
|
+
sources: /* @__PURE__ */ new Map()
|
|
4809
|
+
};
|
|
4810
|
+
for (const rule of rules) {
|
|
4811
|
+
const source = rule.names;
|
|
4812
|
+
let names = [];
|
|
4813
|
+
if ("fixed" in source) names = [...source.fixed];
|
|
4814
|
+
else if ("keysAt" in source) {
|
|
4815
|
+
names = someNames();
|
|
4816
|
+
scope.sources.set(source.keysAt.join("."), { keys: names });
|
|
4817
|
+
} else if ("listAt" in source) {
|
|
4818
|
+
names = someNames();
|
|
4819
|
+
scope.sources.set(source.listAt.join("."), { list: rng.chance(malformed) ? [...names, ...names] : names });
|
|
4820
|
+
} else if ("textAt" in source) {
|
|
4821
|
+
names = [rng.pick(NAMES)];
|
|
4822
|
+
scope.sources.set(source.textAt.join("."), { text: names[0] });
|
|
4823
|
+
}
|
|
4824
|
+
scope.names.push(names);
|
|
4825
|
+
}
|
|
4826
|
+
return scope;
|
|
4827
|
+
};
|
|
4828
|
+
/** The names that a part sees from the scope rules of its parent. A rule `sequential` shows only earlier names. */
|
|
4829
|
+
const seenBy = (scope, path) => {
|
|
4830
|
+
const names = [];
|
|
4831
|
+
let headers = false;
|
|
4832
|
+
scope.rules.forEach((rule, index) => {
|
|
4833
|
+
if (!rule.in.some((pattern) => pathMatches(path, pattern))) return;
|
|
4834
|
+
const ruleNames = scope.names[index] ?? [];
|
|
4835
|
+
if ("headers" in rule.names) headers = true;
|
|
4836
|
+
if (rule.sequential !== true || "headers" in rule.names || (scope.covered[index] ?? 0) > 0) names.push(...ruleNames);
|
|
4837
|
+
});
|
|
4838
|
+
return {
|
|
4839
|
+
names,
|
|
4840
|
+
headers
|
|
4841
|
+
};
|
|
4842
|
+
};
|
|
4843
|
+
/** This function records the part at a path, and the header name that it got, for the rules that cover it. */
|
|
4844
|
+
const saw = (scope, path, part) => {
|
|
4845
|
+
const header = isEnvelope(part) && typeof part.name === "string" ? part.name : void 0;
|
|
4846
|
+
scope.rules.forEach((rule, index) => {
|
|
4847
|
+
if (!rule.in.some((pattern) => pathMatches(path, pattern))) return;
|
|
4848
|
+
scope.covered[index] = (scope.covered[index] ?? 0) + 1;
|
|
4849
|
+
if ("headers" in rule.names && header !== void 0) scope.names[index]?.push(header);
|
|
4850
|
+
});
|
|
4851
|
+
};
|
|
4852
|
+
const partAt = (pos, scope, path, spec, parentType) => {
|
|
4853
|
+
const seen = seenBy(scope, path);
|
|
4854
|
+
const header = seen.headers && rng.chance(.5) ? rng.pick(NAMES) : void 0;
|
|
4855
|
+
const part = docAt({
|
|
4856
|
+
declared: spec.of,
|
|
4857
|
+
kinds: "kinds" in spec ? spec.kinds : void 0,
|
|
4858
|
+
names: seen.names.length === 0 ? pos.names : [...pos.names, ...seen.names],
|
|
4859
|
+
depth: pos.depth + 1,
|
|
4860
|
+
hint: parentType === void 0 ? void 0 : options.hints?.(parentType, path)
|
|
4861
|
+
}, header);
|
|
4862
|
+
saw(scope, path, part);
|
|
4863
|
+
return part;
|
|
4864
|
+
};
|
|
4865
|
+
/** This function builds a value of a shape (SHAPE.*). A part is a document at the next depth. */
|
|
4866
|
+
const valueOf = (shape, path, pos, scope, type) => {
|
|
4867
|
+
const source = scope.sources.get(path.join("."));
|
|
4868
|
+
if (source !== void 0 && "list" in source) return [...source.list];
|
|
4869
|
+
if (source !== void 0 && "text" in source) return source.text;
|
|
4870
|
+
const term = (of) => of === void 0 ? { shape: "term" } : {
|
|
4871
|
+
shape: "term",
|
|
4872
|
+
of
|
|
4873
|
+
};
|
|
4874
|
+
const small = () => pos.depth >= maxDepth ? 0 : size >= maxSize ? rng.int(2) : rng.int(4);
|
|
4875
|
+
if (shape === void 0 || shape.shape === "none") return path.length === 0 && type !== void 0 ? dataOf(type) : anyJson();
|
|
4876
|
+
switch (shape.shape) {
|
|
4877
|
+
case "term": return partAt(pos, scope, path, shape, type);
|
|
4878
|
+
case "list": {
|
|
4879
|
+
const items = Array.from({ length: small() }, (_, index) => valueOf(shape.item ?? term(shape.of), [...path, index], pos, scope, type));
|
|
4880
|
+
if (shape.distinct !== true || rng.chance(malformed)) return items;
|
|
4881
|
+
return items.filter((item, index) => !items.slice(0, index).some((before) => docEquals(before, item)));
|
|
4882
|
+
}
|
|
4883
|
+
case "map": {
|
|
4884
|
+
const out = {};
|
|
4885
|
+
const names = source !== void 0 && "keys" in source ? source.keys : Array.from({ length: small() }, () => rng.pick(FIELDS));
|
|
4886
|
+
for (const key of names) Object.defineProperty(out, key, {
|
|
4887
|
+
value: valueOf(shape.item ?? term(shape.of), [...path, key], pos, scope, type),
|
|
4888
|
+
enumerable: true,
|
|
4889
|
+
writable: true,
|
|
4890
|
+
configurable: true
|
|
4891
|
+
});
|
|
4892
|
+
return out;
|
|
4893
|
+
}
|
|
4894
|
+
case "record": {
|
|
4895
|
+
const named = Array.isArray(shape.fields) ? Object.fromEntries(shape.fields.map((name) => [name, term(shape.of)])) : shape.fields;
|
|
4896
|
+
const fieldKeys = /* @__PURE__ */ new Set([...shape.required ?? [], ...Object.keys(named).filter(() => rng.chance(.75))]);
|
|
4897
|
+
if (shape.closed !== true && shape.rest === void 0 && rng.chance(.15)) fieldKeys.add("extra");
|
|
4898
|
+
const ordered = rng.chance(.2) ? [...fieldKeys].reverse() : [...fieldKeys];
|
|
4899
|
+
const out = {};
|
|
4900
|
+
for (const key of ordered) {
|
|
4901
|
+
if (rng.chance(malformed / 2) && (shape.required ?? []).includes(key)) continue;
|
|
4902
|
+
const fieldShape = Object.hasOwn(named, key) ? named[key] : shape.rest;
|
|
4903
|
+
Object.defineProperty(out, key, {
|
|
4904
|
+
value: valueOf(fieldShape, [...path, key], pos, scope, type),
|
|
4905
|
+
enumerable: true,
|
|
4906
|
+
writable: true,
|
|
4907
|
+
configurable: true
|
|
4908
|
+
});
|
|
4909
|
+
}
|
|
4910
|
+
if (shape.closed === true && rng.chance(malformed)) out["stray"] = 1;
|
|
4911
|
+
return out;
|
|
4912
|
+
}
|
|
4913
|
+
case "deep": return rng.chance(.5) ? [partAt(pos, scope, [...path, 0], term(shape.of), type), anyJson()] : {
|
|
4914
|
+
at: partAt(pos, scope, [...path, "at"], term(shape.of), type),
|
|
4915
|
+
data: anyJson()
|
|
4916
|
+
};
|
|
4917
|
+
case "text": return rng.chance(malformed) ? 1 : rng.pick([
|
|
4918
|
+
"",
|
|
4919
|
+
"a",
|
|
4920
|
+
"label"
|
|
4921
|
+
]);
|
|
4922
|
+
case "number": {
|
|
4923
|
+
if (rng.chance(malformed)) return "1";
|
|
4924
|
+
const value = (shape.min ?? -2) + rng.int(5) + (shape.whole === true ? 0 : rng.pick([0, .5]));
|
|
4925
|
+
return shape.max !== void 0 && value > shape.max ? shape.max : value;
|
|
4926
|
+
}
|
|
4927
|
+
case "one": return rng.chance(malformed) ? "other" : rng.pick(shape.values);
|
|
4928
|
+
}
|
|
4929
|
+
};
|
|
4930
|
+
/**
|
|
4931
|
+
* A malformed document: an unknown kind, or an unknown name under a namespace with a fallback. It can also be a
|
|
4932
|
+
* bad shape, a bare value without a type, or a bad reference.
|
|
4933
|
+
*/
|
|
4934
|
+
const brokenAt = (pos) => {
|
|
4935
|
+
const roll = rng.int(5);
|
|
4936
|
+
if (roll === 0 && unknownNames.length > 0 && rng.chance(.5)) {
|
|
4937
|
+
const type = rng.pick(unknownNames);
|
|
4938
|
+
const kind = kindOf(type);
|
|
4939
|
+
return envelope(type, kind?.parts === void 0 ? dataOf(type) : valueOf(kind.parts, [], pos, scopeOf(kind), type));
|
|
4940
|
+
}
|
|
4941
|
+
if (roll === 0) return {
|
|
4942
|
+
type: rng.pick(["nothing", ["no", "such"]]),
|
|
4943
|
+
value: 1
|
|
4944
|
+
};
|
|
4945
|
+
if (roll === 1) return pos.declared === void 0 ? rng.pick([
|
|
4946
|
+
7,
|
|
4947
|
+
"bare",
|
|
4948
|
+
[1]
|
|
4949
|
+
]) : {
|
|
4950
|
+
type: "ref",
|
|
4951
|
+
value: ["names", "unbound"]
|
|
4952
|
+
};
|
|
4953
|
+
if (roll === 2) {
|
|
4954
|
+
const shaped = types.filter((type) => kindOf(type)?.parts !== void 0);
|
|
4955
|
+
if (shaped.length > 0) return envelope(rng.pick(shaped), rng.pick([
|
|
4956
|
+
7,
|
|
4957
|
+
"x",
|
|
4958
|
+
null
|
|
4959
|
+
]));
|
|
4960
|
+
}
|
|
4961
|
+
if (roll === 3) return {
|
|
4962
|
+
type: "ref",
|
|
4963
|
+
value: rng.pick([
|
|
4964
|
+
["names", "unbound"],
|
|
4965
|
+
["nowhere"],
|
|
4966
|
+
"names"
|
|
4967
|
+
])
|
|
4968
|
+
};
|
|
4969
|
+
return {
|
|
4970
|
+
type: rng.pick(types),
|
|
4971
|
+
value: anyJson()
|
|
4972
|
+
};
|
|
4973
|
+
};
|
|
4974
|
+
const docAt = (pos, header) => {
|
|
4975
|
+
size += 1;
|
|
4976
|
+
const leaf = pos.depth >= maxDepth || size >= maxSize;
|
|
4977
|
+
if (rng.chance(malformed)) return brokenAt(pos);
|
|
4978
|
+
if (pos.declared !== void 0 && rng.chance(leaf ? .7 : .3)) {
|
|
4979
|
+
const bare = dataOf([pos.declared]);
|
|
4980
|
+
if (!isEnvelope(bare) || permitted(typePath(bare.type) ?? [], pos.kinds)) return bare;
|
|
4981
|
+
}
|
|
4982
|
+
let candidates = types.filter((type) => permitted(type, pos.kinds) && (!leaf || leafKind(kindOf(type))));
|
|
4983
|
+
if (pos.hint !== void 0 && rng.chance(.6)) {
|
|
4984
|
+
const hinted = candidates.filter((type) => pos.hint?.some((hint) => hint.length === type.length && hint.every((key, index) => key === type[index])));
|
|
4985
|
+
if (hinted.length > 0) candidates = hinted;
|
|
4986
|
+
}
|
|
4987
|
+
if (candidates.length === 0) candidates = [...types];
|
|
4988
|
+
const type = rng.pick(candidates);
|
|
4989
|
+
const kind = kindOf(type);
|
|
4990
|
+
if (type.length === 1 && type[0] === "ref") return envelope(type, pathFor(pos, kind), header);
|
|
4991
|
+
const value = kind === void 0 ? dataOf(type) : valueOf(kind.parts, [], pos, scopeOf(kind), type);
|
|
4992
|
+
return envelope(type, value, header);
|
|
4993
|
+
};
|
|
4994
|
+
return docAt({
|
|
4995
|
+
declared: options.declaredType,
|
|
4996
|
+
kinds: void 0,
|
|
4997
|
+
names: options.names ?? [],
|
|
4998
|
+
depth: 0,
|
|
4999
|
+
hint: void 0
|
|
5000
|
+
});
|
|
3796
5001
|
}
|
|
3797
5002
|
//#endregion
|
|
3798
|
-
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, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaEvaluators, formulaKinds, formulaMachine, fromJson, 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, ref, refsOf, resolve, run, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, valueOfThing, walk, worldThing };
|
|
5003
|
+
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, formulaKnown, 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, partsOf, pathMatches, permits, placed, planner, prepare, random, ref, reference, refsOf, resolve, run, sameValue, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, typesOf, valueOfThing, walk, worldThing };
|
|
3799
5004
|
|
|
3800
5005
|
//# sourceMappingURL=index.js.map
|