@mark1russell7/coeval 0.6.3 → 0.7.1
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/README.md +63 -5
- package/build/index.d.ts +1771 -254
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5809 -511
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +15 -3
- package/src/codec.ts +185 -0
- package/src/conform.ts +1068 -0
- package/src/deref.ts +284 -47
- package/src/durable.ts +238 -0
- package/src/effects.ts +110 -0
- package/src/errors.ts +2 -1
- package/src/evaluate.ts +480 -113
- package/src/facts.ts +252 -0
- package/src/formula.ts +450 -42
- package/src/graph.ts +399 -0
- package/src/index.ts +46 -0
- package/src/ladder.ts +40 -1
- package/src/merge.ts +91 -4
- package/src/parts.ts +26 -11
- package/src/providers.ts +286 -0
- package/src/reads.ts +105 -0
- package/src/reference.ts +23 -9
- package/src/schedule.ts +436 -0
- package/src/static.ts +266 -29
- package/src/stdlib.ts +24 -7
- package/src/syntax.ts +846 -0
- package/src/types.ts +768 -0
- package/src/typing.ts +390 -0
- package/src/world.ts +63 -9
package/src/deref.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Envelope, type Json, type JsonRecord, type Key, type TypeRef, field, isEnvelope, isKey, isRecord, typePath } from "./doc.ts";
|
|
2
|
-
import { type World, entriesAsJson, lookupKind } from "./world.ts";
|
|
2
|
+
import { type Kind, type World, entriesAsJson, lookupKind } from "./world.ts";
|
|
3
|
+
import { answerOf } from "./ladder.ts";
|
|
3
4
|
import type { ErrorCode } from "./errors.ts";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -162,28 +163,126 @@ export interface ResolveOptions {
|
|
|
162
163
|
readonly hops?: boolean;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
function
|
|
166
|
-
|
|
166
|
+
/** This function gives the own entries of an envelope, when it has a record of them. */
|
|
167
|
+
function ownEntries(envelope: Envelope): Readonly<Record<string, string>> | undefined {
|
|
168
|
+
const own = field(envelope as JsonRecord, "own");
|
|
169
|
+
return isRecord(own) ? (own as Readonly<Record<string, string>>) : undefined;
|
|
167
170
|
}
|
|
168
171
|
|
|
172
|
+
/** One node of the memo of the kinds of a world: the kind of the keys up to this node, and the longer keys. */
|
|
173
|
+
interface KindNode {
|
|
174
|
+
/** The kind of the keys, `null` for a type without a kind, or `undefined` before the first lookup. */
|
|
175
|
+
kind?: Kind | null;
|
|
176
|
+
next?: Map<Key, KindNode>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The memo of the kinds of one world, by the keys of a type. */
|
|
180
|
+
interface KindMemo {
|
|
181
|
+
first: Map<Key, KindNode>;
|
|
182
|
+
/** The number of kinds in the memo. */
|
|
183
|
+
size: number;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The most kinds in the memo of one world. A larger memo starts again, thus the types of the data cannot fill the memory. */
|
|
187
|
+
const MAX_KINDS = 1000;
|
|
188
|
+
|
|
189
|
+
const kindMemos = /* @__PURE__ */ new WeakMap<World, KindMemo>();
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* This function gives the kind of a type for the ladder: `null` when the world does not have the kind. The result
|
|
193
|
+
* depends only on the world and on the keys of the type, thus a memo for each world keeps it (PLAN.KIND-MEMO). A
|
|
194
|
+
* world does not change after a plan or a resolution uses it, as for `worldThing`.
|
|
195
|
+
*/
|
|
196
|
+
function kindFor(world: World, type: TypeRef): Kind | null {
|
|
197
|
+
const keys = typeof type === "string" ? undefined : typePath(type);
|
|
198
|
+
// A computed type has no keys, and `lookupKind` gives it no kind.
|
|
199
|
+
if (typeof type !== "string" && (keys === undefined || keys.length === 0)) {
|
|
200
|
+
const found = lookupKind(world, type);
|
|
201
|
+
return found.ok ? found.kind : null;
|
|
202
|
+
}
|
|
203
|
+
let memo = kindMemos.get(world);
|
|
204
|
+
if (memo === undefined || memo.size >= MAX_KINDS) {
|
|
205
|
+
memo = { first: new Map(), size: 0 };
|
|
206
|
+
kindMemos.set(world, memo);
|
|
207
|
+
}
|
|
208
|
+
const node = keys === undefined ? nodeAt(memo.first, type as string) : nodeOf(memo.first, keys);
|
|
209
|
+
if (node.kind === undefined) {
|
|
210
|
+
const found = lookupKind(world, type);
|
|
211
|
+
node.kind = found.ok ? found.kind : null;
|
|
212
|
+
memo.size += 1;
|
|
213
|
+
}
|
|
214
|
+
return node.kind;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** This function gives the node of one key in a level of the memo, and makes it when the level does not have it. */
|
|
218
|
+
function nodeAt(level: Map<Key, KindNode>, key: Key): KindNode {
|
|
219
|
+
let node = level.get(key);
|
|
220
|
+
if (node === undefined) {
|
|
221
|
+
node = {};
|
|
222
|
+
level.set(key, node);
|
|
223
|
+
}
|
|
224
|
+
return node;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** This function gives the node of the keys of a type in the memo. */
|
|
228
|
+
function nodeOf(first: Map<Key, KindNode>, keys: readonly Key[]): KindNode {
|
|
229
|
+
let node = nodeAt(first, keys[0] as Key);
|
|
230
|
+
for (let index = 1; index < keys.length; index += 1) node = nodeAt((node.next ??= new Map()), keys[index] as Key);
|
|
231
|
+
return node;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** This function tells if a table of code has the name. An entry of a document can hold another value than a text. */
|
|
235
|
+
function codeIn(table: Readonly<Record<string, unknown>>, name: unknown): boolean {
|
|
236
|
+
return typeof name === "string" && Object.hasOwn(table, name);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* This function finds the walker of a step document through the ladder of the verb `step` (VERB.LADDER). The rungs
|
|
241
|
+
* are the own entry, then the kind, then the default of the world. A rung answers only with a walker that the
|
|
242
|
+
* navigator has. A resolution and `check` use this function, thus they agree on `#STEP?`.
|
|
243
|
+
*
|
|
244
|
+
* @param nav - The world and the walkers.
|
|
245
|
+
* @param step - A step of a path that is not a key.
|
|
246
|
+
* @returns The walker, or `undefined` when no rung answers.
|
|
247
|
+
*/
|
|
248
|
+
export function walkerOf(nav: Navigator, step: unknown): Walker | undefined {
|
|
249
|
+
const walkers = nav.walkers;
|
|
250
|
+
if (walkers === undefined || !isEnvelope(step)) return undefined;
|
|
251
|
+
const answer = answerOf(nav.world, ownEntries(step), kindFor(nav.world, step.type), "step", (name) => codeIn(walkers, name));
|
|
252
|
+
return answer === undefined ? undefined : walkers[answer.name];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The answer of `plainStep` for a field that the value does not have. A field can hold `undefined`. */
|
|
256
|
+
const NOTHING: unique symbol = /* @__PURE__ */ Symbol("nothing") as never;
|
|
257
|
+
|
|
169
258
|
/** The own fields of a value: the items and the length of a list, and the own properties of an object. */
|
|
170
|
-
function plainStep(thing: unknown, key: Key):
|
|
259
|
+
function plainStep(thing: unknown, key: Key): unknown {
|
|
171
260
|
if (Array.isArray(thing)) {
|
|
172
|
-
if (key === "length") return
|
|
261
|
+
if (key === "length") return thing.length;
|
|
173
262
|
const index = typeof key === "number" ? key : /^\d+$/.test(key) ? Number(key) : -1;
|
|
174
|
-
return index >= 0 && index < thing.length ?
|
|
263
|
+
return index >= 0 && index < thing.length ? (thing as readonly unknown[])[index] : NOTHING;
|
|
175
264
|
}
|
|
176
265
|
if (typeof thing === "object" && thing !== null) {
|
|
177
266
|
const name = String(key);
|
|
178
|
-
return Object.hasOwn(thing, name) ?
|
|
267
|
+
return Object.hasOwn(thing, name) ? (thing as Readonly<Record<string, unknown>>)[name] : NOTHING;
|
|
179
268
|
}
|
|
180
|
-
return
|
|
269
|
+
return NOTHING;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* This function tells if a value can be an envelope by its field `type`. An envelope has its own field `type`, thus
|
|
274
|
+
* a value without that field is not an envelope, and the full test is not necessary.
|
|
275
|
+
*/
|
|
276
|
+
function mayBeEnvelope(thing: unknown): boolean {
|
|
277
|
+
return typeof thing === "object" && thing !== null && (thing as { readonly type?: unknown }).type !== undefined;
|
|
181
278
|
}
|
|
182
279
|
|
|
183
280
|
/**
|
|
184
281
|
* This function answers one step of a path on a thing, through the ladder of the verb `deref`. A host thing
|
|
185
|
-
* answers for itself (own). A document asks its own entry, then its kind
|
|
186
|
-
*
|
|
282
|
+
* answers for itself (own). A document asks its own entry, then its kind, then the default of the world
|
|
283
|
+
* (VERB.LADDER). A rung answers only with a `deref` that the navigator has. When no rung answers, the step goes into
|
|
284
|
+
* the value: an own field of an object, or an item of a list. A value without a type is data, thus the step goes
|
|
285
|
+
* into it at once.
|
|
187
286
|
*
|
|
188
287
|
* @param nav - The world and the code of the evaluators.
|
|
189
288
|
* @param thing - The thing that the path reached.
|
|
@@ -196,27 +295,20 @@ export function derefStep(nav: Navigator, thing: Thing, key: Key): { readonly th
|
|
|
196
295
|
// DEREF.HOST-UNDEFINED: `has` tells a field that holds undefined from a missing field.
|
|
197
296
|
return reached !== undefined || thing.has?.(key) === true ? { thing: reached, by: "own" } : undefined;
|
|
198
297
|
}
|
|
199
|
-
if (nav.plain !== true && isEnvelope(thing)) {
|
|
298
|
+
if (nav.plain !== true && mayBeEnvelope(thing) && isEnvelope(thing)) {
|
|
200
299
|
const envelope = thing as JsonRecord & Envelope;
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
if (
|
|
205
|
-
const reached =
|
|
206
|
-
return reached === undefined ? undefined : { thing: reached, by:
|
|
207
|
-
}
|
|
208
|
-
const found = lookupKind(nav.world, envelope.type);
|
|
209
|
-
const name = found.ok ? entryOf(found.kind.verbs, "deref") : undefined;
|
|
210
|
-
const code = name === undefined ? undefined : nav.derefs?.[name];
|
|
211
|
-
if (code !== undefined) {
|
|
212
|
-
const reached = code(envelope, key);
|
|
213
|
-
return reached === undefined ? undefined : { thing: reached, by: "kind" };
|
|
300
|
+
const derefs = nav.derefs;
|
|
301
|
+
// A navigator without the code of a deref needs no lookup of the kind: no rung can answer.
|
|
302
|
+
const answer = derefs === undefined ? undefined : answerOf(nav.world, ownEntries(envelope), kindFor(nav.world, envelope.type), "deref", (name) => codeIn(derefs, name));
|
|
303
|
+
if (answer !== undefined) {
|
|
304
|
+
const reached = (derefs as Readonly<Record<string, DerefCode>>)[answer.name]?.(envelope, key);
|
|
305
|
+
return reached === undefined ? undefined : { thing: reached, by: answer.rung };
|
|
214
306
|
}
|
|
215
307
|
const reached = plainStep(envelope.value, key);
|
|
216
|
-
return reached ===
|
|
308
|
+
return reached === NOTHING ? undefined : { thing: reached, by: "default" };
|
|
217
309
|
}
|
|
218
310
|
const reached = plainStep(thing, key);
|
|
219
|
-
return reached ===
|
|
311
|
+
return reached === NOTHING ? undefined : { thing: reached, by: "default" };
|
|
220
312
|
}
|
|
221
313
|
|
|
222
314
|
/**
|
|
@@ -226,6 +318,10 @@ export function derefStep(nav: Navigator, thing: Thing, key: Key): { readonly th
|
|
|
226
318
|
* @returns `true` for a reference document.
|
|
227
319
|
*/
|
|
228
320
|
export function isRef(thing: unknown): thing is Envelope & { readonly value: readonly Json[] } {
|
|
321
|
+
if (typeof thing !== "object" || thing === null) return false;
|
|
322
|
+
// A text type other than `ref` names another kind, thus the full test of the envelope is not necessary.
|
|
323
|
+
const type = (thing as { readonly type?: unknown }).type;
|
|
324
|
+
if (type === undefined || (typeof type === "string" && type !== "ref")) return false;
|
|
229
325
|
if (!isEnvelope(thing) || !Array.isArray(thing.value)) return false;
|
|
230
326
|
const path = typePath(thing.type as TypeRef);
|
|
231
327
|
return path !== undefined && path.length === 1 && path[0] === "ref";
|
|
@@ -280,6 +376,11 @@ function keysFrom(path: readonly unknown[], start: number): boolean {
|
|
|
280
376
|
return true;
|
|
281
377
|
}
|
|
282
378
|
|
|
379
|
+
/** This function tells if a root has a value of its own for the short path: each root except `caps`. */
|
|
380
|
+
function quickRoot(root: unknown): root is "names" | "self" | "event" | "world" {
|
|
381
|
+
return root === "names" || root === "self" || root === "event" || root === "world";
|
|
382
|
+
}
|
|
383
|
+
|
|
283
384
|
/**
|
|
284
385
|
* This function resolves the common path without a record of hops: a root, then keys. Plain values answer the
|
|
285
386
|
* keys, or a host thing answers all of them in one call. It gives `undefined` when the path needs the full
|
|
@@ -288,17 +389,31 @@ function keysFrom(path: readonly unknown[], start: number): boolean {
|
|
|
288
389
|
*/
|
|
289
390
|
function resolveQuick(nav: Navigator, ctx: Context, path: readonly unknown[]): Resolution | undefined {
|
|
290
391
|
const root = path[0];
|
|
291
|
-
if (root
|
|
392
|
+
if (!quickRoot(root)) return undefined;
|
|
292
393
|
if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) return undefined;
|
|
293
394
|
if (nav.adopt !== undefined) return undefined;
|
|
294
|
-
|
|
295
|
-
|
|
395
|
+
return walkQuick(ctx[root], path, nav.plain === true, false);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* This function walks the steps of the short path from the value of the root (DEREF.QUICK). `resolveQuick` and each
|
|
400
|
+
* compiled path use it, thus the two cannot disagree. It gives `undefined` when the path needs the full algorithm.
|
|
401
|
+
*
|
|
402
|
+
* @param start - The value of the root.
|
|
403
|
+
* @param path - The steps. The walk starts at the index 1.
|
|
404
|
+
* @param plain - `true` for a plain navigation.
|
|
405
|
+
* @param keyed - `true` when each step after the root is a key. The walk then does not test the steps again.
|
|
406
|
+
*/
|
|
407
|
+
function walkQuick(start: unknown, path: readonly unknown[], plain: boolean, keyed: boolean): Resolution | undefined {
|
|
408
|
+
let here = start;
|
|
296
409
|
for (let index = 1; index < path.length; index += 1) {
|
|
297
410
|
if (typeof here !== "object" || here === null) return undefined;
|
|
298
|
-
|
|
411
|
+
// One read of the field `type` finds a reference document. Without the field, the value is not an envelope.
|
|
412
|
+
const type = plain ? undefined : (here as { readonly type?: unknown }).type;
|
|
413
|
+
if (type === "ref") return undefined;
|
|
299
414
|
if ((here as Partial<HostThing>)[THING] === true) {
|
|
300
415
|
const thing = here as HostThing;
|
|
301
|
-
if (thing.resolve === undefined || !keysFrom(path, index)) return undefined;
|
|
416
|
+
if (thing.resolve === undefined || !(keyed || keysFrom(path, index))) return undefined;
|
|
302
417
|
const keys = path.slice(index) as readonly Key[];
|
|
303
418
|
const reached = thing.resolve(keys);
|
|
304
419
|
if (reached === undefined) return { ok: false, code: "#N/A", reason: `The path ${keys.join(".")} reaches nothing.`, hops: NO_HOPS };
|
|
@@ -312,20 +427,132 @@ function resolveQuick(nav: Navigator, ctx: Context, path: readonly unknown[]): R
|
|
|
312
427
|
continue;
|
|
313
428
|
}
|
|
314
429
|
if (typeof step !== "string" || !Object.hasOwn(here, step)) return undefined;
|
|
315
|
-
if (
|
|
430
|
+
if (type !== undefined && isEnvelope(here)) return undefined;
|
|
316
431
|
here = (here as Readonly<Record<string, unknown>>)[step];
|
|
317
432
|
}
|
|
318
433
|
if (!plain && mayBeRef(here)) return undefined;
|
|
319
434
|
return { ok: true, thing: here, hops: NO_HOPS };
|
|
320
435
|
}
|
|
321
436
|
|
|
437
|
+
/**
|
|
438
|
+
* A compiled path: one static path for one navigator (PLAN.PATH). `compilePath` reads the root, the open roots and
|
|
439
|
+
* the steps one time. It asks the ladder of the verb `step` for each step document one time. Each resolution then
|
|
440
|
+
* examines only the values of the context.
|
|
441
|
+
*/
|
|
442
|
+
export interface CompiledPath {
|
|
443
|
+
/** The steps of the path, as the document gives them. The list of reads records these steps. */
|
|
444
|
+
readonly path: readonly unknown[];
|
|
445
|
+
/** The navigator of the compilation. A runtime with another navigator resolves the steps again. */
|
|
446
|
+
readonly nav: Navigator;
|
|
447
|
+
/**
|
|
448
|
+
* This function resolves the path from a context, without a record of hops.
|
|
449
|
+
*
|
|
450
|
+
* @param ctx - The context.
|
|
451
|
+
* @returns The same resolution as `resolve(nav, ctx, path, { hops: false })`.
|
|
452
|
+
*/
|
|
453
|
+
resolve(ctx: Context): Resolution;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/** The walker of each step document of a path, by its index: `null` when no rung answers, `undefined` for a key. */
|
|
457
|
+
type KnownWalkers = readonly (Walker | null | undefined)[];
|
|
458
|
+
|
|
459
|
+
/** The root whose value starts the short path of a compiled path. `FULL` takes the full algorithm at once. */
|
|
460
|
+
const FULL = 0;
|
|
461
|
+
const FROM_NAMES = 1;
|
|
462
|
+
const FROM_SELF = 2;
|
|
463
|
+
const FROM_EVENT = 3;
|
|
464
|
+
const FROM_WORLD = 4;
|
|
465
|
+
type From = typeof FULL | typeof FROM_NAMES | typeof FROM_SELF | typeof FROM_EVENT | typeof FROM_WORLD;
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* A compiled path as an object with fields (PLAN.LEAN). The code of `resolve` is on the class, thus each compiled path
|
|
469
|
+
* keeps only its fields and makes no closure.
|
|
470
|
+
*/
|
|
471
|
+
class StaticPath implements CompiledPath {
|
|
472
|
+
// The constructor writes each field one time, thus each field stays constant for V8, as the variable of a closure.
|
|
473
|
+
declare readonly path: readonly unknown[];
|
|
474
|
+
declare readonly nav: Navigator;
|
|
475
|
+
/** The walker of each step document, from the ladder of `step`, or `undefined` for a path of keys. */
|
|
476
|
+
declare private readonly known: KnownWalkers | undefined;
|
|
477
|
+
/** The root that starts the short path, or `FULL`. */
|
|
478
|
+
declare private readonly from: From;
|
|
479
|
+
declare private readonly plain: boolean;
|
|
480
|
+
|
|
481
|
+
constructor(path: readonly unknown[], nav: Navigator, known: KnownWalkers | undefined, from: From, plain: boolean) {
|
|
482
|
+
this.path = path;
|
|
483
|
+
this.nav = nav;
|
|
484
|
+
this.known = known;
|
|
485
|
+
this.from = from;
|
|
486
|
+
this.plain = plain;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
resolve(ctx: Context): Resolution {
|
|
490
|
+
const from = this.from;
|
|
491
|
+
if (from !== FULL) {
|
|
492
|
+
const start = from === FROM_NAMES ? ctx.names : from === FROM_WORLD ? ctx.world : from === FROM_SELF ? ctx.self : ctx.event;
|
|
493
|
+
const quick = walkQuick(start, this.path, this.plain, true);
|
|
494
|
+
if (quick !== undefined) return quick;
|
|
495
|
+
}
|
|
496
|
+
return resolveFully(this.nav, ctx, this.path, undefined, this.known);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* This function compiles one static path for one navigator. The resolver gives the same thing, error and reason as
|
|
502
|
+
* `resolve` without hops (PLAN.PATH-SAME). It keeps no value of a context.
|
|
503
|
+
*
|
|
504
|
+
* - Some paths take the full algorithm at once, because the short path cannot answer them. A path from `caps` or from
|
|
505
|
+
* a closed root is one. A path with a step document and each path of a navigator with `adopt` are others.
|
|
506
|
+
* - Each other path walks the short path from its root, and it takes the full algorithm when the values need it.
|
|
507
|
+
* - The walker of each step document comes from the ladder one time, here. The navigator and the step document do
|
|
508
|
+
* not change, thus the answer does not change either.
|
|
509
|
+
*
|
|
510
|
+
* @param nav - The navigator. A plan compiles its paths for its machine.
|
|
511
|
+
* @param path - The steps. The first step is a root.
|
|
512
|
+
* @returns The compiled path.
|
|
513
|
+
*/
|
|
514
|
+
export function compilePath(nav: Navigator, path: readonly unknown[]): CompiledPath {
|
|
515
|
+
const keyed = keysFrom(path, 1);
|
|
516
|
+
const known = keyed ? undefined : walkersOf(nav, path);
|
|
517
|
+
return new StaticPath(path, nav, known, startOf(nav, path, keyed), nav.plain === true);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** This function gives the root that starts the short path of a compiled path, or `FULL`. */
|
|
521
|
+
function startOf(nav: Navigator, path: readonly unknown[], keyed: boolean): From {
|
|
522
|
+
const root = path[0];
|
|
523
|
+
// The short path of `resolveQuick` refuses each of these paths before it reads a value, thus it is not necessary.
|
|
524
|
+
if (!keyed || !quickRoot(root) || nav.adopt !== undefined) return FULL;
|
|
525
|
+
if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) return FULL;
|
|
526
|
+
return root === "names" ? FROM_NAMES : root === "self" ? FROM_SELF : root === "event" ? FROM_EVENT : FROM_WORLD;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* This function asks the ladder of the verb `step` for each step document of a path. A step whose lookup throws
|
|
531
|
+
* stays unknown, thus the full algorithm asks the ladder again for it.
|
|
532
|
+
*/
|
|
533
|
+
function walkersOf(nav: Navigator, path: readonly unknown[]): KnownWalkers | undefined {
|
|
534
|
+
// Without walkers, `walkerOf` gives `undefined` at once.
|
|
535
|
+
if (nav.walkers === undefined) return undefined;
|
|
536
|
+
const out = new Array<Walker | null | undefined>(path.length);
|
|
537
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
538
|
+
const step = path[index];
|
|
539
|
+
if (isKey(step)) continue;
|
|
540
|
+
try {
|
|
541
|
+
out[index] = walkerOf(nav, step) ?? null;
|
|
542
|
+
} catch {
|
|
543
|
+
// The full algorithm asks the ladder again, and it gives the exception there.
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return out;
|
|
547
|
+
}
|
|
548
|
+
|
|
322
549
|
/**
|
|
323
550
|
* This function resolves a path from a context, and records each hop.
|
|
324
551
|
*
|
|
325
552
|
* The first step is a root of the context. Each later step is a key or a step document. A key goes up the ladder
|
|
326
|
-
* of the verb `deref` on the thing that the path reached. A step document is evaluated by the walker
|
|
327
|
-
* with the thing that the path reached. After each hop, a reference document is
|
|
328
|
-
* something else (collapse), except in a plain navigation.
|
|
553
|
+
* of the verb `deref` on the thing that the path reached. A step document is evaluated by the walker that the
|
|
554
|
+
* ladder of the verb `step` gives, with the thing that the path reached. After each hop, a reference document is
|
|
555
|
+
* followed until the path reaches something else (collapse), except in a plain navigation.
|
|
329
556
|
*
|
|
330
557
|
* The codes of a failure:
|
|
331
558
|
*
|
|
@@ -346,7 +573,20 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
346
573
|
const quick = resolveQuick(nav, ctx, path);
|
|
347
574
|
if (quick !== undefined) return quick;
|
|
348
575
|
}
|
|
349
|
-
|
|
576
|
+
return resolveFully(nav, ctx, path, options.hops === false ? undefined : [], undefined);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* This function resolves a path with the full algorithm of `resolve`.
|
|
581
|
+
*
|
|
582
|
+
* @param nav - The world and the code of the evaluators.
|
|
583
|
+
* @param ctx - The context.
|
|
584
|
+
* @param path - The steps.
|
|
585
|
+
* @param hops - The list that gets each hop, or `undefined` for no record.
|
|
586
|
+
* @param known - The walker of each step document of the path, from `compilePath`. A reference that a step reaches has its own steps, and the ladder answers them.
|
|
587
|
+
* @returns What the path reached and each hop, or an error.
|
|
588
|
+
*/
|
|
589
|
+
function resolveFully(nav: Navigator, ctx: Context, path: readonly unknown[], hops: Hop[] | undefined, known: KnownWalkers | undefined): Resolution {
|
|
350
590
|
const done = hops ?? NO_HOPS;
|
|
351
591
|
const failed = (code: ResolveCode | ErrorCode, reason: string): Resolution => ({ ok: false, code, reason, hops: done });
|
|
352
592
|
let collapses = 0;
|
|
@@ -358,7 +598,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
358
598
|
while (isRef(here)) {
|
|
359
599
|
collapses += 1;
|
|
360
600
|
if (collapses > MAX_COLLAPSE) return failed("#CYCLE!", "The references form a cycle.");
|
|
361
|
-
const inner = walk(here.value);
|
|
601
|
+
const inner = walk(here.value, undefined);
|
|
362
602
|
if (!inner.ok) return inner;
|
|
363
603
|
here = adopt(inner.thing);
|
|
364
604
|
hops?.push({ step: "collapse", thing: here, by: "collapse" });
|
|
@@ -366,7 +606,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
366
606
|
return { ok: true, thing: here, hops: done };
|
|
367
607
|
};
|
|
368
608
|
|
|
369
|
-
const walk = (steps: readonly unknown[]): Resolution => {
|
|
609
|
+
const walk = (steps: readonly unknown[], walkers: KnownWalkers | undefined): Resolution => {
|
|
370
610
|
const root = steps[0];
|
|
371
611
|
if (typeof root !== "string" || !(ROOTS as readonly string[]).includes(root)) {
|
|
372
612
|
return failed("#REF!", `A path starts with a root: ${ROOTS.join(", ")}.`);
|
|
@@ -381,7 +621,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
381
621
|
here = first.thing;
|
|
382
622
|
for (let index = 1; index < steps.length; index += 1) {
|
|
383
623
|
const step = steps[index];
|
|
384
|
-
if (isHostThing(here) && here.resolve !== undefined && steps
|
|
624
|
+
if (isHostThing(here) && here.resolve !== undefined && keysFrom(steps, index)) {
|
|
385
625
|
const keys = steps.slice(index) as readonly Key[];
|
|
386
626
|
const reached = here.resolve(keys);
|
|
387
627
|
if (reached === undefined) return failed("#N/A", `The path ${keys.join(".")} reaches nothing.`);
|
|
@@ -398,13 +638,10 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
398
638
|
here = adopt(reached.thing);
|
|
399
639
|
hops?.push({ step, thing: here, by: reached.by });
|
|
400
640
|
} else {
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
if (envelope === undefined || walker === undefined) {
|
|
406
|
-
return failed("#STEP?", "A step is a key, or a document of a kind with the verb step.");
|
|
407
|
-
}
|
|
641
|
+
const given = walkers?.[index];
|
|
642
|
+
const walker = given === undefined ? walkerOf(nav, step) : (given ?? undefined);
|
|
643
|
+
if (walker === undefined) return failed("#STEP?", "A step is a key, or a document of a kind with the verb step.");
|
|
644
|
+
const envelope = step as Envelope;
|
|
408
645
|
const result = walker({
|
|
409
646
|
data: envelope.value,
|
|
410
647
|
here,
|
|
@@ -426,7 +663,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
|
|
|
426
663
|
};
|
|
427
664
|
|
|
428
665
|
if (path.length === 0) return failed("#REF!", "A path has at least one step: its root.");
|
|
429
|
-
return walk(path);
|
|
666
|
+
return walk(path, known);
|
|
430
667
|
}
|
|
431
668
|
|
|
432
669
|
/**
|