@mark1russell7/coeval 0.16.0 → 0.18.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 +9 -4
- package/build/index.d.ts +545 -233
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2619 -383
- package/build/index.js.map +1 -1
- package/package.json +3 -2
- package/src/agree.ts +128 -16
- package/src/conform.ts +75 -5
- package/src/deref.ts +76 -11
- package/src/evaluate.ts +151 -21
- package/src/facts.ts +113 -78
- package/src/formula.ts +67 -11
- package/src/index.ts +23 -1
- package/src/merge.ts +49 -14
- package/src/reference.ts +110 -15
- package/src/replay.ts +1052 -18
- package/src/static.ts +2 -2
- package/src/stdlib.ts +55 -2
- package/src/syntax.ts +357 -52
- package/src/types.ts +520 -17
package/src/formula.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { type CompiledPath, type Context, type Resolution, compilePath, valueOfT
|
|
|
4
4
|
import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
|
|
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
|
-
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
|
-
import type { TypeInput, TypeRule } from "./typing.ts";
|
|
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";
|
|
9
9
|
import { decodeDomain, domainTypeOf } from "./codec.ts";
|
|
10
10
|
import { type StaticRead, factsOf } from "./facts.ts";
|
|
11
11
|
|
|
@@ -31,8 +31,13 @@ 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`.
|
|
38
|
+
* - `truthType`: the value type of a truth for the own `truthy`, for a check of types (TYPE.TRUTH, D81). A test of
|
|
39
|
+
* `if` and each term of `and` and `or` must fit it. Without it, the check accepts each type there. A profile without
|
|
40
|
+
* `truthy` ignores it, because the default truth needs `bool`.
|
|
36
41
|
* - `otherwise`: the value of an `if` without `else`. The default is `null`. A profile with this key gives its value,
|
|
37
42
|
* also `undefined`.
|
|
38
43
|
* - `logic`: what `and` and `or` give: a truth value (the default), or the value that decided.
|
|
@@ -50,13 +55,16 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
|
|
|
50
55
|
* overloads. An operation without a signature takes and gives `unknown` (TYPE.GRADUAL).
|
|
51
56
|
*
|
|
52
57
|
* A check of types trusts the signatures. Each operation must give a member of the result of each signature that
|
|
53
|
-
* its arguments match. The hook `check` must keep each result in that type.
|
|
58
|
+
* its arguments match. The hook `check` must keep each result in that type. The function `truthy` must give a truth
|
|
59
|
+
* for each member of `truthType`. Each extension must give a member of its type in `extTypes`.
|
|
54
60
|
*/
|
|
55
61
|
export interface FormulaProfile {
|
|
56
62
|
readonly ops?: Readonly<Record<string, OpFn>> | ((name: string) => OpFn | undefined);
|
|
57
63
|
readonly lazy?: Readonly<Record<string, LazyOp>>;
|
|
58
64
|
readonly ext?: Readonly<Record<string, ExtFn>>;
|
|
65
|
+
readonly extTypes?: Readonly<Record<string, ValueType>>;
|
|
59
66
|
readonly truthy?: (value: unknown) => boolean | undefined;
|
|
67
|
+
readonly truthType?: ValueType;
|
|
60
68
|
readonly otherwise?: unknown;
|
|
61
69
|
readonly logic?: "boolean" | "value";
|
|
62
70
|
readonly omit?: (error: EvalError) => boolean;
|
|
@@ -266,12 +274,23 @@ function paramsOf(value: unknown): readonly string[] {
|
|
|
266
274
|
|
|
267
275
|
/**
|
|
268
276
|
* This function makes the type rules of the formula kit for one profile (TYPE.SYNTH, D25). Each evaluator of the kit
|
|
269
|
-
* holds its rule. The rules read the rules of the profile that change a type: `truthy`, `
|
|
270
|
-
* and `
|
|
277
|
+
* holds its rule. The rules read the rules of the profile that change a type: `truthy`, `truthType`, `logic`,
|
|
278
|
+
* `otherwise`, `omit`, `sigs` and `extTypes`.
|
|
271
279
|
*/
|
|
272
280
|
function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule>> {
|
|
273
|
-
//
|
|
274
|
-
const
|
|
281
|
+
// The default truth needs a truth value. An own truth needs its declared type, or nothing without a declaration (D81).
|
|
282
|
+
const declared = profile.truthy !== undefined && profile.truthType !== undefined;
|
|
283
|
+
const truth: ValueType | undefined = profile.truthy === undefined ? "bool" : profile.truthType;
|
|
284
|
+
/**
|
|
285
|
+
* The need of a test or a term. The check examines a declared type of a truth as a contract, one time. A declared
|
|
286
|
+
* type with a problem needs nothing, thus one problem gives one issue.
|
|
287
|
+
*/
|
|
288
|
+
const truthPart = (input: TypeInput, what: string): PartType => {
|
|
289
|
+
if (truth === undefined) return {};
|
|
290
|
+
if (!declared) return { expect: truth, what };
|
|
291
|
+
const type = input.contract(truth, "truth type of the profile");
|
|
292
|
+
return typeIssues(type, input.machine.domains).length > 0 ? {} : { expect: type, what };
|
|
293
|
+
};
|
|
275
294
|
const logicValue = profile.logic === "value";
|
|
276
295
|
const otherwise = Object.hasOwn(profile, "otherwise") ? valueType(profile.otherwise) : "null";
|
|
277
296
|
const lookup = (name: string): readonly unknown[] | undefined => {
|
|
@@ -307,7 +326,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
307
326
|
return { sigs, bad: sigs.length !== raw.length };
|
|
308
327
|
};
|
|
309
328
|
const logic = (word: "and" | "or"): TypeRule => ({
|
|
310
|
-
part: (
|
|
329
|
+
part: (input, path) => truthPart(input, `term ${Number(path[0]) + 1} of ${word}`),
|
|
311
330
|
synth: (input) => {
|
|
312
331
|
if (!logicValue) return "bool";
|
|
313
332
|
// With the logic "value", the result is the term that decided, or the last term.
|
|
@@ -438,7 +457,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
438
457
|
|
|
439
458
|
"formula.if": {
|
|
440
459
|
part: (input, path) => {
|
|
441
|
-
if (path[0] === "test") return
|
|
460
|
+
if (path[0] === "test") return truthPart(input, "test of if");
|
|
442
461
|
return isUnknown(input.expected, input.machine.domains) ? {} : { expect: input.expected, what: `${String(path[0])} branch of if` };
|
|
443
462
|
},
|
|
444
463
|
synth: (input) => {
|
|
@@ -494,7 +513,14 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
494
513
|
synth: (input) => {
|
|
495
514
|
const name = nameOf(input);
|
|
496
515
|
const own = profile.ext !== undefined && Object.hasOwn(profile.ext, name);
|
|
497
|
-
if (own)
|
|
516
|
+
if (own) {
|
|
517
|
+
// The declared type is a contract. A type with a problem gives unknown, thus one problem gives one issue (D103).
|
|
518
|
+
const types = profile.extTypes;
|
|
519
|
+
const declared = types !== undefined && Object.hasOwn(types, name) ? types[name] : undefined;
|
|
520
|
+
if (declared === undefined) return "unknown";
|
|
521
|
+
const type = input.contract(declared, `type of ext.${name} in extTypes`);
|
|
522
|
+
return typeIssues(type, input.machine.domains).length > 0 ? "unknown" : type;
|
|
523
|
+
}
|
|
498
524
|
// The standard extensions give the values that JSON cannot write, thus each has a base type (D48).
|
|
499
525
|
if (name === "undefined") return "undefined";
|
|
500
526
|
if (name === "number") return "number";
|
|
@@ -518,15 +544,43 @@ export function formulaEvaluators(profile: FormulaProfile = {}): Readonly<Record
|
|
|
518
544
|
return { ...kitOf(profile).evaluators };
|
|
519
545
|
}
|
|
520
546
|
|
|
547
|
+
/** The declared truth of a profile: its own `truthy` and its `truthType` (D81). */
|
|
548
|
+
export interface DeclaredTruth {
|
|
549
|
+
readonly truthy: (value: unknown) => boolean | undefined;
|
|
550
|
+
readonly truthType: ValueType;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** The evaluators of the kit that read the truth of a value. */
|
|
554
|
+
const TRUTH_READERS: readonly string[] = ["formula.if", "formula.and", "formula.or"];
|
|
555
|
+
|
|
556
|
+
/** The declared truth of each evaluator of the kit that reads a truth, for `conforms` (D86). */
|
|
557
|
+
const truthByCode = /* @__PURE__ */ new WeakMap<object, DeclaredTruth>();
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* This function gives the declared truth of the profile of an evaluator of the kit (D86). Only `formula.if`,
|
|
561
|
+
* `formula.and` and `formula.or` of a profile with an own `truthy` and a `truthType` have one. The kernel does not
|
|
562
|
+
* export the function.
|
|
563
|
+
*
|
|
564
|
+
* @param code - The evaluator or the runner.
|
|
565
|
+
* @returns The declared truth, or `undefined`.
|
|
566
|
+
*/
|
|
567
|
+
export function declaredTruthOf(code: unknown): DeclaredTruth | undefined {
|
|
568
|
+
return (typeof code === "object" || typeof code === "function") && code !== null ? truthByCode.get(code) : undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
521
571
|
/** This function makes new evaluators of the formula kit for the fields of one profile. */
|
|
522
572
|
function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evaluator>> {
|
|
523
573
|
const evaluators = formulaCode(profile);
|
|
524
574
|
const types = formulaTypes(profile);
|
|
575
|
+
const { truthy, truthType } = profile;
|
|
576
|
+
const truth = truthy === undefined || truthType === undefined ? undefined : { truthy, truthType };
|
|
525
577
|
const out: Record<string, Evaluator> = {};
|
|
526
578
|
for (const name of Object.keys(evaluators)) {
|
|
527
579
|
const evaluator = evaluators[name] as CompiledEvaluator;
|
|
528
580
|
const type = Object.hasOwn(types, name) ? types[name] : undefined;
|
|
529
|
-
|
|
581
|
+
const made = Object.freeze(type === undefined ? evaluator : { compile: evaluator.compile, type });
|
|
582
|
+
if (truth !== undefined && TRUTH_READERS.includes(name)) truthByCode.set(made, truth);
|
|
583
|
+
out[name] = made;
|
|
530
584
|
}
|
|
531
585
|
return Object.freeze(out);
|
|
532
586
|
}
|
|
@@ -536,7 +590,9 @@ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
|
|
|
536
590
|
"ops",
|
|
537
591
|
"lazy",
|
|
538
592
|
"ext",
|
|
593
|
+
"extTypes",
|
|
539
594
|
"truthy",
|
|
595
|
+
"truthType",
|
|
540
596
|
"otherwise",
|
|
541
597
|
"logic",
|
|
542
598
|
"omit",
|
package/src/index.ts
CHANGED
|
@@ -174,6 +174,7 @@ export {
|
|
|
174
174
|
STD_RUNNERS,
|
|
175
175
|
STD_WALKERS,
|
|
176
176
|
STD_WORLD,
|
|
177
|
+
WORLD_SYNTAX,
|
|
177
178
|
attributeTrace,
|
|
178
179
|
nearestWalker,
|
|
179
180
|
placeOf,
|
|
@@ -238,8 +239,11 @@ export {
|
|
|
238
239
|
type ParseResult,
|
|
239
240
|
type SyntaxAssoc,
|
|
240
241
|
type SyntaxCalls,
|
|
242
|
+
type SyntaxEnvelopes,
|
|
241
243
|
type SyntaxFixity,
|
|
242
244
|
type SyntaxForm,
|
|
245
|
+
type SyntaxFormArgs,
|
|
246
|
+
type SyntaxHeader,
|
|
243
247
|
type SyntaxIssue,
|
|
244
248
|
type SyntaxLiterals,
|
|
245
249
|
type SyntaxOperator,
|
|
@@ -277,7 +281,25 @@ export {
|
|
|
277
281
|
agreeSchedule,
|
|
278
282
|
} from "./meaning.ts";
|
|
279
283
|
export { type GraphCycle, type GraphEdge, type GraphNode, type GraphOptions, type NodeGraph, components, cycles, heights, necessary, nodeLayers } from "./graph.ts";
|
|
280
|
-
export {
|
|
284
|
+
export {
|
|
285
|
+
type Recorder,
|
|
286
|
+
type ReplayCausePartsJson,
|
|
287
|
+
type ReplayEntry,
|
|
288
|
+
type ReplayEntryJson,
|
|
289
|
+
type ReplayErrorJson,
|
|
290
|
+
type ReplayExceptionJson,
|
|
291
|
+
type ReplayLog,
|
|
292
|
+
type ReplayLogJson,
|
|
293
|
+
type ReplayPartJson,
|
|
294
|
+
type ReplayPartsJson,
|
|
295
|
+
type ReplayOptions,
|
|
296
|
+
type ReplayResultJson,
|
|
297
|
+
type ReplaySettle,
|
|
298
|
+
decodeLog,
|
|
299
|
+
encodeLog,
|
|
300
|
+
recorder,
|
|
301
|
+
replayer,
|
|
302
|
+
} from "./replay.ts";
|
|
281
303
|
export { type GenerateOptions, type Random, generate, random, typesOf } from "./generate.ts";
|
|
282
304
|
export { type Domain, type DomainCodec, type TypeIssue, type ValueType, STD_DOMAINS, fits, join, member, typeIssues, typeText } from "./types.ts";
|
|
283
305
|
export { type PartType, type RootTypes, type TypeInput, type TypeProblem, type TypeRule, type Typed, CAPS_TYPE } from "./typing.ts";
|
package/src/merge.ts
CHANGED
|
@@ -89,32 +89,54 @@ function same(a: unknown, b: unknown): boolean {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Two domains are the same when they are one object. They are also the same when they have the same test `is`,
|
|
92
|
-
* parents, summary and
|
|
92
|
+
* parents, summary, steps, codec, list of values, samples and covers. Thus two languages that give one test under one name give one
|
|
93
|
+
* domain. Two codecs are the same when they have the same functions `encode` and `decode` (D80).
|
|
93
94
|
*/
|
|
94
95
|
function sameDomain(a: Domain, b: Domain): boolean {
|
|
95
|
-
|
|
96
|
+
if (a === b) return true;
|
|
97
|
+
// The type names each field of Domain, thus a new field fails the check of types until the merge compares it (K180).
|
|
98
|
+
const fields: { readonly [K in keyof Domain]-?: boolean } = {
|
|
99
|
+
is: a.is === b.is,
|
|
100
|
+
under: same(a.under ?? [], b.under ?? []),
|
|
101
|
+
summary: a.summary === b.summary,
|
|
102
|
+
steps: same(a.steps ?? {}, b.steps ?? {}),
|
|
103
|
+
codec: same(a.codec ?? null, b.codec ?? null),
|
|
104
|
+
values: same(a.values ?? null, b.values ?? null),
|
|
105
|
+
samples: same(a.samples ?? null, b.samples ?? null),
|
|
106
|
+
covers: same(a.covers ?? [], b.covers ?? []),
|
|
107
|
+
};
|
|
108
|
+
return Object.values(fields).every(Boolean);
|
|
96
109
|
}
|
|
97
110
|
|
|
98
|
-
/** What `extendDomain` adds to a domain: more parents,
|
|
111
|
+
/** What `extendDomain` adds to a domain: more parents, the types of more steps, and more covers. */
|
|
99
112
|
export interface DomainExtension {
|
|
100
113
|
/** The names of the parents to add. A member of the domain then also passes the test of each new parent. */
|
|
101
114
|
readonly under?: readonly string[];
|
|
102
115
|
/** The types of the steps to add. A step with the key of an earlier step replaces its type. */
|
|
103
116
|
readonly steps?: Readonly<Record<string, ValueType>>;
|
|
117
|
+
/**
|
|
118
|
+
* The covers to add (TYPE.FITS-COVER, D85). A cover that the domain has already does not change. The language that
|
|
119
|
+
* adds a cover keeps it true, because `fits` trusts it.
|
|
120
|
+
*/
|
|
121
|
+
readonly covers?: readonly (readonly string[])[];
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
/**
|
|
107
|
-
* This function makes a part of a merge that adds parents or
|
|
108
|
-
* example is a domain of another language. The part reads the domain from the merge before it
|
|
109
|
-
* copy with the same test
|
|
110
|
-
*
|
|
125
|
+
* This function makes a part of a merge that adds parents, steps or covers to a domain of an earlier part
|
|
126
|
+
* (TYPE.PARENTS). An example is a domain of another language. The part reads the domain from the merge before it
|
|
127
|
+
* (`prev`). It gives a copy with the same test, summary, codec, list of values and samples, and it names
|
|
128
|
+
* `["domains", name]` in `replaces` (D17).
|
|
129
|
+
* A domain of the kernel that the earlier parts do not name comes from `STD_DOMAINS`.
|
|
130
|
+
*
|
|
131
|
+
* An extension does not change the codec. Thus each member keeps its document (D80).
|
|
111
132
|
*
|
|
112
133
|
* A new parent adds its test to the domain (TYPE.MEMBER). Thus `fits` stays sound, but a value that fails the test of
|
|
113
134
|
* the parent is no longer a member. The language that adds the parent keeps the signatures of the other language
|
|
114
|
-
* true. A
|
|
135
|
+
* true. A new parent of a domain in a cover of another domain can make that cover false (D85). A name without a
|
|
136
|
+
* domain gives an empty part, and `typeIssues` reports each use of that name.
|
|
115
137
|
*
|
|
116
138
|
* @param name - The name of the domain.
|
|
117
|
-
* @param more - The parents and the
|
|
139
|
+
* @param more - The parents, the steps and the covers to add.
|
|
118
140
|
* @returns The part, as a function of the merge before it.
|
|
119
141
|
*/
|
|
120
142
|
export function extendDomain(name: string, more: DomainExtension): (prev: Machine) => MachinePart {
|
|
@@ -125,14 +147,27 @@ export function extendDomain(name: string, more: DomainExtension): (prev: Machin
|
|
|
125
147
|
const under = [...(found.under ?? [])];
|
|
126
148
|
for (const parent of more.under ?? []) if (parent !== name && !under.includes(parent)) under.push(parent);
|
|
127
149
|
const steps = { ...found.steps, ...more.steps };
|
|
128
|
-
const
|
|
150
|
+
const covers = [...(found.covers ?? [])];
|
|
151
|
+
for (const cover of more.covers ?? []) if (!covers.some((each) => same(each, cover))) covers.push([...cover]);
|
|
152
|
+
// The type names each field of Domain, thus a new field fails the check of types until the copy reads it (K180).
|
|
153
|
+
const fields: { readonly [K in keyof Domain]-?: Domain[K] | undefined } = {
|
|
129
154
|
is: found.is,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
under: under.length === 0 ? undefined : under,
|
|
156
|
+
summary: found.summary,
|
|
157
|
+
steps: Object.keys(steps).length === 0 ? undefined : steps,
|
|
158
|
+
// A new parent only adds a test, thus each member keeps its document (D80).
|
|
159
|
+
codec: found.codec,
|
|
160
|
+
// A new parent only adds a test, thus the list of values stays complete (TYPE.FITS-FINITE).
|
|
161
|
+
values: found.values,
|
|
162
|
+
// A sample outside the new line does not count, thus the samples stay (D99).
|
|
163
|
+
samples: found.samples,
|
|
164
|
+
// A new parent only adds a test, thus each cover stays true (TYPE.FITS-COVER). A new cover is a claim of the extension.
|
|
165
|
+
covers: covers.length === 0 ? undefined : covers,
|
|
133
166
|
};
|
|
167
|
+
const domain: Record<string, unknown> = {};
|
|
168
|
+
for (const [key, value] of Object.entries(fields)) if (value !== undefined) domain[key] = value;
|
|
134
169
|
const domains: Record<string, Domain> = {};
|
|
135
|
-
setOwn(domains, name, domain);
|
|
170
|
+
setOwn(domains, name, domain as unknown as Domain);
|
|
136
171
|
return { domains, replaces: [["domains", name]] };
|
|
137
172
|
};
|
|
138
173
|
}
|
package/src/reference.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type Envelope, type Json, type Key, isEnvelope, isRecord, typeName } from "./doc.ts";
|
|
1
|
+
import { type Envelope, type Json, type Key, isEnvelope, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
2
|
import { ask } from "./ladder.ts";
|
|
3
3
|
import { type AttributeDeclaration, type CapsRule, type Kind, type TreeContract, equationType, lookupKind } from "./world.ts";
|
|
4
4
|
import { type PartSlot, type Split, partsOf, pathMatches } from "./parts.ts";
|
|
5
|
-
import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
|
|
5
|
+
import { type Cap, type Context, type HostThing, type Resolution, type ResolveOptions, type Thing, ROOTS, THING, isHostThing, resolve, valueOfThing, worldThing } from "./deref.ts";
|
|
6
6
|
import { type AttributeRung, STD_EVALUATORS } from "./stdlib.ts";
|
|
7
7
|
import { type EvalError, type Failed, type Result, Failure, describe, fail, ok, placed, thrown } from "./errors.ts";
|
|
8
8
|
import {
|
|
@@ -14,15 +14,18 @@ import {
|
|
|
14
14
|
type Machine,
|
|
15
15
|
type Part,
|
|
16
16
|
type Perform,
|
|
17
|
+
type Read,
|
|
17
18
|
type RunFn,
|
|
18
19
|
type RunInput,
|
|
19
20
|
type RunPart,
|
|
20
21
|
type Steps,
|
|
21
22
|
type StopSignal,
|
|
22
23
|
JOURNAL,
|
|
24
|
+
heldReason,
|
|
23
25
|
makeContext,
|
|
24
26
|
permits,
|
|
25
27
|
placeText,
|
|
28
|
+
reachedOf,
|
|
26
29
|
waitedReason,
|
|
27
30
|
} from "./evaluate.ts";
|
|
28
31
|
import { type FormulaProfile, type OpFn, Held, STD_EXT } from "./formula.ts";
|
|
@@ -57,6 +60,12 @@ export interface ReferenceOptions {
|
|
|
57
60
|
* each verb by the same rules (VERB.AGREE).
|
|
58
61
|
*/
|
|
59
62
|
readonly verb?: string;
|
|
63
|
+
/**
|
|
64
|
+
* A list that gets each read of a resolution, in its order (REF.READS). A read has the shape of a read of a plan:
|
|
65
|
+
* the path, what it reached and the mark `free`. The reads of a closure and of an equation of a tree go in the
|
|
66
|
+
* same list. The reference adds no kept reads, because it has no static reads of a closure.
|
|
67
|
+
*/
|
|
68
|
+
readonly reads?: Read[];
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
/** The options of a run of the reference: the options of an evaluation, with the host and a signal (REF.RUN). */
|
|
@@ -80,6 +89,13 @@ interface Scope {
|
|
|
80
89
|
alls: number;
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
/** The record of the reads of one evaluation of the reference (REF.READS). */
|
|
93
|
+
interface Reading {
|
|
94
|
+
readonly list: Read[];
|
|
95
|
+
/** The context at the root of the evaluation. A free read reads this context. */
|
|
96
|
+
readonly root: Context;
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
/** The state of one evaluation of the reference. */
|
|
84
100
|
interface Env {
|
|
85
101
|
readonly machine: Machine;
|
|
@@ -95,6 +111,10 @@ interface Env {
|
|
|
95
111
|
readonly verb: string;
|
|
96
112
|
/** The scope of the code that evaluates at this time. `all` changes it for each item. */
|
|
97
113
|
scope: Scope;
|
|
114
|
+
/** The count of the drives without a wait that are active. In a run, an effect under one does not reach the host (RUN.HELD). */
|
|
115
|
+
held: number;
|
|
116
|
+
/** The record of the reads. A nested evaluation for an equation of a tree keeps the record of its tree. */
|
|
117
|
+
readonly reading: Reading | undefined;
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
/** The code that answers a document: a function evaluator, a runner, or a gap of the reference. */
|
|
@@ -221,6 +241,44 @@ function resolveFully(machine: Machine, ctx: Context, path: readonly unknown[],
|
|
|
221
241
|
return options?.hops === true ? found : { ...found, hops: [] };
|
|
222
242
|
}
|
|
223
243
|
|
|
244
|
+
/** The text that names a resolution in the error of an exception of the host (READS.RECORD). */
|
|
245
|
+
const READ_FAILED = "the resolution of a path";
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* This function resolves a path for a document, and records the read when the evaluation has a list (REF.READS,
|
|
249
|
+
* READS.RECORD). An exception of the host gives the error value of the read, and the exception goes on.
|
|
250
|
+
*/
|
|
251
|
+
function readAt(env: Env, ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
252
|
+
const reading = env.reading;
|
|
253
|
+
if (reading === undefined) return resolveFully(env.machine, ctx, path, options);
|
|
254
|
+
const free = freeIn(reading.root, ctx, path);
|
|
255
|
+
let found: Resolution;
|
|
256
|
+
try {
|
|
257
|
+
found = resolveFully(env.machine, ctx, path, options);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
reading.list.push({ path, reached: thrown(error, READ_FAILED), free });
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
reading.list.push(options?.hops === true ? { path, reached: reachedOf(found), free, hops: found.hops } : { path, reached: reachedOf(found), free });
|
|
263
|
+
return found;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* This function tells if a read reads the context at the root of the evaluation (READS.FREE). The reference reads
|
|
268
|
+
* the rule from SPEC. A name that a kind binds, a `self` that a kind sets and a path without a root are not free. A
|
|
269
|
+
* name that a kind does not bind keeps its value from the root.
|
|
270
|
+
*/
|
|
271
|
+
function freeIn(root: Context, ctx: Context, path: readonly unknown[]): boolean {
|
|
272
|
+
const head = path[0];
|
|
273
|
+
if (head === "self") return Object.is(ctx.self, root.self);
|
|
274
|
+
if (head !== "names") return typeof head === "string" && (ROOTS as readonly string[]).includes(head);
|
|
275
|
+
const name = path[1];
|
|
276
|
+
if (ctx.names === root.names || !isKey(name)) return true;
|
|
277
|
+
const key = String(name);
|
|
278
|
+
const bound = Object.hasOwn(ctx.names, key);
|
|
279
|
+
return bound === Object.hasOwn(root.names, key) && (!bound || Object.is(ctx.names[key], root.names[key]));
|
|
280
|
+
}
|
|
281
|
+
|
|
224
282
|
/**
|
|
225
283
|
* This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
|
|
226
284
|
* address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
|
|
@@ -254,6 +312,12 @@ function* causeAndWait(env: Env, ctx: Context, kind: Cap, payload: unknown, plac
|
|
|
254
312
|
if ("ok" in effect) return effect;
|
|
255
313
|
const perform = env.perform;
|
|
256
314
|
if (perform === undefined) return (effect.outcome = ok(payload));
|
|
315
|
+
if (env.held > 0) {
|
|
316
|
+
// The parent cannot wait for the answer of the host, thus the host does not get the effect (RUN.HELD).
|
|
317
|
+
effect.outcome = fail("#CALC!", heldReason(kind), { place });
|
|
318
|
+
yield Promise.resolve(undefined);
|
|
319
|
+
return effect.outcome;
|
|
320
|
+
}
|
|
257
321
|
let given: unknown;
|
|
258
322
|
try {
|
|
259
323
|
given = perform(effect);
|
|
@@ -327,7 +391,16 @@ function evaluateSeen(env: Env, seen: Seen, at: At): Result {
|
|
|
327
391
|
const { chosen } = seen;
|
|
328
392
|
const { place } = at;
|
|
329
393
|
if ("gap" in chosen) return fail("#NOEVAL", chosen.gap, { place });
|
|
330
|
-
if ("runner" in chosen)
|
|
394
|
+
if ("runner" in chosen) {
|
|
395
|
+
if (!env.running) return drive(runSeen(env, seen, at, chosen.runner), place, seen.kindText);
|
|
396
|
+
// In a run, an effect under this drive does not reach the host (RUN.HELD).
|
|
397
|
+
env.held += 1;
|
|
398
|
+
try {
|
|
399
|
+
return drive(runSeen(env, seen, at, chosen.runner), place, seen.kindText);
|
|
400
|
+
} finally {
|
|
401
|
+
env.held -= 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
331
404
|
const input = evalInput(env, seen, at);
|
|
332
405
|
try {
|
|
333
406
|
return placed(chosen.evaluator(input), place);
|
|
@@ -385,7 +458,7 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
|
|
|
385
458
|
place: at.place,
|
|
386
459
|
machine: env.machine,
|
|
387
460
|
...(at.address ? { address: true } : {}),
|
|
388
|
-
resolve: (path, options) =>
|
|
461
|
+
resolve: (path, options) => readAt(env, at.ctx, path, options),
|
|
389
462
|
effect: (kind, payload) => causeFromEvaluator(env, at.ctx, kind, payload, at.place),
|
|
390
463
|
fill,
|
|
391
464
|
};
|
|
@@ -413,7 +486,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
|
|
|
413
486
|
context: at.ctx,
|
|
414
487
|
place: at.place,
|
|
415
488
|
machine: env.machine,
|
|
416
|
-
resolve: (path, options) =>
|
|
489
|
+
resolve: (path, options) => readAt(env, at.ctx, path, options),
|
|
417
490
|
effect: (kind, payload) => causeAndWait(env, at.ctx, kind, payload, at.place),
|
|
418
491
|
all: (list, options) => together(env, list, options),
|
|
419
492
|
fill,
|
|
@@ -458,6 +531,7 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
|
|
|
458
531
|
outer.alls += 1;
|
|
459
532
|
let failed = false;
|
|
460
533
|
let started = 0;
|
|
534
|
+
let before = 0;
|
|
461
535
|
const end = (index: number, result: Result): void => {
|
|
462
536
|
results[index] = result;
|
|
463
537
|
if (failFast && !result.ok) failed = true;
|
|
@@ -508,10 +582,11 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
|
|
|
508
582
|
};
|
|
509
583
|
startMore();
|
|
510
584
|
while (waiting.length > 0 && !failed) {
|
|
511
|
-
const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index));
|
|
585
|
+
const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index), before);
|
|
512
586
|
const pick = chosen === undefined ? undefined : waiting.find((waiter) => waiter.index === chosen);
|
|
513
587
|
const resumed = (yield pick === undefined ? Promise.race(waiting.map((waiter) => waiter.next)) : pick.next) as Resumed;
|
|
514
588
|
env.journal?.settled?.(address, resumed.waiter.index);
|
|
589
|
+
before += 1;
|
|
515
590
|
waiting.splice(waiting.indexOf(resumed.waiter), 1);
|
|
516
591
|
goOn(resumed.waiter.index, resumed.waiter.steps, resumed);
|
|
517
592
|
startMore();
|
|
@@ -543,9 +618,18 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
|
|
|
543
618
|
* @returns The result.
|
|
544
619
|
*/
|
|
545
620
|
export function reference(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceOptions = {}): Result {
|
|
546
|
-
|
|
621
|
+
return evaluateRoot(machine, doc, context, options, undefined);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* This function evaluates a document from its root. An equation of a tree gives the record of its tree. Thus the
|
|
626
|
+
* reads of the equation go in the list of the outer evaluation (REF.READS).
|
|
627
|
+
*/
|
|
628
|
+
function evaluateRoot(machine: Machine, doc: unknown, context: Context, options: ReferenceOptions, outer: Reading | undefined): Result {
|
|
629
|
+
const at = rootAt(machine, doc, context, options);
|
|
630
|
+
const env = envOf(machine, options, false, at.ctx, outer);
|
|
547
631
|
try {
|
|
548
|
-
return within(env, () => evaluateAt(env,
|
|
632
|
+
return within(env, () => evaluateAt(env, at));
|
|
549
633
|
} catch (error) {
|
|
550
634
|
return placed(thrown(error, "the reference"), []);
|
|
551
635
|
}
|
|
@@ -567,13 +651,17 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
|
|
|
567
651
|
* @returns The result, when the run ends.
|
|
568
652
|
*/
|
|
569
653
|
export function referenceRun(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceRunOptions = {}): Promise<Result> {
|
|
570
|
-
const
|
|
571
|
-
const
|
|
654
|
+
const at = rootAt(machine, doc, context, options);
|
|
655
|
+
const env = envOf(machine, options, true, at.ctx, undefined);
|
|
656
|
+
const steps = stepsAt(env, at);
|
|
572
657
|
return driveRun(env, steps, options.signal).catch((error: unknown) => placed(thrown(error, "the reference"), []));
|
|
573
658
|
}
|
|
574
659
|
|
|
575
|
-
/**
|
|
576
|
-
function
|
|
660
|
+
/**
|
|
661
|
+
* This function makes the state of the reference for one evaluation or one run. The record of the reads comes from
|
|
662
|
+
* the evaluation around it, or from the list of the options.
|
|
663
|
+
*/
|
|
664
|
+
function envOf(machine: Machine, options: ReferenceOptions, running: boolean, root: Context, outer: Reading | undefined): Env {
|
|
577
665
|
const perform = options.perform;
|
|
578
666
|
return {
|
|
579
667
|
machine,
|
|
@@ -585,6 +673,8 @@ function envOf(machine: Machine, options: ReferenceOptions, running: boolean): E
|
|
|
585
673
|
running,
|
|
586
674
|
verb: options.verb ?? "eval",
|
|
587
675
|
scope: { prefix: "", counts: new Map(), alls: 0 },
|
|
676
|
+
held: 0,
|
|
677
|
+
reading: outer ?? (options.reads === undefined ? undefined : { list: options.reads, root }),
|
|
588
678
|
};
|
|
589
679
|
}
|
|
590
680
|
|
|
@@ -698,13 +788,14 @@ function refNodeOf(thing: unknown): RefNode | undefined {
|
|
|
698
788
|
|
|
699
789
|
/**
|
|
700
790
|
* TREE.LADDER: an equation of the kind of a node evaluates in the reference. It gets the node as `self`, the names of
|
|
701
|
-
* its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind.
|
|
791
|
+
* its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind. The reads of the
|
|
792
|
+
* equation go in the record of the evaluation that made the node (REF.READS).
|
|
702
793
|
*/
|
|
703
794
|
function equationOf(node: RefNode, term: Json, names: Readonly<Record<string, unknown>>): Result {
|
|
704
795
|
const env = node.env;
|
|
705
796
|
if (env === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
|
|
706
797
|
const ctx: Context = { ...node.ctx, names, caps: [], self: node.thing };
|
|
707
|
-
return
|
|
798
|
+
return evaluateRoot(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) }, env.reading);
|
|
708
799
|
}
|
|
709
800
|
|
|
710
801
|
function termIn<T>(table: Readonly<Record<string, T>> | undefined, name: string): T | undefined {
|
|
@@ -960,7 +1051,11 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
|
|
|
960
1051
|
// FORMULA.LET: a path through a name whose binding failed gives the error of the binding.
|
|
961
1052
|
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : undefined;
|
|
962
1053
|
const bound = head === undefined ? undefined : own(context.names, head);
|
|
963
|
-
if (bound instanceof Held)
|
|
1054
|
+
if (bound instanceof Held) {
|
|
1055
|
+
// The result comes from the binding of the name, thus the name is a read (READS.RECORD).
|
|
1056
|
+
walk(["names", head]);
|
|
1057
|
+
return { ok: false, error: bound.error };
|
|
1058
|
+
}
|
|
964
1059
|
const found = walk(steps);
|
|
965
1060
|
if (!found.ok) return fail(found.code, found.reason);
|
|
966
1061
|
// FORMULA.ADDRESS: an address gives what the path reaches.
|