@mark1russell7/coeval 0.7.0 → 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/README.md +31 -0
- package/build/index.d.ts +399 -100
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1818 -172
- package/build/index.js.map +1 -1
- package/package.json +2 -2
- package/src/agree.ts +26 -5
- package/src/codec.ts +185 -0
- package/src/conform.ts +2 -3
- package/src/doc.ts +2 -2
- package/src/evaluate.ts +281 -9
- package/src/formula.ts +19 -8
- package/src/index.ts +36 -2
- package/src/parts.ts +70 -1
- package/src/reference.ts +143 -18
- package/src/static.ts +54 -74
- package/src/stdlib.ts +220 -18
- package/src/syntax.ts +846 -0
- package/src/types.ts +28 -1
- 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
|
}
|
|
@@ -307,6 +316,7 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
|
|
|
307
316
|
parts,
|
|
308
317
|
context: at.ctx,
|
|
309
318
|
place: at.place,
|
|
319
|
+
machine: env.machine,
|
|
310
320
|
...(at.address ? { address: true } : {}),
|
|
311
321
|
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
312
322
|
effect: (kind, payload) => cause(env, at.ctx, kind, payload, at.place),
|
|
@@ -335,6 +345,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
|
|
|
335
345
|
parts,
|
|
336
346
|
context: at.ctx,
|
|
337
347
|
place: at.place,
|
|
348
|
+
machine: env.machine,
|
|
338
349
|
resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
|
|
339
350
|
effect: function* (kind, payload) {
|
|
340
351
|
return cause(env, at.ctx, kind, payload, at.place);
|
|
@@ -418,13 +429,20 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
|
|
|
418
429
|
scope: { prefix: "", counts: new Map(), alls: 0 },
|
|
419
430
|
};
|
|
420
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);
|
|
421
434
|
try {
|
|
422
435
|
return evaluateAt(env, { value: doc, place: [], declared: options.declaredType, depth: 0, address: false, ctx });
|
|
423
436
|
} catch (error) {
|
|
424
437
|
return placed(thrown(error, "the reference"), []);
|
|
438
|
+
} finally {
|
|
439
|
+
activeEnvs.pop();
|
|
425
440
|
}
|
|
426
441
|
}
|
|
427
442
|
|
|
443
|
+
/** The evaluations of the reference that run at this time, the innermost last. */
|
|
444
|
+
const activeEnvs: Env[] = [];
|
|
445
|
+
|
|
428
446
|
/*
|
|
429
447
|
* The definition of the decorated tree (TREE.*), written from docs/SPEC.md. It shares no code with `tree.node` of
|
|
430
448
|
* stdlib.ts: its nodes, its memo, its marks for attributes in evaluation and its JSON are its own.
|
|
@@ -442,6 +460,16 @@ interface RefNode {
|
|
|
442
460
|
readonly place: readonly Key[];
|
|
443
461
|
readonly parts: readonly Part[];
|
|
444
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;
|
|
445
473
|
/** The results of the attributes that ended. */
|
|
446
474
|
readonly values: Map<string, Result>;
|
|
447
475
|
/** The attributes in evaluation. */
|
|
@@ -461,8 +489,86 @@ function refNodeOf(thing: unknown): RefNode | undefined {
|
|
|
461
489
|
return typeof thing === "object" && thing !== null ? refNodes.get(thing) : undefined;
|
|
462
490
|
}
|
|
463
491
|
|
|
464
|
-
/**
|
|
465
|
-
|
|
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 {
|
|
466
572
|
const done = node.values.get(name);
|
|
467
573
|
if (done !== undefined) return done;
|
|
468
574
|
if (node.pending.has(name)) {
|
|
@@ -470,14 +576,16 @@ function attributeOf(node: RefNode, self: HostThing, name: string): Result | und
|
|
|
470
576
|
const steps = from < 0 ? [nameOfAttribute(node, name)] : inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name));
|
|
471
577
|
return fail("#CYCLE!", `The attributes form a cycle: ${[...steps, nameOfAttribute(node, name)].join(" -> ")}.`);
|
|
472
578
|
}
|
|
473
|
-
const part = node.parts.find((item) => item.key === name);
|
|
474
|
-
if (part === undefined) return undefined;
|
|
475
579
|
node.pending.add(name);
|
|
476
580
|
inEvaluation.push({ node, name });
|
|
477
581
|
try {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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;
|
|
481
589
|
} finally {
|
|
482
590
|
node.pending.delete(name);
|
|
483
591
|
inEvaluation.pop();
|
|
@@ -488,7 +596,7 @@ function attributeOf(node: RefNode, self: HostThing, name: string): Result | und
|
|
|
488
596
|
function jsonOfNode(node: RefNode, self: HostThing, open: readonly object[]): Result {
|
|
489
597
|
const record: Record<string, unknown> = {};
|
|
490
598
|
for (const part of node.parts) {
|
|
491
|
-
const result = attributeOf(node,
|
|
599
|
+
const result = attributeOf(node, part.key) as Result;
|
|
492
600
|
if (!result.ok) return result;
|
|
493
601
|
const value = plainValue(result.value, [...open, self]);
|
|
494
602
|
if (!value.ok) return value;
|
|
@@ -528,10 +636,26 @@ function plainValue(value: unknown, open: readonly object[]): Result {
|
|
|
528
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
|
|
529
637
|
* tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error.
|
|
530
638
|
*/
|
|
531
|
-
const treeDefinition: EvalFn = ({ parts, context, kind, place }) => {
|
|
639
|
+
const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath }) => {
|
|
532
640
|
const outer = refNodeOf(context.self);
|
|
533
641
|
const nested = outer !== undefined && outer.tree.open;
|
|
534
|
-
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
|
+
};
|
|
535
659
|
const self: HostThing & { readonly type: string } = {
|
|
536
660
|
[THING]: true,
|
|
537
661
|
type: kind,
|
|
@@ -539,17 +663,18 @@ const treeDefinition: EvalFn = ({ parts, context, kind, place }) => {
|
|
|
539
663
|
deref: (key): Thing | undefined => {
|
|
540
664
|
if (key === "parent") return context.self;
|
|
541
665
|
if (key === "type") return kind;
|
|
542
|
-
const result = attributeOf(node,
|
|
666
|
+
const result = attributeOf(node, String(key));
|
|
543
667
|
if (result === undefined || result.ok) return result?.ok === true ? result.value : undefined;
|
|
544
668
|
throw new Failure(result.error);
|
|
545
669
|
},
|
|
546
|
-
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)),
|
|
547
671
|
toJson: () => {
|
|
548
672
|
const result = jsonOfNode(node, self, []);
|
|
549
673
|
if (result.ok) return result.value;
|
|
550
674
|
throw new Failure(result.error);
|
|
551
675
|
},
|
|
552
676
|
};
|
|
677
|
+
node.thing = self;
|
|
553
678
|
refNodes.set(self, node);
|
|
554
679
|
if (nested) return ok(self);
|
|
555
680
|
try {
|
|
@@ -748,12 +873,12 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
|
|
|
748
873
|
}
|
|
749
874
|
};
|
|
750
875
|
|
|
751
|
-
const ext: EvalFn = ({ typePath, doc, context }) => {
|
|
876
|
+
const ext: EvalFn = ({ typePath, doc, context, machine }) => {
|
|
752
877
|
const name = typePath.slice(1).join(".");
|
|
753
878
|
const handler = own(profile.ext, name) ?? own(STD_EXT, name);
|
|
754
879
|
if (handler === undefined) return fail("#NAME?", `The language has no extension ${name}.`);
|
|
755
880
|
try {
|
|
756
|
-
return handler(doc.value, context);
|
|
881
|
+
return handler(doc.value, context, machine?.domains);
|
|
757
882
|
} catch (error) {
|
|
758
883
|
return thrown(error, `the extension ${name}`);
|
|
759
884
|
}
|
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.
|