@mark1russell7/coeval 0.2.0 → 0.2.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/coeval",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
6
6
  "type": "module",
7
7
  "main": "./build/index.js",
package/src/deref.ts CHANGED
@@ -112,6 +112,8 @@ export type Walker = (input: WalkInput) => WalkResult;
112
112
  * - `plain`: `true` when the values are data. Then a step does not ask the kind of an envelope, and a reference
113
113
  * that a step reaches stays a value (no collapse).
114
114
  * - `roots`: the roots that a path can start at. The default is each root.
115
+ * - `adopt`: the thing of each value that a path reaches. A host gives its own values a host thing with this
116
+ * function, for example a value whose fields read through signals. A host thing is not followed as a reference.
115
117
  */
116
118
  export interface Navigator {
117
119
  readonly world: World;
@@ -119,6 +121,7 @@ export interface Navigator {
119
121
  readonly walkers?: Readonly<Record<string, Walker>>;
120
122
  readonly plain?: boolean;
121
123
  readonly roots?: readonly Root[];
124
+ readonly adopt?: (value: unknown) => Thing;
122
125
  }
123
126
 
124
127
  /** The way that one hop of a path was answered. */
@@ -256,21 +259,37 @@ function mayBeRef(thing: unknown): boolean {
256
259
  return typeof thing === "object" && thing !== null && (thing as { readonly type?: unknown }).type === "ref";
257
260
  }
258
261
 
262
+ /** This function tells if each step of a path from an index is a key. */
263
+ function keysFrom(path: readonly unknown[], start: number): boolean {
264
+ for (let index = start; index < path.length; index += 1) if (!isKey(path[index])) return false;
265
+ return true;
266
+ }
267
+
259
268
  /**
260
- * This function resolves the common path without a record of hops: a root, then keys, through plain values. It
261
- * gives `undefined` when the path needs the full algorithm. Examples are a host thing, an envelope, a reference to
262
- * follow, a step document and a failure (for its exact reason).
269
+ * This function resolves the common path without a record of hops: a root, then keys. Plain values answer the
270
+ * keys, or a host thing answers all of them in one call. It gives `undefined` when the path needs the full
271
+ * algorithm. Examples are a host thing without `resolve`, an envelope, a reference to follow, a step document and a
272
+ * failure of a step (for its exact reason).
263
273
  */
264
274
  function resolveQuick(nav: Navigator, ctx: Context, path: readonly unknown[]): Resolution | undefined {
265
275
  const root = path[0];
266
- if (root !== "names" && root !== "self" && root !== "event") return undefined;
276
+ if (root !== "names" && root !== "self" && root !== "event" && root !== "world") return undefined;
267
277
  if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) return undefined;
278
+ if (nav.adopt !== undefined) return undefined;
268
279
  let here: unknown = ctx[root];
269
280
  const plain = nav.plain === true;
270
281
  for (let index = 1; index < path.length; index += 1) {
271
282
  if (typeof here !== "object" || here === null) return undefined;
272
283
  if (!plain && mayBeRef(here)) return undefined;
273
- if ((here as Partial<HostThing>)[THING] === true) return undefined;
284
+ if ((here as Partial<HostThing>)[THING] === true) {
285
+ const thing = here as HostThing;
286
+ if (thing.resolve === undefined || !keysFrom(path, index)) return undefined;
287
+ const keys = path.slice(index) as readonly Key[];
288
+ const reached = thing.resolve(keys);
289
+ if (reached === undefined) return { ok: false, code: "#N/A", reason: `The path ${keys.join(".")} reaches nothing.`, hops: NO_HOPS };
290
+ if (!plain && mayBeRef(reached.thing)) return undefined;
291
+ return { ok: true, thing: reached.thing, hops: NO_HOPS };
292
+ }
274
293
  const step = path[index];
275
294
  if (Array.isArray(here)) {
276
295
  if (typeof step !== "number" || !Number.isInteger(step) || step < 0 || step >= here.length) return undefined;
@@ -316,6 +335,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
316
335
  const done = hops ?? NO_HOPS;
317
336
  const failed = (code: ResolveCode, reason: string): Resolution => ({ ok: false, code, reason, hops: done });
318
337
  let collapses = 0;
338
+ const adopt = nav.adopt ?? ((value: unknown): Thing => value);
319
339
 
320
340
  const collapse = (thing: Thing): Resolution => {
321
341
  let here = thing;
@@ -325,7 +345,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
325
345
  if (collapses > MAX_COLLAPSE) return failed("#CYCLE!", "The references form a cycle.");
326
346
  const inner = walk(here.value);
327
347
  if (!inner.ok) return inner;
328
- here = inner.thing;
348
+ here = adopt(inner.thing);
329
349
  hops?.push({ step: "collapse", thing: here, by: "collapse" });
330
350
  }
331
351
  return { ok: true, thing: here, hops: done };
@@ -339,7 +359,7 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
339
359
  if (nav.roots !== undefined && !(nav.roots as readonly string[]).includes(root)) {
340
360
  return failed("#REF!", `The root ${root} is not open here. The open roots are ${nav.roots.join(", ")}.`);
341
361
  }
342
- let here: Thing = ctx[root as Root];
362
+ let here: Thing = adopt(ctx[root as Root]);
343
363
  hops?.push({ step: root, thing: here, by: "root" });
344
364
  const first = collapse(here);
345
365
  if (!first.ok) return first;
@@ -350,8 +370,9 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
350
370
  const keys = steps.slice(index) as readonly Key[];
351
371
  const reached = here.resolve(keys);
352
372
  if (reached === undefined) return failed("#N/A", `The path ${keys.join(".")} reaches nothing.`);
353
- hops?.push({ step: keys as readonly Json[], thing: reached.thing, by: "own" });
354
- return collapse(reached.thing);
373
+ const thing = adopt(reached.thing);
374
+ hops?.push({ step: keys as readonly Json[], thing, by: "own" });
375
+ return collapse(thing);
355
376
  }
356
377
  if (isKey(step)) {
357
378
  const reached = derefStep(nav, here, step);
@@ -359,8 +380,8 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
359
380
  if (root === "names" && index === 1) return failed("#NAME?", `The context has no name ${String(step)}.`);
360
381
  return failed("#N/A", `The step ${String(step)} reaches nothing.`);
361
382
  }
362
- hops?.push({ step, thing: reached.thing, by: reached.by });
363
- here = reached.thing;
383
+ here = adopt(reached.thing);
384
+ hops?.push({ step, thing: here, by: reached.by });
364
385
  } else {
365
386
  const envelope = isEnvelope(step) ? step : undefined;
366
387
  const found = envelope === undefined ? undefined : lookupKind(nav.world, envelope.type);
@@ -373,11 +394,14 @@ export function resolve(nav: Navigator, ctx: Context, path: readonly unknown[],
373
394
  data: envelope.value,
374
395
  here,
375
396
  context: ctx,
376
- deref: (thing, key) => derefStep(nav, thing, key)?.thing,
397
+ deref: (thing, key) => {
398
+ const reached = derefStep(nav, thing, key);
399
+ return reached === undefined ? undefined : adopt(reached.thing);
400
+ },
377
401
  });
378
402
  if (!result.ok) return failed("#N/A", result.reason);
379
- hops?.push({ step: envelope as Json, thing: result.thing, by: "step" });
380
- here = result.thing;
403
+ here = adopt(result.thing);
404
+ hops?.push({ step: envelope as Json, thing: here, by: "step" });
381
405
  }
