@mark1russell7/coeval 0.18.0 → 0.19.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/stdlib.ts CHANGED
@@ -236,7 +236,7 @@ export const STD_EVALUATORS: Readonly<Record<string, Evaluator>> = {
236
236
  const found = resolve(data as readonly Json[]);
237
237
  return found.ok ? ok(valueOfThing(found.thing)) : fail(found.code, found.reason);
238
238
  },
239
- "tree.node": (input) => treeNode(input),
239
+ "tree.node": (input) => treeNode(input, undefined),
240
240
  };
241
241
 
242
242
  /*
@@ -316,6 +316,57 @@ interface NodeState {
316
316
  json(path: Set<object>): Result;
317
317
  }
318
318
 
319
+ /** The answer of the ladder for one attribute of one node (TREE.CELL, D108). */
320
+ export interface AttributeAnswer {
321
+ /** The result of the attribute. It is `undefined` when no rung answered. */
322
+ readonly result: Result | undefined;
323
+ /** The rung that answered. It is `undefined` when no rung answered. */
324
+ readonly by: AttributeRung | undefined;
325
+ }
326
+
327
+ /**
328
+ * This function gives the memo cell of one attribute of one node, for a host that keeps its cells between evaluations
329
+ * (TREE.CELL, D108). The kit uses it at each demand of an attribute in an evaluation.
330
+ *
331
+ * The array `place` is one object for each document of a plan, and it stays the same across the evaluations of that
332
+ * plan. A new plan gives new arrays. Thus a host keeps its cells by the identity of `place` and by `attribute`.
333
+ * A host that keeps its cells by the keys of `place` can give a stale answer after a change of the document.
334
+ *
335
+ * The function gives the answer of `compute`, or a kept answer. A host with signals gives a computed signal: a change
336
+ * of its store then makes only the attributes that read the store compute again. Each demand gives a new `compute`, and
337
+ * a cell that computes again uses the last one.
338
+ *
339
+ * The kit marks an attribute in evaluation before it uses the cell. Thus a cycle gives `#CYCLE!` before it reaches the
340
+ * cell again. An exception of the function that `compute` did not throw gives `#CYCLE!` with the chain of the demands.
341
+ *
342
+ * @param place - The keys from the root document to the document of the node.
343
+ * @param attribute - The name of the attribute.
344
+ * @param compute - The function that visits the rungs of the ladder.
345
+ * @returns The answer.
346
+ */
347
+ export type AttributeCell = (place: readonly Key[], attribute: string, compute: () => AttributeAnswer) => AttributeAnswer;
348
+
349
+ /** The options of the tree kit (D108). */
350
+ export interface TreeOptions {
351
+ /**
352
+ * The memo cells of the host (TREE.CELL). The default is a plain memo for each node and attribute, for one
353
+ * evaluation (TREE.MEMO).
354
+ */
355
+ readonly cell?: AttributeCell;
356
+ }
357
+
358
+ /**
359
+ * This function gives the code of the kind of a node of the decorated tree, with the options of the host. A host puts
360
+ * the code at the name `tree.node` of its machine. Without a cell, the code is the standard code.
361
+ *
362
+ * @param options - The options, for example the memo cells of the host.
363
+ * @returns The code.
364
+ */
365
+ export function treeNodeEvaluator(options: TreeOptions = {}): Evaluator {
366
+ const host = options.cell;
367
+ return host === undefined ? (STD_EVALUATORS["tree.node"] as Evaluator) : (input: EvalInput) => treeNode(input, host);
368
+ }
369
+
319
370
  /**
320
371
  * The memo cell of one attribute: in evaluation, or done with its result and the rung that answered. A done cell
321
372
  * without a result tells that no rung answered.
@@ -373,6 +424,31 @@ function contractOf(machine: Machine | undefined, typePath: readonly Key[]): Nod
373
424
 
374
425
  const BUSY: Cell = /* @__PURE__ */ Object.freeze({ busy: true });
375
426
 
