@mark1russell7/coeval 0.14.0 → 0.16.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 +471 -190
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1209 -182
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +175 -7
- package/src/deref.ts +27 -9
- package/src/evaluate.ts +258 -12
- package/src/formula.ts +17 -3
- package/src/index.ts +12 -2
- package/src/meaning.ts +472 -0
- package/src/reads.ts +9 -2
- package/src/reference.ts +247 -42
- package/src/schedule.ts +14 -4
- package/src/stdlib.ts +26 -4
package/src/evaluate.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { answerOf, ask, type Answer } from "./ladder.ts";
|
|
|
4
4
|
import type { CapsRule, Kind, KindFacts, Side } from "./world.ts";
|
|
5
5
|
import type { Domain } from "./types.ts";
|
|
6
6
|
import type { TypeRule } from "./typing.ts";
|
|
7
|
+
import type { StaticRead } from "./facts.ts";
|
|
7
8
|
import { type ErrorCode, type EvalError, type Result, Failure, fail, ok, placed, thrown, describe } from "./errors.ts";
|
|
8
9
|
import {
|
|
9
10
|
type Cap,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
type Thing,
|
|
18
19
|
ROOTS,
|
|
19
20
|
context as makeRootContext,
|
|
21
|
+
derefStep,
|
|
20
22
|
isHostThing,
|
|
21
23
|
isRef,
|
|
22
24
|
resolve,
|
|
@@ -269,8 +271,8 @@ export interface Read {
|
|
|
269
271
|
*/
|
|
270
272
|
readonly free: boolean;
|
|
271
273
|
/**
|
|
272
|
-
* `true` when the kernel adds the read, because a value keeps
|
|
273
|
-
*
|
|
274
|
+
* `true` when the kernel adds the read, because a value keeps the context for later (READS.KEPT). Such a read has
|
|
275
|
+
* a whole root as its path, or a path that a closure can read (READS.CLOSURE).
|
|
274
276
|
*/
|
|
275
277
|
readonly kept?: true;
|
|
276
278
|
/** The hops of the resolution, only when the code asked for them. `verifyReads` then compares each hop too. */
|
|
@@ -459,9 +461,13 @@ export interface Runtime {
|
|
|
459
461
|
/**
|
|
460
462
|
* This optional function tells the runtime that a value keeps the context for later, for example a closure of
|
|
461
463
|
* `fn`. A later use of the value reads that context, and no evaluation records these reads. Thus the list of reads
|
|
462
|
-
* gets a read of each
|
|
464
|
+
* gets a kept read of each path that the value can read (READS.CLOSURE). Without the static reads, the list gets a
|
|
465
|
+
* read of each root of the context (READS.KEPT).
|
|
466
|
+
*
|
|
467
|
+
* @param context - The context that the value keeps.
|
|
468
|
+
* @param reads - This function gives the static reads of the value, or `undefined` when they are not known.
|
|
463
469
|
*/
|
|
464
|
-
keepsContext?(): void;
|
|
470
|
+
keepsContext?(context?: Context, reads?: () => readonly StaticRead[] | undefined): void;
|
|
465
471
|
}
|
|
466
472
|
|
|
467
473
|
/**
|
|
@@ -894,6 +900,97 @@ interface Reading {
|
|
|
894
900
|
kept: boolean;
|
|
895
901
|
/** `true` after the kernel added the read of the root `self`. */
|
|
896
902
|
self: boolean;
|
|
903
|
+
/** The paths of the kept reads of closures, as JSON texts (READS.CLOSURE). The set is made at the first such read. */
|
|
904
|
+
paths?: Set<string>;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** The walk of a kept path: the path, and what it reached when a host thing answered a step with `known`. */
|
|
908
|
+
interface KeptWalk {
|
|
909
|
+
readonly path: readonly unknown[];
|
|
910
|
+
/** What the path reached. Without it, the read resolves its path with `readAgain`. */
|
|
911
|
+
readonly reached?: Result;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* This function gives the path of a kept read (READS.CLOSURE). The evaluation did not ask for the read. Thus the read
|
|
916
|
+
* does not step into a host thing with `deref`. A host thing with `known` answers a step without new work
|
|
917
|
+
* (SCHED.CLOSURE). Each other host thing ends the path, and a host thing that does not know the step ends it too.
|
|
918
|
+
* A reference in the data reads the context of its reader, thus the function then gives `undefined`.
|
|
919
|
+
*
|
|
920
|
+
* @param nav - The world and the code of the evaluators.
|
|
921
|
+
* @param root - The context at the root of the evaluation.
|
|
922
|
+
* @param path - The root and the keys of the static read.
|
|
923
|
+
* @returns The path of the kept read with what it reached after a step of `known`, or `undefined` for a reference on the way.
|
|
924
|
+
*/
|
|
925
|
+
function keptPath(nav: Navigator, root: Context, path: readonly unknown[]): KeptWalk | undefined {
|
|
926
|
+
const adopt = nav.adopt ?? ((value: unknown): Thing => value);
|
|
927
|
+
let here = adopt(root[path[0] as Root]);
|
|
928
|
+
// This mark is `true` after a step of `known`. Then the walk gives what the path reached, and no resolution follows.
|
|
929
|
+
let known = false;
|
|
930
|
+
const end = (index: number): KeptWalk => {
|
|
931
|
+
const kept = index >= path.length ? path : path.slice(0, index);
|
|
932
|
+
return known ? { path: kept, reached: ok(here) } : { path: kept };
|
|
933
|
+
};
|
|
934
|
+
for (let index = 1; ; index += 1) {
|
|
935
|
+
if (isHostThing(here)) {
|
|
936
|
+
if (index >= path.length || here.known === undefined) return end(index);
|
|
937
|
+
let step: { readonly thing: Thing } | undefined;
|
|
938
|
+
try {
|
|
939
|
+
step = here.known(path[index] as Key);
|
|
940
|
+
} catch (error) {
|
|
941
|
+
// A failed value gives its error to the read, as an exception in `readAgain` does.
|
|
942
|
+
return { path: path.slice(0, index + 1), reached: thrown(error, READ_FAILED) };
|
|
943
|
+
}
|
|
944
|
+
if (step === undefined) return end(index);
|
|
945
|
+
known = true;
|
|
946
|
+
here = adopt(step.thing);
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (nav.plain !== true && isRef(here)) return undefined;
|
|
950
|
+
if (index >= path.length) return end(index);
|
|
951
|
+
const reached = derefStep(nav, here, path[index] as Key);
|
|
952
|
+
// A step that reaches nothing fails, and the resolution of the path fails at the same step.
|
|
953
|
+
if (reached === undefined) return known ? end(index) : { path };
|
|
954
|
+
here = adopt(reached.thing);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* This function resolves the path of a kept read again, for `verifyReads` (SCHED.CLOSURE). It walks the path as
|
|
960
|
+
* `keptPath` did. A host thing with `known` answers its step. A path without such a step resolves with `readAgain`.
|
|
961
|
+
*
|
|
962
|
+
* @param nav - The world and the code of the evaluators.
|
|
963
|
+
* @param ctx - The new context.
|
|
964
|
+
* @param path - The path of the kept read.
|
|
965
|
+
* @returns What the path reaches, or `undefined` when a host does not know a step yet. Then the read changed.
|
|
966
|
+
*/
|
|
967
|
+
export function keptAgain(nav: Navigator, ctx: Context, path: readonly unknown[]): Result | undefined {
|
|
968
|
+
const adopt = nav.adopt ?? ((value: unknown): Thing => value);
|
|
969
|
+
let here = adopt(ctx[path[0] as Root]);
|
|
970
|
+
let known = false;
|
|
971
|
+
// Before a step of `known`, each other answer of the walk is the answer of `readAgain`, as before SCHED.CLOSURE.
|
|
972
|
+
const other = (): Result | undefined => (known ? undefined : readAgain(nav, ctx, path, false).reached);
|
|
973
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
974
|
+
if (isHostThing(here)) {
|
|
975
|
+
if (here.known === undefined) return other();
|
|
976
|
+
let step: { readonly thing: Thing } | undefined;
|
|
977
|
+
try {
|
|
978
|
+
step = here.known(path[index] as Key);
|
|
979
|
+
} catch (error) {
|
|
980
|
+
return thrown(error, READ_FAILED);
|
|
981
|
+
}
|
|
982
|
+
if (step === undefined) return undefined;
|
|
983
|
+
known = true;
|
|
984
|
+
here = adopt(step.thing);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (nav.plain !== true && isRef(here)) return other();
|
|
988
|
+
const reached = derefStep(nav, here, path[index] as Key);
|
|
989
|
+
if (reached === undefined) return other();
|
|
990
|
+
here = adopt(reached.thing);
|
|
991
|
+
}
|
|
992
|
+
if (!known) return other();
|
|
993
|
+
return nav.plain !== true && isRef(here) ? undefined : ok(here);
|
|
897
994
|
}
|
|
898
995
|
|
|
899
996
|
/** This function makes the record of the reads of an evaluation, when the host gives a list. */
|
|
@@ -995,13 +1092,46 @@ class Session implements Runtime {
|
|
|
995
1092
|
return resolve(this.machine, ctx, path, hops ? WITH_HOPS : WITHOUT_HOPS);
|
|
996
1093
|
}
|
|
997
1094
|
|
|
998
|
-
keepsContext(): void {
|
|
1095
|
+
keepsContext(ctx?: Context, reads?: () => readonly StaticRead[] | undefined): void {
|
|
999
1096
|
const reading = this.reading;
|
|
1000
1097
|
if (reading === undefined || reading.kept) return;
|
|
1098
|
+
const known = ctx === undefined ? undefined : reads?.();
|
|
1099
|
+
// A value without its static reads can read each root later (READS.KEPT).
|
|
1100
|
+
if (ctx !== undefined && known !== undefined && known.every((read) => this.keepRead(reading, ctx, read))) return;
|
|
1001
1101
|
reading.kept = true;
|
|
1002
1102
|
this.keep(reading, reading.self ? ROOTS.filter((root) => root !== "self") : ROOTS);
|
|
1003
1103
|
}
|
|
1004
1104
|
|
|
1105
|
+
/**
|
|
1106
|
+
* This function adds the kept read of one static read of a closure (READS.CLOSURE). The read resolves from the
|
|
1107
|
+
* context at the root, one time in an evaluation. A name that a kind binds in the kept context gives no read,
|
|
1108
|
+
* because the reads of its binding are already in the list.
|
|
1109
|
+
*
|
|
1110
|
+
* @returns `false` when the path meets a reference in the data. Then the closure keeps each whole root.
|
|
1111
|
+
*/
|
|
1112
|
+
private keepRead(reading: Reading, ctx: Context, read: StaticRead): boolean {
|
|
1113
|
+
const root = read.root;
|
|
1114
|
+
const exact: readonly unknown[] = [root, ...read.keys];
|
|
1115
|
+
if (!isFree(reading.root, ctx, exact)) {
|
|
1116
|
+
// A kind that sets another self can give the old self on, as for a direct read (READS.KEPT).
|
|
1117
|
+
if (root === "self" && !reading.self) this.keep(reading, ["self"]);
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
// A read of the whole root self already holds each path of self.
|
|
1121
|
+
if (root === "self" && reading.self) return true;
|
|
1122
|
+
// A step document can walk outside the keys of its path, thus such a read keeps the whole root.
|
|
1123
|
+
const walk = keptPath(this.machine, reading.root, read.exact ? exact : [root]);
|
|
1124
|
+
if (walk === undefined) return false;
|
|
1125
|
+
const path = walk.path;
|
|
1126
|
+
const paths = (reading.paths ??= new Set());
|
|
1127
|
+
const text = JSON.stringify(path);
|
|
1128
|
+
if (paths.has(text) || paths.has(JSON.stringify([root]))) return true;
|
|
1129
|
+
paths.add(text);
|
|
1130
|
+
if (path.length === 1 && root === "self") reading.self = true;
|
|
1131
|
+
reading.list.push({ path, reached: walk.reached ?? readAgain(this.machine, reading.root, path, false).reached, free: true, kept: true });
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1005
1135
|
/**
|
|
1006
1136
|
* This function adds a read of each whole root to the list, from the context at the root (READS.KEPT). An exception
|
|
1007
1137
|
* of the host gives the error value of the read, because the evaluation did not ask for the read.
|
|
@@ -1338,6 +1468,15 @@ export interface PrepareOptions {
|
|
|
1338
1468
|
* evaluates one time, when the plan compiles. Its result is one frozen value for each evaluation (PLAN.FOLD).
|
|
1339
1469
|
*/
|
|
1340
1470
|
readonly fold?: boolean;
|
|
1471
|
+
/**
|
|
1472
|
+
* The values that the host declares fixed for each evaluation of the plan (PLAN.FIXED, D60). The field `names` gives
|
|
1473
|
+
* a value for each fixed name. A path from a fixed name then resolves one time, when the plan compiles, and a pure
|
|
1474
|
+
* part that reads only fixed names folds. Only a deeply frozen value of plain data fixes its name.
|
|
1475
|
+
*
|
|
1476
|
+
* Each folded part compares the value of its names with one own-key test and `Object.is`. A different value gives
|
|
1477
|
+
* the code without the fold. A plan without folds ignores this option.
|
|
1478
|
+
*/
|
|
1479
|
+
readonly fixed?: { readonly names?: Readonly<Record<string, unknown>> };
|
|
1341
1480
|
}
|
|
1342
1481
|
|
|
1343
1482
|
/** A compiled document. It evaluates many times, and it does the work of the ladder one time. */
|
|
@@ -1376,6 +1515,8 @@ interface Compiler {
|
|
|
1376
1515
|
readonly noAnswer: Answer;
|
|
1377
1516
|
/** `true` when closed pure parts fold (PLAN.FOLD). A plan with a trace does not fold. */
|
|
1378
1517
|
readonly fold: boolean;
|
|
1518
|
+
/** The fixed names and their values, or `undefined` when the plan has none (PLAN.FIXED). */
|
|
1519
|
+
readonly fixed: Readonly<Record<string, unknown>> | undefined;
|
|
1379
1520
|
/** The runtime of the folds of this compilation. The first fold makes it. */
|
|
1380
1521
|
probe?: Probe;
|
|
1381
1522
|
}
|
|
@@ -1536,7 +1677,11 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1536
1677
|
if (frees === undefined || kind === undefined || !pureCode(c, envelope, kind, typePath, name, quick)) return done;
|
|
1537
1678
|
const free = freeOf(doc, kind, split.parts, frees);
|
|
1538
1679
|
if (free === undefined) return done;
|
|
1539
|
-
|
|
1680
|
+
if (free.length === 0) return folded(c, done, undefined);
|
|
1681
|
+
// A part that reads only fixed names folds, and its code compares the values of these names (PLAN.FIXED).
|
|
1682
|
+
const fixed = c.fixed;
|
|
1683
|
+
if (fixed !== undefined && free.every((each) => Object.hasOwn(fixed, each))) return folded(c, done, free);
|
|
1684
|
+
return { code: done.code, run: done.run, free };
|
|
1540
1685
|
}
|
|
1541
1686
|
|
|
1542
1687
|
/*
|
|
@@ -1629,6 +1774,20 @@ class Probe implements Runtime {
|
|
|
1629
1774
|
this.root = root;
|
|
1630
1775
|
}
|
|
1631
1776
|
|
|
1777
|
+
/** The context of a part that folds with fixed names: the context of the probe, with the fixed names (PLAN.FIXED). */
|
|
1778
|
+
private withFixed: Context | undefined;
|
|
1779
|
+
|
|
1780
|
+
/**
|
|
1781
|
+
* This function gives the context of a part that folds with fixed names. Its root `names` holds only the fixed
|
|
1782
|
+
* names, thus the probe resolves each fixed name and refuses each other name.
|
|
1783
|
+
*
|
|
1784
|
+
* @param fixed - The fixed names and their values.
|
|
1785
|
+
* @returns The context.
|
|
1786
|
+
*/
|
|
1787
|
+
fixedRoot(fixed: Readonly<Record<string, unknown>>): Context {
|
|
1788
|
+
return (this.withFixed ??= { ...this.root, names: fixed });
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1632
1791
|
resolve(ctx: Context, path: readonly unknown[]): Resolution {
|
|
1633
1792
|
const name = path[1];
|
|
1634
1793
|
if (path[0] === "names" && isKey(name) && Object.hasOwn(ctx.names, String(name))) {
|
|
@@ -1693,25 +1852,106 @@ function trap(refuse: () => void, copies: boolean): object {
|
|
|
1693
1852
|
* one deeply frozen copy, and the code gives that copy at each evaluation (PLAN.SHARED). A fold that the probe cancels
|
|
1694
1853
|
* gives the code without free names, thus no parent tries the fold again.
|
|
1695
1854
|
*/
|
|
1696
|
-
function folded(c: Compiler, done: Compiled): Compiled {
|
|
1855
|
+
function folded(c: Compiler, done: Compiled, fixed: readonly string[] | undefined): Compiled {
|
|
1697
1856
|
const probe = (c.probe ??= new Probe(c.machine));
|
|
1698
1857
|
probe.refused = false;
|
|
1699
1858
|
probe.reads = 0;
|
|
1700
1859
|
let constant: Result | undefined;
|
|
1701
1860
|
try {
|
|
1702
|
-
const
|
|
1861
|
+
const root = fixed === undefined ? probe.root : probe.fixedRoot(c.fixed as Readonly<Record<string, unknown>>);
|
|
1862
|
+
const result = done.code(root, probe);
|
|
1703
1863
|
if (!probe.refused && (result.ok || result.error.code !== "#DEPTH!")) constant = sharedCopy(result) as Result | undefined;
|
|
1704
1864
|
} catch {
|
|
1705
1865
|
constant = undefined;
|
|
1706
1866
|
}
|
|
1707
1867
|
if (constant === undefined) return { code: done.code, run: done.run };
|
|
1708
1868
|
const value = constant;
|
|
1869
|
+
const original = done.code;
|
|
1870
|
+
if (fixed !== undefined) return { code: fixedCode(fixed, c.fixed as Readonly<Record<string, unknown>>, value, original), run: undefined, free: fixed };
|
|
1709
1871
|
if (probe.reads === 0) return { code: () => value, run: undefined, free: CLOSED };
|
|
1710
1872
|
// The part reads its own names. A list of reads gets each read, thus the code evaluates again (READS.RECORD).
|
|
1711
|
-
const original = done.code;
|
|
1712
1873
|
return { code: (ctx, rt) => (rt instanceof Session && !rt.traced && !rt.recording ? value : original(ctx, rt)), run: undefined, free: CLOSED };
|
|
1713
1874
|
}
|
|
1714
1875
|
|
|
1876
|
+
/**
|
|
1877
|
+
* This function gives the code of a part that folded with fixed names (PLAN.FIXED, D60). The code gives the folded
|
|
1878
|
+
* value when the context has the fixed value of each name, by an own-key test and `Object.is`. A fixed value is
|
|
1879
|
+
* deeply frozen, thus the same object gives the same result. Each other context, a list of reads and another runtime
|
|
1880
|
+
* get the code without the fold. The probe of a parent fold gets the folded value, and it counts a read.
|
|
1881
|
+
*/
|
|
1882
|
+
function fixedCode(names: readonly string[], fixed: Readonly<Record<string, unknown>>, value: Result, original: Code): Code {
|
|
1883
|
+
if (names.length === 1) {
|
|
1884
|
+
const name = names[0] as string;
|
|
1885
|
+
const want = fixed[name];
|
|
1886
|
+
return (ctx, rt) => {
|
|
1887
|
+
const given = ctx.names;
|
|
1888
|
+
return Object.hasOwn(given, name) && Object.is(given[name], want) && folds(rt) ? value : original(ctx, rt);
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
const wants = names.map((name) => fixed[name]);
|
|
1892
|
+
return (ctx, rt) => {
|
|
1893
|
+
const given = ctx.names;
|
|
1894
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
1895
|
+
const name = names[index] as string;
|
|
1896
|
+
if (!Object.hasOwn(given, name) || !Object.is(given[name], wants[index])) return original(ctx, rt);
|
|
1897
|
+
}
|
|
1898
|
+
return folds(rt) ? value : original(ctx, rt);
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* This function tells if the code of a part with fixed names can give its folded value in a runtime. The probe of a
|
|
1904
|
+
* fold counts the read of the part, as the code without the fold reads the names.
|
|
1905
|
+
*/
|
|
1906
|
+
function folds(rt: Runtime): boolean {
|
|
1907
|
+
if (rt instanceof Session) return !rt.recording;
|
|
1908
|
+
if (!(rt instanceof Probe)) return false;
|
|
1909
|
+
rt.reads += 1;
|
|
1910
|
+
return true;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
/**
|
|
1914
|
+
* This function gives the fixed names of the options of `prepare` (PLAN.FIXED). A name fixes only when its value is
|
|
1915
|
+
* deeply frozen plain data. Then no change in place can make the folded value stale.
|
|
1916
|
+
*
|
|
1917
|
+
* @returns A record without a prototype, or `undefined` when no name fixes.
|
|
1918
|
+
*/
|
|
1919
|
+
function fixedNamesOf(given: Readonly<Record<string, unknown>> | undefined): Readonly<Record<string, unknown>> | undefined {
|
|
1920
|
+
if (given === undefined) return undefined;
|
|
1921
|
+
let out: Record<string, unknown> | undefined;
|
|
1922
|
+
for (const name of Object.keys(given)) {
|
|
1923
|
+
const value = given[name];
|
|
1924
|
+
if (!frozenPlain(value)) continue;
|
|
1925
|
+
out ??= Object.create(null) as Record<string, unknown>;
|
|
1926
|
+
Object.defineProperty(out, name, { value, enumerable: true });
|
|
1927
|
+
}
|
|
1928
|
+
return out;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/** This function tells if a value is plain data, as `sharedCopy` reads it, and if each of its objects is frozen. */
|
|
1932
|
+
function frozenPlain(value: unknown): boolean {
|
|
1933
|
+
const seen = new Set<object>();
|
|
1934
|
+
const walk = (here: unknown): boolean => {
|
|
1935
|
+
if (typeof here === "function") return false;
|
|
1936
|
+
if (typeof here !== "object" || here === null || SHARED.has(here) || seen.has(here)) return true;
|
|
1937
|
+
if (!Object.isFrozen(here)) return false;
|
|
1938
|
+
seen.add(here);
|
|
1939
|
+
const proto: unknown = Object.getPrototypeOf(here);
|
|
1940
|
+
if (Array.isArray(here) ? proto !== Array.prototype : proto !== Object.prototype && proto !== null) return false;
|
|
1941
|
+
for (const key of Reflect.ownKeys(here)) {
|
|
1942
|
+
const field = Object.getOwnPropertyDescriptor(here, key);
|
|
1943
|
+
if (typeof key !== "string" || field === undefined || !("value" in field) || !walk(field.value)) return false;
|
|
1944
|
+
}
|
|
1945
|
+
return true;
|
|
1946
|
+
};
|
|
1947
|
+
try {
|
|
1948
|
+
return walk(value);
|
|
1949
|
+
} catch {
|
|
1950
|
+
// A very deep value overflows the stack of the host.
|
|
1951
|
+
return false;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1715
1955
|
/** The objects that a fold made. Each one is deeply frozen, thus a later fold uses it again and does not copy it. */
|
|
1716
1956
|
const SHARED: WeakSet<object> = /* @__PURE__ */ new WeakSet();
|
|
1717
1957
|
|
|
@@ -1721,9 +1961,13 @@ const NOT_PLAIN: unique symbol = /* @__PURE__ */ Symbol("not plain");
|
|
|
1721
1961
|
/**
|
|
1722
1962
|
* This function gives a deeply frozen copy of plain data, or `undefined` for other values. Plain data is a primitive or
|
|
1723
1963
|
* an array without holes and extra keys. It is also a record of enumerable data fields with the prototype of an object
|
|
1724
|
-
* or no prototype. The copy keeps cycles and shared objects. The objects of the host do not change.
|
|
1964
|
+
* or no prototype. The copy keeps cycles and shared objects. The objects of the host do not change. The tier `fixed`
|
|
1965
|
+
* of `agree` uses it. The package does not export it.
|
|
1966
|
+
*
|
|
1967
|
+
* @param value - The value.
|
|
1968
|
+
* @returns The frozen copy, or `undefined`.
|
|
1725
1969
|
*/
|
|
1726
|
-
function sharedCopy(value: unknown): unknown {
|
|
1970
|
+
export function sharedCopy(value: unknown): unknown {
|
|
1727
1971
|
try {
|
|
1728
1972
|
const out = copyPlain(value, new Map());
|
|
1729
1973
|
return out === NOT_PLAIN ? undefined : out;
|
|
@@ -1978,7 +2222,9 @@ export function prepare(machine: Machine, doc: unknown, options: PrepareOptions
|
|
|
1978
2222
|
const verb = options.verb ?? "eval";
|
|
1979
2223
|
const noAnswer: Answer = verb === "eval" ? NO_ANSWER : { verb, error: "no-answer", steps: [] };
|
|
1980
2224
|
const traced = options.trace === true;
|
|
1981
|
-
const
|
|
2225
|
+
const fold = !traced && options.fold !== false;
|
|
2226
|
+
const fixed = fold ? fixedNamesOf(options.fixed?.names) : undefined;
|
|
2227
|
+
const c: Compiler = { machine, traced, maxDepth: machine.maxDepth ?? 1000, verb, noAnswer, fold, fixed };
|
|
1982
2228
|
let root: Compiled;
|
|
1983
2229
|
try {
|
|
1984
2230
|
root = compileNode(c, doc, [], options.declaredType, 0);
|
package/src/formula.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type MachinePart, mergeMachines } from "./merge.ts";
|
|
|
7
7
|
import { type Domain, type ValueType, HEADER_KEYS, STD_DOMAINS, callable, fieldOf, fits, fnOf, holdsEnvelope, isPrecise, isUnknown, itemOf, join, member, stepType, typeText } from "./types.ts";
|
|
8
8
|
import type { TypeInput, TypeRule } from "./typing.ts";
|
|
9
9
|
import { decodeDomain, domainTypeOf } from "./codec.ts";
|
|
10
|
+
import { type StaticRead, factsOf } from "./facts.ts";
|
|
10
11
|
|
|
11
12
|
/** An operation of a formula language: a function of the values of its arguments. */
|
|
12
13
|
export type OpFn = (...args: never[]) => unknown;
|
|
@@ -777,7 +778,7 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
777
778
|
};
|
|
778
779
|
}),
|
|
779
780
|
|
|
780
|
-
"formula.fn": compiled(({ value, data }) => {
|
|
781
|
+
"formula.fn": compiled(({ value, data, doc, machine }) => {
|
|
781
782
|
const params = isRecord(data) ? data["params"] : undefined;
|
|
782
783
|
if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) {
|
|
783
784
|
const bad = fail("#VALUE!", "The params of fn are a list of different texts.");
|
|
@@ -785,9 +786,22 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
785
786
|
}
|
|
786
787
|
const names = params as readonly string[];
|
|
787
788
|
const body = partsOf(value)["body"] as CompiledPart;
|
|
789
|
+
// The static reads of the function come at the first closure of an evaluation with a list of reads.
|
|
790
|
+
let known: readonly StaticRead[] | undefined | null = null;
|
|
791
|
+
const reads = (): readonly StaticRead[] | undefined => {
|
|
792
|
+
if (known !== null) return known;
|
|
793
|
+
// Only a pure document reads its context through the runtime alone (FACTS.PURE). Each other closure keeps each root.
|
|
794
|
+
try {
|
|
795
|
+
const facts = factsOf(machine, doc);
|
|
796
|
+
known = facts.pure ? facts.reads : undefined;
|
|
797
|
+
} catch {
|
|
798
|
+
known = undefined;
|
|
799
|
+
}
|
|
800
|
+
return known;
|
|
801
|
+
};
|
|
788
802
|
return (ctx, rt) => {
|
|
789
|
-
// A closure keeps the context for each later use, thus the list of reads gets each
|
|
790
|
-
rt.keepsContext?.();
|
|
803
|
+
// A closure keeps the context for each later use, thus the list of reads gets each path that it can read (READS.CLOSURE).
|
|
804
|
+
rt.keepsContext?.(ctx, reads);
|
|
791
805
|
const closure = (...args: readonly unknown[]): unknown => {
|
|
792
806
|
const scope: Record<string, unknown> = { ...ctx.names };
|
|
793
807
|
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
package/src/index.ts
CHANGED
|
@@ -176,6 +176,7 @@ export {
|
|
|
176
176
|
STD_WORLD,
|
|
177
177
|
attributeTrace,
|
|
178
178
|
nearestWalker,
|
|
179
|
+
placeOf,
|
|
179
180
|
} from "./stdlib.ts";
|
|
180
181
|
export {
|
|
181
182
|
type Edge,
|
|
@@ -250,8 +251,8 @@ export {
|
|
|
250
251
|
print,
|
|
251
252
|
syntaxFormat,
|
|
252
253
|
} from "./syntax.ts";
|
|
253
|
-
export { type Definitions, type ReferenceOptions, STD_DEFINITIONS, formulaDefinitions, reference } from "./reference.ts";
|
|
254
|
-
export { type AgreeOptions, type Agreement, type Tier, type TierInput, type TierResult, agree, sameValue } from "./agree.ts";
|
|
254
|
+
export { type Definitions, type ReferenceOptions, type ReferenceRunOptions, STD_DEFINITIONS, formulaDefinitions, reference, referenceRun } from "./reference.ts";
|
|
255
|
+
export { type AgreeOptions, type AgreeRunOptions, type Agreement, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
|
|
255
256
|
export { type ConformOptions, type ConformProbe, type Finding, type Obligation, conforms } from "./conform.ts";
|
|
256
257
|
export { sameResult } from "./same.ts";
|
|
257
258
|
export { type ReadsCheck, type VerifyOptions, verifyReads } from "./reads.ts";
|
|
@@ -266,6 +267,15 @@ export {
|
|
|
266
267
|
edgesOf,
|
|
267
268
|
schedule,
|
|
268
269
|
} from "./schedule.ts";
|
|
270
|
+
export {
|
|
271
|
+
type AgreeScheduleOptions,
|
|
272
|
+
type ScheduleAgreement,
|
|
273
|
+
type ScheduleAgreementCounts,
|
|
274
|
+
type ScheduleEngine,
|
|
275
|
+
type ScheduleEngineFactory,
|
|
276
|
+
type ScheduleEngineStart,
|
|
277
|
+
agreeSchedule,
|
|
278
|
+
} from "./meaning.ts";
|
|
269
279
|
export { type GraphCycle, type GraphEdge, type GraphNode, type GraphOptions, type NodeGraph, components, cycles, heights, necessary, nodeLayers } from "./graph.ts";
|
|
270
280
|
export { type Recorder, type ReplayEntry, type ReplayLog, type ReplayOptions, type ReplaySettle, recorder, replayer } from "./replay.ts";
|
|
271
281
|
export { type GenerateOptions, type Random, generate, random, typesOf } from "./generate.ts";
|