@mark1russell7/coeval 0.26.0 → 0.27.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/stdlib.ts CHANGED
@@ -9,7 +9,9 @@ import {
9
9
  type Result,
10
10
  type RunPart,
11
11
  type Runner,
12
+ type Runtime,
12
13
  type Steps,
14
+ NODE_WORK,
13
15
  RUNTIME,
14
16
  evaluateWithin,
15
17
  fail,
@@ -245,11 +247,20 @@ function isNumber(value: unknown): value is number {
245
247
  function allValues(results: readonly Result[]): { readonly values: readonly unknown[] } | { readonly error: Result } {
246
248
  const errors = results.filter((result) => !result.ok);
247
249
  const first = errors[0];
250
+ // D190: the test of the errors comes before the values. With the values first, a case of the bench took 1.2 to 1.4
251
+ // times the time in the Maglev tier of V8, in probe 4 of K216.
248
252
  if (first !== undefined && errors.length === 1) return { error: first };
249
- if (errors.length > 1) return { error: fail("#ARGS", `${errors.length} parts failed.`) };
253
+ if (errors.length > 1) return { error: partsFailed(errors) };
250
254
  return { values: results.map((result) => (result.ok ? result.value : null)) };
251
255
  }
252
256
 
257
+ /** This function gives the error of more than one failed part. */
258
+ function partsFailed(errors: readonly Result[]): Result {
259
+ // WORK.LIMIT: after the limit each counted part fails, and the code of the limit stays the code of the result.
260
+ const stopped = errors.find((result) => !result.ok && result.error.code === "#WORK!");
261
+ return stopped ?? fail("#ARGS", `${errors.length} parts failed.`);
262
+ }
263
+
253
264
  function numbers(values: readonly unknown[], kind: string): readonly number[] | Result {
254
265
  const bad = values.findIndex((value) => !isNumber(value));
255
266
  if (bad !== -1) return fail("#VALUE!", `Part ${bad} of ${kind} is not a number.`);
@@ -379,7 +390,7 @@ interface NodeState extends LoopNode {
379
390
  * @param path - The nodes and the containers whose JSON is in progress. A node on it gives `#CYCLE!`.
380
391
  * @returns The JSON, or the first error.
381
392
  */
382
- json(path: Set<object>): Result;
393
+ json(path: JsonWalk): Result;
383
394
  }
384
395
 
385
396
  /** The answer of the ladder for one attribute of one node (TREE.CELL, D108). */
@@ -887,24 +898,35 @@ function putField(target: Record<string, unknown>, key: string, value: unknown):
887
898
  Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
888
899
  }
889
900
 
901
+ /**
902
+ * The record of a walk for JSON. A node, list or record has `true` while its JSON is in progress, and `false` after
903
+ * that. Thus a value in the map with `false` is a second visit (WORK.SHARED).
904
+ */
905
+ type JsonWalk = Map<object, boolean>;
906
+
890
907
  /**
891
908
  * This function gives a value with the JSON of each node of a tree in it. A list or a plain record is new only when
892
909
  * it holds a node. A container that holds itself stays as it is.
893
910
  */
894
- function plainOf(value: unknown, path: Set<object>): Result {
911
+ function plainOf(value: unknown, path: JsonWalk, runtime: Runtime | undefined): Result {
912
+ if (typeof value !== "object" || value === null) return ok(value);
895
913
  const state = stateOf(value);
914
+ const open = path.get(value);
896
915
  if (state !== undefined) {
897
- if (path.has(value as object)) return fail("#CYCLE!", `The JSON of the node ${state.type} at [${state.place.join(", ")}] holds the node itself.`);
916
+ if (open === true) return fail("#CYCLE!", `The JSON of the node ${state.type} at [${state.place.join(", ")}] holds the node itself.`);
917
+ if (open === false) runtime?.spend?.(1);
898
918
  return state.json(path);
899
919
  }
900
- if (typeof value !== "object" || value === null || path.has(value) || isHostThing(value)) return ok(value);
920
+ if (open === true || isHostThing(value)) return ok(value);
901
921
  if (!Array.isArray(value) && !isRecord(value)) return ok(value);
902
- path.add(value);
922
+ // WORK.SHARED: a second visit of a container counts one point, because a chain of shared containers doubles the walk.
923
+ if (open === false) runtime?.spend?.(1);
924
+ path.set(value, true);
903
925
  try {
904
926
  if (Array.isArray(value)) {
905
927
  let list: unknown[] | undefined;
906
928
  for (let index = 0; index < value.length; index += 1) {
907
- const item = plainOf(value[index], path);
929
+ const item = plainOf(value[index], path, runtime);
908
930
  if (!item.ok) return item;
909
931
  if (item.value !== value[index]) (list ??= [...(value as readonly unknown[])])[index] = item.value;
910
932
  }
@@ -913,7 +935,7 @@ function plainOf(value: unknown, path: Set<object>): Result {
913
935
  const record = value as Readonly<Record<string, unknown>>;
914
936
  let out: Record<string, unknown> | undefined;
915
937
  for (const key of Object.keys(record)) {
916
- const item = plainOf(record[key], path);
938
+ const item = plainOf(record[key], path, runtime);
917
939
  if (!item.ok) return item;
918
940
  if (item.value === record[key]) continue;
919
941
  if (out === undefined) {
@@ -924,7 +946,7 @@ function plainOf(value: unknown, path: Set<object>): Result {
924
946
  }
925
947
  return ok(out ?? value);
926
948
  } finally {
927
- path.delete(value);
949
+ path.set(value, false);
928
950
  }
929
951
  }
930
952
 
@@ -1323,19 +1345,19 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
1323
1345
  return cell.by === undefined ? { attribute: key, rungs } : { attribute: key, rungs, by: cell.by };
1324
1346
  },
1325
1347
  json(path) {
1326
- path.add(node);
1348
+ path.set(node, true);
1327
1349
  try {
1328
1350
  const out: Record<string, unknown> = {};
1329
1351
  for (const part of parts) {
1330
1352
  const result = read(part.key) as Result;
1331
1353
  if (!result.ok) return result;
1332
- const value = plainOf(result.value, path);
1354
+ const value = plainOf(result.value, path, runtime);
1333
1355
  if (!value.ok) return value;
1334
1356
  putField(out, part.key, value.value);
1335
1357
  }
1336
1358
  return ok(out);
1337
1359
  } finally {
1338
- path.delete(node);
1360
+ path.set(node, false);
1339
1361
  }
1340
1362
  },
1341
1363
  };
@@ -1359,7 +1381,7 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
1359
1381
  return cell !== undefined && !cell.busy && cell.result !== undefined;
1360
1382
  },
1361
1383
  toJson(): unknown {
1362
- const result = state.json(new Set());
1384
+ const result = state.json(new Map());
1363
1385
  if (!result.ok) throw new Failure(result.error);
1364
1386
  return result.value;
1365
1387
  },
@@ -1404,6 +1426,9 @@ const NO_HOPS = /* @__PURE__ */ Object.freeze({ hops: false });
1404
1426
  * The argument `host` gives the memo cells of the host (TREE.CELL), or `undefined` for the plain memo of each node.
1405
1427
  */
1406
1428
  function treeNode(input: EvalInput, host: AttributeCell | undefined): Result {
1429
+ const runtime = (input as InputWithRuntime)[RUNTIME];
1430
+ // WORK.NODE: a node that an equation makes counts. A node of the text of the document does not.
1431
+ if (runtime?.nested === true) runtime.spend?.(NODE_WORK);
1407
1432
  const found = input.resolve(SELF_PATH, NO_HOPS);
1408
1433
  const outer = found.ok ? found.thing : undefined;
1409
1434
  const above = stateOf(outer);
@@ -1415,7 +1440,7 @@ function treeNode(input: EvalInput, host: AttributeCell | undefined): Result {
1415
1440
  build.open = true;
1416
1441
  const root = treeThing(input, place, build, outer, host);
1417
1442
  try {
1418
- return (stateOf(root) as NodeState).json(new Set());
1443
+ return (stateOf(root) as NodeState).json(new Map());
1419
1444
  } finally {
1420
1445
  build.open = false;
1421
1446
  }