@mark1russell7/coeval 0.27.2 → 0.29.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.2",
4
+ "version": "0.29.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",
@@ -36,10 +36,10 @@
36
36
  ],
37
37
  "devDependencies": {
38
38
  "@mark1russell7/cue": "^2.1.1",
39
- "@mark1russell7/rigor-bench": "^0.2.29",
39
+ "@mark1russell7/rigor-bench": "^0.2.34",
40
40
  "@mark1russell7/rigor-statistics": "^0.1.16",
41
41
  "@types/node": "^26.6.4",
42
- "@vitest/coverage-v8": "5.0.5",
42
+ "@vitest/coverage-v8": "5.1.0",
43
43
  "esbuild": "^0.28.2",
44
44
  "typescript": "^7.0.2",
45
45
  "vitest": "^5.0.3"
@@ -48,7 +48,7 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@mark1russell7/collections": "^0.9.11",
51
+ "@mark1russell7/collections": "^0.10.0",
52
52
  "@mark1russell7/rigor-generators": "^0.1.8"
53
53
  },
54
54
  "scripts": {
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`. */