@mark1russell7/coeval 0.20.1 → 0.22.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,13 +1,14 @@
1
- import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
2
- import { type Kind, type World, type WorldEntry, circularOf, eachKind, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
3
- import { ask } from "./ladder.ts";
4
- import { type PartSlot, partsOf, scopesOf } from "./parts.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
+ import { answerOf, ask } from "./ladder.ts";
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";
6
6
  import type { ErrorCode } from "./errors.ts";
7
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 {
@@ -405,6 +406,19 @@ export interface Typing {
405
406
  readonly places: readonly Typed[];
406
407
  }
407
408
 
409
+ /**
410
+ * This function gives the parts that the code of a runner evaluates itself, without a wait (RUN.DRIVES, D119). The
411
+ * answer of the hook `facts` for the code wins over the contract. The contract describes only the code of its kind for
412
+ * `eval`. Without a declaration, the runner passes each wait of its parts through.
413
+ */
414
+ function driveRules(machine: Machine, v: DocVisit, kind: Kind, name: string, rung: string | undefined, verb: string): readonly LazyRule[] | undefined {
415
+ if (verb !== "eval") return undefined;
416
+ const hook = machine.facts?.(v.typePath ?? [], name)?.drives;
417
+ if (hook !== undefined) return hook;
418
+ const described = v.declared || rung !== "own" || name === answerOf(machine.world, undefined, kind, verb)?.name;
419
+ return described ? kind.drives : undefined;
420
+ }
421
+
408
422
  /** The least authority: the capability `[]` permits each effect (CAP.KEYS). */
409
423
  const EACH: readonly Cap[] = /* @__PURE__ */ Object.freeze([[]]);
410
424
 
@@ -412,16 +426,42 @@ const EACH: readonly Cap[] = /* @__PURE__ */ Object.freeze([[]]);
412
426
  const NO_EFFECTS: readonly (readonly Key[])[] = /* @__PURE__ */ Object.freeze([]);
413
427
 
414
428
  /**
415
- * This function gives the effects that the contract of the kind of a visit declares for a verb, as keys
416
- * (FACTS.EFFECTS, VERB.EFFECTS). The text `"type"` gives the type of the document after each alias. A visit without a
417
- * kind declares no effect. `check`, `factsOf` and `effectsOf` read the declarations with this one function (D27).
429
+ * This function gives the effects that the code of a visit declares for a verb, as keys (FACTS.EFFECTS, VERB.EFFECTS).
430
+ * The text `"type"` gives the type of the document after each alias. A visit without a kind declares no effect.
431
+ * `check`, `factsOf`, `effectsOf` and `checkLatent` read the declarations with this one function (D27).
432
+ *
433
+ * For `eval`, the answer of the hook `facts` of the machine for the code of the visit wins over the contract of the
434
+ * kind (CAP.OWN-EFFECTS, D118). Thus a language gives the effects of an own entry for another evaluator. Without a
435
+ * machine, or without the field `effects` in the answer, the contract gives the effects.
418
436
  *
419
437
  * @param v - The visit.
420
438
  * @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
421
- * @returns The keys of each declared effect, in the order of the contract.
439
+ * @param machine - The machine with the hook `facts`, when the caller has it.
440
+ * @param name - The name of the evaluator of the visit, when the caller knows it. Else the ladder gives it.
441
+ * @returns The keys of each declared effect, in the order of the declaration.
442
+ */
443
+ export function declaredEffects(v: DocVisit, verb = "eval", machine?: Machine, name?: string): readonly (readonly Key[])[] {
444
+ return hookEffects(v, verb, machine, name) ?? verbDeclared(v, verb) ?? NO_EFFECTS;
445
+ }
446
+
447
+ /**
448
+ * This function gives the effects that the hook `facts` gives for the code of a visit, as keys (D118). The result is
449
+ * `undefined` for another verb than `eval`, without the hook, or when the answer has no field `effects`.
422
450
  */
423
- export function declaredEffects(v: DocVisit, verb = "eval"): readonly (readonly Key[])[] {
424
- return verbDeclared(v, verb) ?? NO_EFFECTS;
451
+ function hookEffects(v: DocVisit, verb: string, machine: Machine | undefined, name: string | undefined): readonly (readonly Key[])[] | undefined {
452
+ if (verb !== "eval" || machine?.facts === undefined || v.kind === undefined) return undefined;
453
+ const code = name ?? answerOf(machine.world, v.declared ? undefined : v.doc.own, v.kind, "eval")?.name;
454
+ const effects = code === undefined ? undefined : machine.facts(v.typePath ?? [], code)?.effects;
455
+ return effects === undefined ? undefined : effectKeys(v, effects);
456
+ }
457
+
458
+ /**
459
+ * This function gives the keys of a declaration of effects at a visit (D27). The text `"type"` gives the type of the
460
+ * document after each alias.
461
+ */
462
+ export function effectKeys(v: DocVisit, effects: "type" | readonly string[]): readonly (readonly Key[])[] {
463
+ if (effects.length === 0) return NO_EFFECTS;
464
+ return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
425
465
  }
426
466
 
427
467
  /**
@@ -433,8 +473,7 @@ function verbDeclared(v: DocVisit, verb: string): readonly (readonly Key[])[] |
433
473
  if (v.kind === undefined) return verb === "eval" ? NO_EFFECTS : undefined;
434
474
  const effects = effectsOfVerb(v.kind, verb);
435
475
  if (effects === undefined) return undefined;
436
- if (effects.length === 0) return NO_EFFECTS;
437
- return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
476
+ return effectKeys(v, effects);
438
477
  }
439
478
 
440
479
  /**
@@ -637,7 +676,8 @@ const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "ite
637
676
 
638
677
  /**
639
678
  * This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
640
- * `equations`, a `unit` and a `default` see no names. `check` and `factsOf` read the equations with this one function.
679
+ * `equations`, a `unit`, a `default` and a contribution see no names. `check` and `factsOf` read the equations with
680
+ * this one function.
641
681
  */
642
682
  export function equationsOf(kind: Kind): readonly Equation[] {
643
683
  const tree = kind.tree;
@@ -651,6 +691,18 @@ export function equationsOf(kind: Kind): readonly Equation[] {
651
691
  out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES, attribute: name, down: false });
652
692
  }
653
693
  if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [], attribute: name, down: false });
