@mark1russell7/coeval 0.16.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/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,53 @@ 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 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
+ covers: same(a.covers ?? [], b.covers ?? []),
106
+ };
107
+ return Object.values(fields).every(Boolean);
96
108
  }
97
109
 
98
- /** What `extendDomain` adds to a domain: more parents, and the types of more steps. */
110
+ /** What `extendDomain` adds to a domain: more parents, the types of more steps, and more covers. */
99
111
  export interface DomainExtension {
100
112
  /** The names of the parents to add. A member of the domain then also passes the test of each new parent. */
101
113
  readonly under?: readonly string[];
102
114
  /** The types of the steps to add. A step with the key of an earlier step replaces its type. */
103
115
  readonly steps?: Readonly<Record<string, ValueType>>;
116
+ /**
117
+ * The covers to add (TYPE.FITS-COVER, D85). A cover that the domain has already does not change. The language that
118
+ * adds a cover keeps it true, because `fits` trusts it.
119
+ */
120
+ readonly covers?: readonly (readonly string[])[];
104
121
  }
105
122
 
106
123
  /**
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`.
124
+ * This function makes a part of a merge that adds parents, steps or covers to a domain of an earlier part
125
+ * (TYPE.PARENTS). An example is a domain of another language. The part reads the domain from the merge before it
126
+ * (`prev`). It gives a copy with the same test, summary, codec and list of values, and it names `["domains", name]`
127
+ * in `replaces` (D17).
128
+ * A domain of the kernel that the earlier parts do not name comes from `STD_DOMAINS`.
129
+ *
130
+ * An extension does not change the codec. Thus each member keeps its document (D80).
111
131
  *
112
132
  * A new parent adds its test to the domain (TYPE.MEMBER). Thus `fits` stays sound, but a value that fails the test of
113
133
  * 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.
134
+ * true. A new parent of a domain in a cover of another domain can make that cover false (D85). A name without a
135
+ * domain gives an empty part, and `typeIssues` reports each use of that name.
115
136
  *
116
137
  * @param name - The name of the domain.
117
- * @param more - The parents and the steps to add.
138
+ * @param more - The parents, the steps and the covers to add.
118
139
  * @returns The part, as a function of the merge before it.
119
140
  */
120
141
  export function extendDomain(name: string, more: DomainExtension): (prev: Machine) => MachinePart {
@@ -125,14 +146,25 @@ export function extendDomain(name: string, more: DomainExtension): (prev: Machin
125
146
  const under = [...(found.under ?? [])];
126
147
  for (const parent of more.under ?? []) if (parent !== name && !under.includes(parent)) under.push(parent);
127
148
  const steps = { ...found.steps, ...more.steps };
128
- const domain: Domain = {
149
+ const covers = [...(found.covers ?? [])];
150
+ for (const cover of more.covers ?? []) if (!covers.some((each) => same(each, cover))) covers.push([...cover]);
151
+ // The type names each field of Domain, thus a new field fails the check of types until the copy reads it (K180).
152
+ const fields: { readonly [K in keyof Domain]-?: Domain[K] | undefined } = {
129
153
  is: found.is,
130
- ...(under.length === 0 ? {} : { under }),
131
- ...(found.summary === undefined ? {} : { summary: found.summary }),
132
- ...(Object.keys(steps).length === 0 ? {} : { steps }),
154
+ under: under.length === 0 ? undefined : under,
155
+ summary: found.summary,
156
+ steps: Object.keys(steps).length === 0 ? undefined : steps,
157
+ // A new parent only adds a test, thus each member keeps its document (D80).
158
+ codec: found.codec,
159
+ // A new parent only adds a test, thus the list of values stays complete (TYPE.FITS-FINITE).
160
+ values: found.values,
161
+ // A new parent only adds a test, thus each cover stays true (TYPE.FITS-COVER). A new cover is a claim of the extension.
162
+ covers: covers.length === 0 ? undefined : covers,
133
163
  };
164
+ const domain: Record<string, unknown> = {};
165
+ for (const [key, value] of Object.entries(fields)) if (value !== undefined) domain[key] = value;
134
166
  const domains: Record<string, Domain> = {};
135
- setOwn(domains, name, domain);
167
+ setOwn(domains, name, domain as unknown as Domain);
136
168
  return { domains, replaces: [["domains", name]] };
137
169
  };
138
170
  }
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.