@mark1russell7/coeval 0.15.0 → 0.17.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/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,6 +14,7 @@ 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,
@@ -23,6 +24,7 @@ import {
23
24
  makeContext,
24
25
  permits,
25
26
  placeText,
27
+ reachedOf,
26
28
  waitedReason,
27
29
  } from "./evaluate.ts";
28
30
  import { type FormulaProfile, type OpFn, Held, STD_EXT } from "./formula.ts";
@@ -57,6 +59,12 @@ export interface ReferenceOptions {
57
59
  * each verb by the same rules (VERB.AGREE).
58
60
  */
59
61
  readonly verb?: string;
62
+ /**
63
+ * 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:
64
+ * the path, what it reached and the mark `free`. The reads of a closure and of an equation of a tree go in the
65
+ * same list. The reference adds no kept reads, because it has no static reads of a closure.
66
+ */
67
+ readonly reads?: Read[];
60
68
  }
61
69
 
62
70
  /** The options of a run of the reference: the options of an evaluation, with the host and a signal (REF.RUN). */
@@ -80,6 +88,13 @@ interface Scope {
80
88
  alls: number;
81
89
  }
82
90
 
91
+ /** The record of the reads of one evaluation of the reference (REF.READS). */
92
+ interface Reading {
93
+ readonly list: Read[];
94
+ /** The context at the root of the evaluation. A free read reads this context. */
95
+ readonly root: Context;
96
+ }
97
+
83
98
  /** The state of one evaluation of the reference. */
84
99
  interface Env {
85
100
  readonly machine: Machine;
@@ -95,6 +110,8 @@ interface Env {
95
110
  readonly verb: string;
96
111
  /** The scope of the code that evaluates at this time. `all` changes it for each item. */
97
112
  scope: Scope;
113
+ /** The record of the reads. A nested evaluation for an equation of a tree keeps the record of its tree. */
114
+ readonly reading: Reading | undefined;
98
115
  }
99
116
 
100
117
  /** The code that answers a document: a function evaluator, a runner, or a gap of the reference. */
@@ -221,6 +238,44 @@ function resolveFully(machine: Machine, ctx: Context, path: readonly unknown[],
221
238
  return options?.hops === true ? found : { ...found, hops: [] };
222
239
  }
223
240
 
241
+ /** The text that names a resolution in the error of an exception of the host (READS.RECORD). */
242
+ const READ_FAILED = "the resolution of a path";
243
+
244
+ /**
245
+ * This function resolves a path for a document, and records the read when the evaluation has a list (REF.READS,
246
+ * READS.RECORD). An exception of the host gives the error value of the read, and the exception goes on.
247
+ */
248
+ function readAt(env: Env, ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
249
+ const reading = env.reading;
250
+ if (reading === undefined) return resolveFully(env.machine, ctx, path, options);
251
+ const free = freeIn(reading.root, ctx, path);
252
+ let found: Resolution;
253
+ try {
254
+ found = resolveFully(env.machine, ctx, path, options);
255
+ } catch (error) {
256
+ reading.list.push({ path, reached: thrown(error, READ_FAILED), free });
257
+ throw error;
258
+ }
259
+ reading.list.push(options?.hops === true ? { path, reached: reachedOf(found), free, hops: found.hops } : { path, reached: reachedOf(found), free });
260
+ return found;
261
+ }
262
+
263
+ /**
264
+ * This function tells if a read reads the context at the root of the evaluation (READS.FREE). The reference reads
265
+ * 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
266
+ * name that a kind does not bind keeps its value from the root.
267
+ */
268
+ function freeIn(root: Context, ctx: Context, path: readonly unknown[]): boolean {
269
+ const head = path[0];
270
+ if (head === "self") return Object.is(ctx.self, root.self);
271
+ if (head !== "names") return typeof head === "string" && (ROOTS as readonly string[]).includes(head);
272
+ const name = path[1];
273
+ if (ctx.names === root.names || !isKey(name)) return true;
274
+ const key = String(name);
275
+ const bound = Object.hasOwn(ctx.names, key);
276
+ return bound === Object.hasOwn(root.names, key) && (!bound || Object.is(ctx.names[key], root.names[key]));
277
+ }
278
+
224
279
  /**
225
280
  * This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
226
281
  * address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
@@ -385,7 +440,7 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
385
440
  place: at.place,
386
441
  machine: env.machine,
387
442
  ...(at.address ? { address: true } : {}),
388
- resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
443
+ resolve: (path, options) => readAt(env, at.ctx, path, options),
389
444
  effect: (kind, payload) => causeFromEvaluator(env, at.ctx, kind, payload, at.place),
390
445
  fill,
391
446
  };
@@ -413,7 +468,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
413
468
  context: at.ctx,
414
469
  place: at.place,
415
470
  machine: env.machine,
416
- resolve: (path, options) => resolveFully(env.machine, at.ctx, path, options),
471
+ resolve: (path, options) => readAt(env, at.ctx, path, options),
417
472
  effect: (kind, payload) => causeAndWait(env, at.ctx, kind, payload, at.place),
418
473
  all: (list, options) => together(env, list, options),
419
474
  fill,
@@ -458,6 +513,7 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
458
513
  outer.alls += 1;
459
514
  let failed = false;
460
515
  let started = 0;
516
+ let before = 0;
461
517
  const end = (index: number, result: Result): void => {
462
518
  results[index] = result;
463
519
  if (failFast && !result.ok) failed = true;
@@ -508,10 +564,11 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
508
564
  };
509
565
  startMore();
510
566
  while (waiting.length > 0 && !failed) {
511
- const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index));
567
+ const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index), before);
512
568
  const pick = chosen === undefined ? undefined : waiting.find((waiter) => waiter.index === chosen);
513
569
  const resumed = (yield pick === undefined ? Promise.race(waiting.map((waiter) => waiter.next)) : pick.next) as Resumed;
514
570
  env.journal?.settled?.(address, resumed.waiter.index);
571
+ before += 1;
515
572
  waiting.splice(waiting.indexOf(resumed.waiter), 1);
516
573
  goOn(resumed.waiter.index, resumed.waiter.steps, resumed);
517
574
  startMore();
@@ -543,9 +600,18 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
543
600
  * @returns The result.
544
601
  */
545
602
  export function reference(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceOptions = {}): Result {
546
- const env = envOf(machine, options, false);
603
+ return evaluateRoot(machine, doc, context, options, undefined);
604
+ }
605
+
606
+ /**
607
+ * This function evaluates a document from its root. An equation of a tree gives the record of its tree. Thus the
608
+ * reads of the equation go in the list of the outer evaluation (REF.READS).
609
+ */
610
+ function evaluateRoot(machine: Machine, doc: unknown, context: Context, options: ReferenceOptions, outer: Reading | undefined): Result {
611
+ const at = rootAt(machine, doc, context, options);
612
+ const env = envOf(machine, options, false, at.ctx, outer);
547
613
  try {
548
- return within(env, () => evaluateAt(env, rootAt(machine, doc, context, options)));
614
+ return within(env, () => evaluateAt(env, at));
549
615
  } catch (error) {
550
616
  return placed(thrown(error, "the reference"), []);
551
617
  }
@@ -567,13 +633,17 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
567
633
  * @returns The result, when the run ends.
568
634
  */
569
635
  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));