382
406
  const next = collapse(here);
383
407
  if (!next.ok) return next;
package/src/evaluate.ts CHANGED
@@ -445,9 +445,14 @@ export interface EvalOptions {
445
445
  readonly effects?: Effect[];
446
446
  }
447
447
 
448
+ /** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
449
+ const WITHOUT_HOPS: ResolveOptions = Object.freeze({ hops: false });
450
+ const WITH_HOPS: ResolveOptions = Object.freeze({ hops: true });
451
+
448
452
  /** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
449
453
  class Session implements Runtime {
450
- readonly effects: Effect[];
454
+ /** The list of the effects. It is made at the first effect, thus an evaluation without effects makes no list. */
455
+ private list: Effect[] | undefined;
451
456
  tick = 0;
452
457
  nextId = 0;
453
458
  current: Building | undefined;
@@ -461,13 +466,19 @@ class Session implements Runtime {
461
466
  readonly signal: StopSignal | undefined,
462
467
  effects: Effect[] | undefined,
463
468
  ) {
464
- this.effects = effects ?? [];
469
+ this.list = effects;
465
470
  this.holder = traced ? holderNode() : undefined;
466
471
  this.current = this.holder;
467
472
  }
468
473
 
474
+ /** The effects of the evaluation, in their order. */
475
+ get effects(): Effect[] {
476
+ return (this.list ??= []);
477
+ }
478
+
469
479
  resolve(ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
470
- return resolve(this.machine, ctx, path, { hops: options?.hops ?? this.traced });
480
+ const hops = options?.hops ?? this.traced;
481
+ return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
471
482
  }
472
483
 
473
484
  private permitted(ctx: Context, keys: readonly Key[]): boolean {
@@ -479,8 +490,9 @@ class Session implements Runtime {
479
490
  const text = keys.join(".");
480
491
  if (!this.permitted(ctx, keys)) return fail("#CAP!", `The context has no capability for the effect ${text}.`, { place });
481
492
  const node = this.current;
482
- const effect: Effect = { index: this.effects.length, kind: text, path: keys, payload, node: node?.id ?? -1, at: this.tick, place, context: ctx };
483
- this.effects.push(effect);
493
+ const list = this.effects;
494
+ const effect: Effect = { index: list.length, kind: text, path: keys, payload, node: node?.id ?? -1, at: this.tick, place, context: ctx };
495
+ list.push(effect);
484
496
  if (node !== undefined) node.effects.push(effect.index);
485
497
  return effect;
486
498
  }