@mark1russell7/coeval 0.16.0 → 0.17.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 +522 -171
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2352 -296
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agree.ts +128 -16
- package/src/conform.ts +49 -5
- package/src/deref.ts +76 -11
- package/src/evaluate.ts +113 -19
- package/src/facts.ts +113 -78
- package/src/formula.ts +55 -10
- package/src/index.ts +23 -1
- package/src/merge.ts +46 -14
- package/src/reference.ts +90 -14
- package/src/replay.ts +964 -18
- package/src/static.ts +2 -2
- package/src/stdlib.ts +55 -2
- package/src/syntax.ts +357 -52
- package/src/types.ts +443 -16
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,53 @@ 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 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
|
+
covers: same(a.covers ?? [], b.covers ?? []),
|
|
106
|
+
};
|
|
107
|
+
return Object.values(fields).every(Boolean);
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
/** What `extendDomain` adds to a domain: more parents,
|
|
110
|
+
/** What `extendDomain` adds to a domain: more parents, the types of more steps, and more covers. */
|
|
99
111
|
export interface DomainExtension {
|
|
100
112
|
/** The names of the parents to add. A member of the domain then also passes the test of each new parent. */
|
|
101
113
|
readonly under?: readonly string[];
|
|
102
114
|
/** The types of the steps to add. A step with the key of an earlier step replaces its type. */
|
|
103
115
|
readonly steps?: Readonly<Record<string, ValueType>>;
|
|
116
|
+
/**
|
|
117
|
+
* The covers to add (TYPE.FITS-COVER, D85). A cover that the domain has already does not change. The language that
|
|
118
|
+
* adds a cover keeps it true, because `fits` trusts it.
|
|
119
|
+
*/
|
|
120
|
+
readonly covers?: readonly (readonly string[])[];
|
|
104
121
|
}
|
|
105
122
|
|
|
106
123
|
/**
|
|
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 and
|
|
110
|
-
*
|
|
124
|
+
* This function makes a part of a merge that adds parents, steps or covers to a domain of an earlier part
|
|
125
|
+
* (TYPE.PARENTS). An example is a domain of another language. The part reads the domain from the merge before it
|
|
126
|
+
* (`prev`). It gives a copy with the same test, summary, codec and list of values, and it names `["domains", name]`
|
|
127
|
+
* in `replaces` (D17).
|
|
128
|
+
* A domain of the kernel that the earlier parts do not name comes from `STD_DOMAINS`.
|
|
129
|
+
*
|
|
130
|
+
* An extension does not change the codec. Thus each member keeps its document (D80).
|
|
111
131
|
*
|
|
112
132
|
* A new parent adds its test to the domain (TYPE.MEMBER). Thus `fits` stays sound, but a value that fails the test of
|
|
113
133
|
* the parent is no longer a member. The language that adds the parent keeps the signatures of the other language
|
|
114
|
-
* true. A
|
|
134
|
+
* true. A new parent of a domain in a cover of another domain can make that cover false (D85). A name without a
|
|
135
|
+
* domain gives an empty part, and `typeIssues` reports each use of that name.
|
|
115
136
|
*
|
|
116
137
|
* @param name - The name of the domain.
|
|
117
|
-
* @param more - The parents and the
|
|
138
|
+
* @param more - The parents, the steps and the covers to add.
|
|
118
139
|
* @returns The part, as a function of the merge before it.
|
|
119
140
|
*/
|
|
120
141
|
export function extendDomain(name: string, more: DomainExtension): (prev: Machine) => MachinePart {
|
|
@@ -125,14 +146,25 @@ export function extendDomain(name: string, more: DomainExtension): (prev: Machin
|
|
|
125
146
|
const under = [...(found.under ?? [])];
|
|
126
147
|
for (const parent of more.under ?? []) if (parent !== name && !under.includes(parent)) under.push(parent);
|
|
127
148
|
const steps = { ...found.steps, ...more.steps };
|
|
128
|
-
const
|
|
149
|
+
const covers = [...(found.covers ?? [])];
|
|
150
|
+
for (const cover of more.covers ?? []) if (!covers.some((each) => same(each, cover))) covers.push([...cover]);
|
|
151
|
+
// The type names each field of Domain, thus a new field fails the check of types until the copy reads it (K180).
|
|
152
|
+
const fields: { readonly [K in keyof Domain]-?: Domain[K] | undefined } = {
|
|
129
153
|
is: found.is,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
under: under.length === 0 ? undefined : under,
|
|
155
|
+
summary: found.summary,
|
|
156
|
+
steps: Object.keys(steps).length === 0 ? undefined : steps,
|
|
157
|
+
// A new parent only adds a test, thus each member keeps its document (D80).
|
|
158
|
+
codec: found.codec,
|
|
159
|
+
// A new parent only adds a test, thus the list of values stays complete (TYPE.FITS-FINITE).
|
|
160
|
+
values: found.values,
|
|
161
|
+
// A new parent only adds a test, thus each cover stays true (TYPE.FITS-COVER). A new cover is a claim of the extension.
|
|
162
|
+
covers: covers.length === 0 ? undefined : covers,
|
|
133
163
|
};
|
|
164
|
+
const domain: Record<string, unknown> = {};
|
|
165
|
+
for (const [key, value] of Object.entries(fields)) if (value !== undefined) domain[key] = value;
|
|
134
166
|
const domains: Record<string, Domain> = {};
|
|
135
|
-
setOwn(domains, name, domain);
|
|
167
|
+
setOwn(domains, name, domain as unknown as Domain);
|
|
136
168
|
return { domains, replaces: [["domains", name]] };
|
|
137
169
|
};
|
|
138
170
|
}
|
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,6 +14,7 @@ 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,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
makeContext,
|
|
24
25
|
permits,
|
|
25
26
|
placeText,
|
|
27
|
+
reachedOf,
|
|
26
28
|
waitedReason,
|
|
27
29
|
} from "./evaluate.ts";
|
|
28
30
|
import { type FormulaProfile, type OpFn, Held, STD_EXT } from "./formula.ts";
|
|
@@ -57,6 +59,12 @@ export interface ReferenceOptions {
|
|
|
57
59
|
* each verb by the same rules (VERB.AGREE).
|
|
58
60
|
*/
|
|
59
61
|
readonly verb?: string;
|
|
62
|
+
/**
|
|
63
|
+
* 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:
|
|
64
|
+
* the path, what it reached and the mark `free`. The reads of a closure and of an equation of a tree go in the
|
|
65
|
+
* same list. The reference adds no kept reads, because it has no static reads of a closure.
|
|
66
|
+
*/
|
|
67
|
+
readonly reads?: Read[];
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
/** The options of a run of the reference: the options of an evaluation, with the host and a signal (REF.RUN). */
|
|
@@ -80,6 +88,13 @@ interface Scope {
|
|
|
80
88
|
alls: number;
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
/** The record of the reads of one evaluation of the reference (REF.READS). */
|
|
92
|
+
interface Reading {
|
|
93
|
+
readonly list: Read[];
|
|
94
|
+
/** The context at the root of the evaluation. A free read reads this context. */
|
|
95
|
+
readonly root: Context;
|
|
96
|
+
}
|
|
97
|
+
|
|
83
98
|
/** The state of one evaluation of the reference. */
|
|
84
99
|
interface Env {
|
|
85
100
|
readonly machine: Machine;
|
|
@@ -95,6 +110,8 @@ interface Env {
|
|
|
95
110
|
readonly verb: string;
|
|
96
111
|
/** The scope of the code that evaluates at this time. `all` changes it for each item. */
|
|
97
112
|
scope: Scope;
|
|
113
|
+
/** The record of the reads. A nested evaluation for an equation of a tree keeps the record of its tree. */
|
|
114
|
+
readonly reading: Reading | undefined;
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
/** The code that answers a document: a function evaluator, a runner, or a gap of the reference. */
|
|
@@ -221,6 +238,44 @@ function resolveFully(machine: Machine, ctx: Context, path: readonly unknown[],
|
|
|
221
238
|
return options?.hops === true ? found : { ...found, hops: [] };
|
|
222
239
|
}
|
|
223
240
|
|
|
241
|
+
/** The text that names a resolution in the error of an exception of the host (READS.RECORD). */
|
|
242
|
+
const READ_FAILED = "the resolution of a path";
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* This function resolves a path for a document, and records the read when the evaluation has a list (REF.READS,
|
|
246
|
+
* READS.RECORD). An exception of the host gives the error value of the read, and the exception goes on.
|
|
247
|
+
*/
|
|
248
|
+
function readAt(env: Env, ctx: Context, path: readonly unknown[], options?: ResolveOptions): Resolution {
|
|
249
|
+
const reading = env.reading;
|
|
250
|
+
if (reading === undefined) return resolveFully(env.machine, ctx, path, options);
|
|
251
|
+
const free = freeIn(reading.root, ctx, path);
|
|
252
|
+
let found: Resolution;
|
|
253
|
+
try {
|
|
254
|
+
found = resolveFully(env.machine, ctx, path, options);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
reading.list.push({ path, reached: thrown(error, READ_FAILED), free });
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
reading.list.push(options?.hops === true ? { path, reached: reachedOf(found), free, hops: found.hops } : { path, reached: reachedOf(found), free });
|
|
260
|
+
return found;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* This function tells if a read reads the context at the root of the evaluation (READS.FREE). The reference reads
|
|
265
|
+
* 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
|
|
266
|
+
* name that a kind does not bind keeps its value from the root.
|
|
267
|
+
*/
|
|
268
|
+
function freeIn(root: Context, ctx: Context, path: readonly unknown[]): boolean {
|
|
269
|
+
const head = path[0];
|
|
270
|
+
if (head === "self") return Object.is(ctx.self, root.self);
|
|
271
|
+
if (head !== "names") return typeof head === "string" && (ROOTS as readonly string[]).includes(head);
|
|
272
|
+
const name = path[1];
|
|
273
|
+
if (ctx.names === root.names || !isKey(name)) return true;
|
|
274
|
+
const key = String(name);
|
|
275
|
+
const bound = Object.hasOwn(ctx.names, key);
|
|
276
|
+
return bound === Object.hasOwn(root.names, key) && (!bound || Object.is(ctx.names[key], root.names[key]));
|
|
277
|
+
}
|
|
278
|
+
|
|
224
279
|
/**
|
|
225
280
|
* This function causes an effect when the capabilities permit it (RUN.CAP, CAP.HOOK). It records the effect with its
|
|
226
281
|
* address (RUN.ADDRESS) and its outcome (RUN.OUTCOME). The mock answers the effect at once (RUN.MOCK). Without a
|
|
@@ -385,7 +440,7 @@ function evalInput(env: Env, seen: Seen, at: At): EvalInput {
|
|
|
385
440
|
place: at.place,
|
|
386
441
|
machine: env.machine,
|
|
387
442
|
...(at.address ? { address: true } : {}),
|
|
388
|
-
resolve: (path, options) =>
|
|
443
|
+
resolve: (path, options) => readAt(env, at.ctx, path, options),
|
|
389
444
|
effect: (kind, payload) => causeFromEvaluator(env, at.ctx, kind, payload, at.place),
|
|
390
445
|
fill,
|
|
391
446
|
};
|
|
@@ -413,7 +468,7 @@ function runInput(env: Env, seen: Seen, at: At): RunInput {
|
|
|
413
468
|
context: at.ctx,
|
|
414
469
|
place: at.place,
|
|
415
470
|
machine: env.machine,
|
|
416
|
-
resolve: (path, options) =>
|
|
471
|
+
resolve: (path, options) => readAt(env, at.ctx, path, options),
|
|
417
472
|
effect: (kind, payload) => causeAndWait(env, at.ctx, kind, payload, at.place),
|
|
418
473
|
all: (list, options) => together(env, list, options),
|
|
419
474
|
fill,
|
|
@@ -458,6 +513,7 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
|
|
|
458
513
|
outer.alls += 1;
|
|
459
514
|
let failed = false;
|
|
460
515
|
let started = 0;
|
|
516
|
+
let before = 0;
|
|
461
517
|
const end = (index: number, result: Result): void => {
|
|
462
518
|
results[index] = result;
|
|
463
519
|
if (failFast && !result.ok) failed = true;
|
|
@@ -508,10 +564,11 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
|
|
|
508
564
|
};
|
|
509
565
|
startMore();
|
|
510
566
|
while (waiting.length > 0 && !failed) {
|
|
511
|
-
const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index));
|
|
567
|
+
const chosen = env.journal?.next?.(address, waiting.map((waiter) => waiter.index), before);
|
|
512
568
|
const pick = chosen === undefined ? undefined : waiting.find((waiter) => waiter.index === chosen);
|
|
513
569
|
const resumed = (yield pick === undefined ? Promise.race(waiting.map((waiter) => waiter.next)) : pick.next) as Resumed;
|
|
514
570
|
env.journal?.settled?.(address, resumed.waiter.index);
|
|
571
|
+
before += 1;
|
|
515
572
|
waiting.splice(waiting.indexOf(resumed.waiter), 1);
|
|
516
573
|
goOn(resumed.waiter.index, resumed.waiter.steps, resumed);
|
|
517
574
|
startMore();
|
|
@@ -543,9 +600,18 @@ function* together(env: Env, list: readonly RunPart[], options: AllOptions = {})
|
|
|
543
600
|
* @returns The result.
|
|
544
601
|
*/
|
|
545
602
|
export function reference(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceOptions = {}): Result {
|
|
546
|
-
|
|
603
|
+
return evaluateRoot(machine, doc, context, options, undefined);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* This function evaluates a document from its root. An equation of a tree gives the record of its tree. Thus the
|
|
608
|
+
* reads of the equation go in the list of the outer evaluation (REF.READS).
|
|
609
|
+
*/
|
|
610
|
+
function evaluateRoot(machine: Machine, doc: unknown, context: Context, options: ReferenceOptions, outer: Reading | undefined): Result {
|
|
611
|
+
const at = rootAt(machine, doc, context, options);
|
|
612
|
+
const env = envOf(machine, options, false, at.ctx, outer);
|
|
547
613
|
try {
|
|
548
|
-
return within(env, () => evaluateAt(env,
|
|
614
|
+
return within(env, () => evaluateAt(env, at));
|
|
549
615
|
} catch (error) {
|
|
550
616
|
return placed(thrown(error, "the reference"), []);
|
|
551
617
|
}
|
|
@@ -567,13 +633,17 @@ export function reference(machine: Machine, doc: unknown, context: Context = mak
|
|
|
567
633
|
* @returns The result, when the run ends.
|
|
568
634
|
*/
|
|
569
635
|
export function referenceRun(machine: Machine, doc: unknown, context: Context = makeContext(), options: ReferenceRunOptions = {}): Promise<Result> {
|
|
570
|
-
const
|
|
571
|
-
const
|
|
636
|
+
const at = rootAt(machine, doc, context, options);
|
|
637
|
+
const env = envOf(machine, options, true, at.ctx, undefined);
|
|
638
|
+
const steps = stepsAt(env, at);
|
|
572
639
|
return driveRun(env, steps, options.signal).catch((error: unknown) => placed(thrown(error, "the reference"), []));
|
|
573
640
|
}
|
|
574
641
|
|
|
575
|
-
/**
|
|
576
|
-
function
|
|
642
|
+
/**
|
|
643
|
+
* This function makes the state of the reference for one evaluation or one run. The record of the reads comes from
|
|
644
|
+
* the evaluation around it, or from the list of the options.
|
|
645
|
+
*/
|
|
646
|
+
function envOf(machine: Machine, options: ReferenceOptions, running: boolean, root: Context, outer: Reading | undefined): Env {
|
|
577
647
|
const perform = options.perform;
|
|
578
648
|
return {
|
|
579
649
|
machine,
|
|
@@ -585,6 +655,7 @@ function envOf(machine: Machine, options: ReferenceOptions, running: boolean): E
|
|
|
585
655
|
running,
|
|
586
656
|
verb: options.verb ?? "eval",
|
|
587
657
|
scope: { prefix: "", counts: new Map(), alls: 0 },
|
|
658
|
+
reading: outer ?? (options.reads === undefined ? undefined : { list: options.reads, root }),
|
|
588
659
|
};
|
|
589
660
|
}
|
|
590
661
|
|
|
@@ -698,13 +769,14 @@ function refNodeOf(thing: unknown): RefNode | undefined {
|
|
|
698
769
|
|
|
699
770
|
/**
|
|
700
771
|
* 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.
|
|
772
|
+
* its rung and no capabilities. A bare equation gets the type `of` of the parts of the kind. The reads of the
|
|
773
|
+
* equation go in the record of the evaluation that made the node (REF.READS).
|
|
702
774
|
*/
|
|
703
775
|
function equationOf(node: RefNode, term: Json, names: Readonly<Record<string, unknown>>): Result {
|
|
704
776
|
const env = node.env;
|
|
705
777
|
if (env === undefined) return fail("#NOEVAL", "The evaluation of an equation needs the machine.");
|
|
706
778
|
const ctx: Context = { ...node.ctx, names, caps: [], self: node.thing };
|
|
707
|
-
return
|
|
779
|
+
return evaluateRoot(env.machine, term, ctx, { definitions: env.definitions, ...(node.of === undefined ? {} : { declaredType: node.of }) }, env.reading);
|
|
708
780
|
}
|
|
709
781
|
|
|
710
782
|
function termIn<T>(table: Readonly<Record<string, T>> | undefined, name: string): T | undefined {
|
|
@@ -960,7 +1032,11 @@ export function formulaDefinitions(profile: FormulaProfile = {}): Definitions {
|
|
|
960
1032
|
// FORMULA.LET: a path through a name whose binding failed gives the error of the binding.
|
|
961
1033
|
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : undefined;
|
|
962
1034
|
const bound = head === undefined ? undefined : own(context.names, head);
|
|
963
|
-
if (bound instanceof Held)
|
|
1035
|
+
if (bound instanceof Held) {
|
|
1036
|
+
// The result comes from the binding of the name, thus the name is a read (READS.RECORD).
|
|
1037
|
+
walk(["names", head]);
|
|
1038
|
+
return { ok: false, error: bound.error };
|
|
1039
|
+
}
|
|
964
1040
|
const found = walk(steps);
|
|
965
1041
|
if (!found.ok) return fail(found.code, found.reason);
|
|
966
1042
|
// FORMULA.ADDRESS: an address gives what the path reaches.
|