@mark1russell7/coeval 0.7.1 → 0.9.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 +1 -1
- package/build/index.d.ts +226 -99
- package/build/index.d.ts.map +1 -1
- package/build/index.js +977 -146
- 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 +275 -7
- package/src/facts.ts +17 -3
- package/src/formula.ts +121 -7
- package/src/index.ts +19 -1
- package/src/parts.ts +70 -1
- package/src/reference.ts +145 -19
- package/src/static.ts +122 -74
- package/src/stdlib.ts +235 -20
- package/src/world.ts +54 -1
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 Namespace, type World, type WorldEntry, equationType, isKind, isNamespace } 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
|
|
|
@@ -577,6 +508,74 @@ export function typeCheck(machine: Machine, doc: unknown, options: CheckOptions
|
|
|
577
508
|
return { type: pass.type, issues: pass.issues.length === 0 ? issues : [...issues, ...pass.issues], places: pass.places };
|
|
578
509
|
}
|
|
579
510
|
|
|
511
|
+
/**
|
|
512
|
+
* A problem of a world that `checkWorld` finds before a document exists (STATIC.COVERAGE). Each document of the
|
|
513
|
+
* kind gives this problem for the verb, thus `check` with the verb finds it too.
|
|
514
|
+
*/
|
|
515
|
+
export interface WorldIssue {
|
|
516
|
+
/** The code of the problem. `#NOEVAL`: no rung of the ladder gives an evaluator for the verb. */
|
|
517
|
+
readonly code: "#NOEVAL";
|
|
518
|
+
/** The keys of the kind from the root of the world. For a fallback kind, the keys of its namespace. */
|
|
519
|
+
readonly kind: readonly Key[];
|
|
520
|
+
/** `true` for the fallback kind of a namespace or of the world. */
|
|
521
|
+
readonly fallback?: true;
|
|
522
|
+
/** The verb without an evaluator. */
|
|
523
|
+
readonly verb: string;
|
|
524
|
+
readonly reason: string;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** This function tells if a table of verbs has an entry for a verb. */
|
|
528
|
+
function hasVerb(table: Readonly<Record<string, string>> | undefined, verb: string): boolean {
|
|
529
|
+
return table !== undefined && Object.hasOwn(table, verb);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* This function checks the world of a machine before a document exists. It finds each kind that has no entry for a
|
|
534
|
+
* verb of the list, when the world has no default for that verb (STATIC.COVERAGE). A document of such a kind gets no
|
|
535
|
+
* evaluator for the verb, thus the check gives the same proof of coverage as a type system.
|
|
536
|
+
*
|
|
537
|
+
* The check visits each kind of each namespace, at each depth, and the fallback kind of each namespace and of the
|
|
538
|
+
* world. It does not follow an alias, because the kind of an alias has its own place. The check reads only the world.
|
|
539
|
+
*
|
|
540
|
+
* @param machine - The machine, for its world.
|
|
541
|
+
* @param verbs - The verbs that each kind of the language must answer, for example `eval`, `view` and `type`.
|
|
542
|
+
* @returns One issue for each kind and verb without an evaluator. The order is the order of the world, then the
|
|
543
|
+
* order of the verbs. The fallback kind of a namespace comes after the entries of that namespace.
|
|
544
|
+
*/
|
|
545
|
+
export function checkWorld(machine: Machine, verbs: readonly string[]): readonly WorldIssue[] {
|
|
546
|
+
const world = machine.world;
|
|
547
|
+
const open = verbs.filter((verb) => !hasVerb(world.defaults, verb));
|
|
548
|
+
const out: WorldIssue[] = [];
|
|
549
|
+
if (open.length === 0) return out;
|
|
550
|
+
const visit = (kind: Kind, path: readonly Key[], fallback: boolean): void => {
|
|
551
|
+
const text = path.join(".");
|
|
552
|
+
const name = fallback ? (path.length === 0 ? "the fallback kind of the world" : `the fallback kind of ${text}`) : `the kind ${text}`;
|
|
553
|
+
for (const verb of open) {
|
|
554
|
+
if (hasVerb(kind.verbs, verb)) continue;
|
|
555
|
+
const reason = `${name.charAt(0).toUpperCase()}${name.slice(1)} has no entry for the verb ${verb}, and the world has no default for it.`;
|
|
556
|
+
out.push(fallback ? { code: "#NOEVAL", kind: path, fallback: true, verb, reason } : { code: "#NOEVAL", kind: path, verb, reason });
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
// The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
|
|
560
|
+
const way = new Set<Namespace>();
|
|
561
|
+
const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
|
|
562
|
+
for (const key of Object.keys(kinds)) {
|
|
563
|
+
const entry = kinds[key];
|
|
564
|
+
const here = [...path, key];
|
|
565
|
+
if (isKind(entry)) visit(entry, here, false);
|
|
566
|
+
else if (isNamespace(entry) && !way.has(entry)) {
|
|
567
|
+
way.add(entry);
|
|
568
|
+
entries(entry.kinds, here);
|
|
569
|
+
if (entry.fallback !== undefined) visit(entry.fallback, here, true);
|
|
570
|
+
way.delete(entry);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
entries(world.kinds, []);
|
|
575
|
+
if (world.fallback !== undefined) visit(world.fallback, [], true);
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
|
|
580
579
|
/**
|
|
581
580
|
* A runner under an evaluator in a check of a run. The run drives its steps without a wait, thus only a wait for an
|
|
582
581
|
* effect of its runners fails (RUN.DRIVE-NESTED).
|
|
@@ -595,11 +594,55 @@ interface Drive {
|
|
|
595
594
|
/** What the code of a document does with a wait: an evaluator stops it, and a runner passes it on. */
|
|
596
595
|
type Waits = boolean | Drive;
|
|
597
596
|
|
|
597
|
+
/** The names that an equation of `down` sees (TREE.DOWN). */
|
|
598
|
+
const DOWN_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["child", "index", "prev"]);
|
|
599
|
+
|
|
600
|
+
/** The names that the term `combine` of a fold sees (TREE.FOLD). */
|
|
601
|
+
const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "item"]);
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
|
|
605
|
+
* `equations`, a `unit` and a `default` see no names.
|
|
606
|
+
*/
|
|
607
|
+
function equationsOf(kind: Kind): readonly { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] {
|
|
608
|
+
const tree = kind.tree;
|
|
609
|
+
if (tree === undefined) return [];
|
|
610
|
+
const out: { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] = [];
|
|
611
|
+
for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({ label: `tree.equations.${name}`, term, names: [] });
|
|
612
|
+
for (const [name, term] of Object.entries(tree.down ?? {})) out.push({ label: `tree.down.${name}`, term, names: DOWN_NAMES });
|
|
613
|
+
for (const [name, declared] of Object.entries(tree.attributes ?? {})) {
|
|
614
|
+
if (declared.fold !== undefined) {
|
|
615
|
+
out.push({ label: `tree.attributes.${name}.fold.unit`, term: declared.fold.unit, names: [] });
|
|
616
|
+
out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES });
|
|
617
|
+
}
|
|
618
|
+
if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [] });
|
|
619
|
+
}
|
|
620
|
+
return out;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* This function checks the equations of the tree contract of a kind, each with the names of its rung (TREE.DOWN-NAMES).
|
|
625
|
+
* An equation evaluates without capabilities, thus a check with capabilities gives it none. Each issue gets the place
|
|
626
|
+
* of the document of the kind, and its reason names the equation.
|
|
627
|
+
*/
|
|
628
|
+
function equationIssues(machine: Machine, kind: Kind, kindText: string, options: CheckOptions, seen: Set<Kind>): readonly Issue[] {
|
|
629
|
+
const of = equationType(kind);
|
|
630
|
+
const out: Issue[] = [];
|
|
631
|
+
for (const { label, term, names } of equationsOf(kind)) {
|
|
632
|
+
const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
|
|
633
|
+
for (const issue of checkAll(machine, term, inner, undefined, seen)) {
|
|
634
|
+
out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
|
|
598
640
|
/**
|
|
599
641
|
* 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.
|
|
642
|
+
* the pass of types. It marks each document with an issue. The set `seen` holds the kinds whose equations the check
|
|
643
|
+
* walked, thus it walks the equations of each kind one time.
|
|
601
644
|
*/
|
|
602
|
-
function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined): readonly Issue[] {
|
|
645
|
+
function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined, seen: Set<Kind> = new Set()): readonly Issue[] {
|
|
603
646
|
const issues: Issue[] = [];
|
|
604
647
|
const verb = options.verb ?? "eval";
|
|
605
648
|
const indices = nodes === undefined ? undefined : new Map<DocVisit, number>();
|
|
@@ -643,6 +686,11 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
643
686
|
push({ code: "#KIND?", place: v.place, reason: `The world has no kind ${typeName(v.doc.type)}.`, at: v.at });
|
|
644
687
|
return "skip";
|
|
645
688
|
}
|
|
689
|
+
// The equations of a kind are code of the kind. The check walks them at the first document of the kind (D42).
|
|
690
|
+
if (v.kind?.tree !== undefined && !seen.has(v.kind)) {
|
|
691
|
+
seen.add(v.kind);
|
|
692
|
+
for (const issue of equationIssues(machine, v.kind, typeName(v.doc.type), options, seen)) push({ ...issue, place: v.place, at: v.at });
|
|
693
|
+
}
|
|
646
694
|
const name = answer.evaluator;
|
|
647
695
|
if (current !== undefined) current.evaluator = name;
|
|
648
696
|
// A runner hides the evaluator of its name (MACHINE.RUNNER), as in the compilation of a plan.
|
package/src/stdlib.ts
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { type Json, type Key, field, isRecord } from "./doc.ts";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type EvalInput,
|
|
4
|
+
type Evaluator,
|
|
5
|
+
type Machine,
|
|
6
|
+
type Plan,
|
|
7
|
+
type Result,
|
|
8
|
+
type RunPart,
|
|
9
|
+
type Runner,
|
|
10
|
+
type Steps,
|
|
11
|
+
fail,
|
|
12
|
+
ok,
|
|
13
|
+
planner,
|
|
14
|
+
} from "./evaluate.ts";
|
|
3
15
|
import { Failure } from "./errors.ts";
|
|
4
|
-
import { type HostThing, type Thing, type Walker, THING, isHostThing, valueOfThing } from "./deref.ts";
|
|
16
|
+
import { type Cap, type HostThing, type Thing, type Walker, THING, isHostThing, valueOfThing } from "./deref.ts";
|
|
5
17
|
import type { World } from "./ladder.ts";
|
|
18
|
+
import { type AttributeDeclaration, type TreeContract, equationType, lookupKind } from "./world.ts";
|
|
6
19
|
import { STD_DOMAINS } from "./types.ts";
|
|
7
20
|
|
|
8
21
|
/**
|
|
@@ -58,6 +71,8 @@ export const STD_WORLD: World = {
|
|
|
58
71
|
// A node that nothing reads keeps its attributes without an evaluation (TREE.LAZY).
|
|
59
72
|
pure: true,
|
|
60
73
|
lazy: [{}],
|
|
74
|
+
// The code resolves self to find an open tree and the parent (C4, D44).
|
|
75
|
+
reads: ["self"],
|
|
61
76
|
summary: "A node of a tree. Each part is an attribute that evaluates on demand, one time, with the node as self.",
|
|
62
77
|
},
|
|
63
78
|
box: {
|
|
@@ -66,6 +81,7 @@ export const STD_WORLD: World = {
|
|
|
66
81
|
caps: [{ rule: "mask" }],
|
|
67
82
|
pure: true,
|
|
68
83
|
lazy: [{}],
|
|
84
|
+
reads: ["self"],
|
|
69
85
|
summary: "A node of a tree, as panel. A formula in a box can read its parent and its siblings.",
|
|
70
86
|
},
|
|
71
87
|
math: {
|
|
@@ -188,11 +204,57 @@ interface TreeBuild {
|
|
|
188
204
|
open: boolean;
|
|
189
205
|
}
|
|
190
206
|
|
|
207
|
+
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
208
|
+
export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default";
|
|
209
|
+
|
|
210
|
+
/** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
|
|
211
|
+
export const ATTRIBUTE_RUNGS: readonly AttributeRung[] = /* @__PURE__ */ Object.freeze(["own", "kind", "parent", "copy", "default"] as const);
|
|
212
|
+
|
|
213
|
+
/** The record of the demand of one attribute of a node (TREE.RUNGS). */
|
|
214
|
+
export interface AttributeTrace {
|
|
215
|
+
readonly attribute: string;
|
|
216
|
+
/** The rungs that the demand visited, in their order. The last one answered, unless no rung answered. */
|
|
217
|
+
readonly rungs: readonly AttributeRung[];
|
|
218
|
+
/** The rung that answered. It is `undefined` when no rung answered. */
|
|
219
|
+
readonly by?: AttributeRung;
|
|
220
|
+
}
|
|
221
|
+
|
|
191
222
|
/** The state of a node behind its brand. */
|
|
192
223
|
interface NodeState {
|
|
193
224
|
readonly build: TreeBuild;
|
|
194
225
|
readonly type: string;
|
|
195
226
|
readonly place: readonly Key[];
|
|
227
|
+
/** The tree contract of the kind of the node (D42). */
|
|
228
|
+
readonly tree: TreeContract | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* This function gives an attribute through its ladder (TREE.LADDER).
|
|
231
|
+
*
|
|
232
|
+
* @param key - The name of the attribute.
|
|
233
|
+
* @returns The result, or `undefined` when no rung answers.
|
|
234
|
+
*/
|
|
235
|
+
read(key: string): Result | undefined;
|
|
236
|
+
/**
|
|
237
|
+
* This function evaluates an equation of the kind of the node, with the node as `self` and no capabilities.
|
|
238
|
+
*
|
|
239
|
+
* @param term - The equation.
|
|
240
|
+
* @param names - The names that the equation sees.
|
|
241
|
+
* @returns The result.
|
|
242
|
+
*/
|
|
243
|
+
equation(term: Json, names: Readonly<Record<string, unknown>>): Result;
|
|
244
|
+
/**
|
|
245
|
+
* This function gives the names of a child for an equation of `down`: `child`, `index` and `prev` (TREE.DOWN).
|
|
246
|
+
*
|
|
247
|
+
* @param child - The thing of the child.
|
|
248
|
+
* @returns The names, or the error of the list `children`.
|
|
249
|
+
*/
|
|
250
|
+
childNames(child: HostThing): Result;
|
|
251
|
+
/**
|
|
252
|
+
* This function gives the record of the demand of an attribute that ended (TREE.RUNGS).
|
|
253
|
+
*
|
|
254
|
+
* @param key - The name of the attribute.
|
|
255
|
+
* @returns The record, or `undefined` before the end of the demand.
|
|
256
|
+
*/
|
|
257
|
+
trace(key: string): AttributeTrace | undefined;
|
|
196
258
|
/**
|
|
197
259
|
* This function gives the JSON of the node: each attribute in the order of the parts, with the JSON of each node in
|
|
198
260
|
* its value.
|
|
@@ -203,8 +265,60 @@ interface NodeState {
|
|
|
203
265
|
json(path: Set<object>): Result;
|
|
204
266
|
}
|
|
205
267
|
|
|
206
|
-
/**
|
|
207
|
-
|
|
268
|
+
/**
|
|
269
|
+
* The memo cell of one attribute: in evaluation, or done with its result and the rung that answered. A done cell
|
|
270
|
+
* without a result tells that no rung answered.
|
|
271
|
+
*/
|
|
272
|
+
type Cell = { readonly busy: true } | { readonly busy: false; readonly result: Result | undefined; readonly by: AttributeRung | undefined };
|
|
273
|
+
|
|
274
|
+
/** The capabilities of an equation: none (CAP.PURE). */
|
|
275
|
+
const NO_CAPS: readonly Cap[] = /* @__PURE__ */ Object.freeze([]);
|
|
276
|
+
|
|
277
|
+
/** The names of an equation of `equations`, of a `unit` and of a `default`. */
|
|
278
|
+
const NO_NAMES: Readonly<Record<string, unknown>> = /* @__PURE__ */ Object.freeze({});
|
|
279
|
+
|
|
280
|
+
/** The planners of equations, for each machine and each type of a bare equation (TREE.PLAN). */
|
|
281
|
+
const equationPlanners = /* @__PURE__ */ new WeakMap<Machine, Map<string, (doc: unknown) => Plan>>();
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* This function gives the plan of an equation. Each equation compiles one time for each machine (PLAN.CACHE). The
|
|
285
|
+
* plan of the document does not keep it (PLAN.LEAN).
|
|
286
|
+
*/
|
|
287
|
+
function equationPlan(machine: Machine, of: string | undefined, term: Json): Plan {
|
|
288
|
+
let byType = equationPlanners.get(machine);
|
|
289
|
+
if (byType === undefined) {
|
|
290
|
+
byType = new Map();
|
|
291
|
+
equationPlanners.set(machine, byType);
|
|
292
|
+
}
|
|
293
|
+
const key = of ?? "";
|
|
294
|
+
let plans = byType.get(key);
|
|
295
|
+
if (plans === undefined) {
|
|
296
|
+
plans = planner(machine, of === undefined ? {} : { declaredType: of });
|
|
297
|
+
byType.set(key, plans);
|
|
298
|
+
}
|
|
299
|
+
return plans(term);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** This function gives the own entry of a table of a tree contract, or `undefined`. */
|
|
303
|
+
function entryOf<T>(table: Readonly<Record<string, T>> | undefined, key: string): T | undefined {
|
|
304
|
+
return table !== undefined && Object.hasOwn(table, key) ? table[key] : undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** The contract of the kind of a node: its tree contract and the type of a bare equation. */
|
|
308
|
+
interface NodeContract {
|
|
309
|
+
readonly tree: TreeContract | undefined;
|
|
310
|
+
readonly of: string | undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const NO_CONTRACT: NodeContract = /* @__PURE__ */ Object.freeze({ tree: undefined, of: undefined });
|
|
314
|
+
|
|
315
|
+
/** This function finds the tree contract of the kind of a node in the world of the machine. */
|
|
316
|
+
function contractOf(machine: Machine | undefined, typePath: readonly Key[]): NodeContract {
|
|
317
|
+
if (machine === undefined || typePath.length === 0) return NO_CONTRACT;
|
|
318
|
+
const found = lookupKind(machine.world, typePath);
|
|
319
|
+
if (!found.ok || found.kind.tree === undefined) return NO_CONTRACT;
|
|
320
|
+
return { tree: found.kind.tree, of: equationType(found.kind) };
|
|
321
|
+
}
|
|
208
322
|
|
|
209
323
|
const BUSY: Cell = /* @__PURE__ */ Object.freeze({ busy: true });
|
|
210
324
|
|
|
@@ -282,21 +396,72 @@ function plainOf(value: unknown, path: Set<object>): Result {
|
|
|
282
396
|
|
|
283
397
|
/**
|
|
284
398
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
285
|
-
* step `type` gives its type.
|
|
286
|
-
*
|
|
399
|
+
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
400
|
+
* the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
|
|
401
|
+
* fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
|
|
402
|
+
* The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
|
|
287
403
|
*/
|
|
288
|
-
function treeThing(
|
|
404
|
+
function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined): HostThing {
|
|
405
|
+
const { parts, context, kind: type, place, machine } = input;
|
|
406
|
+
const { tree, of } = contractOf(machine, input.typePath);
|
|
407
|
+
const above = stateOf(outer);
|
|
408
|
+
// Only a node of the same tree is a parent for the rungs `parent` and `copy`.
|
|
409
|
+
const up = above !== undefined && above.build === build ? above : undefined;
|
|
289
410
|
const cells = new Map<string, Cell>();
|
|
411
|
+
/** The positions of the items of the list `children`, for the names of the children. */
|
|
412
|
+
let positions: { readonly list: readonly unknown[]; readonly at: Map<unknown, number> } | undefined;
|
|
413
|
+
|
|
414
|
+
const fold = (key: string, declared: NonNullable<AttributeDeclaration["fold"]>): Result => {
|
|
415
|
+
let acc = state.equation(declared.unit, NO_NAMES);
|
|
416
|
+
if (!acc.ok) return acc;
|
|
417
|
+
const children = read("children");
|
|
418
|
+
if (children === undefined) return acc;
|
|
419
|
+
if (!children.ok) return children;
|
|
420
|
+
if (!Array.isArray(children.value)) return fail("#VALUE!", `The fold of ${key} needs a list in children.`);
|
|
421
|
+
for (const item of children.value as readonly unknown[]) {
|
|
422
|
+
const child = stateOf(item);
|
|
423
|
+
// An item that is not a node of this tree does not count.
|
|
424
|
+
if (child === undefined || child.build !== build) continue;
|
|
425
|
+
const value = child.read(key);
|
|
426
|
+
if (value === undefined) return fail("#N/A", `No rung gives ${attributeText(key, child.type, child.place)}.`);
|
|
427
|
+
if (!value.ok) return value;
|
|
428
|
+
acc = state.equation(declared.combine, { acc: acc.value, item: value.value });
|
|
429
|
+
if (!acc.ok) return acc;
|
|
430
|
+
}
|
|
431
|
+
return acc;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
/** This function visits the rungs of the ladder of an attribute in their order, until one answers. */
|
|
435
|
+
const climb = (key: string): { readonly result: Result; readonly by: AttributeRung } | undefined => {
|
|
436
|
+
const part = parts.find((item) => item.key === key);
|
|
437
|
+
if (part !== undefined) return { result: part.evaluate({ ...context, self: node }), by: "own" };
|
|
438
|
+
const equation = entryOf(tree?.equations, key);
|
|
439
|
+
if (equation !== undefined) return { result: state.equation(equation, NO_NAMES), by: "kind" };
|
|
440
|
+
const down = up === undefined ? undefined : entryOf(up.tree?.down, key);
|
|
441
|
+
if (up !== undefined && down !== undefined) {
|
|
442
|
+
const names = up.childNames(node);
|
|
443
|
+
return { result: names.ok ? up.equation(down, names.value as Readonly<Record<string, unknown>>) : names, by: "parent" };
|
|
444
|
+
}
|
|
445
|
+
const declared = entryOf(tree?.attributes, key);
|
|
446
|
+
if (declared?.copy === true && up !== undefined) {
|
|
447
|
+
const copied = up.read(key);
|
|
448
|
+
if (copied !== undefined) return { result: copied, by: "copy" };
|
|
449
|
+
}
|
|
450
|
+
if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
|
|
451
|
+
if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
|
|
452
|
+
return undefined;
|
|
453
|
+
};
|
|
454
|
+
|
|
290
455
|
const read = (key: string): Result | undefined => {
|
|
291
456
|
const cell = cells.get(key);
|
|
292
457
|
if (cell !== undefined) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
|
|
293
|
-
|
|
294
|
-
if (
|
|
458
|
+
// A node without a contract has only its parts, as before D42. Thus a missing part needs no cell.
|
|
459
|
+
if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
|
|
295
460
|
cells.set(key, BUSY);
|
|
296
461
|
demands.push({ cells, key, text: attributeText(key, type, place) });
|
|
297
|
-
let result: Result;
|
|
462
|
+
let answer: { readonly result: Result; readonly by: AttributeRung } | undefined;
|
|
298
463
|
try {
|
|
299
|
-
|
|
464
|
+
answer = climb(key);
|
|
300
465
|
} catch (error) {
|
|
301
466
|
// An exception of the host, for example an overflow of the stack, leaves the attribute without a result.
|
|
302
467
|
cells.delete(key);
|
|
@@ -304,13 +469,40 @@ function treeThing(parts: readonly Part[], context: Context, type: string, place
|
|
|
304
469
|
} finally {
|
|
305
470
|
demands.pop();
|
|
306
471
|
}
|
|
307
|
-
cells.set(key, { busy: false, result });
|
|
308
|
-
return result;
|
|
472
|
+
cells.set(key, { busy: false, result: answer?.result, by: answer?.by });
|
|
473
|
+
return answer?.result;
|
|
309
474
|
};
|
|
475
|
+
|
|
310
476
|
const state: NodeState = {
|
|
311
477
|
build,
|
|
312
478
|
type,
|
|
313
479
|
place,
|
|
480
|
+
tree,
|
|
481
|
+
read,
|
|
482
|
+
equation(term, names) {
|
|
483
|
+
if (machine === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
|
|
484
|
+
return equationPlan(machine, of, term).evaluate({ ...context, names, caps: NO_CAPS, self: node });
|
|
485
|
+
},
|
|
486
|
+
childNames(child) {
|
|
487
|
+
const children = read("children");
|
|
488
|
+
if (children !== undefined && !children.ok) return children;
|
|
489
|
+
const list = children !== undefined && Array.isArray(children.value) ? (children.value as readonly unknown[]) : [];
|
|
490
|
+
if (positions?.list !== list) {
|
|
491
|
+
const at = new Map<unknown, number>();
|
|
492
|
+
list.forEach((item, index) => {
|
|
493
|
+
if (!at.has(item)) at.set(item, index);
|
|
494
|
+
});
|
|
495
|
+
positions = { list, at };
|
|
496
|
+
}
|
|
497
|
+
const index = positions.at.get(child);
|
|
498
|
+
return ok({ child, index: index ?? null, prev: index === undefined || index === 0 ? null : list[index - 1] });
|
|
499
|
+
},
|
|
500
|
+
trace(key) {
|
|
501
|
+
const cell = cells.get(key);
|
|
502
|
+
if (cell === undefined || cell.busy) return undefined;
|
|
503
|
+
const rungs = cell.by === undefined ? ATTRIBUTE_RUNGS : ATTRIBUTE_RUNGS.slice(0, ATTRIBUTE_RUNGS.indexOf(cell.by) + 1);
|
|
504
|
+
return cell.by === undefined ? { attribute: key, rungs } : { attribute: key, rungs, by: cell.by };
|
|
505
|
+
},
|
|
314
506
|
json(path) {
|
|
315
507
|
path.add(node);
|
|
316
508
|
try {
|
|
@@ -334,7 +526,7 @@ function treeThing(parts: readonly Part[], context: Context, type: string, place
|
|
|
334
526
|
type,
|
|
335
527
|
label: `node ${type}`,
|
|
336
528
|
deref(key: Key): Thing | undefined {
|
|
337
|
-
if (key === "parent") return
|
|
529
|
+
if (key === "parent") return outer;
|
|
338
530
|
if (key === "type") return type;
|
|
339
531
|
const result = read(String(key));
|
|
340
532
|
if (result === undefined) return undefined;
|
|
@@ -343,7 +535,9 @@ function treeThing(parts: readonly Part[], context: Context, type: string, place
|
|
|
343
535
|
},
|
|
344
536
|
has(key: Key): boolean {
|
|
345
537
|
// DEREF.HOST-UNDEFINED: an attribute with the value undefined is a field, not a missing step.
|
|
346
|
-
|
|
538
|
+
if (key === "parent" || key === "type" || parts.some((part) => part.key === String(key))) return true;
|
|
539
|
+
const cell = cells.get(String(key));
|
|
540
|
+
return cell !== undefined && !cell.busy && cell.result !== undefined;
|
|
347
541
|
},
|
|
348
542
|
toJson(): unknown {
|
|
349
543
|
const result = state.json(new Set());
|
|
@@ -354,15 +548,36 @@ function treeThing(parts: readonly Part[], context: Context, type: string, place
|
|
|
354
548
|
return node;
|
|
355
549
|
}
|
|
356
550
|
|
|
551
|
+
/**
|
|
552
|
+
* This function gives the record of the demand of an attribute of a node of the decorated tree (TREE.RUNGS). The
|
|
553
|
+
* record holds the rungs that the demand visited, and the rung that answered. An explorer draws the ladder with it.
|
|
554
|
+
*
|
|
555
|
+
* @param node - The thing of a node.
|
|
556
|
+
* @param attribute - The name of the attribute.
|
|
557
|
+
* @returns The record, or `undefined` for a thing that is not a node or an attribute that no demand reached.
|
|
558
|
+
*/
|
|
559
|
+
export function attributeTrace(node: Thing, attribute: string): AttributeTrace | undefined {
|
|
560
|
+
return stateOf(node)?.trace(attribute);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** The path of the root `self`. A node of the tree resolves it, thus the list of reads holds the read (C4). */
|
|
564
|
+
const SELF_PATH: readonly Json[] = /* @__PURE__ */ Object.freeze(["self"]);
|
|
565
|
+
|
|
566
|
+
/** The options of the resolution of `self`: the node needs no hops. */
|
|
567
|
+
const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
|
|
568
|
+
|
|
357
569
|
/**
|
|
358
570
|
* This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
|
|
359
|
-
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree.
|
|
571
|
+
* thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
|
|
572
|
+
* node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
|
|
360
573
|
*/
|
|
361
|
-
function treeNode(
|
|
362
|
-
const
|
|
363
|
-
|
|
574
|
+
function treeNode(input: EvalInput): Result {
|
|
575
|
+
const found = input.resolve(SELF_PATH, NO_HOPS);
|
|
576
|
+
const outer = found.ok ? found.thing : undefined;
|
|
577
|
+
const above = stateOf(outer);
|
|
578
|
+
if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer));
|
|
364
579
|
const build: TreeBuild = { open: true };
|
|
365
|
-
const root = treeThing(
|
|
580
|
+
const root = treeThing(input, build, outer);
|
|
366
581
|
try {
|
|
367
582
|
return (stateOf(root) as NodeState).json(new Set());
|
|
368
583
|
} finally {
|
package/src/world.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, type TypeRef, field, isRecord, typePath } from "./doc.ts";
|
|
2
|
-
import type { Cap } from "./deref.ts";
|
|
2
|
+
import type { Cap, Root } from "./deref.ts";
|
|
3
3
|
import type { ValueType } from "./types.ts";
|
|
4
4
|
|
|
5
5
|
/** The two sides of evaluation. `derive` is pure. `run` causes effects in order. */
|
|
@@ -114,11 +114,59 @@ export interface LazyRule {
|
|
|
114
114
|
* because the static facts do not see that read.
|
|
115
115
|
* - `volatile`: `true` when its result can change while each read gives the same thing, for example a clock.
|
|
116
116
|
* - `lazy`: the parts that an evaluation can skip and still give a value.
|
|
117
|
+
* - `reads`: the roots that the code of the kind resolves as a whole, with the path of one root. The static facts
|
|
118
|
+
* give a read of each root at each document of the kind (D44). The tree node reads `self` this way.
|
|
117
119
|
*/
|
|
118
120
|
export interface KindFacts {
|
|
119
121
|
readonly pure?: boolean;
|
|
120
122
|
readonly volatile?: boolean;
|
|
121
123
|
readonly lazy?: readonly LazyRule[];
|
|
124
|
+
readonly reads?: readonly Root[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The declaration of one attribute of the nodes of a kind (D42). The last two rungs of the ladder of an attribute
|
|
129
|
+
* read it (TREE.LADDER). Each term is a document. A bare term gets the type `of` of the parts of the kind.
|
|
130
|
+
*
|
|
131
|
+
* - `copy`: `true` gives a node without an answer of the first three rungs the same attribute of its parent
|
|
132
|
+
* (TREE.COPY).
|
|
133
|
+
* - `fold`: the value of a node without an answer of the first four rungs. The fold starts with the term `unit`.
|
|
134
|
+
* Then the term `combine` adds the attribute of each node in the list `children`, one at a time (TREE.FOLD).
|
|
135
|
+
* `combine` sees the names `acc` and `item`.
|
|
136
|
+
* - `default`: the term that gives the value when no rung before it answers and the declaration has no fold.
|
|
137
|
+
*/
|
|
138
|
+
export interface AttributeDeclaration {
|
|
139
|
+
readonly copy?: boolean;
|
|
140
|
+
readonly fold?: { readonly unit: Json; readonly combine: Json };
|
|
141
|
+
readonly default?: Json;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The tree contract of a kind (D42): the equations of the attributes of its nodes. Each equation is a document that
|
|
146
|
+
* evaluates with the node as `self` and without capabilities. A bare equation gets the type `of` of the parts of the
|
|
147
|
+
* kind.
|
|
148
|
+
*
|
|
149
|
+
* - `equations`: the attributes of the node itself. An equation sees no names.
|
|
150
|
+
* - `down`: the attributes of each child of the node. `self` is this node. An equation sees the names `child`,
|
|
151
|
+
* `index` and `prev` (TREE.DOWN).
|
|
152
|
+
* - `attributes`: the declarations of the attributes of the node, for the rungs `copy` and `default`.
|
|
153
|
+
*/
|
|
154
|
+
export interface TreeContract {
|
|
155
|
+
readonly equations?: Readonly<Record<string, Json>>;
|
|
156
|
+
readonly down?: Readonly<Record<string, Json>>;
|
|
157
|
+
readonly attributes?: Readonly<Record<string, AttributeDeclaration>>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* This function gives the type of a bare equation of a kind (D42). It is the type `of` of the shape of its parts, or
|
|
162
|
+
* else the type `of` of the shape `rest` of a record. The plan, the reference and `check` read it with this one function.
|
|
163
|
+
*
|
|
164
|
+
* @param kind - The kind.
|
|
165
|
+
* @returns The type, or `undefined` when the shape declares none.
|
|
166
|
+
*/
|
|
167
|
+
export function equationType(kind: Kind): string | undefined {
|
|
168
|
+
const parts = kind.parts as { readonly of?: string; readonly rest?: { readonly of?: string } } | undefined;
|
|
169
|
+
return parts?.of ?? parts?.rest?.of;
|
|
122
170
|
}
|
|
123
171
|
|
|
124
172
|
/**
|
|
@@ -157,6 +205,11 @@ export interface Kind extends KindFacts {
|
|
|
157
205
|
* document of the kind, in the place of the type that the rule of the evaluator synthesizes.
|
|
158
206
|
*/
|
|
159
207
|
readonly result?: ValueType;
|
|
208
|
+
/**
|
|
209
|
+
* The equations and the declarations of the attributes of a node of the decorated tree (D42). Only the evaluator
|
|
210
|
+
* `tree.node` reads them, and `check` checks each equation one time (TREE.LADDER).
|
|
211
|
+
*/
|
|
212
|
+
readonly tree?: TreeContract;
|
|
160
213
|
/** One sentence that tells what the kind is. */
|
|
161
214
|
readonly summary?: string;
|
|
162
215
|
}
|