@mark1russell7/coeval 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/build/index.d.ts +94 -10
- package/build/index.d.ts.map +1 -1
- package/build/index.js +495 -118
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +8 -2
- package/src/doc.ts +26 -8
- package/src/errors.ts +4 -2
- package/src/evaluate.ts +27 -20
- package/src/formula.ts +45 -3
- package/src/generate.ts +37 -8
- package/src/index.ts +3 -2
- package/src/parts.ts +59 -3
- package/src/reference.ts +157 -18
- package/src/static.ts +17 -16
- package/src/stdlib.ts +180 -27
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;
|
|
@@ -283,7 +299,7 @@ var Failure = class extends Error {
|
|
|
283
299
|
*/
|
|
284
300
|
function isStackOverflow(error) {
|
|
285
301
|
try {
|
|
286
|
-
return error instanceof RangeError &&
|
|
302
|
+
return (error instanceof RangeError || error instanceof SyntaxError) && error.message.includes("call stack");
|
|
287
303
|
} catch {
|
|
288
304
|
return true;
|
|
289
305
|
}
|
|
@@ -1040,6 +1056,10 @@ const NO_SLOTS = Object.freeze([]);
|
|
|
1040
1056
|
function hasSlots(template) {
|
|
1041
1057
|
return template.t !== "data";
|
|
1042
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
|
+
}
|
|
1043
1063
|
/** This function gives the reason why a value is not data of a shape, or `undefined` when it is. */
|
|
1044
1064
|
function dataFault(spec, item) {
|
|
1045
1065
|
if (spec.shape === "text") return typeof item === "string" ? void 0 : "The value must be a text.";
|
|
@@ -1141,9 +1161,13 @@ function splitParts(shape, value) {
|
|
|
1141
1161
|
shape: "term",
|
|
1142
1162
|
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1143
1163
|
};
|
|
1144
|
-
|
|
1164
|
+
const items = list.map((child, index) => walk(each, child, [...path, index]));
|
|
1165
|
+
return isData(each) ? {
|
|
1166
|
+
t: "data",
|
|
1167
|
+
value: item
|
|
1168
|
+
} : {
|
|
1145
1169
|
t: "list",
|
|
1146
|
-
items
|
|
1170
|
+
items
|
|
1147
1171
|
};
|
|
1148
1172
|
}
|
|
1149
1173
|
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
@@ -1211,6 +1235,54 @@ function splitParts(shape, value) {
|
|
|
1211
1235
|
};
|
|
1212
1236
|
}
|
|
1213
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
|
+
/**
|
|
1214
1286
|
* This function tells if the path of a part matches a pattern of a scope rule. The key `"*"` of a pattern matches
|
|
1215
1287
|
* each key.
|
|
1216
1288
|
*
|
|
@@ -1655,15 +1727,15 @@ function finish(c, meta, code, run) {
|
|
|
1655
1727
|
* This function finds the evaluator of the verb `eval` without the record of the rungs, for a compilation without a
|
|
1656
1728
|
* trace. It gives the same evaluator as `ask`: the own entry, then the kind, then the default of the world.
|
|
1657
1729
|
*/
|
|
1658
|
-
function evaluatorOf(world, envelope,
|
|
1730
|
+
function evaluatorOf(world, envelope, kind) {
|
|
1659
1731
|
const own = envelope?.own;
|
|
1660
1732
|
const ownName = own !== void 0 && Object.hasOwn(own, "eval") ? own["eval"] : void 0;
|
|
1661
1733
|
if (ownName !== void 0) return {
|
|
1662
1734
|
name: ownName,
|
|
1663
1735
|
unknown: false
|
|
1664
1736
|
};
|
|
1665
|
-
if (
|
|
1666
|
-
const verbs =
|
|
1737
|
+
if (kind === void 0) return { unknown: true };
|
|
1738
|
+
const verbs = kind.verbs;
|
|
1667
1739
|
const name = Object.hasOwn(verbs, "eval") ? verbs["eval"] : Object.hasOwn(world.defaults, "eval") ? world.defaults["eval"] : void 0;
|
|
1668
1740
|
return name === void 0 ? { unknown: false } : {
|
|
1669
1741
|
name,
|
|
@@ -1682,23 +1754,28 @@ const NO_META = Object.freeze({
|
|
|
1682
1754
|
side: "derive"
|
|
1683
1755
|
});
|
|
1684
1756
|
function compileNode(c, value, place, declared, depth, address = false) {
|
|
1685
|
-
const
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
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;
|
|
1690
1771
|
const meta0 = c.traced ? {
|
|
1691
1772
|
path: place.map(String),
|
|
1692
1773
|
doc,
|
|
1693
|
-
declared:
|
|
1774
|
+
declared: read.declared,
|
|
1694
1775
|
answer: NO_ANSWER,
|
|
1695
1776
|
side: "derive"
|
|
1696
1777
|
} : NO_META;
|
|
1697
|
-
if (envelope === void 0 && declared === void 0) return failing(c, meta0, place, "#PART?", "The value has no envelope, and its position declares no type.");
|
|
1698
1778
|
if (depth > c.maxDepth) return failing(c, meta0, place, "#DEPTH!", `The nesting is deeper than ${c.maxDepth} documents.`);
|
|
1699
|
-
const world = c.machine.world;
|
|
1700
|
-
const found = lookupKind(world, doc.type);
|
|
1701
|
-
const kind = found.ok ? found.kind : void 0;
|
|
1702
1779
|
let meta = meta0;
|
|
1703
1780
|
let name;
|
|
1704
1781
|
let unknown;
|
|
@@ -1712,7 +1789,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1712
1789
|
name = answer.evaluator;
|
|
1713
1790
|
unknown = answer.error === "unknown-kind";
|
|
1714
1791
|
} else {
|
|
1715
|
-
const quick = evaluatorOf(world, envelope,
|
|
1792
|
+
const quick = evaluatorOf(world, envelope, kind);
|
|
1716
1793
|
name = quick.name;
|
|
1717
1794
|
unknown = quick.unknown;
|
|
1718
1795
|
}
|
|
@@ -1722,11 +1799,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1722
1799
|
const runner = name !== void 0 && Object.hasOwn(runners, name) ? runners[name] : void 0;
|
|
1723
1800
|
const evaluator = name !== void 0 && Object.hasOwn(c.machine.evaluators, name) ? c.machine.evaluators[name] : void 0;
|
|
1724
1801
|
if (name === void 0 || runner === void 0 && evaluator === void 0) return failing(c, meta, place, "#NOEVAL", `No rung gives an evaluator for ${kindText}.`);
|
|
1725
|
-
|
|
1726
|
-
if (!split.ok) {
|
|
1727
|
-
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
1728
|
-
return failing(c, meta, place, "#VALUE!", `${split.reason}${at} The kind is ${kindText}.`);
|
|
1729
|
-
}
|
|
1802
|
+
if (!split.ok) return failing(c, meta, place, "#VALUE!", split.reason);
|
|
1730
1803
|
const parts = split.parts.map((slot) => {
|
|
1731
1804
|
const item = slot.item;
|
|
1732
1805
|
const own = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1, slot.address === true);
|
|
@@ -1748,7 +1821,7 @@ function compileNode(c, value, place, declared, depth, address = false) {
|
|
|
1748
1821
|
});
|
|
1749
1822
|
const input = {
|
|
1750
1823
|
kind: kindText,
|
|
1751
|
-
typePath:
|
|
1824
|
+
typePath: read.typePath ?? [],
|
|
1752
1825
|
doc,
|
|
1753
1826
|
value: split.parts.length === 0 ? doc.value : split.fill(parts),
|
|
1754
1827
|
data: split.data,
|
|
@@ -2184,7 +2257,8 @@ const STD_WORLD = {
|
|
|
2184
2257
|
],
|
|
2185
2258
|
of: "number"
|
|
2186
2259
|
},
|
|
2187
|
-
|
|
2260
|
+
caps: [{ rule: "mask" }],
|
|
2261
|
+
summary: "A node of a tree. Each part is an attribute that evaluates on demand, one time, with the node as self."
|
|
2188
2262
|
},
|
|
2189
2263
|
box: {
|
|
2190
2264
|
verbs: {
|
|
@@ -2200,7 +2274,8 @@ const STD_WORLD = {
|
|
|
2200
2274
|
],
|
|
2201
2275
|
of: "number"
|
|
2202
2276
|
},
|
|
2203
|
-
|
|
2277
|
+
caps: [{ rule: "mask" }],
|
|
2278
|
+
summary: "A node of a tree, as panel. A formula in a box can read its parent and its siblings."
|
|
2204
2279
|
},
|
|
2205
2280
|
math: { kinds: {
|
|
2206
2281
|
sum: {
|
|
@@ -2317,53 +2392,167 @@ const STD_EVALUATORS = {
|
|
|
2317
2392
|
const found = resolve(data);
|
|
2318
2393
|
return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
|
|
2319
2394
|
},
|
|
2320
|
-
"tree.node": (input) => treeNode(input
|
|
2395
|
+
"tree.node": (input) => treeNode(input)
|
|
2321
2396
|
};
|
|
2397
|
+
/** The brand of a node of the decorated tree. Its value is the state that the kit reads. */
|
|
2398
|
+
const TREE_NODE = Symbol.for("coeval.tree.node");
|
|
2399
|
+
const BUSY = Object.freeze({ busy: true });
|
|
2400
|
+
/** The attributes in evaluation, in the order of their demands. An evaluation does not wait, thus one list serves. */
|
|
2401
|
+
const demands = [];
|
|
2402
|
+
function attributeText(key, type, place) {
|
|
2403
|
+
return `${key} of ${type} at [${place.join(", ")}]`;
|
|
2404
|
+
}
|
|
2405
|
+
/** This function gives the `#CYCLE!` of a demand that reached an attribute in evaluation, with the chain of demands. */
|
|
2406
|
+
function cycleAt(cells, key, text) {
|
|
2407
|
+
const first = demands.findIndex((demand) => demand.cells === cells && demand.key === key);
|
|
2408
|
+
const chain = first === -1 ? [text] : demands.slice(first).map((demand) => demand.text);
|
|
2409
|
+
chain.push(text);
|
|
2410
|
+
return fail("#CYCLE!", `The attributes form a cycle: ${chain.join(" -> ")}.`);
|
|
2411
|
+
}
|
|
2412
|
+
function stateOf(thing) {
|
|
2413
|
+
return typeof thing === "object" && thing !== null ? thing[TREE_NODE] : void 0;
|
|
2414
|
+
}
|
|
2415
|
+
/** This function sets an own field, also the field `__proto__` (SHAPE.PROTO). */
|
|
2416
|
+
function putField(target, key, value) {
|
|
2417
|
+
Object.defineProperty(target, key, {
|
|
2418
|
+
value,
|
|
2419
|
+
enumerable: true,
|
|
2420
|
+
writable: true,
|
|
2421
|
+
configurable: true
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2322
2424
|
/**
|
|
2323
|
-
* This function
|
|
2324
|
-
* it
|
|
2425
|
+
* This function gives a value with the JSON of each node of a tree in it. A list or a plain record is new only when
|
|
2426
|
+
* it holds a node. A container that holds itself stays as it is.
|
|
2325
2427
|
*/
|
|
2326
|
-
function
|
|
2327
|
-
const
|
|
2428
|
+
function plainOf(value, path) {
|
|
2429
|
+
const state = stateOf(value);
|
|
2430
|
+
if (state !== void 0) {
|
|
2431
|
+
if (path.has(value)) return fail("#CYCLE!", `The JSON of the node ${state.type} at [${state.place.join(", ")}] holds the node itself.`);
|
|
2432
|
+
return state.json(path);
|
|
2433
|
+
}
|
|
2434
|
+
if (typeof value !== "object" || value === null || path.has(value) || isHostThing(value)) return ok(value);
|
|
2435
|
+
if (!Array.isArray(value) && !isRecord(value)) return ok(value);
|
|
2436
|
+
path.add(value);
|
|
2437
|
+
try {
|
|
2438
|
+
if (Array.isArray(value)) {
|
|
2439
|
+
let list;
|
|
2440
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2441
|
+
const item = plainOf(value[index], path);
|
|
2442
|
+
if (!item.ok) return item;
|
|
2443
|
+
if (item.value !== value[index]) (list ??= [...value])[index] = item.value;
|
|
2444
|
+
}
|
|
2445
|
+
return ok(list ?? value);
|
|
2446
|
+
}
|
|
2447
|
+
const record = value;
|
|
2448
|
+
let out;
|
|
2449
|
+
for (const key of Object.keys(record)) {
|
|
2450
|
+
const item = plainOf(record[key], path);
|
|
2451
|
+
if (!item.ok) return item;
|
|
2452
|
+
if (item.value === record[key]) continue;
|
|
2453
|
+
if (out === void 0) {
|
|
2454
|
+
out = {};
|
|
2455
|
+
for (const other of Object.keys(record)) putField(out, other, record[other]);
|
|
2456
|
+
}
|
|
2457
|
+
putField(out, key, item.value);
|
|
2458
|
+
}
|
|
2459
|
+
return ok(out ?? value);
|
|
2460
|
+
} finally {
|
|
2461
|
+
path.delete(value);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
2466
|
+
* step `type` gives its type. A step with the key of a part reads that attribute. A failed attribute throws `Failure`
|
|
2467
|
+
* with its own error, thus a reference to it gives that error.
|
|
2468
|
+
*/
|
|
2469
|
+
function treeThing(parts, context, type, place, build) {
|
|
2470
|
+
const cells = /* @__PURE__ */ new Map();
|
|
2328
2471
|
const read = (key) => {
|
|
2329
|
-
const
|
|
2330
|
-
if (
|
|
2472
|
+
const cell = cells.get(key);
|
|
2473
|
+
if (cell !== void 0) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
|
|
2331
2474
|
const part = parts.find((item) => item.key === key);
|
|
2332
2475
|
if (part === void 0) return void 0;
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2476
|
+
cells.set(key, BUSY);
|
|
2477
|
+
demands.push({
|
|
2478
|
+
cells,
|
|
2479
|
+
key,
|
|
2480
|
+
text: attributeText(key, type, place)
|
|
2481
|
+
});
|
|
2482
|
+
let result;
|
|
2483
|
+
try {
|
|
2484
|
+
result = part.evaluate({
|
|
2485
|
+
...context,
|
|
2486
|
+
self: node
|
|
2487
|
+
});
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
cells.delete(key);
|
|
2490
|
+
throw error;
|
|
2491
|
+
} finally {
|
|
2492
|
+
demands.pop();
|
|
2493
|
+
}
|
|
2494
|
+
cells.set(key, {
|
|
2495
|
+
busy: false,
|
|
2496
|
+
result
|
|
2336
2497
|
});
|
|
2337
|
-
memo.set(key, result);
|
|
2338
2498
|
return result;
|
|
2339
2499
|
};
|
|
2340
|
-
const
|
|
2500
|
+
const state = {
|
|
2501
|
+
build,
|
|
2502
|
+
type,
|
|
2503
|
+
place,
|
|
2504
|
+
json(path) {
|
|
2505
|
+
path.add(node);
|
|
2506
|
+
try {
|
|
2507
|
+
const out = {};
|
|
2508
|
+
for (const part of parts) {
|
|
2509
|
+
const result = read(part.key);
|
|
2510
|
+
if (!result.ok) return result;
|
|
2511
|
+
const value = plainOf(result.value, path);
|
|
2512
|
+
if (!value.ok) return value;
|
|
2513
|
+
putField(out, part.key, value.value);
|
|
2514
|
+
}
|
|
2515
|
+
return ok(out);
|
|
2516
|
+
} finally {
|
|
2517
|
+
path.delete(node);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
const node = {
|
|
2341
2522
|
[THING]: true,
|
|
2523
|
+
[TREE_NODE]: state,
|
|
2342
2524
|
type,
|
|
2343
2525
|
label: `node ${type}`,
|
|
2344
2526
|
deref(key) {
|
|
2345
2527
|
if (key === "parent") return context.self;
|
|
2346
2528
|
if (key === "type") return type;
|
|
2347
2529
|
const result = read(String(key));
|
|
2348
|
-
|
|
2530
|
+
if (result === void 0) return void 0;
|
|
2531
|
+
if (!result.ok) throw new Failure(result.error);
|
|
2532
|
+
return result.value;
|
|
2349
2533
|
},
|
|
2350
2534
|
toJson() {
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
out[part.key] = result?.ok === true ? result.value : null;
|
|
2355
|
-
}
|
|
2356
|
-
return out;
|
|
2535
|
+
const result = state.json(/* @__PURE__ */ new Set());
|
|
2536
|
+
if (!result.ok) throw new Failure(result.error);
|
|
2537
|
+
return result.value;
|
|
2357
2538
|
}
|
|
2358
2539
|
};
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2540
|
+
return node;
|
|
2541
|
+
}
|
|
2542
|
+
/**
|
|
2543
|
+
* This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
|
|
2544
|
+
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree.
|
|
2545
|
+
*/
|
|
2546
|
+
function treeNode({ parts, context, kind, place }) {
|
|
2547
|
+
const above = stateOf(context.self);
|
|
2548
|
+
if (above !== void 0 && above.build.open) return ok(treeThing(parts, context, kind, place, above.build));
|
|
2549
|
+
const build = { open: true };
|
|
2550
|
+
const root = treeThing(parts, context, kind, place, build);
|
|
2551
|
+
try {
|
|
2552
|
+
return stateOf(root).json(/* @__PURE__ */ new Set());
|
|
2553
|
+
} finally {
|
|
2554
|
+
build.open = false;
|
|
2365
2555
|
}
|
|
2366
|
-
return ok(out);
|
|
2367
2556
|
}
|
|
2368
2557
|
function hostType(node, wanted) {
|
|
2369
2558
|
return isHostThing(node) && node.type === wanted;
|
|
@@ -3020,40 +3209,32 @@ function walk(world, doc, visit, options = {}) {
|
|
|
3020
3209
|
}];
|
|
3021
3210
|
while (stack.length > 0) {
|
|
3022
3211
|
const item = stack.pop();
|
|
3023
|
-
const
|
|
3024
|
-
if (
|
|
3212
|
+
const read = partsOf(world, item.value, item.declared);
|
|
3213
|
+
if (!read.ok) {
|
|
3025
3214
|
options.problem?.({
|
|
3026
3215
|
place: keysOf(item.place),
|
|
3027
3216
|
at: keysOf(item.at),
|
|
3028
|
-
code:
|
|
3029
|
-
reason:
|
|
3217
|
+
code: read.code,
|
|
3218
|
+
reason: read.reason
|
|
3030
3219
|
});
|
|
3031
3220
|
continue;
|
|
3032
3221
|
}
|
|
3033
|
-
const doc
|
|
3034
|
-
|
|
3035
|
-
value: item.value
|
|
3036
|
-
};
|
|
3037
|
-
const found = lookupKind(world, doc.type);
|
|
3038
|
-
const kind = found.ok ? found.kind : 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);
|
|
3222
|
+
const { doc, kind, split } = read;
|
|
3223
|
+
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);
|
|
3040
3224
|
if (visit(here) === "skip" || kind === void 0) continue;
|
|
3041
|
-
const
|
|
3042
|
-
const base = envelope === void 0 ? item.at : {
|
|
3225
|
+
const base = read.declared ? item.at : {
|
|
3043
3226
|
up: item.at,
|
|
3044
3227
|
keys: ["value"]
|
|
3045
3228
|
};
|
|
3046
3229
|
if (!split.ok) {
|
|
3047
|
-
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
3048
|
-
const where = keysOf({
|
|
3049
|
-
up: base,
|
|
3050
|
-
keys: split.at
|
|
3051
|
-
});
|
|
3052
3230
|
options.problem?.({
|
|
3053
3231
|
place: keysOf(item.place),
|
|
3054
|
-
at:
|
|
3232
|
+
at: keysOf({
|
|
3233
|
+
up: base,
|
|
3234
|
+
keys: split.at
|
|
3235
|
+
}),
|
|
3055
3236
|
code: "#VALUE!",
|
|
3056
|
-
reason:
|
|
3237
|
+
reason: split.reason
|
|
3057
3238
|
});
|
|
3058
3239
|
continue;
|
|
3059
3240
|
}
|
|
@@ -3185,7 +3366,8 @@ function capReason(v, kind) {
|
|
|
3185
3366
|
* - `#PART?`: a value without a type.
|
|
3186
3367
|
* - `#DEPTH!`: too much nesting.
|
|
3187
3368
|
* - `#REF!` and `#STEP?`: a bad path.
|
|
3188
|
-
* - `#NAME?`: a name that nothing binds.
|
|
3369
|
+
* - `#NAME?`: a name that nothing binds. With the hook `known` of the machine, also a name that the language does not
|
|
3370
|
+
* have, for example an operation.
|
|
3189
3371
|
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
3190
3372
|
*
|
|
3191
3373
|
* @param machine - The world and the code of the evaluators.
|
|
@@ -3227,6 +3409,15 @@ function check(machine, doc, options = {}) {
|
|
|
3227
3409
|
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`,
|
|
3228
3410
|
at: v.at
|
|
3229
3411
|
});
|
|
3412
|
+
else if (machine.known !== void 0) {
|
|
3413
|
+
const known = machine.known(v.typePath ?? [], name);
|
|
3414
|
+
if (known !== true) issues.push({
|
|
3415
|
+
code: "#NAME?",
|
|
3416
|
+
place: v.place,
|
|
3417
|
+
reason: known,
|
|
3418
|
+
at: v.at
|
|
3419
|
+
});
|
|
3420
|
+
}
|
|
3230
3421
|
const caps = v.caps;
|
|
3231
3422
|
if (caps !== void 0 && v.kind?.effects !== void 0) {
|
|
3232
3423
|
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
@@ -3259,7 +3450,7 @@ function check(machine, doc, options = {}) {
|
|
|
3259
3450
|
reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
|
|
3260
3451
|
at: stepAt(0)
|
|
3261
3452
|
});
|
|
3262
|
-
return
|
|
3453
|
+
return;
|
|
3263
3454
|
}
|
|
3264
3455
|
if (!roots.includes(root)) issues.push({
|
|
3265
3456
|
code: "#REF!",
|
|
@@ -3366,7 +3557,7 @@ function valuesOf(parts, ctx, rt) {
|
|
|
3366
3557
|
}
|
|
3367
3558
|
return values;
|
|
3368
3559
|
}
|
|
3369
|
-
function partsOf(value) {
|
|
3560
|
+
function partsOf$1(value) {
|
|
3370
3561
|
return isRecord(value) ? value : {};
|
|
3371
3562
|
}
|
|
3372
3563
|
const LIT_SUMMARY = "A literal: its value is the result.";
|
|
@@ -3430,7 +3621,14 @@ function formulaKinds() {
|
|
|
3430
3621
|
verbs: { eval: "formula.fn" },
|
|
3431
3622
|
parts: {
|
|
3432
3623
|
shape: "record",
|
|
3433
|
-
fields: {
|
|
3624
|
+
fields: {
|
|
3625
|
+
params: {
|
|
3626
|
+
shape: "list",
|
|
3627
|
+
item: { shape: "text" },
|
|
3628
|
+
distinct: true
|
|
3629
|
+
},
|
|
3630
|
+
body: term
|
|
3631
|
+
},
|
|
3434
3632
|
required: ["params", "body"]
|
|
3435
3633
|
},
|
|
3436
3634
|
scope: [{
|
|
@@ -3582,7 +3780,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3582
3780
|
};
|
|
3583
3781
|
}),
|
|
3584
3782
|
"formula.record": compiled(({ value }) => {
|
|
3585
|
-
const fields = partsOf(value);
|
|
3783
|
+
const fields = partsOf$1(value);
|
|
3586
3784
|
const keys = Object.keys(fields);
|
|
3587
3785
|
const codes = keys.map((key) => fields[key].code);
|
|
3588
3786
|
if (combine !== void 0) {
|
|
@@ -3615,8 +3813,8 @@ function formulaEvaluators(profile = {}) {
|
|
|
3615
3813
|
return Array.isArray(values) ? ok(values) : values;
|
|
3616
3814
|
}),
|
|
3617
3815
|
"formula.let": compiled(({ value }) => {
|
|
3618
|
-
const record = partsOf(value);
|
|
3619
|
-
const bindings = Object.entries(partsOf(record["bind"]));
|
|
3816
|
+
const record = partsOf$1(value);
|
|
3817
|
+
const bindings = Object.entries(partsOf$1(record["bind"]));
|
|
3620
3818
|
const body = record["body"];
|
|
3621
3819
|
return (ctx, rt) => {
|
|
3622
3820
|
const names = { ...ctx.names };
|
|
@@ -3637,7 +3835,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3637
3835
|
return () => bad;
|
|
3638
3836
|
}
|
|
3639
3837
|
const names = params;
|
|
3640
|
-
const body = partsOf(value)["body"];
|
|
3838
|
+
const body = partsOf$1(value)["body"];
|
|
3641
3839
|
return (ctx, rt) => {
|
|
3642
3840
|
const closure = (...args) => {
|
|
3643
3841
|
const scope = { ...ctx.names };
|
|
@@ -3654,7 +3852,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3654
3852
|
};
|
|
3655
3853
|
}),
|
|
3656
3854
|
"formula.call": compiled(({ value }) => {
|
|
3657
|
-
const record = partsOf(value);
|
|
3855
|
+
const record = partsOf$1(value);
|
|
3658
3856
|
const fnPart = record["fn"];
|
|
3659
3857
|
const args = Array.isArray(record["args"]) ? record["args"] : [];
|
|
3660
3858
|
return (ctx, rt) => {
|
|
@@ -3675,7 +3873,7 @@ function formulaEvaluators(profile = {}) {
|
|
|
3675
3873
|
};
|
|
3676
3874
|
}),
|
|
3677
3875
|
"formula.if": compiled(({ value }) => {
|
|
3678
|
-
const record = partsOf(value);
|
|
3876
|
+
const record = partsOf$1(value);
|
|
3679
3877
|
const test = record["test"];
|
|
3680
3878
|
const then = record["then"];
|
|
3681
3879
|
const other = record["else"];
|
|
@@ -3794,14 +3992,52 @@ function formulaEvaluators(profile = {}) {
|
|
|
3794
3992
|
};
|
|
3795
3993
|
}
|
|
3796
3994
|
/**
|
|
3995
|
+
* This function makes the hook `known` of the formula kit for one profile (STATIC.KNOWN). The evaluator
|
|
3996
|
+
* `formula.op` knows an operation of `lazy` or of `ops`. A profile whose `ops` is a function answers through a call
|
|
3997
|
+
* of that function. A function that throws counts as an answer, because the run then gives `#CALC!`. The evaluator
|
|
3998
|
+
* `formula.ext` knows an extension of `ext` and each standard extension. The hook knows each name of the other
|
|
3999
|
+
* evaluators.
|
|
4000
|
+
*
|
|
4001
|
+
* @param profile - The operations and the extensions of the language.
|
|
4002
|
+
* @returns The hook: `true`, or the reason of the `#NAME?` that the evaluator gives in a run.
|
|
4003
|
+
*/
|
|
4004
|
+
function formulaKnown(profile = {}) {
|
|
4005
|
+
const hasOp = (name) => {
|
|
4006
|
+
if (profile.lazy !== void 0 && Object.hasOwn(profile.lazy, name)) return true;
|
|
4007
|
+
const ops = profile.ops;
|
|
4008
|
+
if (ops === void 0) return false;
|
|
4009
|
+
if (typeof ops !== "function") return Object.hasOwn(ops, name);
|
|
4010
|
+
try {
|
|
4011
|
+
return ops(name) !== void 0;
|
|
4012
|
+
} catch {
|
|
4013
|
+
return true;
|
|
4014
|
+
}
|
|
4015
|
+
};
|
|
4016
|
+
const hasExt = (name) => profile.ext !== void 0 && Object.hasOwn(profile.ext, name) || Object.hasOwn(STD_EXT, name);
|
|
4017
|
+
return (path, evaluator) => {
|
|
4018
|
+
if (evaluator !== "formula.op" && evaluator !== "formula.ext") return true;
|
|
4019
|
+
const name = path.slice(1).join(".");
|
|
4020
|
+
if (evaluator === "formula.op") return hasOp(name) ? true : `The language has no operation ${name}.`;
|
|
4021
|
+
return hasExt(name) ? true : `The language has no extension ${name}.`;
|
|
4022
|
+
};
|
|
4023
|
+
}
|
|
4024
|
+
/**
|
|
3797
4025
|
* This function makes a machine for a formula language. The machine has the kinds and the evaluators of the
|
|
3798
|
-
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds.
|
|
4026
|
+
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds. Its hook `known` comes
|
|
4027
|
+
* from the profile (`formulaKnown`). A hook `known` of the language answers each name that the kit knows.
|
|
3799
4028
|
*
|
|
3800
4029
|
* @param profile - The operations and the rules of the language.
|
|
3801
4030
|
* @param more - The parts of a machine that the language adds. Its kinds win over the kinds of the kit.
|
|
3802
4031
|
* @returns The machine.
|
|
3803
4032
|
*/
|
|
3804
4033
|
function formulaMachine(profile = {}, more = {}) {
|
|
4034
|
+
const kit = formulaKnown(profile);
|
|
4035
|
+
const replaced = (name) => more.evaluators !== void 0 && Object.hasOwn(more.evaluators, name) || more.runners !== void 0 && Object.hasOwn(more.runners, name);
|
|
4036
|
+
const theirs = more.known;
|
|
4037
|
+
const known = (path, evaluator) => {
|
|
4038
|
+
const answer = replaced(evaluator) ? true : kit(path, evaluator);
|
|
4039
|
+
return answer === true && theirs !== void 0 ? theirs(path, evaluator) : answer;
|
|
4040
|
+
};
|
|
3805
4041
|
return {
|
|
3806
4042
|
...more,
|
|
3807
4043
|
world: {
|
|
@@ -3814,7 +4050,8 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
3814
4050
|
evaluators: {
|
|
3815
4051
|
...formulaEvaluators(profile),
|
|
3816
4052
|
...more.evaluators
|
|
3817
|
-
}
|
|
4053
|
+
},
|
|
4054
|
+
known
|
|
3818
4055
|
};
|
|
3819
4056
|
}
|
|
3820
4057
|
//#endregion
|
|
@@ -3822,7 +4059,10 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
3822
4059
|
function own(table, name) {
|
|
3823
4060
|
return table !== void 0 && Object.hasOwn(table, name) ? table[name] : void 0;
|
|
3824
4061
|
}
|
|
3825
|
-
/**
|
|
4062
|
+
/**
|
|
4063
|
+
* This function finds the code of an evaluator name. A runner wins over an evaluator of the same name. A standard
|
|
4064
|
+
* evaluator gets the definition of the reference, unless the options give another definition (REF.TREE).
|
|
4065
|
+
*/
|
|
3826
4066
|
function choose(env, name) {
|
|
3827
4067
|
const runner = own(env.machine.runners, name);
|
|
3828
4068
|
if (runner !== void 0) {
|
|
@@ -3832,7 +4072,8 @@ function choose(env, name) {
|
|
|
3832
4072
|
}
|
|
3833
4073
|
const evaluator = own(env.machine.evaluators, name);
|
|
3834
4074
|
if (evaluator === void 0) return void 0;
|
|
3835
|
-
const
|
|
4075
|
+
const standard = evaluator === own(STD_EVALUATORS, name) ? own(STD_DEFINITIONS.evaluators, name) : void 0;
|
|
4076
|
+
const defined = own(env.definitions.evaluators, name) ?? standard;
|
|
3836
4077
|
if (defined !== void 0) return { evaluator: defined };
|
|
3837
4078
|
return typeof evaluator === "function" ? { evaluator } : { gap: `The reference has no definition of the compiled evaluator ${name}.` };
|
|
3838
4079
|
}
|
|
@@ -3843,31 +4084,22 @@ function choose(env, name) {
|
|
|
3843
4084
|
*/
|
|
3844
4085
|
function examine(env, at) {
|
|
3845
4086
|
const { place, declared } = at;
|
|
3846
|
-
const
|
|
3847
|
-
|
|
4087
|
+
const world = env.machine.world;
|
|
4088
|
+
const read = partsOf(world, at.value, declared);
|
|
4089
|
+
if (!read.ok) return fail(read.code, read.reason, { place });
|
|
3848
4090
|
if (at.depth > env.maxDepth) return fail("#DEPTH!", `The nesting is deeper than ${env.maxDepth} documents.`, { place });
|
|
3849
|
-
const doc
|
|
3850
|
-
type: declared,
|
|
3851
|
-
value: at.value
|
|
3852
|
-
};
|
|
4091
|
+
const { doc, kind, split } = read;
|
|
3853
4092
|
const kindText = typeName(doc.type);
|
|
3854
|
-
const
|
|
3855
|
-
const answer = ask(world, envelope ?? {}, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
4093
|
+
const answer = ask(world, read.declared ? {} : doc, "eval", declared === void 0 ? {} : { declaredType: declared });
|
|
3856
4094
|
if (answer.error === "unknown-kind") return fail("#KIND?", `The world has no kind ${kindText}.`, { place });
|
|
3857
4095
|
const name = answer.evaluator;
|
|
3858
4096
|
const chosen = name === void 0 ? void 0 : choose(env, name);
|
|
3859
4097
|
if (name === void 0 || chosen === void 0) return fail("#NOEVAL", `No rung gives an evaluator for ${kindText}.`, { place });
|
|
3860
|
-
|
|
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
|
-
}
|
|
4098
|
+
if (!split.ok) return fail("#VALUE!", split.reason, { place });
|
|
3867
4099
|
return {
|
|
3868
4100
|
doc,
|
|
3869
4101
|
kindText,
|
|
3870
|
-
typePath:
|
|
4102
|
+
typePath: read.typePath ?? [],
|
|
3871
4103
|
kind,
|
|
3872
4104
|
name,
|
|
3873
4105
|
chosen,
|
|
@@ -4106,6 +4338,126 @@ function reference(machine, doc, context = makeContext(), options = {}) {
|
|
|
4106
4338
|
return placed(thrown(error, "the reference"), []);
|
|
4107
4339
|
}
|
|
4108
4340
|
}
|
|
4341
|
+
const refNodes = /* @__PURE__ */ new WeakMap();
|
|
4342
|
+
/** The attributes in evaluation in the reference, in the order of their demands (TREE.CYCLE). */
|
|
4343
|
+
const inEvaluation = [];
|
|
4344
|
+
function nameOfAttribute(node, name) {
|
|
4345
|
+
return `${name} of ${node.kind} at [${node.place.join(", ")}]`;
|
|
4346
|
+
}
|
|
4347
|
+
function refNodeOf(thing) {
|
|
4348
|
+
return typeof thing === "object" && thing !== null ? refNodes.get(thing) : void 0;
|
|
4349
|
+
}
|
|
4350
|
+
/** TREE.ONCE, TREE.MEMO and TREE.CYCLE: the result of one attribute, or `undefined` when the node has no such part. */
|
|
4351
|
+
function attributeOf(node, self, name) {
|
|
4352
|
+
const done = node.values.get(name);
|
|
4353
|
+
if (done !== void 0) return done;
|
|
4354
|
+
if (node.pending.has(name)) {
|
|
4355
|
+
const from = inEvaluation.findIndex((entry) => entry.node === node && entry.name === name);
|
|
4356
|
+
return fail("#CYCLE!", `The attributes form a cycle: ${[...from < 0 ? [nameOfAttribute(node, name)] : inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name)), nameOfAttribute(node, name)].join(" -> ")}.`);
|
|
4357
|
+
}
|
|
4358
|
+
const part = node.parts.find((item) => item.key === name);
|
|
4359
|
+
if (part === void 0) return void 0;
|
|
4360
|
+
node.pending.add(name);
|
|
4361
|
+
inEvaluation.push({
|
|
4362
|
+
node,
|
|
4363
|
+
name
|
|
4364
|
+
});
|
|
4365
|
+
try {
|
|
4366
|
+
const result = part.evaluate({
|
|
4367
|
+
...node.ctx,
|
|
4368
|
+
self
|
|
4369
|
+
});
|
|
4370
|
+
node.values.set(name, result);
|
|
4371
|
+
return result;
|
|
4372
|
+
} finally {
|
|
4373
|
+
node.pending.delete(name);
|
|
4374
|
+
inEvaluation.pop();
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
/** TREE.RESULT: the JSON of a node. `open` holds the nodes and the containers whose JSON is in progress. */
|
|
4378
|
+
function jsonOfNode(node, self, open) {
|
|
4379
|
+
const record = {};
|
|
4380
|
+
for (const part of node.parts) {
|
|
4381
|
+
const result = attributeOf(node, self, part.key);
|
|
4382
|
+
if (!result.ok) return result;
|
|
4383
|
+
const value = plainValue(result.value, [...open, self]);
|
|
4384
|
+
if (!value.ok) return value;
|
|
4385
|
+
setField(record, part.key, value.value);
|
|
4386
|
+
}
|
|
4387
|
+
return ok(record);
|
|
4388
|
+
}
|
|
4389
|
+
/** This function gives a copy of a value with the JSON of each node in it. */
|
|
4390
|
+
function plainValue(value, open) {
|
|
4391
|
+
const node = refNodeOf(value);
|
|
4392
|
+
if (node !== void 0) {
|
|
4393
|
+
if (open.includes(value)) return fail("#CYCLE!", `The JSON of the node ${node.kind} at [${node.place.join(", ")}] holds the node itself.`);
|
|
4394
|
+
return jsonOfNode(node, value, open);
|
|
4395
|
+
}
|
|
4396
|
+
if (typeof value !== "object" || value === null || isHostThing(value) || open.includes(value)) return ok(value);
|
|
4397
|
+
if (Array.isArray(value)) {
|
|
4398
|
+
const items = [];
|
|
4399
|
+
for (const item of value) {
|
|
4400
|
+
const plain = plainValue(item, [...open, value]);
|
|
4401
|
+
if (!plain.ok) return plain;
|
|
4402
|
+
items.push(plain.value);
|
|
4403
|
+
}
|
|
4404
|
+
return ok(items);
|
|
4405
|
+
}
|
|
4406
|
+
if (!isRecord(value)) return ok(value);
|
|
4407
|
+
const copy = {};
|
|
4408
|
+
for (const key of Object.keys(value)) {
|
|
4409
|
+
const plain = plainValue(value[key], [...open, value]);
|
|
4410
|
+
if (!plain.ok) return plain;
|
|
4411
|
+
setField(copy, key, plain.value);
|
|
4412
|
+
}
|
|
4413
|
+
return ok(copy);
|
|
4414
|
+
}
|
|
4415
|
+
/**
|
|
4416
|
+
* TREE.LAZY, TREE.PARENT, TREE.SIBLING and TREE.ERROR: a node of an open tree gives its thing, and the root of a new
|
|
4417
|
+
* tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error.
|
|
4418
|
+
*/
|
|
4419
|
+
const treeDefinition = ({ parts, context, kind, place }) => {
|
|
4420
|
+
const outer = refNodeOf(context.self);
|
|
4421
|
+
const nested = outer !== void 0 && outer.tree.open;
|
|
4422
|
+
const node = {
|
|
4423
|
+
tree: nested ? outer.tree : { open: true },
|
|
4424
|
+
kind,
|
|
4425
|
+
place,
|
|
4426
|
+
parts,
|
|
4427
|
+
ctx: context,
|
|
4428
|
+
values: /* @__PURE__ */ new Map(),
|
|
4429
|
+
pending: /* @__PURE__ */ new Set()
|
|
4430
|
+
};
|
|
4431
|
+
const self = {
|
|
4432
|
+
[THING]: true,
|
|
4433
|
+
type: kind,
|
|
4434
|
+
label: `node ${kind}`,
|
|
4435
|
+
deref: (key) => {
|
|
4436
|
+
if (key === "parent") return context.self;
|
|
4437
|
+
if (key === "type") return kind;
|
|
4438
|
+
const result = attributeOf(node, self, String(key));
|
|
4439
|
+
if (result === void 0 || result.ok) return result?.ok === true ? result.value : void 0;
|
|
4440
|
+
throw new Failure(result.error);
|
|
4441
|
+
},
|
|
4442
|
+
toJson: () => {
|
|
4443
|
+
const result = jsonOfNode(node, self, []);
|
|
4444
|
+
if (result.ok) return result.value;
|
|
4445
|
+
throw new Failure(result.error);
|
|
4446
|
+
}
|
|
4447
|
+
};
|
|
4448
|
+
refNodes.set(self, node);
|
|
4449
|
+
if (nested) return ok(self);
|
|
4450
|
+
try {
|
|
4451
|
+
return jsonOfNode(node, self, []);
|
|
4452
|
+
} finally {
|
|
4453
|
+
node.tree.open = false;
|
|
4454
|
+
}
|
|
4455
|
+
};
|
|
4456
|
+
/**
|
|
4457
|
+
* The definitions of the reference for the evaluators of the standard machine that need their own reading. The
|
|
4458
|
+
* reference uses them for the standard evaluators, also without the option `definitions`. It holds `tree.node`.
|
|
4459
|
+
*/
|
|
4460
|
+
const STD_DEFINITIONS = { evaluators: { "tree.node": treeDefinition } };
|
|
4109
4461
|
const CLOSURE$1 = Symbol.for("coeval.closure");
|
|
4110
4462
|
/** This function sets an own field, also the field `__proto__` (SHAPE.PROTO). */
|
|
4111
4463
|
function setField(target, key, value) {
|
|
@@ -4327,8 +4679,14 @@ function valuesOfStrict(parts) {
|
|
|
4327
4679
|
/**
|
|
4328
4680
|
* This function tells if two values are the same for `agree`. Lists and plain records compare item by item and field
|
|
4329
4681
|
* 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`.
|
|
4331
|
-
*
|
|
4682
|
+
* `0`, and `NaN` is `NaN`. The identity of a list or of a plain record is not compared.
|
|
4683
|
+
*
|
|
4684
|
+
* Two closures of `fn` are the same when they have the same parameters. Two `Held` values are the same when their
|
|
4685
|
+
* errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
|
|
4686
|
+
* tells otherwise.
|
|
4687
|
+
*
|
|
4688
|
+
* `docEquals` uses the same rules for lists, plain records and the other objects. It has no rules for closures, for
|
|
4689
|
+
* `Held` values and for a rule of the caller. Thus two closures are equal there only when they are the same function.
|
|
4332
4690
|
*
|
|
4333
4691
|
* @param a - The first value.
|
|
4334
4692
|
* @param b - The second value.
|
|
@@ -4564,8 +4922,9 @@ function leafKind(kind) {
|
|
|
4564
4922
|
/**
|
|
4565
4923
|
* This function builds a random document for a world from the contracts of its kinds. It reads the shapes of the
|
|
4566
4924
|
* parts, the declared types, the permitted kinds and the names that the scope rules bind. Some choices are malformed
|
|
4567
|
-
* on purpose
|
|
4568
|
-
* nothing binds. The depth and the size have
|
|
4925
|
+
* on purpose, for example an unknown kind or an unknown name under a namespace with a fallback. Other examples are a
|
|
4926
|
+
* value without its shape, a bare value without a type and a name that nothing binds. The depth and the size have
|
|
4927
|
+
* limits.
|
|
4569
4928
|
*
|
|
4570
4929
|
* @param world - The world.
|
|
4571
4930
|
* @param options - The seed, the types, the limits and the data of the leaves.
|
|
@@ -4578,10 +4937,16 @@ function generate(world, options) {
|
|
|
4578
4937
|
const maxSize = options.maxSize ?? 24;
|
|
4579
4938
|
const malformed = options.malformed ?? .05;
|
|
4580
4939
|
const keys = options.keys ?? KEYS;
|
|
4940
|
+
const steps = options.steps ?? 2;
|
|
4941
|
+
const roots = options.roots ?? ROOTS;
|
|
4581
4942
|
const stepTypes = types.filter((type) => {
|
|
4582
4943
|
const found = lookupKind(world, type);
|
|
4583
4944
|
return found.ok && Object.hasOwn(found.kind.verbs, "step");
|
|
4584
4945
|
});
|
|
4946
|
+
const unknownNames = Object.keys(world.kinds).filter((key) => {
|
|
4947
|
+
const entry = world.kinds[key];
|
|
4948
|
+
return isNamespace(entry) && entry.fallback !== void 0;
|
|
4949
|
+
}).map((key) => [key, "nope"]);
|
|
4585
4950
|
let size = 0;
|
|
4586
4951
|
const kindOf = (type) => {
|
|
4587
4952
|
const found = lookupKind(world, type);
|
|
@@ -4651,11 +5016,12 @@ function generate(world, options) {
|
|
|
4651
5016
|
{ names: "a" },
|
|
4652
5017
|
["nowhere", "a"]
|
|
4653
5018
|
]);
|
|
4654
|
-
const
|
|
5019
|
+
const others = roots.filter((name) => name !== "names");
|
|
5020
|
+
const root = roots.includes("names") && pos.names.length > 0 && (others.length === 0 || rng.chance(.75)) || others.length === 0 ? "names" : rng.pick(others);
|
|
4655
5021
|
const path = [root];
|
|
4656
5022
|
if (root === "names") path.push(pickName(pos.names));
|
|
4657
5023
|
if (root === "world") path.push(String(rng.pick(types)[0]));
|
|
4658
|
-
for (let index = rng.int(
|
|
5024
|
+
for (let index = rng.int(steps + 1); index > 0; index -= 1) if (stepTypes.length > 0 && rng.chance(.2)) {
|
|
4659
5025
|
const type = rng.pick(stepTypes);
|
|
4660
5026
|
const kind = kindOf(type);
|
|
4661
5027
|
const value = refKind?.parts?.shape === "deep" && kind?.parts !== void 0 ? valueOf(kind.parts, [], {
|
|
@@ -4804,9 +5170,17 @@ function generate(world, options) {
|
|
|
4804
5170
|
case "one": return rng.chance(malformed) ? "other" : rng.pick(shape.values);
|
|
4805
5171
|
}
|
|
4806
5172
|
};
|
|
4807
|
-
/**
|
|
5173
|
+
/**
|
|
5174
|
+
* A malformed document: an unknown kind, or an unknown name under a namespace with a fallback. It can also be a
|
|
5175
|
+
* bad shape, a bare value without a type, or a bad reference.
|
|
5176
|
+
*/
|
|
4808
5177
|
const brokenAt = (pos) => {
|
|
4809
5178
|
const roll = rng.int(5);
|
|
5179
|
+
if (roll === 0 && unknownNames.length > 0 && rng.chance(.5)) {
|
|
5180
|
+
const type = rng.pick(unknownNames);
|
|
5181
|
+
const kind = kindOf(type);
|
|
5182
|
+
return envelope(type, kind?.parts === void 0 ? dataOf(type) : valueOf(kind.parts, [], pos, scopeOf(kind), type));
|
|
5183
|
+
}
|
|
4810
5184
|
if (roll === 0) return {
|
|
4811
5185
|
type: rng.pick(["nothing", ["no", "such"]]),
|
|
4812
5186
|
value: 1
|
|
@@ -4844,7 +5218,10 @@ function generate(world, options) {
|
|
|
4844
5218
|
size += 1;
|
|
4845
5219
|
const leaf = pos.depth >= maxDepth || size >= maxSize;
|
|
4846
5220
|
if (rng.chance(malformed)) return brokenAt(pos);
|
|
4847
|
-
if (pos.declared !== void 0 && rng.chance(leaf ? .7 : .3))
|
|
5221
|
+
if (pos.declared !== void 0 && rng.chance(leaf ? .7 : .3)) {
|
|
5222
|
+
const bare = dataOf([pos.declared]);
|
|
5223
|
+
if (!isEnvelope(bare) || permitted(typePath(bare.type) ?? [], pos.kinds)) return bare;
|
|
5224
|
+
}
|
|
4848
5225
|
let candidates = types.filter((type) => permitted(type, pos.kinds) && (!leaf || leafKind(kindOf(type))));
|
|
4849
5226
|
if (pos.hint !== void 0 && rng.chance(.6)) {
|
|
4850
5227
|
const hinted = candidates.filter((type) => pos.hint?.some((hint) => hint.length === type.length && hint.every((key, index) => key === type[index])));
|
|
@@ -4866,6 +5243,6 @@ function generate(world, options) {
|
|
|
4866
5243
|
});
|
|
4867
5244
|
}
|
|
4868
5245
|
//#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 };
|
|
5246
|
+
export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, 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 };
|
|
4870
5247
|
|
|
4871
5248
|
//# sourceMappingURL=index.js.map
|