@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/build/index.d.ts +39 -78
- package/build/index.d.ts.map +1 -1
- package/build/index.js +313 -133
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/conform.ts +35 -9
- package/src/evaluate.ts +38 -2
- package/src/formula.ts +14 -3
- package/src/merge.ts +6 -3
- package/src/reference.ts +20 -1
- package/src/replay.ts +123 -35
- package/src/types.ts +97 -21
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
|
|
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
|
|
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
|
|
592
|
-
if (
|
|
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
|
|
608
|
-
*
|
|
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
|
|
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))
|
|
678
|
+
for (const cover of covers as readonly unknown[]) if (validCover(each, cover, domains)) out.push(cover);
|
|
616
679
|
}
|
|
617
|
-
return
|
|
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
|
|
633
|
-
return
|
|
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
|
|
747
|
-
* members. A case that does not fit splits again at its path, by the same rules. A base type there uses only
|
|
748
|
-
*
|
|
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.
|
|
755
|
-
*
|
|
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
|
*
|