427
+ /** The answer of the ladder when no rung answers. */
428
+ const NO_ANSWER: AttributeAnswer = /* @__PURE__ */ Object.freeze({ result: undefined, by: undefined });
429
+
430
+ /**
431
+ * The marks of the attributes in evaluation, for each place of a plan, with the cells of a host (TREE.CELL). Nodes of
432
+ * two evaluations share their place and their kept cells, thus they share their marks.
433
+ */
434
+ const marksByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], Map<string, Cell>>();
435
+
436
+ /** The trees of the cells of a host, for each place of a root in a plan. Nodes of two evaluations stay in one tree. */
437
+ const buildsByPlace = /* @__PURE__ */ new WeakMap<readonly Key[], TreeBuild>();
438
+
439
+ /** This function gives the value of a weak map, or puts a new value in it. */
440
+ function keptAt<T>(map: WeakMap<readonly Key[], T>, place: readonly Key[], make: () => T): T {
441
+ let value = map.get(place);
442
+ if (value === undefined) {
443
+ value = make();
444
+ map.set(place, value);
445
+ }
446
+ return value;
447
+ }
448
+
449
+ /** A value that no exception has. */
450
+ const NOT_THROWN: unique symbol = /* @__PURE__ */ Symbol("coeval.tree.not-thrown") as never;
451
+
376
452
  /** One attribute in evaluation. The text names it in the reason of a cycle. */
