@mark1russell7/coeval 0.26.1 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +42 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1560 -1363
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +1 -0
- package/src/evaluate.ts +118 -14
- package/src/formula.ts +13 -5
- package/src/generate.ts +6 -1
- package/src/index.ts +2 -1
- package/src/language.ts +51 -1
- package/src/merge.ts +1 -1
- package/src/reference.ts +102 -39
- package/src/stdlib.ts +39 -14
- package/src/stepper.ts +49 -6
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.27.1",
|
|
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",
|
package/src/errors.ts
CHANGED
package/src/evaluate.ts
CHANGED
|
@@ -541,6 +541,25 @@ export function placeText(place: readonly Key[]): string {
|
|
|
541
541
|
/** An effect while the kernel waits for its outcome. */
|
|
542
542
|
type Recorded = { -readonly [K in keyof Effect]: Effect[K] };
|
|
543
543
|
|
|
544
|
+
/**
|
|
545
|
+
* The most work of one evaluation for a machine without its own `maxWork` (WORK.LIMIT, D134). One application of a
|
|
546
|
+
* closure is one point.
|
|
547
|
+
*/
|
|
548
|
+
export const WORK_LIMIT = 200000;
|
|
549
|
+
|
|
550
|
+
/** The work of one node that an equation makes (WORK.NODE, D134). A node costs more than an application. */
|
|
551
|
+
export const NODE_WORK = 8;
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* This function gives the error of the limit of work. Each tier gives this error (WORK.AGREES).
|
|
555
|
+
*
|
|
556
|
+
* @param limit - The limit of the machine.
|
|
557
|
+
* @returns The error.
|
|
558
|
+
*/
|
|
559
|
+
export function workFailure(limit: number): EvalError {
|
|
560
|
+
return fail("#WORK!", `The work of one evaluation is more than ${limit} points. The field maxWork of the machine sets the limit.`).error;
|
|
561
|
+
}
|
|
562
|
+
|
|
544
563
|
/**
|
|
545
564
|
* The runtime of one evaluation or run. Compiled code gets it with the context, and asks it to resolve paths and
|
|
546
565
|
* to cause effects.
|
|
@@ -549,6 +568,15 @@ export interface Runtime {
|
|
|
549
568
|
readonly machine: Machine;
|
|
550
569
|
/** `true` in a run: the host does each effect, and a part that waits needs a parent that can wait. */
|
|
551
570
|
readonly running: boolean;
|
|
571
|
+
/** `true` when a node that this runtime makes costs work: in an equation of a tree (WORK.NODE) and in a fold. */
|
|
572
|
+
readonly nested?: boolean;
|
|
573
|
+
/**
|
|
574
|
+
* This optional function adds work to the count of the evaluation (WORK.LIMIT). A runtime without it counts nothing.
|
|
575
|
+
*
|
|
576
|
+
* @param points - The work.
|
|
577
|
+
* @throws {Failure} The error `#WORK!`, when the count is over the limit of the machine.
|
|
578
|
+
*/
|
|
579
|
+
spend?(points: number): void;
|
|
552
580
|
/**
|
|
553
581
|
* This function resolves a path from a context.
|
|
554
582
|
*
|
|
@@ -819,6 +847,11 @@ export interface CompiledEvaluator {
|
|
|
819
847
|
* (TYPE.RULE). Only a check reads it. An evaluator without a rule gives `unknown`.
|
|
820
848
|
*/
|
|
821
849
|
readonly type?: TypeRule;
|
|
850
|
+
/**
|
|
851
|
+
* `true` when a value that the code gives can use its runtime after the end of the evaluation (WORK.KEEPS). The plan
|
|
852
|
+
* then marks that end. A compiled evaluator without this field gives no such value.
|
|
853
|
+
*/
|
|
854
|
+
readonly keeps?: boolean;
|
|
822
855
|
}
|
|
823
856
|
|
|
824
857
|
/** An evaluator: a function of its input, or a compiler. */
|
|
@@ -848,6 +881,7 @@ export type Runner = RunFn | CompiledRunner;
|
|
|
848
881
|
* A machine: a world of kinds, and the code of each evaluator name, of each `deref` and of each walker.
|
|
849
882
|
*
|
|
850
883
|
* - `maxDepth`: the deepest nesting of documents. The default is 1000.
|
|
884
|
+
* - `maxWork`: the most work of one evaluation (WORK.LIMIT, D134). The default is `WORK_LIMIT`.
|
|
851
885
|
* - `permits`: the rule of the host for an effect. The default is the function `permits` on the capabilities. A run,
|
|
852
886
|
* `check` and `effectsOf` ask it (CAP.PERMITS). In a check, its context has only the capabilities and the world.
|
|
853
887
|
* - `known`: the names that the evaluators know, for `check`. Without it, `check` does not check such names.
|
|
@@ -859,6 +893,7 @@ export interface Machine extends Navigator {
|
|
|
859
893
|
/** The runners, by evaluator name. A runner wins over an evaluator of the same name. */
|
|
860
894
|
readonly runners?: Readonly<Record<string, Runner>>;
|
|
861
895
|
readonly maxDepth?: number;
|
|
896
|
+
readonly maxWork?: number;
|
|
862
897
|
readonly permits?: (caps: readonly Cap[], kind: readonly Key[], context: Context) => boolean;
|
|
863
898
|
/**
|
|
864
899
|
* This function tells if an evaluator knows the name in the path of a kind. An example is the operation
|
|
@@ -1157,6 +1192,18 @@ function readingOf(list: Read[] | undefined, root: Context): Reading | undefined
|
|
|
1157
1192
|
|
|
1158
1193
|
/** This class holds the state of one evaluation or run: the machine, the effects, the clock and the trace. */
|
|
1159
1194
|
class Session implements Runtime {
|
|
1195
|
+
/**
|
|
1196
|
+
* The count of the work, on the runtime of the root of the evaluation (WORK.LIMIT). The first count adds the field,
|
|
1197
|
+
* thus an evaluation without a counted point has no field.
|
|
1198
|
+
*/
|
|
1199
|
+
declare work?: number;
|
|
1200
|
+
/** The count of the applications of closures that run at this time. `formula.fn` writes it (FORMULA.CALL-DEPTH). */
|
|
1201
|
+
declare applying?: number;
|
|
1202
|
+
/**
|
|
1203
|
+
* The count of the applications of closures that run at this time, on the root and on each runtime of an equation
|
|
1204
|
+
* (WORK.AFTER). `formula.fn` writes it on the runtime of the root. The first application adds the field.
|
|
1205
|
+
*/
|
|
1206
|
+
declare active?: number;
|
|
1160
1207
|
readonly machine: Machine;
|
|
1161
1208
|
/**
|
|
1162
1209
|
* The epoch of the evaluation: the list of capabilities that the host gave the root (CAP.NARROW-KEPT). A context
|
|
@@ -1166,7 +1213,12 @@ class Session implements Runtime {
|
|
|
1166
1213
|
readonly running: boolean;
|
|
1167
1214
|
readonly traced: boolean;
|
|
1168
1215
|
readonly perform: Perform | undefined;
|
|
1169
|
-
|
|
1216
|
+
/**
|
|
1217
|
+
* The runtime of the root, on the runtime of an equation of a tree (WORK.EQUATION). The runtime of a root holds
|
|
1218
|
+
* itself after the end of its evaluation (WORK.AFTER, WORK.KEEPS), thus the end mark adds no field to the object.
|
|
1219
|
+
* Each runtime has this field, thus a tree and its equations have one object shape.
|
|
1220
|
+
*/
|
|
1221
|
+
top: Session | undefined;
|
|
1170
1222
|
/**
|
|
1171
1223
|
* The record of the reads. Without it, a resolution records nothing. A nested evaluation of an equation of a tree
|
|
1172
1224
|
* shares the record of the evaluation around it (READS.EQUATION).
|
|
@@ -1196,7 +1248,7 @@ class Session implements Runtime {
|
|
|
1196
1248
|
running: boolean,
|
|
1197
1249
|
traced: boolean,
|
|
1198
1250
|
perform: Perform | undefined,
|
|
1199
|
-
|
|
1251
|
+
top: Session | undefined,
|
|
1200
1252
|
effects: Effect[] | undefined,
|
|
1201
1253
|
reading: Reading | undefined = undefined,
|
|
1202
1254
|
) {
|
|
@@ -1205,13 +1257,36 @@ class Session implements Runtime {
|
|
|
1205
1257
|
this.running = running;
|
|
1206
1258
|
this.traced = traced;
|
|
1207
1259
|
this.perform = perform;
|
|
1208
|
-
this.
|
|
1260
|
+
this.top = top;
|
|
1209
1261
|
this.reading = reading;
|
|
1210
1262
|
this.list = effects;
|
|
1211
1263
|
this.holder = traced ? holderNode() : undefined;
|
|
1212
1264
|
this.current = this.holder;
|
|
1213
1265
|
}
|
|
1214
1266
|
|
|
1267
|
+
/** This getter gives `true` for the runtime of an equation of a tree (WORK.NODE). */
|
|
1268
|
+
get nested(): boolean {
|
|
1269
|
+
const top = this.top;
|
|
1270
|
+
return top !== undefined && top !== this;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* This function adds work to the count of the evaluation (WORK.LIMIT). One count is on the runtime of the root, thus
|
|
1275
|
+
* each equation of a tree adds to it. After the end of the evaluation, each entry from the host starts a new count
|
|
1276
|
+
* (WORK.AFTER). An entry is a point with no application of a closure that runs, on any runtime of the evaluation.
|
|
1277
|
+
*
|
|
1278
|
+
* @param points - The work.
|
|
1279
|
+
* @throws {Failure} The error `#WORK!`, when the count is over the limit of the machine.
|
|
1280
|
+
*/
|
|
1281
|
+
spend(points: number): void {
|
|
1282
|
+
const root = this.top ?? this;
|
|
1283
|
+
const entry = root.top === root && (root.active ?? 0) === 0;
|
|
1284
|
+
const work = (entry ? 0 : (root.work ?? 0)) + points;
|
|
1285
|
+
root.work = work;
|
|
1286
|
+
const limit = this.machine.maxWork ?? WORK_LIMIT;
|
|
1287
|
+
if (work > limit) throw new Failure(workFailure(limit));
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1215
1290
|
/** The effects of the evaluation, in their order. */
|
|
1216
1291
|
get effects(): Effect[] {
|
|
1217
1292
|
return (this.list ??= []);
|
|
@@ -1753,6 +1828,8 @@ interface Compiler {
|
|
|
1753
1828
|
readonly guards: ReadonlyMap<string, ValueType> | undefined;
|
|
1754
1829
|
/** The runtime of the folds of this compilation. The first fold makes it. */
|
|
1755
1830
|
probe?: Probe;
|
|
1831
|
+
/** `true` when a document of this compilation can use its runtime after the end of the evaluation (WORK.KEEPS). */
|
|
1832
|
+
keeps?: boolean;
|
|
1756
1833
|
}
|
|
1757
1834
|
|
|
1758
1835
|
function failing(c: Compiler, meta: Meta, place: readonly Key[], code: ErrorCode, reason: string): Compiled {
|
|
@@ -1890,6 +1967,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1890
1967
|
};
|
|
1891
1968
|
// A compiler that throws fails its own document when that document evaluates, with its place (PLAN.FAIL-LATE).
|
|
1892
1969
|
if (runner !== undefined) {
|
|
1970
|
+
c.keeps = true;
|
|
1893
1971
|
let inner: RunCode;
|
|
1894
1972
|
try {
|
|
1895
1973
|
inner = isCompiledRunner(runner) ? runner.compileRun(input) : interpretRun(runner, input, split.fill, kind?.caps?.some((rule) => rule.rule === "grant") === true);
|
|
@@ -1901,6 +1979,7 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1901
1979
|
return finish(c, meta, runnerCode(run, place, kindText), run);
|
|
1902
1980
|
}
|
|
1903
1981
|
|
|
1982
|
+
if (!isCompiledEvaluator(evaluator as Evaluator) || (evaluator as CompiledEvaluator).keeps === true) c.keeps = true;
|
|
1904
1983
|
let inner: Code;
|
|
1905
1984
|
try {
|
|
1906
1985
|
inner = isCompiledEvaluator(evaluator as Evaluator) ? (evaluator as CompiledEvaluator).compile(input) : interpret(evaluator as EvalFn, input, split.fill);
|
|
@@ -1990,6 +2069,8 @@ class Probe implements Runtime {
|
|
|
1990
2069
|
readonly running = false;
|
|
1991
2070
|
/** `true` after the part asked for something that a fold cannot give. */
|
|
1992
2071
|
refused = false;
|
|
2072
|
+
/** A node in a fold costs work, thus a tree stops the fold before it evaluates an equation (WORK.PREPARE). */
|
|
2073
|
+
readonly nested = true;
|
|
1993
2074
|
/** The count of the names that the part read. */
|
|
1994
2075
|
reads = 0;
|
|
1995
2076
|
/**
|
|
@@ -2058,6 +2139,12 @@ class Probe implements Runtime {
|
|
|
2058
2139
|
keepsContext(): void {
|
|
2059
2140
|
this.refused = true;
|
|
2060
2141
|
}
|
|
2142
|
+
|
|
2143
|
+
/** This function stops the fold at the first point of work, thus `prepare` does no such work (WORK.PREPARE). */
|
|
2144
|
+
spend(): void {
|
|
2145
|
+
this.refused = true;
|
|
2146
|
+
throw new Failure(workFailure(0));
|
|
2147
|
+
}
|
|
2061
2148
|
}
|
|
2062
2149
|
|
|
2063
2150
|
/**
|
|
@@ -2418,8 +2505,8 @@ export interface InputWithRuntime extends EvalInput {
|
|
|
2418
2505
|
/**
|
|
2419
2506
|
* This function evaluates a plan inside the evaluation of another runtime, for example an equation of a tree inside
|
|
2420
2507
|
* the evaluation of its tree (READS.EQUATION). The plan gets its own runtime, as in `plan.evaluate`. The reads of the
|
|
2421
|
-
* plan go in the record of the reads of `outer
|
|
2422
|
-
* (READS.FREE). Without
|
|
2508
|
+
* plan go in the record of the reads of `outer`, and its work goes in the count of `outer` (WORK.EQUATION). The context
|
|
2509
|
+
* at the root of `outer` tells which reads are free (READS.FREE). Without a session, the function gives `plan.evaluate(context)`.
|
|
2423
2510
|
*
|
|
2424
2511
|
* @param plan - The plan.
|
|
2425
2512
|
* @param context - The context of the plan.
|
|
@@ -2427,11 +2514,10 @@ export interface InputWithRuntime extends EvalInput {
|
|
|
2427
2514
|
* @returns The result.
|
|
2428
2515
|
*/
|
|
2429
2516
|
export function evaluateWithin(plan: Plan, context: Context, outer: Runtime | undefined): Result {
|
|
2430
|
-
|
|
2431
|
-
if (reading === undefined || !(plan instanceof CompiledPlan)) return plan.evaluate(context);
|
|
2517
|
+
if (!(outer instanceof Session) || !(plan instanceof CompiledPlan)) return plan.evaluate(context);
|
|
2432
2518
|
const machine = plan.machine;
|
|
2433
2519
|
const ctx = rootContext(machine, context);
|
|
2434
|
-
return plan.code(ctx, new Session(machine, ctx.caps, false, plan.traced, undefined,
|
|
2520
|
+
return plan.code(ctx, new Session(machine, ctx.caps, false, plan.traced, undefined, outer.top ?? outer, undefined, outer.reading));
|
|
2435
2521
|
}
|
|
2436
2522
|
|
|
2437
2523
|
function interpret(fn: EvalFn, input: CompileInput, fill: <P>(handles: readonly P[]) => unknown): Code {
|
|
@@ -2570,7 +2656,23 @@ export function prepare(machine: Machine, doc: unknown, options: PrepareOptions
|
|
|
2570
2656
|
const result = placed(thrown(error, "the compilation"), []);
|
|
2571
2657
|
root = { code: () => result, run: undefined };
|
|
2572
2658
|
}
|
|
2573
|
-
return new CompiledPlan(machine, c.traced, root.code, root.run);
|
|
2659
|
+
return new CompiledPlan(machine, c.traced, c.keeps === true ? endingCode(root.code) : root.code, root.run);
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
/**
|
|
2663
|
+
* This function gives the code of a plan with a document that can use its runtime after the end of the evaluation. The
|
|
2664
|
+
* code marks that end (WORK.KEEPS). Each other plan has no such step, thus the count adds nothing to a read.
|
|
2665
|
+
*
|
|
2666
|
+
* @param code - The code of the root.
|
|
2667
|
+
* @returns The code that marks the end.
|
|
2668
|
+
*/
|
|
2669
|
+
function endingCode(code: Code): Code {
|
|
2670
|
+
return (ctx, rt) => {
|
|
2671
|
+
const result = code(ctx, rt);
|
|
2672
|
+
const session = rt as Session;
|
|
2673
|
+
session.top ??= session;
|
|
2674
|
+
return result;
|
|
2675
|
+
};
|
|
2574
2676
|
}
|
|
2575
2677
|
|
|
2576
2678
|
/**
|
|
@@ -2619,8 +2721,10 @@ class CompiledPlan implements Plan {
|
|
|
2619
2721
|
run(context: Context = makeContext(), runOptions: PlanRunOptions = {}): Promise<Result> {
|
|
2620
2722
|
const machine = this.machine;
|
|
2621
2723
|
const ctx = rootContext(machine, context);
|
|
2622
|
-
const s = new Session(machine, ctx.caps, true, this.traced, runOptions.perform,
|
|
2623
|
-
return driveWaiting(this.runCode()(ctx, s), runOptions.signal)
|
|
2724
|
+
const s = new Session(machine, ctx.caps, true, this.traced, runOptions.perform, undefined, undefined, readingOf(runOptions.reads, ctx));
|
|
2725
|
+
return driveWaiting(this.runCode()(ctx, s), runOptions.signal).finally(() => {
|
|
2726
|
+
s.top = s;
|
|
2727
|
+
});
|
|
2624
2728
|
}
|
|
2625
2729
|
}
|
|
2626
2730
|
|
|
@@ -2689,8 +2793,7 @@ export function evaluate(machine: Machine, doc: unknown, context: Context = make
|
|
|
2689
2793
|
});
|
|
2690
2794
|
const ctx = rootContext(machine, context);
|
|
2691
2795
|
const s = new Session(machine, ctx.caps, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
|
|
2692
|
-
|
|
2693
|
-
return record(s, result);
|
|
2796
|
+
return record(s, (plan as CompiledPlan).code(ctx, s));
|
|
2694
2797
|
}
|
|
2695
2798
|
|
|
2696
2799
|
/**
|
|
@@ -2712,8 +2815,9 @@ export async function run(machine: Machine, doc: unknown, context: Context = mak
|
|
|
2712
2815
|
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
2713
2816
|
});
|
|
2714
2817
|
const ctx = rootContext(machine, context);
|
|
2715
|
-
const s = new Session(machine, ctx.caps, true, true, options.perform,
|
|
2818
|
+
const s = new Session(machine, ctx.caps, true, true, options.perform, undefined, undefined, readingOf(options.reads, ctx));
|
|
2716
2819
|
const result = await driveWaiting((plan as CompiledPlan).runCode()(ctx, s), options.signal);
|
|
2820
|
+
s.top = s;
|
|
2717
2821
|
return record(s, result);
|
|
2718
2822
|
}
|
|
2719
2823
|
|
package/src/formula.ts
CHANGED
|
@@ -139,7 +139,7 @@ export function isClosure(value: unknown): value is Closure {
|
|
|
139
139
|
* The runtime of an evaluation with the clock of `call` (FORMULA.CALL-DEPTH, D125): the count of the applications of
|
|
140
140
|
* closures that run at this time. The first application adds the field, thus an evaluation without one has no field.
|
|
141
141
|
*/
|
|
142
|
-
type Clocked = Runtime & { applying?: number };
|
|
142
|
+
type Clocked = Runtime & { applying?: number; active?: number; top?: Clocked | undefined };
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
145
|
* The standard extensions: the values that JSON cannot write. `ext.domain` reads a member of a domain through the
|
|
@@ -175,8 +175,8 @@ function setOwn(target: Record<string, unknown>, key: string, value: unknown): v
|
|
|
175
175
|
else target[key] = value;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function compiled(compile: (input: CompileInput) => Code): CompiledEvaluator {
|
|
179
|
-
return { compile };
|
|
178
|
+
function compiled(compile: (input: CompileInput) => Code, keeps?: true): CompiledEvaluator {
|
|
179
|
+
return keeps === undefined ? { compile } : { compile, keeps };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/** This function evaluates parts in order. It gives the value of each part, or the first error. */
|
|
@@ -756,7 +756,7 @@ function makeEvaluators(profile: FormulaProfile): Readonly<Record<string, Evalua
|
|
|
756
756
|
for (const name of Object.keys(evaluators)) {
|
|
757
757
|
const evaluator = evaluators[name] as CompiledEvaluator;
|
|
758
758
|
const type = Object.hasOwn(types, name) ? types[name] : undefined;
|
|
759
|
-
const made = Object.freeze(type === undefined ? evaluator : {
|
|
759
|
+
const made = Object.freeze(type === undefined ? evaluator : { ...evaluator, type });
|
|
760
760
|
if (truth !== undefined && TRUTH_READERS.includes(name)) truthByCode.set(made, truth);
|
|
761
761
|
if (PROFILE_READERS.includes(name)) readerByCode.set(made, { name, profile });
|
|
762
762
|
out[name] = made;
|
|
@@ -1045,7 +1045,13 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
1045
1045
|
// Each application counts one level on the runtime of its evaluation, and the count goes down after it (FORMULA.CALL-DEPTH).
|
|
1046
1046
|
const level = (clock.applying ?? 0) + 1;
|
|
1047
1047
|
if (level > limit) throw new Failure(fail("#DEPTH!", `The applications of functions nest deeper than ${limit} levels.`).error);
|
|
1048
|
+
// WORK.LIMIT: an application is a place where work can multiply, thus each one counts one point (D134).
|
|
1049
|
+
rt.spend?.(1);
|
|
1050
|
+
// WORK.AFTER: the runtime of the root counts the applications of each runtime of the evaluation. An equation of
|
|
1051
|
+
// a tree in the body has its own runtime, and its work does not start a new count.
|
|
1052
|
+
const root = clock.top ?? clock;
|
|
1048
1053
|
clock.applying = level;
|
|
1054
|
+
root.active = (root.active ?? 0) + 1;
|
|
1049
1055
|
try {
|
|
1050
1056
|
const scope: Record<string, unknown> = { ...ctx.names };
|
|
1051
1057
|
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
|
@@ -1054,12 +1060,14 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
1054
1060
|
return result.value;
|
|
1055
1061
|
} finally {
|
|
1056
1062
|
clock.applying = level - 1;
|
|
1063
|
+
root.active = (root.active ?? 1) - 1;
|
|
1057
1064
|
}
|
|
1058
1065
|
};
|
|
1059
1066
|
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
1060
1067
|
return ok(closure);
|
|
1061
1068
|
};
|
|
1062
|
-
|
|
1069
|
+
// A closure uses its runtime after the end of the evaluation (WORK.KEEPS).
|
|
1070
|
+
}, true),
|
|
1063
1071
|
|
|
1064
1072
|
"formula.call": compiled(({ value }) => {
|
|
1065
1073
|
const record = partsOf(value);
|
package/src/generate.ts
CHANGED
|
@@ -198,7 +198,12 @@ export function generate(world: World, options: GenerateOptions): unknown {
|
|
|
198
198
|
const envelope = (type: readonly Key[], value: Json, header?: string): Json => {
|
|
199
199
|
const out: Record<string, Json> = { type: renderType(type), value };
|
|
200
200
|
if (header !== undefined) out["name"] = header;
|
|
201
|
-
if (options.own !== undefined && options.own.length > 0 && rng.chance(0.03))
|
|
201
|
+
if (options.own !== undefined && options.own.length > 0 && rng.chance(0.03)) {
|
|
202
|
+
const code = rng.pick(options.own);
|
|
203
|
+
// The code of a kind binds the names of its scope rules (C7). An own entry of other code does not, thus a kind
|
|
204
|
+
// with scope rules gets no own entry (GEN.CONTRACTS). The draws stay, thus each other document stays the same.
|
|
205
|
+
if ((kindOf(type)?.scope ?? []).length === 0) out["own"] = { eval: code };
|
|
206
|
+
}
|
|
202
207
|
return out;
|
|
203
208
|
};
|
|
204
209
|
|
package/src/index.ts
CHANGED
|
@@ -91,7 +91,7 @@ export {
|
|
|
91
91
|
restrictMachine,
|
|
92
92
|
restrictWorld,
|
|
93
93
|
} from "./merge.ts";
|
|
94
|
-
export { type Language, type LanguageCode, type LanguageDefinition, compose, defineLanguage } from "./language.ts";
|
|
94
|
+
export { type Language, type LanguageCode, type LanguageDefinition, boundary, compose, defineLanguage } from "./language.ts";
|
|
95
95
|
export {
|
|
96
96
|
type Answer,
|
|
97
97
|
type AskOptions,
|
|
@@ -186,6 +186,7 @@ export {
|
|
|
186
186
|
prepare,
|
|
187
187
|
resolveCompiled,
|
|
188
188
|
run,
|
|
189
|
+
WORK_LIMIT,
|
|
189
190
|
} from "./evaluate.ts";
|
|
190
191
|
export {
|
|
191
192
|
type AttributeAnswer,
|
package/src/language.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Key, isRecord } from "./doc.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type InputWithRuntime, type Machine, type Plan, RUNTIME, evaluateWithin, makeContext, planner } from "./evaluate.ts";
|
|
3
|
+
import { fail } from "./errors.ts";
|
|
3
4
|
import { type MachinePart, type MergeConflict, type WorldPart, MergeError, mergeMachines } from "./merge.ts";
|
|
4
5
|
|
|
5
6
|
/** The code half of a language: each field of a machine part, without the world. */
|
|
@@ -145,3 +146,52 @@ export function compose(...languages: readonly Language[]): Machine {
|
|
|
145
146
|
if (conflicts.length > 0) throw new MergeError(conflicts);
|
|
146
147
|
return mergeMachines(...parts);
|
|
147
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* This function makes the language of the boundary kind `lang` (K38, D193). A document of the kind holds the name of a
|
|
152
|
+
* language and a document in that language: `{ type: "lang", value: { language, doc } }`. The machine of the named
|
|
153
|
+
* language prepares and evaluates the inner document, as a module of another language (COMPOSE.BOUNDARY).
|
|
154
|
+
*
|
|
155
|
+
* The boundary is closed. The inner document gets no names, no capabilities and no node as `self`. Only its result
|
|
156
|
+
* crosses the boundary. Thus an effect in the inner document fails with `#CAP!`, and the inner machine cannot read the
|
|
157
|
+
* names of the outer document.
|
|
158
|
+
*
|
|
159
|
+
* @param machines - The machine of each language, by the name that a document gives.
|
|
160
|
+
* @returns The language, for `compose`.
|
|
161
|
+
*/
|
|
162
|
+
export function boundary(machines: Readonly<Record<string, Machine>>): Language {
|
|
163
|
+
const plans = new Map<string, (doc: unknown) => Plan>();
|
|
164
|
+
const planOf = (name: string): ((doc: unknown) => Plan) | undefined => {
|
|
165
|
+
let found = plans.get(name);
|
|
166
|
+
if (found === undefined && Object.hasOwn(machines, name)) {
|
|
167
|
+
found = planner(machines[name] as Machine);
|
|
168
|
+
plans.set(name, found);
|
|
169
|
+
}
|
|
170
|
+
return found;
|
|
171
|
+
};
|
|
172
|
+
return defineLanguage({
|
|
173
|
+
name: "boundary",
|
|
174
|
+
version: "1.0.0",
|
|
175
|
+
world: {
|
|
176
|
+
kinds: {
|
|
177
|
+
lang: {
|
|
178
|
+
verbs: { eval: "boundary.lang" },
|
|
179
|
+
summary: "A document in another language. The machine of that language evaluates it, with no names and no capabilities.",
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
code: {
|
|
184
|
+
evaluators: {
|
|
185
|
+
"boundary.lang": (input) => {
|
|
186
|
+
const { data, place } = input;
|
|
187
|
+
if (!isRecord(data) || typeof data["language"] !== "string" || !("doc" in data)) {
|
|
188
|
+
return fail("#VALUE!", "A document of the kind lang has the value { language, doc }.", { place });
|
|
189
|
+
}
|
|
190
|
+
const plans = planOf(data["language"]);
|
|
191
|
+
if (plans === undefined) return fail("#NAME?", `The boundary knows no language ${JSON.stringify(data["language"])}.`, { place });
|
|
192
|
+
return evaluateWithin(plans(data["doc"]), makeContext(), (input as InputWithRuntime)[RUNTIME]);
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
package/src/merge.ts
CHANGED
|
@@ -581,7 +581,7 @@ function checkHidden(m: Merger, draft: MachineDraft): void {
|
|
|
581
581
|
* - A runner hides the evaluator of its name. Thus one part must give both, or the merge gives a conflict.
|
|
582
582
|
* - The hooks `known` compose in the order of the parts. A hook does not answer for a name that a later part replaces.
|
|
583
583
|
* The hooks `facts` compose the same way, and the first answer that is not `undefined` wins.
|
|
584
|
-
* - Each other field, for example `permits`, `adopt`, `roots`, `plain` or `
|
|
584
|
+
* - Each other field, for example `permits`, `adopt`, `roots`, `plain`, `maxDepth` or `maxWork`, is one value. Two different
|
|
585
585
|
* values are a conflict. An absent field has no value.
|
|
586
586
|
*
|
|
587
587
|
* A conflict stops the merge, unless the later part names its path in `replaces`. A part can be a function of the
|