@mark1russell7/coeval 0.3.2 → 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,34 +1700,53 @@ function compileNode(c, value, place, declared, depth) {
1663
1700
  parts,
1664
1701
  place,
1665
1702
  machine: c.machine,
1666
- fill: (items) => split.parts.length === 0 ? doc.value : split.fill(items)
1703
+ fill: fillOf(split, doc),
1704
+ ...address ? { address: true } : {}
1667
1705
  };
1668
1706
  if (runner !== void 0) {
1669
- const inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill);
1670
- const run = function* (ctx, rt) {
1671
- try {
1672
- return placed(yield* inner(ctx, rt), place);
1673
- } catch (error) {
1674
- return placed(thrown(error, `the runner ${name}`), place);
1675
- }
1676
- };
1677
- const code = (ctx, rt) => rt.running ? fail("#CALC!", `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`, { place }) : drive(run(ctx, rt));
1678
- return finish(c, meta, code, run);
1707
+ const run = placedRun(isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill), place, name);
1708
+ return finish(c, meta, runnerCode(run, place, kindText), run);
1679
1709
  }
1680
- const inner = isCompiledEvaluator(evaluator) ? evaluator.compile(input) : interpret(evaluator, input, split.fill);
1681
- const code = (ctx, rt) => {
1710
+ const code = placedCode(isCompiledEvaluator(evaluator) ? evaluator.compile(input) : interpret(evaluator, input, split.fill), place, name);
1711
+ return finish(c, meta, code, runOf(code));
1712
+ }
1713
+ /** This function gives the code of a document: the code of its evaluator, with the place of each error and of each exception. */
1714
+ function placedCode(inner, place, name) {
1715
+ return (ctx, rt) => {
1682
1716
  try {
1683
1717
  return placed(inner(ctx, rt), place);
1684
1718
  } catch (error) {
1685
1719
  return placed(thrown(error, `the evaluator ${name}`), place);
1686
1720
  }
1687
1721
  };
1688
- return finish(c, meta, code, function* (ctx, rt) {
1722
+ }
1723
+ /** This function gives the steps of a runner, with the place of each error and of each exception. */
1724
+ function placedRun(inner, place, name) {
1725
+ return function* (ctx, rt) {
1726
+ try {
1727
+ return placed(yield* inner(ctx, rt), place);
1728
+ } catch (error) {
1729
+ return placed(thrown(error, `the runner ${name}`), place);
1730
+ }
1731
+ };
1732
+ }
1733
+ /** This function gives the code of a runner in an evaluation: its steps, which cannot wait. */
1734
+ function runnerCode(run, place, kindText) {
1735
+ return (ctx, rt) => rt.running ? fail("#CALC!", `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`, { place }) : drive(run(ctx, rt));
1736
+ }
1737
+ /** This function gives the steps of a run of an evaluator. The code runs at the first step, and it does not wait. */
1738
+ function runOf(code) {
1739
+ return function* (ctx, rt) {
1689
1740
  return code(ctx, rt);
1690
- });
1741
+ };
1742
+ }
1743
+ /** This function gives the fill of a compile input: the value with an item in the place of each part. */
1744
+ function fillOf(split, doc) {
1745
+ return (items) => split.parts.length === 0 ? doc.value : split.fill(items);
1691
1746
  }
