@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.
@@ -0,0 +1,637 @@
1
+ import { type Json, type Key, field, isKey, isRecord, typeName } from "./doc.ts";
2
+ import { type Kind, equationType } from "./world.ts";
3
+ import { ask } from "./ladder.ts";
4
+ import { isRef, walkerOf } from "./deref.ts";
5
+ import type { Machine } from "./evaluate.ts";
6
+ import { type Above, codeFactsOf, partLazy } from "./facts.ts";
7
+ import { type NodeGraph, type GraphEdge, cycles } from "./graph.ts";
8
+ import { STD_WALKERS } from "./stdlib.ts";
9
+ import {
10
+ type CheckOptions,
11
+ type DocVisit,
12
+ type Issue,
13
+ type SeenNode,
14
+ REF_CODE,
15
+ TREE_NODE,
16
+ downNames,
17
+ isStructural,
18
+ keepsSelf,
19
+ selvesOf,
20
+ walk,
21
+ } from "./static.ts";
22
+
23
+ /*
24
+ * The graph of attribute instances (R3 P5, D107). Each vertex is one attribute of one node of the decorated tree of a
25
+ * document. Each edge is one read: the attribute that the read reaches, and the attribute whose code reads it. The
26
+ * edges come from the references of the code of each attribute, and from the rungs of its ladder. A read that depends
27
+ * on the value of an attribute gets an edge with an unknown source. `check` reports each certain cycle before a run.
28
+ * The evaluator of the kernel does not use the graph (D3).
29
+ */
30
+
31
+ /** One vertex of the graph of attribute instances: one attribute of one node of the decorated tree (D107). */
32
+ export interface AttributeVertex {
33
+ /** The name of the vertex: the JSON text of the place of the node with the name of the attribute after it. */
34
+ readonly id: string;
35
+ /** The place of the document of the node. */
36
+ readonly place: readonly Key[];
37
+ readonly attribute: string;
38
+ /** The text of the type of the node. */
39
+ readonly type: string;
40
+ }
41
+
42
+ /** One read of an attribute by the code of another attribute (D107). */
43
+ export interface AttributeEdge {
44
+ /** The vertex that the read reaches. It is `undefined` when only the run knows it: the read depends on a value. */
45
+ readonly from: string | undefined;
46
+ /** The vertex whose code makes the read. */
47
+ readonly to: string;
48
+ /** `true` when only a lazy part reaches the read, or when an earlier rung can answer first (FACTS.LAZY). */
49
+ readonly lazy: boolean;
50
+ /** The place of the reference document. A read of a rung has the place of the node. */
51
+ readonly place: readonly Key[];
52
+ }
53
+
54
+ /** One cycle of the graph of attribute instances (D107). */
55
+ export interface AttributeCycle {
56
+ /** The vertices of the cycle, in the order of the graph. */
57
+ readonly vertices: readonly string[];
58
+ /** `true` when the eager reads with a known source close the cycle (GRAPH.CYCLE). */
59
+ readonly certain: boolean;
60
+ }
61
+
62
+ /** The graph of attribute instances of a document (TREE.GRAPH). */
63
+ export interface AttributeGraph {
64
+ /** The vertices, by name, in the order of the document. */
65
+ readonly vertices: ReadonlyMap<string, AttributeVertex>;
66
+ /** The edges, in the order of the code of each vertex. */
67
+ readonly edges: readonly AttributeEdge[];
68
+ /** The graph for the analysis of `graph.ts`: each vertex with the vertices that it reads. */
69
+ readonly graph: NodeGraph<string>;
70
+ /** The edges with an unknown source. The check of the cycles is not complete for them: only the run knows them. */
71
+ readonly unknown: readonly AttributeEdge[];
72
+ /** The cycles of the graph, deepest first. */
73
+ readonly cycles: readonly AttributeCycle[];
74
+ }
75
+
76
+ /** The options of `attributeGraph`. */
77
+ export interface AttributeGraphOptions {
78
+ /** The type of a bare root value. */
79
+ readonly declaredType?: string;
80
+ }
81
+
82
+ /**
83
+ * This function gives the name of a vertex of the graph of attribute instances. The name is the JSON text of the
84
+ * place of the node, with the name of the attribute after it.
85
+ *
86
+ * @param place - The place of the document of the node.
87
+ * @param attribute - The name of the attribute.
88
+ * @returns The name of the vertex.
89
+ */
90
+ export function attributeVertexId(place: readonly Key[], attribute: string): string {
91
+ return JSON.stringify([...place, attribute]);
92
+ }
93
+
94
+ /** One node of the decorated tree of the document. */
95
+ interface NodeInfo {
96
+ readonly visit: DocVisit;
97
+ readonly kind: Kind;
98
+ readonly text: string;
99
+ /** The names of the own parts, in the order of the parts. */
100
+ readonly own: readonly string[];
101
+ /** The document of each own part. */
102
+ readonly parts: Map<string, DocInfo>;
103
+ /** The parent node of the same tree. `null`: the node is the root of a tree. `undefined`: the check does not know it. */
104
+ readonly up: NodeInfo | null | undefined;
105
+ }
106
+
107
+ /** One document of the document: its code, its node, and the items of a list. */
108
+ interface DocInfo {
109
+ readonly visit: DocVisit;
110
+ readonly evaluator: string | undefined;
111
+ readonly node: NodeInfo | undefined;
112
+ /** The documents of the items of a list, by their index. */
113
+ readonly items: Map<number, DocInfo>;
114
+ }
115
+
116
+ /** The attribute that a document is a part of: the nearest node around it, and the own part of that node. */
117
+ interface Owner {
118
+ readonly node: NodeInfo;
119
+ readonly key: string;
120
+ }
121
+
122
+ /** The state of a visit: its document, its attribute and its lazy mark from the start of the attribute. */
123
+ interface VisitState {
124
+ readonly info: DocInfo;
125
+ readonly owner: Owner | undefined;
126
+ readonly lazy: boolean;
127
+ readonly above: Above;
128
+ }
129
+
130
+ /** The value that a step reaches before the run. It is a node, a list document, data, a value outside the tree, or unknown. */
131
+ type Value = { readonly node: NodeInfo } | { readonly list: DocInfo } | "data" | "outside" | "unknown";
132
+
133
+ /** One reference of an equation of a kind, for each node of the kind (D107). */
134
+ interface TermRef {
135
+ readonly path: readonly Json[];
136
+ readonly lazy: boolean;
137
+ /** `true` when the self of the reference is the node of the equation. */
138
+ readonly self: boolean;
139
+ /** The names that a kind inside the equation binds for the reference. */
140
+ readonly bound: ReadonlySet<string>;
141
+ /** `true` for code that the check does not know: its reads are not known before the run. */
142
+ readonly opaque: boolean;
143
+ }
144
+
145
+ /** The standard code whose value is the plain data of its document. */
146
+ const DATA: ReadonlySet<string> = /* @__PURE__ */ new Set(["data.number", "data.text", "data.bool"]);
147
+
148
+ /** The standard code of a list. Its value holds the value of each item, in the order of the items. */
149
+ const LIST = "list.all";
150
+
151
+ /** The self of the root of a walk: the self of the context, outside each tree of the document. */
152
+ const OUTSIDE: SeenNode = { kind: { verbs: {} }, text: "", own: undefined, parent: undefined };
153
+
154
+ /** The self of an equation, for the references of the equation. */
155
+ const EQUATION_SELF: SeenNode = { kind: { verbs: {} }, text: "", own: undefined, parent: undefined };
156
+
157
+ const NO_BOUND: ReadonlySet<string> = /* @__PURE__ */ new Set();
158
+
159
+ /** This function gives the name of an own part from the slot of its document. */
160
+ function slotKey(slot: readonly Key[]): string {
161
+ return slot.map(String).join(".");
162
+ }
163
+
164
+ /** This function gives the value of a document before the run. */
165
+ function valueOf(info: DocInfo | undefined): Value {
166
+ if (info === undefined) return "unknown";
167
+ if (info.node !== undefined) return { node: info.node };
168
+ if (info.evaluator === LIST) return { list: info };
169
+ return info.evaluator !== undefined && DATA.has(info.evaluator) ? "data" : "unknown";
170
+ }
171
+
172
+ /** This function tells if a table of a tree contract has its own entry for a name. */
173
+ function hasOwnEntry(table: Readonly<Record<string, unknown>> | undefined, name: string): boolean {
174
+ return table !== undefined && Object.hasOwn(table, name);
175
+ }
176
+
177
+ /** The builder of one graph: the vertices, the edges and the reads of the rungs. */
178
+ interface Builder {
179
+ readonly machine: Machine;
180
+ readonly vertices: Map<string, AttributeVertex>;
181
+ readonly nodes: Map<string, NodeInfo>;
182
+ readonly edges: AttributeEdge[];
183
+ /** The JSON path of the document of each edge, for an issue. */
184
+ readonly ats: (readonly Key[])[];
185
+ /** The vertices whose rungs the builder did not read yet. */
186
+ readonly work: string[];
187
+ readonly terms: Map<Kind, Map<string, readonly TermRef[]>>;
188
+ }
189
+
190
+ /** A read in progress: the vertex that reads, the lazy mark, the place and the JSON path of the read. */
191
+ interface Reader {
192
+ readonly to: string;
193
+ readonly lazy: boolean;
194
+ readonly place: readonly Key[];
195
+ readonly at: readonly Key[];
196
+ }
197
+
198
+ /** This function gives the vertex of an attribute of a node. A new vertex goes on the work list for its rungs. */
199
+ function vertexOf(b: Builder, node: NodeInfo, attribute: string): string {
200
+ const id = attributeVertexId(node.visit.place, attribute);
201
+ if (!b.vertices.has(id)) {
202
+ b.vertices.set(id, { id, place: node.visit.place, attribute, type: node.text });
203
+ b.nodes.set(id, node);
204
+ if (!node.parts.has(attribute)) b.work.push(id);
205
+ }
206
+ return id;
207
+ }
208
+
209
+ function edge(b: Builder, from: string | undefined, r: Reader, lazy = false): void {
210
+ b.edges.push({ from, to: r.to, lazy: r.lazy || lazy, place: r.place });
211
+ b.ats.push(r.at);
212
+ }
213
+
214
+ /** This function gives the items of a list document, in the order of their indices. */
215
+ function itemsOf(list: DocInfo): readonly DocInfo[] {
216
+ return [...list.items].sort(([x], [y]) => x - y).map(([, item]) => item);
217
+ }
218
+
219
+ /**
220
+ * This function gives the attributes that the JSON of a value reads, in their order (TREE.RESULT). A node reads each
221
+ * own part, with the JSON of each node in its value after it. A list gives the JSON of each item.
222
+ */
223
+ function jsonOrder(value: Value, out: [NodeInfo, string][], done: Set<NodeInfo>): void {
224
+ if (typeof value !== "object") return;
225
+ if ("list" in value) {
226
+ for (const item of itemsOf(value.list)) jsonOrder(valueOf(item), out, done);
227
+ return;
228
+ }
229
+ const node = value.node;
230
+ if (done.has(node)) return;
231
+ done.add(node);
232
+ for (const key of node.own) {
233
+ out.push([node, key]);
234
+ jsonOrder(valueOf(node.parts.get(key)), out, done);
235
+ }
236
+ }
237
+
238
+ /** This function adds the reads of the JSON of a value. */
239
+ function jsonReads(b: Builder, value: Value, r: Reader): void {
240
+ const order: [NodeInfo, string][] = [];
241
+ jsonOrder(value, order, new Set());
242
+ for (const [node, key] of order) edge(b, vertexOf(b, node, key), r);
243
+ }
244
+
245
+ /** This function gives the node that a walker `nearest` with the key `parent` reaches before the run. */
246
+ function nearestOf(start: NodeInfo, data: Json | undefined): Value {
247
+ const follow = isRecord(data) && typeof field(data, "follow") === "string" ? String(field(data, "follow")) : "parent";
248
+ if (follow !== "parent") return "unknown";
249
+ const wanted = isRecord(data) ? field(data, "type") : undefined;
250
+ let at: NodeInfo | null | undefined = isRecord(data) && field(data, "self") === true ? start : start.up;
251
+ while (at !== null && at !== undefined) {
252
+ if (at.text === wanted) return { node: at };
253
+ at = at.up;
254
+ }
255
+ return at === null ? "outside" : "unknown";
256
+ }
257
+
258
+ /**
259
+ * This function follows the steps of a path from a value, and it adds each read on the way (TREE.GRAPH). A step
260
+ * `parent` goes to the parent node, and a step `type` gives data. Another key reads an attribute. A walker `nearest`
261
+ * with the key `parent` depends only on the structure. A step after a value that only the run knows gives an edge with
262
+ * an unknown source. A path that ends at a node reads the JSON of the node.
263
+ */
264
+ function follow(b: Builder, start: Value, path: readonly Json[], from: number, r: Reader): void {
265
+ let here = start;
266
+ for (let index = from; index < path.length; index += 1) {
267
+ const step = path[index];
268
+ if (here === "unknown") {
269
+ edge(b, undefined, r);
270
+ return;
271
+ }
272
+ if (here === "data" || here === "outside") return;
273
+ if ("list" in here) {
274
+ if (!isKey(step)) {
275
+ edge(b, undefined, r);
276
+ return;
277
+ }
278
+ if (step === "length") {
279
+ here = "data";
280
+ continue;
281
+ }
282
+ const at = typeof step === "number" ? step : /^\d+$/.test(step) ? Number(step) : -1;
283
+ const item = here.list.items.get(at);
284
+ if (item === undefined) return;
285
+ here = valueOf(item);
286
+ continue;
287
+ }
288
+ const node = here.node;
289
+ if (isKey(step)) {
290
+ const key = String(step);
291
+ if (key === "parent") here = node.up === null ? "outside" : node.up === undefined ? "unknown" : { node: node.up };
292
+ else if (isStructural(key)) here = "data";
293
+ else {
294
+ edge(b, vertexOf(b, node, key), r);
295
+ here = valueOf(node.parts.get(key));
296
+ }
297
+ continue;
298
+ }
299
+ // The standard walker nearest depends only on the structure (R3 P5). Each other walker can read each attribute.
300
+ if (walkerOf(b.machine, step) !== STD_WALKERS["walk.nearest"]) {
301
+ edge(b, undefined, r);
302
+ return;
303
+ }
304
+ here = nearestOf(node, isRecord(step) ? field(step, "value") : undefined);
305
+ }
306
+ // The value of a reference to a node is the JSON of the node. A list keeps its items.
307
+ if (typeof here === "object" && "node" in here) jsonReads(b, here, r);
308
+ }
309
+
310
+ /**
311
+ * This function gives the references of an equation of a kind, with the self and the lazy mark of each (D107). It walks
312
+ * the equation with the rules of `check`, thus a reference keeps the self of the equation only through standard code.
313
+ */
314
+ function termRefs(b: Builder, kind: Kind, label: string, term: Json): readonly TermRef[] {
315
+ let byLabel = b.terms.get(kind);
316
+ if (byLabel === undefined) {
317
+ byLabel = new Map();
318
+ b.terms.set(kind, byLabel);
319
+ }
320
+ const known = byLabel.get(label);
321
+ if (known !== undefined) return known;
322
+ const out: TermRef[] = [];
323
+ const of = equationType(kind);
324
+ const selves = selvesOf(b.machine, "eval", EQUATION_SELF);
325
+ const above = new Map<DocVisit, Above>();
326
+ walk(
327
+ b.machine.world,
328
+ term,
329
+ (v) => {
330
+ const answer = ask(b.machine.world, v.declared ? {} : v.doc, "eval", v.declared ? { declaredType: typeName(v.doc.type) } : {});
331
+ if (answer.error === "unknown-kind") return "skip";
332
+ const up = v.parent === undefined ? undefined : above.get(v.parent);
333
+ const lazy = up !== undefined && v.parent !== undefined && (up.lazy || partLazy(up, v.parent, v.slot));
334
+ above.set(v, { lazy, rules: v.kind === undefined ? undefined : codeFactsOf(b.machine, v, v.kind, "eval").rules });
335
+ const { self } = selves.enter(v, answer.evaluator);
336
+ const evaluator = answer.evaluator;
337
+ if (evaluator === REF_CODE && isRef(v.doc)) {
338
+ if (v.address !== true) out.push({ path: v.doc.value, lazy, self: self === EQUATION_SELF, bound: v.names, opaque: false });
339
+ } else if (self === EQUATION_SELF && evaluator !== TREE_NODE && !keepsSelf(evaluator)) {
340
+ out.push({ path: [], lazy, self: true, bound: NO_BOUND, opaque: true });
341
+ }
342
+ return undefined;
343
+ },
344
+ of === undefined ? {} : { declaredType: of },
345
+ );
346
+ byLabel.set(label, out);
347
+ return out;
348
+ }
349
+
350
+ /** The names of the equation of a rung, before the run. */
351
+ interface TermNames {
352
+ readonly child?: NodeInfo;
353
+ readonly prev?: Value;
354
+ }
355
+
356
+ /** This function adds the reads of an equation of a rung: self is a node, and the names of the rung have their values. */
357
+ function termReads(b: Builder, refs: readonly TermRef[], self: NodeInfo, names: TermNames | undefined, r: Reader): void {
358
+ for (const ref of refs) {
359
+ const read: Reader = ref.lazy ? { ...r, lazy: true } : r;
360
+ if (ref.opaque) {
361
+ edge(b, undefined, read);
362
+ continue;
363
+ }
364
+ const root = ref.path[0];
365
+ if (root === "self") {
366
+ if (ref.self) follow(b, { node: self }, ref.path, 1, read);
367
+ else if (ref.path.length > 1) edge(b, undefined, read);
368
+ continue;
369
+ }
370
+ if (root !== "names" || ref.path.length < 2) continue;
371
+ const name = String(ref.path[1]);
372
+ // A name that a kind inside the equation binds holds a value of the run. The others are the names of the rung.
373
+ let start: Value;
374
+ if (ref.bound.has(name)) start = "unknown";
375
+ else if (names?.child !== undefined && name === "child") start = { node: names.child };
376
+ else if (names?.prev !== undefined && name === "prev") start = names.prev;
377
+ else if (name === "index") start = "data";
378
+ else if (name === "acc" || name === "item") start = "unknown";
379
+ else continue;
380
+ follow(b, start, ref.path, 2, read);
381
+ }
382
+ }
383
+
384
+ /** This function gives the previous sibling of a node in the list `children` of its parent, before the run. */
385
+ function prevOf(parent: NodeInfo, child: NodeInfo): Value {
386
+ const list = parent.parts.get("children");
387
+ if (list === undefined || list.evaluator !== LIST) return "unknown";
388
+ for (const [index, item] of list.items) {
389
+ if (item.node !== child) continue;
390
+ return index === 0 ? "data" : valueOf(list.items.get(index - 1));
391
+ }
392
+ return "data";
393
+ }
394
+
395
+ /**
396
+ * This function adds the reads of the rungs of an attribute that is not an own part, in the order of the ladder
397
+ * (TREE.LADDER). After a rung that can fail to answer, the reads of the later rungs are lazy.
398
+ */
399
+ function rungReads(b: Builder, id: string): void {
400
+ const node = b.nodes.get(id) as NodeInfo;
401
+ const key = (b.vertices.get(id) as AttributeVertex).attribute;
402
+ const tree = node.kind.tree;
403
+ const r: Reader = { to: id, lazy: false, place: node.visit.place, at: node.visit.at };
404
+ const equation = hasOwnEntry(tree?.equations, key) ? tree?.equations?.[key] : undefined;
405
+ if (equation !== undefined) {
406
+ termReads(b, termRefs(b, node.kind, `tree.equations.${key}`, equation), node, undefined, r);
407
+ return;
408
+ }
409
+ let later = r;
410
+ const up = node.up;
411
+ if (up === undefined) {
412
+ // A parent that the check does not know can answer with each name of down in the world (WORLD.OPEN).
413
+ if (downNames(b.machine.world).has(key)) {
414
+ edge(b, undefined, r);
415
+ later = { ...r, lazy: true };
416
+ }
417
+ } else if (up !== null) {
418
+ const down = hasOwnEntry(up.kind.tree?.down, key) ? up.kind.tree?.down?.[key] : undefined;
419
+ if (down !== undefined) {
420
+ edge(b, vertexOf(b, up, "children"), r);
421
+ termReads(b, termRefs(b, up.kind, `tree.down.${key}`, down), up, { child: node, prev: prevOf(up, node) }, r);
422
+ return;
423
+ }
424
+ }
425
+ const declared = hasOwnEntry(tree?.attributes, key) ? tree?.attributes?.[key] : undefined;
426
+ if (declared?.copy === true && up !== null) {
427
+ edge(b, up === undefined ? undefined : vertexOf(b, up, key), later);
428
+ later = { ...later, lazy: true };
429
+ }
430
+ const fold = declared?.fold;
431
+ if (fold !== undefined) {
432
+ termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.fold.unit`, fold.unit), node, undefined, later);
433
+ edge(b, vertexOf(b, node, "children"), later);
434
+ const children = valueOf(node.parts.get("children"));
435
+ let combined = false;
436
+ if (children === "unknown") edge(b, undefined, later);
437
+ else if (typeof children === "object" && "list" in children) {
438
+ for (const item of itemsOf(children.list)) {
439
+ const child = item.node;
440
+ if (child === undefined) continue;
441
+ edge(b, vertexOf(b, child, key), later);
442
+ combined = true;
443
+ }
444
+ }
445
+ // The combine evaluates once for each child node, thus without a known child node its reads are lazy.
446
+ termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.fold.combine`, fold.combine), node, undefined, combined ? later : { ...later, lazy: true });
447
+ return;
448
+ }
449
+ if (declared?.default !== undefined) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, undefined, later);
450
+ }
451
+
452
+ /** The vertices and the edges of a document, and the roots of the demand of the run: the own parts of each tree root. */
453
+ interface Built {
454
+ readonly b: Builder;
455
+ readonly roots: readonly NodeInfo[];
456
+ }
457
+
458
+ function build(machine: Machine, doc: unknown, declaredType: string | undefined): Built {
459
+ const b: Builder = { machine, vertices: new Map(), nodes: new Map(), edges: [], ats: [], work: [], terms: new Map() };
460
+ const roots: NodeInfo[] = [];
461
+ const all: NodeInfo[] = [];
462
+ const reads: { readonly owner: Owner; readonly v: DocVisit; readonly lazy: boolean; readonly self: Value; readonly opaque: boolean }[] = [];
463
+ const states = new Map<DocVisit, VisitState>();
464
+ const nodes = new Map<SeenNode, NodeInfo>();
465
+ const selves = selvesOf(machine, "eval", OUTSIDE);
466
+ walk(
467
+ machine.world,
468
+ doc,
469
+ (v) => {
470
+ const answer = ask(machine.world, v.declared ? {} : v.doc, "eval", v.declared ? { declaredType: typeName(v.doc.type) } : {});
471
+ if (answer.error === "unknown-kind") return "skip";
472
+ const evaluator = answer.evaluator;
473
+ const parent = v.parent === undefined ? undefined : states.get(v.parent);
474
+ const { self, node: seen } = selves.enter(v, evaluator);
475
+ let owner: Owner | undefined;
476
+ let lazy = false;
477
+ if (parent?.info.node !== undefined) owner = { node: parent.info.node, key: slotKey(v.slot) };
478
+ else if (parent !== undefined && v.parent !== undefined) {
479
+ owner = parent.owner;
480
+ lazy = parent.lazy || partLazy(parent.above, v.parent, v.slot);
481
+ }
482
+ let node: NodeInfo | undefined;
483
+ if (seen !== undefined && v.kind !== undefined) {
484
+ const up = self === OUTSIDE ? null : self === undefined ? undefined : nodes.get(self);
485
+ node = { visit: v, kind: v.kind, text: seen.text, own: [...(seen.own ?? [])], parts: new Map(), up };
486
+ nodes.set(seen, node);
487
+ all.push(node);
488
+ // The run reads each own part of a tree root that an eager position holds (TREE.RESULT).
489
+ if (up === null && !lazy) roots.push(node);
490
+ }
491
+ const info: DocInfo = { visit: v, evaluator, node, items: new Map() };
492
+ if (parent?.info.node !== undefined) parent.info.node.parts.set(slotKey(v.slot), info);
493
+ else if (parent?.info.evaluator === LIST && v.slot.length === 1 && typeof v.slot[0] === "number") parent.info.items.set(v.slot[0], info);
494
+ states.set(v, { info, owner, lazy, above: { lazy, rules: v.kind === undefined ? undefined : codeFactsOf(machine, v, v.kind, "eval").rules } });
495
+ if (owner === undefined || node !== undefined) return undefined;
496
+ const value: Value = self === OUTSIDE ? "outside" : self === undefined ? "unknown" : { node: nodes.get(self) as NodeInfo };
497
+ if (evaluator === REF_CODE && isRef(v.doc)) {
498
+ if (v.address !== true) reads.push({ owner, v, lazy, self: value, opaque: false });
499
+ } else if (value !== "unknown" && evaluator !== REF_CODE && !keepsSelf(evaluator)) {
500
+ // Code that the check does not know can read each attribute of its self.
501
+ reads.push({ owner, v, lazy, self: value, opaque: true });
502
+ }
503
+ return undefined;
504
+ },
505
+ declaredType === undefined ? {} : { declaredType },
506
+ );
507
+ for (const node of all) for (const key of node.own) vertexOf(b, node, key);
508
+ for (const { owner, v, lazy, self, opaque } of reads) {
509
+ const r: Reader = { to: vertexOf(b, owner.node, owner.key), lazy, place: v.place, at: v.at };
510
+ if (opaque) {
511
+ edge(b, undefined, r);
512
+ continue;
513
+ }
514
+ const path = v.doc.value as readonly Json[];
515
+ const root = path[0];
516
+ if (root === "self") {
517
+ if (self === "unknown") edge(b, undefined, r);
518
+ else follow(b, self, path, 1, r);
519
+ } else if (root === "names" && path.length >= 2 && v.names.has(String(path[1]))) {
520
+ // A name that a kind binds holds a value of the run, and that value can hold a node.
521
+ edge(b, undefined, r);
522
+ }
523
+ }
524
+ for (let next = b.work.shift(); next !== undefined; next = b.work.shift()) rungReads(b, next);
525
+ return { b, roots };
526
+ }
527
+
528
+ /** This function gives the graph for `graph.ts`: each vertex with the vertices that it reads. */
529
+ function nodeGraph(b: Builder): NodeGraph<string> {
530
+ const reads = new Map<string, GraphEdge<string>[]>();
531
+ for (const id of b.vertices.keys()) reads.set(id, []);
532
+ for (const e of b.edges) reads.get(e.to)?.push({ to: e.from, lazy: e.lazy });
533
+ return new Map([...reads].map(([id, edges]) => [id, { edges }] as const));
534
+ }
535
+
536
+ /**
537
+ * This function gives the graph of attribute instances of a document (TREE.GRAPH, R3 P5, D107). It has one vertex for
538
+ * each attribute of each node of the decorated tree. It has one edge for each read of the code of an attribute.
539
+ *
540
+ * - A path from `self` reads the attribute of the node. `self.parent` goes to the parent node.
541
+ * - The step `nearest` with the key `parent` depends only on the structure, thus it goes to a node of the document.
542
+ * - The rungs of the ladder read too: `names.prev` of `down` reads the previous sibling, and a fold reads each child.
543
+ * - A read that depends on the value of an attribute gets an edge with an unknown source.
544
+ *
545
+ * The walk is the walk of `check`, with its rules for the self of each document (D106). The evaluator of the kernel
546
+ * does not use the graph (D3).
547
+ *
548
+ * @param machine - The world and the code of the evaluators.
549
+ * @param doc - The document, as JSON.
550
+ * @param options - The type of a bare root value.
551
+ * @returns The vertices, the edges, the graph for the analysis, the reads that only the run knows and the cycles.
552
+ */
553
+ export function attributeGraph(machine: Machine, doc: unknown, options: AttributeGraphOptions = {}): AttributeGraph {
554
+ const { b } = build(machine, doc, options.declaredType);
555
+ const graph = nodeGraph(b);
556
+ return {
557
+ vertices: b.vertices,
558
+ edges: b.edges,
559
+ graph,
560
+ unknown: b.edges.filter((e) => e.from === undefined),
561
+ cycles: cycles(graph).map((cycle) => ({ vertices: cycle.nodes, certain: cycle.certain })),
562
+ };
563
+ }
564
+
565
+ function vertexText(vertex: AttributeVertex): string {
566
+ return `${vertex.attribute} of ${vertex.type} at [${vertex.place.join(", ")}]`;
567
+ }
568
+
569
+ /**
570
+ * This function finds each certain cycle of the attributes of a document before a run (TREE.GRAPH-CYCLE, D107). A run
571
+ * that gives a value reads each own part of each tree root, and each eager read of each attribute that it reads. Thus a
572
+ * certain cycle that these reads reach always gives `#CYCLE!`. The issue gets the place of the read that closes the
573
+ * 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
+ *
575
+ * @param machine - The world and the code of the evaluators.
576
+ * @param doc - The document, as JSON.
577
+ * @param options - The type of a bare root value.
578
+ * @returns One issue for each certain cycle that the run reaches.
579
+ */
580
+ export function cycleIssues(machine: Machine, doc: unknown, options: CheckOptions): readonly Issue[] {
581
+ const { b, roots } = build(machine, doc, options.declaredType);
582
+ const graph = nodeGraph(b);
583
+ const certain = new Map<string, number>();
584
+ cycles(graph).forEach((cycle, index) => {
585
+ if (cycle.certain) for (const id of cycle.nodes) certain.set(id, index);
586
+ });
587
+ if (certain.size === 0) return [];
588
+ // The eager reads with a known source of each vertex, in the order of its code, with the index of each edge.
589
+ const eager = new Map<string, number[]>();
590
+ b.edges.forEach((e, index) => {
591
+ if (e.lazy || e.from === undefined) return;
592
+ const list = eager.get(e.to);
593
+ if (list === undefined) eager.set(e.to, [index]);
594
+ else list.push(index);
595
+ });
596
+ // The demand of the run: the own parts of each tree root in the order of the JSON, then the reads of each attribute.
597
+ const demands: [NodeInfo, string][] = [];
598
+ const done = new Set<NodeInfo>();
599
+ for (const root of roots) jsonOrder({ node: root }, demands, done);
600
+ const order = demands.map(([node, key]) => attributeVertexId(node.visit.place, key));
601
+ const state = new Map<string, "busy" | "done">();
602
+ const reported = new Set<number>();
603
+ const out: Issue[] = [];
604
+ for (const start of order) {
605
+ if (state.has(start)) continue;
606
+ const stack: { readonly id: string; next: number }[] = [{ id: start, next: 0 }];
607
+ state.set(start, "busy");
608
+ while (stack.length > 0) {
609
+ const top = stack[stack.length - 1] as { readonly id: string; next: number };
610
+ const list = eager.get(top.id) ?? [];
611
+ if (top.next >= list.length) {
612
+ state.set(top.id, "done");
613
+ stack.pop();
614
+ continue;
615
+ }
616
+ const index = list[top.next] as number;
617
+ top.next += 1;
618
+ const e = b.edges[index] as AttributeEdge;
619
+ const from = e.from as string;
620
+ const seen = state.get(from);
621
+ if (seen === undefined) {
622
+ state.set(from, "busy");
623
+ stack.push({ id: from, next: 0 });
624
+ continue;
625
+ }
626
+ if (seen === "done") continue;
627
+ // The read reaches an attribute in evaluation: the run gives #CYCLE! here, with the chain of the demands.
628
+ const cycle = certain.get(from);
629
+ if (cycle === undefined || reported.has(cycle)) continue;
630
+ reported.add(cycle);
631
+ const first = stack.findIndex((frame) => frame.id === from);
632
+ const chain = [...stack.slice(first).map((frame) => frame.id), from].map((id) => vertexText(b.vertices.get(id) as AttributeVertex));
633
+ out.push({ code: "#CYCLE!", place: e.place, reason: `The attributes form a cycle: ${chain.join(" -> ")}.`, at: b.ats[index] as readonly Key[] });
634
+ }
635
+ }
636
+ return out;
637
+ }