@mark1russell7/coeval 0.21.0 → 0.23.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/deref.ts CHANGED
@@ -58,6 +58,15 @@ export interface HostThing {
58
58
  * @returns What the step reaches, or `undefined` when the host does not know it yet. Then the kept read ends at the thing.
59
59
  */
60
60
  known?(key: Key): { readonly thing: Thing } | undefined;
61
+ /**
62
+ * This optional function holds a kept read (SCHED.OWN-NODE). The evaluation did not ask for that read. Thus a host
63
+ * that follows the reads of an evaluation can tell the two apart. The kernel calls this function of the root `world`
64
+ * around each kept read that resolves its path.
65
+ *
66
+ * @param read - This function does the read.
67
+ * @returns What `read` gives.
68
+ */
69
+ keeping?<T>(read: () => T): T;
61
70
  }
62
71
 
63
72
  /** Something that a path can reach: any value of the host, or a host thing. */
@@ -522,7 +531,8 @@ function walkQuick(start: unknown, path: readonly unknown[], plain: boolean, key
522
531
  /**
523
532
  * A compiled path: one static path for one navigator (PLAN.PATH). `compilePath` reads the root, the open roots and
524
533
  * the steps one time. It asks the ladder of the verb `step` for each step document one time. Each resolution then
525
- * examines only the values of the context.
534
+ * examines only the values of the context. Code of a language reads a compiled path with `resolveCompiled`, thus the
535
+ * list of reads gets the read (PLAN.PATH-READS).
526
536
  */
527
537
  export interface CompiledPath {
528
538
  /** The steps of the path, as the document gives them. The list of reads records these steps. */
@@ -530,7 +540,8 @@ export interface CompiledPath {
530
540
  /** The navigator of the compilation. A runtime with another navigator resolves the steps again. */
531
541
  readonly nav: Navigator;
532
542
  /**
533
- * This function resolves the path from a context, without a record of hops.
543
+ * This function resolves the path from a context, without a record of hops. It records no read and it does not
544
+ * apply the grant of the evaluation, as `resolve` does not. Use it only outside an evaluation.
534
545
  *
535
546
  * @param ctx - The context.
536
547
  * @returns The same resolution as `resolve(nav, ctx, path, { hops: false })`.
@@ -592,8 +603,11 @@ class StaticPath implements CompiledPath {
592
603
  * - The walker of each step document comes from the ladder one time, here. The navigator and the step document do
593
604
  * not change, thus the answer does not change either.
594
605
  *
606
+ * An evaluator of a language calls this function in its `compile`, with the field `machine` of the input, and reads the
607
+ * result with `resolveCompiled` (PLAN.PATH-EXPORT).
608
+ *
595
609
  * @param nav - The navigator. A plan compiles its paths for its machine.
596
- * @param path - The steps. The first step is a root.
610
+ * @param path - The steps. The first step is a root. The compiled path keeps this list, thus the host must not change it.
597
611
  * @returns The compiled path.
598
612
  */
599
613
  export function compilePath(nav: Navigator, path: readonly unknown[]): CompiledPath {
package/src/durable.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Context } from "./deref.ts";
2
- import type { Result } from "./errors.ts";
2
+ import { type Result, overflowCount } from "./errors.ts";
3
3
  import { type Machine, type Plan, type PrepareOptions, type Read, prepare } from "./evaluate.ts";
4
4
  import { type DocFacts, type FactsOptions, factsOf } from "./facts.ts";
5
5
  import { type PrintOptions, type ReadPrints, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
@@ -126,20 +126,24 @@ const NO_READS: readonly Read[] = /* @__PURE__ */ Object.freeze([]);
126
126
  /** The key of the document `-0` in a cache of value documents. A `Map` finds the entry of `0` for `-0`. */
127
127
  const NEGATIVE_ZERO = /* @__PURE__ */ Symbol("coeval.negative-zero");
128
128
 
129
- /** This function makes a cache by document. A document object gives its value one time, as PLAN.CACHE says. */
130
- function byDocument<T>(make: (doc: unknown) => T): (doc: unknown) => T {
129
+ /**
130
+ * This function makes a cache by document. A document object gives its value one time, as PLAN.CACHE says. A value
131
+ * that `lasts` refuses stays out of the cache, and the next call makes it again.
132
+ */
133
+ function byDocument<T>(make: (doc: unknown) => T, lasts?: () => boolean): (doc: unknown) => T {
131
134
  const objects = new WeakMap<object, T>();
132
135
  const values = new Map<unknown, T>();
133
136
  return (doc) => {
134
137
  if (typeof doc === "object" && doc !== null) {
135
138
  if (objects.has(doc)) return objects.get(doc) as T;
136
139
  const made = make(doc);
137
- objects.set(doc, made);
140
+ if (lasts === undefined || lasts()) objects.set(doc, made);
138
141
  return made;
139
142
  }
140
143
  const key = Object.is(doc, -0) ? NEGATIVE_ZERO : doc;
141
144
  if (values.has(key)) return values.get(key) as T;
142
145
  const made = make(doc);
146
+ if (lasts !== undefined && !lasts()) return made;
143
147
  if (values.size >= MAX_VALUES) values.clear();
144
148
  values.set(key, made);
145
149
  return made;
@@ -180,11 +184,19 @@ export function durable(machine: Machine, options: DurableOptions = {}): Durable
180
184
  const results = new Map<unknown, Entry>();
181
185
  let current = machine;
182
186
  let epochs: Epochs = { structure: 0, data: 0 };
183
- const plansOf = (m: Machine): ((doc: unknown) => Plan) =>
184
- byDocument((doc) => {
185
- counts.compiles += 1;
186
- return prepare(m, doc, prepareOptions);
187
- });
187
+ const plansOf = (m: Machine): ((doc: unknown) => Plan) => {
188
+ let before = 0;
189
+ // A compilation near the limit of the stack gives a plan that fails. That failure depends on the depth of the
190
+ // demand, not on the document, thus such a plan stays out of the cache (SCHED.DEEP-CYCLE-CHANGE).
191
+ return byDocument(
192
+ (doc) => {
193
+ counts.compiles += 1;
194
+ before = overflowCount();
195
+ return prepare(m, doc, prepareOptions);
196
+ },
197
+ () => overflowCount() === before,
198
+ );
199
+ };
188
200
  const factsFor = (m: Machine): ((doc: unknown) => DocFacts) =>
189
201
  byDocument((doc) => {
190
202
  counts.facts += 1;
package/src/errors.ts CHANGED
@@ -116,6 +116,19 @@ export function isStackOverflow(error: unknown): boolean {
116
116
  }
117
117
  }
118
118
 
119
+ /** The count of the stack overflows that `thrown` changed into `#DEPTH!`. */
120
+ let overflows = 0;
121
+
122
+ /**
123
+ * This function gives the count of the stack overflows that `thrown` changed into `#DEPTH!`. A schedule compares two
124
+ * counts. Thus it finds an overflow also when a document catches the error (SCHED.DEEP-CYCLE).
125
+ *
126
+ * @returns The count since the start of the process.
127
+ */
128
+ export function overflowCount(): number {
129
+ return overflows;
130
+ }
131
+
119
132
  /**
120
133
  * This function changes an exception into an error value. A `Failure` gives its error value. A stack overflow
121
134
  * gives `#DEPTH!`. Each other exception gives `#CALC!`, with the exception as the cause.
@@ -126,7 +139,10 @@ export function isStackOverflow(error: unknown): boolean {
126
139
  */
127
140
  export function thrown(error: unknown, who: string): Failed {
128
141
  if (error instanceof Failure) return { ok: false, error: error.error };
129
- if (isStackOverflow(error)) return fail("#DEPTH!", `The nesting of ${who} is too deep for the stack of the host.`, { cause: error });
142
+ if (isStackOverflow(error)) {
143
+ overflows += 1;
144
+ return fail("#DEPTH!", `The nesting of ${who} is too deep for the stack of the host.`, { cause: error });
145
+ }
130
146
  return fail("#CALC!", `${upper(who)} threw: ${describe(error)}`, { cause: error });
131
147
  }
132
148
 
package/src/evaluate.ts CHANGED
@@ -1004,9 +1004,24 @@ function keptPath(nav: Navigator, root: Context, path: readonly unknown[]): Kept
1004
1004
  }
1005
1005
  }
1006
1006
 
1007
+ /**
1008
+ * This function resolves the path of a kept read. A reference on the path can reach a node of a schedule, thus the
1009
+ * resolution goes through `keeping` of the root `world` (SCHED.OWN-NODE).
1010
+ *
1011
+ * @param nav - The world and the code of the evaluators.
1012
+ * @param ctx - The context.
1013
+ * @param path - The path of the kept read.
1014
+ * @returns What the path reaches.
1015
+ */
1016
+ function readKept(nav: Navigator, ctx: Context, path: readonly unknown[]): Result {
1017
+ const read = (): Result => readAgain(nav, ctx, path, false).reached;
1018
+ const world = ctx.world;
1019
+ return isHostThing(world) && world.keeping !== undefined ? world.keeping(read) : read();
1020
+ }
1021
+
1007
1022
  /**
1008
1023
  * This function resolves the path of a kept read again, for `verifyReads` (SCHED.CLOSURE). It walks the path as
1009
- * `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `readAgain`.
1024
+ * `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `readKept`.
1010
1025
  *
1011
1026
  * @param nav - The world and the code of the evaluators.
1012
1027
  * @param ctx - The new context.
@@ -1018,7 +1033,7 @@ export function keptAgain(nav: Navigator, ctx: Context, path: readonly unknown[]
1018
1033
  let here = adopt(ctx[path[0] as Root]);
1019
1034
  let known = false;
1020
1035
  // Before a step of `known`, each other answer of the walk is the answer of `readAgain`, as before SCHED.CLOSURE.
1021
- const other = (): Result | undefined => (known ? undefined : readAgain(nav, ctx, path, false).reached);
1036
+ const other = (): Result | undefined => (known ? undefined : readKept(nav, ctx, path));
1022
1037
  for (let index = 1; index < path.length; index += 1) {
1023
1038
  if (isHostThing(here)) {
1024
1039
  if (here.known === undefined) return other();
@@ -1049,6 +1064,22 @@ function readingOf(list: Read[] | undefined, root: Context): Reading | undefined
1049
1064
 
1050
1065
  /** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
1051
1066
  class Session implements Runtime {
1067
+ readonly machine: Machine;
1068
+ /**
1069
+ * The epoch of the evaluation: the list of capabilities that the host gave the root (CAP.NARROW-KEPT). A context
1070
+ * keeps its grant only in an evaluation of the same epoch.
1071
+ */
1072
+ readonly epoch: readonly Cap[];
1073
+ readonly running: boolean;
1074
+ readonly traced: boolean;
1075
+ readonly perform: Perform | undefined;
1076
+ readonly signal: StopSignal | undefined;
1077
+ /**
1078
+ * The record of the reads. Without it, a resolution records nothing. A nested evaluation of an equation of a tree
1079
+ * shares the record of the evaluation around it (READS.EQUATION).
1080
+ */
1081
+ readonly reading: Reading | undefined;
1082
+
1052
1083
  /** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
1053
1084
  private list: Effect[] | undefined;
1054
1085
  tick = 0;
@@ -1067,23 +1098,22 @@ class Session implements Runtime {
1067
1098
  private scope: Scope | undefined;
1068
1099
 
1069
1100
  constructor(
1070
- readonly machine: Machine,
1071
- /**
1072
- * The epoch of the evaluation: the list of capabilities that the host gave the root (CAP.NARROW-KEPT). A context
1073
- * keeps its grant only in an evaluation of the same epoch.
1074
- */
1075
- readonly epoch: readonly Cap[],
1076
- readonly running: boolean,
1077
- readonly traced: boolean,
1078
- readonly perform: Perform | undefined,
1079
- readonly signal: StopSignal | undefined,
1101
+ machine: Machine,
1102
+ epoch: readonly Cap[],
1103
+ running: boolean,
1104
+ traced: boolean,
1105
+ perform: Perform | undefined,
1106
+ signal: StopSignal | undefined,
1080
1107
  effects: Effect[] | undefined,
1081
- /**
1082
- * The record of the reads. Without it, a resolution records nothing. A nested evaluation of an equation of a tree
1083
- * shares the record of the evaluation around it (READS.EQUATION).
1084
- */
1085
- readonly reading: Reading | undefined = undefined,
1108
+ reading: Reading | undefined = undefined,
1086
1109
  ) {
1110
+ this.machine = machine;
1111
+ this.epoch = epoch;
1112
+ this.running = running;
1113
+ this.traced = traced;
1114
+ this.perform = perform;
1115
+ this.signal = signal;
1116
+ this.reading = reading;
1087
1117
  this.list = effects;
1088
1118
  this.holder = traced ? holderNode() : undefined;
1089
1119
  this.current = this.holder;
@@ -1185,7 +1215,7 @@ class Session implements Runtime {
1185
1215
  if (paths.has(text) || paths.has(JSON.stringify([root]))) return true;
1186
1216
  paths.add(text);
1187
1217
  if (path.length === 1 && root === "self") reading.self = true;
1188
- reading.list.push({ path, reached: walk.reached ?? readAgain(this.machine, reading.root, path, false).reached, free: true, kept: true });
1218
+ reading.list.push({ path, reached: walk.reached ?? readKept(this.machine, reading.root, path), free: true, kept: true });
1189
1219
  return true;
1190
1220
  }
1191
1221
 
@@ -1197,7 +1227,7 @@ class Session implements Runtime {
1197
1227
  for (const root of roots) {
1198
1228
  if (root === "self") reading.self = true;
1199
1229
  const path = [root];
1200
- reading.list.push({ path, reached: readAgain(this.machine, reading.root, path, false).reached, free: true, kept: true });
1230
+ reading.list.push({ path, reached: readKept(this.machine, reading.root, path), free: true, kept: true });
1201
1231
  }
1202
1232
  }
1203
1233
 
@@ -1422,7 +1452,9 @@ class Session implements Runtime {
1422
1452
 
1423
1453
  /**
1424
1454
  * This function resolves a compiled path through a runtime (PLAN.PATH). The runtime of the kernel uses the compiled
1425
- * path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path.
1455
+ * path. Each other runtime gets the steps through `resolve`, thus a runtime that wraps another runtime sees each path
1456
+ * (PLAN.PATH-ROUTE). The result and the list of reads are the same as for `rt.resolve(ctx, compiled.path)`
1457
+ * (PLAN.PATH-READS).
1426
1458
  *
1427
1459
  * @param rt - The runtime.
1428
1460
  * @param ctx - The context.
@@ -1831,6 +1863,7 @@ function freeOf(doc: Envelope, kind: Kind, slots: readonly PartSlot[], frees: re
1831
1863
 
1832
1864
  /** This class is the runtime of a fold. It reads only the names that the folded part binds, and it causes no effect. */
1833
1865
  class Probe implements Runtime {
1866
+ readonly machine: Machine;
1834
1867
  readonly running = false;
1835
1868
  /** `true` after the part asked for something that a fold cannot give. */
1836
1869
  refused = false;
@@ -1842,7 +1875,8 @@ class Probe implements Runtime {
1842
1875
  */
1843
1876
  readonly root: Context;
1844
1877
 
1845
- constructor(readonly machine: Machine) {
1878
+ constructor(machine: Machine) {
1879
+ this.machine = machine;
1846
1880
  const refuse = (): void => {
1847
1881
  this.refused = true;
1848
1882
  };
package/src/formula.ts CHANGED
@@ -97,7 +97,11 @@ const SKIPPED: unique symbol = /* @__PURE__ */ Symbol("skipped");
97
97
 
98
98
  /** The value of a name whose binding failed. A reference to the name gives the error. */
99
99
  export class Held {
100
- constructor(readonly error: EvalError) {}
100
+ readonly error: EvalError;
101
+
102
+ constructor(error: EvalError) {
103
+ this.error = error;
104
+ }
101
105
  }
102
106
 
103
107
  const CLOSURE: unique symbol = /* @__PURE__ */ Symbol.for("coeval.closure") as never;
@@ -115,6 +119,12 @@ export function isClosure(value: unknown): value is Closure {
115
119
  return typeof value === "function" && (value as Partial<Closure>)[CLOSURE] !== undefined;
116
120
  }
117
121
 
122
+ /**
123
+ * The runtime of an evaluation with the clock of `call` (FORMULA.CALL-DEPTH, D125): the count of the applications of
124
+ * closures that run at this time. The first application adds the field, thus an evaluation without one has no field.
125
+ */
126
+ type Clocked = Runtime & { applying?: number };
127
+
118
128
  /**
119
129
  * The standard extensions: the values that JSON cannot write. `ext.domain` reads a member of a domain through the
120
130
  * codec of the domain (EXT.DOMAIN, D40).
@@ -236,6 +246,36 @@ function isSignature(value: unknown): value is Signature {
236
246
  return typeof value === "object" && value !== null && Array.isArray((value as { readonly params?: unknown }).params) && Object.hasOwn(value, "to");
237
247
  }
238
248
 
249
+ /** This function gives the signatures of an operation as the profile holds them, or `undefined` for no entry. */
250
+ function rawSignatures(profile: FormulaProfile, name: string): readonly unknown[] | undefined {
251
+ const sigs = profile.sigs;
252
+ if (sigs === undefined) return undefined;
253
+ let found: unknown;
254
+ if (typeof sigs === "function") {
255
+ try {
256
+ found = sigs(name);
257
+ } catch {
258
+ return undefined;
259
+ }
260
+ } else {
261
+ found = Object.hasOwn(sigs, name) ? sigs[name] : undefined;
262
+ }
263
+ if (found === undefined) return undefined;
264
+ return Array.isArray(found) ? found : [found];
265
+ }
266
+
267
+ /**
268
+ * This function gives each signature of an operation that has the form of a signature, for `conforms` (D137). The
269
+ * kernel does not export the function.
270
+ *
271
+ * @param profile - The profile.
272
+ * @param name - The name of the operation.
273
+ * @returns The signatures.
274
+ */
275
+ export function signaturesOf(profile: FormulaProfile, name: string): readonly Signature[] {
276
+ return (rawSignatures(profile, name) ?? []).filter(isSignature);
277
+ }
278
+
239
279
  /** This function tells if two signatures have the same parameters and the same rest parameter. */
240
280
  function sameParams(a: Signature, b: Signature): boolean {
241
281
  if (!docEquals(a.params, b.params)) return false;
@@ -323,22 +363,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
323
363
  };
324
364
  const logicValue = profile.logic === "value";
325
365
  const otherwise = Object.hasOwn(profile, "otherwise") ? valueType(profile.otherwise) : "null";
326
- const lookup = (name: string): readonly unknown[] | undefined => {
327
- const sigs = profile.sigs;
328
- if (sigs === undefined) return undefined;
329
- let found: unknown;
330
- if (typeof sigs === "function") {
331
- try {
332
- found = sigs(name);
333
- } catch {
334
- return undefined;
335
- }
336
- } else {
337
- found = Object.hasOwn(sigs, name) ? sigs[name] : undefined;
338
- }
339
- if (found === undefined) return undefined;
340
- return Array.isArray(found) ? found : [found];
341
- };
366
+ const lookup = (name: string): readonly unknown[] | undefined => rawSignatures(profile, name);
342
367
  const nameOf = (input: TypeInput): string => input.typePath.slice(1).join(".");
343
368
  /** The signatures of an operation. Each type of a signature is a contract, thus the check examines it one time. */
344
369
  const signatures = (input: TypeInput): { readonly sigs: readonly Signature[]; readonly bad: boolean } | undefined => {
@@ -638,6 +663,29 @@ export function declaredTruthOf(code: unknown): DeclaredTruth | undefined {
638
663
  return (typeof code === "object" || typeof code === "function") && code !== null ? truthByCode.get(code) : undefined;
639
664
  }
640
665
 
666
+ /** An evaluator of the kit that reads functions of a profile: its name, and the fields of the profile (D137). */
667
+ export interface ProfileReader {
668
+ readonly name: string;
669
+ readonly profile: FormulaProfile;
670
+ }
671
+
672
+ /** The evaluators of the kit that `conforms` examines the functions of a profile with. */
673
+ const PROFILE_READERS: readonly string[] = ["formula.op", "formula.ext", "formula.record", "formula.ref"];
674
+
675
+ /** The profile of each evaluator of the kit that reads its functions, for `conforms` (D137). */
676
+ const readerByCode = /* @__PURE__ */ new WeakMap<object, ProfileReader>();
677
+
678
+ /**
679
+ * This function gives the profile of an evaluator of the kit, when that evaluator reads functions of the profile
680
+ * (D137). The kernel does not export the function.
681
+ *
682
+ * @param code - The evaluator or the runner.
683
+ * @returns The name of the evaluator and the profile, or `undefined`.
684
+ */
685
+ export function profileReaderOf(code: unknown): ProfileReader | undefined {
686
+ return (typeof code === "object" || typeof code === "function") && code !== null ? readerByCode.get(code) : undefined;
687
+ }
688
+
641
689
  /** This function makes new evaluators of the formula kit for the fields of one profile. */
642
690
  function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evaluator>> {
643
691
  const evaluators = formulaCode(profile);
@@ -650,6 +698,7 @@ function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evalua
650
698
  const type = Object.hasOwn(types, name) ? types[name] : undefined;
651
699
  const made = Object.freeze(type === undefined ? evaluator : { compile: evaluator.compile, type });
652
700
  if (truth !== undefined && TRUTH_READERS.includes(name)) truthByCode.set(made, truth);
701
+ if (PROFILE_READERS.includes(name)) readerByCode.set(made, { name, profile });
653
702
  out[name] = made;
654
703
  }
655
704
  return Object.freeze(out);
@@ -925,15 +974,26 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
925
974
  }
926
975
  return known;
927
976
  };
977
+ // The clock of call permits a quarter of the depth limit, because an application nests its body (D125).
978
+ const limit = Math.ceil((machine.maxDepth ?? 1000) / 4);
928
979
  return (ctx, rt) => {
929
980
  // A closure keeps the context for each later use, thus the list of reads gets each path that it can read (READS.CLOSURE).
930
981
  rt.keepsContext?.(ctx, reads);
982
+ const clock = rt as Clocked;
931
983
  const closure = (...args: readonly unknown[]): unknown => {
932
- const scope: Record<string, unknown> = { ...ctx.names };
933
- names.forEach((name, index) => setOwn(scope, name, args[index]));
934
- const result = body.code({ ...ctx, names: scope }, rt);
935
- if (!result.ok) throw new Failure(result.error);
936
- return result.value;
984
+ // Each application counts one level on the runtime of its evaluation, and the count goes down after it (FORMULA.CALL-DEPTH).
985
+ const level = (clock.applying ?? 0) + 1;
986
+ if (level > limit) throw new Failure(fail("#DEPTH!", `The applications of functions nest deeper than ${limit} levels.`).error);
987
+ clock.applying = level;
988
+ try {
989
+ const scope: Record<string, unknown> = { ...ctx.names };
990
+ names.forEach((name, index) => setOwn(scope, name, args[index]));
991
+ const result = body.code({ ...ctx, names: scope }, rt);
992
+ if (!result.ok) throw new Failure(result.error);
993
+ return result.value;
994
+ } finally {
995
+ clock.applying = level - 1;
996
+ }
937
997
  };
938
998
  Object.defineProperty(closure, CLOSURE, { value: { params: names } });
939
999
  return ok(closure);
package/src/index.ts CHANGED
@@ -36,6 +36,9 @@ export {
36
36
  } from "./errors.ts";
37
37
  export {
38
38
  type Alias,
39
+ type AtArgs,
40
+ type AtEquation,
41
+ type AtRule,
39
42
  type AttributeDeclaration,
40
43
  type CapsRule,
41
44
  type CircularDeclaration,
@@ -57,6 +60,11 @@ export {
57
60
  type World,
58
61
  type WorldEntry,
59
62
  CIRCULAR_LIMIT,
63
+ GENERATION_LIMIT,
64
+ argsKey,
65
+ atArgsOf,
66
+ atRuleOf,
67
+ documentKey,
60
68
  circularOf,
61
69
  collectOf,
62
70
  effectsOfVerb,
@@ -98,6 +106,7 @@ export {
98
106
  export { type StepKey, type StepLadder, type StepNeed, type StepReason, type StepRefusal, type StepWalk, checkStep, isStep, stepLadder, stepOf, stepRank } from "./steps.ts";
99
107
  export {
100
108
  type Cap,
109
+ type CompiledPath,
101
110
  type Context,
102
111
  type DerefCode,
103
112
  type Hop,
@@ -115,6 +124,7 @@ export {
115
124
  type Walker,
116
125
  ROOTS,
117
126
  THING,
127
+ compilePath,
118
128
  context,
119
129
  derefStep,
120
130
  isHostThing,
@@ -170,6 +180,7 @@ export {
170
180
  permits,
171
181
  planner,
172
182
  prepare,
183
+ resolveCompiled,
173
184
  run,
174
185
  } from "./evaluate.ts";
175
186
  export {
@@ -249,10 +260,12 @@ export {
249
260
  jsTruthy,
250
261
  } from "./formula.ts";
251
262
  export { decodeDomain, decodeValue, encodeValue } from "./codec.ts";
263
+ export { type CodecFinding, type CodecLaw, type CodecLawsOptions, type CodecProbe, codecLaws } from "./codec-laws.ts";
252
264
  export {
253
265
  type ParseResult,
254
266
  type SyntaxAssoc,
255
267
  type SyntaxCalls,
268
+ type SyntaxConstructs,
256
269
  type SyntaxEnvelopes,
257
270
  type SyntaxFixity,
258
271
  type SyntaxForm,
@@ -264,6 +277,7 @@ export {
264
277
  type SyntaxProfile,
265
278
  type SyntaxRefPattern,
266
279
  type SyntaxRefs,
280
+ MAX_SYNTAX_ISSUES,
267
281
  STD_SYNTAX,
268
282
  parse,
269
283
  print,
@@ -276,12 +290,14 @@ export { sameResult } from "./same.ts";
276
290
  export { type PrintOptions, type ReadPrints, type ReadsCheck, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
277
291
  export { type Durable, type DurableCounts, type DurableOptions, type Epochs, durable } from "./durable.ts";
278
292
  export {
293
+ type CycleIssue,
279
294
  type NodeEdge,
280
295
  type NodeInfo,
281
296
  type NodeRunOptions,
282
297
  type Schedule,
283
298
  type ScheduleCounts,
284
299
  type ScheduleOptions,
300
+ checkCycles,
285
301
  edgesOf,
286
302
  schedule,
287
303
  } from "./schedule.ts";
package/src/instances.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Json, type Key, field, isKey, isRecord, typeName } from "./doc.ts";
2
- import { type CollectRule, type Kind, collectOf, equationType } from "./world.ts";
2
+ import { type CollectRule, type Kind, argsKey, collectOf, equationType } from "./world.ts";
3
3
  import { ask } from "./ladder.ts";
4
4
  import { isRef, walkerOf } from "./deref.ts";
5
5
  import type { Machine } from "./evaluate.ts";
@@ -7,13 +7,16 @@ import { type Above, codeFactsOf, partLazy } from "./facts.ts";
7
7
  import { type NodeGraph, type GraphEdge, cycles } from "./graph.ts";
8
8
  import { STD_WALKERS } from "./stdlib.ts";
9
9
  import {
10
+ type AtStep,
10
11
  type CheckOptions,
11
12
  type DocVisit,
12
13
  type Issue,
13
14
  type SeenNode,
14
15
  REF_CODE,
15
16
  TREE_NODE,
17
+ atStepOf,
16
18
  downNames,
19
+ isAtStep,
17
20
  isStructural,
18
21
  keepsSelf,
19
22
  selvesOf,
@@ -39,6 +42,8 @@ export interface AttributeVertex {
39
42
  readonly type: string;
40
43
  /** `true` when the kind of the node declares the attribute `circular` (TREE.CIRCULAR-CHECK). Else the field is absent. */
41
44
  readonly circular?: true;
45
+ /** The arguments of an instance of an attribute with parameters (TREE.AT-CHECK). Else the field is absent. */
46
+ readonly args?: Readonly<Record<string, Json>>;
42
47
  }
43
48
 
44
49
  /** One read of an attribute by the code of another attribute (D107). */
@@ -220,6 +225,20 @@ function vertexOf(b: Builder, node: NodeInfo, attribute: string): string {
220
225
  return id;
221
226
  }
222
227
 
228
+ /**
229
+ * This function gives the vertex of an instance of an attribute with parameters (TREE.AT-CHECK, D124). Its name is the
230
+ * name of a plain vertex with the key of the arguments after the name of the attribute.
231
+ */
232
+ function atVertexOf(b: Builder, node: NodeInfo, at: AtStep & { readonly kind: "exact" }): string {
233
+ const id = `${attributeVertexId(node.visit.place, at.attr).slice(0, -1)},${at.key}]`;
234
+ if (!b.vertices.has(id)) {
235
+ b.vertices.set(id, { id, place: node.visit.place, attribute: at.attr, type: node.text, args: at.names });
236
+ b.nodes.set(id, node);
237
+ b.work.push(id);
238
+ }
239
+ return id;
240
+ }
241
+
223
242
  function edge(b: Builder, from: string | undefined, r: Reader, lazy = false): void {
224
243
  b.edges.push({ from, to: r.to, lazy: r.lazy || lazy, place: r.place });
225
244
  b.ats.push(r.at);
@@ -310,6 +329,15 @@ function follow(b: Builder, start: Value, path: readonly Json[], from: number, r
310
329
  }
311
330
  continue;
312
331
  }
332
+ // A step tree.at with literal arguments reads one instance (TREE.AT-CHECK). A step that fails gives no read.
333
+ if (isAtStep(b.machine, step)) {
334
+ const at = atStepOf(node.kind, node.text, step.value);
335
+ if (at.kind === "unknown") edge(b, undefined, r);
336
+ if (at.kind !== "exact") return;
337
+ edge(b, atVertexOf(b, node, at), r);
338
+ here = "unknown";
339
+ continue;
340
+ }
313
341
  // The standard walker nearest depends only on the structure (R3 P5). Each other walker can read each attribute.
314
342
  if (walkerOf(b.machine, step) !== STD_WALKERS["walk.nearest"]) {
315
343
  edge(b, undefined, r);
@@ -361,10 +389,11 @@ function termRefs(b: Builder, kind: Kind, label: string, term: Json): readonly T
361
389
  return out;
362
390
  }
363
391
 
364
- /** The names of the equation of a rung, before the run. */
392
+ /** The names of the equation of a rung, before the run. The parameters of an attribute hold JSON data (TREE.AT). */
365
393
  interface TermNames {
366
394
  readonly child?: NodeInfo;
367
395
  readonly prev?: Value;
396
+ readonly params?: readonly string[];
368
397
  }
369
398
 
370
399
  /** This function adds the reads of an equation of a rung: self is a node, and the names of the rung have their values. */
@@ -386,7 +415,10 @@ function termReads(b: Builder, refs: readonly TermRef[], self: NodeInfo, names:
386
415
  // A name that a kind inside the equation binds holds a value of the run. The others are the names of the rung.
387
416
  let start: Value;
388
417
  if (ref.bound.has(name)) start = "unknown";
389
- else if (names?.child !== undefined && name === "child") start = { node: names.child };
418
+ else if (names?.params !== undefined) {
419
+ if (!names.params.includes(name)) continue;
420
+ start = "data";
421
+ } else if (names?.child !== undefined && name === "child") start = { node: names.child };
390
422
  else if (names?.prev !== undefined && name === "prev") start = names.prev;
391
423
  else if (name === "index") start = "data";
392
424
  else if (name === "acc" || name === "item") start = "unknown";
@@ -412,9 +444,16 @@ function prevOf(parent: NodeInfo, child: NodeInfo): Value {
412
444
  */
413
445
  function rungReads(b: Builder, id: string): void {
414
446
  const node = b.nodes.get(id) as NodeInfo;
415
- const key = (b.vertices.get(id) as AttributeVertex).attribute;
447
+ const vertex = b.vertices.get(id) as AttributeVertex;
448
+ const key = vertex.attribute;
416
449
  const tree = node.kind.tree;
417
450
  const r: Reader = { to: id, lazy: false, place: node.visit.place, at: node.visit.at };
451
+ // An instance of an attribute with parameters has one rung: its equation in tree.at (TREE.AT).
452
+ if (vertex.args !== undefined) {
453
+ const at = atStepOf(node.kind, node.text, { attr: key, args: vertex.args });
454
+ if (at.kind === "exact") termReads(b, termRefs(b, node.kind, `tree.at.${key}`, at.equation), node, { params: at.params }, r);
455
+ return;
456
+ }
418
457
  const equation = hasOwnEntry(tree?.equations, key) ? tree?.equations?.[key] : undefined;
419
458
  if (equation !== undefined) {
420
459
  termReads(b, termRefs(b, node.kind, `tree.equations.${key}`, equation), node, undefined, r);
@@ -465,7 +504,22 @@ function rungReads(b: Builder, id: string): void {
465
504
  collectReads(b, node, key, collect, later);
466
505
  return;
467
506
  }
468
- if (declared?.default !== undefined) termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, undefined, later);
507
+ if (declared?.default !== undefined) {
508
+ termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.default`, declared.default), node, undefined, later);
509
+ return;
510
+ }
511
+ // The document of a higher-order attribute is known only in the run, thus its reads are not known (TREE.HIGHER-CHECK).
512
+ if (declared?.higher !== undefined) {
513
+ termReads(b, termRefs(b, node.kind, `tree.attributes.${key}.higher`, declared.higher), node, undefined, later);
514
+ edge(b, undefined, later);
515
+ return;
516
+ }
517
+ // The last rung reads the attribute `forwards` and an attribute of a node that only the run knows (TREE.FORWARD-CHECK).
518
+ const forwards = tree?.forwards;
519
+ if (typeof forwards === "string" && forwards !== key) {
520
+ edge(b, vertexOf(b, node, forwards), later);
521
+ edge(b, undefined, later);
522
+ }
469
523
  }
470
524
 
471
525
  /** This function gives the list children of a node before the run. A node without a rung for it has no children. */
@@ -634,7 +688,8 @@ function allCircular(b: Builder, ids: readonly string[]): boolean {
634
688
  }
635
689
 
636
690
  function vertexText(vertex: AttributeVertex): string {
637
- return `${vertex.attribute} of ${vertex.type} at [${vertex.place.join(", ")}]`;
691
+ const name = vertex.args === undefined ? vertex.attribute : `${vertex.attribute} with ${String(argsKey(vertex.args))}`;
692
+ return `${name} of ${vertex.type} at [${vertex.place.join(", ")}]`;
638
693
  }
639
694
 
640
695
  /**