@mark1russell7/coeval 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/schedule.ts CHANGED
@@ -166,12 +166,12 @@ const NO_KEYS: readonly Key[] = /* @__PURE__ */ Object.freeze([]);
166
166
  const NO_EDGES: readonly NodeEdge[] = /* @__PURE__ */ Object.freeze([]);
167
167
 
168
168
  /** This function gives the error of a node on a cycle. It names only the node, thus each order of demands gives the same error. */
169
- function cycleOf(id: string): Failed {
169
+ export function cycleOf(id: string): Failed {
170
170
  return fail("#CYCLE!", `The node ${id} is on a cycle of reads.`);
171
171
  }
172
172
 
173
173
  /** This function gives the error of a read of a node on the run side. */
174
- function runSide(id: string): Failed {
174
+ export function runSide(id: string): Failed {
175
175
  return fail("#REF!", `The node ${id} is on the run side, thus it has no value for a reader.`);
176
176
  }
177
177
 
@@ -354,10 +354,12 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
354
354
 
355
355
  /**
356
356
  * This function gives the state of the current data epoch. Each data epoch has a new host thing at the root
357
- * `world`. Thus a read that reaches the whole root changes at each change of data (READS.KEPT).
357
+ * `world`. Thus a read that reaches the whole root changes at each change of data (READS.KEPT). A kept read of a
358
+ * closure reads a node through `known`, without a demand (SCHED.CLOSURE).
358
359
  */
359
360
  const current = (): Epoch => {
360
361
  if (now !== undefined) return now;
362
+ const values = new Map<string, Result>();
361
363
  const nodesThing: HostThing = {
362
364
  [THING]: true,
363
365
  label: "nodes",
@@ -371,10 +373,18 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
371
373
  },
372
374
  has: (key) => docs.has(String(key)),
373
375
  toJson: record,
376
+ // A demand from a kept read can reach the node in evaluation, and that cycle is false (SCHED.ONE-MEANING).
377
+ // Thus only a node with its result in this epoch gives a value. Each such value is final (D31).
378
+ known(key) {
379
+ const result = values.get(String(key));
380
+ if (result === undefined) return undefined;
381
+ if (!result.ok) throw new Failure(result.error);
382
+ return { thing: result.value };
383
+ },
374
384
  };
375
385
  // The context has a world, thus `rootContext` only gives it its grant (CAP.NARROW-CODE). Each evaluation of the
376
386
  // epoch then uses this object with no copy.
377
- now = { context: rootContext(machine, { ...base, world: nodesThing }), values: new Map() };
387
+ now = { context: rootContext(machine, { ...base, world: nodesThing }), values };
378
388
  return now;
379
389
  };
380
390
 
package/src/static.ts CHANGED
@@ -619,9 +619,9 @@ const FOLD_NAMES: readonly string[] = /* @__PURE__ */ Object.freeze(["acc", "ite
619
619
 
620
620
  /**
621
621
  * This function gives each equation of the tree contract of a kind, with the names that it sees (D42). An equation of
622
- * `equations`, a `unit` and a `default` see no names.
622
+ * `equations`, a `unit` and a `default` see no names. `check` and `factsOf` read the equations with this one function.
623
623
  */
624
- function equationsOf(kind: Kind): readonly { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] {
624
+ export function equationsOf(kind: Kind): readonly { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] {
625
625
  const tree = kind.tree;
626
626
  if (tree === undefined) return [];
627
627
  const out: { readonly label: string; readonly term: Json; readonly names: readonly string[] }[] = [];
package/src/stdlib.ts CHANGED
@@ -2,12 +2,15 @@ import { type Json, type Key, field, isRecord } from "./doc.ts";
2
2
  import {
3
3
  type EvalInput,
4
4
  type Evaluator,
5
+ type InputWithRuntime,
5
6
  type Machine,
6
7
  type Plan,
7
8
  type Result,
8
9
  type RunPart,
9
10
  type Runner,
10
11
  type Steps,
12
+ RUNTIME,
13
+ evaluateWithin,
11
14
  fail,
12
15
  ok,
13
16
  planner,
@@ -17,6 +20,7 @@ import { type Cap, type HostThing, type Thing, type Walker, THING, isHostThing,
17
20
  import type { World } from "./ladder.ts";
18
21
  import { type AttributeDeclaration, type TreeContract, equationType, lookupKind } from "./world.ts";
19
22
  import { STD_DOMAINS } from "./types.ts";
23
+ import type { SyntaxProfile } from "./syntax.ts";
20
24
 
21
25
  /**
22
26
  * The world of the examples: small kinds for numbers, lists, conditions, names, sequences and effects.
@@ -127,6 +131,52 @@ export const STD_WORLD: World = {
127
131
  defaults: { view: "view.card" },
128
132
  };
129
133
 
134
+ /**
135
+ * The text syntax of the standard world (K162, D64). The syntax kit (D41) reads it, thus a host of `STD_WORLD` does
136
+ * not write its own profile. The operators `>`, `+` and `*` make `greater`, `add` and `multiply`. A call form makes
137
+ * each other kind, and `ref` uses the reference syntax of the kit.
138
+ *
139
+ * A literal in a place that takes a document gets its data envelope (K159, D65). Such a place is a part of `if`,
140
+ * `list`, `seq` or `pure`, the value of `write`, or the whole text. The document after a header name is such a place
141
+ * too. Thus `list(1, "a")` evaluates to `[1, "a"]`. The forms `add`, `multiply` and `greater` write an operator document that its operator
142
+ * does not write, for example `add(5)` (K163).
143
+ *
144
+ * A host with more kinds spreads this profile and adds its forms to `calls.forms`. `STD_SYNTAX` stays the profile of
145
+ * the formula kit.
146
+ */
147
+ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
148
+ operators: [
149
+ { symbol: ">", fixity: "infix", prec: 4, assoc: "none", kind: "greater" },
150
+ { symbol: "+", fixity: "infix", prec: 5, assoc: "flat", kind: "add" },
151
+ { symbol: "*", fixity: "infix", prec: 6, assoc: "flat", kind: "multiply" },
152
+ ],
153
+ calls: {
154
+ ops: [],
155
+ forms: [
156
+ { name: "if", kind: "if", fields: ["test", "then", "else"], min: 2, wrap: true },
157
+ { name: "list", kind: "list", wrap: true },
158
+ { name: "seq", kind: "seq", wrap: true },
159
+ { name: "pure", kind: "pure", min: 1, wrap: true },
160
+ { name: "log", kind: "log", min: 1 },
161
+ { name: "write", kind: "write", fields: ["key", "value"], wrap: ["value"] },
162
+ { name: "math.sum", kind: "math.sum" },
163
+ { name: "math.product", kind: "math.product" },
164
+ { name: "number", kind: "number", args: "literal" },
165
+ { name: "text", kind: "text", args: "literal" },
166
+ { name: "bool", kind: "bool", args: "literal" },
167
+ { name: "panel", kind: "panel", args: "named", fields: ["width", "height", "children"] },
168
+ { name: "box", kind: "box", args: "named", fields: ["width", "height", "children"] },
169
+ { name: "nearest", kind: "nearest", args: "named", fields: ["type", "follow", "self"] },
170
+ // An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
171
+ { name: "add", kind: "add" },
172
+ { name: "multiply", kind: "multiply" },
173
+ { name: "greater", kind: "greater" },
174
+ ],
175
+ },
176
+ literals: { envelopes: { number: "number", text: "text", bool: "bool" } },
177
+ header: { bind: "=" },
178
+ } satisfies SyntaxProfile);
179
+
130
180
  function isNumber(value: unknown): value is number {
131
181
  return typeof value === "number" && Number.isFinite(value);
132
182
  }
@@ -234,7 +284,8 @@ interface NodeState {
234
284
  */
235
285
  read(key: string): Result | undefined;
236
286
  /**
237
- * This function evaluates an equation of the kind of the node, with the node as `self` and no capabilities.
287
+ * This function evaluates an equation of the kind of the node, with the node as `self` and no capabilities. Its
288
+ * reads go in the list of the evaluation that made the node (READS.EQUATION).
238
289
  *
239
290
  * @param term - The equation.
240
291
  * @param names - The names that the equation sees.
@@ -403,6 +454,8 @@ function plainOf(value: unknown, path: Set<object>): Result {
403
454
  */
404
455
  function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined): HostThing {
405
456
  const { parts, context, kind: type, place, machine } = input;
457
+ // The equations evaluate in the evaluation that made the node, thus their reads go in its list (READS.EQUATION).
458
+ const runtime = (input as InputWithRuntime)[RUNTIME];
406
459
  const { tree, of } = contractOf(machine, input.typePath);
407
460
  const above = stateOf(outer);
408
461
  // Only a node of the same tree is a parent for the rungs `parent` and `copy`.
@@ -481,7 +534,7 @@ function treeThing(input: EvalInput, build: TreeBuild, outer: Thing | undefined)
481
534
  read,
482
535
  equation(term, names) {
483
536
  if (machine === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
484
- return equationPlan(machine, of, term).evaluate({ ...context, names, caps: NO_CAPS, self: node });
537
+ return evaluateWithin(equationPlan(machine, of, term), { ...context, names, caps: NO_CAPS, self: node }, runtime);
485
538
  },
486
539
  childNames(child) {
487
540
  const children = read("children");
@@ -560,6 +613,18 @@ export function attributeTrace(node: Thing, attribute: string): AttributeTrace |
560
613
  return stateOf(node)?.trace(attribute);
561
614
  }
562
615
 
616
+ /**
617
+ * This function gives the place of a node of the decorated tree: the keys from the root document to the document of
618
+ * the node (HOP.PLACE). A view puts each hop of a resolution on its node with it. The node keeps its place already,
619
+ * thus the function adds no record.
620
+ *
621
+ * @param thing - A thing, for example the thing of a hop.
622
+ * @returns The keys of the place, or `undefined` for a thing that is not a node of the decorated tree.
623
+ */
624
+ export function placeOf(thing: Thing): readonly Key[] | undefined {
625
+ return stateOf(thing)?.place;
626
+ }
627
+
563
628
  /** The path of the root `self`. A node of the tree resolves it, thus the list of reads holds the read (C4). */
564
629
  const SELF_PATH: readonly Json[] = /* @__PURE__ */ Object.freeze(["self"]);
565
630
 
@@ -597,6 +662,11 @@ export interface NearestOptions {
597
662
  readonly matches?: (node: Thing, wanted: Json | undefined) => boolean;
598
663
  /** The most hops of one walk. The default is 256. */
599
664
  readonly maxHops?: number;
665
+ /**
666
+ * `true`: the walk records one hop for each node on the way before the node that it reaches (HOP.EACH-STEP). Each
667
+ * such hop has the followed key as its step. The default is `false`: one hop for the whole walk.
668
+ */
669
+ readonly eachStep?: boolean;
600
670
  }
601
671
 
602
672
  function hostType(node: Thing, wanted: Json | undefined): boolean {
@@ -606,23 +676,28 @@ function hostType(node: Thing, wanted: Json | undefined): boolean {
606
676
  /**
607
677
  * This function makes a walker `nearest`. The walker follows a key (the default is `parent`) until the node that it
608
678
  * reaches has the wanted type. With `self: true`, the node where the walk starts is the first candidate. A node
609
- * without that key ends the walk with an error.
679
+ * without that key ends the walk with an error. With `eachStep`, a resolution with hops gets a hop for each node on the way.
610
680
  *
611
- * @param options - The test of a type and the most hops.
681
+ * @param options - The test of a type, the most hops and the record of each step.
612
682
  * @returns The walker.
613
683
  */
614
684
  export function nearestWalker(options: NearestOptions = {}): Walker {
615
685
  const matches = options.matches ?? hostType;
616
686
  const maxHops = options.maxHops ?? 256;
617
- return ({ data, here, deref }) => {
687
+ const eachStep = options.eachStep === true;
688
+ return ({ data, here, deref, visit }) => {
618
689
  const follow = isRecord(data) && typeof field(data, "follow") === "string" ? String(field(data, "follow")) : "parent";
619
690
  const wanted = isRecord(data) ? field(data, "type") : undefined;
620
691
  const fromSelf = isRecord(data) && field(data, "self") === true;
692
+ const record = eachStep ? visit : undefined;
621
693
  let at: Thing | undefined = here;
622
694
  for (let hops = 0; hops < maxHops; hops += 1) {
623
- if (hops > 0 || !fromSelf) at = at === undefined || at === null ? undefined : deref(at, follow);
695
+ const stepped = hops > 0 || !fromSelf;
696
+ if (stepped) at = at === undefined || at === null ? undefined : deref(at, follow);
624
697
  if (at === undefined || at === null) return { ok: false, reason: `No node of the type ${String(wanted)} is on the way.` };
625
698
  if (matches(at, wanted)) return { ok: true, thing: at };
699
+ // The hop of the whole walk records the node that the walk reaches. Each node before it gets its own hop.
700
+ if (stepped) record?.(follow, at);
626
701
  }
627
702
  return { ok: false, reason: "The walk is too long." };
628
703
  };