@mark1russell7/coeval 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/reference.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { type Envelope, type Json, type Key, isEnvelope, isRecord, typeName } from "./doc.ts";
2
2
  import { ask } from "./ladder.ts";
3
- import type { CapsRule, Kind } from "./world.ts";
3
+ import { type AttributeDeclaration, type CapsRule, type Kind, type TreeContract, equationType, lookupKind } from "./world.ts";
4
4
  import { type PartSlot, type Split, partsOf, pathMatches } from "./parts.ts";
5
5
  import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
6
- import { STD_EVALUATORS } from "./stdlib.ts";
6
+ import { type AttributeRung, STD_EVALUATORS } from "./stdlib.ts";
7
7
  import { type EvalError, type Failed, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
8
8
  import {
9
9
  type AllOptions,
@@ -17,7 +17,6 @@ import {
17
17
  type RunInput,
18
18
  type RunPart,
19
19
  type Steps,
20
- intersectCaps,
21
20
  makeContext,
22
21
  permits,
23
22
  placeText,
@@ -150,13 +149,23 @@ function examine(env: Env, at: At): Seen | Failed {
150
149
  return { doc, kindText, typePath: read.typePath ?? [], kind, name, chosen, split };
151
150
  }
152
151
 
152
+ /**
153
+ * This function gives the effects that two lists of capabilities both permit (CAP.ONLY). The reference reads the rule
154
+ * from SPEC directly, and not from the intersection of the plan. Thus `agree` finds a bug in either one (D15).
155
+ */
156
+ function bothPermit(a: readonly Cap[], b: readonly Cap[]): readonly Cap[] {
157
+ const out: Cap[] = [];
158
+ for (const cap of [...a, ...b]) if (permits(a, cap) && permits(b, cap) && !permits(out, cap)) out.push(cap);
159
+ return out;
160
+ }
161
+
153
162
  /** This function gives the capabilities that the rules of a kind give one part (D14, CAP.CONTRACT, CAP.ONLY). */
154
163
  function capsOfPart(rules: readonly CapsRule[] | undefined, path: readonly Key[], caps: readonly Cap[]): readonly Cap[] {
155
164
  let out = caps;
156
165
  for (const rule of rules ?? []) {
157
166
  if (rule.in !== undefined && !rule.in.some((pattern) => pathMatches(path, pattern))) continue;
158
167
  if (rule.rule === "mask") return [];
159
- if (rule.rule === "only") out = intersectCaps(out, rule.caps);
168
+ if (rule.rule === "only") out = bothPermit(out, rule.caps);
160
169
  }
161
170
  return out;
162
171
  }
@@ -307,6 +316,7 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
307
316
  parts,
308
317
  context: at.ctx,
309
318
  place: at.place,
319
+ machine: env.machine,
310
320
  ...(at.address ? { address: true } : {}),
311
321
  resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
312
322
  effect: (kind, payload) => cause(env, at.ctx, kind, payload, at.place),
@@ -335,6 +345,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
335
345
  parts,
336
346
  context: at.ctx,
337
347
  place: at.place,
348
+ machine: env.machine,
338
349
  resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
339
350
  effect: function* (kind, payload) {
340
351
  return cause(env, at.ctx, kind, payload, at.place);
@@ -418,13 +429,20 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
418
429
  scope: { prefix: "", counts: new Map(), alls: 0 },
419
430
  };
420
431
  const ctx = context.world === null ? { ...context, world: worldThing(machine.world) } : context;
432
+ // A node of a tree reads the definitions of this evaluation for its equations (TREE.LADDER).
433
+ activeEnvs.push(env);
421
434
  try {
422
435
  return evaluateAt(env, { value: doc, place: [], declared: options.declaredType, depth: 0, address: false, ctx });
423
436
  } catch (error) {
424
437
  return placed(thrown(error, "the reference"), []);
438
+ } finally {
439
+ activeEnvs.pop();
425
440
  }
426
441
  }
427
442
 
443
+ /** The evaluations of the reference that run at this time, the innermost last. */
444
+ const activeEnvs: Env[] = [];
445
+
428
446
  /*
429
447
  * The definition of the decorated tree (TREE.*), written from docs/SPEC.md. It shares no code with `tree.node` of
430
448
  * stdlib.ts: its nodes, its memo, its marks for attributes in evaluation and its JSON are its own.
@@ -442,6 +460,16 @@ interface RefNode {
442
460
  readonly place: readonly Key[];
443
461
  readonly parts: readonly Part[];
444
462
  readonly ctx: Context;
463
+ /** The tree contract of the kind of the node (D42). */
464
+ readonly contract: TreeContract | undefined;
465
+ /** The type of a bare equation: the type `of` of the parts of the kind. */
466
+ readonly of: string | undefined;
467
+ /** The parent node in the same tree, for the rungs `parent` and `copy`. */
468
+ readonly up: RefNode | undefined;
469
+ /** The evaluation of the reference that made the node. Its equations evaluate with its definitions. */
470
+ readonly env: Env | undefined;
471
+ /** The host thing of the node. */
472
+ thing: HostThing;
445
473
  /** The results of the attributes that ended. */
446
474
  readonly values: Map<string, Result>;
447
475
  /** The attributes in evaluation. */
@@ -461,8 +489,86 @@ function refNodeOf(thing: unknown): RefNode | undefined {
461
489
  return typeof thing === "object" && thing !== null ? refNodes.get(thing) : undefined;
462
490
  }
463
491
 
464
- /** TREE.ONCE, TREE.MEMO and TREE.CYCLE: the result of one attribute, or `undefined` when the node has no such part. */
465
- function attributeOf(node: RefNode, self: HostThing, name: string): Result | undefined {
492
+ /**
493
+ * TREE.LADDER: an equation of the kind of a node evaluates in the reference. It gets the node as `self`, the names of
494
+ * its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind.
495
+ */
496
+ function equationOf(node: RefNode, term: Json, names: Readonly<Record<string, unknown>>): Result {
497
+ const env = node.env;
498
+ if (env === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
499
+ const ctx: Context = { ...node.ctx, names, caps: [], self: node.thing };
500
+ return reference(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) });
501
+ }
502
+
503
+ function termIn<T>(table: Readonly<Record<string, T>> | undefined, name: string): T | undefined {
504
+ return table === undefined || !Object.hasOwn(table, name) ? undefined : table[name];
505
+ }
506
+
507
+ /** TREE.DOWN: the names of a child for an equation of `down` of its parent: `child`, `index` and `prev`. */
508
+ function namesOfChild(up: RefNode, child: RefNode): Result {
509
+ const children = attributeOf(up, "children");
510
+ if (children !== undefined && !children.ok) return children;
511
+ const list: readonly unknown[] = children !== undefined && Array.isArray(children.value) ? children.value : [];
512
+ const index = list.indexOf(child.thing);
513
+ return ok({ child: child.thing, index: index < 0 ? null : index, prev: index > 0 ? list[index - 1] : null });
514
+ }
515
+
516
+ /** TREE.FOLD: the unit, then `combine` with the attribute of each node of this tree in the list `children`. */
517
+ function foldOf(node: RefNode, name: string, fold: NonNullable<AttributeDeclaration["fold"]>): Result {
518
+ let acc = equationOf(node, fold.unit, {});
519
+ if (!acc.ok) return acc;
520
+ const children = attributeOf(node, "children");
521
+ if (children === undefined) return acc;
522
+ if (!children.ok) return children;
523
+ if (!Array.isArray(children.value)) return fail("#VALUE!", `The fold of ${name} needs a list in children.`);
524
+ for (const item of children.value) {
525
+ const child = refNodeOf(item);
526
+ if (child === undefined || child.tree !== node.tree) continue;
527
+ const value = attributeOf(child, name);
528
+ if (value === undefined) return fail("#N/A", `No rung gives ${nameOfAttribute(child, name)}.`);
529
+ if (!value.ok) return value;
530
+ acc = equationOf(node, fold.combine, { acc: acc.value, item: value.value });
531
+ if (!acc.ok) return acc;
532
+ }
533
+ return acc;
534
+ }
535
+
536
+ /**
537
+ * TREE.LADDER: the rungs of the ladder of an attribute, in their order. A rung gives `undefined` when it does not
538
+ * answer, and the next rung then tries.
539
+ */
540
+ const REF_RUNGS: readonly (readonly [AttributeRung, (node: RefNode, name: string) => Result | undefined])[] = [
541
+ ["own", (node, name) => node.parts.find((item) => item.key === name)?.evaluate({ ...node.ctx, self: node.thing })],
542
+ [
543
+ "kind",
544
+ (node, name) => {
545
+ const term = termIn(node.contract?.equations, name);
546
+ return term === undefined ? undefined : equationOf(node, term, {});
547
+ },
548
+ ],
549
+ [
550
+ "parent",
551
+ (node, name) => {
552
+ const up = node.up;
553
+ const term = up === undefined ? undefined : termIn(up.contract?.down, name);
554
+ if (up === undefined || term === undefined) return undefined;
555
+ const names = namesOfChild(up, node);
556
+ return names.ok ? equationOf(up, term, names.value as Readonly<Record<string, unknown>>) : names;
557
+ },
558
+ ],
559
+ ["copy", (node, name) => (node.up !== undefined && termIn(node.contract?.attributes, name)?.copy === true ? attributeOf(node.up, name) : undefined)],
560
+ [
561
+ "default",
562
+ (node, name) => {
563
+ const declared = termIn(node.contract?.attributes, name);
564
+ if (declared?.fold !== undefined) return foldOf(node, name, declared.fold);
565
+ return declared?.default === undefined ? undefined : equationOf(node, declared.default, {});
566
+ },
567
+ ],
568
+ ];
569
+
570
+ /** TREE.ONCE, TREE.MEMO and TREE.CYCLE: the result of one attribute, or `undefined` when no rung of its ladder answers. */
571
+ function attributeOf(node: RefNode, name: string): Result | undefined {
466
572
  const done = node.values.get(name);
467
573
  if (done !== undefined) return done;
468
574
  if (node.pending.has(name)) {
@@ -470,14 +576,16 @@ function attributeOf(node: RefNode, self: HostThing, name: string): Result | und
470
576
  const steps = from < 0 ? [nameOfAttribute(node, name)] : inEvaluation.slice(from).map((entry) => nameOfAttribute(entry.node, entry.name));
471
577
  return fail("#CYCLE!", `The attributes form a cycle: ${[...steps, nameOfAttribute(node, name)].join(" -> ")}.`);
472
578
  }
473
- const part = node.parts.find((item) => item.key === name);
474
- if (part === undefined) return undefined;
475
579
  node.pending.add(name);
476
580
  inEvaluation.push({ node, name });
477
581
  try {
478
- const result = part.evaluate({ ...node.ctx, self });
479
- node.values.set(name, result);
480
- return result;
582
+ for (const [, rung] of REF_RUNGS) {
583
+ const result = rung(node, name);
584
+ if (result === undefined) continue;
585
+ node.values.set(name, result);
586
+ return result;
587
+ }
588
+ return undefined;
481
589
  } finally {
482
590
  node.pending.delete(name);
483
591
  inEvaluation.pop();
@@ -488,7 +596,7 @@ function attributeOf(node: RefNode, self: HostThing, name: string): Result | und
488
596
  function jsonOfNode(node: RefNode, self: HostThing, open: readonly object[]): Result {
489
597
  const record: Record<string, unknown> = {};
490
598
  for (const part of node.parts) {
491
- const result = attributeOf(node, self, part.key) as Result;
599
+ const result = attributeOf(node, part.key) as Result;
492
600
  if (!result.ok) return result;
493
601
  const value = plainValue(result.value, [...open, self]);
494
602
  if (!value.ok) return value;
@@ -528,10 +636,26 @@ function plainValue(value: unknown, open: readonly object[]): Result {
528
636
  * TREE.LAZY, TREE.PARENT, TREE.SIBLING and TREE.ERROR: a node of an open tree gives its thing, and the root of a new
529
637
  * tree gives its JSON. A step on the thing reads an attribute, and a failed attribute throws its own error.
530
638
  */
531
- const treeDefinition: EvalFn = ({ parts, context, kind, place }) => {
639
+ const treeDefinition: EvalFn = ({ parts, context, kind, place, machine, typePath }) => {
532
640
  const outer = refNodeOf(context.self);
533
641
  const nested = outer !== undefined && outer.tree.open;
534
- const node: RefNode = { tree: nested ? outer.tree : { open: true }, kind, place, parts, ctx: context, values: new Map(), pending: new Set() };
642
+ const found = machine === undefined || typePath.length === 0 ? undefined : lookupKind(machine.world, typePath);
643
+ const contract = found?.ok === true ? found.kind.tree : undefined;
644
+ const of = contract === undefined || found?.ok !== true ? undefined : equationType(found.kind);
645
+ const node: RefNode = {
646
+ tree: nested ? outer.tree : { open: true },
647
+ kind,
648
+ place,
649
+ parts,
650
+ ctx: context,
651
+ contract,
652
+ of,
653
+ up: nested ? outer : undefined,
654
+ env: activeEnvs[activeEnvs.length - 1],
655
+ thing: null as unknown as HostThing,
656
+ values: new Map(),
657
+ pending: new Set(),
658
+ };
535
659
  const self: HostThing & { readonly type: string } = {
536
660
  [THING]: true,
537
661
  type: kind,
@@ -539,17 +663,18 @@ const treeDefinition: EvalFn = ({ parts, context, kind, place }) => {
539
663
  deref: (key): Thing | undefined => {
540
664
  if (key === "parent") return context.self;
541
665
  if (key === "type") return kind;
542
- const result = attributeOf(node, self, String(key));
666
+ const result = attributeOf(node, String(key));
543
667
  if (result === undefined || result.ok) return result?.ok === true ? result.value : undefined;
544
668
  throw new Failure(result.error);
545
669
  },
546
- has: (key) => key === "parent" || key === "type" || parts.some((part) => part.key === String(key)),
670
+ has: (key) => key === "parent" || key === "type" || parts.some((part) => part.key === String(key)) || node.values.has(String(key)),
547
671
  toJson: () => {
548
672
  const result = jsonOfNode(node, self, []);
549
673
  if (result.ok) return result.value;
550
674
  throw new Failure(result.error);
551
675
  },
552
676
  };
677
+ node.thing = self;
553
678
  refNodes.set(self, node);
554
679
  if (nested) return ok(self);
555
680
  try {
@@ -748,12 +873,12 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
748
873
  }
749
874
  };
750
875
 
751
- const ext: EvalFn = ({ typePath, doc, context }) => {
876
+ const ext: EvalFn = ({ typePath, doc, context, machine }) => {
752
877
  const name = typePath.slice(1).join(".");
753
878
  const handler = own(profile.ext, name) ?? own(STD_EXT, name);
754
879
  if (handler === undefined) return fail("#NAME?", `The language has no extension ${name}.`);
755
880
  try {
756
- return handler(doc.value, context);
881
+ return handler(doc.value, context, machine?.domains);
757
882
  } catch (error) {
758
883
  return thrown(error, `the extension ${name}`);
759
884
  }
package/src/static.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type Envelope, type Json, type Key, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
2
- import type { Kind, NamesSource, ScopeRule, World } from "./world.ts";
1
+ import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
2
+ import { type Kind, type World, equationType } from "./world.ts";
3
3
  import { ask } from "./ladder.ts";
4
- import { type PartSlot, partsOf, pathMatches } from "./parts.ts";
4
+ import { type PartSlot, partsOf, scopesOf } from "./parts.ts";
5
5
  import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
6
6
  import type { ErrorCode } from "./errors.ts";
7
7
  import { type Machine, partCaps, permits, waitedReason } from "./evaluate.ts";
@@ -67,75 +67,6 @@ export interface WalkOptions {
67
67
  readonly problem?: (problem: WalkProblem) => void;
68
68
  }
69
69
 
70
- function valueAt(value: unknown, path: readonly Key[]): unknown {
71
- let here: unknown = value;
72
- for (const key of path) {
73
- if (Array.isArray(here)) here = here[Number(key)];
74
- else if (isRecord(here) && Object.hasOwn(here, String(key))) here = here[String(key)];
75
- else return undefined;
76
- }
77
- return here;
78
- }
79
-
80
- function headerName(item: Json): string | undefined {
81
- return isEnvelope(item) && typeof (item as { readonly name?: unknown }).name === "string" ? (item as { readonly name: string }).name : undefined;
82
- }
83
-
84
- function namesOf(source: NamesSource, value: unknown, covered: readonly PartSlot[]): readonly string[] {
85
- if ("fixed" in source) return source.fixed;
86
- if ("keysAt" in source) {
87
- const at = valueAt(value, source.keysAt);
88
- return isRecord(at) ? Object.keys(at) : [];
89
- }
90
- if ("listAt" in source) {
91
- const at = valueAt(value, source.listAt);
92
- return Array.isArray(at) ? (at as readonly unknown[]).filter((item): item is string => typeof item === "string") : [];
93
- }
94
- if ("textAt" in source) {
95
- const at = valueAt(value, source.textAt);
96
- if (typeof at === "string") return [at];
97
- return source.otherwise === undefined ? [] : [source.otherwise];
98
- }
99
- return covered.map((slot) => headerName(slot.item)).filter((name): name is string => name !== undefined);
100
- }
101
-
102
- /**
103
- * This function gives the names that the scope rules of a kind give each part, and the names that only later parts
104
- * see. `check` and `conforms` read the scope rules with it.
105
- *
106
- * @param rules - The scope rules of the kind.
107
- * @param value - The value of the document.
108
- * @param slots - The parts of the value, in order.
109
- * @returns The names that each part sees, and the names that only the later parts see, by the index of the part.
110
- */
111
- export function scopesOf(rules: readonly ScopeRule[] | undefined, value: unknown, slots: readonly PartSlot[]): { readonly sees: readonly (readonly string[])[]; readonly later: readonly (readonly string[])[] } {
112
- const sees: string[][] = slots.map(() => []);
113
- const later: string[][] = slots.map(() => []);
114
- for (const rule of rules ?? []) {
115
- const covered = slots.map((slot, index) => ({ slot, index })).filter(({ slot }) => rule.in.some((pattern) => pathMatches(slot.path, pattern)));
116
- if (rule.sequential !== true) {
117
- const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
118
- for (const { index } of covered) sees[index]?.push(...names);
119
- continue;
120
- }
121
- if ("headers" in rule.names) {
122
- covered.forEach(({ index }, position) => {
123
- const before = covered.slice(0, position).map(({ slot }) => headerName(slot.item)).filter((name): name is string => name !== undefined);
124
- const after = covered.slice(position).map(({ slot }) => headerName(slot.item)).filter((name): name is string => name !== undefined);
125
- sees[index]?.push(...before);
126
- later[index]?.push(...after);
127
- });
128
- continue;
129
- }
130
- const names = namesOf(rule.names, value, covered.map(({ slot }) => slot));
131
- covered.forEach(({ index }, position) => {
132
- if (position > 0) sees[index]?.push(...names);
133
- else later[index]?.push(...names);
134
- });
135
- }
136
- return { sees, later };
137
- }
138
-
139
70
  /** The names of a walk without names. A walk does not change a set at any time: it makes a new set to add names. */
140
71
  const NO_NAMES: ReadonlySet<string> = /* @__PURE__ */ new Set();
141
72
 
@@ -595,11 +526,55 @@ interface Drive {
595
526
  /** What the code of a document does with a wait: an evaluator stops it, and a runner passes it on. */
596
527
  type Waits = boolean | Drive;
597
528
 
529
+ /** The names that an equation of `down` sees (TREE.DOWN). */
530
+ const DOWN_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["child", "index", "prev"]);
531
+
532
+ /** The names that the term `combine` of a fold sees (TREE.FOLD). */
533
+ const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "item"]);
534
+
535
+ /**
536
+ * This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
537
+ * `equations`, a `unit` and a `default` see no names.
538
+ */
539
+ function equationsOf(kind: Kind): readonly { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] {
540
+ const tree = kind.tree;
541
+ if (tree === undefined) return [];
542
+ const out: { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] = [];
543
+ for (const [name, term] of Object.entries(tree.equations ?? {})) out.push({ label: `tree.equations.${name}`, term, names: [] });
544
+ for (const [name, term] of Object.entries(tree.down ?? {})) out.push({ label: `tree.down.${name}`, term, names: DOWN_NAMES });
545
+ for (const [name, declared] of Object.entries(tree.attributes ?? {})) {
546
+ if (declared.fold !== undefined) {
547
+ out.push({ label: `tree.attributes.${name}.fold.unit`, term: declared.fold.unit, names: [] });
548
+ out.push({ label: `tree.attributes.${name}.fold.combine`, term: declared.fold.combine, names: FOLD_NAMES });
549
+ }
550
+ if (declared.default !== undefined) out.push({ label: `tree.attributes.${name}.default`, term: declared.default, names: [] });
551
+ }
552
+ return out;
553
+ }
554
+
555
+ /**
556
+ * This function checks the equations of the tree contract of a kind, each with the names of its rung (TREE.DOWN-NAMES).
557
+ * An equation evaluates without capabilities, thus a check with capabilities gives it none. Each issue gets the place
558
+ * of the document of the kind, and its reason names the equation.
559
+ */
560
+ function equationIssues(machine: Machine, kind: Kind, kindText: string, options: CheckOptions, seen: Set<Kind>): readonly Issue[] {
561
+ const of = equationType(kind);
562
+ const out: Issue[] = [];
563
+ for (const { label, term, names } of equationsOf(kind)) {
564
+ const inner: CheckOptions = { names, ...(of === undefined ? {} : { declaredType: of }), ...(options.caps === undefined ? {} : { caps: [] }) };
565
+ for (const issue of checkAll(machine, term, inner, undefined, seen)) {
566
+ out.push({ code: issue.code, place: [], reason: `In the equation ${label} of the kind ${kindText}: ${issue.reason}` });
567
+ }
568
+ }
569
+ return out;
570
+ }
571
+
598
572
  /**
599
573
  * This function finds the issues of `check`. With a list of nodes, it also records each document that it visits for
600
- * the pass of types. It marks each document with an issue.
574
+ * the pass of types. It marks each document with an issue. The set `seen` holds the kinds whose equations the check
575
+ * walked, thus it walks the equations of each kind one time.
601
576
  */
602
- function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined): readonly Issue[] {
577
+ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes: TypeNode[] | undefined, seen: Set<Kind> = new Set()): readonly Issue[] {
603
578
  const issues: Issue[] = [];
604
579
  const verb = options.verb ?? "eval";
605
580
  const indices = nodes === undefined ? undefined : new Map<DocVisit, number>();
@@ -643,6 +618,11 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
643
618
  push({ code: "#KIND?", place: v.place, reason: `The world has no kind ${typeName(v.doc.type)}.`, at: v.at });
644
619
  return "skip";
645
620
  }
621
+ // The equations of a kind are code of the kind. The check walks them at the first document of the kind (D42).
622
+ if (v.kind?.tree !== undefined && !seen.has(v.kind)) {
623
+ seen.add(v.kind);
624
+ for (const issue of equationIssues(machine, v.kind, typeName(v.doc.type), options, seen)) push({ ...issue, place: v.place, at: v.at });
625
+ }
646
626
  const name = answer.evaluator;
647
627
  if (current !== undefined) current.evaluator = name;
648
628
  // A runner hides the evaluator of its name (MACHINE.RUNNER), as in the compilation of a plan.