@mark1russell7/coeval 0.20.0 → 0.21.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 -0
- package/build/index.d.ts +420 -236
- package/build/index.d.ts.map +1 -1
- package/build/index.js +746 -76
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/conform.ts +13 -4
- package/src/deref.ts +32 -2
- package/src/effects.ts +4 -2
- package/src/evaluate.ts +2 -0
- package/src/facts.ts +3 -2
- package/src/formula.ts +31 -5
- package/src/index.ts +5 -0
- package/src/instances.ts +50 -1
- package/src/parts.ts +30 -1
- package/src/providers.ts +209 -29
- package/src/reference.ts +52 -2
- package/src/static.ts +79 -19
- package/src/stdlib.ts +88 -2
- package/src/steps.ts +181 -0
- package/src/types.ts +12 -4
- package/src/world.ts +50 -0
package/src/stdlib.ts
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { Failure } from "./errors.ts";
|
|
19
19
|
import { type Cap, type HostThing, type Thing, type Walker, THING, isHostThing, valueOfThing } from "./deref.ts";
|
|
20
20
|
import type { World } from "./ladder.ts";
|
|
21
|
-
import { type AttributeDeclaration, type CircularRule, type TreeContract, circularOf, equationType, lookupKind } from "./world.ts";
|
|
21
|
+
import { type AttributeDeclaration, type CircularRule, type TreeContract, circularOf, collectOf, equationType, lookupKind } from "./world.ts";
|
|
22
22
|
import { STD_DOMAINS } from "./types.ts";
|
|
23
23
|
import type { SyntaxProfile } from "./syntax.ts";
|
|
24
24
|
|
|
@@ -67,6 +67,15 @@ export const STD_WORLD: World = {
|
|
|
67
67
|
pure: true,
|
|
68
68
|
summary: "A step of a path: it follows a key again and again, until it reaches a node of a type.",
|
|
69
69
|
},
|
|
70
|
+
tree: {
|
|
71
|
+
kinds: {
|
|
72
|
+
named: {
|
|
73
|
+
verbs: { step: "walk.named", view: "view.card" },
|
|
74
|
+
pure: true,
|
|
75
|
+
summary: "A step of a path: it goes up to the nearest registry of names, and it gives the node with the name (TREE.NAMED).",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
70
79
|
panel: {
|
|
71
80
|
verbs: { eval: "tree.node", view: "view.op" },
|
|
72
81
|
parts: { shape: "record", fields: ["width", "height", "children"], of: "number" },
|
|
@@ -167,6 +176,7 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
167
176
|
{ name: "panel", kind: "panel", args: "named", fields: ["width", "height", "children"] },
|
|
168
177
|
{ name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
|
|
169
178
|
{ name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
|
|
179
|
+
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
170
180
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
171
181
|
{ name: "add", kind: "add" },
|
|
172
182
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -272,6 +282,8 @@ export interface AttributeTrace {
|
|
|
272
282
|
/** The state of a node behind its brand. */
|
|
273
283
|
interface NodeState extends LoopNode {
|
|
274
284
|
readonly build: TreeBuild;
|
|
285
|
+
/** The binding name from the header of the document of the node, for the registry of names (TREE.NAMED). */
|
|
286
|
+
readonly name: string | undefined;
|
|
275
287
|
/**
|
|
276
288
|
* This function gives an attribute through its ladder (TREE.LADDER).
|
|
277
289
|
*
|
|
@@ -853,6 +865,57 @@ function plainOf(value: unknown, path: Set<object>): Result {
|
|
|
853
865
|
}
|
|
854
866
|
}
|
|
855
867
|
|
|
868
|
+
/**
|
|
869
|
+
* This function gives the collection of an attribute of a node (TREE.COLLECT, D122). It visits the node, then each node
|
|
870
|
+
* of the same tree in each list `children`, in the order of the lists. Each node is visited one time.
|
|
871
|
+
* The registry of names (TREE.NAMED) gives a record from each binding name to its node.
|
|
872
|
+
*/
|
|
873
|
+
function collected(root: HostThing, key: string, declared: unknown): Result {
|
|
874
|
+
const top = stateOf(root) as NodeState;
|
|
875
|
+
const rule = collectOf(declared);
|
|
876
|
+
if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(key, top.type, top.place)} has a bad declaration collect. ${rule.reason}`);
|
|
877
|
+
const registry: Record<string, unknown> = {};
|
|
878
|
+
const owners = new Map<string, NodeState>();
|
|
879
|
+
let acc = rule.names ? ok(registry) : top.equation(rule.unit, NO_NAMES);
|
|
880
|
+
if (!acc.ok) return acc;
|
|
881
|
+
const seen = new Set<NodeState>();
|
|
882
|
+
const stack: unknown[] = [root];
|
|
883
|
+
while (stack.length > 0) {
|
|
884
|
+
const thing = stack.pop();
|
|
885
|
+
const at = stateOf(thing);
|
|
886
|
+
// An item that is not a node of this tree does not count, and a node counts one time.
|
|
887
|
+
if (at === undefined || at.build !== top.build || seen.has(at)) continue;
|
|
888
|
+
seen.add(at);
|
|
889
|
+
if (rule.names) {
|
|
890
|
+
const name = at.name;
|
|
891
|
+
const earlier = name === undefined ? undefined : owners.get(name);
|
|
892
|
+
if (earlier !== undefined) {
|
|
893
|
+
const both = `${earlier.type} at [${earlier.place.join(", ")}] and ${at.type} at [${at.place.join(", ")}]`;
|
|
894
|
+
return fail("#VALUE!", `Two nodes have the name ${name} in the registry ${attributeText(key, top.type, top.place)}: ${both}.`);
|
|
895
|
+
}
|
|
896
|
+
if (name !== undefined) {
|
|
897
|
+
owners.set(name, at);
|
|
898
|
+
putField(registry, name, thing);
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
const term = entryOf(at.tree?.contributes, key);
|
|
902
|
+
if (term !== undefined) {
|
|
903
|
+
const item = at.equation(term, NO_NAMES);
|
|
904
|
+
if (!item.ok) return item;
|
|
905
|
+
acc = top.equation(rule.combine, { acc: acc.value, item: item.value });
|
|
906
|
+
if (!acc.ok) return acc;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const children = at.read("children");
|
|
910
|
+
if (children === undefined) continue;
|
|
911
|
+
if (!children.ok) return children;
|
|
912
|
+
if (!Array.isArray(children.value)) return fail("#VALUE!", `The collection of ${key} needs a list in children of ${at.type} at [${at.place.join(", ")}].`);
|
|
913
|
+
const list = children.value as readonly unknown[];
|
|
914
|
+
for (let index = list.length - 1; index >= 0; index -= 1) stack.push(list[index]);
|
|
915
|
+
}
|
|
916
|
+
return acc;
|
|
917
|
+
}
|
|
918
|
+
|
|
856
919
|
/**
|
|
857
920
|
* This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
|
|
858
921
|
* step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
|
|
@@ -916,6 +979,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
916
979
|
if (copied !== undefined) return { result: copied, by: "copy" };
|
|
917
980
|
}
|
|
918
981
|
if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
|
|
982
|
+
if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
|
|
919
983
|
if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
|
|
920
984
|
return undefined;
|
|
921
985
|
};
|
|
@@ -984,6 +1048,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
984
1048
|
|
|
985
1049
|
const state: NodeState = {
|
|
986
1050
|
build,
|
|
1051
|
+
name: input.doc.name,
|
|
987
1052
|
type,
|
|
988
1053
|
place,
|
|
989
1054
|
tree,
|
|
@@ -1170,8 +1235,29 @@ export function nearestWalker(options: NearestOptions = {}): Walker {
|
|
|
1170
1235
|
|
|
1171
1236
|
const nearest: Walker = /* @__PURE__ */ nearestWalker();
|
|
1172
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* The walker of the step `tree.named` (TREE.NAMED, D122). It goes up from the thing before the step through the key
|
|
1240
|
+
* `parent`, to the first thing that answers the registry. The field `in` names the registry, and the default is
|
|
1241
|
+
* `names`. The walker gives the node with the field `name` in that registry.
|
|
1242
|
+
*/
|
|
1243
|
+
const named: Walker = ({ data, here, deref }) => {
|
|
1244
|
+
const name = isRecord(data) ? field(data, "name") : undefined;
|
|
1245
|
+
if (typeof name !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.named needs a text name." };
|
|
1246
|
+
const registry = isRecord(data) && typeof field(data, "in") === "string" ? String(field(data, "in")) : "names";
|
|
1247
|
+
let at: Thing | undefined = here;
|
|
1248
|
+
for (let hops = 0; hops < 256 && at !== undefined && at !== null; hops += 1) {
|
|
1249
|
+
const found = deref(at, registry);
|
|
1250
|
+
if (found !== undefined) {
|
|
1251
|
+
if (!isRecord(found) || !Object.hasOwn(found, name)) return { ok: false, reason: `The registry ${registry} has no node with the name ${name}.` };
|
|
1252
|
+
return { ok: true, thing: field(found, name) as Thing };
|
|
1253
|
+
}
|
|
1254
|
+
at = deref(at, "parent");
|
|
1255
|
+
}
|
|
1256
|
+
return { ok: false, reason: `No registry ${registry} is on the way.` };
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1173
1259
|
/** The walkers of the standard world, by name. */
|
|
1174
|
-
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest };
|
|
1260
|
+
export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named };
|
|
1175
1261
|
|
|
1176
1262
|
/** This function evaluates parts one after the other, and stops at the first error. */
|
|
1177
1263
|
function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { readonly values: readonly unknown[] } | { readonly error: Result }, unknown> {
|
package/src/steps.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The steps of a language (STEPS.*). A step is a level of the power of a language. A low step accepts only programs
|
|
3
|
+
* that end for a known reason, for example loops with a fixed count. A high step accepts more programs. The language
|
|
4
|
+
* declares the step of each construct as data. `stepOf` gives the lowest step that a document needs, with the reason,
|
|
5
|
+
* and `checkStep` compares that step with the highest step that the host accepts. A document cannot raise its own
|
|
6
|
+
* step, because the declaration and the limit come from the language and the host. The ladder of `ask` (`ladder.ts`)
|
|
7
|
+
* is a different thing: it finds the evaluator of a verb.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** A key of a path from the root to a node: a field name or a position. */
|
|
11
|
+
export type StepKey = string | number;
|
|
12
|
+
|
|
13
|
+
/** The steps of a language, from the lowest to the highest, and the step that each construct needs. It is data. */
|
|
14
|
+
export interface StepLadder<S extends string = string> {
|
|
15
|
+
/** The names of the steps, from the lowest to the highest. */
|
|
16
|
+
readonly steps: readonly S[];
|
|
17
|
+
/** The step that each construct needs. A construct without an entry needs the lowest step. */
|
|
18
|
+
readonly constructs: Readonly<Record<string, S>>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The walk of the documents of a language. The language gives these two functions. */
|
|
22
|
+
export interface StepWalk<N> {
|
|
23
|
+
/** This function gives the name of the construct of a node, or `undefined` for a node without a construct. */
|
|
24
|
+
readonly construct: (node: N) => string | undefined;
|
|
25
|
+
/** This function gives the child nodes of a node in document order, each with the key of its path. */
|
|
26
|
+
readonly children: (node: N) => Iterable<readonly [StepKey, N]>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The reason for a step: the first node in document order whose construct needs that step. */
|
|
30
|
+
export interface StepReason<N, S extends string = string> {
|
|
31
|
+
/** The name of the construct. */
|
|
32
|
+
readonly construct: string;
|
|
33
|
+
/** The step that the construct needs. */
|
|
34
|
+
readonly step: S;
|
|
35
|
+
/** The keys from the root to the node. The root has the empty path. */
|
|
36
|
+
readonly path: readonly StepKey[];
|
|
37
|
+
readonly node: N;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The lowest step that a document needs. */
|
|
41
|
+
export interface StepNeed<N, S extends string = string> {
|
|
42
|
+
readonly step: S;
|
|
43
|
+
/** The reason. It is `undefined` when no construct needs more than the lowest step. */
|
|
44
|
+
readonly reason?: StepReason<N, S>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The refusal of a document whose step is higher than the limit of the host. */
|
|
48
|
+
export interface StepRefusal<N, S extends string = string> {
|
|
49
|
+
/** The step that the document needs. */
|
|
50
|
+
readonly needed: S;
|
|
51
|
+
/** The highest step that the host accepts. */
|
|
52
|
+
readonly maxStep: S;
|
|
53
|
+
/** The reason of the step. A need that a host makes without `stepOf` can have no reason. */
|
|
54
|
+
readonly reason?: StepReason<N, S>;
|
|
55
|
+
/** The text of the refusal. It names the needed step and the limit, and the construct and its path of the reason. */
|
|
56
|
+
readonly message: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* This function makes a ladder of steps, and it checks the declaration (STEPS.LADDER). The list of steps must have one
|
|
61
|
+
* step or more, with no name two times. The step of each construct must be a step of the list.
|
|
62
|
+
*
|
|
63
|
+
* @param steps - The names of the steps, from the lowest to the highest.
|
|
64
|
+
* @param constructs - The step that each construct needs.
|
|
65
|
+
* @returns The ladder, frozen.
|
|
66
|
+
* @throws TypeError for a declaration that is not valid, because it is a defect of the language.
|
|
67
|
+
*/
|
|
68
|
+
export function stepLadder<const S extends string>(steps: readonly S[], constructs: Readonly<Record<string, NoInfer<S>>>): StepLadder<S> {
|
|
69
|
+
if (steps.length === 0) throw new TypeError("a ladder of steps needs one step or more");
|
|
70
|
+
const seen = new Set<string>();
|
|
71
|
+
for (const step of steps) {
|
|
72
|
+
if (typeof step !== "string") throw new TypeError(`the step ${String(step)} is not a name`);
|
|
73
|
+
if (seen.has(step)) throw new TypeError(`the step "${step}" is in the ladder two times`);
|
|
74
|
+
seen.add(step);
|
|
75
|
+
}
|
|
76
|
+
for (const [construct, step] of Object.entries(constructs)) {
|
|
77
|
+
if (!seen.has(step)) throw new TypeError(`the construct "${construct}" needs the step "${String(step)}", which is not a step of the ladder`);
|
|
78
|
+
}
|
|
79
|
+
return Object.freeze({ steps: Object.freeze([...steps]), constructs: Object.freeze({ ...constructs }) });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* This function tells if a value is a step of a ladder. A host uses it to check its option for the highest step.
|
|
84
|
+
*
|
|
85
|
+
* @param ladder - The ladder.
|
|
86
|
+
* @param value - The value.
|
|
87
|
+
* @returns `true` when the value is the name of a step of the ladder.
|
|
88
|
+
*/
|
|
89
|
+
export function isStep<S extends string>(ladder: StepLadder<S>, value: unknown): value is S {
|
|
90
|
+
return typeof value === "string" && ladder.steps.includes(value as S);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* This function gives the position of a step in its ladder. The lowest step has the position 0.
|
|
95
|
+
*
|
|
96
|
+
* @param ladder - The ladder.
|
|
97
|
+
* @param step - The step.
|
|
98
|
+
* @returns The position.
|
|
99
|
+
* @throws TypeError for a name that is not a step of the ladder.
|
|
100
|
+
*/
|
|
101
|
+
export function stepRank<S extends string>(ladder: StepLadder<S>, step: S): number {
|
|
102
|
+
const rank = ladder.steps.indexOf(step);
|
|
103
|
+
if (rank < 0) throw new TypeError(`"${String(step)}" is not a step of the ladder. The steps are ${ladder.steps.join(", ")}`);
|
|
104
|
+
return rank;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** An item of the walk: a node, the key of its path and the item of its parent. Thus a path costs nothing until a reason needs it. */
|
|
108
|
+
interface Visit<N> {
|
|
109
|
+
readonly node: N;
|
|
110
|
+
readonly key: StepKey | undefined;
|
|
111
|
+
readonly parent: Visit<N> | undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function pathOf<N>(visit: Visit<N>): StepKey[] {
|
|
115
|
+
const path: StepKey[] = [];
|
|
116
|
+
for (let v: Visit<N> | undefined = visit; v?.key !== undefined; v = v.parent) path.push(v.key);
|
|
117
|
+
return path.reverse();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* This function gives the lowest step that a document needs, with the reason (STEPS.LOWEST, STEPS.REASON). The step is
|
|
122
|
+
* the highest step of the constructs of its nodes. The reason is the first node in document order with that step. The
|
|
123
|
+
* walk uses its own stack, thus a deep document does not overflow (STEPS.LINEAR). It visits a shared node one time,
|
|
124
|
+
* and it stops at the first node with the highest step of the ladder.
|
|
125
|
+
*
|
|
126
|
+
* @param ladder - The ladder of the language.
|
|
127
|
+
* @param walk - The construct and the child nodes of a node.
|
|
128
|
+
* @param root - The root node of the document.
|
|
129
|
+
* @returns The step, and the reason when the step is not the lowest step.
|
|
130
|
+
*/
|
|
131
|
+
export function stepOf<N, S extends string>(ladder: StepLadder<S>, walk: StepWalk<N>, root: N): StepNeed<N, S> {
|
|
132
|
+
const top = ladder.steps.length - 1;
|
|
133
|
+
let best = 0;
|
|
134
|
+
let reason: StepReason<N, S> | undefined;
|
|
135
|
+
const seen = new Set<unknown>();
|
|
136
|
+
const todo: Visit<N>[] = [{ node: root, key: undefined, parent: undefined }];
|
|
137
|
+
for (let visit = todo.pop(); visit !== undefined; visit = todo.pop()) {
|
|
138
|
+
const { node } = visit;
|
|
139
|
+
if (typeof node === "object" && node !== null) {
|
|
140
|
+
if (seen.has(node)) continue;
|
|
141
|
+
seen.add(node);
|
|
142
|
+
}
|
|
143
|
+
const construct = walk.construct(node);
|
|
144
|
+
const step = construct === undefined || !Object.hasOwn(ladder.constructs, construct) ? undefined : ladder.constructs[construct];
|
|
145
|
+
const rank = step === undefined ? 0 : ladder.steps.indexOf(step);
|
|
146
|
+
if (rank > best) {
|
|
147
|
+
best = rank;
|
|
148
|
+
reason = { construct: construct as string, step: step as S, path: pathOf(visit), node };
|
|
149
|
+
if (best === top) break;
|
|
150
|
+
}
|
|
151
|
+
const kids = [...walk.children(node)];
|
|
152
|
+
for (let i = kids.length - 1; i >= 0; i--) {
|
|
153
|
+
const [key, child] = kids[i] as readonly [StepKey, N];
|
|
154
|
+
todo.push({ node: child, key, parent: visit });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const step = ladder.steps[best] as S;
|
|
158
|
+
return reason === undefined ? { step } : { step, reason };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* This function compares the step of a document with the highest step that the host accepts (STEPS.MAX). The limit
|
|
163
|
+
* changes only which documents the host accepts. The language evaluates an accepted document as before, thus a limit
|
|
164
|
+
* does not change a value.
|
|
165
|
+
*
|
|
166
|
+
* @param ladder - The ladder of the language.
|
|
167
|
+
* @param need - The step of the document, from `stepOf`.
|
|
168
|
+
* @param maxStep - The highest step that the host accepts.
|
|
169
|
+
* @returns `undefined` when the host accepts the document, or the refusal that names the needed step.
|
|
170
|
+
* @throws TypeError when `maxStep` or the step of `need` is not a step of the ladder.
|
|
171
|
+
*/
|
|
172
|
+
export function checkStep<N, S extends string>(ladder: StepLadder<S>, need: StepNeed<N, S>, maxStep: S): StepRefusal<N, S> | undefined {
|
|
173
|
+
const limit = stepRank(ladder, maxStep);
|
|
174
|
+
const needed = stepRank(ladder, need.step);
|
|
175
|
+
if (needed <= limit) return undefined;
|
|
176
|
+
const head = `the document needs the step ${need.step}, and the host accepts the steps up to ${maxStep}`;
|
|
177
|
+
const reason = need.reason;
|
|
178
|
+
if (reason === undefined) return { needed: need.step, maxStep, message: head };
|
|
179
|
+
const where = reason.path.length === 0 ? "at the root" : `at the path ${JSON.stringify(reason.path)}`;
|
|
180
|
+
return { needed: need.step, maxStep, reason, message: `${head}. The construct "${reason.construct}" ${where} needs the step ${reason.step}` };
|
|
181
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1144,8 +1144,10 @@ function valueStep(value: unknown, key: Key): { readonly found: unknown } | { re
|
|
|
1144
1144
|
*
|
|
1145
1145
|
* - A record gives the type of its field. A closed record without the field has no such step. An open record gives
|
|
1146
1146
|
* `unknown`.
|
|
1147
|
-
* - A list gives the type of its items for an index, and `number` for `length`.
|
|
1148
|
-
* - A base type reads the steps of its domain, then of each parent (`Domain.steps`).
|
|
1147
|
+
* - A list gives the type of its items for an index, and `number` for `length`. Another key has no such step.
|
|
1148
|
+
* - A base type reads the steps of its domain, then of each parent (`Domain.steps`). A base type of the kernel
|
|
1149
|
+
* without such a step has no step, because its values are not objects (TYPE.NO-STEP, D120).
|
|
1150
|
+
* - A function type has no step, because a step into a function reaches nothing.
|
|
1149
1151
|
* - A union joins the steps of its types. `one` gives the values that the step reaches.
|
|
1150
1152
|
* - Each other step gives `unknown`, and `never` gives `never`.
|
|
1151
1153
|
*
|
|
@@ -1171,10 +1173,13 @@ export function stepType(
|
|
|
1171
1173
|
const found = steps[name] as ValueType;
|
|
1172
1174
|
return { type: examine === undefined ? found : examine(found, `type of the step ${name} of the domain ${each}`) };
|
|
1173
1175
|
}
|
|
1174
|
-
|
|
1176
|
+
// A value of a base type of the kernel is not an object, thus a step into it reaches nothing (TYPE.NO-STEP).
|
|
1177
|
+
return Object.hasOwn(STD_DOMAINS, form.name) ? NO_STEP : { type: "unknown" };
|
|
1178
|
+
case "fn":
|
|
1179
|
+
return NO_STEP;
|
|
1175
1180
|
case "list":
|
|
1176
1181
|
if (key === "length") return { type: "number" };
|
|
1177
|
-
return
|
|
1182
|
+
return indexOf(key) >= 0 ? { type: form.item } : NO_STEP;
|
|
1178
1183
|
case "record":
|
|
1179
1184
|
if (Object.hasOwn(form.fields, name)) return { type: form.fields[name] as ValueType };
|
|
1180
1185
|
return form.closed ? { missing: Object.keys(form.fields) } : { type: "unknown" };
|
|
@@ -1204,6 +1209,9 @@ export function stepType(
|
|
|
1204
1209
|
}
|
|
1205
1210
|
}
|
|
1206
1211
|
|
|
1212
|
+
/** The answer of `stepType` for a type whose members have no step with the key, and no fields to name. */
|
|
1213
|
+
const NO_STEP: StepType = { missing: [] };
|
|
1214
|
+
|
|
1207
1215
|
/** A term that `typeIssues` reads later: the term, its keys from the root and its depth. */
|
|
1208
1216
|
interface Pending {
|
|
1209
1217
|
readonly type: unknown;
|
package/src/world.ts
CHANGED
|
@@ -116,12 +116,20 @@ export interface LazyRule {
|
|
|
116
116
|
* - `lazy`: the parts that an evaluation can skip and still give a value.
|
|
117
117
|
* - `reads`: the roots that the code of the kind resolves as a whole, with the path of one root. The static facts
|
|
118
118
|
* give a read of each root at each document of the kind (D44). The tree node reads `self` this way.
|
|
119
|
+
* - `effects`: the effects that the code of `eval` can cause, in the form of the field `effects` of a kind (D27). In
|
|
120
|
+
* the answer of the hook `facts`, it gives the effects of the code of a name. The contract of a kind does not
|
|
121
|
+
* describe an own entry for another evaluator, thus only the hook can give its effects (CAP.OWN-EFFECTS, D118).
|
|
122
|
+
* - `drives`: the parts that the code of a runner evaluates itself, without a wait, in the form of the lazy rules. A
|
|
123
|
+
* step under such a part cannot wait for the host. The check of a run then refuses a part that can wait there
|
|
124
|
+
* (RUN.DRIVES, D119). Without `drives`, the check assumes that the runner passes each wait of its parts through.
|
|
119
125
|
*/
|
|
120
126
|
export interface KindFacts {
|
|
121
127
|
readonly pure?: boolean;
|
|
122
128
|
readonly volatile?: boolean;
|
|
123
129
|
readonly lazy?: readonly LazyRule[];
|
|
124
130
|
readonly reads?: readonly Root[];
|
|
131
|
+
readonly effects?: "type" | readonly string[];
|
|
132
|
+
readonly drives?: readonly LazyRule[];
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
/**
|
|
@@ -136,14 +144,38 @@ export interface KindFacts {
|
|
|
136
144
|
* - `default`: the term that gives the value when no rung before it answers and the declaration has no fold.
|
|
137
145
|
* - `circular`: a cycle of this attribute gives its fixed point, not `#CYCLE!` (TREE.CIRCULAR, D113). Only a cycle
|
|
138
146
|
* whose attributes each declare `circular` gives a fixed point.
|
|
147
|
+
* - `collect`: the value of a node without an answer of the first four rungs and without a fold. The node combines
|
|
148
|
+
* the contributions of each node of its subtree (TREE.COLLECT, D122).
|
|
139
149
|
*/
|
|
140
150
|
export interface AttributeDeclaration {
|
|
141
151
|
readonly copy?: boolean;
|
|
142
152
|
readonly fold?: { readonly unit: Json; readonly combine: Json };
|
|
143
153
|
readonly default?: Json;
|
|
144
154
|
readonly circular?: CircularDeclaration;
|
|
155
|
+
readonly collect?: CollectDeclaration;
|
|
145
156
|
}
|
|
146
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The declaration `collect` of an attribute (TREE.COLLECT, D122). The node of the attribute visits each node of its
|
|
160
|
+
* subtree. It visits the node itself first. Then it visits the nodes in its list `children`, in the order of the list.
|
|
161
|
+
*
|
|
162
|
+
* - `{ unit, combine }`: the collection starts with the term `unit`. The term `combine` then adds the contribution
|
|
163
|
+
* of each node, with the names `acc` and `item`. A node contributes when its kind has an equation for the
|
|
164
|
+
* attribute in `tree.contributes`.
|
|
165
|
+
* - `"names"`: the registry of binding names. Its value is a record from the header name of each node to its node
|
|
166
|
+
* (TREE.NAMED). Two nodes with one name give `#VALUE!` at the read of the registry.
|
|
167
|
+
*/
|
|
168
|
+
export type CollectDeclaration = { readonly unit: Json; readonly combine: Json } | "names";
|
|
169
|
+
|
|
170
|
+
/** A declaration `collect` that the kit can use, or the reason of a bad declaration (D122). */
|
|
171
|
+
export type CollectRule =
|
|
172
|
+
| { readonly ok: true; readonly names: true }
|
|
173
|
+
| { readonly ok: true; readonly names: false; readonly unit: Json; readonly combine: Json }
|
|
174
|
+
| { readonly ok: false; readonly reason: string };
|
|
175
|
+
|
|
176
|
+
/** The rule of the registry of binding names. */
|
|
177
|
+
const NAMES_RULE: CollectRule = /* @__PURE__ */ Object.freeze({ ok: true, names: true });
|
|
178
|
+
|
|
147
179
|
/**
|
|
148
180
|
* The declaration `circular` of an attribute (TREE.CIRCULAR, D113). The attribute that a cycle reaches first leads it.
|
|
149
181
|
* The leader evaluates again until no value of the cycle changes.
|
|
@@ -200,6 +232,21 @@ export function circularOf(declared: unknown): CircularRule {
|
|
|
200
232
|
return { ok: true, start: start as Json, limit };
|
|
201
233
|
}
|
|
202
234
|
|
|
235
|
+
/**
|
|
236
|
+
* This function reads the declaration `collect` of an attribute (D122). The tree kit, the reference, `check` and the
|
|
237
|
+
* graph of attribute instances read it with this one function.
|
|
238
|
+
*
|
|
239
|
+
* @param declared - The value of the field `collect`.
|
|
240
|
+
* @returns The rule of the collection, or the reason when the declaration has no correct form.
|
|
241
|
+
*/
|
|
242
|
+
export function collectOf(declared: unknown): CollectRule {
|
|
243
|
+
if (declared === "names") return NAMES_RULE;
|
|
244
|
+
if (!isRecord(declared) || !Object.hasOwn(declared, "unit") || !Object.hasOwn(declared, "combine")) {
|
|
245
|
+
return { ok: false, reason: 'The declaration collect needs the text "names" or a record with the fields unit and combine.' };
|
|
246
|
+
}
|
|
247
|
+
return { ok: true, names: false, unit: field(declared, "unit") as Json, combine: field(declared, "combine") as Json };
|
|
248
|
+
}
|
|
249
|
+
|
|
203
250
|
/**
|
|
204
251
|
* The tree contract of a kind (D42): the equations of the attributes of its nodes. Each equation is a document that
|
|
205
252
|
* evaluates with the node as `self` and without capabilities. A bare equation gets the type `of` of the parts of the
|
|
@@ -209,11 +256,14 @@ export function circularOf(declared: unknown): CircularRule {
|
|
|
209
256
|
* - `down`: the attributes of each child of the node. `self` is this node. An equation sees the names `child`,
|
|
210
257
|
* `index` and `prev` (TREE.DOWN).
|
|
211
258
|
* - `attributes`: the declarations of the attributes of the node, for the rungs `copy` and `default`.
|
|
259
|
+
* - `contributes`: the contribution of the node to each collection of an attribute (TREE.COLLECT, D122). An
|
|
260
|
+
* equation sees no names.
|
|
212
261
|
*/
|
|
213
262
|
export interface TreeContract {
|
|
214
263
|
readonly equations?: Readonly<Record<string, Json>>;
|
|
215
264
|
readonly down?: Readonly<Record<string, Json>>;
|
|
216
265
|
readonly attributes?: Readonly<Record<string, AttributeDeclaration>>;
|
|
266
|
+
readonly contributes?: Readonly<Record<string, Json>>;
|
|
217
267
|
}
|
|
218
268
|
|
|
219
269
|
/**
|