377
453
  interface Demand {
378
454
  readonly cells: Map<string, Cell>;
@@ -451,8 +527,10 @@ function plainOf(value: unknown, path: Set<object>): Result {
451
527
  * the node, an equation of its kind and an equation `down` of the kind of its parent. A copy from the parent, then a
452
528
  * fold or a default, come last. A failed attribute throws `Failure` with its own error, thus a reference to it gives that error.
453
529
  * The argument `outer` is the self of the context, that `treeNode` resolved. The node reads no root of the context directly (C4).
530
+ *
531
+ * The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of the node.
454
532
  */
455
- function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined): HostThing {
533
+ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined, host: AttributeCell | undefined): HostThing {
456
534
  const { parts, context, kind: type, place, machine } = input;
457
535
  // The equations evaluate in the evaluation that made the node, thus their reads go in its list (READS.EQUATION).
458
536
  const runtime = (input as InputWithRuntime)[RUNTIME];
@@ -461,6 +539,8 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
461
539
  // Only a node of the same tree is a parent for the rungs `parent` and `copy`.
462
540
  const up = above !== undefined && above.build === build ? above : undefined;
463
541
  const cells = new Map<string, Cell>();
542
+ // With the cells of a host, the marks of a place serve each node of the place (TREE.CELL).
543
+ const marks = host === undefined ? cells : keptAt(marksByPlace, place, () => new Map<string, Cell>());
464
544
  /** The positions of the items of the list `children`, for the names of the children. */
465
545
  let positions: { readonly list: readonly unknown[]; readonly at: Map<unknown, number> } | undefined;
466
546
 
@@ -505,7 +585,45 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
505
585
  return undefined;
506
586
  };
507
587
 
588
+ /**
589
+ * This function gives an attribute through the cell of the host (TREE.CELL). The cell is the memo. The node keeps
590
+ * the last answer only for `has` and `trace`.
591
+ */
592
+ const through =
593
+ host === undefined
594
+ ? undefined
595
+ : (key: string): Result | undefined => {
596
+ const text = attributeText(key, type, place);
597
+ if (marks.has(key)) return cycleAt(marks, key, text);
598
+ if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
599
+ marks.set(key, BUSY);
600
+ demands.push({ cells: marks, key, text });
601
+ let thrown: unknown = NOT_THROWN;
602
+ let answer: AttributeAnswer;
603
+ try {
604
+ answer = host(place, key, () => {
605
+ try {
606
+ return climb(key) ?? NO_ANSWER;
607
+ } catch (error) {
608
+ thrown = error;
609
+ throw error;
610
+ }
611
+ });
612
+ } catch (error) {
613
+ // An exception of the kit or of the stack goes on, as without a host. Another exception is a cycle of the host.
614
+ if (error === thrown) throw error;
615
+ const chain = demands.map((demand) => demand.text).join(" -> ");
616
+ answer = { result: fail("#CYCLE!", `The cells of the host found a cycle in the demands: ${chain}.`, { cause: error }), by: undefined };
617
+ } finally {
618
+ demands.pop();
619
+ marks.delete(key);
620
+ }
621
+ cells.set(key, { busy: false, result: answer.result, by: answer.by });
622
+ return answer.result;
623
+ };
624
+
508
625
  const read = (key: string): Result | undefined => {
626
+ if (through !== undefined) return through(key);
509
627
  const cell = cells.get(key);
510
628
  if (cell !== undefined) return cell.busy ? cycleAt(cells, key, attributeText(key, type, place)) : cell.result;
511
629
  // A node without a contract has only its parts, as before D42. Thus a missing part needs no cell.
@@ -635,14 +753,17 @@ const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
635
753
  * This function evaluates a node of the decorated tree. Inside an attribute of an open tree, the node gives its host
636
754
  * thing, and no attribute evaluates. Else the node is the root of a new tree, and it gives the JSON of the tree. The
637
755
  * node reads `self` through `resolve` (C4, D44). A machine that closes the root `self` thus gives no nested trees.
756
+ * The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of each node.
638
757
  */
639
- function treeNode(input: EvalInput): Result {
758
+ function treeNode(input: EvalInput, host: AttributeCell | undefined): Result {
640
759
  const found = input.resolve(SELF_PATH, NO_HOPS);
641
760
  const outer = found.ok ? found.thing : undefined;
642
761
  const above = stateOf(outer);
643
- if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer));
644
- const build: TreeBuild = { open: true };
645
- const root = treeThing(input, build, outer);
762
+ if (above !== undefined && above.build.open) return ok(treeThing(input, above.build, outer, host));
763
+ // With the cells of a host, a kept value can hold a node of an earlier evaluation. Thus that node stays in the tree.
764
+ const build: TreeBuild = host === undefined ? { open: true } : keptAt(buildsByPlace, input.place, () => ({ open: true }));
765
+ build.open = true;
766
+ const root = treeThing(input, build, outer, host);
646
767
  try {
647
768
  return (stateOf(root) as NodeState).json(new Set());
648
769
  } finally {
package/src/types.ts CHANGED
@@ -205,8 +205,12 @@ function formOf(type: unknown, domains: Readonly<Record<string, Domain>> | undef
205
205
  return { t: "record", fields: fields as Readonly<Record<string, ValueType>>, optional: new Set(names), closed };
206
206
  }
207
207
 
208
- /** This function gives each base type that holds the members of a base type: the type itself and each parent, through each level. */
209
- function lineOf(name: string, domains: Readonly<Record<string, Domain>> | undefined): ReadonlySet<string> {
208
+ /**
209
+ * This function gives each base type that holds the members of a base type: the type itself and each parent, through
210
+ * each level. A cycle of parents ends at a name that the line has, and a parent that is not a domain ends its branch.
211
+ * The function is internal: `encodeValue` uses it too (D109).
212
+ */
213
+ export function lineOf(name: string, domains: Readonly<Record<string, Domain>> | undefined): ReadonlySet<string> {
210
214
  const seen = new Set<string>([name]);
211
215
  const queue = [name];
212
216
  while (queue.length > 0) {
package/src/typing.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { type Envelope, type Key, isRecord, typeName } from "./doc.ts";
2
2
  import type { Machine } from "./evaluate.ts";
3
- import type { DocVisit, Issue } from "./static.ts";
4
- import { type ValueType, fits, isUnknown, typeIssues, typeText } from "./types.ts";
3
+ import type { DocVisit, Issue, WorldIssue } from "./static.ts";
4
+ import { type ValueType, domainOf, fits, isUnknown, typeIssues, typeText } from "./types.ts";
5
+ import { type Kind, type Shape, eachKind, kindAt } from "./world.ts";
5
6
 
6
7
  /*
7
8
  * The check of value types (R5 P3, D25). The pass reads the documents that the walk of `check` visited. It goes down
@@ -133,8 +134,36 @@ export interface TypeRule {
133
134
  * @returns The type of the document.
134
135
  */
135
136
  readonly synth: (input: TypeInput, report: (problem: TypeProblem) => void) => ValueType;
137
+ /**
138
+ * This function lists the declarations that the rule reads, for example the signatures of a formula profile
139
+ * (TYPE.WORLD, D110). `checkWorld` with the option `types` examines each one before a document exists. A rule
140
+ * without this function declares nothing.
141
+ *
142
+ * @returns The value types that the rule reads, and the problems that the rule finds in its declarations.
143
+ */
144
+ readonly declared?: () => readonly Declared[];
136
145
  }
137
146
 
147
+ /**
148
+ * One declaration of a type rule (TYPE.WORLD, D110). It is a value type that the rule reads, with what it is. Or it is
149
+ * a problem that the rule finds in a declaration, for example an overload that the check does not select at any time.
150
+ */
151
+ export type Declared =
152
+ | {
153
+ /** The keys of the declaration in the declarations of the rule, for example `["sigs", "add", "to"]`. */
154
+ readonly at: readonly Key[];
155
+ /** The value type. `checkWorld` examines it with `typeIssues`. */
156
+ readonly type: unknown;
157
+ /** What the type is, for the reason of an issue, for example "result of a signature of ops.add". */
158
+ readonly what: string;
159
+ }
160
+ | {
161
+ /** The keys of the declaration in the declarations of the rule. */
162
+ readonly at: readonly Key[];
163
+ /** The reason of the problem. */
164
+ readonly problem: string;
165
+ };
166
+
138
167
  /** The type of one document of a check of types. */
139
168
  export interface Typed {
140
169
  /** The keys from the root document to the document. */
@@ -206,7 +235,7 @@ function pathKey(path: readonly Key[]): string {
206
235
  }
207
236
 
208
237
  /** This function gives the type rule of an evaluator name. A runner hides the evaluator of its name, and a runner has no rule yet (K29). */
209
- function ruleOf(machine: Machine, name: string | undefined): TypeRule | undefined {
238
+ export function ruleOf(machine: Machine, name: string | undefined): TypeRule | undefined {
210
239
  if (name === undefined || (machine.runners !== undefined && Object.hasOwn(machine.runners, name))) return undefined;
211
240
  const evaluator = Object.hasOwn(machine.evaluators, name) ? machine.evaluators[name] : undefined;
212
241
  const rule = typeof evaluator === "object" && evaluator !== null ? (evaluator as { readonly type?: unknown }).type : undefined;
@@ -388,3 +417,114 @@ export function typePass(machine: Machine, nodes: readonly TypeNode[], verb: str
388
417
  const issues = found.sort((a, b) => a.index - b.index).map((item) => item.issue);
389
418
  return { type: types[0] ?? "unknown", issues, places };
390
419
  }
420
+
421
+ /** The owner of a value type of a machine: a kind, a domain or a type rule. */
422
+ type Owner = Pick<WorldIssue, "kind" | "fallback" | "domain" | "evaluator">;
423
+
424
+ /**
425
+ * This function examines each value type of a machine before a document exists (TYPE.WORLD, D110). It reads the
426
+ * `result` and each `expect` of each kind, the parents and the steps of each domain, and the declarations of each type
427
+ * rule. Each problem of `typeIssues` gives one issue with the keys of the bad term.
428
+ *
429
+ * A term shape with `kinds` and `expect` also gets an issue for each kind of its list whose `result` does not fit
430
+ * `expect`. A check of types gives `#TYPE!` at each document of that kind there.
431
+ *
432
+ * @param machine - The machine.
433
+ * @returns The issues: the kinds in the order of the world, then the domains, then the type rules.
434
+ */
435
+ export function machineTypeIssues(machine: Machine): readonly WorldIssue[] {
436
+ const domains = machine.domains;
437
+ const world = machine.world;
438
+ const out: WorldIssue[] = [];
439
+ const add = (owner: Owner, at: readonly Key[], reason: string): void => {
440
+ out.push({ code: "#TYPE!", ...owner, verb: "eval", at, reason });
441
+ };
442
+ /** This function examines one value type. It gives `true` when the type has no problem. */
443
+ const examine = (owner: Owner, at: readonly Key[], type: unknown, what: string): boolean => {
444
+ const problems = typeIssues(type, domains);
445
+ for (const problem of problems) add(owner, [...at, ...problem.at], `The ${what} is not a correct value type. ${problem.reason}`);
446
+ return problems.length === 0;
447
+ };
448
+ /**
449
+ * This function finds each kind of the list `kinds` of a term whose result does not fit the `expect` of the term.
450
+ * Only a kind at the path itself counts. A path through an alias or to a fallback kind gives no issue.
451
+ */
452
+ const results = (owner: Owner, kinds: readonly (readonly Key[])[], at: readonly Key[], expect: ValueType): void => {
453
+ if (isUnknown(expect, domains)) return;
454
+ kinds.forEach((path, index) => {
455
+ const found: Kind | undefined = Array.isArray(path) ? kindAt(world, path) : undefined;
456
+ const result = found?.result;
457
+ if (result === undefined || typeIssues(result, domains).length > 0 || fits(result, expect, domains)) return;
458
+ const text = path.join(".");
459
+ add(owner, [...at, "kinds", index], `The kind ${text} has the result ${typeText(result)}, and the part needs the type ${typeText(expect)}. Each document of ${text} here gets #TYPE!.`);
460
+ });
461
+ };
462
+
463
+ eachKind(world, (kind, path, fallback) => {
464
+ const owner: Owner = fallback ? { kind: path, fallback: true } : { kind: path };
465
+ const name = fallback ? (path.length === 0 ? "fallback kind of the world" : `fallback kind of ${path.join(".")}`) : `kind ${path.join(".")}`;
466
+ if (kind.result !== undefined) examine(owner, ["result"], kind.result, `result of the ${name}`);
467
+ // The set holds the shapes on the way, thus a shape that holds itself does not loop.
468
+ const way = new Set<Shape>();
469
+ const shape = (spec: Shape | undefined, at: readonly Key[]): void => {
470
+ if (typeof spec !== "object" || spec === null || way.has(spec)) return;
471
+ way.add(spec);
472
+ const expect = (spec as { readonly expect?: unknown }).expect;
473
+ const where = [...at, "expect"];
474
+ if (expect !== undefined && examine(owner, where, expect, `expected type at ${where.join(".")} of the ${name}`) && spec.shape === "term" && Array.isArray(spec.kinds)) {
475
+ results(owner, spec.kinds, at, expect as ValueType);
476
+ }
477
+ if (spec.shape === "list" || spec.shape === "map") shape(spec.item, [...at, "item"]);
478
+ if (spec.shape === "record") {
479
+ const fields: unknown = spec.fields;
480
+ if (typeof fields === "object" && fields !== null && !Array.isArray(fields)) {
481
+ for (const key of Object.keys(fields)) shape((fields as Readonly<Record<string, Shape>>)[key], [...at, "fields", key]);
482
+ }
483
+ shape(spec.rest, [...at, "rest"]);
484
+ }
485
+ way.delete(spec);
486
+ };
487
+ shape(kind.parts, ["parts"]);
488
+ });
489
+
490
+ const table = domains ?? {};
491
+ for (const name of Object.keys(table)) {
492
+ // A table entry with the name of the top or the bottom has no effect (TYPE.OPEN).
493
+ if (name === "unknown" || name === "never") continue;
494
+ const owner: Owner = { kind: [], domain: name };
495
+ const domain = domainOf(name, domains);
496
+ if (domain === undefined) {
497
+ add(owner, ["is"], `The domain ${name} has no test is. Each use of the name ${name} reads as unknown.`);
498
+ continue;
499
+ }
500
+ const under: unknown = domain.under;
501
+ if (Array.isArray(under)) {
502
+ under.forEach((parent: unknown, index) => {
503
+ if (parent === "unknown" || parent === "never") return;
504
+ if (typeof parent !== "string" || domainOf(parent, domains) === undefined) add(owner, ["under", index], `The parent ${String(parent)} of the domain ${name} is not a domain of the machine.`);
505
+ });
506
+ }
507
+ const steps: unknown = domain.steps;
508
+ if (typeof steps === "object" && steps !== null) {
509
+ for (const key of Object.keys(steps)) examine(owner, ["steps", key], (steps as Readonly<Record<string, unknown>>)[key], `type of the step ${key} of the domain ${name}`);
510
+ }
511
+ }
512
+
513
+ for (const name of Object.keys(machine.evaluators)) {
514
+ const declared = ruleOf(machine, name)?.declared;
515
+ if (typeof declared !== "function") continue;
516
+ const owner: Owner = { kind: [], evaluator: name };
517
+ let list: readonly Declared[];
518
+ try {
519
+ list = declared();
520
+ } catch (error) {
521
+ add(owner, [], `The type rule of ${name} fails: ${message(error)}`);
522
+ continue;
523
+ }
524
+ for (const item of list) {
525
+ if ("problem" in item) add(owner, item.at, item.problem);
526
+ else examine(owner, item.at, item.type, item.what);
527
+ }
528
+ }
529
+ return out;
530
+ }
package/src/world.ts CHANGED
@@ -323,6 +323,55 @@ export function isNamespace(entry: WorldEntry | undefined): entry is Namespace {
323
323
  return entry !== undefined && !isKind(entry) && !isAlias(entry) && Object.hasOwn(entry, "kinds");
324
324
  }
325
325
 
326
+ /**
327
+ * This function visits each kind of a world. These are the kinds of each namespace, at each depth, and the fallback
328
+ * kind of each namespace and of the world. It does not follow an alias, because the kind of an alias has its own place. A
329
+ * fallback kind has the keys of its namespace. It comes after the entries of that namespace. `checkWorld` reads the
330
+ * kinds with this one function.
331
+ *
332
+ * @param world - The world.
333
+ * @param visit - The function that gets each kind, its keys from the root of the world, and `true` for a fallback kind.
334
+ */
335
+ export function eachKind(world: World, visit: (kind: Kind, path: readonly Key[], fallback: boolean) => void): void {
336
+ // The set holds the namespaces on the way, thus a namespace that holds itself does not loop.
337
+ const way = new Set<Namespace>();
338
+ const entries = (kinds: Readonly<Record<string, WorldEntry>>, path: readonly Key[]): void => {
339
+ for (const key of Object.keys(kinds)) {
340
+ const entry = kinds[key];
341
+ const here = [...path, key];
342
+ if (isKind(entry)) visit(entry, here, false);
343
+ else if (isNamespace(entry) && !way.has(entry)) {
344
+ way.add(entry);
345
+ entries(entry.kinds, here);
346
+ if (entry.fallback !== undefined) visit(entry.fallback, here, true);
347
+ way.delete(entry);
348
+ }
349
+ }
350
+ };
351
+ entries(world.kinds, []);
352
+ if (world.fallback !== undefined) visit(world.fallback, [], true);
353
+ }
354
+
355
+ /**
356
+ * This function gives the kind at a path of a world, without an alias and without a fallback kind. A namespace on the
357
+ * way gives its entries.
358
+ *
359
+ * @param world - The world.
360
+ * @param path - The keys from the root of the world.
361
+ * @returns The kind, or `undefined` when no kind is at the path.
362
+ */
363
+ export function kindAt(world: World, path: readonly Key[]): Kind | undefined {
364
+ let entries = world.kinds;
365
+ let entry: WorldEntry | undefined;
366
+ for (let index = 0; index < path.length; index += 1) {
367
+ entry = entryAt(entries, path[index] as Key);
368
+ if (index === path.length - 1) break;
369
+ if (!isNamespace(entry)) return undefined;
370
+ entries = entry.kinds;
371
+ }
372
+ return isKind(entry) ? entry : undefined;
373
+ }
374
+
326
375
  function entryAt(entries: Readonly<Record<string, WorldEntry>>, key: Key): WorldEntry | undefined {
327
376
  const name = String(key);
328
377
  return Object.hasOwn(entries, name) ? entries[name] : undefined;