636
+ const at = rootAt(machine, doc, context, options);
637
+ const env = envOf(machine, options, true, at.ctx, undefined);
638
+ const steps = stepsAt(env, at);
572
639
  return driveRun(env, steps, options.signal).catch((error: unknown) => placed(thrown(error, "the reference"), []));
573
640
  }
574
641
 
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 {
642
+ /**
643
+ * This function makes the state of the reference for one evaluation or one run. The record of the reads comes from
644
+ * the evaluation around it, or from the list of the options.
645
+ */
646
+ function envOf(machine: Machine, options: ReferenceOptions, running: boolean, root: Context, outer: Reading | undefined): Env {
577
647
  const perform = options.perform;
578
648
  return {
579
649
  machine,
@@ -585,6 +655,7 @@ function envOf(machine: Machine, options: ReferenceOptions, running: boolean): E
585
655
  running,
586
656
  verb: options.verb ?? "eval",
587
657
  scope: { prefix: "", counts: new Map(), alls: 0 },
658
+ reading: outer ?? (options.reads === undefined ? undefined : { list: options.reads, root }),
588
659
  };
589
660
  }
590
661
 
@@ -698,13 +769,14 @@ function refNodeOf(thing: unknown): RefNode | undefined {
698
769
 
699
770
  /**
700
771
  * 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.
772
+ * its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind. The reads of the
773
+ * equation go in the record of the evaluation that made the node (REF.READS).
702
774
  */
703
775
  function equationOf(node: RefNode, term: Json, names: Readonly<Record<string, unknown>>): Result {
704
776
  const env = node.env;
705
777
  if (env === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
706
778
  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 }) });
779
+ return evaluateRoot(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) }, env.reading);
708
780
  }
709
781
 
710
782
  function termIn<T>(table: Readonly<Record<string, T>> | undefined, name: string): T | undefined {
@@ -960,7 +1032,11 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
960
1032
  // FORMULA.LET: a path through a name whose binding failed gives the error of the binding.
961
1033
  const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : undefined;
962
1034
  const bound = head === undefined ? undefined : own(context.names, head);
963
- if (bound instanceof Held) return { ok: false, error: bound.error };
1035
+ if (bound instanceof Held) {
1036
+ // The result comes from the binding of the name, thus the name is a read (READS.RECORD).
1037
+ walk(["names", head]);
1038
+ return { ok: false, error: bound.error };
1039
+ }
964
1040
  const found = walk(steps);
965
1041
  if (!found.ok) return fail(found.code, found.reason);
966
1042
  // FORMULA.ADDRESS: an address gives what the path reaches.