@mark1russell7/coeval 0.18.0 → 0.19.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 +4 -4
- package/build/index.d.ts +263 -20
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2686 -1379
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codec.ts +30 -10
- package/src/durable.ts +17 -5
- package/src/facts.ts +55 -19
- package/src/formula.ts +45 -1
- package/src/index.ts +16 -2
- package/src/instances.ts +637 -0
- package/src/reads.ts +186 -2
- package/src/static.ts +357 -50
- package/src/stdlib.ts +127 -6
- package/src/types.ts +6 -2
- package/src/typing.ts +143 -3
- package/src/world.ts +49 -0
package/build/index.js
CHANGED
|
@@ -415,6 +415,52 @@ function isAlias(entry) {
|
|
|
415
415
|
function isNamespace(entry) {
|
|
416
416
|
return entry !== void 0 && !isKind(entry) && !isAlias(entry) && Object.hasOwn(entry, "kinds");
|
|
417
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* This function visits each kind of a world. These are the kinds of each namespace, at each depth, and the fallback
|
|
420
|
+
* kind of each namespace and of the world. It does not follow an alias, because the kind of an alias has its own place. A
|
|
421
|
+
* fallback kind has the keys of its namespace. It comes after the entries of that namespace. `checkWorld` reads the
|
|
422
|
+
* kinds with this one function.
|
|
423
|
+
*
|
|
424
|
+
* @param world - The world.
|
|
425
|
+
* @param visit - The function that gets each kind, its keys from the root of the world, and `true` for a fallback kind.
|
|
426
|
+
*/
|
|
427
|
+
function eachKind(world, visit) {
|
|
428
|
+
const way = /* @__PURE__ */ new Set();
|
|
429
|
+
const entries = (kinds, path) => {
|
|
430
|
+
for (const key of Object.keys(kinds)) {
|
|
431
|
+
const entry = kinds[key];
|
|
432
|
+
const here = [...path, key];
|
|
433
|
+
if (isKind(entry)) visit(entry, here, false);
|
|
434
|
+
else if (isNamespace(entry) && !way.has(entry)) {
|
|
435
|
+
way.add(entry);
|
|
436
|
+
entries(entry.kinds, here);
|
|
437
|
+
if (entry.fallback !== void 0) visit(entry.fallback, here, true);
|
|
438
|
+
way.delete(entry);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
entries(world.kinds, []);
|
|
443
|
+
if (world.fallback !== void 0) visit(world.fallback, [], true);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* This function gives the kind at a path of a world, without an alias and without a fallback kind. A namespace on the
|
|
447
|
+
* way gives its entries.
|
|
448
|
+
*
|
|
449
|
+
* @param world - The world.
|
|
450
|
+
* @param path - The keys from the root of the world.
|
|
451
|
+
* @returns The kind, or `undefined` when no kind is at the path.
|
|
452
|
+
*/
|
|
453
|
+
function kindAt(world, path) {
|
|
454
|
+
let entries = world.kinds;
|
|
455
|
+
let entry;
|
|
456
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
457
|
+
entry = entryAt(entries, path[index]);
|
|
458
|
+
if (index === path.length - 1) break;
|
|
459
|
+
if (!isNamespace(entry)) return void 0;
|
|
460
|
+
entries = entry.kinds;
|
|
461
|
+
}
|
|
462
|
+
return isKind(entry) ? entry : void 0;
|
|
463
|
+
}
|
|
418
464
|
function entryAt(entries, key) {
|
|
419
465
|
const name = String(key);
|
|
420
466
|
return Object.hasOwn(entries, name) ? entries[name] : void 0;
|
|
@@ -679,7 +725,11 @@ function formOf(type, domains) {
|
|
|
679
725
|
closed
|
|
680
726
|
};
|
|
681
727
|
}
|
|
682
|
-
/**
|
|
728
|
+
/**
|
|
729
|
+
* This function gives each base type that holds the members of a base type: the type itself and each parent, through
|
|
730
|
+
* each level. A cycle of parents ends at a name that the line has, and a parent that is not a domain ends its branch.
|
|
731
|
+
* The function is internal: `encodeValue` uses it too (D109).
|
|
732
|
+
*/
|
|
683
733
|
function lineOf(name, domains) {
|
|
684
734
|
const seen = /* @__PURE__ */ new Set([name]);
|
|
685
735
|
const queue = [name];
|
|
@@ -3580,7 +3630,7 @@ function selfLabel(self) {
|
|
|
3580
3630
|
return describe(self).slice(0, 40);
|
|
3581
3631
|
}
|
|
3582
3632
|
}
|
|
3583
|
-
const NO_ANSWER = {
|
|
3633
|
+
const NO_ANSWER$1 = {
|
|
3584
3634
|
verb: "eval",
|
|
3585
3635
|
error: "no-answer",
|
|
3586
3636
|
steps: []
|
|
@@ -4065,7 +4115,7 @@ function holderNode() {
|
|
|
4065
4115
|
value: null
|
|
4066
4116
|
},
|
|
4067
4117
|
declared: false,
|
|
4068
|
-
answer: NO_ANSWER,
|
|
4118
|
+
answer: NO_ANSWER$1,
|
|
4069
4119
|
side: "derive",
|
|
4070
4120
|
names: [],
|
|
4071
4121
|
scope: {},
|
|
@@ -4219,7 +4269,7 @@ const NO_META = /* @__PURE__ */ Object.freeze({
|
|
|
4219
4269
|
value: null
|
|
4220
4270
|
},
|
|
4221
4271
|
declared: false,
|
|
4222
|
-
answer: NO_ANSWER,
|
|
4272
|
+
answer: NO_ANSWER$1,
|
|
4223
4273
|
side: "derive"
|
|
4224
4274
|
});
|
|
4225
4275
|
function compileNode(c, value, place, declared, depth, address = false) {
|
|
@@ -4879,7 +4929,7 @@ function rootContext(machine, ctx) {
|
|
|
4879
4929
|
*/
|
|
4880
4930
|
function prepare(machine, doc, options = {}) {
|
|
4881
4931
|
const verb = options.verb ?? "eval";
|
|
4882
|
-
const noAnswer = verb === "eval" ? NO_ANSWER : {
|
|
4932
|
+
const noAnswer = verb === "eval" ? NO_ANSWER$1 : {
|
|
4883
4933
|
verb,
|
|
4884
4934
|
error: "no-answer",
|
|
4885
4935
|
steps: []
|
|
@@ -5500,10 +5550,10 @@ const STD_EVALUATORS = {
|
|
|
5500
5550
|
const found = resolve(data);
|
|
5501
5551
|
return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
|
|
5502
5552
|
},
|
|
5503
|
-
"tree.node": (input) => treeNode(input)
|
|
5553
|
+
"tree.node": (input) => treeNode(input, void 0)
|
|
5504
5554
|
};
|
|
5505
5555
|
/** The brand of a node of the decorated tree. Its value is the state that the kit reads. */
|
|
5506
|
-
const TREE_NODE = /* @__PURE__ */ Symbol.for("coeval.tree.node");
|
|
5556
|
+
const TREE_NODE$1 = /* @__PURE__ */ Symbol.for("coeval.tree.node");
|
|
5507
5557
|
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
5508
5558
|
const ATTRIBUTE_RUNGS = /* @__PURE__ */ Object.freeze([
|
|
5509
5559
|
"own",
|
|
@@ -5512,6 +5562,17 @@ const ATTRIBUTE_RUNGS = /* @__PURE__ */ Object.freeze([
|
|
|
5512
5562
|
"copy",
|
|
5513
5563
|
"default"
|
|
5514
5564
|
]);
|
|
5565
|
+
/**
|
|
5566
|
+
* This function gives the code of the kind of a node of the decorated tree, with the options of the host. A host puts
|
|
5567
|
+
* the code at the name `tree.node` of its machine. Without a cell, the code is the standard code.
|
|
5568
|
+
*
|
|
5569
|
+
* @param options - The options, for example the memo cells of the host.
|
|
5570
|
+
* @returns The code.
|
|
5571
|
+
*/
|
|
5572
|
+
function treeNodeEvaluator(options = {}) {
|
|
5573
|
+
const host = options.cell;
|
|
5574
|
+
return host === void 0 ? STD_EVALUATORS["tree.node"] : (input) => treeNode(input, host);
|
|
5575
|
+
}
|
|
5515
5576
|
/** The capabilities of an equation: none (CAP.PURE). */
|
|
5516
5577
|
const NO_CAPS$1 = /* @__PURE__ */ Object.freeze([]);
|
|
5517
5578
|
/** The names of an equation of `equations`, of a `unit` and of a `default`. */
|
|
@@ -5555,6 +5616,29 @@ function contractOf$1(machine, typePath) {
|
|
|
5555
5616
|
};
|
|
5556
5617
|
}
|
|
5557
5618
|
const BUSY = /* @__PURE__ */ Object.freeze({ busy: true });
|
|
5619
|
+
/** The answer of the ladder when no rung answers. */
|
|
5620
|
+
const NO_ANSWER = /* @__PURE__ */ Object.freeze({
|
|
5621
|
+
result: void 0,
|
|
5622
|
+
by: void 0
|
|
5623
|
+
});
|
|
5624
|
+
/**
|
|
5625
|
+
* The marks of the attributes in evaluation, for each place of a plan, with the cells of a host (TREE.CELL). Nodes of
|
|
5626
|
+
* two evaluations share their place and their kept cells, thus they share their marks.
|
|
5627
|
+
*/
|
|
5628
|
+
const marksByPlace = /* @__PURE__ */ new WeakMap();
|
|
5629
|
+
/** The trees of the cells of a host, for each place of a root in a plan. Nodes of two evaluations stay in one tree. */
|
|
5630
|
+
const buildsByPlace = /* @__PURE__ */ new WeakMap();
|
|
5631
|
+
/** This function gives the value of a weak map, or puts a new value in it. */
|
|
5632
|
+
function keptAt(map, place, make) {
|
|
5633
|
+
let value = map.get(place);
|
|
5634
|
+
if (value === void 0) {
|
|
5635
|
+
value = make();
|
|
5636
|
+
map.set(place, value);
|
|
5637
|
+
}
|
|
5638
|
+
return value;
|
|
5639
|
+
}
|
|
5640
|
+
/** A value that no exception has. */
|
|
5641
|
+
const NOT_THROWN = /* @__PURE__ */ Symbol("coeval.tree.not-thrown");
|
|
5558
5642
|
/** The attributes in evaluation, in the order of their demands. An evaluation does not wait, thus one list serves. */
|
|
5559
5643
|
const demands = [];
|
|
5560
5644
|
function attributeText(key, type, place) {
|
|
@@ -5568,7 +5652,7 @@ function cycleAt(cells, key, text) {
|
|
|
5568
5652
|
return fail("#CYCLE!", `The attributes form a cycle: ${chain.join(" -> ")}.`);
|
|
5569
5653
|
}
|
|
5570
5654
|
function stateOf(thing) {
|
|
5571
|
-
return typeof thing === "object" && thing !== null ? thing[TREE_NODE] : void 0;
|
|
5655
|
+
return typeof thing === "object" && thing !== null ? thing[TREE_NODE$1] : void 0;
|
|
5572
5656
|
}
|
|
5573
5657
|
/** This function sets an own field, also the field `__proto__` (SHAPE.PROTO). */
|
|
5574
5658
|
function putField(target, key, value) {
|
|
@@ -5625,14 +5709,17 @@ function plainOf(value, path) {
|
|
|
5625
5709
|
* the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
|
|
5626
5710
|
* fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
|
|
5627
5711
|
* The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
|
|
5712
|
+
*
|
|
5713
|
+
* The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of the node.
|
|
5628
5714
|
*/
|
|
5629
|
-
function treeThing(input, build, outer) {
|
|
5715
|
+
function treeThing(input, build, outer, host) {
|
|
5630
5716
|
const { parts, context, kind: type, place, machine } = input;
|
|
5631
5717
|
const runtime = input[RUNTIME];
|
|
5632
5718
|
const { tree, of } = contractOf$1(machine, input.typePath);
|
|
5633
5719
|
const above = stateOf(outer);
|
|
5634
5720
|
const up = above !== void 0 && above.build === build ? above : void 0;
|
|
5635
5721
|
const cells = /* @__PURE__ */ new Map();
|
|
5722
|
+
const marks = host === void 0 ? cells : keptAt(marksByPlace, place, () => /* @__PURE__ */ new Map());
|
|
5636
5723
|
/** The positions of the items of the list `children`, for the names of the children. */
|
|
5637
5724
|
let positions;
|
|
5638
5725
|
const fold = (key, declared) => {
|
|
@@ -5696,7 +5783,50 @@ function treeThing(input, build, outer) {
|
|
|
5696
5783
|
by: "default"
|
|
5697
5784
|
};
|
|
5698
5785
|
};
|
|
5786
|
+
/**
|
|
5787
|
+
* This function gives an attribute through the cell of the host (TREE.CELL). The cell is the memo. The node keeps
|
|
5788
|
+
* the last answer only for `has` and `trace`.
|
|
5789
|
+
*/
|
|
5790
|
+
const through = host === void 0 ? void 0 : (key) => {
|
|
5791
|
+
const text = attributeText(key, type, place);
|
|
5792
|
+
if (marks.has(key)) return cycleAt(marks, key, text);
|
|
5793
|
+
if (tree === void 0 && up?.tree === void 0 && !parts.some((item) => item.key === key)) return void 0;
|
|
5794
|
+
marks.set(key, BUSY);
|
|
5795
|
+
demands.push({
|
|
5796
|
+
cells: marks,
|
|
5797
|
+
key,
|
|
5798
|
+
text
|
|
5799
|
+
});
|
|
5800
|
+
let thrown = NOT_THROWN;
|
|
5801
|
+
let answer;
|
|
5802
|
+
try {
|
|
5803
|
+
answer = host(place, key, () => {
|
|
5804
|
+
try {
|
|
5805
|
+
return climb(key) ?? NO_ANSWER;
|
|
5806
|
+
} catch (error) {
|
|
5807
|
+
thrown = error;
|
|
5808
|
+
throw error;
|
|
5809
|
+
}
|
|
5810
|
+
});
|
|
5811
|
+
} catch (error) {
|
|
5812
|
+
if (error === thrown) throw error;
|
|
5813
|
+
answer = {
|
|
5814
|
+
result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${demands.map((demand) => demand.text).join(" -> ")}.`, { cause: error }),
|
|
5815
|
+
by: void 0
|
|
5816
|
+
};
|
|
5817
|
+
} finally {
|
|
5818
|
+
demands.pop();
|
|
5819
|
+
marks.delete(key);
|
|
5820
|
+
}
|
|
5821
|
+
cells.set(key, {
|
|
5822
|
+
busy: false,
|
|
5823
|
+
result: answer.result,
|
|
5824
|
+
by: answer.by
|
|
5825
|
+
});
|
|
5826
|
+
return answer.result;
|
|
5827
|
+
};
|
|
5699
5828
|
const read = (key) => {
|
|
5829
|
+
if (through !== void 0) return through(key);
|
|
5700
5830
|
const cell = cells.get(key);
|
|
5701
5831
|
if (cell !== void 0) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
|
|
5702
5832
|
if (tree === void 0 && up?.tree === void 0 && !parts.some((item) => item.key === key)) return void 0;
|
|
@@ -5790,7 +5920,7 @@ function treeThing(input, build, outer) {
|
|
|
5790
5920
|
};
|
|
5791
5921
|
const node = {
|
|
5792
5922
|
[THING]: true,
|
|
5793
|
-
[TREE_NODE]: state,
|
|
5923
|
+
[TREE_NODE$1]: state,
|
|
5794
5924
|
type,
|
|
5795
5925
|
label: `node ${type}`,
|
|
5796
5926
|
deref(key) {
|
|
@@ -5844,14 +5974,16 @@ const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
|
|
|
5844
5974
|
* This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
|
|
5845
5975
|
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
|
|
5846
5976
|
* node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
|
|
5977
|
+
* The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of each node.
|
|
5847
5978
|
*/
|
|
5848
|
-
function treeNode(input) {
|
|
5979
|
+
function treeNode(input, host) {
|
|
5849
5980
|
const found = input.resolve(SELF_PATH, NO_HOPS);
|
|
5850
5981
|
const outer = found.ok ? found.thing : void 0;
|
|
5851
5982
|
const above = stateOf(outer);
|
|
5852
|
-
if (above !== void 0 && above.build.open) return ok(treeThing(input, above.build, outer));
|
|
5853
|
-
const build = { open: true };
|
|
5854
|
-
|
|
5983
|
+
if (above !== void 0 && above.build.open) return ok(treeThing(input, above.build, outer, host));
|
|
5984
|
+
const build = host === void 0 ? { open: true } : keptAt(buildsByPlace, input.place, () => ({ open: true }));
|
|
5985
|
+
build.open = true;
|
|
5986
|
+
const root = treeThing(input, build, outer, host);
|
|
5855
5987
|
try {
|
|
5856
5988
|
return stateOf(root).json(/* @__PURE__ */ new Set());
|
|
5857
5989
|
} finally {
|
|
@@ -6593,501 +6725,1968 @@ function typePass(machine, nodes, verb, expect, rootTypes = {}) {
|
|
|
6593
6725
|
places
|
|
6594
6726
|
};
|
|
6595
6727
|
}
|
|
6596
|
-
// src/static.ts
|
|
6597
|
-
/** The names of a walk without names. A walk does not change a set at any time: it makes a new set to add names. */
|
|
6598
|
-
const NO_NAMES = /* @__PURE__ */ new Set();
|
|
6599
|
-
/** This function gives the options of a walk without the names of the host. */
|
|
6600
|
-
function withoutNames(options) {
|
|
6601
|
-
const { names: _host, ...rest } = options;
|
|
6602
|
-
return rest;
|
|
6603
|
-
}
|
|
6604
|
-
/** This function gives the keys of a trail, from its start. It keeps the result in the trail. */
|
|
6605
|
-
function keysOf(trail) {
|
|
6606
|
-
if (trail === void 0) return [];
|
|
6607
|
-
if (trail.full !== void 0) return trail.full;
|
|
6608
|
-
const steps = [];
|
|
6609
|
-
let here = trail;
|
|
6610
|
-
while (here !== void 0 && here.full === void 0) {
|
|
6611
|
-
steps.push(here);
|
|
6612
|
-
here = here.up;
|
|
6613
|
-
}
|
|
6614
|
-
const keys = here?.full === void 0 ? [] : [...here.full];
|
|
6615
|
-
for (let index = steps.length - 1; index >= 0; index -= 1) for (const key of steps[index].keys) keys.push(key);
|
|
6616
|
-
trail.full = keys;
|
|
6617
|
-
return keys;
|
|
6618
|
-
}
|
|
6619
|
-
/** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
|
|
6620
|
-
var Visit = class {
|
|
6621
|
-
doc;
|
|
6622
|
-
declared;
|
|
6623
|
-
depth;
|
|
6624
|
-
names;
|
|
6625
|
-
later;
|
|
6626
|
-
slot;
|
|
6627
|
-
parent;
|
|
6628
|
-
kind;
|
|
6629
|
-
typePath;
|
|
6630
|
-
address;
|
|
6631
|
-
caps;
|
|
6632
|
-
#place;
|
|
6633
|
-
#at;
|
|
6634
|
-
#bound;
|
|
6635
|
-
#expect;
|
|
6636
|
-
constructor(place, at, doc, declared, depth, names, later, slot, parent, kind, typePath, address, caps, bound, expect) {
|
|
6637
|
-
this.doc = doc;
|
|
6638
|
-
this.declared = declared;
|
|
6639
|
-
this.depth = depth;
|
|
6640
|
-
this.names = names;
|
|
6641
|
-
this.later = later;
|
|
6642
|
-
this.slot = slot;
|
|
6643
|
-
this.parent = parent;
|
|
6644
|
-
this.kind = kind;
|
|
6645
|
-
this.typePath = typePath;
|
|
6646
|
-
this.address = address;
|
|
6647
|
-
this.caps = caps;
|
|
6648
|
-
this.#place = place;
|
|
6649
|
-
this.#at = at;
|
|
6650
|
-
this.#bound = bound;
|
|
6651
|
-
this.#expect = expect;
|
|
6652
|
-
}
|
|
6653
|
-
/** The names that the scope rules of the parent bind for this document. The check of types reads them. */
|
|
6654
|
-
get bound() {
|
|
6655
|
-
return this.#bound;
|
|
6656
|
-
}
|
|
6657
|
-
/** The type that the shape of the parent declares for this part. The check of types reads it. */
|
|
6658
|
-
get expect() {
|
|
6659
|
-
return this.#expect;
|
|
6660
|
-
}
|
|
6661
|
-
get place() {
|
|
6662
|
-
return keysOf(this.#place);
|
|
6663
|
-
}
|
|
6664
|
-
get at() {
|
|
6665
|
-
return keysOf(this.#at);
|
|
6666
|
-
}
|
|
6667
|
-
};
|
|
6668
6728
|
/**
|
|
6669
|
-
* This function
|
|
6670
|
-
*
|
|
6671
|
-
*
|
|
6672
|
-
* depth, thus a deep document cannot overflow it. The time and the memory of a walk are linear in the size of the
|
|
6673
|
-
* document. A visit makes the keys of its place and of its path only when a reader asks for them.
|
|
6729
|
+
* This function examines each value type of a machine before a document exists (TYPE.WORLD, D110). It reads the
|
|
6730
|
+
* `result` and each `expect` of each kind, the parents and the steps of each domain, and the declarations of each type
|
|
6731
|
+
* rule. Each problem of `typeIssues` gives one issue with the keys of the bad term.
|
|
6674
6732
|
*
|
|
6675
|
-
*
|
|
6676
|
-
*
|
|
6677
|
-
*
|
|
6678
|
-
* @param
|
|
6733
|
+
* A term shape with `kinds` and `expect` also gets an issue for each kind of its list whose `result` does not fit
|
|
6734
|
+
* `expect`. A check of types gives `#TYPE!` at each document of that kind there.
|
|
6735
|
+
*
|
|
6736
|
+
* @param machine - The machine.
|
|
6737
|
+
* @returns The issues: the kinds in the order of the world, then the domains, then the type rules.
|
|
6679
6738
|
*/
|
|
6680
|
-
function
|
|
6681
|
-
const
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6739
|
+
function machineTypeIssues(machine) {
|
|
6740
|
+
const domains = machine.domains;
|
|
6741
|
+
const world = machine.world;
|
|
6742
|
+
const out = [];
|
|
6743
|
+
const add = (owner, at, reason) => {
|
|
6744
|
+
out.push({
|
|
6745
|
+
code: "#TYPE!",
|
|
6746
|
+
...owner,
|
|
6747
|
+
verb: "eval",
|
|
6748
|
+
at,
|
|
6749
|
+
reason
|
|
6750
|
+
});
|
|
6751
|
+
};
|
|
6752
|
+
/** This function examines one value type. It gives `true` when the type has no problem. */
|
|
6753
|
+
const examine = (owner, at, type, what) => {
|
|
6754
|
+
const problems = typeIssues(type, domains);
|
|
6755
|
+
for (const problem of problems) add(owner, [...at, ...problem.at], `The ${what} is not a correct value type. ${problem.reason}`);
|
|
6756
|
+
return problems.length === 0;
|
|
6757
|
+
};
|
|
6758
|
+
/**
|
|
6759
|
+
* This function finds each kind of the list `kinds` of a term whose result does not fit the `expect` of the term.
|
|
6760
|
+
* Only a kind at the path itself counts. A path through an alias or to a fallback kind gives no issue.
|
|
6761
|
+
*/
|
|
6762
|
+
const results = (owner, kinds, at, expect) => {
|
|
6763
|
+
if (isUnknown(expect, domains)) return;
|
|
6764
|
+
kinds.forEach((path, index) => {
|
|
6765
|
+
const result = (Array.isArray(path) ? kindAt(world, path) : void 0)?.result;
|
|
6766
|
+
if (result === void 0 || typeIssues(result, domains).length > 0 || fits(result, expect, domains)) return;
|
|
6767
|
+
const text = path.join(".");
|
|
6768
|
+
add(owner, [
|
|
6769
|
+
...at,
|
|
6770
|
+
"kinds",
|
|
6771
|
+
index
|
|
6772
|
+
], `The kind ${text} has the result ${typeText(result)}, and the part needs the type ${typeText(expect)}. Each document of ${text} here gets #TYPE!.`);
|
|
6773
|
+
});
|
|
6774
|
+
};
|
|
6775
|
+
eachKind(world, (kind, path, fallback) => {
|
|
6776
|
+
const owner = fallback ? {
|
|
6777
|
+
kind: path,
|
|
6778
|
+
fallback: true
|
|
6779
|
+
} : { kind: path };
|
|
6780
|
+
const name = fallback ? path.length === 0 ? "fallback kind of the world" : `fallback kind of ${path.join(".")}` : `kind ${path.join(".")}`;
|
|
6781
|
+
if (kind.result !== void 0) examine(owner, ["result"], kind.result, `result of the ${name}`);
|
|
6782
|
+
const way = /* @__PURE__ */ new Set();
|
|
6783
|
+
const shape = (spec, at) => {
|
|
6784
|
+
if (typeof spec !== "object" || spec === null || way.has(spec)) return;
|
|
6785
|
+
way.add(spec);
|
|
6786
|
+
const expect = spec.expect;
|
|
6787
|
+
const where = [...at, "expect"];
|
|
6788
|
+
if (expect !== void 0 && examine(owner, where, expect, `expected type at ${where.join(".")} of the ${name}`) && spec.shape === "term" && Array.isArray(spec.kinds)) results(owner, spec.kinds, at, expect);
|
|
6789
|
+
if (spec.shape === "list" || spec.shape === "map") shape(spec.item, [...at, "item"]);
|
|
6790
|
+
if (spec.shape === "record") {
|
|
6791
|
+
const fields = spec.fields;
|
|
6792
|
+
if (typeof fields === "object" && fields !== null && !Array.isArray(fields)) for (const key of Object.keys(fields)) shape(fields[key], [
|
|
6793
|
+
...at,
|
|
6794
|
+
"fields",
|
|
6795
|
+
key
|
|
6796
|
+
]);
|
|
6797
|
+
shape(spec.rest, [...at, "rest"]);
|
|
6798
|
+
}
|
|
6799
|
+
way.delete(spec);
|
|
6800
|
+
};
|
|
6801
|
+
shape(kind.parts, ["parts"]);
|
|
6802
|
+
});
|
|
6803
|
+
const table = domains ?? {};
|
|
6804
|
+
for (const name of Object.keys(table)) {
|
|
6805
|
+
if (name === "unknown" || name === "never") continue;
|
|
6806
|
+
const owner = {
|
|
6807
|
+
kind: [],
|
|
6808
|
+
domain: name
|
|
6809
|
+
};
|
|
6810
|
+
const domain = domainOf(name, domains);
|
|
6811
|
+
if (domain === void 0) {
|
|
6812
|
+
add(owner, ["is"], `The domain ${name} has no test is. Each use of the name ${name} reads as unknown.`);
|
|
6706
6813
|
continue;
|
|
6707
6814
|
}
|
|
6708
|
-
const
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6815
|
+
const under = domain.under;
|
|
6816
|
+
if (Array.isArray(under)) under.forEach((parent, index) => {
|
|
6817
|
+
if (parent === "unknown" || parent === "never") return;
|
|
6818
|
+
if (typeof parent !== "string" || domainOf(parent, domains) === void 0) add(owner, ["under", index], `The parent ${String(parent)} of the domain ${name} is not a domain of the machine.`);
|
|
6819
|
+
});
|
|
6820
|
+
const steps = domain.steps;
|
|
6821
|
+
if (typeof steps === "object" && steps !== null) for (const key of Object.keys(steps)) examine(owner, ["steps", key], steps[key], `type of the step ${key} of the domain ${name}`);
|
|
6822
|
+
}
|
|
6823
|
+
for (const name of Object.keys(machine.evaluators)) {
|
|
6824
|
+
const declared = ruleOf(machine, name)?.declared;
|
|
6825
|
+
if (typeof declared !== "function") continue;
|
|
6826
|
+
const owner = {
|
|
6827
|
+
kind: [],
|
|
6828
|
+
evaluator: name
|
|
6714
6829
|
};
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
keys: split.at
|
|
6721
|
-
}),
|
|
6722
|
-
code: "#VALUE!",
|
|
6723
|
-
reason: split.reason
|
|
6724
|
-
});
|
|
6830
|
+
let list;
|
|
6831
|
+
try {
|
|
6832
|
+
list = declared();
|
|
6833
|
+
} catch (error) {
|
|
6834
|
+
add(owner, [], `The type rule of ${name} fails: ${message(error)}`);
|
|
6725
6835
|
continue;
|
|
6726
6836
|
}
|
|
6727
|
-
const
|
|
6728
|
-
|
|
6729
|
-
const slot = split.parts[index];
|
|
6730
|
-
const extra = sees[index] ?? [];
|
|
6731
|
-
const hidden = later[index] ?? [];
|
|
6732
|
-
stack.push({
|
|
6733
|
-
value: slot.item,
|
|
6734
|
-
place: slot.path.length === 0 ? item.place : {
|
|
6735
|
-
up: item.place,
|
|
6736
|
-
keys: slot.path
|
|
6737
|
-
},
|
|
6738
|
-
at: slot.path.length === 0 ? base : {
|
|
6739
|
-
up: base,
|
|
6740
|
-
keys: slot.path
|
|
6741
|
-
},
|
|
6742
|
-
declared: slot.of,
|
|
6743
|
-
depth: item.depth + 1,
|
|
6744
|
-
names: extra.length === 0 ? item.names : /* @__PURE__ */ new Set([...item.names, ...extra]),
|
|
6745
|
-
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden]),
|
|
6746
|
-
address: slot.address === true,
|
|
6747
|
-
slot: slot.path,
|
|
6748
|
-
parent: here,
|
|
6749
|
-
caps: item.caps === void 0 || kind.caps === void 0 ? item.caps : partCaps(kind.caps, slot.path, item.caps),
|
|
6750
|
-
bound: extra,
|
|
6751
|
-
expect: slot.expect
|
|
6752
|
-
});
|
|
6753
|
-
}
|
|
6837
|
+
for (const item of list) if ("problem" in item) add(owner, item.at, item.problem);
|
|
6838
|
+
else examine(owner, item.at, item.type, item.what);
|
|
6754
6839
|
}
|
|
6840
|
+
return out;
|
|
6755
6841
|
}
|
|
6842
|
+
// src/facts.ts
|
|
6843
|
+
/** The lazy rules that cover each part: the rules of a document whose evaluator the contract does not describe. */
|
|
6844
|
+
const EACH$2 = "each";
|
|
6845
|
+
/** An empty list. One frozen list serves each empty list of the facts, thus a store of many facts keeps less (PLAN.LEAN). */
|
|
6846
|
+
const NONE = /* @__PURE__ */ Object.freeze([]);
|
|
6756
6847
|
/**
|
|
6757
|
-
* This function
|
|
6758
|
-
*
|
|
6759
|
-
* outside: its dependencies. When the kind `ref` has parts, the walk also finds the references in its step documents.
|
|
6760
|
-
*
|
|
6761
|
-
* @param world - The world of the kinds.
|
|
6762
|
-
* @param doc - The document, as JSON.
|
|
6763
|
-
* @param options - The type of a bare root value. The names of the host do not change the result.
|
|
6764
|
-
* @returns The references, in the order of the document.
|
|
6765
|
-
*/
|
|
6766
|
-
function refsOf(world, doc, options = {}) {
|
|
6767
|
-
const out = [];
|
|
6768
|
-
walk(world, doc, (v) => {
|
|
6769
|
-
if (!isRef(v.doc)) return void 0;
|
|
6770
|
-
const path = v.doc.value;
|
|
6771
|
-
const root = typeof path[0] === "string" ? path[0] : "";
|
|
6772
|
-
const first = path[1];
|
|
6773
|
-
const use = {
|
|
6774
|
-
path,
|
|
6775
|
-
root,
|
|
6776
|
-
free: !(root === "names" && isKey(first) && v.names.has(String(first))),
|
|
6777
|
-
address: v.address === true
|
|
6778
|
-
};
|
|
6779
|
-
Object.defineProperty(use, "place", {
|
|
6780
|
-
enumerable: true,
|
|
6781
|
-
get: () => v.place
|
|
6782
|
-
});
|
|
6783
|
-
Object.defineProperty(use, "at", {
|
|
6784
|
-
enumerable: true,
|
|
6785
|
-
get: () => v.at
|
|
6786
|
-
});
|
|
6787
|
-
Object.defineProperty(use, "visit", {
|
|
6788
|
-
enumerable: false,
|
|
6789
|
-
value: v
|
|
6790
|
-
});
|
|
6791
|
-
out.push(use);
|
|
6792
|
-
}, options.names === void 0 ? options : withoutNames(options));
|
|
6793
|
-
return out;
|
|
6794
|
-
}
|
|
6795
|
-
function setAt(value, path, next) {
|
|
6796
|
-
if (path.length === 0) return next;
|
|
6797
|
-
const [key, ...rest] = path;
|
|
6798
|
-
if (Array.isArray(value)) {
|
|
6799
|
-
const copy = [...value];
|
|
6800
|
-
copy[Number(key)] = setAt(value[Number(key)], rest, next);
|
|
6801
|
-
return copy;
|
|
6802
|
-
}
|
|
6803
|
-
const record = isRecord(value) ? value : {};
|
|
6804
|
-
const copy = {};
|
|
6805
|
-
for (const name of Object.keys(record)) Object.defineProperty(copy, name, {
|
|
6806
|
-
value: record[name],
|
|
6807
|
-
enumerable: true,
|
|
6808
|
-
writable: true,
|
|
6809
|
-
configurable: true
|
|
6810
|
-
});
|
|
6811
|
-
Object.defineProperty(copy, String(key), {
|
|
6812
|
-
value: setAt(record[String(key)], rest, next),
|
|
6813
|
-
enumerable: true,
|
|
6814
|
-
writable: true,
|
|
6815
|
-
configurable: true
|
|
6816
|
-
});
|
|
6817
|
-
return copy;
|
|
6818
|
-
}
|
|
6819
|
-
/**
|
|
6820
|
-
* This function changes the free references of a document. The document is not changed: the function gives a new
|
|
6821
|
-
* document that shares each part without a change.
|
|
6822
|
-
*
|
|
6823
|
-
* @param world - The world of the kinds.
|
|
6824
|
-
* @param doc - The document, as JSON.
|
|
6825
|
-
* @param change - The function that gives the new document of a reference, or `undefined` to keep it.
|
|
6826
|
-
* @param options - The type of a bare root value, and the names that the host binds at the root.
|
|
6827
|
-
* @returns The new document.
|
|
6828
|
-
*/
|
|
6829
|
-
function mapRefs(world, doc, change, options = {}) {
|
|
6830
|
-
let out = doc;
|
|
6831
|
-
for (const use of refsOf(world, doc, options)) {
|
|
6832
|
-
if (!use.free) continue;
|
|
6833
|
-
const next = change(use);
|
|
6834
|
-
if (next !== void 0) out = setAt(out, use.at, next);
|
|
6835
|
-
}
|
|
6836
|
-
return out;
|
|
6837
|
-
}
|
|
6838
|
-
/** The least authority: the capability `[]` permits each effect (CAP.KEYS). */
|
|
6839
|
-
const EACH$2 = /* @__PURE__ */ Object.freeze([[]]);
|
|
6840
|
-
/** No declared effect. One list serves each visit without effects. */
|
|
6841
|
-
const NO_EFFECTS = /* @__PURE__ */ Object.freeze([]);
|
|
6842
|
-
/**
|
|
6843
|
-
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys
|
|
6844
|
-
* (FACTS.EFFECTS, VERB.EFFECTS). The text `"type"` gives the type of the document after each alias. A visit without a
|
|
6845
|
-
* kind declares no effect. `check`, `factsOf` and `effectsOf` read the declarations with this one function (D27).
|
|
6846
|
-
*
|
|
6847
|
-
* @param v - The visit.
|
|
6848
|
-
* @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
|
|
6849
|
-
* @returns The keys of each declared effect, in the order of the contract.
|
|
6848
|
+
* This function gives a list of the facts with no free room. A list that grows by `push` keeps room for more items,
|
|
6849
|
+
* thus a copy of a short list is smaller.
|
|
6850
6850
|
*/
|
|
6851
|
-
function
|
|
6852
|
-
return
|
|
6851
|
+
function tight(list) {
|
|
6852
|
+
return list.length === 0 ? NONE : list.slice();
|
|
6853
6853
|
}
|
|
6854
6854
|
/**
|
|
6855
|
-
* This function gives the
|
|
6856
|
-
*
|
|
6857
|
-
* for
|
|
6855
|
+
* This function gives the name of the evaluator that the ladder finds for a verb (VERB.LADDER). It also tells if
|
|
6856
|
+
* the contract of the kind describes that code. The contract describes the code of its kind for its verb `contract`.
|
|
6857
|
+
* An own entry for another evaluator has code that the contract does not describe.
|
|
6858
6858
|
*/
|
|
6859
|
-
function
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6859
|
+
function codeOf(world, own, kind, verb, contract) {
|
|
6860
|
+
const found = answerOf(world, own, kind, verb);
|
|
6861
|
+
if (verb !== contract) return {
|
|
6862
|
+
name: found?.name,
|
|
6863
|
+
described: false
|
|
6864
|
+
};
|
|
6865
|
+
if (found?.rung !== "own") return {
|
|
6866
|
+
name: found?.name,
|
|
6867
|
+
described: true
|
|
6868
|
+
};
|
|
6869
|
+
return {
|
|
6870
|
+
name: found.name,
|
|
6871
|
+
described: found.name === answerOf(world, void 0, kind, verb)?.name
|
|
6872
|
+
};
|
|
6865
6873
|
}
|
|
6866
|
-
/**
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
});
|
|
6874
|
+
/** This function gives the position of a part among the parts that a rule covers, by the split of its document. */
|
|
6875
|
+
function positionOf(above, parent, rule, slot) {
|
|
6876
|
+
const positions = above.positions ??= /* @__PURE__ */ new Map();
|
|
6877
|
+
let table = positions.get(rule);
|
|
6878
|
+
if (table === void 0) {
|
|
6879
|
+
table = /* @__PURE__ */ new Map();
|
|
6880
|
+
const split = splitParts(parent.kind?.parts, parent.doc.value);
|
|
6881
|
+
let count = 0;
|
|
6882
|
+
for (const part of split.ok ? split.parts : []) {
|
|
6883
|
+
if (rule.in !== void 0 && !rule.in.some((pattern) => pathMatches(part.path, pattern))) continue;
|
|
6884
|
+
table.set(JSON.stringify(part.path), count);
|
|
6885
|
+
count += 1;
|
|
6886
|
+
}
|
|
6887
|
+
positions.set(rule, table);
|
|
6888
|
+
}
|
|
6889
|
+
return table.get(JSON.stringify(slot)) ?? 0;
|
|
6883
6890
|
}
|
|
6884
|
-
/**
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
const
|
|
6890
|
-
|
|
6891
|
-
|
|
6891
|
+
/** This function tells if the lazy rules of a document cover one of its parts. */
|
|
6892
|
+
function partLazy(above, parent, slot) {
|
|
6893
|
+
const rules = above.rules;
|
|
6894
|
+
if (rules === void 0) return false;
|
|
6895
|
+
if (rules === EACH$2) return true;
|
|
6896
|
+
for (const rule of rules) {
|
|
6897
|
+
if (rule.in !== void 0 && !rule.in.some((pattern) => pathMatches(slot, pattern))) continue;
|
|
6898
|
+
const after = rule.after ?? 0;
|
|
6899
|
+
if (after <= 0 || positionOf(above, parent, rule, slot) >= after) return true;
|
|
6900
|
+
}
|
|
6901
|
+
return false;
|
|
6892
6902
|
}
|
|
6893
6903
|
/**
|
|
6894
|
-
* This function
|
|
6895
|
-
*
|
|
6896
|
-
*
|
|
6897
|
-
* - `#KIND?`: a kind that the world does not have.
|
|
6898
|
-
* - `#NOEVAL`: no evaluator.
|
|
6899
|
-
* - `#VALUE!`: a value without the shape of its kind.
|
|
6900
|
-
* - `#PART?`: a value without a type.
|
|
6901
|
-
* - `#DEPTH!`: too much nesting.
|
|
6902
|
-
* - `#REF!` and `#STEP?`: a bad path.
|
|
6903
|
-
* - `#NAME?`: a name that nothing binds. With the hook `known` of the machine, also a name that the language does not
|
|
6904
|
-
* have, for example an operation.
|
|
6905
|
-
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
6906
|
-
* The hook `permits` of the machine decides, as in a run (CAP.PERMITS).
|
|
6907
|
-
* - `#CALC!`: with the option `run`, a runner under a parent without a runner, when an effect of its runners can reach
|
|
6908
|
-
* the host. Also a kind that declares effects and has no runner (RUN.CAN-WAIT). The issue has the place and the
|
|
6909
|
-
* reason of the run.
|
|
6910
|
-
* - `#TYPE!`: with the option `types`, a part whose type does not fit the type that its position needs (TYPE.CHECK).
|
|
6911
|
-
* A run does not always fail there, thus a type issue is a static finding, not an error of a run.
|
|
6904
|
+
* This function gives the code of a document of a kind for the facts (FACTS.LAZY). `factsOf` and the graph of
|
|
6905
|
+
* attribute instances read the lazy rules with this one function (D107).
|
|
6912
6906
|
*
|
|
6913
|
-
*
|
|
6914
|
-
*
|
|
6915
|
-
*
|
|
6916
|
-
*
|
|
6917
|
-
*
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6907
|
+
* @param machine - The world and the hook `facts`.
|
|
6908
|
+
* @param v - The visit of the document.
|
|
6909
|
+
* @param kind - The kind of the document.
|
|
6910
|
+
* @param verb - The verb whose code the facts describe.
|
|
6911
|
+
* @returns The answer of the hook and the lazy rules.
|
|
6912
|
+
*/
|
|
6913
|
+
function codeFactsOf(machine, v, kind, verb) {
|
|
6914
|
+
const code = codeOf(machine.world, v.declared ? void 0 : v.doc.own, kind, verb, "eval");
|
|
6915
|
+
const answer = code.name === void 0 ? void 0 : machine.facts?.(v.typePath ?? [], code.name);
|
|
6916
|
+
return {
|
|
6917
|
+
described: code.described,
|
|
6918
|
+
answer,
|
|
6919
|
+
rules: code.described ? answer?.lazy ?? kind.lazy : answer?.lazy ?? EACH$2
|
|
6920
|
+
};
|
|
6925
6921
|
}
|
|
6926
6922
|
/**
|
|
6927
|
-
* This function
|
|
6928
|
-
*
|
|
6929
|
-
* against the type that its position needs. The type rule of the parent, the signature of an operation or the shape
|
|
6930
|
-
* of the contract gives that type. A part with an issue gets the type `unknown`, thus one
|
|
6931
|
-
* problem does not give a chain of issues.
|
|
6923
|
+
* This function gives the static facts of a document, for a schedule (R4 P2, D24). It walks the document one time,
|
|
6924
|
+
* by the contracts of its kinds:
|
|
6932
6925
|
*
|
|
6933
|
-
*
|
|
6934
|
-
*
|
|
6926
|
+
* - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
|
|
6927
|
+
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`. A kind that
|
|
6928
|
+
* declares `reads` adds a read of each of its roots at each document of the kind. The first document of a tree
|
|
6929
|
+
* kind also gets the lazy reads of the equations of its kind (FACTS.EQUATIONS). A read from `self` or from a name
|
|
6930
|
+
* of a rung is not one of them, because it is not free (READS.EQUATION).
|
|
6931
|
+
* - `side` and `effects`: from the contract of each kind, for the verb of the facts.
|
|
6932
|
+
* - `pure`: each kind declares `pure`, also in an equation, and the document has no effect and is not volatile.
|
|
6933
|
+
* - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
|
|
6935
6934
|
*
|
|
6936
|
-
* The
|
|
6937
|
-
*
|
|
6935
|
+
* The hook `facts` of the machine answers for a name that a fallback kind covers, for example an operation. Its
|
|
6936
|
+
* answer wins over the contract of the kind. The contract of a kind does not describe the evaluator of an own
|
|
6937
|
+
* entry. Thus a document with an own entry for another evaluator is not pure, and each of its parts is lazy. The
|
|
6938
|
+
* hook can still answer for that evaluator.
|
|
6938
6939
|
*
|
|
6939
|
-
*
|
|
6940
|
-
*
|
|
6941
|
-
*
|
|
6942
|
-
*
|
|
6943
|
-
|
|
6944
|
-
function typeCheck(machine, doc, options = {}) {
|
|
6945
|
-
const nodes = [];
|
|
6946
|
-
const issues = checkAll(machine, doc, options, nodes);
|
|
6947
|
-
const pass = typePass(machine, nodes, options.verb ?? "eval", options.expect, options.rootTypes);
|
|
6948
|
-
return {
|
|
6949
|
-
type: pass.type,
|
|
6950
|
-
issues: pass.issues.length === 0 ? issues : [...issues, ...pass.issues],
|
|
6951
|
-
places: pass.places
|
|
6952
|
-
};
|
|
6953
|
-
}
|
|
6954
|
-
/** This function tells if a table of verbs has an entry for a verb. */
|
|
6955
|
-
function hasVerb(table, verb) {
|
|
6956
|
-
return table !== void 0 && Object.hasOwn(table, verb);
|
|
6957
|
-
}
|
|
6958
|
-
/**
|
|
6959
|
-
* This function checks the world of a machine before a document exists. It finds each kind that has no entry for a
|
|
6960
|
-
* verb of the list, when the world has no default for that verb (STATIC.COVERAGE). A document of such a kind gets no
|
|
6961
|
-
* evaluator for the verb, thus the check gives the same proof of coverage as a type system.
|
|
6940
|
+
* The option `verb` names the verb whose code the facts describe (FACTS.VERB). The marks of a contract describe the
|
|
6941
|
+
* code of `eval`, thus the rule of an own entry applies to each document for another verb. For another verb, the
|
|
6942
|
+
* effects are the effects that the entry of the verb in `verbEffects` declares (FACTS.VERB-EFFECTS, D111). A kind is
|
|
6943
|
+
* on the run side for that verb when its entry declares an effect (FACTS.VERB-SIDE). A kind without an entry for the
|
|
6944
|
+
* verb adds no effect, as in `check` (FACTS.VERB-UNDECLARED). Its code is still not pure without an answer of the hook.
|
|
6962
6945
|
*
|
|
6963
|
-
*
|
|
6964
|
-
*
|
|
6946
|
+
* A read is one resolution of its path. A reference that the resolution reaches and follows is a part of that read
|
|
6947
|
+
* (DEREF.COLLAPSE), not a separate read. The facts depend only on the document and the machine. Thus a host computes
|
|
6948
|
+
* them again only after an edit of the document or of the machine.
|
|
6965
6949
|
*
|
|
6966
|
-
* @param machine - The
|
|
6967
|
-
* @param
|
|
6968
|
-
* @
|
|
6969
|
-
*
|
|
6950
|
+
* @param machine - The world, the code and the hook `facts`.
|
|
6951
|
+
* @param doc - The document, as JSON.
|
|
6952
|
+
* @param options - The type of a bare root value, the verb and the volatile reads of the host.
|
|
6953
|
+
* @returns The facts.
|
|
6970
6954
|
*/
|
|
6971
|
-
function
|
|
6955
|
+
function factsOf(machine, doc, options = {}) {
|
|
6972
6956
|
const world = machine.world;
|
|
6973
|
-
const
|
|
6974
|
-
const
|
|
6975
|
-
|
|
6976
|
-
const
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
fallback: true,
|
|
6986
|
-
verb,
|
|
6987
|
-
reason
|
|
6988
|
-
} : {
|
|
6989
|
-
code: "#NOEVAL",
|
|
6990
|
-
kind: path,
|
|
6991
|
-
verb,
|
|
6992
|
-
reason
|
|
6993
|
-
});
|
|
6994
|
-
}
|
|
6957
|
+
const hook = machine.facts;
|
|
6958
|
+
const reads = [];
|
|
6959
|
+
const effects = [];
|
|
6960
|
+
const above = /* @__PURE__ */ new Map();
|
|
6961
|
+
let side = "derive";
|
|
6962
|
+
let pure = true;
|
|
6963
|
+
let volatile = false;
|
|
6964
|
+
let size = 0;
|
|
6965
|
+
let seen;
|
|
6966
|
+
const note = (isPure, isVolatile) => {
|
|
6967
|
+
if (isPure !== true) pure = false;
|
|
6968
|
+
if (isVolatile === true) volatile = true;
|
|
6995
6969
|
};
|
|
6996
|
-
const
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
6970
|
+
const add = (read) => {
|
|
6971
|
+
reads.push(read);
|
|
6972
|
+
if (options.volatile?.(read) === true) volatile = true;
|
|
6973
|
+
};
|
|
6974
|
+
/**
|
|
6975
|
+
* This function walks a document. Without `node`, it walks the document of the facts. With `node`, it walks an
|
|
6976
|
+
* equation of a tree kind, and each read gets the place of that node: the first document of the kind.
|
|
6977
|
+
*/
|
|
6978
|
+
const scan = (term, walkOptions, verb, node) => {
|
|
6979
|
+
walk(world, term, (v) => {
|
|
6980
|
+
if (node === void 0) size += 1;
|
|
6981
|
+
const up = v.parent === void 0 ? void 0 : above.get(v.parent);
|
|
6982
|
+
const lazy = node !== void 0 || up !== void 0 && v.parent !== void 0 && (up.lazy || partLazy(up, v.parent, v.slot));
|
|
6983
|
+
const place = node ?? v;
|
|
6984
|
+
const kind = v.kind;
|
|
6985
|
+
if (kind === void 0) {
|
|
6986
|
+
pure = false;
|
|
6987
|
+
above.set(v, {
|
|
6988
|
+
lazy,
|
|
6989
|
+
rules: void 0
|
|
6990
|
+
});
|
|
6991
|
+
return;
|
|
7007
6992
|
}
|
|
7008
|
-
|
|
6993
|
+
const { described, answer, rules } = codeFactsOf(machine, v, kind, verb);
|
|
6994
|
+
above.set(v, {
|
|
6995
|
+
lazy,
|
|
6996
|
+
rules
|
|
6997
|
+
});
|
|
6998
|
+
let roots;
|
|
6999
|
+
if (described) {
|
|
7000
|
+
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
7001
|
+
roots = answer?.reads ?? kind.reads;
|
|
7002
|
+
} else {
|
|
7003
|
+
note(answer?.pure, answer?.volatile);
|
|
7004
|
+
roots = answer?.reads;
|
|
7005
|
+
}
|
|
7006
|
+
for (const root of roots ?? NONE) {
|
|
7007
|
+
if (node !== void 0 && root === "self") continue;
|
|
7008
|
+
add({
|
|
7009
|
+
root,
|
|
7010
|
+
keys: NONE,
|
|
7011
|
+
exact: true,
|
|
7012
|
+
lazy,
|
|
7013
|
+
address: false,
|
|
7014
|
+
path: [root],
|
|
7015
|
+
place: tight(place.place),
|
|
7016
|
+
at: tight(place.at)
|
|
7017
|
+
});
|
|
7018
|
+
}
|
|
7019
|
+
const declared = declaredEffects(v, verb);
|
|
7020
|
+
if (verb === "eval" ? kind.side === "run" : declared.length > 0) {
|
|
7021
|
+
if (node === void 0) side = "run";
|
|
7022
|
+
else pure = false;
|
|
7023
|
+
}
|
|
7024
|
+
for (const keys of declared) {
|
|
7025
|
+
if (node !== void 0) {
|
|
7026
|
+
pure = false;
|
|
7027
|
+
continue;
|
|
7028
|
+
}
|
|
7029
|
+
const text = keys.join(".");
|
|
7030
|
+
if (!effects.includes(text)) effects.push(text);
|
|
7031
|
+
}
|
|
7032
|
+
if (kind.tree !== void 0 && !(seen ??= /* @__PURE__ */ new Set()).has(kind)) {
|
|
7033
|
+
seen.add(kind);
|
|
7034
|
+
const of = equationType(kind);
|
|
7035
|
+
for (const { term: equation, names } of equationsOf(kind)) scan(equation, of === void 0 ? { names } : {
|
|
7036
|
+
names,
|
|
7037
|
+
declaredType: of
|
|
7038
|
+
}, "eval", place);
|
|
7039
|
+
}
|
|
7040
|
+
if (!isRef(v.doc)) return void 0;
|
|
7041
|
+
const path = v.doc.value;
|
|
7042
|
+
const root = path[0];
|
|
7043
|
+
if (typeof root !== "string" || !ROOTS.includes(root)) return void 0;
|
|
7044
|
+
if (node !== void 0 && root === "self") return void 0;
|
|
7045
|
+
let exact = true;
|
|
7046
|
+
const keys = [];
|
|
7047
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
7048
|
+
const step = path[index];
|
|
7049
|
+
if (exact && isKey(step)) {
|
|
7050
|
+
keys.push(step);
|
|
7051
|
+
continue;
|
|
7052
|
+
}
|
|
7053
|
+
exact = false;
|
|
7054
|
+
const found = isEnvelope(step) ? lookupKind(world, step.type) : void 0;
|
|
7055
|
+
if (found?.ok !== true) {
|
|
7056
|
+
pure = false;
|
|
7057
|
+
continue;
|
|
7058
|
+
}
|
|
7059
|
+
const walker = codeOf(world, step.own, found.kind, "step", "step");
|
|
7060
|
+
const stepAnswer = walker.name === void 0 ? void 0 : hook?.(found.path, walker.name);
|
|
7061
|
+
if (walker.described) note(stepAnswer?.pure ?? found.kind.pure, stepAnswer?.volatile ?? found.kind.volatile);
|
|
7062
|
+
else note(stepAnswer?.pure, stepAnswer?.volatile);
|
|
7063
|
+
}
|
|
7064
|
+
const first = path[1];
|
|
7065
|
+
if (root === "names" && isKey(first) && v.names.has(String(first))) return void 0;
|
|
7066
|
+
add({
|
|
7067
|
+
root,
|
|
7068
|
+
keys: tight(keys),
|
|
7069
|
+
exact,
|
|
7070
|
+
lazy,
|
|
7071
|
+
address: v.address === true,
|
|
7072
|
+
path,
|
|
7073
|
+
place: tight(place.place),
|
|
7074
|
+
at: tight(place.at)
|
|
7075
|
+
});
|
|
7076
|
+
}, walkOptions);
|
|
7077
|
+
};
|
|
7078
|
+
scan(doc, options.declaredType === void 0 ? {} : { declaredType: options.declaredType }, options.verb ?? "eval", void 0);
|
|
7079
|
+
return {
|
|
7080
|
+
reads: tight(reads),
|
|
7081
|
+
side,
|
|
7082
|
+
effects: tight(effects),
|
|
7083
|
+
pure: pure && !volatile && effects.length === 0 && side === "derive",
|
|
7084
|
+
volatile,
|
|
7085
|
+
size
|
|
7009
7086
|
};
|
|
7010
|
-
entries(world.kinds, []);
|
|
7011
|
-
if (world.fallback !== void 0) visit(world.fallback, [], true);
|
|
7012
|
-
return out;
|
|
7013
7087
|
}
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
const
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
*/
|
|
7026
|
-
function equationsOf(kind) {
|
|
7027
|
-
const tree = kind.tree;
|
|
7028
|
-
if (tree === void 0) return [];
|
|
7029
|
-
const out = [];
|
|
7030
|
-
for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({
|
|
7031
|
-
label: `tree.equations.${name}`,
|
|
7032
|
-
term,
|
|
7033
|
-
names: []
|
|
7034
|
-
});
|
|
7035
|
-
for (const [name, term] of Object.entries(tree.down ?? {})) out.push({
|
|
7036
|
-
label: `tree.down.${name}`,
|
|
7037
|
-
term,
|
|
7038
|
-
names: DOWN_NAMES
|
|
7088
|
+
// src/graph.ts
|
|
7089
|
+
/** This function gives the indexed form of a graph, with the edges that the options keep. */
|
|
7090
|
+
function indexed(graph, options) {
|
|
7091
|
+
const eager = options.eager === true;
|
|
7092
|
+
const ids = [...graph.keys()];
|
|
7093
|
+
const count = ids.length;
|
|
7094
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
7095
|
+
const sink = new Uint8Array(count);
|
|
7096
|
+
ids.forEach((id, index) => {
|
|
7097
|
+
numbers.set(id, index);
|
|
7098
|
+
if (graph.get(id)?.side === "run") sink[index] = 1;
|
|
7039
7099
|
});
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7100
|
+
const start = new Int32Array(count + 2);
|
|
7101
|
+
const targets = [];
|
|
7102
|
+
let unknown = false;
|
|
7103
|
+
let from = 0;
|
|
7104
|
+
for (const node of graph.values()) {
|
|
7105
|
+
start[from] = targets.length;
|
|
7106
|
+
for (const edge of node.edges) {
|
|
7107
|
+
if (eager && edge.lazy) continue;
|
|
7108
|
+
if (edge.to === void 0) {
|
|
7109
|
+
if (eager) continue;
|
|
7110
|
+
unknown = true;
|
|
7111
|
+
targets.push(count);
|
|
7112
|
+
continue;
|
|
7113
|
+
}
|
|
7114
|
+
const to = numbers.get(edge.to);
|
|
7115
|
+
if (to !== void 0 && sink[to] === 0) targets.push(to);
|
|
7052
7116
|
}
|
|
7053
|
-
|
|
7054
|
-
label: `tree.attributes.${name}.default`,
|
|
7055
|
-
term: declared.default,
|
|
7056
|
-
names: []
|
|
7057
|
-
});
|
|
7117
|
+
from += 1;
|
|
7058
7118
|
}
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
* This function checks the equations of the tree contract of a kind, each with the names of its rung (TREE.DOWN-NAMES).
|
|
7063
|
-
* An equation evaluates without capabilities, thus a check with capabilities gives it none. Each issue gets the place
|
|
7064
|
-
* of the document of the kind, and its reason names the equation.
|
|
7065
|
-
*/
|
|
7066
|
-
function equationIssues(machine, kind, kindText, options, seen) {
|
|
7067
|
-
const of = equationType(kind);
|
|
7068
|
-
const out = [];
|
|
7069
|
-
for (const { label, term, names } of equationsOf(kind)) {
|
|
7070
|
-
const inner = {
|
|
7071
|
-
names,
|
|
7072
|
-
...of === void 0 ? {} : { declaredType: of },
|
|
7073
|
-
...options.caps === void 0 ? {} : { caps: [] }
|
|
7074
|
-
};
|
|
7075
|
-
for (const issue of checkAll(machine, term, inner, void 0, seen)) out.push({
|
|
7076
|
-
code: issue.code,
|
|
7077
|
-
place: [],
|
|
7078
|
-
reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}`
|
|
7079
|
-
});
|
|
7119
|
+
start[count] = targets.length;
|
|
7120
|
+
if (unknown) {
|
|
7121
|
+
for (let to = 0; to < count; to += 1) if (sink[to] === 0) targets.push(to);
|
|
7080
7122
|
}
|
|
7081
|
-
|
|
7123
|
+
start[count + 1] = targets.length;
|
|
7124
|
+
return {
|
|
7125
|
+
ids,
|
|
7126
|
+
size: unknown ? count + 1 : count,
|
|
7127
|
+
start,
|
|
7128
|
+
targets: Int32Array.from(targets),
|
|
7129
|
+
all: unknown ? count : -1
|
|
7130
|
+
};
|
|
7082
7131
|
}
|
|
7083
7132
|
/**
|
|
7084
|
-
* This function finds the
|
|
7085
|
-
*
|
|
7086
|
-
*
|
|
7133
|
+
* This function finds the strongly connected components with the algorithm of Tarjan. It keeps its own stack of
|
|
7134
|
+
* calls in two arrays, thus a long chain does not overflow the stack of the host. Each component comes after each
|
|
7135
|
+
* component that its nodes reach: the deepest component comes first.
|
|
7087
7136
|
*/
|
|
7088
|
-
function
|
|
7089
|
-
const
|
|
7090
|
-
const
|
|
7137
|
+
function strong(graph) {
|
|
7138
|
+
const { size, start, targets } = graph;
|
|
7139
|
+
const order = new Int32Array(size).fill(-1);
|
|
7140
|
+
const low = new Int32Array(size);
|
|
7141
|
+
const of = new Int32Array(size).fill(-1);
|
|
7142
|
+
const open = new Int32Array(size);
|
|
7143
|
+
const onOpen = new Uint8Array(size);
|
|
7144
|
+
const calls = new Int32Array(size);
|
|
7145
|
+
const next = new Int32Array(size);
|
|
7146
|
+
const list = [];
|
|
7147
|
+
let top = 0;
|
|
7148
|
+
let count = 0;
|
|
7149
|
+
const enter = (node, depth) => {
|
|
7150
|
+
order[node] = count;
|
|
7151
|
+
low[node] = count;
|
|
7152
|
+
count += 1;
|
|
7153
|
+
open[top] = node;
|
|
7154
|
+
top += 1;
|
|
7155
|
+
onOpen[node] = 1;
|
|
7156
|
+
calls[depth] = node;
|
|
7157
|
+
next[depth] = start[node];
|
|
7158
|
+
};
|
|
7159
|
+
for (let root = 0; root < size; root += 1) {
|
|
7160
|
+
if (order[root] !== -1) continue;
|
|
7161
|
+
enter(root, 0);
|
|
7162
|
+
let depth = 1;
|
|
7163
|
+
while (depth > 0) {
|
|
7164
|
+
const node = calls[depth - 1];
|
|
7165
|
+
const edge = next[depth - 1];
|
|
7166
|
+
if (edge < start[node + 1]) {
|
|
7167
|
+
next[depth - 1] = edge + 1;
|
|
7168
|
+
const to = targets[edge];
|
|
7169
|
+
if (order[to] === -1) {
|
|
7170
|
+
enter(to, depth);
|
|
7171
|
+
depth += 1;
|
|
7172
|
+
} else if (onOpen[to] === 1 && order[to] < low[node]) low[node] = order[to];
|
|
7173
|
+
continue;
|
|
7174
|
+
}
|
|
7175
|
+
depth -= 1;
|
|
7176
|
+
if (low[node] === order[node]) {
|
|
7177
|
+
const members = [];
|
|
7178
|
+
let member;
|
|
7179
|
+
do {
|
|
7180
|
+
top -= 1;
|
|
7181
|
+
member = open[top];
|
|
7182
|
+
onOpen[member] = 0;
|
|
7183
|
+
of[member] = list.length;
|
|
7184
|
+
members.push(member);
|
|
7185
|
+
} while (member !== node);
|
|
7186
|
+
list.push(members.sort((a, b) => a - b));
|
|
7187
|
+
}
|
|
7188
|
+
if (depth > 0) {
|
|
7189
|
+
const reader = calls[depth - 1];
|
|
7190
|
+
if (low[node] < low[reader]) low[reader] = low[node];
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
}
|
|
7194
|
+
return {
|
|
7195
|
+
of,
|
|
7196
|
+
list
|
|
7197
|
+
};
|
|
7198
|
+
}
|
|
7199
|
+
/** This function tells if a component has a cycle: two or more nodes, or one node with an edge to itself. */
|
|
7200
|
+
function cyclic(graph, members) {
|
|
7201
|
+
if (members.length > 1) return true;
|
|
7202
|
+
const node = members[0];
|
|
7203
|
+
for (let edge = graph.start[node]; edge < graph.start[node + 1]; edge += 1) if (graph.targets[edge] === node) return true;
|
|
7204
|
+
return false;
|
|
7205
|
+
}
|
|
7206
|
+
/** This function gives the names of the nodes of a component, without the node `all`. */
|
|
7207
|
+
function named$1(graph, members) {
|
|
7208
|
+
const out = [];
|
|
7209
|
+
for (const member of members) if (member !== graph.all) out.push(graph.ids[member]);
|
|
7210
|
+
return out;
|
|
7211
|
+
}
|
|
7212
|
+
/** This function gives the height of each component, in the order of `strong`. The node `all` adds no height. */
|
|
7213
|
+
function heightsOf(graph, found) {
|
|
7214
|
+
const { start, targets } = graph;
|
|
7215
|
+
const heights = new Int32Array(found.list.length);
|
|
7216
|
+
found.list.forEach((members, component) => {
|
|
7217
|
+
let highest = -1;
|
|
7218
|
+
let real = false;
|
|
7219
|
+
for (const node of members) {
|
|
7220
|
+
if (node !== graph.all) real = true;
|
|
7221
|
+
for (let edge = start[node]; edge < start[node + 1]; edge += 1) {
|
|
7222
|
+
const other = found.of[targets[edge]];
|
|
7223
|
+
if (other !== component && heights[other] > highest) highest = heights[other];
|
|
7224
|
+
}
|
|
7225
|
+
}
|
|
7226
|
+
heights[component] = real ? highest + 1 : highest;
|
|
7227
|
+
});
|
|
7228
|
+
return heights;
|
|
7229
|
+
}
|
|
7230
|
+
/**
|
|
7231
|
+
* This function gives the strongly connected components of a graph of nodes (GRAPH.SCC), with the algorithm of
|
|
7232
|
+
* Tarjan. It keeps its own stack, thus a chain of 100000 nodes does not overflow the stack of the host.
|
|
7233
|
+
*
|
|
7234
|
+
* - Each component comes after each component that it reads: the deepest component comes first.
|
|
7235
|
+
* - A node that is on no cycle is a component of one node.
|
|
7236
|
+
* - The nodes of a component are in the order of the graph.
|
|
7237
|
+
*
|
|
7238
|
+
* @param graph - The nodes, each with its edges.
|
|
7239
|
+
* @param options - The option `eager` keeps only the eager edges with a target node.
|
|
7240
|
+
* @returns The components, deepest first.
|
|
7241
|
+
*/
|
|
7242
|
+
function components(graph, options = {}) {
|
|
7243
|
+
const index = indexed(graph, options);
|
|
7244
|
+
const out = [];
|
|
7245
|
+
for (const members of strong(index).list) {
|
|
7246
|
+
const names = named$1(index, members);
|
|
7247
|
+
if (names.length > 0) out.push(names);
|
|
7248
|
+
}
|
|
7249
|
+
return out;
|
|
7250
|
+
}
|
|
7251
|
+
/**
|
|
7252
|
+
* This function gives the height of each node: the order of a push schedule (GRAPH.ORDER). A schedule that evaluates
|
|
7253
|
+
* the nodes from the lowest height up evaluates each node after each node that it reads, except on a cycle.
|
|
7254
|
+
*
|
|
7255
|
+
* - A node that reads no node outside its component has the height 0.
|
|
7256
|
+
* - Each other node is one higher than the highest node that it reads outside its component.
|
|
7257
|
+
* - The nodes of one component have one height.
|
|
7258
|
+
* - An edge without a target reads each node that is not a sink (GRAPH.UNKNOWN).
|
|
7259
|
+
*
|
|
7260
|
+
* @param graph - The nodes, each with its edges.
|
|
7261
|
+
* @param options - The option `eager` keeps only the eager edges with a target node.
|
|
7262
|
+
* @returns The height of each node, in the order of the graph.
|
|
7263
|
+
*/
|
|
7264
|
+
function heights(graph, options = {}) {
|
|
7265
|
+
const index = indexed(graph, options);
|
|
7266
|
+
const found = strong(index);
|
|
7267
|
+
const levels = heightsOf(index, found);
|
|
7268
|
+
const out = /* @__PURE__ */ new Map();
|
|
7269
|
+
index.ids.forEach((id, node) => out.set(id, levels[found.of[node]]));
|
|
7270
|
+
return out;
|
|
7271
|
+
}
|
|
7272
|
+
/**
|
|
7273
|
+
* This function puts the nodes in layers by their heights, in linear time (GRAPH.LAYERS). The first layer has the
|
|
7274
|
+
* height 0. Two nodes of one layer read each other only inside one component. Thus a push schedule can evaluate the
|
|
7275
|
+
* components of one layer at the same time.
|
|
7276
|
+
*
|
|
7277
|
+
* @param graph - The nodes, each with its edges.
|
|
7278
|
+
* @param options - The option `eager` keeps only the eager edges with a target node.
|
|
7279
|
+
* @returns The layers, from the height 0 up. Each layer keeps the order of the graph.
|
|
7280
|
+
*/
|
|
7281
|
+
function nodeLayers(graph, options = {}) {
|
|
7282
|
+
const out = [];
|
|
7283
|
+
for (const [id, height] of heights(graph, options)) {
|
|
7284
|
+
while (out.length <= height) out.push([]);
|
|
7285
|
+
out[height]?.push(id);
|
|
7286
|
+
}
|
|
7287
|
+
return out;
|
|
7288
|
+
}
|
|
7289
|
+
/**
|
|
7290
|
+
* This function gives the nodes that a set of observed nodes needs (GRAPH.NECESSARY). These are the observed nodes,
|
|
7291
|
+
* and each node with a path of edges from them. A push schedule evaluates only these nodes. An observed name that is
|
|
7292
|
+
* not a node of the graph adds nothing.
|
|
7293
|
+
*
|
|
7294
|
+
* @param graph - The nodes, each with its edges.
|
|
7295
|
+
* @param observed - The nodes that the host shows or reads.
|
|
7296
|
+
* @param options - The option `eager` keeps only the eager edges with a target node.
|
|
7297
|
+
* @returns The necessary nodes, in the order of the graph.
|
|
7298
|
+
*/
|
|
7299
|
+
function necessary(graph, observed, options = {}) {
|
|
7300
|
+
const index = indexed(graph, options);
|
|
7301
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
7302
|
+
index.ids.forEach((id, node) => numbers.set(id, node));
|
|
7303
|
+
const seen = new Uint8Array(index.size);
|
|
7304
|
+
const work = [];
|
|
7305
|
+
for (const id of observed) {
|
|
7306
|
+
const node = numbers.get(id);
|
|
7307
|
+
if (node === void 0 || seen[node] === 1) continue;
|
|
7308
|
+
seen[node] = 1;
|
|
7309
|
+
work.push(node);
|
|
7310
|
+
}
|
|
7311
|
+
while (work.length > 0) {
|
|
7312
|
+
const node = work.pop();
|
|
7313
|
+
for (let edge = index.start[node]; edge < index.start[node + 1]; edge += 1) {
|
|
7314
|
+
const to = index.targets[edge];
|
|
7315
|
+
if (seen[to] === 1) continue;
|
|
7316
|
+
seen[to] = 1;
|
|
7317
|
+
work.push(to);
|
|
7318
|
+
}
|
|
7319
|
+
}
|
|
7320
|
+
const out = /* @__PURE__ */ new Set();
|
|
7321
|
+
index.ids.forEach((id, node) => {
|
|
7322
|
+
if (seen[node] === 1) out.add(id);
|
|
7323
|
+
});
|
|
7324
|
+
return out;
|
|
7325
|
+
}
|
|
7326
|
+
/**
|
|
7327
|
+
* This function gives the static cycles of a graph (GRAPH.CYCLE): each component with two or more nodes, and each
|
|
7328
|
+
* node with an edge to itself. A cycle of the eager edges with a target is certain, and `check` or a host can report
|
|
7329
|
+
* it before a run. Another cycle is possible: an evaluation can skip a lazy read.
|
|
7330
|
+
*
|
|
7331
|
+
* - A component of all the edges that is also a component of the eager edges is one certain cycle.
|
|
7332
|
+
* - Each other component of all the edges is one possible cycle, and each certain cycle in it comes after it.
|
|
7333
|
+
* - A read can still fail before it occurs. Thus the demand of D31, not a static cycle, gives the value of each node.
|
|
7334
|
+
*
|
|
7335
|
+
* @param graph - The nodes, each with its edges.
|
|
7336
|
+
* @returns The cycles, deepest first.
|
|
7337
|
+
*/
|
|
7338
|
+
function cycles(graph) {
|
|
7339
|
+
const every = indexed(graph, {});
|
|
7340
|
+
const found = strong(every);
|
|
7341
|
+
const eager = indexed(graph, { eager: true });
|
|
7342
|
+
const inside = /* @__PURE__ */ new Map();
|
|
7343
|
+
for (const members of strong(eager).list) {
|
|
7344
|
+
if (!cyclic(eager, members)) continue;
|
|
7345
|
+
const component = found.of[members[0]];
|
|
7346
|
+
const list = inside.get(component);
|
|
7347
|
+
if (list === void 0) inside.set(component, [members]);
|
|
7348
|
+
else list.push(members);
|
|
7349
|
+
}
|
|
7350
|
+
const out = [];
|
|
7351
|
+
found.list.forEach((members, component) => {
|
|
7352
|
+
if (!cyclic(every, members)) return;
|
|
7353
|
+
const nodes = named$1(every, members);
|
|
7354
|
+
const certain = inside.get(component) ?? [];
|
|
7355
|
+
if (certain.length === 1 && certain[0]?.length === nodes.length) {
|
|
7356
|
+
out.push({
|
|
7357
|
+
nodes,
|
|
7358
|
+
certain: true
|
|
7359
|
+
});
|
|
7360
|
+
return;
|
|
7361
|
+
}
|
|
7362
|
+
out.push({
|
|
7363
|
+
nodes,
|
|
7364
|
+
certain: false
|
|
7365
|
+
});
|
|
7366
|
+
for (const part of certain) out.push({
|
|
7367
|
+
nodes: named$1(eager, part),
|
|
7368
|
+
certain: true
|
|
7369
|
+
});
|
|
7370
|
+
});
|
|
7371
|
+
return out;
|
|
7372
|
+
}
|
|
7373
|
+
/**
|
|
7374
|
+
* This function gives the nodes that a demand of a node needs first, deepest first (SCHED.LONG-CHAIN). It follows the
|
|
7375
|
+
* eager edges with a target from the node, with its own stack. Each node comes after each node that it reads, except
|
|
7376
|
+
* on a cycle, as in an order by heights. The walk costs only the nodes that it enters.
|
|
7377
|
+
*
|
|
7378
|
+
* @param from - The node of the demand. It is not in the answer.
|
|
7379
|
+
* @param edgesOf - This function gives the edges of a node, or `undefined` for a node that the walk does not enter.
|
|
7380
|
+
* @returns The nodes, deepest first.
|
|
7381
|
+
*/
|
|
7382
|
+
function deepestFirst(from, edgesOf) {
|
|
7383
|
+
const out = [];
|
|
7384
|
+
const first = edgesOf(from);
|
|
7385
|
+
if (first === void 0) return out;
|
|
7386
|
+
const seen = /* @__PURE__ */ new Set([from]);
|
|
7387
|
+
const nodes = [from];
|
|
7388
|
+
const lists = [first];
|
|
7389
|
+
const positions = [0];
|
|
7390
|
+
while (nodes.length > 0) {
|
|
7391
|
+
const top = nodes.length - 1;
|
|
7392
|
+
const edges = lists[top];
|
|
7393
|
+
let position = positions[top];
|
|
7394
|
+
let entered = false;
|
|
7395
|
+
while (position < edges.length) {
|
|
7396
|
+
const edge = edges[position];
|
|
7397
|
+
position += 1;
|
|
7398
|
+
const to = edge.to;
|
|
7399
|
+
if (edge.lazy || to === void 0 || seen.has(to)) continue;
|
|
7400
|
+
seen.add(to);
|
|
7401
|
+
const more = edgesOf(to);
|
|
7402
|
+
if (more === void 0) continue;
|
|
7403
|
+
positions[top] = position;
|
|
7404
|
+
nodes.push(to);
|
|
7405
|
+
lists.push(more);
|
|
7406
|
+
positions.push(0);
|
|
7407
|
+
entered = true;
|
|
7408
|
+
break;
|
|
7409
|
+
}
|
|
7410
|
+
if (entered) continue;
|
|
7411
|
+
const done = nodes.pop();
|
|
7412
|
+
lists.pop();
|
|
7413
|
+
positions.pop();
|
|
7414
|
+
if (nodes.length > 0) out.push(done);
|
|
7415
|
+
}
|
|
7416
|
+
return out;
|
|
7417
|
+
}
|
|
7418
|
+
// src/instances.ts
|
|
7419
|
+
/**
|
|
7420
|
+
* This function gives the name of a vertex of the graph of attribute instances. The name is the JSON text of the
|
|
7421
|
+
* place of the node, with the name of the attribute after it.
|
|
7422
|
+
*
|
|
7423
|
+
* @param place - The place of the document of the node.
|
|
7424
|
+
* @param attribute - The name of the attribute.
|
|
7425
|
+
* @returns The name of the vertex.
|
|
7426
|
+
*/
|
|
7427
|
+
function attributeVertexId(place, attribute) {
|
|
7428
|
+
return JSON.stringify([...place, attribute]);
|
|
7429
|
+
}
|
|
7430
|
+
/** The standard code whose value is the plain data of its document. */
|
|
7431
|
+
const DATA$1 = /* @__PURE__ */ new Set([
|
|
7432
|
+
"data.number",
|
|
7433
|
+
"data.text",
|
|
7434
|
+
"data.bool"
|
|
7435
|
+
]);
|
|
7436
|
+
/** The standard code of a list. Its value holds the value of each item, in the order of the items. */
|
|
7437
|
+
const LIST$1 = "list.all";
|
|
7438
|
+
/** The self of the root of a walk: the self of the context, outside each tree of the document. */
|
|
7439
|
+
const OUTSIDE = {
|
|
7440
|
+
kind: { verbs: {} },
|
|
7441
|
+
text: "",
|
|
7442
|
+
own: void 0,
|
|
7443
|
+
parent: void 0
|
|
7444
|
+
};
|
|
7445
|
+
/** The self of an equation, for the references of the equation. */
|
|
7446
|
+
const EQUATION_SELF = {
|
|
7447
|
+
kind: { verbs: {} },
|
|
7448
|
+
text: "",
|
|
7449
|
+
own: void 0,
|
|
7450
|
+
parent: void 0
|
|
7451
|
+
};
|
|
7452
|
+
const NO_BOUND = /* @__PURE__ */ new Set();
|
|
7453
|
+
/** This function gives the name of an own part from the slot of its document. */
|
|
7454
|
+
function slotKey(slot) {
|
|
7455
|
+
return slot.map(String).join(".");
|
|
7456
|
+
}
|
|
7457
|
+
/** This function gives the value of a document before the run. */
|
|
7458
|
+
function valueOf$1(info) {
|
|
7459
|
+
if (info === void 0) return "unknown";
|
|
7460
|
+
if (info.node !== void 0) return { node: info.node };
|
|
7461
|
+
if (info.evaluator === LIST$1) return { list: info };
|
|
7462
|
+
return info.evaluator !== void 0 && DATA$1.has(info.evaluator) ? "data" : "unknown";
|
|
7463
|
+
}
|
|
7464
|
+
/** This function tells if a table of a tree contract has its own entry for a name. */
|
|
7465
|
+
function hasOwnEntry(table, name) {
|
|
7466
|
+
return table !== void 0 && Object.hasOwn(table, name);
|
|
7467
|
+
}
|
|
7468
|
+
/** This function gives the vertex of an attribute of a node. A new vertex goes on the work list for its rungs. */
|
|
7469
|
+
function vertexOf(b, node, attribute) {
|
|
7470
|
+
const id = attributeVertexId(node.visit.place, attribute);
|
|
7471
|
+
if (!b.vertices.has(id)) {
|
|
7472
|
+
b.vertices.set(id, {
|
|
7473
|
+
id,
|
|
7474
|
+
place: node.visit.place,
|
|
7475
|
+
attribute,
|
|
7476
|
+
type: node.text
|
|
7477
|
+
});
|
|
7478
|
+
b.nodes.set(id, node);
|
|
7479
|
+
if (!node.parts.has(attribute)) b.work.push(id);
|
|
7480
|
+
}
|
|
7481
|
+
return id;
|
|
7482
|
+
}
|
|
7483
|
+
function edge(b, from, r, lazy = false) {
|
|
7484
|
+
b.edges.push({
|
|
7485
|
+
from,
|
|
7486
|
+
to: r.to,
|
|
7487
|
+
lazy: r.lazy || lazy,
|
|
7488
|
+
place: r.place
|
|
7489
|
+
});
|
|
7490
|
+
b.ats.push(r.at);
|
|
7491
|
+
}
|
|
7492
|
+
/** This function gives the items of a list document, in the order of their indices. */
|
|
7493
|
+
function itemsOf(list) {
|
|
7494
|
+
return [...list.items].sort(([x], [y]) => x - y).map(([, item]) => item);
|
|
7495
|
+
}
|
|
7496
|
+
/**
|
|
7497
|
+
* This function gives the attributes that the JSON of a value reads, in their order (TREE.RESULT). A node reads each
|
|
7498
|
+
* own part, with the JSON of each node in its value after it. A list gives the JSON of each item.
|
|
7499
|
+
*/
|
|
7500
|
+
function jsonOrder(value, out, done) {
|
|
7501
|
+
if (typeof value !== "object") return;
|
|
7502
|
+
if ("list" in value) {
|
|
7503
|
+
for (const item of itemsOf(value.list)) jsonOrder(valueOf$1(item), out, done);
|
|
7504
|
+
return;
|
|
7505
|
+
}
|
|
7506
|
+
const node = value.node;
|
|
7507
|
+
if (done.has(node)) return;
|
|
7508
|
+
done.add(node);
|
|
7509
|
+
for (const key of node.own) {
|
|
7510
|
+
out.push([node, key]);
|
|
7511
|
+
jsonOrder(valueOf$1(node.parts.get(key)), out, done);
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7514
|
+
/** This function adds the reads of the JSON of a value. */
|
|
7515
|
+
function jsonReads(b, value, r) {
|
|
7516
|
+
const order = [];
|
|
7517
|
+
jsonOrder(value, order, /* @__PURE__ */ new Set());
|
|
7518
|
+
for (const [node, key] of order) edge(b, vertexOf(b, node, key), r);
|
|
7519
|
+
}
|
|
7520
|
+
/** This function gives the node that a walker `nearest` with the key `parent` reaches before the run. */
|
|
7521
|
+
function nearestOf(start, data) {
|
|
7522
|
+
if ((isRecord(data) && typeof field(data, "follow") === "string" ? String(field(data, "follow")) : "parent") !== "parent") return "unknown";
|
|
7523
|
+
const wanted = isRecord(data) ? field(data, "type") : void 0;
|
|
7524
|
+
let at = isRecord(data) && field(data, "self") === true ? start : start.up;
|
|
7525
|
+
while (at !== null && at !== void 0) {
|
|
7526
|
+
if (at.text === wanted) return { node: at };
|
|
7527
|
+
at = at.up;
|
|
7528
|
+
}
|
|
7529
|
+
return at === null ? "outside" : "unknown";
|
|
7530
|
+
}
|
|
7531
|
+
/**
|
|
7532
|
+
* This function follows the steps of a path from a value, and it adds each read on the way (TREE.GRAPH). A step
|
|
7533
|
+
* `parent` goes to the parent node, and a step `type` gives data. Another key reads an attribute. A walker `nearest`
|
|
7534
|
+
* with the key `parent` depends only on the structure. A step after a value that only the run knows gives an edge with
|
|
7535
|
+
* an unknown source. A path that ends at a node reads the JSON of the node.
|
|
7536
|
+
*/
|
|
7537
|
+
function follow(b, start, path, from, r) {
|
|
7538
|
+
let here = start;
|
|
7539
|
+
for (let index = from; index < path.length; index += 1) {
|
|
7540
|
+
const step = path[index];
|
|
7541
|
+
if (here === "unknown") {
|
|
7542
|
+
edge(b, void 0, r);
|
|
7543
|
+
return;
|
|
7544
|
+
}
|
|
7545
|
+
if (here === "data" || here === "outside") return;
|
|
7546
|
+
if ("list" in here) {
|
|
7547
|
+
if (!isKey(step)) {
|
|
7548
|
+
edge(b, void 0, r);
|
|
7549
|
+
return;
|
|
7550
|
+
}
|
|
7551
|
+
if (step === "length") {
|
|
7552
|
+
here = "data";
|
|
7553
|
+
continue;
|
|
7554
|
+
}
|
|
7555
|
+
const at = typeof step === "number" ? step : /^\d+$/.test(step) ? Number(step) : -1;
|
|
7556
|
+
const item = here.list.items.get(at);
|
|
7557
|
+
if (item === void 0) return;
|
|
7558
|
+
here = valueOf$1(item);
|
|
7559
|
+
continue;
|
|
7560
|
+
}
|
|
7561
|
+
const node = here.node;
|
|
7562
|
+
if (isKey(step)) {
|
|
7563
|
+
const key = String(step);
|
|
7564
|
+
if (key === "parent") here = node.up === null ? "outside" : node.up === void 0 ? "unknown" : { node: node.up };
|
|
7565
|
+
else if (isStructural(key)) here = "data";
|
|
7566
|
+
else {
|
|
7567
|
+
edge(b, vertexOf(b, node, key), r);
|
|
7568
|
+
here = valueOf$1(node.parts.get(key));
|
|
7569
|
+
}
|
|
7570
|
+
continue;
|
|
7571
|
+
}
|
|
7572
|
+
if (walkerOf(b.machine, step) !== STD_WALKERS["walk.nearest"]) {
|
|
7573
|
+
edge(b, void 0, r);
|
|
7574
|
+
return;
|
|
7575
|
+
}
|
|
7576
|
+
here = nearestOf(node, isRecord(step) ? field(step, "value") : void 0);
|
|
7577
|
+
}
|
|
7578
|
+
if (typeof here === "object" && "node" in here) jsonReads(b, here, r);
|
|
7579
|
+
}
|
|
7580
|
+
/**
|
|
7581
|
+
* This function gives the references of an equation of a kind, with the self and the lazy mark of each (D107). It walks
|
|
7582
|
+
* the equation with the rules of `check`, thus a reference keeps the self of the equation only through standard code.
|
|
7583
|
+
*/
|
|
7584
|
+
function termRefs(b, kind, label, term) {
|
|
7585
|
+
let byLabel = b.terms.get(kind);
|
|
7586
|
+
if (byLabel === void 0) {
|
|
7587
|
+
byLabel = /* @__PURE__ */ new Map();
|
|
7588
|
+
b.terms.set(kind, byLabel);
|
|
7589
|
+
}
|
|
7590
|
+
const known = byLabel.get(label);
|
|
7591
|
+
if (known !== void 0) return known;
|
|
7592
|
+
const out = [];
|
|
7593
|
+
const of = equationType(kind);
|
|
7594
|
+
const selves = selvesOf(b.machine, "eval", EQUATION_SELF);
|
|
7595
|
+
const above = /* @__PURE__ */ new Map();
|
|
7596
|
+
walk(b.machine.world, term, (v) => {
|
|
7597
|
+
const answer = ask(b.machine.world, v.declared ? {} : v.doc, "eval", v.declared ? { declaredType: typeName(v.doc.type) } : {});
|
|
7598
|
+
if (answer.error === "unknown-kind") return "skip";
|
|
7599
|
+
const up = v.parent === void 0 ? void 0 : above.get(v.parent);
|
|
7600
|
+
const lazy = up !== void 0 && v.parent !== void 0 && (up.lazy || partLazy(up, v.parent, v.slot));
|
|
7601
|
+
above.set(v, {
|
|
7602
|
+
lazy,
|
|
7603
|
+
rules: v.kind === void 0 ? void 0 : codeFactsOf(b.machine, v, v.kind, "eval").rules
|
|
7604
|
+
});
|
|
7605
|
+
const { self } = selves.enter(v, answer.evaluator);
|
|
7606
|
+
const evaluator = answer.evaluator;
|
|
7607
|
+
if (evaluator === "ref.resolve" && isRef(v.doc)) {
|
|
7608
|
+
if (v.address !== true) out.push({
|
|
7609
|
+
path: v.doc.value,
|
|
7610
|
+
lazy,
|
|
7611
|
+
self: self === EQUATION_SELF,
|
|
7612
|
+
bound: v.names,
|
|
7613
|
+
opaque: false
|
|
7614
|
+
});
|
|
7615
|
+
} else if (self === EQUATION_SELF && evaluator !== "tree.node" && !keepsSelf(evaluator)) out.push({
|
|
7616
|
+
path: [],
|
|
7617
|
+
lazy,
|
|
7618
|
+
self: true,
|
|
7619
|
+
bound: NO_BOUND,
|
|
7620
|
+
opaque: true
|
|
7621
|
+
});
|
|
7622
|
+
}, of === void 0 ? {} : { declaredType: of });
|
|
7623
|
+
byLabel.set(label, out);
|
|
7624
|
+
return out;
|
|
7625
|
+
}
|
|
7626
|
+
/** This function adds the reads of an equation of a rung: self is a node, and the names of the rung have their values. */
|
|
7627
|
+
function termReads(b, refs, self, names, r) {
|
|
7628
|
+
for (const ref of refs) {
|
|
7629
|
+
const read = ref.lazy ? {
|
|
7630
|
+
...r,
|
|
7631
|
+
lazy: true
|
|
7632
|
+
} : r;
|
|
7633
|
+
if (ref.opaque) {
|
|
7634
|
+
edge(b, void 0, read);
|
|
7635
|
+
continue;
|
|
7636
|
+
}
|
|
7637
|
+
const root = ref.path[0];
|
|
7638
|
+
if (root === "self") {
|
|
7639
|
+
if (ref.self) follow(b, { node: self }, ref.path, 1, read);
|
|
7640
|
+
else if (ref.path.length > 1) edge(b, void 0, read);
|
|
7641
|
+
continue;
|
|
7642
|
+
}
|
|
7643
|
+
if (root !== "names" || ref.path.length < 2) continue;
|
|
7644
|
+
const name = String(ref.path[1]);
|
|
7645
|
+
let start;
|
|
7646
|
+
if (ref.bound.has(name)) start = "unknown";
|
|
7647
|
+
else if (names?.child !== void 0 && name === "child") start = { node: names.child };
|
|
7648
|
+
else if (names?.prev !== void 0 && name === "prev") start = names.prev;
|
|
7649
|
+
else if (name === "index") start = "data";
|
|
7650
|
+
else if (name === "acc" || name === "item") start = "unknown";
|
|
7651
|
+
else continue;
|
|
7652
|
+
follow(b, start, ref.path, 2, read);
|
|
7653
|
+
}
|
|
7654
|
+
}
|
|
7655
|
+
/** This function gives the previous sibling of a node in the list `children` of its parent, before the run. */
|
|
7656
|
+
function prevOf(parent, child) {
|
|
7657
|
+
const list = parent.parts.get("children");
|
|
7658
|
+
if (list === void 0 || list.evaluator !== LIST$1) return "unknown";
|
|
7659
|
+
for (const [index, item] of list.items) {
|
|
7660
|
+
if (item.node !== child) continue;
|
|
7661
|
+
return index === 0 ? "data" : valueOf$1(list.items.get(index - 1));
|
|
7662
|
+
}
|
|
7663
|
+
return "data";
|
|
7664
|
+
}
|
|
7665
|
+
/**
|
|
7666
|
+
* This function adds the reads of the rungs of an attribute that is not an own part, in the order of the ladder
|
|
7667
|
+
* (TREE.LADDER). After a rung that can fail to answer, the reads of the later rungs are lazy.
|
|
7668
|
+
*/
|
|
7669
|
+
function rungReads(b, id) {
|
|
7670
|
+
const node = b.nodes.get(id);
|
|
7671
|
+
const key = b.vertices.get(id).attribute;
|
|
7672
|
+
const tree = node.kind.tree;
|
|
7673
|
+
const r = {
|
|
7674
|
+
to: id,
|
|
7675
|
+
lazy: false,
|
|
7676
|
+
place: node.visit.place,
|
|
7677
|
+
at: node.visit.at
|
|
7678
|
+
};
|
|
7679
|
+
const equation = hasOwnEntry(tree?.equations, key) ? tree?.equations?.[key] : void 0;
|
|
7680
|
+
if (equation !== void 0) {
|
|
7681
|
+
termReads(b, termRefs(b, node.kind, `tree.equations.${key}`, equation), node, void 0, r);
|
|
7682
|
+
return;
|
|
7683
|
+
}
|
|
7684
|
+
let later = r;
|
|
7685
|
+
const up = node.up;
|
|
7686
|
+
if (up === void 0) {
|
|
7687
|
+
if (downNames(b.machine.world).has(key)) {
|
|
7688
|
+
edge(b, void 0, r);
|
|
7689
|
+
later = {
|
|
7690
|
+
...r,
|
|
7691
|
+
lazy: true
|
|
7692
|
+
};
|
|
7693
|
+
}
|
|
7694
|
+
} else if (up !== null) {
|
|
7695
|
+
const down = hasOwnEntry(up.kind.tree?.down, key) ? up.kind.tree?.down?.[key] : void 0;
|
|
7696
|
+
if (down !== void 0) {
|
|
7697
|
+
edge(b, vertexOf(b, up, "children"), r);
|
|
7698
|
+
termReads(b, termRefs(b, up.kind, `tree.down.${key}`, down), up, {
|
|
7699
|
+
child: node,
|
|
7700
|
+
prev: prevOf(up, node)
|
|
7701
|
+
}, r);
|
|
7702
|
+
return;
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
const declared = hasOwnEntry(tree?.attributes, key) ? tree?.attributes?.[key] : void 0;
|
|
7706
|
+
if (declared?.copy === true && up !== null) {
|
|
7707
|
+
edge(b, up === void 0 ? void 0 : vertexOf(b, up, key), later);
|
|
7708
|
+
later = {
|
|
7709
|
+
...later,
|
|
7710
|
+
lazy: true
|
|
7711
|
+
};
|
|
7712
|
+
}
|
|
7713
|
+
const fold = declared?.fold;
|
|
7714
|
+
if (fold !== void 0) {
|
|
7715
|
+
termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.fold.unit`, fold.unit), node, void 0, later);
|
|
7716
|
+
edge(b, vertexOf(b, node, "children"), later);
|
|
7717
|
+
const children = valueOf$1(node.parts.get("children"));
|
|
7718
|
+
let combined = false;
|
|
7719
|
+
if (children === "unknown") edge(b, void 0, later);
|
|
7720
|
+
else if (typeof children === "object" && "list" in children) for (const item of itemsOf(children.list)) {
|
|
7721
|
+
const child = item.node;
|
|
7722
|
+
if (child === void 0) continue;
|
|
7723
|
+
edge(b, vertexOf(b, child, key), later);
|
|
7724
|
+
combined = true;
|
|
7725
|
+
}
|
|
7726
|
+
termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.fold.combine`, fold.combine), node, void 0, combined ? later : {
|
|
7727
|
+
...later,
|
|
7728
|
+
lazy: true
|
|
7729
|
+
});
|
|
7730
|
+
return;
|
|
7731
|
+
}
|
|
7732
|
+
if (declared?.default !== void 0) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, void 0, later);
|
|
7733
|
+
}
|
|
7734
|
+
function build(machine, doc, declaredType) {
|
|
7735
|
+
const b = {
|
|
7736
|
+
machine,
|
|
7737
|
+
vertices: /* @__PURE__ */ new Map(),
|
|
7738
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
7739
|
+
edges: [],
|
|
7740
|
+
ats: [],
|
|
7741
|
+
work: [],
|
|
7742
|
+
terms: /* @__PURE__ */ new Map()
|
|
7743
|
+
};
|
|
7744
|
+
const roots = [];
|
|
7745
|
+
const all = [];
|
|
7746
|
+
const reads = [];
|
|
7747
|
+
const states = /* @__PURE__ */ new Map();
|
|
7748
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
7749
|
+
const selves = selvesOf(machine, "eval", OUTSIDE);
|
|
7750
|
+
walk(machine.world, doc, (v) => {
|
|
7751
|
+
const answer = ask(machine.world, v.declared ? {} : v.doc, "eval", v.declared ? { declaredType: typeName(v.doc.type) } : {});
|
|
7752
|
+
if (answer.error === "unknown-kind") return "skip";
|
|
7753
|
+
const evaluator = answer.evaluator;
|
|
7754
|
+
const parent = v.parent === void 0 ? void 0 : states.get(v.parent);
|
|
7755
|
+
const { self, node: seen } = selves.enter(v, evaluator);
|
|
7756
|
+
let owner;
|
|
7757
|
+
let lazy = false;
|
|
7758
|
+
if (parent?.info.node !== void 0) owner = {
|
|
7759
|
+
node: parent.info.node,
|
|
7760
|
+
key: slotKey(v.slot)
|
|
7761
|
+
};
|
|
7762
|
+
else if (parent !== void 0 && v.parent !== void 0) {
|
|
7763
|
+
owner = parent.owner;
|
|
7764
|
+
lazy = parent.lazy || partLazy(parent.above, v.parent, v.slot);
|
|
7765
|
+
}
|
|
7766
|
+
let node;
|
|
7767
|
+
if (seen !== void 0 && v.kind !== void 0) {
|
|
7768
|
+
const up = self === OUTSIDE ? null : self === void 0 ? void 0 : nodes.get(self);
|
|
7769
|
+
node = {
|
|
7770
|
+
visit: v,
|
|
7771
|
+
kind: v.kind,
|
|
7772
|
+
text: seen.text,
|
|
7773
|
+
own: [...seen.own ?? []],
|
|
7774
|
+
parts: /* @__PURE__ */ new Map(),
|
|
7775
|
+
up
|
|
7776
|
+
};
|
|
7777
|
+
nodes.set(seen, node);
|
|
7778
|
+
all.push(node);
|
|
7779
|
+
if (up === null && !lazy) roots.push(node);
|
|
7780
|
+
}
|
|
7781
|
+
const info = {
|
|
7782
|
+
visit: v,
|
|
7783
|
+
evaluator,
|
|
7784
|
+
node,
|
|
7785
|
+
items: /* @__PURE__ */ new Map()
|
|
7786
|
+
};
|
|
7787
|
+
if (parent?.info.node !== void 0) parent.info.node.parts.set(slotKey(v.slot), info);
|
|
7788
|
+
else if (parent?.info.evaluator === LIST$1 && v.slot.length === 1 && typeof v.slot[0] === "number") parent.info.items.set(v.slot[0], info);
|
|
7789
|
+
states.set(v, {
|
|
7790
|
+
info,
|
|
7791
|
+
owner,
|
|
7792
|
+
lazy,
|
|
7793
|
+
above: {
|
|
7794
|
+
lazy,
|
|
7795
|
+
rules: v.kind === void 0 ? void 0 : codeFactsOf(machine, v, v.kind, "eval").rules
|
|
7796
|
+
}
|
|
7797
|
+
});
|
|
7798
|
+
if (owner === void 0 || node !== void 0) return void 0;
|
|
7799
|
+
const value = self === OUTSIDE ? "outside" : self === void 0 ? "unknown" : { node: nodes.get(self) };
|
|
7800
|
+
if (evaluator === "ref.resolve" && isRef(v.doc)) {
|
|
7801
|
+
if (v.address !== true) reads.push({
|
|
7802
|
+
owner,
|
|
7803
|
+
v,
|
|
7804
|
+
lazy,
|
|
7805
|
+
self: value,
|
|
7806
|
+
opaque: false
|
|
7807
|
+
});
|
|
7808
|
+
} else if (value !== "unknown" && evaluator !== "ref.resolve" && !keepsSelf(evaluator)) reads.push({
|
|
7809
|
+
owner,
|
|
7810
|
+
v,
|
|
7811
|
+
lazy,
|
|
7812
|
+
self: value,
|
|
7813
|
+
opaque: true
|
|
7814
|
+
});
|
|
7815
|
+
}, declaredType === void 0 ? {} : { declaredType });
|
|
7816
|
+
for (const node of all) for (const key of node.own) vertexOf(b, node, key);
|
|
7817
|
+
for (const { owner, v, lazy, self, opaque } of reads) {
|
|
7818
|
+
const r = {
|
|
7819
|
+
to: vertexOf(b, owner.node, owner.key),
|
|
7820
|
+
lazy,
|
|
7821
|
+
place: v.place,
|
|
7822
|
+
at: v.at
|
|
7823
|
+
};
|
|
7824
|
+
if (opaque) {
|
|
7825
|
+
edge(b, void 0, r);
|
|
7826
|
+
continue;
|
|
7827
|
+
}
|
|
7828
|
+
const path = v.doc.value;
|
|
7829
|
+
const root = path[0];
|
|
7830
|
+
if (root === "self") {
|
|
7831
|
+
if (self === "unknown") edge(b, void 0, r);
|
|
7832
|
+
else follow(b, self, path, 1, r);
|
|
7833
|
+
} else if (root === "names" && path.length >= 2 && v.names.has(String(path[1]))) edge(b, void 0, r);
|
|
7834
|
+
}
|
|
7835
|
+
for (let next = b.work.shift(); next !== void 0; next = b.work.shift()) rungReads(b, next);
|
|
7836
|
+
return {
|
|
7837
|
+
b,
|
|
7838
|
+
roots
|
|
7839
|
+
};
|
|
7840
|
+
}
|
|
7841
|
+
/** This function gives the graph for `graph.ts`: each vertex with the vertices that it reads. */
|
|
7842
|
+
function nodeGraph(b) {
|
|
7843
|
+
const reads = /* @__PURE__ */ new Map();
|
|
7844
|
+
for (const id of b.vertices.keys()) reads.set(id, []);
|
|
7845
|
+
for (const e of b.edges) reads.get(e.to)?.push({
|
|
7846
|
+
to: e.from,
|
|
7847
|
+
lazy: e.lazy
|
|
7848
|
+
});
|
|
7849
|
+
return new Map([...reads].map(([id, edges]) => [id, { edges }]));
|
|
7850
|
+
}
|
|
7851
|
+
/**
|
|
7852
|
+
* This function gives the graph of attribute instances of a document (TREE.GRAPH, R3 P5, D107). It has one vertex for
|
|
7853
|
+
* each attribute of each node of the decorated tree. It has one edge for each read of the code of an attribute.
|
|
7854
|
+
*
|
|
7855
|
+
* - A path from `self` reads the attribute of the node. `self.parent` goes to the parent node.
|
|
7856
|
+
* - The step `nearest` with the key `parent` depends only on the structure, thus it goes to a node of the document.
|
|
7857
|
+
* - The rungs of the ladder read too: `names.prev` of `down` reads the previous sibling, and a fold reads each child.
|
|
7858
|
+
* - A read that depends on the value of an attribute gets an edge with an unknown source.
|
|
7859
|
+
*
|
|
7860
|
+
* The walk is the walk of `check`, with its rules for the self of each document (D106). The evaluator of the kernel
|
|
7861
|
+
* does not use the graph (D3).
|
|
7862
|
+
*
|
|
7863
|
+
* @param machine - The world and the code of the evaluators.
|
|
7864
|
+
* @param doc - The document, as JSON.
|
|
7865
|
+
* @param options - The type of a bare root value.
|
|
7866
|
+
* @returns The vertices, the edges, the graph for the analysis, the reads that only the run knows and the cycles.
|
|
7867
|
+
*/
|
|
7868
|
+
function attributeGraph(machine, doc, options = {}) {
|
|
7869
|
+
const { b } = build(machine, doc, options.declaredType);
|
|
7870
|
+
const graph = nodeGraph(b);
|
|
7871
|
+
return {
|
|
7872
|
+
vertices: b.vertices,
|
|
7873
|
+
edges: b.edges,
|
|
7874
|
+
graph,
|
|
7875
|
+
unknown: b.edges.filter((e) => e.from === void 0),
|
|
7876
|
+
cycles: cycles(graph).map((cycle) => ({
|
|
7877
|
+
vertices: cycle.nodes,
|
|
7878
|
+
certain: cycle.certain
|
|
7879
|
+
}))
|
|
7880
|
+
};
|
|
7881
|
+
}
|
|
7882
|
+
function vertexText(vertex) {
|
|
7883
|
+
return `${vertex.attribute} of ${vertex.type} at [${vertex.place.join(", ")}]`;
|
|
7884
|
+
}
|
|
7885
|
+
/**
|
|
7886
|
+
* This function finds each certain cycle of the attributes of a document before a run (TREE.GRAPH-CYCLE, D107). A run
|
|
7887
|
+
* that gives a value reads each own part of each tree root, and each eager read of each attribute that it reads. Thus a
|
|
7888
|
+
* certain cycle that these reads reach always gives `#CYCLE!`. The issue gets the place of the read that closes the
|
|
7889
|
+
* cycle in the order of the run, and the reason of the run. A cycle through a lazy read or an unknown read is not an issue.
|
|
7890
|
+
*
|
|
7891
|
+
* @param machine - The world and the code of the evaluators.
|
|
7892
|
+
* @param doc - The document, as JSON.
|
|
7893
|
+
* @param options - The type of a bare root value.
|
|
7894
|
+
* @returns One issue for each certain cycle that the run reaches.
|
|
7895
|
+
*/
|
|
7896
|
+
function cycleIssues(machine, doc, options) {
|
|
7897
|
+
const { b, roots } = build(machine, doc, options.declaredType);
|
|
7898
|
+
const graph = nodeGraph(b);
|
|
7899
|
+
const certain = /* @__PURE__ */ new Map();
|
|
7900
|
+
cycles(graph).forEach((cycle, index) => {
|
|
7901
|
+
if (cycle.certain) for (const id of cycle.nodes) certain.set(id, index);
|
|
7902
|
+
});
|
|
7903
|
+
if (certain.size === 0) return [];
|
|
7904
|
+
const eager = /* @__PURE__ */ new Map();
|
|
7905
|
+
b.edges.forEach((e, index) => {
|
|
7906
|
+
if (e.lazy || e.from === void 0) return;
|
|
7907
|
+
const list = eager.get(e.to);
|
|
7908
|
+
if (list === void 0) eager.set(e.to, [index]);
|
|
7909
|
+
else list.push(index);
|
|
7910
|
+
});
|
|
7911
|
+
const demands = [];
|
|
7912
|
+
const done = /* @__PURE__ */ new Set();
|
|
7913
|
+
for (const root of roots) jsonOrder({ node: root }, demands, done);
|
|
7914
|
+
const order = demands.map(([node, key]) => attributeVertexId(node.visit.place, key));
|
|
7915
|
+
const state = /* @__PURE__ */ new Map();
|
|
7916
|
+
const reported = /* @__PURE__ */ new Set();
|
|
7917
|
+
const out = [];
|
|
7918
|
+
for (const start of order) {
|
|
7919
|
+
if (state.has(start)) continue;
|
|
7920
|
+
const stack = [{
|
|
7921
|
+
id: start,
|
|
7922
|
+
next: 0
|
|
7923
|
+
}];
|
|
7924
|
+
state.set(start, "busy");
|
|
7925
|
+
while (stack.length > 0) {
|
|
7926
|
+
const top = stack[stack.length - 1];
|
|
7927
|
+
const list = eager.get(top.id) ?? [];
|
|
7928
|
+
if (top.next >= list.length) {
|
|
7929
|
+
state.set(top.id, "done");
|
|
7930
|
+
stack.pop();
|
|
7931
|
+
continue;
|
|
7932
|
+
}
|
|
7933
|
+
const index = list[top.next];
|
|
7934
|
+
top.next += 1;
|
|
7935
|
+
const e = b.edges[index];
|
|
7936
|
+
const from = e.from;
|
|
7937
|
+
const seen = state.get(from);
|
|
7938
|
+
if (seen === void 0) {
|
|
7939
|
+
state.set(from, "busy");
|
|
7940
|
+
stack.push({
|
|
7941
|
+
id: from,
|
|
7942
|
+
next: 0
|
|
7943
|
+
});
|
|
7944
|
+
continue;
|
|
7945
|
+
}
|
|
7946
|
+
if (seen === "done") continue;
|
|
7947
|
+
const cycle = certain.get(from);
|
|
7948
|
+
if (cycle === void 0 || reported.has(cycle)) continue;
|
|
7949
|
+
reported.add(cycle);
|
|
7950
|
+
const first = stack.findIndex((frame) => frame.id === from);
|
|
7951
|
+
const chain = [...stack.slice(first).map((frame) => frame.id), from].map((id) => vertexText(b.vertices.get(id)));
|
|
7952
|
+
out.push({
|
|
7953
|
+
code: "#CYCLE!",
|
|
7954
|
+
place: e.place,
|
|
7955
|
+
reason: `The attributes form a cycle: ${chain.join(" -> ")}.`,
|
|
7956
|
+
at: b.ats[index]
|
|
7957
|
+
});
|
|
7958
|
+
}
|
|
7959
|
+
}
|
|
7960
|
+
return out;
|
|
7961
|
+
}
|
|
7962
|
+
// src/static.ts
|
|
7963
|
+
/** The names of a walk without names. A walk does not change a set at any time: it makes a new set to add names. */
|
|
7964
|
+
const NO_NAMES = /* @__PURE__ */ new Set();
|
|
7965
|
+
/** This function gives the options of a walk without the names of the host. */
|
|
7966
|
+
function withoutNames(options) {
|
|
7967
|
+
const { names: _host, ...rest } = options;
|
|
7968
|
+
return rest;
|
|
7969
|
+
}
|
|
7970
|
+
/** This function gives the keys of a trail, from its start. It keeps the result in the trail. */
|
|
7971
|
+
function keysOf(trail) {
|
|
7972
|
+
if (trail === void 0) return [];
|
|
7973
|
+
if (trail.full !== void 0) return trail.full;
|
|
7974
|
+
const steps = [];
|
|
7975
|
+
let here = trail;
|
|
7976
|
+
while (here !== void 0 && here.full === void 0) {
|
|
7977
|
+
steps.push(here);
|
|
7978
|
+
here = here.up;
|
|
7979
|
+
}
|
|
7980
|
+
const keys = here?.full === void 0 ? [] : [...here.full];
|
|
7981
|
+
for (let index = steps.length - 1; index >= 0; index -= 1) for (const key of steps[index].keys) keys.push(key);
|
|
7982
|
+
trail.full = keys;
|
|
7983
|
+
return keys;
|
|
7984
|
+
}
|
|
7985
|
+
/** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
|
|
7986
|
+
var Visit = class {
|
|
7987
|
+
doc;
|
|
7988
|
+
declared;
|
|
7989
|
+
depth;
|
|
7990
|
+
names;
|
|
7991
|
+
later;
|
|
7992
|
+
slot;
|
|
7993
|
+
parent;
|
|
7994
|
+
kind;
|
|
7995
|
+
typePath;
|
|
7996
|
+
address;
|
|
7997
|
+
caps;
|
|
7998
|
+
#place;
|
|
7999
|
+
#at;
|
|
8000
|
+
#bound;
|
|
8001
|
+
#expect;
|
|
8002
|
+
constructor(place, at, doc, declared, depth, names, later, slot, parent, kind, typePath, address, caps, bound, expect) {
|
|
8003
|
+
this.doc = doc;
|
|
8004
|
+
this.declared = declared;
|
|
8005
|
+
this.depth = depth;
|
|
8006
|
+
this.names = names;
|
|
8007
|
+
this.later = later;
|
|
8008
|
+
this.slot = slot;
|
|
8009
|
+
this.parent = parent;
|
|
8010
|
+
this.kind = kind;
|
|
8011
|
+
this.typePath = typePath;
|
|
8012
|
+
this.address = address;
|
|
8013
|
+
this.caps = caps;
|
|
8014
|
+
this.#place = place;
|
|
8015
|
+
this.#at = at;
|
|
8016
|
+
this.#bound = bound;
|
|
8017
|
+
this.#expect = expect;
|
|
8018
|
+
}
|
|
8019
|
+
/** The names that the scope rules of the parent bind for this document. The check of types reads them. */
|
|
8020
|
+
get bound() {
|
|
8021
|
+
return this.#bound;
|
|
8022
|
+
}
|
|
8023
|
+
/** The type that the shape of the parent declares for this part. The check of types reads it. */
|
|
8024
|
+
get expect() {
|
|
8025
|
+
return this.#expect;
|
|
8026
|
+
}
|
|
8027
|
+
get place() {
|
|
8028
|
+
return keysOf(this.#place);
|
|
8029
|
+
}
|
|
8030
|
+
get at() {
|
|
8031
|
+
return keysOf(this.#at);
|
|
8032
|
+
}
|
|
8033
|
+
};
|
|
8034
|
+
/**
|
|
8035
|
+
* This function visits each document of a document: the root and each part at each depth, by the contracts of the
|
|
8036
|
+
* kinds. A part of a kind that the world does not have is not visited. With capabilities in the options, the walk
|
|
8037
|
+
* narrows them for each part by the rules of its kind, as a run does. The walk uses no stack of the host for the
|
|
8038
|
+
* depth, thus a deep document cannot overflow it. The time and the memory of a walk are linear in the size of the
|
|
8039
|
+
* document. A visit makes the keys of its place and of its path only when a reader asks for them.
|
|
8040
|
+
*
|
|
8041
|
+
* @param world - The world of the kinds.
|
|
8042
|
+
* @param doc - The document, as JSON.
|
|
8043
|
+
* @param visit - The function that gets each document. It gives `"skip"` to not visit the parts of a document.
|
|
8044
|
+
* @param options - The type of a bare root value, the names, the capabilities and a function for the problems.
|
|
8045
|
+
*/
|
|
8046
|
+
function walk(world, doc, visit, options = {}) {
|
|
8047
|
+
const stack = [{
|
|
8048
|
+
value: doc,
|
|
8049
|
+
place: void 0,
|
|
8050
|
+
at: void 0,
|
|
8051
|
+
declared: options.declaredType,
|
|
8052
|
+
depth: 0,
|
|
8053
|
+
names: options.names === void 0 || options.names.length === 0 ? NO_NAMES : new Set(options.names),
|
|
8054
|
+
later: NO_NAMES,
|
|
8055
|
+
address: false,
|
|
8056
|
+
slot: [],
|
|
8057
|
+
parent: void 0,
|
|
8058
|
+
caps: options.caps,
|
|
8059
|
+
bound: [],
|
|
8060
|
+
expect: void 0
|
|
8061
|
+
}];
|
|
8062
|
+
while (stack.length > 0) {
|
|
8063
|
+
const item = stack.pop();
|
|
8064
|
+
const read = partsOf(world, item.value, item.declared);
|
|
8065
|
+
if (!read.ok) {
|
|
8066
|
+
options.problem?.({
|
|
8067
|
+
place: keysOf(item.place),
|
|
8068
|
+
at: keysOf(item.at),
|
|
8069
|
+
code: read.code,
|
|
8070
|
+
reason: read.reason
|
|
8071
|
+
});
|
|
8072
|
+
continue;
|
|
8073
|
+
}
|
|
8074
|
+
const { doc, kind, split } = read;
|
|
8075
|
+
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, item.bound, item.expect);
|
|
8076
|
+
if (visit(here) === "skip" || kind === void 0) continue;
|
|
8077
|
+
const base = read.declared ? item.at : {
|
|
8078
|
+
up: item.at,
|
|
8079
|
+
keys: ["value"]
|
|
8080
|
+
};
|
|
8081
|
+
if (!split.ok) {
|
|
8082
|
+
options.problem?.({
|
|
8083
|
+
place: keysOf(item.place),
|
|
8084
|
+
at: keysOf({
|
|
8085
|
+
up: base,
|
|
8086
|
+
keys: split.at
|
|
8087
|
+
}),
|
|
8088
|
+
code: "#VALUE!",
|
|
8089
|
+
reason: split.reason
|
|
8090
|
+
});
|
|
8091
|
+
continue;
|
|
8092
|
+
}
|
|
8093
|
+
const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
|
|
8094
|
+
for (let index = split.parts.length - 1; index >= 0; index -= 1) {
|
|
8095
|
+
const slot = split.parts[index];
|
|
8096
|
+
const extra = sees[index] ?? [];
|
|
8097
|
+
const hidden = later[index] ?? [];
|
|
8098
|
+
stack.push({
|
|
8099
|
+
value: slot.item,
|
|
8100
|
+
place: slot.path.length === 0 ? item.place : {
|
|
8101
|
+
up: item.place,
|
|
8102
|
+
keys: slot.path
|
|
8103
|
+
},
|
|
8104
|
+
at: slot.path.length === 0 ? base : {
|
|
8105
|
+
up: base,
|
|
8106
|
+
keys: slot.path
|
|
8107
|
+
},
|
|
8108
|
+
declared: slot.of,
|
|
8109
|
+
depth: item.depth + 1,
|
|
8110
|
+
names: extra.length === 0 ? item.names : /* @__PURE__ */ new Set([...item.names, ...extra]),
|
|
8111
|
+
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden]),
|
|
8112
|
+
address: slot.address === true,
|
|
8113
|
+
slot: slot.path,
|
|
8114
|
+
parent: here,
|
|
8115
|
+
caps: item.caps === void 0 || kind.caps === void 0 ? item.caps : partCaps(kind.caps, slot.path, item.caps),
|
|
8116
|
+
bound: extra,
|
|
8117
|
+
expect: slot.expect
|
|
8118
|
+
});
|
|
8119
|
+
}
|
|
8120
|
+
}
|
|
8121
|
+
}
|
|
8122
|
+
/**
|
|
8123
|
+
* This function finds each reference of a document: each document of the type `ref` that a walk reaches. A
|
|
8124
|
+
* reference to a name that a kind around it binds is not free. The others are what the document reads from
|
|
8125
|
+
* outside: its dependencies. When the kind `ref` has parts, the walk also finds the references in its step documents.
|
|
8126
|
+
*
|
|
8127
|
+
* @param world - The world of the kinds.
|
|
8128
|
+
* @param doc - The document, as JSON.
|
|
8129
|
+
* @param options - The type of a bare root value. The names of the host do not change the result.
|
|
8130
|
+
* @returns The references, in the order of the document.
|
|
8131
|
+
*/
|
|
8132
|
+
function refsOf(world, doc, options = {}) {
|
|
8133
|
+
const out = [];
|
|
8134
|
+
walk(world, doc, (v) => {
|
|
8135
|
+
if (!isRef(v.doc)) return void 0;
|
|
8136
|
+
const path = v.doc.value;
|
|
8137
|
+
const root = typeof path[0] === "string" ? path[0] : "";
|
|
8138
|
+
const first = path[1];
|
|
8139
|
+
const use = {
|
|
8140
|
+
path,
|
|
8141
|
+
root,
|
|
8142
|
+
free: !(root === "names" && isKey(first) && v.names.has(String(first))),
|
|
8143
|
+
address: v.address === true
|
|
8144
|
+
};
|
|
8145
|
+
Object.defineProperty(use, "place", {
|
|
8146
|
+
enumerable: true,
|
|
8147
|
+
get: () => v.place
|
|
8148
|
+
});
|
|
8149
|
+
Object.defineProperty(use, "at", {
|
|
8150
|
+
enumerable: true,
|
|
8151
|
+
get: () => v.at
|
|
8152
|
+
});
|
|
8153
|
+
Object.defineProperty(use, "visit", {
|
|
8154
|
+
enumerable: false,
|
|
8155
|
+
value: v
|
|
8156
|
+
});
|
|
8157
|
+
out.push(use);
|
|
8158
|
+
}, options.names === void 0 ? options : withoutNames(options));
|
|
8159
|
+
return out;
|
|
8160
|
+
}
|
|
8161
|
+
function setAt(value, path, next) {
|
|
8162
|
+
if (path.length === 0) return next;
|
|
8163
|
+
const [key, ...rest] = path;
|
|
8164
|
+
if (Array.isArray(value)) {
|
|
8165
|
+
const copy = [...value];
|
|
8166
|
+
copy[Number(key)] = setAt(value[Number(key)], rest, next);
|
|
8167
|
+
return copy;
|
|
8168
|
+
}
|
|
8169
|
+
const record = isRecord(value) ? value : {};
|
|
8170
|
+
const copy = {};
|
|
8171
|
+
for (const name of Object.keys(record)) Object.defineProperty(copy, name, {
|
|
8172
|
+
value: record[name],
|
|
8173
|
+
enumerable: true,
|
|
8174
|
+
writable: true,
|
|
8175
|
+
configurable: true
|
|
8176
|
+
});
|
|
8177
|
+
Object.defineProperty(copy, String(key), {
|
|
8178
|
+
value: setAt(record[String(key)], rest, next),
|
|
8179
|
+
enumerable: true,
|
|
8180
|
+
writable: true,
|
|
8181
|
+
configurable: true
|
|
8182
|
+
});
|
|
8183
|
+
return copy;
|
|
8184
|
+
}
|
|
8185
|
+
/**
|
|
8186
|
+
* This function changes the free references of a document. The document is not changed: the function gives a new
|
|
8187
|
+
* document that shares each part without a change.
|
|
8188
|
+
*
|
|
8189
|
+
* @param world - The world of the kinds.
|
|
8190
|
+
* @param doc - The document, as JSON.
|
|
8191
|
+
* @param change - The function that gives the new document of a reference, or `undefined` to keep it.
|
|
8192
|
+
* @param options - The type of a bare root value, and the names that the host binds at the root.
|
|
8193
|
+
* @returns The new document.
|
|
8194
|
+
*/
|
|
8195
|
+
function mapRefs(world, doc, change, options = {}) {
|
|
8196
|
+
let out = doc;
|
|
8197
|
+
for (const use of refsOf(world, doc, options)) {
|
|
8198
|
+
if (!use.free) continue;
|
|
8199
|
+
const next = change(use);
|
|
8200
|
+
if (next !== void 0) out = setAt(out, use.at, next);
|
|
8201
|
+
}
|
|
8202
|
+
return out;
|
|
8203
|
+
}
|
|
8204
|
+
/** The least authority: the capability `[]` permits each effect (CAP.KEYS). */
|
|
8205
|
+
const EACH$1 = /* @__PURE__ */ Object.freeze([[]]);
|
|
8206
|
+
/** No declared effect. One list serves each visit without effects. */
|
|
8207
|
+
const NO_EFFECTS = /* @__PURE__ */ Object.freeze([]);
|
|
8208
|
+
/**
|
|
8209
|
+
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys
|
|
8210
|
+
* (FACTS.EFFECTS, VERB.EFFECTS). The text `"type"` gives the type of the document after each alias. A visit without a
|
|
8211
|
+
* kind declares no effect. `check`, `factsOf` and `effectsOf` read the declarations with this one function (D27).
|
|
8212
|
+
*
|
|
8213
|
+
* @param v - The visit.
|
|
8214
|
+
* @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
|
|
8215
|
+
* @returns The keys of each declared effect, in the order of the contract.
|
|
8216
|
+
*/
|
|
8217
|
+
function declaredEffects(v, verb = "eval") {
|
|
8218
|
+
return verbDeclared(v, verb) ?? NO_EFFECTS;
|
|
8219
|
+
}
|
|
8220
|
+
/**
|
|
8221
|
+
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys. The result
|
|
8222
|
+
* is `undefined` when the contract has no declaration for the verb (D49). A visit without a kind declares no effect
|
|
8223
|
+
* for `eval` and no declaration for another verb.
|
|
8224
|
+
*/
|
|
8225
|
+
function verbDeclared(v, verb) {
|
|
8226
|
+
if (v.kind === void 0) return verb === "eval" ? NO_EFFECTS : void 0;
|
|
8227
|
+
const effects = effectsOfVerb(v.kind, verb);
|
|
8228
|
+
if (effects === void 0) return void 0;
|
|
8229
|
+
if (effects.length === 0) return NO_EFFECTS;
|
|
8230
|
+
return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
|
|
8231
|
+
}
|
|
8232
|
+
/**
|
|
8233
|
+
* This function gives the rule of a machine for an effect, for the static kit (CAP.PERMITS). The rule is the hook
|
|
8234
|
+
* `permits` of the machine, as in a run, or else the function `permits`. A check has no values, thus the hook gets a
|
|
8235
|
+
* context with the capabilities of the document and the world of the machine. Its names, its self and its event are
|
|
8236
|
+
* empty.
|
|
8237
|
+
*
|
|
8238
|
+
* @param machine - The machine.
|
|
8239
|
+
* @returns The rule. It gets the capabilities of a document and the keys of an effect.
|
|
8240
|
+
*/
|
|
8241
|
+
function permitsOf(machine) {
|
|
8242
|
+
const hook = machine.permits;
|
|
8243
|
+
if (hook === void 0) return permits;
|
|
8244
|
+
let base;
|
|
8245
|
+
return (caps, kind) => hook(caps, kind, {
|
|
8246
|
+
...base ??= context({ world: worldThing(machine.world) }),
|
|
8247
|
+
caps
|
|
8248
|
+
});
|
|
8249
|
+
}
|
|
8250
|
+
/**
|
|
8251
|
+
* This function gives the reason of a `#CAP!`. When a kind around the document narrowed the capabilities, the
|
|
8252
|
+
* reason names the nearest such kind.
|
|
8253
|
+
*/
|
|
8254
|
+
function capReason(v, kind, allowed) {
|
|
8255
|
+
const text = kind.join(".");
|
|
8256
|
+
for (let up = v.parent; up !== void 0; up = up.parent) if (up.caps !== void 0 && allowed(up.caps, kind)) return `The capabilities do not permit the effect ${text} here. The kind ${typeName(up.doc.type)} around it narrows them for its parts.`;
|
|
8257
|
+
return `The capabilities do not permit the effect ${text}.`;
|
|
8258
|
+
}
|
|
8259
|
+
/**
|
|
8260
|
+
* This function checks a document before an evaluation, by the contracts of its kinds. It finds the problems
|
|
8261
|
+
* that an evaluation finds when it reaches them:
|
|
8262
|
+
*
|
|
8263
|
+
* - `#KIND?`: a kind that the world does not have.
|
|
8264
|
+
* - `#NOEVAL`: no evaluator.
|
|
8265
|
+
* - `#VALUE!`: a value without the shape of its kind.
|
|
8266
|
+
* - `#PART?`: a value without a type.
|
|
8267
|
+
* - `#DEPTH!`: too much nesting.
|
|
8268
|
+
* - `#REF!` and `#STEP?`: a bad path.
|
|
8269
|
+
* - `#NAME?`: a name that nothing binds. With the hook `known` of the machine, also a name that the language does not
|
|
8270
|
+
* have, for example an operation.
|
|
8271
|
+
* - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
|
|
8272
|
+
* The hook `permits` of the machine decides, as in a run (CAP.PERMITS).
|
|
8273
|
+
* - `#CALC!`: with the option `run`, a runner under a parent without a runner, when an effect of its runners can reach
|
|
8274
|
+
* the host. Also a kind that declares effects and has no runner (RUN.CAN-WAIT). The issue has the place and the
|
|
8275
|
+
* reason of the run.
|
|
8276
|
+
* - `#TYPE!`: with the option `types`, a part whose type does not fit the type that its position needs (TYPE.CHECK).
|
|
8277
|
+
* A run does not always fail there, thus a type issue is a static finding, not an error of a run.
|
|
8278
|
+
*
|
|
8279
|
+
* The check asks the ladder for the verb of the options, `eval` by default (VERB.CHECK). The shapes, the paths and the
|
|
8280
|
+
* names follow the contracts, thus they are the same for each verb. The check tests the effects that each contract
|
|
8281
|
+
* declares for the verb: `effects` for `eval`, and the entry of `verbEffects` for another verb (VERB.EFFECTS). A kind
|
|
8282
|
+
* without a declaration for the verb gives no `#CAP!` and no `#CALC!` for an effect.
|
|
8283
|
+
*
|
|
8284
|
+
* @param machine - The world and the code of the evaluators.
|
|
8285
|
+
* @param doc - The document, as JSON.
|
|
8286
|
+
* @param options - The type of a bare root value, the names of the host, the capabilities, the verb, the types and the run.
|
|
8287
|
+
* @returns The problems, in the order of the document. The type issues come after the other issues.
|
|
8288
|
+
*/
|
|
8289
|
+
function check(machine, doc, options = {}) {
|
|
8290
|
+
return options.types === true ? typeCheck(machine, doc, options).issues : checkAll(machine, doc, options, void 0);
|
|
8291
|
+
}
|
|
8292
|
+
/**
|
|
8293
|
+
* This function checks a document, and it also checks the value types (TYPE.CHECK, D25). It finds each issue of
|
|
8294
|
+
* `check`. Then it synthesizes the type of each document from the types of its parts. It also checks each part
|
|
8295
|
+
* against the type that its position needs. The type rule of the parent, the signature of an operation or the shape
|
|
8296
|
+
* of the contract gives that type. A part with an issue gets the type `unknown`, thus one
|
|
8297
|
+
* problem does not give a chain of issues.
|
|
8298
|
+
*
|
|
8299
|
+
* A type issue has the code `#TYPE!`, the type that the position needs (`expected`) and the type that the check found
|
|
8300
|
+
* (`found`). It is a static finding: the evaluation of the document does not change.
|
|
8301
|
+
*
|
|
8302
|
+
* The option `rootTypes` gives the types of the roots and of the names of the host (TYPE.ROOTS). A reference then gets
|
|
8303
|
+
* the type that its path reaches, and a field that a closed record does not have is a type issue (TYPE.CLOSED).
|
|
8304
|
+
*
|
|
8305
|
+
* @param machine - The world and the code of the evaluators, with their type rules.
|
|
8306
|
+
* @param doc - The document, as JSON.
|
|
8307
|
+
* @param options - The options of `check`, the type that the root must have and the types of the roots.
|
|
8308
|
+
* @returns The type of the root, each issue, and the type of each document.
|
|
8309
|
+
*/
|
|
8310
|
+
function typeCheck(machine, doc, options = {}) {
|
|
8311
|
+
const nodes = [];
|
|
8312
|
+
const issues = checkAll(machine, doc, options, nodes);
|
|
8313
|
+
const pass = typePass(machine, nodes, options.verb ?? "eval", options.expect, options.rootTypes);
|
|
8314
|
+
return {
|
|
8315
|
+
type: pass.type,
|
|
8316
|
+
issues: pass.issues.length === 0 ? issues : [...issues, ...pass.issues],
|
|
8317
|
+
places: pass.places
|
|
8318
|
+
};
|
|
8319
|
+
}
|
|
8320
|
+
/** This function tells if a table of verbs has an entry for a verb. */
|
|
8321
|
+
function hasVerb(table, verb) {
|
|
8322
|
+
return table !== void 0 && Object.hasOwn(table, verb);
|
|
8323
|
+
}
|
|
8324
|
+
/**
|
|
8325
|
+
* This function checks the world of a machine before a document exists. It finds each kind that has no entry for a
|
|
8326
|
+
* verb of the list, when the world has no default for that verb (STATIC.COVERAGE). A document of such a kind gets no
|
|
8327
|
+
* evaluator for the verb, thus the check gives the same proof of coverage as a type system.
|
|
8328
|
+
*
|
|
8329
|
+
* The check visits each kind of each namespace, at each depth, and the fallback kind of each namespace and of the
|
|
8330
|
+
* world. It does not follow an alias, because the kind of an alias has its own place. Without the option `types`, the
|
|
8331
|
+
* check reads only the world.
|
|
8332
|
+
*
|
|
8333
|
+
* With the option `types`, the check also examines each value type of the machine (TYPE.WORLD, D110). These issues
|
|
8334
|
+
* have the code `#TYPE!`, and they come after the issues of the verbs.
|
|
8335
|
+
*
|
|
8336
|
+
* @param machine - The machine, for its world.
|
|
8337
|
+
* @param verbs - The verbs that each kind of the language must answer, for example `eval`, `view` and `type`.
|
|
8338
|
+
* @param options - The option `types`.
|
|
8339
|
+
* @returns One issue for each kind and verb without an evaluator. The order is the order of the world, then the
|
|
8340
|
+
* order of the verbs. The fallback kind of a namespace comes after the entries of that namespace.
|
|
8341
|
+
*/
|
|
8342
|
+
function checkWorld(machine, verbs, options = {}) {
|
|
8343
|
+
const world = machine.world;
|
|
8344
|
+
const open = verbs.filter((verb) => !hasVerb(world.defaults, verb));
|
|
8345
|
+
const out = [];
|
|
8346
|
+
if (open.length > 0) eachKind(world, (kind, path, fallback) => {
|
|
8347
|
+
const text = path.join(".");
|
|
8348
|
+
const name = fallback ? path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}` : `the kind ${text}`;
|
|
8349
|
+
for (const verb of open) {
|
|
8350
|
+
if (hasVerb(kind.verbs, verb)) continue;
|
|
8351
|
+
const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
|
|
8352
|
+
out.push(fallback ? {
|
|
8353
|
+
code: "#NOEVAL",
|
|
8354
|
+
kind: path,
|
|
8355
|
+
fallback: true,
|
|
8356
|
+
verb,
|
|
8357
|
+
reason
|
|
8358
|
+
} : {
|
|
8359
|
+
code: "#NOEVAL",
|
|
8360
|
+
kind: path,
|
|
8361
|
+
verb,
|
|
8362
|
+
reason
|
|
8363
|
+
});
|
|
8364
|
+
}
|
|
8365
|
+
});
|
|
8366
|
+
if (options.types === true) out.push(...machineTypeIssues(machine));
|
|
8367
|
+
return out;
|
|
8368
|
+
}
|
|
8369
|
+
/** The names that an equation of `down` sees (TREE.DOWN). */
|
|
8370
|
+
const DOWN_NAMES = /* @__PURE__ */ Object.freeze([
|
|
8371
|
+
"child",
|
|
8372
|
+
"index",
|
|
8373
|
+
"prev"
|
|
8374
|
+
]);
|
|
8375
|
+
/** The names that the term `combine` of a fold sees (TREE.FOLD). */
|
|
8376
|
+
const FOLD_NAMES = /* @__PURE__ */ Object.freeze(["acc", "item"]);
|
|
8377
|
+
/**
|
|
8378
|
+
* This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
|
|
8379
|
+
* `equations`, a `unit` and a `default` see no names. `check` and `factsOf` read the equations with this one function.
|
|
8380
|
+
*/
|
|
8381
|
+
function equationsOf(kind) {
|
|
8382
|
+
const tree = kind.tree;
|
|
8383
|
+
if (tree === void 0) return [];
|
|
8384
|
+
const out = [];
|
|
8385
|
+
for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({
|
|
8386
|
+
label: `tree.equations.${name}`,
|
|
8387
|
+
term,
|
|
8388
|
+
names: [],
|
|
8389
|
+
attribute: name,
|
|
8390
|
+
down: false
|
|
8391
|
+
});
|
|
8392
|
+
for (const [name, term] of Object.entries(tree.down ?? {})) out.push({
|
|
8393
|
+
label: `tree.down.${name}`,
|
|
8394
|
+
term,
|
|
8395
|
+
names: DOWN_NAMES,
|
|
8396
|
+
attribute: name,
|
|
8397
|
+
down: true
|
|
8398
|
+
});
|
|
8399
|
+
for (const [name, declared] of Object.entries(tree.attributes ?? {})) {
|
|
8400
|
+
if (declared.fold !== void 0) {
|
|
8401
|
+
out.push({
|
|
8402
|
+
label: `tree.attributes.${name}.fold.unit`,
|
|
8403
|
+
term: declared.fold.unit,
|
|
8404
|
+
names: [],
|
|
8405
|
+
attribute: name,
|
|
8406
|
+
down: false
|
|
8407
|
+
});
|
|
8408
|
+
out.push({
|
|
8409
|
+
label: `tree.attributes.${name}.fold.combine`,
|
|
8410
|
+
term: declared.fold.combine,
|
|
8411
|
+
names: FOLD_NAMES,
|
|
8412
|
+
attribute: name,
|
|
8413
|
+
down: false
|
|
8414
|
+
});
|
|
8415
|
+
}
|
|
8416
|
+
if (declared.default !== void 0) out.push({
|
|
8417
|
+
label: `tree.attributes.${name}.default`,
|
|
8418
|
+
term: declared.default,
|
|
8419
|
+
names: [],
|
|
8420
|
+
attribute: name,
|
|
8421
|
+
down: false
|
|
8422
|
+
});
|
|
8423
|
+
}
|
|
8424
|
+
return out;
|
|
8425
|
+
}
|
|
8426
|
+
/**
|
|
8427
|
+
* The steps that a node of the decorated tree answers before its ladder: `parent` and `type` (D106). An attribute with
|
|
8428
|
+
* one of these names gets no read.
|
|
8429
|
+
*/
|
|
8430
|
+
const STRUCTURAL = /* @__PURE__ */ Object.freeze({
|
|
8431
|
+
parent: "the step parent gives the parent of the node",
|
|
8432
|
+
type: "the step type gives the type of the node"
|
|
8433
|
+
});
|
|
8434
|
+
/**
|
|
8435
|
+
* The standard code that gives its parts the self of its own context (D106). `check` knows the self of a document only
|
|
8436
|
+
* through this code and the nodes of the decorated tree.
|
|
8437
|
+
*/
|
|
8438
|
+
const KEEPS_SELF = /* @__PURE__ */ new Set([
|
|
8439
|
+
"data.number",
|
|
8440
|
+
"data.text",
|
|
8441
|
+
"data.bool",
|
|
8442
|
+
"math.add",
|
|
8443
|
+
"math.multiply",
|
|
8444
|
+
"math.greater",
|
|
8445
|
+
"control.seq",
|
|
8446
|
+
"control.pure",
|
|
8447
|
+
"control.if",
|
|
8448
|
+
"list.all",
|
|
8449
|
+
"effect.log",
|
|
8450
|
+
"effect.write"
|
|
8451
|
+
]);
|
|
8452
|
+
/** This function tells if standard code gives its parts the self of its own context (D106). */
|
|
8453
|
+
function keepsSelf(evaluator) {
|
|
8454
|
+
return evaluator !== void 0 && KEEPS_SELF.has(evaluator);
|
|
8455
|
+
}
|
|
8456
|
+
/**
|
|
8457
|
+
* This function makes the selves of one walk (D106). Only `eval` reads attributes, and only when the root `self` is
|
|
8458
|
+
* open. A node gets the self of its code as its parent. The standard code that keeps self gives its parts its own self.
|
|
8459
|
+
* Other code gives its parts a self that the walk does not know.
|
|
8460
|
+
*
|
|
8461
|
+
* @param machine - The machine.
|
|
8462
|
+
* @param verb - The verb of the walk.
|
|
8463
|
+
* @param root - The self of the root document: the node of an equation, or `undefined`.
|
|
8464
|
+
* @returns The selves.
|
|
8465
|
+
*/
|
|
8466
|
+
function selvesOf(machine, verb, root) {
|
|
8467
|
+
const roots = machine.roots ?? ROOTS;
|
|
8468
|
+
const on = verb === "eval" && roots.includes("self") && hasEntry(machine.evaluators, "tree.node") && !hasEntry(machine.runners, "tree.node");
|
|
8469
|
+
const selves = /* @__PURE__ */ new Map();
|
|
8470
|
+
const isNode = (v, evaluator) => on && evaluator === "tree.node" && v.kind !== void 0;
|
|
8471
|
+
return {
|
|
8472
|
+
isNode,
|
|
8473
|
+
enter(v, evaluator) {
|
|
8474
|
+
const self = v.parent === void 0 ? root : selves.get(v.parent);
|
|
8475
|
+
if (!isNode(v, evaluator) || v.kind === void 0) {
|
|
8476
|
+
selves.set(v, keepsSelf(evaluator) ? self : void 0);
|
|
8477
|
+
return {
|
|
8478
|
+
self,
|
|
8479
|
+
node: void 0
|
|
8480
|
+
};
|
|
8481
|
+
}
|
|
8482
|
+
const read = partsOf(machine.world, v.declared ? v.doc.value : v.doc, v.declared ? typeName(v.doc.type) : void 0);
|
|
8483
|
+
const own = read.ok && read.split.ok ? new Set(read.split.parts.map((part) => part.key)) : void 0;
|
|
8484
|
+
const node = {
|
|
8485
|
+
kind: v.kind,
|
|
8486
|
+
text: typeName(v.doc.type),
|
|
8487
|
+
own,
|
|
8488
|
+
parent: self
|
|
8489
|
+
};
|
|
8490
|
+
selves.set(v, node);
|
|
8491
|
+
return {
|
|
8492
|
+
self,
|
|
8493
|
+
node
|
|
8494
|
+
};
|
|
8495
|
+
}
|
|
8496
|
+
};
|
|
8497
|
+
}
|
|
8498
|
+
/**
|
|
8499
|
+
* This function tells if a name is a structural step of a node of the decorated tree: `parent` or `type` (D106). An
|
|
8500
|
+
* attribute with such a name gets no read.
|
|
8501
|
+
*/
|
|
8502
|
+
function isStructural(name) {
|
|
8503
|
+
return Object.hasOwn(STRUCTURAL, name);
|
|
8504
|
+
}
|
|
8505
|
+
/** The names of `down` in the world of each machine, for a parent that `check` does not know (D106). */
|
|
8506
|
+
const DOWNS = /* @__PURE__ */ new WeakMap();
|
|
8507
|
+
/**
|
|
8508
|
+
* This function gives each name that an equation of `down` of a kind of the world gives. The kinds include the kinds
|
|
8509
|
+
* of the namespaces and each fallback kind (WORLD.OPEN). A parent that `check` does not know has one of these kinds.
|
|
8510
|
+
*/
|
|
8511
|
+
function downNames(world) {
|
|
8512
|
+
const known = DOWNS.get(world);
|
|
8513
|
+
if (known !== void 0) return known;
|
|
8514
|
+
const out = /* @__PURE__ */ new Set();
|
|
8515
|
+
const done = /* @__PURE__ */ new Set();
|
|
8516
|
+
const add = (kind) => {
|
|
8517
|
+
for (const name of Object.keys(kind?.tree?.down ?? {})) out.add(name);
|
|
8518
|
+
};
|
|
8519
|
+
const visit = (entries, fallback) => {
|
|
8520
|
+
if (done.has(entries)) return;
|
|
8521
|
+
done.add(entries);
|
|
8522
|
+
add(fallback);
|
|
8523
|
+
for (const entry of Object.values(entries)) if (isKind(entry)) add(entry);
|
|
8524
|
+
else if (isNamespace(entry)) visit(entry.kinds, entry.fallback);
|
|
8525
|
+
};
|
|
8526
|
+
visit(world.kinds, world.fallback);
|
|
8527
|
+
DOWNS.set(world, out);
|
|
8528
|
+
return out;
|
|
8529
|
+
}
|
|
8530
|
+
/** This function tells if a table of a tree contract has its own entry for a name. */
|
|
8531
|
+
function hasEntry(table, name) {
|
|
8532
|
+
return table !== void 0 && Object.hasOwn(table, name);
|
|
8533
|
+
}
|
|
8534
|
+
/**
|
|
8535
|
+
* This function tells if a rung of the ladder can answer an attribute of a node (TREE.LADDER). An unknown parent can
|
|
8536
|
+
* be a node of each kind of the world, thus each name of `down` in the world can answer. A copy can always answer,
|
|
8537
|
+
* because `check` does not know a node without a parent.
|
|
8538
|
+
*/
|
|
8539
|
+
function answered(world, node, name) {
|
|
8540
|
+
if (node.own?.has(name) === true) return true;
|
|
8541
|
+
const tree = node.kind.tree;
|
|
8542
|
+
if (hasEntry(tree?.equations, name)) return true;
|
|
8543
|
+
if (node.parent === void 0 ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
|
|
8544
|
+
const declared = tree?.attributes !== void 0 && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : void 0;
|
|
8545
|
+
return declared !== void 0 && (declared.copy === true || declared.fold !== void 0 || declared.default !== void 0);
|
|
8546
|
+
}
|
|
8547
|
+
/**
|
|
8548
|
+
* This function checks the equations of the tree contract of a kind, each with the names of its rung (TREE.DOWN-NAMES).
|
|
8549
|
+
* An equation evaluates without capabilities, thus a check with capabilities gives it none. Each issue gets the place
|
|
8550
|
+
* of the document of the kind, and its reason names the equation. The function also records the needs of the kind:
|
|
8551
|
+
* each declared attribute, and each attribute of the node that an equation reads (TREE.CHECK-COMPLETE).
|
|
8552
|
+
*/
|
|
8553
|
+
function equationIssues(machine, kind, kindText, options, state) {
|
|
8554
|
+
const of = equationType(kind);
|
|
8555
|
+
const out = [];
|
|
8556
|
+
const needs = [];
|
|
8557
|
+
state.needs.set(kind, needs);
|
|
8558
|
+
for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({
|
|
8559
|
+
name,
|
|
8560
|
+
why: "the kind declares it",
|
|
8561
|
+
hidden: void 0
|
|
8562
|
+
});
|
|
8563
|
+
const self = {
|
|
8564
|
+
kind,
|
|
8565
|
+
text: kindText,
|
|
8566
|
+
own: void 0,
|
|
8567
|
+
parent: void 0
|
|
8568
|
+
};
|
|
8569
|
+
for (const { label, term, names, attribute, down } of equationsOf(kind)) {
|
|
8570
|
+
const inner = {
|
|
8571
|
+
names,
|
|
8572
|
+
...of === void 0 ? {} : { declaredType: of },
|
|
8573
|
+
...options.caps === void 0 ? {} : { caps: [] }
|
|
8574
|
+
};
|
|
8575
|
+
const read = (name) => {
|
|
8576
|
+
needs.push({
|
|
8577
|
+
name,
|
|
8578
|
+
why: `the equation ${label} reads it`,
|
|
8579
|
+
hidden: down ? void 0 : attribute
|
|
8580
|
+
});
|
|
8581
|
+
};
|
|
8582
|
+
for (const issue of checkAll(machine, term, inner, void 0, state, {
|
|
8583
|
+
node: self,
|
|
8584
|
+
read
|
|
8585
|
+
})) out.push({
|
|
8586
|
+
code: issue.code,
|
|
8587
|
+
place: [],
|
|
8588
|
+
reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}`
|
|
8589
|
+
});
|
|
8590
|
+
}
|
|
8591
|
+
return out;
|
|
8592
|
+
}
|
|
8593
|
+
/**
|
|
8594
|
+
* This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
|
|
8595
|
+
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes.
|
|
8596
|
+
*/
|
|
8597
|
+
function contractIssues(kind, kindText) {
|
|
8598
|
+
const out = [];
|
|
8599
|
+
const tree = kind.tree;
|
|
8600
|
+
for (const table of [
|
|
8601
|
+
"equations",
|
|
8602
|
+
"down",
|
|
8603
|
+
"attributes"
|
|
8604
|
+
]) for (const name of Object.keys(tree?.[table] ?? {})) {
|
|
8605
|
+
const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : void 0;
|
|
8606
|
+
if (why !== void 0) out.push({
|
|
8607
|
+
code: "#NAME?",
|
|
8608
|
+
place: [],
|
|
8609
|
+
reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.`
|
|
8610
|
+
});
|
|
8611
|
+
}
|
|
8612
|
+
if (kind.side === "run") out.push({
|
|
8613
|
+
code: "#CALC!",
|
|
8614
|
+
place: [],
|
|
8615
|
+
reason: `The tree kind ${kindText} is on the run side: a node evaluates its attributes on demand, thus in no fixed order.`
|
|
8616
|
+
});
|
|
8617
|
+
return out;
|
|
8618
|
+
}
|
|
8619
|
+
/**
|
|
8620
|
+
* This function finds the issues of a node of a document (D106). An own part with the name of a structural step is
|
|
8621
|
+
* not read at any time (TREE.CHECK-RESERVED). Each need of the kind gets an answer from a rung (TREE.CHECK-COMPLETE).
|
|
8622
|
+
*/
|
|
8623
|
+
function nodeIssues(machine, node, state, v) {
|
|
8624
|
+
const out = [];
|
|
8625
|
+
const at = (key) => [
|
|
8626
|
+
...v.at,
|
|
8627
|
+
...v.declared ? [] : ["value"],
|
|
8628
|
+
key
|
|
8629
|
+
];
|
|
8630
|
+
for (const name of node.own ?? []) {
|
|
8631
|
+
const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : void 0;
|
|
8632
|
+
if (why !== void 0) out.push({
|
|
8633
|
+
code: "#NAME?",
|
|
8634
|
+
place: v.place,
|
|
8635
|
+
reason: `The part ${name} of the kind ${node.text} has a reserved name: ${why}.`,
|
|
8636
|
+
at: at(name)
|
|
8637
|
+
});
|
|
8638
|
+
}
|
|
8639
|
+
const done = /* @__PURE__ */ new Set();
|
|
8640
|
+
for (const need of state.needs.get(node.kind) ?? []) {
|
|
8641
|
+
if (done.has(need.name) || need.hidden !== void 0 && node.own?.has(need.hidden) === true) continue;
|
|
8642
|
+
done.add(need.name);
|
|
8643
|
+
if (answered(machine.world, node, need.name)) continue;
|
|
8644
|
+
out.push({
|
|
8645
|
+
code: "#N/A",
|
|
8646
|
+
place: v.place,
|
|
8647
|
+
reason: `No rung gives the attribute ${need.name} of the kind ${node.text}, and ${need.why}.`,
|
|
8648
|
+
at: v.at
|
|
8649
|
+
});
|
|
8650
|
+
}
|
|
8651
|
+
return out;
|
|
8652
|
+
}
|
|
8653
|
+
/**
|
|
8654
|
+
* This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
|
|
8655
|
+
* nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
|
|
8656
|
+
* function gives the index of the step and the reason. In an equation, a read of the node of the equation goes to the
|
|
8657
|
+
* needs of its kind.
|
|
8658
|
+
*/
|
|
8659
|
+
function readIssue(machine, self, path, rootSelf) {
|
|
8660
|
+
let node = self;
|
|
8661
|
+
let index = 1;
|
|
8662
|
+
while (node !== void 0 && index < path.length && path[index] === "parent") {
|
|
8663
|
+
node = node.parent;
|
|
8664
|
+
index += 1;
|
|
8665
|
+
}
|
|
8666
|
+
const step = path[index];
|
|
8667
|
+
if (node === void 0 || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return void 0;
|
|
8668
|
+
const name = String(step);
|
|
8669
|
+
if (node.own === void 0) {
|
|
8670
|
+
if (node === rootSelf?.node) rootSelf.read(name);
|
|
8671
|
+
return;
|
|
8672
|
+
}
|
|
8673
|
+
return answered(machine.world, node, name) ? void 0 : {
|
|
8674
|
+
index,
|
|
8675
|
+
reason: `No rung gives the attribute ${name} of the kind ${node.text}.`
|
|
8676
|
+
};
|
|
8677
|
+
}
|
|
8678
|
+
/**
|
|
8679
|
+
* This function finds the issues of `check`. With a list of nodes, it also records each document that it visits for
|
|
8680
|
+
* the pass of types. It marks each document with an issue. The state holds the kinds whose equations the check
|
|
8681
|
+
* walked, thus it walks the equations of each kind one time. In an equation, `rootSelf` is the node of the equation.
|
|
8682
|
+
*/
|
|
8683
|
+
function checkAll(machine, doc, options, nodes, state = {
|
|
8684
|
+
seen: /* @__PURE__ */ new Set(),
|
|
8685
|
+
needs: /* @__PURE__ */ new Map()
|
|
8686
|
+
}, rootSelf) {
|
|
8687
|
+
const seen = state.seen;
|
|
8688
|
+
const issues = [];
|
|
8689
|
+
const verb = options.verb ?? "eval";
|
|
7091
8690
|
const indices = nodes === void 0 ? void 0 : /* @__PURE__ */ new Map();
|
|
7092
8691
|
let current;
|
|
7093
8692
|
const push = (issue) => {
|
|
@@ -7101,6 +8700,9 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
|
|
|
7101
8700
|
const anyName = options.names === "any";
|
|
7102
8701
|
const roots = machine.roots ?? ROOTS;
|
|
7103
8702
|
const has = (table, name) => table !== void 0 && Object.hasOwn(table, name);
|
|
8703
|
+
const attributes = verb === "eval" && roots.includes("self");
|
|
8704
|
+
const selves = selvesOf(machine, verb, rootSelf?.node);
|
|
8705
|
+
let trees = false;
|
|
7104
8706
|
walk(machine.world, doc, (v) => {
|
|
7105
8707
|
if (nodes !== void 0 && indices !== void 0) {
|
|
7106
8708
|
current = {
|
|
@@ -7136,14 +8738,19 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
|
|
|
7136
8738
|
});
|
|
7137
8739
|
return "skip";
|
|
7138
8740
|
}
|
|
7139
|
-
|
|
8741
|
+
const node = selves.isNode(v, answer.evaluator);
|
|
8742
|
+
if (v.kind !== void 0 && (v.kind.tree !== void 0 || node) && !seen.has(v.kind)) {
|
|
7140
8743
|
seen.add(v.kind);
|
|
7141
|
-
|
|
8744
|
+
const kindText = typeName(v.doc.type);
|
|
8745
|
+
for (const issue of [...contractIssues(v.kind, kindText), ...equationIssues(machine, v.kind, kindText, options, state)]) push({
|
|
7142
8746
|
...issue,
|
|
7143
8747
|
place: v.place,
|
|
7144
8748
|
at: v.at
|
|
7145
8749
|
});
|
|
7146
8750
|
}
|
|
8751
|
+
const { self, node: here } = selves.enter(v, answer.evaluator);
|
|
8752
|
+
if (here !== void 0) trees = true;
|
|
8753
|
+
if (here?.own !== void 0) for (const issue of nodeIssues(machine, here, state, v)) push(issue);
|
|
7147
8754
|
const name = answer.evaluator;
|
|
7148
8755
|
if (current !== void 0) current.evaluator = name;
|
|
7149
8756
|
const runner = name !== void 0 && has(machine.runners, name);
|
|
@@ -7244,6 +8851,15 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
|
|
|
7244
8851
|
at: stepAt(1)
|
|
7245
8852
|
});
|
|
7246
8853
|
}
|
|
8854
|
+
if (attributes && root === "self" && v.address !== true && name === "ref.resolve") {
|
|
8855
|
+
const issue = readIssue(machine, self, path, rootSelf);
|
|
8856
|
+
if (issue !== void 0) push({
|
|
8857
|
+
code: "#N/A",
|
|
8858
|
+
place: v.place,
|
|
8859
|
+
reason: issue.reason,
|
|
8860
|
+
at: stepAt(issue.index)
|
|
8861
|
+
});
|
|
8862
|
+
}
|
|
7247
8863
|
path.forEach((step, index) => {
|
|
7248
8864
|
if (index === 0 || isKey(step)) return;
|
|
7249
8865
|
if (walkerOf(machine, step) === void 0) push({
|
|
@@ -7256,7 +8872,7 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
|
|
|
7256
8872
|
}, {
|
|
7257
8873
|
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
|
|
7258
8874
|
...Array.isArray(options.names) ? { names: options.names } : {},
|
|
7259
|
-
...options.caps === void 0 ? waits === void 0 ? {} : { caps: EACH$
|
|
8875
|
+
...options.caps === void 0 ? waits === void 0 ? {} : { caps: EACH$1 } : { caps: options.caps },
|
|
7260
8876
|
problem: (problem) => {
|
|
7261
8877
|
issues.push({
|
|
7262
8878
|
code: problem.code,
|
|
@@ -7273,237 +8889,9 @@ function checkAll(machine, doc, options, nodes, seen = /* @__PURE__ */ new Set()
|
|
|
7273
8889
|
issues.splice(drive.index, 0, drive.issue);
|
|
7274
8890
|
if (drive.node !== void 0) drive.node.marked = true;
|
|
7275
8891
|
}
|
|
8892
|
+
if (trees && rootSelf === void 0) for (const issue of cycleIssues(machine, doc, options)) issues.push(issue);
|
|
7276
8893
|
return issues;
|
|
7277
8894
|
}
|
|
7278
|
-
// src/facts.ts
|
|
7279
|
-
/** The lazy rules that cover each part: the rules of a document whose evaluator the contract does not describe. */
|
|
7280
|
-
const EACH$1 = "each";
|
|
7281
|
-
/** An empty list. One frozen list serves each empty list of the facts, thus a store of many facts keeps less (PLAN.LEAN). */
|
|
7282
|
-
const NONE = /* @__PURE__ */ Object.freeze([]);
|
|
7283
|
-
/**
|
|
7284
|
-
* This function gives a list of the facts with no free room. A list that grows by `push` keeps room for more items,
|
|
7285
|
-
* thus a copy of a short list is smaller.
|
|
7286
|
-
*/
|
|
7287
|
-
function tight(list) {
|
|
7288
|
-
return list.length === 0 ? NONE : list.slice();
|
|
7289
|
-
}
|
|
7290
|
-
/**
|
|
7291
|
-
* This function gives the name of the evaluator that the ladder finds for a verb (VERB.LADDER). It also tells if
|
|
7292
|
-
* the contract of the kind describes that code. The contract describes the code of its kind for its verb `contract`.
|
|
7293
|
-
* An own entry for another evaluator has code that the contract does not describe.
|
|
7294
|
-
*/
|
|
7295
|
-
function codeOf(world, own, kind, verb, contract) {
|
|
7296
|
-
const found = answerOf(world, own, kind, verb);
|
|
7297
|
-
if (verb !== contract) return {
|
|
7298
|
-
name: found?.name,
|
|
7299
|
-
described: false
|
|
7300
|
-
};
|
|
7301
|
-
if (found?.rung !== "own") return {
|
|
7302
|
-
name: found?.name,
|
|
7303
|
-
described: true
|
|
7304
|
-
};
|
|
7305
|
-
return {
|
|
7306
|
-
name: found.name,
|
|
7307
|
-
described: found.name === answerOf(world, void 0, kind, verb)?.name
|
|
7308
|
-
};
|
|
7309
|
-
}
|
|
7310
|
-
/** This function gives the position of a part among the parts that a rule covers, by the split of its document. */
|
|
7311
|
-
function positionOf(above, parent, rule, slot) {
|
|
7312
|
-
const positions = above.positions ??= /* @__PURE__ */ new Map();
|
|
7313
|
-
let table = positions.get(rule);
|
|
7314
|
-
if (table === void 0) {
|
|
7315
|
-
table = /* @__PURE__ */ new Map();
|
|
7316
|
-
const split = splitParts(parent.kind?.parts, parent.doc.value);
|
|
7317
|
-
let count = 0;
|
|
7318
|
-
for (const part of split.ok ? split.parts : []) {
|
|
7319
|
-
if (rule.in !== void 0 && !rule.in.some((pattern) => pathMatches(part.path, pattern))) continue;
|
|
7320
|
-
table.set(JSON.stringify(part.path), count);
|
|
7321
|
-
count += 1;
|
|
7322
|
-
}
|
|
7323
|
-
positions.set(rule, table);
|
|
7324
|
-
}
|
|
7325
|
-
return table.get(JSON.stringify(slot)) ?? 0;
|
|
7326
|
-
}
|
|
7327
|
-
/** This function tells if the lazy rules of a document cover one of its parts. */
|
|
7328
|
-
function partLazy(above, parent, slot) {
|
|
7329
|
-
const rules = above.rules;
|
|
7330
|
-
if (rules === void 0) return false;
|
|
7331
|
-
if (rules === EACH$1) return true;
|
|
7332
|
-
for (const rule of rules) {
|
|
7333
|
-
if (rule.in !== void 0 && !rule.in.some((pattern) => pathMatches(slot, pattern))) continue;
|
|
7334
|
-
const after = rule.after ?? 0;
|
|
7335
|
-
if (after <= 0 || positionOf(above, parent, rule, slot) >= after) return true;
|
|
7336
|
-
}
|
|
7337
|
-
return false;
|
|
7338
|
-
}
|
|
7339
|
-
/**
|
|
7340
|
-
* This function gives the static facts of a document, for a schedule (R4 P2, D24). It walks the document one time,
|
|
7341
|
-
* by the contracts of its kinds:
|
|
7342
|
-
*
|
|
7343
|
-
* - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
|
|
7344
|
-
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`. A kind that
|
|
7345
|
-
* declares `reads` adds a read of each of its roots at each document of the kind. The first document of a tree
|
|
7346
|
-
* kind also gets the lazy reads of the equations of its kind (FACTS.EQUATIONS). A read from `self` or from a name
|
|
7347
|
-
* of a rung is not one of them, because it is not free (READS.EQUATION).
|
|
7348
|
-
* - `side` and `effects`: from the contract of each kind.
|
|
7349
|
-
* - `pure`: each kind declares `pure`, also in an equation, and the document has no effect and is not volatile.
|
|
7350
|
-
* - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
|
|
7351
|
-
*
|
|
7352
|
-
* The hook `facts` of the machine answers for a name that a fallback kind covers, for example an operation. Its
|
|
7353
|
-
* answer wins over the contract of the kind. The contract of a kind does not describe the evaluator of an own
|
|
7354
|
-
* entry. Thus a document with an own entry for another evaluator is not pure, and each of its parts is lazy. The
|
|
7355
|
-
* hook can still answer for that evaluator.
|
|
7356
|
-
*
|
|
7357
|
-
* The option `verb` names the verb whose code the facts describe (FACTS.VERB). A contract describes the code of
|
|
7358
|
-
* `eval`, thus the rule of an own entry applies to each document for another verb. The side and the effects come
|
|
7359
|
-
* from the contracts for each verb.
|
|
7360
|
-
*
|
|
7361
|
-
* A read is one resolution of its path. A reference that the resolution reaches and follows is a part of that read
|
|
7362
|
-
* (DEREF.COLLAPSE), not a separate read. The facts depend only on the document and the machine. Thus a host computes
|
|
7363
|
-
* them again only after an edit of the document or of the machine.
|
|
7364
|
-
*
|
|
7365
|
-
* @param machine - The world, the code and the hook `facts`.
|
|
7366
|
-
* @param doc - The document, as JSON.
|
|
7367
|
-
* @param options - The type of a bare root value, the verb and the volatile reads of the host.
|
|
7368
|
-
* @returns The facts.
|
|
7369
|
-
*/
|
|
7370
|
-
function factsOf(machine, doc, options = {}) {
|
|
7371
|
-
const world = machine.world;
|
|
7372
|
-
const hook = machine.facts;
|
|
7373
|
-
const reads = [];
|
|
7374
|
-
const effects = [];
|
|
7375
|
-
const above = /* @__PURE__ */ new Map();
|
|
7376
|
-
let side = "derive";
|
|
7377
|
-
let pure = true;
|
|
7378
|
-
let volatile = false;
|
|
7379
|
-
let size = 0;
|
|
7380
|
-
let seen;
|
|
7381
|
-
const note = (isPure, isVolatile) => {
|
|
7382
|
-
if (isPure !== true) pure = false;
|
|
7383
|
-
if (isVolatile === true) volatile = true;
|
|
7384
|
-
};
|
|
7385
|
-
const add = (read) => {
|
|
7386
|
-
reads.push(read);
|
|
7387
|
-
if (options.volatile?.(read) === true) volatile = true;
|
|
7388
|
-
};
|
|
7389
|
-
/**
|
|
7390
|
-
* This function walks a document. Without `node`, it walks the document of the facts. With `node`, it walks an
|
|
7391
|
-
* equation of a tree kind, and each read gets the place of that node: the first document of the kind.
|
|
7392
|
-
*/
|
|
7393
|
-
const scan = (term, walkOptions, verb, node) => {
|
|
7394
|
-
walk(world, term, (v) => {
|
|
7395
|
-
if (node === void 0) size += 1;
|
|
7396
|
-
const up = v.parent === void 0 ? void 0 : above.get(v.parent);
|
|
7397
|
-
const lazy = node !== void 0 || up !== void 0 && v.parent !== void 0 && (up.lazy || partLazy(up, v.parent, v.slot));
|
|
7398
|
-
const place = node ?? v;
|
|
7399
|
-
const kind = v.kind;
|
|
7400
|
-
if (kind === void 0) {
|
|
7401
|
-
pure = false;
|
|
7402
|
-
above.set(v, {
|
|
7403
|
-
lazy,
|
|
7404
|
-
rules: void 0
|
|
7405
|
-
});
|
|
7406
|
-
return;
|
|
7407
|
-
}
|
|
7408
|
-
const code = codeOf(world, v.declared ? void 0 : v.doc.own, kind, verb, "eval");
|
|
7409
|
-
const answer = code.name === void 0 ? void 0 : hook?.(v.typePath ?? [], code.name);
|
|
7410
|
-
let roots;
|
|
7411
|
-
if (code.described) {
|
|
7412
|
-
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
7413
|
-
above.set(v, {
|
|
7414
|
-
lazy,
|
|
7415
|
-
rules: answer?.lazy ?? kind.lazy
|
|
7416
|
-
});
|
|
7417
|
-
roots = answer?.reads ?? kind.reads;
|
|
7418
|
-
} else {
|
|
7419
|
-
note(answer?.pure, answer?.volatile);
|
|
7420
|
-
above.set(v, {
|
|
7421
|
-
lazy,
|
|
7422
|
-
rules: answer?.lazy ?? EACH$1
|
|
7423
|
-
});
|
|
7424
|
-
roots = answer?.reads;
|
|
7425
|
-
}
|
|
7426
|
-
for (const root of roots ?? NONE) {
|
|
7427
|
-
if (node !== void 0 && root === "self") continue;
|
|
7428
|
-
add({
|
|
7429
|
-
root,
|
|
7430
|
-
keys: NONE,
|
|
7431
|
-
exact: true,
|
|
7432
|
-
lazy,
|
|
7433
|
-
address: false,
|
|
7434
|
-
path: [root],
|
|
7435
|
-
place: tight(place.place),
|
|
7436
|
-
at: tight(place.at)
|
|
7437
|
-
});
|
|
7438
|
-
}
|
|
7439
|
-
if (kind.side === "run") {
|
|
7440
|
-
if (node === void 0) side = "run";
|
|
7441
|
-
else pure = false;
|
|
7442
|
-
}
|
|
7443
|
-
for (const keys of declaredEffects(v)) {
|
|
7444
|
-
if (node !== void 0) {
|
|
7445
|
-
pure = false;
|
|
7446
|
-
continue;
|
|
7447
|
-
}
|
|
7448
|
-
const text = keys.join(".");
|
|
7449
|
-
if (!effects.includes(text)) effects.push(text);
|
|
7450
|
-
}
|
|
7451
|
-
if (kind.tree !== void 0 && !(seen ??= /* @__PURE__ */ new Set()).has(kind)) {
|
|
7452
|
-
seen.add(kind);
|
|
7453
|
-
const of = equationType(kind);
|
|
7454
|
-
for (const { term: equation, names } of equationsOf(kind)) scan(equation, of === void 0 ? { names } : {
|
|
7455
|
-
names,
|
|
7456
|
-
declaredType: of
|
|
7457
|
-
}, "eval", place);
|
|
7458
|
-
}
|
|
7459
|
-
if (!isRef(v.doc)) return void 0;
|
|
7460
|
-
const path = v.doc.value;
|
|
7461
|
-
const root = path[0];
|
|
7462
|
-
if (typeof root !== "string" || !ROOTS.includes(root)) return void 0;
|
|
7463
|
-
if (node !== void 0 && root === "self") return void 0;
|
|
7464
|
-
let exact = true;
|
|
7465
|
-
const keys = [];
|
|
7466
|
-
for (let index = 1; index < path.length; index += 1) {
|
|
7467
|
-
const step = path[index];
|
|
7468
|
-
if (exact && isKey(step)) {
|
|
7469
|
-
keys.push(step);
|
|
7470
|
-
continue;
|
|
7471
|
-
}
|
|
7472
|
-
exact = false;
|
|
7473
|
-
const found = isEnvelope(step) ? lookupKind(world, step.type) : void 0;
|
|
7474
|
-
if (found?.ok !== true) {
|
|
7475
|
-
pure = false;
|
|
7476
|
-
continue;
|
|
7477
|
-
}
|
|
7478
|
-
const walker = codeOf(world, step.own, found.kind, "step", "step");
|
|
7479
|
-
const stepAnswer = walker.name === void 0 ? void 0 : hook?.(found.path, walker.name);
|
|
7480
|
-
if (walker.described) note(stepAnswer?.pure ?? found.kind.pure, stepAnswer?.volatile ?? found.kind.volatile);
|
|
7481
|
-
else note(stepAnswer?.pure, stepAnswer?.volatile);
|
|
7482
|
-
}
|
|
7483
|
-
const first = path[1];
|
|
7484
|
-
if (root === "names" && isKey(first) && v.names.has(String(first))) return void 0;
|
|
7485
|
-
add({
|
|
7486
|
-
root,
|
|
7487
|
-
keys: tight(keys),
|
|
7488
|
-
exact,
|
|
7489
|
-
lazy,
|
|
7490
|
-
address: v.address === true,
|
|
7491
|
-
path,
|
|
7492
|
-
place: tight(place.place),
|
|
7493
|
-
at: tight(place.at)
|
|
7494
|
-
});
|
|
7495
|
-
}, walkOptions);
|
|
7496
|
-
};
|
|
7497
|
-
scan(doc, options.declaredType === void 0 ? {} : { declaredType: options.declaredType }, options.verb ?? "eval", void 0);
|
|
7498
|
-
return {
|
|
7499
|
-
reads: tight(reads),
|
|
7500
|
-
side,
|
|
7501
|
-
effects: tight(effects),
|
|
7502
|
-
pure: pure && !volatile && effects.length === 0 && side === "derive",
|
|
7503
|
-
volatile,
|
|
7504
|
-
size
|
|
7505
|
-
};
|
|
7506
|
-
}
|
|
7507
8895
|
// src/effects.ts
|
|
7508
8896
|
/** Each capability: one capability with no keys. It permits each effect, because each kind starts with no keys (CAP.KEYS). */
|
|
7509
8897
|
const EACH = /* @__PURE__ */ Object.freeze([[]]);
|
|
@@ -7721,7 +9109,8 @@ function encodeTerm(value, encoder) {
|
|
|
7721
9109
|
*
|
|
7722
9110
|
* - A value that JSON writes exactly stays as it is. A literal with the shape of an envelope goes into `lit`.
|
|
7723
9111
|
* - `NaN`, an infinity and `-0` become `ext.number`, and `undefined` becomes `ext.undefined`.
|
|
7724
|
-
* - A member of a domain with a codec becomes `ext.domain`. The
|
|
9112
|
+
* - A member of a domain with a codec becomes `ext.domain`. The codec of a domain under another domain comes first
|
|
9113
|
+
* (EXT.SPECIFIC, D109). The order of the names decides between unrelated domains.
|
|
7725
9114
|
* - A list or a plain record with such a value becomes the construct `list` or `record`.
|
|
7726
9115
|
*
|
|
7727
9116
|
* @param machine - The machine, or only its domains.
|
|
@@ -7731,20 +9120,38 @@ function encodeTerm(value, encoder) {
|
|
|
7731
9120
|
*/
|
|
7732
9121
|
function encodeValue(machine, value) {
|
|
7733
9122
|
const domains = machine.domains;
|
|
7734
|
-
const codecs = [];
|
|
7735
|
-
if (domains !== void 0) for (const name of Object.keys(domains).sort()) {
|
|
7736
|
-
const domain = domainOf(name, domains);
|
|
7737
|
-
const codec = domain === void 0 ? void 0 : codecOf(domain);
|
|
7738
|
-
if (codec !== void 0) codecs.push([name, codec]);
|
|
7739
|
-
}
|
|
7740
9123
|
return encodeTerm(value, {
|
|
7741
9124
|
domains,
|
|
7742
|
-
codecs,
|
|
9125
|
+
codecs: codecOrder(domains),
|
|
7743
9126
|
json: /* @__PURE__ */ new WeakMap(),
|
|
7744
9127
|
open: /* @__PURE__ */ new Set()
|
|
7745
9128
|
});
|
|
7746
9129
|
}
|
|
7747
9130
|
/**
|
|
9131
|
+
* This function gives the domains with a codec in the order of `encodeValue` (EXT.SPECIFIC, D109). The depth of a
|
|
9132
|
+
* domain is the count of the other domains with a codec in its line. A deeper domain comes first, and the order of the
|
|
9133
|
+
* names decides between domains of the same depth (EXT.TIE). Thus a domain comes before each domain with a codec above it.
|
|
9134
|
+
*/
|
|
9135
|
+
function codecOrder(domains) {
|
|
9136
|
+
const codecs = [];
|
|
9137
|
+
if (domains === void 0) return codecs;
|
|
9138
|
+
let parents = false;
|
|
9139
|
+
for (const name of Object.keys(domains).sort()) {
|
|
9140
|
+
const domain = domainOf(name, domains);
|
|
9141
|
+
const codec = domain === void 0 ? void 0 : codecOf(domain);
|
|
9142
|
+
if (domain === void 0 || codec === void 0) continue;
|
|
9143
|
+
codecs.push([name, codec]);
|
|
9144
|
+
if (Array.isArray(domain.under)) parents = true;
|
|
9145
|
+
}
|
|
9146
|
+
if (!parents) return codecs;
|
|
9147
|
+
const depth = /* @__PURE__ */ new Map();
|
|
9148
|
+
for (const [name] of codecs) {
|
|
9149
|
+
const line = lineOf(name, domains);
|
|
9150
|
+
depth.set(name, codecs.filter(([other]) => other !== name && line.has(other)).length);
|
|
9151
|
+
}
|
|
9152
|
+
return codecs.sort(([a], [b]) => depth.get(b) - depth.get(a));
|
|
9153
|
+
}
|
|
9154
|
+
/**
|
|
7748
9155
|
* This function reads a document of `encodeValue` again: it evaluates the document as a term, in an empty context.
|
|
7749
9156
|
* A bare root value is a literal.
|
|
7750
9157
|
*
|
|
@@ -7981,6 +9388,11 @@ function valueType(value) {
|
|
|
7981
9388
|
function isSignature(value) {
|
|
7982
9389
|
return typeof value === "object" && value !== null && Array.isArray(value.params) && Object.hasOwn(value, "to");
|
|
7983
9390
|
}
|
|
9391
|
+
/** This function tells if two signatures have the same parameters and the same rest parameter. */
|
|
9392
|
+
function sameParams(a, b) {
|
|
9393
|
+
if (!docEquals(a.params, b.params)) return false;
|
|
9394
|
+
return a.rest === void 0 ? b.rest === void 0 : b.rest !== void 0 && docEquals(a.rest, b.rest);
|
|
9395
|
+
}
|
|
7984
9396
|
/** This function gives the text of a signature, for the reason of an issue. */
|
|
7985
9397
|
function signatureText(sig) {
|
|
7986
9398
|
const params = sig.params.map(typeText);
|
|
@@ -8071,6 +9483,58 @@ function formulaTypes(profile) {
|
|
|
8071
9483
|
bad: sigs.length !== raw.length
|
|
8072
9484
|
};
|
|
8073
9485
|
};
|
|
9486
|
+
/**
|
|
9487
|
+
* The declarations of the signatures, for `checkWorld` (TYPE.WORLD, D110). Only a record of signatures lists them.
|
|
9488
|
+
* An overload with the parameters of an earlier overload is a problem: the earlier one always fits first.
|
|
9489
|
+
*/
|
|
9490
|
+
const sigsDeclared = () => {
|
|
9491
|
+
const sigs = profile.sigs;
|
|
9492
|
+
if (sigs === void 0 || typeof sigs === "function") return [];
|
|
9493
|
+
const out = [];
|
|
9494
|
+
for (const name of Object.keys(sigs)) {
|
|
9495
|
+
const raw = sigs[name];
|
|
9496
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
9497
|
+
list.forEach((sig, index) => {
|
|
9498
|
+
const at = Array.isArray(raw) ? [
|
|
9499
|
+
"sigs",
|
|
9500
|
+
name,
|
|
9501
|
+
index
|
|
9502
|
+
] : ["sigs", name];
|
|
9503
|
+
if (!isSignature(sig)) {
|
|
9504
|
+
out.push({
|
|
9505
|
+
at,
|
|
9506
|
+
problem: `A signature of ops.${name} is not a record with params and to. The check does not read it.`
|
|
9507
|
+
});
|
|
9508
|
+
return;
|
|
9509
|
+
}
|
|
9510
|
+
sig.params.forEach((param, position) => out.push({
|
|
9511
|
+
at: [
|
|
9512
|
+
...at,
|
|
9513
|
+
"params",
|
|
9514
|
+
position
|
|
9515
|
+
],
|
|
9516
|
+
type: param,
|
|
9517
|
+
what: `parameter ${position + 1} of a signature of ops.${name}`
|
|
9518
|
+
}));
|
|
9519
|
+
if (sig.rest !== void 0) out.push({
|
|
9520
|
+
at: [...at, "rest"],
|
|
9521
|
+
type: sig.rest,
|
|
9522
|
+
what: `rest parameter of a signature of ops.${name}`
|
|
9523
|
+
});
|
|
9524
|
+
out.push({
|
|
9525
|
+
at: [...at, "to"],
|
|
9526
|
+
type: sig.to,
|
|
9527
|
+
what: `result of a signature of ops.${name}`
|
|
9528
|
+
});
|
|
9529
|
+
const earlier = list.slice(0, index).findIndex((other) => isSignature(other) && sameParams(other, sig));
|
|
9530
|
+
if (earlier >= 0) out.push({
|
|
9531
|
+
at,
|
|
9532
|
+
problem: `The overload ${index + 1} of ops.${name} has the parameters of the overload ${earlier + 1}. Thus the check never selects it.`
|
|
9533
|
+
});
|
|
9534
|
+
});
|
|
9535
|
+
}
|
|
9536
|
+
return out;
|
|
9537
|
+
};
|
|
8074
9538
|
const logic = (word) => ({
|
|
8075
9539
|
part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
|
|
8076
9540
|
synth: (input) => {
|
|
@@ -8215,6 +9679,11 @@ function formulaTypes(profile) {
|
|
|
8215
9679
|
return want.to;
|
|
8216
9680
|
} },
|
|
8217
9681
|
"formula.if": {
|
|
9682
|
+
declared: () => declared ? [{
|
|
9683
|
+
at: ["truthType"],
|
|
9684
|
+
type: truth,
|
|
9685
|
+
what: "truth type of the profile"
|
|
9686
|
+
}] : [],
|
|
8218
9687
|
part: (input, path) => {
|
|
8219
9688
|
if (path[0] === "test") return truthPart(input, "test of if");
|
|
8220
9689
|
return isUnknown(input.expected, input.machine.domains) ? {} : {
|
|
@@ -8231,6 +9700,7 @@ function formulaTypes(profile) {
|
|
|
8231
9700
|
"formula.and": logic("and"),
|
|
8232
9701
|
"formula.or": logic("or"),
|
|
8233
9702
|
"formula.op": {
|
|
9703
|
+
declared: sigsDeclared,
|
|
8234
9704
|
part: (input, path) => {
|
|
8235
9705
|
const read = signatures(input);
|
|
8236
9706
|
if (read === void 0 || read.sigs.length !== 1) return {};
|
|
@@ -8268,19 +9738,31 @@ function formulaTypes(profile) {
|
|
|
8268
9738
|
return fitting.every((sig) => docEquals(sig.to, first.to)) ? first.to : "unknown";
|
|
8269
9739
|
}
|
|
8270
9740
|
},
|
|
8271
|
-
"formula.ext": {
|
|
8272
|
-
|
|
8273
|
-
if (profile.ext !== void 0 && Object.hasOwn(profile.ext, name)) {
|
|
9741
|
+
"formula.ext": {
|
|
9742
|
+
declared: () => {
|
|
8274
9743
|
const types = profile.extTypes;
|
|
8275
|
-
const
|
|
8276
|
-
if (
|
|
8277
|
-
|
|
8278
|
-
|
|
9744
|
+
const own = profile.ext;
|
|
9745
|
+
if (types === void 0 || own === void 0) return [];
|
|
9746
|
+
return Object.keys(types).filter((name) => Object.hasOwn(own, name)).map((name) => ({
|
|
9747
|
+
at: ["extTypes", name],
|
|
9748
|
+
type: types[name],
|
|
9749
|
+
what: `type of ext.${name} in extTypes`
|
|
9750
|
+
}));
|
|
9751
|
+
},
|
|
9752
|
+
synth: (input) => {
|
|
9753
|
+
const name = nameOf(input);
|
|
9754
|
+
if (profile.ext !== void 0 && Object.hasOwn(profile.ext, name)) {
|
|
9755
|
+
const types = profile.extTypes;
|
|
9756
|
+
const declared = types !== void 0 && Object.hasOwn(types, name) ? types[name] : void 0;
|
|
9757
|
+
if (declared === void 0) return "unknown";
|
|
9758
|
+
const type = input.contract(declared, `type of ext.${name} in extTypes`);
|
|
9759
|
+
return typeIssues(type, input.machine.domains).length > 0 ? "unknown" : type;
|
|
9760
|
+
}
|
|
9761
|
+
if (name === "undefined") return "undefined";
|
|
9762
|
+
if (name === "number") return "number";
|
|
9763
|
+
return name === "domain" ? domainTypeOf(input.doc.value, input.machine.domains) : "unknown";
|
|
8279
9764
|
}
|
|
8280
|
-
|
|
8281
|
-
if (name === "number") return "number";
|
|
8282
|
-
return name === "domain" ? domainTypeOf(input.doc.value, input.machine.domains) : "unknown";
|
|
8283
|
-
} }
|
|
9765
|
+
}
|
|
8284
9766
|
};
|
|
8285
9767
|
}
|
|
8286
9768
|
/**
|
|
@@ -13803,707 +15285,532 @@ function checkTruth(env, code, probe) {
|
|
|
13803
15285
|
const truth = declaredTruthOf(code);
|
|
13804
15286
|
if (truth === void 0) return;
|
|
13805
15287
|
const domains = env.machine.domains;
|
|
13806
|
-
if (typeIssues(truth.truthType, domains).length > 0) return;
|
|
13807
|
-
const samples = env.results.flatMap((result) => result.ok ? [result.value] : []);
|
|
13808
|
-
const of = (value) => `the member ${valueText(value)} of the truth type ${typeText(truth.truthType)}`;
|
|
13809
|
-
for (const value of membersOf(truth.truthType, domains, samples)) {
|
|
13810
|
-
let given;
|
|
13811
|
-
try {
|
|
13812
|
-
given = truth.truthy(value);
|
|
13813
|
-
} catch (error) {
|
|
13814
|
-
report(env, "truth", `The function truthy of the profile throws for ${of(value)}: ${describe(error)}`, () => probe(value));
|
|
13815
|
-
return;
|
|
13816
|
-
}
|
|
13817
|
-
if (typeof given === "boolean") continue;
|
|
13818
|
-
report(env, "truth", `The function truthy of the profile gives ${given === void 0 ? "no truth" : valueText(given)} for ${of(value)}. A truth is true or false.`, () => probe(value));
|
|
13819
|
-
return;
|
|
13820
|
-
}
|
|
13821
|
-
}
|
|
13822
|
-
/**
|
|
13823
|
-
* This function checks the covers of the domains of the machine from samples (D94). Each sample of a covered domain
|
|
13824
|
-
* must be in one domain of each valid cover. The samples of a domain come from its list `values`. A domain without
|
|
13825
|
-
* that list gets its members from the palette and from the samples of the domains, as for the check `truth`.
|
|
13826
|
-
*/
|
|
13827
|
-
function checkCovers(env, probe) {
|
|
13828
|
-
const domains = env.machine.domains;
|
|
13829
|
-
if (domains === void 0) return;
|
|
13830
|
-
const palette = env.results.flatMap((result) => result.ok ? [result.value] : []);
|
|
13831
|
-
const samples = {};
|
|
13832
|
-
for (const [name, domain] of Object.entries(domains)) if (domain.covers !== void 0) samples[name] = membersOf(name, domains, palette);
|
|
13833
|
-
const found = falseCovers(domains, samples)[0];
|
|
13834
|
-
if (found === void 0) return;
|
|
13835
|
-
report(env, "cover", `The member ${valueText(found.value)} of the domain ${found.domain} is in no domain of its cover ${found.cover.join(", ")}. The check of types trusts the cover, thus it is unsound for ${found.domain}.`, () => probe(found.value));
|
|
13836
|
-
}
|
|
13837
|
-
/**
|
|
13838
|
-
* This function examines one evaluator or runner of a machine against the obligations of host code (K92, D36). It
|
|
13839
|
-
* starts the code directly, without the kernel, with probe parts and a probe runtime. It generates probe documents from
|
|
13840
|
-
* the contract of each kind that names the code, and it gives the probe parts chosen and random results.
|
|
13841
|
-
*
|
|
13842
|
-
* The checks follow the obligations C1 to C8 of `docs/SEMANTICS.md`. Two more checks follow the premises of the
|
|
13843
|
-
* simulation of the plan. The check `local` finds state that stays from one call to the next. The check `agree` compares
|
|
13844
|
-
* compiled code with its definition. The finding of an obligation names the first probe that breaks it. An empty list
|
|
13845
|
-
* means that each probe conforms.
|
|
13846
|
-
*
|
|
13847
|
-
* The check `truth` tests the declared truth of a formula profile (D86). The check `cover` tests the covers of the
|
|
13848
|
-
* domains of the machine from samples (D94).
|
|
13849
|
-
*
|
|
13850
|
-
* @param machine - The machine with the code and the world of the kinds.
|
|
13851
|
-
* @param name - The name of the evaluator or of the runner. A runner wins over an evaluator of the same name.
|
|
13852
|
-
* @param options - The verb, the types and the values of the probe documents, and the results of the probe parts. Also
|
|
13853
|
-
* the definitions, the capabilities, the names, the seed, the mock and the rule of equality.
|
|
13854
|
-
* @returns The first finding of each obligation that the code breaks, in the order of the obligations.
|
|
13855
|
-
* @throws {Error} The machine has no code with the name, or no kind names it and the options give no types.
|
|
13856
|
-
*/
|
|
13857
|
-
function conforms(machine, name, options = {}) {
|
|
13858
|
-
const verb = options.verb ?? "eval";
|
|
13859
|
-
const runnerCode = own(machine.runners, name);
|
|
13860
|
-
const code = runnerCode ?? own(machine.evaluators, name);
|
|
13861
|
-
if (code === void 0) throw new Error(`The machine has no evaluator and no runner with the name ${name}.`);
|
|
13862
|
-
const runner = runnerCode !== void 0;
|
|
13863
|
-
const env = {
|
|
13864
|
-
machine,
|
|
13865
|
-
name,
|
|
13866
|
-
verb,
|
|
13867
|
-
under: underOf(code, runner),
|
|
13868
|
-
definition: definitionOf(machine, name, runner, options.definitions),
|
|
13869
|
-
runner,
|
|
13870
|
-
results: options.results ?? RESULTS,
|
|
13871
|
-
names: options.names ?? NAMES,
|
|
13872
|
-
runs: options.runs ?? 12,
|
|
13873
|
-
random: random(options.seed ?? 1),
|
|
13874
|
-
perform: options.perform,
|
|
13875
|
-
equal: options.equal,
|
|
13876
|
-
found: /* @__PURE__ */ new Map()
|
|
13877
|
-
};
|
|
13878
|
-
const types = options.types ?? typesNaming(machine.world, name, verb);
|
|
13879
|
-
let first;
|
|
13880
|
-
if (types.length === 0) throw new Error(`No kind of the world names ${name} for the verb ${verb}. Give the types of the probe documents in the option types.`);
|
|
13881
|
-
for (const type of types) {
|
|
13882
|
-
const found = lookupKind(machine.world, type);
|
|
13883
|
-
if (!found.ok) throw new Error(`The world has no kind for the type ${typeName(type)}.`);
|
|
13884
|
-
const contract = contractOf(env, found.kind, found.path, options.caps);
|
|
13885
|
-
const extra = own(options.values, typeName(type)) ?? own(options.values, found.path.join(".")) ?? [];
|
|
13886
|
-
for (const sample of samplesOf(type, found.path, found.kind, contract, extra)) {
|
|
13887
|
-
first ??= sample;
|
|
13888
|
-
examine(env, sample, contract);
|
|
13889
|
-
}
|
|
13890
|
-
}
|
|
13891
|
-
const probeWith = (value) => first === void 0 ? {
|
|
13892
|
-
type: typeName(types[0]),
|
|
13893
|
-
doc: {
|
|
13894
|
-
type: types[0],
|
|
13895
|
-
value: null
|
|
13896
|
-
},
|
|
13897
|
-
results: {},
|
|
13898
|
-
waits: [],
|
|
13899
|
-
caps: NO_CAPS,
|
|
13900
|
-
running: runner
|
|
13901
|
-
} : probeOf(first, {
|
|
13902
|
-
results: first.split.parts.map(() => ok(value)),
|
|
13903
|
-
waits: [],
|
|
13904
|
-
caps: NO_CAPS,
|
|
13905
|
-
running: runner
|
|
13906
|
-
});
|
|
13907
|
-
checkTruth(env, code, probeWith);
|
|
13908
|
-
checkCovers(env, probeWith);
|
|
13909
|
-
return ORDER.filter((obligation) => env.found.has(obligation)).map((obligation) => env.found.get(obligation));
|
|
13910
|
-
}
|
|
13911
|
-
// src/same.ts
|
|
13912
|
-
/**
|
|
13913
|
-
* This function tells if two results are the same for an early cutoff (RESULT.SAME). A schedule uses it to stop the
|
|
13914
|
-
* work when a part that evaluates again gives the same result. A wrong `true` keeps a stale value, but a wrong `false`
|
|
13915
|
-
* only costs work. Thus each rule gives `false` when it is not sure.
|
|
13916
|
-
*
|
|
13917
|
-
* - Two values compare by the rules of `docEquals`. Lists and plain records compare by their items and fields. Each
|
|
13918
|
-
* other value compares with `Object.is`.
|
|
13919
|
-
* - Thus `-0` is not `0`, and `NaN` is `NaN`. A date, a map and a closure of `fn` are the same only as themselves.
|
|
13920
|
-
* - Two errors are the same when they have the same code, reason and place (RESULT.SAME-ERROR). The cause is not
|
|
13921
|
-
* compared. A `Held` value compares its error by the same rule.
|
|
13922
|
-
* - A value and an error are not the same.
|
|
13923
|
-
*
|
|
13924
|
-
* `sameValue` of `agree` uses the same walk, with rules for two tiers (D20). It compares two closures by their
|
|
13925
|
-
* parameters, and two errors by their code and place.
|
|
13926
|
-
*
|
|
13927
|
-
* @param a - The earlier result.
|
|
13928
|
-
* @param b - The new result.
|
|
13929
|
-
* @param equal - The rule of the host for two values, for example two dates. It answers first, and `undefined` gives the rules above.
|
|
13930
|
-
* @returns `true` when the results are the same.
|
|
13931
|
-
*/
|
|
13932
|
-
function sameResult(a, b, equal) {
|
|
13933
|
-
if (a.ok && b.ok) return equalWith(a.value, b.value, equal === void 0 ? sameHeld : (x, y) => equal(x, y) ?? sameHeld(x, y));
|
|
13934
|
-
if (!a.ok && !b.ok) return sameError(a.error, b.error);
|
|
13935
|
-
return false;
|
|
13936
|
-
}
|
|
13937
|
-
/** The rule of `sameResult` for held errors. Two `Held` values are the same when their errors are the same. */
|
|
13938
|
-
function sameHeld(x, y) {
|
|
13939
|
-
if (x instanceof Held || y instanceof Held) return x instanceof Held && y instanceof Held && sameError(x.error, y.error);
|
|
13940
|
-
}
|
|
13941
|
-
/** Two errors are the same when they have the same code, reason and place. An error without a place has no place in both. */
|
|
13942
|
-
function sameError(a, b) {
|
|
13943
|
-
if (a.code !== b.code || a.reason !== b.reason) return false;
|
|
13944
|
-
if (a.place === void 0 || b.place === void 0) return a.place === b.place;
|
|
13945
|
-
return docEquals(a.place, b.place);
|
|
13946
|
-
}
|
|
13947
|
-
// src/reads.ts
|
|
13948
|
-
/** One answer serves each list whose reads are all the same. */
|
|
13949
|
-
const SAME = /* @__PURE__ */ Object.freeze({ same: true });
|
|
13950
|
-
/** This function tells if two lists of hops are the same: the same steps, rungs and things, in order. */
|
|
13951
|
-
function sameHops(a, b, equal) {
|
|
13952
|
-
if (b === void 0 || a.length !== b.length) return false;
|
|
13953
|
-
for (let index = 0; index < a.length; index += 1) {
|
|
13954
|
-
const x = a[index];
|
|
13955
|
-
const y = b[index];
|
|
13956
|
-
if (x.by !== y.by || !docEquals(x.step, y.step) || !sameResult(ok(x.thing), ok(y.thing), equal)) return false;
|
|
13957
|
-
}
|
|
13958
|
-
return true;
|
|
13959
|
-
}
|
|
13960
|
-
/**
|
|
13961
|
-
* This function gives the error of a name that holds a held error and stops a longer path, or `undefined`. A
|
|
13962
|
-
* reference of the formula kit tests the binding of its first name before it resolves the rest of the path
|
|
13963
|
-
* (FORMULA.LET). The resolution of the whole path does not show that test.
|
|
13964
|
-
*/
|
|
13965
|
-
function heldStop(root, path) {
|
|
13966
|
-
const name = path[1];
|
|
13967
|
-
if (path.length <= 2 || path[0] !== "names" || typeof name !== "string" || !Object.hasOwn(root.names, name)) return void 0;
|
|
13968
|
-
const bound = root.names[name];
|
|
13969
|
-
return bound instanceof Held ? {
|
|
13970
|
-
ok: false,
|
|
13971
|
-
error: bound.error
|
|
13972
|
-
} : void 0;
|
|
13973
|
-
}
|
|
13974
|
-
/**
|
|
13975
|
-
* This function tells if an old result stays valid in a new context (READS.VERIFY, D26). It resolves the path of
|
|
13976
|
-
* each free read again, in the order of the list, and it compares what the path reaches with `sameResult`. It stops at
|
|
13977
|
-
* the first read that reaches another thing, and it gives that read (READS.CHANGED).
|
|
13978
|
-
*
|
|
13979
|
-
* - A read that is not free reads a binding that a kind made from earlier reads, for example a name of `let`. The
|
|
13980
|
-
* function does not resolve it again.
|
|
13981
|
-
* - A thing compares as in `sameResult`. Thus a host thing or a date is the same only as itself. A host gives a new
|
|
13982
|
-
* object for a new state, or the option `equal`.
|
|
13983
|
-
* - A longer path through a name that holds a `Held` in the new context is a changed read. A reference of the formula
|
|
13984
|
-
* kit gives the error of that binding, and the resolution of the path does not show it.
|
|
13985
|
-
* - The answer is sound only for a pure document (FACTS.PURE). A document with an effect, a volatile operation or an
|
|
13986
|
-
* extension of a profile can read outside `resolve`. The schedule asks `factsOf` first.
|
|
13987
|
-
*
|
|
13988
|
-
* The values of the context must not change in place, as PLAN.CACHE says for the documents. A value that changed in
|
|
13989
|
-
* place is the same object, thus the function cannot see the change.
|
|
13990
|
-
*
|
|
13991
|
-
* @param target - The machine of the evaluation, or its plan.
|
|
13992
|
-
* @param reads - The reads of the evaluation, from the option `reads`.
|
|
13993
|
-
* @param context - The new context.
|
|
13994
|
-
* @param options - The rule of the host for two things.
|
|
13995
|
-
* @returns `{ same: true }` when each free read reaches the same thing, or else the first changed read.
|
|
13996
|
-
*/
|
|
13997
|
-
function verifyReads(target, reads, context, options = {}) {
|
|
13998
|
-
const machine = "evaluators" in target ? target : target.machine;
|
|
13999
|
-
const root = rootContext(machine, context);
|
|
14000
|
-
const equal = options.equal;
|
|
14001
|
-
for (let index = 0; index < reads.length; index += 1) {
|
|
14002
|
-
const read = reads[index];
|
|
14003
|
-
if (!read.free) continue;
|
|
14004
|
-
const stop = heldStop(root, read.path);
|
|
14005
|
-
if (stop !== void 0) return {
|
|
14006
|
-
same: false,
|
|
14007
|
-
index,
|
|
14008
|
-
read,
|
|
14009
|
-
now: stop
|
|
14010
|
-
};
|
|
14011
|
-
if (read.kept === true) {
|
|
14012
|
-
const now = keptAgain(machine, root, read.path);
|
|
14013
|
-
if (now === void 0) return {
|
|
14014
|
-
same: false,
|
|
14015
|
-
index,
|
|
14016
|
-
read,
|
|
14017
|
-
now: fail("#N/A", "The host does not know the value of the kept read yet.")
|
|
14018
|
-
};
|
|
14019
|
-
if (!sameResult(read.reached, now, equal)) return {
|
|
14020
|
-
same: false,
|
|
14021
|
-
index,
|
|
14022
|
-
read,
|
|
14023
|
-
now
|
|
14024
|
-
};
|
|
14025
|
-
continue;
|
|
14026
|
-
}
|
|
14027
|
-
const asked = read.hops !== void 0;
|
|
14028
|
-
const now = readAgain(machine, root, read.path, asked);
|
|
14029
|
-
if (!sameResult(read.reached, now.reached, equal) || read.hops !== void 0 && !sameHops(read.hops, now.hops, equal)) return {
|
|
14030
|
-
same: false,
|
|
14031
|
-
index,
|
|
14032
|
-
read,
|
|
14033
|
-
now: now.reached
|
|
14034
|
-
};
|
|
14035
|
-
}
|
|
14036
|
-
return SAME;
|
|
14037
|
-
}
|
|
14038
|
-
// src/durable.ts
|
|
14039
|
-
/** The most values that a cache of value documents keeps, as in `planner`. */
|
|
14040
|
-
const MAX_VALUES = 1e3;
|
|
14041
|
-
/** No reads. One frozen list serves each result of a pure document without reads. */
|
|
14042
|
-
const NO_READS = /* @__PURE__ */ Object.freeze([]);
|
|
14043
|
-
/** The key of the document `-0` in a cache of value documents. A `Map` finds the entry of `0` for `-0`. */
|
|
14044
|
-
const NEGATIVE_ZERO = /* @__PURE__ */ Symbol("coeval.negative-zero");
|
|
14045
|
-
/** This function makes a cache by document. A document object gives its value one time, as PLAN.CACHE says. */
|
|
14046
|
-
function byDocument(make) {
|
|
14047
|
-
const objects = /* @__PURE__ */ new WeakMap();
|
|
14048
|
-
const values = /* @__PURE__ */ new Map();
|
|
14049
|
-
return (doc) => {
|
|
14050
|
-
if (typeof doc === "object" && doc !== null) {
|
|
14051
|
-
if (objects.has(doc)) return objects.get(doc);
|
|
14052
|
-
const made = make(doc);
|
|
14053
|
-
objects.set(doc, made);
|
|
14054
|
-
return made;
|
|
15288
|
+
if (typeIssues(truth.truthType, domains).length > 0) return;
|
|
15289
|
+
const samples = env.results.flatMap((result) => result.ok ? [result.value] : []);
|
|
15290
|
+
const of = (value) => `the member ${valueText(value)} of the truth type ${typeText(truth.truthType)}`;
|
|
15291
|
+
for (const value of membersOf(truth.truthType, domains, samples)) {
|
|
15292
|
+
let given;
|
|
15293
|
+
try {
|
|
15294
|
+
given = truth.truthy(value);
|
|
15295
|
+
} catch (error) {
|
|
15296
|
+
report(env, "truth", `The function truthy of the profile throws for ${of(value)}: ${describe(error)}`, () => probe(value));
|
|
15297
|
+
return;
|
|
14055
15298
|
}
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
values.set(key, made);
|
|
14061
|
-
return made;
|
|
14062
|
-
};
|
|
15299
|
+
if (typeof given === "boolean") continue;
|
|
15300
|
+
report(env, "truth", `The function truthy of the profile gives ${given === void 0 ? "no truth" : valueText(given)} for ${of(value)}. A truth is true or false.`, () => probe(value));
|
|
15301
|
+
return;
|
|
15302
|
+
}
|
|
14063
15303
|
}
|
|
14064
15304
|
/**
|
|
14065
|
-
* This function
|
|
14066
|
-
*
|
|
14067
|
-
*
|
|
15305
|
+
* This function checks the covers of the domains of the machine from samples (D94). Each sample of a covered domain
|
|
15306
|
+
* must be in one domain of each valid cover. The samples of a domain come from its list `values`. A domain without
|
|
15307
|
+
* that list gets its members from the palette and from the samples of the domains, as for the check `truth`.
|
|
15308
|
+
*/
|
|
15309
|
+
function checkCovers(env, probe) {
|
|
15310
|
+
const domains = env.machine.domains;
|
|
15311
|
+
if (domains === void 0) return;
|
|
15312
|
+
const palette = env.results.flatMap((result) => result.ok ? [result.value] : []);
|
|
15313
|
+
const samples = {};
|
|
15314
|
+
for (const [name, domain] of Object.entries(domains)) if (domain.covers !== void 0) samples[name] = membersOf(name, domains, palette);
|
|
15315
|
+
const found = falseCovers(domains, samples)[0];
|
|
15316
|
+
if (found === void 0) return;
|
|
15317
|
+
report(env, "cover", `The member ${valueText(found.value)} of the domain ${found.domain} is in no domain of its cover ${found.cover.join(", ")}. The check of types trusts the cover, thus it is unsound for ${found.domain}.`, () => probe(found.value));
|
|
15318
|
+
}
|
|
15319
|
+
/**
|
|
15320
|
+
* This function examines one evaluator or runner of a machine against the obligations of host code (K92, D36). It
|
|
15321
|
+
* starts the code directly, without the kernel, with probe parts and a probe runtime. It generates probe documents from
|
|
15322
|
+
* the contract of each kind that names the code, and it gives the probe parts chosen and random results.
|
|
14068
15323
|
*
|
|
14069
|
-
*
|
|
14070
|
-
*
|
|
14071
|
-
*
|
|
14072
|
-
*
|
|
14073
|
-
* - An edit gives a new document object, thus only that document compiles. A new machine drops each plan, each fact
|
|
14074
|
-
* and each result (DURABLE.STRUCTURE).
|
|
15324
|
+
* The checks follow the obligations C1 to C8 of `docs/SEMANTICS.md`. Two more checks follow the premises of the
|
|
15325
|
+
* simulation of the plan. The check `local` finds state that stays from one call to the next. The check `agree` compares
|
|
15326
|
+
* compiled code with its definition. The finding of an obligation names the first probe that breaks it. An empty list
|
|
15327
|
+
* means that each probe conforms.
|
|
14075
15328
|
*
|
|
14076
|
-
* The
|
|
14077
|
-
*
|
|
15329
|
+
* The check `truth` tests the declared truth of a formula profile (D86). The check `cover` tests the covers of the
|
|
15330
|
+
* domains of the machine from samples (D94).
|
|
14078
15331
|
*
|
|
14079
|
-
* @param machine - The machine
|
|
14080
|
-
* @param
|
|
14081
|
-
* @
|
|
15332
|
+
* @param machine - The machine with the code and the world of the kinds.
|
|
15333
|
+
* @param name - The name of the evaluator or of the runner. A runner wins over an evaluator of the same name.
|
|
15334
|
+
* @param options - The verb, the types and the values of the probe documents, and the results of the probe parts. Also
|
|
15335
|
+
* the definitions, the capabilities, the names, the seed, the mock and the rule of equality.
|
|
15336
|
+
* @returns The first finding of each obligation that the code breaks, in the order of the obligations.
|
|
15337
|
+
* @throws {Error} The machine has no code with the name, or no kind names it and the options give no types.
|
|
14082
15338
|
*/
|
|
14083
|
-
function
|
|
14084
|
-
const
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
};
|
|
14088
|
-
const
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
structure: 0,
|
|
14104
|
-
data: 0
|
|
14105
|
-
};
|
|
14106
|
-
const plansOf = (m) => byDocument((doc) => {
|
|
14107
|
-
counts.compiles += 1;
|
|
14108
|
-
return prepare(m, doc, prepareOptions);
|
|
14109
|
-
});
|
|
14110
|
-
const factsFor = (m) => byDocument((doc) => {
|
|
14111
|
-
counts.facts += 1;
|
|
14112
|
-
return factsOf(m, doc, factsOptions);
|
|
14113
|
-
});
|
|
14114
|
-
let plans = plansOf(machine);
|
|
14115
|
-
let facts = factsFor(machine);
|
|
14116
|
-
return {
|
|
14117
|
-
get machine() {
|
|
14118
|
-
return current;
|
|
14119
|
-
},
|
|
14120
|
-
get epochs() {
|
|
14121
|
-
return epochs;
|
|
14122
|
-
},
|
|
14123
|
-
get counts() {
|
|
14124
|
-
return { ...counts };
|
|
14125
|
-
},
|
|
14126
|
-
plan: (doc) => plans(doc),
|
|
14127
|
-
facts: (doc) => facts(doc),
|
|
14128
|
-
evaluate(key, doc, context) {
|
|
14129
|
-
const plan = plans(doc);
|
|
14130
|
-
const entry = results.get(key);
|
|
14131
|
-
if (entry !== void 0 && entry.plan === plan) {
|
|
14132
|
-
if (entry.data === epochs.data) {
|
|
14133
|
-
counts.reuses += 1;
|
|
14134
|
-
return entry.result;
|
|
14135
|
-
}
|
|
14136
|
-
if (entry.reads !== void 0) {
|
|
14137
|
-
counts.verifications += 1;
|
|
14138
|
-
if (verifyReads(plan, entry.reads, context, verifyOptions).same) {
|
|
14139
|
-
entry.data = epochs.data;
|
|
14140
|
-
counts.reuses += 1;
|
|
14141
|
-
return entry.result;
|
|
14142
|
-
}
|
|
14143
|
-
}
|
|
14144
|
-
}
|
|
14145
|
-
const reads = facts(doc).pure ? [] : void 0;
|
|
14146
|
-
counts.evaluations += 1;
|
|
14147
|
-
const result = plan.evaluate(context, reads === void 0 ? void 0 : { reads });
|
|
14148
|
-
results.set(key, {
|
|
14149
|
-
plan,
|
|
14150
|
-
result,
|
|
14151
|
-
reads: reads === void 0 ? void 0 : reads.length === 0 ? NO_READS : reads.slice(),
|
|
14152
|
-
data: epochs.data
|
|
14153
|
-
});
|
|
14154
|
-
return result;
|
|
14155
|
-
},
|
|
14156
|
-
changeData() {
|
|
14157
|
-
epochs = {
|
|
14158
|
-
structure: epochs.structure,
|
|
14159
|
-
data: epochs.data + 1
|
|
14160
|
-
};
|
|
14161
|
-
},
|
|
14162
|
-
changeStructure(next) {
|
|
14163
|
-
epochs = {
|
|
14164
|
-
structure: epochs.structure + 1,
|
|
14165
|
-
data: epochs.data + 1
|
|
14166
|
-
};
|
|
14167
|
-
if (next === void 0 || next === current) return;
|
|
14168
|
-
current = next;
|
|
14169
|
-
plans = plansOf(next);
|
|
14170
|
-
facts = factsFor(next);
|
|
14171
|
-
results.clear();
|
|
14172
|
-
},
|
|
14173
|
-
forget(key) {
|
|
14174
|
-
results.delete(key);
|
|
14175
|
-
}
|
|
15339
|
+
function conforms(machine, name, options = {}) {
|
|
15340
|
+
const verb = options.verb ?? "eval";
|
|
15341
|
+
const runnerCode = own(machine.runners, name);
|
|
15342
|
+
const code = runnerCode ?? own(machine.evaluators, name);
|
|
15343
|
+
if (code === void 0) throw new Error(`The machine has no evaluator and no runner with the name ${name}.`);
|
|
15344
|
+
const runner = runnerCode !== void 0;
|
|
15345
|
+
const env = {
|
|
15346
|
+
machine,
|
|
15347
|
+
name,
|
|
15348
|
+
verb,
|
|
15349
|
+
under: underOf(code, runner),
|
|
15350
|
+
definition: definitionOf(machine, name, runner, options.definitions),
|
|
15351
|
+
runner,
|
|
15352
|
+
results: options.results ?? RESULTS,
|
|
15353
|
+
names: options.names ?? NAMES,
|
|
15354
|
+
runs: options.runs ?? 12,
|
|
15355
|
+
random: random(options.seed ?? 1),
|
|
15356
|
+
perform: options.perform,
|
|
15357
|
+
equal: options.equal,
|
|
15358
|
+
found: /* @__PURE__ */ new Map()
|
|
14176
15359
|
};
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
if (graph.get(id)?.side === "run") sink[index] = 1;
|
|
14189
|
-
});
|
|
14190
|
-
const start = new Int32Array(count + 2);
|
|
14191
|
-
const targets = [];
|
|
14192
|
-
let unknown = false;
|
|
14193
|
-
let from = 0;
|
|
14194
|
-
for (const node of graph.values()) {
|
|
14195
|
-
start[from] = targets.length;
|
|
14196
|
-
for (const edge of node.edges) {
|
|
14197
|
-
if (eager && edge.lazy) continue;
|
|
14198
|
-
if (edge.to === void 0) {
|
|
14199
|
-
if (eager) continue;
|
|
14200
|
-
unknown = true;
|
|
14201
|
-
targets.push(count);
|
|
14202
|
-
continue;
|
|
14203
|
-
}
|
|
14204
|
-
const to = numbers.get(edge.to);
|
|
14205
|
-
if (to !== void 0 && sink[to] === 0) targets.push(to);
|
|
15360
|
+
const types = options.types ?? typesNaming(machine.world, name, verb);
|
|
15361
|
+
let first;
|
|
15362
|
+
if (types.length === 0) throw new Error(`No kind of the world names ${name} for the verb ${verb}. Give the types of the probe documents in the option types.`);
|
|
15363
|
+
for (const type of types) {
|
|
15364
|
+
const found = lookupKind(machine.world, type);
|
|
15365
|
+
if (!found.ok) throw new Error(`The world has no kind for the type ${typeName(type)}.`);
|
|
15366
|
+
const contract = contractOf(env, found.kind, found.path, options.caps);
|
|
15367
|
+
const extra = own(options.values, typeName(type)) ?? own(options.values, found.path.join(".")) ?? [];
|
|
15368
|
+
for (const sample of samplesOf(type, found.path, found.kind, contract, extra)) {
|
|
15369
|
+
first ??= sample;
|
|
15370
|
+
examine(env, sample, contract);
|
|
14206
15371
|
}
|
|
14207
|
-
from += 1;
|
|
14208
|
-
}
|
|
14209
|
-
start[count] = targets.length;
|
|
14210
|
-
if (unknown) {
|
|
14211
|
-
for (let to = 0; to < count; to += 1) if (sink[to] === 0) targets.push(to);
|
|
14212
15372
|
}
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
15373
|
+
const probeWith = (value) => first === void 0 ? {
|
|
15374
|
+
type: typeName(types[0]),
|
|
15375
|
+
doc: {
|
|
15376
|
+
type: types[0],
|
|
15377
|
+
value: null
|
|
15378
|
+
},
|
|
15379
|
+
results: {},
|
|
15380
|
+
waits: [],
|
|
15381
|
+
caps: NO_CAPS,
|
|
15382
|
+
running: runner
|
|
15383
|
+
} : probeOf(first, {
|
|
15384
|
+
results: first.split.parts.map(() => ok(value)),
|
|
15385
|
+
waits: [],
|
|
15386
|
+
caps: NO_CAPS,
|
|
15387
|
+
running: runner
|
|
15388
|
+
});
|
|
15389
|
+
checkTruth(env, code, probeWith);
|
|
15390
|
+
checkCovers(env, probeWith);
|
|
15391
|
+
return ORDER.filter((obligation) => env.found.has(obligation)).map((obligation) => env.found.get(obligation));
|
|
15392
|
+
}
|
|
15393
|
+
// src/same.ts
|
|
15394
|
+
/**
|
|
15395
|
+
* This function tells if two results are the same for an early cutoff (RESULT.SAME). A schedule uses it to stop the
|
|
15396
|
+
* work when a part that evaluates again gives the same result. A wrong `true` keeps a stale value, but a wrong `false`
|
|
15397
|
+
* only costs work. Thus each rule gives `false` when it is not sure.
|
|
15398
|
+
*
|
|
15399
|
+
* - Two values compare by the rules of `docEquals`. Lists and plain records compare by their items and fields. Each
|
|
15400
|
+
* other value compares with `Object.is`.
|
|
15401
|
+
* - Thus `-0` is not `0`, and `NaN` is `NaN`. A date, a map and a closure of `fn` are the same only as themselves.
|
|
15402
|
+
* - Two errors are the same when they have the same code, reason and place (RESULT.SAME-ERROR). The cause is not
|
|
15403
|
+
* compared. A `Held` value compares its error by the same rule.
|
|
15404
|
+
* - A value and an error are not the same.
|
|
15405
|
+
*
|
|
15406
|
+
* `sameValue` of `agree` uses the same walk, with rules for two tiers (D20). It compares two closures by their
|
|
15407
|
+
* parameters, and two errors by their code and place.
|
|
15408
|
+
*
|
|
15409
|
+
* @param a - The earlier result.
|
|
15410
|
+
* @param b - The new result.
|
|
15411
|
+
* @param equal - The rule of the host for two values, for example two dates. It answers first, and `undefined` gives the rules above.
|
|
15412
|
+
* @returns `true` when the results are the same.
|
|
15413
|
+
*/
|
|
15414
|
+
function sameResult(a, b, equal) {
|
|
15415
|
+
if (a.ok && b.ok) return equalWith(a.value, b.value, equal === void 0 ? sameHeld : (x, y) => equal(x, y) ?? sameHeld(x, y));
|
|
15416
|
+
if (!a.ok && !b.ok) return sameError(a.error, b.error);
|
|
15417
|
+
return false;
|
|
14221
15418
|
}
|
|
14222
|
-
/**
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
15419
|
+
/** The rule of `sameResult` for held errors. Two `Held` values are the same when their errors are the same. */
|
|
15420
|
+
function sameHeld(x, y) {
|
|
15421
|
+
if (x instanceof Held || y instanceof Held) return x instanceof Held && y instanceof Held && sameError(x.error, y.error);
|
|
15422
|
+
}
|
|
15423
|
+
/** Two errors are the same when they have the same code, reason and place. An error without a place has no place in both. */
|
|
15424
|
+
function sameError(a, b) {
|
|
15425
|
+
if (a.code !== b.code || a.reason !== b.reason) return false;
|
|
15426
|
+
if (a.place === void 0 || b.place === void 0) return a.place === b.place;
|
|
15427
|
+
return docEquals(a.place, b.place);
|
|
15428
|
+
}
|
|
15429
|
+
// src/reads.ts
|
|
15430
|
+
/** One answer serves each list whose reads are all the same. */
|
|
15431
|
+
const SAME = /* @__PURE__ */ Object.freeze({ same: true });
|
|
15432
|
+
/** The default limit of the entries in the print of one value. */
|
|
15433
|
+
const PRINT_LIMIT = 1e4;
|
|
15434
|
+
/** The marks of a print. Each mark is a private object, thus no value of a host is the same as a mark. */
|
|
15435
|
+
const OBJECT = /* @__PURE__ */ Object.freeze({ mark: "object" });
|
|
15436
|
+
const LIST = /* @__PURE__ */ Object.freeze({ mark: "list" });
|
|
15437
|
+
const DATE = /* @__PURE__ */ Object.freeze({ mark: "date" });
|
|
15438
|
+
const MAP = /* @__PURE__ */ Object.freeze({ mark: "map" });
|
|
15439
|
+
const SET = /* @__PURE__ */ Object.freeze({ mark: "set" });
|
|
15440
|
+
const SEEN = /* @__PURE__ */ Object.freeze({ mark: "seen" });
|
|
15441
|
+
const STAMP = /* @__PURE__ */ Object.freeze({ mark: "stamp" });
|
|
15442
|
+
/** The print of a value that the walk cannot finish: it is too large, or the host threw. It is not the same as any print. */
|
|
15443
|
+
const NO_PRINT = /* @__PURE__ */ Object.freeze([]);
|
|
15444
|
+
/**
|
|
15445
|
+
* This function gives the print of a value: a flat list of its parts, in the order of a walk (D112). Two prints are
|
|
15446
|
+
* the same when they have the same length, and each entry is the same by `Object.is`.
|
|
15447
|
+
*
|
|
15448
|
+
* - An object starts with a mark and its prototype. A list adds its length, a date its time, and a map or a set
|
|
15449
|
+
* its size and its entries. Then come the count of the own enumerable fields, each name and the kind of each field.
|
|
15450
|
+
* - A field with a getter gives the getter function. The walk does not start host code of a field.
|
|
15451
|
+
* - A primitive value and a function are entries. A function compares by its identity.
|
|
15452
|
+
* - An object that the walk already visited gives the mark `SEEN` and its number. Thus a cycle ends.
|
|
15453
|
+
* - A value with more than `limit` entries, or an exception of the host, gives `NO_PRINT`.
|
|
15454
|
+
*
|
|
15455
|
+
* @param value - The value of a read.
|
|
15456
|
+
* @param options - The limit and the stamp of the host.
|
|
15457
|
+
* @returns The print.
|
|
14226
15458
|
*/
|
|
14227
|
-
function
|
|
14228
|
-
const
|
|
14229
|
-
const
|
|
14230
|
-
const
|
|
14231
|
-
const
|
|
14232
|
-
const
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
const enter = (node, depth) => {
|
|
14240
|
-
order[node] = count;
|
|
14241
|
-
low[node] = count;
|
|
14242
|
-
count += 1;
|
|
14243
|
-
open[top] = node;
|
|
14244
|
-
top += 1;
|
|
14245
|
-
onOpen[node] = 1;
|
|
14246
|
-
calls[depth] = node;
|
|
14247
|
-
next[depth] = start[node];
|
|
14248
|
-
};
|
|
14249
|
-
for (let root = 0; root < size; root += 1) {
|
|
14250
|
-
if (order[root] !== -1) continue;
|
|
14251
|
-
enter(root, 0);
|
|
14252
|
-
let depth = 1;
|
|
14253
|
-
while (depth > 0) {
|
|
14254
|
-
const node = calls[depth - 1];
|
|
14255
|
-
const edge = next[depth - 1];
|
|
14256
|
-
if (edge < start[node + 1]) {
|
|
14257
|
-
next[depth - 1] = edge + 1;
|
|
14258
|
-
const to = targets[edge];
|
|
14259
|
-
if (order[to] === -1) {
|
|
14260
|
-
enter(to, depth);
|
|
14261
|
-
depth += 1;
|
|
14262
|
-
} else if (onOpen[to] === 1 && order[to] < low[node]) low[node] = order[to];
|
|
15459
|
+
function printOf(value, options) {
|
|
15460
|
+
const limit = options.limit ?? PRINT_LIMIT;
|
|
15461
|
+
const stamp = options.stamp;
|
|
15462
|
+
const out = [];
|
|
15463
|
+
const ids = /* @__PURE__ */ new Map();
|
|
15464
|
+
const stack = [value];
|
|
15465
|
+
try {
|
|
15466
|
+
while (stack.length > 0) {
|
|
15467
|
+
if (out.length > limit) return NO_PRINT;
|
|
15468
|
+
const x = stack.pop();
|
|
15469
|
+
if (typeof x !== "object" || x === null) {
|
|
15470
|
+
out.push(x);
|
|
14263
15471
|
continue;
|
|
14264
15472
|
}
|
|
14265
|
-
|
|
14266
|
-
if (
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
do {
|
|
14270
|
-
top -= 1;
|
|
14271
|
-
member = open[top];
|
|
14272
|
-
onOpen[member] = 0;
|
|
14273
|
-
of[member] = list.length;
|
|
14274
|
-
members.push(member);
|
|
14275
|
-
} while (member !== node);
|
|
14276
|
-
list.push(members.sort((a, b) => a - b));
|
|
15473
|
+
const id = ids.get(x);
|
|
15474
|
+
if (id !== void 0) {
|
|
15475
|
+
out.push(SEEN, id);
|
|
15476
|
+
continue;
|
|
14277
15477
|
}
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
15478
|
+
ids.set(x, ids.size);
|
|
15479
|
+
const stamped = stamp?.(x);
|
|
15480
|
+
if (stamped !== void 0) {
|
|
15481
|
+
out.push(STAMP, stamped);
|
|
15482
|
+
continue;
|
|
15483
|
+
}
|
|
15484
|
+
const children = [];
|
|
15485
|
+
out.push(OBJECT, Object.getPrototypeOf(x));
|
|
15486
|
+
if (Array.isArray(x)) out.push(LIST, x.length);
|
|
15487
|
+
else if (x instanceof Date) out.push(DATE, Date.prototype.getTime.call(x));
|
|
15488
|
+
else if (x instanceof Map) {
|
|
15489
|
+
out.push(MAP, x.size);
|
|
15490
|
+
if (out.length + 2 * x.size > limit) return NO_PRINT;
|
|
15491
|
+
for (const [key, item] of Map.prototype.entries.call(x)) children.push(key, item);
|
|
15492
|
+
} else if (x instanceof Set) {
|
|
15493
|
+
out.push(SET, x.size);
|
|
15494
|
+
if (out.length + x.size > limit) return NO_PRINT;
|
|
15495
|
+
for (const item of Set.prototype.values.call(x)) children.push(item);
|
|
15496
|
+
}
|
|
15497
|
+
const keys = Object.keys(x);
|
|
15498
|
+
if (out.length + 3 * keys.length > limit) return NO_PRINT;
|
|
15499
|
+
out.push(keys.length);
|
|
15500
|
+
for (const key of keys) {
|
|
15501
|
+
const field = Object.getOwnPropertyDescriptor(x, key);
|
|
15502
|
+
const data = field === void 0 || "value" in field;
|
|
15503
|
+
out.push(key, data);
|
|
15504
|
+
children.push(field === void 0 ? void 0 : "value" in field ? field.value : field.get);
|
|
14281
15505
|
}
|
|
15506
|
+
for (let index = children.length - 1; index >= 0; index -= 1) stack.push(children[index]);
|
|
14282
15507
|
}
|
|
15508
|
+
} catch {
|
|
15509
|
+
return NO_PRINT;
|
|
14283
15510
|
}
|
|
14284
|
-
return
|
|
14285
|
-
of,
|
|
14286
|
-
list
|
|
14287
|
-
};
|
|
14288
|
-
}
|
|
14289
|
-
/** This function tells if a component has a cycle: two or more nodes, or one node with an edge to itself. */
|
|
14290
|
-
function cyclic(graph, members) {
|
|
14291
|
-
if (members.length > 1) return true;
|
|
14292
|
-
const node = members[0];
|
|
14293
|
-
for (let edge = graph.start[node]; edge < graph.start[node + 1]; edge += 1) if (graph.targets[edge] === node) return true;
|
|
14294
|
-
return false;
|
|
15511
|
+
return out.length > limit ? NO_PRINT : out;
|
|
14295
15512
|
}
|
|
14296
|
-
/** This function
|
|
14297
|
-
function
|
|
14298
|
-
|
|
14299
|
-
for (
|
|
14300
|
-
return
|
|
15513
|
+
/** This function tells if two prints are the same. `NO_PRINT` is not the same as any print, also not as itself. */
|
|
15514
|
+
function samePrint(a, b) {
|
|
15515
|
+
if (a === NO_PRINT || b === NO_PRINT || a.length !== b.length) return false;
|
|
15516
|
+
for (let index = 0; index < a.length; index += 1) if (!Object.is(a[index], b[index])) return false;
|
|
15517
|
+
return true;
|
|
14301
15518
|
}
|
|
14302
|
-
/** This function gives the
|
|
14303
|
-
function
|
|
14304
|
-
|
|
14305
|
-
const
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
const other = found.of[targets[edge]];
|
|
14313
|
-
if (other !== component && heights[other] > highest) highest = heights[other];
|
|
14314
|
-
}
|
|
14315
|
-
}
|
|
14316
|
-
heights[component] = real ? highest + 1 : highest;
|
|
14317
|
-
});
|
|
14318
|
-
return heights;
|
|
15519
|
+
/** This function gives the print of the value of a result, or `undefined` when the value holds no object. */
|
|
15520
|
+
function printOfResult(result, options, cache) {
|
|
15521
|
+
if (!result.ok || typeof result.value !== "object" || result.value === null) return void 0;
|
|
15522
|
+
const value = result.value;
|
|
15523
|
+
let print = cache.get(value);
|
|
15524
|
+
if (print === void 0) {
|
|
15525
|
+
print = printOf(value, options);
|
|
15526
|
+
cache.set(value, print);
|
|
15527
|
+
}
|
|
15528
|
+
return print;
|
|
14319
15529
|
}
|
|
14320
15530
|
/**
|
|
14321
|
-
* This function gives the
|
|
14322
|
-
*
|
|
15531
|
+
* This function gives the prints of the values of a list of reads (READS.IN-PLACE, D112). A host starts it after
|
|
15532
|
+
* the evaluation, and it gives the prints to `verifyReads` with the option `prints`.
|
|
14323
15533
|
*
|
|
14324
|
-
* -
|
|
14325
|
-
*
|
|
14326
|
-
* -
|
|
15534
|
+
* - The print of a value walks its lists, its plain records and the own enumerable fields of each other object. A
|
|
15535
|
+
* date adds its time, and a map or a set adds its entries.
|
|
15536
|
+
* - A read that is not free, a read that reached an error and a read with a primitive value get no print.
|
|
15537
|
+
* - A cycle ends, because the walk visits each object one time.
|
|
15538
|
+
* - A value with more entries than the limit gets no print. `verifyReads` then gives that read as changed.
|
|
14327
15539
|
*
|
|
14328
|
-
*
|
|
14329
|
-
*
|
|
14330
|
-
*
|
|
15540
|
+
* The walk does not see the state in a closure or a private field. It does not see a field with a symbol name, a
|
|
15541
|
+
* field that is not enumerable or the value of a getter. A host gives a stamp for such an object, for example its version.
|
|
15542
|
+
*
|
|
15543
|
+
* @param reads - The reads of the evaluation, from the option `reads`.
|
|
15544
|
+
* @param options - The limit and the stamp of the host.
|
|
15545
|
+
* @returns The prints, for the option `prints` of `verifyReads`.
|
|
14331
15546
|
*/
|
|
14332
|
-
function
|
|
14333
|
-
const
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
15547
|
+
function printReads(reads, options = {}) {
|
|
15548
|
+
const cache = /* @__PURE__ */ new Map();
|
|
15549
|
+
return {
|
|
15550
|
+
prints: reads.map((read) => read.free ? printOfResult(read.reached, options, cache) : void 0),
|
|
15551
|
+
options
|
|
15552
|
+
};
|
|
15553
|
+
}
|
|
15554
|
+
/** This function tells if the print of a read changed, when the read has a print. */
|
|
15555
|
+
function changedInPlace(prints, index, now, cache) {
|
|
15556
|
+
const old = prints.prints[index];
|
|
15557
|
+
if (old === void 0) return false;
|
|
15558
|
+
const print = printOfResult(now, prints.options, cache);
|
|
15559
|
+
return print === void 0 || !samePrint(old, print);
|
|
15560
|
+
}
|
|
15561
|
+
/** This function tells if two lists of hops are the same: the same steps, rungs and things, in order. */
|
|
15562
|
+
function sameHops(a, b, equal) {
|
|
15563
|
+
if (b === void 0 || a.length !== b.length) return false;
|
|
15564
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
15565
|
+
const x = a[index];
|
|
15566
|
+
const y = b[index];
|
|
15567
|
+
if (x.by !== y.by || !docEquals(x.step, y.step) || !sameResult(ok(x.thing), ok(y.thing), equal)) return false;
|
|
14338
15568
|
}
|
|
14339
|
-
return
|
|
15569
|
+
return true;
|
|
14340
15570
|
}
|
|
14341
15571
|
/**
|
|
14342
|
-
* This function gives the
|
|
14343
|
-
*
|
|
14344
|
-
*
|
|
14345
|
-
* - A node that reads no node outside its component has the height 0.
|
|
14346
|
-
* - Each other node is one higher than the highest node that it reads outside its component.
|
|
14347
|
-
* - The nodes of one component have one height.
|
|
14348
|
-
* - An edge without a target reads each node that is not a sink (GRAPH.UNKNOWN).
|
|
14349
|
-
*
|
|
14350
|
-
* @param graph - The nodes, each with its edges.
|
|
14351
|
-
* @param options - The option `eager` keeps only the eager edges with a target node.
|
|
14352
|
-
* @returns The height of each node, in the order of the graph.
|
|
15572
|
+
* This function gives the error of a name that holds a held error and stops a longer path, or `undefined`. A
|
|
15573
|
+
* reference of the formula kit tests the binding of its first name before it resolves the rest of the path
|
|
15574
|
+
* (FORMULA.LET). The resolution of the whole path does not show that test.
|
|
14353
15575
|
*/
|
|
14354
|
-
function
|
|
14355
|
-
const
|
|
14356
|
-
|
|
14357
|
-
const
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
15576
|
+
function heldStop(root, path) {
|
|
15577
|
+
const name = path[1];
|
|
15578
|
+
if (path.length <= 2 || path[0] !== "names" || typeof name !== "string" || !Object.hasOwn(root.names, name)) return void 0;
|
|
15579
|
+
const bound = root.names[name];
|
|
15580
|
+
return bound instanceof Held ? {
|
|
15581
|
+
ok: false,
|
|
15582
|
+
error: bound.error
|
|
15583
|
+
} : void 0;
|
|
14361
15584
|
}
|
|
14362
15585
|
/**
|
|
14363
|
-
* This function
|
|
14364
|
-
*
|
|
14365
|
-
*
|
|
15586
|
+
* This function tells if an old result stays valid in a new context (READS.VERIFY, D26). It resolves the path of
|
|
15587
|
+
* each free read again, in the order of the list, and it compares what the path reaches with `sameResult`. It stops at
|
|
15588
|
+
* the first read that reaches another thing, and it gives that read (READS.CHANGED).
|
|
14366
15589
|
*
|
|
14367
|
-
*
|
|
14368
|
-
*
|
|
14369
|
-
*
|
|
15590
|
+
* - A read that is not free reads a binding that a kind made from earlier reads, for example a name of `let`. The
|
|
15591
|
+
* function does not resolve it again.
|
|
15592
|
+
* - A thing compares as in `sameResult`. Thus a host thing or a date is the same only as itself. A host gives a new
|
|
15593
|
+
* object for a new state, or the option `equal`.
|
|
15594
|
+
* - A longer path through a name that holds a `Held` in the new context is a changed read. A reference of the formula
|
|
15595
|
+
* kit gives the error of that binding, and the resolution of the path does not show it.
|
|
15596
|
+
* - The answer is sound only for a pure document (FACTS.PURE). A document with an effect, a volatile operation or an
|
|
15597
|
+
* extension of a profile can read outside `resolve`. The schedule asks `factsOf` first.
|
|
15598
|
+
*
|
|
15599
|
+
* The values of the context must not change in place, as PLAN.CACHE says for the documents. A value that changed in
|
|
15600
|
+
* place is the same object, thus `sameResult` cannot see the change. The option `prints` finds such a change
|
|
15601
|
+
* (READS.IN-PLACE). The prints come from `printReads` after the evaluation, and the answer then has the mark `inPlace`.
|
|
15602
|
+
*
|
|
15603
|
+
* @param target - The machine of the evaluation, or its plan.
|
|
15604
|
+
* @param reads - The reads of the evaluation, from the option `reads`.
|
|
15605
|
+
* @param context - The new context.
|
|
15606
|
+
* @param options - The rule of the host for two things, and the prints of the reads.
|
|
15607
|
+
* @returns `{ same: true }` when each free read reaches the same thing, or else the first changed read.
|
|
14370
15608
|
*/
|
|
14371
|
-
function
|
|
14372
|
-
const
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
15609
|
+
function verifyReads(target, reads, context, options = {}) {
|
|
15610
|
+
const machine = "evaluators" in target ? target : target.machine;
|
|
15611
|
+
const root = rootContext(machine, context);
|
|
15612
|
+
const equal = options.equal;
|
|
15613
|
+
const prints = options.prints;
|
|
15614
|
+
let printed;
|
|
15615
|
+
for (let index = 0; index < reads.length; index += 1) {
|
|
15616
|
+
const read = reads[index];
|
|
15617
|
+
if (!read.free) continue;
|
|
15618
|
+
const stop = heldStop(root, read.path);
|
|
15619
|
+
if (stop !== void 0) return {
|
|
15620
|
+
same: false,
|
|
15621
|
+
index,
|
|
15622
|
+
read,
|
|
15623
|
+
now: stop
|
|
15624
|
+
};
|
|
15625
|
+
if (read.kept === true) {
|
|
15626
|
+
const now = keptAgain(machine, root, read.path);
|
|
15627
|
+
if (now === void 0) return {
|
|
15628
|
+
same: false,
|
|
15629
|
+
index,
|
|
15630
|
+
read,
|
|
15631
|
+
now: fail("#N/A", "The host does not know the value of the kept read yet.")
|
|
15632
|
+
};
|
|
15633
|
+
if (!sameResult(read.reached, now, equal)) return {
|
|
15634
|
+
same: false,
|
|
15635
|
+
index,
|
|
15636
|
+
read,
|
|
15637
|
+
now
|
|
15638
|
+
};
|
|
15639
|
+
if (prints !== void 0 && changedInPlace(prints, index, now, printed ??= /* @__PURE__ */ new Map())) return {
|
|
15640
|
+
same: false,
|
|
15641
|
+
index,
|
|
15642
|
+
read,
|
|
15643
|
+
now,
|
|
15644
|
+
inPlace: true
|
|
15645
|
+
};
|
|
15646
|
+
continue;
|
|
15647
|
+
}
|
|
15648
|
+
const asked = read.hops !== void 0;
|
|
15649
|
+
const now = readAgain(machine, root, read.path, asked);
|
|
15650
|
+
if (!sameResult(read.reached, now.reached, equal) || read.hops !== void 0 && !sameHops(read.hops, now.hops, equal)) return {
|
|
15651
|
+
same: false,
|
|
15652
|
+
index,
|
|
15653
|
+
read,
|
|
15654
|
+
now: now.reached
|
|
15655
|
+
};
|
|
15656
|
+
if (prints !== void 0 && changedInPlace(prints, index, now.reached, printed ??= /* @__PURE__ */ new Map())) return {
|
|
15657
|
+
same: false,
|
|
15658
|
+
index,
|
|
15659
|
+
read,
|
|
15660
|
+
now: now.reached,
|
|
15661
|
+
inPlace: true
|
|
15662
|
+
};
|
|
14376
15663
|
}
|
|
14377
|
-
return
|
|
15664
|
+
return SAME;
|
|
14378
15665
|
}
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
*/
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
const node = numbers.get(id);
|
|
14397
|
-
if (node === void 0 || seen[node] === 1) continue;
|
|
14398
|
-
seen[node] = 1;
|
|
14399
|
-
work.push(node);
|
|
14400
|
-
}
|
|
14401
|
-
while (work.length > 0) {
|
|
14402
|
-
const node = work.pop();
|
|
14403
|
-
for (let edge = index.start[node]; edge < index.start[node + 1]; edge += 1) {
|
|
14404
|
-
const to = index.targets[edge];
|
|
14405
|
-
if (seen[to] === 1) continue;
|
|
14406
|
-
seen[to] = 1;
|
|
14407
|
-
work.push(to);
|
|
15666
|
+
// src/durable.ts
|
|
15667
|
+
/** The most values that a cache of value documents keeps, as in `planner`. */
|
|
15668
|
+
const MAX_VALUES = 1e3;
|
|
15669
|
+
/** No reads. One frozen list serves each result of a pure document without reads. */
|
|
15670
|
+
const NO_READS = /* @__PURE__ */ Object.freeze([]);
|
|
15671
|
+
/** The key of the document `-0` in a cache of value documents. A `Map` finds the entry of `0` for `-0`. */
|
|
15672
|
+
const NEGATIVE_ZERO = /* @__PURE__ */ Symbol("coeval.negative-zero");
|
|
15673
|
+
/** This function makes a cache by document. A document object gives its value one time, as PLAN.CACHE says. */
|
|
15674
|
+
function byDocument(make) {
|
|
15675
|
+
const objects = /* @__PURE__ */ new WeakMap();
|
|
15676
|
+
const values = /* @__PURE__ */ new Map();
|
|
15677
|
+
return (doc) => {
|
|
15678
|
+
if (typeof doc === "object" && doc !== null) {
|
|
15679
|
+
if (objects.has(doc)) return objects.get(doc);
|
|
15680
|
+
const made = make(doc);
|
|
15681
|
+
objects.set(doc, made);
|
|
15682
|
+
return made;
|
|
14408
15683
|
}
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
if (
|
|
14413
|
-
|
|
14414
|
-
|
|
15684
|
+
const key = Object.is(doc, -0) ? NEGATIVE_ZERO : doc;
|
|
15685
|
+
if (values.has(key)) return values.get(key);
|
|
15686
|
+
const made = make(doc);
|
|
15687
|
+
if (values.size >= MAX_VALUES) values.clear();
|
|
15688
|
+
values.set(key, made);
|
|
15689
|
+
return made;
|
|
15690
|
+
};
|
|
14415
15691
|
}
|
|
14416
15692
|
/**
|
|
14417
|
-
* This function
|
|
14418
|
-
*
|
|
14419
|
-
*
|
|
15693
|
+
* This function makes a cache with two levels of durability for a host or a schedule (R4 P7, D30). The structure is
|
|
15694
|
+
* the machine and the documents. The data is each value that a context gives: the names, `self`, `event`, the
|
|
15695
|
+
* capabilities and the world of the host.
|
|
14420
15696
|
*
|
|
14421
|
-
* - A
|
|
14422
|
-
*
|
|
14423
|
-
* - A
|
|
15697
|
+
* - A plan and the facts depend only on the structure. A document object compiles one time for a machine, thus a
|
|
15698
|
+
* change of data compiles nothing (DURABLE.DATA).
|
|
15699
|
+
* - A result depends on the data too. It stays in its data epoch. After a change of data, a pure document verifies
|
|
15700
|
+
* its reads with `verifyReads`, and each other document evaluates again.
|
|
15701
|
+
* - An edit gives a new document object, thus only that document compiles. A new machine drops each plan, each fact
|
|
15702
|
+
* and each result (DURABLE.STRUCTURE).
|
|
14424
15703
|
*
|
|
14425
|
-
*
|
|
14426
|
-
*
|
|
14427
|
-
|
|
14428
|
-
function cycles(graph) {
|
|
14429
|
-
const every = indexed(graph, {});
|
|
14430
|
-
const found = strong(every);
|
|
14431
|
-
const eager = indexed(graph, { eager: true });
|
|
14432
|
-
const inside = /* @__PURE__ */ new Map();
|
|
14433
|
-
for (const members of strong(eager).list) {
|
|
14434
|
-
if (!cyclic(eager, members)) continue;
|
|
14435
|
-
const component = found.of[members[0]];
|
|
14436
|
-
const list = inside.get(component);
|
|
14437
|
-
if (list === void 0) inside.set(component, [members]);
|
|
14438
|
-
else list.push(members);
|
|
14439
|
-
}
|
|
14440
|
-
const out = [];
|
|
14441
|
-
found.list.forEach((members, component) => {
|
|
14442
|
-
if (!cyclic(every, members)) return;
|
|
14443
|
-
const nodes = named$1(every, members);
|
|
14444
|
-
const certain = inside.get(component) ?? [];
|
|
14445
|
-
if (certain.length === 1 && certain[0]?.length === nodes.length) {
|
|
14446
|
-
out.push({
|
|
14447
|
-
nodes,
|
|
14448
|
-
certain: true
|
|
14449
|
-
});
|
|
14450
|
-
return;
|
|
14451
|
-
}
|
|
14452
|
-
out.push({
|
|
14453
|
-
nodes,
|
|
14454
|
-
certain: false
|
|
14455
|
-
});
|
|
14456
|
-
for (const part of certain) out.push({
|
|
14457
|
-
nodes: named$1(eager, part),
|
|
14458
|
-
certain: true
|
|
14459
|
-
});
|
|
14460
|
-
});
|
|
14461
|
-
return out;
|
|
14462
|
-
}
|
|
14463
|
-
/**
|
|
14464
|
-
* This function gives the nodes that a demand of a node needs first, deepest first (SCHED.LONG-CHAIN). It follows the
|
|
14465
|
-
* eager edges with a target from the node, with its own stack. Each node comes after each node that it reads, except
|
|
14466
|
-
* on a cycle, as in an order by heights. The walk costs only the nodes that it enters.
|
|
15704
|
+
* The host keeps two promises. In one data epoch, the context of a key gives the same data. The values of a
|
|
15705
|
+
* context and the documents do not change in place, as PLAN.CACHE and `verifyReads` say. The option `inPlace`
|
|
15706
|
+
* finds a change in place of a value that a pure document read (READS.IN-PLACE).
|
|
14467
15707
|
*
|
|
14468
|
-
* @param
|
|
14469
|
-
* @param
|
|
14470
|
-
* @returns The
|
|
15708
|
+
* @param machine - The machine of the first structure epoch.
|
|
15709
|
+
* @param options - The type of a bare root value, the verb, the volatile reads, the rule of the host and the check of changes in place.
|
|
15710
|
+
* @returns The cache.
|
|
14471
15711
|
*/
|
|
14472
|
-
function
|
|
14473
|
-
const
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
const
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
|
|
14486
|
-
|
|
14487
|
-
|
|
14488
|
-
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
|
|
14498
|
-
|
|
15712
|
+
function durable(machine, options = {}) {
|
|
15713
|
+
const prepareOptions = {
|
|
15714
|
+
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
|
|
15715
|
+
...options.verb === void 0 ? {} : { verb: options.verb }
|
|
15716
|
+
};
|
|
15717
|
+
const factsOptions = {
|
|
15718
|
+
...prepareOptions,
|
|
15719
|
+
...options.volatile === void 0 ? {} : { volatile: options.volatile }
|
|
15720
|
+
};
|
|
15721
|
+
const verifyOptions = options.equal === void 0 ? {} : { equal: options.equal };
|
|
15722
|
+
const printOptions = options.inPlace === true ? {} : options.inPlace;
|
|
15723
|
+
const printsOf = printOptions === void 0 ? void 0 : /* @__PURE__ */ new WeakMap();
|
|
15724
|
+
const counts = {
|
|
15725
|
+
compiles: 0,
|
|
15726
|
+
facts: 0,
|
|
15727
|
+
evaluations: 0,
|
|
15728
|
+
verifications: 0,
|
|
15729
|
+
reuses: 0
|
|
15730
|
+
};
|
|
15731
|
+
const results = /* @__PURE__ */ new Map();
|
|
15732
|
+
let current = machine;
|
|
15733
|
+
let epochs = {
|
|
15734
|
+
structure: 0,
|
|
15735
|
+
data: 0
|
|
15736
|
+
};
|
|
15737
|
+
const plansOf = (m) => byDocument((doc) => {
|
|
15738
|
+
counts.compiles += 1;
|
|
15739
|
+
return prepare(m, doc, prepareOptions);
|
|
15740
|
+
});
|
|
15741
|
+
const factsFor = (m) => byDocument((doc) => {
|
|
15742
|
+
counts.facts += 1;
|
|
15743
|
+
return factsOf(m, doc, factsOptions);
|
|
15744
|
+
});
|
|
15745
|
+
let plans = plansOf(machine);
|
|
15746
|
+
let facts = factsFor(machine);
|
|
15747
|
+
return {
|
|
15748
|
+
get machine() {
|
|
15749
|
+
return current;
|
|
15750
|
+
},
|
|
15751
|
+
get epochs() {
|
|
15752
|
+
return epochs;
|
|
15753
|
+
},
|
|
15754
|
+
get counts() {
|
|
15755
|
+
return { ...counts };
|
|
15756
|
+
},
|
|
15757
|
+
plan: (doc) => plans(doc),
|
|
15758
|
+
facts: (doc) => facts(doc),
|
|
15759
|
+
evaluate(key, doc, context) {
|
|
15760
|
+
const plan = plans(doc);
|
|
15761
|
+
const entry = results.get(key);
|
|
15762
|
+
if (entry !== void 0 && entry.plan === plan) {
|
|
15763
|
+
if (entry.data === epochs.data) {
|
|
15764
|
+
counts.reuses += 1;
|
|
15765
|
+
return entry.result;
|
|
15766
|
+
}
|
|
15767
|
+
if (entry.reads !== void 0) {
|
|
15768
|
+
counts.verifications += 1;
|
|
15769
|
+
const prints = printsOf?.get(entry);
|
|
15770
|
+
if (verifyReads(plan, entry.reads, context, prints === void 0 ? verifyOptions : {
|
|
15771
|
+
...verifyOptions,
|
|
15772
|
+
prints
|
|
15773
|
+
}).same) {
|
|
15774
|
+
entry.data = epochs.data;
|
|
15775
|
+
counts.reuses += 1;
|
|
15776
|
+
return entry.result;
|
|
15777
|
+
}
|
|
15778
|
+
}
|
|
15779
|
+
}
|
|
15780
|
+
const reads = facts(doc).pure ? [] : void 0;
|
|
15781
|
+
counts.evaluations += 1;
|
|
15782
|
+
const result = plan.evaluate(context, reads === void 0 ? void 0 : { reads });
|
|
15783
|
+
const made = {
|
|
15784
|
+
plan,
|
|
15785
|
+
result,
|
|
15786
|
+
reads: reads === void 0 ? void 0 : reads.length === 0 ? NO_READS : reads.slice(),
|
|
15787
|
+
data: epochs.data
|
|
15788
|
+
};
|
|
15789
|
+
results.set(key, made);
|
|
15790
|
+
if (printsOf !== void 0 && made.reads !== void 0) printsOf.set(made, printReads(made.reads, printOptions));
|
|
15791
|
+
return result;
|
|
15792
|
+
},
|
|
15793
|
+
changeData() {
|
|
15794
|
+
epochs = {
|
|
15795
|
+
structure: epochs.structure,
|
|
15796
|
+
data: epochs.data + 1
|
|
15797
|
+
};
|
|
15798
|
+
},
|
|
15799
|
+
changeStructure(next) {
|
|
15800
|
+
epochs = {
|
|
15801
|
+
structure: epochs.structure + 1,
|
|
15802
|
+
data: epochs.data + 1
|
|
15803
|
+
};
|
|
15804
|
+
if (next === void 0 || next === current) return;
|
|
15805
|
+
current = next;
|
|
15806
|
+
plans = plansOf(next);
|
|
15807
|
+
facts = factsFor(next);
|
|
15808
|
+
results.clear();
|
|
15809
|
+
},
|
|
15810
|
+
forget(key) {
|
|
15811
|
+
results.delete(key);
|
|
14499
15812
|
}
|
|
14500
|
-
|
|
14501
|
-
const done = nodes.pop();
|
|
14502
|
-
lists.pop();
|
|
14503
|
-
positions.pop();
|
|
14504
|
-
if (nodes.length > 0) out.push(done);
|
|
14505
|
-
}
|
|
14506
|
-
return out;
|
|
15813
|
+
};
|
|
14507
15814
|
}
|
|
14508
15815
|
// src/schedule.ts
|
|
14509
15816
|
/**
|
|
@@ -15507,6 +16814,6 @@ function samplesAt(values, closed, depth) {
|
|
|
15507
16814
|
function typeFromSamples(values, options = {}) {
|
|
15508
16815
|
return samplesAt(values, options.closed === true, 0);
|
|
15509
16816
|
}
|
|
15510
|
-
export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeLog, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
|
|
16817
|
+
export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, ask, attributeGraph, attributeTrace, attributeVertexId, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeLog, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, printReads, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, treeNodeEvaluator, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
|
|
15511
16818
|
|
|
15512
16819
|
//# sourceMappingURL=index.js.map
|