@mark1russell7/coeval 0.21.0 → 0.23.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/src/static.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
2
- import { type Kind, type LazyRule, type World, type WorldEntry, circularOf, collectOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
1
+ import { type Envelope, type Json, type Key, field, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
2
+ import { type Kind, type LazyRule, type World, type WorldEntry, atArgsOf, atRuleOf, circularOf, collectOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
3
3
  import { answerOf, ask } from "./ladder.ts";
4
4
  import { type PartSlot, partsOf, rulesCover, scopesOf } from "./parts.ts";
5
5
  import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
@@ -8,6 +8,7 @@ import { type Machine, partCaps, permits, waitedReason } from "./evaluate.ts";
8
8
  import type { ValueType } from "./types.ts";
9
9
  import { type RootTypes, type TypeNode, type Typed, machineTypeIssues, typePass } from "./typing.ts";
10
10
  import { cycleIssues } from "./instances.ts";
11
+ import { STD_WALKERS } from "./stdlib.ts";
11
12
 
12
13
  /** One document that a walk visits. */
13
14
  export interface DocVisit {
@@ -102,6 +103,17 @@ function keysOf(trail: Trail | undefined): readonly Key[] {
102
103
 
103
104
  /** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
104
105
  class Visit implements DocVisit {
106
+ readonly doc: Envelope;
107
+ readonly declared: boolean;
108
+ readonly depth: number;
109
+ readonly names: ReadonlySet<string>;
110
+ readonly later: ReadonlySet<string>;
111
+ readonly slot: readonly Key[];
112
+ readonly parent: DocVisit | undefined;
113
+ readonly kind: Kind | undefined;
114
+ readonly typePath: readonly Key[] | undefined;
115
+ readonly address: true | undefined;
116
+ readonly caps: readonly Cap[] | undefined;
105
117
  readonly #place: Trail | undefined;
106
118
  readonly #at: Trail | undefined;
107
119
  readonly #bound: readonly string[];
@@ -110,20 +122,31 @@ class Visit implements DocVisit {
110
122
  constructor(
111
123
  place: Trail | undefined,
112
124
  at: Trail | undefined,
113
- readonly doc: Envelope,
114
- readonly declared: boolean,
115
- readonly depth: number,
116
- readonly names: ReadonlySet<string>,
117
- readonly later: ReadonlySet<string>,
118
- readonly slot: readonly Key[],
119
- readonly parent: DocVisit | undefined,
120
- readonly kind: Kind | undefined,
121
- readonly typePath: readonly Key[] | undefined,
122
- readonly address: true | undefined,
123
- readonly caps: readonly Cap[] | undefined,
125
+ doc: Envelope,
126
+ declared: boolean,
127
+ depth: number,
128
+ names: ReadonlySet<string>,
129
+ later: ReadonlySet<string>,
130
+ slot: readonly Key[],
131
+ parent: DocVisit | undefined,
132
+ kind: Kind | undefined,
133
+ typePath: readonly Key[] | undefined,
134
+ address: true | undefined,
135
+ caps: readonly Cap[] | undefined,
124
136
  bound: readonly string[],
125
137
  expect: ValueType | undefined,
126
138
  ) {
139
+ this.doc = doc;
140
+ this.declared = declared;
141
+ this.depth = depth;
142
+ this.names = names;
143
+ this.later = later;
144
+ this.slot = slot;
145
+ this.parent = parent;
146
+ this.kind = kind;
147
+ this.typePath = typePath;
148
+ this.address = address;
149
+ this.caps = caps;
127
150
  this.#place = place;
128
151
  this.#at = at;
129
152
  this.#bound = bound;
@@ -690,6 +713,8 @@ export function equationsOf(kind: Kind): readonly Equation[] {
690
713
  out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES, attribute: name, down: false });
691
714
  }
692
715
  if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [], attribute: name, down: false });
716
+ // Only the equation is known before the run. The document that it gives is not known (TREE.HIGHER-CHECK, D126).
717
+ if (declared.higher !== undefined) out.push({ label: `tree.attributes.${name}.higher`, term: declared.higher, names: [], attribute: name, down: false });
693
718
  const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
694
719
  if (collect?.ok === true && !collect.names) {
695
720
  out.push({ label: `tree.attributes.${name}.collect.unit`, term: collect.unit, names: [], attribute: name, down: false });
@@ -698,6 +723,11 @@ export function equationsOf(kind: Kind): readonly Equation[] {
698
723
  }
699
724
  // A contribution is not the attribute of its node, thus an own part of its name does not hide it (D122).
700
725
  for (const [name, term] of Object.entries(tree.contributes ?? {})) out.push({ label: `tree.contributes.${name}`, term, names: [], attribute: name, down: false, contributes: true });
726
+ // An equation of an attribute with parameters sees each parameter as a name (TREE.AT, D124).
727
+ for (const [name, declared] of Object.entries(tree.at ?? {})) {
728
+ const rule = atRuleOf(declared);
729
+ if (rule.ok) out.push({ label: `tree.at.${name}`, term: rule.equation, names: rule.params, attribute: name, down: false, at: true });
730
+ }
701
731
  return out;
702
732
  }
703
733
 
@@ -713,6 +743,8 @@ export interface Equation {
713
743
  readonly down: boolean;
714
744
  /** `true` for an equation of `contributes`: it gives a contribution to a collection (TREE.COLLECT). */
715
745
  readonly contributes?: true;
746
+ /** `true` for an equation of `at`: it gives an attribute with parameters (TREE.AT). */
747
+ readonly at?: true;
716
748
  }
717
749
 
718
750
  /**
@@ -877,15 +909,16 @@ function hasEntry(table: Readonly<Record<string, unknown>> | undefined, name: st
877
909
  /**
878
910
  * This function tells if a rung of the ladder can answer an attribute of a node (TREE.LADDER). An unknown parent can
879
911
  * be a node of each kind of the world, thus each name of `down` in the world can answer. A copy can always answer,
880
- * because `check` does not know a node without a parent.
912
+ * because `check` does not know a node without a parent. A kind with `forwards` can answer each other name, because the
913
+ * forwarded node is not known before the run (TREE.FORWARD-CHECK).
881
914
  */
882
915
  export function answered(world: World, node: SeenNode, name: string): boolean {
883
916
  if (node.own?.has(name) === true) return true;
884
917
  const tree = node.kind.tree;
885
- if (hasEntry(tree?.equations, name)) return true;
918
+ if (hasEntry(tree?.equations, name) || (tree?.forwards !== undefined && tree.forwards !== name)) return true;
886
919
  if (node.parent === undefined ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
887
920
  const declared = tree?.attributes !== undefined && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : undefined;
888
- return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.collect !== undefined || declared.default !== undefined);
921
+ return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.collect !== undefined || declared.default !== undefined || declared.higher !== undefined);
889
922
  }
890
923
 
891
924
  /**
@@ -900,11 +933,12 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
900
933
  const needs: Need[] = [];
901
934
  state.needs.set(kind, needs);
902
935
  for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({ name, why: "the kind declares it", hidden: undefined });
936
+ if (typeof kind.tree?.forwards === "string" && !Object.hasOwn(STRUCTURAL, kind.tree.forwards)) needs.push({ name: kind.tree.forwards, why: "the kind forwards to it", hidden: undefined });
903
937
  const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
904
- for (const { label, term, names, attribute, down, contributes } of equationsOf(kind)) {
938
+ for (const { label, term, names, attribute, down, contributes, at } of equationsOf(kind)) {
905
939
  const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
906
940
  const read = (name: string): void => {
907
- needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true ? undefined : attribute });
941
+ needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true || at === true ? undefined : attribute });
908
942
  };
909
943
  for (const issue of checkAll(machine, term, inner, undefined, state, { node: self, read })) {
910
944
  out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
@@ -917,12 +951,13 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
917
951
  * This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
918
952
  * structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
919
953
  * declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK). A declaration
920
- * `collect` without its form gives `#VALUE!` too (TREE.COLLECT).
954
+ * `collect` without its form gives `#VALUE!` too (TREE.COLLECT). So does an entry of `at` without its form (TREE.AT-CHECK).
955
+ * A field `forwards` that is not a text gives `#VALUE!`, and a structural name gives `#NAME?` (TREE.FORWARD-CHECK).
921
956
  */
922
957
  function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
923
958
  const out: Issue[] = [];
924
959
  const tree = kind.tree;
925
- for (const table of ["equations", "down", "attributes", "contributes"] as const) {
960
+ for (const table of ["equations", "down", "attributes", "contributes", "at"] as const) {
926
961
  for (const name of Object.keys(tree?.[table] ?? {})) {
927
962
  const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
928
963
  if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
@@ -935,6 +970,15 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
935
970
  const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
936
971
  if (collect !== undefined && !collect.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.collect. ${collect.reason}` });
937
972
  }
973
+ for (const [name, declared] of Object.entries(tree?.at ?? {})) {
974
+ const rule = atRuleOf(declared);
975
+ if (!rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad entry tree.at.${name}. ${rule.reason}` });
976
+ }
977
+ const forwards: unknown = tree?.forwards;
978
+ if (forwards !== undefined && typeof forwards !== "string") out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad field tree.forwards. It must be a text.` });
979
+ if (typeof forwards === "string" && Object.hasOwn(STRUCTURAL, forwards)) {
980
+ out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.forwards: ${String(STRUCTURAL[forwards])}.` });
981
+ }
938
982
  if (kind.side === "run") {
939
983
  out.push({ code: "#CALC!", place: [], reason: `The tree kind ${kindText} is on the run side: a node evaluates its attributes on demand, thus in no fixed order.` });
940
984
  }
@@ -966,9 +1010,14 @@ function nodeIssues(machine: Machine, node: SeenNode, state: TreeState, v: DocVi
966
1010
  * This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
967
1011
  * nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
968
1012
  * function gives the index of the step and the reason. In an equation, a read of the node of the equation goes to the
969
- * needs of its kind.
1013
+ * needs of its kind. A step `tree.at` with literal arguments gets the checks of TREE.AT-CHECK.
970
1014
  */
971
- function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly Json[], rootSelf: RootSelf | undefined): { readonly index: number; readonly reason: string } | undefined {
1015
+ function readIssue(
1016
+ machine: Machine,
1017
+ self: SeenNode | undefined,
1018
+ path: readonly Json[],
1019
+ rootSelf: RootSelf | undefined,
1020
+ ): { readonly index: number; readonly reason: string; readonly code?: ErrorCode } | undefined {
972
1021
  let node = self;
973
1022
  let index = 1;
974
1023
  while (node !== undefined && index < path.length && path[index] === "parent") {
@@ -976,6 +1025,10 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
976
1025
  index += 1;
977
1026
  }
978
1027
  const step = path[index];
1028
+ if (node !== undefined && isAtStep(machine, step)) {
1029
+ const at = atStepOf(node.kind, node.text, step.value);
1030
+ return at.kind === "issue" ? { index, reason: at.reason, code: at.code } : undefined;
1031
+ }
979
1032
  if (node === undefined || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return undefined;
980
1033
  const name = String(step);
981
1034
  if (node.own === undefined) {
@@ -985,6 +1038,46 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
985
1038
  return answered(machine.world, node, name) ? undefined : { index, reason: `No rung gives the attribute ${name} of the kind ${node.text}.` };
986
1039
  }
987
1040
 
1041
+ /** This function tells if a step of a path is a step `tree.at` of the standard walker (TREE.AT, D124). */
1042
+ export function isAtStep(machine: Machine, step: unknown): step is Envelope {
1043
+ return isEnvelope(step) && walkerOf(machine, step) === STD_WALKERS["walk.tree.at"];
1044
+ }
1045
+
1046
+ /**
1047
+ * A step `tree.at` of a known kind before the run (TREE.AT-CHECK, D124). An exact step names an instance. The run of a
1048
+ * step with an issue fails at the step. Only the run knows a step with a document in its data, or a bad entry.
1049
+ */
1050
+ export type AtStep =
1051
+ | { readonly kind: "exact"; readonly attr: string; readonly key: string; readonly equation: Json; readonly params: readonly string[]; readonly names: Readonly<Record<string, Json>> }
1052
+ | { readonly kind: "issue"; readonly code: ErrorCode; readonly reason: string }
1053
+ | { readonly kind: "unknown" };
1054
+
1055
+ /**
1056
+ * This function examines the data of a step `tree.at` on a node of a known kind (TREE.AT-CHECK, D124). `check` and the
1057
+ * graph of attribute instances read the step with this one function.
1058
+ */
1059
+ export function atStepOf(kind: Kind, kindText: string, data: unknown): AtStep {
1060
+ if (holdsDocument(data)) return { kind: "unknown" };
1061
+ const attr = isRecord(data) ? field(data, "attr") : undefined;
1062
+ if (typeof attr !== "string") return { kind: "issue", code: "#VALUE!", reason: "The step tree.at needs a text attr." };
1063
+ const table = kind.tree?.at;
1064
+ const declared = table !== undefined && Object.hasOwn(table, attr) ? table[attr] : undefined;
1065
+ if (declared === undefined) return { kind: "issue", code: "#N/A", reason: `No rung gives the attribute ${attr} with parameters of the kind ${kindText}.` };
1066
+ const rule = atRuleOf(declared);
1067
+ // The issue of a bad entry is an issue of the kind (contractIssues).
1068
+ if (!rule.ok) return { kind: "unknown" };
1069
+ const given = atArgsOf(rule.params, isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {});
1070
+ if (!given.ok) return { kind: "issue", code: "#VALUE!", reason: `The step tree.at of ${attr} of the kind ${kindText} has bad arguments. ${given.reason}` };
1071
+ return { kind: "exact", attr, key: given.key, equation: rule.equation, params: rule.params, names: given.names };
1072
+ }
1073
+
1074
+ /** This function tells if a value holds a document. A document in the data of a step can be a part, thus only the run knows its value. */
1075
+ function holdsDocument(value: unknown): boolean {
1076
+ if (isEnvelope(value)) return true;
1077
+ if (Array.isArray(value)) return value.some(holdsDocument);
1078
+ return isRecord(value) && Object.values(value).some(holdsDocument);
1079
+ }
1080
+
988
1081
  /** The self of the root of a walk for the checks of attributes: the node of an equation, and the function for its reads. */
989
1082
  interface RootSelf {
990
1083
  readonly node: SeenNode;
@@ -1149,7 +1242,7 @@ function checkAll(
1149
1242
  // Only the standard code of a reference reads the attribute, thus a header that changes the code hides the read.
1150
1243
  if (attributes && root === "self" && v.address !== true && name === REF_CODE) {
1151
1244
  const issue = readIssue(machine, self, path, rootSelf);
1152
- if (issue !== undefined) push({ code: "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
1245
+ if (issue !== undefined) push({ code: issue.code ?? "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
1153
1246
  }
1154
1247
  path.forEach((step, index) => {
1155
1248
  if (index === 0 || isKey(step)) return;
package/src/stdlib.ts CHANGED
@@ -18,7 +18,19 @@ 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, collectOf, equationType, lookupKind } from "./world.ts";
21
+ import {
22
+ type AttributeDeclaration,
23
+ type CircularRule,
24
+ type TreeContract,
25
+ GENERATION_LIMIT,
26
+ atArgsOf,
27
+ atRuleOf,
28
+ circularOf,
29
+ collectOf,
30
+ documentKey,
31
+ equationType,
32
+ lookupKind,
33
+ } from "./world.ts";
22
34
  import { STD_DOMAINS } from "./types.ts";
23
35
  import type { SyntaxProfile } from "./syntax.ts";
24
36
 
@@ -74,6 +86,16 @@ export const STD_WORLD: World = {
74
86
  pure: true,
75
87
  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
88
  },
89
+ at: {
90
+ verbs: { step: "walk.tree.at", view: "view.card" },
91
+ pure: true,
92
+ summary: "A step of a path: it reads an attribute with parameters of the node before it, with the arguments of the step (TREE.AT).",
93
+ },
94
+ quote: {
95
+ verbs: { eval: "data.quote", view: "view.value" },
96
+ pure: true,
97
+ summary: "A document as data. Its value is the result, and nothing in it evaluates. A higher-order attribute uses it (TREE.HIGHER).",
98
+ },
77
99
  },
78
100
  },
79
101
  panel: {
@@ -177,6 +199,8 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
177
199
  { name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
178
200
  { name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
179
201
  { name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
202
+ { name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
203
+ { name: "tree.quote", kind: "tree.quote", args: "literal" },
180
204
  // An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
181
205
  { name: "add", kind: "add" },
182
206
  { name: "multiply", kind: "multiply" },
@@ -218,6 +242,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
218
242
  "data.number": /* @__PURE__ */ dataOf("number"),
219
243
  "data.text": /* @__PURE__ */ dataOf("string"),
220
244
  "data.bool": /* @__PURE__ */ dataOf("boolean"),
245
+ "data.quote": ({ data }) => ok(data ?? null),
221
246
  "math.add": ({ parts }) => {
222
247
  const all = allValues(parts.map((part) => part.evaluate()));
223
248
  if ("error" in all) return all.error;
@@ -265,10 +290,10 @@ interface TreeBuild {
265
290
  }
266
291
 
267
292
  /** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
268
- export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default";
293
+ export type AttributeRung = "own" | "kind" | "parent" | "copy" | "default" | "forward";
269
294
 
270
295
  /** The rungs of the ladder of an attribute, in their order (TREE.LADDER, D42). */
271
- export const ATTRIBUTE_RUNGS: readonly AttributeRung[] = /* @__PURE__ */ Object.freeze(["own", "kind", "parent", "copy", "default"] as const);
296
+ export const ATTRIBUTE_RUNGS: readonly AttributeRung[] = /* @__PURE__ */ Object.freeze(["own", "kind", "parent", "copy", "default", "forward"] as const);
272
297
 
273
298
  /** The record of the demand of one attribute of a node (TREE.RUNGS). */
274
299
  export interface AttributeTrace {
@@ -343,6 +368,10 @@ export interface AttributeAnswer {
343
368
  * A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
344
369
  * A host that keeps its cells by the keys of `place` can give the answer of another node or a stale answer.
345
370
  *
371
+ * An attribute with parameters gets one cell for each key of its arguments (TREE.AT-CELL, D124). Its `place` is a
372
+ * second array with the same keys, one for each key of a node. Its `attribute` is the JSON text of the name and the
373
+ * arguments, for example `["height",{"width":200}]`.
374
+ *
346
375
  * The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
347
376
  * of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
348
377
  * a cell that computes again uses the last one.
@@ -916,6 +945,180 @@ function collected(root: HostThing, key: string, declared: unknown): Result {
916
945
  return acc;
917
946
  }
918
947
 
948
+ /** A function from the name of an attribute and the arguments to the result, or `undefined` when no rung answers (D124). */
949
+ export type AtReader = (attr: string, args: unknown) => Result | undefined;
950
+
951
+ /**
952
+ * The host things that answer the step `tree.at` with their own function (D124). A node of the reference is in it, and
953
+ * its fields do not change. The walker reads the state of a node of the kit.
954
+ */
955
+ export const AT_READERS: WeakMap<object, AtReader> = /* @__PURE__ */ new WeakMap();
956
+
957
+ /** The memo of the instances of the attributes with parameters of each node, without the cells of a host (TREE.AT). */
958
+ const atMemos = /* @__PURE__ */ new WeakMap<NodeState, Map<string, Cell>>();
959
+
960
+ /** The second key of each key of a node, for the cells of the attributes with parameters (TREE.AT-CELL). */
961
+ const atPlaces = /* @__PURE__ */ new WeakMap<readonly Key[], readonly Key[]>();
962
+
963
+ /**
964
+ * This function gives an instance of an attribute with parameters of a node (TREE.AT, D124). The equation in
965
+ * `tree.at` of the kind of the node evaluates with the arguments as names. The memo keeps one result for each key of
966
+ * the arguments, and a demand of an instance in evaluation gives `#CYCLE!`. The function is outside the code of
967
+ * `read`, thus a node without such attributes does no new work.
968
+ */
969
+ function atRead(n: NodeState, attr: string, args: unknown): Result | undefined {
970
+ const declared: unknown = entryOf(n.tree?.at, attr);
971
+ if (declared === undefined) return undefined;
972
+ const rule = atRuleOf(declared);
973
+ if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(attr, n.type, n.place)} has a bad entry tree.at. ${rule.reason}`);
974
+ const given = atArgsOf(rule.params, args);
975
+ if (!given.ok) return fail("#VALUE!", `The step tree.at of ${attributeText(attr, n.type, n.place)} has bad arguments. ${given.reason}`);
976
+ // An open fixed point does not use the cells of the host (D113).
977
+ const host = openPoint === undefined ? n.host : undefined;
978
+ const place = n.host === undefined ? n.place : keptAt(atPlaces, n.place, () => Object.freeze([...n.place]));
979
+ let marks: Map<string, Cell> | undefined = n.host === undefined ? atMemos.get(n) : keptAt(marksByPlace, place, () => new Map<string, Cell>());
980
+ if (marks === undefined) {
981
+ marks = new Map();
982
+ atMemos.set(n, marks);
983
+ }
984
+ const key = `[${JSON.stringify(attr)},${given.key}]`;
985
+ const text = attributeText(`${attr} with ${given.key}`, n.type, n.place);
986
+ const cell = marks.get(key);
987
+ if (cell !== undefined) return cell.busy ? cycleAt(marks, key, text) : cell.result;
988
+ const demand: Demand = { cells: marks, key, text };
989
+ const loop = openPoint;
990
+ const compute = (): AttributeAnswer => ({ result: n.equation(rule.equation, given.names), by: "kind" });
991
+ marks.set(key, BUSY);
992
+ demands.push(demand);
993
+ let thrown: unknown = NOT_THROWN;
994
+ let answer: AttributeAnswer;
995
+ try {
996
+ answer =
997
+ host === undefined
998
+ ? compute()
999
+ : host(place, key, () => {
1000
+ try {
1001
+ return compute();
1002
+ } catch (error) {
1003
+ thrown = error;
1004
+ throw error;
1005
+ }
1006
+ });
1007
+ } catch (error) {
1008
+ marks.delete(key);
1009
+ // An exception of the kit or of the stack goes on. Another exception of the cell is a cycle of the host (D108).
1010
+ if (host === undefined || error === thrown) throw error;
1011
+ const chain = demands.map((item) => item.text).join(" -> ");
1012
+ answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
1013
+ } finally {
1014
+ demands.pop();
1015
+ }
1016
+ // A result that read an approximation is not final. With the cells of a host, the cell is the memo.
1017
+ if (n.host !== undefined || loop?.tentative.has(demand) === true) marks.delete(key);
1018
+ else marks.set(key, { busy: false, result: answer.result, by: answer.by });
1019
+ return answer.result;
1020
+ }
1021
+
1022
+ /** The most documents and the most characters of the keys in the table of generated documents. */
1023
+ const INTERNED_DOCUMENTS = 1000;
1024
+ const INTERNED_CHARACTERS = 16_000_000;
1025
+
1026
+ /** The first document of each structural key (TREE.HIGHER-PLAN, D126). */
1027
+ const internedDocuments = /* @__PURE__ */ new Map<string, Json>();
1028
+
1029
+ /** The kept document of each document object. An object gets its key one time, as PLAN.CACHE says. */
1030
+ const internedObjects = /* @__PURE__ */ new WeakMap<object, Json>();
1031
+
1032
+ /** The characters of the keys of `internedDocuments`. */
1033
+ let internedCharacters = 0;
1034
+
1035
+ /**
1036
+ * This function gives the one object of a document (TREE.HIGHER-PLAN, D126). `equationPlan` caches by object, thus a
1037
+ * new object with a known key compiles nothing. The table has a limit, and a full table starts again. A document that
1038
+ * left the table compiles again, and that costs only time.
1039
+ *
1040
+ * @param doc - The value of an equation.
1041
+ * @returns The document, or `undefined` for a value that is not JSON data.
1042
+ */
1043
+ function internedDocument(doc: unknown): Json | undefined {
1044
+ const object = typeof doc === "object" && doc !== null;
1045
+ const known = object ? internedObjects.get(doc) : undefined;
1046
+ if (known !== undefined) return known;
1047
+ const key = documentKey(doc);
1048
+ if (key === undefined) return undefined;
1049
+ if (!object) return doc as Json;
1050
+ let kept = internedDocuments.get(key);
1051
+ if (kept === undefined) {
1052
+ if (internedDocuments.size >= INTERNED_DOCUMENTS || internedCharacters + key.length > INTERNED_CHARACTERS) {
1053
+ internedDocuments.clear();
1054
+ internedCharacters = 0;
1055
+ }
1056
+ kept = doc as Json;
1057
+ internedDocuments.set(key, kept);
1058
+ internedCharacters += key.length;
1059
+ }
1060
+ internedObjects.set(doc, kept);
1061
+ return kept;
1062
+ }
1063
+
1064
+ /** The depth of each root of a generated document: the generated documents from the root of the tree to it (D126). */
1065
+ const generations = /* @__PURE__ */ new WeakMap<NodeState, number>();
1066
+
1067
+ /** This function gives the depth of the nearest root of a generated document at or above a node of a tree, or 0. */
1068
+ function generationOf(node: HostThing, build: TreeBuild): number {
1069
+ let thing: Thing | undefined = node;
1070
+ for (let at = stateOf(thing); at?.build === build; at = stateOf(thing)) {
1071
+ const depth = generations.get(at);
1072
+ if (depth !== undefined) return depth;
1073
+ thing = (thing as HostThing).deref("parent");
1074
+ }
1075
+ return 0;
1076
+ }
1077
+
1078
+ /**
1079
+ * This function gives a higher-order attribute of a node (TREE.HIGHER, D126). The equation gives a document as data.
1080
+ * The document evaluates as an equation, with the node as `self`. Thus its root is a node under the node: it finds the
1081
+ * open tree, and it has the node as its parent. The function is outside the code of `read`, as a fold is.
1082
+ */
1083
+ function generated(n: NodeState, node: HostThing, key: string, term: Json): Result {
1084
+ const value = n.equation(term, NO_NAMES);
1085
+ if (!value.ok) return value;
1086
+ const doc = internedDocument(value.value);
1087
+ const text = attributeText(key, n.type, n.place);
1088
+ if (doc === undefined) return fail("#VALUE!", `The equation of the higher-order attribute ${text} gives a value that is not JSON data.`);
1089
+ const depth = generationOf(node, n.build) + 1;
1090
+ if (depth > GENERATION_LIMIT) return fail("#DEPTH!", `The higher-order attribute ${text} is under more than ${GENERATION_LIMIT} generated documents.`);
1091
+ const root = n.equation(doc, NO_NAMES);
1092
+ if (!root.ok) return root;
1093
+ const made = stateOf(root.value);
1094
+ if (made === undefined) return fail("#VALUE!", `The document of the higher-order attribute ${text} gives no node of the tree.`);
1095
+ generations.set(made, depth);
1096
+ return root;
1097
+ }
1098
+
1099
+ /**
1100
+ * This function gives the last rung of the ladder (TREE.FORWARD, D126). The attribute `forwards` gives a node, and that
1101
+ * node answers the attribute through its own ladder. The attribute `forwards` itself gets no such answer.
1102
+ */
1103
+ function forwarded(n: NodeState, node: HostThing, key: string, name: unknown): { readonly result: Result; readonly by: AttributeRung } | undefined {
1104
+ if (key === name) return undefined;
1105
+ const text = attributeText(key, n.type, n.place);
1106
+ if (typeof name !== "string") return { result: fail("#VALUE!", `The attribute ${text} has a contract with a bad field forwards. It must be a text.`), by: "forward" };
1107
+ const target = n.read(name);
1108
+ if (target === undefined) return undefined;
1109
+ if (!target.ok) return { result: target, by: "forward" };
1110
+ const to = stateOf(target.value);
1111
+ if (to === undefined) return { result: fail("#VALUE!", `The attribute ${name} that ${text} forwards to gives no node of a tree.`), by: "forward" };
1112
+ // An equation can give a node without a declaration higher, thus the rung counts each node that it reaches.
1113
+ if (!generations.has(to)) {
1114
+ const depth = generationOf(node, n.build) + 1;
1115
+ if (depth > GENERATION_LIMIT) return { result: fail("#DEPTH!", `The attribute ${text} forwards through more than ${GENERATION_LIMIT} generated documents.`), by: "forward" };
1116
+ generations.set(to, depth);
1117
+ }
1118
+ const result = to.read(key);
1119
+ return result === undefined ? undefined : { result, by: "forward" };
1120
+ }
1121
+
919
1122
  /**
920
1123
  * This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
921
1124
  * step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
@@ -981,7 +1184,8 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
981
1184
  if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
982
1185
  if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
983
1186
  if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
984
- return undefined;
1187
+ if (declared?.higher !== undefined) return { result: generated(state, node, key, declared.higher), by: "default" };
1188
+ return tree?.forwards === undefined ? undefined : forwarded(state, node, key, tree.forwards);
985
1189
  };
986
1190
 
987
1191
  /**
@@ -1078,7 +1282,9 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
1078
1282
  trace(key) {
1079
1283
  const cell = cells.get(key);
1080
1284
  if (cell === undefined || cell.busy) return undefined;
1081
- const rungs = cell.by === undefined ? ATTRIBUTE_RUNGS : ATTRIBUTE_RUNGS.slice(0, ATTRIBUTE_RUNGS.indexOf(cell.by) + 1);
1285
+ // The last rung is on the way only for a kind with the field forwards.
1286
+ const end = cell.by === undefined ? ATTRIBUTE_RUNGS.length - (tree?.forwards === undefined ? 1 : 0) : ATTRIBUTE_RUNGS.indexOf(cell.by) + 1;
1287
+ const rungs = ATTRIBUTE_RUNGS.slice(0, end);
1082
1288
  return cell.by === undefined ? { attribute: key, rungs } : { attribute: key, rungs, by: cell.by };
1083
1289
  },
1084
1290
  json(path) {
@@ -1256,8 +1462,25 @@ const named: Walker = ({ data, here, deref }) => {
1256
1462
  return { ok: false, reason: `No registry ${registry} is on the way.` };
1257
1463
  };
1258
1464
 
1465
+ /**
1466
+ * The walker of the step `tree.at` (TREE.AT, D124). The field `attr` names an attribute with parameters, and the
1467
+ * field `args` gives the arguments. The thing before the step is a node. A failed instance throws its own error.
1468
+ */
1469
+ const at: Walker = ({ data, here }) => {
1470
+ const attr = isRecord(data) ? field(data, "attr") : undefined;
1471
+ if (typeof attr !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.at needs a text attr." };
1472
+ const args = isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {};
1473
+ const state = stateOf(here);
1474
+ const reader = state === undefined && typeof here === "object" && here !== null ? AT_READERS.get(here) : undefined;
1475
+ if (state === undefined && reader === undefined) return { ok: false, reason: `The step tree.at reads ${attr} of a thing that is not a node of a tree.` };
1476
+ const result = state === undefined ? (reader as AtReader)(attr, args) : atRead(state, attr, args);
1477
+ if (result === undefined) return { ok: false, reason: `No rung gives the attribute ${attr} with parameters.` };
1478
+ if (!result.ok) throw new Failure(result.error);
1479
+ return { ok: true, thing: result.value as Thing };
1480
+ };
1481
+
1259
1482
  /** The walkers of the standard world, by name. */
1260
- export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named };
1483
+ export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named, "walk.tree.at": at };
1261
1484
 
1262
1485
  /** This function evaluates parts one after the other, and stops at the first error. */
1263
1486
  function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { readonly values: readonly unknown[] } | { readonly error: Result }, unknown> {