@mark1russell7/coeval 0.19.0 → 0.20.1
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 +2 -1
- package/build/index.d.ts +151 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +689 -34
- package/build/index.js.map +1 -1
- package/package.json +3 -1
- package/src/index.ts +5 -0
- package/src/instances.ts +33 -5
- package/src/reference.ts +142 -12
- package/src/static.ts +8 -2
- package/src/stdlib.ts +367 -23
- package/src/steps.ts +181 -0
- package/src/world.ts +59 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/coeval",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.20.1",
|
|
5
5
|
"description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./build/index.js",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
],
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@mark1russell7/cue": "^1.0.2",
|
|
39
|
+
"@mark1russell7/rigor-bench": "^0.2.1",
|
|
40
|
+
"@mark1russell7/rigor-statistics": "^0.1.5",
|
|
39
41
|
"@types/node": "^26.6.4",
|
|
40
42
|
"esbuild": "^0.28.2",
|
|
41
43
|
"typescript": "^7.0.2",
|
package/src/index.ts
CHANGED
|
@@ -38,6 +38,8 @@ export {
|
|
|
38
38
|
type Alias,
|
|
39
39
|
type AttributeDeclaration,
|
|
40
40
|
type CapsRule,
|
|
41
|
+
type CircularDeclaration,
|
|
42
|
+
type CircularRule,
|
|
41
43
|
type Kind,
|
|
42
44
|
type KindFacts,
|
|
43
45
|
type LazyRule,
|
|
@@ -52,6 +54,8 @@ export {
|
|
|
52
54
|
type VerbEffects,
|
|
53
55
|
type World,
|
|
54
56
|
type WorldEntry,
|
|
57
|
+
CIRCULAR_LIMIT,
|
|
58
|
+
circularOf,
|
|
55
59
|
effectsOfVerb,
|
|
56
60
|
entriesAsJson,
|
|
57
61
|
equationType,
|
|
@@ -88,6 +92,7 @@ export {
|
|
|
88
92
|
kindNamed,
|
|
89
93
|
kindOf,
|
|
90
94
|
} from "./ladder.ts";
|
|
95
|
+
export { type StepKey, type StepLadder, type StepNeed, type StepReason, type StepRefusal, type StepWalk, checkStep, isStep, stepLadder, stepOf, stepRank } from "./steps.ts";
|
|
91
96
|
export {
|
|
92
97
|
type Cap,
|
|
93
98
|
type Context,
|
package/src/instances.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface AttributeVertex {
|
|
|
37
37
|
readonly attribute: string;
|
|
38
38
|
/** The text of the type of the node. */
|
|
39
39
|
readonly type: string;
|
|
40
|
+
/** `true` when the kind of the node declares the attribute `circular` (TREE.CIRCULAR-CHECK). Else the field is absent. */
|
|
41
|
+
readonly circular?: true;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/** One read of an attribute by the code of another attribute (D107). */
|
|
@@ -57,6 +59,11 @@ export interface AttributeCycle {
|
|
|
57
59
|
readonly vertices: readonly string[];
|
|
58
60
|
/** `true` when the eager reads with a known source close the cycle (GRAPH.CYCLE). */
|
|
59
61
|
readonly certain: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* `true` when each attribute of the cycle is circular: the run gives its fixed point, thus `check` gives no issue
|
|
64
|
+
* (TREE.CIRCULAR-CHECK). Else the field is absent.
|
|
65
|
+
*/
|
|
66
|
+
readonly circular?: true;
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
/** The graph of attribute instances of a document (TREE.GRAPH). */
|
|
@@ -195,11 +202,18 @@ interface Reader {
|
|
|
195
202
|
readonly at: readonly Key[];
|
|
196
203
|
}
|
|
197
204
|
|
|
205
|
+
/** This function tells if the kind of a node declares an attribute `circular` (D113). */
|
|
206
|
+
function isCircular(kind: Kind, attribute: string): boolean {
|
|
207
|
+
const declared = hasOwnEntry(kind.tree?.attributes, attribute) ? kind.tree?.attributes?.[attribute] : undefined;
|
|
208
|
+
return declared?.circular !== undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
198
211
|
/** This function gives the vertex of an attribute of a node. A new vertex goes on the work list for its rungs. */
|
|
199
212
|
function vertexOf(b: Builder, node: NodeInfo, attribute: string): string {
|
|
200
213
|
const id = attributeVertexId(node.visit.place, attribute);
|
|
201
214
|
if (!b.vertices.has(id)) {
|
|
202
|
-
|
|
215
|
+
const vertex: AttributeVertex = { id, place: node.visit.place, attribute, type: node.text };
|
|
216
|
+
b.vertices.set(id, isCircular(node.kind, attribute) ? { ...vertex, circular: true } : vertex);
|
|
203
217
|
b.nodes.set(id, node);
|
|
204
218
|
if (!node.parts.has(attribute)) b.work.push(id);
|
|
205
219
|
}
|
|
@@ -558,10 +572,18 @@ export function attributeGraph(machine: Machine, doc: unknown, options: Attribut
|
|
|
558
572
|
edges: b.edges,
|
|
559
573
|
graph,
|
|
560
574
|
unknown: b.edges.filter((e) => e.from === undefined),
|
|
561
|
-
cycles: cycles(graph).map((cycle) =>
|
|
575
|
+
cycles: cycles(graph).map((cycle) => {
|
|
576
|
+
const found: AttributeCycle = { vertices: cycle.nodes, certain: cycle.certain };
|
|
577
|
+
return allCircular(b, cycle.nodes) ? { ...found, circular: true } : found;
|
|
578
|
+
}),
|
|
562
579
|
};
|
|
563
580
|
}
|
|
564
581
|
|
|
582
|
+
/** This function tells if each vertex of a list is a circular attribute (D113). */
|
|
583
|
+
function allCircular(b: Builder, ids: readonly string[]): boolean {
|
|
584
|
+
return ids.every((id) => b.vertices.get(id)?.circular === true);
|
|
585
|
+
}
|
|
586
|
+
|
|
565
587
|
function vertexText(vertex: AttributeVertex): string {
|
|
566
588
|
return `${vertex.attribute} of ${vertex.type} at [${vertex.place.join(", ")}]`;
|
|
567
589
|
}
|
|
@@ -572,6 +594,9 @@ function vertexText(vertex: AttributeVertex): string {
|
|
|
572
594
|
* certain cycle that these reads reach always gives `#CYCLE!`. The issue gets the place of the read that closes the
|
|
573
595
|
* 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.
|
|
574
596
|
*
|
|
597
|
+
* A cycle whose attributes are each circular gives a fixed point, thus it is not an issue (TREE.CIRCULAR-CHECK). In a
|
|
598
|
+
* cycle with a plain attribute, the run gives `#CYCLE!` at the first read that closes a chain with a plain attribute.
|
|
599
|
+
*
|
|
575
600
|
* @param machine - The world and the code of the evaluators.
|
|
576
601
|
* @param doc - The document, as JSON.
|
|
577
602
|
* @param options - The type of a bare root value.
|
|
@@ -582,7 +607,7 @@ export function cycleIssues(machine: Machine, doc: unknown, options: CheckOption
|
|
|
582
607
|
const graph = nodeGraph(b);
|
|
583
608
|
const certain = new Map<string, number>();
|
|
584
609
|
cycles(graph).forEach((cycle, index) => {
|
|
585
|
-
if (cycle.certain) for (const id of cycle.nodes) certain.set(id, index);
|
|
610
|
+
if (cycle.certain && !allCircular(b, cycle.nodes)) for (const id of cycle.nodes) certain.set(id, index);
|
|
586
611
|
});
|
|
587
612
|
if (certain.size === 0) return [];
|
|
588
613
|
// The eager reads with a known source of each vertex, in the order of its code, with the index of each edge.
|
|
@@ -627,9 +652,12 @@ export function cycleIssues(machine: Machine, doc: unknown, options: CheckOption
|
|
|
627
652
|
// The read reaches an attribute in evaluation: the run gives #CYCLE! here, with the chain of the demands.
|
|
628
653
|
const cycle = certain.get(from);
|
|
629
654
|
if (cycle === undefined || reported.has(cycle)) continue;
|
|
630
|
-
reported.add(cycle);
|
|
631
655
|
const first = stack.findIndex((frame) => frame.id === from);
|
|
632
|
-
const
|
|
656
|
+
const ids = stack.slice(first).map((frame) => frame.id);
|
|
657
|
+
// A chain of circular attributes reads an approximation in the run, thus it does not close the cycle (D113).
|
|
658
|
+
if (allCircular(b, ids)) continue;
|
|
659
|
+
reported.add(cycle);
|
|
660
|
+
const chain = [...ids, from].map((id) => vertexText(b.vertices.get(id) as AttributeVertex));
|
|
633
661
|
out.push({ code: "#CYCLE!", place: e.place, reason: `The attributes form a cycle: ${chain.join(" -> ")}.`, at: b.ats[index] as readonly Key[] });
|
|
634
662
|
}
|
|
635
663
|
}
|
package/src/reference.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Envelope, type Json, type Key, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
|
|
1
|
+
import { type Envelope, type Json, type Key, docEquals, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { ask } from "./ladder.ts";
|
|
3
|
-
import { type AttributeDeclaration, type CapsRule, type Kind, type TreeContract, equationType, lookupKind } from "./world.ts";
|
|
3
|
+
import { type AttributeDeclaration, type CapsRule, type CircularRule, type Kind, type TreeContract, circularOf, equationType, lookupKind } from "./world.ts";
|
|
4
4
|
import { type PartSlot, type Split, partsOf, pathMatches } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, ROOTS, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
|
|
6
6
|
import { type AttributeRung, STD_EVALUATORS } from "./stdlib.ts";
|
|
@@ -776,7 +776,7 @@ interface RefNode {
|
|
|
776
776
|
const refNodes = /* @__PURE__ */ new WeakMap<object, RefNode>();
|
|
777
777
|
|
|
778
778
|
/** The attributes in evaluation in the reference, in the order of their demands (TREE.CYCLE). */
|
|
779
|
-
const inEvaluation: { readonly node: RefNode; readonly name: string }[] = [];
|
|
779
|
+
const inEvaluation: { readonly node: RefNode; readonly name: string; readonly circular: boolean; tentative: boolean }[] = [];
|
|
780
780
|
|
|
781
781
|
function nameOfAttribute(node: RefNode, name: string): string {
|
|
782
782
|
return `${name} of ${node.kind} at [${node.place.join(", ")}]`;
|
|
@@ -865,29 +865,159 @@ const REF_RUNGS: readonly (readonly [AttributeRung, (node: RefNode, name: string
|
|
|
865
865
|
],
|
|
866
866
|
];
|
|
867
867
|
|
|
868
|
-
/** TREE.
|
|
868
|
+
/** TREE.LADDER: the answer of the first rung that answers, or `undefined`. */
|
|
869
|
+
function ladderOf(node: RefNode, name: string): Result | undefined {
|
|
870
|
+
for (const [, rung] of REF_RUNGS) {
|
|
871
|
+
const result = rung(node, name);
|
|
872
|
+
if (result !== undefined) return result;
|
|
873
|
+
}
|
|
874
|
+
return undefined;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/** One approximation of a circular attribute in the reference, with its round (TREE.CIRCULAR). */
|
|
878
|
+
interface RefApproximation {
|
|
879
|
+
readonly result: Result | undefined;
|
|
880
|
+
readonly round: number;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* TREE.CIRCULAR: a fixed point in evaluation in the reference. The first circular attribute of a demand leads it, and
|
|
885
|
+
* each circular attribute that a demand reaches during its rounds joins it.
|
|
886
|
+
*/
|
|
887
|
+
interface RefPoint {
|
|
888
|
+
/** The index of the leader in `inEvaluation`. */
|
|
889
|
+
readonly depth: number;
|
|
890
|
+
round: number;
|
|
891
|
+
changed: boolean;
|
|
892
|
+
readonly values: Map<RefNode, Map<string, RefApproximation>>;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/** The open fixed point of the reference, or `undefined`. */
|
|
896
|
+
let refPoint: RefPoint | undefined;
|
|
897
|
+
|
|
898
|
+
/** TREE.CIRCULAR-CHECK: the declaration `circular` of an attribute of a node, or `undefined`. */
|
|
899
|
+
function circularRuleOf(node: RefNode, name: string): CircularRule | undefined {
|
|
900
|
+
const declared = termIn(node.contract?.attributes, name)?.circular;
|
|
901
|
+
return declared === undefined ? undefined : circularOf(declared);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/** TREE.CIRCULAR: two approximations are the same when two values are equal (DOC.EQUALS) or two errors have one code and reason. */
|
|
905
|
+
function sameApproximation(a: Result | undefined, b: Result | undefined): boolean {
|
|
906
|
+
if (a === undefined || b === undefined) return a === b;
|
|
907
|
+
if (a.ok && b.ok) return docEquals(a.value, b.value);
|
|
908
|
+
if (!a.ok && !b.ok) return a.error.code === b.error.code && a.error.reason === b.error.reason;
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/** TREE.CIRCULAR: the attributes above the leader read an approximation, thus their results are not final. */
|
|
913
|
+
function readApproximation(point: RefPoint): void {
|
|
914
|
+
for (const entry of inEvaluation.slice(point.depth + 1)) entry.tentative = true;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function approximationsOf(point: RefPoint, node: RefNode): Map<string, RefApproximation> {
|
|
918
|
+
const known = point.values.get(node);
|
|
919
|
+
if (known !== undefined) return known;
|
|
920
|
+
const made = new Map<string, RefApproximation>();
|
|
921
|
+
point.values.set(node, made);
|
|
922
|
+
return made;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* TREE.CIRCULAR and TREE.CIRCULAR-LIMIT: the rounds of the leader of a fixed point. A round without a new value ends
|
|
927
|
+
* the rounds, and each circular attribute of that round keeps its value. An error ends the rounds at once. The last
|
|
928
|
+
* round of the limit gives `#CYCLE!` at the place of the node of the leader.
|
|
929
|
+
*/
|
|
930
|
+
function fixedPointOf(node: RefNode, name: string, rule: CircularRule & { readonly ok: true }): Result | undefined {
|
|
931
|
+
const point: RefPoint = { depth: inEvaluation.length - 1, round: 0, changed: false, values: new Map() };
|
|
932
|
+
refPoint = point;
|
|
933
|
+
try {
|
|
934
|
+
let before: Result | undefined = ok(rule.start);
|
|
935
|
+
for (;;) {
|
|
936
|
+
point.round += 1;
|
|
937
|
+
point.changed = false;
|
|
938
|
+
const result = ladderOf(node, name);
|
|
939
|
+
if (result !== undefined && !result.ok) return result;
|
|
940
|
+
if (!sameApproximation(before, result)) point.changed = true;
|
|
941
|
+
before = result;
|
|
942
|
+
approximationsOf(point, node).set(name, { result, round: point.round });
|
|
943
|
+
if (!point.changed) {
|
|
944
|
+
for (const [member, values] of point.values) {
|
|
945
|
+
for (const [key, value] of values) if (value.round === point.round && value.result !== undefined) member.values.set(key, value.result);
|
|
946
|
+
}
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
if (point.round >= rule.limit) {
|
|
950
|
+
return fail("#CYCLE!", `The cycle of the circular attribute ${nameOfAttribute(node, name)} does not become stable within its limit of ${rule.limit} ${rule.limit === 1 ? "round" : "rounds"}.`, {
|
|
951
|
+
place: node.place,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
} finally {
|
|
956
|
+
refPoint = undefined;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** TREE.CIRCULAR: a circular attribute in the open fixed point of another leader evaluates one time in each round. */
|
|
961
|
+
function roundOf(point: RefPoint, node: RefNode, name: string, rule: CircularRule & { readonly ok: true }): Result | undefined {
|
|
962
|
+
let value = point.values.get(node)?.get(name);
|
|
963
|
+
if (value === undefined || value.round !== point.round) {
|
|
964
|
+
node.pending.add(name);
|
|
965
|
+
inEvaluation.push({ node, name, circular: true, tentative: false });
|
|
966
|
+
let result: Result | undefined;
|
|
967
|
+
try {
|
|
968
|
+
result = ladderOf(node, name);
|
|
969
|
+
} finally {
|
|
970
|
+
node.pending.delete(name);
|
|
971
|
+
inEvaluation.pop();
|
|
972
|
+
}
|
|
973
|
+
if (!sameApproximation(value === undefined ? ok(rule.start) : value.result, result)) point.changed = true;
|
|
974
|
+
value = { result, round: point.round };
|
|
975
|
+
approximationsOf(point, node).set(name, value);
|
|
976
|
+
}
|
|
977
|
+
readApproximation(point);
|
|
978
|
+
return value.result;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* TREE.ONCE, TREE.MEMO, TREE.CYCLE and TREE.CIRCULAR: the result of one attribute, or `undefined` when no rung of its
|
|
983
|
+
* ladder answers. A result that read an approximation of an open fixed point is not kept.
|
|
984
|
+
*/
|
|
869
985
|
function attributeOf(node: RefNode, name: string): Result | undefined {
|
|
870
986
|
const done = node.values.get(name);
|
|
871
987
|
if (done !== undefined) return done;
|
|
872
988
|
if (node.pending.has(name)) {
|
|
873
989
|
const from = inEvaluation.findIndex((entry) => entry.node === node && entry.name === name);
|
|
990
|
+
const point = refPoint;
|
|
991
|
+
if (point !== undefined && from >= point.depth && inEvaluation.slice(from).every((entry) => entry.circular)) {
|
|
992
|
+
readApproximation(point);
|
|
993
|
+
const kept = point.values.get(node)?.get(name);
|
|
994
|
+
if (kept !== undefined) return kept.result;
|
|
995
|
+
const rule = circularRuleOf(node, name);
|
|
996
|
+
return ok(rule?.ok === true ? rule.start : null);
|
|
997
|
+
}
|
|
874
998
|
const steps = from < 0 ? [nameOfAttribute(node, name)] : inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name));
|
|
875
999
|
return fail("#CYCLE!", `The attributes form a cycle: ${[...steps, nameOfAttribute(node, name)].join(" -> ")}.`);
|
|
876
1000
|
}
|
|
1001
|
+
const rule = circularRuleOf(node, name);
|
|
1002
|
+
if (rule !== undefined && !rule.ok) {
|
|
1003
|
+
const bad = fail("#VALUE!", `The attribute ${nameOfAttribute(node, name)} has a bad declaration circular. ${rule.reason}`);
|
|
1004
|
+
node.values.set(name, bad);
|
|
1005
|
+
return bad;
|
|
1006
|
+
}
|
|
1007
|
+
const point = refPoint;
|
|
1008
|
+
if (point !== undefined && rule !== undefined) return roundOf(point, node, name, rule);
|
|
1009
|
+
const entry = { node, name, circular: rule !== undefined, tentative: false };
|
|
877
1010
|
node.pending.add(name);
|
|
878
|
-
inEvaluation.push(
|
|
1011
|
+
inEvaluation.push(entry);
|
|
1012
|
+
let result: Result | undefined;
|
|
879
1013
|
try {
|
|
880
|
-
|
|
881
|
-
const result = rung(node, name);
|
|
882
|
-
if (result === undefined) continue;
|
|
883
|
-
node.values.set(name, result);
|
|
884
|
-
return result;
|
|
885
|
-
}
|
|
886
|
-
return undefined;
|
|
1014
|
+
result = rule === undefined ? ladderOf(node, name) : fixedPointOf(node, name, rule);
|
|
887
1015
|
} finally {
|
|
888
1016
|
node.pending.delete(name);
|
|
889
1017
|
inEvaluation.pop();
|
|
890
1018
|
}
|
|
1019
|
+
if (!entry.tentative && result !== undefined) node.values.set(name, result);
|
|
1020
|
+
return result;
|
|
891
1021
|
}
|
|
892
1022
|
|
|
893
1023
|
/** TREE.RESULT: the JSON of a node. `open` holds the nodes and the containers whose JSON is in progress. */
|
package/src/static.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
-
import { type Kind, type World, type WorldEntry, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
|
|
2
|
+
import { type Kind, type World, type WorldEntry, circularOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
|
|
3
3
|
import { ask } from "./ladder.ts";
|
|
4
4
|
import { type PartSlot, partsOf, scopesOf } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
|
|
@@ -867,7 +867,8 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
|
|
|
867
867
|
|
|
868
868
|
/**
|
|
869
869
|
* This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
|
|
870
|
-
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes.
|
|
870
|
+
* structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
|
|
871
|
+
* declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK).
|
|
871
872
|
*/
|
|
872
873
|
function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
873
874
|
const out: Issue[] = [];
|
|
@@ -878,6 +879,11 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
|
|
|
878
879
|
if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
|
|
879
880
|
}
|
|
880
881
|
}
|
|
882
|
+
// A declaration circular needs a start value and a correct limit (TREE.CIRCULAR-CHECK, D113).
|
|
883
|
+
for (const [name, declared] of Object.entries(tree?.attributes ?? {})) {
|
|
884
|
+
const rule = declared.circular === undefined ? undefined : circularOf(declared.circular);
|
|
885
|
+
if (rule !== undefined && !rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.circular. ${rule.reason}` });
|
|
886
|
+
}
|
|
881
887
|
if (kind.side === "run") {
|
|
882
888
|
out.push({ code: "#CALC!", place: [], reason: `The tree kind ${kindText} is on the run side: a node evaluates its attributes on demand, thus in no fixed order.` });
|
|
883
889
|
}
|