694
+ const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
695
+ if (collect?.ok === true && !collect.names) {
696
+ out.push({ label: `tree.attributes.${name}.collect.unit`, term: collect.unit, names: [], attribute: name, down: false });
697
+ out.push({ label: `tree.attributes.${name}.collect.combine`, term: collect.combine, names: FOLD_NAMES, attribute: name, down: false });
698
+ }
699
+ }
700
+ // A contribution is not the attribute of its node, thus an own part of its name does not hide it (D122).
701
+ for (const [name, term] of Object.entries(tree.contributes ?? {})) out.push({ label: `tree.contributes.${name}`, term, names: [], attribute: name, down: false, contributes: true });
702
+ // An equation of an attribute with parameters sees each parameter as a name (TREE.AT, D124).
703
+ for (const [name, declared] of Object.entries(tree.at ?? {})) {
704
+ const rule = atRuleOf(declared);
705
+ if (rule.ok) out.push({ label: `tree.at.${name}`, term: rule.equation, names: rule.params, attribute: name, down: false, at: true });
654
706
  }
655
707
  return out;
656
708
  }
@@ -665,6 +717,10 @@ export interface Equation {
665
717
  readonly attribute: string;
666
718
  /** `true` for an equation of `down`: it gives the attribute of a child. */
667
719
  readonly down: boolean;
720
+ /** `true` for an equation of `contributes`: it gives a contribution to a collection (TREE.COLLECT). */
721
+ readonly contributes?: true;
722
+ /** `true` for an equation of `at`: it gives an attribute with parameters (TREE.AT). */
723
+ readonly at?: true;
668
724
  }
669
725
 
