@mark1russell7/coeval 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.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 === "term") return slot(item, path, spec.of);
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: item.map((child, index) => walk(each, child, [...path, index]))
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
  }
@@ -1244,6 +1281,52 @@ function narrow(parent, next) {
1244
1281
  caps
1245
1282
  };
1246
1283
  }
1284
+ /** No capabilities. One list serves each masked part. */
1285
+ const NO_CAPS = Object.freeze([]);
1286
+ /** No capability rules. One list serves each part that no rule narrows. */
1287
+ const NO_RULES = Object.freeze([]);
1288
+ /**
1289
+ * This function gives the capabilities that two lists both permit. An effect is permitted by the result when each
1290
+ * list permits it. When the list `b` permits each capability of `a`, the result is the list `a` itself.
1291
+ *
1292
+ * @param a - The first list, for example the capabilities of a document.
1293
+ * @param b - The second list, for example the list of a rule `only`.
1294
+ * @returns The capabilities that both lists permit.
1295
+ */
1296
+ function intersectCaps(a, b) {
1297
+ if (a.every((cap) => permits(b, cap))) return a;
1298
+ const out = [];
1299
+ for (const cap of b) if (permits(a, cap)) out.push(cap);
1300
+ for (const cap of a) if (permits(b, cap) && !permits(out, cap)) out.push(cap);
1301
+ return out;
1302
+ }
1303
+ /** This function gives the capability rules of a kind that cover a part and change its capabilities. */
1304
+ function rulesFor(rules, path) {
1305
+ if (rules === void 0 || rules.length === 0) return NO_RULES;
1306
+ const out = rules.filter((rule) => rule.rule !== "same" && (rule.in === void 0 || rule.in.some((pattern) => pathMatches(path, pattern))));
1307
+ return out.length === 0 ? NO_RULES : out;
1308
+ }
1309
+ /** This function applies the rules that cover a part to the capabilities of its document. */
1310
+ function capsUnder(rules, caps) {
1311
+ let out = caps;
1312
+ for (const rule of rules) {
1313
+ if (rule.rule === "mask") return NO_CAPS;
1314
+ if (rule.rule === "only") out = intersectCaps(out, rule.caps);
1315
+ }
1316
+ return out;
1317
+ }
1318
+ /**
1319
+ * This function gives the capabilities that a kind gives one of its parts, by the capability rules of its contract.
1320
+ * The kernel and the static kit both use it, thus a run and a check narrow the same way.
1321
+ *
1322
+ * @param rules - The capability rules of the kind (`Kind.caps`).
1323
+ * @param path - The path of the part in the value of the document.
1324
+ * @param caps - The capabilities of the document.
1325
+ * @returns The capabilities of the part: the same list or a smaller one.
1326
+ */
1327
+ function partCaps(rules, path, caps) {
1328
+ return capsUnder(rulesFor(rules, path), caps);
1329
+ }
1247
1330
  function selfLabel(self) {
1248
1331
  try {
1249
1332
  if (isHostThing(self)) return self.label;
@@ -1591,7 +1674,7 @@ const NO_META = Object.freeze({
1591
1674
  answer: NO_ANSWER,
1592
1675
  side: "derive"
1593
1676
  });
1594
- function compileNode(c, value, place, declared, depth) {
1677
+ function compileNode(c, value, place, declared, depth, address = false) {
1595
1678
  const envelope = isEnvelope(value) ? value : void 0;
1596
1679
  const doc = envelope ?? {
1597
1680
  type: declared ?? "?",
@@ -1639,7 +1722,9 @@ function compileNode(c, value, place, declared, depth) {
1639
1722
  }
1640
1723
  const parts = split.parts.map((slot) => {
1641
1724
  const item = slot.item;
1642
- const compiled = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1);
1725
+ const own = compileNode(c, item, place.concat(slot.path), slot.of, depth + 1, slot.address === true);
1726
+ const rules = rulesFor(kind?.caps, slot.path);
1727
+ const compiled = rules.length === 0 ? own : gated(rules, own);
1643
1728
  const header = typeof item?.name === "string" && isEnvelope(item) ? item.name : void 0;
1644
1729
  return header === void 0 ? {
1645
1730
  key: slot.key,
@@ -1663,7 +1748,8 @@ function compileNode(c, value, place, declared, depth) {
1663
1748
  parts,
1664
1749
  place,
1665
1750
  machine: c.machine,
1666
- fill: fillOf(split, doc)
1751
+ fill: fillOf(split, doc),
1752
+ ...address ? { address: true } : {}
1667
1753
  };
1668
1754
  if (runner !== void 0) {
1669
1755
  const run = placedRun(isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill), place, name);
@@ -1682,6 +1768,25 @@ function placedCode(inner, place, name) {
1682
1768
  }
1683
1769
  };
1684
1770
  }
1771
+ /**
1772
+ * This function gives the code of a part that capability rules cover. The code narrows the context of the part by
1773
+ * the rules before the part starts.
1774
+ */
1775
+ function gated(rules, part) {
1776
+ const inner = part.code;
1777
+ const steps = part.run;
1778
+ const under = (ctx) => {
1779
+ const caps = capsUnder(rules, ctx.caps);
1780
+ return caps === ctx.caps || caps.length === 0 && ctx.caps.length === 0 ? ctx : {
1781
+ ...ctx,
1782
+ caps
1783
+ };
1784
+ };
1785
+ return {
1786
+ code: (ctx, rt) => inner(under(ctx), rt),
1787
+ run: (ctx, rt) => steps(under(ctx), rt)
1788
+ };
1789
+ }
1685
1790
  /** This function gives the steps of a runner, with the place of each error and of each exception. */
1686
1791
  function placedRun(inner, place, name) {
1687
1792
  return function* (ctx, rt) {
@@ -1708,6 +1813,7 @@ function fillOf(split, doc) {
1708
1813
  }
1709
1814
  function interpret(fn, input, fill) {
1710
1815
  const { kind, typePath, doc, data, place } = input;
1816
+ const address = input.address === true ? { address: true } : {};
1711
1817
  const compiled = input.parts;
1712
1818
  const hasParts = compiled.length > 0;
1713
1819
  return (ctx, rt) => {
@@ -1726,6 +1832,7 @@ function interpret(fn, input, fill) {
1726
1832
  parts,
1727
1833
  context: ctx,
1728
1834
  place,
1835
+ ...address,
1729
1836
  resolve: (path, options) => rt.resolve(ctx, path, options),
1730
1837
  effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
1731
1838
  fill: input.fill
@@ -2106,6 +2213,7 @@ const STD_WORLD = {
2106
2213
  view: "view.op"
2107
2214
  },
2108
2215
  parts: { shape: "list" },
2216
+ caps: [{ rule: "mask" }],
2109
2217
  summary: "It evaluates its part without capabilities, thus an effect in it fails."
2110
2218
  },
2111
2219
  log: {
@@ -2118,6 +2226,7 @@ const STD_WORLD = {
2118
2226
  of: "text"
2119
2227
  },
2120
2228
  side: "run",
2229
+ effects: ["log"],
2121
2230
  summary: "It writes the result of its part to the log. This is an effect."
2122
2231
  },
2123
2232
  write: {
@@ -2130,6 +2239,7 @@ const STD_WORLD = {
2130
2239
  fields: ["value"]
2131
2240
  },
2132
2241
  side: "run",
2242
+ effects: ["write"],
2133
2243
  summary: "It writes the result of its part to the store, at its key. This is an effect."
2134
2244
  }
2135
2245
  },
@@ -2307,13 +2417,10 @@ const STD_RUNNERS = {
2307
2417
  }
2308
2418
  return ok(last);
2309
2419
  },
2310
- "control.pure": function* ({ parts, context }) {
2420
+ "control.pure": function* ({ parts }) {
2311
2421
  const only = parts[0];
2312
2422
  if (only === void 0) return fail("#VALUE!", "pure needs one part.");
2313
- return yield* only.evaluate({
2314
- ...context,
2315
- caps: []
2316
- });
2423
+ return yield* only.evaluate();
2317
2424
  },
2318
2425
  "control.if": function* ({ parts }) {
2319
2426
  const test = parts.find((part) => part.key === "test");
@@ -2811,34 +2918,91 @@ function withoutNames(options) {
2811
2918
  const { names: _host, ...rest } = options;
2812
2919
  return rest;
2813
2920
  }
2921
+ /** This function gives the keys of a trail, from its start. It keeps the result in the trail. */
2922
+ function keysOf(trail) {
2923
+ if (trail === void 0) return [];
2924
+ if (trail.full !== void 0) return trail.full;
2925
+ const steps = [];
2926
+ let here = trail;
2927
+ while (here !== void 0 && here.full === void 0) {
2928
+ steps.push(here);
2929
+ here = here.up;
2930
+ }
2931
+ const keys = here?.full === void 0 ? [] : [...here.full];
2932
+ for (let index = steps.length - 1; index >= 0; index -= 1) for (const key of steps[index].keys) keys.push(key);
2933
+ trail.full = keys;
2934
+ return keys;
2935
+ }
2936
+ /** A visit with its paths as trails. It makes the keys of a path only when a reader asks for them. */
2937
+ var Visit = class {
2938
+ doc;
2939
+ declared;
2940
+ depth;
2941
+ names;
2942
+ later;
2943
+ slot;
2944
+ parent;
2945
+ kind;
2946
+ typePath;
2947
+ address;
2948
+ caps;
2949
+ #place;
2950
+ #at;
2951
+ constructor(place, at, doc, declared, depth, names, later, slot, parent, kind, typePath, address, caps) {
2952
+ this.doc = doc;
2953
+ this.declared = declared;
2954
+ this.depth = depth;
2955
+ this.names = names;
2956
+ this.later = later;
2957
+ this.slot = slot;
2958
+ this.parent = parent;
2959
+ this.kind = kind;
2960
+ this.typePath = typePath;
2961
+ this.address = address;
2962
+ this.caps = caps;
2963
+ this.#place = place;
2964
+ this.#at = at;
2965
+ }
2966
+ get place() {
2967
+ return keysOf(this.#place);
2968
+ }
2969
+ get at() {
2970
+ return keysOf(this.#at);
2971
+ }
2972
+ };
2814
2973
  /**
2815
2974
  * This function visits each document of a document: the root and each part at each depth, by the contracts of the
2816
- * 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.
2975
+ * kinds. A part of a kind that the world does not have is not visited. With capabilities in the options, the walk
2976
+ * narrows them for each part by the rules of its kind, as a run does. The walk uses no stack of the host for the
2977
+ * depth, thus a deep document cannot overflow it. The time and the memory of a walk are linear in the size of the
2978
+ * document. A visit makes the keys of its place and of its path only when a reader asks for them.
2818
2979
  *
2819
2980
  * @param world - The world of the kinds.
2820
2981
  * @param doc - The document, as JSON.
2821
2982
  * @param visit - The function that gets each document. It gives `"skip"` to not visit the parts of a document.
2822
- * @param options - The type of a bare root value, the names of the host, and a function for the problems.
2983
+ * @param options - The type of a bare root value, the names, the capabilities and a function for the problems.
2823
2984
  */
2824
2985
  function walk(world, doc, visit, options = {}) {
2825
- const names = options.names === void 0 || options.names.length === 0 ? NO_NAMES : new Set(options.names);
2826
2986
  const stack = [{
2827
2987
  value: doc,
2828
- place: [],
2829
- at: [],
2988
+ place: void 0,
2989
+ at: void 0,
2830
2990
  declared: options.declaredType,
2831
2991
  depth: 0,
2832
- names,
2833
- later: NO_NAMES
2992
+ names: options.names === void 0 || options.names.length === 0 ? NO_NAMES : new Set(options.names),
2993
+ later: NO_NAMES,
2994
+ address: false,
2995
+ slot: [],
2996
+ parent: void 0,
2997
+ caps: options.caps
2834
2998
  }];
2835
2999
  while (stack.length > 0) {
2836
3000
  const item = stack.pop();
2837
3001
  const envelope = isEnvelope(item.value) ? item.value : void 0;
2838
3002
  if (envelope === void 0 && item.declared === void 0) {
2839
3003
  options.problem?.({
2840
- place: item.place,
2841
- at: item.at,
3004
+ place: keysOf(item.place),
3005
+ at: keysOf(item.at),
2842
3006
  code: "#PART?",
2843
3007
  reason: "The value has no envelope, and its position declares no type."
2844
3008
  });
@@ -2850,50 +3014,50 @@ function walk(world, doc, visit, options = {}) {
2850
3014
  };
2851
3015
  const found = lookupKind(world, doc.type);
2852
3016
  const kind = found.ok ? found.kind : void 0;
2853
- if (visit(kind === void 0 ? {
2854
- place: item.place,
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;
3017
+ 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, item.caps);
3018
+ if (visit(here) === "skip" || kind === void 0) continue;
2872
3019
  const split = splitParts(kind.parts, doc.value);
3020
+ const base = envelope === void 0 ? item.at : {
3021
+ up: item.at,
3022
+ keys: ["value"]
3023
+ };
2873
3024
  if (!split.ok) {
2874
3025
  const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
3026
+ const where = keysOf({
3027
+ up: base,
3028
+ keys: split.at
3029
+ });
2875
3030
  options.problem?.({
2876
- place: item.place,
2877
- at: item.at,
3031
+ place: keysOf(item.place),
3032
+ at: where,
2878
3033
  code: "#VALUE!",
2879
3034
  reason: `${split.reason}${at} The kind is ${typeName(doc.type)}.`
2880
3035
  });
2881
3036
  continue;
2882
3037
  }
2883
3038
  const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
2884
- const base = envelope === void 0 ? item.at : [...item.at, "value"];
2885
3039
  for (let index = split.parts.length - 1; index >= 0; index -= 1) {
2886
3040
  const slot = split.parts[index];
2887
3041
  const extra = sees[index] ?? [];
2888
3042
  const hidden = later[index] ?? [];
2889
3043
  stack.push({
2890
3044
  value: slot.item,
2891
- place: [...item.place, ...slot.path],
2892
- at: [...base, ...slot.path],
3045
+ place: slot.path.length === 0 ? item.place : {
3046
+ up: item.place,
3047
+ keys: slot.path
3048
+ },
3049
+ at: slot.path.length === 0 ? base : {
3050
+ up: base,
3051
+ keys: slot.path
3052
+ },
2893
3053
  declared: slot.of,
2894
3054
  depth: item.depth + 1,
2895
3055
  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])
3056
+ later: hidden.length === 0 ? item.later : /* @__PURE__ */ new Set([...item.later, ...hidden]),
3057
+ address: slot.address === true,
3058
+ slot: slot.path,
3059
+ parent: here,
3060
+ caps: item.caps === void 0 || kind.caps === void 0 ? item.caps : partCaps(kind.caps, slot.path, item.caps)
2897
3061
  });
2898
3062
  }
2899
3063
  }
@@ -2901,7 +3065,7 @@ function walk(world, doc, visit, options = {}) {
2901
3065
  /**
2902
3066
  * This function finds each reference of a document: each document of the type `ref` that a walk reaches. A
2903
3067
  * 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.
3068
+ * outside: its dependencies. When the kind `ref` has parts, the walk also finds the references in its step documents.
2905
3069
  *
2906
3070
  * @param world - The world of the kinds.
2907
3071
  * @param doc - The document, as JSON.
@@ -2915,15 +3079,25 @@ function refsOf(world, doc, options = {}) {
2915
3079
  const path = v.doc.value;
2916
3080
  const root = typeof path[0] === "string" ? path[0] : "";
2917
3081
  const first = path[1];
2918
- const bound = root === "names" && isKey(first) && v.names.has(String(first));
2919
- out.push({
2920
- place: v.place,
2921
- at: v.at,
3082
+ const use = {
2922
3083
  path,
2923
3084
  root,
2924
- free: !bound
3085
+ free: !(root === "names" && isKey(first) && v.names.has(String(first))),
3086
+ address: v.address === true
3087
+ };
3088
+ Object.defineProperty(use, "place", {
3089
+ enumerable: true,
3090
+ get: () => v.place
2925
3091
  });
2926
- return "skip";
3092
+ Object.defineProperty(use, "at", {
3093
+ enumerable: true,
3094
+ get: () => v.at
3095
+ });
3096
+ Object.defineProperty(use, "visit", {
3097
+ enumerable: false,
3098
+ value: v
3099
+ });
3100
+ out.push(use);
2927
3101
  }, options.names === void 0 ? options : withoutNames(options));
2928
3102
  return out;
2929
3103
  }
@@ -2971,6 +3145,15 @@ function mapRefs(world, doc, change, options = {}) {
2971
3145
  return out;
2972
3146
  }
2973
3147
  /**
3148
+ * This function gives the reason of a `#CAP!`. When a kind around the document narrowed the capabilities, the
3149
+ * reason names the nearest such kind.
3150
+ */
3151
+ function capReason(v, kind) {
3152
+ const text = kind.join(".");
3153
+ for (let up = v.parent; up !== void 0; up = up.parent) if (up.caps !== void 0 && permits(up.caps, kind)) return `The capabilities do not permit the effect ${text} here. The kind ${typeName(up.doc.type)} around it narrows them for its parts.`;
3154
+ return `The capabilities do not permit the effect ${text}.`;
3155
+ }
3156
+ /**
2974
3157
  * This function checks a document before an evaluation, by the contracts of its kinds. It finds the problems
2975
3158
  * that an evaluation finds when it reaches them:
2976
3159
  *
@@ -2981,7 +3164,7 @@ function mapRefs(world, doc, change, options = {}) {
2981
3164
  * - `#DEPTH!`: too much nesting.
2982
3165
  * - `#REF!` and `#STEP?`: a bad path.
2983
3166
  * - `#NAME?`: a name that nothing binds.
2984
- * - `#CAP!`: an effect that the capabilities do not permit.
3167
+ * - `#CAP!`: an effect that the capabilities of its document do not permit. A kind around the document can narrow them.
2985
3168
  *
2986
3169
  * @param machine - The world and the code of the evaluators.
2987
3170
  * @param doc - The document, as JSON.
@@ -2999,7 +3182,8 @@ function check(machine, doc, options = {}) {
2999
3182
  issues.push({
3000
3183
  code: "#DEPTH!",
3001
3184
  place: v.place,
3002
- reason: `The nesting is deeper than ${maxDepth} documents.`
3185
+ reason: `The nesting is deeper than ${maxDepth} documents.`,
3186
+ at: v.at
3003
3187
  });
3004
3188
  return "skip";
3005
3189
  }
@@ -3009,7 +3193,8 @@ function check(machine, doc, options = {}) {
3009
3193
  issues.push({
3010
3194
  code: "#KIND?",
3011
3195
  place: v.place,
3012
- reason: `The world has no kind ${typeName(v.doc.type)}.`
3196
+ reason: `The world has no kind ${typeName(v.doc.type)}.`,
3197
+ at: v.at
3013
3198
  });
3014
3199
  return "skip";
3015
3200
  }
@@ -3017,31 +3202,48 @@ function check(machine, doc, options = {}) {
3017
3202
  if (name === void 0 || !has(machine.evaluators, name) && !has(machine.runners, name)) issues.push({
3018
3203
  code: "#NOEVAL",
3019
3204
  place: v.place,
3020
- reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`
3205
+ reason: `No rung gives an evaluator for ${typeName(v.doc.type)}.`,
3206
+ at: v.at
3021
3207
  });
3022
- if (options.caps !== void 0 && v.kind?.effects !== void 0) {
3208
+ const caps = v.caps;
3209
+ if (caps !== void 0 && v.kind?.effects !== void 0) {
3023
3210
  const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
3024
- for (const kind of kinds) if (!permits(options.caps, kind)) issues.push({
3211
+ for (const kind of kinds) if (!permits(caps, kind)) issues.push({
3025
3212
  code: "#CAP!",
3026
3213
  place: v.place,
3027
- reason: `The capabilities do not permit the effect ${kind.join(".")}.`
3214
+ reason: capReason(v, kind),
3215
+ at: v.at
3028
3216
  });
3029
3217
  }
3030
- if (!isRef(v.doc)) return void 0;
3218
+ const isPath = isRef(v.doc);
3219
+ if (!isPath && v.typePath?.length === 1 && v.typePath[0] === "ref") issues.push({
3220
+ code: "#VALUE!",
3221
+ place: v.place,
3222
+ reason: "The value of ref is a path: a list that starts with a root.",
3223
+ at: [...v.at, ...v.declared ? [] : ["value"]]
3224
+ });
3225
+ if (!isPath) return void 0;
3031
3226
  const path = v.doc.value;
3032
3227
  const root = path[0];
3228
+ const stepAt = (index) => [
3229
+ ...v.at,
3230
+ "value",
3231
+ ...index < path.length ? [index] : []
3232
+ ];
3033
3233
  if (typeof root !== "string" || !ROOTS.includes(root)) {
3034
3234
  issues.push({
3035
3235
  code: "#REF!",
3036
3236
  place: v.place,
3037
- reason: `A path starts with a root: ${ROOTS.join(", ")}.`
3237
+ reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
3238
+ at: stepAt(0)
3038
3239
  });
3039
3240
  return "skip";
3040
3241
  }
3041
3242
  if (!roots.includes(root)) issues.push({
3042
3243
  code: "#REF!",
3043
3244
  place: v.place,
3044
- reason: `The root ${root} is not open here. The open roots are ${roots.join(", ")}.`
3245
+ reason: `The root ${root} is not open here. The open roots are ${roots.join(", ")}.`,
3246
+ at: stepAt(0)
3045
3247
  });
3046
3248
  const first = path[1];
3047
3249
  if (root === "names" && !anyName && isKey(first) && !v.names.has(String(first))) {
@@ -3049,33 +3251,38 @@ function check(machine, doc, options = {}) {
3049
3251
  issues.push({
3050
3252
  code: "#NAME?",
3051
3253
  place: v.place,
3052
- reason: later ? `The name ${String(first)} is bound by a later step.` : `Nothing binds the name ${String(first)}.`
3254
+ reason: later ? `The name ${String(first)} is bound by a later step.` : `Nothing binds the name ${String(first)}.`,
3255
+ at: stepAt(1)
3053
3256
  });
3054
3257
  }
3055
- path.slice(1).forEach((step) => {
3056
- if (isKey(step)) return;
3258
+ path.forEach((step, index) => {
3259
+ if (index === 0 || isKey(step)) return;
3057
3260
  const found = isEnvelope(step) ? lookupKind(machine.world, step.type) : void 0;
3058
3261
  const walker = found?.ok === true && Object.hasOwn(found.kind.verbs, "step") ? found.kind.verbs["step"] : void 0;
3059
3262
  if (walker === void 0 || !has(machine.walkers, walker)) issues.push({
3060
3263
  code: "#STEP?",
3061
3264
  place: v.place,
3062
- reason: "A step is a key, or a document of a kind with the verb step."
3265
+ reason: "A step is a key, or a document of a kind with the verb step.",
3266
+ at: stepAt(index)
3063
3267
  });
3064
3268
  });
3065
- return "skip";
3066
3269
  }, {
3067
3270
  ...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
3068
3271
  ...Array.isArray(options.names) ? { names: options.names } : {},
3272
+ ...options.caps === void 0 ? {} : { caps: options.caps },
3069
3273
  problem: (problem) => issues.push({
3070
3274
  code: problem.code,
3071
3275
  place: problem.place,
3072
- reason: problem.reason
3276
+ reason: problem.reason,
3277
+ at: problem.at
3073
3278
  })
3074
3279
  });
3075
3280
  return issues;
3076
3281
  }
3077
3282
  //#endregion
3078
3283
  //#region src/formula.ts
3284
+ /** The mark of a field that the profile leaves out of a record. */
3285
+ const SKIPPED = Symbol("skipped");
3079
3286
  /** The value of a name whose binding failed. A reference to the name gives the error. */
3080
3287
  var Held = class {
3081
3288
  error;
@@ -3127,6 +3334,16 @@ function setOwn(target, key, value) {
3127
3334
  function compiled(compile) {
3128
3335
  return { compile };
3129
3336
  }
3337
+ /** This function evaluates parts in order. It gives the value of each part, or the first error. */
3338
+ function valuesOf(parts, ctx, rt) {
3339
+ const values = new Array(parts.length);
3340
+ for (let index = 0; index < parts.length; index += 1) {
3341
+ const result = parts[index].code(ctx, rt);
3342
+ if (!result.ok) return result;
3343
+ values[index] = result.value;
3344
+ }
3345
+ return values;
3346
+ }
3130
3347
  function partsOf(value) {
3131
3348
  return isRecord(value) ? value : {};
3132
3349
  }
@@ -3273,18 +3490,48 @@ function formulaEvaluators(profile = {}) {
3273
3490
  return Object.hasOwn(ops, name) ? ops[name] : void 0;
3274
3491
  };
3275
3492
  const notTruth = (what) => fail("#VALUE!", `The ${what} must give a truth value.`);
3493
+ const combine = profile.combine;
3494
+ /** This function evaluates each part. Without `combine`, the first error stops it. A field that `omit` skips is absent. */
3495
+ const collect = (parts, ctx, rt, skip) => {
3496
+ const values = new Array(parts.length);
3497
+ let errors;
3498
+ for (let index = 0; index < parts.length; index += 1) {
3499
+ const result = parts[index].code(ctx, rt);
3500
+ if (result.ok) values[index] = result.value;
3501
+ else if (skip !== void 0 && skip(result.error)) values[index] = SKIPPED;
3502
+ else if (combine === void 0) return result;
3503
+ else (errors ??= []).push(result.error);
3504
+ }
3505
+ if (errors === void 0 || combine === void 0) return values;
3506
+ return {
3507
+ ok: false,
3508
+ error: errors.length === 1 ? errors[0] : combine(errors)
3509
+ };
3510
+ };
3276
3511
  return {
3277
3512
  "formula.lit": compiled(({ doc }) => {
3278
3513
  const result = ok(doc.value);
3279
3514
  return () => result;
3280
3515
  }),
3281
- "formula.ref": compiled(({ doc }) => {
3516
+ "formula.ref": compiled(({ doc, parts, fill, address }) => {
3282
3517
  const path = doc.value;
3283
3518
  if (!Array.isArray(path) || path.length === 0) {
3284
3519
  const bad = fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
3285
3520
  return () => bad;
3286
3521
  }
3287
3522
  const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : void 0;
3523
+ const give = (found) => {
3524
+ if (!found.ok) return fail(found.code, found.reason);
3525
+ if (address === true) return ok(found.thing);
3526
+ const reached = valueOfThing(found.thing);
3527
+ if (reached instanceof Held) return {
3528
+ ok: false,
3529
+ error: reached.error
3530
+ };
3531
+ if (missing !== void 0 && missing(reached)) return fail("#N/A", "The path reaches no value.");
3532
+ return ok(reached);
3533
+ };
3534
+ const computed = parts.length > 0;
3288
3535
  return (ctx, rt) => {
3289
3536
  if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
3290
3537
  const bound = ctx.names[head];
@@ -3293,21 +3540,40 @@ function formulaEvaluators(profile = {}) {
3293
3540
  error: bound.error
3294
3541
  };
3295
3542
  }
3296
- const found = rt.resolve(ctx, path);
3297
- if (!found.ok) return fail(found.code, found.reason);
3298
- const value = valueOfThing(found.thing);
3299
- if (value instanceof Held) return {
3300
- ok: false,
3301
- error: value.error
3302
- };
3303
- if (missing !== void 0 && missing(value)) return fail("#N/A", "The path reaches no value.");
3304
- return ok(value);
3543
+ if (!computed) return give(rt.resolve(ctx, path));
3544
+ const steps = valuesOf(parts, ctx, rt);
3545
+ return Array.isArray(steps) ? give(rt.resolve(ctx, fill(steps))) : steps;
3546
+ };
3547
+ }),
3548
+ "formula.step": compiled(({ doc, parts, fill }) => {
3549
+ if (parts.length === 0) {
3550
+ const same = ok(doc);
3551
+ return () => same;
3552
+ }
3553
+ return (ctx, rt) => {
3554
+ const values = valuesOf(parts, ctx, rt);
3555
+ return Array.isArray(values) ? ok({
3556
+ ...doc,
3557
+ value: fill(values)
3558
+ }) : values;
3305
3559
  };
3306
3560
  }),
3307
3561
  "formula.record": compiled(({ value }) => {
3308
3562
  const fields = partsOf(value);
3309
3563
  const keys = Object.keys(fields);
3310
3564
  const codes = keys.map((key) => fields[key].code);
3565
+ if (combine !== void 0) {
3566
+ const parts = keys.map((key) => fields[key]);
3567
+ return (ctx, rt) => {
3568
+ const values = collect(parts, ctx, rt, omit);
3569
+ if (!Array.isArray(values)) return values;
3570
+ const out = {};
3571
+ keys.forEach((key, index) => {
3572
+ if (values[index] !== SKIPPED) setOwn(out, key, values[index]);
3573
+ });
3574
+ return ok(out);
3575
+ };
3576
+ }
3311
3577
  return (ctx, rt) => {
3312
3578
  const out = {};
3313
3579
  for (let index = 0; index < keys.length; index += 1) {
@@ -3322,13 +3588,8 @@ function formulaEvaluators(profile = {}) {
3322
3588
  };
3323
3589
  }),
3324
3590
  "formula.list": compiled(({ parts }) => (ctx, rt) => {
3325
- const out = new Array(parts.length);
3326
- for (let index = 0; index < parts.length; index += 1) {
3327
- const result = parts[index].code(ctx, rt);
3328
- if (!result.ok) return result;
3329
- out[index] = result.value;
3330
- }
3331
- return ok(out);
3591
+ const values = collect(parts, ctx, rt);
3592
+ return Array.isArray(values) ? ok(values) : values;
3332
3593
  }),
3333
3594
  "formula.let": compiled(({ value }) => {
3334
3595
  const record = partsOf(value);
@@ -3443,6 +3704,15 @@ function formulaEvaluators(profile = {}) {
3443
3704
  }
3444
3705
  const done = (out) => check === void 0 ? ok(out) : check(out, name) ?? ok(out);
3445
3706
  const failed = (error) => thrown(error, `the operation ${name}`);
3707
+ if (combine !== void 0) return (ctx, rt) => {
3708
+ const values = collect(parts, ctx, rt);
3709
+ if (!Array.isArray(values)) return values;
3710
+ try {
3711
+ return done(fn(...values));
3712
+ } catch (error) {
3713
+ return failed(error);
3714
+ }
3715
+ };
3446
3716
  if (parts.length === 1) {
3447
3717
  const [a] = parts;
3448
3718
  return (ctx, rt) => {
@@ -3490,9 +3760,9 @@ function formulaEvaluators(profile = {}) {
3490
3760
  const unknown = fail("#NAME?", `The language has no extension ${name}.`);
3491
3761
  return () => unknown;
3492
3762
  }
3493
- return () => {
3763
+ return (ctx) => {
3494
3764
  try {
3495
- return handler(doc.value);
3765
+ return handler(doc.value, ctx);
3496
3766
  } catch (error) {
3497
3767
  return thrown(error, `the extension ${name}`);
3498
3768
  }
@@ -3525,6 +3795,6 @@ function formulaMachine(profile = {}, more = {}) {
3525
3795
  };
3526
3796
  }
3527
3797
  //#endregion
3528
- export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaEvaluators, formulaKinds, formulaMachine, fromJson, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, narrow, nearestWalker, nodeAt, ok, pathMatches, permits, placed, planner, prepare, ref, refsOf, resolve, run, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, valueOfThing, walk, worldThing };
3798
+ export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaEvaluators, formulaKinds, formulaMachine, fromJson, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, narrow, nearestWalker, nodeAt, ok, partCaps, pathMatches, permits, placed, planner, prepare, ref, refsOf, resolve, run, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, valueOfThing, walk, worldThing };
3529
3799
 
3530
3800
  //# sourceMappingURL=index.js.map