1692
1747
  function interpret(fn, input, fill) {
1693
1748
  const { kind, typePath, doc, data, place } = input;
1749
+ const address = input.address === true ? { address: true } : {};
1694
1750
  const compiled = input.parts;
1695
1751
  const hasParts = compiled.length > 0;
1696
1752
  return (ctx, rt) => {
@@ -1709,6 +1765,7 @@ function interpret(fn, input, fill) {
1709
1765
  parts,
1710
1766
  context: ctx,
1711
1767
  place,
1768
+ ...address,
1712
1769
  resolve: (path, options) => rt.resolve(ctx, path, options),
1713
1770
  effect: (effectKind, payload) => rt.effect(ctx, effectKind, payload, place),
1714
1771
  fill: input.fill
@@ -1781,16 +1838,20 @@ function prepare(machine, doc, options = {}) {
1781
1838
  code: root.code,
1782
1839
  run: root.run
1783
1840
  };
1841
+ return planOf(machine, c.traced, compiled);
1842
+ }
1843
+ /** This function makes the object of a plan. Its closures keep only the machine and the compiled root. */
1844
+ function planOf(machine, traced, root) {
1784
1845
  return {
1785
1846
  machine,
1786
- traced: c.traced,
1787
- root: compiled,
1847
+ traced,
1848
+ root,
1788
1849
  evaluate(context = makeContext(), evalOptions) {
1789
- const s = new Session(machine, false, c.traced, void 0, void 0, evalOptions?.effects);
1850
+ const s = new Session(machine, false, traced, void 0, void 0, evalOptions?.effects);
1790
1851
  return root.code(rootContext(machine, context), s);
1791
1852
  },
1792
1853
  run(context = makeContext(), runOptions = {}) {
1793
- const s = new Session(machine, true, c.traced, runOptions.perform, runOptions.signal, void 0);
1854
+ const s = new Session(machine, true, traced, runOptions.perform, runOptions.signal, void 0);
1794
1855
  return driveWaiting(root.run(rootContext(machine, context), s), runOptions.signal);
1795
1856
  }
1796
1857
  };
@@ -2790,10 +2851,61 @@ function withoutNames(options) {
2790
2851
  const { names: _host, ...rest } = options;
2791
2852
  return rest;
2792
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
+ };
2793
2904
  /**
2794
2905
  * This function visits each document of a document: the root and each part at each depth, by the contracts of the
2795
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
2796
- * 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.
2797
2909
  *
2798
2910
  * @param world - The world of the kinds.
2799
2911
  * @param doc - The document, as JSON.
@@ -2801,23 +2913,25 @@ function withoutNames(options) {
2801
2913
  * @param options - The type of a bare root value, the names of the host, and a function for the problems.
2802
2914
  */
2803
2915
  function walk(world, doc, visit, options = {}) {
2804
- const names = options.names === void 0 || options.names.length === 0 ? NO_NAMES : new Set(options.names);
2805
2916
  const stack = [{
2806
2917
  value: doc,
2807
- place: [],
2808
- at: [],
2918
+ place: void 0,
2919
+ at: void 0,
2809
2920
  declared: options.declaredType,
2810
2921
  depth: 0,
2811
- names,
2812
- 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
2813
2927
  }];
2814
2928
  while (stack.length > 0) {
2815
2929
  const item = stack.pop();
2816
2930
  const envelope = isEnvelope(item.value) ? item.value : void 0;
2817
2931
  if (envelope === void 0 && item.declared === void 0) {
2818
2932
  options.problem?.({
2819
- place: item.place,
2820
- at: item.at,
2933
+ place: keysOf(item.place),
2934
+ at: keysOf(item.at),
2821
2935
  code: "#PART?",
2822
2936
  reason: "The value has no envelope, and its position declares no type."
2823
2937
  });
@@ -2829,50 +2943,49 @@ function walk(world, doc, visit, options = {}) {
2829
2943
  };
2830
2944
  const found = lookupKind(world, doc.type);
2831
2945
  const kind = found.ok ? found.kind : void 0;
2832
- if (visit(kind === void 0 ? {
2833
- place: item.place,
2834
- at: item.at,
2835
- doc,
2836
- declared: envelope === void 0,
2837
- depth: item.depth,
2838
- names: item.names,
2839
- later: item.later
2840
- } : {
2841
- place: item.place,
2842
- at: item.at,
2843
- doc,
2844
- declared: envelope === void 0,
2845
- kind,
2846
- typePath: found.ok ? found.path : [],
2847
- depth: item.depth,
2848
- names: item.names,
2849
- later: item.later
2850
- }) === "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;
2851
2948
  const split = splitParts(kind.parts, doc.value);
2949
+ const base = envelope === void 0 ? item.at : {
2950
+ up: item.at,
2951
+ keys: ["value"]
2952
+ };
2852
2953
  if (!split.ok) {
2853
2954
  const at = split.at.length === 0 ? "" : ` (at ${split.at.join(".")})`;
2955
+ const where = keysOf({
2956
+ up: base,
2957
+ keys: split.at
2958
+ });
2854
2959
  options.problem?.({
2855
- place: item.place,
2856
- at: item.at,
2960
+ place: keysOf(item.place),
2961
+ at: where,
2857
2962
  code: "#VALUE!",
2858
2963
  reason: `${split.reason}${at} The kind is ${typeName(doc.type)}.`
2859
2964
  });
2860
2965
  continue;
2861
2966
  }
2862
2967
  const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
2863
- const base = envelope === void 0 ? item.at : [...item.at, "value"];
2864
2968
  for (let index = split.parts.length - 1; index >= 0; index -= 1) {
2865
2969
  const slot = split.parts[index];
2866
2970
  const extra = sees[index] ?? [];
2867
2971
  const hidden = later[index] ?? [];
2868
2972
  stack.push({
2869
2973
  value: slot.item,
2870
- place: [...item.place, ...slot.path],
2871
- 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
+ },
2872
2982
  declared: slot.of,
2873
2983
  depth: item.depth + 1,
2874
2984
  names: extra.length === 0 ? item.names : /* @__PURE__ */ new Set([...item.names, ...extra]),
2875
- 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
2876
2989
  });
2877
2990
  }
2878
2991
  }
@@ -2880,7 +2993,7 @@ function walk(world, doc, visit, options = {}) {
2880
2993
  /**
2881
2994
  * This function finds each reference of a document: each document of the type `ref` that a walk reaches. A
2882
2995
  * reference to a name that a kind around it binds is not free. The others are what the document reads from
2883
- * outside: its dependencies.
2996
+ * outside: its dependencies. When the kind `ref` has parts, the walk also finds the references in its step documents.
2884
2997
  *
2885
2998
  * @param world - The world of the kinds.
2886
2999
  * @param doc - The document, as JSON.
@@ -2894,15 +3007,25 @@ function refsOf(world, doc, options = {}) {
2894
3007
  const path = v.doc.value;
2895
3008
  const root = typeof path[0] === "string" ? path[0] : "";
2896
3009
  const first = path[1];
2897
- const bound = root === "names" && isKey(first) && v.names.has(String(first));
2898
- out.push({
2899
- place: v.place,
2900
- at: v.at,
3010
+ const use = {
2901
3011
  path,
2902
3012
  root,
2903
- 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
2904
3019
  });
2905
- return "skip";
3020
+ Object.defineProperty(use, "at", {
3021
+ enumerable: true,
3022
+ get: () => v.at
3023
+ });
3024
+ Object.defineProperty(use, "visit", {
3025
+ enumerable: false,
3026
+ value: v
3027
+ });
3028
+ out.push(use);
2906
3029
  }, options.names === void 0 ? options : withoutNames(options));
2907
3030
  return out;
2908
3031
  }
@@ -2978,7 +3101,8 @@ function check(machine, doc, options = {}) {
2978
3101
  issues.push({
2979
3102
  code: "#DEPTH!",
2980
3103
  place: v.place,
2981
- reason: `The nesting is deeper than ${maxDepth} documents.`
3104
+ reason: `The nesting is deeper than ${maxDepth} documents.`,
3105
+ at: v.at
2982
3106
  });
2983
3107
  return "skip";
2984
3108
  }
@@ -2988,7 +3112,8 @@ function check(machine, doc, options = {}) {
2988
3112
  issues.push({
2989
3113
  code: "#KIND?",
2990
3114
  place: v.place,
2991
- 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
2992
3117
  });
2993
3118
  return "skip";
2994
3119
  }
@@ -2996,31 +3121,47 @@ function check(machine, doc, options = {}) {
2996
3121
  if (name === void 0 || !has(machine.evaluators, name) && !has(machine.runners, name)) issues.push({
2997
3122
  code: "#NOEVAL",
2998
3123
  place: v.place,
2999
- 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
3000
3126
  });
3001
3127
  if (options.caps !== void 0 && v.kind?.effects !== void 0) {
3002
3128
  const kinds = v.kind.effects === "type" ? [v.typePath ?? []] : v.kind.effects.map((kind) => kind.split("."));
3003
3129
  for (const kind of kinds) if (!permits(options.caps, kind)) issues.push({
3004
3130
  code: "#CAP!",
3005
3131
  place: v.place,
3006
- 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
3007
3134
  });
3008
3135
  }
3009
- 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;
3010
3144
  const path = v.doc.value;
3011
3145
  const root = path[0];
3146
+ const stepAt = (index) => [
3147
+ ...v.at,
3148
+ "value",
3149
+ ...index < path.length ? [index] : []
3150
+ ];
3012
3151
  if (typeof root !== "string" || !ROOTS.includes(root)) {
3013
3152
  issues.push({
3014
3153
  code: "#REF!",
3015
3154
  place: v.place,
3016
- reason: `A path starts with a root: ${ROOTS.join(", ")}.`
3155
+ reason: `A path starts with a root: ${ROOTS.join(", ")}.`,
3156
+ at: stepAt(0)
3017
3157
  });
3018
3158
  return "skip";
3019
3159
  }
3020
3160
  if (!roots.includes(root)) issues.push({
3021
3161
  code: "#REF!",
3022
3162
  place: v.place,
3023
- 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)
3024
3165
  });
3025
3166
  const first = path[1];
3026
3167
  if (root === "names" && !anyName && isKey(first) && !v.names.has(String(first))) {
@@ -3028,33 +3169,37 @@ function check(machine, doc, options = {}) {
3028
3169
  issues.push({
3029
3170
  code: "#NAME?",
3030
3171
  place: v.place,
3031
- 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)
3032
3174
  });
3033
3175
  }
3034
- path.slice(1).forEach((step) => {
3035
- if (isKey(step)) return;
3176
+ path.forEach((step, index) => {
3177
+ if (index === 0 || isKey(step)) return;
3036
3178
  const found = isEnvelope(step) ? lookupKind(machine.world, step.type) : void 0;
3037
3179
  const walker = found?.ok === true && Object.hasOwn(found.kind.verbs, "step") ? found.kind.verbs["step"] : void 0;
3038
3180
  if (walker === void 0 || !has(machine.walkers, walker)) issues.push({
3039
3181
  code: "#STEP?",
3040
3182
  place: v.place,
3041
- 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)
3042
3185
  });
3043
3186
  });
3044
- return "skip";
3045
3187
  }, {
3046
3188
  ...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
3047
3189
  ...Array.isArray(options.names) ? { names: options.names } : {},
3048
3190
  problem: (problem) => issues.push({
3049
3191
  code: problem.code,
3050
3192
  place: problem.place,
3051
- reason: problem.reason
3193
+ reason: problem.reason,
3194
+ at: problem.at
3052
3195
  })
3053
3196
  });
3054
3197
  return issues;
3055
3198
  }
3056
3199
  //#endregion
3057
3200
  //#region src/formula.ts
3201
+ /** The mark of a field that the profile leaves out of a record. */
3202
+ const SKIPPED = Symbol("skipped");
3058
3203
  /** The value of a name whose binding failed. A reference to the name gives the error. */
3059
3204
  var Held = class {
3060
3205
  error;
@@ -3106,6 +3251,16 @@ function setOwn(target, key, value) {
3106
3251
  function compiled(compile) {
3107
3252
  return { compile };
3108
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
+ }
3109
3264
  function partsOf(value) {
3110
3265
  return isRecord(value) ? value : {};
3111
3266
  }
@@ -3252,18 +3407,48 @@ function formulaEvaluators(profile = {}) {
3252
3407
  return Object.hasOwn(ops, name) ? ops[name] : void 0;
3253
3408
  };
3254
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
+ };
3255
3428
  return {
3256
3429
  "formula.lit": compiled(({ doc }) => {
3257
3430
  const result = ok(doc.value);
3258
3431
  return () => result;
3259
3432
  }),
3260
- "formula.ref": compiled(({ doc }) => {
3433
+ "formula.ref": compiled(({ doc, parts, fill, address }) => {
3261
3434
  const path = doc.value;
3262
3435
  if (!Array.isArray(path) || path.length === 0) {
3263
3436
  const bad = fail("#VALUE!", "The value of ref is a path: a list that starts with a root, for example [\"names\", \"a\"].");
3264
3437
  return () => bad;
3265
3438
  }
3266
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;
3267
3452
  return (ctx, rt) => {
3268
3453
  if (head !== void 0 && Object.hasOwn(ctx.names, head)) {
3269
3454
  const bound = ctx.names[head];
@@ -3272,21 +3457,40 @@ function formulaEvaluators(profile = {}) {
3272
3457
  error: bound.error
3273
3458
  };
3274
3459
  }
3275
- const found = rt.resolve(ctx, path);
3276
- if (!found.ok) return fail(found.code, found.reason);
3277
- const value = valueOfThing(found.thing);
3278
- if (value instanceof Held) return {
3279
- ok: false,
3280
- error: value.error
3281
- };
3282
- if (missing !== void 0 && missing(value)) return fail("#N/A", "The path reaches no value.");
3283
- 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;
3284
3476
  };
3285
3477
  }),
3286
3478
  "formula.record": compiled(({ value }) => {
3287
3479
  const fields = partsOf(value);
3288
3480
  const keys = Object.keys(fields);
3289
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
+ }
3290
3494
  return (ctx, rt) => {
3291
3495
  const out = {};
3292
3496
  for (let index = 0; index < keys.length; index += 1) {
@@ -3301,13 +3505,8 @@ function formulaEvaluators(profile = {}) {
3301
3505
  };
3302
3506
  }),
3303
3507
  "formula.list": compiled(({ parts }) => (ctx, rt) => {
3304
- const out = new Array(parts.length);
3305
- for (let index = 0; index < parts.length; index += 1) {
3306
- const result = parts[index].code(ctx, rt);
3307
- if (!result.ok) return result;
3308
- out[index] = result.value;
3309
- }
3310
- return ok(out);
3508
+ const values = collect(parts, ctx, rt);
3509
+ return Array.isArray(values) ? ok(values) : values;
3311
3510
  }),
3312
3511
  "formula.let": compiled(({ value }) => {
3313
3512
  const record = partsOf(value);
@@ -3422,6 +3621,15 @@ function formulaEvaluators(profile = {}) {
3422
3621
  }
3423
3622
  const done = (out) => check === void 0 ? ok(out) : check(out, name) ?? ok(out);
3424
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
+ };
3425
3633
  if (parts.length === 1) {
3426
3634
  const [a] = parts;
3427
3635
  return (ctx, rt) => {
@@ -3469,9 +3677,9 @@ function formulaEvaluators(profile = {}) {
3469
3677
  const unknown = fail("#NAME?", `The language has no extension ${name}.`);
3470
3678
  return () => unknown;
3471
3679
  }
3472
- return () => {
3680
+ return (ctx) => {
3473
3681
  try {
3474
- return handler(doc.value);
3682
+ return handler(doc.value, ctx);
3475
3683
  } catch (error) {
3476
3684
  return thrown(error, `the extension ${name}`);
3477
3685
  }