@mark1russell7/coeval 0.17.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
@@ -66,6 +66,12 @@ export interface Domain {
66
66
  * domain does not count. The check reads a list of at most 64 values.
67
67
  */
68
68
  readonly values?: readonly unknown[];
69
+ /**
70
+ * Some members of the domain for the tests of `conforms`, for example `0` and `-1` of `number` (D99). The checks
71
+ * `truth` and `cover` add the samples of each domain to the palette. A sample that fails a test of a type does not
72
+ * count. The list is not a full list of the members, thus `fits` does not read it.
73
+ */
74
+ readonly samples?: readonly unknown[];
69
75
  /**
70
76
  * The covers of the domain (TYPE.FITS-COVER, D85). A cover is a list of domains under this domain, and its domains
71
77
  * together hold each member of this domain. An example is a domain of negative numbers and a domain of the other
@@ -73,7 +79,7 @@ export interface Domain {
73
79
  *
74
80
  * A split of a record also narrows a field of the domain to each domain of a cover (D90).
75
81
  * The language keeps each cover true, and `fits` trusts it, as it trusts `values`. A cover that is empty, or that
76
- * names this domain or a name that is not under it, does not count.
82
+ * names this domain or a name that is not under it, does not count. `conforms` tests each cover from samples (D94).
77
83
  */
78
84
  readonly covers?: readonly (readonly string[])[];
79
85
  }
@@ -115,7 +121,7 @@ export const STD_DOMAINS: Readonly<Record<string, Domain>> = {
115
121
  // JSON has no undefined, thus a document holds it as ext.undefined (D48). The domain has no codec: the codec of values writes undefined first.
116
122
  undefined: { is: (value) => value === undefined, values: [undefined], summary: "The value undefined. A document holds it as the extension ext.undefined." },
117
123
  bool: { is: (value) => typeof value === "boolean", values: [true, false], summary: "A truth value: true or false." },
118
- number: { is: (value) => typeof value === "number", summary: "A number of the host, also NaN, an infinity and -0." },
124
+ number: { is: (value) => typeof value === "number", samples: [0, -1], summary: "A number of the host, also NaN, an infinity and -0." },
119
125
  text: { is: (value) => typeof value === "string", summary: "A text." },
120
126
  };
121
127
 
@@ -199,8 +205,12 @@ function formOf(type: unknown, domains: Readonly<Record<string, Domain>> | undef
199
205
  return { t: "record", fields: fields as Readonly<Record<string, ValueType>>, optional: new Set(names), closed };
200
206
  }
201
207
 
