@mark1russell7/coeval 0.16.0 → 0.18.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/formula.ts CHANGED
@@ -4,8 +4,8 @@ import { type CompiledPath, type Context, type Resolution, compilePath, valueOfT
4
4
  import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
5
5
  import { type Code, type CompileInput, type CompiledEvaluator, type CompiledPart, type Evaluator, type Machine, type Runtime, resolveCompiled, totalCode } from "./evaluate.ts";
6
6
  import { type MachinePart, mergeMachines } from "./merge.ts";
7
- import { type Domain, type ValueType, HEADER_KEYS, STD_DOMAINS, callable, fieldOf, fits, fnOf, holdsEnvelope, isPrecise, isUnknown, itemOf, join, member, stepType, typeText } from "./types.ts";
8
- import type { TypeInput, TypeRule } from "./typing.ts";
7
+ import { type Domain, type ValueType, HEADER_KEYS, STD_DOMAINS, callable, fieldOf, fits, fnOf, holdsEnvelope, isPrecise, isUnknown, itemOf, join, member, stepType, typeIssues, typeText } from "./types.ts";
8
+ import type { PartType, TypeInput, TypeRule } from "./typing.ts";
9
9
  import { decodeDomain, domainTypeOf } from "./codec.ts";
10
10
  import { type StaticRead, factsOf } from "./facts.ts";
11
11
 
@@ -31,8 +31,13 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
31
31
  * - `lazy`: the operations that evaluate their arguments themselves.
32
32
  * - `ext`: the extensions of the namespace `ext`, by their name. The kit has `ext.undefined`, `ext.number` and
33
33
  * `ext.domain`.
34
+ * - `extTypes`: the value type of each extension of `ext`, by its name, for a check of types (TYPE.EXT, D103). An
35
+ * extension without an entry gives `unknown` (TYPE.GRADUAL). An entry for a standard extension has no effect.
34
36
  * - `truthy`: the truth of a value for `if`, `and` and `or`. `undefined` is an error `#VALUE!`. The default
35
37
  * accepts only `true` and `false`.
38
+ * - `truthType`: the value type of a truth for the own `truthy`, for a check of types (TYPE.TRUTH, D81). A test of
39
+ * `if` and each term of `and` and `or` must fit it. Without it, the check accepts each type there. A profile without
40
+ * `truthy` ignores it, because the default truth needs `bool`.
36
41
  * - `otherwise`: the value of an `if` without `else`. The default is `null`. A profile with this key gives its value,
37
42
  * also `undefined`.
38
43
  * - `logic`: what `and` and `or` give: a truth value (the default), or the value that decided.
@@ -50,13 +55,16 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
50
55
  * overloads. An operation without a signature takes and gives `unknown` (TYPE.GRADUAL).
51
56
  *
52
57
  * A check of types trusts the signatures. Each operation must give a member of the result of each signature that
53
- * its arguments match. The hook `check` must keep each result in that type.
58
+ * its arguments match. The hook `check` must keep each result in that type. The function `truthy` must give a truth
59
+ * for each member of `truthType`. Each extension must give a member of its type in `extTypes`.
54
60
  */
55
61
  export interface FormulaProfile {
56
62
  readonly ops?: Readonly<Record<string, OpFn>> | ((name: string) => OpFn | undefined);
57
63
  readonly lazy?: Readonly<Record<string, LazyOp>>;
58
64
  readonly ext?: Readonly<Record<string, ExtFn>>;
65
+ readonly extTypes?: Readonly<Record<string, ValueType>>;
59
66
  readonly truthy?: (value: unknown) => boolean | undefined;
67
+ readonly truthType?: ValueType;
60
68
  readonly otherwise?: unknown;
61
69
  readonly logic?: "boolean" | "value";
62
70
  readonly omit?: (error: EvalError) => boolean;
@@ -266,12 +274,23 @@ function paramsOf(value: unknown): readonly string[] {
266
274
 
267
275
  /**
268
276
  * This function makes the type rules of the formula kit for one profile (TYPE.SYNTH, D25). Each evaluator of the kit
269
- * holds its rule. The rules read the rules of the profile that change a type: `truthy`, `logic`, `otherwise`, `omit`
270
- * and `sigs`.
277
+ * holds its rule. The rules read the rules of the profile that change a type: `truthy`, `truthType`, `logic`,
278
+ * `otherwise`, `omit`, `sigs` and `extTypes`.
271
279
  */
272
280
  function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule>> {
273
- // Only the default truth accepts nothing but a truth value. A truth of the profile accepts each value.
274
- const truth: ValueType | undefined = profile.truthy === undefined ? "bool" : undefined;
281
+ // The default truth needs a truth value. An own truth needs its declared type, or nothing without a declaration (D81).
282
+ const declared = profile.truthy !== undefined && profile.truthType !== undefined;
283
+ const truth: ValueType | undefined = profile.truthy === undefined ? "bool" : profile.truthType;
284
+ /**
285
+ * The need of a test or a term. The check examines a declared type of a truth as a contract, one time. A declared
286
+ * type with a problem needs nothing, thus one problem gives one issue.
287
+ */
288
+ const truthPart = (input: TypeInput, what: string): PartType => {
289
+ if (truth === undefined) return {};
290
+ if (!declared) return { expect: truth, what };
291
+ const type = input.contract(truth, "truth type of the profile");
292
+ return typeIssues(type, input.machine.domains).length > 0 ? {} : { expect: type, what };
293
+ };
275
294
  const logicValue = profile.logic === "value";
276
295
  const otherwise = Object.hasOwn(profile, "otherwise") ? valueType(profile.otherwise) : "null";
277
296
  const lookup = (name: string): readonly unknown[] | undefined => {
@@ -307,7 +326,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
307
326
  return { sigs, bad: sigs.length !== raw.length };
308
327
  };
309
328
  const logic = (word: "and" | "or"): TypeRule => ({
310
- part: (_input, path) => (truth === undefined ? {} : { expect: truth, what: `term ${Number(path[0]) + 1} of ${word}` }),
329
+ part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
311
330
  synth: (input) => {
312
331
  if (!logicValue) return "bool";
313
332
  // With the logic "value", the result is the term that decided, or the last term.
@@ -438,7 +457,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
438
457
 
439
458
  "formula.if": {
440
459
  part: (input, path) => {
441
- if (path[0] === "test") return truth === undefined ? {} : { expect: truth, what: "test of if" };
460
+ if (path[0] === "test") return truthPart(input, "test of if");
442
461
  return isUnknown(input.expected, input.machine.domains) ? {} : { expect: input.expected, what: `${String(path[0])} branch of if` };
443
462
  },
444
463
  synth: (input) => {
@@ -494,7 +513,14 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
494
513
  synth: (input) => {
495
514
  const name = nameOf(input);
496
515
  const own = profile.ext !== undefined && Object.hasOwn(profile.ext, name);
497
- if (own) return "unknown";
516
+ if (own) {
517
+ // The declared type is a contract. A type with a problem gives unknown, thus one problem gives one issue (D103).
518
+ const types = profile.extTypes;
519
+ const declared = types !== undefined && Object.hasOwn(types, name) ? types[name] : undefined;
520
+ if (declared === undefined) return "unknown";
521
+ const type = input.contract(declared, `type of ext.${name} in extTypes`);
522
+ return typeIssues(type, input.machine.domains).length > 0 ? "unknown" : type;
523
+ }
498
524
  // The standard extensions give the values that JSON cannot write, thus each has a base type (D48).
499
525
  if (name === "undefined") return "undefined";
500
526
  if (name === "number") return "number";
@@ -518,15 +544,43 @@ export function formulaEvaluators(profile: FormulaProfile = {}): Readonly<Record
518
544
  return { ...kitOf(profile).evaluators };
519
545
  }
520
546
 
547
+ /** The declared truth of a profile: its own `truthy` and its `truthType` (D81). */
548
+ export interface DeclaredTruth {
549
+ readonly truthy: (value: unknown) => boolean | undefined;
550
+ readonly truthType: ValueType;
551
+ }
552
+
553
+ /** The evaluators of the kit that read the truth of a value. */
554
+ const TRUTH_READERS: readonly string[] = ["formula.if", "formula.and", "formula.or"];
555
+
556
+ /** The declared truth of each evaluator of the kit that reads a truth, for `conforms` (D86). */
557
+ const truthByCode = /* @__PURE__ */ new WeakMap<object, DeclaredTruth>();
558
+
559
+ /**
560
+ * This function gives the declared truth of the profile of an evaluator of the kit (D86). Only `formula.if`,
561
+ * `formula.and` and `formula.or` of a profile with an own `truthy` and a `truthType` have one. The kernel does not
562
+ * export the function.
563
+ *
564
+ * @param code - The evaluator or the runner.
565
+ * @returns The declared truth, or `undefined`.
566
+ */
567
+ export function declaredTruthOf(code: unknown): DeclaredTruth | undefined {
568
+ return (typeof code === "object" || typeof code === "function") && code !== null ? truthByCode.get(code) : undefined;
569
+ }
570
+
521
571
  /** This function makes new evaluators of the formula kit for the fields of one profile. */
522
572
  function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evaluator>> {
523
573
  const evaluators = formulaCode(profile);
524
574
  const types = formulaTypes(profile);
575
+ const { truthy, truthType } = profile;
576
+ const truth = truthy === undefined || truthType === undefined ? undefined : { truthy, truthType };
525
577
  const out: Record<string, Evaluator> = {};
526
578
  for (const name of Object.keys(evaluators)) {
527
579
  const evaluator = evaluators[name] as CompiledEvaluator;
528
580
  const type = Object.hasOwn(types, name) ? types[name] : undefined;
529
- out[name] = Object.freeze(type === undefined ? evaluator : { compile: evaluator.compile, type });
581
+ const made = Object.freeze(type === undefined ? evaluator : { compile: evaluator.compile, type });
582
+ if (truth !== undefined && TRUTH_READERS.includes(name)) truthByCode.set(made, truth);
583
+ out[name] = made;
530
584
  }
531
585
  return Object.freeze(out);
532
586
  }
@@ -536,7 +590,9 @@ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
536
590
  "ops",
537
591
  "lazy",
538
592
  "ext",
593
+ "extTypes",
539
594
  "truthy",
595
+ "truthType",
540
596
  "otherwise",
541
597
  "logic",
542
598
  "omit",
package/src/index.ts CHANGED
@@ -174,6 +174,7 @@ export {
174
174
  STD_RUNNERS,
175
175
  STD_WALKERS,
176
176
  STD_WORLD,
177
+ WORLD_SYNTAX,
177
178
  attributeTrace,
178
179
  nearestWalker,
179
180
  placeOf,
@@ -238,8 +239,11 @@ export {
238
239
  type ParseResult,
239
240
  type SyntaxAssoc,
240
241
  type SyntaxCalls,
242
+ type SyntaxEnvelopes,
241
243
  type SyntaxFixity,
242
244
  type SyntaxForm,
245
+ type SyntaxFormArgs,
246
+ type SyntaxHeader,
243
247
  type SyntaxIssue,
244
248
  type SyntaxLiterals,
245
249
  type SyntaxOperator,
@@ -277,7 +281,25 @@ export {
277
281
  agreeSchedule,
278
282
  } from "./meaning.ts";
279
283
  export { type GraphCycle, type GraphEdge, type GraphNode, type GraphOptions, type NodeGraph, components, cycles, heights, necessary, nodeLayers } from "./graph.ts";
280
- export { type Recorder, type ReplayEntry, type ReplayLog, type ReplayOptions, type ReplaySettle, recorder, replayer } from "./replay.ts";
284
+ export {
285
+ type Recorder,
286
+ type ReplayCausePartsJson,
287
+ type ReplayEntry,
288
+ type ReplayEntryJson,
289
+ type ReplayErrorJson,
290
+ type ReplayExceptionJson,
291
+ type ReplayLog,
292
+ type ReplayLogJson,
293
+ type ReplayPartJson,
294
+ type ReplayPartsJson,
295
+ type ReplayOptions,
296
+ type ReplayResultJson,
297
+ type ReplaySettle,
298
+ decodeLog,
299
+ encodeLog,
300
+ recorder,
301
+ replayer,
302
+ } from "./replay.ts";
281
303
  export { type GenerateOptions, type Random, generate, random, typesOf } from "./generate.ts";
282
304
  export { type Domain, type DomainCodec, type TypeIssue, type ValueType, STD_DOMAINS, fits, join, member, typeIssues, typeText } from "./types.ts";
283
305
  export { type PartType, type RootTypes, type TypeInput, type TypeProblem, type TypeRule, type Typed, CAPS_TYPE } from "./typing.ts";
package/src/merge.ts CHANGED
@@ -89,32 +89,54 @@ function same(a: unknown, b: unknown): boolean {
89
89
 
90
90
  /**
91
91
  * Two domains are the same when they are one object. They are also the same when they have the same test `is`,
92
- * parents, summary and steps. Thus two languages that give one test under one name give one domain.
92
+ * parents, summary, steps, codec, list of values, samples and covers. Thus two languages that give one test under one name give one
93
+ * domain. Two codecs are the same when they have the same functions `encode` and `decode` (D80).
93
94
  */
94
95
  function sameDomain(a: Domain, b: Domain): boolean {
95
- return a === b || (a.is === b.is && same(a.under ?? [], b.under ?? []) && a.summary === b.summary && same(a.steps ?? {}, b.steps ?? {}));
96
+ if (a === b) return true;
97
+ // The type names each field of Domain, thus a new field fails the check of types until the merge compares it (K180).
98
+ const fields: { readonly [K in keyof Domain]-?: boolean } = {
99
+ is: a.is === b.is,
100
+ under: same(a.under ?? [], b.under ?? []),
101
+ summary: a.summary === b.summary,
102
+ steps: same(a.steps ?? {}, b.steps ?? {}),
103
+ codec: same(a.codec ?? null, b.codec ?? null),
104
+ values: same(a.values ?? null, b.values ?? null),
105
+ samples: same(a.samples ?? null, b.samples ?? null),
106
+ covers: same(a.covers ?? [], b.covers ?? []),
107
+ };
108
+ return Object.values(fields).every(Boolean);
96
109
  }
97
110
 
98
- /** What `extendDomain` adds to a domain: more parents, and the types of more steps. */
111
+ /** What `extendDomain` adds to a domain: more parents, the types of more steps, and more covers. */
99
112
  export interface DomainExtension {
100
113
  /** The names of the parents to add. A member of the domain then also passes the test of each new parent. */
101
114
  readonly under?: readonly string[];
102
115
  /** The types of the steps to add. A step with the key of an earlier step replaces its type. */
103
116
  readonly steps?: Readonly<Record<string, ValueType>>;
117
+ /**
118
+ * The covers to add (TYPE.FITS-COVER, D85). A cover that the domain has already does not change. The language that
119
+ * adds a cover keeps it true, because `fits` trusts it.
120
+ */
121
+ readonly covers?: readonly (readonly string[])[];
104
122
  }
105
123
 
106
124
  /**
107
- * This function makes a part of a merge that adds parents or steps to a domain of an earlier part (TYPE.PARENTS). An
108
- * example is a domain of another language. The part reads the domain from the merge before it (`prev`). It gives a
109
- * copy with the same test and summary, and it names `["domains", name]` in `replaces` (D17). A domain of the kernel
110
- * that the earlier parts do not name comes from `STD_DOMAINS`.
125
+ * This function makes a part of a merge that adds parents, steps or covers to a domain of an earlier part
126
+ * (TYPE.PARENTS). An example is a domain of another language. The part reads the domain from the merge before it
127
+ * (`prev`). It gives a copy with the same test, summary, codec, list of values and samples, and it names
128
+ * `["domains", name]` in `replaces` (D17).
129
+ * A domain of the kernel that the earlier parts do not name comes from `STD_DOMAINS`.
130
+ *
131
+ * An extension does not change the codec. Thus each member keeps its document (D80).
111
132
  *
112
133
  * A new parent adds its test to the domain (TYPE.MEMBER). Thus `fits` stays sound, but a value that fails the test of
113
134
  * the parent is no longer a member. The language that adds the parent keeps the signatures of the other language
114
- * true. A name without a domain gives an empty part, and `typeIssues` reports each use of that name.
135
+ * true. A new parent of a domain in a cover of another domain can make that cover false (D85). A name without a
136
+ * domain gives an empty part, and `typeIssues` reports each use of that name.
115
137
  *
116
138
  * @param name - The name of the domain.
117
- * @param more - The parents and the steps to add.
139
+ * @param more - The parents, the steps and the covers to add.
118
140
  * @returns The part, as a function of the merge before it.
119
141
  */
120
142
  export function extendDomain(name: string, more: DomainExtension): (prev: Machine) => MachinePart {
@@ -125,14 +147,27 @@ export function extendDomain(name: string, more: DomainExtension): (prev: Machin
125
147
  const under = [...(found.under ?? [])];
126
148
  for (const parent of more.under ?? []) if (parent !== name && !under.includes(parent)) under.push(parent);
127
149
  const steps = { ...found.steps, ...more.steps };
128
- const domain: Domain = {
150
+ const covers = [...(found.covers ?? [])];
151
+ for (const cover of more.covers ?? []) if (!covers.some((each) => same(each, cover))) covers.push([...cover]);
152
+ // The type names each field of Domain, thus a new field fails the check of types until the copy reads it (K180).
153
+ const fields: { readonly [K in keyof Domain]-?: Domain[K] | undefined } = {
129
154
  is: found.is,
130
- ...(under.length === 0 ? {} : { under }),
131
- ...(found.summary === undefined ? {} : { summary: found.summary }),
132
- ...(Object.keys(steps).length === 0 ? {} : { steps }),
155
+ under: under.length === 0 ? undefined : under,
156
+ summary: found.summary,
157
+ steps: Object.keys(steps).length === 0 ? undefined : steps,
158
+ // A new parent only adds a test, thus each member keeps its document (D80).
159
+ codec: found.codec,
160
+ // A new parent only adds a test, thus the list of values stays complete (TYPE.FITS-FINITE).
161
+ values: found.values,
162
+ // A sample outside the new line does not count, thus the samples stay (D99).
163
+ samples: found.samples,
164
+ // A new parent only adds a test, thus each cover stays true (TYPE.FITS-COVER). A new cover is a claim of the extension.
165
+ covers: covers.length === 0 ? undefined : covers,
133
166
  };
167
+ const domain: Record<string, unknown> = {};
168
+ for (const [key, value] of Object.entries(fields)) if (value !== undefined) domain[key] = value;
134
169
  const domains: Record<string, Domain> = {};
135
- setOwn(domains, name, domain);
170
+ setOwn(domains, name, domain as unknown as Domain);
136
171
  return { domains, replaces: [["domains", name]] };
137
172
  };
138
173
  }
package/src/reference.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { type Envelope, type Json, type Key, isEnvelope, isRecord, typeName } from "./doc.ts";
1
+ import { type Envelope, type Json, type Key, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
2
2
  import { ask } from "./ladder.ts";
3
3
  import { type AttributeDeclaration, type CapsRule, type Kind, type TreeContract, equationType, lookupKind } from "./world.ts";
4
4
  import { type PartSlot, type Split, partsOf, pathMatches } from "./parts.ts";
5
- import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
5
+ import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, ROOTS, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
6
6
  import { type AttributeRung, STD_EVALUATORS } from "./stdlib.ts";
7
7
  import { type EvalError, type Failed, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
8
8
  import {
@@ -14,15 +14,18 @@ import {
14
14
  type Machine,
15
15
  type Part,
16
16
  type Perform,
17
+ type Read,
17
18
  type RunFn,
18
19
  type RunInput,
19
20
  type RunPart,
20
21
  type Steps,
21
22
  type StopSignal,
22
23
  JOURNAL,
24
+ heldReason,
23
25
  makeContext,
24
26
  permits,
25
27
  placeText,
28
+ reachedOf,
26
29
  waitedReason,
27
30
  } from "./evaluate.ts";
28
31
  import { type FormulaProfile, type OpFn, Held, STD_EXT } from "./formula.ts";
@@ -57,6 +60,12 @@ export interface ReferenceOptions {
57
60
  * each verb by the same rules (VERB.AGREE).
58
61
  */
59
62
  readonly verb?: string;
63
+ /**
64
+ * A list that gets each read of a resolution, in its order (REF.READS). A read has the shape of a read of a plan:
65
+ * the path, what it reached and the mark `free`. The reads of a closure and of an equation of a tree go in the
66
+ * same list. The reference adds no kept reads, because it has no static reads of a closure.
67
+ */
68
+ readonly reads?: Read[];
60
69
  }
61
70
 
62
71
  /** The options of a run of the reference: the options of an evaluation, with the host and a signal (REF.RUN). */
@@ -80,6 +89,13 @@ interface Scope {
80
89
  alls: number;
81
90
  }
82
91
 
92
+ /** The record of the reads of one evaluation of the reference (REF.READS). */
93
+ interface Reading {
94
+ readonly list: Read[];
95
+ /** The context at the root of the evaluation. A free read reads this context. */
96
+ readonly root: Context;
97
+ }
98
+
83
99
  /** The state of one evaluation of the reference. */
84
100
  interface Env {
85
101
  readonly machine: Machine;
@@ -95,6 +111,10 @@ interface Env {
95
111
  readonly verb: string;
96
112
  /** The scope of the code that evaluates at this time. `all` changes it for each item. */
97
113
  scope: Scope;
114
+ /** The count of the drives without a wait that are active. In a run, an effect under one does not reach the host (RUN.HELD). */
115
+ held: number;
116
+ /** The record of the reads. A nested evaluation for an equation of a tree keeps the record of its tree. */
117
+ readonly reading: Reading | undefined;
98
118
  }
99
119
 
100
120
  /** The code that answers a document: a function evaluator, a runner, or a gap of the reference. */
@@ -221,6 +241,44 @@ function resolveFully(machine: Machine, ctx: Context, path: readonly unknown[],
221
241
  return options?.hops === true ? found : { ...found, hops: [] };
222
242
  }
223
243
 
244
+ /** The text that names a resolution in the error of an exception of the host (READS.RECORD). */
245
+ const READ_FAILED = "the resolution of a path";
246
+
247
+ /**
248
+ * This function resolves a path for a document, and records the read when the evaluation has a list (REF.READS,
249
+ * READS.RECORD). An exception of the host gives the error value of the read, and the exception goes on.
250
+ */
251
+ function readAt(env: Env, ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
252
+ const reading = env.reading;
253
+ if (reading === undefined) return resolveFully(env.machine, ctx, path, options);
254
+ const free = freeIn(reading.root, ctx, path);
255
+ let found: Resolution;
256
+ try {
257
+ found = resolveFully(env.machine, ctx, path, options);
258
+ } catch (error) {
259
+ reading.list.push({ path, reached: thrown(error, READ_FAILED), free });
260
+ throw error;
261
+ }
262
+ reading.list.push(options?.hops === true ? { path, reached: reachedOf(found), free, hops: found.hops } : { path, reached: reachedOf(found), free });
263
+ return found;
264
+ }
265
+
266
+ /**
267
+ * This function tells if a read reads the context at the root of the evaluation (READS.FREE). The reference reads
268
+ * the rule from SPEC. A name that a kind binds, a `self` that a kind sets and a path without a root are not free. A
269
+ * name that a kind does not bind keeps its value from the root.
270
+ */
271
+ function freeIn(root: Context, ctx: Context, path: readonly unknown[]): boolean {
272
+ const head = path[0];
273
+ if (head === "self") return Object.is(ctx.self, root.self);
274
+ if (head !== "names") return typeof head === "string" && (ROOTS as readonly string[]).includes(head);
275
+ const name = path[1];
276
+ if (ctx.names === root.names || !isKey(name)) return true;
277
+ const key = String(name);
278
+ const bound = Object.hasOwn(ctx.names, key);
279
+ return bound === Object.hasOwn(root.names, key) && (!bound || Object.is(ctx.names[key], root.names[key]));
280
+ }
281
+
224
282
  /**
225
283
  * This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
226
284
  * address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
@@ -254,6 +312,12 @@ function* causeAndWait(env: Env, ctx: Context, kind: Cap, payload: unknown, plac
254
312
  if ("ok" in effect) return effect;
255
313
  const perform = env.perform;
256
314
  if (perform === undefined) return (effect.outcome = ok(payload));
315
+ if (env.held > 0) {
316
+ // The parent cannot wait for the answer of the host, thus the host does not get the effect (RUN.HELD).
317
+ effect.outcome = fail("#CALC!", heldReason(kind), { place });
318
+ yield Promise.resolve(undefined);
319
+ return effect.outcome;
320
+ }
257
321
  let given: unknown;
258
322
  try {
259
323
  given = perform(effect);
@@ -327,7 +391,16 @@ function evaluateSeen(env: Env, seen: Seen, at: At): Result {
327
391
  const { chosen } = seen;
328
392
  const { place } = at;
329
393
  if ("gap" in chosen) return fail("#NOEVAL", chosen.gap, { place });
330
- if ("runner" in chosen) return drive(runSeen(env, seen, at, chosen.runner), place, seen.kindText);
394
+ if ("runner" in chosen) {
395
+ if (!env.running) return drive(runSeen(env, seen, at, chosen.runner), place, seen.kindText);
396
+ // In a run, an effect under this drive does not reach the host (RUN.HELD).
397
+ env.held += 1;
398
+ try {
399
+ return drive(runSeen(env, seen, at, chosen.runner), place, seen.kindText);
400
+ } finally {
401
+ env.held -= 1;
402
+ }
403
+ }
331
404
  const input = evalInput(env, seen, at);
332
405
  try {
333
406
  return placed(chosen.evaluator(input), place);
@@ -385,7 +458,7 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
385
458
  place: at.place,
386
459
  machine: env.machine,
387
460
  ...(at.address ? { address: true } : {}),
388
- resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
461
+ resolve: (path, options) => readAt(env, at.ctx, path, options),
389
462
  effect: (kind, payload) => causeFromEvaluator(env, at.ctx, kind, payload, at.place),
390
463
  fill,
391
464
  };
@@ -413,7 +486,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
413
486
  context: at.ctx,
414
487
  place: at.place,
415
488
  machine: env.machine,
416
- resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
489
+ resolve: (path, options) => readAt(env, at.ctx, path, options),
417
490
  effect: (kind, payload) => causeAndWait(env, at.ctx, kind, payload, at.place),
418
491
  all: (list, options) => together(env, list, options),
419
492
  fill,
@@ -458,6 +531,7 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
458
531
  outer.alls += 1;
459
532
  let failed = false;
460
533
  let started = 0;
534
+ let before = 0;
461
535
  const end = (index: number, result: Result): void => {
462
536
  results[index] = result;
463
537
  if (failFast && !result.ok) failed = true;
@@ -508,10 +582,11 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
508
582
  };
509
583
  startMore();
510
584
  while (waiting.length > 0 && !failed) {
511
- const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index));
585
+ const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index), before);
512
586
  const pick = chosen === undefined ? undefined : waiting.find((waiter) => waiter.index === chosen);
513
587
  const resumed = (yield pick === undefined ? Promise.race(waiting.map((waiter) => waiter.next)) : pick.next) as Resumed;
514
588
  env.journal?.settled?.(address, resumed.waiter.index);
589
+ before += 1;
515
590
  waiting.splice(waiting.indexOf(resumed.waiter), 1);
516
591
  goOn(resumed.waiter.index, resumed.waiter.steps, resumed);
517
592
  startMore();
@@ -543,9 +618,18 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
543
618
  * @returns The result.
544
619
  */
545
620
  export function reference(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceOptions = {}): Result {
546
- const env = envOf(machine, options, false);
621
+ return evaluateRoot(machine, doc, context, options, undefined);
622
+ }
623
+
624
+ /**
625
+ * This function evaluates a document from its root. An equation of a tree gives the record of its tree. Thus the
626
+ * reads of the equation go in the list of the outer evaluation (REF.READS).
627
+ */
628
+ function evaluateRoot(machine: Machine, doc: unknown, context: Context, options: ReferenceOptions, outer: Reading | undefined): Result {
629
+ const at = rootAt(machine, doc, context, options);
630
+ const env = envOf(machine, options, false, at.ctx, outer);
547
631
  try {
548
- return within(env, () => evaluateAt(env, rootAt(machine, doc, context, options)));
632
+ return within(env, () => evaluateAt(env, at));
549
633
  } catch (error) {
550
634
  return placed(thrown(error, "the reference"), []);
551
635
  }
@@ -567,13 +651,17 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
567
651
  * @returns The result, when the run ends.
568
652
  */
569
653
  export function referenceRun(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceRunOptions = {}): Promise<Result> {
570
- const env = envOf(machine, options, true);
571
- const steps = stepsAt(env, rootAt(machine, doc, context, options));
654
+ const at = rootAt(machine, doc, context, options);
655
+ const env = envOf(machine, options, true, at.ctx, undefined);
656
+ const steps = stepsAt(env, at);
572
657
  return driveRun(env, steps, options.signal).catch((error: unknown) => placed(thrown(error, "the reference"), []));
573
658
  }
574
659
 
575
- /** This function makes the state of the reference for one evaluation or one run. */
576
- function envOf(machine: Machine, options: ReferenceOptions, running: boolean): Env {
660
+ /**
661
+ * This function makes the state of the reference for one evaluation or one run. The record of the reads comes from
662
+ * the evaluation around it, or from the list of the options.
663
+ */
664
+ function envOf(machine: Machine, options: ReferenceOptions, running: boolean, root: Context, outer: Reading | undefined): Env {
577
665
  const perform = options.perform;
578
666
  return {
579
667
  machine,
@@ -585,6 +673,8 @@ function envOf(machine: Machine, options: ReferenceOptions, running: boolean): E
585
673
  running,
586
674
  verb: options.verb ?? "eval",
587
675
  scope: { prefix: "", counts: new Map(), alls: 0 },
676
+ held: 0,
677
+ reading: outer ?? (options.reads === undefined ? undefined : { list: options.reads, root }),
588
678
  };
589
679
  }
590
680
 
@@ -698,13 +788,14 @@ function refNodeOf(thing: unknown): RefNode | undefined {
698
788
 
699
789
  /**
700
790
  * TREE.LADDER: an equation of the kind of a node evaluates in the reference. It gets the node as `self`, the names of
701
- * its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind.
791
+ * its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind. The reads of the
792
+ * equation go in the record of the evaluation that made the node (REF.READS).
702
793
  */
703
794
  function equationOf(node: RefNode, term: Json, names: Readonly<Record<string, unknown>>): Result {
704
795
  const env = node.env;
705
796
  if (env === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
706
797
  const ctx: Context = { ...node.ctx, names, caps: [], self: node.thing };
707
- return reference(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) });
798
+ return evaluateRoot(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) }, env.reading);
708
799
  }
709
800
 
710
801
  function termIn<T>(table: Readonly<Record<string, T>> | undefined, name: string): T | undefined {
@@ -960,7 +1051,11 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
960
1051
  // FORMULA.LET: a path through a name whose binding failed gives the error of the binding.
961
1052
  const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : undefined;
962
1053
  const bound = head === undefined ? undefined : own(context.names, head);
963
- if (bound instanceof Held) return { ok: false, error: bound.error };
1054
+ if (bound instanceof Held) {
1055
+ // The result comes from the binding of the name, thus the name is a read (READS.RECORD).
1056
+ walk(["names", head]);
1057
+ return { ok: false, error: bound.error };
1058
+ }
964
1059
  const found = walk(steps);
965
1060
  if (!found.ok) return fail(found.code, found.reason);
966
1061
  // FORMULA.ADDRESS: an address gives what the path reaches.