@mark1russell7/coeval 0.6.2 → 0.6.3

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
@@ -184,19 +184,36 @@ function fromJson(value) {
184
184
  * with this function does not keep a stale host value.
185
185
  *
186
186
  * The function does not use the stack for the depth, thus a deep document cannot overflow it. A cycle of lists or
187
- * records does not make it loop. `sameValue` of `agree` uses the same rules, and it adds rules for closures and for
188
- * a rule of the caller.
187
+ * records does not make it loop. `sameValue` of `agree` and `sameResult` use the same walk, and each adds its rules
188
+ * for other values (D20).
189
189
  *
190
190
  * @param a - The first value.
191
191
  * @param b - The second value.
192
192
  * @returns `true` when the values are equal.
193
193
  */
194
194
  function docEquals(a, b) {
195
+ return equalWith(a, b, void 0);
196
+ }
197
+ /**
198
+ * This function is the one walk of `docEquals`, `sameValue` and `sameResult` (D20). It compares two values by the
199
+ * rules of `docEquals`. A rule of the caller answers first for each pair that is not the same by `Object.is`.
200
+ *
201
+ * @param a - The first value.
202
+ * @param b - The second value.
203
+ * @param rule - The rule of the caller. Its `undefined` gives the rules of `docEquals`.
204
+ * @returns `true` when the values are equal.
205
+ */
206
+ function equalWith(a, b, rule) {
195
207
  const stack = [[a, b]];
196
208
  let seen;
197
209
  while (stack.length > 0) {
198
210
  const [x, y] = stack.pop();
199
211
  if (Object.is(x, y)) continue;
212
+ const told = rule?.(x, y);
213
+ if (told !== void 0) {
214
+ if (told) continue;
215
+ return false;
216
+ }
200
217
  const list = Array.isArray(x);
201
218
  if (list !== Array.isArray(y)) return false;
202
219
  if (!list && (!isRecord(x) || !isRecord(y))) return false;
@@ -1143,10 +1160,10 @@ function plainStep(thing, key) {
1143
1160
  function derefStep(nav, thing, key) {
1144
1161
  if (isHostThing(thing)) {
1145
1162
  const reached = thing.deref(key);
1146
- return reached === void 0 ? void 0 : {
1163
+ return reached !== void 0 || thing.has?.(key) === true ? {
1147
1164
  thing: reached,
1148
1165
  by: "own"
1149
- };
1166
+ } : void 0;
1150
1167
  }
1151
1168
  if (nav.plain !== true && isEnvelope(thing)) {
1152
1169
  const envelope = thing;
@@ -1843,6 +1860,43 @@ function capsUnder(rules, caps) {
1843
1860
  function partCaps(rules, path, caps) {
1844
1861
  return capsUnder(rulesFor(rules, path), caps);
1845
1862
  }
1863
+ /** The key of the journal of a recorder or of a replayer, on its function. Two copies of the kernel share it. */
1864
+ const JOURNAL = /* @__PURE__ */ Symbol.for("coeval.journal");
1865
+ /** This function gives the journal of a function `perform`, when it has one. */
1866
+ function journalOf(perform) {
1867
+ return perform === void 0 ? void 0 : perform[JOURNAL];
1868
+ }
1869
+ /**
1870
+ * This function gives the outcome of an effect that the host failed. A `Failure` gives its own error value, for
1871
+ * example the recorded error of a replay or `#REPLAY!`. Each other exception gives `#CALC!`, with the exception as the
1872
+ * cause (RUN.CAUSE).
1873
+ */
1874
+ function hostFailed(kind, place, error) {
1875
+ if (error instanceof Failure) return placed({
1876
+ ok: false,
1877
+ error: error.error
1878
+ }, place);
1879
+ return fail("#CALC!", `The host failed the effect ${kind}: ${describe(error)}`, {
1880
+ place,
1881
+ cause: error
1882
+ });
1883
+ }
1884
+ /**
1885
+ * This function gives the text of a place in an address: each key, with a slash between two keys. A text key with
1886
+ * other characters than letters, digits, `_`, `$` and `-` gets quotes.
1887
+ *
1888
+ * @param place - The keys from the root document to a document.
1889
+ * @returns The text of the place.
1890
+ */
1891
+ function placeText(place) {
1892
+ let text = "";
1893
+ for (let index = 0; index < place.length; index += 1) {
1894
+ const key = place[index];
1895
+ if (index > 0) text += "/";
1896
+ text += typeof key === "number" || /^[\w$-]+$/.test(key) ? String(key) : JSON.stringify(key);
1897
+ }
1898
+ return text;
1899
+ }
1846
1900
  function selfLabel(self) {
1847
1901
  try {
1848
1902
  if (isHostThing(self)) return self.label;
@@ -1873,6 +1927,11 @@ var Session = class {
1873
1927
  nextId = 0;
1874
1928
  current;
1875
1929
  holder;
1930
+ /**
1931
+ * The scope of the addresses of the code that runs at this time (RUN.ADDRESS). `all` changes it for each item. The
1932
+ * root scope is made at the first effect or `all`, thus an evaluation without effects makes no scope.
1933
+ */
1934
+ scope;
1876
1935
  constructor(machine, running, traced, perform, signal, effects) {
1877
1936
  this.machine = machine;
1878
1937
  this.running = running;
@@ -1894,12 +1953,22 @@ var Session = class {
1894
1953
  permitted(ctx, keys) {
1895
1954
  return this.machine.permits === void 0 ? permits(ctx.caps, keys) : this.machine.permits(ctx.caps, keys, ctx);
1896
1955
  }
1956
+ /** This function checks the capability of an effect, and records the effect with its address (RUN.ADDRESS). */
1897
1957
  record(ctx, kind, payload, place) {
1898
1958
  const keys = typeof kind === "string" ? kind.split(".") : kind;
1899
1959
  const text = keys.join(".");
1900
1960
  if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
1901
1961
  const node = this.current;
1902
1962
  const list = this.effects;
1963
+ const scope = this.scope ??= {
1964
+ prefix: "",
1965
+ counts: void 0,
1966
+ alls: 0
1967
+ };
1968
+ const at = placeText(place);
1969
+ const counts = scope.counts ??= /* @__PURE__ */ new Map();
1970
+ const count = counts.get(at) ?? 0;
1971
+ counts.set(at, count + 1);
1903
1972
  const effect = {
1904
1973
  index: list.length,
1905
1974
  kind: text,
@@ -1908,40 +1977,63 @@ var Session = class {
1908
1977
  node: node?.id ?? -1,
1909
1978
  at: this.tick,
1910
1979
  place,
1911
- context: ctx
1980
+ context: ctx,
1981
+ address: `${scope.prefix}${at}#${count}`
1912
1982
  };
1913
1983
  list.push(effect);
1914
1984
  if (node !== void 0) node.effects.push(effect.index);
1915
1985
  return effect;
1916
1986
  }
1987
+ /**
1988
+ * This function answers an effect at once, without a wait: the mock gives the result, or the effect gives its
1989
+ * payload without a mock (RUN.MOCK, RUN.EVAL). A promise from the mock gives `#CALC!`.
1990
+ */
1991
+ answer(effect, place) {
1992
+ const perform = this.perform;
1993
+ if (perform === void 0) return effect.outcome = ok(effect.payload);
1994
+ let done;
1995
+ try {
1996
+ done = perform(effect);
1997
+ } catch (error) {
1998
+ return effect.outcome = hostFailed(effect.kind, place, error);
1999
+ }
2000
+ if (!(done instanceof Promise)) return effect.outcome = ok(done);
2001
+ done.catch(() => void 0);
2002
+ return effect.outcome = fail("#CALC!", `The mock gave a promise for the effect ${effect.kind}, and an evaluation does not wait.`, { place });
2003
+ }
1917
2004
  effect(ctx, kind, payload, place) {
1918
2005
  const text = typeof kind === "string" ? kind : kind.join(".");
1919
2006
  if (this.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
1920
2007
  const made = this.record(ctx, kind, payload, place);
1921
- return "ok" in made ? made : ok(payload);
2008
+ return "ok" in made ? made : this.answer(made, place);
1922
2009
  }
1923
2010
  *runEffect(ctx, kind, payload, place) {
1924
2011
  const made = this.record(ctx, kind, payload, place);
1925
2012
  if ("ok" in made) return made;
1926
2013
  const perform = this.perform;
1927
- if (!this.running || perform === void 0) return ok(payload);
1928
- const failed = (error) => fail("#CALC!", `The host failed the effect ${made.kind}: ${describe(error)}`, {
1929
- place,
1930
- cause: error
1931
- });
2014
+ if (!this.running || perform === void 0) return this.answer(made, place);
1932
2015
  let done;
1933
2016
  try {
1934
2017
  done = perform(made);
1935
2018
  } catch (error) {
1936
- return failed(error);
2019
+ return made.outcome = hostFailed(made.kind, place, error);
1937
2020
  }
1938
- if (!(done instanceof Promise)) return ok(done);
1939
- return yield done.then((value) => ok(value), failed);
2021
+ if (!(done instanceof Promise)) return made.outcome = ok(done);
2022
+ return made.outcome = yield done.then((value) => ok(value), (error) => hostFailed(made.kind, place, error));
1940
2023
  }
1941
2024
  *all(list, options = {}) {
1942
2025
  const results = new Array(list.length);
1943
2026
  const limit = Math.max(1, options.concurrency ?? list.length);
1944
2027
  const failFast = options.failFast === true;
2028
+ const outer = this.scope ??= {
2029
+ prefix: "",
2030
+ counts: void 0,
2031
+ alls: 0
2032
+ };
2033
+ const address = `${outer.prefix}all${outer.alls}`;
2034
+ outer.alls += 1;
2035
+ const scopes = new Array(list.length);
2036
+ const journal = journalOf(this.perform);
1945
2037
  let failed = false;
1946
2038
  const active = [];
1947
2039
  let next = 0;
@@ -1951,11 +2043,15 @@ var Session = class {
1951
2043
  };
1952
2044
  const advance = (index, steps, input) => {
1953
2045
  let step;
2046
+ const before = this.scope;
2047
+ this.scope = scopes[index];
1954
2048
  try {
1955
2049
  step = input.threw ? steps.throw(input.error) : steps.next(input.value);
1956
2050
  } catch (error) {
1957
2051
  settle(index, thrown(error, "a part"));
1958
2052
  return;
2053
+ } finally {
2054
+ this.scope = before;
1959
2055
  }
1960
2056
  if (step.done === true) {
1961
2057
  settle(index, step.value);
@@ -1981,20 +2077,33 @@ var Session = class {
1981
2077
  while (!failed && active.length < limit && next < list.length) {
1982
2078
  const index = next;
1983
2079
  next += 1;
2080
+ const scope = {
2081
+ prefix: `${address}.${index}/`,
2082
+ counts: void 0,
2083
+ alls: 0
2084
+ };
2085
+ scopes[index] = scope;
1984
2086
  const begin = list[index];
1985
2087
  let steps;
2088
+ const before = this.scope;
2089
+ this.scope = scope;
1986
2090
  try {
1987
2091
  steps = begin();
1988
2092
  } catch (error) {
1989
2093
  settle(index, thrown(error, "a part"));
1990
2094
  continue;
2095
+ } finally {
2096
+ this.scope = before;
1991
2097
  }
1992
2098
  advance(index, steps, { threw: false });
1993
2099
  }
1994
2100
  };
1995
2101
  start();
1996
2102
  while (active.length > 0 && !failed) {
1997
- const settled = yield Promise.race(active.map((item) => item.pending));
2103
+ const chosen = journal?.next?.(address, active.map((item) => item.index));
2104
+ const pick = chosen === void 0 ? void 0 : active.find((item) => item.index === chosen);
2105
+ const settled = yield pick === void 0 ? Promise.race(active.map((item) => item.pending)) : pick.pending;
2106
+ journal?.settled?.(address, settled.item.index);
1998
2107
  active.splice(active.indexOf(settled.item), 1);
1999
2108
  advance(settled.item.index, settled.item.steps, settled);
2000
2109
  start();
@@ -2449,7 +2558,7 @@ function planOf(machine, traced, root) {
2449
2558
  traced,
2450
2559
  root,
2451
2560
  evaluate(context = makeContext(), evalOptions) {
2452
- const s = new Session(machine, false, traced, void 0, void 0, evalOptions?.effects);
2561
+ const s = new Session(machine, false, traced, evalOptions?.perform, void 0, evalOptions?.effects);
2453
2562
  return root.code(rootContext(machine, context), s);
2454
2563
  },
2455
2564
  run(context = makeContext(), runOptions = {}) {
@@ -2487,7 +2596,7 @@ function planner(machine, options = {}) {
2487
2596
  return plan;
2488
2597
  };
2489
2598
  }
2490
- function record$1(s, result) {
2599
+ function record(s, result) {
2491
2600
  const root = s.holder?.children[0];
2492
2601
  if (root === void 0) throw new Error("The evaluation made no trace.");
2493
2602
  return {
@@ -2502,21 +2611,24 @@ function record$1(s, result) {
2502
2611
  *
2503
2612
  * The verb `eval` goes up the ladder for each document. The kind of the document tells where its parts are. The
2504
2613
  * evaluator gets each part sealed: it can evaluate the part, and it cannot read its value. A failure gives an error
2505
- * value, and the function does not throw. An effect is recorded in its order, and nothing does it.
2614
+ * value, and the function does not throw. An effect is recorded in its order, and the host does not do it. A mock
2615
+ * can answer each effect at once (RUN.MOCK).
2506
2616
  *
2507
2617
  * @param machine - The world and the code of the evaluators.
2508
2618
  * @param doc - The document, as JSON.
2509
2619
  * @param context - The names and the capabilities at the root.
2510
- * @param declaredType - The type of a bare root value.
2620
+ * @param options - The type of a bare root value, or the options: that type and the mock of the host.
2511
2621
  * @returns The result, the trace and the effects in their order.
2512
2622
  */
2513
- function evaluate(machine, doc, context = makeContext(), declaredType) {
2623
+ function evaluate(machine, doc, context = makeContext(), options) {
2624
+ const given = typeof options === "string" ? { declaredType: options } : options ?? {};
2625
+ const declaredType = given.declaredType;
2514
2626
  const plan = prepare(machine, doc, {
2515
2627
  trace: true,
2516
2628
  ...declaredType === void 0 ? {} : { declaredType }
2517
2629
  });
2518
- const s = new Session(machine, false, true, void 0, void 0, void 0);
2519
- return record$1(s, plan.root.code(rootContext(machine, context), s));
2630
+ const s = new Session(machine, false, true, given.perform, void 0, void 0);
2631
+ return record(s, plan.root.code(rootContext(machine, context), s));
2520
2632
  }
2521
2633
  /**
2522
2634
  * This function runs a document: an evaluation in which the host does each effect. A runner waits for its
@@ -2535,7 +2647,7 @@ async function run(machine, doc, context = makeContext(), options = {}) {
2535
2647
  ...options.declaredType === void 0 ? {} : { declaredType: options.declaredType }
2536
2648
  });
2537
2649
  const s = new Session(machine, true, true, options.perform, options.signal, void 0);
2538
- return record$1(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
2650
+ return record(s, await driveWaiting(plan.root.run(rootContext(machine, context), s), options.signal));
2539
2651
  }
2540
2652
  /**
2541
2653
  * This function finds a node of a trace by its path.
@@ -2971,6 +3083,9 @@ function treeThing(parts, context, type, place, build) {
2971
3083
  if (!result.ok) throw new Failure(result.error);
2972
3084
  return result.value;
2973
3085
  },
3086
+ has(key) {
3087
+ return key === "parent" || key === "type" || parts.some((part) => part.key === String(key));
3088
+ },
2974
3089
  toJson() {
2975
3090
  const result = state.json(/* @__PURE__ */ new Set());
2976
3091
  if (!result.ok) throw new Failure(result.error);
@@ -4581,13 +4696,20 @@ function resolveFully(machine, ctx, path, options) {
4581
4696
  hops: []
4582
4697
  };
4583
4698
  }
4584
- /** This function records an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). */
4585
- function record(env, ctx, kind, payload, place) {
4699
+ /**
4700
+ * This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
4701
+ * address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
4702
+ * mock, the effect gives its payload (RUN.EVAL).
4703
+ */
4704
+ function cause(env, ctx, kind, payload, place) {
4586
4705
  const keys = typeof kind === "string" ? kind.split(".") : kind;
4587
4706
  const text = keys.join(".");
4588
4707
  const machine = env.machine;
4589
4708
  if (!(machine.permits === void 0 ? permits(ctx.caps, keys) : machine.permits(ctx.caps, keys, ctx))) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
4590
- env.effects.push({
4709
+ const at = placeText(place);
4710
+ const count = env.scope.counts.get(at) ?? 0;
4711
+ env.scope.counts.set(at, count + 1);
4712
+ const effect = {
4591
4713
  index: env.effects.length,
4592
4714
  kind: text,
4593
4715
  path: keys,
@@ -4595,8 +4717,35 @@ function record(env, ctx, kind, payload, place) {
4595
4717
  node: -1,
4596
4718
  at: 0,
4597
4719
  place,
4598
- context: ctx
4599
- });
4720
+ context: ctx,
4721
+ address: `${env.scope.prefix}${at}#${count}`
4722
+ };
4723
+ env.effects.push(effect);
4724
+ effect.outcome = answer(env.perform, effect, place);
4725
+ return effect.outcome;
4726
+ }
4727
+ /**
4728
+ * This function gives the answer of the mock to an effect. A throw gives `#CALC!` with the cause, and a `Failure`
4729
+ * gives its own error value. A promise gives `#CALC!`, because an evaluation does not wait.
4730
+ */
4731
+ function answer(perform, effect, place) {
4732
+ if (perform === void 0) return ok(effect.payload);
4733
+ let given;
4734
+ try {
4735
+ given = perform(effect);
4736
+ } catch (error) {
4737
+ if (error instanceof Failure) return placed({
4738
+ ok: false,
4739
+ error: error.error
4740
+ }, place);
4741
+ return fail("#CALC!", `The host failed the effect ${effect.kind}: ${describe(error)}`, {
4742
+ place,
4743
+ cause: error
4744
+ });
4745
+ }
4746
+ if (!(given instanceof Promise)) return ok(given);
4747
+ given.catch(() => void 0);
4748
+ return fail("#CALC!", `The mock gave a promise for the effect ${effect.kind}, and an evaluation does not wait.`, { place });
4600
4749
  }
4601
4750
  /** This function evaluates a document. Each error gets the place of the document where it started (K5). */
4602
4751
  function evaluateAt(env, at) {
@@ -4662,7 +4811,7 @@ function evalInput(env, seen, at) {
4662
4811
  place: at.place,
4663
4812
  ...at.address ? { address: true } : {},
4664
4813
  resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
4665
- effect: (kind, payload) => record(env, at.ctx, kind, payload, at.place) ?? ok(payload),
4814
+ effect: (kind, payload) => cause(env, at.ctx, kind, payload, at.place),
4666
4815
  fill
4667
4816
  };
4668
4817
  }
@@ -4689,9 +4838,9 @@ function runInput(env, seen, at) {
4689
4838
  place: at.place,
4690
4839
  resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
4691
4840
  effect: function* (kind, payload) {
4692
- return record(env, at.ctx, kind, payload, at.place) ?? ok(payload);
4841
+ return cause(env, at.ctx, kind, payload, at.place);
4693
4842
  },
4694
- all: (list, options) => together(list, options),
4843
+ all: (list, options) => together(env, list, options),
4695
4844
  fill
4696
4845
  };
4697
4846
  }
@@ -4700,14 +4849,25 @@ function runInput(env, seen, at) {
4700
4849
  * goes on until it ends or waits. In an evaluation, a part that waits does not end, thus it keeps its place in the
4701
4850
  * limit. With `failFast`, the first failure stops the parts that wait, and the later parts do not start
4702
4851
  * (RUN.FAIL-FAST). When no part failed and a part waits, the steps wait, and the drive gives `#CALC!`.
4852
+ *
4853
+ * Each part has its own scope of addresses. Its prefix names the use of `all` in the scope around it, and the part
4854
+ * (RUN.ADDRESS).
4703
4855
  */
4704
- function* together(list, options = {}) {
4856
+ function* together(env, list, options = {}) {
4705
4857
  const limit = Math.max(1, options.concurrency ?? list.length);
4706
4858
  const results = list.map(() => void 0);
4707
4859
  const waiting = [];
4860
+ const outer = env.scope;
4861
+ const address = `${outer.prefix}all${outer.alls}`;
4862
+ outer.alls += 1;
4708
4863
  let failed = false;
4709
4864
  for (let index = 0; index < list.length && !failed && waiting.length < limit; index += 1) {
4710
4865
  let result;
4866
+ env.scope = {
4867
+ prefix: `${address}.${index}/`,
4868
+ counts: /* @__PURE__ */ new Map(),
4869
+ alls: 0
4870
+ };
4711
4871
  try {
4712
4872
  const steps = list[index].evaluate();
4713
4873
  const step = steps.next();
@@ -4718,6 +4878,8 @@ function* together(list, options = {}) {
4718
4878
  });
4719
4879
  } catch (error) {
4720
4880
  result = thrown(error, "a part");
4881
+ } finally {
4882
+ env.scope = outer;
4721
4883
  }
4722
4884
  results[index] = result;
4723
4885
  if (result !== void 0 && options.failFast === true && !result.ok) failed = true;
@@ -4735,12 +4897,12 @@ function* together(list, options = {}) {
4735
4897
  /**
4736
4898
  * This function evaluates a document by the rules, without a compilation. It is the reference of the plans: a plan,
4737
4899
  * a traced plan and the second tier of a language must give its results. It covers evaluations, not runs: an effect
4738
- * gives its payload, and a part that waits gives `#CALC!`.
4900
+ * gives the answer of the mock or its payload, and a part that waits gives `#CALC!`.
4739
4901
  *
4740
4902
  * @param machine - The world and the code of the evaluators.
4741
4903
  * @param doc - The document.
4742
4904
  * @param context - The names and the capabilities at the root.
4743
- * @param options - The type of a bare root value, the definitions of compiled code, and a list for the effects.
4905
+ * @param options - The type of a bare root value, the definitions of compiled code, a list for the effects and a mock.
4744
4906
  * @returns The result.
4745
4907
  */
4746
4908
  function reference(machine, doc, context = makeContext(), options = {}) {
@@ -4748,7 +4910,13 @@ function reference(machine, doc, context = makeContext(), options = {}) {
4748
4910
  machine,
4749
4911
  definitions: options.definitions ?? {},
4750
4912
  effects: options.effects ?? [],
4751
- maxDepth: machine.maxDepth ?? 1e3
4913
+ maxDepth: machine.maxDepth ?? 1e3,
4914
+ perform: options.perform,
4915
+ scope: {
4916
+ prefix: "",
4917
+ counts: /* @__PURE__ */ new Map(),
4918
+ alls: 0
4919
+ }
4752
4920
  };
4753
4921
  const ctx = context.world === null ? {
4754
4922
  ...context,
@@ -4868,6 +5036,7 @@ const treeDefinition = ({ parts, context, kind, place }) => {
4868
5036
  if (result === void 0 || result.ok) return result?.ok === true ? result.value : void 0;
4869
5037
  throw new Failure(result.error);
4870
5038
  },
5039
+ has: (key) => key === "parent" || key === "type" || parts.some((part) => part.key === String(key)),
4871
5040
  toJson: () => {
4872
5041
  const result = jsonOfNode(node, self, []);
4873
5042
  if (result.ok) return result.value;
@@ -5114,8 +5283,9 @@ function valuesOfStrict(parts) {
5114
5283
  * errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
5115
5284
  * tells otherwise.
5116
5285
  *
5117
- * `docEquals` uses the same rules for lists, plain records and the other objects. It has no rules for closures, for
5118
- * `Held` values and for a rule of the caller. Thus two closures are equal there only when they are the same function.
5286
+ * `docEquals` and `sameResult` use the same walk (D20). `docEquals` has no rules for closures, for `Held` values and
5287
+ * for a rule of the caller. `sameResult` is for an early cutoff, thus a closure is the same there only as itself, and
5288
+ * two errors also need the same reason.
5119
5289
  *
5120
5290
  * @param a - The first value.
5121
5291
  * @param b - The second value.
@@ -5123,46 +5293,12 @@ function valuesOfStrict(parts) {
5123
5293
  * @returns `true` when the values are the same.
5124
5294
  */
5125
5295
  function sameValue(a, b, equal) {
5126
- const stack = [[a, b]];
5127
- const seen = /* @__PURE__ */ new Map();
5128
- while (stack.length > 0) {
5129
- const [x, y] = stack.pop();
5130
- if (Object.is(x, y)) continue;
5131
- const told = equal?.(x, y);
5132
- if (told !== void 0) {
5133
- if (told) continue;
5134
- return false;
5135
- }
5136
- if (typeof x !== "object" && typeof x !== "function") return false;
5137
- if (typeof y !== "object" && typeof y !== "function") return false;
5138
- if (x === null || y === null) return false;
5139
- const pairs = seen.get(x) ?? /* @__PURE__ */ new Set();
5140
- if (pairs.has(y)) continue;
5141
- pairs.add(y);
5142
- seen.set(x, pairs);
5143
- if (isClosure(x) || isClosure(y)) {
5144
- if (!(isClosure(x) && isClosure(y))) return false;
5145
- stack.push([closureParams(x), closureParams(y)]);
5146
- continue;
5147
- }
5148
- if (x instanceof Held || y instanceof Held) {
5149
- if (!(x instanceof Held && y instanceof Held) || errorDiffers(x.error, y.error, false) !== void 0) return false;
5150
- continue;
5151
- }
5152
- if (Array.isArray(x) || Array.isArray(y)) {
5153
- if (!Array.isArray(x) || !Array.isArray(y) || x.length !== y.length) return false;
5154
- for (let index = 0; index < x.length; index += 1) stack.push([x[index], y[index]]);
5155
- continue;
5156
- }
5157
- if (!isRecord(x) || !isRecord(y)) return false;
5158
- const keys = Object.keys(x);
5159
- if (keys.length !== Object.keys(y).length) return false;
5160
- for (const key of keys) {
5161
- if (!Object.hasOwn(y, key)) return false;
5162
- stack.push([x[key], y[key]]);
5163
- }
5164
- }
5165
- return true;
5296
+ return equalWith(a, b, equal === void 0 ? sameInTiers : (x, y) => equal(x, y) ?? sameInTiers(x, y));
5297
+ }
5298
+ /** The rules of `sameValue` for the values that two tiers make again: closures and held errors. */
5299
+ function sameInTiers(x, y) {
5300
+ if (isClosure(x) || isClosure(y)) return isClosure(x) && isClosure(y) && docEquals(closureParams(x), closureParams(y));
5301
+ if (x instanceof Held || y instanceof Held) return x instanceof Held && y instanceof Held && errorDiffers(x.error, y.error, false) === void 0;
5166
5302
  }
5167
5303
  const CLOSURE = /* @__PURE__ */ Symbol.for("coeval.closure");
5168
5304
  function closureParams(fn) {
@@ -5188,34 +5324,44 @@ function resultDiffers(a, b, options) {
5188
5324
  if (!a.ok && !b.ok) return errorDiffers(a.error, b.error, options.strict === true);
5189
5325
  return a.ok ? `The plan gives a value, and the tier gives ${codeAndPlace(b.error)}.` : `The plan gives ${codeAndPlace(a.error)}, and the tier gives a value.`;
5190
5326
  }
5191
- /** This function gives the difference of two lists of effects, or `undefined` when they agree (EFFECT.ORDER). */
5327
+ /**
5328
+ * This function gives the difference of two lists of effects, or `undefined` when they agree (EFFECT.ORDER). When both
5329
+ * tiers record the address or the outcome of an effect, these must agree too (AGREE.MOCK).
5330
+ */
5192
5331
  function effectsDiffer(a, b, options) {
5193
5332
  if (a.length !== b.length) return `The plan causes ${a.length} effects, and the tier causes ${b.length}.`;
5194
5333
  for (let index = 0; index < a.length; index += 1) {
5195
5334
  const x = a[index];
5196
5335
  const y = b[index];
5197
5336
  if (x.kind !== y.kind || !docEquals(x.place, y.place) || !sameValue(x.payload, y.payload, options.equal)) return `The effect ${index} differs: ${x.kind} at [${x.place.join(", ")}] in the plan, and ${y.kind} at [${y.place.join(", ")}] in the tier.`;
5337
+ if (x.address !== void 0 && y.address !== void 0 && x.address !== y.address) return `The effect ${index} has the address ${x.address} in the plan, and ${y.address} in the tier.`;
5338
+ const why = x.outcome !== void 0 && y.outcome !== void 0 ? resultDiffers(x.outcome, y.outcome, options) : void 0;
5339
+ if (why !== void 0) return `The outcome of the effect ${index} differs. ${why}`;
5198
5340
  }
5199
5341
  }
5200
5342
  /**
5201
5343
  * This function evaluates a document with a plan and with other tiers, and compares the results (PLAN.AGREES). Two
5202
5344
  * results agree when they have the same value by `sameValue`, or errors with the same code, and the same place when
5203
5345
  * both have one. The effects must have the same kinds, places and payloads, in the same order. A language gives its
5204
- * own tier with the option `other`.
5346
+ * own tier with the option `other`. Each tier gets the mock of the option `perform` (AGREE.MOCK).
5205
5347
  *
5206
5348
  * @param machine - The world and the code of the evaluators.
5207
5349
  * @param doc - The document.
5208
5350
  * @param context - The names and the capabilities at the root.
5209
- * @param options - The tiers, the definitions for the reference, the rules of the comparison and the second tier.
5351
+ * @param options - The tiers, the definitions for the reference, the rules of the comparison, the second tier and the mock.
5210
5352
  * @returns The result of the plan, or the first tier that disagrees and the reason.
5211
5353
  */
5212
5354
  function agree(machine, doc, context = makeContext(), options = {}) {
5213
5355
  const declared = options.declaredType === void 0 ? {} : { declaredType: options.declaredType };
5356
+ const mock = options.perform === void 0 ? {} : { perform: options.perform };
5214
5357
  const observe = options.observe;
5215
5358
  observe?.();
5216
5359
  const plan = prepare(machine, doc, declared);
5217
5360
  const effects = [];
5218
- const result = plan.evaluate(context, { effects });
5361
+ const result = plan.evaluate(context, {
5362
+ effects,
5363
+ ...mock
5364
+ });
5219
5365
  const saw = observe?.();
5220
5366
  const differs = (other, otherEffects) => {
5221
5367
  if (overflowed(result) || overflowed(other)) return void 0;
@@ -5232,14 +5378,21 @@ function agree(machine, doc, context = makeContext(), options = {}) {
5232
5378
  let otherEffects = [];
5233
5379
  if (tier === "reference") other = reference(machine, doc, context, {
5234
5380
  ...declared,
5381
+ ...mock,
5235
5382
  effects: otherEffects,
5236
5383
  ...options.definitions === void 0 ? {} : { definitions: options.definitions }
5237
5384
  });
5238
5385
  else if (tier === "traced") {
5239
- const traced = evaluate(machine, doc, context, options.declaredType);
5386
+ const traced = evaluate(machine, doc, context, {
5387
+ ...declared,
5388
+ ...mock
5389
+ });
5240
5390
  other = traced.result;
5241
5391
  otherEffects = [...traced.effects];
5242
- } else other = plan.evaluate(context, { effects: otherEffects });
5392
+ } else other = plan.evaluate(context, {
5393
+ effects: otherEffects,
5394
+ ...mock
5395
+ });
5243
5396
  const why = differs(other, otherEffects);
5244
5397
  if (why !== void 0) return {
5245
5398
  ok: false,
@@ -5252,7 +5405,7 @@ function agree(machine, doc, context = makeContext(), options = {}) {
5252
5405
  if (options.other !== void 0) {
5253
5406
  let given;
5254
5407
  try {
5255
- given = options.other(doc, context);
5408
+ given = options.other(doc, context, mock);
5256
5409
  } catch (error) {
5257
5410
  given = thrown(error, "the tier");
5258
5411
  }
@@ -5273,6 +5426,138 @@ function agree(machine, doc, context = makeContext(), options = {}) {
5273
5426
  };
5274
5427
  }
5275
5428
  //#endregion
5429
+ //#region src/same.ts
5430
+ /**
5431
+ * This function tells if two results are the same for an early cutoff (RESULT.SAME). A schedule uses it to stop the
5432
+ * work when a part that evaluates again gives the same result. A wrong `true` keeps a stale value, but a wrong `false`
5433
+ * only costs work. Thus each rule gives `false` when it is not sure.
5434
+ *
5435
+ * - Two values compare by the rules of `docEquals`. Lists and plain records compare by their items and fields. Each
5436
+ * other value compares with `Object.is`.
5437
+ * - Thus `-0` is not `0`, and `NaN` is `NaN`. A date, a map and a closure of `fn` are the same only as themselves.
5438
+ * - Two errors are the same when they have the same code, reason and place (RESULT.SAME-ERROR). The cause is not
5439
+ * compared. A `Held` value compares its error by the same rule.
5440
+ * - A value and an error are not the same.
5441
+ *
5442
+ * `sameValue` of `agree` uses the same walk, with rules for two tiers (D20). It compares two closures by their
5443
+ * parameters, and two errors by their code and place.
5444
+ *
5445
+ * @param a - The earlier result.
5446
+ * @param b - The new result.
5447
+ * @param equal - The rule of the host for two values, for example two dates. It answers first, and `undefined` gives the rules above.
5448
+ * @returns `true` when the results are the same.
5449
+ */
5450
+ function sameResult(a, b, equal) {
5451
+ if (a.ok && b.ok) return equalWith(a.value, b.value, equal === void 0 ? sameHeld : (x, y) => equal(x, y) ?? sameHeld(x, y));
5452
+ if (!a.ok && !b.ok) return sameError(a.error, b.error);
5453
+ return false;
5454
+ }
5455
+ /** The rule of `sameResult` for held errors. Two `Held` values are the same when their errors are the same. */
5456
+ function sameHeld(x, y) {
5457
+ if (x instanceof Held || y instanceof Held) return x instanceof Held && y instanceof Held && sameError(x.error, y.error);
5458
+ }
5459
+ /** Two errors are the same when they have the same code, reason and place. An error without a place has no place in both. */
5460
+ function sameError(a, b) {
5461
+ if (a.code !== b.code || a.reason !== b.reason) return false;
5462
+ if (a.place === void 0 || b.place === void 0) return a.place === b.place;
5463
+ return docEquals(a.place, b.place);
5464
+ }
5465
+ //#endregion
5466
+ //#region src/replay.ts
5467
+ /**
5468
+ * This function makes a recorder (RUN.RECORD). The recorder gives each effect to the function `perform` of the host.
5469
+ * Its log keeps each effect with its address, its kind, its payload and the outcome that the kernel gives it. The log
5470
+ * also keeps the order in which the waits of each `all` settle.
5471
+ *
5472
+ * @param perform - The function of the host. Without it, each effect gives its payload, as in a run without a host.
5473
+ * @returns The function `perform` for the run, and the log.
5474
+ */
5475
+ function recorder(perform) {
5476
+ const seen = [];
5477
+ const settles = [];
5478
+ const journal = { settled: (all, item) => {
5479
+ settles.push({
5480
+ all,
5481
+ item
5482
+ });
5483
+ } };
5484
+ const host = (effect) => {
5485
+ const entry = {
5486
+ effect,
5487
+ waited: false
5488
+ };
5489
+ seen.push(entry);
5490
+ const answer = perform === void 0 ? effect.payload : perform(effect);
5491
+ if (answer instanceof Promise) entry.waited = true;
5492
+ return answer;
5493
+ };
5494
+ return {
5495
+ perform: Object.assign(host, { [JOURNAL]: journal }),
5496
+ log: () => ({
5497
+ effects: seen.map(({ effect, waited }) => ({
5498
+ address: effect.address ?? "",
5499
+ kind: effect.kind,
5500
+ payload: effect.payload,
5501
+ waited,
5502
+ ...effect.outcome === void 0 ? {} : { outcome: effect.outcome }
5503
+ })),
5504
+ settles: [...settles]
5505
+ })
5506
+ };
5507
+ }
5508
+ /**
5509
+ * This function makes a replayer: a function `perform` that answers each effect from a log, by its address (RUN.REPLAY). A
5510
+ * recorded error comes again with its code, its reason, its place and its cause. The waits of each `all` settle in the
5511
+ * recorded order. An effect that the log does not have at its address gives `#REPLAY!` (RUN.REPLAY-MISMATCH).
5512
+ *
5513
+ * A replayer keeps its place in the order of the waits, thus each run needs a new replayer.
5514
+ *
5515
+ * @param log - The log of a recorder.
5516
+ * @param options - `wait: false` for an evaluation.
5517
+ * @returns The function `perform` for the replay.
5518
+ */
5519
+ function replayer(log, options = {}) {
5520
+ const wait = options.wait !== false;
5521
+ const entries = /* @__PURE__ */ new Map();
5522
+ for (const entry of log.effects) entries.set(entry.address, entry);
5523
+ const order = /* @__PURE__ */ new Map();
5524
+ for (const settle of log.settles) {
5525
+ const items = order.get(settle.all);
5526
+ if (items === void 0) order.set(settle.all, [settle.item]);
5527
+ else items.push(settle.item);
5528
+ }
5529
+ const journal = { next: (all, waiting) => {
5530
+ const items = order.get(all);
5531
+ const item = items?.[0];
5532
+ if (items === void 0 || item === void 0 || !waiting.includes(item)) return void 0;
5533
+ items.shift();
5534
+ return item;
5535
+ } };
5536
+ const perform = (effect) => {
5537
+ const entry = effect.address === void 0 ? void 0 : entries.get(effect.address);
5538
+ const wrong = mismatch(effect, entry);
5539
+ if (wrong !== void 0 || entry === void 0) throw wrong;
5540
+ const outcome = entry.outcome;
5541
+ const failure = outcome === void 0 ? replayError(effect, `The log has no outcome for the effect ${effect.kind} at ${effect.address ?? ""}: the recorded run stopped before the answer.`) : outcome.ok ? void 0 : new Failure(outcome.error);
5542
+ const value = outcome?.ok === true ? outcome.value : void 0;
5543
+ if (wait && entry.waited) return failure === void 0 ? Promise.resolve(value) : Promise.reject(failure);
5544
+ if (failure !== void 0) throw failure;
5545
+ return value;
5546
+ };
5547
+ return Object.assign(perform, { [JOURNAL]: journal });
5548
+ }
5549
+ /** This function gives the error of an effect that the log does not have at its address, or `undefined`. */
5550
+ function mismatch(effect, entry) {
5551
+ const at = effect.address ?? "";
5552
+ if (entry === void 0) return replayError(effect, `The log has no effect at the address ${at}, and the run asks for the effect ${effect.kind}.`);
5553
+ if (entry.kind !== effect.kind) return replayError(effect, `The log has the effect ${entry.kind} at the address ${at}, and the run asks for the effect ${effect.kind}.`);
5554
+ if (!docEquals(entry.payload, effect.payload)) return replayError(effect, `The effect ${effect.kind} at the address ${at} has another payload than the log.`);
5555
+ }
5556
+ /** This function gives the exception that carries `#REPLAY!` to the kernel, with the place of the effect. */
5557
+ function replayError(effect, reason) {
5558
+ return new Failure(fail("#REPLAY!", reason, { place: effect.place }).error);
5559
+ }
5560
+ //#endregion
5276
5561
  //#region src/generate.ts
5277
5562
  /**
5278
5563
  * This function makes a source of random numbers with a seed (the algorithm mulberry32). It needs no package.
@@ -5672,6 +5957,6 @@ function generate(world, options) {
5672
5957
  });
5673
5958
  }
5674
5959
  //#endregion
5675
- export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaDefinitions, formulaEvaluators, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, mergeMachines, mergeWorlds, narrow, nearestWalker, nodeAt, ok, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, random, ref, reference, refsOf, resolve, restrictMachine, restrictWorld, run, sameValue, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, typesOf, valueOfThing, walk, worldThing };
5960
+ export { CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, THING, agree, ask, capKeys, check, checkSteps, classify, context, dataEdges, derefStep, describe, docEquals, effectEdges, entriesAsJson, evaluate, fail, failures, field, flatten, formulaDefinitions, formulaEvaluators, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, mergeMachines, mergeWorlds, narrow, nearestWalker, nodeAt, ok, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, random, recorder, ref, reference, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, splitParts, stored, tag, thrown, toJson, typeName, typePath, typed, typesOf, valueOfThing, walk, worldThing };
5676
5961
 
5677
5962
  //# sourceMappingURL=index.js.map