@mark1russell7/coeval 0.16.0 → 0.17.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/README.md +9 -4
- package/build/index.d.ts +522 -171
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2352 -296
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +128 -16
- package/src/conform.ts +49 -5
- package/src/deref.ts +76 -11
- package/src/evaluate.ts +113 -19
- package/src/facts.ts +113 -78
- package/src/formula.ts +55 -10
- package/src/index.ts +23 -1
- package/src/merge.ts +46 -14
- package/src/reference.ts +90 -14
- package/src/replay.ts +964 -18
- package/src/static.ts +2 -2
- package/src/stdlib.ts +55 -2
- package/src/syntax.ts +357 -52
- package/src/types.ts +443 -16
package/build/index.js
CHANGED
|
@@ -577,14 +577,17 @@ function looksLikeKind(value) {
|
|
|
577
577
|
const STD_DOMAINS = {
|
|
578
578
|
null: {
|
|
579
579
|
is: (value) => value === null,
|
|
580
|
+
values: [null],
|
|
580
581
|
summary: "The value null."
|
|
581
582
|
},
|
|
582
583
|
undefined: {
|
|
583
584
|
is: (value) => value === void 0,
|
|
585
|
+
values: [void 0],
|
|
584
586
|
summary: "The value undefined. A document holds it as the extension ext.undefined."
|
|
585
587
|
},
|
|
586
588
|
bool: {
|
|
587
589
|
is: (value) => typeof value === "boolean",
|
|
590
|
+
values: [true, false],
|
|
588
591
|
summary: "A truth value: true or false."
|
|
589
592
|
},
|
|
590
593
|
number: {
|
|
@@ -598,6 +601,12 @@ const STD_DOMAINS = {
|
|
|
598
601
|
};
|
|
599
602
|
/** The deepest term that a value type can have. A deeper term reads as `unknown`, thus a check stays total. */
|
|
600
603
|
const MAX_TYPE_DEPTH = 256;
|
|
604
|
+
/** The most values that the finite rule of `fits` reads for one type (TYPE.FITS-FINITE). A type with more values is not finite for the rule. */
|
|
605
|
+
const MAX_FINITE_VALUES = 64;
|
|
606
|
+
/** The most domains of covers that the cover rule of `fits` tests for one type (TYPE.FITS-COVER). A larger test gives `false`. */
|
|
607
|
+
const MAX_COVER_STEPS = 64;
|
|
608
|
+
/** The most records that one split of `fits` builds, with each split inside its work (TYPE.FITS-PATH, D89). A larger split gives `false`. */
|
|
609
|
+
const MAX_SPLIT_RECORDS = 64;
|
|
601
610
|
/** The keys that each term permits beside its own key. */
|
|
602
611
|
const TERM_KEYS = {
|
|
603
612
|
list: ["list"],
|
|
@@ -620,7 +629,7 @@ function domainOf(name, domains) {
|
|
|
620
629
|
return typeof found === "object" && found !== null && typeof found.is === "function" ? found : void 0;
|
|
621
630
|
}
|
|
622
631
|
/** This function gives the term key of a record, when the record has exactly one term key and no other key. */
|
|
623
|
-
function termOf$
|
|
632
|
+
function termOf$2(term) {
|
|
624
633
|
const keys = Object.keys(term);
|
|
625
634
|
const own = keys.filter((key) => TERMS.includes(key));
|
|
626
635
|
if (own.length !== 1) return void 0;
|
|
@@ -638,7 +647,7 @@ function formOf(type, domains) {
|
|
|
638
647
|
};
|
|
639
648
|
}
|
|
640
649
|
if (!isRecord(type)) return TOP;
|
|
641
|
-
const term = termOf$
|
|
650
|
+
const term = termOf$2(type);
|
|
642
651
|
if (term === "list") return {
|
|
643
652
|
t: "list",
|
|
644
653
|
item: type["list"]
|
|
@@ -745,39 +754,358 @@ function member(value, type, domains) {
|
|
|
745
754
|
return memberAt(value, type, domains, 0);
|
|
746
755
|
}
|
|
747
756
|
/** This function tells if the record type `a` fits the record type `b`: width, closed records and optional fields. */
|
|
748
|
-
function recordFits(a, b, domains, depth, gradual) {
|
|
757
|
+
function recordFits(a, b, domains, depth, gradual, budget) {
|
|
749
758
|
if (b.closed && (!a.closed || Object.keys(a.fields).some((key) => !Object.hasOwn(b.fields, key)))) return false;
|
|
750
759
|
for (const key of Object.keys(b.fields)) {
|
|
751
760
|
const want = b.fields[key];
|
|
752
761
|
const needed = !b.optional.has(key);
|
|
753
762
|
if (Object.hasOwn(a.fields, key)) {
|
|
754
763
|
if (needed && a.optional.has(key)) return false;
|
|
755
|
-
if (!fitsAt(a.fields[key], want, domains, depth, gradual)) return false;
|
|
764
|
+
if (!fitsAt(a.fields[key], want, domains, depth, gradual, true, budget)) return false;
|
|
756
765
|
} else if (needed) return false;
|
|
757
766
|
else if (!a.closed && formOf(want, domains).t !== "top") return false;
|
|
758
767
|
}
|
|
759
768
|
return true;
|
|
760
769
|
}
|
|
770
|
+
/** This function adds a value to a list of distinct values, by `docEquals`. */
|
|
771
|
+
function addValue(out, value) {
|
|
772
|
+
if (!out.some((item) => docEquals(item, value))) out.push(value);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* This function gives the values of a base type with a finite list. It reads the list of the first domain of the line
|
|
776
|
+
* with at most 64 values, and it keeps only the members of the type. Thus the list of a parent serves each domain under it.
|
|
777
|
+
*/
|
|
778
|
+
function baseValues(name, domains) {
|
|
779
|
+
const domain = domainOf(name, domains);
|
|
780
|
+
if (domain === void 0) return void 0;
|
|
781
|
+
const line = Array.isArray(domain.values) || !Array.isArray(domain.under) ? [name] : lineOf(name, domains);
|
|
782
|
+
for (const each of line) {
|
|
783
|
+
const values = domainOf(each, domains)?.values;
|
|
784
|
+
if (!Array.isArray(values) || values.length > MAX_FINITE_VALUES) continue;
|
|
785
|
+
const out = [];
|
|
786
|
+
for (let index = 0; index < values.length; index += 1) if (inDomain(values[index], name, domains)) addValue(out, values[index]);
|
|
787
|
+
return out;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/** This function gives a copy of a record with one more field. */
|
|
791
|
+
function withField(record, key, value) {
|
|
792
|
+
const out = {};
|
|
793
|
+
for (const name of Object.keys(record)) setOwn$6(out, name, record[name]);
|
|
794
|
+
setOwn$6(out, key, value);
|
|
795
|
+
return out;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* This function gives the members of a record type, when they are few. A record with a needed field without members
|
|
799
|
+
* has no members. Only a closed record with a finite list for each field has a finite list.
|
|
800
|
+
*/
|
|
801
|
+
function recordValues(form, domains, depth) {
|
|
802
|
+
let out = [{}];
|
|
803
|
+
let finite = form.closed;
|
|
804
|
+
for (const key of Object.keys(form.fields)) {
|
|
805
|
+
const values = valuesOf$1(form.fields[key], domains, depth);
|
|
806
|
+
const optional = form.optional.has(key);
|
|
807
|
+
if (values === void 0) {
|
|
808
|
+
finite = false;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (values.length === 0 && !optional) return [];
|
|
812
|
+
if (!finite) continue;
|
|
813
|
+
if (out.length * (values.length + (optional ? 1 : 0)) > MAX_FINITE_VALUES) {
|
|
814
|
+
finite = false;
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
out = out.flatMap((record) => [...optional ? [record] : [], ...values.map((value) => withField(record, key, value))]);
|
|
818
|
+
}
|
|
819
|
+
return finite ? out : void 0;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* This function gives a list of the members of a type, when the type has at most 64 members (TYPE.FITS-FINITE). Each
|
|
823
|
+
* other type gives `undefined`. Such a type is `unknown`, a base type without a finite list, or a function type. A
|
|
824
|
+
* list that can hold an item and an open record with members are also such types.
|
|
825
|
+
*/
|
|
826
|
+
function valuesOf$1(type, domains, depth) {
|
|
827
|
+
if (depth > MAX_TYPE_DEPTH) return void 0;
|
|
828
|
+
const form = formOf(type, domains);
|
|
829
|
+
const next = depth + 1;
|
|
830
|
+
switch (form.t) {
|
|
831
|
+
case "bottom": return [];
|
|
832
|
+
case "base": return baseValues(form.name, domains);
|
|
833
|
+
case "list": return valuesOf$1(form.item, domains, next)?.length === 0 ? [[]] : void 0;
|
|
834
|
+
case "record": return recordValues(form, domains, next);
|
|
835
|
+
case "one":
|
|
836
|
+
case "union": {
|
|
837
|
+
const out = [];
|
|
838
|
+
const items = form.t === "one" ? form.values : form.members;
|
|
839
|
+
for (const item of items) {
|
|
840
|
+
const values = form.t === "one" ? [item] : valuesOf$1(item, domains, next);
|
|
841
|
+
if (values === void 0) return void 0;
|
|
842
|
+
for (const value of values) addValue(out, value);
|
|
843
|
+
if (out.length > MAX_FINITE_VALUES) return void 0;
|
|
844
|
+
}
|
|
845
|
+
return out;
|
|
846
|
+
}
|
|
847
|
+
default: return;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/** This function gives the members of a type at one depth, for `membersOf`. */
|
|
851
|
+
function membersAt(type, domains, samples, depth) {
|
|
852
|
+
const finite = valuesOf$1(type, domains, depth);
|
|
853
|
+
if (finite !== void 0) return finite;
|
|
854
|
+
const form = formOf(type, domains);
|
|
855
|
+
if (form.t !== "union" || depth > MAX_TYPE_DEPTH) return samples.filter((sample) => member(sample, type, domains));
|
|
856
|
+
const out = [];
|
|
857
|
+
for (const item of form.members) for (const value of membersAt(item, domains, samples, depth + 1)) addValue(out, value);
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* This function gives members of a type for a test of a declaration (D86). A type with at most 64 members gives each
|
|
862
|
+
* member, from the lists `values` of its domains (TYPE.FITS-FINITE). A union gives the members of each of its types.
|
|
863
|
+
* Each other type gives the samples that are its members. The kernel does not export the function.
|
|
864
|
+
*
|
|
865
|
+
* @param type - The type.
|
|
866
|
+
* @param domains - The domains of the machine.
|
|
867
|
+
* @param samples - The values to test when the type has no full list of members.
|
|
868
|
+
* @returns Distinct members of the type.
|
|
869
|
+
*/
|
|
870
|
+
function membersOf(type, domains, samples) {
|
|
871
|
+
return membersAt(type, domains, samples, 0);
|
|
872
|
+
}
|
|
873
|
+
/** This function holds the rules of the shapes of `fits`: base types, lists, records and functions. */
|
|
874
|
+
function shapeFits(a, b, domains, next, gradual, budget) {
|
|
875
|
+
if (a.t === "base") return b.t === "base" && lineOf(a.name, domains).has(b.name);
|
|
876
|
+
if (a.t === "list") return b.t === "list" && fitsAt(a.item, b.item, domains, next, gradual, true, budget);
|
|
877
|
+
if (a.t === "record") return b.t === "record" && recordFits(a, b, domains, next, gradual, budget);
|
|
878
|
+
if (a.t !== "fn" || b.t !== "fn" || a.params.length > b.params.length) return false;
|
|
879
|
+
return a.params.every((param, index) => fitsAt(b.params[index], param, domains, next, gradual, true, budget)) && fitsAt(a.to, b.to, domains, next, gradual, true, budget);
|
|
880
|
+
}
|
|
761
881
|
/**
|
|
762
882
|
* This function is the relation `fits` at one depth of two types. The order of its tests is the order of the rules of
|
|
763
883
|
* `fits`. Without `gradual`, `unknown` is only the top: it fits only `unknown`. The join uses this strict form.
|
|
884
|
+
*
|
|
885
|
+
* When the rules of the shapes refuse a type with few members, the finite rule tests each member (TYPE.FITS-FINITE).
|
|
886
|
+
* The cover rule and the split rule come after it. Without `finite`, the call skips these three rules. The members of
|
|
887
|
+
* a union in `expected` get such calls, because the call for the union tests `found` against the full union.
|
|
888
|
+
*
|
|
889
|
+
* The budget is the budget of the outer split, when the call is part of the work of a split (TYPE.FITS-PATH, D89).
|
|
764
890
|
*/
|
|
765
|
-
function fitsAt(found, expected, domains, depth, gradual = true) {
|
|
891
|
+
function fitsAt(found, expected, domains, depth, gradual = true, finite = true, budget) {
|
|
766
892
|
if (depth > MAX_TYPE_DEPTH) return true;
|
|
767
893
|
const a = formOf(found, domains);
|
|
768
894
|
const b = formOf(expected, domains);
|
|
769
895
|
const next = depth + 1;
|
|
770
896
|
if (b.t === "top" || a.t === "bottom") return true;
|
|
771
897
|
if (a.t === "top") return gradual;
|
|
772
|
-
if (a.t === "union") return a.members.every((item) => fitsAt(item, expected, domains, next, gradual));
|
|
898
|
+
if (a.t === "union") return a.members.every((item) => fitsAt(item, expected, domains, next, gradual, true, budget));
|
|
773
899
|
if (a.t === "one") return a.values.every((item) => memberAt(item, expected, domains, next));
|
|
774
|
-
if (b.t === "bottom") return
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
if (
|
|
778
|
-
if (a.t === "
|
|
779
|
-
|
|
780
|
-
|
|
900
|
+
if (b.t === "union" ? b.members.some((item) => fitsAt(found, item, domains, next, gradual, false, budget)) : b.t !== "bottom" && shapeFits(a, b, domains, next, gradual, budget)) return true;
|
|
901
|
+
if (!finite || a.t === "fn") return false;
|
|
902
|
+
const values = valuesOf$1(found, domains, next);
|
|
903
|
+
if (values !== void 0) return values.every((value) => memberAt(value, expected, domains, next));
|
|
904
|
+
if (a.t === "base") return (b.t === "union" || b.t === "one") && lineCovered(a.name, expected, domains, depth, gradual, void 0);
|
|
905
|
+
return a.t === "record" && (b.t === "union" || b.t === "one" && a.closed) && splitFits(found, a, expected, domains, depth, gradual, budget);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* This function is the cover rule of `fits` (TYPE.FITS-COVER, D85). A base type fits when a domain of its line has a
|
|
909
|
+
* cover, and each domain of the cover fits `expected`. The function makes the state at the first domain with covers.
|
|
910
|
+
*/
|
|
911
|
+
function lineCovered(name, expected, domains, depth, gradual, state) {
|
|
912
|
+
let now = state;
|
|
913
|
+
for (const each of lineOf(name, domains)) {
|
|
914
|
+
if (!Array.isArray(domainOf(each, domains)?.covers)) continue;
|
|
915
|
+
now ??= {
|
|
916
|
+
held: /* @__PURE__ */ new Map(),
|
|
917
|
+
left: MAX_COVER_STEPS
|
|
918
|
+
};
|
|
919
|
+
if (covered(each, expected, domains, depth, gradual, now)) return true;
|
|
920
|
+
}
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* This function tells if a cover of one domain holds the domain in `expected`. A cover that is empty, or that names
|
|
925
|
+
* the domain or a name that is not under it, does not count. Each domain of a cover costs one step, and the rule
|
|
926
|
+
* stops after 64 steps.
|
|
927
|
+
*/
|
|
928
|
+
function covered(name, expected, domains, depth, gradual, state) {
|
|
929
|
+
const known = state.held.get(name);
|
|
930
|
+
if (known !== void 0) return known;
|
|
931
|
+
state.held.set(name, false);
|
|
932
|
+
const covers = domainOf(name, domains)?.covers;
|
|
933
|
+
if (!Array.isArray(covers)) return false;
|
|
934
|
+
for (const cover of covers) {
|
|
935
|
+
if (!Array.isArray(cover) || cover.length === 0) continue;
|
|
936
|
+
let held = true;
|
|
937
|
+
for (const part of cover) {
|
|
938
|
+
state.left -= 1;
|
|
939
|
+
if (state.left < 0) return false;
|
|
940
|
+
if (typeof part !== "string" || part === name || !lineOf(part, domains).has(name) || !partFits(part, expected, domains, depth, gradual, state)) {
|
|
941
|
+
held = false;
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (held) {
|
|
946
|
+
state.held.set(name, true);
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
/** This function tells if a domain of a cover fits `expected`. It tries the rules of the shapes, the finite rule and its own covers. */
|
|
953
|
+
function partFits(part, expected, domains, depth, gradual, state) {
|
|
954
|
+
if (fitsAt(part, expected, domains, depth, gradual, false)) return true;
|
|
955
|
+
const values = valuesOf$1(part, domains, depth + 1);
|
|
956
|
+
if (values !== void 0) return values.every((value) => memberAt(value, expected, domains, depth + 1));
|
|
957
|
+
return lineCovered(part, expected, domains, depth, gradual, state);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* This function gives the fields that the split rule can split, in the order of the split (TYPE.FITS-PATH, D89). The
|
|
961
|
+
* fields of the record come first, in the order of the keys. Then the paths of the next depth follow, and so on. A path
|
|
962
|
+
* goes into a field only when the field is a record without a finite list. A field with a finite list of two or more
|
|
963
|
+
* cases is a split. An optional field also has the absent case.
|
|
964
|
+
*
|
|
965
|
+
* The type splits follow each value split, in the same order (D90). A base type with a valid cover in its line splits
|
|
966
|
+
* into the domains of the cover. A union without a finite list splits into its members. The prefix is the path of `a`.
|
|
967
|
+
*/
|
|
968
|
+
function splitsOf(a, domains, depth, prefix = []) {
|
|
969
|
+
const out = [];
|
|
970
|
+
const later = [];
|
|
971
|
+
let level = [{
|
|
972
|
+
path: prefix,
|
|
973
|
+
form: a
|
|
974
|
+
}];
|
|
975
|
+
for (let at = depth + 1; level.length > 0 && at <= MAX_TYPE_DEPTH; at += 1) {
|
|
976
|
+
const deeper = [];
|
|
977
|
+
for (const { path, form } of level) for (const key of Object.keys(form.fields)) {
|
|
978
|
+
const values = valuesOf$1(form.fields[key], domains, at);
|
|
979
|
+
const optional = form.optional.has(key);
|
|
980
|
+
if (values !== void 0) {
|
|
981
|
+
if (values.length + (optional ? 1 : 0) > 1) out.push({
|
|
982
|
+
path: [...path, key],
|
|
983
|
+
values,
|
|
984
|
+
optional
|
|
985
|
+
});
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
const inner = formOf(form.fields[key], domains);
|
|
989
|
+
if (inner.t === "record") deeper.push({
|
|
990
|
+
path: [...path, key],
|
|
991
|
+
form: inner
|
|
992
|
+
});
|
|
993
|
+
else if (inner.t === "union") later.push({
|
|
994
|
+
path: [...path, key],
|
|
995
|
+
types: inner.members,
|
|
996
|
+
optional
|
|
997
|
+
});
|
|
998
|
+
else if (inner.t === "base") {
|
|
999
|
+
const cover = coverOf(inner.name, domains, false);
|
|
1000
|
+
if (cover !== void 0) later.push({
|
|
1001
|
+
path: [...path, key],
|
|
1002
|
+
types: cover,
|
|
1003
|
+
optional
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
level = deeper;
|
|
1008
|
+
}
|
|
1009
|
+
return later.length === 0 ? out : [...out, ...later];
|
|
1010
|
+
}
|
|
1011
|
+
/** This function tells if a cover of a domain is valid by the line rule of D85. Such a cover is not empty, and each name in it is a domain under the domain. */
|
|
1012
|
+
function validCover(name, cover, domains) {
|
|
1013
|
+
return Array.isArray(cover) && cover.length > 0 && cover.every((part) => typeof part === "string" && part !== name && lineOf(part, domains).has(name));
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* This function gives the cover of a base type for a type split (D90). The cover is the first valid cover of the first
|
|
1017
|
+
* domain in the line with a valid cover. With `own`, only the domain of the type itself counts. Each other type gives
|
|
1018
|
+
* `undefined`.
|
|
1019
|
+
*/
|
|
1020
|
+
function coverOf(name, domains, own) {
|
|
1021
|
+
for (const each of own ? [name] : lineOf(name, domains)) {
|
|
1022
|
+
const covers = domainOf(each, domains)?.covers;
|
|
1023
|
+
if (!Array.isArray(covers)) continue;
|
|
1024
|
+
for (const cover of covers) if (validCover(each, cover, domains)) return cover;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* This function gives the splits of a case of a type split, at the path of the case (D90). A case with a finite list
|
|
1029
|
+
* splits by its members. A base type splits by a cover of its own domain, because the covers of its parents hold it
|
|
1030
|
+
* already. A union splits by its members, and a record splits by the paths into its fields.
|
|
1031
|
+
*/
|
|
1032
|
+
function againOf(type, path, domains, depth) {
|
|
1033
|
+
const at = depth + path.length;
|
|
1034
|
+
const values = valuesOf$1(type, domains, at);
|
|
1035
|
+
if (values !== void 0) return values.length > 1 ? [{
|
|
1036
|
+
path,
|
|
1037
|
+
values,
|
|
1038
|
+
optional: false
|
|
1039
|
+
}] : [];
|
|
1040
|
+
const form = formOf(type, domains);
|
|
1041
|
+
if (form.t === "record") return splitsOf(form, domains, at, path);
|
|
1042
|
+
if (form.t === "union") return [{
|
|
1043
|
+
path,
|
|
1044
|
+
types: form.members,
|
|
1045
|
+
optional: false
|
|
1046
|
+
}];
|
|
1047
|
+
const cover = form.t === "base" ? coverOf(form.name, domains, true) : void 0;
|
|
1048
|
+
return cover === void 0 ? [] : [{
|
|
1049
|
+
path,
|
|
1050
|
+
types: cover,
|
|
1051
|
+
optional: false
|
|
1052
|
+
}];
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* This function gives a copy of a record type with one field narrowed at a path. A case type replaces the field. The
|
|
1056
|
+
* absent case removes the field of a closed record, and it gives an optional field of type `never` in an open record.
|
|
1057
|
+
*/
|
|
1058
|
+
function narrowAt(type, path, at, narrowed, domains) {
|
|
1059
|
+
const form = formOf(type, domains);
|
|
1060
|
+
const key = path[at];
|
|
1061
|
+
if (form.t !== "record" || key === void 0) return type;
|
|
1062
|
+
const last = at + 1 === path.length;
|
|
1063
|
+
const fields = {};
|
|
1064
|
+
for (const name of Object.keys(form.fields)) if (name !== key) setOwn$6(fields, name, form.fields[name]);
|
|
1065
|
+
else if (!last) setOwn$6(fields, name, narrowAt(form.fields[name], path, at + 1, narrowed, domains));
|
|
1066
|
+
else if (narrowed !== void 0) setOwn$6(fields, name, narrowed);
|
|
1067
|
+
else if (!form.closed) setOwn$6(fields, name, "never");
|
|
1068
|
+
const optional = [...form.optional].filter((name) => !last || name !== key || narrowed === void 0 && !form.closed);
|
|
1069
|
+
return {
|
|
1070
|
+
record: fields,
|
|
1071
|
+
...optional.length === 0 ? {} : { optional },
|
|
1072
|
+
...form.closed ? { closed: true } : {}
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* This function is the split rule of `fits` (TYPE.FITS-FINITE, D84, TYPE.FITS-PATH, D89). A record type fits when each
|
|
1077
|
+
* record of its split fits. A split takes the first field in the order of `splitsOf`. Each member of the field gives a
|
|
1078
|
+
* record with the field narrowed to that member. An optional field also gives a record without the field. A type split
|
|
1079
|
+
* narrows the field to each case type instead (TYPE.FITS-SPLIT-TYPE, D90).
|
|
1080
|
+
*
|
|
1081
|
+
* A record of the split that does not fit splits the next such field. A record of a type split first splits its case
|
|
1082
|
+
* again, at the same path. One budget of 64 records serves the outer split and each split inside its work, at each
|
|
1083
|
+
* depth. Thus nested splits do not multiply the work.
|
|
1084
|
+
*/
|
|
1085
|
+
function splitFits(found, a, expected, domains, depth, gradual, budget) {
|
|
1086
|
+
const splits = splitsOf(a, domains, depth);
|
|
1087
|
+
if (splits.length === 0) return false;
|
|
1088
|
+
return casesFit(found, a.closed, splits, 0, expected, domains, depth, gradual, budget ?? { left: MAX_SPLIT_RECORDS });
|
|
1089
|
+
}
|
|
1090
|
+
/** This function tells if each record of the split of one field fits `expected`. It splits the next field for a record that does not fit. */
|
|
1091
|
+
function casesFit(type, closed, splits, index, expected, domains, depth, gradual, budget) {
|
|
1092
|
+
const split = splits[index];
|
|
1093
|
+
if (split === void 0) return false;
|
|
1094
|
+
const cases = (split.values?.map((value) => ({ one: [value] })) ?? split.types ?? []).map((each) => narrowAt(type, split.path, 0, each, domains));
|
|
1095
|
+
if (split.optional) cases.push(narrowAt(type, split.path, 0, void 0, domains));
|
|
1096
|
+
for (const [at, record] of cases.entries()) {
|
|
1097
|
+
budget.left -= 1;
|
|
1098
|
+
if (budget.left < 0) return false;
|
|
1099
|
+
if (fitsAt(record, expected, domains, depth, gradual, false, budget)) continue;
|
|
1100
|
+
const values = closed ? valuesOf$1(record, domains, depth + 1) : void 0;
|
|
1101
|
+
if (values !== void 0) {
|
|
1102
|
+
if (values.every((value) => memberAt(value, expected, domains, depth + 1))) continue;
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
const again = split.types !== void 0 && at < split.types.length ? againOf(split.types[at], split.path, domains, depth) : [];
|
|
1106
|
+
if (!casesFit(record, closed, again.length === 0 ? splits : [...again, ...splits.slice(index + 1)], again.length === 0 ? index + 1 : 0, expected, domains, depth, gradual, budget)) return false;
|
|
1107
|
+
}
|
|
1108
|
+
return true;
|
|
781
1109
|
}
|
|
782
1110
|
/**
|
|
783
1111
|
* This function tells if each value of the type `found` can be where the type `expected` must be (TYPE.FITS). Each
|
|
@@ -794,9 +1122,33 @@ function fitsAt(found, expected, domains, depth, gradual = true) {
|
|
|
794
1122
|
* field is `unknown`.
|
|
795
1123
|
* - A function fits when it has the same or fewer parameters, each expected parameter fits its parameter, and its
|
|
796
1124
|
* result fits.
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
*
|
|
1125
|
+
* - The finite rule comes after these rules (TYPE.FITS-FINITE). A type with at most 64 members fits when each member
|
|
1126
|
+
* is a member of `expected`. Thus `bool` fits `{ "one": [true, false] }`.
|
|
1127
|
+
* - A base type has few members when its domain or a parent declares `values`, as `null`, `undefined` and `bool` do.
|
|
1128
|
+
* A union, a `one`, a closed record of such types and a list of `never` also have few members.
|
|
1129
|
+
* - The cover rule comes next (TYPE.FITS-COVER, D85). A base type fits a union or a `one` when each domain of a cover
|
|
1130
|
+
* fits. The cover is in `covers` of a domain of its line. Each domain of a cover is under the covered
|
|
1131
|
+
* domain. Thus a cover of `number` by `neg` and `nonneg` gives `number` a fit against `neg | nonneg`.
|
|
1132
|
+
* - The split rule comes last (D84). An open record, or a closed record with more than 64 members, splits a field with
|
|
1133
|
+
* few members over a union. A closed record also splits over a `one`. Each record of the split narrows the field to
|
|
1134
|
+
* one member, and each record must fit.
|
|
1135
|
+
* Thus `{ "record": { "a": "bool" } }` fits a union of a record for each value of `a`.
|
|
1136
|
+
* - A split also narrows a field of a record in a field, by its path (TYPE.FITS-PATH, D89). The fields of the record
|
|
1137
|
+
* split first, then the paths of each next depth, in the order of the keys. A path goes into a record without a
|
|
1138
|
+
* finite list. Thus `{a: {x: bool}}` fits the union of `{a: {x: true}}` and `{a: {x: false}}`.
|
|
1139
|
+
* - A type split comes after each value split (TYPE.FITS-SPLIT-TYPE, D90). A field of a base type with a valid cover in
|
|
1140
|
+
* its line splits into the domains of the first such cover. A field of a union without a finite list splits into its
|
|
1141
|
+
* members. A case that does not fit splits again at its path, by the same rules. A base type there uses only a
|
|
1142
|
+
* cover of its own domain. Thus `{a: number}` fits `{a: neg} | {a: nonneg}` when `neg` and `nonneg` cover `number`.
|
|
1143
|
+
*
|
|
1144
|
+
* The relation is sound: a member of `found` is a member of `expected` when no `unknown` is in the types
|
|
1145
|
+
* (TYPE.FITS-SOUND). The finite rule trusts `values`, and the cover rule trusts `covers`. Thus a list without a member
|
|
1146
|
+
* or a false cover makes the relation unsound for that domain.
|
|
1147
|
+
*
|
|
1148
|
+
* The relation is not complete. A split builds at most 64 records, with each split inside its work at each depth. A
|
|
1149
|
+
* type split uses one cover of a field, not each cover of its line. A domain without `values` gets no finite rule, and
|
|
1150
|
+
* neither does a type with more than 64 members that is not a record. The cover rule tests at most 64 domains of
|
|
1151
|
+
* covers. The function does not throw.
|
|
800
1152
|
*
|
|
801
1153
|
* @param found - The type that a part has.
|
|
802
1154
|
* @param expected - The type that the position needs.
|
|
@@ -807,7 +1159,7 @@ function fits(found, expected, domains) {
|
|
|
807
1159
|
return fitsAt(found, expected, domains, 0);
|
|
808
1160
|
}
|
|
809
1161
|
/** This function sets an own field of a new record. Only the key `__proto__` needs `defineProperty`. */
|
|
810
|
-
function setOwn$
|
|
1162
|
+
function setOwn$6(target, key, value) {
|
|
811
1163
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
812
1164
|
value,
|
|
813
1165
|
enumerable: true,
|
|
@@ -826,10 +1178,10 @@ function joinRecords(a, b, domains, depth) {
|
|
|
826
1178
|
const inA = Object.hasOwn(a.fields, key);
|
|
827
1179
|
const inB = Object.hasOwn(b.fields, key);
|
|
828
1180
|
if (inA && inB) {
|
|
829
|
-
setOwn$
|
|
1181
|
+
setOwn$6(fields, key, joinAt(a.fields[key], b.fields[key], domains, depth));
|
|
830
1182
|
if (a.optional.has(key) || b.optional.has(key)) optional.push(key);
|
|
831
1183
|
} else if (closed) {
|
|
832
|
-
setOwn$
|
|
1184
|
+
setOwn$6(fields, key, inA ? a.fields[key] : b.fields[key]);
|
|
833
1185
|
optional.push(key);
|
|
834
1186
|
}
|
|
835
1187
|
}
|
|
@@ -932,7 +1284,7 @@ function valueText$1(value) {
|
|
|
932
1284
|
function textAt(type, depth, wrap) {
|
|
933
1285
|
if (depth > MAX_TYPE_DEPTH) return "unknown";
|
|
934
1286
|
if (typeof type === "string") return type;
|
|
935
|
-
const term = isRecord(type) ? termOf$
|
|
1287
|
+
const term = isRecord(type) ? termOf$2(type) : void 0;
|
|
936
1288
|
if (!isRecord(type) || term === void 0) return "unknown";
|
|
937
1289
|
const next = depth + 1;
|
|
938
1290
|
const group = (items) => items.length === 0 ? "never" : items.length > 1 && wrap ? `(${items.join(" | ")})` : items.join(" | ");
|
|
@@ -1155,7 +1507,7 @@ function typeIssues(type, domains) {
|
|
|
1155
1507
|
if (here !== "unknown" && here !== "never" && domainOf(here, domains) === void 0) issue(`The machine has no domain ${here}.`);
|
|
1156
1508
|
continue;
|
|
1157
1509
|
}
|
|
1158
|
-
const term = isRecord(here) ? termOf$
|
|
1510
|
+
const term = isRecord(here) ? termOf$2(here) : void 0;
|
|
1159
1511
|
if (!isRecord(here) || term === void 0) {
|
|
1160
1512
|
issue(`A value type is a text, or a record with one of the keys ${TERMS.join(", ")} and the keys that go with it.`);
|
|
1161
1513
|
continue;
|
|
@@ -1233,7 +1585,7 @@ function keyOf(path) {
|
|
|
1233
1585
|
return JSON.stringify(path.map(String));
|
|
1234
1586
|
}
|
|
1235
1587
|
/** This function sets an own data property. An assignment to the key `__proto__` changes the prototype. */
|
|
1236
|
-
function setOwn$
|
|
1588
|
+
function setOwn$5(target, key, value) {
|
|
1237
1589
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
1238
1590
|
value,
|
|
1239
1591
|
enumerable: true,
|
|
@@ -1250,7 +1602,7 @@ function defined(value) {
|
|
|
1250
1602
|
if (Array.isArray(value)) return value.map(defined);
|
|
1251
1603
|
if (!isRecord(value)) return value;
|
|
1252
1604
|
const out = {};
|
|
1253
|
-
for (const key of Object.keys(value)) if (value[key] !== void 0) setOwn$
|
|
1605
|
+
for (const key of Object.keys(value)) if (value[key] !== void 0) setOwn$5(out, key, defined(value[key]));
|
|
1254
1606
|
return out;
|
|
1255
1607
|
}
|
|
1256
1608
|
/** Two values are the same when they are equal by `docEquals`. A function and a host value are equal only to themselves. */
|
|
@@ -1259,23 +1611,38 @@ function same(a, b) {
|
|
|
1259
1611
|
}
|
|
1260
1612
|
/**
|
|
1261
1613
|
* Two domains are the same when they are one object. They are also the same when they have the same test `is`,
|
|
1262
|
-
* parents, summary and
|
|
1614
|
+
* parents, summary, steps, codec, list of values and covers. Thus two languages that give one test under one name give one
|
|
1615
|
+
* domain. Two codecs are the same when they have the same functions `encode` and `decode` (D80).
|
|
1263
1616
|
*/
|
|
1264
1617
|
function sameDomain(a, b) {
|
|
1265
|
-
|
|
1618
|
+
if (a === b) return true;
|
|
1619
|
+
const fields = {
|
|
1620
|
+
is: a.is === b.is,
|
|
1621
|
+
under: same(a.under ?? [], b.under ?? []),
|
|
1622
|
+
summary: a.summary === b.summary,
|
|
1623
|
+
steps: same(a.steps ?? {}, b.steps ?? {}),
|
|
1624
|
+
codec: same(a.codec ?? null, b.codec ?? null),
|
|
1625
|
+
values: same(a.values ?? null, b.values ?? null),
|
|
1626
|
+
covers: same(a.covers ?? [], b.covers ?? [])
|
|
1627
|
+
};
|
|
1628
|
+
return Object.values(fields).every(Boolean);
|
|
1266
1629
|
}
|
|
1267
1630
|
/**
|
|
1268
|
-
* This function makes a part of a merge that adds parents or
|
|
1269
|
-
* example is a domain of another language. The part reads the domain from the merge before it
|
|
1270
|
-
* copy with the same test and
|
|
1271
|
-
*
|
|
1631
|
+
* This function makes a part of a merge that adds parents, steps or covers to a domain of an earlier part
|
|
1632
|
+
* (TYPE.PARENTS). An example is a domain of another language. The part reads the domain from the merge before it
|
|
1633
|
+
* (`prev`). It gives a copy with the same test, summary, codec and list of values, and it names `["domains", name]`
|
|
1634
|
+
* in `replaces` (D17).
|
|
1635
|
+
* A domain of the kernel that the earlier parts do not name comes from `STD_DOMAINS`.
|
|
1636
|
+
*
|
|
1637
|
+
* An extension does not change the codec. Thus each member keeps its document (D80).
|
|
1272
1638
|
*
|
|
1273
1639
|
* A new parent adds its test to the domain (TYPE.MEMBER). Thus `fits` stays sound, but a value that fails the test of
|
|
1274
1640
|
* the parent is no longer a member. The language that adds the parent keeps the signatures of the other language
|
|
1275
|
-
* true. A
|
|
1641
|
+
* true. A new parent of a domain in a cover of another domain can make that cover false (D85). A name without a
|
|
1642
|
+
* domain gives an empty part, and `typeIssues` reports each use of that name.
|
|
1276
1643
|
*
|
|
1277
1644
|
* @param name - The name of the domain.
|
|
1278
|
-
* @param more - The parents and the
|
|
1645
|
+
* @param more - The parents, the steps and the covers to add.
|
|
1279
1646
|
* @returns The part, as a function of the merge before it.
|
|
1280
1647
|
*/
|
|
1281
1648
|
function extendDomain(name, more) {
|
|
@@ -1288,14 +1655,21 @@ function extendDomain(name, more) {
|
|
|
1288
1655
|
...found.steps,
|
|
1289
1656
|
...more.steps
|
|
1290
1657
|
};
|
|
1291
|
-
const
|
|
1658
|
+
const covers = [...found.covers ?? []];
|
|
1659
|
+
for (const cover of more.covers ?? []) if (!covers.some((each) => same(each, cover))) covers.push([...cover]);
|
|
1660
|
+
const fields = {
|
|
1292
1661
|
is: found.is,
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1662
|
+
under: under.length === 0 ? void 0 : under,
|
|
1663
|
+
summary: found.summary,
|
|
1664
|
+
steps: Object.keys(steps).length === 0 ? void 0 : steps,
|
|
1665
|
+
codec: found.codec,
|
|
1666
|
+
values: found.values,
|
|
1667
|
+
covers: covers.length === 0 ? void 0 : covers
|
|
1296
1668
|
};
|
|
1669
|
+
const domain = {};
|
|
1670
|
+
for (const [key, value] of Object.entries(fields)) if (value !== void 0) domain[key] = value;
|
|
1297
1671
|
const domains = {};
|
|
1298
|
-
setOwn$
|
|
1672
|
+
setOwn$5(domains, name, domain);
|
|
1299
1673
|
return {
|
|
1300
1674
|
domains,
|
|
1301
1675
|
replaces: [["domains", name]]
|
|
@@ -1345,10 +1719,10 @@ function mergeLeaf(m, table, key, later, path, part, replaces, equal, reason) {
|
|
|
1345
1719
|
if (later === void 0) return;
|
|
1346
1720
|
const earlier = own$2(table, key);
|
|
1347
1721
|
if (earlier === void 0) {
|
|
1348
|
-
setOwn$
|
|
1722
|
+
setOwn$5(table, key, later);
|
|
1349
1723
|
m.give(path, part);
|
|
1350
1724
|
} else if (replaces.has(keyOf(path))) {
|
|
1351
|
-
setOwn$
|
|
1725
|
+
setOwn$5(table, key, later);
|
|
1352
1726
|
m.replace(path, part);
|
|
1353
1727
|
} else if (!equal(earlier, later)) m.clash(path, part, reason);
|
|
1354
1728
|
}
|
|
@@ -1369,9 +1743,9 @@ function mergeEntries(m, into, from, path, part, replaces) {
|
|
|
1369
1743
|
const at = [...path, key];
|
|
1370
1744
|
const earlier = own$2(into, key);
|
|
1371
1745
|
if (earlier === void 0) {
|
|
1372
|
-
setOwn$
|
|
1746
|
+
setOwn$5(into, key, later);
|
|
1373
1747
|
m.give(at, part);
|
|
1374
|
-
} else setOwn$
|
|
1748
|
+
} else setOwn$5(into, key, mergeEntry(m, earlier, later, at, part, replaces));
|
|
1375
1749
|
}
|
|
1376
1750
|
}
|
|
1377
1751
|
/** This function merges two entries at one path. Two namespaces merge key by key, and their fallbacks merge too. */
|
|
@@ -1524,8 +1898,8 @@ function mergeCode(m, draft, part, index, replaces) {
|
|
|
1524
1898
|
const runnerPath = ["runners", name];
|
|
1525
1899
|
if (replaces.has(keyOf(evaluatorPath)) || replaces.has(keyOf(runnerPath))) {
|
|
1526
1900
|
if (evaluator === void 0) delete draft.evaluators[name];
|
|
1527
|
-
else setOwn$
|
|
1528
|
-
if (runner !== void 0) setOwn$
|
|
1901
|
+
else setOwn$5(draft.evaluators, name, evaluator);
|
|
1902
|
+
if (runner !== void 0) setOwn$5(draft.runners ??= {}, name, runner);
|
|
1529
1903
|
else if (draft.runners !== void 0) delete draft.runners[name];
|
|
1530
1904
|
m.replace(evaluatorPath, index);
|
|
1531
1905
|
m.replace(runnerPath, index);
|
|
@@ -1648,7 +2022,7 @@ function removeEntry(entries, path) {
|
|
|
1648
2022
|
}
|
|
1649
2023
|
const out = { ...entries };
|
|
1650
2024
|
if (next === void 0) delete out[key];
|
|
1651
|
-
else setOwn$
|
|
2025
|
+
else setOwn$5(out, key, next);
|
|
1652
2026
|
return out;
|
|
1653
2027
|
}
|
|
1654
2028
|
/**
|
|
@@ -2043,11 +2417,12 @@ function derefStep(nav, thing, key) {
|
|
|
2043
2417
|
const derefs = nav.derefs;
|
|
2044
2418
|
const answer = derefs === void 0 ? void 0 : answerOf(nav.world, ownEntries(envelope), kindFor(nav.world, envelope.type), "deref", (name) => codeIn(derefs, name));
|
|
2045
2419
|
if (answer !== void 0) {
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2420
|
+
const code = derefs[answer.name];
|
|
2421
|
+
const reached = code?.(envelope, key);
|
|
2422
|
+
return reached !== void 0 || code?.has?.(envelope, key) === true ? {
|
|
2048
2423
|
thing: reached,
|
|
2049
2424
|
by: answer.rung
|
|
2050
|
-
};
|
|
2425
|
+
} : void 0;
|
|
2051
2426
|
}
|
|
2052
2427
|
const reached = plainStep(envelope.value, key);
|
|
2053
2428
|
return reached === NOTHING ? void 0 : {
|
|
@@ -2285,6 +2660,25 @@ function resolve(nav, ctx, path, options = {}) {
|
|
|
2285
2660
|
}
|
|
2286
2661
|
return resolveFully$1(nav, ctx, path, options.hops === false ? void 0 : [], void 0);
|
|
2287
2662
|
}
|
|
2663
|
+
/** The functions of the last navigator that gave a walker its input. A walk with the same navigator makes no new functions. */
|
|
2664
|
+
let lastSteps;
|
|
2665
|
+
/**
|
|
2666
|
+
* This function gives the functions `deref` and `has` of the input of a walker. They depend only on the navigator,
|
|
2667
|
+
* thus the function keeps the functions of the last navigator (K170).
|
|
2668
|
+
*/
|
|
2669
|
+
function walkSteps(nav) {
|
|
2670
|
+
if (lastSteps !== void 0 && lastSteps.nav === nav) return lastSteps;
|
|
2671
|
+
const adopt = nav.adopt;
|
|
2672
|
+
lastSteps = {
|
|
2673
|
+
nav,
|
|
2674
|
+
deref: adopt === void 0 ? (thing, key) => derefStep(nav, thing, key)?.thing : (thing, key) => {
|
|
2675
|
+
const reached = derefStep(nav, thing, key);
|
|
2676
|
+
return reached === void 0 ? void 0 : adopt(reached.thing);
|
|
2677
|
+
},
|
|
2678
|
+
has: (thing, key) => derefStep(nav, thing, key) !== void 0
|
|
2679
|
+
};
|
|
2680
|
+
return lastSteps;
|
|
2681
|
+
}
|
|
2288
2682
|
/**
|
|
2289
2683
|
* This function resolves a path with the full algorithm of `resolve`.
|
|
2290
2684
|
*
|
|
@@ -2379,20 +2773,19 @@ function resolveFully$1(nav, ctx, path, hops, known) {
|
|
|
2379
2773
|
const walker = given === void 0 ? walkerOf(nav, step) : given ?? void 0;
|
|
2380
2774
|
if (walker === void 0) return failed("#STEP?", "A step is a key, or a document of a kind with the verb step.");
|
|
2381
2775
|
const envelope = step;
|
|
2382
|
-
const deref = (
|
|
2383
|
-
const reached = derefStep(nav, thing, key);
|
|
2384
|
-
return reached === void 0 ? void 0 : adopt(reached.thing);
|
|
2385
|
-
};
|
|
2776
|
+
const { deref, has } = walkSteps(nav);
|
|
2386
2777
|
const result = walker(hops === void 0 ? {
|
|
2387
2778
|
data: envelope.value,
|
|
2388
2779
|
here,
|
|
2389
2780
|
context: ctx,
|
|
2390
|
-
deref
|
|
2781
|
+
deref,
|
|
2782
|
+
has
|
|
2391
2783
|
} : {
|
|
2392
2784
|
data: envelope.value,
|
|
2393
2785
|
here,
|
|
2394
2786
|
context: ctx,
|
|
2395
2787
|
deref,
|
|
2788
|
+
has,
|
|
2396
2789
|
visit: (key, thing) => hops.push({
|
|
2397
2790
|
step: key,
|
|
2398
2791
|
thing,
|
|
@@ -2479,7 +2872,7 @@ var Bad = class {
|
|
|
2479
2872
|
}
|
|
2480
2873
|
};
|
|
2481
2874
|
/** This function sets an own data property. Only the key `__proto__` needs `defineProperty`. */
|
|
2482
|
-
function setOwn$
|
|
2875
|
+
function setOwn$4(target, key, value) {
|
|
2483
2876
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
2484
2877
|
value,
|
|
2485
2878
|
enumerable: true,
|
|
@@ -2493,7 +2886,7 @@ function hasSlots(template) {
|
|
|
2493
2886
|
return template.t !== "data";
|
|
2494
2887
|
}
|
|
2495
2888
|
/** This function gives the shape of a part with the default type `of` and the value type `expect` of its container. */
|
|
2496
|
-
function termOf(of, expect) {
|
|
2889
|
+
function termOf$1(of, expect) {
|
|
2497
2890
|
return {
|
|
2498
2891
|
shape: "term",
|
|
2499
2892
|
...of === void 0 ? {} : { of },
|
|
@@ -2602,7 +2995,7 @@ function splitParts(shape, value) {
|
|
|
2602
2995
|
const twice = list.findIndex((child, index) => list.slice(0, index).some((before) => docEquals(before, child)));
|
|
2603
2996
|
if (twice !== -1) throw new Bad("The items of the list must be different.", [...path, twice]);
|
|
2604
2997
|
}
|
|
2605
|
-
const each = spec.item ?? termOf(spec.of, spec.expect);
|
|
2998
|
+
const each = spec.item ?? termOf$1(spec.of, spec.expect);
|
|
2606
2999
|
const items = list.map((child, index) => walk(each, child, [...path, index]));
|
|
2607
3000
|
return isData(each) ? {
|
|
2608
3001
|
t: "data",
|
|
@@ -2614,14 +3007,14 @@ function splitParts(shape, value) {
|
|
|
2614
3007
|
}
|
|
2615
3008
|
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
2616
3009
|
if (spec.shape === "map") {
|
|
2617
|
-
const each = spec.item ?? termOf(spec.of, spec.expect);
|
|
3010
|
+
const each = spec.item ?? termOf$1(spec.of, spec.expect);
|
|
2618
3011
|
return {
|
|
2619
3012
|
t: "record",
|
|
2620
3013
|
fields: Object.keys(item).map((key) => [key, walk(each, item[key], [...path, key])])
|
|
2621
3014
|
};
|
|
2622
3015
|
}
|
|
2623
3016
|
for (const name of spec.required ?? []) if (!Object.hasOwn(item, name)) throw new Bad(`The field ${name} is missing.`, path);
|
|
2624
|
-
const named = Array.isArray(spec.fields) ? Object.fromEntries(spec.fields.map((name) => [name, termOf(spec.of, spec.expect)])) : spec.fields;
|
|
3017
|
+
const named = Array.isArray(spec.fields) ? Object.fromEntries(spec.fields.map((name) => [name, termOf$1(spec.of, spec.expect)])) : spec.fields;
|
|
2625
3018
|
const fields = [];
|
|
2626
3019
|
for (const key of Object.keys(item)) {
|
|
2627
3020
|
if (spec.closed === true && !Object.hasOwn(named, key)) throw new Bad(`The field ${key} is not a field of this kind.`, [...path, key]);
|
|
@@ -2651,7 +3044,7 @@ function splitParts(shape, value) {
|
|
|
2651
3044
|
const out = {};
|
|
2652
3045
|
for (const [key, child] of node.fields) {
|
|
2653
3046
|
const kept = strip(child);
|
|
2654
|
-
if (kept !== void 0) setOwn$
|
|
3047
|
+
if (kept !== void 0) setOwn$4(out, key, kept);
|
|
2655
3048
|
}
|
|
2656
3049
|
return out;
|
|
2657
3050
|
};
|
|
@@ -2660,7 +3053,7 @@ function splitParts(shape, value) {
|
|
|
2660
3053
|
if (node.t === "slot") return handles[node.index];
|
|
2661
3054
|
if (node.t === "list") return node.items.map((child) => fillWith(child, handles));
|
|
2662
3055
|
const out = {};
|
|
2663
|
-
for (const [key, child] of node.fields) setOwn$
|
|
3056
|
+
for (const [key, child] of node.fields) setOwn$4(out, key, fillWith(child, handles));
|
|
2664
3057
|
return out;
|
|
2665
3058
|
};
|
|
2666
3059
|
return {
|
|
@@ -2918,10 +3311,19 @@ const GRANT = /* @__PURE__ */ Symbol("coeval.grant");
|
|
|
2918
3311
|
/**
|
|
2919
3312
|
* The key of the epoch of a grant (CAP.NARROW-KEPT). The epoch is the list of capabilities that the host gave the root
|
|
2920
3313
|
* of the evaluation of the grant. A spread of a context keeps its epoch, as it keeps its grant. An
|
|
2921
|
-
* effect and a resolution read a context of another epoch as a context without a grant.
|
|
3314
|
+
* effect and a resolution read a context of another epoch as a context without a grant. The root has no field of
|
|
3315
|
+
* the epoch, because its grant is the list of the host (`epochOf`).
|
|
2922
3316
|
*/
|
|
2923
3317
|
const EPOCH = /* @__PURE__ */ Symbol("coeval.epoch");
|
|
2924
3318
|
/**
|
|
3319
|
+
* This function gives the epoch of the grant of a context (CAP.NARROW-KEPT). A context without the field of the epoch
|
|
3320
|
+
* has the epoch of its grant. Only a root has no field, thus its grant is the list of the host. The root then has
|
|
3321
|
+
* one field fewer, and each evaluation makes it faster (K154).
|
|
3322
|
+
*/
|
|
3323
|
+
function epochOf(ctx) {
|
|
3324
|
+
return ctx[EPOCH] ?? ctx[GRANT];
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
2925
3327
|
* This function gives the capabilities that a context and its grant both permit, the meet `intersectCaps` (CAP.MEET).
|
|
2926
3328
|
* A context whose grant is its own list gives that list after one comparison. A context without a grant gives none.
|
|
2927
3329
|
*/
|
|
@@ -2941,10 +3343,36 @@ function granted(ctx) {
|
|
|
2941
3343
|
return {
|
|
2942
3344
|
...ctx,
|
|
2943
3345
|
caps,
|
|
2944
|
-
[GRANT]: caps
|
|
3346
|
+
[GRANT]: caps,
|
|
3347
|
+
[EPOCH]: epochOf(ctx)
|
|
2945
3348
|
};
|
|
2946
3349
|
}
|
|
2947
3350
|
/**
|
|
3351
|
+
* This function tells if a list of capabilities is equal to the epoch of the evaluation (CAP.EPOCH-EQUAL). An equal list
|
|
3352
|
+
* has the same length, and at each index the same text or the same keys in the same order. The runtime uses it only
|
|
3353
|
+
* when the two lists are not the same object. Thus a host that gives a new copy of its list for each evaluation keeps
|
|
3354
|
+
* the grant of a kept context.
|
|
3355
|
+
*/
|
|
3356
|
+
function sameCaps(caps, epoch) {
|
|
3357
|
+
if (caps === void 0 || caps.length !== epoch.length) return false;
|
|
3358
|
+
for (let index = 0; index < caps.length; index += 1) {
|
|
3359
|
+
const a = caps[index];
|
|
3360
|
+
const b = epoch[index];
|
|
3361
|
+
if (a === b) continue;
|
|
3362
|
+
if (typeof a === "string" || typeof b === "string" || a.length !== b.length) return false;
|
|
3363
|
+
for (let key = 0; key < a.length; key += 1) if (!Object.is(a[key], b[key])) return false;
|
|
3364
|
+
}
|
|
3365
|
+
return true;
|
|
3366
|
+
}
|
|
3367
|
+
/**
|
|
3368
|
+
* This function tells if a context has the epoch of the evaluation: the same list, or an equal list (CAP.NARROW-KEPT,
|
|
3369
|
+
* CAP.EPOCH-EQUAL). The comparison of the entries happens only when the two lists are not the same object.
|
|
3370
|
+
*/
|
|
3371
|
+
function inEpoch(ctx, epoch) {
|
|
3372
|
+
const own = epochOf(ctx);
|
|
3373
|
+
return own === epoch || sameCaps(own, epoch);
|
|
3374
|
+
}
|
|
3375
|
+
/**
|
|
2948
3376
|
* This function gives a context of another epoch the epoch of the evaluation, without capabilities (CAP.NARROW-KEPT).
|
|
2949
3377
|
* Thus the context reads as a context without a grant. The kernel cannot know the document of the context in this
|
|
2950
3378
|
* evaluation.
|
|
@@ -2962,16 +3390,26 @@ function ungranted(ctx, epoch) {
|
|
|
2962
3390
|
* The context has the meet of its list and its grant, and a context without a grant has none. Thus a read of the root
|
|
2963
3391
|
* `caps` and a walker of a step document get the capabilities of the document from a widened context.
|
|
2964
3392
|
* A context that the code narrowed keeps its own list. A context whose list does not change stays the same object.
|
|
2965
|
-
*
|
|
3393
|
+
*
|
|
3394
|
+
* A context of another epoch has no capabilities (CAP.NARROW-KEPT). An equal list of the host is the same epoch
|
|
3395
|
+
* (CAP.EPOCH-EQUAL). The first test answers for a context of the epoch whose grant is its list, for example the root.
|
|
3396
|
+
* The other contexts go to `readableOther`, thus the hot part of the function stays small (K154).
|
|
2966
3397
|
*/
|
|
2967
3398
|
function readable(ctx, epoch) {
|
|
2968
|
-
|
|
3399
|
+
const grant = ctx[GRANT];
|
|
3400
|
+
if (grant === ctx.caps && (ctx[EPOCH] ?? grant) === epoch) return ctx;
|
|
3401
|
+
return readableOther(ctx, epoch);
|
|
3402
|
+
}
|
|
3403
|
+
/** This function gives the result of `readable` for each other context: a context of another epoch or a narrowed context. */
|
|
3404
|
+
function readableOther(ctx, epoch) {
|
|
3405
|
+
if (!inEpoch(ctx, epoch)) return ctx.caps.length === 0 ? ctx : ungranted(ctx, epoch);
|
|
2969
3406
|
const caps = grantedCaps(ctx);
|
|
2970
3407
|
if (caps === ctx.caps || caps.length === 0 && ctx.caps.length === 0) return ctx;
|
|
2971
3408
|
return {
|
|
2972
3409
|
...ctx,
|
|
2973
3410
|
caps,
|
|
2974
|
-
[GRANT]: caps
|
|
3411
|
+
[GRANT]: caps,
|
|
3412
|
+
[EPOCH]: epochOf(ctx)
|
|
2975
3413
|
};
|
|
2976
3414
|
}
|
|
2977
3415
|
/**
|
|
@@ -2981,8 +3419,8 @@ function readable(ctx, epoch) {
|
|
|
2981
3419
|
*/
|
|
2982
3420
|
function handed(parent, next) {
|
|
2983
3421
|
const ctx = narrow(parent, next);
|
|
2984
|
-
const epoch = parent
|
|
2985
|
-
if (ctx[GRANT] === ctx.caps && ctx
|
|
3422
|
+
const epoch = epochOf(parent);
|
|
3423
|
+
if (ctx[GRANT] === ctx.caps && epochOf(ctx) === epoch) return ctx;
|
|
2986
3424
|
return {
|
|
2987
3425
|
...ctx,
|
|
2988
3426
|
[GRANT]: ctx.caps,
|
|
@@ -2990,7 +3428,7 @@ function handed(parent, next) {
|
|
|
2990
3428
|
};
|
|
2991
3429
|
}
|
|
2992
3430
|
/** The text that names a resolution in the error of an exception of the host. */
|
|
2993
|
-
const READ_FAILED = "the resolution of a path";
|
|
3431
|
+
const READ_FAILED$1 = "the resolution of a path";
|
|
2994
3432
|
/**
|
|
2995
3433
|
* This function gives what a resolution reached as a result: the thing, or the error with its code and its reason.
|
|
2996
3434
|
* The record of a read and `verifyReads` share it.
|
|
@@ -3019,7 +3457,7 @@ function readAgain(nav, ctx, path, hops) {
|
|
|
3019
3457
|
hops: found.hops
|
|
3020
3458
|
} : { reached: reachedOf(found) };
|
|
3021
3459
|
} catch (error) {
|
|
3022
|
-
return { reached: thrown(error, READ_FAILED) };
|
|
3460
|
+
return { reached: thrown(error, READ_FAILED$1) };
|
|
3023
3461
|
}
|
|
3024
3462
|
}
|
|
3025
3463
|
/**
|
|
@@ -3124,7 +3562,7 @@ function keptPath(nav, root, path) {
|
|
|
3124
3562
|
} catch (error) {
|
|
3125
3563
|
return {
|
|
3126
3564
|
path: path.slice(0, index + 1),
|
|
3127
|
-
reached: thrown(error, READ_FAILED)
|
|
3565
|
+
reached: thrown(error, READ_FAILED$1)
|
|
3128
3566
|
};
|
|
3129
3567
|
}
|
|
3130
3568
|
if (step === void 0) return end(index);
|
|
@@ -3160,7 +3598,7 @@ function keptAgain(nav, ctx, path) {
|
|
|
3160
3598
|
try {
|
|
3161
3599
|
step = here.known(path[index]);
|
|
3162
3600
|
} catch (error) {
|
|
3163
|
-
return thrown(error, READ_FAILED);
|
|
3601
|
+
return thrown(error, READ_FAILED$1);
|
|
3164
3602
|
}
|
|
3165
3603
|
if (step === void 0) return void 0;
|
|
3166
3604
|
known = true;
|
|
@@ -3255,7 +3693,7 @@ var Session = class {
|
|
|
3255
3693
|
} catch (error) {
|
|
3256
3694
|
reading.list.push({
|
|
3257
3695
|
path,
|
|
3258
|
-
reached: thrown(error, READ_FAILED),
|
|
3696
|
+
reached: thrown(error, READ_FAILED$1),
|
|
3259
3697
|
free
|
|
3260
3698
|
});
|
|
3261
3699
|
throw error;
|
|
@@ -3343,10 +3781,10 @@ var Session = class {
|
|
|
3343
3781
|
* This function checks the capability of an effect, and records the effect with its address (RUN.ADDRESS). The
|
|
3344
3782
|
* context keeps only the capabilities of its grant, thus compiled code cannot widen the context of its own effect
|
|
3345
3783
|
* (CAP.NARROW-CODE). A context of another epoch has no capabilities, thus code cannot keep a context for a later
|
|
3346
|
-
* evaluation with fewer capabilities (CAP.NARROW-KEPT).
|
|
3784
|
+
* evaluation with fewer capabilities (CAP.NARROW-KEPT). An equal list of the host is the same epoch (CAP.EPOCH-EQUAL).
|
|
3347
3785
|
*/
|
|
3348
3786
|
record(given, kind, payload, place) {
|
|
3349
|
-
const ctx = given
|
|
3787
|
+
const ctx = inEpoch(given, this.epoch) ? granted(given) : ungranted(given, this.epoch);
|
|
3350
3788
|
const keys = typeof kind === "string" ? kind.split(".") : kind;
|
|
3351
3789
|
const text = keys.join(".");
|
|
3352
3790
|
if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
|
|
@@ -3429,6 +3867,7 @@ var Session = class {
|
|
|
3429
3867
|
let failed = false;
|
|
3430
3868
|
const active = [];
|
|
3431
3869
|
let next = 0;
|
|
3870
|
+
let before = 0;
|
|
3432
3871
|
const settle = (index, result) => {
|
|
3433
3872
|
results[index] = result;
|
|
3434
3873
|
if (failFast && !result.ok) failed = true;
|
|
@@ -3492,10 +3931,11 @@ var Session = class {
|
|
|
3492
3931
|
};
|
|
3493
3932
|
start();
|
|
3494
3933
|
while (active.length > 0 && !failed) {
|
|
3495
|
-
const chosen = journal?.next?.(address, active.map((item) => item.index));
|
|
3934
|
+
const chosen = journal?.next?.(address, active.map((item) => item.index), before);
|
|
3496
3935
|
const pick = chosen === void 0 ? void 0 : active.find((item) => item.index === chosen);
|
|
3497
3936
|
const settled = yield pick === void 0 ? Promise.race(active.map((item) => item.pending)) : pick.pending;
|
|
3498
3937
|
journal?.settled?.(address, settled.item.index);
|
|
3938
|
+
before += 1;
|
|
3499
3939
|
active.splice(active.indexOf(settled.item), 1);
|
|
3500
3940
|
advance(settled.item.index, settled.item.steps, settled);
|
|
3501
3941
|
start();
|
|
@@ -4184,7 +4624,8 @@ function gated(rules, part) {
|
|
|
4184
4624
|
return {
|
|
4185
4625
|
...ctx,
|
|
4186
4626
|
caps,
|
|
4187
|
-
[GRANT]: caps
|
|
4627
|
+
[GRANT]: caps,
|
|
4628
|
+
[EPOCH]: epochOf(ctx)
|
|
4188
4629
|
};
|
|
4189
4630
|
};
|
|
4190
4631
|
return {
|
|
@@ -4223,6 +4664,30 @@ function runOf(code) {
|
|
|
4223
4664
|
function fillOf(split, doc) {
|
|
4224
4665
|
return (items) => split.parts.length === 0 ? doc.value : split.fill(items);
|
|
4225
4666
|
}
|
|
4667
|
+
/**
|
|
4668
|
+
* The key of the runtime on the input of an evaluator. Only the kernel reads it. The node of a tree evaluates its
|
|
4669
|
+
* equations in the evaluation of this runtime, thus their reads go in its list (READS.EQUATION). A copy of the input
|
|
4670
|
+
* with a spread keeps the key.
|
|
4671
|
+
*/
|
|
4672
|
+
const RUNTIME = /* @__PURE__ */ Symbol("coeval.runtime");
|
|
4673
|
+
/**
|
|
4674
|
+
* This function evaluates a plan inside the evaluation of another runtime, for example an equation of a tree inside
|
|
4675
|
+
* the evaluation of its tree (READS.EQUATION). The plan gets its own runtime, as in `plan.evaluate`. The reads of the
|
|
4676
|
+
* plan go in the record of the reads of `outer`. The context at the root of `outer` tells which reads are free
|
|
4677
|
+
* (READS.FREE). Without such a record, the function gives `plan.evaluate(context)`.
|
|
4678
|
+
*
|
|
4679
|
+
* @param plan - The plan.
|
|
4680
|
+
* @param context - The context of the plan.
|
|
4681
|
+
* @param outer - The runtime of the evaluation around the plan, or `undefined`.
|
|
4682
|
+
* @returns The result.
|
|
4683
|
+
*/
|
|
4684
|
+
function evaluateWithin(plan, context, outer) {
|
|
4685
|
+
const reading = outer instanceof Session ? outer.reading : void 0;
|
|
4686
|
+
if (reading === void 0 || !(plan instanceof CompiledPlan)) return plan.evaluate(context);
|
|
4687
|
+
const machine = plan.machine;
|
|
4688
|
+
const ctx = rootContext(machine, context);
|
|
4689
|
+
return plan.code(ctx, new Session(machine, ctx.caps, false, plan.traced, void 0, void 0, void 0, reading));
|
|
4690
|
+
}
|
|
4226
4691
|
function interpret(fn, input, fill) {
|
|
4227
4692
|
const { kind, typePath, doc, data, place, machine } = input;
|
|
4228
4693
|
const address = input.address === true ? { address: true } : {};
|
|
@@ -4249,7 +4714,8 @@ function interpret(fn, input, fill) {
|
|
|
4249
4714
|
...address,
|
|
4250
4715
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
4251
4716
|
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
|
|
4252
|
-
fill: refill
|
|
4717
|
+
fill: refill,
|
|
4718
|
+
[RUNTIME]: rt
|
|
4253
4719
|
});
|
|
4254
4720
|
};
|
|
4255
4721
|
}
|
|
@@ -4285,18 +4751,27 @@ function interpretRun(fn, input, fill) {
|
|
|
4285
4751
|
/**
|
|
4286
4752
|
* This function gives the context at the root of an evaluation. It is the context of the host, with the world of
|
|
4287
4753
|
* the machine when the context has none. The host gives the capabilities, thus the root has the grant of each one
|
|
4288
|
-
* (CAP.NARROW-CODE). The list of the host is also the epoch of the grant (CAP.NARROW-KEPT).
|
|
4289
|
-
* `verifyReads` share it.
|
|
4754
|
+
* (CAP.NARROW-CODE). The list of the host is also the epoch of the grant (CAP.NARROW-KEPT). The root has no field of
|
|
4755
|
+
* the epoch, thus it has the shape of a context before K120 (K154). An evaluation and `verifyReads` share it.
|
|
4756
|
+
*
|
|
4757
|
+
* The function reads the epoch of the context one time, and one test keeps a context that is already a root (K169).
|
|
4290
4758
|
*
|
|
4291
4759
|
* @param machine - The machine.
|
|
4292
4760
|
* @param ctx - The context of the host.
|
|
4293
4761
|
* @returns The context at the root.
|
|
4294
4762
|
*/
|
|
4295
4763
|
function rootContext(machine, ctx) {
|
|
4296
|
-
|
|
4764
|
+
const epoch = ctx[EPOCH];
|
|
4765
|
+
if (ctx.world !== null && ctx[GRANT] === ctx.caps && (epoch === void 0 || epoch === ctx.caps)) return ctx;
|
|
4766
|
+
const world = ctx.world === null ? worldThing(machine.world) : ctx.world;
|
|
4767
|
+
if (epoch === void 0) return {
|
|
4768
|
+
...ctx,
|
|
4769
|
+
world,
|
|
4770
|
+
[GRANT]: ctx.caps
|
|
4771
|
+
};
|
|
4297
4772
|
return {
|
|
4298
4773
|
...ctx,
|
|
4299
|
-
world
|
|
4774
|
+
world,
|
|
4300
4775
|
[GRANT]: ctx.caps,
|
|
4301
4776
|
[EPOCH]: ctx.caps
|
|
4302
4777
|
};
|
|
@@ -4736,6 +5211,158 @@ const STD_WORLD = {
|
|
|
4736
5211
|
},
|
|
4737
5212
|
defaults: { view: "view.card" }
|
|
4738
5213
|
};
|
|
5214
|
+
/**
|
|
5215
|
+
* The text syntax of the standard world (K162, D64). The syntax kit (D41) reads it, thus a host of `STD_WORLD` does
|
|
5216
|
+
* not write its own profile. The operators `>`, `+` and `*` make `greater`, `add` and `multiply`. A call form makes
|
|
5217
|
+
* each other kind, and `ref` uses the reference syntax of the kit.
|
|
5218
|
+
*
|
|
5219
|
+
* A literal in a place that takes a document gets its data envelope (K159, D65). Such a place is a part of `if`,
|
|
5220
|
+
* `list`, `seq` or `pure`, the value of `write`, or the whole text. The document after a header name is such a place
|
|
5221
|
+
* too. Thus `list(1, "a")` evaluates to `[1, "a"]`. The forms `add`, `multiply` and `greater` write an operator document that its operator
|
|
5222
|
+
* does not write, for example `add(5)` (K163).
|
|
5223
|
+
*
|
|
5224
|
+
* A host with more kinds spreads this profile and adds its forms to `calls.forms`. `STD_SYNTAX` stays the profile of
|
|
5225
|
+
* the formula kit.
|
|
5226
|
+
*/
|
|
5227
|
+
const WORLD_SYNTAX = /* @__PURE__ */ Object.freeze({
|
|
5228
|
+
operators: [
|
|
5229
|
+
{
|
|
5230
|
+
symbol: ">",
|
|
5231
|
+
fixity: "infix",
|
|
5232
|
+
prec: 4,
|
|
5233
|
+
assoc: "none",
|
|
5234
|
+
kind: "greater"
|
|
5235
|
+
},
|
|
5236
|
+
{
|
|
5237
|
+
symbol: "+",
|
|
5238
|
+
fixity: "infix",
|
|
5239
|
+
prec: 5,
|
|
5240
|
+
assoc: "flat",
|
|
5241
|
+
kind: "add"
|
|
5242
|
+
},
|
|
5243
|
+
{
|
|
5244
|
+
symbol: "*",
|
|
5245
|
+
fixity: "infix",
|
|
5246
|
+
prec: 6,
|
|
5247
|
+
assoc: "flat",
|
|
5248
|
+
kind: "multiply"
|
|
5249
|
+
}
|
|
5250
|
+
],
|
|
5251
|
+
calls: {
|
|
5252
|
+
ops: [],
|
|
5253
|
+
forms: [
|
|
5254
|
+
{
|
|
5255
|
+
name: "if",
|
|
5256
|
+
kind: "if",
|
|
5257
|
+
fields: [
|
|
5258
|
+
"test",
|
|
5259
|
+
"then",
|
|
5260
|
+
"else"
|
|
5261
|
+
],
|
|
5262
|
+
min: 2,
|
|
5263
|
+
wrap: true
|
|
5264
|
+
},
|
|
5265
|
+
{
|
|
5266
|
+
name: "list",
|
|
5267
|
+
kind: "list",
|
|
5268
|
+
wrap: true
|
|
5269
|
+
},
|
|
5270
|
+
{
|
|
5271
|
+
name: "seq",
|
|
5272
|
+
kind: "seq",
|
|
5273
|
+
wrap: true
|
|
5274
|
+
},
|
|
5275
|
+
{
|
|
5276
|
+
name: "pure",
|
|
5277
|
+
kind: "pure",
|
|
5278
|
+
min: 1,
|
|
5279
|
+
wrap: true
|
|
5280
|
+
},
|
|
5281
|
+
{
|
|
5282
|
+
name: "log",
|
|
5283
|
+
kind: "log",
|
|
5284
|
+
min: 1
|
|
5285
|
+
},
|
|
5286
|
+
{
|
|
5287
|
+
name: "write",
|
|
5288
|
+
kind: "write",
|
|
5289
|
+
fields: ["key", "value"],
|
|
5290
|
+
wrap: ["value"]
|
|
5291
|
+
},
|
|
5292
|
+
{
|
|
5293
|
+
name: "math.sum",
|
|
5294
|
+
kind: "math.sum"
|
|
5295
|
+
},
|
|
5296
|
+
{
|
|
5297
|
+
name: "math.product",
|
|
5298
|
+
kind: "math.product"
|
|
5299
|
+
},
|
|
5300
|
+
{
|
|
5301
|
+
name: "number",
|
|
5302
|
+
kind: "number",
|
|
5303
|
+
args: "literal"
|
|
5304
|
+
},
|
|
5305
|
+
{
|
|
5306
|
+
name: "text",
|
|
5307
|
+
kind: "text",
|
|
5308
|
+
args: "literal"
|
|
5309
|
+
},
|
|
5310
|
+
{
|
|
5311
|
+
name: "bool",
|
|
5312
|
+
kind: "bool",
|
|
5313
|
+
args: "literal"
|
|
5314
|
+
},
|
|
5315
|
+
{
|
|
5316
|
+
name: "panel",
|
|
5317
|
+
kind: "panel",
|
|
5318
|
+
args: "named",
|
|
5319
|
+
fields: [
|
|
5320
|
+
"width",
|
|
5321
|
+
"height",
|
|
5322
|
+
"children"
|
|
5323
|
+
]
|
|
5324
|
+
},
|
|
5325
|
+
{
|
|
5326
|
+
name: "box",
|
|
5327
|
+
kind: "box",
|
|
5328
|
+
args: "named",
|
|
5329
|
+
fields: [
|
|
5330
|
+
"width",
|
|
5331
|
+
"height",
|
|
5332
|
+
"children"
|
|
5333
|
+
]
|
|
5334
|
+
},
|
|
5335
|
+
{
|
|
5336
|
+
name: "nearest",
|
|
5337
|
+
kind: "nearest",
|
|
5338
|
+
args: "named",
|
|
5339
|
+
fields: [
|
|
5340
|
+
"type",
|
|
5341
|
+
"follow",
|
|
5342
|
+
"self"
|
|
5343
|
+
]
|
|
5344
|
+
},
|
|
5345
|
+
{
|
|
5346
|
+
name: "add",
|
|
5347
|
+
kind: "add"
|
|
5348
|
+
},
|
|
5349
|
+
{
|
|
5350
|
+
name: "multiply",
|
|
5351
|
+
kind: "multiply"
|
|
5352
|
+
},
|
|
5353
|
+
{
|
|
5354
|
+
name: "greater",
|
|
5355
|
+
kind: "greater"
|
|
5356
|
+
}
|
|
5357
|
+
]
|
|
5358
|
+
},
|
|
5359
|
+
literals: { envelopes: {
|
|
5360
|
+
number: "number",
|
|
5361
|
+
text: "text",
|
|
5362
|
+
bool: "bool"
|
|
5363
|
+
} },
|
|
5364
|
+
header: { bind: "=" }
|
|
5365
|
+
});
|
|
4739
5366
|
function isNumber(value) {
|
|
4740
5367
|
return typeof value === "number" && Number.isFinite(value);
|
|
4741
5368
|
}
|
|
@@ -4914,6 +5541,7 @@ function plainOf(value, path) {
|
|
|
4914
5541
|
*/
|
|
4915
5542
|
function treeThing(input, build, outer) {
|
|
4916
5543
|
const { parts, context, kind: type, place, machine } = input;
|
|
5544
|
+
const runtime = input[RUNTIME];
|
|
4917
5545
|
const { tree, of } = contractOf$1(machine, input.typePath);
|
|
4918
5546
|
const above = stateOf(outer);
|
|
4919
5547
|
const up = above !== void 0 && above.build === build ? above : void 0;
|
|
@@ -5015,12 +5643,12 @@ function treeThing(input, build, outer) {
|
|
|
5015
5643
|
read,
|
|
5016
5644
|
equation(term, names) {
|
|
5017
5645
|
if (machine === void 0) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
|
|
5018
|
-
return equationPlan(machine, of, term)
|
|
5646
|
+
return evaluateWithin(equationPlan(machine, of, term), {
|
|
5019
5647
|
...context,
|
|
5020
5648
|
names,
|
|
5021
5649
|
caps: NO_CAPS$1,
|
|
5022
5650
|
self: node
|
|
5023
|
-
});
|
|
5651
|
+
}, runtime);
|
|
5024
5652
|
},
|
|
5025
5653
|
childNames(child) {
|
|
5026
5654
|
const children = read("children");
|
|
@@ -6310,7 +6938,7 @@ const DOWN_NAMES = /* @__PURE__ */ Object.freeze([
|
|
|
6310
6938
|
const FOLD_NAMES = /* @__PURE__ */ Object.freeze(["acc", "item"]);
|
|
6311
6939
|
/**
|
|
6312
6940
|
* This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
|
|
6313
|
-
* `equations`, a `unit` and a `default` see no names.
|
|
6941
|
+
* `equations`, a `unit` and a `default` see no names. `check` and `factsOf` read the equations with this one function.
|
|
6314
6942
|
*/
|
|
6315
6943
|
function equationsOf(kind) {
|
|
6316
6944
|
const tree = kind.tree;
|
|
@@ -6632,9 +7260,11 @@ function partLazy(above, parent, slot) {
|
|
|
6632
7260
|
*
|
|
6633
7261
|
* - `reads`: each free reference, with the root and the keys of its path. A read under a lazy part has the mark
|
|
6634
7262
|
* `lazy`, for example a read in a branch of `if`. A read after a step document has `exact: false`. A kind that
|
|
6635
|
-
* declares `reads` adds a read of each of its roots at each document of the kind.
|
|
7263
|
+
* declares `reads` adds a read of each of its roots at each document of the kind. The first document of a tree
|
|
7264
|
+
* kind also gets the lazy reads of the equations of its kind (FACTS.EQUATIONS). A read from `self` or from a name
|
|
7265
|
+
* of a rung is not one of them, because it is not free (READS.EQUATION).
|
|
6636
7266
|
* - `side` and `effects`: from the contract of each kind.
|
|
6637
|
-
* - `pure`: each kind declares `pure`, and the document has no effect and is not volatile.
|
|
7267
|
+
* - `pure`: each kind declares `pure`, also in an equation, and the document has no effect and is not volatile.
|
|
6638
7268
|
* - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
|
|
6639
7269
|
*
|
|
6640
7270
|
* The hook `facts` of the machine answers for a name that a fallback kind covers, for example an operation. Its
|
|
@@ -6658,7 +7288,6 @@ function partLazy(above, parent, slot) {
|
|
|
6658
7288
|
function factsOf(machine, doc, options = {}) {
|
|
6659
7289
|
const world = machine.world;
|
|
6660
7290
|
const hook = machine.facts;
|
|
6661
|
-
const verb = options.verb ?? "eval";
|
|
6662
7291
|
const reads = [];
|
|
6663
7292
|
const effects = [];
|
|
6664
7293
|
const above = /* @__PURE__ */ new Map();
|
|
@@ -6666,115 +7295,141 @@ function factsOf(machine, doc, options = {}) {
|
|
|
6666
7295
|
let pure = true;
|
|
6667
7296
|
let volatile = false;
|
|
6668
7297
|
let size = 0;
|
|
7298
|
+
let seen;
|
|
6669
7299
|
const note = (isPure, isVolatile) => {
|
|
6670
7300
|
if (isPure !== true) pure = false;
|
|
6671
7301
|
if (isVolatile === true) volatile = true;
|
|
6672
7302
|
};
|
|
6673
|
-
|
|
6674
|
-
size += 1;
|
|
6675
|
-
const up = v.parent === void 0 ? void 0 : above.get(v.parent);
|
|
6676
|
-
const lazy = up !== void 0 && v.parent !== void 0 && (up.lazy || partLazy(up, v.parent, v.slot));
|
|
6677
|
-
const kind = v.kind;
|
|
6678
|
-
if (kind === void 0) {
|
|
6679
|
-
pure = false;
|
|
6680
|
-
above.set(v, {
|
|
6681
|
-
lazy,
|
|
6682
|
-
rules: void 0
|
|
6683
|
-
});
|
|
6684
|
-
return;
|
|
6685
|
-
}
|
|
6686
|
-
const code = codeOf(world, v.declared ? void 0 : v.doc.own, kind, verb, "eval");
|
|
6687
|
-
const answer = code.name === void 0 ? void 0 : hook?.(v.typePath ?? [], code.name);
|
|
6688
|
-
let roots;
|
|
6689
|
-
if (code.described) {
|
|
6690
|
-
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
6691
|
-
above.set(v, {
|
|
6692
|
-
lazy,
|
|
6693
|
-
rules: answer?.lazy ?? kind.lazy
|
|
6694
|
-
});
|
|
6695
|
-
roots = answer?.reads ?? kind.reads;
|
|
6696
|
-
} else {
|
|
6697
|
-
note(answer?.pure, answer?.volatile);
|
|
6698
|
-
above.set(v, {
|
|
6699
|
-
lazy,
|
|
6700
|
-
rules: answer?.lazy ?? EACH$1
|
|
6701
|
-
});
|
|
6702
|
-
roots = answer?.reads;
|
|
6703
|
-
}
|
|
6704
|
-
for (const root of roots ?? NONE) {
|
|
6705
|
-
const read = {
|
|
6706
|
-
root,
|
|
6707
|
-
keys: NONE,
|
|
6708
|
-
exact: true,
|
|
6709
|
-
lazy,
|
|
6710
|
-
address: false,
|
|
6711
|
-
path: [root],
|
|
6712
|
-
place: tight(v.place),
|
|
6713
|
-
at: tight(v.at)
|
|
6714
|
-
};
|
|
6715
|
-
reads.push(read);
|
|
6716
|
-
if (options.volatile?.(read) === true) volatile = true;
|
|
6717
|
-
}
|
|
6718
|
-
if (kind.side === "run") side = "run";
|
|
6719
|
-
for (const keys of declaredEffects(v)) {
|
|
6720
|
-
const text = keys.join(".");
|
|
6721
|
-
if (!effects.includes(text)) effects.push(text);
|
|
6722
|
-
}
|
|
6723
|
-
if (!isRef(v.doc)) return void 0;
|
|
6724
|
-
const path = v.doc.value;
|
|
6725
|
-
const root = path[0];
|
|
6726
|
-
if (typeof root !== "string" || !ROOTS.includes(root)) return void 0;
|
|
6727
|
-
let exact = true;
|
|
6728
|
-
const keys = [];
|
|
6729
|
-
for (let index = 1; index < path.length; index += 1) {
|
|
6730
|
-
const step = path[index];
|
|
6731
|
-
if (exact && isKey(step)) {
|
|
6732
|
-
keys.push(step);
|
|
6733
|
-
continue;
|
|
6734
|
-
}
|
|
6735
|
-
exact = false;
|
|
6736
|
-
const found = isEnvelope(step) ? lookupKind(world, step.type) : void 0;
|
|
6737
|
-
if (found?.ok !== true) {
|
|
6738
|
-
pure = false;
|
|
6739
|
-
continue;
|
|
6740
|
-
}
|
|
6741
|
-
const walker = codeOf(world, step.own, found.kind, "step", "step");
|
|
6742
|
-
const stepAnswer = walker.name === void 0 ? void 0 : hook?.(found.path, walker.name);
|
|
6743
|
-
if (walker.described) note(stepAnswer?.pure ?? found.kind.pure, stepAnswer?.volatile ?? found.kind.volatile);
|
|
6744
|
-
else note(stepAnswer?.pure, stepAnswer?.volatile);
|
|
6745
|
-
}
|
|
6746
|
-
const first = path[1];
|
|
6747
|
-
if (root === "names" && isKey(first) && v.names.has(String(first))) return void 0;
|
|
6748
|
-
const read = {
|
|
6749
|
-
root,
|
|
6750
|
-
keys: tight(keys),
|
|
6751
|
-
exact,
|
|
6752
|
-
lazy,
|
|
6753
|
-
address: v.address === true,
|
|
6754
|
-
path,
|
|
6755
|
-
place: tight(v.place),
|
|
6756
|
-
at: tight(v.at)
|
|
6757
|
-
};
|
|
7303
|
+
const add = (read) => {
|
|
6758
7304
|
reads.push(read);
|
|
6759
7305
|
if (options.volatile?.(read) === true) volatile = true;
|
|
6760
|
-
}, options.declaredType === void 0 ? {} : { declaredType: options.declaredType });
|
|
6761
|
-
return {
|
|
6762
|
-
reads: tight(reads),
|
|
6763
|
-
side,
|
|
6764
|
-
effects: tight(effects),
|
|
6765
|
-
pure: pure && !volatile && effects.length === 0 && side === "derive",
|
|
6766
|
-
volatile,
|
|
6767
|
-
size
|
|
6768
7306
|
};
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
const
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
7307
|
+
/**
|
|
7308
|
+
* This function walks a document. Without `node`, it walks the document of the facts. With `node`, it walks an
|
|
7309
|
+
* equation of a tree kind, and each read gets the place of that node: the first document of the kind.
|
|
7310
|
+
*/
|
|
7311
|
+
const scan = (term, walkOptions, verb, node) => {
|
|
7312
|
+
walk(world, term, (v) => {
|
|
7313
|
+
if (node === void 0) size += 1;
|
|
7314
|
+
const up = v.parent === void 0 ? void 0 : above.get(v.parent);
|
|
7315
|
+
const lazy = node !== void 0 || up !== void 0 && v.parent !== void 0 && (up.lazy || partLazy(up, v.parent, v.slot));
|
|
7316
|
+
const place = node ?? v;
|
|
7317
|
+
const kind = v.kind;
|
|
7318
|
+
if (kind === void 0) {
|
|
7319
|
+
pure = false;
|
|
7320
|
+
above.set(v, {
|
|
7321
|
+
lazy,
|
|
7322
|
+
rules: void 0
|
|
7323
|
+
});
|
|
7324
|
+
return;
|
|
7325
|
+
}
|
|
7326
|
+
const code = codeOf(world, v.declared ? void 0 : v.doc.own, kind, verb, "eval");
|
|
7327
|
+
const answer = code.name === void 0 ? void 0 : hook?.(v.typePath ?? [], code.name);
|
|
7328
|
+
let roots;
|
|
7329
|
+
if (code.described) {
|
|
7330
|
+
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
7331
|
+
above.set(v, {
|
|
7332
|
+
lazy,
|
|
7333
|
+
rules: answer?.lazy ?? kind.lazy
|
|
7334
|
+
});
|
|
7335
|
+
roots = answer?.reads ?? kind.reads;
|
|
7336
|
+
} else {
|
|
7337
|
+
note(answer?.pure, answer?.volatile);
|
|
7338
|
+
above.set(v, {
|
|
7339
|
+
lazy,
|
|
7340
|
+
rules: answer?.lazy ?? EACH$1
|
|
7341
|
+
});
|
|
7342
|
+
roots = answer?.reads;
|
|
7343
|
+
}
|
|
7344
|
+
for (const root of roots ?? NONE) {
|
|
7345
|
+
if (node !== void 0 && root === "self") continue;
|
|
7346
|
+
add({
|
|
7347
|
+
root,
|
|
7348
|
+
keys: NONE,
|
|
7349
|
+
exact: true,
|
|
7350
|
+
lazy,
|
|
7351
|
+
address: false,
|
|
7352
|
+
path: [root],
|
|
7353
|
+
place: tight(place.place),
|
|
7354
|
+
at: tight(place.at)
|
|
7355
|
+
});
|
|
7356
|
+
}
|
|
7357
|
+
if (kind.side === "run") {
|
|
7358
|
+
if (node === void 0) side = "run";
|
|
7359
|
+
else pure = false;
|
|
7360
|
+
}
|
|
7361
|
+
for (const keys of declaredEffects(v)) {
|
|
7362
|
+
if (node !== void 0) {
|
|
7363
|
+
pure = false;
|
|
7364
|
+
continue;
|
|
7365
|
+
}
|
|
7366
|
+
const text = keys.join(".");
|
|
7367
|
+
if (!effects.includes(text)) effects.push(text);
|
|
7368
|
+
}
|
|
7369
|
+
if (kind.tree !== void 0 && !(seen ??= /* @__PURE__ */ new Set()).has(kind)) {
|
|
7370
|
+
seen.add(kind);
|
|
7371
|
+
const of = equationType(kind);
|
|
7372
|
+
for (const { term: equation, names } of equationsOf(kind)) scan(equation, of === void 0 ? { names } : {
|
|
7373
|
+
names,
|
|
7374
|
+
declaredType: of
|
|
7375
|
+
}, "eval", place);
|
|
7376
|
+
}
|
|
7377
|
+
if (!isRef(v.doc)) return void 0;
|
|
7378
|
+
const path = v.doc.value;
|
|
7379
|
+
const root = path[0];
|
|
7380
|
+
if (typeof root !== "string" || !ROOTS.includes(root)) return void 0;
|
|
7381
|
+
if (node !== void 0 && root === "self") return void 0;
|
|
7382
|
+
let exact = true;
|
|
7383
|
+
const keys = [];
|
|
7384
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
7385
|
+
const step = path[index];
|
|
7386
|
+
if (exact && isKey(step)) {
|
|
7387
|
+
keys.push(step);
|
|
7388
|
+
continue;
|
|
7389
|
+
}
|
|
7390
|
+
exact = false;
|
|
7391
|
+
const found = isEnvelope(step) ? lookupKind(world, step.type) : void 0;
|
|
7392
|
+
if (found?.ok !== true) {
|
|
7393
|
+
pure = false;
|
|
7394
|
+
continue;
|
|
7395
|
+
}
|
|
7396
|
+
const walker = codeOf(world, step.own, found.kind, "step", "step");
|
|
7397
|
+
const stepAnswer = walker.name === void 0 ? void 0 : hook?.(found.path, walker.name);
|
|
7398
|
+
if (walker.described) note(stepAnswer?.pure ?? found.kind.pure, stepAnswer?.volatile ?? found.kind.volatile);
|
|
7399
|
+
else note(stepAnswer?.pure, stepAnswer?.volatile);
|
|
7400
|
+
}
|
|
7401
|
+
const first = path[1];
|
|
7402
|
+
if (root === "names" && isKey(first) && v.names.has(String(first))) return void 0;
|
|
7403
|
+
add({
|
|
7404
|
+
root,
|
|
7405
|
+
keys: tight(keys),
|
|
7406
|
+
exact,
|
|
7407
|
+
lazy,
|
|
7408
|
+
address: v.address === true,
|
|
7409
|
+
path,
|
|
7410
|
+
place: tight(place.place),
|
|
7411
|
+
at: tight(place.at)
|
|
7412
|
+
});
|
|
7413
|
+
}, walkOptions);
|
|
7414
|
+
};
|
|
7415
|
+
scan(doc, options.declaredType === void 0 ? {} : { declaredType: options.declaredType }, options.verb ?? "eval", void 0);
|
|
7416
|
+
return {
|
|
7417
|
+
reads: tight(reads),
|
|
7418
|
+
side,
|
|
7419
|
+
effects: tight(effects),
|
|
7420
|
+
pure: pure && !volatile && effects.length === 0 && side === "derive",
|
|
7421
|
+
volatile,
|
|
7422
|
+
size
|
|
7423
|
+
};
|
|
7424
|
+
}
|
|
7425
|
+
//#endregion
|
|
7426
|
+
//#region src/effects.ts
|
|
7427
|
+
/** Each capability: one capability with no keys. It permits each effect, because each kind starts with no keys (CAP.KEYS). */
|
|
7428
|
+
const EACH = /* @__PURE__ */ Object.freeze([[]]);
|
|
7429
|
+
/**
|
|
7430
|
+
* This function finds each declared effect that can reach the host, in the order of the document. It is the one
|
|
7431
|
+
* inference of `effectsOf` and `checkLatent`. `check` and `factsOf` read the same declarations (`declaredEffects`).
|
|
7432
|
+
*/
|
|
6778
7433
|
function reaching(machine, doc, options) {
|
|
6779
7434
|
const out = [];
|
|
6780
7435
|
const rule = options.caps === void 0 ? permits : permitsOf(machine);
|
|
@@ -6896,7 +7551,7 @@ function domainTypeOf(data, domains) {
|
|
|
6896
7551
|
return domain !== void 0 && codecOf(domain) !== void 0 ? data["domain"] : "unknown";
|
|
6897
7552
|
}
|
|
6898
7553
|
/** This function sets an own data property. Only the key `__proto__` needs `defineProperty`. */
|
|
6899
|
-
function setOwn$
|
|
7554
|
+
function setOwn$3(target, key, value) {
|
|
6900
7555
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
6901
7556
|
value,
|
|
6902
7557
|
enumerable: true,
|
|
@@ -6971,7 +7626,7 @@ function encodeTerm(value, encoder) {
|
|
|
6971
7626
|
else {
|
|
6972
7627
|
const fields = {};
|
|
6973
7628
|
const record = value;
|
|
6974
|
-
for (const key of Object.keys(record)) setOwn$
|
|
7629
|
+
for (const key of Object.keys(record)) setOwn$3(fields, key, encodeTerm(record[key], encoder));
|
|
6975
7630
|
out = {
|
|
6976
7631
|
type: "record",
|
|
6977
7632
|
value: fields
|
|
@@ -7067,7 +7722,7 @@ function strictTruthy(value) {
|
|
|
7067
7722
|
* This function sets an own data property. Only the key `__proto__` needs `defineProperty`: an assignment to it
|
|
7068
7723
|
* changes the prototype. An assignment is faster for each other key of a new plain object.
|
|
7069
7724
|
*/
|
|
7070
|
-
function setOwn$
|
|
7725
|
+
function setOwn$2(target, key, value) {
|
|
7071
7726
|
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
7072
7727
|
value,
|
|
7073
7728
|
enumerable: true,
|
|
@@ -7281,11 +7936,28 @@ function paramsOf(value) {
|
|
|
7281
7936
|
}
|
|
7282
7937
|
/**
|
|
7283
7938
|
* This function makes the type rules of the formula kit for one profile (TYPE.SYNTH, D25). Each evaluator of the kit
|
|
7284
|
-
* holds its rule. The rules read the rules of the profile that change a type: `truthy`, `
|
|
7285
|
-
* and `sigs`.
|
|
7939
|
+
* holds its rule. The rules read the rules of the profile that change a type: `truthy`, `truthType`, `logic`,
|
|
7940
|
+
* `otherwise`, `omit` and `sigs`.
|
|
7286
7941
|
*/
|
|
7287
7942
|
function formulaTypes(profile) {
|
|
7288
|
-
const
|
|
7943
|
+
const declared = profile.truthy !== void 0 && profile.truthType !== void 0;
|
|
7944
|
+
const truth = profile.truthy === void 0 ? "bool" : profile.truthType;
|
|
7945
|
+
/**
|
|
7946
|
+
* The need of a test or a term. The check examines a declared type of a truth as a contract, one time. A declared
|
|
7947
|
+
* type with a problem needs nothing, thus one problem gives one issue.
|
|
7948
|
+
*/
|
|
7949
|
+
const truthPart = (input, what) => {
|
|
7950
|
+
if (truth === void 0) return {};
|
|
7951
|
+
if (!declared) return {
|
|
7952
|
+
expect: truth,
|
|
7953
|
+
what
|
|
7954
|
+
};
|
|
7955
|
+
const type = input.contract(truth, "truth type of the profile");
|
|
7956
|
+
return typeIssues(type, input.machine.domains).length > 0 ? {} : {
|
|
7957
|
+
expect: type,
|
|
7958
|
+
what
|
|
7959
|
+
};
|
|
7960
|
+
};
|
|
7289
7961
|
const logicValue = profile.logic === "value";
|
|
7290
7962
|
const otherwise = Object.hasOwn(profile, "otherwise") ? valueType(profile.otherwise) : "null";
|
|
7291
7963
|
const lookup = (name) => {
|
|
@@ -7321,10 +7993,7 @@ function formulaTypes(profile) {
|
|
|
7321
7993
|
};
|
|
7322
7994
|
};
|
|
7323
7995
|
const logic = (word) => ({
|
|
7324
|
-
part: (
|
|
7325
|
-
expect: truth,
|
|
7326
|
-
what: `term ${Number(path[0]) + 1} of ${word}`
|
|
7327
|
-
},
|
|
7996
|
+
part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
|
|
7328
7997
|
synth: (input) => {
|
|
7329
7998
|
if (!logicValue) return "bool";
|
|
7330
7999
|
if (input.paths.length === 0) return { one: [word === "and"] };
|
|
@@ -7373,7 +8042,7 @@ function formulaTypes(profile) {
|
|
|
7373
8042
|
},
|
|
7374
8043
|
synth: (input) => {
|
|
7375
8044
|
const fields = {};
|
|
7376
|
-
for (const path of input.paths) setOwn$
|
|
8045
|
+
for (const path of input.paths) setOwn$2(fields, String(path[0]), input.typeOf(path));
|
|
7377
8046
|
const keys = Object.keys(fields);
|
|
7378
8047
|
return {
|
|
7379
8048
|
record: fields,
|
|
@@ -7396,7 +8065,7 @@ function formulaTypes(profile) {
|
|
|
7396
8065
|
part: (input, path) => {
|
|
7397
8066
|
if (path[0] !== "body") return {};
|
|
7398
8067
|
const names = {};
|
|
7399
|
-
for (const each of input.paths) if (each[0] === "bind" && each.length === 2) setOwn$
|
|
8068
|
+
for (const each of input.paths) if (each[0] === "bind" && each.length === 2) setOwn$2(names, String(each[1]), input.typeOf(each));
|
|
7400
8069
|
return {
|
|
7401
8070
|
names,
|
|
7402
8071
|
...isUnknown(input.expected, input.machine.domains) ? {} : {
|
|
@@ -7412,7 +8081,7 @@ function formulaTypes(profile) {
|
|
|
7412
8081
|
if (path[0] !== "body") return {};
|
|
7413
8082
|
const want = fnOf(input.expected, input.machine.domains);
|
|
7414
8083
|
const names = {};
|
|
7415
|
-
paramsOf(input.doc.value).forEach((name, index) => setOwn$
|
|
8084
|
+
paramsOf(input.doc.value).forEach((name, index) => setOwn$2(names, name, want?.params[index] ?? "unknown"));
|
|
7416
8085
|
return {
|
|
7417
8086
|
names,
|
|
7418
8087
|
...want === void 0 || isUnknown(want.to, input.machine.domains) ? {} : {
|
|
@@ -7468,10 +8137,7 @@ function formulaTypes(profile) {
|
|
|
7468
8137
|
} },
|
|
7469
8138
|
"formula.if": {
|
|
7470
8139
|
part: (input, path) => {
|
|
7471
|
-
if (path[0] === "test") return
|
|
7472
|
-
expect: truth,
|
|
7473
|
-
what: "test of if"
|
|
7474
|
-
};
|
|
8140
|
+
if (path[0] === "test") return truthPart(input, "test of if");
|
|
7475
8141
|
return isUnknown(input.expected, input.machine.domains) ? {} : {
|
|
7476
8142
|
expect: input.expected,
|
|
7477
8143
|
what: `${String(path[0])} branch of if`
|
|
@@ -7545,18 +8211,44 @@ function formulaTypes(profile) {
|
|
|
7545
8211
|
function formulaEvaluators(profile = {}) {
|
|
7546
8212
|
return { ...kitOf(profile).evaluators };
|
|
7547
8213
|
}
|
|
8214
|
+
/** The evaluators of the kit that read the truth of a value. */
|
|
8215
|
+
const TRUTH_READERS = [
|
|
8216
|
+
"formula.if",
|
|
8217
|
+
"formula.and",
|
|
8218
|
+
"formula.or"
|
|
8219
|
+
];
|
|
8220
|
+
/** The declared truth of each evaluator of the kit that reads a truth, for `conforms` (D86). */
|
|
8221
|
+
const truthByCode = /* @__PURE__ */ new WeakMap();
|
|
8222
|
+
/**
|
|
8223
|
+
* This function gives the declared truth of the profile of an evaluator of the kit (D86). Only `formula.if`,
|
|
8224
|
+
* `formula.and` and `formula.or` of a profile with an own `truthy` and a `truthType` have one. The kernel does not
|
|
8225
|
+
* export the function.
|
|
8226
|
+
*
|
|
8227
|
+
* @param code - The evaluator or the runner.
|
|
8228
|
+
* @returns The declared truth, or `undefined`.
|
|
8229
|
+
*/
|
|
8230
|
+
function declaredTruthOf(code) {
|
|
8231
|
+
return (typeof code === "object" || typeof code === "function") && code !== null ? truthByCode.get(code) : void 0;
|
|
8232
|
+
}
|
|
7548
8233
|
/** This function makes new evaluators of the formula kit for the fields of one profile. */
|
|
7549
8234
|
function makeEvaluators(profile) {
|
|
7550
8235
|
const evaluators = formulaCode(profile);
|
|
7551
8236
|
const types = formulaTypes(profile);
|
|
8237
|
+
const { truthy, truthType } = profile;
|
|
8238
|
+
const truth = truthy === void 0 || truthType === void 0 ? void 0 : {
|
|
8239
|
+
truthy,
|
|
8240
|
+
truthType
|
|
8241
|
+
};
|
|
7552
8242
|
const out = {};
|
|
7553
8243
|
for (const name of Object.keys(evaluators)) {
|
|
7554
8244
|
const evaluator = evaluators[name];
|
|
7555
8245
|
const type = Object.hasOwn(types, name) ? types[name] : void 0;
|
|
7556
|
-
|
|
8246
|
+
const made = Object.freeze(type === void 0 ? evaluator : {
|
|
7557
8247
|
compile: evaluator.compile,
|
|
7558
8248
|
type
|
|
7559
8249
|
});
|
|
8250
|
+
if (truth !== void 0 && TRUTH_READERS.includes(name)) truthByCode.set(made, truth);
|
|
8251
|
+
out[name] = made;
|
|
7560
8252
|
}
|
|
7561
8253
|
return Object.freeze(out);
|
|
7562
8254
|
}
|
|
@@ -7566,6 +8258,7 @@ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
|
|
|
7566
8258
|
"lazy",
|
|
7567
8259
|
"ext",
|
|
7568
8260
|
"truthy",
|
|
8261
|
+
"truthType",
|
|
7569
8262
|
"otherwise",
|
|
7570
8263
|
"logic",
|
|
7571
8264
|
"omit",
|
|
@@ -7750,7 +8443,7 @@ function formulaCode(profile) {
|
|
|
7750
8443
|
if (!Array.isArray(values)) return values;
|
|
7751
8444
|
const out = {};
|
|
7752
8445
|
keys.forEach((key, index) => {
|
|
7753
|
-
if (values[index] !== SKIPPED) setOwn$
|
|
8446
|
+
if (values[index] !== SKIPPED) setOwn$2(out, key, values[index]);
|
|
7754
8447
|
});
|
|
7755
8448
|
return ok(out);
|
|
7756
8449
|
};
|
|
@@ -7763,7 +8456,7 @@ function formulaCode(profile) {
|
|
|
7763
8456
|
if (omit !== void 0 && omit(result.error)) continue;
|
|
7764
8457
|
return result;
|
|
7765
8458
|
}
|
|
7766
|
-
setOwn$
|
|
8459
|
+
setOwn$2(out, keys[index], result.value);
|
|
7767
8460
|
}
|
|
7768
8461
|
return ok(out);
|
|
7769
8462
|
};
|
|
@@ -7780,7 +8473,7 @@ function formulaCode(profile) {
|
|
|
7780
8473
|
const names = { ...ctx.names };
|
|
7781
8474
|
for (const [name, part] of bindings) {
|
|
7782
8475
|
const result = part.code(ctx, rt);
|
|
7783
|
-
setOwn$
|
|
8476
|
+
setOwn$2(names, name, result.ok ? result.value : new Held(result.error));
|
|
7784
8477
|
}
|
|
7785
8478
|
return body.code({
|
|
7786
8479
|
...ctx,
|
|
@@ -7811,7 +8504,7 @@ function formulaCode(profile) {
|
|
|
7811
8504
|
rt.keepsContext?.(ctx, reads);
|
|
7812
8505
|
const closure = (...args) => {
|
|
7813
8506
|
const scope = { ...ctx.names };
|
|
7814
|
-
names.forEach((name, index) => setOwn$
|
|
8507
|
+
names.forEach((name, index) => setOwn$2(scope, name, args[index]));
|
|
7815
8508
|
const result = body.code({
|
|
7816
8509
|
...ctx,
|
|
7817
8510
|
names: scope
|
|
@@ -8084,7 +8777,8 @@ function formulaMachine(profile = {}, more = {}) {
|
|
|
8084
8777
|
/**
|
|
8085
8778
|
* The standard syntax of the formula kit. It gives the site and the playground a text form. The operators are
|
|
8086
8779
|
* `or`, `and`, `not`, the comparisons, `+ - * / %`, a minus sign and `^`, from the loosest to the tightest. A call
|
|
8087
|
-
* `name(args)` applies the operation `ops.name`. `if(test, then, else)` and `list(items)` make the constructs.
|
|
8780
|
+
* `name(args)` applies the operation `ops.name`. `if(test, then, else)` and `list(items)` make the constructs. The
|
|
8781
|
+
* text `total = 2 + 3` gives the document the header name `total` (D62).
|
|
8088
8782
|
*/
|
|
8089
8783
|
const STD_SYNTAX = /* @__PURE__ */ Object.freeze({
|
|
8090
8784
|
operators: [
|
|
@@ -8214,7 +8908,8 @@ const STD_SYNTAX = /* @__PURE__ */ Object.freeze({
|
|
|
8214
8908
|
}, {
|
|
8215
8909
|
name: "list",
|
|
8216
8910
|
kind: "list"
|
|
8217
|
-
}] }
|
|
8911
|
+
}] },
|
|
8912
|
+
header: { bind: "=" }
|
|
8218
8913
|
});
|
|
8219
8914
|
const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
8220
8915
|
const NAME_AT = /[A-Za-z_][A-Za-z0-9_]*/y;
|
|
@@ -8232,6 +8927,12 @@ const DEFAULT_ROOTS = [
|
|
|
8232
8927
|
"event",
|
|
8233
8928
|
"caps"
|
|
8234
8929
|
];
|
|
8930
|
+
const FORM_ARGS = [
|
|
8931
|
+
"list",
|
|
8932
|
+
"fields",
|
|
8933
|
+
"named",
|
|
8934
|
+
"literal"
|
|
8935
|
+
];
|
|
8235
8936
|
const compiledProfiles = /* @__PURE__ */ new WeakMap();
|
|
8236
8937
|
function typeOfKind(kind) {
|
|
8237
8938
|
const keys = kind.split(".");
|
|
@@ -8287,6 +8988,37 @@ function compileOps(profile, separator) {
|
|
|
8287
8988
|
opWords
|
|
8288
8989
|
};
|
|
8289
8990
|
}
|
|
8991
|
+
/** This function examines the bind symbol of a header. A text with the symbol after a name was not valid before. */
|
|
8992
|
+
function compileBind(bind, separator, binary) {
|
|
8993
|
+
if (bind === void 0) return void 0;
|
|
8994
|
+
if (!SYMBOL.test(bind) || bind.includes(separator)) throw new TypeError(`The bind symbol "${bind}" is not a group of symbols.`);
|
|
8995
|
+
for (const symbol of binary.keys()) if (bind.startsWith(symbol)) throw new TypeError(`The bind symbol "${bind}" starts with the operator "${symbol}".`);
|
|
8996
|
+
return bind;
|
|
8997
|
+
}
|
|
8998
|
+
/** This function examines the field `wrap` of a call form (K159). A name that is not a field of the form throws. */
|
|
8999
|
+
function compileWrap(given, args) {
|
|
9000
|
+
const wrap = given.wrap;
|
|
9001
|
+
if (wrap === void 0 || wrap === false) return false;
|
|
9002
|
+
if (args === "literal") throw new TypeError(`The call form "${given.name}" with the arguments literal takes no wrap.`);
|
|
9003
|
+
if (wrap === true) return true;
|
|
9004
|
+
if (args === "list") throw new TypeError(`The call form "${given.name}" with the arguments list takes only the wrap true.`);
|
|
9005
|
+
for (const name of wrap) if (given.fields !== void 0 && !given.fields.includes(name)) throw new TypeError(`The call form "${given.name}" has no field ${name} for its wrap.`);
|
|
9006
|
+
return new Set(wrap);
|
|
9007
|
+
}
|
|
9008
|
+
/** This function gives the types of the data envelopes of a profile (K159), or `undefined` without them. */
|
|
9009
|
+
function compileEnvelopes(given) {
|
|
9010
|
+
if (given === void 0) return void 0;
|
|
9011
|
+
const out = {};
|
|
9012
|
+
for (const key of [
|
|
9013
|
+
"number",
|
|
9014
|
+
"text",
|
|
9015
|
+
"bool"
|
|
9016
|
+
]) {
|
|
9017
|
+
const kind = given[key];
|
|
9018
|
+
if (kind !== void 0) out[key] = typeOfKind(kind);
|
|
9019
|
+
}
|
|
9020
|
+
return out;
|
|
9021
|
+
}
|
|
8290
9022
|
function compileProfile(profile) {
|
|
8291
9023
|
const cached = compiledProfiles.get(profile);
|
|
8292
9024
|
if (cached !== void 0) return cached;
|
|
@@ -8301,13 +9033,22 @@ function compileProfile(profile) {
|
|
|
8301
9033
|
const forms = /* @__PURE__ */ new Map();
|
|
8302
9034
|
const formsByKind = /* @__PURE__ */ new Map();
|
|
8303
9035
|
for (const given of calls.forms ?? []) {
|
|
8304
|
-
|
|
9036
|
+
const keys = given.name.split(".");
|
|
9037
|
+
if (!keys.every((key) => NAME.test(key))) throw new TypeError(`The call form "${given.name}" is not a plain name or a path of plain names.`);
|
|
9038
|
+
if (keys.length > 1 && refs.fields === false) throw new TypeError(`The call form "${given.name}" has dots, but the syntax has no field steps.`);
|
|
9039
|
+
if (keys.length > 1 && ops.opWords.has(keys[0])) throw new TypeError(`The call form "${given.name}" starts with the word operator "${keys[0]}".`);
|
|
8305
9040
|
const { type, key } = typeOfKind(given.kind);
|
|
9041
|
+
const args = given.args ?? (given.fields === void 0 ? "list" : "fields");
|
|
9042
|
+
if (!FORM_ARGS.includes(args)) throw new TypeError(`The call form "${given.name}" has the unknown arguments ${String(args)}.`);
|
|
9043
|
+
if (args === "fields" !== (given.fields !== void 0) && args !== "named") throw new TypeError(`The call form "${given.name}" with the arguments ${args} ${args === "fields" ? "needs" : "takes no"} fields.`);
|
|
9044
|
+
if (given.min !== void 0 && (args === "named" || args === "literal")) throw new TypeError(`The call form "${given.name}" with the arguments ${args} takes no min.`);
|
|
8306
9045
|
const form = {
|
|
8307
9046
|
name: given.name,
|
|
8308
9047
|
type,
|
|
9048
|
+
args,
|
|
8309
9049
|
fields: given.fields,
|
|
8310
|
-
min: given.min ?? given.fields?.length ?? 0
|
|
9050
|
+
min: given.min ?? given.fields?.length ?? 0,
|
|
9051
|
+
wrap: compileWrap(given, args)
|
|
8311
9052
|
};
|
|
8312
9053
|
forms.set(form.name, form);
|
|
8313
9054
|
addTo(formsByKind, key, form);
|
|
@@ -8319,6 +9060,7 @@ function compileProfile(profile) {
|
|
|
8319
9060
|
quote: strings === void 0 ? void 0 : strings.quote ?? "\"",
|
|
8320
9061
|
doubled: strings !== void 0 && strings.escape === "double",
|
|
8321
9062
|
words,
|
|
9063
|
+
envelopes: compileEnvelopes(literals.envelopes),
|
|
8322
9064
|
bare: refs.bare ?? "names",
|
|
8323
9065
|
roots,
|
|
8324
9066
|
fields: refs.fields ?? true,
|
|
@@ -8335,6 +9077,7 @@ function compileProfile(profile) {
|
|
|
8335
9077
|
forms,
|
|
8336
9078
|
formsByKind,
|
|
8337
9079
|
separator,
|
|
9080
|
+
bind: compileBind(profile.header?.bind, separator, ops.binary),
|
|
8338
9081
|
reserved: /* @__PURE__ */ new Set([
|
|
8339
9082
|
...words.keys(),
|
|
8340
9083
|
...ops.opWords,
|
|
@@ -8361,6 +9104,37 @@ function opDoc(op, value) {
|
|
|
8361
9104
|
value
|
|
8362
9105
|
};
|
|
8363
9106
|
}
|
|
9107
|
+
/** This function gives the kind of the data envelope of a literal (K159), or `undefined` when the literal has none. */
|
|
9108
|
+
function envelopeOf(syn, value) {
|
|
9109
|
+
const kinds = syn.envelopes;
|
|
9110
|
+
if (kinds === void 0 || !isScalar(value)) return void 0;
|
|
9111
|
+
return typeof value === "string" ? kinds.text : typeof value === "number" ? kinds.number : typeof value === "boolean" ? kinds.bool : void 0;
|
|
9112
|
+
}
|
|
9113
|
+
/** This function gives a literal in a place that takes a document its data envelope (K159). Each other document stays. */
|
|
9114
|
+
function envelop(syn, doc) {
|
|
9115
|
+
const kind = isEnvelope(doc) ? void 0 : envelopeOf(syn, doc);
|
|
9116
|
+
return kind === void 0 ? doc : {
|
|
9117
|
+
type: kind.type,
|
|
9118
|
+
value: doc
|
|
9119
|
+
};
|
|
9120
|
+
}
|
|
9121
|
+
/** This function tells if an argument of a form takes a document (K159). */
|
|
9122
|
+
function wraps(form, key) {
|
|
9123
|
+
return form.wrap === true || form.wrap !== false && form.wrap.has(String(key));
|
|
9124
|
+
}
|
|
9125
|
+
/** This function tells if a value is a literal that JSON writes exactly: a text, a finite number, a truth value or `null`. */
|
|
9126
|
+
function isScalar(value) {
|
|
9127
|
+
return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0);
|
|
9128
|
+
}
|
|
9129
|
+
function setOwn$1(target, key, value) {
|
|
9130
|
+
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
9131
|
+
value,
|
|
9132
|
+
enumerable: true,
|
|
9133
|
+
writable: true,
|
|
9134
|
+
configurable: true
|
|
9135
|
+
});
|
|
9136
|
+
else target[key] = value;
|
|
9137
|
+
}
|
|
8364
9138
|
const ESCAPES = {
|
|
8365
9139
|
"\"": "\"",
|
|
8366
9140
|
"'": "'",
|
|
@@ -8384,10 +9158,10 @@ var Parser = class {
|
|
|
8384
9158
|
parse() {
|
|
8385
9159
|
this.skip();
|
|
8386
9160
|
if (this.pos >= this.text.length) this.stop("The text is empty.", 0, 0);
|
|
8387
|
-
const doc = this.
|
|
9161
|
+
const doc = this.bound();
|
|
8388
9162
|
this.skip();
|
|
8389
9163
|
if (this.pos < this.text.length) this.stop("The text continues after the expression.", this.pos, this.tokenLength());
|
|
8390
|
-
return doc;
|
|
9164
|
+
return envelop(this.syn, doc);
|
|
8391
9165
|
}
|
|
8392
9166
|
position() {
|
|
8393
9167
|
return this.pos;
|
|
@@ -8409,6 +9183,60 @@ var Parser = class {
|
|
|
8409
9183
|
tokenLength() {
|
|
8410
9184
|
return (this.at(NAME_AT) ?? this.at(DIGITS_AT) ?? "x").length;
|
|
8411
9185
|
}
|
|
9186
|
+
/**
|
|
9187
|
+
* This method reads an expression that can have a header name: `name = expr` (D62). The bind symbol of the profile
|
|
9188
|
+
* follows the name. A longer operator that starts with the symbol wins, for example `==` over `=`.
|
|
9189
|
+
*/
|
|
9190
|
+
bound() {
|
|
9191
|
+
const name = this.bindingName();
|
|
9192
|
+
if (name === void 0) return this.expr(0);
|
|
9193
|
+
this.skip();
|
|
9194
|
+
const start = this.pos;
|
|
9195
|
+
const doc = this.expr(0);
|
|
9196
|
+
if (isEnvelope(doc)) {
|
|
9197
|
+
if (doc.name !== void 0) this.stop(`The document already has the name ${doc.name}.`, start, this.pos - start);
|
|
9198
|
+
return {
|
|
9199
|
+
...doc,
|
|
9200
|
+
name
|
|
9201
|
+
};
|
|
9202
|
+
}
|
|
9203
|
+
const kind = envelopeOf(this.syn, doc);
|
|
9204
|
+
return kind === void 0 ? {
|
|
9205
|
+
type: "lit",
|
|
9206
|
+
value: doc,
|
|
9207
|
+
name
|
|
9208
|
+
} : {
|
|
9209
|
+
type: kind.type,
|
|
9210
|
+
value: doc,
|
|
9211
|
+
name
|
|
9212
|
+
};
|
|
9213
|
+
}
|
|
9214
|
+
/** This method reads a name and the bind symbol after it. Without them, it gives `undefined` and keeps the position. */
|
|
9215
|
+
bindingName() {
|
|
9216
|
+
const bind = this.syn.bind;
|
|
9217
|
+
if (bind === void 0) return void 0;
|
|
9218
|
+
this.skip();
|
|
9219
|
+
const back = this.pos;
|
|
9220
|
+
let name;
|
|
9221
|
+
if (this.text[this.pos] === "`") try {
|
|
9222
|
+
name = this.quoted();
|
|
9223
|
+
} catch (error) {
|
|
9224
|
+
if (!(error instanceof Stop)) throw error;
|
|
9225
|
+
}
|
|
9226
|
+
else {
|
|
9227
|
+
name = this.at(NAME_AT);
|
|
9228
|
+
if (name !== void 0) this.pos += name.length;
|
|
9229
|
+
}
|
|
9230
|
+
if (name !== void 0) {
|
|
9231
|
+
this.skip();
|
|
9232
|
+
const longer = this.syn.binarySymbols.some((symbol) => symbol.length > bind.length && symbol.startsWith(bind) && this.text.startsWith(symbol, this.pos));
|
|
9233
|
+
if (this.text.startsWith(bind, this.pos) && !longer) {
|
|
9234
|
+
this.pos += bind.length;
|
|
9235
|
+
return name;
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
this.pos = back;
|
|
9239
|
+
}
|
|
8412
9240
|
/** This method reads an expression by precedence climbing. It stops at an operator below `min`. */
|
|
8413
9241
|
expr(min) {
|
|
8414
9242
|
const start = this.pos;
|
|
@@ -8541,7 +9369,7 @@ var Parser = class {
|
|
|
8541
9369
|
group() {
|
|
8542
9370
|
const open = this.pos;
|
|
8543
9371
|
this.pos += 1;
|
|
8544
|
-
const inner = this.
|
|
9372
|
+
const inner = this.bound();
|
|
8545
9373
|
this.skip();
|
|
8546
9374
|
if (this.text[this.pos] !== ")") {
|
|
8547
9375
|
if (this.pos >= this.text.length) this.stop("A closing parenthesis is missing.", open, 1);
|
|
@@ -8591,6 +9419,11 @@ var Parser = class {
|
|
|
8591
9419
|
}
|
|
8592
9420
|
this.pos += 1;
|
|
8593
9421
|
this.skip();
|
|
9422
|
+
if (this.text[this.pos] === "(") {
|
|
9423
|
+
path.push(this.group());
|
|
9424
|
+
allPlain = false;
|
|
9425
|
+
continue;
|
|
9426
|
+
}
|
|
8594
9427
|
if (this.text[this.pos] === "`") {
|
|
8595
9428
|
path.push(this.quoted());
|
|
8596
9429
|
allPlain = false;
|
|
@@ -8606,7 +9439,7 @@ var Parser = class {
|
|
|
8606
9439
|
const back = this.pos;
|
|
8607
9440
|
this.skip();
|
|
8608
9441
|
if (this.text[this.pos] === "(") {
|
|
8609
|
-
if (!allPlain) this.stop("
|
|
9442
|
+
if (!allPlain) this.stop("Each key of the name of a call is a plain name.", start, back - start);
|
|
8610
9443
|
return this.call(path.join("."), start, back - start);
|
|
8611
9444
|
}
|
|
8612
9445
|
this.pos = back;
|
|
@@ -8616,14 +9449,18 @@ var Parser = class {
|
|
|
8616
9449
|
value: plain && this.syn.roots.has(first) ? path : [this.syn.bare, ...path]
|
|
8617
9450
|
};
|
|
8618
9451
|
}
|
|
8619
|
-
call
|
|
9452
|
+
/** This method reads the arguments of a call in parentheses. `item` reads one argument. */
|
|
9453
|
+
argumentsOf(item) {
|
|
8620
9454
|
const open = this.pos;
|
|
8621
9455
|
const args = [];
|
|
8622
9456
|
this.pos += 1;
|
|
8623
9457
|
this.skip();
|
|
8624
|
-
if (this.text[this.pos] === ")")
|
|
8625
|
-
|
|
8626
|
-
args
|
|
9458
|
+
if (this.text[this.pos] === ")") {
|
|
9459
|
+
this.pos += 1;
|
|
9460
|
+
return args;
|
|
9461
|
+
}
|
|
9462
|
+
for (;;) {
|
|
9463
|
+
args.push(item());
|
|
8627
9464
|
this.skip();
|
|
8628
9465
|
if (this.text.startsWith(this.syn.separator, this.pos)) {
|
|
8629
9466
|
this.pos += this.syn.separator.length;
|
|
@@ -8631,22 +9468,55 @@ var Parser = class {
|
|
|
8631
9468
|
}
|
|
8632
9469
|
if (this.text[this.pos] === ")") {
|
|
8633
9470
|
this.pos += 1;
|
|
8634
|
-
|
|
9471
|
+
return args;
|
|
8635
9472
|
}
|
|
8636
9473
|
if (this.pos >= this.text.length) this.stop("A closing parenthesis is missing.", open, 1);
|
|
8637
9474
|
this.stop(`A "${this.syn.separator}" or a closing parenthesis is missing.`, this.pos, this.tokenLength());
|
|
8638
9475
|
}
|
|
9476
|
+
}
|
|
9477
|
+
/** This method reads the arguments `key: expr` of a form `named` into a record (SYNTAX.RECORD). */
|
|
9478
|
+
namedArguments(form) {
|
|
9479
|
+
const value = {};
|
|
9480
|
+
this.argumentsOf(() => {
|
|
9481
|
+
this.skip();
|
|
9482
|
+
const at = this.pos;
|
|
9483
|
+
let key;
|
|
9484
|
+
if (this.text[at] === "`") key = this.quoted();
|
|
9485
|
+
else {
|
|
9486
|
+
key = this.at(NAME_AT);
|
|
9487
|
+
if (key !== void 0) this.pos += key.length;
|
|
9488
|
+
}
|
|
9489
|
+
const end = this.pos;
|
|
9490
|
+
this.skip();
|
|
9491
|
+
if (key === void 0 || this.text[this.pos] !== ":") this.stop(`Each argument of the form ${form.name} is a key, a ":" and a document.`, at, Math.max(end - at, this.tokenLength()));
|
|
9492
|
+
if (form.fields !== void 0 && !form.fields.includes(key)) this.stop(`The form ${form.name} has no field ${key}.`, at, end - at);
|
|
9493
|
+
if (Object.hasOwn(value, key)) this.stop(`The form ${form.name} has the field ${key} two times.`, at, end - at);
|
|
9494
|
+
this.pos += 1;
|
|
9495
|
+
const arg = this.bound();
|
|
9496
|
+
setOwn$1(value, key, wraps(form, key) ? envelop(this.syn, arg) : arg);
|
|
9497
|
+
});
|
|
9498
|
+
return opDoc(form, value);
|
|
9499
|
+
}
|
|
9500
|
+
call(name, start, nameLength) {
|
|
8639
9501
|
const form = this.syn.forms.get(name);
|
|
9502
|
+
if (form?.args === "named") return this.namedArguments(form);
|
|
9503
|
+
const args = this.argumentsOf(() => this.bound());
|
|
9504
|
+
if (form?.args === "literal") {
|
|
9505
|
+
const [only] = args;
|
|
9506
|
+
if (args.length !== 1 || !isScalar(only)) this.stop(`The form ${name} takes one literal that JSON writes.`, start, this.pos - start);
|
|
9507
|
+
return opDoc(form, only);
|
|
9508
|
+
}
|
|
8640
9509
|
if (form !== void 0) {
|
|
8641
9510
|
const most = form.fields?.length ?? Number.POSITIVE_INFINITY;
|
|
8642
9511
|
if (args.length < form.min || args.length > most) {
|
|
8643
9512
|
const range = most === form.min ? String(most) : most === Number.POSITIVE_INFINITY ? `${String(form.min)} or more` : `${String(form.min)} to ${String(most)}`;
|
|
8644
9513
|
this.stop(`The form ${name} takes ${range} arguments.`, start, this.pos - start);
|
|
8645
9514
|
}
|
|
8646
|
-
if (form.fields === void 0) return opDoc(form, args);
|
|
9515
|
+
if (form.fields === void 0) return opDoc(form, form.wrap === true ? args.map((arg) => envelop(this.syn, arg)) : args);
|
|
8647
9516
|
const value = {};
|
|
8648
9517
|
args.forEach((arg, index) => {
|
|
8649
|
-
|
|
9518
|
+
const key = form.fields?.[index];
|
|
9519
|
+
value[key] = wraps(form, key) ? envelop(this.syn, arg) : arg;
|
|
8650
9520
|
});
|
|
8651
9521
|
return opDoc(form, value);
|
|
8652
9522
|
}
|
|
@@ -8725,8 +9595,10 @@ function literalShape(syn, value) {
|
|
|
8725
9595
|
function plainSegment(syn, name) {
|
|
8726
9596
|
return NAME.test(name) && !syn.reserved.has(name) && !syn.patterns.some((p) => p.whole.test(name));
|
|
8727
9597
|
}
|
|
8728
|
-
function
|
|
9598
|
+
/** This function gives the text of a later step of a path, or the step and its index when the step is a document. */
|
|
9599
|
+
function laterSegment(step, index) {
|
|
8729
9600
|
if (typeof step === "string") return NAME.test(step) ? step : quoteName(step);
|
|
9601
|
+
if (isEnvelope(step)) return [index, step];
|
|
8730
9602
|
return typeof step === "number" && Number.isSafeInteger(step) && step >= 0 ? String(step) : void 0;
|
|
8731
9603
|
}
|
|
8732
9604
|
function refShape(syn, value) {
|
|
@@ -8740,18 +9612,23 @@ function refShape(syn, value) {
|
|
|
8740
9612
|
}
|
|
8741
9613
|
const [root, ...rest] = value;
|
|
8742
9614
|
if ((rest.length > 1 || root !== syn.bare && rest.length > 0) && !syn.fields) return none("This syntax has no field steps.");
|
|
8743
|
-
const later = rest.slice(1).map(laterSegment);
|
|
8744
9615
|
let first;
|
|
8745
|
-
let tail =
|
|
8746
|
-
if (root === syn.bare && typeof rest[0] === "string")
|
|
8747
|
-
|
|
9616
|
+
let tail = [];
|
|
9617
|
+
if (root === syn.bare && typeof rest[0] === "string") {
|
|
9618
|
+
first = plainSegment(syn, rest[0]) ? rest[0] : quoteName(rest[0]);
|
|
9619
|
+
tail = rest.slice(1).map((step, index) => laterSegment(step, index + 2));
|
|
9620
|
+
} else if (typeof root === "string" && syn.roots.has(root) && !syn.patterns.some((p) => p.whole.test(root))) {
|
|
8748
9621
|
first = root;
|
|
8749
|
-
tail = rest.map(laterSegment);
|
|
9622
|
+
tail = rest.map((step, index) => laterSegment(step, index + 1));
|
|
8750
9623
|
}
|
|
8751
9624
|
if (first === void 0 || tail.includes(void 0)) return none("This syntax has no text for the path of the reference.");
|
|
8752
|
-
|
|
9625
|
+
const segments = [first, ...tail];
|
|
9626
|
+
return segments.every((segment) => typeof segment === "string") ? {
|
|
8753
9627
|
tag: "text",
|
|
8754
|
-
text:
|
|
9628
|
+
text: segments.join(".")
|
|
9629
|
+
} : {
|
|
9630
|
+
tag: "path",
|
|
9631
|
+
segments
|
|
8755
9632
|
};
|
|
8756
9633
|
}
|
|
8757
9634
|
function callName(syn, keys) {
|
|
@@ -8760,13 +9637,58 @@ function callName(syn, keys) {
|
|
|
8760
9637
|
if (syn.forms.has(name) || syn.opWords.has(keys[0])) return void 0;
|
|
8761
9638
|
return syn.ops === true || syn.ops.has(name) ? name : void 0;
|
|
8762
9639
|
}
|
|
8763
|
-
|
|
9640
|
+
const WRAPPED = "This syntax reads a literal in this place as its data envelope, thus a bare literal has no text here.";
|
|
9641
|
+
/**
|
|
9642
|
+
* This function gives the text of a document in a place that takes a document (K159). A data envelope of a literal
|
|
9643
|
+
* is the bare literal. A literal without its envelope has no text, because `parse` gives it the envelope. The
|
|
9644
|
+
* function gives `undefined` for each other document.
|
|
9645
|
+
*/
|
|
9646
|
+
function wrappedShape(syn, doc) {
|
|
9647
|
+
if (syn.envelopes === void 0) return void 0;
|
|
9648
|
+
if (!isEnvelope(doc)) return envelopeOf(syn, doc) === void 0 ? void 0 : none(WRAPPED);
|
|
9649
|
+
if (doc.name !== void 0 || doc.own !== void 0 || !Object.hasOwn(doc, "value")) return void 0;
|
|
9650
|
+
const path = typePath(doc.type);
|
|
9651
|
+
if (path === void 0) return void 0;
|
|
9652
|
+
const key = JSON.stringify(path);
|
|
9653
|
+
if (key === JSON.stringify(["lit"])) return envelopeOf(syn, doc.value) === void 0 ? void 0 : none(WRAPPED);
|
|
9654
|
+
if (envelopeOf(syn, doc.value)?.key !== key) return void 0;
|
|
9655
|
+
const literal = literalShape(syn, doc.value);
|
|
9656
|
+
return literal.tag === "text" ? literal : void 0;
|
|
9657
|
+
}
|
|
9658
|
+
function formShape(syn, form, value) {
|
|
9659
|
+
if (form.args === "literal") {
|
|
9660
|
+
if (!isScalar(value)) return void 0;
|
|
9661
|
+
const literal = literalShape(syn, value);
|
|
9662
|
+
return literal.tag === "text" ? {
|
|
9663
|
+
tag: "text",
|
|
9664
|
+
text: `${form.name}(${literal.text})`
|
|
9665
|
+
} : void 0;
|
|
9666
|
+
}
|
|
9667
|
+
if (form.args === "named") {
|
|
9668
|
+
if (!isRecord(value)) return void 0;
|
|
9669
|
+
const keys = Object.keys(value);
|
|
9670
|
+
if (form.fields !== void 0 && !keys.every((key) => form.fields?.includes(key) === true)) return void 0;
|
|
9671
|
+
return {
|
|
9672
|
+
tag: "call",
|
|
9673
|
+
name: form.name,
|
|
9674
|
+
args: keys.map((key) => [
|
|
9675
|
+
key,
|
|
9676
|
+
value[key],
|
|
9677
|
+
wraps(form, key)
|
|
9678
|
+
]),
|
|
9679
|
+
named: true
|
|
9680
|
+
};
|
|
9681
|
+
}
|
|
8764
9682
|
if (form.fields === void 0) {
|
|
8765
9683
|
if (!Array.isArray(value) || value.length < form.min) return void 0;
|
|
8766
9684
|
return {
|
|
8767
9685
|
tag: "call",
|
|
8768
9686
|
name: form.name,
|
|
8769
|
-
args: value.map((item, index) => [
|
|
9687
|
+
args: value.map((item, index) => [
|
|
9688
|
+
index,
|
|
9689
|
+
item,
|
|
9690
|
+
wraps(form, index)
|
|
9691
|
+
])
|
|
8770
9692
|
};
|
|
8771
9693
|
}
|
|
8772
9694
|
if (!isRecord(value)) return void 0;
|
|
@@ -8776,7 +9698,11 @@ function formShape(form, value) {
|
|
|
8776
9698
|
return {
|
|
8777
9699
|
tag: "call",
|
|
8778
9700
|
name: form.name,
|
|
8779
|
-
args: present.map((key) => [
|
|
9701
|
+
args: present.map((key) => [
|
|
9702
|
+
key,
|
|
9703
|
+
value[key],
|
|
9704
|
+
wraps(form, key)
|
|
9705
|
+
])
|
|
8780
9706
|
};
|
|
8781
9707
|
}
|
|
8782
9708
|
function opShape(syn, key, terms) {
|
|
@@ -8789,7 +9715,16 @@ function opShape(syn, key, terms) {
|
|
|
8789
9715
|
/** This function gives the shape of a document: a literal or a reference, an operator, a call, or no text. */
|
|
8790
9716
|
function shapeOf(syn, doc) {
|
|
8791
9717
|
if (!isEnvelope(doc)) return Array.isArray(doc) || isRecord(doc) ? none("This syntax has no text for a list or a record literal.") : literalShape(syn, doc);
|
|
8792
|
-
if (doc.
|
|
9718
|
+
if (doc.own !== void 0) return none("This syntax has no text for the own entries of a document.");
|
|
9719
|
+
if (doc.name !== void 0) {
|
|
9720
|
+
if (syn.bind === void 0) return none("This syntax has no text for the name of a document.");
|
|
9721
|
+
const { name, ...rest } = doc;
|
|
9722
|
+
return {
|
|
9723
|
+
tag: "bind",
|
|
9724
|
+
name,
|
|
9725
|
+
doc: rest
|
|
9726
|
+
};
|
|
9727
|
+
}
|
|
8793
9728
|
const path = typePath(doc.type);
|
|
8794
9729
|
if (path === void 0) return none("This syntax has no text for a computed type.");
|
|
8795
9730
|
const value = doc.value;
|
|
@@ -8810,7 +9745,7 @@ function shapeOf(syn, doc) {
|
|
|
8810
9745
|
const op = Array.isArray(value) ? opShape(syn, key, value) : void 0;
|
|
8811
9746
|
if (op !== void 0) return op;
|
|
8812
9747
|
for (const form of syn.formsByKind.get(key) ?? []) {
|
|
8813
|
-
const shape = formShape(form, value);
|
|
9748
|
+
const shape = formShape(syn, form, value);
|
|
8814
9749
|
if (shape !== void 0) return shape;
|
|
8815
9750
|
}
|
|
8816
9751
|
const called = path[0] === "ops" && Array.isArray(value) ? callName(syn, path.slice(1)) : void 0;
|
|
@@ -8823,7 +9758,7 @@ function shapeOf(syn, doc) {
|
|
|
8823
9758
|
}
|
|
8824
9759
|
function levelOf(syn, doc) {
|
|
8825
9760
|
const shape = shapeOf(syn, doc);
|
|
8826
|
-
return shape.tag === "op" ? shape.op.prec : Number.POSITIVE_INFINITY;
|
|
9761
|
+
return shape.tag === "op" ? shape.op.prec : shape.tag === "bind" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
8827
9762
|
}
|
|
8828
9763
|
/** This function tells if a term needs parentheses in its position, so that `parse` reads the same tree. */
|
|
8829
9764
|
function needsGroup(op, index, level) {
|
|
@@ -8853,18 +9788,42 @@ function printNode(syn, doc, sub) {
|
|
|
8853
9788
|
if (shape.tag === "text") return ok(shape.text);
|
|
8854
9789
|
if (shape.tag === "none") return fail("#VALUE!", shape.reason);
|
|
8855
9790
|
if (shape.tag === "op") return opText(syn, shape.op, shape.terms, sub);
|
|
9791
|
+
if (shape.tag === "bind") {
|
|
9792
|
+
const head = wrappedShape(syn, shape.doc);
|
|
9793
|
+
const inner = head === void 0 ? printNode(syn, shape.doc, sub) : head.tag === "text" ? ok(head.text) : fail("#VALUE!", WRAPPED);
|
|
9794
|
+
return inner.ok ? ok(`${NAME.test(shape.name) ? shape.name : quoteName(shape.name)} ${syn.bind} ${inner.value}`) : inner;
|
|
9795
|
+
}
|
|
9796
|
+
if (shape.tag === "path") {
|
|
9797
|
+
const texts = [];
|
|
9798
|
+
for (const segment of shape.segments) {
|
|
9799
|
+
if (typeof segment === "string") {
|
|
9800
|
+
texts.push(segment);
|
|
9801
|
+
continue;
|
|
9802
|
+
}
|
|
9803
|
+
const result = sub(segment[1], [segment[0]]);
|
|
9804
|
+
if (!result.ok) return result;
|
|
9805
|
+
texts.push(`(${result.value})`);
|
|
9806
|
+
}
|
|
9807
|
+
return ok(texts.join("."));
|
|
9808
|
+
}
|
|
8856
9809
|
const texts = [];
|
|
8857
|
-
for (const [key, arg] of shape.args) {
|
|
8858
|
-
const result = sub(arg, [key]);
|
|
9810
|
+
for (const [key, arg, wrapped] of shape.args) {
|
|
9811
|
+
const result = sub(arg, [key], wrapped);
|
|
8859
9812
|
if (!result.ok) return result;
|
|
8860
|
-
texts.push(result.value);
|
|
9813
|
+
texts.push(shape.named === true ? `${NAME.test(String(key)) ? String(key) : quoteName(String(key))}: ${result.value}` : result.value);
|
|
8861
9814
|
}
|
|
8862
9815
|
return ok(`${shape.name}(${texts.join(`${syn.separator} `)})`);
|
|
8863
9816
|
}
|
|
8864
9817
|
function printTree(syn, doc, place) {
|
|
8865
|
-
const result = printNode(syn, doc, (child, path) => printTree(syn, child, [...place, ...path]));
|
|
9818
|
+
const result = printNode(syn, doc, (child, path, wrapped) => (wrapped === true ? printPlace : printTree)(syn, child, [...place, ...path]));
|
|
8866
9819
|
return result.ok || result.error.place !== void 0 ? result : fail(result.error.code, result.error.reason, { place });
|
|
8867
9820
|
}
|
|
9821
|
+
/** This function writes a document in a place that takes a document, for example the whole text (K159). */
|
|
9822
|
+
function printPlace(syn, doc, place) {
|
|
9823
|
+
const shape = wrappedShape(syn, doc);
|
|
9824
|
+
if (shape === void 0) return printTree(syn, doc, place);
|
|
9825
|
+
return shape.tag === "text" ? ok(shape.text) : fail("#VALUE!", WRAPPED, { place });
|
|
9826
|
+
}
|
|
8868
9827
|
/**
|
|
8869
9828
|
* This function writes a document as text with the rules of a profile (SYNTAX.PRINT). It writes the fewest
|
|
8870
9829
|
* parentheses that keep the tree. `parse` reads the text back to the same document (SYNTAX.ROUNDTRIP).
|
|
@@ -8877,7 +9836,7 @@ function printTree(syn, doc, place) {
|
|
|
8877
9836
|
function print(profile, doc) {
|
|
8878
9837
|
const syn = compileProfile(profile);
|
|
8879
9838
|
try {
|
|
8880
|
-
return
|
|
9839
|
+
return printPlace(syn, doc, []);
|
|
8881
9840
|
} catch (error) {
|
|
8882
9841
|
if (isStackOverflow(error)) return fail("#DEPTH!", "The document is too deep for the stack of the host.", { cause: error });
|
|
8883
9842
|
throw error;
|
|
@@ -8895,7 +9854,9 @@ function syntaxFormat(profile) {
|
|
|
8895
9854
|
const syn = compileProfile(profile);
|
|
8896
9855
|
const code = ({ doc, parts, place }) => {
|
|
8897
9856
|
const byPath = new Map(parts.map((part) => [JSON.stringify(part.path), part]));
|
|
8898
|
-
return printNode(syn, doc, (child, path) => {
|
|
9857
|
+
return printNode(syn, doc, (child, path, wrapped) => {
|
|
9858
|
+
const own = wrapped === true ? wrappedShape(syn, child) : void 0;
|
|
9859
|
+
if (own !== void 0) return own.tag === "text" ? ok(own.text) : fail("#VALUE!", WRAPPED, { place: [...place, ...path] });
|
|
8899
9860
|
const part = byPath.get(JSON.stringify(path));
|
|
8900
9861
|
if (part === void 0) return printTree(syn, child, [...place, ...path]);
|
|
8901
9862
|
const result = part.evaluate();
|
|
@@ -9016,6 +9977,54 @@ function resolveFully(machine, ctx, path, options) {
|
|
|
9016
9977
|
hops: []
|
|
9017
9978
|
};
|
|
9018
9979
|
}
|
|
9980
|
+
/** The text that names a resolution in the error of an exception of the host (READS.RECORD). */
|
|
9981
|
+
const READ_FAILED = "the resolution of a path";
|
|
9982
|
+
/**
|
|
9983
|
+
* This function resolves a path for a document, and records the read when the evaluation has a list (REF.READS,
|
|
9984
|
+
* READS.RECORD). An exception of the host gives the error value of the read, and the exception goes on.
|
|
9985
|
+
*/
|
|
9986
|
+
function readAt(env, ctx, path, options) {
|
|
9987
|
+
const reading = env.reading;
|
|
9988
|
+
if (reading === void 0) return resolveFully(env.machine, ctx, path, options);
|
|
9989
|
+
const free = freeIn(reading.root, ctx, path);
|
|
9990
|
+
let found;
|
|
9991
|
+
try {
|
|
9992
|
+
found = resolveFully(env.machine, ctx, path, options);
|
|
9993
|
+
} catch (error) {
|
|
9994
|
+
reading.list.push({
|
|
9995
|
+
path,
|
|
9996
|
+
reached: thrown(error, READ_FAILED),
|
|
9997
|
+
free
|
|
9998
|
+
});
|
|
9999
|
+
throw error;
|
|
10000
|
+
}
|
|
10001
|
+
reading.list.push(options?.hops === true ? {
|
|
10002
|
+
path,
|
|
10003
|
+
reached: reachedOf(found),
|
|
10004
|
+
free,
|
|
10005
|
+
hops: found.hops
|
|
10006
|
+
} : {
|
|
10007
|
+
path,
|
|
10008
|
+
reached: reachedOf(found),
|
|
10009
|
+
free
|
|
10010
|
+
});
|
|
10011
|
+
return found;
|
|
10012
|
+
}
|
|
10013
|
+
/**
|
|
10014
|
+
* This function tells if a read reads the context at the root of the evaluation (READS.FREE). The reference reads
|
|
10015
|
+
* the rule from SPEC. A name that a kind binds, a `self` that a kind sets and a path without a root are not free. A
|
|
10016
|
+
* name that a kind does not bind keeps its value from the root.
|
|
10017
|
+
*/
|
|
10018
|
+
function freeIn(root, ctx, path) {
|
|
10019
|
+
const head = path[0];
|
|
10020
|
+
if (head === "self") return Object.is(ctx.self, root.self);
|
|
10021
|
+
if (head !== "names") return typeof head === "string" && ROOTS.includes(head);
|
|
10022
|
+
const name = path[1];
|
|
10023
|
+
if (ctx.names === root.names || !isKey(name)) return true;
|
|
10024
|
+
const key = String(name);
|
|
10025
|
+
const bound = Object.hasOwn(ctx.names, key);
|
|
10026
|
+
return bound === Object.hasOwn(root.names, key) && (!bound || Object.is(ctx.names[key], root.names[key]));
|
|
10027
|
+
}
|
|
9019
10028
|
/**
|
|
9020
10029
|
* This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
|
|
9021
10030
|
* address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
|
|
@@ -9170,7 +10179,7 @@ function evalInput(env, seen, at) {
|
|
|
9170
10179
|
place: at.place,
|
|
9171
10180
|
machine: env.machine,
|
|
9172
10181
|
...at.address ? { address: true } : {},
|
|
9173
|
-
resolve: (path, options) =>
|
|
10182
|
+
resolve: (path, options) => readAt(env, at.ctx, path, options),
|
|
9174
10183
|
effect: (kind, payload) => causeFromEvaluator(env, at.ctx, kind, payload, at.place),
|
|
9175
10184
|
fill
|
|
9176
10185
|
};
|
|
@@ -9197,7 +10206,7 @@ function runInput(env, seen, at) {
|
|
|
9197
10206
|
context: at.ctx,
|
|
9198
10207
|
place: at.place,
|
|
9199
10208
|
machine: env.machine,
|
|
9200
|
-
resolve: (path, options) =>
|
|
10209
|
+
resolve: (path, options) => readAt(env, at.ctx, path, options),
|
|
9201
10210
|
effect: (kind, payload) => causeAndWait(env, at.ctx, kind, payload, at.place),
|
|
9202
10211
|
all: (list, options) => together(env, list, options),
|
|
9203
10212
|
fill
|
|
@@ -9226,6 +10235,7 @@ function* together(env, list, options = {}) {
|
|
|
9226
10235
|
outer.alls += 1;
|
|
9227
10236
|
let failed = false;
|
|
9228
10237
|
let started = 0;
|
|
10238
|
+
let before = 0;
|
|
9229
10239
|
const end = (index, result) => {
|
|
9230
10240
|
results[index] = result;
|
|
9231
10241
|
if (failFast && !result.ok) failed = true;
|
|
@@ -9289,10 +10299,11 @@ function* together(env, list, options = {}) {
|
|
|
9289
10299
|
};
|
|
9290
10300
|
startMore();
|
|
9291
10301
|
while (waiting.length > 0 && !failed) {
|
|
9292
|
-
const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index));
|
|
10302
|
+
const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index), before);
|
|
9293
10303
|
const pick = chosen === void 0 ? void 0 : waiting.find((waiter) => waiter.index === chosen);
|
|
9294
10304
|
const resumed = yield pick === void 0 ? Promise.race(waiting.map((waiter) => waiter.next)) : pick.next;
|
|
9295
10305
|
env.journal?.settled?.(address, resumed.waiter.index);
|
|
10306
|
+
before += 1;
|
|
9296
10307
|
waiting.splice(waiting.indexOf(resumed.waiter), 1);
|
|
9297
10308
|
goOn(resumed.waiter.index, resumed.waiter.steps, resumed);
|
|
9298
10309
|
startMore();
|
|
@@ -9319,9 +10330,17 @@ function* together(env, list, options = {}) {
|
|
|
9319
10330
|
* @returns The result.
|
|
9320
10331
|
*/
|
|
9321
10332
|
function reference(machine, doc, context = makeContext(), options = {}) {
|
|
9322
|
-
|
|
10333
|
+
return evaluateRoot(machine, doc, context, options, void 0);
|
|
10334
|
+
}
|
|
10335
|
+
/**
|
|
10336
|
+
* This function evaluates a document from its root. An equation of a tree gives the record of its tree. Thus the
|
|
10337
|
+
* reads of the equation go in the list of the outer evaluation (REF.READS).
|
|
10338
|
+
*/
|
|
10339
|
+
function evaluateRoot(machine, doc, context, options, outer) {
|
|
10340
|
+
const at = rootAt(machine, doc, context, options);
|
|
10341
|
+
const env = envOf(machine, options, false, at.ctx, outer);
|
|
9323
10342
|
try {
|
|
9324
|
-
return within(env, () => evaluateAt(env,
|
|
10343
|
+
return within(env, () => evaluateAt(env, at));
|
|
9325
10344
|
} catch (error) {
|
|
9326
10345
|
return placed(thrown(error, "the reference"), []);
|
|
9327
10346
|
}
|
|
@@ -9342,11 +10361,15 @@ function reference(machine, doc, context = makeContext(), options = {}) {
|
|
|
9342
10361
|
* @returns The result, when the run ends.
|
|
9343
10362
|
*/
|
|
9344
10363
|
function referenceRun(machine, doc, context = makeContext(), options = {}) {
|
|
9345
|
-
const
|
|
9346
|
-
|
|
10364
|
+
const at = rootAt(machine, doc, context, options);
|
|
10365
|
+
const env = envOf(machine, options, true, at.ctx, void 0);
|
|
10366
|
+
return driveRun(env, stepsAt(env, at), options.signal).catch((error) => placed(thrown(error, "the reference"), []));
|
|
9347
10367
|
}
|
|
9348
|
-
/**
|
|
9349
|
-
function
|
|
10368
|
+
/**
|
|
10369
|
+
* This function makes the state of the reference for one evaluation or one run. The record of the reads comes from
|
|
10370
|
+
* the evaluation around it, or from the list of the options.
|
|
10371
|
+
*/
|
|
10372
|
+
function envOf(machine, options, running, root, outer) {
|
|
9350
10373
|
const perform = options.perform;
|
|
9351
10374
|
return {
|
|
9352
10375
|
machine,
|
|
@@ -9361,7 +10384,11 @@ function envOf(machine, options, running) {
|
|
|
9361
10384
|
prefix: "",
|
|
9362
10385
|
counts: /* @__PURE__ */ new Map(),
|
|
9363
10386
|
alls: 0
|
|
9364
|
-
}
|
|
10387
|
+
},
|
|
10388
|
+
reading: outer ?? (options.reads === void 0 ? void 0 : {
|
|
10389
|
+
list: options.reads,
|
|
10390
|
+
root
|
|
10391
|
+
})
|
|
9365
10392
|
};
|
|
9366
10393
|
}
|
|
9367
10394
|
/** This function gives the root position of a document. A context without a world gets the world of the machine. */
|
|
@@ -9436,7 +10463,8 @@ function refNodeOf(thing) {
|
|
|
9436
10463
|
}
|
|
9437
10464
|
/**
|
|
9438
10465
|
* TREE.LADDER: an equation of the kind of a node evaluates in the reference. It gets the node as `self`, the names of
|
|
9439
|
-
* its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind.
|
|
10466
|
+
* its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind. The reads of the
|
|
10467
|
+
* equation go in the record of the evaluation that made the node (REF.READS).
|
|
9440
10468
|
*/
|
|
9441
10469
|
function equationOf(node, term, names) {
|
|
9442
10470
|
const env = node.env;
|
|
@@ -9447,10 +10475,10 @@ function equationOf(node, term, names) {
|
|
|
9447
10475
|
caps: [],
|
|
9448
10476
|
self: node.thing
|
|
9449
10477
|
};
|
|
9450
|
-
return
|
|
10478
|
+
return evaluateRoot(env.machine, term, ctx, {
|
|
9451
10479
|
definitions: env.definitions,
|
|
9452
10480
|
...node.of === void 0 ? {} : { declaredType: node.of }
|
|
9453
|
-
});
|
|
10481
|
+
}, env.reading);
|
|
9454
10482
|
}
|
|
9455
10483
|
function termIn(table, name) {
|
|
9456
10484
|
return table === void 0 || !Object.hasOwn(table, name) ? void 0 : table[name];
|
|
@@ -9696,10 +10724,13 @@ function formulaDefinitions(profile = {}) {
|
|
|
9696
10724
|
}
|
|
9697
10725
|
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
|
|
9698
10726
|
const bound = head === void 0 ? void 0 : own$1(context.names, head);
|
|
9699
|
-
if (bound instanceof Held)
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
10727
|
+
if (bound instanceof Held) {
|
|
10728
|
+
walk(["names", head]);
|
|
10729
|
+
return {
|
|
10730
|
+
ok: false,
|
|
10731
|
+
error: bound.error
|
|
10732
|
+
};
|
|
10733
|
+
}
|
|
9703
10734
|
const found = walk(steps);
|
|
9704
10735
|
if (!found.ok) return fail(found.code, found.reason);
|
|
9705
10736
|
if (address === true) return ok(found.thing);
|
|
@@ -9862,18 +10893,21 @@ function valuesOfStrict(parts) {
|
|
|
9862
10893
|
* Its log keeps each effect with its address, its kind, its payload and the outcome that the kernel gives it. The log
|
|
9863
10894
|
* also keeps the order in which the waits of each `all` settle.
|
|
9864
10895
|
*
|
|
10896
|
+
* The recorder gives each call of its journal to the journal of the inner function, for example of a replayer. Thus a
|
|
10897
|
+
* recording of a replay settles the waits in the order of the replayer, and it gives the same log (RUN.REPLAY-ORDER).
|
|
10898
|
+
*
|
|
9865
10899
|
* @param perform - The function of the host. Without it, each effect gives its payload, as in a run without a host.
|
|
9866
10900
|
* @returns The function `perform` for the run, and the log.
|
|
9867
10901
|
*/
|
|
9868
10902
|
function recorder(perform) {
|
|
9869
10903
|
const seen = [];
|
|
9870
10904
|
const settles = [];
|
|
9871
|
-
const journal =
|
|
10905
|
+
const journal = relay(perform, (all, item) => {
|
|
9872
10906
|
settles.push({
|
|
9873
10907
|
all,
|
|
9874
10908
|
item
|
|
9875
10909
|
});
|
|
9876
|
-
}
|
|
10910
|
+
});
|
|
9877
10911
|
const host = (effect) => {
|
|
9878
10912
|
const entry = {
|
|
9879
10913
|
effect,
|
|
@@ -9903,14 +10937,17 @@ function recorder(perform) {
|
|
|
9903
10937
|
* recorded error comes again with its code, its reason, its place and its cause. The waits of each `all` settle in the
|
|
9904
10938
|
* recorded order. An effect that the log does not have at its address gives `#REPLAY!` (RUN.REPLAY-MISMATCH).
|
|
9905
10939
|
*
|
|
9906
|
-
*
|
|
10940
|
+
* One replayer serves more than one run, also two runs at the same time (RUN.REPLAY-RUNS). The kernel tells the journal
|
|
10941
|
+
* the number of the waits of each `all` that settled before, thus the replayer keeps no state of a run. When the
|
|
10942
|
+
* recorded item of a wait does not wait in the run, that wait follows the order of the host.
|
|
9907
10943
|
*
|
|
9908
|
-
* @param log - The log of a recorder
|
|
9909
|
-
* @param options - `wait: false` for an evaluation.
|
|
10944
|
+
* @param log - The log of a recorder, or of `decodeLog`.
|
|
10945
|
+
* @param options - `wait: false` for an evaluation, and the rule of the host for payloads.
|
|
9910
10946
|
* @returns The function `perform` for the replay.
|
|
9911
10947
|
*/
|
|
9912
10948
|
function replayer(log, options = {}) {
|
|
9913
10949
|
const wait = options.wait !== false;
|
|
10950
|
+
const equal = options.equal;
|
|
9914
10951
|
const entries = /* @__PURE__ */ new Map();
|
|
9915
10952
|
for (const entry of log.effects) entries.set(entry.address, entry);
|
|
9916
10953
|
const order = /* @__PURE__ */ new Map();
|
|
@@ -9919,16 +10956,19 @@ function replayer(log, options = {}) {
|
|
|
9919
10956
|
if (items === void 0) order.set(settle.all, [settle.item]);
|
|
9920
10957
|
else items.push(settle.item);
|
|
9921
10958
|
}
|
|
9922
|
-
|
|
10959
|
+
let places;
|
|
10960
|
+
const journal = { next: (all, waiting, before) => {
|
|
9923
10961
|
const items = order.get(all);
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
items
|
|
10962
|
+
if (items === void 0) return void 0;
|
|
10963
|
+
const at = before ?? (places ??= /* @__PURE__ */ new Map()).get(all) ?? 0;
|
|
10964
|
+
const item = items[at];
|
|
10965
|
+
if (item === void 0 || !waiting.includes(item)) return void 0;
|
|
10966
|
+
if (before === void 0) places?.set(all, at + 1);
|
|
9927
10967
|
return item;
|
|
9928
10968
|
} };
|
|
9929
10969
|
const perform = (effect) => {
|
|
9930
10970
|
const entry = effect.address === void 0 ? void 0 : entries.get(effect.address);
|
|
9931
|
-
const wrong = mismatch(effect, entry);
|
|
10971
|
+
const wrong = mismatch(effect, entry, equal);
|
|
9932
10972
|
if (wrong !== void 0 || entry === void 0) throw wrong;
|
|
9933
10973
|
const outcome = entry.outcome;
|
|
9934
10974
|
const failure = outcome === void 0 ? replayError(effect, `The log has no outcome for the effect ${effect.kind} at ${effect.address ?? ""}: the recorded run stopped before the answer.`) : outcome.ok ? void 0 : new Failure(outcome.error);
|
|
@@ -9940,16 +10980,882 @@ function replayer(log, options = {}) {
|
|
|
9940
10980
|
return Object.assign(perform, { [JOURNAL]: journal });
|
|
9941
10981
|
}
|
|
9942
10982
|
/** This function gives the error of an effect that the log does not have at its address, or `undefined`. */
|
|
9943
|
-
function mismatch(effect, entry) {
|
|
10983
|
+
function mismatch(effect, entry, equal) {
|
|
9944
10984
|
const at = effect.address ?? "";
|
|
9945
10985
|
if (entry === void 0) return replayError(effect, `The log has no effect at the address ${at}, and the run asks for the effect ${effect.kind}.`);
|
|
9946
10986
|
if (entry.kind !== effect.kind) return replayError(effect, `The log has the effect ${entry.kind} at the address ${at}, and the run asks for the effect ${effect.kind}.`);
|
|
9947
|
-
if (!
|
|
10987
|
+
if (!equalWith(entry.payload, effect.payload, equal)) return replayError(effect, `The effect ${effect.kind} at the address ${at} has another payload than the log.`);
|
|
9948
10988
|
}
|
|
9949
10989
|
/** This function gives the exception that carries `#REPLAY!` to the kernel, with the place of the effect. */
|
|
9950
10990
|
function replayError(effect, reason) {
|
|
9951
10991
|
return new Failure(fail("#REPLAY!", reason, { place: effect.place }).error);
|
|
9952
10992
|
}
|
|
10993
|
+
/**
|
|
10994
|
+
* This function gives a journal that tells a function of its own each wait that settles. It gives each call to the
|
|
10995
|
+
* journal of the inner function `perform`, when that function has one.
|
|
10996
|
+
*
|
|
10997
|
+
* @param perform - The inner function.
|
|
10998
|
+
* @param settled - The function of its own.
|
|
10999
|
+
* @returns The journal.
|
|
11000
|
+
*/
|
|
11001
|
+
function relay(perform, settled) {
|
|
11002
|
+
const inner = perform === void 0 ? void 0 : perform[JOURNAL];
|
|
11003
|
+
const tell = (all, item) => {
|
|
11004
|
+
settled(all, item);
|
|
11005
|
+
inner?.settled?.(all, item);
|
|
11006
|
+
};
|
|
11007
|
+
if (inner?.next === void 0) return { settled: tell };
|
|
11008
|
+
return {
|
|
11009
|
+
settled: tell,
|
|
11010
|
+
next: (all, waiting, before) => inner.next?.(all, waiting, before)
|
|
11011
|
+
};
|
|
11012
|
+
}
|
|
11013
|
+
/** This function tells if a key is a field of an error value that the JSON form keeps. */
|
|
11014
|
+
function errorField(key) {
|
|
11015
|
+
return key === "code" || key === "reason" || key === "place" || key === "cause";
|
|
11016
|
+
}
|
|
11017
|
+
/** This function tells if a key is a field of an exception that the JSON form keeps in a field of its own, or drops. */
|
|
11018
|
+
function ownField(key) {
|
|
11019
|
+
return key === "name" || key === "message" || key === "stack" || key === "cause";
|
|
11020
|
+
}
|
|
11021
|
+
/** This function tells if a key is a field of the JSON form of an exception. */
|
|
11022
|
+
function exceptionField(key) {
|
|
11023
|
+
return key === "name" || key === "message" || key === "fields" || key === "parts" || key === "dropped" || key === "hidden" || key === "cause" || key === "exception" || key === "loop" || key === "causeParts";
|
|
11024
|
+
}
|
|
11025
|
+
/**
|
|
11026
|
+
* The own fields that are not enumerable and that the JSON form keeps in `hidden` (D77). The standard classes define
|
|
11027
|
+
* them: `errors` of `AggregateError`, `error` and `suppressed` of `SuppressedError`.
|
|
11028
|
+
*/
|
|
11029
|
+
const HIDDEN = /* @__PURE__ */ Object.freeze([
|
|
11030
|
+
"errors",
|
|
11031
|
+
"error",
|
|
11032
|
+
"suppressed"
|
|
11033
|
+
]);
|
|
11034
|
+
/** The result of `readField` for a field that the walk of `causeJson` cannot read. */
|
|
11035
|
+
const MISSING = /* @__PURE__ */ Symbol("missing");
|
|
11036
|
+
/**
|
|
11037
|
+
* This function tells if a value holds a hole of a list at some depth. `encodeValue` writes a hole as `undefined`. Thus
|
|
11038
|
+
* the walk of `causeJson` keeps such a value in parts, and the hole keeps its mark (D82).
|
|
11039
|
+
*/
|
|
11040
|
+
function holdsHole(value) {
|
|
11041
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11042
|
+
const stack = [value];
|
|
11043
|
+
while (stack.length > 0) {
|
|
11044
|
+
const at = stack.pop();
|
|
11045
|
+
if (typeof at !== "object" || at === null || seen.has(at) || !(Array.isArray(at) || isRecord(at))) continue;
|
|
11046
|
+
seen.add(at);
|
|
11047
|
+
if (Array.isArray(at)) for (let item = 0; item < at.length; item += 1) {
|
|
11048
|
+
if (!Object.hasOwn(at, item)) return true;
|
|
11049
|
+
stack.push(at[item]);
|
|
11050
|
+
}
|
|
11051
|
+
else for (const key of Object.keys(at)) stack.push(at[key]);
|
|
11052
|
+
}
|
|
11053
|
+
return false;
|
|
11054
|
+
}
|
|
11055
|
+
/**
|
|
11056
|
+
* This function tells if a list or a plain record holds an exception at some depth (D87). It reads the items as the
|
|
11057
|
+
* walk of `causeJson` does, thus a cause container with an exception gets a form.
|
|
11058
|
+
*/
|
|
11059
|
+
function holdsException(value) {
|
|
11060
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11061
|
+
const stack = [value];
|
|
11062
|
+
while (stack.length > 0) {
|
|
11063
|
+
const at = stack.pop();
|
|
11064
|
+
if (at instanceof Error) return true;
|
|
11065
|
+
if (typeof at !== "object" || at === null || seen.has(at) || !(Array.isArray(at) || isRecord(at))) continue;
|
|
11066
|
+
seen.add(at);
|
|
11067
|
+
if (Array.isArray(at)) {
|
|
11068
|
+
for (let item = 0; item < at.length; item += 1) if (Object.hasOwn(at, item)) stack.push(readField(at, item));
|
|
11069
|
+
} else for (const key of Object.keys(at)) stack.push(readField(at, key));
|
|
11070
|
+
}
|
|
11071
|
+
return false;
|
|
11072
|
+
}
|
|
11073
|
+
/** This function gives the field of a cause for its form after the walk (D76, D87). A cause without a form has no field. */
|
|
11074
|
+
function causeOf(built) {
|
|
11075
|
+
const form = built.form;
|
|
11076
|
+
if (form === void 0) return { names: built.names };
|
|
11077
|
+
if (Array.isArray(form) || "record" in form || "share" in form) return {
|
|
11078
|
+
link: { causeParts: form },
|
|
11079
|
+
names: built.names
|
|
11080
|
+
};
|
|
11081
|
+
return {
|
|
11082
|
+
link: "value" in form ? { cause: form.value } : form,
|
|
11083
|
+
names: built.names
|
|
11084
|
+
};
|
|
11085
|
+
}
|
|
11086
|
+
/** This function gives the term document of a value of an exception, or `undefined` when the value has none. */
|
|
11087
|
+
function termOrNothing(machine, value) {
|
|
11088
|
+
if (holdsHole(value)) return void 0;
|
|
11089
|
+
try {
|
|
11090
|
+
return encodeValue(machine, value);
|
|
11091
|
+
} catch {
|
|
11092
|
+
return;
|
|
11093
|
+
}
|
|
11094
|
+
}
|
|
11095
|
+
/** This function reads a field of a value, or gives `MISSING` when the read throws. */
|
|
11096
|
+
function readField(value, key) {
|
|
11097
|
+
try {
|
|
11098
|
+
return value[key];
|
|
11099
|
+
} catch {
|
|
11100
|
+
return MISSING;
|
|
11101
|
+
}
|
|
11102
|
+
}
|
|
11103
|
+
/** This function gives the forms of the parts with a form, with their keys, in the order of the keys. */
|
|
11104
|
+
function keptOf(keys, built) {
|
|
11105
|
+
const kept = [];
|
|
11106
|
+
for (let place = 0; place < keys.length; place += 1) {
|
|
11107
|
+
const form = built[place]?.form;
|
|
11108
|
+
if (form !== void 0) kept.push([keys[place], form]);
|
|
11109
|
+
}
|
|
11110
|
+
return kept;
|
|
11111
|
+
}
|
|
11112
|
+
/**
|
|
11113
|
+
* This function writes a cause without a term document as JSON (RUN.REPLAY-CAUSES, RUN.REPLAY-HIDDEN, RUN.REPLAY-FIELDS).
|
|
11114
|
+
* The cause is an exception with its hidden fields and its causes, or a container with an exception (D87). A host
|
|
11115
|
+
* makes the exception, thus a part without a term document is a note in `dropped`, not a refusal (D75, D76).
|
|
11116
|
+
*
|
|
11117
|
+
* The walk gives each exception an index in the order of a depth-first walk. It takes the fields in `parts` in the
|
|
11118
|
+
* order of their keys, the hidden fields in the order of `HIDDEN`, then the cause (D78). A list or a plain record
|
|
11119
|
+
* without a term document is a container of parts, with its items in their order (D82). A cause is a container only
|
|
11120
|
+
* when it holds an exception (D87). A value that is an earlier exception of the walk is its index in `loop` (D77).
|
|
11121
|
+
*
|
|
11122
|
+
* A container that an earlier place holds is its index in `share`, also a container that holds itself (D88). The walk is
|
|
11123
|
+
* a loop, thus a long chain does not overflow the stack.
|
|
11124
|
+
*/
|
|
11125
|
+
function causeJson(machine, root) {
|
|
11126
|
+
const items = [];
|
|
11127
|
+
const index = /* @__PURE__ */ new Map();
|
|
11128
|
+
const holders = /* @__PURE__ */ new Map();
|
|
11129
|
+
const tasks = [];
|
|
11130
|
+
const later = (children) => {
|
|
11131
|
+
for (let place = children.length - 1; place >= 0; place -= 1) tasks.push(children[place]);
|
|
11132
|
+
};
|
|
11133
|
+
const hold = (name, value, keep, children) => {
|
|
11134
|
+
const at = items.length;
|
|
11135
|
+
holders.set(value, at);
|
|
11136
|
+
const list = Array.isArray(value);
|
|
11137
|
+
const keys = list ? [] : Object.keys(value);
|
|
11138
|
+
const slots = [];
|
|
11139
|
+
items.push({
|
|
11140
|
+
name,
|
|
11141
|
+
list,
|
|
11142
|
+
keep,
|
|
11143
|
+
keys,
|
|
11144
|
+
slots
|
|
11145
|
+
});
|
|
11146
|
+
const size = list ? value.length : keys.length;
|
|
11147
|
+
for (let place = 0; place < size; place += 1) {
|
|
11148
|
+
const key = list ? place : keys[place];
|
|
11149
|
+
const item = list && !Object.hasOwn(value, place) ? MISSING : readField(value, key);
|
|
11150
|
+
const put = (part) => {
|
|
11151
|
+
slots[place] = part;
|
|
11152
|
+
};
|
|
11153
|
+
children.push({
|
|
11154
|
+
value: item,
|
|
11155
|
+
name: `${name}.${key}`,
|
|
11156
|
+
tried: false,
|
|
11157
|
+
nest: true,
|
|
11158
|
+
keep: false,
|
|
11159
|
+
put
|
|
11160
|
+
});
|
|
11161
|
+
}
|
|
11162
|
+
return at;
|
|
11163
|
+
};
|
|
11164
|
+
const open = (current) => {
|
|
11165
|
+
const at = items.length;
|
|
11166
|
+
index.set(current, index.size);
|
|
11167
|
+
const fields = [];
|
|
11168
|
+
const linked = [];
|
|
11169
|
+
for (const key of Object.keys(current)) {
|
|
11170
|
+
if (ownField(key)) continue;
|
|
11171
|
+
const value = readField(current, key);
|
|
11172
|
+
const term = value === MISSING ? void 0 : termOrNothing(machine, value);
|
|
11173
|
+
if (term === void 0) linked.push([key, value]);
|
|
11174
|
+
else fields.push([key, term]);
|
|
11175
|
+
}
|
|
11176
|
+
const node = {
|
|
11177
|
+
head: {
|
|
11178
|
+
name: String(current.name),
|
|
11179
|
+
message: String(current.message),
|
|
11180
|
+
...fields.length === 0 ? {} : { fields: Object.fromEntries(fields) }
|
|
11181
|
+
},
|
|
11182
|
+
keys: linked.map(([key]) => key),
|
|
11183
|
+
parts: [],
|
|
11184
|
+
hiddenKeys: [],
|
|
11185
|
+
hidden: []
|
|
11186
|
+
};
|
|
11187
|
+
items.push(node);
|
|
11188
|
+
const children = [];
|
|
11189
|
+
for (let place = 0; place < linked.length; place += 1) {
|
|
11190
|
+
const [key, value] = linked[place];
|
|
11191
|
+
const put = (part) => {
|
|
11192
|
+
node.parts[place] = part;
|
|
11193
|
+
};
|
|
11194
|
+
children.push({
|
|
11195
|
+
value,
|
|
11196
|
+
name: key,
|
|
11197
|
+
tried: true,
|
|
11198
|
+
nest: true,
|
|
11199
|
+
keep: false,
|
|
11200
|
+
put
|
|
11201
|
+
});
|
|
11202
|
+
}
|
|
11203
|
+
for (const key of HIDDEN) {
|
|
11204
|
+
if (Object.getOwnPropertyDescriptor(current, key)?.enumerable !== false) continue;
|
|
11205
|
+
const place = node.hiddenKeys.length;
|
|
11206
|
+
node.hiddenKeys.push(key);
|
|
11207
|
+
const value = readField(current, key);
|
|
11208
|
+
const put = (part) => {
|
|
11209
|
+
node.hidden[place] = part;
|
|
11210
|
+
};
|
|
11211
|
+
children.push({
|
|
11212
|
+
value,
|
|
11213
|
+
name: key,
|
|
11214
|
+
tried: false,
|
|
11215
|
+
nest: true,
|
|
11216
|
+
keep: Array.isArray(value),
|
|
11217
|
+
put
|
|
11218
|
+
});
|
|
11219
|
+
}
|
|
11220
|
+
if (Object.hasOwn(current, "cause")) {
|
|
11221
|
+
const put = (part) => {
|
|
11222
|
+
node.cause = part;
|
|
11223
|
+
};
|
|
11224
|
+
children.push({
|
|
11225
|
+
value: current.cause,
|
|
11226
|
+
name: "cause",
|
|
11227
|
+
tried: false,
|
|
11228
|
+
nest: false,
|
|
11229
|
+
keep: false,
|
|
11230
|
+
put
|
|
11231
|
+
});
|
|
11232
|
+
}
|
|
11233
|
+
later(children);
|
|
11234
|
+
return at;
|
|
11235
|
+
};
|
|
11236
|
+
let top = { none: "cause" };
|
|
11237
|
+
tasks.push({
|
|
11238
|
+
value: root,
|
|
11239
|
+
name: "cause",
|
|
11240
|
+
tried: true,
|
|
11241
|
+
nest: false,
|
|
11242
|
+
keep: false,
|
|
11243
|
+
put: (part) => {
|
|
11244
|
+
top = part;
|
|
11245
|
+
}
|
|
11246
|
+
});
|
|
11247
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
11248
|
+
const value = task.value;
|
|
11249
|
+
if (value === MISSING) {
|
|
11250
|
+
task.put({ none: task.name });
|
|
11251
|
+
continue;
|
|
11252
|
+
}
|
|
11253
|
+
const loop = index.get(value);
|
|
11254
|
+
if (loop !== void 0) {
|
|
11255
|
+
task.put({ loop });
|
|
11256
|
+
continue;
|
|
11257
|
+
}
|
|
11258
|
+
const term = task.tried || task.keep ? void 0 : termOrNothing(machine, value);
|
|
11259
|
+
if (term !== void 0) task.put({ value: term });
|
|
11260
|
+
else if (value instanceof Error) task.put({ node: open(value) });
|
|
11261
|
+
else if ((Array.isArray(value) || isRecord(value)) && (task.nest || holdsException(value))) {
|
|
11262
|
+
const held = holders.get(value);
|
|
11263
|
+
if (held === void 0) {
|
|
11264
|
+
const children = [];
|
|
11265
|
+
task.put({ holder: hold(task.name, value, task.keep, children) });
|
|
11266
|
+
later(children);
|
|
11267
|
+
} else {
|
|
11268
|
+
if (task.keep) items[held].keep = true;
|
|
11269
|
+
task.put({
|
|
11270
|
+
same: held,
|
|
11271
|
+
name: task.name
|
|
11272
|
+
});
|
|
11273
|
+
}
|
|
11274
|
+
} else task.put({ none: task.name });
|
|
11275
|
+
}
|
|
11276
|
+
const { formed, shares } = formsOf(items);
|
|
11277
|
+
const forms = [];
|
|
11278
|
+
const builtOf = (part) => {
|
|
11279
|
+
if ("none" in part) return { names: [part.none] };
|
|
11280
|
+
if ("node" in part) return {
|
|
11281
|
+
form: { exception: forms[part.node] },
|
|
11282
|
+
names: []
|
|
11283
|
+
};
|
|
11284
|
+
if ("holder" in part) return forms[part.holder];
|
|
11285
|
+
if ("same" in part) return formed[part.same] === true ? {
|
|
11286
|
+
form: { share: shares[part.same] },
|
|
11287
|
+
names: []
|
|
11288
|
+
} : { names: [part.name] };
|
|
11289
|
+
return {
|
|
11290
|
+
form: part,
|
|
11291
|
+
names: []
|
|
11292
|
+
};
|
|
11293
|
+
};
|
|
11294
|
+
for (let at = items.length - 1; at >= 0; at -= 1) {
|
|
11295
|
+
const item = items[at];
|
|
11296
|
+
if ("slots" in item) {
|
|
11297
|
+
if (formed[at] !== true) {
|
|
11298
|
+
forms[at] = { names: [item.name] };
|
|
11299
|
+
continue;
|
|
11300
|
+
}
|
|
11301
|
+
const built = Array.from(item.slots, builtOf);
|
|
11302
|
+
const form = item.list ? built.map((part) => part.form ?? { dropped: true }) : { record: Object.fromEntries(keptOf(item.keys, built)) };
|
|
11303
|
+
forms[at] = {
|
|
11304
|
+
form,
|
|
11305
|
+
names: built.flatMap((part) => part.names)
|
|
11306
|
+
};
|
|
11307
|
+
continue;
|
|
11308
|
+
}
|
|
11309
|
+
const { head, keys, parts, hiddenKeys, hidden, cause } = item;
|
|
11310
|
+
const linked = Array.from(parts, builtOf);
|
|
11311
|
+
const kept = Array.from(hidden, builtOf);
|
|
11312
|
+
const link = cause === void 0 ? { names: [] } : causeOf(builtOf(cause));
|
|
11313
|
+
const dropped = [
|
|
11314
|
+
...linked,
|
|
11315
|
+
...kept,
|
|
11316
|
+
link
|
|
11317
|
+
].flatMap((part) => part.names);
|
|
11318
|
+
const partsOf = keptOf(keys, linked);
|
|
11319
|
+
const hiddenOf = keptOf(hiddenKeys, kept);
|
|
11320
|
+
forms[at] = {
|
|
11321
|
+
...head,
|
|
11322
|
+
...partsOf.length === 0 ? {} : { parts: Object.fromEntries(partsOf) },
|
|
11323
|
+
...dropped.length === 0 ? {} : { dropped },
|
|
11324
|
+
...hiddenOf.length === 0 ? {} : { hidden: Object.fromEntries(hiddenOf) },
|
|
11325
|
+
...link.link
|
|
11326
|
+
};
|
|
11327
|
+
}
|
|
11328
|
+
return causeOf(builtOf(top));
|
|
11329
|
+
}
|
|
11330
|
+
/**
|
|
11331
|
+
* This function tells which containers of the walk of `causeJson` get a form, and it gives each such container its
|
|
11332
|
+
* index for `share` (D88). A container gets a form when it is a hidden list, or when an item has a form. A share has
|
|
11333
|
+
* the form of its container. Thus a cycle of containers without another form has no form. The index counts the
|
|
11334
|
+
* containers with a form in the order of the walk, as `decodeLog` meets them.
|
|
11335
|
+
*/
|
|
11336
|
+
function formsOf(items) {
|
|
11337
|
+
const formed = [];
|
|
11338
|
+
const users = /* @__PURE__ */ new Map();
|
|
11339
|
+
const work = [];
|
|
11340
|
+
for (let at = 0; at < items.length; at += 1) {
|
|
11341
|
+
const item = items[at];
|
|
11342
|
+
if (!("slots" in item)) continue;
|
|
11343
|
+
let seed = item.keep;
|
|
11344
|
+
for (const part of item.slots) {
|
|
11345
|
+
const on = "holder" in part ? part.holder : "same" in part ? part.same : void 0;
|
|
11346
|
+
if (on === void 0) seed ||= !("none" in part);
|
|
11347
|
+
else {
|
|
11348
|
+
const list = users.get(on);
|
|
11349
|
+
if (list === void 0) users.set(on, [at]);
|
|
11350
|
+
else list.push(at);
|
|
11351
|
+
}
|
|
11352
|
+
}
|
|
11353
|
+
if (seed) {
|
|
11354
|
+
formed[at] = true;
|
|
11355
|
+
work.push(at);
|
|
11356
|
+
}
|
|
11357
|
+
}
|
|
11358
|
+
for (let at = work.pop(); at !== void 0; at = work.pop()) for (const user of users.get(at) ?? []) {
|
|
11359
|
+
if (formed[user] === true) continue;
|
|
11360
|
+
formed[user] = true;
|
|
11361
|
+
work.push(user);
|
|
11362
|
+
}
|
|
11363
|
+
const shares = [];
|
|
11364
|
+
let count = 0;
|
|
11365
|
+
for (let at = 0; at < items.length; at += 1) {
|
|
11366
|
+
if (formed[at] !== true) continue;
|
|
11367
|
+
shares[at] = count;
|
|
11368
|
+
count += 1;
|
|
11369
|
+
}
|
|
11370
|
+
return {
|
|
11371
|
+
formed,
|
|
11372
|
+
shares
|
|
11373
|
+
};
|
|
11374
|
+
}
|
|
11375
|
+
/** This function gives the message of an exception, or a short text for another value. */
|
|
11376
|
+
function messageOf(error) {
|
|
11377
|
+
return error instanceof Error ? error.message : describe(error);
|
|
11378
|
+
}
|
|
11379
|
+
/** This function writes a value of a log as a term document, or throws a `TypeError` that names the value. */
|
|
11380
|
+
function termOf(machine, value, what) {
|
|
11381
|
+
try {
|
|
11382
|
+
return encodeValue(machine, value);
|
|
11383
|
+
} catch (error) {
|
|
11384
|
+
throw new TypeError(`encodeLog cannot write ${what()}: ${messageOf(error)}`);
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11387
|
+
/** This function writes an error value of a log as JSON, or throws a `TypeError` that names the error. */
|
|
11388
|
+
function errorJson(machine, error, what) {
|
|
11389
|
+
for (const key of Object.keys(error)) if (!errorField(key)) throw new TypeError(`encodeLog cannot write ${what()}: the error has the field ${key}, and the JSON form has no field for it.`);
|
|
11390
|
+
const base = {
|
|
11391
|
+
code: error.code,
|
|
11392
|
+
reason: error.reason,
|
|
11393
|
+
...error.place === void 0 ? {} : { place: error.place }
|
|
11394
|
+
};
|
|
11395
|
+
if (!Object.hasOwn(error, "cause")) return base;
|
|
11396
|
+
const cause = error.cause;
|
|
11397
|
+
try {
|
|
11398
|
+
return {
|
|
11399
|
+
...base,
|
|
11400
|
+
cause: encodeValue(machine, cause)
|
|
11401
|
+
};
|
|
11402
|
+
} catch (failure) {
|
|
11403
|
+
const { link, names } = causeJson(machine, cause);
|
|
11404
|
+
if (link === void 0) throw new TypeError(`encodeLog cannot write the cause of ${what()}: ${messageOf(failure)}`);
|
|
11405
|
+
return {
|
|
11406
|
+
...base,
|
|
11407
|
+
...link,
|
|
11408
|
+
...names.length === 0 ? {} : { dropped: names }
|
|
11409
|
+
};
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
/**
|
|
11413
|
+
* This function writes a log as JSON (RUN.REPLAY-JSON, D75). Then `JSON.stringify` gives the text of a golden file.
|
|
11414
|
+
*
|
|
11415
|
+
* - Each payload and each value of an outcome is a term document of `encodeValue`. Thus `NaN`, `-0` and `undefined`
|
|
11416
|
+
* stay exact, and a member of a domain with a codec is `ext.domain`.
|
|
11417
|
+
* - An error keeps its code, its reason, its place and its cause. A cause that `encodeValue` cannot write and that is
|
|
11418
|
+
* an exception keeps its name and its message in the field `exception`.
|
|
11419
|
+
* - An exception also keeps its own fields that `encodeValue` writes, and the chain of its causes (RUN.REPLAY-CAUSES).
|
|
11420
|
+
* The list `dropped` names each other own field. A cause that is an earlier exception of the chain is its index in `loop`.
|
|
11421
|
+
* - An exception keeps the hidden fields of the standard classes in `hidden`, for example `errors` of an
|
|
11422
|
+
* `AggregateError` (RUN.REPLAY-HIDDEN, D77). Each exception in them has the rule of a cause.
|
|
11423
|
+
* - An enumerable field without a term document that is an exception, a list or a plain record is in `parts`
|
|
11424
|
+
* (RUN.REPLAY-FIELDS, D78, D82). An item of a list without a form is the mark `dropped` at its place.
|
|
11425
|
+
* - A cause without a term document that is a list or a plain record with an exception is in `causeParts`
|
|
11426
|
+
* (RUN.REPLAY-CAUSE-PARTS, D87). The rule is the same for the cause of an exception and of an error value.
|
|
11427
|
+
* - A list or a record of parts that an earlier place of one error holds is the part `share` with its index
|
|
11428
|
+
* (RUN.REPLAY-SHARED, D88). This rule also covers a container that holds itself.
|
|
11429
|
+
* - An outcome that the log does not have stays absent.
|
|
11430
|
+
*
|
|
11431
|
+
* @param machine - The machine of the run, or only its domains.
|
|
11432
|
+
* @param log - The log of a recorder.
|
|
11433
|
+
* @returns The JSON form. `decodeLog` gives the log again.
|
|
11434
|
+
* @throws {TypeError} A value has no term document, for example a closure, a host thing, a symbol, or a value that no
|
|
11435
|
+
* domain with a codec holds. The message names the effect and its address. A part of an exception is not a refusal.
|
|
11436
|
+
*/
|
|
11437
|
+
function encodeLog(machine, log) {
|
|
11438
|
+
return {
|
|
11439
|
+
version: 1,
|
|
11440
|
+
effects: log.effects.map((entry) => {
|
|
11441
|
+
const at = (field) => () => `the ${field} of the effect ${entry.kind} at the address ${entry.address}`;
|
|
11442
|
+
const head = {
|
|
11443
|
+
address: entry.address,
|
|
11444
|
+
kind: entry.kind,
|
|
11445
|
+
payload: termOf(machine, entry.payload, at("payload"))
|
|
11446
|
+
};
|
|
11447
|
+
const outcome = entry.outcome;
|
|
11448
|
+
if (outcome === void 0) return {
|
|
11449
|
+
...head,
|
|
11450
|
+
waited: entry.waited
|
|
11451
|
+
};
|
|
11452
|
+
const json = outcome.ok ? {
|
|
11453
|
+
ok: true,
|
|
11454
|
+
value: termOf(machine, outcome.value, at("outcome"))
|
|
11455
|
+
} : {
|
|
11456
|
+
ok: false,
|
|
11457
|
+
error: errorJson(machine, outcome.error, at("outcome"))
|
|
11458
|
+
};
|
|
11459
|
+
return {
|
|
11460
|
+
...head,
|
|
11461
|
+
outcome: json,
|
|
11462
|
+
waited: entry.waited
|
|
11463
|
+
};
|
|
11464
|
+
}),
|
|
11465
|
+
settles: log.settles.map((settle) => ({
|
|
11466
|
+
all: settle.all,
|
|
11467
|
+
item: settle.item
|
|
11468
|
+
}))
|
|
11469
|
+
};
|
|
11470
|
+
}
|
|
11471
|
+
/** This function gives an exception with a name and a message. A standard name gives an instance of its class. */
|
|
11472
|
+
function exception(name, message) {
|
|
11473
|
+
const made = standard(name, message);
|
|
11474
|
+
if (made.name !== name) made.name = name;
|
|
11475
|
+
return made;
|
|
11476
|
+
}
|
|
11477
|
+
/**
|
|
11478
|
+
* This function gives an instance of the standard class with a name, or an `Error`. An `AggregateError` gets an empty
|
|
11479
|
+
* list `errors`, and a `SuppressedError` gets `undefined` for `error` and `suppressed`, as their constructors do.
|
|
11480
|
+
*/
|
|
11481
|
+
function standard(name, message) {
|
|
11482
|
+
switch (name) {
|
|
11483
|
+
case "TypeError": return new TypeError(message);
|
|
11484
|
+
case "RangeError": return new RangeError(message);
|
|
11485
|
+
case "SyntaxError": return new SyntaxError(message);
|
|
11486
|
+
case "ReferenceError": return new ReferenceError(message);
|
|
11487
|
+
case "AggregateError": return new AggregateError([], message);
|
|
11488
|
+
case "SuppressedError": return typeof SuppressedError === "function" ? new SuppressedError(void 0, void 0, message) : new Error(message);
|
|
11489
|
+
default: return new Error(message);
|
|
11490
|
+
}
|
|
11491
|
+
}
|
|
11492
|
+
/** This function gives an exception a field that is not enumerable, as the constructor of `Error` does for `cause`. */
|
|
11493
|
+
function hide(error, key, value) {
|
|
11494
|
+
Object.defineProperty(error, key, {
|
|
11495
|
+
value,
|
|
11496
|
+
writable: true,
|
|
11497
|
+
enumerable: false,
|
|
11498
|
+
configurable: true
|
|
11499
|
+
});
|
|
11500
|
+
}
|
|
11501
|
+
/** This function gives the failed result of a JSON form that `decodeLog` cannot read. */
|
|
11502
|
+
function bad(place, reason) {
|
|
11503
|
+
return fail("#VALUE!", `decodeLog cannot read the log: ${reason}`, { place });
|
|
11504
|
+
}
|
|
11505
|
+
/** This function reads a term document of a log again, with the formula kit and the domains of the machine. */
|
|
11506
|
+
function readTerm(kit, doc, place) {
|
|
11507
|
+
const value = decodeValue(kit, doc);
|
|
11508
|
+
return value.ok ? value : bad(place, `the term document gives ${value.error.code}: ${value.error.reason}`);
|
|
11509
|
+
}
|
|
11510
|
+
/** This function reads the JSON form of an error value again. */
|
|
11511
|
+
function readError(kit, data, place) {
|
|
11512
|
+
if (!isRecord(data) || typeof data["code"] !== "string" || !data["code"].startsWith("#") || typeof data["reason"] !== "string") return bad(place, "an error is a record with a code that starts with # and a reason.");
|
|
11513
|
+
for (const key of Object.keys(data)) if (!errorField(key) && key !== "exception" && key !== "causeParts" && key !== "dropped") return bad(place, `an error has no field ${key}.`);
|
|
11514
|
+
const keys = data["place"];
|
|
11515
|
+
if (keys !== void 0 && !(Array.isArray(keys) && keys.every((key) => isKey(key)))) return bad([...place, "place"], "a place is a list of texts and numbers.");
|
|
11516
|
+
const dropped = data["dropped"];
|
|
11517
|
+
if (dropped !== void 0 && !(Array.isArray(dropped) && dropped.every((name) => typeof name === "string"))) return bad([...place, "dropped"], "the list dropped holds texts.");
|
|
11518
|
+
const error = {
|
|
11519
|
+
code: data["code"],
|
|
11520
|
+
reason: data["reason"]
|
|
11521
|
+
};
|
|
11522
|
+
if (keys !== void 0) error.place = keys;
|
|
11523
|
+
const record = data;
|
|
11524
|
+
const links = LINKS.filter((key) => key !== "loop" && Object.hasOwn(record, key));
|
|
11525
|
+
if (links.length > 1) return bad(place, "an error has a cause, an exception or causeParts, not more than one.");
|
|
11526
|
+
const link = links[0];
|
|
11527
|
+
if (link === "cause") {
|
|
11528
|
+
const cause = readTerm(kit, data["cause"], [...place, "cause"]);
|
|
11529
|
+
if (!cause.ok) return cause;
|
|
11530
|
+
error.cause = cause.value;
|
|
11531
|
+
} else if (link !== void 0) {
|
|
11532
|
+
const read = readCause(kit, link, data[link], [...place, link]);
|
|
11533
|
+
if (!read.ok) return read;
|
|
11534
|
+
error.cause = read.value;
|
|
11535
|
+
}
|
|
11536
|
+
return ok(error);
|
|
11537
|
+
}
|
|
11538
|
+
/**
|
|
11539
|
+
* This function reads the JSON form of a cause again: an exception in `exception`, or a container in `causeParts`
|
|
11540
|
+
* (RUN.REPLAY-CAUSES, RUN.REPLAY-HIDDEN, RUN.REPLAY-FIELDS, D87). The walk has the order of `causeJson`, thus a loop
|
|
11541
|
+
* gives the same exception. A list or a record of parts gives a new list or plain record (D82). A share gives the same
|
|
11542
|
+
* list or record as its earlier place (D88). The walk is a loop, thus a long chain does not overflow the stack.
|
|
11543
|
+
*/
|
|
11544
|
+
function readCause(kit, kind, data, place) {
|
|
11545
|
+
const chain = [];
|
|
11546
|
+
const held = [];
|
|
11547
|
+
const tasks = [];
|
|
11548
|
+
const open = (at, where) => {
|
|
11549
|
+
if (!isRecord(at) || typeof at["name"] !== "string" || typeof at["message"] !== "string" || !Object.keys(at).every(exceptionField)) return bad(where, "an exception is a record with the texts name and message, and only the fields fields, parts, dropped, hidden, cause, exception, loop and causeParts.");
|
|
11550
|
+
const made = exception(at["name"], at["message"]);
|
|
11551
|
+
const fields = at["fields"];
|
|
11552
|
+
if (fields !== void 0) {
|
|
11553
|
+
if (!isRecord(fields)) return bad([...where, "fields"], "the fields of an exception are a record.");
|
|
11554
|
+
for (const key of Object.keys(fields)) {
|
|
11555
|
+
if (ownField(key)) return bad([
|
|
11556
|
+
...where,
|
|
11557
|
+
"fields",
|
|
11558
|
+
key
|
|
11559
|
+
], `the field ${key} of an exception is not in its fields.`);
|
|
11560
|
+
const value = readTerm(kit, fields[key], [
|
|
11561
|
+
...where,
|
|
11562
|
+
"fields",
|
|
11563
|
+
key
|
|
11564
|
+
]);
|
|
11565
|
+
if (!value.ok) return value;
|
|
11566
|
+
Object.defineProperty(made, key, {
|
|
11567
|
+
value: value.value,
|
|
11568
|
+
writable: true,
|
|
11569
|
+
enumerable: true,
|
|
11570
|
+
configurable: true
|
|
11571
|
+
});
|
|
11572
|
+
}
|
|
11573
|
+
}
|
|
11574
|
+
const dropped = at["dropped"];
|
|
11575
|
+
if (dropped !== void 0 && !(Array.isArray(dropped) && dropped.every((name) => typeof name === "string"))) return bad([...where, "dropped"], "the list dropped holds texts.");
|
|
11576
|
+
chain.push(made);
|
|
11577
|
+
const children = [];
|
|
11578
|
+
const taken = (key) => isRecord(fields) && Object.hasOwn(fields, key);
|
|
11579
|
+
const parts = at["parts"];
|
|
11580
|
+
if (parts !== void 0) {
|
|
11581
|
+
if (!isRecord(parts)) return bad([...where, "parts"], "the parts of an exception are a record.");
|
|
11582
|
+
for (const key of Object.keys(parts)) {
|
|
11583
|
+
if (ownField(key) || taken(key)) return bad([
|
|
11584
|
+
...where,
|
|
11585
|
+
"parts",
|
|
11586
|
+
key
|
|
11587
|
+
], `the field ${key} of an exception is not in its parts.`);
|
|
11588
|
+
const show = (value) => {
|
|
11589
|
+
Object.defineProperty(made, key, {
|
|
11590
|
+
value,
|
|
11591
|
+
writable: true,
|
|
11592
|
+
enumerable: true,
|
|
11593
|
+
configurable: true
|
|
11594
|
+
});
|
|
11595
|
+
};
|
|
11596
|
+
const read = slotTask(parts[key], [
|
|
11597
|
+
...where,
|
|
11598
|
+
"parts",
|
|
11599
|
+
key
|
|
11600
|
+
], false, show);
|
|
11601
|
+
if (!read.ok) return read;
|
|
11602
|
+
show(void 0);
|
|
11603
|
+
children.push(read.value);
|
|
11604
|
+
}
|
|
11605
|
+
}
|
|
11606
|
+
const hidden = at["hidden"];
|
|
11607
|
+
if (hidden !== void 0) {
|
|
11608
|
+
if (!isRecord(hidden)) return bad([...where, "hidden"], "the hidden fields of an exception are a record.");
|
|
11609
|
+
for (const key of Object.keys(hidden)) if (!HIDDEN.includes(key) || taken(key) || isRecord(parts) && Object.hasOwn(parts, key)) return bad([
|
|
11610
|
+
...where,
|
|
11611
|
+
"hidden",
|
|
11612
|
+
key
|
|
11613
|
+
], `an exception has no hidden field ${key}.`);
|
|
11614
|
+
for (const key of HIDDEN) {
|
|
11615
|
+
if (!Object.hasOwn(hidden, key)) continue;
|
|
11616
|
+
const read = slotTask(hidden[key], [
|
|
11617
|
+
...where,
|
|
11618
|
+
"hidden",
|
|
11619
|
+
key
|
|
11620
|
+
], false, (value) => hide(made, key, value));
|
|
11621
|
+
if (!read.ok) return read;
|
|
11622
|
+
children.push(read.value);
|
|
11623
|
+
}
|
|
11624
|
+
}
|
|
11625
|
+
const record = at;
|
|
11626
|
+
const links = LINKS.filter((key) => Object.hasOwn(record, key));
|
|
11627
|
+
if (links.length > 1) return bad(where, "an exception has a cause, an exception, a loop or causeParts, not more than one.");
|
|
11628
|
+
const link = links[0];
|
|
11629
|
+
if (link !== void 0) {
|
|
11630
|
+
const read = linkTask(link, record[link], [...where, link], (value) => hide(made, "cause", value));
|
|
11631
|
+
if (!read.ok) return read;
|
|
11632
|
+
children.push(read.value);
|
|
11633
|
+
}
|
|
11634
|
+
for (let index = children.length - 1; index >= 0; index -= 1) tasks.push(children[index]);
|
|
11635
|
+
return ok(made);
|
|
11636
|
+
};
|
|
11637
|
+
let cause;
|
|
11638
|
+
const root = linkTask(kind, data, place, (value) => {
|
|
11639
|
+
cause = value;
|
|
11640
|
+
});
|
|
11641
|
+
if (!root.ok) return root;
|
|
11642
|
+
tasks.push(root.value);
|
|
11643
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
11644
|
+
const { kind, data: part, where } = task;
|
|
11645
|
+
if (kind === "dropped") continue;
|
|
11646
|
+
if (kind === "loop") {
|
|
11647
|
+
if (typeof part !== "number" || !Number.isInteger(part) || part < 0 || part >= chain.length) return bad(where, "a loop is the index of an exception of the chain until this one.");
|
|
11648
|
+
task.put(chain[part]);
|
|
11649
|
+
continue;
|
|
11650
|
+
}
|
|
11651
|
+
if (kind === "share") {
|
|
11652
|
+
if (typeof part !== "number" || !Number.isInteger(part) || part < 0 || part >= held.length) return bad(where, "a share is the index of a list or a record of parts of the walk until this one.");
|
|
11653
|
+
task.put(held[part]);
|
|
11654
|
+
continue;
|
|
11655
|
+
}
|
|
11656
|
+
if (kind === "list" || kind === "record") {
|
|
11657
|
+
const items = kind === "list" ? listTasks(part, where, held, task.put) : recordTasks(part, where, held, task.put);
|
|
11658
|
+
if (!items.ok) return items;
|
|
11659
|
+
for (let index = items.value.length - 1; index >= 0; index -= 1) tasks.push(items.value[index]);
|
|
11660
|
+
continue;
|
|
11661
|
+
}
|
|
11662
|
+
const value = kind === "exception" ? open(part, where) : readTerm(kit, part, where);
|
|
11663
|
+
if (!value.ok) return value;
|
|
11664
|
+
task.put(value.value);
|
|
11665
|
+
}
|
|
11666
|
+
return ok(cause);
|
|
11667
|
+
}
|
|
11668
|
+
/**
|
|
11669
|
+
* This function reads the field of a cause: `cause`, `exception`, `loop` or `causeParts`. A value of `causeParts` is a
|
|
11670
|
+
* list of parts, the part `record` or the part `share`, and no other part (D87, D88).
|
|
11671
|
+
*/
|
|
11672
|
+
function linkTask(kind, data, where, put) {
|
|
11673
|
+
if (kind !== "causeParts") return ok({
|
|
11674
|
+
kind,
|
|
11675
|
+
data,
|
|
11676
|
+
where,
|
|
11677
|
+
put
|
|
11678
|
+
});
|
|
11679
|
+
if (Array.isArray(data)) return ok({
|
|
11680
|
+
kind: "list",
|
|
11681
|
+
data,
|
|
11682
|
+
where,
|
|
11683
|
+
put
|
|
11684
|
+
});
|
|
11685
|
+
const key = isRecord(data) && Object.keys(data).length === 1 ? Object.keys(data)[0] : void 0;
|
|
11686
|
+
if (isRecord(data) && (key === "record" || key === "share")) return ok({
|
|
11687
|
+
kind: key,
|
|
11688
|
+
data: data[key],
|
|
11689
|
+
where: [...where, key],
|
|
11690
|
+
put
|
|
11691
|
+
});
|
|
11692
|
+
return bad(where, "a cause of parts is a list of parts, the part record or the part share.");
|
|
11693
|
+
}
|
|
11694
|
+
/**
|
|
11695
|
+
* This function reads one value of a field in `parts`, in `hidden` or in a record of parts. A list is a list of parts,
|
|
11696
|
+
* and another value is one part.
|
|
11697
|
+
*/
|
|
11698
|
+
function slotTask(data, where, item, put) {
|
|
11699
|
+
return Array.isArray(data) ? ok({
|
|
11700
|
+
kind: "list",
|
|
11701
|
+
data,
|
|
11702
|
+
where,
|
|
11703
|
+
put
|
|
11704
|
+
}) : partTask(data, where, item, put);
|
|
11705
|
+
}
|
|
11706
|
+
/**
|
|
11707
|
+
* This function gives a list of parts its length at once, and it gives the tasks of its items. Thus the mark `dropped`
|
|
11708
|
+
* leaves a hole at its place, and each later item keeps its index (D78). The list goes in `held` for a share (D88).
|
|
11709
|
+
*/
|
|
11710
|
+
function listTasks(data, where, held, put) {
|
|
11711
|
+
const items = data;
|
|
11712
|
+
const list = [];
|
|
11713
|
+
list.length = items.length;
|
|
11714
|
+
held.push(list);
|
|
11715
|
+
put(list);
|
|
11716
|
+
const tasks = [];
|
|
11717
|
+
for (let item = 0; item < items.length; item += 1) {
|
|
11718
|
+
const task = slotTask(items[item], [...where, item], true, (value) => {
|
|
11719
|
+
list[item] = value;
|
|
11720
|
+
});
|
|
11721
|
+
if (!task.ok) return task;
|
|
11722
|
+
tasks.push(task.value);
|
|
11723
|
+
}
|
|
11724
|
+
return ok(tasks);
|
|
11725
|
+
}
|
|
11726
|
+
/**
|
|
11727
|
+
* This function gives a record of parts a new plain record (D82). It gives the tasks of its fields in the order of
|
|
11728
|
+
* the keys. Each key gets its place at once, thus the record keeps the order of the keys. The record goes in `held`
|
|
11729
|
+
* for a share (D88).
|
|
11730
|
+
*/
|
|
11731
|
+
function recordTasks(data, where, held, put) {
|
|
11732
|
+
if (!isRecord(data)) return bad(where, "a record of parts is a record.");
|
|
11733
|
+
const record = {};
|
|
11734
|
+
held.push(record);
|
|
11735
|
+
put(record);
|
|
11736
|
+
const tasks = [];
|
|
11737
|
+
for (const key of Object.keys(data)) {
|
|
11738
|
+
const show = (value) => {
|
|
11739
|
+
Object.defineProperty(record, key, {
|
|
11740
|
+
value,
|
|
11741
|
+
writable: true,
|
|
11742
|
+
enumerable: true,
|
|
11743
|
+
configurable: true
|
|
11744
|
+
});
|
|
11745
|
+
};
|
|
11746
|
+
const task = slotTask(data[key], [...where, key], false, show);
|
|
11747
|
+
if (!task.ok) return task;
|
|
11748
|
+
show(void 0);
|
|
11749
|
+
tasks.push(task.value);
|
|
11750
|
+
}
|
|
11751
|
+
return ok(tasks);
|
|
11752
|
+
}
|
|
11753
|
+
/**
|
|
11754
|
+
* This function reads one part: a record with only one of the fields `value`, `exception`, `loop`, `record` and
|
|
11755
|
+
* `share`. An item of a list can also be the mark `{ "dropped": true }`.
|
|
11756
|
+
*/
|
|
11757
|
+
function partTask(data, where, item, put) {
|
|
11758
|
+
const keys = isRecord(data) ? Object.keys(data) : [];
|
|
11759
|
+
const kind = keys[0];
|
|
11760
|
+
const mark = kind === "dropped" && item && isRecord(data) && data[kind] === true;
|
|
11761
|
+
if (!isRecord(data) || keys.length !== 1 || kind === void 0 || !(PARTS$1.includes(kind) || mark)) return bad(where, "a field of parts is a part or a list of parts. A part has only one of the fields value, exception, loop, record and share, or an item is the mark dropped.");
|
|
11762
|
+
return ok({
|
|
11763
|
+
kind,
|
|
11764
|
+
data: data[kind],
|
|
11765
|
+
where: [...where, kind],
|
|
11766
|
+
put
|
|
11767
|
+
});
|
|
11768
|
+
}
|
|
11769
|
+
/** The fields of the JSON form of an exception that give its cause. */
|
|
11770
|
+
const LINKS = /* @__PURE__ */ Object.freeze([
|
|
11771
|
+
"cause",
|
|
11772
|
+
"exception",
|
|
11773
|
+
"loop",
|
|
11774
|
+
"causeParts"
|
|
11775
|
+
]);
|
|
11776
|
+
/** The fields of the JSON form of a part of a field in `parts` or `hidden`. */
|
|
11777
|
+
const PARTS$1 = /* @__PURE__ */ Object.freeze([
|
|
11778
|
+
"value",
|
|
11779
|
+
"exception",
|
|
11780
|
+
"loop",
|
|
11781
|
+
"record",
|
|
11782
|
+
"share"
|
|
11783
|
+
]);
|
|
11784
|
+
/** This function reads the JSON form of one effect again. */
|
|
11785
|
+
function readEntry(kit, data, place) {
|
|
11786
|
+
if (!isRecord(data) || typeof data["address"] !== "string" || typeof data["kind"] !== "string" || typeof data["waited"] !== "boolean" || !Object.hasOwn(data, "payload")) return bad(place, "an effect is a record with the texts address and kind, a payload, and the truth value waited.");
|
|
11787
|
+
for (const key of Object.keys(data)) if (key !== "address" && key !== "kind" && key !== "payload" && key !== "outcome" && key !== "waited") return bad(place, `an effect has no field ${key}.`);
|
|
11788
|
+
const payload = readTerm(kit, data["payload"], [...place, "payload"]);
|
|
11789
|
+
if (!payload.ok) return payload;
|
|
11790
|
+
const head = {
|
|
11791
|
+
address: data["address"],
|
|
11792
|
+
kind: data["kind"],
|
|
11793
|
+
payload: payload.value,
|
|
11794
|
+
waited: data["waited"]
|
|
11795
|
+
};
|
|
11796
|
+
if (!Object.hasOwn(data, "outcome")) return ok(head);
|
|
11797
|
+
const raw = data["outcome"];
|
|
11798
|
+
const at = [...place, "outcome"];
|
|
11799
|
+
if (isRecord(raw) && raw["ok"] === true && Object.hasOwn(raw, "value") && Object.keys(raw).length === 2) {
|
|
11800
|
+
const value = readTerm(kit, raw["value"], [...at, "value"]);
|
|
11801
|
+
return value.ok ? ok({
|
|
11802
|
+
...head,
|
|
11803
|
+
outcome: ok(value.value)
|
|
11804
|
+
}) : value;
|
|
11805
|
+
}
|
|
11806
|
+
if (isRecord(raw) && raw["ok"] === false && Object.keys(raw).length === 2) {
|
|
11807
|
+
const error = readError(kit, raw["error"], [...at, "error"]);
|
|
11808
|
+
return error.ok ? ok({
|
|
11809
|
+
...head,
|
|
11810
|
+
outcome: {
|
|
11811
|
+
ok: false,
|
|
11812
|
+
error: error.value
|
|
11813
|
+
}
|
|
11814
|
+
}) : error;
|
|
11815
|
+
}
|
|
11816
|
+
return bad(at, "an outcome is a record with ok true and a value, or with ok false and an error.");
|
|
11817
|
+
}
|
|
11818
|
+
/**
|
|
11819
|
+
* This function reads the JSON form of a log again (RUN.REPLAY-JSON). It reads each term document with the formula kit
|
|
11820
|
+
* and the domains of the machine. A cause in the field `exception` becomes an exception with that name and message.
|
|
11821
|
+
* The exception gets its fields and the chain of its causes again, also a cycle (RUN.REPLAY-CAUSES). A list gets a
|
|
11822
|
+
* hole at the place of the mark `dropped`, thus each later item keeps its index (D78). Each share of a list or a record
|
|
11823
|
+
* gives the same object (RUN.REPLAY-SHARED).
|
|
11824
|
+
*
|
|
11825
|
+
* @param machine - The machine of the run, or only its domains.
|
|
11826
|
+
* @param data - The JSON form, for example from `JSON.parse` of a golden file.
|
|
11827
|
+
* @returns The log for `replayer`, or `#VALUE!` with the place of the first field that it cannot read.
|
|
11828
|
+
*/
|
|
11829
|
+
function decodeLog(machine, data) {
|
|
11830
|
+
if (!isRecord(data) || data["version"] !== 1) return bad([], "the form is a record with the version 1.");
|
|
11831
|
+
const list = data["effects"];
|
|
11832
|
+
const order = data["settles"];
|
|
11833
|
+
if (!Array.isArray(list) || !Array.isArray(order) || Object.keys(data).length !== 3) return bad([], "the form has only the version and the lists effects and settles.");
|
|
11834
|
+
const domains = machine.domains;
|
|
11835
|
+
const kit = {
|
|
11836
|
+
...formulaMachine(),
|
|
11837
|
+
...domains === void 0 ? {} : { domains }
|
|
11838
|
+
};
|
|
11839
|
+
const effects = [];
|
|
11840
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
11841
|
+
const entry = readEntry(kit, list[index], ["effects", index]);
|
|
11842
|
+
if (!entry.ok) return entry;
|
|
11843
|
+
effects.push(entry.value);
|
|
11844
|
+
}
|
|
11845
|
+
const settles = [];
|
|
11846
|
+
for (let index = 0; index < order.length; index += 1) {
|
|
11847
|
+
const settle = order[index];
|
|
11848
|
+
if (!isRecord(settle) || typeof settle["all"] !== "string" || !Number.isInteger(settle["item"]) || Object.keys(settle).length !== 2) return bad(["settles", index], "a settle is a record with only the text all and the whole number item.");
|
|
11849
|
+
settles.push({
|
|
11850
|
+
all: settle["all"],
|
|
11851
|
+
item: settle["item"]
|
|
11852
|
+
});
|
|
11853
|
+
}
|
|
11854
|
+
return ok({
|
|
11855
|
+
effects,
|
|
11856
|
+
settles
|
|
11857
|
+
});
|
|
11858
|
+
}
|
|
9953
11859
|
//#endregion
|
|
9954
11860
|
//#region src/agree.ts
|
|
9955
11861
|
/** The tiers that `agree` compares when its options name none. */
|
|
@@ -10221,18 +12127,12 @@ function agree(machine, doc, context = makeContext(), options = {}) {
|
|
|
10221
12127
|
function watched(perform) {
|
|
10222
12128
|
const effects = [];
|
|
10223
12129
|
const settles = [];
|
|
10224
|
-
const
|
|
10225
|
-
const settled = (all, item) => {
|
|
12130
|
+
const journal = relay(perform, (all, item) => {
|
|
10226
12131
|
settles.push({
|
|
10227
12132
|
all,
|
|
10228
12133
|
item
|
|
10229
12134
|
});
|
|
10230
|
-
|
|
10231
|
-
};
|
|
10232
|
-
const journal = inner?.next === void 0 ? { settled } : {
|
|
10233
|
-
settled,
|
|
10234
|
-
next: (all, waiting) => inner.next?.(all, waiting)
|
|
10235
|
-
};
|
|
12135
|
+
});
|
|
10236
12136
|
const host = (effect) => {
|
|
10237
12137
|
effects.push(effect);
|
|
10238
12138
|
return perform(effect);
|
|
@@ -10254,6 +12154,82 @@ function settlesDiffer(a, b) {
|
|
|
10254
12154
|
if (x?.all !== y?.all || x?.item !== y?.item) return `The wait ${index} settles ${settleText(x)} in the plan, and ${settleText(y)} in the tier.`;
|
|
10255
12155
|
}
|
|
10256
12156
|
}
|
|
12157
|
+
/** The roots beside `self` that a value without static reads keeps whole (READS.KEPT). */
|
|
12158
|
+
const KEPT_ROOTS = /* @__PURE__ */ Object.freeze([
|
|
12159
|
+
"names",
|
|
12160
|
+
"world",
|
|
12161
|
+
"event",
|
|
12162
|
+
"caps"
|
|
12163
|
+
]);
|
|
12164
|
+
/**
|
|
12165
|
+
* This function tells if the plan does not know the reads of a run. A value without static reads keeps each root
|
|
12166
|
+
* (READS.KEPT), thus the list holds a kept read of each whole root.
|
|
12167
|
+
*/
|
|
12168
|
+
function readsUnknown(reads) {
|
|
12169
|
+
return KEPT_ROOTS.every((root) => reads.some((read) => read.kept === true && read.path.length === 1 && read.path[0] === root));
|
|
12170
|
+
}
|
|
12171
|
+
/**
|
|
12172
|
+
* This function gives the reads that `agreeRun` compares: the free reads of the resolutions (RUN.AGREES-READS). A
|
|
12173
|
+
* kept read is the bound of the plan for the later reads of a value, and the reference has no such bound.
|
|
12174
|
+
*/
|
|
12175
|
+
function resolutions(reads) {
|
|
12176
|
+
return reads.filter((read) => read.free && read.kept !== true);
|
|
12177
|
+
}
|
|
12178
|
+
function pathText(path) {
|
|
12179
|
+
if (path === void 0) return "no read";
|
|
12180
|
+
try {
|
|
12181
|
+
return JSON.stringify(path);
|
|
12182
|
+
} catch {
|
|
12183
|
+
return path.map(String).join(", ");
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
/**
|
|
12187
|
+
* This function gives the rule of a comparison of reads. The rule of the caller answers first. Each tier makes its
|
|
12188
|
+
* own nodes of a tree and its own adopted things. Thus two host things are the same when they have the same label,
|
|
12189
|
+
* and they are nodes or have the same JSON object.
|
|
12190
|
+
*/
|
|
12191
|
+
function sameThing(equal) {
|
|
12192
|
+
return (a, b) => {
|
|
12193
|
+
const told = equal?.(a, b);
|
|
12194
|
+
if (told !== void 0) return told;
|
|
12195
|
+
if (!isHostThing(a) || !isHostThing(b)) return void 0;
|
|
12196
|
+
if (a.label !== b.label) return false;
|
|
12197
|
+
if (a.label.startsWith("node ")) return true;
|
|
12198
|
+
try {
|
|
12199
|
+
return Object.is(a.toJson(), b.toJson());
|
|
12200
|
+
} catch {
|
|
12201
|
+
return false;
|
|
12202
|
+
}
|
|
12203
|
+
};
|
|
12204
|
+
}
|
|
12205
|
+
/** This function gives the difference of what two reads reached, or `undefined` when they agree. */
|
|
12206
|
+
function reachedDiffers(a, b, equal, strict) {
|
|
12207
|
+
if (a.ok && b.ok) return sameValue(a.value, b.value, equal) ? void 0 : "The read reaches another value in the tier.";
|
|
12208
|
+
if (!a.ok && !b.ok) {
|
|
12209
|
+
if (a.error.code !== b.error.code) return `The read gives ${a.error.code} in the plan, and ${b.error.code} in the tier.`;
|
|
12210
|
+
return strict && a.error.reason !== b.error.reason ? `The read gives ${a.error.code} with another reason in the tier: "${b.error.reason}" for "${a.error.reason}".` : void 0;
|
|
12211
|
+
}
|
|
12212
|
+
if (!b.ok) return `The read reaches a value in the plan, and ${b.error.code} in the tier.`;
|
|
12213
|
+
return `The read gives ${a.error.code} in the plan, and a value in the tier.`;
|
|
12214
|
+
}
|
|
12215
|
+
/**
|
|
12216
|
+
* This function gives the first difference of the reads of the plan and of a tier, or `undefined` when they agree
|
|
12217
|
+
* (RUN.AGREES-READS). Only the free reads of the resolutions compare, in their order. When the plan does not know its
|
|
12218
|
+
* reads, the function compares nothing.
|
|
12219
|
+
*/
|
|
12220
|
+
function readsDiffer(plan, tier, options) {
|
|
12221
|
+
if (readsUnknown(plan)) return void 0;
|
|
12222
|
+
const a = resolutions(plan);
|
|
12223
|
+
const b = resolutions(tier);
|
|
12224
|
+
const equal = sameThing(options.equal);
|
|
12225
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
12226
|
+
const x = a[index];
|
|
12227
|
+
const y = b[index];
|
|
12228
|
+
if (x === void 0 || y === void 0 || !sameValue(x.path, y.path, equal)) return `The free read ${index} is ${pathText(x?.path)} in the plan, and ${pathText(y?.path)} in the tier.`;
|
|
12229
|
+
const why = reachedDiffers(x.reached, y.reached, equal, options.strict === true);
|
|
12230
|
+
if (why !== void 0) return `The free read ${index} of ${pathText(x.path)} differs. ${why}`;
|
|
12231
|
+
}
|
|
12232
|
+
}
|
|
10257
12233
|
/**
|
|
10258
12234
|
* This function compares the run of a document in a plan with its run in other tiers (RUN.AGREES). First the plan
|
|
10259
12235
|
* starts the run with a recorder around the host. Then each tier replays the log of that run. These tiers are the
|
|
@@ -10262,6 +12238,13 @@ function settlesDiffer(a, b) {
|
|
|
10262
12238
|
* The rules of `agree` compare the results and the effects, with their addresses and outcomes. A tier must also
|
|
10263
12239
|
* settle the waits of each `all` in the order of the plan, thus a replay keeps the order of the waits.
|
|
10264
12240
|
*
|
|
12241
|
+
* The reference run must also give the free reads of the plan, in their order (RUN.AGREES-READS). Another replay of
|
|
12242
|
+
* the plan records these reads. A kept read does not compare. When the plan keeps each root, its reads are not known,
|
|
12243
|
+
* and no read compares.
|
|
12244
|
+
*
|
|
12245
|
+
* One replayer of the log serves each tier and the replay for the reads (RUN.AGREES-ONE-REPLAYER). The replayer keeps
|
|
12246
|
+
* no state of a run (RUN.REPLAY-RUNS), thus each tier replays the same log as with a replayer of its own.
|
|
12247
|
+
*
|
|
10265
12248
|
* @param machine - The world, the evaluators and the runners.
|
|
10266
12249
|
* @param doc - The document.
|
|
10267
12250
|
* @param context - The names and the capabilities at the root.
|
|
@@ -10285,17 +12268,24 @@ async function agreeRun(machine, doc, context = makeContext(), options = {}) {
|
|
|
10285
12268
|
const result = await plan.run(context, { perform: first.perform });
|
|
10286
12269
|
const saw = observe?.();
|
|
10287
12270
|
const log = recording.log();
|
|
12271
|
+
let planReads;
|
|
12272
|
+
let compared;
|
|
12273
|
+
const replay = replayer(log);
|
|
10288
12274
|
for (const tier of options.tiers ?? DEFAULT_TIERS) {
|
|
10289
12275
|
if (tier === "fixed") continue;
|
|
10290
|
-
const
|
|
10291
|
-
const host = { perform:
|
|
12276
|
+
const tierReplay = watched(replay);
|
|
12277
|
+
const host = { perform: tierReplay.perform };
|
|
10292
12278
|
let other;
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
12279
|
+
let tierReads;
|
|
12280
|
+
if (tier === "reference") {
|
|
12281
|
+
tierReads = [];
|
|
12282
|
+
other = await referenceRun(machine, doc, context, {
|
|
12283
|
+
...declared,
|
|
12284
|
+
...host,
|
|
12285
|
+
reads: tierReads,
|
|
12286
|
+
...options.definitions === void 0 ? {} : { definitions: options.definitions }
|
|
12287
|
+
});
|
|
12288
|
+
} else if (tier === "traced") other = (await run(machine, doc, context, {
|
|
10299
12289
|
...declared,
|
|
10300
12290
|
...host
|
|
10301
12291
|
})).result;
|
|
@@ -10307,7 +12297,20 @@ async function agreeRun(machine, doc, context = makeContext(), options = {}) {
|
|
|
10307
12297
|
const compareCalls = observe !== void 0 && tier !== "folds";
|
|
10308
12298
|
const calls = observe?.();
|
|
10309
12299
|
if (overflowed(result) || overflowed(other)) continue;
|
|
10310
|
-
|
|
12300
|
+
let why = resultDiffers(result, other, options) ?? effectsDiffer(first.effects, tierReplay.effects, options) ?? settlesDiffer(first.settles, tierReplay.settles) ?? (compareCalls && !sameValue(saw, calls, options.equal) ? "The host saw other calls in the tier." : void 0);
|
|
12301
|
+
if (why === void 0 && tierReads !== void 0) {
|
|
12302
|
+
if (planReads === void 0) {
|
|
12303
|
+
const reads = [];
|
|
12304
|
+
await plan.run(context, {
|
|
12305
|
+
perform: replay,
|
|
12306
|
+
reads
|
|
12307
|
+
});
|
|
12308
|
+
observe?.();
|
|
12309
|
+
planReads = reads;
|
|
12310
|
+
}
|
|
12311
|
+
why = readsDiffer(planReads, tierReads, options);
|
|
12312
|
+
if (why === void 0 && !readsUnknown(planReads)) compared = resolutions(planReads);
|
|
12313
|
+
}
|
|
10311
12314
|
if (why !== void 0) return {
|
|
10312
12315
|
ok: false,
|
|
10313
12316
|
tier,
|
|
@@ -10316,10 +12319,15 @@ async function agreeRun(machine, doc, context = makeContext(), options = {}) {
|
|
|
10316
12319
|
reason: why
|
|
10317
12320
|
};
|
|
10318
12321
|
}
|
|
10319
|
-
return {
|
|
12322
|
+
return compared === void 0 ? {
|
|
10320
12323
|
ok: true,
|
|
10321
12324
|
result,
|
|
10322
12325
|
effects: first.effects
|
|
12326
|
+
} : {
|
|
12327
|
+
ok: true,
|
|
12328
|
+
result,
|
|
12329
|
+
effects: first.effects,
|
|
12330
|
+
reads: compared
|
|
10323
12331
|
};
|
|
10324
12332
|
}
|
|
10325
12333
|
//#endregion
|
|
@@ -10738,7 +12746,8 @@ const ORDER = [
|
|
|
10738
12746
|
"C7",
|
|
10739
12747
|
"C8",
|
|
10740
12748
|
"local",
|
|
10741
|
-
"agree"
|
|
12749
|
+
"agree",
|
|
12750
|
+
"truth"
|
|
10742
12751
|
];
|
|
10743
12752
|
/** A function in the results of the probe parts. It gives the count of its arguments. */
|
|
10744
12753
|
const PROBE_FN = (...args) => args.length;
|
|
@@ -11616,14 +13625,41 @@ function examine(env, sample, contract) {
|
|
|
11616
13625
|
if (env.runner) for (const scenario of scenariosOf(env, sample, contract, true)) checkShot(env, sample, contract, scenario, shoot(env, code, sample, scenario));
|
|
11617
13626
|
}
|
|
11618
13627
|
/**
|
|
13628
|
+
* This function checks the declared truth of a profile (D86). The function `truthy` must give `true` or `false` for
|
|
13629
|
+
* each member of `truthType`, and it must not throw. The members come from the lists `values` of the domains of the
|
|
13630
|
+
* type. A type without such a list gets the values of the results of the palette that are its members.
|
|
13631
|
+
*/
|
|
13632
|
+
function checkTruth(env, code, probe) {
|
|
13633
|
+
const truth = declaredTruthOf(code);
|
|
13634
|
+
if (truth === void 0) return;
|
|
13635
|
+
const domains = env.machine.domains;
|
|
13636
|
+
if (typeIssues(truth.truthType, domains).length > 0) return;
|
|
13637
|
+
const samples = env.results.flatMap((result) => result.ok ? [result.value] : []);
|
|
13638
|
+
const of = (value) => `the member ${valueText(value)} of the truth type ${typeText(truth.truthType)}`;
|
|
13639
|
+
for (const value of membersOf(truth.truthType, domains, samples)) {
|
|
13640
|
+
let given;
|
|
13641
|
+
try {
|
|
13642
|
+
given = truth.truthy(value);
|
|
13643
|
+
} catch (error) {
|
|
13644
|
+
report(env, "truth", `The function truthy of the profile throws for ${of(value)}: ${describe(error)}`, () => probe(value));
|
|
13645
|
+
return;
|
|
13646
|
+
}
|
|
13647
|
+
if (typeof given === "boolean") continue;
|
|
13648
|
+
report(env, "truth", `The function truthy of the profile gives ${given === void 0 ? "no truth" : valueText(given)} for ${of(value)}. A truth is true or false.`, () => probe(value));
|
|
13649
|
+
return;
|
|
13650
|
+
}
|
|
13651
|
+
}
|
|
13652
|
+
/**
|
|
11619
13653
|
* This function examines one evaluator or runner of a machine against the obligations of host code (K92, D36). It
|
|
11620
13654
|
* starts the code directly, without the kernel, with probe parts and a probe runtime. It generates probe documents from
|
|
11621
13655
|
* the contract of each kind that names the code, and it gives the probe parts chosen and random results.
|
|
11622
13656
|
*
|
|
11623
13657
|
* The checks follow the obligations C1 to C8 of `docs/SEMANTICS.md`. Two more checks follow the premises of the
|
|
11624
13658
|
* simulation of the plan. The check `local` finds state that stays from one call to the next. The check `agree` compares
|
|
11625
|
-
* compiled code with its definition. The
|
|
11626
|
-
*
|
|
13659
|
+
* compiled code with its definition. The finding of an obligation names the first probe that breaks it. An empty list
|
|
13660
|
+
* means that each probe conforms.
|
|
13661
|
+
*
|
|
13662
|
+
* The check `truth` tests the declared truth of a formula profile (D86).
|
|
11627
13663
|
*
|
|
11628
13664
|
* @param machine - The machine with the code and the world of the kinds.
|
|
11629
13665
|
* @param name - The name of the evaluator or of the runner. A runner wins over an evaluator of the same name.
|
|
@@ -11654,14 +13690,34 @@ function conforms(machine, name, options = {}) {
|
|
|
11654
13690
|
found: /* @__PURE__ */ new Map()
|
|
11655
13691
|
};
|
|
11656
13692
|
const types = options.types ?? typesNaming(machine.world, name, verb);
|
|
13693
|
+
let first;
|
|
11657
13694
|
if (types.length === 0) throw new Error(`No kind of the world names ${name} for the verb ${verb}. Give the types of the probe documents in the option types.`);
|
|
11658
13695
|
for (const type of types) {
|
|
11659
13696
|
const found = lookupKind(machine.world, type);
|
|
11660
13697
|
if (!found.ok) throw new Error(`The world has no kind for the type ${typeName(type)}.`);
|
|
11661
13698
|
const contract = contractOf(env, found.kind, found.path, options.caps);
|
|
11662
13699
|
const extra = own(options.values, typeName(type)) ?? own(options.values, found.path.join(".")) ?? [];
|
|
11663
|
-
for (const sample of samplesOf(type, found.path, found.kind, contract, extra))
|
|
13700
|
+
for (const sample of samplesOf(type, found.path, found.kind, contract, extra)) {
|
|
13701
|
+
first ??= sample;
|
|
13702
|
+
examine(env, sample, contract);
|
|
13703
|
+
}
|
|
11664
13704
|
}
|
|
13705
|
+
checkTruth(env, code, (value) => first === void 0 ? {
|
|
13706
|
+
type: typeName(types[0]),
|
|
13707
|
+
doc: {
|
|
13708
|
+
type: types[0],
|
|
13709
|
+
value: null
|
|
13710
|
+
},
|
|
13711
|
+
results: {},
|
|
13712
|
+
waits: [],
|
|
13713
|
+
caps: NO_CAPS,
|
|
13714
|
+
running: runner
|
|
13715
|
+
} : probeOf(first, {
|
|
13716
|
+
results: first.split.parts.map(() => ok(value)),
|
|
13717
|
+
waits: [],
|
|
13718
|
+
caps: NO_CAPS,
|
|
13719
|
+
running: runner
|
|
13720
|
+
}));
|
|
11665
13721
|
return ORDER.filter((obligation) => env.found.has(obligation)).map((obligation) => env.found.get(obligation));
|
|
11666
13722
|
}
|
|
11667
13723
|
//#endregion
|
|
@@ -13271,6 +15327,6 @@ function typeFromSamples(values, options = {}) {
|
|
|
13271
15327
|
return samplesAt(values, options.closed === true, 0);
|
|
13272
15328
|
}
|
|
13273
15329
|
//#endregion
|
|
13274
|
-
export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, agree, agreeRun, agreeSchedule, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
|
|
15330
|
+
export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, WORLD_SYNTAX, agree, agreeRun, agreeSchedule, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeLog, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeLog, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
|
|
13275
15331
|
|
|
13276
15332
|
//# sourceMappingURL=index.js.map
|