@mark1russell7/coeval 0.22.0 → 0.24.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/src/conform.ts CHANGED
@@ -1,6 +1,22 @@
1
- import { type Envelope, type Json, type Key, type TypeRef, docEquals, isEnvelope, isKey, typeName } from "./doc.ts";
2
- import { type Cap, type Context, type Resolution, type ResolveOptions, resolve } from "./deref.ts";
3
- import { type Result, describe, fail, ok, placed, thrown } from "./errors.ts";
1
+ import { type Envelope, type Json, type Key, type TypeRef, docEquals, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
2
+ import {
3
+ type Cap,
4
+ type Context,
5
+ type DerefCode,
6
+ type HostThing,
7
+ type Resolution,
8
+ type ResolveOptions,
9
+ type Thing,
10
+ type WalkResult,
11
+ type Walker,
12
+ THING,
13
+ derefStep,
14
+ isHostThing,
15
+ isRef,
16
+ resolve,
17
+ valueOfThing,
18
+ } from "./deref.ts";
19
+ import { type EvalError, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
4
20
  import {
5
21
  type AllOptions,
6
22
  type Code,
@@ -32,8 +48,8 @@ import { type PartSlot, type Split, pathMatches, scopesOf, splitParts } from "./
32
48
  import { type Definitions, STD_DEFINITIONS } from "./reference.ts";
33
49
  import { STD_EVALUATORS } from "./stdlib.ts";
34
50
  import { sameValue } from "./agree.ts";
35
- import { declaredTruthOf } from "./formula.ts";
36
- import { falseCovers, membersOf, typeIssues, typeText } from "./types.ts";
51
+ import { type FormulaProfile, type ProfileReader, declaredTruthOf, profileReaderOf, signaturesOf } from "./formula.ts";
52
+ import { type ValueType, falseCovers, member, membersOf, typeIssues, typeText } from "./types.ts";
37
53
  import { type Random, random } from "./generate.ts";
38
54
  import { type Kind, type LazyRule, type Shape, type World, type WorldEntry, effectsOfVerb, isKind, isNamespace, lookupKind } from "./world.ts";
39
55
 
@@ -58,12 +74,20 @@ import { type Kind, type LazyRule, type Shape, type World, type WorldEntry, effe
58
74
  * - `C8`: compiled code gives the result of code that compiles again at the time of the evaluation.
59
75
  * - `local`: the code keeps no state from one call to the next.
60
76
  * - `agree`: compiled code gives the result of its definition for the reference interpreter.
77
+ * - `parallel`: the code gives the same result, the same parts and the same effects in each order of the waits of
78
+ * `all` (D128). Only a runner gets this check.
61
79
  * - `truth`: the own `truthy` of a formula profile gives `true` or `false` for each member of its `truthType` (D86).
62
80
  * Only `formula.if`, `formula.and` and `formula.or` of the formula kit get this check.
63
81
  * - `cover`: each sample of a covered domain of the machine is in one domain of each cover of the domain (D94). Each
64
82
  * evaluator and each runner gets this check, because `fits` trusts the covers for each program.
83
+ * - `result`: a walker, the code of a `deref` or a function of a profile gives the form that its type states. A walker
84
+ * and the code of a `deref` throw nothing, except a `Failure` (D137).
85
+ * - `ladder`: a walker steps on a host thing only through `deref` and `has` of its input (D137).
86
+ * - `hops`: each `visit` of a walker tells a step that the walk followed. Without `visit`, the walk stays the same (D137).
87
+ * - `has`: the function `has` of the code of a `deref` gives a truth, and `true` for each key that the code answers (D137).
88
+ * - `typed`: a walker keeps its type rule, an operation its signatures and an extension its entry in `extTypes` (D137).
65
89
  */
66
- export type Obligation = "C1" | "C2" | "C3" | "C4" | "C5" | "C6" | "C7" | "C8" | "local" | "agree" | "truth" | "cover";
90
+ export type Obligation = "C1" | "C2" | "C3" | "C4" | "C5" | "C6" | "C7" | "C8" | "local" | "agree" | "parallel" | "truth" | "cover" | "result" | "ladder" | "hops" | "has" | "typed";
67
91
 
68
92
  /** The probe of a finding: the document, the result of each probe part, the capabilities and the handler. */
69
93
  export interface ConformProbe {
@@ -79,6 +103,10 @@ export interface ConformProbe {
79
103
  readonly caps: readonly Cap[];
80
104
  /** `true` in a run, in which the host does each effect and a part can wait. */
81
105
  readonly running: boolean;
106
+ /** The thing before the step, in a finding of a walker. */
107
+ readonly here?: unknown;
108
+ /** The key of the step, in a finding of the code of a `deref`. */
109
+ readonly key?: Key;
82
110
  }
83
111
 
84
112
  /** One obligation that the code breaks, with the reason and the first probe that shows it. */
@@ -95,6 +123,9 @@ export interface ConformOptions {
95
123
  * The verb that the evaluator serves. The default is `eval`. The facts `pure` and `lazy` of a contract describe only
96
124
  * `eval`, thus `conforms` checks C3 and C4 only for `eval` (D23). `conforms` checks C2 for each verb that the
97
125
  * contract declares effects for: `eval`, and each verb with an entry in `verbEffects` (D49).
126
+ *
127
+ * The verb `step` selects the walker of the name, and the verb `deref` selects the code of the `deref` of the name
128
+ * (D137). Without the option, a name that no evaluator and no runner has selects its walker, and then its `deref`.
98
129
  */
99
130
  readonly verb?: string;
100
131
  /**
@@ -117,11 +148,16 @@ export interface ConformOptions {
117
148
  * definition from `STD_DEFINITIONS`.
118
149
  */
119
150
  readonly definitions?: Definitions;
151
+ /**
152
+ * More things before the step of a walker (D137). Use them for the things that a walker needs, for example a host
153
+ * thing of the language. The default holds three probe nodes, a document, `undefined` and the values of the results.
154
+ */
155
+ readonly things?: readonly unknown[];
120
156
  /** The capabilities of half of the probes. The other half has none. The default is the declared effects and `probe`. */
121
157
  readonly caps?: readonly Cap[];
122
158
  /** The names of the context of each probe. */
123
159
  readonly names?: Readonly<Record<string, unknown>>;
124
- /** The seed of the random results. The same seed gives the same probes. The default is 1. */
160
+ /** The seed of the random results and of the orders of `all`. The same seed gives the same probes. The default is 1. */
125
161
  readonly seed?: number;
126
162
  /** The count of probes with random results for each probe document. The default is 12. */
127
163
  readonly runs?: number;
@@ -138,7 +174,7 @@ export interface ConformOptions {
138
174
  }
139
175
 
140
176
  /** The order of the obligations in the list of findings. */
141
- const ORDER: readonly Obligation[] = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "local", "agree", "truth", "cover"];
177
+ const ORDER: readonly Obligation[] = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "local", "agree", "parallel", "truth", "cover", "result", "ladder", "hops", "has", "typed"];
142
178
 
143
179
  /** A function in the results of the probe parts. It gives the count of its arguments. */
144
180
  const PROBE_FN = (...args: readonly unknown[]): number => args.length;
@@ -170,6 +206,18 @@ const STOPPED: Result = { ok: false, error: { code: "#CALC!", reason: "The probe
170
206
  /** The result of an item of `all` after a failure with `failFast`. */
171
207
  const ABORTED: Result = { ok: false, error: { code: "#ABORT", reason: "The part stopped: a part before it failed, and the parts run with failFast." } };
172
208
 
209
+ /** The order of the waits of one evaluation: this function gives the index of the next wait in the list of the waits of `all`. */
210
+ type Pick = (waiting: number) => number;
211
+
212
+ /** The waits settle in the order that they began. Each shot uses this order first. */
213
+ const FORWARD: Pick = () => 0;
214
+
215
+ /** The waits settle in the other order: the last wait that began settles first. */
216
+ const BACKWARD: Pick = (waiting) => waiting - 1;
217
+
218
+ /** The count of orders from the seed of a probe document, for each scenario with a choice (D128). */
219
+ const SEEDED_ORDERS = 4;
220
+
173
221
  /** The default names of the context of each probe. The generated names of a value (`a`, `b` and `c`) are other names. */
174
222
  const NAMES: Readonly<Record<string, unknown>> = { x: "outer x", y: 2.5, z: ["outer"] };
175
223
 
@@ -262,27 +310,35 @@ interface Wait {
262
310
  done: boolean;
263
311
  }
264
312
 
265
- /** The state of `conforms` for one evaluator. */
266
- interface Env {
313
+ /** The state of `conforms` that each form of code needs. */
314
+ interface BaseEnv {
267
315
  readonly machine: Machine;
268
316
  readonly name: string;
269
317
  readonly verb: string;
318
+ readonly results: readonly Result[];
319
+ readonly names: Readonly<Record<string, unknown>>;
320
+ readonly equal: ((a: unknown, b: unknown) => boolean | undefined) | undefined;
321
+ readonly found: Map<Obligation, Finding>;
322
+ }
323
+
324
+ /** The state of `conforms` for one evaluator. */
325
+ interface Env extends BaseEnv {
270
326
  readonly under: Under;
271
327
  readonly definition: Under | undefined;
272
328
  readonly runner: boolean;
273
- readonly results: readonly Result[];
274
- readonly names: Readonly<Record<string, unknown>>;
275
329
  readonly runs: number;
276
330
  readonly random: Random;
331
+ /** The option `seed`. With the text of a probe document, it gives the orders of the waits of `all` (D128). */
332
+ readonly seed: number;
277
333
  readonly perform: Perform | undefined;
278
- readonly equal: ((a: unknown, b: unknown) => boolean | undefined) | undefined;
279
- readonly found: Map<Obligation, Finding>;
280
334
  }
281
335
 
282
336
  /** The facts of the kind of one type, as the contract and the hook `facts` of the machine give them. */
283
337
  interface Contract {
284
338
  readonly declared: readonly Cap[];
285
339
  readonly pure: boolean;
340
+ /** `true` when the results of the code can change while its input stays the same (FACTS.VOLATILE). */
341
+ readonly volatile: boolean;
286
342
  readonly lazy: readonly LazyRule[] | undefined;
287
343
  /** The parts that the runner evaluates with its code, without a wait (RUN.DRIVES). */
288
344
  readonly drives: readonly LazyRule[] | undefined;
@@ -298,6 +354,21 @@ interface Contract {
298
354
  * resolution. In a run, a probe part and an effect wait with a promise that the probe driver answers at once.
299
355
  */
300
356
  class Shot implements Runtime {
357
+ readonly machine: Machine;
358
+ /** `true` when the code under test is a runner. */
359
+ readonly runner: boolean;
360
+ readonly scenario: Scenario;
361
+ /** The context of the document. */
362
+ readonly context: Context;
363
+ /** The key of each part, for the reasons. */
364
+ readonly keys: readonly string[];
365
+ /** `true` for each part that the runner can evaluate with its code, by the rules `drives` (D119). */
366
+ private readonly driven: readonly boolean[];
367
+ private readonly perform: Perform | undefined;
368
+ /** The resolutions of an earlier evaluation. Each resolution with the same path gets the same answer (C4). */
369
+ private readonly replay: readonly Asked[] | undefined;
370
+ /** The order of the waits of `all`. */
371
+ private readonly pick: Pick;
301
372
  readonly calls: Call[] = [];
302
373
  readonly caused: Caused[] = [];
303
374
  readonly asked: Asked[] = [];
@@ -306,25 +377,36 @@ class Shot implements Runtime {
306
377
  readonly waits = new Map<Promise<unknown>, Wait>();
307
378
  /** The results of the calls of a function that the result gives. */
308
379
  readonly later: Result[] = [];
380
+ /** The waits of each `all`, in the order that they settled. */
381
+ readonly settled: string[] = [];
382
+ /** The count of the times that `all` selected one wait from two or more. Without a choice, each order gives this shot. */
383
+ choices = 0;
384
+ /** `true` when a failure with `failFast` stopped an `all`. Which items stop then depends on the order (RUN.ALL). */
385
+ stopped = false;
309
386
  result: Result = NO_RESULT;
310
387
  private afterwards = false;
311
388
  readonly running: boolean;
312
389
 
313
390
  constructor(
314
- readonly machine: Machine,
315
- /** `true` when the code under test is a runner. */
316
- readonly runner: boolean,
317
- readonly scenario: Scenario,
318
- /** The context of the document. */
319
- readonly context: Context,
320
- /** The key of each part, for the reasons. */
321
- readonly keys: readonly string[],
322
- /** `true` for each part that the runner can evaluate with its code, by the rules `drives` (D119). */
323
- private readonly driven: readonly boolean[],
324
- private readonly perform: Perform | undefined,
325
- /** The resolutions of an earlier evaluation. Each resolution with the same path gets the same answer (C4). */
326
- private readonly replay: readonly Asked[] | undefined,
391
+ machine: Machine,
392
+ runner: boolean,
393
+ scenario: Scenario,
394
+ context: Context,
395
+ keys: readonly string[],
396
+ driven: readonly boolean[],
397
+ perform: Perform | undefined,
398
+ replay: readonly Asked[] | undefined,
399
+ pick: Pick,
327
400
  ) {
401
+ this.machine = machine;
402
+ this.runner = runner;
403
+ this.scenario = scenario;
404
+ this.context = context;
405
+ this.keys = keys;
406
+ this.driven = driven;
407
+ this.perform = perform;
408
+ this.replay = replay;
409
+ this.pick = pick;
328
410
  this.running = scenario.running;
329
411
  }
330
412
 
@@ -401,25 +483,74 @@ class Shot implements Runtime {
401
483
  return fail("#CALC!", `The mock gave a promise for the effect ${effect.kind}, and an evaluation does not wait.`, { place });
402
484
  }
403
485
 
486
+ /**
487
+ * This function is the rule ALL of the kernel, with the order of `pick` in place of the order of the host. The items
488
+ * start in their order up to the limit. Each item goes on to its first wait, and the chosen wait resumes its item.
489
+ */
404
490
  *all(list: readonly (() => Steps)[], options: AllOptions = {}): Generator<Promise<unknown>, readonly Result[], unknown> {
405
491
  if (!this.runner) this.problems.push("The code of an evaluator starts the steps of all. Only a runner waits.");
406
- // The probe runtime starts the items one after the other. Each wait of an item passes through, as in the kernel.
407
- const results: Result[] = [];
492
+ const results: Result[] = new Array<Result>(list.length);
493
+ const limit = Math.max(1, options.concurrency ?? list.length);
494
+ const failFast = options.failFast === true;
495
+ interface Active {
496
+ readonly index: number;
497
+ readonly steps: Steps;
498
+ readonly pending: Promise<unknown>;
499
+ }
500
+ const active: Active[] = [];
501
+ let next = 0;
408
502
  let failed = false;
409
- for (const begin of list) {
410
- if (failed) {
411
- results.push(ABORTED);
412
- continue;
413
- }
414
- let result: Result;
503
+ const settle = (index: number, result: Result): void => {
504
+ results[index] = result;
505
+ if (failFast && !result.ok) failed = true;
506
+ };
507
+ const advance = (index: number, steps: Steps, input: unknown): void => {
508
+ let step: IteratorResult<Promise<unknown>, Result>;
415
509
  try {
416
- result = yield* begin();
510
+ step = steps.next(input);
417
511
  } catch (error) {
418
- result = thrown(error, "a part");
512
+ settle(index, thrown(error, "a part"));
513
+ return;
514
+ }
515
+ if (step.done === true) settle(index, step.value);
516
+ else active.push({ index, steps, pending: step.value });
517
+ };
518
+ const start = (): void => {
519
+ while (!failed && active.length < limit && next < list.length) {
520
+ const index = next;
521
+ next += 1;
522
+ try {
523
+ advance(index, (list[index] as () => Steps)(), undefined);
524
+ } catch (error) {
525
+ settle(index, thrown(error, "a part"));
526
+ }
527
+ }
528
+ };
529
+ try {
530
+ start();
531
+ while (active.length > 0 && !failed) {
532
+ if (active.length > 1) this.choices += 1;
533
+ const [item] = active.splice(this.pick(active.length), 1) as [Active];
534
+ const wait = this.waits.get(item.pending);
535
+ if (wait !== undefined) this.settled.push(wait.what);
536
+ const input: unknown = yield item.pending;
537
+ advance(item.index, item.steps, input);
538
+ start();
539
+ }
540
+ } finally {
541
+ // An item that stops gives up its wait, as in the kernel. The probe also stops the items of an `all` that stops.
542
+ for (const item of active) {
543
+ const wait = this.waits.get(item.pending);
544
+ if (wait !== undefined) wait.done = true;
545
+ try {
546
+ item.steps.return(ABORTED);
547
+ } catch {
548
+ // The finally code of a part threw. The part stops with the abort.
549
+ }
419
550
  }
420
- results.push(result);
421
- if (options.failFast === true && !result.ok) failed = true;
422
551
  }
552
+ if (failed) this.stopped = true;
553
+ for (let index = 0; index < list.length; index += 1) results[index] ??= ABORTED;
423
554
  return results;
424
555
  }
425
556
 
@@ -501,7 +632,7 @@ function typesNaming(world: World, name: string, verb: string): readonly TypeRef
501
632
  }
502
633
 
503
634
  /** This function gives the facts of the kind of a type. The hook `facts` of the machine wins over the contract. */
504
- function contractOf(env: Env, kind: Kind, typePath: readonly Key[], caps: readonly Cap[] | undefined): Contract {
635
+ function contractOf(env: BaseEnv, kind: Kind, typePath: readonly Key[], caps: readonly Cap[] | undefined): Contract {
505
636
  const facts = env.machine.facts?.(typePath, env.name);
506
637
  // A verb without a declaration keeps the effects of eval for the capabilities of the probes, as before D49.
507
638
  // For eval, the effects of the answer of the hook win over the contract, as in check (D118).
@@ -511,6 +642,7 @@ function contractOf(env: Env, kind: Kind, typePath: readonly Key[], caps: readon
511
642
  return {
512
643
  declared,
513
644
  pure: (facts?.pure ?? kind.pure) === true,
645
+ volatile: (facts?.volatile ?? kind.volatile) === true,
514
646
  lazy: facts?.lazy ?? kind.lazy,
515
647
  drives: env.verb === "eval" ? (facts?.drives ?? kind.drives) : undefined,
516
648
  describes: env.verb === "eval",
@@ -735,7 +867,7 @@ function instanceOf(env: Env, under: Under, doc: Envelope, split: SplitOk, sampl
735
867
  const fn = under.fn;
736
868
  return (shot) => {
737
869
  const ctx = shot.context;
738
- const parts: RunPart[] = split.parts.map((slot, index) => ({ ...handleOf(slot), evaluate: (given?: Context) => shot.steps(index, "evaluate", given ?? ctx) }));
870
+ const parts: RunPart[] = split.parts.map((slot, index) => ({ ...handleOf(slot), evaluate: (given?: Context) => shot.steps(index, "evaluate", given ?? ctx), handled: () => shot.steps(index, "evaluate", ctx) }));
739
871
  const input: RunInput = {
740
872
  ...base,
741
873
  value: fill(parts),
@@ -776,9 +908,9 @@ function instanceOf(env: Env, under: Under, doc: Envelope, split: SplitOk, sampl
776
908
  }
777
909
 
778
910
  /** This function evaluates a probe document one time in a new shot. */
779
- function shoot(env: Env, instance: Instance, sample: Sample, scenario: Scenario, more: { readonly context?: Context; readonly replay?: readonly Asked[] } = {}): Shot {
911
+ function shoot(env: Env, instance: Instance, sample: Sample, scenario: Scenario, more: { readonly context?: Context; readonly replay?: readonly Asked[]; readonly pick?: Pick } = {}): Shot {
780
912
  const context = more.context ?? rootContext(env.machine, makeContext(env.names, scenario.caps));
781
- const shot = new Shot(env.machine, env.runner, scenario, context, sample.split.parts.map((slot) => slot.key), sample.driven, env.perform, more.replay);
913
+ const shot = new Shot(env.machine, env.runner, scenario, context, sample.split.parts.map((slot) => slot.key), sample.driven, env.perform, more.replay, more.pick ?? FORWARD);
782
914
  instance(shot);
783
915
  return shot;
784
916
  }
@@ -861,6 +993,49 @@ function differs(a: Shot, b: Shot, equal: Env["equal"], paths = true): string |
861
993
  return undefined;
862
994
  }
863
995
 
996
+ /** This function tells if two lists of effects hold the same effects. Under `all`, the order follows the host (X-RECORD). */
997
+ function sameEffects(a: readonly Caused[], b: readonly Caused[], equal: Env["equal"]): boolean {
998
+ const left = [...b];
999
+ return (
1000
+ a.length === b.length &&
1001
+ a.every((effect) => {
1002
+ const at = left.findIndex((other) => other.kind === effect.kind && sameValue(effect.payload, other.payload, equal));
1003
+ if (at >= 0) left.splice(at, 1);
1004
+ return at >= 0;
1005
+ })
1006
+ );
1007
+ }
1008
+
1009
+ /**
1010
+ * This function gives the difference of two shots of one probe in two orders of the waits, or `undefined` when they
1011
+ * agree (D128). Two orders agree when they give the same results, use the same parts and cause the same effects. The
1012
+ * order of the parts and of the effects does not count, because it follows the host. The resolutions do not count, as
1013
+ * in the simulation of the plan.
1014
+ */
1015
+ function differsByOrder(a: Shot, b: Shot, equal: Env["equal"]): string | undefined {
1016
+ if (!sameOutcome(a.result, b.result, equal)) return `The first order gives ${resultText(a.result)}, and the second gives ${resultText(b.result)}.`;
1017
+ if (!sameResults(a, b, equal)) return "The function that the result gives returns another result.";
1018
+ const parts = (shot: Shot): string =>
1019
+ shot.calls
1020
+ .map((call) => `${call.later ? "later " : ""}${shot.keys[call.part] ?? String(call.part)}`)
1021
+ .sort()
1022
+ .join(", ");
1023
+ if (parts(a) !== parts(b)) return `The parts that evaluate differ: [${parts(a)}] and [${parts(b)}].`;
1024
+ if (sameEffects(a.caused, b.caused, equal)) return undefined;
1025
+ const effects = (shot: Shot): string => shot.caused.map((effect) => `${effect.kind} ${valueText(effect.payload)}`).join(", ");
1026
+ return `The effects differ: [${effects(a)}] and [${effects(b)}].`;
1027
+ }
1028
+
1029
+ /**
1030
+ * This function gives the seed of the orders of a probe document (FNV-1a of its JSON). No time goes into it (D128).
1031
+ * The home of a hash for seeds is `@mark1russell7/rigor-generators`. This copy stays here because core has no dependencies.
1032
+ */
1033
+ function orderSeed(doc: Envelope, seed: number): number {
1034
+ let hash = (2166136261 ^ seed) >>> 0;
1035
+ for (const unit of JSON.stringify(doc)) hash = Math.imul(hash ^ (unit.codePointAt(0) as number), 16777619) >>> 0;
1036
+ return hash;
1037
+ }
1038
+
864
1039
  /** This function gives the probe of a finding. */
865
1040
  function probeOf(sample: Sample, scenario: Scenario, doc: Envelope = sample.doc): ConformProbe {
866
1041
  const results: Record<string, Result> = {};
@@ -870,7 +1045,7 @@ function probeOf(sample: Sample, scenario: Scenario, doc: Envelope = sample.doc)
870
1045
  }
871
1046
 
872
1047
  /** This function keeps the first finding of each obligation. */
873
- function report(env: Env, obligation: Obligation, reason: string, probe: () => ConformProbe): void {
1048
+ function report(env: BaseEnv, obligation: Obligation, reason: string, probe: () => ConformProbe): void {
874
1049
  if (!env.found.has(obligation)) env.found.set(obligation, { obligation, reason, probe: probe() });
875
1050
  }
876
1051
 
@@ -1036,7 +1211,24 @@ function examine(env: Env, sample: Sample, contract: Contract): void {
1036
1211
 
1037
1212
  // C6 and the other checks of one shot in a run: each probe part can wait.
1038
1213
  if (env.runner) {
1039
- for (const scenario of scenariosOf(env, sample, contract, true)) checkShot(env, sample, contract, scenario, shoot(env, code, sample, scenario));
1214
+ const seeded = random(orderSeed(sample.doc, env.seed));
1215
+ const pickSeeded: Pick = (waiting) => seeded.int(waiting);
1216
+ for (const scenario of scenariosOf(env, sample, contract, true)) {
1217
+ const forward = shoot(env, code, sample, scenario);
1218
+ checkShot(env, sample, contract, scenario, forward);
1219
+ // parallel: a shot with a choice runs again in the other orders (D128). Each order also gets the checks of one shot.
1220
+ if (forward.choices === 0) continue;
1221
+ for (const pick of [BACKWARD, ...Array.from({ length: SEEDED_ORDERS }, () => pickSeeded)]) {
1222
+ const other = shoot(env, code, sample, scenario, { pick });
1223
+ checkShot(env, sample, contract, scenario, other);
1224
+ // The first failure with failFast stops other items in another order. That difference is the rule ALL.
1225
+ if (forward.stopped || other.stopped) continue;
1226
+ const why = differsByOrder(forward, other, env.equal);
1227
+ if (why === undefined) continue;
1228
+ const orders = `the order [${forward.settled.join(", ")}] and the order [${other.settled.join(", ")}]`;
1229
+ report(env, "parallel", `The code of ${sample.type} depends on the order of its parallel parts. The waits of all settle in ${orders}. ${why}`, () => probeOf(sample, scenario));
1230
+ }
1231
+ }
1040
1232
  }
1041
1233
  }
1042
1234
 
@@ -1090,6 +1282,396 @@ function checkCovers(env: Env, probe: (value: unknown) => ConformProbe): void {
1090
1282
  );
1091
1283
  }
1092
1284
 
1285
+ /** The code of the verb `step` or `deref` that `conforms` examines (D137). */
1286
+ type StepCode = { readonly verb: "step"; readonly code: Walker } | { readonly verb: "deref"; readonly code: DerefCode };
1287
+
1288
+ /** The keys of each probe of the code of a `deref`. The keys of a record value come after them. */
1289
+ const STEP_KEYS: readonly Key[] = ["a", "x", "length", 0, "missing"];
1290
+
1291
+ /** The most lists of arguments that one signature gets. */
1292
+ const MAX_ARGUMENTS = 64;
1293
+
1294
+ /** One step of a walk through the input: the key, and what the input gave. */
1295
+ interface Stepped {
1296
+ readonly key: Key;
1297
+ readonly thing: Thing;
1298
+ }
1299
+
1300
+ /** The record of the reads of the probe nodes. A read while `open` is `false` skips the input of the walker. */
1301
+ interface Gate {
1302
+ open: boolean;
1303
+ direct: Key[];
1304
+ }
1305
+
1306
+ /** The record of one call of a walker. */
1307
+ interface Walked {
1308
+ /** The result of the walk, as the rule R-WALKER reads it. A throw gives its error. */
1309
+ readonly outcome: Result;
1310
+ /** The text of a throw or of a wrong form. */
1311
+ readonly problem: string | undefined;
1312
+ readonly steps: readonly Stepped[];
1313
+ /** The key of each `visit` that names no step of the input. */
1314
+ readonly stray: readonly Key[];
1315
+ readonly direct: readonly Key[];
1316
+ }
1317
+
1318
+ /** One probe of a walker, and its first walk. */
1319
+ interface Trial {
1320
+ readonly input: { readonly data: Json; readonly here: Thing; readonly context: Context };
1321
+ readonly probe: () => ConformProbe;
1322
+ readonly first: Walked;
1323
+ }
1324
+
1325
+ /** This function makes a host thing whose fields are probe values. The gate gets each read outside the input of a walker. */
1326
+ function probeNode(gate: Gate, label: string, type: string | undefined, fields: Readonly<Record<string, Thing>>): HostThing {
1327
+ const read = (key: Key): boolean => {
1328
+ if (!gate.open) gate.direct.push(key);
1329
+ return Object.hasOwn(fields, String(key));
1330
+ };
1331
+ const node: HostThing & { readonly type: string | undefined } = { [THING]: true, label, type, deref: (key) => (read(key) ? fields[String(key)] : undefined), has: read, toJson: () => ({ node: label }) };
1332
+ return node;
1333
+ }
1334
+
1335
+ /** This function gives the things before a step: a leaf and its two parents, a document, `undefined` and the values of the results. */
1336
+ function probeThings(gate: Gate, results: readonly Result[]): readonly Thing[] {
1337
+ const root = probeNode(gate, "the root", undefined, { a: 1 });
1338
+ const middle = probeNode(gate, "the middle", "panel", { parent: root, names: { a: "named" } });
1339
+ const leaf = probeNode(gate, "the leaf", "box", { parent: middle, x: undefined });
1340
+ return [leaf, middle, root, { type: "probe", value: { a: 1 } }, undefined, ...results.flatMap((result) => (result.ok ? [result.value] : []))];
1341
+ }
1342
+
1343
+ /** This function gives a context for a walker. A frozen context refuses a write to its capabilities, its names and its roots. */
1344
+ function stepContext(machine: Machine, ctx: Context): Context {
1345
+ return Object.freeze({ ...rootContext(machine, ctx), names: Object.freeze({ ...ctx.names }), caps: Object.freeze([...ctx.caps]) });
1346
+ }
1347
+
1348
+ /** This function calls host code. A `Failure` gives its error (E-THROW). Each other throw also gives the text of the throw. */
1349
+ function attempt<T>(call: () => T, who: string): { readonly result: Result<T>; readonly threw: string | undefined } {
1350
+ try {
1351
+ return { result: ok(call()), threw: undefined };
1352
+ } catch (error) {
1353
+ return { result: thrown(error, who), threw: error instanceof Failure ? undefined : describe(error) };
1354
+ }
1355
+ }
1356
+
1357
+ function isObject(value: unknown): value is Readonly<Record<string, unknown>> {
1358
+ return typeof value === "object" && value !== null;
1359
+ }
1360
+
1361
+ function isErrorCode(value: unknown): boolean {
1362
+ return typeof value === "string" && value.startsWith("#");
1363
+ }
1364
+
1365
+ function isEvalError(value: unknown): value is EvalError {
1366
+ return isObject(value) && isErrorCode(value["code"]) && typeof value["reason"] === "string";
1367
+ }
1368
+
1369
+ function isResult(value: unknown): value is Result {
1370
+ return isObject(value) && (value["ok"] === true ? Object.hasOwn(value, "value") : value["ok"] === false && isEvalError(value["error"]));
1371
+ }
1372
+
1373
+ function isWalkResult(value: unknown): value is WalkResult {
1374
+ if (!isObject(value)) return false;
1375
+ if (value["ok"] === true) return Object.hasOwn(value, "thing");
1376
+ return value["ok"] === false && typeof value["reason"] === "string" && (value["code"] === undefined || isErrorCode(value["code"]));
1377
+ }
1378
+
1379
+ /** This function starts a walker one time, with `visit` when `hops` is `true`. */
1380
+ function walkOnce(env: BaseEnv, walker: Walker, gate: Gate, input: Trial["input"], hops: boolean): Walked {
1381
+ const steps: Stepped[] = [];
1382
+ const stray: Key[] = [];
1383
+ const direct: Key[] = [];
1384
+ gate.direct = direct;
1385
+ const adopt = env.machine.adopt ?? ((value: unknown): Thing => value);
1386
+ const ladder = (thing: Thing, key: Key): ReturnType<typeof derefStep> => {
1387
+ gate.open = true;
1388
+ try {
1389
+ return derefStep(env.machine, thing, key);
1390
+ } finally {
1391
+ gate.open = false;
1392
+ }
1393
+ };
1394
+ const visit = (key: Key, thing: Thing): void => {
1395
+ if (!steps.some((step) => String(step.key) === String(key) && Object.is(step.thing, thing))) stray.push(key);
1396
+ };
1397
+ const deref = (thing: Thing, key: Key): Thing => {
1398
+ const reached = ladder(thing, key);
1399
+ const given = reached === undefined ? undefined : adopt(reached.thing);
1400
+ steps.push({ key, thing: given });
1401
+ return given;
1402
+ };
1403
+ const has = (thing: Thing, key: Key): boolean => ladder(thing, key) !== undefined;
1404
+ const tried = attempt(() => walker({ ...input, deref, has, ...(hops ? { visit } : {}) }), `the walker ${env.name}`);
1405
+ const close = (outcome: Result, problem: string | undefined): Walked => ({ outcome, problem, steps, stray, direct });
1406
+ if (!tried.result.ok) return close(tried.result, tried.threw === undefined ? undefined : `throws: ${tried.threw}`);
1407
+ const given: unknown = tried.result.value;
1408
+ if (!isWalkResult(given)) return close(fail("#CALC!", "The walker gives no result."), `gives ${valueText(given)}, and a walker gives a thing or the reason of a failure,`);
1409
+ return close(given.ok ? ok(given.thing) : fail(given.code ?? "#N/A", given.reason), undefined);
1410
+ }
1411
+
1412
+ /** This function gives the difference of two walks with one input, or `undefined` when they agree. */
1413
+ function walkDiffers(a: Walked, b: Walked, equal: BaseEnv["equal"]): string | undefined {
1414
+ if (!sameOutcome(a.outcome, b.outcome, equal)) return `The first walk gives ${resultText(a.outcome)}, and the second gives ${resultText(b.outcome)}.`;
1415
+ const keys = (walked: Walked): string => walked.steps.map((step) => String(step.key)).join(", ");
1416
+ return keys(a) === keys(b) ? undefined : `The steps differ: [${keys(a)}] and [${keys(b)}].`;
1417
+ }
1418
+
1419
+ function thingText(thing: Thing): string {
1420
+ return isHostThing(thing) ? thing.label : valueText(thing);
1421
+ }
1422
+
1423
+ /** This function gives the value type of a thing for the rule of a walker. A JSON value has its plain type, and each other thing has `unknown`. */
1424
+ function plainType(thing: Thing): ValueType {
1425
+ if (isHostThing(thing)) return "unknown";
1426
+ if (typeof thing === "number") return "number";
1427
+ if (typeof thing === "boolean") return "bool";
1428
+ if (typeof thing === "string") return "text";
1429
+ if (thing === null || thing === undefined) return String(thing);
1430
+ if (Array.isArray(thing)) return { list: "unknown" };
1431
+ if (!isRecord(thing)) return "unknown";
1432
+ return { record: Object.fromEntries(Object.keys(thing).map((key) => [key, plainType(thing[key])])) };
1433
+ }
1434
+
1435
+ /** This function checks the type rule of a walker on one walk, from the type `unknown` and from the plain type of the thing (TYPE.WALKER). */
1436
+ function checkWalkType(env: BaseEnv, walker: Walker, sample: Sample, data: Json, trial: Trial): void {
1437
+ const rule = walker.type;
1438
+ const outcome = trial.first.outcome;
1439
+ // A step document with parts has no rule, and a path follows a reference that the walker reaches.
1440
+ if (rule === undefined || sample.split.parts.length > 0 || !outcome.ok || isRef(outcome.value)) return;
1441
+ const domains = env.machine.domains;
1442
+ for (const type of ["unknown", plainType(trial.input.here)]) {
1443
+ const reached = attempt(() => rule({ data, here: type }), "the type rule").result;
1444
+ // A rule that throws, or a bad type, is a type issue of the check (TYPE.RULE).
1445
+ if (!reached.ok || typeIssues(reached.value, domains).length > 0) continue;
1446
+ const value = valueOfThing(outcome.value);
1447
+ if (member(value, reached.value, domains)) continue;
1448
+ report(env, "typed", `The walker ${env.name} reaches ${valueText(value)} from ${thingText(trial.input.here)} of the type ${typeText(type)}, and its type rule gives ${typeText(reached.value)}.`, trial.probe);
1449
+ return;
1450
+ }
1451
+ }
1452
+
1453
+ /** This function gives the probe of a finding of a walker or of the code of a `deref`. */
1454
+ function stepProbe(sample: Sample, caps: readonly Cap[], at: { readonly here: unknown } | { readonly key: Key }): ConformProbe {
1455
+ return { type: sample.type, doc: sample.doc, results: {}, waits: [], caps, running: false, ...at };
1456
+ }
1457
+
1458
+ /** This function examines one walker on one step document, from each thing. */
1459
+ function examineWalker(env: BaseEnv, walker: Walker, gate: Gate, things: readonly Thing[], sample: Sample, contract: Contract): void {
1460
+ // A step document with parts gets the values of its parts (FORMULA.STEPS).
1461
+ const { parts, fill } = sample.split;
1462
+ const data = (parts.length === 0 ? sample.doc.value : fill(parts.map(() => 1))) as Json;
1463
+ const trials = things.map((here, index): Trial => {
1464
+ const input = { data, here, context: stepContext(env.machine, makeContext(env.names, index % 2 === 0 ? contract.caps : NO_CAPS)) };
1465
+ return { input, probe: () => stepProbe(sample, input.context.caps, { here }), first: walkOnce(env, walker, gate, input, true) };
1466
+ });
1467
+ for (const trial of trials) {
1468
+ const { first, probe } = trial;
1469
+ const here = thingText(trial.input.here);
1470
+ if (first.problem !== undefined) report(env, "result", `The walker ${env.name} ${first.problem} on the step ${sample.type} from ${here}.`, probe);
1471
+ const key = first.direct[0];
1472
+ if (key !== undefined) report(env, "ladder", `The walker ${env.name} reads the key ${String(key)} of ${here} directly. A walker steps through deref and has of its input.`, probe);
1473
+ const lost = first.stray[0];
1474
+ if (lost !== undefined) report(env, "hops", `The walker ${env.name} gives a visit for the key ${String(lost)} from ${here}, and no step of its deref gave that thing.`, probe);
1475
+ checkWalkType(env, walker, sample, data, trial);
1476
+ }
1477
+ // A volatile kind declares that its results change (FACTS.VOLATILE).
1478
+ if (contract.volatile) return;
1479
+ const compare = (other: (trial: Trial) => Walked, obligation: Obligation, reason: (why: string) => string): boolean => {
1480
+ let clean = true;
1481
+ for (const trial of trials) {
1482
+ const why = walkDiffers(trial.first, other(trial), env.equal);
1483
+ if (why === undefined) continue;
1484
+ clean = false;
1485
+ report(env, obligation, reason(why), trial.probe);
1486
+ }
1487
+ return clean;
1488
+ };
1489
+ // local: the same input gives the same walk in the other order of the probes.
1490
+ const later = new Map([...trials].reverse().map((trial) => [trial, walkOnce(env, walker, gate, trial.input, true)] as const));
1491
+ const stable = compare((trial) => later.get(trial) as Walked, "local", (why) => `The walker ${env.name} keeps state from one call to the next. The same input gives another walk in a later call. ${why}`);
1492
+ // The other comparisons need code without state, because each of them starts the walk again.
1493
+ if (!stable) return;
1494
+ // hops: a resolution without hops gives no visit (DEREF.HOPS).
1495
+ compare((trial) => walkOnce(env, walker, gate, trial.input, false), "hops", (why) => `The walker ${env.name} walks in another way without visit, and a resolution without hops gives no visit. ${why}`);
1496
+ // C4: the marks of a contract describe the walker of a step document too (FACTS.VERB).
1497
+ if (!contract.pure) return;
1498
+ const varied = (trial: Trial): Walked => walkOnce(env, walker, gate, { ...trial.input, context: stepContext(env.machine, variedContext(trial.input.context, [])) }, true);
1499
+ compare(varied, "C4", (why) => `The kind ${sample.type} is pure, and the walker ${env.name} reads the context. Other values in the roots change the walk. ${why}`);
1500
+ }
1501
+
1502
+ /** This function examines the code of a `deref` on one document, for each key. */
1503
+ function examineDeref(env: BaseEnv, code: DerefCode, sample: Sample, contract: Contract): void {
1504
+ const doc = sample.doc;
1505
+ const keys = [...STEP_KEYS, ...(isRecord(doc.value) ? Object.keys(doc.value) : [])];
1506
+ const who = `the deref ${env.name}`;
1507
+ const ask = (key: Key): { readonly key: Key; readonly reached: Result; readonly has: Result; readonly threw: string | undefined } => {
1508
+ const reached = attempt(() => code(doc, key), who);
1509
+ const has = attempt(() => code.has?.(doc, key), `the function has of ${who}`);
1510
+ return { key, reached: reached.result, has: has.result, threw: reached.threw ?? has.threw };
1511
+ };
1512
+ const first = keys.map(ask);
1513
+ for (const { key, reached, has, threw } of first) {
1514
+ const probe = (): ConformProbe => stepProbe(sample, NO_CAPS, { key });
1515
+ if (threw !== undefined) report(env, "result", `The deref ${env.name} throws for the key ${String(key)} of ${sample.type}: ${threw}`, probe);
1516
+ if (code.has === undefined || !has.ok) continue;
1517
+ if (typeof has.value !== "boolean") report(env, "has", `The function has of the deref ${env.name} gives ${valueText(has.value)} for the key ${String(key)}. It gives true or false.`, probe);
1518
+ else if (reached.ok && reached.value !== undefined && !has.value) report(env, "has", `The deref ${env.name} answers the key ${String(key)} of ${sample.type}, and its function has gives false.`, probe);
1519
+ }
1520
+ if (contract.volatile) return;
1521
+ // local: the same document and key give the same answer in the other order of the probes.
1522
+ for (const before of [...first].reverse()) {
1523
+ const again = ask(before.key);
1524
+ if (sameOutcome(before.reached, again.reached, env.equal) && sameOutcome(before.has, again.has, env.equal)) continue;
1525
+ report(env, "local", `The deref ${env.name} keeps state from one call to the next. The key ${String(before.key)} gives ${resultText(before.reached)}, and later ${resultText(again.reached)}.`, () => stepProbe(sample, NO_CAPS, { key: before.key }));
1526
+ }
1527
+ }
1528
+
1529
+ /** This function gives the argument lists of a signature: the members of each parameter, and one more argument for a rest parameter. */
1530
+ function argumentsOf(types: readonly ValueType[], domains: Machine["domains"], palette: readonly unknown[]): readonly (readonly unknown[])[] {
1531
+ const lists = types.map((type) => membersOf(type, domains, palette));
1532
+ const count = Math.min(
1533
+ MAX_ARGUMENTS,
1534
+ lists.reduce((product, list) => product * list.length, 1),
1535
+ );
1536
+ return Array.from({ length: count }, (_unused, index) => {
1537
+ let rest = index;
1538
+ return lists.map((list) => {
1539
+ const item = list[rest % list.length];
1540
+ rest = Math.floor(rest / list.length);
1541
+ return item;
1542
+ });
1543
+ });
1544
+ }
1545
+
1546
+ /** This function gives the names of the operations of a profile that its tables list. */
1547
+ function opNames(profile: FormulaProfile): readonly string[] {
1548
+ const { ops, lazy, sigs } = profile;
1549
+ return [...new Set([ops, lazy, sigs].flatMap((table) => (table === undefined || typeof table === "function" ? [] : Object.keys(table))))];
1550
+ }
1551
+
1552
+ /** This function gives the document of a probe of an operation, with the arguments as the results of its parts. */
1553
+ function opProbe(name: string, args: readonly unknown[]): ConformProbe {
1554
+ const results: Record<string, Result> = {};
1555
+ args.forEach((value, index) => setField(results, String(index), ok(value)));
1556
+ const value: Json = args.map((_unused, index) => ({ type: "probe", value: { probe: index } }));
1557
+ return { type: `ops.${name}`, doc: { type: ["ops", ...name.split(".")], value }, results, waits: [], caps: NO_CAPS, running: false };
1558
+ }
1559
+
1560
+ /** This function tests that each operation keeps its signatures, with the hook `check` of the profile (TYPE.SOUND). */
1561
+ function checkSignatures(env: Env, profile: FormulaProfile, palette: readonly unknown[]): void {
1562
+ const domains = env.machine.domains;
1563
+ const { ops, lazy, check } = profile;
1564
+ const answer = (name: string, args: readonly unknown[]): Result | undefined => {
1565
+ const delayed = own(lazy, name);
1566
+ if (delayed !== undefined) return delayed(args.map((value) => () => ok(value)));
1567
+ const fn = (typeof ops === "function" ? ops(name) : own(ops, name)) as ((...values: readonly unknown[]) => unknown) | undefined;
1568
+ if (fn === undefined) return undefined;
1569
+ const out = fn(...args);
1570
+ return check?.(out, name) ?? ok(out);
1571
+ };
1572
+ for (const name of opNames(profile)) {
1573
+ for (const sig of signaturesOf(profile, name)) {
1574
+ const types = [...sig.params, ...(sig.rest === undefined ? [] : [sig.rest])];
1575
+ // A bad type is a type issue of the check.
1576
+ if ([...types, sig.to].some((type) => typeIssues(type, domains).length > 0)) continue;
1577
+ for (const args of argumentsOf(types, domains, palette)) {
1578
+ // A throw and an error are the errors of the language (E-THROW). A signature describes only a value.
1579
+ const out = attempt(() => answer(name, args), `the operation ${name}`).result;
1580
+ const given = out.ok ? out.value : undefined;
1581
+ if (given === undefined || !given.ok || member(given.value, sig.to, domains)) continue;
1582
+ report(env, "typed", `The operation ${name} gives ${valueText(given.value)} for the arguments ${valueText(args)}, and its signature gives ${typeText(sig.to)}.`, () => opProbe(name, args));
1583
+ return;
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+
1589
+ /** This function tests that each extension gives a result, and a member of its type in `extTypes`. */
1590
+ function checkExtensions(env: Env, profile: FormulaProfile, options: ConformOptions): void {
1591
+ const domains = env.machine.domains;
1592
+ const context = rootContext(env.machine, makeContext(env.names));
1593
+ for (const name of Object.keys(profile.ext ?? {})) {
1594
+ const fn = own(profile.ext, name);
1595
+ const declared = own(profile.extTypes, name);
1596
+ const type = declared !== undefined && typeIssues(declared, domains).length === 0 ? declared : undefined;
1597
+ for (const data of [...DATA, ...(own(options.values, `ext.${name}`) ?? [])]) {
1598
+ const probe = (): ConformProbe => ({ type: `ext.${name}`, doc: { type: ["ext", ...name.split(".")], value: data }, results: {}, waits: [], caps: NO_CAPS, running: false });
1599
+ const given = attempt(() => fn?.(data, context, domains) as unknown, `the extension ${name}`).result;
1600
+ if (!given.ok) continue;
1601
+ if (!isResult(given.value)) report(env, "result", `The extension ${name} gives ${valueText(given.value)}. It gives a result.`, probe);
1602
+ else if (type !== undefined && given.value.ok && !member(given.value.value, type, domains)) report(env, "typed", `The extension ${name} gives ${valueText(given.value.value)}, and its entry in extTypes gives ${typeText(type)}.`, probe);
1603
+ }
1604
+ }
1605
+ }
1606
+
1607
+ /**
1608
+ * This function tests the functions of a profile that the evaluator reads, by the forms of their types (D137). A throw
1609
+ * gets no finding here, because the kit gives it an error (E-THROW). Only `truthy` has a stated law against a throw.
1610
+ */
1611
+ function checkProfile(env: Env, reader: ProfileReader, options: ConformOptions, probe: (value: unknown, result?: Result) => ConformProbe): void {
1612
+ const { profile } = reader;
1613
+ const { missing, omit, combine, check, lazy } = profile;
1614
+ const values = env.results.flatMap((result) => (result.ok ? [result.value] : []));
1615
+ const errors = env.results.flatMap((result) => (result.ok ? [] : [result.error]));
1616
+ const form = (what: string, call: () => unknown, good: (value: unknown) => boolean, want: string, at: () => ConformProbe): void => {
1617
+ const given = attempt(call, what).result;
1618
+ if (given.ok && !good(given.value)) report(env, "result", `The function ${what} of the profile gives ${valueText(given.value)}. It gives ${want}.`, at);
1619
+ };
1620
+ const isBool = (value: unknown): boolean => typeof value === "boolean";
1621
+ if (reader.name === "formula.ref" && missing !== undefined) for (const value of values) form("missing", () => missing(value), isBool, "true or false", () => probe(value));
1622
+ if (reader.name === "formula.record" && omit !== undefined) for (const error of errors) form("omit", () => omit(error), isBool, "true or false", () => probe(error, { ok: false, error }));
1623
+ if (reader.name === "formula.ext") checkExtensions(env, profile, options);
1624
+ if (reader.name !== "formula.op") return;
1625
+ checkSignatures(env, profile, values);
1626
+ if (combine !== undefined && errors.length > 0) form("combine", () => combine([...errors, ...errors]), isEvalError, "an error with a code and a reason", () => probe(errors[0], { ok: false, error: errors[0] as EvalError }));
1627
+ for (const name of Object.keys(lazy ?? {})) {
1628
+ const fn = own(lazy, name);
1629
+ for (const value of values) form(`lazy ${name}`, () => fn?.([() => ok(value), () => ok(value)]), isResult, "a result", () => opProbe(name, [value, value]));
1630
+ }
1631
+ if (check !== undefined) for (const value of values) form("check", () => check(value, "probe"), (given) => given === undefined || isResult(given), "a result or no value", () => probe(value));
1632
+ }
1633
+
1634
+ /** This function gives the types of the operations or the extensions of a profile that the evaluator serves (D137). */
1635
+ function profileTypes(machine: Machine, name: string, verb: string, reader: ProfileReader | undefined): readonly TypeRef[] {
1636
+ if (reader === undefined || (reader.name !== "formula.op" && reader.name !== "formula.ext")) return [];
1637
+ const { profile } = reader;
1638
+ const extensions = reader.name === "formula.ext";
1639
+ const names = extensions ? Object.keys(profile.ext ?? {}) : opNames(profile);
1640
+ const paths = names.filter((item) => profile.volatile?.includes(item) !== true).map((item) => [extensions ? "ext" : "ops", ...item.split(".")]);
1641
+ return paths.filter((path) => {
1642
+ const found = lookupKind(machine.world, path);
1643
+ return found.ok && own(found.kind.verbs, verb) === name;
1644
+ });
1645
+ }
1646
+
1647
+ /** This function gives the code that the name and the verb select: a walker or the code of a `deref`. The default is `undefined`. */
1648
+ function stepCodeOf(machine: Machine, name: string, verb: string | undefined): StepCode | undefined {
1649
+ const walker = own(machine.walkers, name);
1650
+ const code = own(machine.derefs, name);
1651
+ if (verb === "step" && walker !== undefined) return { verb, code: walker };
1652
+ if (verb === "deref" && code !== undefined) return { verb, code };
1653
+ if (verb !== undefined || own(machine.evaluators, name) !== undefined || own(machine.runners, name) !== undefined) return undefined;
1654
+ if (walker !== undefined) return { verb: "step", code: walker };
1655
+ return code === undefined ? undefined : { verb: "deref", code };
1656
+ }
1657
+
1658
+ /** This function gives each probe document of each type, with the contract of its kind. */
1659
+ function eachSample(env: BaseEnv, types: readonly TypeRef[], options: ConformOptions, visit: (sample: Sample, contract: Contract) => void): void {
1660
+ if (types.length === 0) throw new Error(`No kind of the world names ${env.name} for the verb ${env.verb}. Give the types of the probe documents in the option types.`);
1661
+ for (const type of types) {
1662
+ const found = lookupKind(env.machine.world, type);
1663
+ if (!found.ok) throw new Error(`The world has no kind for the type ${typeName(type)}.`);
1664
+ const contract = contractOf(env, found.kind, found.path, options.caps);
1665
+ const extra = own(options.values, typeName(type)) ?? own(options.values, found.path.join(".")) ?? [];
1666
+ for (const sample of samplesOf(type, found.path, found.kind, contract, extra)) visit(sample, contract);
1667
+ }
1668
+ }
1669
+
1670
+ /** This function gives the findings of the state of `conforms`, in the order of the obligations. */
1671
+ function findingsOf(env: BaseEnv): readonly Finding[] {
1672
+ return ORDER.filter((obligation) => env.found.has(obligation)).map((obligation) => env.found.get(obligation) as Finding);
1673
+ }
1674
+
1093
1675
  /**
1094
1676
  * This function examines one evaluator or runner of a machine against the obligations of host code (K92, D36). It
1095
1677
  * starts the code directly, without the kernel, with probe parts and a probe runtime. It generates probe documents from
@@ -1100,56 +1682,66 @@ function checkCovers(env: Env, probe: (value: unknown) => ConformProbe): void {
1100
1682
  * compiled code with its definition. The finding of an obligation names the first probe that breaks it. An empty list
1101
1683
  * means that each probe conforms.
1102
1684
  *
1685
+ * The check `parallel` starts a runner with a choice of waits under `all` again, in other orders of the waits (D128).
1103
1686
  * The check `truth` tests the declared truth of a formula profile (D86). The check `cover` tests the covers of the
1104
1687
  * domains of the machine from samples (D94).
1105
1688
  *
1689
+ * The name can also select a walker or the code of a `deref` (D137). The checks of a walker are `result`, `local`,
1690
+ * `ladder`, `hops`, `typed` and `C4`. The checks of the code of a `deref` are `result`, `local` and `has`. The evaluators
1691
+ * `formula.op` and `formula.ext` also examine each operation and each extension that the tables of their profile list.
1692
+ * The checks `typed` and `result` test the functions of the profile directly.
1693
+ *
1106
1694
  * @param machine - The machine with the code and the world of the kinds.
1107
- * @param name - The name of the evaluator or of the runner. A runner wins over an evaluator of the same name.
1695
+ * @param name - The name of the code. A runner wins over an evaluator of the same name, and each of them wins over a
1696
+ * walker or a `deref` without the option `verb`.
1108
1697
  * @param options - The verb, the types and the values of the probe documents, and the results of the probe parts. Also
1109
1698
  * the definitions, the capabilities, the names, the seed, the mock and the rule of equality.
1110
1699
  * @returns The first finding of each obligation that the code breaks, in the order of the obligations.
1111
1700
  * @throws {Error} The machine has no code with the name, or no kind names it and the options give no types.
1112
1701
  */
1113
1702
  export function conforms(machine: Machine, name: string, options: ConformOptions = {}): readonly Finding[] {
1114
- const verb = options.verb ?? "eval";
1703
+ const step = stepCodeOf(machine, name, options.verb);
1704
+ const verb = step?.verb ?? options.verb ?? "eval";
1705
+ const base: BaseEnv = { machine, name, verb, results: options.results ?? RESULTS, names: options.names ?? NAMES, equal: options.equal, found: new Map() };
1706
+ if (step !== undefined) {
1707
+ const gate: Gate = { open: false, direct: [] };
1708
+ const things = [...probeThings(gate, base.results), ...(options.things ?? [])];
1709
+ eachSample(base, options.types ?? typesNaming(machine.world, name, verb), options, (sample, contract) => {
1710
+ if (step.verb === "step") examineWalker(base, step.code, gate, things, sample, contract);
1711
+ else examineDeref(base, step.code, sample, contract);
1712
+ });
1713
+ return findingsOf(base);
1714
+ }
1115
1715
  const runnerCode = own(machine.runners, name);
1116
1716
  const code = runnerCode ?? own(machine.evaluators, name);
1117
- if (code === undefined) throw new Error(`The machine has no evaluator and no runner with the name ${name}.`);
1717
+ if (code === undefined) throw new Error(`The machine has no evaluator and no runner with the name ${name}, and no walker and no deref.`);
1118
1718
  const runner = runnerCode !== undefined;
1119
1719
  const env: Env = {
1120
- machine,
1121
- name,
1122
- verb,
1720
+ ...base,
1123
1721
  under: underOf(code, runner),
1124
1722
  definition: definitionOf(machine, name, runner, options.definitions),
1125
1723
  runner,
1126
- results: options.results ?? RESULTS,
1127
- names: options.names ?? NAMES,
1128
1724
  runs: options.runs ?? 12,
1129
1725
  random: random(options.seed ?? 1),
1726
+ seed: options.seed ?? 1,
1130
1727
  perform: options.perform,
1131
- equal: options.equal,
1132
- found: new Map(),
1133
1728
  };
1134
- const types = options.types ?? typesNaming(machine.world, name, verb);
1729
+ const reader = profileReaderOf(code);
1730
+ const types = options.types ?? [...typesNaming(machine.world, name, verb), ...profileTypes(machine, name, verb, reader)];
1135
1731
  let first: Sample | undefined;
1136
- if (types.length === 0) throw new Error(`No kind of the world names ${name} for the verb ${verb}. Give the types of the probe documents in the option types.`);
1137
- for (const type of types) {
1138
- const found = lookupKind(machine.world, type);
1139
- if (!found.ok) throw new Error(`The world has no kind for the type ${typeName(type)}.`);
1140
- const contract = contractOf(env, found.kind, found.path, options.caps);
1141
- const extra = own(options.values, typeName(type)) ?? own(options.values, found.path.join(".")) ?? [];
1142
- for (const sample of samplesOf(type, found.path, found.kind, contract, extra)) {
1143
- first ??= sample;
1144
- examine(env, sample, contract);
1145
- }
1146
- }
1732
+ eachSample(env, types, options, (sample, contract) => {
1733
+ first ??= sample;
1734
+ examine(env, sample, contract);
1735
+ });
1147
1736
  // A probe of the truth or of a cover gives the value to each part of the first probe document.
1148
- const probeWith = (value: unknown): ConformProbe =>
1149
- first === undefined
1737
+ const probeWith = (value: unknown, result: Result = ok(value)): ConformProbe => {
1738
+ const sample = first as Sample | undefined;
1739
+ return sample === undefined
1150
1740
  ? { type: typeName(types[0] as TypeRef), doc: { type: types[0] as TypeRef, value: null }, results: {}, waits: [], caps: NO_CAPS, running: runner }
1151
- : probeOf(first, { results: first.split.parts.map(() => ok(value)), waits: [], caps: NO_CAPS, running: runner });
1741
+ : probeOf(sample, { results: sample.split.parts.map(() => result), waits: [], caps: NO_CAPS, running: runner });
1742
+ };
1152
1743
  checkTruth(env, code, probeWith);
1153
1744
  checkCovers(env, probeWith);
1154
- return ORDER.filter((obligation) => env.found.has(obligation)).map((obligation) => env.found.get(obligation) as Finding);
1745
+ if (reader !== undefined) checkProfile(env, reader, options, probeWith);
1746
+ return findingsOf(env);
1155
1747
  }