@mark1russell7/coeval 0.14.0 → 0.16.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
@@ -2379,14 +2379,25 @@ function resolveFully$1(nav, ctx, path, hops, known) {
2379
2379
  const walker = given === void 0 ? walkerOf(nav, step) : given ?? void 0;
2380
2380
  if (walker === void 0) return failed("#STEP?", "A step is a key, or a document of a kind with the verb step.");
2381
2381
  const envelope = step;
2382
- const result = walker({
2382
+ const deref = (thing, key) => {
2383
+ const reached = derefStep(nav, thing, key);
2384
+ return reached === void 0 ? void 0 : adopt(reached.thing);
2385
+ };
2386
+ const result = walker(hops === void 0 ? {
2383
2387
  data: envelope.value,
2384
2388
  here,
2385
2389
  context: ctx,
2386
- deref: (thing, key) => {
2387
- const reached = derefStep(nav, thing, key);
2388
- return reached === void 0 ? void 0 : adopt(reached.thing);
2389
- }
2390
+ deref
2391
+ } : {
2392
+ data: envelope.value,
2393
+ here,
2394
+ context: ctx,
2395
+ deref,
2396
+ visit: (key, thing) => hops.push({
2397
+ step: key,
2398
+ thing,
2399
+ by: "step"
2400
+ })
2390
2401
  });
2391
2402
  if (!result.ok) return failed(result.code ?? "#N/A", result.reason);
2392
2403
  here = adopt(result.thing);
@@ -3082,6 +3093,88 @@ const NO_ANSWER = {
3082
3093
  /** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
3083
3094
  const WITHOUT_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
3084
3095
  const WITH_HOPS = /* @__PURE__ */ Object.freeze({ hops: true });
3096
+ /**
3097
+ * This function gives the path of a kept read (READS.CLOSURE). The evaluation did not ask for the read. Thus the read
3098
+ * does not step into a host thing with `deref`. A host thing with `known` answers a step without new work
3099
+ * (SCHED.CLOSURE). Each other host thing ends the path, and a host thing that does not know the step ends it too.
3100
+ * A reference in the data reads the context of its reader, thus the function then gives `undefined`.
3101
+ *
3102
+ * @param nav - The world and the code of the evaluators.
3103
+ * @param root - The context at the root of the evaluation.
3104
+ * @param path - The root and the keys of the static read.
3105
+ * @returns The path of the kept read with what it reached after a step of `known`, or `undefined` for a reference on the way.
3106
+ */
3107
+ function keptPath(nav, root, path) {
3108
+ const adopt = nav.adopt ?? ((value) => value);
3109
+ let here = adopt(root[path[0]]);
3110
+ let known = false;
3111
+ const end = (index) => {
3112
+ const kept = index >= path.length ? path : path.slice(0, index);
3113
+ return known ? {
3114
+ path: kept,
3115
+ reached: ok(here)
3116
+ } : { path: kept };
3117
+ };
3118
+ for (let index = 1;; index += 1) {
3119
+ if (isHostThing(here)) {
3120
+ if (index >= path.length || here.known === void 0) return end(index);
3121
+ let step;
3122
+ try {
3123
+ step = here.known(path[index]);
3124
+ } catch (error) {
3125
+ return {
3126
+ path: path.slice(0, index + 1),
3127
+ reached: thrown(error, READ_FAILED)
3128
+ };
3129
+ }
3130
+ if (step === void 0) return end(index);
3131
+ known = true;
3132
+ here = adopt(step.thing);
3133
+ continue;
3134
+ }
3135
+ if (nav.plain !== true && isRef(here)) return void 0;
3136
+ if (index >= path.length) return end(index);
3137
+ const reached = derefStep(nav, here, path[index]);
3138
+ if (reached === void 0) return known ? end(index) : { path };
3139
+ here = adopt(reached.thing);
3140
+ }
3141
+ }
3142
+ /**
3143
+ * This function resolves the path of a kept read again, for `verifyReads` (SCHED.CLOSURE). It walks the path as
3144
+ * `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `readAgain`.
3145
+ *
3146
+ * @param nav - The world and the code of the evaluators.
3147
+ * @param ctx - The new context.
3148
+ * @param path - The path of the kept read.
3149
+ * @returns What the path reaches, or `undefined` when a host does not know a step yet. Then the read changed.
3150
+ */
3151
+ function keptAgain(nav, ctx, path) {
3152
+ const adopt = nav.adopt ?? ((value) => value);
3153
+ let here = adopt(ctx[path[0]]);
3154
+ let known = false;
3155
+ const other = () => known ? void 0 : readAgain(nav, ctx, path, false).reached;
3156
+ for (let index = 1; index < path.length; index += 1) {
3157
+ if (isHostThing(here)) {
3158
+ if (here.known === void 0) return other();
3159
+ let step;
3160
+ try {
3161
+ step = here.known(path[index]);
3162
+ } catch (error) {
3163
+ return thrown(error, READ_FAILED);
3164
+ }
3165
+ if (step === void 0) return void 0;
3166
+ known = true;
3167
+ here = adopt(step.thing);
3168
+ continue;
3169
+ }
3170
+ if (nav.plain !== true && isRef(here)) return other();
3171
+ const reached = derefStep(nav, here, path[index]);
3172
+ if (reached === void 0) return other();
3173
+ here = adopt(reached.thing);
3174
+ }
3175
+ if (!known) return other();
3176
+ return nav.plain !== true && isRef(here) ? void 0 : ok(here);
3177
+ }
3085
3178
  /** This function makes the record of the reads of an evaluation, when the host gives a list. */
3086
3179
  function readingOf(list, root) {
3087
3180
  return list === void 0 ? void 0 : {
@@ -3188,13 +3281,46 @@ var Session = class {
3188
3281
  if (compiled !== void 0 && !hops && compiled.nav === this.machine) return compiled.resolve(ctx);
3189
3282
  return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
3190
3283
  }
3191
- keepsContext() {
3284
+ keepsContext(ctx, reads) {
3192
3285
  const reading = this.reading;
3193
3286
  if (reading === void 0 || reading.kept) return;
3287
+ const known = ctx === void 0 ? void 0 : reads?.();
3288
+ if (ctx !== void 0 && known !== void 0 && known.every((read) => this.keepRead(reading, ctx, read))) return;
3194
3289
  reading.kept = true;
3195
3290
  this.keep(reading, reading.self ? ROOTS.filter((root) => root !== "self") : ROOTS);
3196
3291
  }
3197
3292
  /**
3293
+ * This function adds the kept read of one static read of a closure (READS.CLOSURE). The read resolves from the
3294
+ * context at the root, one time in an evaluation. A name that a kind binds in the kept context gives no read,
3295
+ * because the reads of its binding are already in the list.
3296
+ *
3297
+ * @returns `false` when the path meets a reference in the data. Then the closure keeps each whole root.
3298
+ */
3299
+ keepRead(reading, ctx, read) {
3300
+ const root = read.root;
3301
+ const exact = [root, ...read.keys];
3302
+ if (!isFree(reading.root, ctx, exact)) {
3303
+ if (root === "self" && !reading.self) this.keep(reading, ["self"]);
3304
+ return true;
3305
+ }
3306
+ if (root === "self" && reading.self) return true;
3307
+ const walk = keptPath(this.machine, reading.root, read.exact ? exact : [root]);
3308
+ if (walk === void 0) return false;
3309
+ const path = walk.path;
3310
+ const paths = reading.paths ??= /* @__PURE__ */ new Set();
3311
+ const text = JSON.stringify(path);
3312
+ if (paths.has(text) || paths.has(JSON.stringify([root]))) return true;
3313
+ paths.add(text);
3314
+ if (path.length === 1 && root === "self") reading.self = true;
3315
+ reading.list.push({
3316
+ path,
3317
+ reached: walk.reached ?? readAgain(this.machine, reading.root, path, false).reached,
3318
+ free: true,
3319
+ kept: true
3320
+ });
3321
+ return true;
3322
+ }
3323
+ /**
3198
3324
  * This function adds a read of each whole root to the list, from the context at the root (READS.KEPT). An exception
3199
3325
  * of the host gives the error value of the read, because the evaluation did not ask for the read.
3200
3326
  */
@@ -3680,7 +3806,10 @@ function compileNode(c, value, place, declared, depth, address = false) {
3680
3806
  if (frees === void 0 || kind === void 0 || !pureCode(c, envelope, kind, typePath, name, quick)) return done;
3681
3807
  const free = freeOf(doc, kind, split.parts, frees);
3682
3808
  if (free === void 0) return done;
3683
- return free.length === 0 ? folded(c, done) : {
3809
+ if (free.length === 0) return folded(c, done, void 0);
3810
+ const fixed = c.fixed;
3811
+ if (fixed !== void 0 && free.every((each) => Object.hasOwn(fixed, each))) return folded(c, done, free);
3812
+ return {
3684
3813
  code: done.code,
3685
3814
  run: done.run,
3686
3815
  free
@@ -3760,6 +3889,21 @@ var Probe = class {
3760
3889
  };
3761
3890
  this.root = root;
3762
3891
  }
3892
+ /** The context of a part that folds with fixed names: the context of the probe, with the fixed names (PLAN.FIXED). */
3893
+ withFixed;
3894
+ /**
3895
+ * This function gives the context of a part that folds with fixed names. Its root `names` holds only the fixed
3896
+ * names, thus the probe resolves each fixed name and refuses each other name.
3897
+ *
3898
+ * @param fixed - The fixed names and their values.
3899
+ * @returns The context.
3900
+ */
3901
+ fixedRoot(fixed) {
3902
+ return this.withFixed ??= {
3903
+ ...this.root,
3904
+ names: fixed
3905
+ };
3906
+ }
3763
3907
  resolve(ctx, path) {
3764
3908
  const name = path[1];
3765
3909
  if (path[0] === "names" && isKey(name) && Object.hasOwn(ctx.names, String(name))) {
@@ -3818,13 +3962,14 @@ function trap(refuse, copies) {
3818
3962
  * one deeply frozen copy, and the code gives that copy at each evaluation (PLAN.SHARED). A fold that the probe cancels
3819
3963
  * gives the code without free names, thus no parent tries the fold again.
3820
3964
  */
3821
- function folded(c, done) {
3965
+ function folded(c, done, fixed) {
3822
3966
  const probe = c.probe ??= new Probe(c.machine);
3823
3967
  probe.refused = false;
3824
3968
  probe.reads = 0;
3825
3969
  let constant;
3826
3970
  try {
3827
- const result = done.code(probe.root, probe);
3971
+ const root = fixed === void 0 ? probe.root : probe.fixedRoot(c.fixed);
3972
+ const result = done.code(root, probe);
3828
3973
  if (!probe.refused && (result.ok || result.error.code !== "#DEPTH!")) constant = sharedCopy(result);
3829
3974
  } catch {
3830
3975
  constant = void 0;
@@ -3834,18 +3979,100 @@ function folded(c, done) {
3834
3979
  run: done.run
3835
3980
  };
3836
3981
  const value = constant;
3982
+ const original = done.code;
3983
+ if (fixed !== void 0) return {
3984
+ code: fixedCode(fixed, c.fixed, value, original),
3985
+ run: void 0,
3986
+ free: fixed
3987
+ };
3837
3988
  if (probe.reads === 0) return {
3838
3989
  code: () => value,
3839
3990
  run: void 0,
3840
3991
  free: CLOSED
3841
3992
  };
3842
- const original = done.code;
3843
3993
  return {
3844
3994
  code: (ctx, rt) => rt instanceof Session && !rt.traced && !rt.recording ? value : original(ctx, rt),
3845
3995
  run: void 0,
3846
3996
  free: CLOSED
3847
3997
  };
3848
3998
  }
3999
+ /**
4000
+ * This function gives the code of a part that folded with fixed names (PLAN.FIXED, D60). The code gives the folded
4001
+ * value when the context has the fixed value of each name, by an own-key test and `Object.is`. A fixed value is
4002
+ * deeply frozen, thus the same object gives the same result. Each other context, a list of reads and another runtime
4003
+ * get the code without the fold. The probe of a parent fold gets the folded value, and it counts a read.
4004
+ */
4005
+ function fixedCode(names, fixed, value, original) {
4006
+ if (names.length === 1) {
4007
+ const name = names[0];
4008
+ const want = fixed[name];
4009
+ return (ctx, rt) => {
4010
+ const given = ctx.names;
4011
+ return Object.hasOwn(given, name) && Object.is(given[name], want) && folds(rt) ? value : original(ctx, rt);
4012
+ };
4013
+ }
4014
+ const wants = names.map((name) => fixed[name]);
4015
+ return (ctx, rt) => {
4016
+ const given = ctx.names;
4017
+ for (let index = 0; index < names.length; index += 1) {
4018
+ const name = names[index];
4019
+ if (!Object.hasOwn(given, name) || !Object.is(given[name], wants[index])) return original(ctx, rt);
4020
+ }
4021
+ return folds(rt) ? value : original(ctx, rt);
4022
+ };
4023
+ }
4024
+ /**
4025
+ * This function tells if the code of a part with fixed names can give its folded value in a runtime. The probe of a
4026
+ * fold counts the read of the part, as the code without the fold reads the names.
4027
+ */
4028
+ function folds(rt) {
4029
+ if (rt instanceof Session) return !rt.recording;
4030
+ if (!(rt instanceof Probe)) return false;
4031
+ rt.reads += 1;
4032
+ return true;
4033
+ }
4034
+ /**
4035
+ * This function gives the fixed names of the options of `prepare` (PLAN.FIXED). A name fixes only when its value is
4036
+ * deeply frozen plain data. Then no change in place can make the folded value stale.
4037
+ *
4038
+ * @returns A record without a prototype, or `undefined` when no name fixes.
4039
+ */
4040
+ function fixedNamesOf(given) {
4041
+ if (given === void 0) return void 0;
4042
+ let out;
4043
+ for (const name of Object.keys(given)) {
4044
+ const value = given[name];
4045
+ if (!frozenPlain(value)) continue;
4046
+ out ??= Object.create(null);
4047
+ Object.defineProperty(out, name, {
4048
+ value,
4049
+ enumerable: true
4050
+ });
4051
+ }
4052
+ return out;
4053
+ }
4054
+ /** This function tells if a value is plain data, as `sharedCopy` reads it, and if each of its objects is frozen. */
4055
+ function frozenPlain(value) {
4056
+ const seen = /* @__PURE__ */ new Set();
4057
+ const walk = (here) => {
4058
+ if (typeof here === "function") return false;
4059
+ if (typeof here !== "object" || here === null || SHARED.has(here) || seen.has(here)) return true;
4060
+ if (!Object.isFrozen(here)) return false;
4061
+ seen.add(here);
4062
+ const proto = Object.getPrototypeOf(here);
4063
+ if (Array.isArray(here) ? proto !== Array.prototype : proto !== Object.prototype && proto !== null) return false;
4064
+ for (const key of Reflect.ownKeys(here)) {
4065
+ const field = Object.getOwnPropertyDescriptor(here, key);
4066
+ if (typeof key !== "string" || field === void 0 || !("value" in field) || !walk(field.value)) return false;
4067
+ }
4068
+ return true;
4069
+ };
4070
+ try {
4071
+ return walk(value);
4072
+ } catch {
4073
+ return false;
4074
+ }
4075
+ }
3849
4076
  /** The objects that a fold made. Each one is deeply frozen, thus a later fold uses it again and does not copy it. */
3850
4077
  const SHARED = /* @__PURE__ */ new WeakSet();
3851
4078
  /** The mark of a value that is not plain data. */
@@ -3853,7 +4080,11 @@ const NOT_PLAIN = /* @__PURE__ */ Symbol("not plain");
3853
4080
  /**
3854
4081
  * This function gives a deeply frozen copy of plain data, or `undefined` for other values. Plain data is a primitive or
3855
4082
  * an array without holes and extra keys. It is also a record of enumerable data fields with the prototype of an object
3856
- * or no prototype. The copy keeps cycles and shared objects. The objects of the host do not change.
4083
+ * or no prototype. The copy keeps cycles and shared objects. The objects of the host do not change. The tier `fixed`
4084
+ * of `agree` uses it. The package does not export it.
4085
+ *
4086
+ * @param value - The value.
4087
+ * @returns The frozen copy, or `undefined`.
3857
4088
  */
3858
4089
  function sharedCopy(value) {
3859
4090
  try {
@@ -4091,13 +4322,16 @@ function prepare(machine, doc, options = {}) {
4091
4322
  steps: []
4092
4323
  };
4093
4324
  const traced = options.trace === true;
4325
+ const fold = !traced && options.fold !== false;
4326
+ const fixed = fold ? fixedNamesOf(options.fixed?.names) : void 0;
4094
4327
  const c = {
4095
4328
  machine,
4096
4329
  traced,
4097
4330
  maxDepth: machine.maxDepth ?? 1e3,
4098
4331
  verb,
4099
4332
  noAnswer,
4100
- fold: !traced && options.fold !== false
4333
+ fold,
4334
+ fixed
4101
4335
  };
4102
4336
  let root;
4103
4337
  try {
@@ -4876,6 +5110,17 @@ function treeThing(input, build, outer) {
4876
5110
  function attributeTrace(node, attribute) {
4877
5111
  return stateOf(node)?.trace(attribute);
4878
5112
  }
5113
+ /**
5114
+ * This function gives the place of a node of the decorated tree: the keys from the root document to the document of
5115
+ * the node (HOP.PLACE). A view puts each hop of a resolution on its node with it. The node keeps its place already,
5116
+ * thus the function adds no record.
5117
+ *
5118
+ * @param thing - A thing, for example the thing of a hop.
5119
+ * @returns The keys of the place, or `undefined` for a thing that is not a node of the decorated tree.
5120
+ */
5121
+ function placeOf(thing) {
5122
+ return stateOf(thing)?.place;
5123
+ }
4879
5124
  /** The path of the root `self`. A node of the tree resolves it, thus the list of reads holds the read (C4). */
4880
5125
  const SELF_PATH = /* @__PURE__ */ Object.freeze(["self"]);
4881
5126
  /** The options of the resolution of `self`: the node needs no hops. */
@@ -4904,21 +5149,24 @@ function hostType(node, wanted) {
4904
5149
  /**
4905
5150
  * This function makes a walker `nearest`. The walker follows a key (the default is `parent`) until the node that it
4906
5151
  * reaches has the wanted type. With `self: true`, the node where the walk starts is the first candidate. A node
4907
- * without that key ends the walk with an error.
5152
+ * without that key ends the walk with an error. With `eachStep`, a resolution with hops gets a hop for each node on the way.
4908
5153
  *
4909
- * @param options - The test of a type and the most hops.
5154
+ * @param options - The test of a type, the most hops and the record of each step.
4910
5155
  * @returns The walker.
4911
5156
  */
4912
5157
  function nearestWalker(options = {}) {
4913
5158
  const matches = options.matches ?? hostType;
4914
5159
  const maxHops = options.maxHops ?? 256;
4915
- return ({ data, here, deref }) => {
5160
+ const eachStep = options.eachStep === true;
5161
+ return ({ data, here, deref, visit }) => {
4916
5162
  const follow = isRecord(data) && typeof field(data, "follow") === "string" ? String(field(data, "follow")) : "parent";
4917
5163
  const wanted = isRecord(data) ? field(data, "type") : void 0;
4918
5164
  const fromSelf = isRecord(data) && field(data, "self") === true;
5165
+ const record = eachStep ? visit : void 0;
4919
5166
  let at = here;
4920
5167
  for (let hops = 0; hops < maxHops; hops += 1) {
4921
- if (hops > 0 || !fromSelf) at = at === void 0 || at === null ? void 0 : deref(at, follow);
5168
+ const stepped = hops > 0 || !fromSelf;
5169
+ if (stepped) at = at === void 0 || at === null ? void 0 : deref(at, follow);
4922
5170
  if (at === void 0 || at === null) return {
4923
5171
  ok: false,
4924
5172
  reason: `No node of the type ${String(wanted)} is on the way.`
@@ -4927,6 +5175,7 @@ function nearestWalker(options = {}) {
4927
5175
  ok: true,
4928
5176
  thing: at
4929
5177
  };
5178
+ if (stepped) record?.(follow, at);
4930
5179
  }
4931
5180
  return {
4932
5181
  ok: false,
@@ -7539,7 +7788,7 @@ function formulaCode(profile) {
7539
7788
  }, rt);
7540
7789
  };
7541
7790
  }),
7542
- "formula.fn": compiled(({ value, data }) => {
7791
+ "formula.fn": compiled(({ value, data, doc, machine }) => {
7543
7792
  const params = isRecord(data) ? data["params"] : void 0;
7544
7793
  if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) {
7545
7794
  const bad = fail("#VALUE!", "The params of fn are a list of different texts.");
@@ -7547,8 +7796,19 @@ function formulaCode(profile) {
7547
7796
  }
7548
7797
  const names = params;
7549
7798
  const body = partsOf$1(value)["body"];
7799
+ let known = null;
7800
+ const reads = () => {
7801
+ if (known !== null) return known;
7802
+ try {
7803
+ const facts = factsOf(machine, doc);
7804
+ known = facts.pure ? facts.reads : void 0;
7805
+ } catch {
7806
+ known = void 0;
7807
+ }
7808
+ return known;
7809
+ };
7550
7810
  return (ctx, rt) => {
7551
- rt.keepsContext?.();
7811
+ rt.keepsContext?.(ctx, reads);
7552
7812
  const closure = (...args) => {
7553
7813
  const scope = { ...ctx.names };
7554
7814
  names.forEach((name, index) => setOwn$1(scope, name, args[index]));
@@ -8762,6 +9022,42 @@ function resolveFully(machine, ctx, path, options) {
8762
9022
  * mock, the effect gives its payload (RUN.EVAL).
8763
9023
  */
8764
9024
  function cause(env, ctx, kind, payload, place) {
9025
+ const effect = recordEffect(env, ctx, kind, payload, place);
9026
+ if ("ok" in effect) return effect;
9027
+ effect.outcome = answer(env.perform, effect, place);
9028
+ return effect.outcome;
9029
+ }
9030
+ /**
9031
+ * This function causes the effect of an evaluator. In a run, the host does each effect, thus only a runner can cause
9032
+ * one. The effect then gives `#CALC!` before the test of its capability (RUN.NEEDS-RUNNER).
9033
+ */
9034
+ function causeFromEvaluator(env, ctx, kind, payload, place) {
9035
+ const text = typeof kind === "string" ? kind : kind.join(".");
9036
+ if (env.running) return fail("#CALC!", `The effect ${text} needs a runner: in a run, the host does each effect.`, { place });
9037
+ return cause(env, ctx, kind, payload, place);
9038
+ }
9039
+ /**
9040
+ * This function causes the effect of a runner. In an evaluation, the mock answers it at once. In a run, the host does
9041
+ * it, and a promise from the host makes the steps wait (RUN.SEQ). An effect whose wait a stop ends keeps no outcome
9042
+ * (RUN.OUTCOME).
9043
+ */
9044
+ function* causeAndWait(env, ctx, kind, payload, place) {
9045
+ if (!env.running) return cause(env, ctx, kind, payload, place);
9046
+ const effect = recordEffect(env, ctx, kind, payload, place);
9047
+ if ("ok" in effect) return effect;
9048
+ const perform = env.perform;
9049
+ if (perform === void 0) return effect.outcome = ok(payload);
9050
+ let given;
9051
+ try {
9052
+ given = perform(effect);
9053
+ } catch (error) {
9054
+ return effect.outcome = hostError(effect, place, error);
9055
+ }
9056
+ if (!(given instanceof Promise)) return effect.outcome = ok(given);
9057
+ return effect.outcome = yield given.then((value) => ok(value), (error) => hostError(effect, place, error));
9058
+ }
9059
+ /** This function checks the capability of an effect, and records the effect with its address (RUN.CAP, RUN.ADDRESS). */
9060
+ function recordEffect(env, ctx, kind, payload, place) {
8765
9061
  const keys = typeof kind === "string" ? kind.split(".") : kind;
8766
9062
  const text = keys.join(".");
8767
9063
  const machine = env.machine;
@@ -8781,8 +9077,18 @@ function cause(env, ctx, kind, payload, place) {
8781
9077
  address: `${env.scope.prefix}${at}#${count}`
8782
9078
  };
8783
9079
  env.effects.push(effect);
8784
- effect.outcome = answer(env.perform, effect, place);
8785
- return effect.outcome;
9080
+ return effect;
9081
+ }
9082
+ /** This function gives the outcome of an effect that the host failed. A `Failure` gives its own error value (RUN.CAUSE). */
9083
+ function hostError(effect, place, error) {
9084
+ if (error instanceof Failure) return placed({
9085
+ ok: false,
9086
+ error: error.error
9087
+ }, place);
9088
+ return fail("#CALC!", `The host failed the effect ${effect.kind}: ${describe(error)}`, {
9089
+ place,
9090
+ cause: error
9091
+ });
8786
9092
  }
8787
9093
  /**
8788
9094
  * This function gives the answer of the mock to an effect. A throw gives `#CALC!` with the cause, and a `Failure`
@@ -8794,14 +9100,7 @@ function answer(perform, effect, place) {
8794
9100
  try {
8795
9101
  given = perform(effect);
8796
9102
  } catch (error) {
8797
- if (error instanceof Failure) return placed({
8798
- ok: false,
8799
- error: error.error
8800
- }, place);
8801
- return fail("#CALC!", `The host failed the effect ${effect.kind}: ${describe(error)}`, {
8802
- place,
8803
- cause: error
8804
- });
9103
+ return hostError(effect, place, error);
8805
9104
  }
8806
9105
  if (!(given instanceof Promise)) return ok(given);
8807
9106
  given.catch(() => void 0);
@@ -8872,7 +9171,7 @@ function evalInput(env, seen, at) {
8872
9171
  machine: env.machine,
8873
9172
  ...at.address ? { address: true } : {},
8874
9173
  resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
8875
- effect: (kind, payload) => cause(env, at.ctx, kind, payload, at.place),
9174
+ effect: (kind, payload) => causeFromEvaluator(env, at.ctx, kind, payload, at.place),
8876
9175
  fill
8877
9176
  };
8878
9177
  }
@@ -8899,67 +9198,116 @@ function runInput(env, seen, at) {
8899
9198
  place: at.place,
8900
9199
  machine: env.machine,
8901
9200
  resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
8902
- effect: function* (kind, payload) {
8903
- return cause(env, at.ctx, kind, payload, at.place);
8904
- },
9201
+ effect: (kind, payload) => causeAndWait(env, at.ctx, kind, payload, at.place),
8905
9202
  all: (list, options) => together(env, list, options),
8906
9203
  fill
8907
9204
  };
8908
9205
  }
8909
9206
  /**
8910
9207
  * This function evaluates parts for `all` (RUN.ALL). The parts start in their order, up to the limit, and each part
8911
- * goes on until it ends or waits. In an evaluation, a part that waits does not end, thus it keeps its place in the
8912
- * limit. With `failFast`, the first failure stops the parts that wait, and the later parts do not start
8913
- * (RUN.FAIL-FAST). When no part failed and a part waits, the steps wait, and the drive gives `#CALC!`.
9208
+ * goes on until it ends or waits. A part that waits keeps its place in the limit. With `failFast`, the first failure
9209
+ * stops the parts that wait, and the later parts do not start (RUN.FAIL-FAST).
9210
+ *
9211
+ * When a part waits, the steps wait for the next part that settles. The journal of the host can choose that part
9212
+ * (RUN.REPLAY), and it gets each part that settled (RUN.RECORD). In an evaluation, the drive stops at the first wait
9213
+ * and gives `#CALC!`. In a run, the host settles the wait, and the part goes on in its own scope.
8914
9214
  *
8915
9215
  * Each part has its own scope of addresses. Its prefix names the use of `all` in the scope around it, and the part
8916
9216
  * (RUN.ADDRESS).
8917
9217
  */
8918
9218
  function* together(env, list, options = {}) {
8919
9219
  const limit = Math.max(1, options.concurrency ?? list.length);
9220
+ const failFast = options.failFast === true;
8920
9221
  const results = list.map(() => void 0);
9222
+ const scopes = [];
8921
9223
  const waiting = [];
8922
9224
  const outer = env.scope;
8923
9225
  const address = `${outer.prefix}all${outer.alls}`;
8924
9226
  outer.alls += 1;
8925
9227
  let failed = false;
8926
- for (let index = 0; index < list.length && !failed && waiting.length < limit; index += 1) {
8927
- let result;
8928
- env.scope = {
8929
- prefix: `${address}.${index}/`,
8930
- counts: /* @__PURE__ */ new Map(),
8931
- alls: 0
8932
- };
9228
+ let started = 0;
9229
+ const end = (index, result) => {
9230
+ results[index] = result;
9231
+ if (failFast && !result.ok) failed = true;
9232
+ };
9233
+ /** This function gives a part its input in its own scope, and keeps it in the list when it waits again. */
9234
+ const goOn = (index, steps, input) => {
9235
+ const around = env.scope;
9236
+ env.scope = scopes[index];
9237
+ let step;
8933
9238
  try {
8934
- const steps = list[index].evaluate();
8935
- const step = steps.next();
8936
- if (step.done === true) result = step.value;
8937
- else waiting.push({
8938
- steps,
8939
- promise: step.value
8940
- });
9239
+ step = input.threw ? steps.throw(input.error) : steps.next(input.value);
8941
9240
  } catch (error) {
8942
- result = thrown(error, "a part");
9241
+ end(index, thrown(error, "a part"));
9242
+ return;
8943
9243
  } finally {
8944
- env.scope = outer;
9244
+ env.scope = around;
8945
9245
  }
8946
- results[index] = result;
8947
- if (result !== void 0 && options.failFast === true && !result.ok) failed = true;
8948
- }
8949
- if (failed) {
8950
- const stopped = fail("#ABORT", "The part stopped: a part before it failed, and the parts run with failFast.");
8951
- for (const item of waiting) try {
8952
- item.steps.return(stopped);
8953
- } catch {}
8954
- return results.map((result) => result ?? stopped);
9246
+ if (step.done === true) {
9247
+ end(index, step.value);
9248
+ return;
9249
+ }
9250
+ const waiter = {
9251
+ index,
9252
+ steps,
9253
+ next: Promise.resolve()
9254
+ };
9255
+ waiter.next = step.value.then((value) => ({
9256
+ waiter,
9257
+ value,
9258
+ threw: false
9259
+ }), (error) => ({
9260
+ waiter,
9261
+ error,
9262
+ threw: true
9263
+ }));
9264
+ waiting.push(waiter);
9265
+ };
9266
+ /** This function starts the next parts in their order, while the limit permits it and no part failed. */
9267
+ const startMore = () => {
9268
+ while (!failed && waiting.length < limit && started < list.length) {
9269
+ const index = started;
9270
+ started += 1;
9271
+ scopes[index] = {
9272
+ prefix: `${address}.${index}/`,
9273
+ counts: /* @__PURE__ */ new Map(),
9274
+ alls: 0
9275
+ };
9276
+ const around = env.scope;
9277
+ env.scope = scopes[index];
9278
+ let steps;
9279
+ try {
9280
+ steps = list[index].evaluate();
9281
+ } catch (error) {
9282
+ end(index, thrown(error, "a part"));
9283
+ continue;
9284
+ } finally {
9285
+ env.scope = around;
9286
+ }
9287
+ goOn(index, steps, { threw: false });
9288
+ }
9289
+ };
9290
+ startMore();
9291
+ while (waiting.length > 0 && !failed) {
9292
+ const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index));
9293
+ const pick = chosen === void 0 ? void 0 : waiting.find((waiter) => waiter.index === chosen);
9294
+ const resumed = yield pick === void 0 ? Promise.race(waiting.map((waiter) => waiter.next)) : pick.next;
9295
+ env.journal?.settled?.(address, resumed.waiter.index);
9296
+ waiting.splice(waiting.indexOf(resumed.waiter), 1);
9297
+ goOn(resumed.waiter.index, resumed.waiter.steps, resumed);
9298
+ startMore();
8955
9299
  }
8956
- if (waiting.length > 0) yield Promise.race(waiting.map((item) => item.promise));
8957
- return results;
9300
+ if (!failed) return results;
9301
+ const stopped = fail("#ABORT", "The part stopped: a part before it failed, and the parts run with failFast.");
9302
+ for (const waiter of waiting) try {
9303
+ waiter.steps.return(stopped);
9304
+ } catch {}
9305
+ return results.map((result) => result ?? stopped);
8958
9306
  }
8959
9307
  /**
8960
9308
  * This function evaluates a document by the rules, without a compilation. It is the reference of the plans: a plan,
8961
- * a traced plan and the second tier of a language must give its results. It covers evaluations, not runs: an effect
8962
- * gives the answer of the mock or its payload, and a part that waits gives `#CALC!`.
9309
+ * a traced plan and the second tier of a language must give its results. In an evaluation, an effect gives the answer
9310
+ * of the mock or its payload, and a part that waits gives `#CALC!`. `referenceRun` reads the rules of a run.
8963
9311
  *
8964
9312
  * The verb of the options goes up the ladder at each document, as in `prepare`. Each verb has the same rules: the
8965
9313
  * ladder, the contract of the kind and the sealed parts (VERB.AGREE).
@@ -8971,12 +9319,43 @@ function* together(env, list, options = {}) {
8971
9319
  * @returns The result.
8972
9320
  */
8973
9321
  function reference(machine, doc, context = makeContext(), options = {}) {
8974
- const env = {
9322
+ const env = envOf(machine, options, false);
9323
+ try {
9324
+ return within(env, () => evaluateAt(env, rootAt(machine, doc, context, options)));
9325
+ } catch (error) {
9326
+ return placed(thrown(error, "the reference"), []);
9327
+ }
9328
+ }
9329
+ /**
9330
+ * This function starts a run of a document by the rules, without a compilation (REF.RUN). The host does each effect of a runner,
9331
+ * and a step waits when the host waits (RUN.SEQ). `all` settles the waits in the order of the host, or in the order
9332
+ * of the journal of a replay (RUN.REPLAY). An effect of an evaluator gives `#CALC!` (RUN.NEEDS-RUNNER). A signal that
9333
+ * aborts stops the run with `#ABORT` (RUN.ABORT).
9334
+ *
9335
+ * The part of the run before the first wait happens at once, in the call (RUN.EAGER). `agreeRun` compares the run of
9336
+ * a plan with this run.
9337
+ *
9338
+ * @param machine - The world, the evaluators and the runners.
9339
+ * @param doc - The document.
9340
+ * @param context - The names and the capabilities at the root.
9341
+ * @param options - The options of `reference`, the host and a signal.
9342
+ * @returns The result, when the run ends.
9343
+ */
9344
+ function referenceRun(machine, doc, context = makeContext(), options = {}) {
9345
+ const env = envOf(machine, options, true);
9346
+ return driveRun(env, stepsAt(env, rootAt(machine, doc, context, options)), options.signal).catch((error) => placed(thrown(error, "the reference"), []));
9347
+ }
9348
+ /** This function makes the state of the reference for one evaluation or one run. */
9349
+ function envOf(machine, options, running) {
9350
+ const perform = options.perform;
9351
+ return {
8975
9352
  machine,
8976
9353
  definitions: options.definitions ?? {},
8977
9354
  effects: options.effects ?? [],
8978
9355
  maxDepth: machine.maxDepth ?? 1e3,
8979
- perform: options.perform,
9356
+ perform,
9357
+ journal: perform === void 0 ? void 0 : perform[JOURNAL],
9358
+ running,
8980
9359
  verb: options.verb ?? "eval",
8981
9360
  scope: {
8982
9361
  prefix: "",
@@ -8984,26 +9363,66 @@ function reference(machine, doc, context = makeContext(), options = {}) {
8984
9363
  alls: 0
8985
9364
  }
8986
9365
  };
9366
+ }
9367
+ /** This function gives the root position of a document. A context without a world gets the world of the machine. */
9368
+ function rootAt(machine, doc, context, options) {
8987
9369
  const ctx = context.world === null ? {
8988
9370
  ...context,
8989
9371
  world: worldThing(machine.world)
8990
9372
  } : context;
9373
+ return {
9374
+ value: doc,
9375
+ place: [],
9376
+ declared: options.declaredType,
9377
+ depth: 0,
9378
+ address: false,
9379
+ ctx
9380
+ };
9381
+ }
9382
+ /**
9383
+ * This function does one synchronous step of an evaluation or a run. A node of a tree reads the definitions of the
9384
+ * evaluation for its equations (TREE.LADDER). Thus the state of the evaluation is the innermost one in the step.
9385
+ */
9386
+ function within(env, step) {
8991
9387
  activeEnvs.push(env);
8992
9388
  try {
8993
- return evaluateAt(env, {
8994
- value: doc,
8995
- place: [],
8996
- declared: options.declaredType,
8997
- depth: 0,
8998
- address: false,
8999
- ctx
9000
- });
9001
- } catch (error) {
9002
- return placed(thrown(error, "the reference"), []);
9389
+ return step();
9003
9390
  } finally {
9004
9391
  activeEnvs.pop();
9005
9392
  }
9006
9393
  }
9394
+ /** The mark of a signal that aborted. */
9395
+ const STOPPED$1 = /* @__PURE__ */ Symbol("stopped");
9396
+ /**
9397
+ * This function drives the steps of a run. Each step waits for its promise. A rejection goes into the steps as an
9398
+ * exception. A signal that aborts stops the steps with `#ABORT`, also before the first step (RUN.ABORT).
9399
+ */
9400
+ async function driveRun(env, steps, signal) {
9401
+ const stop = () => {
9402
+ const aborted = fail("#ABORT", `The host stopped the run: ${describe(signal?.reason ?? "no reason")}`, { cause: signal?.reason });
9403
+ try {
9404
+ within(env, () => steps.return(aborted));
9405
+ } catch {}
9406
+ return aborted;
9407
+ };
9408
+ if (signal?.aborted === true) return stop();
9409
+ const stopping = signal === void 0 ? void 0 : new Promise((resolve) => {
9410
+ signal.addEventListener("abort", () => resolve(STOPPED$1), { once: true });
9411
+ });
9412
+ let step = within(env, () => steps.next());
9413
+ while (step.done !== true) {
9414
+ let input;
9415
+ try {
9416
+ input = await (stopping === void 0 ? step.value : Promise.race([step.value, stopping]));
9417
+ } catch (error) {
9418
+ step = within(env, () => steps.throw(error));
9419
+ continue;
9420
+ }
9421
+ if (input === STOPPED$1) return stop();
9422
+ step = within(env, () => steps.next(input));
9423
+ }
9424
+ return step.value;
9425
+ }
9007
9426
  /** The evaluations of the reference that run at this time, the innermost last. */
9008
9427
  const activeEnvs = [];
9009
9428
  const refNodes = /* @__PURE__ */ new WeakMap();
@@ -9437,26 +9856,122 @@ function valuesOfStrict(parts) {
9437
9856
  return { values };
9438
9857
  }
9439
9858
  //#endregion
9440
- //#region src/agree.ts
9441
- /** The tiers that `agree` compares when its options name none. */
9442
- const DEFAULT_TIERS = /* @__PURE__ */ Object.freeze([
9443
- "reference",
9444
- "traced",
9445
- "again",
9446
- "folds"
9447
- ]);
9859
+ //#region src/replay.ts
9448
9860
  /**
9449
- * This function tells if two values are the same for `agree`. Lists and plain records compare item by item and field
9450
- * by field, and the order of the fields does not matter. Each other leaf compares with `Object.is`, thus `-0` is not
9451
- * `0`, and `NaN` is `NaN`. The identity of a list or of a plain record is not compared.
9861
+ * This function makes a recorder (RUN.RECORD). The recorder gives each effect to the function `perform` of the host.
9862
+ * Its log keeps each effect with its address, its kind, its payload and the outcome that the kernel gives it. The log
9863
+ * also keeps the order in which the waits of each `all` settle.
9452
9864
  *
9453
- * Two closures of `fn` are the same when they have the same parameters. Two `Held` values are the same when their
9454
- * errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
9455
- * tells otherwise.
9865
+ * @param perform - The function of the host. Without it, each effect gives its payload, as in a run without a host.
9866
+ * @returns The function `perform` for the run, and the log.
9867
+ */
9868
+ function recorder(perform) {
9869
+ const seen = [];
9870
+ const settles = [];
9871
+ const journal = { settled: (all, item) => {
9872
+ settles.push({
9873
+ all,
9874
+ item
9875
+ });
9876
+ } };
9877
+ const host = (effect) => {
9878
+ const entry = {
9879
+ effect,
9880
+ waited: false
9881
+ };
9882
+ seen.push(entry);
9883
+ const answer = perform === void 0 ? effect.payload : perform(effect);
9884
+ if (answer instanceof Promise) entry.waited = true;
9885
+ return answer;
9886
+ };
9887
+ return {
9888
+ perform: Object.assign(host, { [JOURNAL]: journal }),
9889
+ log: () => ({
9890
+ effects: seen.map(({ effect, waited }) => ({
9891
+ address: effect.address ?? "",
9892
+ kind: effect.kind,
9893
+ payload: effect.payload,
9894
+ waited,
9895
+ ...effect.outcome === void 0 ? {} : { outcome: effect.outcome }
9896
+ })),
9897
+ settles: [...settles]
9898
+ })
9899
+ };
9900
+ }
9901
+ /**
9902
+ * This function makes a replayer: a function `perform` that answers each effect from a log, by its address (RUN.REPLAY). A
9903
+ * recorded error comes again with its code, its reason, its place and its cause. The waits of each `all` settle in the
9904
+ * recorded order. An effect that the log does not have at its address gives `#REPLAY!` (RUN.REPLAY-MISMATCH).
9456
9905
  *
9457
- * `docEquals` and `sameResult` use the same walk (D20). `docEquals` has no rules for closures, for `Held` values and
9458
- * for a rule of the caller. `sameResult` is for an early cutoff, thus a closure is the same there only as itself, and
9459
- * two errors also need the same reason.
9906
+ * A replayer keeps its place in the order of the waits, thus each run needs a new replayer.
9907
+ *
9908
+ * @param log - The log of a recorder.
9909
+ * @param options - `wait: false` for an evaluation.
9910
+ * @returns The function `perform` for the replay.
9911
+ */
9912
+ function replayer(log, options = {}) {
9913
+ const wait = options.wait !== false;
9914
+ const entries = /* @__PURE__ */ new Map();
9915
+ for (const entry of log.effects) entries.set(entry.address, entry);
9916
+ const order = /* @__PURE__ */ new Map();
9917
+ for (const settle of log.settles) {
9918
+ const items = order.get(settle.all);
9919
+ if (items === void 0) order.set(settle.all, [settle.item]);
9920
+ else items.push(settle.item);
9921
+ }
9922
+ const journal = { next: (all, waiting) => {
9923
+ const items = order.get(all);
9924
+ const item = items?.[0];
9925
+ if (items === void 0 || item === void 0 || !waiting.includes(item)) return void 0;
9926
+ items.shift();
9927
+ return item;
9928
+ } };
9929
+ const perform = (effect) => {
9930
+ const entry = effect.address === void 0 ? void 0 : entries.get(effect.address);
9931
+ const wrong = mismatch(effect, entry);
9932
+ if (wrong !== void 0 || entry === void 0) throw wrong;
9933
+ const outcome = entry.outcome;
9934
+ 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);
9935
+ const value = outcome?.ok === true ? outcome.value : void 0;
9936
+ if (wait && entry.waited) return failure === void 0 ? Promise.resolve(value) : Promise.reject(failure);
9937
+ if (failure !== void 0) throw failure;
9938
+ return value;
9939
+ };
9940
+ return Object.assign(perform, { [JOURNAL]: journal });
9941
+ }
9942
+ /** This function gives the error of an effect that the log does not have at its address, or `undefined`. */
9943
+ function mismatch(effect, entry) {
9944
+ const at = effect.address ?? "";
9945
+ 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}.`);
9946
+ 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}.`);
9947
+ if (!docEquals(entry.payload, effect.payload)) return replayError(effect, `The effect ${effect.kind} at the address ${at} has another payload than the log.`);
9948
+ }
9949
+ /** This function gives the exception that carries `#REPLAY!` to the kernel, with the place of the effect. */
9950
+ function replayError(effect, reason) {
9951
+ return new Failure(fail("#REPLAY!", reason, { place: effect.place }).error);
9952
+ }
9953
+ //#endregion
9954
+ //#region src/agree.ts
9955
+ /** The tiers that `agree` compares when its options name none. */
9956
+ const DEFAULT_TIERS = /* @__PURE__ */ Object.freeze([
9957
+ "reference",
9958
+ "traced",
9959
+ "again",
9960
+ "folds",
9961
+ "fixed"
9962
+ ]);
9963
+ /**
9964
+ * This function tells if two values are the same for `agree`. Lists and plain records compare item by item and field
9965
+ * by field, and the order of the fields does not matter. Each other leaf compares with `Object.is`, thus `-0` is not
9966
+ * `0`, and `NaN` is `NaN`. The identity of a list or of a plain record is not compared.
9967
+ *
9968
+ * Two closures of `fn` are the same when they have the same parameters. Two `Held` values are the same when their
9969
+ * errors have the same code and place. Each other object is the same only as itself, unless the rule of the caller
9970
+ * tells otherwise.
9971
+ *
9972
+ * `docEquals` and `sameResult` use the same walk (D20). `docEquals` has no rules for closures, for `Held` values and
9973
+ * for a rule of the caller. `sameResult` is for an early cutoff, thus a closure is the same there only as itself, and
9974
+ * two errors also need the same reason.
9460
9975
  *
9461
9976
  * @param a - The first value.
9462
9977
  * @param b - The second value.
@@ -9472,6 +9987,7 @@ function sameInTiers(x, y) {
9472
9987
  if (x instanceof Held || y instanceof Held) return x instanceof Held && y instanceof Held && errorDiffers(x.error, y.error, false) === void 0;
9473
9988
  }
9474
9989
  const CLOSURE = /* @__PURE__ */ Symbol.for("coeval.closure");
9990
+ /** This function gives the parameters of a closure of `fn`, or `undefined` for another function. */
9475
9991
  function closureParams(fn) {
9476
9992
  return fn[CLOSURE]?.params;
9477
9993
  }
@@ -9511,6 +10027,67 @@ function effectsDiffer(a, b, options) {
9511
10027
  }
9512
10028
  }
9513
10029
  /**
10030
+ * This function compares the plan with the tier `fixed` (PLAN.FIXED). The tier declares each name of plain data fixed,
10031
+ * with a frozen copy of its value. It evaluates with these copies, thus the folds with fixed names give the result.
10032
+ * Then it evaluates with the context of `agree`, thus the code without the fold gives the result. A fold moves the
10033
+ * calls of the host to the compilation, thus only the result and the effects compare.
10034
+ */
10035
+ function fixedDiffers(machine, doc, context, result, effects, given, options) {
10036
+ const { perform, ...declared } = given;
10037
+ const held = fixedContext(context);
10038
+ const plan = prepare(machine, doc, {
10039
+ ...declared,
10040
+ fixed: { names: held.fixed }
10041
+ });
10042
+ for (const each of [held.context, context]) {
10043
+ const tierEffects = [];
10044
+ const other = plan.evaluate(each, {
10045
+ effects: tierEffects,
10046
+ ...perform === void 0 ? {} : { perform }
10047
+ });
10048
+ options.observe?.();
10049
+ if (overflowed(result) || overflowed(other)) continue;
10050
+ const reason = resultDiffers(result, other, options) ?? effectsDiffer(effects, tierEffects, options);
10051
+ if (reason !== void 0) return {
10052
+ other,
10053
+ reason
10054
+ };
10055
+ }
10056
+ }
10057
+ /**
10058
+ * This function gives the context of the tier `fixed`. Each name of plain data gets a deeply frozen copy of its value,
10059
+ * and the record of the fixed names holds the same copy. Each other name keeps its value.
10060
+ */
10061
+ function fixedContext(context) {
10062
+ const given = context.names;
10063
+ const names = Object.create(Object.getPrototypeOf(given));
10064
+ const fixed = Object.create(null);
10065
+ for (const key of Reflect.ownKeys(given)) {
10066
+ let field = Object.getOwnPropertyDescriptor(given, key);
10067
+ if (typeof key === "string" && "value" in field) {
10068
+ const copy = sharedCopy(field.value);
10069
+ if (copy !== void 0 || field.value === void 0) {
10070
+ Object.defineProperty(fixed, key, {
10071
+ value: copy,
10072
+ enumerable: true
10073
+ });
10074
+ field = {
10075
+ ...field,
10076
+ value: copy
10077
+ };
10078
+ }
10079
+ }
10080
+ Object.defineProperty(names, key, field);
10081
+ }
10082
+ return {
10083
+ context: {
10084
+ ...context,
10085
+ names
10086
+ },
10087
+ fixed
10088
+ };
10089
+ }
10090
+ /**
9514
10091
  * This function evaluates a document with a plan and with other tiers, and compares the results (PLAN.AGREES). Two
9515
10092
  * results agree when they have the same value by `sameValue`, or errors with the same code, and the same place when
9516
10093
  * both have one. The effects must have the same kinds, places and payloads, in the same order. A language gives its
@@ -9567,6 +10144,19 @@ function agree(machine, doc, context = makeContext(), options = {}) {
9567
10144
  });
9568
10145
  other = traced.result;
9569
10146
  otherEffects = [...traced.effects];
10147
+ } else if (tier === "fixed") {
10148
+ const why = fixedDiffers(machine, doc, context, result, effects, {
10149
+ ...declared,
10150
+ ...mock
10151
+ }, options);
10152
+ if (why !== void 0) return {
10153
+ ok: false,
10154
+ tier,
10155
+ plan: result,
10156
+ other: why.other,
10157
+ reason: why.reason
10158
+ };
10159
+ continue;
9570
10160
  } else if (tier === "folds") {
9571
10161
  other = prepare(machine, doc, {
9572
10162
  ...declared,
@@ -9624,6 +10214,114 @@ function agree(machine, doc, context = makeContext(), options = {}) {
9624
10214
  effects
9625
10215
  };
9626
10216
  }
10217
+ /**
10218
+ * This function wraps the function `perform` of a run. The wrapper keeps each effect and each wait of `all` that
10219
+ * settles. It gives each effect to the inner function, and each call of its journal to the inner journal.
10220
+ */
10221
+ function watched(perform) {
10222
+ const effects = [];
10223
+ const settles = [];
10224
+ const inner = perform[JOURNAL];
10225
+ const settled = (all, item) => {
10226
+ settles.push({
10227
+ all,
10228
+ item
10229
+ });
10230
+ inner?.settled?.(all, item);
10231
+ };
10232
+ const journal = inner?.next === void 0 ? { settled } : {
10233
+ settled,
10234
+ next: (all, waiting) => inner.next?.(all, waiting)
10235
+ };
10236
+ const host = (effect) => {
10237
+ effects.push(effect);
10238
+ return perform(effect);
10239
+ };
10240
+ return {
10241
+ perform: Object.assign(host, { [JOURNAL]: journal }),
10242
+ effects,
10243
+ settles
10244
+ };
10245
+ }
10246
+ function settleText(settle) {
10247
+ return settle === void 0 ? "no item" : `the item ${settle.item} of ${settle.all}`;
10248
+ }
10249
+ /** This function gives the first difference of two orders of the waits of `all`, or `undefined` when they agree. */
10250
+ function settlesDiffer(a, b) {
10251
+ for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
10252
+ const x = a[index];
10253
+ const y = b[index];
10254
+ if (x?.all !== y?.all || x?.item !== y?.item) return `The wait ${index} settles ${settleText(x)} in the plan, and ${settleText(y)} in the tier.`;
10255
+ }
10256
+ }
10257
+ /**
10258
+ * This function compares the run of a document in a plan with its run in other tiers (RUN.AGREES). First the plan
10259
+ * starts the run with a recorder around the host. Then each tier replays the log of that run. These tiers are the
10260
+ * reference run, the traced run, a second run of the plan and the plan with the other choice of folds.
10261
+ *
10262
+ * The rules of `agree` compare the results and the effects, with their addresses and outcomes. A tier must also
10263
+ * settle the waits of each `all` in the order of the plan, thus a replay keeps the order of the waits.
10264
+ *
10265
+ * @param machine - The world, the evaluators and the runners.
10266
+ * @param doc - The document.
10267
+ * @param context - The names and the capabilities at the root.
10268
+ * @param options - The tiers, the definitions for the reference, the rules of the comparison and the host.
10269
+ * @returns The result of the plan, or the first tier that disagrees and the reason.
10270
+ */
10271
+ async function agreeRun(machine, doc, context = makeContext(), options = {}) {
10272
+ const declared = {
10273
+ ...options.declaredType === void 0 ? {} : { declaredType: options.declaredType },
10274
+ ...options.verb === void 0 ? {} : { verb: options.verb }
10275
+ };
10276
+ const observe = options.observe;
10277
+ observe?.();
10278
+ const folds = observe === void 0;
10279
+ const plan = prepare(machine, doc, {
10280
+ ...declared,
10281
+ fold: folds
10282
+ });
10283
+ const recording = recorder(options.perform);
10284
+ const first = watched(recording.perform);
10285
+ const result = await plan.run(context, { perform: first.perform });
10286
+ const saw = observe?.();
10287
+ const log = recording.log();
10288
+ for (const tier of options.tiers ?? DEFAULT_TIERS) {
10289
+ if (tier === "fixed") continue;
10290
+ const replay = watched(replayer(log));
10291
+ const host = { perform: replay.perform };
10292
+ let other;
10293
+ if (tier === "reference") other = await referenceRun(machine, doc, context, {
10294
+ ...declared,
10295
+ ...host,
10296
+ ...options.definitions === void 0 ? {} : { definitions: options.definitions }
10297
+ });
10298
+ else if (tier === "traced") other = (await run(machine, doc, context, {
10299
+ ...declared,
10300
+ ...host
10301
+ })).result;
10302
+ else if (tier === "folds") other = await prepare(machine, doc, {
10303
+ ...declared,
10304
+ fold: !folds
10305
+ }).run(context, host);
10306
+ else other = await plan.run(context, host);
10307
+ const compareCalls = observe !== void 0 && tier !== "folds";
10308
+ const calls = observe?.();
10309
+ if (overflowed(result) || overflowed(other)) continue;
10310
+ const why = resultDiffers(result, other, options) ?? effectsDiffer(first.effects, replay.effects, options) ?? settlesDiffer(first.settles, replay.settles) ?? (compareCalls && !sameValue(saw, calls, options.equal) ? "The host saw other calls in the tier." : void 0);
10311
+ if (why !== void 0) return {
10312
+ ok: false,
10313
+ tier,
10314
+ plan: result,
10315
+ other,
10316
+ reason: why
10317
+ };
10318
+ }
10319
+ return {
10320
+ ok: true,
10321
+ result,
10322
+ effects: first.effects
10323
+ };
10324
+ }
9627
10325
  //#endregion
9628
10326
  //#region src/generate.ts
9629
10327
  /**
@@ -11068,6 +11766,22 @@ function verifyReads(target, reads, context, options = {}) {
11068
11766
  read,
11069
11767
  now: stop
11070
11768
  };
11769
+ if (read.kept === true) {
11770
+ const now = keptAgain(machine, root, read.path);
11771
+ if (now === void 0) return {
11772
+ same: false,
11773
+ index,
11774
+ read,
11775
+ now: fail("#N/A", "The host does not know the value of the kept read yet.")
11776
+ };
11777
+ if (!sameResult(read.reached, now, equal)) return {
11778
+ same: false,
11779
+ index,
11780
+ read,
11781
+ now
11782
+ };
11783
+ continue;
11784
+ }
11071
11785
  const asked = read.hops !== void 0;
11072
11786
  const now = readAgain(machine, root, read.path, asked);
11073
11787
  if (!sameResult(read.reached, now.reached, equal) || read.hops !== void 0 && !sameHops(read.hops, now.hops, equal)) return {
@@ -11340,7 +12054,7 @@ function cyclic(graph, members) {
11340
12054
  return false;
11341
12055
  }
11342
12056
  /** This function gives the names of the nodes of a component, without the node `all`. */
11343
- function named(graph, members) {
12057
+ function named$1(graph, members) {
11344
12058
  const out = [];
11345
12059
  for (const member of members) if (member !== graph.all) out.push(graph.ids[member]);
11346
12060
  return out;
@@ -11379,7 +12093,7 @@ function components(graph, options = {}) {
11379
12093
  const index = indexed(graph, options);
11380
12094
  const out = [];
11381
12095
  for (const members of strong(index).list) {
11382
- const names = named(index, members);
12096
+ const names = named$1(index, members);
11383
12097
  if (names.length > 0) out.push(names);
11384
12098
  }
11385
12099
  return out;
@@ -11486,7 +12200,7 @@ function cycles(graph) {
11486
12200
  const out = [];
11487
12201
  found.list.forEach((members, component) => {
11488
12202
  if (!cyclic(every, members)) return;
11489
- const nodes = named(every, members);
12203
+ const nodes = named$1(every, members);
11490
12204
  const certain = inside.get(component) ?? [];
11491
12205
  if (certain.length === 1 && certain[0]?.length === nodes.length) {
11492
12206
  out.push({
@@ -11500,7 +12214,7 @@ function cycles(graph) {
11500
12214
  certain: false
11501
12215
  });
11502
12216
  for (const part of certain) out.push({
11503
- nodes: named(eager, part),
12217
+ nodes: named$1(eager, part),
11504
12218
  certain: true
11505
12219
  });
11506
12220
  });
@@ -11742,10 +12456,12 @@ function schedule(machine, options = {}) {
11742
12456
  };
11743
12457
  /**
11744
12458
  * This function gives the state of the current data epoch. Each data epoch has a new host thing at the root
11745
- * `world`. Thus a read that reaches the whole root changes at each change of data (READS.KEPT).
12459
+ * `world`. Thus a read that reaches the whole root changes at each change of data (READS.KEPT). A kept read of a
12460
+ * closure reads a node through `known`, without a demand (SCHED.CLOSURE).
11746
12461
  */
11747
12462
  const current = () => {
11748
12463
  if (now !== void 0) return now;
12464
+ const values = /* @__PURE__ */ new Map();
11749
12465
  const nodesThing = {
11750
12466
  [THING]: true,
11751
12467
  label: "nodes",
@@ -11757,14 +12473,20 @@ function schedule(machine, options = {}) {
11757
12473
  return result.value;
11758
12474
  },
11759
12475
  has: (key) => docs.has(String(key)),
11760
- toJson: record
12476
+ toJson: record,
12477
+ known(key) {
12478
+ const result = values.get(String(key));
12479
+ if (result === void 0) return void 0;
12480
+ if (!result.ok) throw new Failure(result.error);
12481
+ return { thing: result.value };
12482
+ }
11761
12483
  };
11762
12484
  now = {
11763
12485
  context: rootContext(machine, {
11764
12486
  ...base,
11765
12487
  world: nodesThing
11766
12488
  }),
11767
- values: /* @__PURE__ */ new Map()
12489
+ values
11768
12490
  };
11769
12491
  return now;
11770
12492
  };
@@ -11839,99 +12561,404 @@ function schedule(machine, options = {}) {
11839
12561
  };
11840
12562
  }
11841
12563
  //#endregion
11842
- //#region src/replay.ts
12564
+ //#region src/meaning.ts
12565
+ /** A list of eight reference types. The generator then gives more references, thus more reads of nodes. */
12566
+ function moreRefs() {
12567
+ return Array.from({ length: 8 }, () => ["ref"]);
12568
+ }
12569
+ /** The default options of the document generator, for `STD_MACHINE`. The kinds `log` and `write` need a capability. */
12570
+ function standardGenerate() {
12571
+ return {
12572
+ maxSize: 16,
12573
+ types: [
12574
+ ...typesOf(STD_MACHINE.world).filter((type) => type[0] !== "log" && type[0] !== "write"),
12575
+ ...moreRefs(),
12576
+ ["if"],
12577
+ ["add"]
12578
+ ],
12579
+ data: (type, rng) => {
12580
+ const name = type.join(".");
12581
+ if (name === "number") return rng.chance(.9) ? rng.pick([
12582
+ 0,
12583
+ 1,
12584
+ 2,
12585
+ 3.5,
12586
+ -1
12587
+ ]) : "1";
12588
+ if (name === "text") return rng.chance(.9) ? rng.pick([
12589
+ "",
12590
+ "a",
12591
+ "b"
12592
+ ]) : 1;
12593
+ if (name === "bool") return rng.chance(.9) ? rng.chance(.5) : 0;
12594
+ if (name === "nearest") return rng.pick([{ type: "panel" }, "panel"]);
12595
+ }
12596
+ };
12597
+ }
12598
+ /** The default `wire`, for `STD_MACHINE`. A list evaluates each part, also after a failed part. */
12599
+ function standardWire(rng, node) {
12600
+ if (rng.chance(.5)) return {
12601
+ type: "list",
12602
+ value: [node(), node()]
12603
+ };
12604
+ return rng.chance(.5) ? {
12605
+ type: "add",
12606
+ value: [node(), 1]
12607
+ } : node();
12608
+ }
12609
+ /** The default values of the names of the host. Some are equal by structure, and some are host values. */
12610
+ function standardValues() {
12611
+ return [
12612
+ 0,
12613
+ 1,
12614
+ 2,
12615
+ -0,
12616
+ NaN,
12617
+ "two",
12618
+ null,
12619
+ void 0,
12620
+ [1, 2],
12621
+ { a: 1 },
12622
+ { a: 2 },
12623
+ {
12624
+ type: "ref",
12625
+ value: ["names", "a"]
12626
+ },
12627
+ new Held({
12628
+ code: "#HELD",
12629
+ reason: "The binding failed."
12630
+ })
12631
+ ];
12632
+ }
12633
+ /** The default contexts of the host at the start of a run. */
12634
+ function standardContexts() {
12635
+ return [context({
12636
+ names: {
12637
+ a: 1,
12638
+ b: [1, "two"],
12639
+ x: { a: 1 },
12640
+ n: 2
12641
+ },
12642
+ caps: ["log"]
12643
+ }), context({
12644
+ names: {
12645
+ a: "two",
12646
+ b: 0,
12647
+ x: null
12648
+ },
12649
+ self: { a: 1 },
12650
+ event: { a: 2 }
12651
+ })];
12652
+ }
12653
+ /** This function puts a field on a record, also the field `__proto__`. */
12654
+ function put(target, key, value) {
12655
+ Object.defineProperty(target, key, {
12656
+ value,
12657
+ enumerable: true,
12658
+ writable: true,
12659
+ configurable: true
12660
+ });
12661
+ }
12662
+ /** This function changes each path from `names` to a node into a path from `world`. Some paths read the whole root. */
12663
+ function linked(value, rng, nodes) {
12664
+ if (Array.isArray(value)) {
12665
+ const items = value.map((item) => linked(item, rng, nodes));
12666
+ if (items[0] === "names" && typeof items[1] === "string" && nodes.includes(items[1])) return rng.chance(.03) ? ["world"] : ["world", ...items.slice(1)];
12667
+ return items;
12668
+ }
12669
+ if (typeof value !== "object" || value === null) return value;
12670
+ const out = {};
12671
+ for (const key of Object.keys(value)) put(out, key, linked(value[key], rng, nodes));
12672
+ return out;
12673
+ }
12674
+ /** This function gives new names of the host, with one or two changes. */
12675
+ function change(base, rng, names, values) {
12676
+ const out = { ...base.names };
12677
+ for (let step = 1 + rng.int(2); step > 0; step -= 1) {
12678
+ const name = rng.pick(names);
12679
+ if (rng.chance(.15)) delete out[name];
12680
+ else out[name] = rng.pick(values);
12681
+ }
12682
+ return out;
12683
+ }
12684
+ function shuffle(items, rng) {
12685
+ const out = [...items];
12686
+ for (let index = out.length - 1; index > 0; index -= 1) {
12687
+ const other = rng.int(index + 1);
12688
+ [out[index], out[other]] = [out[other], out[index]];
12689
+ }
12690
+ return out;
12691
+ }
11843
12692
  /**
11844
- * This function makes a recorder (RUN.RECORD). The recorder gives each effect to the function `perform` of the host.
11845
- * Its log keeps each effect with its address, its kind, its payload and the outcome that the kernel gives it. The log
11846
- * also keeps the order in which the waits of each `all` settle.
11847
- *
11848
- * @param perform - The function of the host. Without it, each effect gives its payload, as in a run without a host.
11849
- * @returns The function `perform` for the run, and the log.
12693
+ * This function evaluates each node of a graph from scratch, with the meaning of D31. Each node gets a new plan and
12694
+ * one value. The algorithm of Tarjan over the demands finds the cycles. The function keeps nothing between calls.
11850
12695
  */
11851
- function recorder(perform) {
11852
- const seen = [];
11853
- const settles = [];
11854
- const journal = { settled: (all, item) => {
11855
- settles.push({
11856
- all,
11857
- item
11858
- });
11859
- } };
11860
- const host = (effect) => {
11861
- const entry = {
11862
- effect,
11863
- waited: false
12696
+ function fromScratch(machine, docs, base, declaredType) {
12697
+ const declared = declaredType === void 0 ? {} : { declaredType };
12698
+ const values = /* @__PURE__ */ new Map();
12699
+ const index = /* @__PURE__ */ new Map();
12700
+ const lowest = /* @__PURE__ */ new Map();
12701
+ const component = [];
12702
+ const evaluating = [];
12703
+ const selfReads = /* @__PURE__ */ new Set();
12704
+ const visit = (id) => {
12705
+ const reader = evaluating[evaluating.length - 1];
12706
+ const link = (to) => {
12707
+ if (reader !== void 0) lowest.set(reader, Math.min(lowest.get(reader), to));
11864
12708
  };
11865
- seen.push(entry);
11866
- const answer = perform === void 0 ? effect.payload : perform(effect);
11867
- if (answer instanceof Promise) entry.waited = true;
11868
- return answer;
12709
+ const done = values.get(id);
12710
+ if (done !== void 0) {
12711
+ if (component.includes(id)) link(index.get(id));
12712
+ return done;
12713
+ }
12714
+ if (evaluating.includes(id)) {
12715
+ link(index.get(id));
12716
+ if (reader === id) selfReads.add(id);
12717
+ return cycleOf(id);
12718
+ }
12719
+ if (factsOf(machine, docs.get(id), declared).side === "run") {
12720
+ const refused = runSide(id);
12721
+ values.set(id, refused);
12722
+ return refused;
12723
+ }
12724
+ index.set(id, index.size);
12725
+ lowest.set(id, index.get(id));
12726
+ component.push(id);
12727
+ evaluating.push(id);
12728
+ let result = prepare(machine, docs.get(id), declared).evaluate(ctx);
12729
+ evaluating.pop();
12730
+ if (lowest.get(id) < index.get(id)) result = cycleOf(id);
12731
+ else if (component.splice(component.indexOf(id)).length > 1 || selfReads.has(id)) result = cycleOf(id);
12732
+ values.set(id, result);
12733
+ link(lowest.get(id));
12734
+ return result;
11869
12735
  };
11870
- return {
11871
- perform: Object.assign(host, { [JOURNAL]: journal }),
11872
- log: () => ({
11873
- effects: seen.map(({ effect, waited }) => ({
11874
- address: effect.address ?? "",
11875
- kind: effect.kind,
11876
- payload: effect.payload,
11877
- waited,
11878
- ...effect.outcome === void 0 ? {} : { outcome: effect.outcome }
11879
- })),
11880
- settles: [...settles]
11881
- })
12736
+ const world = {
12737
+ [THING]: true,
12738
+ label: "nodes",
12739
+ deref: (key) => {
12740
+ if (!docs.has(String(key))) return void 0;
12741
+ const result = visit(String(key));
12742
+ if (!result.ok) throw new Failure(result.error);
12743
+ return result.value;
12744
+ },
12745
+ has: (key) => docs.has(String(key)),
12746
+ toJson: () => {
12747
+ const out = {};
12748
+ for (const id of [...docs.keys()].sort()) {
12749
+ if (factsOf(machine, docs.get(id), declared).side === "run") continue;
12750
+ const result = visit(id);
12751
+ if (!result.ok) throw new Failure(result.error);
12752
+ put(out, id, result.value);
12753
+ }
12754
+ return out;
12755
+ }
11882
12756
  };
12757
+ const ctx = {
12758
+ ...base,
12759
+ world
12760
+ };
12761
+ for (const id of [...docs.keys()].sort()) visit(id);
12762
+ return values;
12763
+ }
12764
+ /** This function names a result for the reason of a disagreement: its error code, or "a value". */
12765
+ function named(result) {
12766
+ return result.ok ? "a value" : result.error.code;
11883
12767
  }
11884
12768
  /**
11885
- * This function makes a replayer: a function `perform` that answers each effect from a log, by its address (RUN.REPLAY). A
11886
- * recorded error comes again with its code, its reason, its place and its cause. The waits of each `all` settle in the
11887
- * recorded order. An effect that the log does not have at its address gives `#REPLAY!` (RUN.REPLAY-MISMATCH).
12769
+ * This function examines an engine of a host against an evaluation from scratch (SCHED.AGREES, R4 P4, D58). Each run
12770
+ * makes a new engine and a random graph of named nodes from the document generator. Then it gives random commands:
12771
+ * edits, new nodes, removed nodes, the same document again, new machines and changes of data.
11888
12772
  *
11889
- * A replayer keeps its place in the order of the waits, thus each run needs a new replayer.
12773
+ * - Each read command evaluates the whole graph from scratch two times, with the meaning of D31.
12774
+ * - It then demands each node in a random order, or one node, and compares each answer with `sameResult`.
12775
+ * - A read whose answer from scratch differs between the two evaluations is not compared.
12776
+ * - Two errors agree when they have the same code, reason and place. Thus an engine gives the `#CYCLE!` and the
12777
+ * `#REF!` of D31.
11890
12778
  *
11891
- * @param log - The log of a recorder.
11892
- * @param options - `wait: false` for an evaluation.
11893
- * @returns The function `perform` for the replay.
12779
+ * The same seed and the same options give the same commands, thus a disagreement occurs again.
12780
+ *
12781
+ * @param engine - This function makes a new engine for each run, for example `schedule`.
12782
+ * @param options - The machines, the generator, the contexts, the number of runs and the rule for host values.
12783
+ * @returns What the harness reached, or the first read whose answer differs from the answer from scratch.
11894
12784
  */
11895
- function replayer(log, options = {}) {
11896
- const wait = options.wait !== false;
11897
- const entries = /* @__PURE__ */ new Map();
11898
- for (const entry of log.effects) entries.set(entry.address, entry);
11899
- const order = /* @__PURE__ */ new Map();
11900
- for (const settle of log.settles) {
11901
- const items = order.get(settle.all);
11902
- if (items === void 0) order.set(settle.all, [settle.item]);
11903
- else items.push(settle.item);
12785
+ function agreeSchedule(engine, options = {}) {
12786
+ const machines = options.machines ?? [STD_MACHINE, { ...STD_MACHINE }];
12787
+ const declaredType = options.declaredType;
12788
+ const generateOptions = options.generate ?? standardGenerate();
12789
+ const value = options.value ?? ((rng) => ({
12790
+ type: "number",
12791
+ value: rng.pick([
12792
+ 0,
12793
+ 1,
12794
+ 2
12795
+ ])
12796
+ }));
12797
+ const wire = options.wire ?? standardWire;
12798
+ const wired = options.wired ?? .2;
12799
+ const runs = options.runs ?? true;
12800
+ const nodes = options.nodes ?? [
12801
+ "p",
12802
+ "q",
12803
+ "r",
12804
+ "s",
12805
+ "t"
12806
+ ];
12807
+ const hostNames = options.names ?? [
12808
+ "a",
12809
+ "b",
12810
+ "x",
12811
+ "n"
12812
+ ];
12813
+ const hostValues = options.values ?? standardValues();
12814
+ const contexts = options.contexts ?? standardContexts();
12815
+ const total = options.count ?? 100;
12816
+ const steps = options.steps ?? 30;
12817
+ const first = options.seed ?? 9000011;
12818
+ const given = options.equal;
12819
+ const equal = (a, b) => {
12820
+ const answer = given?.(a, b);
12821
+ if (answer !== void 0) return answer;
12822
+ if (isClosure(a) || isClosure(b)) return isClosure(a) && isClosure(b) && docEquals(closureParams(a), closureParams(b));
12823
+ };
12824
+ const counts = {
12825
+ reads: 0,
12826
+ compared: 0,
12827
+ unrepeatable: 0,
12828
+ values: 0,
12829
+ cycles: 0,
12830
+ runs: 0,
12831
+ data: 0,
12832
+ empty: 0,
12833
+ edits: 0,
12834
+ additions: 0,
12835
+ removes: 0,
12836
+ same: 0,
12837
+ machines: 0
12838
+ };
12839
+ const declared = declaredType === void 0 ? {} : { declaredType };
12840
+ for (let run = 0; run < total; run += 1) {
12841
+ const seed = first + run;
12842
+ const rng = random(seed);
12843
+ const node = () => ({
12844
+ type: "ref",
12845
+ value: ["world", rng.pick(nodes)]
12846
+ });
12847
+ let at = 0;
12848
+ let machine = machines[0];
12849
+ let made = 0;
12850
+ const make = () => {
12851
+ if (rng.chance(wired)) return wire(rng, node);
12852
+ const roll = rng.int(10);
12853
+ if (roll === 0) return value(rng);
12854
+ const names = roll < 5 ? hostNames : [
12855
+ ...hostNames,
12856
+ ...nodes,
12857
+ ...nodes,
12858
+ ...nodes
12859
+ ];
12860
+ const doc = linked(generate(machine.world, {
12861
+ ...generateOptions,
12862
+ names,
12863
+ seed: seed * 31 + made++
12864
+ }), rng, nodes);
12865
+ return runs && roll === 9 ? {
12866
+ type: "log",
12867
+ value: [doc]
12868
+ } : doc;
12869
+ };
12870
+ const docs = new Map(nodes.map((id) => [id, make()]));
12871
+ const start = contexts[run % contexts.length];
12872
+ let base = {
12873
+ ...start,
12874
+ names: { ...start.names }
12875
+ };
12876
+ const host = engine(machine, {
12877
+ ...declared,
12878
+ context: base
12879
+ });
12880
+ for (const [id, doc] of docs) host.set(id, doc);
12881
+ for (let step = 0; step < steps; step += 1) {
12882
+ const what = rng.int(12);
12883
+ if (what < 4) {
12884
+ const fresh = fromScratch(machine, docs, base, declaredType);
12885
+ const again = fromScratch(machine, docs, base, declaredType);
12886
+ const ids = shuffle([...docs.keys()], rng);
12887
+ for (const id of rng.chance(.5) ? ids : ids.slice(0, 1)) {
12888
+ const got = host.read(id);
12889
+ const want = fresh.get(id);
12890
+ counts.reads += 1;
12891
+ if (!sameResult(want, again.get(id), equal)) {
12892
+ counts.unrepeatable += 1;
12893
+ continue;
12894
+ }
12895
+ counts.compared += 1;
12896
+ if (sameResult(want, cycleOf(id))) counts.cycles += 1;
12897
+ else if (sameResult(want, runSide(id))) counts.runs += 1;
12898
+ else if (want.ok) counts.values += 1;
12899
+ if (!sameResult(got, want, equal)) {
12900
+ const reason = got.ok === want.ok && !got.ok ? `The engine and the evaluation from scratch give two different errors ${named(got)} and ${named(want)} for the node ${id}.` : `The engine gives ${named(got)} for the node ${id}, and the evaluation from scratch gives ${named(want)}.`;
12901
+ return {
12902
+ ok: false,
12903
+ seed,
12904
+ step,
12905
+ node: id,
12906
+ engine: got,
12907
+ scratch: want,
12908
+ docs: new Map(docs),
12909
+ context: base,
12910
+ machine,
12911
+ reason,
12912
+ counts: { ...counts }
12913
+ };
12914
+ }
12915
+ }
12916
+ } else if (what < 6) {
12917
+ base = {
12918
+ ...base,
12919
+ names: change(base, rng, hostNames, hostValues)
12920
+ };
12921
+ host.changeData(base);
12922
+ counts.data += 1;
12923
+ } else if (what === 6) {
12924
+ host.changeData(base);
12925
+ counts.empty += 1;
12926
+ } else if (what < 9) {
12927
+ const id = rng.pick(nodes);
12928
+ const doc = make();
12929
+ docs.set(id, doc);
12930
+ host.set(id, doc);
12931
+ counts.edits += 1;
12932
+ } else if (what === 9) {
12933
+ const absent = nodes.filter((id) => !docs.has(id));
12934
+ if (absent.length > 0 && (docs.size <= 2 || rng.chance(.65))) {
12935
+ const id = rng.pick(absent);
12936
+ const doc = make();
12937
+ docs.set(id, doc);
12938
+ host.set(id, doc);
12939
+ counts.additions += 1;
12940
+ } else {
12941
+ const id = rng.pick([...docs.keys()]);
12942
+ docs.delete(id);
12943
+ host.remove(id);
12944
+ counts.removes += 1;
12945
+ }
12946
+ } else if (what === 10) {
12947
+ const id = rng.pick([...docs.keys()]);
12948
+ host.set(id, docs.get(id));
12949
+ counts.same += 1;
12950
+ } else if (host.changeMachine !== void 0) {
12951
+ at = (at + 1) % machines.length;
12952
+ machine = machines[at];
12953
+ host.changeMachine(machine);
12954
+ counts.machines += 1;
12955
+ }
12956
+ }
11904
12957
  }
11905
- const journal = { next: (all, waiting) => {
11906
- const items = order.get(all);
11907
- const item = items?.[0];
11908
- if (items === void 0 || item === void 0 || !waiting.includes(item)) return void 0;
11909
- items.shift();
11910
- return item;
11911
- } };
11912
- const perform = (effect) => {
11913
- const entry = effect.address === void 0 ? void 0 : entries.get(effect.address);
11914
- const wrong = mismatch(effect, entry);
11915
- if (wrong !== void 0 || entry === void 0) throw wrong;
11916
- const outcome = entry.outcome;
11917
- 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);
11918
- const value = outcome?.ok === true ? outcome.value : void 0;
11919
- if (wait && entry.waited) return failure === void 0 ? Promise.resolve(value) : Promise.reject(failure);
11920
- if (failure !== void 0) throw failure;
11921
- return value;
12958
+ return {
12959
+ ok: true,
12960
+ counts
11922
12961
  };
11923
- return Object.assign(perform, { [JOURNAL]: journal });
11924
- }
11925
- /** This function gives the error of an effect that the log does not have at its address, or `undefined`. */
11926
- function mismatch(effect, entry) {
11927
- const at = effect.address ?? "";
11928
- 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}.`);
11929
- 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}.`);
11930
- if (!docEquals(entry.payload, effect.payload)) return replayError(effect, `The effect ${effect.kind} at the address ${at} has another payload than the log.`);
11931
- }
11932
- /** This function gives the exception that carries `#REPLAY!` to the kernel, with the place of the effect. */
11933
- function replayError(effect, reason) {
11934
- return new Failure(fail("#REPLAY!", reason, { place: effect.place }).error);
11935
12962
  }
11936
12963
  //#endregion
11937
12964
  //#region src/providers.ts
@@ -12244,6 +13271,6 @@ function typeFromSamples(values, options = {}) {
12244
13271
  return samplesAt(values, options.closed === true, 0);
12245
13272
  }
12246
13273
  //#endregion
12247
- export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, agree, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placed, planner, prepare, print, random, recorder, ref, reference, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
13274
+ export { ATTRIBUTE_RUNGS, CAPS_TYPE, CONSTRUCTS, ENVELOPE_KEYS, Failure, Held, JOURNAL, MergeError, PARTS, PART_INFO, ROOTS, RUNGS, STD_CAPS, STD_DEFINITIONS, STD_DOMAINS, STD_EVALUATORS, STD_EXT, STD_MACHINE, STD_RUNNERS, STD_SYNTAX, STD_WALKERS, STD_WORLD, THING, agree, agreeRun, agreeSchedule, ask, attributeTrace, capKeys, check, checkLatent, checkSteps, checkWorld, classify, components, conforms, context, cycles, dataEdges, decodeDomain, decodeValue, derefStep, describe, docEquals, durable, edgesOf, effectEdges, effectsOf, effectsOfVerb, encodeValue, entriesAsJson, equationType, evaluate, extendDomain, factsOf, fail, failures, field, fits, flatten, formulaDefinitions, formulaEvaluators, formulaFacts, formulaKinds, formulaKnown, formulaMachine, fromJson, generate, heights, intersectCaps, isAlias, isAllowedOrder, isClosure, isEnvelope, isHostThing, isKey, isKind, isNamespace, isRecord, isRef, isStackOverflow, isTypeRef, join, jsTruthy, kindDocument, kindNamed, kindOf, layers, looksLikeKind, lookupKind, makeContext, mapRefs, member, mergeMachines, mergeWorlds, narrow, nearestWalker, necessary, nodeAt, nodeLayers, ok, parse, partCaps, partsOf, pathMatches, permits, placeOf, placed, planner, prepare, print, random, recorder, ref, reference, referenceRun, refsOf, replayer, resolve, restrictMachine, restrictWorld, run, sameResult, sameValue, schedule, splitParts, stored, syntaxFormat, tag, thrown, toJson, typeCheck, typeFromSamples, typeFromSchema, typeIssues, typeName, typePath, typeText, typed, typesOf, valueOfThing, verifyReads, walk, worldThing };
12248
13275
 
12249
13276
  //# sourceMappingURL=index.js.map