@mark1russell7/coeval 0.3.3 → 0.4.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 +4 -4
- package/build/index.d.ts +56 -9
- package/build/index.d.ts.map +1 -1
- package/build/index.js +267 -80
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/deref.ts +10 -6
- package/src/evaluate.ts +9 -2
- package/src/formula.ts +91 -19
- package/src/parts.ts +42 -5
- package/src/static.ts +144 -42
- package/src/world.ts +15 -3
package/build/index.js
CHANGED
|
@@ -450,6 +450,9 @@ function entriesAsJson(entries) {
|
|
|
450
450
|
}
|
|
451
451
|
function describeShape(shape) {
|
|
452
452
|
if (shape === void 0 || shape.shape === "none") return "none";
|
|
453
|
+
if (shape.shape === "text") return "a text";
|
|
454
|
+
if (shape.shape === "number") return "a number";
|
|
455
|
+
if (shape.shape === "one") return `one of ${JSON.stringify(shape.values)}`;
|
|
453
456
|
if (shape.shape === "term") return `a part${shape.of === void 0 ? "" : ` of ${shape.of}`}`;
|
|
454
457
|
if (shape.shape === "deep") return "each document in the value";
|
|
455
458
|
if (shape.shape === "list") return `a list${shape.of === void 0 ? "" : ` of ${shape.of}`}`;
|
|
@@ -940,7 +943,7 @@ function resolve(nav, ctx, path, options = {}) {
|
|
|
940
943
|
return reached === void 0 ? void 0 : adopt(reached.thing);
|
|
941
944
|
}
|
|
942
945
|
});
|
|
943
|
-
if (!result.ok) return failed("#N/A", result.reason);
|
|
946
|
+
if (!result.ok) return failed(result.code ?? "#N/A", result.reason);
|
|
944
947
|
here = adopt(result.thing);
|
|
945
948
|
hops?.push({
|
|
946
949
|
step: envelope,
|
|
@@ -1033,6 +1036,22 @@ const NO_SLOTS = Object.freeze([]);
|
|
|
1033
1036
|
function hasSlots(template) {
|
|
1034
1037
|
return template.t !== "data";
|
|
1035
1038
|
}
|
|
1039
|
+
/** This function gives the reason why a value is not data of a shape, or `undefined` when it is. */
|
|
1040
|
+
function dataFault(spec, item) {
|
|
1041
|
+
if (spec.shape === "text") return typeof item === "string" ? void 0 : "The value must be a text.";
|
|
1042
|
+
if (spec.shape === "one") return spec.values.some((value) => docEquals(value, item)) ? void 0 : `The value must be one of ${JSON.stringify(spec.values)}.`;
|
|
1043
|
+
if (typeof item !== "number") return "The value must be a number.";
|
|
1044
|
+
if (spec.whole === true && !Number.isInteger(item)) return "The value must be a whole number.";
|
|
1045
|
+
if (spec.min !== void 0 && !(item >= spec.min)) return `The value must be ${String(spec.min)} or more.`;
|
|
1046
|
+
if (spec.max !== void 0 && !(item <= spec.max)) return `The value must be ${String(spec.max)} or less.`;
|
|
1047
|
+
}
|
|
1048
|
+
/** This function stops a split when a part has a type that its position does not permit. */
|
|
1049
|
+
function kindFault(kinds, item, of, path) {
|
|
1050
|
+
const type = isEnvelope(item) ? typePath(item.type) : of === void 0 ? void 0 : [of];
|
|
1051
|
+
if (type !== void 0 && kinds.some((kind) => kind.length <= type.length && kind.every((key, index) => String(key) === String(type[index])))) return;
|
|
1052
|
+
const names = kinds.map((kind) => kind.join(".")).join(", ");
|
|
1053
|
+
throw new Bad(`${isEnvelope(item) ? `The kind ${typeName(item.type)} is not permitted here.` : "A value without an envelope is not permitted here."} The permitted kinds are ${names}.`, path);
|
|
1054
|
+
}
|
|
1036
1055
|
/**
|
|
1037
1056
|
* This function splits a value into its parts, by the shape that its kind declares. The evaluator, the view and
|
|
1038
1057
|
* the static kit use the same split, thus each one sees the same parts.
|
|
@@ -1049,12 +1068,13 @@ function splitParts(shape, value) {
|
|
|
1049
1068
|
fill: () => value
|
|
1050
1069
|
};
|
|
1051
1070
|
const parts = [];
|
|
1052
|
-
const slot = (item, path, of) => {
|
|
1071
|
+
const slot = (item, path, of, address = false) => {
|
|
1053
1072
|
parts.push({
|
|
1054
1073
|
path,
|
|
1055
1074
|
key: path.join("."),
|
|
1056
1075
|
item,
|
|
1057
|
-
...of === void 0 ? {} : { of }
|
|
1076
|
+
...of === void 0 ? {} : { of },
|
|
1077
|
+
...address ? { address: true } : {}
|
|
1058
1078
|
});
|
|
1059
1079
|
return {
|
|
1060
1080
|
t: "slot",
|
|
@@ -1093,17 +1113,33 @@ function splitParts(shape, value) {
|
|
|
1093
1113
|
t: "data",
|
|
1094
1114
|
value: item
|
|
1095
1115
|
};
|
|
1096
|
-
if (spec.shape === "
|
|
1116
|
+
if (spec.shape === "text" || spec.shape === "number" || spec.shape === "one") {
|
|
1117
|
+
const reason = dataFault(spec, item);
|
|
1118
|
+
if (reason !== void 0) throw new Bad(reason, path);
|
|
1119
|
+
return {
|
|
1120
|
+
t: "data",
|
|
1121
|
+
value: item
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
if (spec.shape === "term") {
|
|
1125
|
+
if (spec.kinds !== void 0) kindFault(spec.kinds, item, spec.of, path);
|
|
1126
|
+
return slot(item, path, spec.of, spec.address === true);
|
|
1127
|
+
}
|
|
1097
1128
|
if (spec.shape === "deep") return deep(item, path, spec.of);
|
|
1098
1129
|
if (spec.shape === "list") {
|
|
1099
1130
|
if (!Array.isArray(item)) throw new Bad("The value must be a list.", path);
|
|
1131
|
+
const list = item;
|
|
1132
|
+
if (spec.distinct === true) {
|
|
1133
|
+
const twice = list.findIndex((child, index) => list.slice(0, index).some((before) => docEquals(before, child)));
|
|
1134
|
+
if (twice !== -1) throw new Bad("The items of the list must be different.", [...path, twice]);
|
|
1135
|
+
}
|
|
1100
1136
|
const each = spec.item ?? {
|
|
1101
1137
|
shape: "term",
|
|
1102
1138
|
...spec.of === void 0 ? {} : { of: spec.of }
|
|
1103
1139
|
};
|
|
1104
1140
|
return {
|
|
1105
1141
|
t: "list",
|
|
1106
|
-
items:
|
|
1142
|
+
items: list.map((child, index) => walk(each, child, [...path, index]))
|
|
1107
1143
|
};
|
|
1108
1144
|
}
|
|
1109
1145
|
if (!isRecord(item)) throw new Bad("The value must be a record.", path);
|
|
@@ -1124,6 +1160,7 @@ function splitParts(shape, value) {
|
|
|
1124
1160
|
}])) : spec.fields;
|
|
1125
1161
|
const fields = [];
|
|
1126
1162
|
for (const key of Object.keys(item)) {
|
|
1163
|
+
if (spec.closed === true && !Object.hasOwn(named, key)) throw new Bad(`The field ${key} is not a field of this kind.`, [...path, key]);
|
|
1127
1164
|
const fieldShape = Object.hasOwn(named, key) ? named[key] : spec.rest;
|
|
1128
1165
|
fields.push([key, walk(fieldShape, item[key], [...path, key])]);
|
|
1129
1166
|
}
|
|
@@ -1591,7 +1628,7 @@ const NO_META = Object.freeze({
|
|
|
1591
1628
|
answer: NO_ANSWER,
|
|
1592
1629
|
side: "derive"
|
|
1593
1630
|
});
|
|
1594
|
-
function compileNode(c, value, place, declared, depth) {
|
|
1631
|
+
function compileNode(c, value, place, declared, depth, address = false) {
|
|
1595
1632
|
const envelope = isEnvelope(value) ? value : void 0;
|
|
1596
1633
|
const doc = envelope ?? {
|
|
1597
1634
|
type: declared ?? "?",
|
|
@@ -1639,7 +1676,7 @@ function compileNode(c, value, place, declared, depth) {
|
|
|
1639
1676
|
}
|
|
1640
1677
|
const parts = split.parts.map((slot) => {
|
|
1641
1678
|
const item = slot.item;
|
|
1642
|
-
const compiled = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1);
|
|
1679
|
+
const compiled = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1, slot.address === true);
|
|
1643
1680
|
const header = typeof item?.name === "string" && isEnvelope(item) ? item.name : void 0;
|
|
1644
1681
|
return header === void 0 ? {
|
|
1645
1682
|
key: slot.key,
|
|
@@ -1663,7 +1700,8 @@ function compileNode(c, value, place, declared, depth) {
|
|
|
1663
1700
|
parts,
|
|
1664
1701
|
place,
|
|
1665
1702
|
machine: c.machine,
|
|
1666
|
-
fill: fillOf(split, doc)
|
|
1703
|
+
fill: fillOf(split, doc),
|
|
1704
|
+
...address ? { address: true } : {}
|
|
1667
1705
|
};
|
|
1668
1706
|
if (runner !== void 0) {
|
|
1669
1707
|
const run = placedRun(isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill), place, name);
|
|
@@ -1708,6 +1746,7 @@ function fillOf(split, doc) {
|
|
|
1708
1746
|
}
|
|
1709
1747
|
function interpret(fn, input, fill) {
|
|
1710
1748
|
const { kind, typePath, doc, data, place } = input;
|
|
1749
|
+
const address = input.address === true ? { address: true } : {};
|
|
1711
1750
|
const compiled = input.parts;
|
|
1712
1751
|
const hasParts = compiled.length > 0;
|
|
1713
1752
|
return (ctx, rt) => {
|
|
@@ -1726,6 +1765,7 @@ function interpret(fn, input, fill) {
|
|
|
1726
1765
|
parts,
|
|
1727
1766
|
context: ctx,
|
|
1728
1767
|
place,
|
|
1768
|
+
...address,
|
|
1729
1769
|
resolve: (path, options) => rt.resolve(ctx, path, options),
|
|
1730
1770
|
effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
|
|
1731
1771
|
fill: input.fill
|
|
@@ -2811,10 +2851,61 @@ function withoutNames(options) {
|
|
|
2811
2851
|
const { names: _host, ...rest } = options;
|
|
2812
2852
|
return rest;
|
|
2813
2853
|
}
|
|
2854
|
+
/** This function gives the keys of a trail, from its start. It keeps the result in the trail. */
|
|
2855
|
+
function keysOf(trail) {
|
|
2856
|
+
if (trail === void 0) return [];
|
|
2857
|
+
if (trail.full !== void 0) return trail.full;
|
|
2858
|
+
const steps = [];
|
|
2859
|
+
let here = trail;
|
|
2860
|
+
while (here !== void 0 && here.full === void 0) {
|
|
2861
|
+
steps.push(here);
|
|
2862
|
+
here = here.up;
|
|
2863
|
+
}
|
|
2864
|
+
const keys = here?.full === void 0 ? [] : [...here.full];
|
|
2865
|
+
for (let index = steps.length - 1; index >= 0; index -= 1) for (const key of steps[index].keys) keys.push(key);
|
|
2866
|
+
trail.full = keys;
|
|
2867
|
+
return keys;
|
|
2868
|
+
}
|
|
2869
|
+
/** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
|
|
2870
|
+
var Visit = class {
|
|
2871
|
+
doc;
|
|
2872
|
+
declared;
|
|
2873
|
+
depth;
|
|
2874
|
+
names;
|
|
2875
|
+
later;
|
|
2876
|
+
slot;
|
|
2877
|
+
parent;
|
|
2878
|
+
kind;
|
|
2879
|
+
typePath;
|
|
2880
|
+
address;
|
|
2881
|
+
#place;
|
|
2882
|
+
#at;
|
|
2883
|
+
constructor(place, at, doc, declared, depth, names, later, slot, parent, kind, typePath, address) {
|
|
2884
|
+
this.doc = doc;
|
|
2885
|
+
this.declared = declared;
|
|
2886
|
+
this.depth = depth;
|
|
2887
|
+
this.names = names;
|
|
2888
|
+
this.later = later;
|
|
2889
|
+
this.slot = slot;
|
|
2890
|
+
this.parent = parent;
|
|
2891
|
+
this.kind = kind;
|
|
2892
|
+
this.typePath = typePath;
|
|
2893
|
+
this.address = address;
|
|
2894
|
+
this.#place = place;
|
|
2895
|
+
this.#at = at;
|
|
2896
|
+
}
|
|
2897
|
+
get place() {
|
|
2898
|
+
return keysOf(this.#place);
|
|
2899
|
+
}
|
|
2900
|
+
get at() {
|
|
2901
|
+
return keysOf(this.#at);
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2814
2904
|
/**
|
|
2815
2905
|
* This function visits each document of a document: the root and each part at each depth, by the contracts of the
|
|
2816
2906
|
* kinds. A part of a kind that the world does not have is not visited. The walk uses no stack of the host for the
|
|
2817
|
-
* depth, thus a deep document cannot overflow it.
|
|
2907
|
+
* depth, thus a deep document cannot overflow it. The time and the memory of a walk are linear in the size of the
|
|
2908
|
+
* document. A visit makes the keys of its place and of its path only when a reader asks for them.
|
|
2818
2909
|
*
|
|
2819
2910
|
* @param world - The world of the kinds.
|
|
2820
2911
|
* @param doc - The document, as JSON.
|
|
@@ -2822,23 +2913,25 @@ function withoutNames(options) {
|
|
|
2822
2913
|
* @param options - The type of a bare root value, the names of the host, and a function for the problems.
|
|
2823
2914
|
*/
|
|
2824
2915
|
function walk(world, doc, visit, options = {}) {
|
|
2825
|
-
const names = options.names === void 0 || options.names.length === 0 ? NO_NAMES : new Set(options.names);
|
|
2826
2916
|
const stack = [{
|
|
2827
2917
|
value: doc,
|
|
2828
|
-
place:
|
|
2829
|
-
at:
|
|
2918
|
+
place: void 0,
|
|
2919
|
+
at: void 0,
|
|
2830
2920
|
declared: options.declaredType,
|
|
2831
2921
|
depth: 0,
|
|
2832
|
-
names,
|
|
2833
|
-
later: NO_NAMES
|
|
2922
|
+
names: options.names === void 0 || options.names.length === 0 ? NO_NAMES : new Set(options.names),
|
|
2923
|
+
later: NO_NAMES,
|
|
2924
|
+
address: false,
|
|
2925
|
+
slot: [],
|
|
2926
|
+
parent: void 0
|
|
2834
2927
|
}];
|
|
2835
2928
|
while (stack.length > 0) {
|
|
2836
2929
|
const item = stack.pop();
|
|
2837
2930
|
const envelope = isEnvelope(item.value) ? item.value : void 0;
|
|
2838
2931
|
if (envelope === void 0 && item.declared === void 0) {
|
|
2839
2932
|
options.problem?.({
|
|
2840
|
-
place: item.place,
|
|
2841
|
-
at: item.at,
|
|
2933
|
+
place: keysOf(item.place),
|
|
2934
|
+
at: keysOf(item.at),
|
|
2842
2935
|
code: "#PART?",
|
|
2843
2936
|
reason: "The value has no envelope, and its position declares no type."
|
|
2844
2937
|
});
|
|
@@ -2850,50 +2943,49 @@ function walk(world, doc, visit, options = {}) {
|
|
|
2850
2943
|
};
|
|
2851
2944
|
const found = lookupKind(world, doc.type);
|
|
2852
2945
|
const kind = found.ok ? found.kind : void 0;
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
at: item.at,
|
|
2856
|
-
doc,
|
|
2857
|
-
declared: envelope === void 0,
|
|
2858
|
-
depth: item.depth,
|
|
2859
|
-
names: item.names,
|
|
2860
|
-
later: item.later
|
|
2861
|
-
} : {
|
|
2862
|
-
place: item.place,
|
|
2863
|
-
at: item.at,
|
|
2864
|
-
doc,
|
|
2865
|
-
declared: envelope === void 0,
|
|
2866
|
-
kind,
|
|
2867
|
-
typePath: found.ok ? found.path : [],
|
|
2868
|
-
depth: item.depth,
|
|
2869
|
-
names: item.names,
|
|
2870
|
-
later: item.later
|
|
2871
|
-
}) === "skip" || kind === void 0) continue;
|
|
2946
|
+
const here = new Visit(item.place, item.at, doc, envelope === void 0, item.depth, item.names, item.later, item.slot, item.parent, kind, found.ok ? found.path : void 0, item.address ? true : void 0);
|
|
2947
|
+
if (visit(here) === "skip" || kind === void 0) continue;
|
|
2872
2948
|
const split = splitParts(kind.parts, doc.value);
|
|
2949
|
+
const base = envelope === void 0 ? item.at : {
|
|
2950
|
+
up: item.at,
|
|
2951
|
+
keys: ["value"]
|
|
2952
|
+
};
|
|
2873
2953
|
if (!split.ok) {
|
|
2874
2954
|
const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
|
|
2955
|
+
const where = keysOf({
|
|
2956
|
+
up: base,
|
|
2957
|
+
keys: split.at
|
|
2958
|
+
});
|
|
2875
2959
|
options.problem?.({
|
|
2876
|
-
place: item.place,
|
|
2877
|
-
at:
|
|
2960
|
+
place: keysOf(item.place),
|
|
2961
|
+
at: where,
|
|
2878
2962
|
code: "#VALUE!",
|
|
2879
2963
|
reason: `${split.reason}${at} The kind is ${typeName(doc.type)}.`
|
|
2880
2964
|
});
|
|
2881
2965
|
continue;
|
|
2882
2966
|
}
|
|
2883
2967
|
const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
|
|
2884
|
-
const base = envelope === void 0 ? item.at : [...item.at, "value"];
|
|
2885
2968
|
for (let index = split.parts.length - 1; index >= 0; index -= 1) {
|
|
2886
2969
|
const slot = split.parts[index];
|
|
2887
2970
|
const extra = sees[index] ?? [];
|
|
2888
2971
|
const hidden = later[index] ?? [];
|
|
2889
2972
|
stack.push({
|
|
2890
2973
|
value: slot.item,
|
|
2891
|
-
place:
|
|
2892
|
-
|
|
2974
|
+
place: slot.path.length === 0 ? item.place : {
|
|
2975
|
+
up: item.place,
|
|
2976
|
+
keys: slot.path
|
|
2977
|
+
},
|
|
2978
|
+
at: slot.path.length === 0 ? base : {
|
|
2979
|
+
up: base,
|
|
2980
|
+
keys: slot.path
|
|
2981
|
+
},
|
|
2893
2982
|
declared: slot.of,
|
|
2894
2983
|
depth: item.depth + 1,
|
|
2895
2984
|
names: extra.length === 0 ? item.names : /* @__PURE__ */ new Set([...item.names, ...extra]),
|
|
2896
|
-
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden])
|
|
2985
|
+
later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden]),
|
|
2986
|
+
address: slot.address === true,
|
|
2987
|
+
slot: slot.path,
|
|
2988
|
+
parent: here
|
|
2897
2989
|
});
|
|
2898
2990
|
}
|
|
2899
2991
|
}
|
|
@@ -2901,7 +2993,7 @@ function walk(world, doc, visit, options = {}) {
|
|
|
2901
2993
|
/**
|
|
2902
2994
|
* This function finds each reference of a document: each document of the type `ref` that a walk reaches. A
|
|
2903
2995
|
* reference to a name that a kind around it binds is not free. The others are what the document reads from
|
|
2904
|
-
* outside: its dependencies.
|
|
2996
|
+
* outside: its dependencies. When the kind `ref` has parts, the walk also finds the references in its step documents.
|
|
2905
2997
|
*
|
|
2906
2998
|
* @param world - The world of the kinds.
|
|
2907
2999
|
* @param doc - The document, as JSON.
|
|
@@ -2915,15 +3007,25 @@ function refsOf(world, doc, options = {}) {
|
|
|
2915
3007
|
const path = v.doc.value;
|
|
2916
3008
|
const root = typeof path[0] === "string" ? path[0] : "";
|
|
2917
3009
|
const first = path[1];
|
|
2918
|
-
const
|
|
2919
|
-
out.push({
|
|
2920
|
-
place: v.place,
|
|
2921
|
-
at: v.at,
|
|
3010
|
+
const use = {
|
|
2922
3011
|
path,
|
|
2923
3012
|
root,
|
|
2924
|
-
free: !
|
|
3013
|
+
free: !(root === "names" && isKey(first) && v.names.has(String(first))),
|
|
3014
|
+
address: v.address === true
|
|
3015
|
+
};
|
|
3016
|
+
Object.defineProperty(use, "place", {
|
|
3017
|
+
enumerable: true,
|
|
3018
|
+
get: () => v.place
|
|
3019
|
+
});
|
|
3020
|
+
Object.defineProperty(use, "at", {
|
|
3021
|
+
enumerable: true,
|
|
3022
|
+
get: () => v.at
|
|
2925
3023
|
});
|
|
2926
|
-
|
|
3024
|
+
Object.defineProperty(use, "visit", {
|
|
3025
|
+
enumerable: false,
|
|
3026
|
+
value: v
|
|
3027
|
+
});
|
|
3028
|
+
out.push(use);
|
|
2927
3029
|
}, options.names === void 0 ? options : withoutNames(options));
|
|
2928
3030
|
return out;
|
|
2929
3031
|
}
|
|
@@ -2999,7 +3101,8 @@ function check(machine, doc, options = {}) {
|
|
|
2999
3101
|
issues.push({
|
|
3000
3102
|
code: "#DEPTH!",
|
|
3001
3103
|
place: v.place,
|
|
3002
|
-
reason: `The nesting is deeper than ${maxDepth} documents
|
|
3104
|
+
reason: `The nesting is deeper than ${maxDepth} documents.`,
|
|
3105
|
+
at: v.at
|
|
3003
3106
|
});
|
|
3004
3107
|
return "skip";
|
|
3005
3108
|
}
|
|
@@ -3009,7 +3112,8 @@ function check(machine, doc, options = {}) {
|
|
|
3009
3112
|
issues.push({
|
|
3010
3113
|
code: "#KIND?",
|
|
3011
3114
|
place: v.place,
|
|
3012
|
-
reason: `The world has no kind ${typeName(v.doc.type)}
|
|
3115
|
+
reason: `The world has no kind ${typeName(v.doc.type)}.`,
|
|
3116
|
+
at: v.at
|
|
3013
3117
|
});
|
|
3014
3118
|
return "skip";
|
|
3015
3119
|
}
|
|
@@ -3017,31 +3121,47 @@ function check(machine, doc, options = {}) {
|
|
|
3017
3121
|
if (name === void 0 || !has(machine.evaluators, name) && !has(machine.runners, name)) issues.push({
|
|
3018
3122
|
code: "#NOEVAL",
|
|
3019
3123
|
place: v.place,
|
|
3020
|
-
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}
|
|
3124
|
+
reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`,
|
|
3125
|
+
at: v.at
|
|
3021
3126
|
});
|
|
3022
3127
|
if (options.caps !== void 0 && v.kind?.effects !== void 0) {
|
|
3023
3128
|
const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
|
|
3024
3129
|
for (const kind of kinds) if (!permits(options.caps, kind)) issues.push({
|
|
3025
3130
|
code: "#CAP!",
|
|
3026
3131
|
place: v.place,
|
|
3027
|
-
reason: `The capabilities do not permit the effect ${kind.join(".")}
|
|
3132
|
+
reason: `The capabilities do not permit the effect ${kind.join(".")}.`,
|
|
3133
|
+
at: v.at
|
|
3028
3134
|
});
|
|
3029
3135
|
}
|
|
3030
|
-
|
|
3136
|
+
const isPath = isRef(v.doc);
|
|
3137
|
+
if (!isPath && v.typePath?.length === 1 && v.typePath[0] === "ref") issues.push({
|
|
3138
|
+
code: "#VALUE!",
|
|
3139
|
+
place: v.place,
|
|
3140
|
+
reason: "The value of ref is a path: a list that starts with a root.",
|
|
3141
|
+
at: [...v.at, ...v.declared ? [] : ["value"]]
|
|
3142
|
+
});
|
|
3143
|
+
if (!isPath) return void 0;
|
|
3031
3144
|
const path = v.doc.value;
|
|
3032
3145
|
const root = path[0];
|
|
3146
|
+
const stepAt = (index) => [
|
|
3147
|
+
...v.at,
|
|
3148
|
+
"value",
|
|
3149
|
+
...index < path.length ? [index] : []
|
|
3150
|
+
];
|
|
3033
3151
|
if (typeof root !== "string" || !ROOTS.includes(root)) {
|
|
3034
3152
|
issues.push({
|
|
3035
3153
|
code: "#REF!",
|
|
3036
3154
|
place: v.place,
|
|
3037
|
-
reason: `A path starts with a root: ${ROOTS.join(", ")}
|
|
3155
|
+
reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
|
|
3156
|
+
at: stepAt(0)
|
|
3038
3157
|
});
|
|
3039
3158
|
return "skip";
|
|
3040
3159
|
}
|
|
3041
3160
|
if (!roots.includes(root)) issues.push({
|
|
3042
3161
|
code: "#REF!",
|
|
3043
3162
|
place: v.place,
|
|
3044
|
-
reason: `The root ${root} is not open here. The open roots are ${roots.join(", ")}
|
|
3163
|
+
reason: `The root ${root} is not open here. The open roots are ${roots.join(", ")}.`,
|
|
3164
|
+
at: stepAt(0)
|
|
3045
3165
|
});
|
|
3046
3166
|
const first = path[1];
|
|
3047
3167
|
if (root === "names" && !anyName && isKey(first) && !v.names.has(String(first))) {
|
|
@@ -3049,33 +3169,37 @@ function check(machine, doc, options = {}) {
|
|
|
3049
3169
|
issues.push({
|
|
3050
3170
|
code: "#NAME?",
|
|
3051
3171
|
place: v.place,
|
|
3052
|
-
reason: later ? `The name ${String(first)} is bound by a later step.` : `Nothing binds the name ${String(first)}
|
|
3172
|
+
reason: later ? `The name ${String(first)} is bound by a later step.` : `Nothing binds the name ${String(first)}.`,
|
|
3173
|
+
at: stepAt(1)
|
|
3053
3174
|
});
|
|
3054
3175
|
}
|
|
3055
|
-
path.
|
|
3056
|
-
if (isKey(step)) return;
|
|
3176
|
+
path.forEach((step, index) => {
|
|
3177
|
+
if (index === 0 || isKey(step)) return;
|
|
3057
3178
|
const found = isEnvelope(step) ? lookupKind(machine.world, step.type) : void 0;
|
|
3058
3179
|
const walker = found?.ok === true && Object.hasOwn(found.kind.verbs, "step") ? found.kind.verbs["step"] : void 0;
|
|
3059
3180
|
if (walker === void 0 || !has(machine.walkers, walker)) issues.push({
|
|
3060
3181
|
code: "#STEP?",
|
|
3061
3182
|
place: v.place,
|
|
3062
|
-
reason: "A step is a key, or a document of a kind with the verb step."
|
|
3183
|
+
reason: "A step is a key, or a document of a kind with the verb step.",
|
|
3184
|
+
at: stepAt(index)
|
|
3063
3185
|
});
|
|
3064
3186
|
});
|
|
3065
|
-
return "skip";
|
|
3066
3187
|
}, {
|
|
3067
3188
|
...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
|
|
3068
3189
|
...Array.isArray(options.names) ? { names: options.names } : {},
|
|
3069
3190
|
problem: (problem) => issues.push({
|
|
3070
3191
|
code: problem.code,
|
|
3071
3192
|
place: problem.place,
|
|
3072
|
-
reason: problem.reason
|
|
3193
|
+
reason: problem.reason,
|
|
3194
|
+
at: problem.at
|
|
3073
3195
|
})
|
|
3074
3196
|
});
|
|
3075
3197
|
return issues;
|
|
3076
3198
|
}
|
|
3077
3199
|
//#endregion
|
|
3078
3200
|
//#region src/formula.ts
|
|
3201
|
+
/** The mark of a field that the profile leaves out of a record. */
|
|
3202
|
+
const SKIPPED = Symbol("skipped");
|
|
3079
3203
|
/** The value of a name whose binding failed. A reference to the name gives the error. */
|
|
3080
3204
|
var Held = class {
|
|
3081
3205
|
error;
|
|
@@ -3127,6 +3251,16 @@ function setOwn(target, key, value) {
|
|
|
3127
3251
|
function compiled(compile) {
|
|
3128
3252
|
return { compile };
|
|
3129
3253
|
}
|
|
3254
|
+
/** This function evaluates parts in order. It gives the value of each part, or the first error. */
|
|
3255
|
+
function valuesOf(parts, ctx, rt) {
|
|
3256
|
+
const values = new Array(parts.length);
|
|
3257
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
3258
|
+
const result = parts[index].code(ctx, rt);
|
|
3259
|
+
if (!result.ok) return result;
|
|
3260
|
+
values[index] = result.value;
|
|
3261
|
+
}
|
|
3262
|
+
return values;
|
|
3263
|
+
}
|
|
3130
3264
|
function partsOf(value) {
|
|
3131
3265
|
return isRecord(value) ? value : {};
|
|
3132
3266
|
}
|
|
@@ -3273,18 +3407,48 @@ function formulaEvaluators(profile = {}) {
|
|
|
3273
3407
|
return Object.hasOwn(ops, name) ? ops[name] : void 0;
|
|
3274
3408
|
};
|
|
3275
3409
|
const notTruth = (what) => fail("#VALUE!", `The ${what} must give a truth value.`);
|
|
3410
|
+
const combine = profile.combine;
|
|
3411
|
+
/** This function evaluates each part. Without `combine`, the first error stops it. A field that `omit` skips is absent. */
|
|
3412
|
+
const collect = (parts, ctx, rt, skip) => {
|
|
3413
|
+
const values = new Array(parts.length);
|
|
3414
|
+
let errors;
|
|
3415
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
3416
|
+
const result = parts[index].code(ctx, rt);
|
|
3417
|
+
if (result.ok) values[index] = result.value;
|
|
3418
|
+
else if (skip !== void 0 && skip(result.error)) values[index] = SKIPPED;
|
|
3419
|
+
else if (combine === void 0) return result;
|
|
3420
|
+
else (errors ??= []).push(result.error);
|
|
3421
|
+
}
|
|
3422
|
+
if (errors === void 0 || combine === void 0) return values;
|
|
3423
|
+
return {
|
|
3424
|
+
ok: false,
|
|
3425
|
+
error: errors.length === 1 ? errors[0] : combine(errors)
|
|
3426
|
+
};
|
|
3427
|
+
};
|
|
3276
3428
|
return {
|
|
3277
3429
|
"formula.lit": compiled(({ doc }) => {
|
|
3278
3430
|
const result = ok(doc.value);
|
|
3279
3431
|
return () => result;
|
|
3280
3432
|
}),
|
|
3281
|
-
"formula.ref": compiled(({ doc }) => {
|
|
3433
|
+
"formula.ref": compiled(({ doc, parts, fill, address }) => {
|
|
3282
3434
|
const path = doc.value;
|
|
3283
3435
|
if (!Array.isArray(path) || path.length === 0) {
|
|
3284
3436
|
const bad = fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
|
|
3285
3437
|
return () => bad;
|
|
3286
3438
|
}
|
|
3287
3439
|
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
|
|
3440
|
+
const give = (found) => {
|
|
3441
|
+
if (!found.ok) return fail(found.code, found.reason);
|
|
3442
|
+
if (address === true) return ok(found.thing);
|
|
3443
|
+
const reached = valueOfThing(found.thing);
|
|
3444
|
+
if (reached instanceof Held) return {
|
|
3445
|
+
ok: false,
|
|
3446
|
+
error: reached.error
|
|
3447
|
+
};
|
|
3448
|
+
if (missing !== void 0 && missing(reached)) return fail("#N/A", "The path reaches no value.");
|
|
3449
|
+
return ok(reached);
|
|
3450
|
+
};
|
|
3451
|
+
const computed = parts.length > 0;
|
|
3288
3452
|
return (ctx, rt) => {
|
|
3289
3453
|
if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
|
|
3290
3454
|
const bound = ctx.names[head];
|
|
@@ -3293,21 +3457,40 @@ function formulaEvaluators(profile = {}) {
|
|
|
3293
3457
|
error: bound.error
|
|
3294
3458
|
};
|
|
3295
3459
|
}
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
return
|
|
3460
|
+
if (!computed) return give(rt.resolve(ctx, path));
|
|
3461
|
+
const steps = valuesOf(parts, ctx, rt);
|
|
3462
|
+
return Array.isArray(steps) ? give(rt.resolve(ctx, fill(steps))) : steps;
|
|
3463
|
+
};
|
|
3464
|
+
}),
|
|
3465
|
+
"formula.step": compiled(({ doc, parts, fill }) => {
|
|
3466
|
+
if (parts.length === 0) {
|
|
3467
|
+
const same = ok(doc);
|
|
3468
|
+
return () => same;
|
|
3469
|
+
}
|
|
3470
|
+
return (ctx, rt) => {
|
|
3471
|
+
const values = valuesOf(parts, ctx, rt);
|
|
3472
|
+
return Array.isArray(values) ? ok({
|
|
3473
|
+
...doc,
|
|
3474
|
+
value: fill(values)
|
|
3475
|
+
}) : values;
|
|
3305
3476
|
};
|
|
3306
3477
|
}),
|
|
3307
3478
|
"formula.record": compiled(({ value }) => {
|
|
3308
3479
|
const fields = partsOf(value);
|
|
3309
3480
|
const keys = Object.keys(fields);
|
|
3310
3481
|
const codes = keys.map((key) => fields[key].code);
|
|
3482
|
+
if (combine !== void 0) {
|
|
3483
|
+
const parts = keys.map((key) => fields[key]);
|
|
3484
|
+
return (ctx, rt) => {
|
|
3485
|
+
const values = collect(parts, ctx, rt, omit);
|
|
3486
|
+
if (!Array.isArray(values)) return values;
|
|
3487
|
+
const out = {};
|
|
3488
|
+
keys.forEach((key, index) => {
|
|
3489
|
+
if (values[index] !== SKIPPED) setOwn(out, key, values[index]);
|
|
3490
|
+
});
|
|
3491
|
+
return ok(out);
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3311
3494
|
return (ctx, rt) => {
|
|
3312
3495
|
const out = {};
|
|
3313
3496
|
for (let index = 0; index < keys.length; index += 1) {
|
|
@@ -3322,13 +3505,8 @@ function formulaEvaluators(profile = {}) {
|
|
|
3322
3505
|
};
|
|
3323
3506
|
}),
|
|
3324
3507
|
"formula.list": compiled(({ parts }) => (ctx, rt) => {
|
|
3325
|
-
const
|
|
3326
|
-
|
|
3327
|
-
const result = parts[index].code(ctx, rt);
|
|
3328
|
-
if (!result.ok) return result;
|
|
3329
|
-
out[index] = result.value;
|
|
3330
|
-
}
|
|
3331
|
-
return ok(out);
|
|
3508
|
+
const values = collect(parts, ctx, rt);
|
|
3509
|
+
return Array.isArray(values) ? ok(values) : values;
|
|
3332
3510
|
}),
|
|
3333
3511
|
"formula.let": compiled(({ value }) => {
|
|
3334
3512
|
const record = partsOf(value);
|
|
@@ -3443,6 +3621,15 @@ function formulaEvaluators(profile = {}) {
|
|
|
3443
3621
|
}
|
|
3444
3622
|
const done = (out) => check === void 0 ? ok(out) : check(out, name) ?? ok(out);
|
|
3445
3623
|
const failed = (error) => thrown(error, `the operation ${name}`);
|
|
3624
|
+
if (combine !== void 0) return (ctx, rt) => {
|
|
3625
|
+
const values = collect(parts, ctx, rt);
|
|
3626
|
+
if (!Array.isArray(values)) return values;
|
|
3627
|
+
try {
|
|
3628
|
+
return done(fn(...values));
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
return failed(error);
|
|
3631
|
+
}
|
|
3632
|
+
};
|
|
3446
3633
|
if (parts.length === 1) {
|
|
3447
3634
|
const [a] = parts;
|
|
3448
3635
|
return (ctx, rt) => {
|
|
@@ -3490,9 +3677,9 @@ function formulaEvaluators(profile = {}) {
|
|
|
3490
3677
|
const unknown = fail("#NAME?", `The language has no extension ${name}.`);
|
|
3491
3678
|
return () => unknown;
|
|
3492
3679
|
}
|
|
3493
|
-
return () => {
|
|
3680
|
+
return (ctx) => {
|
|
3494
3681
|
try {
|
|
3495
|
-
return handler(doc.value);
|
|
3682
|
+
return handler(doc.value, ctx);
|
|
3496
3683
|
} catch (error) {
|
|
3497
3684
|
return thrown(error, `the extension ${name}`);
|
|
3498
3685
|
}
|