670
726
  /**
@@ -837,7 +893,7 @@ export function answered(world: World, node: SeenNode, name: string): boolean {
837
893
  if (hasEntry(tree?.equations, name)) return true;
838
894
  if (node.parent === undefined ? downNames(world).has(name) : hasEntry(node.parent.kind.tree?.down, name)) return true;
839
895
  const declared = tree?.attributes !== undefined && Object.hasOwn(tree.attributes, name) ? tree.attributes[name] : undefined;
840
- return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.default !== undefined);
896
+ return declared !== undefined && (declared.copy === true || declared.fold !== undefined || declared.collect !== undefined || declared.default !== undefined);
841
897
  }
842
898
 
843
899
  /**
@@ -853,10 +909,10 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
853
909
  state.needs.set(kind, needs);
854
910
  for (const name of Object.keys(kind.tree?.attributes ?? {})) needs.push({ name, why: "the kind declares it", hidden: undefined });
855
911
  const self: SeenNode = { kind, text: kindText, own: undefined, parent: undefined };
856
- for (const { label, term, names, attribute, down } of equationsOf(kind)) {
912
+ for (const { label, term, names, attribute, down, contributes, at } of equationsOf(kind)) {
857
913
  const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
858
914
  const read = (name: string): void => {
859
- needs.push({ name, why: `the equation ${label} reads it`, hidden: down ? undefined : attribute });
915
+ needs.push({ name, why: `the equation ${label} reads it`, hidden: down || contributes === true || at === true ? undefined : attribute });
860
916
  };
861
917
  for (const issue of checkAll(machine, term, inner, undefined, state, { node: self, read })) {
862
918
  out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
@@ -868,12 +924,13 @@ function equationIssues(machine: Machine, kind: Kind, kindText: string, options:
868
924
  /**
869
925
  * This function finds the issues of the contract of a tree kind (TREE.CHECK-RESERVED). An attribute with the name of a
870
926
  * structural step gets no read. A tree kind of the run side contradicts the evaluation on demand of its attributes. A
871
- * declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK).
927
+ * declaration `circular` without a start value or with a bad limit gives `#VALUE!` (TREE.CIRCULAR-CHECK). A declaration
928
+ * `collect` without its form gives `#VALUE!` too (TREE.COLLECT). So does an entry of `at` without its form (TREE.AT-CHECK).
872
929
  */
873
930
  function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
874
931
  const out: Issue[] = [];
875
932
  const tree = kind.tree;
