@mark1russell7/coeval 0.24.0 → 0.24.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/README.md +2 -1
- package/build/index.d.ts +117 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +355 -73
- package/build/index.js.map +1 -1
- package/package.json +3 -3
- package/src/agree.ts +105 -2
- package/src/evaluate.ts +49 -5
- package/src/formula.ts +1 -0
- package/src/index.ts +5 -2
- package/src/schedule.ts +11 -2
- package/src/static.ts +51 -3
- package/src/stdlib.ts +29 -11
- package/src/syntax.ts +114 -19
- package/src/typing.ts +33 -3
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.24.
|
|
4
|
+
"version": "0.24.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",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
],
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@mark1russell7/cue": "^2.1.1",
|
|
39
|
-
"@mark1russell7/rigor-bench": "^0.2.
|
|
40
|
-
"@mark1russell7/rigor-statistics": "^0.1.
|
|
39
|
+
"@mark1russell7/rigor-bench": "^0.2.29",
|
|
40
|
+
"@mark1russell7/rigor-statistics": "^0.1.16",
|
|
41
41
|
"@types/node": "^26.6.4",
|
|
42
42
|
"esbuild": "^0.28.2",
|
|
43
43
|
"typescript": "^7.0.2",
|
package/src/agree.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type Key, docEquals, equalWith } from "./doc.ts";
|
|
2
2
|
import { type Context, isHostThing } from "./deref.ts";
|
|
3
|
-
import { type EvalError, type Result, isStackOverflow, thrown } from "./errors.ts";
|
|
4
|
-
import { type
|
|
3
|
+
import { type EvalError, type Result, describe, isStackOverflow, thrown } from "./errors.ts";
|
|
4
|
+
import { type CheckOptions, typeCheck } from "./static.ts";
|
|
5
|
+
import { isPrecise, member, typeText } from "./types.ts";
|
|
6
|
+
import { boundariesOf } from "./typing.ts";
|
|
7
|
+
import { type Effect, type Machine, type Perform, type Read, type TraceNode, JOURNAL, evaluate, makeContext, prepare, run, sharedCopy } from "./evaluate.ts";
|
|
5
8
|
import { Held, isClosure } from "./formula.ts";
|
|
6
9
|
import { type Definitions, reference, referenceRun } from "./reference.ts";
|
|
7
10
|
import { type ReplaySettle, recorder, relay, replayer } from "./replay.ts";
|
|
@@ -392,6 +395,15 @@ function resolutions(reads: readonly Read[]): readonly Read[] {
|
|
|
392
395
|
return reads.filter((read) => read.free && read.kept !== true);
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
/** This function gives the text of a value of a trace. It does not throw for a bigint or a record with a cycle. */
|
|
399
|
+
function valueText(value: unknown): string {
|
|
400
|
+
try {
|
|
401
|
+
return JSON.stringify(value) ?? describe(value);
|
|
402
|
+
} catch {
|
|
403
|
+
return describe(value);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
395
407
|
function pathText(path: readonly unknown[] | undefined): string {
|
|
396
408
|
if (path === undefined) return "no read";
|
|
397
409
|
try {
|
|
@@ -537,3 +549,94 @@ export async function agreeRun(machine: Machine, doc: unknown, context: Context
|
|
|
537
549
|
}
|
|
538
550
|
return compared === undefined ? { ok: true, result, effects: first.effects } : { ok: true, result, effects: first.effects, reads: compared };
|
|
539
551
|
}
|
|
552
|
+
|
|
553
|
+
/** The result of `agreeTypes`: the agreement of a check of types and a traced evaluation (STATIC.TYPES-AGREE). */
|
|
554
|
+
export type TypeAgreement =
|
|
555
|
+
| {
|
|
556
|
+
readonly ok: true;
|
|
557
|
+
/** `false` when the document has an issue, or a value breaks a position of an imprecise type. The rule then tells nothing. */
|
|
558
|
+
readonly checked: boolean;
|
|
559
|
+
/** The number of the values of the trace that the rule examined. */
|
|
560
|
+
readonly values: number;
|
|
561
|
+
}
|
|
562
|
+
| {
|
|
563
|
+
readonly ok: false;
|
|
564
|
+
/** `value`: a value is not in the type of its place. `argument`: it is not in the type that its position needs. `error`: a type error of the kernel. */
|
|
565
|
+
readonly clause: "value" | "argument" | "error";
|
|
566
|
+
/** The keys of the place, as texts. */
|
|
567
|
+
readonly place: readonly string[];
|
|
568
|
+
readonly reason: string;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/** The options of `agreeTypes`: the options of `check`, and `guards`. */
|
|
572
|
+
export interface AgreeTypesOptions extends CheckOptions {
|
|
573
|
+
/** `true`: the evaluation tests each boundary (PLAN.GUARD). The rule then holds for each context. The default is `false`. */
|
|
574
|
+
readonly guards?: boolean;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** The type errors of the kernel, by the evaluator that gives them (STATIC.TYPES-AGREE). */
|
|
578
|
+
const TYPE_ERRORS: Readonly<Record<string, string>> = {
|
|
579
|
+
"formula.op": "#NAME?",
|
|
580
|
+
"formula.if": "#VALUE!",
|
|
581
|
+
"formula.and": "#VALUE!",
|
|
582
|
+
"formula.or": "#VALUE!",
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* This function examines the agreement rule for types (STATIC.TYPES-AGREE, D148). A document with no issue evaluates
|
|
587
|
+
* with a trace. Each value of the trace is a member of the type of its place. A value of a precise type is also a member
|
|
588
|
+
* of the type that its position needs. No document with precise parts gives a type error of the kernel.
|
|
589
|
+
*
|
|
590
|
+
* A language imports this function for its own property tests, with `agree`. A failure tells that a signature, a type
|
|
591
|
+
* rule, a cover or a root type does not hold. A value that breaks a position of an imprecise type ends the promise, and
|
|
592
|
+
* the result then has `checked: false` (TYPE.GRADUAL).
|
|
593
|
+
*
|
|
594
|
+
* @param machine - The world and the code of the evaluators, with their type rules.
|
|
595
|
+
* @param doc - The document.
|
|
596
|
+
* @param context - The context. Its roots are members of the root types.
|
|
597
|
+
* @param options - The options of `check`, with the root types, and `guards`.
|
|
598
|
+
* @returns The agreement, or the first place that breaks it.
|
|
599
|
+
*/
|
|
600
|
+
export function agreeTypes(machine: Machine, doc: unknown, context: Context = makeContext(), options: AgreeTypesOptions = {}): TypeAgreement {
|
|
601
|
+
const typing = typeCheck(machine, doc, options);
|
|
602
|
+
if (typing.issues.length > 0) return { ok: true, checked: false, values: 0 };
|
|
603
|
+
const keyOf = (path: readonly unknown[]): string => JSON.stringify(path.map(String));
|
|
604
|
+
const places = new Map(typing.places.map((item) => [keyOf(item.place), item]));
|
|
605
|
+
const domains = machine.domains;
|
|
606
|
+
const guards = options.guards === true ? boundariesOf(typing, domains) : undefined;
|
|
607
|
+
const given = { ...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }), ...(guards === undefined ? {} : { guards }) };
|
|
608
|
+
const stack: TraceNode[] = [evaluate(machine, doc, context, given).trace];
|
|
609
|
+
const nodes: TraceNode[] = [];
|
|
610
|
+
while (stack.length > 0) {
|
|
611
|
+
const node = stack.pop() as TraceNode;
|
|
612
|
+
nodes.push(node);
|
|
613
|
+
stack.push(...node.children);
|
|
614
|
+
}
|
|
615
|
+
// A value that breaks a position of an imprecise type leaves the promise of each later type (K28).
|
|
616
|
+
for (const node of nodes) {
|
|
617
|
+
const place = places.get(keyOf(node.path));
|
|
618
|
+
if (place?.expected === undefined || !node.result.ok || isPrecise(place.type, domains)) continue;
|
|
619
|
+
if (!member(node.result.value, place.expected, domains)) return { ok: true, checked: false, values: 0 };
|
|
620
|
+
}
|
|
621
|
+
// The walk goes from the end, thus a part comes before the kind that reads it, and a finding names the origin.
|
|
622
|
+
let values = 0;
|
|
623
|
+
for (const node of nodes.reverse()) {
|
|
624
|
+
const place = places.get(keyOf(node.path));
|
|
625
|
+
if (place === undefined) continue;
|
|
626
|
+
const at = node.path.map(String);
|
|
627
|
+
if (!node.result.ok) {
|
|
628
|
+
const parts = node.children.map((child) => places.get(keyOf(child.path)));
|
|
629
|
+
const decided = node.children.every((child) => child.result.ok) && parts.every((part) => part?.expected !== undefined && isPrecise(part.type, domains));
|
|
630
|
+
const code = TYPE_ERRORS[node.answer.evaluator ?? ""];
|
|
631
|
+
if (decided && node.result.error.code === code) return { ok: false, clause: "error", place: at, reason: `The document gives ${code}, but it checks with no issue.` };
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const value = node.result.value;
|
|
635
|
+
values += 1;
|
|
636
|
+
if (!member(value, place.type, domains)) return { ok: false, clause: "value", place: at, reason: `The value ${valueText(value)} is not a member of ${typeText(place.type)}.` };
|
|
637
|
+
if (place.expected !== undefined && !member(value, place.expected, domains)) {
|
|
638
|
+
return { ok: false, clause: "argument", place: at, reason: `The value ${valueText(value)} is not a member of the needed type ${typeText(place.expected)}.` };
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return { ok: true, checked: true, values };
|
|
642
|
+
}
|
package/src/evaluate.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { type Envelope, type Json, type Key, type TypeRef, isEnvelope, isKey, ty
|
|
|
2
2
|
import { type PartSlot, type Split, partsOf, pathMatches, scopesOf } from "./parts.ts";
|
|
3
3
|
import { answerOf, ask, type Answer } from "./ladder.ts";
|
|
4
4
|
import type { CapsRule, Kind, KindFacts, Side } from "./world.ts";
|
|
5
|
-
import type
|
|
6
|
-
import type { TypeRule } from "./typing.ts";
|
|
5
|
+
import { type Domain, type ValueType, member, typeText } from "./types.ts";
|
|
6
|
+
import type { Boundary, TypeRule } from "./typing.ts";
|
|
7
7
|
import type { StaticRead } from "./facts.ts";
|
|
8
8
|
import { type ErrorCode, type EvalError, type Result, Failure, fail, ok, placed, thrown, describe } from "./errors.ts";
|
|
9
9
|
import {
|
|
@@ -993,6 +993,8 @@ export interface PlanRunOptions {
|
|
|
993
993
|
export interface RunOptions extends PlanRunOptions {
|
|
994
994
|
/** The type of a bare root value. */
|
|
995
995
|
readonly declaredType?: string;
|
|
996
|
+
/** The boundaries that test their values (PLAN.GUARD). The default is none. */
|
|
997
|
+
readonly guards?: readonly Boundary[];
|
|
996
998
|
/** The verb of `run`, for example `format`. The default is `eval` (VERB.PREPARE). */
|
|
997
999
|
readonly verb?: string;
|
|
998
1000
|
}
|
|
@@ -1023,6 +1025,8 @@ export interface EvaluateOptions {
|
|
|
1023
1025
|
readonly perform?: Perform;
|
|
1024
1026
|
/** A list that gets each read, in the order of the resolutions (READS.RECORD). */
|
|
1025
1027
|
readonly reads?: Read[];
|
|
1028
|
+
/** The boundaries that test their values (PLAN.GUARD). The default is none. */
|
|
1029
|
+
readonly guards?: readonly Boundary[];
|
|
1026
1030
|
}
|
|
1027
1031
|
|
|
1028
1032
|
/** The options of a resolution without a record of hops, and with one. One object of each serves each call. */
|
|
@@ -1700,6 +1704,11 @@ export interface PrepareOptions {
|
|
|
1700
1704
|
* the code without the fold. A plan without folds ignores this option.
|
|
1701
1705
|
*/
|
|
1702
1706
|
readonly fixed?: { readonly names?: Readonly<Record<string, unknown>> };
|
|
1707
|
+
/**
|
|
1708
|
+
* The boundaries of the document, from `boundariesOf` (PLAN.GUARD, D149). A value that is not a member of the type of
|
|
1709
|
+
* its boundary gives `#VALUE!` at that place. The default is none, and a plan then has no test.
|
|
1710
|
+
*/
|
|
1711
|
+
readonly guards?: readonly Boundary[];
|
|
1703
1712
|
}
|
|
1704
1713
|
|
|
1705
1714
|
/** A compiled document. It evaluates many times, and it does the work of the ladder one time. */
|
|
@@ -1740,6 +1749,8 @@ interface Compiler {
|
|
|
1740
1749
|
readonly fold: boolean;
|
|
1741
1750
|
/** The fixed names and their values, or `undefined` when the plan has none (PLAN.FIXED). */
|
|
1742
1751
|
readonly fixed: Readonly<Record<string, unknown>> | undefined;
|
|
1752
|
+
/** The types of the boundaries by their place, or `undefined` when the plan has no guard (PLAN.GUARD). */
|
|
1753
|
+
readonly guards: ReadonlyMap<string, ValueType> | undefined;
|
|
1743
1754
|
/** The runtime of the folds of this compilation. The first fold makes it. */
|
|
1744
1755
|
probe?: Probe;
|
|
1745
1756
|
}
|
|
@@ -1885,7 +1896,8 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1885
1896
|
} catch (error) {
|
|
1886
1897
|
return failingWith(c, meta, placed(thrown(error, `the runner ${name}`), place));
|
|
1887
1898
|
}
|
|
1888
|
-
const
|
|
1899
|
+
const guard = guardOf(c, place);
|
|
1900
|
+
const run = placedRun(guard === undefined ? inner : guardRun(inner, guard, c.machine.domains), place, name);
|
|
1889
1901
|
return finish(c, meta, runnerCode(run, place, kindText), run);
|
|
1890
1902
|
}
|
|
1891
1903
|
|
|
@@ -1895,8 +1907,9 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1895
1907
|
} catch (error) {
|
|
1896
1908
|
return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
|
|
1897
1909
|
}
|
|
1910
|
+
const guard = guardOf(c, place);
|
|
1898
1911
|
// Code that cannot fail needs no place and no catch, and it does not read the context of its grant (PLAN.TOTAL).
|
|
1899
|
-
const done = finish(c, meta, TOTAL.has(inner) ? inner : placedCode(inner, place, name), undefined);
|
|
1912
|
+
const done = finish(c, meta, guard !== undefined ? placedCode(guardCode(inner, guard, c.machine.domains), place, name) : TOTAL.has(inner) ? inner : placedCode(inner, place, name), undefined);
|
|
1900
1913
|
if (frees === undefined || kind === undefined || !pureCode(c, envelope, kind, typePath, name, quick)) return done;
|
|
1901
1914
|
const free = freeOf(doc, kind, split.parts, frees);
|
|
1902
1915
|
if (free === undefined) return done;
|
|
@@ -2308,6 +2321,34 @@ function gated(rules: readonly CapsRule[], part: Compiled): Compiled {
|
|
|
2308
2321
|
* This function gives the steps of a runner, with the place of each error and of each exception. The steps get the
|
|
2309
2322
|
* context that its grant permits (CAP.NARROW-CODE).
|
|
2310
2323
|
*/
|
|
2324
|
+
/** This function gives the key of a place in the guards of a plan. */
|
|
2325
|
+
function guardKey(place: readonly Key[]): string {
|
|
2326
|
+
return JSON.stringify(place.map(String));
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
/** This function gives the type of the boundary at a place, when the plan has one (PLAN.GUARD). */
|
|
2330
|
+
function guardOf(c: Compiler, place: readonly Key[]): ValueType | undefined {
|
|
2331
|
+
return c.guards?.get(guardKey(place));
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
/** This function gives the result of a part of a boundary: a value that is not a member gives `#VALUE!` (PLAN.GUARD). */
|
|
2335
|
+
function guarded(result: Result, type: ValueType, domains: Readonly<Record<string, Domain>> | undefined): Result {
|
|
2336
|
+
if (!result.ok || member(result.value, type, domains)) return result;
|
|
2337
|
+
return fail("#VALUE!", `The value is not a member of ${typeText(type)}, the type of its boundary.`);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
/** This function adds the test of a boundary to the code of a part. */
|
|
2341
|
+
function guardCode(inner: Code, type: ValueType, domains: Readonly<Record<string, Domain>> | undefined): Code {
|
|
2342
|
+
return (ctx, rt) => guarded(inner(ctx, rt), type, domains);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/** This function adds the test of a boundary to the steps of a part. */
|
|
2346
|
+
function guardRun(inner: RunCode, type: ValueType, domains: Readonly<Record<string, Domain>> | undefined): RunCode {
|
|
2347
|
+
return function* (ctx, rt) {
|
|
2348
|
+
return guarded(yield* inner(ctx, rt), type, domains);
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2311
2352
|
function placedRun(inner: RunCode, place: readonly Key[], name: string): RunCode {
|
|
2312
2353
|
return function* (ctx, rt) {
|
|
2313
2354
|
try {
|
|
@@ -2520,7 +2561,8 @@ export function prepare(machine: Machine, doc: unknown, options: PrepareOptions
|
|
|
2520
2561
|
const traced = options.trace === true;
|
|
2521
2562
|
const fold = !traced && options.fold !== false;
|
|
2522
2563
|
const fixed = fold ? fixedNamesOf(options.fixed?.names) : undefined;
|
|
2523
|
-
const
|
|
2564
|
+
const guards = options.guards === undefined || options.guards.length === 0 ? undefined : new Map(options.guards.map((each) => [guardKey(each.place), each.type]));
|
|
2565
|
+
const c: Compiler = { machine, traced, maxDepth: machine.maxDepth ?? 1000, verb, noAnswer, fold, fixed, guards };
|
|
2524
2566
|
let root: Compiled;
|
|
2525
2567
|
try {
|
|
2526
2568
|
root = compileNode(c, doc, [], options.declaredType, 0);
|
|
@@ -2643,6 +2685,7 @@ export function evaluate(machine: Machine, doc: unknown, context: Context = make
|
|
|
2643
2685
|
trace: true,
|
|
2644
2686
|
...(declaredType === undefined ? {} : { declaredType }),
|
|
2645
2687
|
...(given.verb === undefined ? {} : { verb: given.verb }),
|
|
2688
|
+
...(given.guards === undefined ? {} : { guards: given.guards }),
|
|
2646
2689
|
});
|
|
2647
2690
|
const ctx = rootContext(machine, context);
|
|
2648
2691
|
const s = new Session(machine, ctx.caps, false, true, given.perform, undefined, undefined, readingOf(given.reads, ctx));
|
|
@@ -2665,6 +2708,7 @@ export async function run(machine: Machine, doc: unknown, context: Context = mak
|
|
|
2665
2708
|
const plan = prepare(machine, doc, {
|
|
2666
2709
|
trace: true,
|
|
2667
2710
|
...(options.declaredType === undefined ? {} : { declaredType: options.declaredType }),
|
|
2711
|
+
...(options.guards === undefined ? {} : { guards: options.guards }),
|
|
2668
2712
|
...(options.verb === undefined ? {} : { verb: options.verb }),
|
|
2669
2713
|
});
|
|
2670
2714
|
const ctx = rootContext(machine, context);
|
package/src/formula.ts
CHANGED
|
@@ -420,6 +420,7 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
420
420
|
"formula.lit": { synth: (input) => valueType(input.doc.value) },
|
|
421
421
|
|
|
422
422
|
"formula.ref": {
|
|
423
|
+
read: true,
|
|
423
424
|
synth: (input, report) => {
|
|
424
425
|
const path = input.doc.value;
|
|
425
426
|
// An address gives a thing, not a value. The check gives #VALUE! for a path that is not a list.
|
package/src/index.ts
CHANGED
|
@@ -197,6 +197,7 @@ export {
|
|
|
197
197
|
STD_EVALUATORS,
|
|
198
198
|
STD_MACHINE,
|
|
199
199
|
STD_RUNNERS,
|
|
200
|
+
ifRunner,
|
|
200
201
|
STD_WALKERS,
|
|
201
202
|
STD_WORLD,
|
|
202
203
|
WORLD_SYNTAX,
|
|
@@ -227,6 +228,7 @@ export {
|
|
|
227
228
|
} from "./context.ts";
|
|
228
229
|
export {
|
|
229
230
|
type CheckOptions,
|
|
231
|
+
type DocChange,
|
|
230
232
|
type DocVisit,
|
|
231
233
|
type Issue,
|
|
232
234
|
type RefUse,
|
|
@@ -237,6 +239,7 @@ export {
|
|
|
237
239
|
type WorldOptions,
|
|
238
240
|
check,
|
|
239
241
|
checkWorld,
|
|
242
|
+
mapDocs,
|
|
240
243
|
mapRefs,
|
|
241
244
|
refsOf,
|
|
242
245
|
typeCheck,
|
|
@@ -289,7 +292,7 @@ export {
|
|
|
289
292
|
syntaxFormat,
|
|
290
293
|
} from "./syntax.ts";
|
|
291
294
|
export { type Definitions, type ReferenceOptions, type ReferenceRunOptions, STD_DEFINITIONS, formulaDefinitions, reference, referenceRun } from "./reference.ts";
|
|
292
|
-
export { type AgreeOptions, type AgreeRunOptions, type Agreement, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
|
|
295
|
+
export { type AgreeOptions, type AgreeRunOptions, type Agreement, type AgreeTypesOptions, type TypeAgreement, agreeTypes, type Tier, type TierInput, type TierResult, agree, agreeRun, sameValue } from "./agree.ts";
|
|
293
296
|
export { type ConformOptions, type ConformProbe, type Finding, type Obligation, conforms } from "./conform.ts";
|
|
294
297
|
export { sameResult } from "./same.ts";
|
|
295
298
|
export { type PrintOptions, type ReadPrints, type ReadsCheck, type VerifyOptions, printReads, verifyReads } from "./reads.ts";
|
|
@@ -346,5 +349,5 @@ export {
|
|
|
346
349
|
} from "./replay.ts";
|
|
347
350
|
export { type GenerateOptions, type Random, generate, random, typesOf } from "./generate.ts";
|
|
348
351
|
export { type Domain, type DomainCodec, type TypeIssue, type ValueType, STD_DOMAINS, fits, join, member, typeIssues, typeText } from "./types.ts";
|
|
349
|
-
export { type Declared, type PartType, type RootTypes, type TypeInput, type TypeProblem, type TypeRule, type Typed, CAPS_TYPE } from "./typing.ts";
|
|
352
|
+
export { type Boundary, type Declared, type PartType, type RootTypes, type TypeInput, type TypeProblem, type TypeRule, type Typed, CAPS_TYPE, boundariesOf } from "./typing.ts";
|
|
350
353
|
export { type SampleOptions, type TypeReading, typeFromSamples, typeFromSchema } from "./providers.ts";
|
package/src/schedule.ts
CHANGED
|
@@ -294,6 +294,8 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
294
294
|
// The nodes that finished in a kept read, and if that read met a node in evaluation below it (D143).
|
|
295
295
|
const keptNodes: string[] = [];
|
|
296
296
|
let keptMet = false;
|
|
297
|
+
// The count of the visits at the start of the kept read. A node with a lower number started below the read.
|
|
298
|
+
let keptFrom = 0;
|
|
297
299
|
let count = 0;
|
|
298
300
|
let ahead = 0;
|
|
299
301
|
// The node that stopped the evaluation of a walk, because the walk has no result for it.
|
|
@@ -370,8 +372,12 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
370
372
|
const reader = stack[stack.length - 1];
|
|
371
373
|
const known = epoch.values.get(id);
|
|
372
374
|
if (known !== undefined) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
+
if (reader !== undefined && onOpen.has(id)) {
|
|
376
|
+
// The path from that component to a kept read has a read that no evaluation asked for (SCHED.KEPT-OPEN).
|
|
377
|
+
if (keptAt >= 0 && (order.get(id) as number) < keptFrom) keptMet = true;
|
|
378
|
+
// A finished node of an open component is on a cycle with its reader too.
|
|
379
|
+
else lower(reader, order.get(id) as number);
|
|
380
|
+
}
|
|
375
381
|
return known;
|
|
376
382
|
}
|
|
377
383
|
if (busy.has(id)) {
|
|
@@ -592,9 +598,11 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
592
598
|
keeping(read) {
|
|
593
599
|
const outer = keptAt;
|
|
594
600
|
const outerMet = keptMet;
|
|
601
|
+
const outerFrom = keptFrom;
|
|
595
602
|
const from = keptNodes.length;
|
|
596
603
|
keptAt = stack.length;
|
|
597
604
|
keptMet = false;
|
|
605
|
+
keptFrom = count;
|
|
598
606
|
try {
|
|
599
607
|
return read();
|
|
600
608
|
} finally {
|
|
@@ -609,6 +617,7 @@ export function schedule(machine: Machine, options: ScheduleOptions = {}): Sched
|
|
|
609
617
|
}
|
|
610
618
|
keptAt = outer;
|
|
611
619
|
keptMet = outerMet;
|
|
620
|
+
keptFrom = outerFrom;
|
|
612
621
|
}
|
|
613
622
|
},
|
|
614
623
|
});
|
package/src/static.ts
CHANGED
|
@@ -258,11 +258,14 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
258
258
|
continue;
|
|
259
259
|
}
|
|
260
260
|
const { sees, later } = scopesOf(kind.scope, doc.value, split.parts);
|
|
261
|
+
// The rules of a grant are read one time for each kind, not for each part. A kind without such a rule gives no list (K238).
|
|
262
|
+
const grantRules = kind.caps !== undefined && kind.caps.some((rule) => rule.rule === "grant") ? kind.caps : undefined;
|
|
261
263
|
for (let index = split.parts.length - 1; index >= 0; index -= 1) {
|
|
262
264
|
const slot = split.parts[index] as PartSlot;
|
|
263
265
|
const extra = sees[index] ?? [];
|
|
264
266
|
const hidden = later[index] ?? [];
|
|
265
|
-
const grants = partGrants(
|
|
267
|
+
const grants = grantRules === undefined ? undefined : partGrants(grantRules, slot.path, doc.value);
|
|
268
|
+
const granted = grants !== undefined && grants.length > 0 ? grants : undefined;
|
|
266
269
|
stack.push({
|
|
267
270
|
value: slot.item,
|
|
268
271
|
place: slot.path.length === 0 ? item.place : { up: item.place, keys: slot.path },
|
|
@@ -274,8 +277,14 @@ export function walk(world: World, doc: unknown, visit: (visit: DocVisit) => voi
|
|
|
274
277
|
address: slot.address === true,
|
|
275
278
|
slot: slot.path,
|
|
276
279
|
parent: here,
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
// A part with no grant keeps the list of partCaps, with no copy (K238).
|
|
281
|
+
caps:
|
|
282
|
+
item.caps === undefined || kind.caps === undefined
|
|
283
|
+
? item.caps
|
|
284
|
+
: granted === undefined
|
|
285
|
+
? partCaps(kind.caps, slot.path, item.caps)
|
|
286
|
+
: [...partCaps(kind.caps, slot.path, item.caps), ...granted],
|
|
287
|
+
handled: granted === undefined ? item.handled : [...(item.handled ?? []), ...granted],
|
|
279
288
|
bound: extra,
|
|
280
289
|
expect: slot.expect,
|
|
281
290
|
});
|
|
@@ -371,6 +380,45 @@ export function mapRefs(world: World, doc: unknown, change: (use: RefUse) => unk
|
|
|
371
380
|
return out;
|
|
372
381
|
}
|
|
373
382
|
|
|
383
|
+
/**
|
|
384
|
+
* The answer of the function of `mapDocs` for one document.
|
|
385
|
+
*
|
|
386
|
+
* - `undefined`: keep the document and go into its parts.
|
|
387
|
+
* - `"skip"`: keep the document and stop there.
|
|
388
|
+
* - `{ to }`: put `to` in the place of the document and stop there.
|
|
389
|
+
* - `{ to, into: true }`: put `to` in the place of the document, then go into the parts of the old document. Thus
|
|
390
|
+
* `to` must keep the path of each such part.
|
|
391
|
+
*/
|
|
392
|
+
export type DocChange = undefined | "skip" | { readonly to: unknown; readonly into?: boolean };
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* This function changes the documents of a document, from the root to the parts (STATIC.MAPDOCS). The function
|
|
396
|
+
* `change` reads only the original documents. It decides at each node, and it can stop at a node. The kinds of a
|
|
397
|
+
* position come from the world, thus this function names no kind. The document is not changed: the function gives a
|
|
398
|
+
* new document that shares each part without a change.
|
|
399
|
+
*
|
|
400
|
+
* @param world - The world of the kinds.
|
|
401
|
+
* @param doc - The document, as JSON.
|
|
402
|
+
* @param change - The function that gives the answer for each visit. See `DocChange`.
|
|
403
|
+
* @param options - The type of a bare root value, and the names that the host binds at the root.
|
|
404
|
+
* @returns The new document, or `doc` itself when no document changes.
|
|
405
|
+
*/
|
|
406
|
+
export function mapDocs(world: World, doc: unknown, change: (visit: DocVisit) => DocChange, options: WalkOptions = {}): unknown {
|
|
407
|
+
let out = doc;
|
|
408
|
+
walk(
|
|
409
|
+
world,
|
|
410
|
+
doc,
|
|
411
|
+
(visit) => {
|
|
412
|
+
const next = change(visit);
|
|
413
|
+
if (next === undefined || next === "skip") return next;
|
|
414
|
+
out = setAt(out, visit.at, next.to);
|
|
415
|
+
return next.into === true ? undefined : "skip";
|
|
416
|
+
},
|
|
417
|
+
options,
|
|
418
|
+
);
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
|
|
374
422
|
/** A problem that a check finds before a document runs. */
|
|
375
423
|
export interface Issue {
|
|
376
424
|
readonly code: ErrorCode;
|
package/src/stdlib.ts
CHANGED
|
@@ -192,7 +192,8 @@ export const STD_WORLD: World = {
|
|
|
192
192
|
* A literal in a place that takes a document gets its data envelope (K159, D65). Such a place is a part of `if`,
|
|
193
193
|
* `list`, `seq` or `pure`, the value of `write`, or the whole text. The document after a header name is such a place
|
|
194
194
|
* too. Thus `list(1, "a")` evaluates to `[1, "a"]`. The forms `add`, `multiply` and `greater` write an operator document that its operator
|
|
195
|
-
* does not write, for example `add(5)` (K163).
|
|
195
|
+
* does not write, for example `add(5)` (K163). The field `with` of `handle` is a field of data, thus each clause is a
|
|
196
|
+
* record between braces in a list between brackets (D177).
|
|
196
197
|
*
|
|
197
198
|
* A host with more kinds spreads this profile and adds its forms to `calls.forms`. `STD_SYNTAX` stays the profile of
|
|
198
199
|
* the formula kit.
|
|
@@ -223,6 +224,8 @@ export const WORLD_SYNTAX: SyntaxProfile = /* @__PURE__ */ Object.freeze({
|
|
|
223
224
|
{ name: "tree.named", kind: "tree.named", args: "named", fields: ["name", "in"] },
|
|
224
225
|
{ name: "tree.at", kind: "tree.at", args: "named", fields: ["attr", "args"] },
|
|
225
226
|
{ name: "tree.quote", kind: "tree.quote", args: "literal" },
|
|
227
|
+
// The clauses are data with documents in it, for example handle(with: [{op: "log", do: payload + 1}], body: log(2)) (D177).
|
|
228
|
+
{ name: "handle", kind: "handle", args: "named", fields: ["with", "return", "body"], data: ["with"], wrap: ["return", "body"] },
|
|
226
229
|
// An operator document with fewer terms than its operator takes, for example add(5) (K163, D65).
|
|
227
230
|
{ name: "add", kind: "add" },
|
|
228
231
|
{ name: "multiply", kind: "multiply" },
|
|
@@ -835,6 +838,7 @@ function leaderOnHost(n: LoopNode, key: string, rule: CircularRule & { readonly
|
|
|
835
838
|
/**
|
|
836
839
|
* This function gives an attribute of a node with circular attributes, or an attribute in an open fixed point (D113).
|
|
837
840
|
* The memo and the marks of the node do not hold the attribute. An open fixed point does not use the cells of the host.
|
|
841
|
+
* With the cells of a host, only a circular attribute comes here outside a fixed point. A plain attribute takes its cell.
|
|
838
842
|
*/
|
|
839
843
|
function circularRead(n: LoopNode, key: string): Result | undefined {
|
|
840
844
|
const rule = circularRule(n.tree, key);
|
|
@@ -1221,7 +1225,8 @@ function treeThing(input: EvalInput, place: readonly Key[], build: TreeBuild, ou
|
|
|
1221
1225
|
const text = attributeText(key, type, place);
|
|
1222
1226
|
if (marks.has(key)) return busyAt(state, key);
|
|
1223
1227
|
if (tree === undefined && up?.tree === undefined && !parts.some((item) => item.key === key)) return undefined;
|
|
1224
|
-
|
|
1228
|
+
// Outside a fixed point, a plain attribute of a node with a circular attribute also takes the cell of the host.
|
|
1229
|
+
if (openPoint !== undefined || (circular && circularRule(tree, key) !== undefined)) return circularRead(state, key);
|
|
1225
1230
|
marks.set(key, BUSY);
|
|
1226
1231
|
demands.push({ cells: marks, key, text });
|
|
1227
1232
|
let thrown: unknown = NOT_THROWN;
|
|
@@ -1527,6 +1532,27 @@ function isOp(value: unknown): value is Cap {
|
|
|
1527
1532
|
return typeof value === "string" || (Array.isArray(value) && value.every(isKey));
|
|
1528
1533
|
}
|
|
1529
1534
|
|
|
1535
|
+
/**
|
|
1536
|
+
* This function gives the runner of an `if` with its own truth (RUN.IF-TRUTH). A branch can wait for the host, thus a
|
|
1537
|
+
* language with the function `truthy` of a profile does not need a copy of `if`. A missing test and a test without a
|
|
1538
|
+
* truth give `#VALUE!`, a failed test gives its failure, and a missing branch gives `null`.
|
|
1539
|
+
*
|
|
1540
|
+
* @param truth - The function that gives the truth of a value, or `undefined` for a value without a truth.
|
|
1541
|
+
* @returns The runner.
|
|
1542
|
+
*/
|
|
1543
|
+
export function ifRunner(truth: (value: unknown) => boolean | undefined): Runner {
|
|
1544
|
+
return function* ({ parts }) {
|
|
1545
|
+
const test = parts.find((part) => part.key === "test");
|
|
1546
|
+
if (test === undefined) return fail("#VALUE!", "if needs a test.");
|
|
1547
|
+
const chosen = yield* test.evaluate();
|
|
1548
|
+
if (!chosen.ok) return chosen;
|
|
1549
|
+
const held = truth(chosen.value);
|
|
1550
|
+
if (held === undefined) return fail("#VALUE!", "The test of if must give a truth value.");
|
|
1551
|
+
const branch = parts.find((part) => part.key === (held ? "then" : "else"));
|
|
1552
|
+
return branch === undefined ? ok(null) : yield* branch.evaluate();
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1530
1556
|
/**
|
|
1531
1557
|
* The runners of the standard world, by name: the kinds that can hold effects. Each one evaluates its parts in
|
|
1532
1558
|
* order. In a run, an effect ends before the next step starts.
|
|
@@ -1576,15 +1602,7 @@ export const STD_RUNNERS: Readonly<Record<string, Runner>> = {
|
|
|
1576
1602
|
if (!result.ok || after === undefined) return result;
|
|
1577
1603
|
return yield* after.evaluate({ ...context, names: { ...context.names, result: result.value } });
|
|
1578
1604
|
},
|
|
1579
|
-
"control.if":
|
|
1580
|
-
const test = parts.find((part) => part.key === "test");
|
|
1581
|
-
if (test === undefined) return fail("#VALUE!", "if needs a test.");
|
|
1582
|
-
const chosen = yield* test.evaluate();
|
|
1583
|
-
if (!chosen.ok) return chosen;
|
|
1584
|
-
if (typeof chosen.value !== "boolean") return fail("#VALUE!", "The test of if must give a truth value.");
|
|
1585
|
-
const branch = parts.find((part) => part.key === (chosen.value === true ? "then" : "else"));
|
|
1586
|
-
return branch === undefined ? ok(null) : yield* branch.evaluate();
|
|
1587
|
-
},
|
|
1605
|
+
"control.if": /* @__PURE__ */ ifRunner((value) => (typeof value === "boolean" ? value : undefined)),
|
|
1588
1606
|
"list.all": function* ({ parts }) {
|
|
1589
1607
|
const all = yield* inOrder(parts);
|
|
1590
1608
|
return "error" in all ? all.error : ok(all.values);
|