@mark1russell7/coeval 0.17.0 → 0.18.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/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
 
@@ -421,7 +427,8 @@ function membersAt(type: unknown, domains: Readonly<Record<string, Domain>> | un
421
427
  /**
422
428
  * This function gives members of a type for a test of a declaration (D86). A type with at most 64 members gives each
423
429
  * 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.
430
+ * Each other type gives the samples that are its members. The samples of the domains come after the given samples (D99).
431
+ * The kernel does not export the function.
425
432
  *
426
433
  * @param type - The type.
427
434
  * @param domains - The domains of the machine.
@@ -429,7 +436,61 @@ function membersAt(type: unknown, domains: Readonly<Record<string, Domain>> | un
429
436
  * @returns Distinct members of the type.
430
437
  */
431
438
  export function membersOf(type: ValueType, domains: Readonly<Record<string, Domain>> | undefined, samples: readonly unknown[]): readonly unknown[] {
432
- return membersAt(type, domains, samples, 0);
439
+ return membersAt(type, domains, withDomainSamples(samples, domains), 0);
440
+ }
441
+
442
+ /**
443
+ * This function adds the samples of the domains to a palette (D99). It reads the list `samples` of each domain of the
444
+ * table and of each standard domain that the table does not replace. The result holds each value one time.
445
+ */
446
+ function withDomainSamples(palette: readonly unknown[], domains: Readonly<Record<string, Domain>> | undefined): readonly unknown[] {
447
+ const out: unknown[] = [];
448
+ for (const value of palette) addValue(out, value);
449
+ const names = new Set([...Object.keys(STD_DOMAINS), ...(domains === undefined ? [] : Object.keys(domains))]);
450
+ for (const name of names) {
451
+ const samples: unknown = domainOf(name, domains)?.samples;
452
+ if (Array.isArray(samples)) for (const value of samples as readonly unknown[]) addValue(out, value);
453
+ }
454
+ return out;
455
+ }
456
+
457
+ /** A false cover that a sample shows (D94): a member of a covered domain that no domain of the cover holds. */
458
+ export interface FalseCover {
459
+ /** The name of the covered domain. */
460
+ readonly domain: string;
461
+ /** The cover: the names of its domains. */
462
+ readonly cover: readonly string[];
463
+ /** The sample. It is a member of the covered domain. */
464
+ readonly value: unknown;
465
+ }
466
+
467
+ /**
468
+ * This function finds the false covers of a language from samples (D94). A cover is false when a sample of the covered
469
+ * domain is in no domain of the cover. The function tests each valid cover of each domain of the table. It ignores a
470
+ * cover that does not count by the line rule of D85, because `fits` ignores it too. It also ignores a sample outside
471
+ * the covered domain.
472
+ *
473
+ * The samples of the domains come after the given samples (D99). `conforms` and a test of `fits` use the function. The
474
+ * kernel does not export it.
475
+ *
476
+ * @param domains - The domains of the language, by their names.
477
+ * @param samples - The samples of each covered domain, by the name of the domain.
478
+ * @returns Each sample that breaks a cover, in the order of the domains, the covers and the samples.
479
+ */
480
+ export function falseCovers(domains: Readonly<Record<string, Domain>>, samples: Readonly<Record<string, readonly unknown[]>>): readonly FalseCover[] {
481
+ const out: FalseCover[] = [];
482
+ for (const [name, domain] of Object.entries(domains)) {
483
+ const covers: unknown = domain.covers;
484
+ if (!Array.isArray(covers)) continue;
485
+ const values = withDomainSamples((Object.hasOwn(samples, name) ? samples[name] : undefined) ?? [], domains);
486
+ for (const cover of covers as readonly unknown[]) {
487
+ if (!validCover(name, cover, domains)) continue;
488
+ for (const value of values) {
489
+ if (member(value, name, domains) && !cover.some((part) => member(value, part, domains))) out.push({ domain: name, cover, value });
490
+ }
491
+ }
492
+ }
493
+ return out;
433
494
  }
434
495
 
435
496
  /** This function holds the rules of the shapes of `fits`: base types, lists, records and functions. */
@@ -542,12 +603,13 @@ function partFits(part: string, expected: unknown, domains: Readonly<Record<stri
542
603
  /**
543
604
  * 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
605
  * 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).
606
+ * union (TYPE.FITS-SPLIT-TYPE, D90). A cover split has each valid cover of the field, in the order of the line (D102).
546
607
  */
547
608
  interface Split {
548
609
  readonly path: readonly string[];
549
610
  readonly values?: readonly unknown[];
550
611
  readonly types?: readonly ValueType[];
612
+ readonly covers?: readonly (readonly string[])[];
551
613
  readonly optional: boolean;
552
614
  }
553
615
 
@@ -563,7 +625,8 @@ interface SplitBudget {
563
625
  * cases is a split. An optional field also has the absent case.
564
626
  *
565
627
  * 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`.
628
+ * into the domains of a cover, and it tries each valid cover (D102). A union without a finite list splits into its
629
+ * members. The prefix is the path of `a`.
567
630
  */
568
631
  function splitsOf(
569
632
  a: Extract<Form, { readonly t: "record" }>,
@@ -588,8 +651,8 @@ function splitsOf(
588
651
  if (inner.t === "record") deeper.push({ path: [...path, key], form: inner });
589
652
  else if (inner.t === "union") later.push({ path: [...path, key], types: inner.members, optional });
590
653
  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 });
654
+ const covers = coversOf(inner.name, domains, false);
655
+ if (covers.length > 0) later.push({ path: [...path, key], covers, optional });
593
656
  }
594
657
  }
595
658
  }
@@ -604,17 +667,17 @@ function validCover(name: string, cover: unknown, domains: Readonly<Record<strin
604
667
  }
605
668
 
606
669
  /**
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`.
670
+ * This function gives the valid covers of a base type for a type split (D90, D102). The order is the order of the line,
671
+ * then the order of the covers of each domain. With `own`, only the domain of the type itself counts.
610
672
  */
611
- function coverOf(name: string, domains: Readonly<Record<string, Domain>> | undefined, own: boolean): readonly string[] | undefined {
673
+ function coversOf(name: string, domains: Readonly<Record<string, Domain>> | undefined, own: boolean): readonly (readonly string[])[] {
674
+ const out: (readonly string[])[] = [];
612
675
  for (const each of own ? [name] : lineOf(name, domains)) {
613
676
  const covers: unknown = domainOf(each, domains)?.covers;
614
677
  if (!Array.isArray(covers)) continue;
615
- for (const cover of covers as readonly unknown[]) if (validCover(each, cover, domains)) return cover;
678
+ for (const cover of covers as readonly unknown[]) if (validCover(each, cover, domains)) out.push(cover);
616
679
  }
617
- return undefined;
680
+ return out;
618
681
  }
619
682
 
620
683
  /**
@@ -629,8 +692,8 @@ function againOf(type: ValueType, path: readonly string[], domains: Readonly<Rec
629
692
  const form = formOf(type, domains);
630
693
  if (form.t === "record") return splitsOf(form, domains, at, path);
631
694
  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 }];
695
+ const covers = form.t === "base" ? coversOf(form.name, domains, true) : [];
696
+ return covers.length === 0 ? [] : [{ path, covers, optional: false }];
634
697
  }
635
698
 
636
699
  /**
@@ -663,6 +726,8 @@ function narrowAt(type: unknown, path: readonly string[], at: number, narrowed:
663
726
  * 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
727
  * again, at the same path. One budget of 64 records serves the outer split and each split inside its work, at each
665
728
  * depth. Thus nested splits do not multiply the work.
729
+ *
730
+ * A cover split tries each valid cover of its field until one fits (D102). Each try takes from the same budget.
666
731
  */
667
732
  function splitFits(
668
733
  found: unknown,
@@ -692,6 +757,15 @@ function casesFit(
692
757
  ): boolean {
693
758
  const split = splits[index];
694
759
  if (split === undefined) return false;
760
+ // A cover split tries each valid cover in its order. A failed try keeps the records that it took from the budget (D102).
761
+ if (split.covers !== undefined) {
762
+ for (const cover of split.covers) {
763
+ const tried = [...splits];
764
+ tried[index] = { path: split.path, types: cover, optional: split.optional };
765
+ if (casesFit(type, closed, tried, index, expected, domains, depth, gradual, budget)) return true;
766
+ }
767
+ return false;
768
+ }
695
769
  const types: readonly ValueType[] = split.values?.map((value) => ({ one: [value as Json] })) ?? split.types ?? [];
696
770
  const cases: ValueType[] = types.map((each) => narrowAt(type, split.path, 0, each, domains));
697
771
  if (split.optional) cases.push(narrowAt(type, split.path, 0, undefined, domains));
@@ -743,16 +817,18 @@ function casesFit(
743
817
  * split first, then the paths of each next depth, in the order of the keys. A path goes into a record without a
744
818
  * finite list. Thus `{a: {x: bool}}` fits the union of `{a: {x: true}}` and `{a: {x: false}}`.
745
819
  * - 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`.
820
+ * its line splits into the domains of a cover. A field of a union without a finite list splits into its
821
+ * members. A case that does not fit splits again at its path, by the same rules. A base type there uses only the
822
+ * covers of its own domain. Thus `{a: number}` fits `{a: neg} | {a: nonneg}` when `neg` and `nonneg` cover `number`.
823
+ * - A field with more than one valid cover tries each cover until one fits (TYPE.FITS-SPLIT-COVERS, D102). The order
824
+ * is the order of the line, then the order of the covers of each domain.
749
825
  *
750
826
  * The relation is sound: a member of `found` is a member of `expected` when no `unknown` is in the types
751
827
  * (TYPE.FITS-SOUND). The finite rule trusts `values`, and the cover rule trusts `covers`. Thus a list without a member
752
828
  * or a false cover makes the relation unsound for that domain.
753
829
  *
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
830
+ * The relation is not complete. A split builds at most 64 records, with each split inside its work at each depth. The
831
+ * failed tries of covers take from these 64 records too. A domain without `values` gets no finite rule, and
756
832
  * neither does a type with more than 64 members that is not a record. The cover rule tests at most 64 domains of
757
833
  * covers. The function does not throw.
758
834
  *