@mark1russell7/coeval 0.27.1 → 0.28.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/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.27.1",
4
+ "version": "0.28.0",
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",
@@ -39,6 +39,7 @@
39
39
  "@mark1russell7/rigor-bench": "^0.2.29",
40
40
  "@mark1russell7/rigor-statistics": "^0.1.16",
41
41
  "@types/node": "^26.6.4",
42
+ "@vitest/coverage-v8": "5.0.5",
42
43
  "esbuild": "^0.28.2",
43
44
  "typescript": "^7.0.2",
44
45
  "vitest": "^5.0.3"
@@ -47,6 +48,7 @@
47
48
  "access": "public"
48
49
  },
49
50
  "dependencies": {
51
+ "@mark1russell7/collections": "^0.9.11",
50
52
  "@mark1russell7/rigor-generators": "^0.1.8"
51
53
  },
52
54
  "scripts": {
package/src/graph.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { strongComponentsCompact } from "@mark1russell7/collections/graph";
1
2
  import type { Side } from "./world.ts";
2
3
 
3
4
  /*
@@ -110,72 +111,12 @@ interface Strong {
110
111
  }
111
112
 
112
113
  /**
113
- * This function finds the strongly connected components with the algorithm of Tarjan. It keeps its own stack of
114
- * calls in two arrays, thus a long chain does not overflow the stack of the host. Each component comes after each
115
- * component that its nodes reach: the deepest component comes first.
114
+ * This function finds the strongly connected components with `strongComponentsCompact` of
115
+ * `@mark1russell7/collections` (K249). The search keeps its own stack, thus a long chain does not overflow the stack
116
+ * of the host. Each component comes after each component that its nodes reach: the deepest component comes first.
116
117
  */
117
118
  function strong(graph: Indexed<unknown>): Strong {
118
- const { size, start, targets } = graph;
119
- const order = new Int32Array(size).fill(-1);
120
- const low = new Int32Array(size);
121
- const of = new Int32Array(size).fill(-1);
122
- // The nodes of the components that are still open.
123
- const open = new Int32Array(size);
124
- const onOpen = new Uint8Array(size);
125
- // The stack of calls: a node, and the position of its next edge.
126
- const calls = new Int32Array(size);
127
- const next = new Int32Array(size);
128
- const list: number[][] = [];
129
- let top = 0;
130
- let count = 0;
131
- const enter = (node: number, depth: number): void => {
132
- order[node] = count;
133
- low[node] = count;
134
- count += 1;
135
- open[top] = node;
136
- top += 1;
137
- onOpen[node] = 1;
138
- calls[depth] = node;
139
- next[depth] = start[node] as number;
140
- };
141
- for (let root = 0; root < size; root += 1) {
142
- if (order[root] !== -1) continue;
143
- enter(root, 0);
144
- let depth = 1;
145
- while (depth > 0) {
146
- const node = calls[depth - 1] as number;
147
- const edge = next[depth - 1] as number;
148
- if (edge < (start[node + 1] as number)) {
149
- next[depth - 1] = edge + 1;
150
- const to = targets[edge] as number;
151
- if (order[to] === -1) {
152
- enter(to, depth);
153
- depth += 1;
154
- } else if (onOpen[to] === 1 && (order[to] as number) < (low[node] as number)) {
155
- low[node] = order[to] as number;
156
- }
157
- continue;
158
- }
159
- depth -= 1;
160
- if (low[node] === order[node]) {
161
- const members: number[] = [];
162
- let member: number;
163
- do {
164
- top -= 1;
165
- member = open[top] as number;
166
- onOpen[member] = 0;
167
- of[member] = list.length;
168
- members.push(member);
169
- } while (member !== node);
170
- list.push(members.sort((a, b) => a - b));
171
- }
172
- if (depth > 0) {
173
- const reader = calls[depth - 1] as number;
174
- if ((low[node] as number) < (low[reader] as number)) low[reader] = low[node] as number;
175
- }
176
- }
177
- }
178
- return { of, list };
119
+ return strongComponentsCompact(graph.size, graph.start, graph.targets);
179
120
  }
180
121
 
181
122
  /** This function tells if a component has a cycle: two or more nodes, or one node with an edge to itself. */
package/src/schedule.ts CHANGED
@@ -362,8 +362,14 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
362
362
  */
363
363
  const hostDemand = (id: string): Result => {
364
364
  const before = overflowCount();
365
+ const size = current().values.size;
365
366
  const result = visit(id);
366
- return overflowCount() === before && current().walk === undefined ? result : settle(id, result);
367
+ if (overflowCount() === before) return current().walk === undefined ? result : settle(id, result, []);
368
+ // A map keeps the order of its new keys. Thus the entries after the first `size` are the nodes of this demand (S2b).
369
+ const touched: string[] = [];
370
+ let index = 0;
371
+ for (const node of current().values.keys()) if (index++ >= size) touched.push(node);
372
+ return settle(id, result, touched);
367
373
  };
368
374
 
369
375
  /** This function demands one node, with the algorithm of Tarjan over the demands for the cycles of D31. */
@@ -490,20 +496,26 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
490
496
  * Each evaluation enters a new node or finishes a node. Thus the walks of a data epoch do at most `2V` evaluations
491
497
  * for `V` entered nodes. They keep one result and one entry in each list for each such node.
492
498
  *
493
- * A walk that reaches no cycle changes nothing, thus a chain keeps its `#DEPTH!`. After a cycle, each entered node
494
- * keeps only its error. A node with a value evaluates again at its next demand, because a value can hold the root of
495
- * the walk.
499
+ * After a cycle, each entered node keeps only its error. A result of the overflowed demand depends on the depth of
500
+ * the stack, thus each such node loses its result too (S2b). A node with a value does not take the value of the walk,
501
+ * because a value can hold the root of the walk. The schedule demands these nodes again, in the order in which the
502
+ * walk finished them. Thus each read of such a demand finds a result, and a long chain through lazy reads gives its
503
+ * value.
496
504
  *
497
505
  * @param id - The node of the demand.
498
506
  * @param given - The result of the demand.
507
+ * @param touched - The nodes that the overflowed demand gave a result. A demand with no overflow gives none.
499
508
  * @returns The final result of the demand.
500
509
  */
501
- const settle = (id: string, given: Result): Result => {
510
+ const settle = (id: string, given: Result, touched: readonly string[]): Result => {
502
511
  const epoch = current();
503
512
  const walk = (epoch.walk ??= startWalk());
504
513
  const { results } = walk;
505
- if (results.has(id) || cache.facts(docs.get(id)).side === "run") return given;
514
+ if (cache.facts(docs.get(id)).side === "run") return given;
515
+ if (results.has(id) && touched.length === 0) return given;
506
516
  const entered: string[] = [];
517
+ // The entered nodes in the order in which the walk finished them. Each node finishes after the nodes that it reads.
518
+ const finished: string[] = [];
507
519
  const starts: number[] = [];
508
520
  const enter = (node: string): void => {
509
521
  const mine = count++;
@@ -537,6 +549,7 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
537
549
  const self = looped.delete(node);
538
550
  const onCycle = reached < (order.get(node) as number) || close(from) > 1 || self;
539
551
  results.set(node, onCycle ? cycleOf(node) : result);
552
+ finished.push(node);
540
553
  // A node behind a cycle of an earlier walk finds that cycle too.
541
554
  found ||= onCycle || (!result.ok && result.error.code === "#CYCLE!");
542
555
  const reader = stack[stack.length - 1];
@@ -549,14 +562,22 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
549
562
  looped.clear();
550
563
  close(0);
551
564
  }
552
- if (!found) return given;
553
- for (const node of entered) {
554
- const result = results.get(node) as Result;
565
+ if (!found && touched.length === 0) return given;
566
+ for (const node of touched) {
555
567
  cache.forget(node);
556
- if (result.ok) epoch.values.delete(node);
557
- else epoch.values.set(node, result);
568
+ epoch.values.delete(node);
558
569
  }
559
- return visit(id);
570
+ if (found) {
571
+ for (const node of entered) {
572
+ const result = results.get(node) as Result;
573
+ cache.forget(node);
574
+ if (result.ok) epoch.values.delete(node);
575
+ else epoch.values.set(node, result);
576
+ }
577
+ }
578
+ // Each demand starts with no node in evaluation, and the nodes that it reads already have their results.
579
+ for (const node of finished) if (!epoch.values.has(node)) visit(node);
580
+ return epoch.values.get(id) ?? visit(id);
560
581
  };
561
582
 
562
583
  /** This function gives the record of the derive nodes, for a path that ends at the root `world`. */