@mark1russell7/coeval 0.7.1 → 0.8.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/build/index.d.ts +175 -96
- package/build/index.d.ts.map +1 -1
- package/build/index.js +762 -127
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/agree.ts +26 -5
- package/src/conform.ts +1 -2
- package/src/doc.ts +2 -2
- package/src/evaluate.ts +273 -7
- package/src/index.ts +17 -1
- package/src/parts.ts +70 -1
- package/src/reference.ts +139 -16
- package/src/static.ts +54 -74
- package/src/stdlib.ts +220 -18
- package/src/world.ts +50 -0
package/src/reference.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, isEnvelope, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { ask } from "./ladder.ts";
|
|
3
|
-
import type
|
|
3
|
+
import { type AttributeDeclaration, type CapsRule, type Kind, type TreeContract, 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, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
|
|
6
|
-
import { STD_EVALUATORS } from "./stdlib.ts";
|
|
6
|
+
import { type AttributeRung, STD_EVALUATORS } from "./stdlib.ts";
|
|
7
7
|
import { type EvalError, type Failed, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
|
|
8
8
|
import {
|
|
9
9
|
type AllOptions,
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
type RunInput,
|
|
18
18
|
type RunPart,
|
|
19
19
|
type Steps,
|
|
20
|
-
intersectCaps,
|
|
21
20
|
makeContext,
|
|
22
21
|
permits,
|
|
23
22
|
placeText,
|
|
@@ -150,13 +149,23 @@ function examine(env: Env, at: At): Seen | Failed {
|
|
|
150
149
|
return { doc, kindText, typePath: read.typePath ?? [], kind, name, chosen, split };
|
|
151
150
|
}
|
|
152
151
|
|
|
152
|
+
/**
|
|
153
|
+
* This function gives the effects that two lists of capabilities both permit (CAP.ONLY). The reference reads the rule
|
|
154
|
+
* from SPEC directly, and not from the intersection of the plan. Thus `agree` finds a bug in either one (D15).
|
|
155
|
+
*/
|
|
156
|
+
function bothPermit(a: readonly Cap[], b: readonly Cap[]): readonly Cap[] {
|
|
157
|
+
const out: Cap[] = [];
|
|
158
|
+
for (const cap of [...a, ...b]) if (permits(a, cap) && permits(b, cap) && !permits(out, cap)) out.push(cap);
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
153
162
|
/** This function gives the capabilities that the rules of a kind give one part (D14, CAP.CONTRACT, CAP.ONLY). */
|
|
154
163
|
function capsOfPart(rules: readonly CapsRule[] | undefined, path: readonly Key[], caps: readonly Cap[]): readonly Cap[] {
|
|
155
164
|
let out = caps;
|
|
156
165
|
for (const rule of rules ?? []) {
|
|
157
166
|
if (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(path, pattern))) continue;
|
|
158
167
|
if (rule.rule === "mask") return [];
|
|
159
|
-
if (rule.rule === "only") out =
|
|
168
|
+
if (rule.rule === "only") out = bothPermit(out, rule.caps);
|
|
160
169
|
}
|
|
161
170
|
return out;
|
|
162
171
|
}
|
|
@@ -420,13 +429,20 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
|
|
|
420
429
|
scope: { prefix: "", counts: new Map(), alls: 0 },
|
|
421
430
|
};
|
|
422
431
|
const ctx = context.world === null ? { ...context, world: worldThing(machine.world) } : context;
|
|
432
|
+
// A node of a tree reads the definitions of this evaluation for its equations (TREE.LADDER).
|
|
433
|
+
activeEnvs.push(env);
|
|
423
434
|
try {
|
|
424
435
|
return evaluateAt(env, { value: doc, place: [], declared: options.declaredType, depth: 0, address: false, ctx });
|
|
425
436
|
} catch (error) {
|
|
426
437
|
return placed(thrown(error, "the reference"), []);
|
|
438
|
+
} finally {
|
|
439
|
+
activeEnvs.pop();
|
|
427
440
|
}
|
|
428
441
|
}
|
|
429
442
|
|
|
443
|
+
/** The evaluations of the reference that run at this time, the innermost last. */
|
|
444
|
+
const activeEnvs: Env[] = [];
|
|
445
|
+
|
|
430
446
|
/*
|
|
431
447
|
* The definition of the decorated tree (TREE.*), written from docs/SPEC.md. It shares no code with `tree.node` of
|
|
432
448
|
* stdlib.ts: its nodes, its memo, its marks for attributes in evaluation and its JSON are its own.
|
|
@@ -444,6 +460,16 @@ interface RefNode {
|
|
|
444
460
|
readonly place: readonly Key[];
|
|
445
461
|
readonly parts: readonly Part[];
|
|
446
462
|
readonly ctx: Context;
|
|
463
|
+
/** The tree contract of the kind of the node (D42). */
|
|
464
|
+
readonly contract: TreeContract | undefined;
|
|
465
|
+
/** The type of a bare equation: the type `of` of the parts of the kind. */
|
|
466
|
+
readonly of: string | undefined;
|
|
467
|
+
/** The parent node in the same tree, for the rungs `parent` and `copy`. */
|
|
468
|
+
readonly up: RefNode | undefined;
|
|
469
|
+
/** The evaluation of the reference that made the node. Its equations evaluate with its definitions. */
|
|
470
|
+
readonly env: Env | undefined;
|
|
471
|
+
/** The host thing of the node. */
|
|
472
|
+
thing: HostThing;
|
|
447
473
|
/** The results of the attributes that ended. */
|
|
448
474
|
readonly values: Map<string, Result>;
|
|
449
475
|
/** The attributes in evaluation. */
|
|
@@ -463,8 +489,86 @@ function refNodeOf(thing: unknown): RefNode | undefined {
|
|
|
463
489
|
return typeof thing === "object" && thing !== null ? refNodes.get(thing) : undefined;
|
|
464
490
|
}
|
|
465
491
|
|
|
466
|
-
/**
|
|
467
|
-
|
|
492
|
+
/**
|
|
493
|
+
* TREE.LADDER: an equation of the kind of a node evaluates in the reference. It gets the node as `self`, the names of
|
|
494
|
+
* its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind.
|
|
495
|
+
*/
|
|
496
|
+
function equationOf(node: RefNode, term: Json, names: Readonly<Record<string, unknown>>): Result {
|
|
497
|
+
const env = node.env;
|
|
498
|
+
if (env === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
|
|
499
|
+
const ctx: Context = { ...node.ctx, names, caps: [], self: node.thing };
|
|
500
|
+
return reference(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function termIn<T>(table: Readonly<Record<string, T>> | undefined, name: string): T | undefined {
|
|
504
|
+
return table === undefined || !Object.hasOwn(table, name) ? undefined : table[name];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** TREE.DOWN: the names of a child for an equation of `down` of its parent: `child`, `index` and `prev`. */
|
|
508
|
+
function namesOfChild(up: RefNode, child: RefNode): Result {
|
|
509
|
+
const children = attributeOf(up, "children");
|
|
510
|
+
if (children !== undefined && !children.ok) return children;
|
|
511
|
+
const list: readonly unknown[] = children !== undefined && Array.isArray(children.value) ? children.value : [];
|
|
512
|
+
const index = list.indexOf(child.thing);
|
|
513
|
+
return ok({ child: child.thing, index: index < 0 ? null : index, prev: index > 0 ? list[index - 1] : null });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** TREE.FOLD: the unit, then `combine` with the attribute of each node of this tree in the list `children`. */
|
|
517
|
+
function foldOf(node: RefNode, name: string, fold: NonNullable<AttributeDeclaration["fold"]>): Result {
|
|
518
|
+
let acc = equationOf(node, fold.unit, {});
|
|
519
|
+
if (!acc.ok) return acc;
|
|
520
|
+
const children = attributeOf(node, "children");
|
|
521
|
+
if (children === undefined) return acc;
|
|
522
|
+
if (!children.ok) return children;
|
|
523
|
+
if (!Array.isArray(children.value)) return fail("#VALUE!", `The fold of ${name} needs a list in children.`);
|
|
524
|
+
for (const item of children.value) {
|
|
525
|
+
const child = refNodeOf(item);
|
|
526
|
+
if (child === undefined || child.tree !== node.tree) continue;
|
|
527
|
+
const value = attributeOf(child, name);
|
|
528
|
+
if (value === undefined) return fail("#N/A", `No rung gives ${nameOfAttribute(child, name)}.`);
|
|
529
|
+
if (!value.ok) return value;
|
|
530
|
+
acc = equationOf(node, fold.combine, { acc: acc.value, item: value.value });
|
|
531
|
+
if (!acc.ok) return acc;
|
|
532
|
+
}
|
|
533
|
+
return acc;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* TREE.LADDER: the rungs of the ladder of an attribute, in their order. A rung gives `undefined` when it does not
|
|
538
|
+
* answer, and the next rung then tries.
|
|
539
|
+
*/
|
|
540
|
+
const REF_RUNGS: readonly (readonly [AttributeRung, (node: RefNode, name: string) => Result | undefined])[] = [
|
|
541
|
+
["own", (node, name) => node.parts.find((item) => item.key === name)?.evaluate({ ...node.ctx, self: node.thing })],
|
|
542
|
+
[
|
|
543
|
+
"kind",
|
|
544
|
+
(node, name) => {
|
|
545
|
+
const term = termIn(node.contract?.equations, name);
|
|
546
|
+
return term === undefined ? undefined : equationOf(node, term, {});
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
[
|
|
550
|
+
"parent",
|
|
551
|
+
(node, name) => {
|
|
552
|
+
const up = node.up;
|
|
553
|
+
const term = up === undefined ? undefined : termIn(up.contract?.down, name);
|
|
554
|
+
if (up === undefined || term === undefined) return undefined;
|
|
555
|
+
const names = namesOfChild(up, node);
|
|
556
|
+
return names.ok ? equationOf(up, term, names.value as Readonly<Record<string, unknown>>) : names;
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
["copy", (node, name) => (node.up !== undefined && termIn(node.contract?.attributes, name)?.copy === true ? attributeOf(node.up, name) : undefined)],
|
|
560
|
+
[
|
|
561
|
+
"default",
|
|
562
|
+
(node, name) => {
|
|
563
|
+
const declared = termIn(node.contract?.attributes, name);
|
|
564
|
+
if (declared?.fold !== undefined) return foldOf(node, name, declared.fold);
|
|
565
|
+
return declared?.default === undefined ? undefined : equationOf(node, declared.default, {});
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
];
|
|
569
|
+
|
|
570
|
+
/** TREE.ONCE, TREE.MEMO and TREE.CYCLE: the result of one attribute, or `undefined` when no rung of its ladder answers. */
|
|
571
|
+
function attributeOf(node: RefNode, name: string): Result | undefined {
|
|
468
572
|
const done = node.values.get(name);
|
|
469
573
|
if (done !== undefined) return done;
|
|
470
574
|
if (node.pending.has(name)) {
|
|
@@ -472,14 +576,16 @@ function attributeOf(node: RefNode, self: HostThing, name: string): Result | und
|
|
|
472
576
|
const steps = from < 0 ? [nameOfAttribute(node, name)] : inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name));
|
|
473
577
|
return fail("#CYCLE!", `The attributes form a cycle: ${[...steps, nameOfAttribute(node, name)].join(" -> ")}.`);
|
|
474
578
|
}
|
|
475
|
-
const part = node.parts.find((item) => item.key === name);
|
|
476
|
-
if (part === undefined) return undefined;
|
|
477
579
|
node.pending.add(name);
|
|
478
580
|
inEvaluation.push({ node, name });
|
|
479
581
|
try {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
582
|
+
for (const [, rung] of REF_RUNGS) {
|
|
583
|
+
const result = rung(node, name);
|
|
584
|
+
if (result === undefined) continue;
|
|
585
|
+
node.values.set(name, result);
|
|
586
|
+
return result;
|
|
587
|
+
}
|
|
588
|
+
return undefined;
|
|
483
589
|
} finally {
|
|
484
590
|
node.pending.delete(name);
|
|
485
591
|
inEvaluation.pop();
|
|
@@ -490,7 +596,7 @@ function attributeOf(node: RefNode, self: HostThing, name: string): Result | und
|
|
|
490
596
|
function jsonOfNode(node: RefNode, self: HostThing, open: readonly object[]): Result {
|
|
491
597
|
const record: Record<string, unknown> = {};
|
|
492
598
|
for (const part of node.parts) {
|
|
493
|
-
const result = attributeOf(node,
|
|
599
|
+
const result = attributeOf(node, part.key) as Result;
|
|
494
600
|
if (!result.ok) return result;
|
|
495
601
|
const value = plainValue(result.value, [...open, self]);
|
|
496
602
|
if (!value.ok) return value;
|
|
@@ -530,10 +636,26 @@ function plainValue(value: unknown, open: readonly object[]): Result {
|
|
|
530
636
|
* TREE.LAZY, TREE.PARENT, TREE.SIBLING and TREE.ERROR: a node of an open tree gives its thing, and the root of a new
|
|
531
637
|
* tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error.
|
|
532
638
|
*/
|
|
533
|
-
const treeDefinition: EvalFn = ({ parts, context, kind, place }) => {
|
|
639
|
+
const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath }) => {
|
|
534
640
|
const outer = refNodeOf(context.self);
|
|
535
641
|
const nested = outer !== undefined && outer.tree.open;
|
|
536
|
-
const
|
|
642
|
+
const found = machine === undefined || typePath.length === 0 ? undefined : lookupKind(machine.world, typePath);
|
|
643
|
+
const contract = found?.ok === true ? found.kind.tree : undefined;
|
|
644
|
+
const of = contract === undefined || found?.ok !== true ? undefined : equationType(found.kind);
|
|
645
|
+
const node: RefNode = {
|
|
646
|
+
tree: nested ? outer.tree : { open: true },
|
|
647
|
+
kind,
|
|
648
|
+
place,
|
|
649
|
+
parts,
|
|
650
|
+
ctx: context,
|
|
651
|
+
contract,
|
|
652
|
+
of,
|
|
653
|
+
up: nested ? outer : undefined,
|
|
654
|
+
env: activeEnvs[activeEnvs.length - 1],
|
|
655
|
+
thing: null as unknown as HostThing,
|
|
656
|
+
values: new Map(),
|
|
657
|
+
pending: new Set(),
|
|
658
|
+
};
|
|
537
659
|
const self: HostThing & { readonly type: string } = {
|
|
538
660
|
[THING]: true,
|
|
539
661
|
type: kind,
|
|
@@ -541,17 +663,18 @@ const treeDefinition: EvalFn = ({ parts, context, kind, place }) => {
|
|
|
541
663
|
deref: (key): Thing | undefined => {
|
|
542
664
|
if (key === "parent") return context.self;
|
|
543
665
|
if (key === "type") return kind;
|
|
544
|
-
const result = attributeOf(node,
|
|
666
|
+
const result = attributeOf(node, String(key));
|
|
545
667
|
if (result === undefined || result.ok) return result?.ok === true ? result.value : undefined;
|
|
546
668
|
throw new Failure(result.error);
|
|
547
669
|
},
|
|
548
|
-
has: (key) => key === "parent" || key === "type" || parts.some((part) => part.key === String(key)),
|
|
670
|
+
has: (key) => key === "parent" || key === "type" || parts.some((part) => part.key === String(key)) || node.values.has(String(key)),
|
|
549
671
|
toJson: () => {
|
|
550
672
|
const result = jsonOfNode(node, self, []);
|
|
551
673
|
if (result.ok) return result.value;
|
|
552
674
|
throw new Failure(result.error);
|
|
553
675
|
},
|
|
554
676
|
};
|
|
677
|
+
node.thing = self;
|
|
555
678
|
refNodes.set(self, node);
|
|
556
679
|
if (nested) return ok(self);
|
|
557
680
|
try {
|
package/src/static.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type Envelope, type Json, type Key,
|
|
2
|
-
import type
|
|
1
|
+
import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
+
import { type Kind, type World, equationType } from "./world.ts";
|
|
3
3
|
import { ask } from "./ladder.ts";
|
|
4
|
-
import { type PartSlot, partsOf,
|
|
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";
|
|
6
6
|
import type { ErrorCode } from "./errors.ts";
|
|
7
7
|
import { type Machine, partCaps, permits, waitedReason } from "./evaluate.ts";
|
|
@@ -67,75 +67,6 @@ export interface WalkOptions {
|
|
|
67
67
|
readonly problem?: (problem: WalkProblem) => void;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function valueAt(value: unknown, path: readonly Key[]): unknown {
|
|
71
|
-
let here: unknown = value;
|
|
72
|
-
for (const key of path) {
|
|
73
|
-
if (Array.isArray(here)) here = here[Number(key)];
|
|
74
|
-
else if (isRecord(here) && Object.hasOwn(here, String(key))) here = here[String(key)];
|
|
75
|
-
else return undefined;
|
|
76
|
-
}
|
|
77
|
-
return here;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function headerName(item: Json): string | undefined {
|
|
81
|
-
return isEnvelope(item) && typeof (item as { readonly name?: unknown }).name === "string" ? (item as { readonly name: string }).name : undefined;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function namesOf(source: NamesSource, value: unknown, covered: readonly PartSlot[]): readonly string[] {
|
|
85
|
-
if ("fixed" in source) return source.fixed;
|
|
86
|
-
if ("keysAt" in source) {
|
|
87
|
-
const at = valueAt(value, source.keysAt);
|
|
88
|
-
return isRecord(at) ? Object.keys(at) : [];
|
|
89
|
-
}
|
|
90
|
-
if ("listAt" in source) {
|
|
91
|
-
const at = valueAt(value, source.listAt);
|
|
92
|
-
return Array.isArray(at) ? (at as readonly unknown[]).filter((item): item is string => typeof item === "string") : [];
|
|
93
|
-
}
|
|
94
|
-
if ("textAt" in source) {
|
|
95
|
-
const at = valueAt(value, source.textAt);
|
|
96
|
-
if (typeof at === "string") return [at];
|
|
97
|
-
return source.otherwise === undefined ? [] : [source.otherwise];
|
|
98
|
-
}
|
|
99
|
-
return covered.map((slot) => headerName(slot.item)).filter((name): name is string => name !== undefined);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* This function gives the names that the scope rules of a kind give each part, and the names that only later parts
|
|
104
|
-
* see. `check` and `conforms` read the scope rules with it.
|
|
105
|
-
*
|
|
106
|
-
* @param rules - The scope rules of the kind.
|
|
107
|
-
* @param value - The value of the document.
|
|
108
|
-
* @param slots - The parts of the value, in order.
|
|
109
|
-
* @returns The names that each part sees, and the names that only the later parts see, by the index of the part.
|
|
110
|
-
*/
|
|
111
|
-
export function scopesOf(rules: readonly ScopeRule[] | undefined, value: unknown, slots: readonly PartSlot[]): { readonly sees: readonly (readonly string[])[]; readonly later: readonly (readonly string[])[] } {
|
|
112
|
-
const sees: string[][] = slots.map(() => []);
|
|
113
|
-
const later: string[][] = slots.map(() => []);
|
|
114
|
-
for (const rule of rules ?? []) {
|
|
115
|
-
const covered = slots.map((slot, index) => ({ slot, index })).filter(({ slot }) => rule.in.some((pattern) => pathMatches(slot.path, pattern)));
|
|
116
|
-
if (rule.sequential !== true) {
|
|
117
|
-
const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
|
|
118
|
-
for (const { index } of covered) sees[index]?.push(...names);
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if ("headers" in rule.names) {
|
|
122
|
-
covered.forEach(({ index }, position) => {
|
|
123
|
-
const before = covered.slice(0, position).map(({ slot }) => headerName(slot.item)).filter((name): name is string => name !== undefined);
|
|
124
|
-
const after = covered.slice(position).map(({ slot }) => headerName(slot.item)).filter((name): name is string => name !== undefined);
|
|
125
|
-
sees[index]?.push(...before);
|
|
126
|
-
later[index]?.push(...after);
|
|
127
|
-
});
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
|
|
131
|
-
covered.forEach(({ index }, position) => {
|
|
132
|
-
if (position > 0) sees[index]?.push(...names);
|
|
133
|
-
else later[index]?.push(...names);
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
return { sees, later };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
70
|
/** 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. */
|
|
140
71
|
const NO_NAMES: ReadonlySet<string> = /* @__PURE__ */ new Set();
|
|
141
72
|
|
|
@@ -595,11 +526,55 @@ interface Drive {
|
|
|
595
526
|
/** What the code of a document does with a wait: an evaluator stops it, and a runner passes it on. */
|
|
596
527
|
type Waits = boolean | Drive;
|
|
597
528
|
|
|
529
|
+
/** The names that an equation of `down` sees (TREE.DOWN). */
|
|
530
|
+
const DOWN_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["child", "index", "prev"]);
|
|
531
|
+
|
|
532
|
+
/** The names that the term `combine` of a fold sees (TREE.FOLD). */
|
|
533
|
+
const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "item"]);
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
|
|
537
|
+
* `equations`, a `unit` and a `default` see no names.
|
|
538
|
+
*/
|
|
539
|
+
function equationsOf(kind: Kind): readonly { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] {
|
|
540
|
+
const tree = kind.tree;
|
|
541
|
+
if (tree === undefined) return [];
|
|
542
|
+
const out: { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] = [];
|
|
543
|
+
for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({ label: `tree.equations.${name}`, term, names: [] });
|
|
544
|
+
for (const [name, term] of Object.entries(tree.down ?? {})) out.push({ label: `tree.down.${name}`, term, names: DOWN_NAMES });
|
|
545
|
+
for (const [name, declared] of Object.entries(tree.attributes ?? {})) {
|
|
546
|
+
if (declared.fold !== undefined) {
|
|
547
|
+
out.push({ label: `tree.attributes.${name}.fold.unit`, term: declared.fold.unit, names: [] });
|
|
548
|
+
out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES });
|
|
549
|
+
}
|
|
550
|
+
if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [] });
|
|
551
|
+
}
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* This function checks the equations of the tree contract of a kind, each with the names of its rung (TREE.DOWN-NAMES).
|
|
557
|
+
* An equation evaluates without capabilities, thus a check with capabilities gives it none. Each issue gets the place
|
|
558
|
+
* of the document of the kind, and its reason names the equation.
|
|
559
|
+
*/
|
|
560
|
+
function equationIssues(machine: Machine, kind: Kind, kindText: string, options: CheckOptions, seen: Set<Kind>): readonly Issue[] {
|
|
561
|
+
const of = equationType(kind);
|
|
562
|
+
const out: Issue[] = [];
|
|
563
|
+
for (const { label, term, names } of equationsOf(kind)) {
|
|
564
|
+
const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
|
|
565
|
+
for (const issue of checkAll(machine, term, inner, undefined, seen)) {
|
|
566
|
+
out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return out;
|
|
570
|
+
}
|
|
571
|
+
|
|
598
572
|
/**
|
|
599
573
|
* This function finds the issues of `check`. With a list of nodes, it also records each document that it visits for
|
|
600
|
-
* the pass of types. It marks each document with an issue.
|
|
574
|
+
* the pass of types. It marks each document with an issue. The set `seen` holds the kinds whose equations the check
|
|
575
|
+
* walked, thus it walks the equations of each kind one time.
|
|
601
576
|
*/
|
|
602
|
-
function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined): readonly Issue[] {
|
|
577
|
+
function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined, seen: Set<Kind> = new Set()): readonly Issue[] {
|
|
603
578
|
const issues: Issue[] = [];
|
|
604
579
|
const verb = options.verb ?? "eval";
|
|
605
580
|
const indices = nodes === undefined ? undefined : new Map<DocVisit, number>();
|
|
@@ -643,6 +618,11 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
643
618
|
push({ code: "#KIND?", place: v.place, reason: `The world has no kind ${typeName(v.doc.type)}.`, at: v.at });
|
|
644
619
|
return "skip";
|
|
645
620
|
}
|
|
621
|
+
// The equations of a kind are code of the kind. The check walks them at the first document of the kind (D42).
|
|
622
|
+
if (v.kind?.tree !== undefined && !seen.has(v.kind)) {
|
|
623
|
+
seen.add(v.kind);
|
|
624
|
+
for (const issue of equationIssues(machine, v.kind, typeName(v.doc.type), options, seen)) push({ ...issue, place: v.place, at: v.at });
|
|
625
|
+
}
|
|
646
626
|
const name = answer.evaluator;
|
|
647
627
|
if (current !== undefined) current.evaluator = name;
|
|
648
628
|
// A runner hides the evaluator of its name (MACHINE.RUNNER), as in the compilation of a plan.
|