@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/build/index.d.ts.map +1 -1
- package/build/index.js +30 -72
- package/build/index.js.map +1 -1
- package/package.json +3 -1
- package/src/graph.ts +5 -64
- package/src/schedule.ts +33 -12
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.
|
|
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
|
|
114
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
494
|
-
*
|
|
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 (
|
|
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
|
|
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
|
-
|
|
557
|
-
else epoch.values.set(node, result);
|
|
568
|
+
epoch.values.delete(node);
|
|
558
569
|
}
|
|
559
|
-
|
|
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`. */
|