@mark1russell7/coeval 0.17.0 → 0.19.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/README.md +4 -4
- package/build/index.d.ts +302 -98
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3555 -2068
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/codec.ts +30 -10
- package/src/conform.ts +35 -9
- package/src/durable.ts +17 -5
- package/src/evaluate.ts +38 -2
- package/src/facts.ts +55 -19
- package/src/formula.ts +59 -4
- package/src/index.ts +16 -2
- package/src/instances.ts +637 -0
- package/src/merge.ts +6 -3
- package/src/reads.ts +186 -2
- package/src/reference.ts +20 -1
- package/src/replay.ts +123 -35
- package/src/static.ts +357 -50
- package/src/stdlib.ts +127 -6
- package/src/types.ts +103 -23
- package/src/typing.ts +143 -3
- package/src/world.ts +49 -0
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.19.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",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"build",
|
|
17
17
|
"src",
|
|
18
|
-
"!src/**/*.test.ts"
|
|
18
|
+
"!src/**/*.test.ts",
|
|
19
|
+
"!src/**/*.test-support.ts"
|
|
19
20
|
],
|
|
20
21
|
"sideEffects": false,
|
|
21
22
|
"license": "MIT",
|
package/src/codec.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Json, isEnvelope, isRecord } from "./doc.ts";
|
|
2
2
|
import { type Result, describe, fail, ok } from "./errors.ts";
|
|
3
3
|
import { type Machine, makeContext, prepare } from "./evaluate.ts";
|
|
4
|
-
import { type Domain, type DomainCodec, domainOf, member } from "./types.ts";
|
|
4
|
+
import { type Domain, type DomainCodec, domainOf, lineOf, member } from "./types.ts";
|
|
5
5
|
|
|
6
6
|
/*
|
|
7
7
|
* The codec of the values that JSON cannot write (K140, D40). A document holds such a value as a standard extension:
|
|
@@ -71,7 +71,7 @@ function setOwn(target: Record<string, Json>, key: string, value: Json): void {
|
|
|
71
71
|
/** The state of one call of `encodeValue`. */
|
|
72
72
|
interface Encoder {
|
|
73
73
|
readonly domains: Domains;
|
|
74
|
-
/** The domains with a codec, in the order of
|
|
74
|
+
/** The domains with a codec, in the order of `codecOrder`. */
|
|
75
75
|
readonly codecs: readonly (readonly [string, DomainCodec])[];
|
|
76
76
|
/** The answer of `isJson` for each container that it examined. */
|
|
77
77
|
readonly json: WeakMap<object, boolean>;
|
|
@@ -151,7 +151,8 @@ function encodeTerm(value: unknown, encoder: Encoder): Json {
|
|
|
151
151
|
*
|
|
152
152
|
* - A value that JSON writes exactly stays as it is. A literal with the shape of an envelope goes into `lit`.
|
|
153
153
|
* - `NaN`, an infinity and `-0` become `ext.number`, and `undefined` becomes `ext.undefined`.
|
|
154
|
-
* - A member of a domain with a codec becomes `ext.domain`. The
|
|
154
|
+
* - A member of a domain with a codec becomes `ext.domain`. The codec of a domain under another domain comes first
|
|
155
|
+
* (EXT.SPECIFIC, D109). The order of the names decides between unrelated domains.
|
|
155
156
|
* - A list or a plain record with such a value becomes the construct `list` or `record`.
|
|
156
157
|
*
|
|
157
158
|
* @param machine - The machine, or only its domains.
|
|
@@ -161,15 +162,34 @@ function encodeTerm(value: unknown, encoder: Encoder): Json {
|
|
|
161
162
|
*/
|
|
162
163
|
export function encodeValue(machine: Pick<Machine, "domains">, value: unknown): Json {
|
|
163
164
|
const domains = machine.domains;
|
|
165
|
+
return encodeTerm(value, { domains, codecs: codecOrder(domains), json: new WeakMap(), open: new Set() });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* This function gives the domains with a codec in the order of `encodeValue` (EXT.SPECIFIC, D109). The depth of a
|
|
170
|
+
* domain is the count of the other domains with a codec in its line. A deeper domain comes first, and the order of the
|
|
171
|
+
* names decides between domains of the same depth (EXT.TIE). Thus a domain comes before each domain with a codec above it.
|
|
172
|
+
*/
|
|
173
|
+
function codecOrder(domains: Domains): readonly (readonly [string, DomainCodec])[] {
|
|
164
174
|
const codecs: (readonly [string, DomainCodec])[] = [];
|
|
165
|
-
if (domains
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
if (domains === undefined) return codecs;
|
|
176
|
+
let parents = false;
|
|
177
|
+
for (const name of Object.keys(domains).sort()) {
|
|
178
|
+
const domain = domainOf(name, domains);
|
|
179
|
+
const codec = domain === undefined ? undefined : codecOf(domain);
|
|
180
|
+
if (domain === undefined || codec === undefined) continue;
|
|
181
|
+
codecs.push([name, codec]);
|
|
182
|
+
if (Array.isArray(domain.under)) parents = true;
|
|
183
|
+
}
|
|
184
|
+
// Without a parent of a domain with a codec, each depth is zero, thus the order of the names stays.
|
|
185
|
+
if (!parents) return codecs;
|
|
186
|
+
const depth = new Map<string, number>();
|
|
187
|
+
for (const [name] of codecs) {
|
|
188
|
+
const line = lineOf(name, domains);
|
|
189
|
+
depth.set(name, codecs.filter(([other]) => other !== name && line.has(other)).length);
|
|
171
190
|
}
|
|
172
|
-
|
|
191
|
+
// The sort is stable, thus the order of the names stays between domains of the same depth.
|
|
192
|
+
return codecs.sort(([a], [b]) => (depth.get(b) as number) - (depth.get(a) as number));
|
|
173
193
|
}
|
|
174
194
|
|
|
175
195
|
/**
|
package/src/conform.ts
CHANGED
|
@@ -33,7 +33,7 @@ import { type Definitions, STD_DEFINITIONS } from "./reference.ts";
|
|
|
33
33
|
import { STD_EVALUATORS } from "./stdlib.ts";
|
|
34
34
|
import { sameValue } from "./agree.ts";
|
|
35
35
|
import { declaredTruthOf } from "./formula.ts";
|
|
36
|
-
import { membersOf, typeIssues, typeText } from "./types.ts";
|
|
36
|
+
import { falseCovers, membersOf, typeIssues, typeText } from "./types.ts";
|
|
37
37
|
import { type Random, random } from "./generate.ts";
|
|
38
38
|
import { type Kind, type LazyRule, type Shape, type World, type WorldEntry, effectsOfVerb, isKind, isNamespace, lookupKind } from "./world.ts";
|
|
39
39
|
|
|
@@ -60,8 +60,10 @@ import { type Kind, type LazyRule, type Shape, type World, type WorldEntry, effe
|
|
|
60
60
|
* - `agree`: compiled code gives the result of its definition for the reference interpreter.
|
|
61
61
|
* - `truth`: the own `truthy` of a formula profile gives `true` or `false` for each member of its `truthType` (D86).
|
|
62
62
|
* Only `formula.if`, `formula.and` and `formula.or` of the formula kit get this check.
|
|
63
|
+
* - `cover`: each sample of a covered domain of the machine is in one domain of each cover of the domain (D94). Each
|
|
64
|
+
* evaluator and each runner gets this check, because `fits` trusts the covers for each program.
|
|
63
65
|
*/
|
|
64
|
-
export type Obligation = "C1" | "C2" | "C3" | "C4" | "C5" | "C6" | "C7" | "C8" | "local" | "agree" | "truth";
|
|
66
|
+
export type Obligation = "C1" | "C2" | "C3" | "C4" | "C5" | "C6" | "C7" | "C8" | "local" | "agree" | "truth" | "cover";
|
|
65
67
|
|
|
66
68
|
/** The probe of a finding: the document, the result of each probe part, the capabilities and the handler. */
|
|
67
69
|
export interface ConformProbe {
|
|
@@ -136,7 +138,7 @@ export interface ConformOptions {
|
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
/** The order of the obligations in the list of findings. */
|
|
139
|
-
const ORDER: readonly Obligation[] = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "local", "agree", "truth"];
|
|
141
|
+
const ORDER: readonly Obligation[] = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "local", "agree", "truth", "cover"];
|
|
140
142
|
|
|
141
143
|
/** A function in the results of the probe parts. It gives the count of its arguments. */
|
|
142
144
|
const PROBE_FN = (...args: readonly unknown[]): number => args.length;
|
|
@@ -1032,7 +1034,8 @@ function examine(env: Env, sample: Sample, contract: Contract): void {
|
|
|
1032
1034
|
/**
|
|
1033
1035
|
* This function checks the declared truth of a profile (D86). The function `truthy` must give `true` or `false` for
|
|
1034
1036
|
* each member of `truthType`, and it must not throw. The members come from the lists `values` of the domains of the
|
|
1035
|
-
* type. A type without such a list gets
|
|
1037
|
+
* type. A type without such a list gets its members from the results of the palette and from the samples of the
|
|
1038
|
+
* domains (D99).
|
|
1036
1039
|
*/
|
|
1037
1040
|
function checkTruth(env: Env, code: unknown, probe: (value: unknown) => ConformProbe): void {
|
|
1038
1041
|
const truth = declaredTruthOf(code);
|
|
@@ -1057,6 +1060,27 @@ function checkTruth(env: Env, code: unknown, probe: (value: unknown) => ConformP
|
|
|
1057
1060
|
}
|
|
1058
1061
|
}
|
|
1059
1062
|
|
|
1063
|
+
/**
|
|
1064
|
+
* This function checks the covers of the domains of the machine from samples (D94). Each sample of a covered domain
|
|
1065
|
+
* must be in one domain of each valid cover. The samples of a domain come from its list `values`. A domain without
|
|
1066
|
+
* that list gets its members from the palette and from the samples of the domains, as for the check `truth`.
|
|
1067
|
+
*/
|
|
1068
|
+
function checkCovers(env: Env, probe: (value: unknown) => ConformProbe): void {
|
|
1069
|
+
const domains = env.machine.domains;
|
|
1070
|
+
if (domains === undefined) return;
|
|
1071
|
+
const palette = env.results.flatMap((result) => (result.ok ? [result.value] : []));
|
|
1072
|
+
const samples: Record<string, readonly unknown[]> = {};
|
|
1073
|
+
for (const [name, domain] of Object.entries(domains)) if (domain.covers !== undefined) samples[name] = membersOf(name, domains, palette);
|
|
1074
|
+
const found = falseCovers(domains, samples)[0];
|
|
1075
|
+
if (found === undefined) return;
|
|
1076
|
+
report(
|
|
1077
|
+
env,
|
|
1078
|
+
"cover",
|
|
1079
|
+
`The member ${valueText(found.value)} of the domain ${found.domain} is in no domain of its cover ${found.cover.join(", ")}. The check of types trusts the cover, thus it is unsound for ${found.domain}.`,
|
|
1080
|
+
() => probe(found.value),
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1060
1084
|
/**
|
|
1061
1085
|
* This function examines one evaluator or runner of a machine against the obligations of host code (K92, D36). It
|
|
1062
1086
|
* starts the code directly, without the kernel, with probe parts and a probe runtime. It generates probe documents from
|
|
@@ -1067,7 +1091,8 @@ function checkTruth(env: Env, code: unknown, probe: (value: unknown) => ConformP
|
|
|
1067
1091
|
* compiled code with its definition. The finding of an obligation names the first probe that breaks it. An empty list
|
|
1068
1092
|
* means that each probe conforms.
|
|
1069
1093
|
*
|
|
1070
|
-
* The check `truth` tests the declared truth of a formula profile (D86).
|
|
1094
|
+
* The check `truth` tests the declared truth of a formula profile (D86). The check `cover` tests the covers of the
|
|
1095
|
+
* domains of the machine from samples (D94).
|
|
1071
1096
|
*
|
|
1072
1097
|
* @param machine - The machine with the code and the world of the kinds.
|
|
1073
1098
|
* @param name - The name of the evaluator or of the runner. A runner wins over an evaluator of the same name.
|
|
@@ -1110,11 +1135,12 @@ export function conforms(machine: Machine, name: string, options: ConformOptions
|
|
|
1110
1135
|
examine(env, sample, contract);
|
|
1111
1136
|
}
|
|
1112
1137
|
}
|
|
1113
|
-
// A probe of the truth gives the
|
|
1114
|
-
|
|
1138
|
+
// A probe of the truth or of a cover gives the value to each part of the first probe document.
|
|
1139
|
+
const probeWith = (value: unknown): ConformProbe =>
|
|
1115
1140
|
first === undefined
|
|
1116
1141
|
? { type: typeName(types[0] as TypeRef), doc: { type: types[0] as TypeRef, value: null }, results: {}, waits: [], caps: NO_CAPS, running: runner }
|
|
1117
|
-
: probeOf(first, { results: first.split.parts.map(() => ok(value)), waits: [], caps: NO_CAPS, running: runner })
|
|
1118
|
-
);
|
|
1142
|
+
: probeOf(first, { results: first.split.parts.map(() => ok(value)), waits: [], caps: NO_CAPS, running: runner });
|
|
1143
|
+
checkTruth(env, code, probeWith);
|
|
1144
|
+
checkCovers(env, probeWith);
|
|
1119
1145
|
return ORDER.filter((obligation) => env.found.has(obligation)).map((obligation) => env.found.get(obligation) as Finding);
|
|
1120
1146
|
}
|
package/src/durable.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Context } from "./deref.ts";
|
|
|
2
2
|
import type { Result } from "./errors.ts";
|
|
3
3
|
import { type Machine, type Plan, type PrepareOptions, type Read, prepare } from "./evaluate.ts";
|
|
4
4
|
import { type DocFacts, type FactsOptions, factsOf } from "./facts.ts";
|
|
5
|
-
import { type VerifyOptions, verifyReads } from "./reads.ts";
|
|
5
|
+
import { type PrintOptions, type ReadPrints, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
8
|
* Two levels of durability (R4 P7, D30). The plan and the facts of a document depend only on the structure: the
|
|
@@ -42,6 +42,11 @@ export interface DurableOptions {
|
|
|
42
42
|
readonly volatile?: FactsOptions["volatile"];
|
|
43
43
|
/** The rule of the host for two things, as in `verifyReads`. */
|
|
44
44
|
readonly equal?: VerifyOptions["equal"];
|
|
45
|
+
/**
|
|
46
|
+
* The check of changes in place (READS.IN-PLACE, D112). With it, the cache keeps the prints of the reads of each
|
|
47
|
+
* pure document, and a verification compares them. `true` gives the default limit. Without it, the cache makes no prints.
|
|
48
|
+
*/
|
|
49
|
+
readonly inPlace?: true | PrintOptions;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
@@ -154,10 +159,11 @@ function byDocument<T>(make: (doc: unknown) => T): (doc: unknown) => T {
|
|
|
154
159
|
* and each result (DURABLE.STRUCTURE).
|
|
155
160
|
*
|
|
156
161
|
* The host keeps two promises. In one data epoch, the context of a key gives the same data. The values of a
|
|
157
|
-
* context and the documents do not change in place, as PLAN.CACHE and `verifyReads` say.
|
|
162
|
+
* context and the documents do not change in place, as PLAN.CACHE and `verifyReads` say. The option `inPlace`
|
|
163
|
+
* finds a change in place of a value that a pure document read (READS.IN-PLACE).
|
|
158
164
|
*
|
|
159
165
|
* @param machine - The machine of the first structure epoch.
|
|
160
|
-
* @param options - The type of a bare root value, the verb, the volatile reads
|
|
166
|
+
* @param options - The type of a bare root value, the verb, the volatile reads, the rule of the host and the check of changes in place.
|
|
161
167
|
* @returns The cache.
|
|
162
168
|
*/
|
|
163
169
|
export function durable(machine: Machine, options: DurableOptions = {}): Durable {
|
|
@@ -167,6 +173,9 @@ export function durable(machine: Machine, options: DurableOptions = {}): Durable
|
|
|
167
173
|
};
|
|
168
174
|
const factsOptions: FactsOptions = { ...prepareOptions, ...(options.volatile === undefined ? {} : { volatile: options.volatile }) };
|
|
169
175
|
const verifyOptions: VerifyOptions = options.equal === undefined ? {} : { equal: options.equal };
|
|
176
|
+
const printOptions: PrintOptions | undefined = options.inPlace === true ? {} : options.inPlace;
|
|
177
|
+
// The prints of the reads of each entry, only with the option `inPlace`. Thus an entry keeps its shape (PLAN.LEAN).
|
|
178
|
+
const printsOf = printOptions === undefined ? undefined : new WeakMap<Entry, ReadPrints>();
|
|
170
179
|
const counts = { compiles: 0, facts: 0, evaluations: 0, verifications: 0, reuses: 0 };
|
|
171
180
|
const results = new Map<unknown, Entry>();
|
|
172
181
|
let current = machine;
|
|
@@ -205,7 +214,8 @@ export function durable(machine: Machine, options: DurableOptions = {}): Durable
|
|
|
205
214
|
}
|
|
206
215
|
if (entry.reads !== undefined) {
|
|
207
216
|
counts.verifications += 1;
|
|
208
|
-
|
|
217
|
+
const prints = printsOf?.get(entry);
|
|
218
|
+
if (verifyReads(plan, entry.reads, context, prints === undefined ? verifyOptions : { ...verifyOptions, prints }).same) {
|
|
209
219
|
entry.data = epochs.data;
|
|
210
220
|
counts.reuses += 1;
|
|
211
221
|
return entry.result;
|
|
@@ -217,7 +227,9 @@ export function durable(machine: Machine, options: DurableOptions = {}): Durable
|
|
|
217
227
|
counts.evaluations += 1;
|
|
218
228
|
const result = plan.evaluate(context, reads === undefined ? undefined : { reads });
|
|
219
229
|
// A list that grows by push keeps room for more reads. A copy of a short list is smaller (PLAN.LEAN).
|
|
220
|
-
|
|
230
|
+
const made: Entry = { plan, result, reads: reads === undefined ? undefined : reads.length === 0 ? NO_READS : reads.slice(), data: epochs.data };
|
|
231
|
+
results.set(key, made);
|
|
232
|
+
if (printsOf !== undefined && made.reads !== undefined) printsOf.set(made, printReads(made.reads, printOptions));
|
|
221
233
|
return result;
|
|
222
234
|
},
|
|
223
235
|
changeData() {
|
package/src/evaluate.ts
CHANGED
|
@@ -1053,6 +1053,11 @@ class Session implements Runtime {
|
|
|
1053
1053
|
private list: Effect[] | undefined;
|
|
1054
1054
|
tick = 0;
|
|
1055
1055
|
nextId = 0;
|
|
1056
|
+
/**
|
|
1057
|
+
* The count of the drives without a wait that are active at this time. In a run, an effect under such a drive does
|
|
1058
|
+
* not reach the host (RUN.HELD).
|
|
1059
|
+
*/
|
|
1060
|
+
held = 0;
|
|
1056
1061
|
current: Building | undefined;
|
|
1057
1062
|
readonly holder: Building | undefined;
|
|
1058
1063
|
/**
|
|
@@ -1265,6 +1270,13 @@ class Session implements Runtime {
|
|
|
1265
1270
|
if ("ok" in made) return made;
|
|
1266
1271
|
const perform = this.perform;
|
|
1267
1272
|
if (!this.running || perform === undefined) return this.answer(made, place);
|
|
1273
|
+
if (this.held > 0) {
|
|
1274
|
+
// The parent cannot wait for the answer of the host, thus the host does not get the effect (RUN.HELD).
|
|
1275
|
+
made.outcome = fail("#CALC!", heldReason(made.kind), { place });
|
|
1276
|
+
// The drive stops at this wait, and gives the #CALC! of the runner.
|
|
1277
|
+
yield HELD;
|
|
1278
|
+
return made.outcome;
|
|
1279
|
+
}
|
|
1268
1280
|
let done: unknown;
|
|
1269
1281
|
try {
|
|
1270
1282
|
done = perform(made);
|
|
@@ -1452,6 +1464,21 @@ export function waitedReason(kindText: string): string {
|
|
|
1452
1464
|
return `${kindText} waits for effects, and its parent cannot wait. Give the parent a runner.`;
|
|
1453
1465
|
}
|
|
1454
1466
|
|
|
1467
|
+
/**
|
|
1468
|
+
* This function gives the reason of an effect that a run does not give to the host: the parent of its runner cannot
|
|
1469
|
+
* wait (RUN.HELD). The plan and the reference give the same reason.
|
|
1470
|
+
*
|
|
1471
|
+
* @param kind - The kind of the effect.
|
|
1472
|
+
* @returns The reason.
|
|
1473
|
+
*/
|
|
1474
|
+
export function heldReason(kind: Cap): string {
|
|
1475
|
+
const text = typeof kind === "string" ? kind : kind.join(".");
|
|
1476
|
+
return `The parent of the runner cannot wait, thus the host does not get the effect ${text}.`;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/** The wait of an effect that a run does not give to the host. A drive without a wait stops at it (RUN.HELD). */
|
|
1480
|
+
const HELD: Promise<unknown> = /* @__PURE__ */ Promise.resolve(undefined);
|
|
1481
|
+
|
|
1455
1482
|
/** This function drives steps that cannot wait. A step that waits stops the steps, and gives `#CALC!`. */
|
|
1456
1483
|
function drive(steps: Steps, kindText: string): Result {
|
|
1457
1484
|
const step = steps.next();
|
|
@@ -2148,10 +2175,19 @@ function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode
|
|
|
2148
2175
|
/**
|
|
2149
2176
|
* This function gives the code of a runner under a parent that cannot wait: its steps, driven without a wait. An
|
|
2150
2177
|
* evaluation and a run both drive them (RUN.DRIVE-NESTED). Only a step that really waits gives `#CALC!`, at the place
|
|
2151
|
-
* of the runner, where the error started (RUN.WAIT).
|
|
2178
|
+
* of the runner, where the error started (RUN.WAIT). In a run, an effect under the runner does not reach the host,
|
|
2179
|
+
* because the parent cannot wait for its answer (RUN.HELD).
|
|
2152
2180
|
*/
|
|
2153
2181
|
function runnerCode(run: RunCode, place: readonly Key[], kindText: string): Code {
|
|
2154
|
-
return (ctx, rt) =>
|
|
2182
|
+
return (ctx, rt) => {
|
|
2183
|
+
if (!rt.running || !(rt instanceof Session)) return placed(drive(run(ctx, rt), kindText), place);
|
|
2184
|
+
rt.held += 1;
|
|
2185
|
+
try {
|
|
2186
|
+
return placed(drive(run(ctx, rt), kindText), place);
|
|
2187
|
+
} finally {
|
|
2188
|
+
rt.held -= 1;
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2155
2191
|
}
|
|
2156
2192
|
|
|
2157
2193
|
/**
|
package/src/facts.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Json, type Key, isEnvelope, isKey } from "./doc.ts";
|
|
2
|
-
import { type Kind, type LazyRule, type Side, type World, equationType, lookupKind } from "./world.ts";
|
|
2
|
+
import { type Kind, type KindFacts, type LazyRule, type Side, type World, equationType, lookupKind } from "./world.ts";
|
|
3
3
|
import { answerOf } from "./ladder.ts";
|
|
4
4
|
import { pathMatches, splitParts } from "./parts.ts";
|
|
5
5
|
import { type Root, ROOTS, isRef } from "./deref.ts";
|
|
@@ -43,9 +43,12 @@ export interface DocFacts {
|
|
|
43
43
|
* here. The roots that a kind declares in `reads` are here too, each one at the place of its document.
|
|
44
44
|
*/
|
|
45
45
|
readonly reads: readonly StaticRead[];
|
|
46
|
-
/** `"run"` when one kind of the document is on the run side (FACTS.SIDE). */
|
|
46
|
+
/** `"run"` when one kind of the document is on the run side for the verb of the facts (FACTS.SIDE, FACTS.VERB-SIDE). */
|
|
47
47
|
readonly side: Side;
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The kinds of the effects that the contracts declare for the verb of the facts, as texts with dots, each one time
|
|
50
|
+
* (FACTS.EFFECTS, FACTS.VERB-EFFECTS).
|
|
51
|
+
*/
|
|
49
52
|
readonly effects: readonly string[];
|
|
50
53
|
/**
|
|
51
54
|
* `true` when each kind of the document declares `pure`, and the document has no effect and is not volatile. Then
|
|
@@ -63,8 +66,10 @@ export interface FactsOptions {
|
|
|
63
66
|
/** The type of a bare root value. */
|
|
64
67
|
readonly declaredType?: string;
|
|
65
68
|
/**
|
|
66
|
-
* The verb whose evaluators the facts describe, as in `prepare`. The default is `eval`. The
|
|
67
|
-
*
|
|
69
|
+
* The verb whose evaluators the facts describe, as in `prepare`. The default is `eval`. The marks `pure`,
|
|
70
|
+
* `volatile` and `lazy` of a contract describe the code of `eval` only. For another verb, only the hook `facts`
|
|
71
|
+
* describes the code (FACTS.VERB). The side and the effects come from the entry of the verb in `verbEffects`
|
|
72
|
+
* (FACTS.VERB-EFFECTS, D111).
|
|
68
73
|
*/
|
|
69
74
|
readonly verb?: string;
|
|
70
75
|
/**
|
|
@@ -92,7 +97,7 @@ function tight<T>(list: readonly T[]): readonly T[] {
|
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
/** What a document gives to its parts: its own lazy mark, and the lazy rules of its kind for the parts. */
|
|
95
|
-
interface Above {
|
|
100
|
+
export interface Above {
|
|
96
101
|
readonly lazy: boolean;
|
|
97
102
|
readonly rules: readonly LazyRule[] | typeof EACH | undefined;
|
|
98
103
|
/** The position of each part among the parts that a rule `after` covers. It is made at the first need. */
|
|
@@ -136,7 +141,7 @@ function positionOf(above: Above, parent: DocVisit, rule: LazyRule, slot: readon
|
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
/** This function tells if the lazy rules of a document cover one of its parts. */
|
|
139
|
-
function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean {
|
|
144
|
+
export function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean {
|
|
140
145
|
const rules = above.rules;
|
|
141
146
|
if (rules === undefined) return false;
|
|
142
147
|
if (rules === EACH) return true;
|
|
@@ -148,6 +153,32 @@ function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean
|
|
|
148
153
|
return false;
|
|
149
154
|
}
|
|
150
155
|
|
|
156
|
+
/** The code of a document for the facts: the answer of the hook, and the lazy rules that the code gives its parts. */
|
|
157
|
+
export interface CodeFacts {
|
|
158
|
+
/** `true` when the contract of the kind describes the code. */
|
|
159
|
+
readonly described: boolean;
|
|
160
|
+
/** The answer of the hook `facts` of the machine for the code. */
|
|
161
|
+
readonly answer: KindFacts | undefined;
|
|
162
|
+
/** The lazy rules of the parts (FACTS.LAZY). Code that the contract and the hook do not describe makes each part lazy. */
|
|
163
|
+
readonly rules: Above["rules"];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* This function gives the code of a document of a kind for the facts (FACTS.LAZY). `factsOf` and the graph of
|
|
168
|
+
* attribute instances read the lazy rules with this one function (D107).
|
|
169
|
+
*
|
|
170
|
+
* @param machine - The world and the hook `facts`.
|
|
171
|
+
* @param v - The visit of the document.
|
|
172
|
+
* @param kind - The kind of the document.
|
|
173
|
+
* @param verb - The verb whose code the facts describe.
|
|
174
|
+
* @returns The answer of the hook and the lazy rules.
|
|
175
|
+
*/
|
|
176
|
+
export function codeFactsOf(machine: Machine, v: DocVisit, kind: Kind, verb: string): CodeFacts {
|
|
177
|
+
const code = codeOf(machine.world, v.declared ? undefined : v.doc.own, kind, verb, "eval");
|
|
178
|
+
const answer = code.name === undefined ? undefined : machine.facts?.(v.typePath ?? [], code.name);
|
|
179
|
+
return { described: code.described, answer, rules: code.described ? (answer?.lazy ?? kind.lazy) : (answer?.lazy ?? EACH) };
|
|
180
|
+
}
|
|
181
|
+
|
|
151
182
|
/**
|
|
152
183
|
* This function gives the static facts of a document, for a schedule (R4 P2, D24). It walks the document one time,
|
|
153
184
|
* by the contracts of its kinds:
|
|
@@ -157,7 +188,7 @@ function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean
|
|
|
157
188
|
* declares `reads` adds a read of each of its roots at each document of the kind. The first document of a tree
|
|
158
189
|
* kind also gets the lazy reads of the equations of its kind (FACTS.EQUATIONS). A read from `self` or from a name
|
|
159
190
|
* of a rung is not one of them, because it is not free (READS.EQUATION).
|
|
160
|
-
* - `side` and `effects`: from the contract of each kind.
|
|
191
|
+
* - `side` and `effects`: from the contract of each kind, for the verb of the facts.
|
|
161
192
|
* - `pure`: each kind declares `pure`, also in an equation, and the document has no effect and is not volatile.
|
|
162
193
|
* - `volatile`: a kind, an operation or a step document declares `volatile`, or the host marks a read.
|
|
163
194
|
*
|
|
@@ -166,9 +197,11 @@ function partLazy(above: Above, parent: DocVisit, slot: readonly Key[]): boolean
|
|
|
166
197
|
* entry. Thus a document with an own entry for another evaluator is not pure, and each of its parts is lazy. The
|
|
167
198
|
* hook can still answer for that evaluator.
|
|
168
199
|
*
|
|
169
|
-
* The option `verb` names the verb whose code the facts describe (FACTS.VERB).
|
|
170
|
-
* `eval`, thus the rule of an own entry applies to each document for another verb.
|
|
171
|
-
*
|
|
200
|
+
* The option `verb` names the verb whose code the facts describe (FACTS.VERB). The marks of a contract describe the
|
|
201
|
+
* code of `eval`, thus the rule of an own entry applies to each document for another verb. For another verb, the
|
|
202
|
+
* effects are the effects that the entry of the verb in `verbEffects` declares (FACTS.VERB-EFFECTS, D111). A kind is
|
|
203
|
+
* on the run side for that verb when its entry declares an effect (FACTS.VERB-SIDE). A kind without an entry for the
|
|
204
|
+
* verb adds no effect, as in `check` (FACTS.VERB-UNDECLARED). Its code is still not pure without an answer of the hook.
|
|
172
205
|
*
|
|
173
206
|
* A read is one resolution of its path. A reference that the resolution reaches and follows is a part of that read
|
|
174
207
|
* (DEREF.COLLAPSE), not a separate read. The facts depend only on the document and the machine. Thus a host computes
|
|
@@ -220,17 +253,15 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
|
|
|
220
253
|
above.set(v, { lazy, rules: undefined });
|
|
221
254
|
return undefined;
|
|
222
255
|
}
|
|
223
|
-
const
|
|
224
|
-
|
|
256
|
+
const { described, answer, rules } = codeFactsOf(machine, v, kind, verb);
|
|
257
|
+
above.set(v, { lazy, rules });
|
|
225
258
|
let roots: readonly Root[] | undefined;
|
|
226
|
-
if (
|
|
259
|
+
if (described) {
|
|
227
260
|
note(answer?.pure ?? kind.pure, answer?.volatile ?? kind.volatile);
|
|
228
|
-
above.set(v, { lazy, rules: answer?.lazy ?? kind.lazy });
|
|
229
261
|
roots = answer?.reads ?? kind.reads;
|
|
230
262
|
} else {
|
|
231
263
|
// The contract of the kind does not describe this code. Only the hook can.
|
|
232
264
|
note(answer?.pure, answer?.volatile);
|
|
233
|
-
above.set(v, { lazy, rules: answer?.lazy ?? EACH });
|
|
234
265
|
roots = answer?.reads;
|
|
235
266
|
}
|
|
236
267
|
// The code of the kind resolves each of these roots as a whole, at the place of its document (FACTS.KIND-READS).
|
|
@@ -239,13 +270,18 @@ export function factsOf(machine: Machine, doc: unknown, options: FactsOptions =
|
|
|
239
270
|
if (node !== undefined && root === "self") continue;
|
|
240
271
|
add({ root, keys: NONE, exact: true, lazy, address: false, path: [root], place: tight(place.place), at: tight(place.at) });
|
|
241
272
|
}
|
|
273
|
+
// check and effectsOf read the declared effects with the same function (D27). The verb of the facts selects the
|
|
274
|
+
// declaration: effects for eval, and the entry of verbEffects for another verb (D49, D111).
|
|
275
|
+
const declared = declaredEffects(v, verb);
|
|
276
|
+
// The field side gives the side of eval. For another verb, a declared effect needs a runner, as in check, thus
|
|
277
|
+
// the run side (FACTS.VERB-SIDE).
|
|
278
|
+
const runs = verb === "eval" ? kind.side === "run" : declared.length > 0;
|
|
242
279
|
// An equation evaluates without capabilities, thus an effect in it never reaches the host. It is still not pure.
|
|
243
|
-
if (
|
|
280
|
+
if (runs) {
|
|
244
281
|
if (node === undefined) side = "run";
|
|
245
282
|
else pure = false;
|
|
246
283
|
}
|
|
247
|
-
|
|
248
|
-
for (const keys of declaredEffects(v)) {
|
|
284
|
+
for (const keys of declared) {
|
|
249
285
|
if (node !== undefined) {
|
|
250
286
|
pure = false;
|
|
251
287
|
continue;
|
package/src/formula.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } f
|
|
|
5
5
|
import { type Code, type CompileInput, type CompiledEvaluator, type CompiledPart, type Evaluator, type Machine, type Runtime, resolveCompiled, totalCode } from "./evaluate.ts";
|
|
6
6
|
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, typeIssues, typeText } from "./types.ts";
|
|
8
|
-
import type { PartType, TypeInput, TypeRule } from "./typing.ts";
|
|
8
|
+
import type { Declared, PartType, TypeInput, TypeRule } from "./typing.ts";
|
|
9
9
|
import { decodeDomain, domainTypeOf } from "./codec.ts";
|
|
10
10
|
import { type StaticRead, factsOf } from "./facts.ts";
|
|
11
11
|
|
|
@@ -31,6 +31,8 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
|
|
|
31
31
|
* - `lazy`: the operations that evaluate their arguments themselves.
|
|
32
32
|
* - `ext`: the extensions of the namespace `ext`, by their name. The kit has `ext.undefined`, `ext.number` and
|
|
33
33
|
* `ext.domain`.
|
|
34
|
+
* - `extTypes`: the value type of each extension of `ext`, by its name, for a check of types (TYPE.EXT, D103). An
|
|
35
|
+
* extension without an entry gives `unknown` (TYPE.GRADUAL). An entry for a standard extension has no effect.
|
|
34
36
|
* - `truthy`: the truth of a value for `if`, `and` and `or`. `undefined` is an error `#VALUE!`. The default
|
|
35
37
|
* accepts only `true` and `false`.
|
|
36
38
|
* - `truthType`: the value type of a truth for the own `truthy`, for a check of types (TYPE.TRUTH, D81). A test of
|
|
@@ -54,12 +56,13 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
|
|
|
54
56
|
*
|
|
55
57
|
* A check of types trusts the signatures. Each operation must give a member of the result of each signature that
|
|
56
58
|
* its arguments match. The hook `check` must keep each result in that type. The function `truthy` must give a truth
|
|
57
|
-
* for each member of `truthType`.
|
|
59
|
+
* for each member of `truthType`. Each extension must give a member of its type in `extTypes`.
|
|
58
60
|
*/
|
|
59
61
|
export interface FormulaProfile {
|
|
60
62
|
readonly ops?: Readonly<Record<string, OpFn>> | ((name: string) => OpFn | undefined);
|
|
61
63
|
readonly lazy?: Readonly<Record<string, LazyOp>>;
|
|
62
64
|
readonly ext?: Readonly<Record<string, ExtFn>>;
|
|
65
|
+
readonly extTypes?: Readonly<Record<string, ValueType>>;
|
|
63
66
|
readonly truthy?: (value: unknown) => boolean | undefined;
|
|
64
67
|
readonly truthType?: ValueType;
|
|
65
68
|
readonly otherwise?: unknown;
|
|
@@ -232,6 +235,12 @@ function isSignature(value: unknown): value is Signature {
|
|
|
232
235
|
return typeof value === "object" && value !== null && Array.isArray((value as { readonly params?: unknown }).params) && Object.hasOwn(value, "to");
|
|
233
236
|
}
|
|
234
237
|
|
|
238
|
+
/** This function tells if two signatures have the same parameters and the same rest parameter. */
|
|
239
|
+
function sameParams(a: Signature, b: Signature): boolean {
|
|
240
|
+
if (!docEquals(a.params, b.params)) return false;
|
|
241
|
+
return a.rest === undefined ? b.rest === undefined : b.rest !== undefined && docEquals(a.rest, b.rest);
|
|
242
|
+
}
|
|
243
|
+
|
|
235
244
|
/** This function gives the text of a signature, for the reason of an issue. */
|
|
236
245
|
function signatureText(sig: Signature): string {
|
|
237
246
|
const params = sig.params.map(typeText);
|
|
@@ -272,7 +281,7 @@ function paramsOf(value: unknown): readonly string[] {
|
|
|
272
281
|
/**
|
|
273
282
|
* This function makes the type rules of the formula kit for one profile (TYPE.SYNTH, D25). Each evaluator of the kit
|
|
274
283
|
* holds its rule. The rules read the rules of the profile that change a type: `truthy`, `truthType`, `logic`,
|
|
275
|
-
* `otherwise`, `omit` and `
|
|
284
|
+
* `otherwise`, `omit`, `sigs` and `extTypes`.
|
|
276
285
|
*/
|
|
277
286
|
function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule>> {
|
|
278
287
|
// The default truth needs a truth value. An own truth needs its declared type, or nothing without a declaration (D81).
|
|
@@ -322,6 +331,32 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
322
331
|
}
|
|
323
332
|
return { sigs, bad: sigs.length !== raw.length };
|
|
324
333
|
};
|
|
334
|
+
/**
|
|
335
|
+
* The declarations of the signatures, for `checkWorld` (TYPE.WORLD, D110). Only a record of signatures lists them.
|
|
336
|
+
* An overload with the parameters of an earlier overload is a problem: the earlier one always fits first.
|
|
337
|
+
*/
|
|
338
|
+
const sigsDeclared = (): readonly Declared[] => {
|
|
339
|
+
const sigs = profile.sigs;
|
|
340
|
+
if (sigs === undefined || typeof sigs === "function") return [];
|
|
341
|
+
const out: Declared[] = [];
|
|
342
|
+
for (const name of Object.keys(sigs)) {
|
|
343
|
+
const raw: unknown = sigs[name];
|
|
344
|
+
const list: readonly unknown[] = Array.isArray(raw) ? raw : [raw];
|
|
345
|
+
list.forEach((sig, index) => {
|
|
346
|
+
const at: readonly Key[] = Array.isArray(raw) ? ["sigs", name, index] : ["sigs", name];
|
|
347
|
+
if (!isSignature(sig)) {
|
|
348
|
+
out.push({ at, problem: `A signature of ops.${name} is not a record with params and to. The check does not read it.` });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
sig.params.forEach((param, position) => out.push({ at: [...at, "params", position], type: param, what: `parameter ${position + 1} of a signature of ops.${name}` }));
|
|
352
|
+
if (sig.rest !== undefined) out.push({ at: [...at, "rest"], type: sig.rest, what: `rest parameter of a signature of ops.${name}` });
|
|
353
|
+
out.push({ at: [...at, "to"], type: sig.to, what: `result of a signature of ops.${name}` });
|
|
354
|
+
const earlier = list.slice(0, index).findIndex((other) => isSignature(other) && sameParams(other, sig));
|
|
355
|
+
if (earlier >= 0) out.push({ at, problem: `The overload ${index + 1} of ops.${name} has the parameters of the overload ${earlier + 1}. Thus the check never selects it.` });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
};
|
|
325
360
|
const logic = (word: "and" | "or"): TypeRule => ({
|
|
326
361
|
part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
|
|
327
362
|
synth: (input) => {
|
|
@@ -453,6 +488,8 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
453
488
|
},
|
|
454
489
|
|
|
455
490
|
"formula.if": {
|
|
491
|
+
// The rule of if declares the truth type of the profile, thus one bad truth type gives one issue in checkWorld.
|
|
492
|
+
declared: () => (declared ? [{ at: ["truthType"], type: truth, what: "truth type of the profile" }] : []),
|
|
456
493
|
part: (input, path) => {
|
|
457
494
|
if (path[0] === "test") return truthPart(input, "test of if");
|
|
458
495
|
return isUnknown(input.expected, input.machine.domains) ? {} : { expect: input.expected, what: `${String(path[0])} branch of if` };
|
|
@@ -468,6 +505,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
468
505
|
"formula.or": logic("or"),
|
|
469
506
|
|
|
470
507
|
"formula.op": {
|
|
508
|
+
declared: sigsDeclared,
|
|
471
509
|
part: (input, path) => {
|
|
472
510
|
const read = signatures(input);
|
|
473
511
|
// Only one signature gives the types of the arguments before their check. Overloads select after it.
|
|
@@ -507,10 +545,26 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
507
545
|
},
|
|
508
546
|
|
|
509
547
|
"formula.ext": {
|
|
548
|
+
declared: () => {
|
|
549
|
+
const types = profile.extTypes;
|
|
550
|
+
const own = profile.ext;
|
|
551
|
+
if (types === undefined || own === undefined) return [];
|
|
552
|
+
// Only the type of an own extension counts. An entry for a standard extension has no effect (D103).
|
|
553
|
+
return Object.keys(types)
|
|
554
|
+
.filter((name) => Object.hasOwn(own, name))
|
|
555
|
+
.map((name) => ({ at: ["extTypes", name], type: types[name], what: `type of ext.${name} in extTypes` }));
|
|
556
|
+
},
|
|
510
557
|
synth: (input) => {
|
|
511
558
|
const name = nameOf(input);
|
|
512
559
|
const own = profile.ext !== undefined && Object.hasOwn(profile.ext, name);
|
|
513
|
-
if (own)
|
|
560
|
+
if (own) {
|
|
561
|
+
// The declared type is a contract. A type with a problem gives unknown, thus one problem gives one issue (D103).
|
|
562
|
+
const types = profile.extTypes;
|
|
563
|
+
const declared = types !== undefined && Object.hasOwn(types, name) ? types[name] : undefined;
|
|
564
|
+
if (declared === undefined) return "unknown";
|
|
565
|
+
const type = input.contract(declared, `type of ext.${name} in extTypes`);
|
|
566
|
+
return typeIssues(type, input.machine.domains).length > 0 ? "unknown" : type;
|
|
567
|
+
}
|
|
514
568
|
// The standard extensions give the values that JSON cannot write, thus each has a base type (D48).
|
|
515
569
|
if (name === "undefined") return "undefined";
|
|
516
570
|
if (name === "number") return "number";
|
|
@@ -580,6 +634,7 @@ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
|
|
|
580
634
|
"ops",
|
|
581
635
|
"lazy",
|
|
582
636
|
"ext",
|
|
637
|
+
"extTypes",
|
|
583
638
|
"truthy",
|
|
584
639
|
"truthType",
|
|
585
640
|
"otherwise",
|