@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/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
  }
@@ -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
- 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;
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: item.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: [...item.place, ...slot.path],
2892
- at: [...base, ...slot.path],
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 bound = root === "names" && isKey(first) && v.names.has(String(first));
2919
- out.push({
2920
- place: v.place,
2921
- at: v.at,
3010
+ const use = {
2922
3011
  path,
2923
3012
  root,
2924
- free: !bound
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
- return "skip";
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
- if (!isRef(v.doc)) return void 0;
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.slice(1).forEach((step) => {
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
- 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);
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 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);
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
  }