876
- for (const table of ["equations", "down", "attributes"] as const) {
933
+ for (const table of ["equations", "down", "attributes", "contributes", "at"] as const) {
877
934
  for (const name of Object.keys(tree?.[table] ?? {})) {
878
935
  const why = Object.hasOwn(STRUCTURAL, name) ? STRUCTURAL[name] : undefined;
879
936
  if (why !== undefined) out.push({ code: "#NAME?", place: [], reason: `The kind ${kindText} gives the reserved attribute tree.${table}.${name}: ${why}.` });
@@ -883,6 +940,12 @@ function contractIssues(kind: Kind, kindText: string): readonly Issue[] {
883
940
  for (const [name, declared] of Object.entries(tree?.attributes ?? {})) {
884
941
  const rule = declared.circular === undefined ? undefined : circularOf(declared.circular);
885
942
  if (rule !== undefined && !rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.circular. ${rule.reason}` });
943
+ const collect = declared.collect === undefined ? undefined : collectOf(declared.collect);
944
+ if (collect !== undefined && !collect.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad declaration tree.attributes.${name}.collect. ${collect.reason}` });
945
+ }
946
+ for (const [name, declared] of Object.entries(tree?.at ?? {})) {
947
+ const rule = atRuleOf(declared);
948
+ if (!rule.ok) out.push({ code: "#VALUE!", place: [], reason: `The kind ${kindText} has a bad entry tree.at.${name}. ${rule.reason}` });
886
949
  }
887
950
  if (kind.side === "run") {
888
951
  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.` });
@@ -915,9 +978,14 @@ function nodeIssues(machine: Machine, node: SeenNode, state: TreeState, v: DocVi
915
978
  * This function examines a reference from the root `self` (TREE.CHECK-UNKNOWN). The steps `parent` go up the known
916
979
  * nodes. The first other key names an attribute of the node that they reach. When no rung answers the attribute, the
917
980
  * function gives the index of the step and the reason. In an equation, a read of the node of the equation goes to the
918
- * needs of its kind.
981
+ * needs of its kind. A step `tree.at` with literal arguments gets the checks of TREE.AT-CHECK.
919
982
  */
920
- function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly Json[], rootSelf: RootSelf | undefined): { readonly index: number; readonly reason: string } | undefined {
983
+ function readIssue(
984
+ machine: Machine,
985
+ self: SeenNode | undefined,
986
+ path: readonly Json[],
987
+ rootSelf: RootSelf | undefined,
988
+ ): { readonly index: number; readonly reason: string; readonly code?: ErrorCode } | undefined {
921
989
  let node = self;
922
990
  let index = 1;
923
991
  while (node !== undefined && index < path.length && path[index] === "parent") {
@@ -925,6 +993,10 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
925
993
  index += 1;
926
994
  }
927
995
  const step = path[index];
996
+ if (node !== undefined && isAtStep(machine, step)) {
997
+ const at = atStepOf(node.kind, node.text, step.value);
998
+ return at.kind === "issue" ? { index, reason: at.reason, code: at.code } : undefined;
999
+ }
928
1000
  if (node === undefined || !isKey(step) || Object.hasOwn(STRUCTURAL, String(step))) return undefined;
929
1001
  const name = String(step);
930
1002
  if (node.own === undefined) {
@@ -934,6 +1006,46 @@ function readIssue(machine: Machine, self: SeenNode | undefined, path: readonly
934
1006
  return answered(machine.world, node, name) ? undefined : { index, reason: `No rung gives the attribute ${name} of the kind ${node.text}.` };
935
1007
  }
936
1008
 
1009
+ /** This function tells if a step of a path is a step `tree.at` of the standard walker (TREE.AT, D124). */
1010
+ export function isAtStep(machine: Machine, step: unknown): step is Envelope {
1011
+ return isEnvelope(step) && walkerOf(machine, step) === STD_WALKERS["walk.tree.at"];
1012
+ }
1013
+
1014
+ /**
1015
+ * 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
1016
+ * step with an issue fails at the step. Only the run knows a step with a document in its data, or a bad entry.
1017
+ */
1018
+ export type AtStep =
1019
+ | { readonly kind: "exact"; readonly attr: string; readonly key: string; readonly equation: Json; readonly params: readonly string[]; readonly names: Readonly<Record<string, Json>> }
1020
+ | { readonly kind: "issue"; readonly code: ErrorCode; readonly reason: string }
1021
+ | { readonly kind: "unknown" };
1022
+
1023
+ /**
1024
+ * This function examines the data of a step `tree.at` on a node of a known kind (TREE.AT-CHECK, D124). `check` and the
1025
+ * graph of attribute instances read the step with this one function.
1026
+ */
1027
+ export function atStepOf(kind: Kind, kindText: string, data: unknown): AtStep {
1028
+ if (holdsDocument(data)) return { kind: "unknown" };
1029
+ const attr = isRecord(data) ? field(data, "attr") : undefined;
1030
+ if (typeof attr !== "string") return { kind: "issue", code: "#VALUE!", reason: "The step tree.at needs a text attr." };
1031
+ const table = kind.tree?.at;
1032
+ const declared = table !== undefined && Object.hasOwn(table, attr) ? table[attr] : undefined;
1033
+ if (declared === undefined) return { kind: "issue", code: "#N/A", reason: `No rung gives the attribute ${attr} with parameters of the kind ${kindText}.` };
1034
+ const rule = atRuleOf(declared);
1035
+ // The issue of a bad entry is an issue of the kind (contractIssues).
1036
+ if (!rule.ok) return { kind: "unknown" };
1037
+ const given = atArgsOf(rule.params, isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {});
1038
+ if (!given.ok) return { kind: "issue", code: "#VALUE!", reason: `The step tree.at of ${attr} of the kind ${kindText} has bad arguments. ${given.reason}` };
1039
+ return { kind: "exact", attr, key: given.key, equation: rule.equation, params: rule.params, names: given.names };
1040
+ }
1041
+
1042
+ /** 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. */
1043
+ function holdsDocument(value: unknown): boolean {
1044
+ if (isEnvelope(value)) return true;
1045
+ if (Array.isArray(value)) return value.some(holdsDocument);
1046
+ return isRecord(value) && Object.values(value).some(holdsDocument);
1047
+ }
1048
+
937
1049
  /** The self of the root of a walk for the checks of attributes: the node of an equation, and the function for its reads. */
938
1050
  interface RootSelf {
939
1051
  readonly node: SeenNode;
@@ -971,6 +1083,9 @@ function checkAll(
971
1083
  // run, and a drive for a runner under an evaluator. A document without code fails before its parts, thus it is not
972
1084
  // in the map.
973
1085
  const waits = options.run === true ? new Map<DocVisit, Waits>() : undefined;
1086
+ // The parts that the code of a runner evaluates itself, without a wait (RUN.DRIVES). They get no wait, as under an
1087
+ // evaluator. Only a runner that declares `drives` is in the map.
1088
+ const driven = new Map<DocVisit, readonly LazyRule[]>();
974
1089
  const drives: Drive[] = [];
975
1090
  const maxDepth = machine.maxDepth ?? 1000;
976
1091
  const anyName = options.names === "any";
@@ -1026,7 +1141,7 @@ function checkAll(
1026
1141
  if (known !== true) push({ code: "#NAME?", place: v.place, reason: known, at: v.at });
1027
1142
  }
1028
1143
  const caps = v.caps;
1029
- const stated = verbDeclared(v, verb);
1144
+ const stated = hookEffects(v, verb, machine, name) ?? verbDeclared(v, verb);
1030
1145
  const declared = stated ?? NO_EFFECTS;
1031
1146
  if (allowed !== undefined && caps !== undefined) {
1032
1147
  for (const kind of declared) {
@@ -1035,7 +1150,9 @@ function checkAll(
1035
1150
  }
1036
1151
  if (waits !== undefined && coded) {
1037
1152
  // The parent of the root is the driver of the run, and it can wait. An evaluator gives its parts no wait.
1038
- const up = v.parent === undefined ? true : waits.get(v.parent);
1153
+ let up = v.parent === undefined ? true : waits.get(v.parent);
1154
+ const rules = v.parent === undefined ? undefined : driven.get(v.parent);
1155
+ if (up !== false && rules !== undefined && v.parent !== undefined && rulesCover(rules, v.parent.kind?.parts, v.parent.doc.value as Json, v.slot)) up = false;
1039
1156
  let chain: Waits = false;
1040
1157
  if (runner && up === false) {
1041
1158
  // The run drives a runner under an evaluator, and only a real wait fails (RUN.DRIVE-NESTED). Its issue goes in
@@ -1047,6 +1164,10 @@ function checkAll(
1047
1164
  chain = up ?? true;
1048
1165
  }
1049
1166
  waits.set(v, chain);
1167
+ if (runner && v.kind !== undefined) {
1168
+ const own = driveRules(machine, v, v.kind, name, answer.rung, verb);
1169
+ if (own !== undefined && own.length > 0) driven.set(v, own);
1170
+ }
1050
1171
  // A runner waits only for an effect that reaches the host (C6). A refused effect gives #CAP! without a wait. A
1051
1172
  // runner without a declaration for the verb can cause each effect, thus it can wait.
1052
1173
  if (typeof chain === "object" && !chain.wait) {
@@ -1089,7 +1210,7 @@ function checkAll(
1089
1210
  // Only the standard code of a reference reads the attribute, thus a header that changes the code hides the read.
1090
1211
  if (attributes && root === "self" && v.address !== true && name === REF_CODE) {
1091
1212
  const issue = readIssue(machine, self, path, rootSelf);
1092
- if (issue !== undefined) push({ code: "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
1213
+ if (issue !== undefined) push({ code: issue.code ?? "#N/A", place: v.place, reason: issue.reason, at: stepAt(issue.index) });
1093
1214
  }
1094
1215
  path.forEach((step, index) => {
1095
1216
  if (index === 0 || isKey(step)) return;
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, atArgsOf, atRuleOf, 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,20 @@ 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
+ at: {
78
+ verbs: { step: "walk.tree.at", view: "view.card" },
79
+ pure: true,
80
+ 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).",
81
+ },
82
+ },
83
+ },
70
84
  panel: {
71
85
  verbs: { eval: "tree.node", view: "view.op" },
72
86
  parts: { shape: "record", fields: ["width", "height", "children"], of: "number" },
@@ -167,6 +181,8 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
167
181
  { name: "panel", kind: "panel", args: "named", fields: ["width", "height", "children"] },
168
182
  { name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
169
183
  { name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
184
+ { name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
185
+ { name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
170
186
  // An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
171
187
  { name: "add", kind: "add" },
172
188
  { name: "multiply", kind: "multiply" },
@@ -272,6 +288,8 @@ export interface AttributeTrace {
272
288
  /** The state of a node behind its brand. */
273
289
  interface NodeState extends LoopNode {
274
290
  readonly build: TreeBuild;
291
+ /** The binding name from the header of the document of the node, for the registry of names (TREE.NAMED). */
292
+ readonly name: string | undefined;
275
293
  /**
276
294
  * This function gives an attribute through its ladder (TREE.LADDER).
277
295
  *
@@ -331,6 +349,10 @@ export interface AttributeAnswer {
331
349
  * A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
332
350
  * A host that keeps its cells by the keys of `place` can give the answer of another node or a stale answer.
333
351
  *
352
+ * An attribute with parameters gets one cell for each key of its arguments (TREE.AT-CELL, D124). Its `place` is a
353
+ * second array with the same keys, one for each key of a node. Its `attribute` is the JSON text of the name and the
354
+ * arguments, for example `["height",{"width":200}]`.
355
+ *
334
356
  * The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
335
357
  * of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
336
358
  * a cell that computes again uses the last one.
@@ -853,6 +875,131 @@ function plainOf(value: unknown, path: Set<object>): Result {
853
875
  }
854
876
  }
855
877
 
878
+ /**
879
+ * This function gives the collection of an attribute of a node (TREE.COLLECT, D122). It visits the node, then each node
880
+ * of the same tree in each list `children`, in the order of the lists. Each node is visited one time.
881
+ * The registry of names (TREE.NAMED) gives a record from each binding name to its node.
882
+ */
883
+ function collected(root: HostThing, key: string, declared: unknown): Result {
884
+ const top = stateOf(root) as NodeState;
885
+ const rule = collectOf(declared);
886
+ if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(key, top.type, top.place)} has a bad declaration collect. ${rule.reason}`);
887
+ const registry: Record<string, unknown> = {};
888
+ const owners = new Map<string, NodeState>();
889
+ let acc = rule.names ? ok(registry) : top.equation(rule.unit, NO_NAMES);
890
+ if (!acc.ok) return acc;
891
+ const seen = new Set<NodeState>();
892
+ const stack: unknown[] = [root];
893
+ while (stack.length > 0) {
894
+ const thing = stack.pop();
895
+ const at = stateOf(thing);
896
+ // An item that is not a node of this tree does not count, and a node counts one time.
897
+ if (at === undefined || at.build !== top.build || seen.has(at)) continue;
898
+ seen.add(at);
899
+ if (rule.names) {
900
+ const name = at.name;
901
+ const earlier = name === undefined ? undefined : owners.get(name);
902
+ if (earlier !== undefined) {
903
+ const both = `${earlier.type} at [${earlier.place.join(", ")}] and ${at.type} at [${at.place.join(", ")}]`;
904
+ return fail("#VALUE!", `Two nodes have the name ${name} in the registry ${attributeText(key, top.type, top.place)}: ${both}.`);
905
+ }
906
+ if (name !== undefined) {
907
+ owners.set(name, at);
908
+ putField(registry, name, thing);
909
+ }
910
+ } else {
911
+ const term = entryOf(at.tree?.contributes, key);
912
+ if (term !== undefined) {
913
+ const item = at.equation(term, NO_NAMES);
914
+ if (!item.ok) return item;
915
+ acc = top.equation(rule.combine, { acc: acc.value, item: item.value });
916
+ if (!acc.ok) return acc;
917
+ }
918
+ }
919
+ const children = at.read("children");
920
+ if (children === undefined) continue;
921
+ if (!children.ok) return children;
922
+ if (!Array.isArray(children.value)) return fail("#VALUE!", `The collection of ${key} needs a list in children of ${at.type} at [${at.place.join(", ")}].`);
923
+ const list = children.value as readonly unknown[];
924
+ for (let index = list.length - 1; index >= 0; index -= 1) stack.push(list[index]);
925
+ }
926
+ return acc;
927
+ }
928
+
929
+ /** A function from the name of an attribute and the arguments to the result, or `undefined` when no rung answers (D124). */
930
+ export type AtReader = (attr: string, args: unknown) => Result | undefined;
931
+
932
+ /**
933
+ * The host things that answer the step `tree.at` with their own function (D124). A node of the reference is in it, and
934
+ * its fields do not change. The walker reads the state of a node of the kit.
935
+ */
936
+ export const AT_READERS: WeakMap<object, AtReader> = /* @__PURE__ */ new WeakMap();
937
+
938
+ /** The memo of the instances of the attributes with parameters of each node, without the cells of a host (TREE.AT). */
939
+ const atMemos = /* @__PURE__ */ new WeakMap<NodeState, Map<string, Cell>>();
940
+
941
+ /** The second key of each key of a node, for the cells of the attributes with parameters (TREE.AT-CELL). */
942
+ const atPlaces = /* @__PURE__ */ new WeakMap<readonly Key[], readonly Key[]>();
943
+
944
+ /**
945
+ * This function gives an instance of an attribute with parameters of a node (TREE.AT, D124). The equation in
946
+ * `tree.at` of the kind of the node evaluates with the arguments as names. The memo keeps one result for each key of
947
+ * the arguments, and a demand of an instance in evaluation gives `#CYCLE!`. The function is outside the code of
948
+ * `read`, thus a node without such attributes does no new work.
949
+ */
950
+ function atRead(n: NodeState, attr: string, args: unknown): Result | undefined {
951
+ const declared: unknown = entryOf(n.tree?.at, attr);
952
+ if (declared === undefined) return undefined;
953
+ const rule = atRuleOf(declared);
954
+ if (!rule.ok) return fail("#VALUE!", `The attribute ${attributeText(attr, n.type, n.place)} has a bad entry tree.at. ${rule.reason}`);
955
+ const given = atArgsOf(rule.params, args);
956
+ if (!given.ok) return fail("#VALUE!", `The step tree.at of ${attributeText(attr, n.type, n.place)} has bad arguments. ${given.reason}`);
957
+ // An open fixed point does not use the cells of the host (D113).
958
+ const host = openPoint === undefined ? n.host : undefined;
959
+ const place = n.host === undefined ? n.place : keptAt(atPlaces, n.place, () => Object.freeze([...n.place]));
960
+ let marks: Map<string, Cell> | undefined = n.host === undefined ? atMemos.get(n) : keptAt(marksByPlace, place, () => new Map<string, Cell>());
961
+ if (marks === undefined) {
962
+ marks = new Map();
963
+ atMemos.set(n, marks);
964
+ }
965
+ const key = `[${JSON.stringify(attr)},${given.key}]`;
966
+ const text = attributeText(`${attr} with ${given.key}`, n.type, n.place);
967
+ const cell = marks.get(key);
968
+ if (cell !== undefined) return cell.busy ? cycleAt(marks, key, text) : cell.result;
969
+ const demand: Demand = { cells: marks, key, text };
970
+ const loop = openPoint;
971
+ const compute = (): AttributeAnswer => ({ result: n.equation(rule.equation, given.names), by: "kind" });
972
+ marks.set(key, BUSY);
973
+ demands.push(demand);
974
+ let thrown: unknown = NOT_THROWN;
975
+ let answer: AttributeAnswer;
976
+ try {
977
+ answer =
978
+ host === undefined
979
+ ? compute()
980
+ : host(place, key, () => {
981
+ try {
982
+ return compute();
983
+ } catch (error) {
984
+ thrown = error;
985
+ throw error;
986
+ }
987
+ });
988
+ } catch (error) {
989
+ marks.delete(key);
990
+ // An exception of the kit or of the stack goes on. Another exception of the cell is a cycle of the host (D108).
991
+ if (host === undefined || error === thrown) throw error;
992
+ const chain = demands.map((item) => item.text).join(" -> ");
993
+ answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
994
+ } finally {
995
+ demands.pop();
996
+ }
997
+ // A result that read an approximation is not final. With the cells of a host, the cell is the memo.
998
+ if (n.host !== undefined || loop?.tentative.has(demand) === true) marks.delete(key);
999
+ else marks.set(key, { busy: false, result: answer.result, by: answer.by });
1000
+ return answer.result;
1001
+ }
1002
+
856
1003
  /**
857
1004
  * This function makes a node of the decorated tree. A step `parent` gives the self of the context of the node, and a
858
1005
  * step `type` gives its type. Another step reads an attribute through its ladder (TREE.LADDER). The rungs are a part of
@@ -916,6 +1063,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
916
1063
  if (copied !== undefined) return { result: copied, by: "copy" };
917
1064
  }
918
1065
  if (declared?.fold !== undefined) return { result: fold(key, declared.fold), by: "default" };
1066
+ if (declared?.collect !== undefined) return { result: collected(node, key, declared.collect), by: "default" };
919
1067
  if (declared?.default !== undefined) return { result: state.equation(declared.default, NO_NAMES), by: "default" };
920
1068
  return undefined;
921
1069
  };
@@ -984,6 +1132,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
984
1132
 
985
1133
  const state: NodeState = {
986
1134
  build,
1135
+ name: input.doc.name,
987
1136
  type,
988
1137
  place,
989
1138
  tree,
@@ -1170,8 +1319,46 @@ export function nearestWalker(options: NearestOptions = {}): Walker {
1170
1319
 
1171
1320
  const nearest: Walker = /* @__PURE__ */ nearestWalker();
1172
1321
 
1322
+ /**
1323
+ * The walker of the step `tree.named` (TREE.NAMED, D122). It goes up from the thing before the step through the key
1324
+ * `parent`, to the first thing that answers the registry. The field `in` names the registry, and the default is
1325
+ * `names`. The walker gives the node with the field `name` in that registry.
1326
+ */
1327
+ const named: Walker = ({ data, here, deref }) => {
1328
+ const name = isRecord(data) ? field(data, "name") : undefined;
1329
+ if (typeof name !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.named needs a text name." };
1330
+ const registry = isRecord(data) && typeof field(data, "in") === "string" ? String(field(data, "in")) : "names";
1331
+ let at: Thing | undefined = here;
1332
+ for (let hops = 0; hops < 256 && at !== undefined && at !== null; hops += 1) {
1333
+ const found = deref(at, registry);
1334
+ if (found !== undefined) {
1335
+ if (!isRecord(found) || !Object.hasOwn(found, name)) return { ok: false, reason: `The registry ${registry} has no node with the name ${name}.` };
1336
+ return { ok: true, thing: field(found, name) as Thing };
1337
+ }
1338
+ at = deref(at, "parent");
1339
+ }
1340
+ return { ok: false, reason: `No registry ${registry} is on the way.` };
1341
+ };
1342
+
1343
+ /**
1344
+ * The walker of the step `tree.at` (TREE.AT, D124). The field `attr` names an attribute with parameters, and the
1345
+ * field `args` gives the arguments. The thing before the step is a node. A failed instance throws its own error.
1346
+ */
1347
+ const at: Walker = ({ data, here }) => {
1348
+ const attr = isRecord(data) ? field(data, "attr") : undefined;
1349
+ if (typeof attr !== "string") return { ok: false, code: "#VALUE!", reason: "The step tree.at needs a text attr." };
1350
+ const args = isRecord(data) && Object.hasOwn(data, "args") ? field(data, "args") : {};
1351
+ const state = stateOf(here);
1352
+ const reader = state === undefined && typeof here === "object" && here !== null ? AT_READERS.get(here) : undefined;
1353
+ 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.` };
1354
+ const result = state === undefined ? (reader as AtReader)(attr, args) : atRead(state, attr, args);
1355
+ if (result === undefined) return { ok: false, reason: `No rung gives the attribute ${attr} with parameters.` };
1356
+ if (!result.ok) throw new Failure(result.error);
1357
+ return { ok: true, thing: result.value as Thing };
1358
+ };
1359
+
1173
1360
  /** The walkers of the standard world, by name. */
1174
- export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest };
1361
+ export const STD_WALKERS: Readonly<Record<string, Walker>> = { "walk.nearest": nearest, "walk.named": named, "walk.tree.at": at };
1175
1362
 
1176
1363
  /** This function evaluates parts one after the other, and stops at the first error. */
1177
1364
  function* inOrder(parts: readonly RunPart[]): Generator<Promise<unknown>, { readonly values: readonly unknown[] } | { readonly error: Result }, unknown> {
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
- return { type: "unknown" };
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 { type: indexOf(key) >= 0 ? form.item : "unknown" };
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;