202
- /** This function gives each base type that holds the members of a base type: the type itself and each parent, through each level. */
203
- 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> {
204
214
  const seen = new Set<string>([name]);
205
215
  const queue = [name];
206
216
  while (queue.length > 0) {
@@ -421,7 +431,8 @@ function membersAt(type: unknown, domains: Readonly<Record<string, Domain>> | un
421
431
  /**
422
432
  * This function gives members of a type for a test of a declaration (D86). A type with at most 64 members gives each
423
433
  * member, from the lists `values` of its domains (TYPE.FITS-FINITE). A union gives the members of each of its types.
424
- * Each other type gives the samples that are its members. The kernel does not export the function.
434
+ * Each other type gives the samples that are its members. The samples of the domains come after the given samples (D99).
435
+ * The kernel does not export the function.
425
436
  *
426
437
  * @param type - The type.
427
438
  * @param domains - The domains of the machine.
@@ -429,7 +440,61 @@ function membersAt(type: unknown, domains: Readonly<Record<string, Domain>> | un
429
440
  * @returns Distinct members of the type.
430
441
  */
431
442
  export function membersOf(type: ValueType, domains: Readonly<Record<string, Domain>> | undefined, samples: readonly unknown[]): readonly unknown[] {
432
- return membersAt(type, domains, samples, 0);
443
+ return membersAt(type, domains, withDomainSamples(samples, domains), 0);
444
+ }
445
+
446
+ /**
447
+ * This function adds the samples of the domains to a palette (D99). It reads the list `samples` of each domain of the
448
+ * table and of each standard domain that the table does not replace. The result holds each value one time.
449
+ */
450
+ function withDomainSamples(palette: readonly unknown[], domains: Readonly<Record<string, Domain>> | undefined): readonly unknown[] {
451
+ const out: unknown[] = [];
452
+ for (const value of palette) addValue(out, value);
453
+ const names = new Set([...Object.keys(STD_DOMAINS), ...(domains === undefined ? [] : Object.keys(domains))]);
454
+ for (const name of names) {
455
+ const samples: unknown = domainOf(name, domains)?.samples;
456
+ if (Array.isArray(samples)) for (const value of samples as readonly unknown[]) addValue(out, value);
457
+ }
458
+ return out;
459
+ }
460
+
461
+ /** A false cover that a sample shows (D94): a member of a covered domain that no domain of the cover holds. */
462
+ export interface FalseCover {
463
+ /** The name of the covered domain. */
464
+ readonly domain: string;
465
+ /** The cover: the names of its domains. */
466
+ readonly cover: readonly string[];
467
+ /** The sample. It is a member of the covered domain. */
468
+ readonly value: unknown;
469
+ }
470
+
471
+ /**
472
+ * This function finds the false covers of a language from samples (D94). A cover is false when a sample of the covered
473
+ * domain is in no domain of the cover. The function tests each valid cover of each domain of the table. It ignores a
474
+ * cover that does not count by the line rule of D85, because `fits` ignores it too. It also ignores a sample outside
475
+ * the covered domain.
476
+ *
477
+ * The samples of the domains come after the given samples (D99). `conforms` and a test of `fits` use the function. The
478
+ * kernel does not export it.
479
+ *
480
+ * @param domains - The domains of the language, by their names.
481
+ * @param samples - The samples of each covered domain, by the name of the domain.
482
+ * @returns Each sample that breaks a cover, in the order of the domains, the covers and the samples.
483
+ */
484
+ export function falseCovers(domains: Readonly<Record<string, Domain>>, samples: Readonly<Record<string, readonly unknown[]>>): readonly FalseCover[] {
485
+ const out: FalseCover[] = [];
486
+ for (const [name, domain] of Object.entries(domains)) {
487
+ const covers: unknown = domain.covers;
488
+ if (!Array.isArray(covers)) continue;
489
+ const values = withDomainSamples((Object.hasOwn(samples, name) ? samples[name] : undefined) ?? [], domains);
490
+ for (const cover of covers as readonly unknown[]) {
491
+ if (!validCover(name, cover, domains)) continue;
492
+ for (const value of values) {
493
+ if (member(value, name, domains) && !cover.some((part) => member(value, part, domains))) out.push({ domain: name, cover, value });
494
+ }
495
+ }
496
+ }
497
+ return out;
433
498
  }
434
499
 
435
500
  /** This function holds the rules of the shapes of `fits`: base types, lists, records and functions. */
@@ -542,12 +607,13 @@ function partFits(part: string, expected: unknown, domains: Readonly<Record<stri
542
607
  /**
543
608
  * A field that the split rule can split: the path of keys to it, its cases, and if the field is optional. A value
544
609
  * split has the members of the field type. A type split has case types: the domains of a cover, or the members of a
545
- * union (TYPE.FITS-SPLIT-TYPE, D90).
610
+ * union (TYPE.FITS-SPLIT-TYPE, D90). A cover split has each valid cover of the field, in the order of the line (D102).
546
611
  */
547
612
  interface Split {
548
613
  readonly path: readonly string[];
549
614
  readonly values?: readonly unknown[];
550
615
  readonly types?: readonly ValueType[];
616
+ readonly covers?: readonly (readonly string[])[];
551
617
  readonly optional: boolean;
552
618
  }
553
619
 
@@ -563,7 +629,8 @@ interface SplitBudget {
563
629
  * cases is a split. An optional field also has the absent case.
564
630
  *
565
631
  * The type splits follow each value split, in the same order (D90). A base type with a valid cover in its line splits
566
- * into the domains of the cover. A union without a finite list splits into its members. The prefix is the path of `a`.
632
+ * into the domains of a cover, and it tries each valid cover (D102). A union without a finite list splits into its
633
+ * members. The prefix is the path of `a`.
567
634
  */
568
635
  function splitsOf(
569
636
  a: Extract<Form, { readonly t: "record" }>,
@@ -588,8 +655,8 @@ function splitsOf(
588
655
  if (inner.t === "record") deeper.push({ path: [...path, key], form: inner });
589
656
  else if (inner.t === "union") later.push({ path: [...path, key], types: inner.members, optional });
590
657
  else if (inner.t === "base") {
591
- const cover = coverOf(inner.name, domains, false);
592
- if (cover !== undefined) later.push({ path: [...path, key], types: cover, optional });
658
+ const covers = coversOf(inner.name, domains, false);
659
+ if (covers.length > 0) later.push({ path: [...path, key], covers, optional });
593
660
  }
594
661
  }
595
662
  }
@@ -604,17 +671,17 @@ function validCover(name: string, cover: unknown, domains: Readonly<Record<strin
604
671
  }
605
672
 
606
673
  /**
607
- * This function gives the cover of a base type for a type split (D90). The cover is the first valid cover of the first
608
- * domain in the line with a valid cover. With `own`, only the domain of the type itself counts. Each other type gives
609
- * `undefined`.
674
+ * This function gives the valid covers of a base type for a type split (D90, D102). The order is the order of the line,
675
+ * then the order of the covers of each domain. With `own`, only the domain of the type itself counts.
610
676
  */
611
- function coverOf(name: string, domains: Readonly<Record<string, Domain>> | undefined, own: boolean): readonly string[] | undefined {
677
+ function coversOf(name: string, domains: Readonly<Record<string, Domain>> | undefined, own: boolean): readonly (readonly string[])[] {
678
+ const out: (readonly string[])[] = [];
612
679
  for (const each of own ? [name] : lineOf(name, domains)) {
613
680
  const covers: unknown = domainOf(each, domains)?.covers;
614
681
  if (!Array.isArray(covers)) continue;
615
- for (const cover of covers as readonly unknown[]) if (validCover(each, cover, domains)) return cover;
682
+ for (const cover of covers as readonly unknown[]) if (validCover(each, cover, domains)) out.push(cover);
616
683
  }
617
- return undefined;
684
+ return out;
618
685
  }
619
686
 
620
687
  /**
@@ -629,8 +696,8 @@ function againOf(type: ValueType, path: readonly string[], domains: Readonly<Rec
629
696
  const form = formOf(type, domains);
630
697
  if (form.t === "record") return splitsOf(form, domains, at, path);
631
698
  if (form.t === "union") return [{ path, types: form.members, optional: false }];
632
- const cover = form.t === "base" ? coverOf(form.name, domains, true) : undefined;
633
- return cover === undefined ? [] : [{ path, types: cover, optional: false }];
699
+ const covers = form.t === "base" ? coversOf(form.name, domains, true) : [];
700
+ return covers.length === 0 ? [] : [{ path, covers, optional: false }];
634
701
  }
635
702
 
636
703
  /**
@@ -663,6 +730,8 @@ function narrowAt(type: unknown, path: readonly string[], at: number, narrowed:
663
730
  * A record of the split that does not fit splits the next such field. A record of a type split first splits its case
664
731
  * again, at the same path. One budget of 64 records serves the outer split and each split inside its work, at each
665
732
  * depth. Thus nested splits do not multiply the work.
733
+ *
734
+ * A cover split tries each valid cover of its field until one fits (D102). Each try takes from the same budget.
666
735
  */
667
736
  function splitFits(
668
737
  found: unknown,
@@ -692,6 +761,15 @@ function casesFit(
692
761
  ): boolean {
693
762
  const split = splits[index];
694
763
  if (split === undefined) return false;
764
+ // A cover split tries each valid cover in its order. A failed try keeps the records that it took from the budget (D102).
765
+ if (split.covers !== undefined) {
766
+ for (const cover of split.covers) {
767
+ const tried = [...splits];
768
+ tried[index] = { path: split.path, types: cover, optional: split.optional };
769
+ if (casesFit(type, closed, tried, index, expected, domains, depth, gradual, budget)) return true;
770
+ }
771
+ return false;
772
+ }
695
773
  const types: readonly ValueType[] = split.values?.map((value) => ({ one: [value as Json] })) ?? split.types ?? [];
696
774
  const cases: ValueType[] = types.map((each) => narrowAt(type, split.path, 0, each, domains));
697
775
  if (split.optional) cases.push(narrowAt(type, split.path, 0, undefined, domains));
@@ -743,16 +821,18 @@ function casesFit(
743
821
  * split first, then the paths of each next depth, in the order of the keys. A path goes into a record without a
744
822
  * finite list. Thus `{a: {x: bool}}` fits the union of `{a: {x: true}}` and `{a: {x: false}}`.
745
823
  * - A type split comes after each value split (TYPE.FITS-SPLIT-TYPE, D90). A field of a base type with a valid cover in
746
- * its line splits into the domains of the first such cover. A field of a union without a finite list splits into its
747
- * members. A case that does not fit splits again at its path, by the same rules. A base type there uses only a
748
- * cover of its own domain. Thus `{a: number}` fits `{a: neg} | {a: nonneg}` when `neg` and `nonneg` cover `number`.
824
+ * its line splits into the domains of a cover. A field of a union without a finite list splits into its
825
+ * members. A case that does not fit splits again at its path, by the same rules. A base type there uses only the
826
+ * covers of its own domain. Thus `{a: number}` fits `{a: neg} | {a: nonneg}` when `neg` and `nonneg` cover `number`.
827
+ * - A field with more than one valid cover tries each cover until one fits (TYPE.FITS-SPLIT-COVERS, D102). The order
828
+ * is the order of the line, then the order of the covers of each domain.
749
829
  *
750
830
  * The relation is sound: a member of `found` is a member of `expected` when no `unknown` is in the types
751
831
  * (TYPE.FITS-SOUND). The finite rule trusts `values`, and the cover rule trusts `covers`. Thus a list without a member
752
832
  * or a false cover makes the relation unsound for that domain.
753
833
  *
754
- * The relation is not complete. A split builds at most 64 records, with each split inside its work at each depth. A
755
- * type split uses one cover of a field, not each cover of its line. A domain without `values` gets no finite rule, and
834
+ * The relation is not complete. A split builds at most 64 records, with each split inside its work at each depth. The
835
+ * failed tries of covers take from these 64 records too. A domain without `values` gets no finite rule, and
756
836
  * neither does a type with more than 64 members that is not a record. The cover rule tests at most 64 domains of
757
837
  * covers. The function does not throw.
758
838
  *
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;