@mark1russell7/coeval 0.9.0 → 0.11.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 +3 -3
- package/build/index.d.ts +62 -23
- package/build/index.d.ts.map +1 -1
- package/build/index.js +101 -29
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/conform.ts +15 -7
- package/src/evaluate.ts +35 -12
- package/src/formula.ts +34 -15
- package/src/generate.ts +7 -2
- package/src/index.ts +3 -0
- package/src/providers.ts +4 -3
- package/src/static.ts +35 -17
- package/src/types.ts +4 -2
- package/src/world.ts +31 -1
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.11.0",
|
|
5
5
|
"description": "coeval: the kernel of declarative languages. Documents in envelopes, a ladder that finds the evaluator, kind contracts, compiled plans, a static kit, a formula kit and a run side with capabilities.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./build/index.js",
|
package/src/conform.ts
CHANGED
|
@@ -32,7 +32,7 @@ import { type Definitions, STD_DEFINITIONS } from "./reference.ts";
|
|
|
32
32
|
import { STD_EVALUATORS } from "./stdlib.ts";
|
|
33
33
|
import { sameValue } from "./agree.ts";
|
|
34
34
|
import { type Random, random } from "./generate.ts";
|
|
35
|
-
import { type Kind, type LazyRule, type Shape, type World, type WorldEntry, isKind, isNamespace, lookupKind } from "./world.ts";
|
|
35
|
+
import { type Kind, type LazyRule, type Shape, type World, type WorldEntry, effectsOfVerb, isKind, isNamespace, lookupKind } from "./world.ts";
|
|
36
36
|
|
|
37
37
|
/*
|
|
38
38
|
* The conformance tests of one evaluator (K92, R7 P3, D36). `conforms` calls the code of one evaluator or runner
|
|
@@ -85,8 +85,9 @@ export interface Finding {
|
|
|
85
85
|
/** The options of `conforms`. */
|
|
86
86
|
export interface ConformOptions {
|
|
87
87
|
/**
|
|
88
|
-
* The verb that the evaluator serves. The default is `eval`. The facts
|
|
89
|
-
*
|
|
88
|
+
* The verb that the evaluator serves. The default is `eval`. The facts `pure` and `lazy` of a contract describe only
|
|
89
|
+
* `eval`, thus `conforms` checks C3 and C4 only for `eval` (D23). `conforms` checks C2 for each verb that the
|
|
90
|
+
* contract declares effects for: `eval`, and each verb with an entry in `verbEffects` (D49).
|
|
90
91
|
*/
|
|
91
92
|
readonly verb?: string;
|
|
92
93
|
/**
|
|
@@ -276,6 +277,8 @@ interface Contract {
|
|
|
276
277
|
readonly lazy: readonly LazyRule[] | undefined;
|
|
277
278
|
/** `true` when the facts describe the verb: they describe only `eval`. */
|
|
278
279
|
readonly describes: boolean;
|
|
280
|
+
/** `true` when the contract declares the effects of the verb: for `eval`, and for a verb in `verbEffects`. */
|
|
281
|
+
readonly declares: boolean;
|
|
279
282
|
readonly caps: readonly Cap[];
|
|
280
283
|
}
|
|
281
284
|
|
|
@@ -487,13 +490,16 @@ function typesNaming(world: World, name: string, verb: string): readonly TypeRef
|
|
|
487
490
|
/** This function gives the facts of the kind of a type. The hook `facts` of the machine wins over the contract. */
|
|
488
491
|
function contractOf(env: Env, kind: Kind, typePath: readonly Key[], caps: readonly Cap[] | undefined): Contract {
|
|
489
492
|
const facts = env.machine.facts?.(typePath, env.name);
|
|
490
|
-
|
|
493
|
+
// A verb without a declaration keeps the effects of eval for the capabilities of the probes, as before D49.
|
|
494
|
+
const own = effectsOfVerb(kind, env.verb);
|
|
495
|
+
const effects = own ?? kind.effects;
|
|
491
496
|
const declared: readonly Cap[] = effects === undefined ? [] : effects === "type" ? [typePath] : effects;
|
|
492
497
|
return {
|
|
493
498
|
declared,
|
|
494
499
|
pure: (facts?.pure ?? kind.pure) === true,
|
|
495
500
|
lazy: facts?.lazy ?? kind.lazy,
|
|
496
501
|
describes: env.verb === "eval",
|
|
502
|
+
declares: own !== undefined,
|
|
497
503
|
caps: caps ?? [...declared, "probe"],
|
|
498
504
|
};
|
|
499
505
|
}
|
|
@@ -872,13 +878,15 @@ function checkShot(env: Env, sample: Sample, contract: Contract, scenario: Scena
|
|
|
872
878
|
report(env, "C1", `The code gives its effect ${effect.kind} ${capsText(effect.context.caps)}, and its document has ${capsText(docCaps)}.`, probe);
|
|
873
879
|
}
|
|
874
880
|
}
|
|
875
|
-
if (contract.
|
|
876
|
-
// C2: only the declared effects.
|
|
881
|
+
if (contract.declares) {
|
|
882
|
+
// C2: only the declared effects of the verb (VERB.EFFECTS).
|
|
877
883
|
for (const effect of shot.caused) {
|
|
878
884
|
if (!permits(contract.declared, effect.kind)) {
|
|
879
|
-
report(env, "C2", `The code causes the effect ${effect.kind}, and the contract of ${sample.type} declares ${contract.declared.length === 0 ? "no effects" : capsText(contract.declared)}.`, probe);
|
|
885
|
+
report(env, "C2", `The code causes the effect ${effect.kind}, and the contract of ${sample.type} declares ${contract.declared.length === 0 ? "no effects" : capsText(contract.declared)}${env.verb === "eval" ? "" : ` for the verb ${env.verb}`}.`, probe);
|
|
880
886
|
}
|
|
881
887
|
}
|
|
888
|
+
}
|
|
889
|
+
if (contract.describes) {
|
|
882
890
|
// C4: a pure kind causes no effect.
|
|
883
891
|
const first = shot.caused[0];
|
|
884
892
|
if (contract.pure && first !== undefined) report(env, "C4", `The kind ${sample.type} is pure, and the code causes the effect ${first.kind}.`, probe);
|
package/src/evaluate.ts
CHANGED
|
@@ -785,17 +785,13 @@ export interface StopSignal {
|
|
|
785
785
|
addEventListener(type: "abort", listener: () => void, options?: { readonly once?: boolean }): void;
|
|
786
786
|
}
|
|
787
787
|
|
|
788
|
-
/**
|
|
789
|
-
|
|
788
|
+
/**
|
|
789
|
+
* The options of `plan.run`. A plan keeps the verb and the declared type of its `prepare`, thus these options do not
|
|
790
|
+
* have them (PLAN.RUN-OPTIONS).
|
|
791
|
+
*/
|
|
792
|
+
export interface PlanRunOptions {
|
|
790
793
|
/** The host code that does each effect. Without it, an effect gives its payload, as in an evaluation. */
|
|
791
794
|
readonly perform?: Perform;
|
|
792
|
-
/** The type of a bare root value. */
|
|
793
|
-
readonly declaredType?: string;
|
|
794
|
-
/**
|
|
795
|
-
* The verb of `run`, for example `format`. The default is `eval` (VERB.PREPARE). A plan keeps the verb of its
|
|
796
|
-
* `prepare`, thus `plan.run` does not read this option.
|
|
797
|
-
*/
|
|
798
|
-
readonly verb?: string;
|
|
799
795
|
/** A signal of the host. When it aborts, the run stops with `#ABORT`. */
|
|
800
796
|
readonly signal?: StopSignal;
|
|
801
797
|
/**
|
|
@@ -805,6 +801,14 @@ export interface RunOptions {
|
|
|
805
801
|
readonly reads?: Read[];
|
|
806
802
|
}
|
|
807
803
|
|
|
804
|
+
/** The options of `run`: the options of `plan.run`, and the type of a bare root value and the verb for `prepare`. */
|
|
805
|
+
export interface RunOptions extends PlanRunOptions {
|
|
806
|
+
/** The type of a bare root value. */
|
|
807
|
+
readonly declaredType?: string;
|
|
808
|
+
/** The verb of `run`, for example `format`. The default is `eval` (VERB.PREPARE). */
|
|
809
|
+
readonly verb?: string;
|
|
810
|
+
}
|
|
811
|
+
|
|
808
812
|
/** The options of an evaluation of a plan. */
|
|
809
813
|
export interface EvalOptions {
|
|
810
814
|
/** A list that gets each effect, in its order. */
|
|
@@ -1303,7 +1307,7 @@ export interface Plan {
|
|
|
1303
1307
|
* @param options - The host code that does the effects, a signal to stop the run and a list that gets the reads.
|
|
1304
1308
|
* @returns The result, when the run ends.
|
|
1305
1309
|
*/
|
|
1306
|
-
run(context?: Context, options?:
|
|
1310
|
+
run(context?: Context, options?: PlanRunOptions): Promise<Result>;
|
|
1307
1311
|
}
|
|
1308
1312
|
|
|
1309
1313
|
interface Compiler {
|
|
@@ -1471,7 +1475,8 @@ function compileNode(c: Compiler, value: unknown, place: readonly Key[], declare
|
|
|
1471
1475
|
} catch (error) {
|
|
1472
1476
|
return failingWith(c, meta, placed(thrown(error, `the evaluator ${name}`), place));
|
|
1473
1477
|
}
|
|
1474
|
-
|
|
1478
|
+
// Code that cannot fail needs no place and no catch, and it does not read the context of its grant (PLAN.TOTAL).
|
|
1479
|
+
const done = finish(c, meta, TOTAL.has(inner) ? inner : placedCode(inner, place, name), undefined);
|
|
1475
1480
|
if (frees === undefined || kind === undefined || !pureCode(c, envelope, kind, typePath, name, quick)) return done;
|
|
1476
1481
|
const free = freeOf(doc, kind, split.parts, frees);
|
|
1477
1482
|
if (free === undefined) return done;
|
|
@@ -1721,6 +1726,24 @@ function copyPlain(value: unknown, memo: Map<object, unknown>): unknown {
|
|
|
1721
1726
|
* the split of the value. Thus a plan with these helpers keeps only its code.
|
|
1722
1727
|
*/
|
|
1723
1728
|
|
|
1729
|
+
/**
|
|
1730
|
+
* The compiled code that cannot fail (PLAN.TOTAL). Such code gives a value and throws nothing. It does not read its
|
|
1731
|
+
* context or its runtime. Only the kits of the kernel mark code, thus the code of a host always gets its grant.
|
|
1732
|
+
*/
|
|
1733
|
+
const TOTAL: WeakSet<Code> = /* @__PURE__ */ new WeakSet();
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* This function marks compiled code that cannot fail (PLAN.TOTAL). Then `compileNode` does not wrap the code in
|
|
1737
|
+
* `placedCode`, thus a literal costs one call. The kits of the kernel use it. The package does not export it.
|
|
1738
|
+
*
|
|
1739
|
+
* @param code - The code. It gives a value and throws nothing. It does not read its context or its runtime.
|
|
1740
|
+
* @returns The same code.
|
|
1741
|
+
*/
|
|
1742
|
+
export function totalCode(code: Code): Code {
|
|
1743
|
+
TOTAL.add(code);
|
|
1744
|
+
return code;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1724
1747
|
/**
|
|
1725
1748
|
* This function gives the code of a document: the code of its evaluator, with the place of each error and of each
|
|
1726
1749
|
* exception. The code gets the context that its grant permits, thus a compiled parent cannot widen it (CAP.NARROW-CODE).
|
|
@@ -1952,7 +1975,7 @@ class CompiledPlan implements Plan {
|
|
|
1952
1975
|
return this.code(ctx, s);
|
|
1953
1976
|
}
|
|
1954
1977
|
|
|
1955
|
-
run(context: Context = makeContext(), runOptions:
|
|
1978
|
+
run(context: Context = makeContext(), runOptions: PlanRunOptions = {}): Promise<Result> {
|
|
1956
1979
|
const machine = this.machine;
|
|
1957
1980
|
const ctx = rootContext(machine, context);
|
|
1958
1981
|
const s = new Session(machine, true, this.traced, runOptions.perform, runOptions.signal, undefined, readingOf(runOptions.reads, ctx));
|
package/src/formula.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { type Json, type Key, docEquals, isKey, isRecord } from "./doc.ts";
|
|
|
2
2
|
import type { Kind, KindFacts, LazyRule, WorldEntry } from "./world.ts";
|
|
3
3
|
import { type CompiledPath, type Context, type Resolution, compilePath, valueOfThing } from "./deref.ts";
|
|
4
4
|
import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
|
|
5
|
-
import { type Code, type CompileInput, type CompiledEvaluator, type CompiledPart, type Evaluator, type Machine, type Runtime, resolveCompiled } from "./evaluate.ts";
|
|
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, stepType, typeText } from "./types.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
8
|
import type { TypeInput, TypeRule } from "./typing.ts";
|
|
9
9
|
import { decodeDomain, domainTypeOf } from "./codec.ts";
|
|
10
10
|
|
|
@@ -42,6 +42,8 @@ export type ExtFn = (data: Json, context: Context, domains?: Readonly<Record<str
|
|
|
42
42
|
* evaluates, and one failed part gives its own error. Without it, the first error stops the evaluation.
|
|
43
43
|
* - `pure`: the pure operations, for `factsOf` (D24). `true` names each operation that is not volatile. The default
|
|
44
44
|
* names no operation.
|
|
45
|
+
* - `pureExt`: the pure extensions of `ext`, by their name, for `factsOf` and the folds of a plan (PLAN.FOLD, D47).
|
|
46
|
+
* The default names no extension. A standard extension is pure without a name here.
|
|
45
47
|
* - `volatile`: the operations whose results can change while their arguments stay the same, for example a clock.
|
|
46
48
|
* - `sigs`: the signatures of the operations, by their name, for a check of types (TYPE.SIGNATURE). A list gives
|
|
47
49
|
* overloads. An operation without a signature takes and gives `unknown` (TYPE.GRADUAL).
|
|
@@ -61,13 +63,14 @@ export interface FormulaProfile {
|
|
|
61
63
|
readonly check?: (value: unknown, op: string) => Result | undefined;
|
|
62
64
|
readonly combine?: (errors: readonly EvalError[]) => EvalError;
|
|
63
65
|
readonly pure?: true | readonly string[];
|
|
66
|
+
readonly pureExt?: readonly string[];
|
|
64
67
|
readonly volatile?: readonly string[];
|
|
65
68
|
readonly sigs?: Readonly<Record<string, Signature | readonly Signature[]>> | ((name: string) => Signature | readonly Signature[] | undefined);
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
|
-
* The signature of an operation: the value types of its parameters and of its result (TYPE.SIGNATURE). A parameter
|
|
70
|
-
* the
|
|
72
|
+
* The signature of an operation: the value types of its parameters and of its result (TYPE.SIGNATURE). A parameter at
|
|
73
|
+
* the end of `params` is optional when `undefined` is a member of its type, for example `unknown`. A later version can add a key, for example the type that a
|
|
71
74
|
* true result proves.
|
|
72
75
|
*/
|
|
73
76
|
export interface Signature {
|
|
@@ -209,9 +212,13 @@ export function formulaKinds(): Readonly<Record<string, WorldEntry>> {
|
|
|
209
212
|
return kinds as Readonly<Record<string, WorldEntry>>;
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* This function gives the type of a value of a profile: the value itself as `one`. `undefined` gives the base type
|
|
217
|
+
* `undefined`, because JSON and thus `one` cannot hold it (D48). A function gives `unknown`.
|
|
218
|
+
*/
|
|
213
219
|
function valueType(value: unknown): ValueType {
|
|
214
|
-
|
|
220
|
+
if (value === undefined) return "undefined";
|
|
221
|
+
return typeof value === "function" ? "unknown" : { one: [value as Json] };
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
/** This function tells if a value has the form of a signature: a record with `params` as a list, and `to`. */
|
|
@@ -236,14 +243,14 @@ function listText(names: readonly string[]): string {
|
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
/**
|
|
239
|
-
* This function tells if a signature takes a count of arguments. A parameter
|
|
240
|
-
*
|
|
246
|
+
* This function tells if a signature takes a count of arguments. A parameter at the end is optional when its type
|
|
247
|
+
* holds `undefined`, because a missing argument is `undefined`. `unknown` and the base type `undefined` hold it.
|
|
241
248
|
*
|
|
242
249
|
* @returns `undefined` when the number is correct, or else the text of the numbers that the signature takes.
|
|
243
250
|
*/
|
|
244
251
|
function arityOf(sig: Signature, count: number, domains: Machine["domains"]): string | undefined {
|
|
245
252
|
let least = sig.params.length;
|
|
246
|
-
while (least > 0 &&
|
|
253
|
+
while (least > 0 && member(undefined, sig.params[least - 1] as ValueType, domains)) least -= 1;
|
|
247
254
|
const most = sig.rest === undefined ? sig.params.length : Number.POSITIVE_INFINITY;
|
|
248
255
|
if (count >= least && count <= most) return undefined;
|
|
249
256
|
if (most === Number.POSITIVE_INFINITY) return `at least ${argumentsText(least)}`;
|
|
@@ -487,6 +494,8 @@ function formulaTypes(profile: FormulaProfile): Readonly<Record<string, TypeRule
|
|
|
487
494
|
const name = nameOf(input);
|
|
488
495
|
const own = profile.ext !== undefined && Object.hasOwn(profile.ext, name);
|
|
489
496
|
if (own) return "unknown";
|
|
497
|
+
// The standard extensions give the values that JSON cannot write, thus each has a base type (D48).
|
|
498
|
+
if (name === "undefined") return "undefined";
|
|
490
499
|
if (name === "number") return "number";
|
|
491
500
|
return name === "domain" ? domainTypeOf(input.doc.value, input.machine.domains) : "unknown";
|
|
492
501
|
},
|
|
@@ -534,6 +543,7 @@ const PROFILE_KEYS = /* @__PURE__ */ Object.freeze([
|
|
|
534
543
|
"check",
|
|
535
544
|
"combine",
|
|
536
545
|
"pure",
|
|
546
|
+
"pureExt",
|
|
537
547
|
"volatile",
|
|
538
548
|
"sigs",
|
|
539
549
|
] as const satisfies readonly (keyof FormulaProfile)[]);
|
|
@@ -687,8 +697,9 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
687
697
|
|
|
688
698
|
return {
|
|
689
699
|
"formula.lit": compiled(({ doc }) => {
|
|
700
|
+
// A literal cannot fail, thus its code needs no place (PLAN.TOTAL).
|
|
690
701
|
const result = ok(doc.value);
|
|
691
|
-
return () => result;
|
|
702
|
+
return totalCode(() => result);
|
|
692
703
|
}),
|
|
693
704
|
|
|
694
705
|
"formula.ref": compiled(({ doc, parts, fill, address, machine }) => {
|
|
@@ -708,7 +719,7 @@ function formulaCode(profile: FormulaProfile): Readonly<Record<string, CompiledE
|
|
|
708
719
|
// A step document without parts is its own value. A step document with parts gets the result of each part.
|
|
709
720
|
if (parts.length === 0) {
|
|
710
721
|
const same = ok(doc);
|
|
711
|
-
return () => same;
|
|
722
|
+
return totalCode(() => same);
|
|
712
723
|
}
|
|
713
724
|
return (ctx, rt) => {
|
|
714
725
|
const values = valuesOf(parts, ctx, rt);
|
|
@@ -979,10 +990,13 @@ function makeKnown(profile: FormulaProfile): (path: readonly Key[], evaluator: s
|
|
|
979
990
|
};
|
|
980
991
|
}
|
|
981
992
|
|
|
982
|
-
/**
|
|
993
|
+
/**
|
|
994
|
+
* The facts of an extension of the profile that `pureExt` does not name. It gets the context, thus it can read outside
|
|
995
|
+
* `resolve`.
|
|
996
|
+
*/
|
|
983
997
|
const NOT_PURE: KindFacts = /* @__PURE__ */ Object.freeze({ pure: false });
|
|
984
998
|
|
|
985
|
-
/** The facts of a standard extension. */
|
|
999
|
+
/** The facts of a standard extension, and of an extension that `pureExt` names. */
|
|
986
1000
|
const PURE: KindFacts = /* @__PURE__ */ Object.freeze({ pure: true });
|
|
987
1001
|
|
|
988
1002
|
/** The lazy rule of a lazy operation: it covers each argument. */
|
|
@@ -994,7 +1008,8 @@ const EACH_PART: readonly LazyRule[] = /* @__PURE__ */ Object.freeze([/* @__PURE
|
|
|
994
1008
|
*
|
|
995
1009
|
* - An operation of `lazy` has a lazy rule for each argument.
|
|
996
1010
|
* - An operation is pure when the profile names it in `pure`, or when `pure` is `true`. A volatile operation is not pure.
|
|
997
|
-
* - A standard extension is pure. An extension of the profile
|
|
1011
|
+
* - A standard extension is pure. An extension of the profile is pure only when the profile names it in `pureExt` (D47).
|
|
1012
|
+
* Such an extension keeps C4: it reads the context only through `resolve`.
|
|
998
1013
|
*
|
|
999
1014
|
* Equal profiles give the same hook (D46).
|
|
1000
1015
|
*
|
|
@@ -1009,10 +1024,14 @@ export function formulaFacts(profile: FormulaProfile = {}): (path: readonly Key[
|
|
|
1009
1024
|
function makeFacts(profile: FormulaProfile, known: (path: readonly Key[], evaluator: string) => true | string): (path: readonly Key[], code: string) => KindFacts | undefined {
|
|
1010
1025
|
const volatile = new Set(profile.volatile ?? []);
|
|
1011
1026
|
const pure = profile.pure === true ? undefined : new Set(profile.pure ?? []);
|
|
1027
|
+
const pureExt = new Set(profile.pureExt ?? []);
|
|
1012
1028
|
return (path, code) => {
|
|
1013
1029
|
if (code !== "formula.op" && code !== "formula.ext") return undefined;
|
|
1014
1030
|
const name = path.slice(1).join(".");
|
|
1015
|
-
if (code === "formula.ext")
|
|
1031
|
+
if (code === "formula.ext") {
|
|
1032
|
+
if (profile.ext !== undefined && Object.hasOwn(profile.ext, name)) return pureExt.has(name) ? PURE : NOT_PURE;
|
|
1033
|
+
return Object.hasOwn(STD_EXT, name) ? PURE : undefined;
|
|
1034
|
+
}
|
|
1016
1035
|
// An operation that the language does not have gives #NAME?. The kit gives it no facts.
|
|
1017
1036
|
if (known(path, code) !== true) return undefined;
|
|
1018
1037
|
const isVolatile = volatile.has(name);
|
package/src/generate.ts
CHANGED
|
@@ -146,9 +146,11 @@ export function generate(world: World, options: GenerateOptions): unknown {
|
|
|
146
146
|
const keys = options.keys ?? KEYS;
|
|
147
147
|
const steps = options.steps ?? 2;
|
|
148
148
|
const roots: readonly Root[] = options.roots ?? ROOTS;
|
|
149
|
+
// A kind with the verb step walks a step document. With a default of step, each other kind walks one too (GEN.STEPS).
|
|
150
|
+
const defaultStep = Object.hasOwn(world.defaults, "step");
|
|
149
151
|
const stepTypes = types.filter((type) => {
|
|
150
152
|
const found = lookupKind(world, type);
|
|
151
|
-
return found.ok && Object.hasOwn(found.kind.verbs, "step");
|
|
153
|
+
return found.ok && (defaultStep || Object.hasOwn(found.kind.verbs, "step"));
|
|
152
154
|
});
|
|
153
155
|
// A name under each namespace with a fallback that no evaluator knows, for example ["ops", "nope"].
|
|
154
156
|
const unknownNames: readonly (readonly Key[])[] = Object.keys(world.kinds)
|
|
@@ -205,7 +207,10 @@ export function generate(world: World, options: GenerateOptions): unknown {
|
|
|
205
207
|
return out;
|
|
206
208
|
};
|
|
207
209
|
|
|
208
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* A path from a root (D10). It uses the names in scope, and sometimes a step document. The kind of a step document
|
|
212
|
+
* has the verb `step`, or the default of the world for `step` serves it.
|
|
213
|
+
*/
|
|
209
214
|
const pathFor = (pos: Position, refKind: Kind | undefined): Json => {
|
|
210
215
|
if (rng.chance(malformed)) return rng.pick<Json>(["names", [], { names: "a" }, ["nowhere", "a"]]);
|
|
211
216
|
const others = roots.filter((name) => name !== "names");
|
package/src/index.ts
CHANGED
|
@@ -49,8 +49,10 @@ export {
|
|
|
49
49
|
type Shape as PartShape,
|
|
50
50
|
type Side,
|
|
51
51
|
type TreeContract,
|
|
52
|
+
type VerbEffects,
|
|
52
53
|
type World,
|
|
53
54
|
type WorldEntry,
|
|
55
|
+
effectsOfVerb,
|
|
54
56
|
entriesAsJson,
|
|
55
57
|
equationType,
|
|
56
58
|
isAlias,
|
|
@@ -132,6 +134,7 @@ export {
|
|
|
132
134
|
type Part,
|
|
133
135
|
type Perform,
|
|
134
136
|
type Plan,
|
|
137
|
+
type PlanRunOptions,
|
|
135
138
|
type PrepareOptions,
|
|
136
139
|
type Read,
|
|
137
140
|
type Run,
|
package/src/providers.ts
CHANGED
|
@@ -235,6 +235,7 @@ function samplesAt(values: readonly unknown[], closed: boolean, depth: number):
|
|
|
235
235
|
let lists = false;
|
|
236
236
|
for (const value of values) {
|
|
237
237
|
if (value === null) bases.add("null");
|
|
238
|
+
else if (value === undefined) bases.add("undefined");
|
|
238
239
|
else if (typeof value === "boolean") bases.add("bool");
|
|
239
240
|
else if (typeof value === "number") bases.add("number");
|
|
240
241
|
else if (typeof value === "string") bases.add("text");
|
|
@@ -243,10 +244,10 @@ function samplesAt(values: readonly unknown[], closed: boolean, depth: number):
|
|
|
243
244
|
// A hole of a list holds undefined, thus a loop by index reads it too.
|
|
244
245
|
for (let index = 0; index < value.length; index += 1) items.push(value[index]);
|
|
245
246
|
} else if (isRecord(value)) records.push(value);
|
|
246
|
-
// A value without a type of the kernel, for example a function
|
|
247
|
+
// A value without a type of the kernel, for example a function, has each type.
|
|
247
248
|
else return "unknown";
|
|
248
249
|
}
|
|
249
|
-
for (const base of ["null", "bool", "number", "text"]) if (bases.has(base)) types.push(base);
|
|
250
|
+
for (const base of ["null", "undefined", "bool", "number", "text"]) if (bases.has(base)) types.push(base);
|
|
250
251
|
if (lists) types.push({ list: samplesAt(items, closed, depth + 1) });
|
|
251
252
|
if (records.length > 0) {
|
|
252
253
|
// All records join into one record. A field that a record does not have is optional.
|
|
@@ -268,7 +269,7 @@ function samplesAt(values: readonly unknown[], closed: boolean, depth: number):
|
|
|
268
269
|
* This function infers a value type from sample values (TYPE.SAMPLES), as a type provider of F# does. A host uses it
|
|
269
270
|
* for the type of a root, for example from some events.
|
|
270
271
|
*
|
|
271
|
-
* - `null`, a truth value, a number and a text give their base types, not their values.
|
|
272
|
+
* - `null`, `undefined`, a truth value, a number and a text give their base types, not their values.
|
|
272
273
|
* - The items of each list join into one type of items.
|
|
273
274
|
* - All records join into one record with each key of the samples. A key that a record does not have is optional.
|
|
274
275
|
* - Values of different forms give a union, as `join` gives. No samples give `never`.
|
package/src/static.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Envelope, type Json, type Key, isKey, isRecord, typeName } from "./doc.ts";
|
|
2
|
-
import { type Kind, type Namespace, type World, type WorldEntry, equationType, isKind, isNamespace } from "./world.ts";
|
|
2
|
+
import { type Kind, type Namespace, type World, type WorldEntry, effectsOfVerb, equationType, isKind, isNamespace } from "./world.ts";
|
|
3
3
|
import { ask } from "./ladder.ts";
|
|
4
4
|
import { type PartSlot, partsOf, scopesOf } from "./parts.ts";
|
|
5
5
|
import { type Cap, type Context, type Root, ROOTS, context, isRef, walkerOf, worldThing } from "./deref.ts";
|
|
@@ -364,8 +364,11 @@ export interface CheckOptions {
|
|
|
364
364
|
readonly caps?: readonly Cap[];
|
|
365
365
|
/**
|
|
366
366
|
* The verb that goes up the ladder at each document, as in `prepare`. The default is `eval`. `#NOEVAL` and the hook
|
|
367
|
-
* `known` follow this verb. The
|
|
368
|
-
*
|
|
367
|
+
* `known` follow this verb. The type rule of an evaluator serves each verb.
|
|
368
|
+
*
|
|
369
|
+
* The check tests the effects that the contract declares for this verb (VERB.EFFECTS). A kind without a declaration
|
|
370
|
+
* for the verb gives no effect issue. The contract declares the value types of `eval` only, thus only `eval` checks
|
|
371
|
+
* them (VERB.CHECK).
|
|
369
372
|
*/
|
|
370
373
|
readonly verb?: string;
|
|
371
374
|
/**
|
|
@@ -408,16 +411,28 @@ const EACH: readonly Cap[] = /* @__PURE__ */ Object.freeze([[]]);
|
|
|
408
411
|
const NO_EFFECTS: readonly (readonly Key[])[] = /* @__PURE__ */ Object.freeze([]);
|
|
409
412
|
|
|
410
413
|
/**
|
|
411
|
-
* This function gives the effects that the contract of the kind of a visit declares, as keys
|
|
412
|
-
* text `"type"` gives the type of the document after each alias. A visit without a
|
|
413
|
-
* `factsOf` and `effectsOf` read the declarations with this one function (D27).
|
|
414
|
+
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys
|
|
415
|
+
* (FACTS.EFFECTS, VERB.EFFECTS). The text `"type"` gives the type of the document after each alias. A visit without a
|
|
416
|
+
* kind declares no effect. `check`, `factsOf` and `effectsOf` read the declarations with this one function (D27).
|
|
414
417
|
*
|
|
415
418
|
* @param v - The visit.
|
|
419
|
+
* @param verb - The verb. The default is `eval`. A kind without a declaration for the verb gives no effect.
|
|
416
420
|
* @returns The keys of each declared effect, in the order of the contract.
|
|
417
421
|
*/
|
|
418
|
-
export function declaredEffects(v: DocVisit): readonly (readonly Key[])[] {
|
|
419
|
-
|
|
420
|
-
|
|
422
|
+
export function declaredEffects(v: DocVisit, verb = "eval"): readonly (readonly Key[])[] {
|
|
423
|
+
return verbDeclared(v, verb) ?? NO_EFFECTS;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* This function gives the effects that the contract of the kind of a visit declares for a verb, as keys. The result
|
|
428
|
+
* is `undefined` when the contract has no declaration for the verb (D49). A visit without a kind declares no effect
|
|
429
|
+
* for `eval` and no declaration for another verb.
|
|
430
|
+
*/
|
|
431
|
+
function verbDeclared(v: DocVisit, verb: string): readonly (readonly Key[])[] | undefined {
|
|
432
|
+
if (v.kind === undefined) return verb === "eval" ? NO_EFFECTS : undefined;
|
|
433
|
+
const effects = effectsOfVerb(v.kind, verb);
|
|
434
|
+
if (effects === undefined) return undefined;
|
|
435
|
+
if (effects.length === 0) return NO_EFFECTS;
|
|
421
436
|
return effects === "type" ? [v.typePath ?? []] : effects.map((kind) => kind.split("."));
|
|
422
437
|
}
|
|
423
438
|
|
|
@@ -472,7 +487,9 @@ function capReason(v: DocVisit, kind: readonly Key[], allowed: (caps: readonly C
|
|
|
472
487
|
* A run does not always fail there, thus a type issue is a static finding, not an error of a run.
|
|
473
488
|
*
|
|
474
489
|
* The check asks the ladder for the verb of the options, `eval` by default (VERB.CHECK). The shapes, the paths and the
|
|
475
|
-
* names follow the contracts, thus they are the same for each verb.
|
|
490
|
+
* names follow the contracts, thus they are the same for each verb. The check tests the effects that each contract
|
|
491
|
+
* declares for the verb: `effects` for `eval`, and the entry of `verbEffects` for another verb (VERB.EFFECTS). A kind
|
|
492
|
+
* without a declaration for the verb gives no `#CAP!` and no `#CALC!` for an effect.
|
|
476
493
|
*
|
|
477
494
|
* @param machine - The world and the code of the evaluators.
|
|
478
495
|
* @param doc - The document, as JSON.
|
|
@@ -651,9 +668,8 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
651
668
|
issues.push(issue);
|
|
652
669
|
if (current !== undefined) current.marked = true;
|
|
653
670
|
};
|
|
654
|
-
// The effects
|
|
655
|
-
// it happens (RUN.CAP).
|
|
656
|
-
const effects = verb === "eval";
|
|
671
|
+
// The field effects declares eval, and verbEffects declares the other verbs (VERB.EFFECTS). An effect of a verb
|
|
672
|
+
// without a declaration meets its test when it happens (RUN.CAP).
|
|
657
673
|
const allowed = options.caps === undefined ? undefined : permitsOf(machine);
|
|
658
674
|
// In a run, a part that can wait needs a parent that can wait (RUN.CAN-WAIT). The map tells for each document with
|
|
659
675
|
// code if its code passes a wait on: false for an evaluator, true for a runner whose waits reach the driver of the
|
|
@@ -704,7 +720,8 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
704
720
|
if (known !== true) push({ code: "#NAME?", place: v.place, reason: known, at: v.at });
|
|
705
721
|
}
|
|
706
722
|
const caps = v.caps;
|
|
707
|
-
const
|
|
723
|
+
const stated = verbDeclared(v, verb);
|
|
724
|
+
const declared = stated ?? NO_EFFECTS;
|
|
708
725
|
if (allowed !== undefined && caps !== undefined) {
|
|
709
726
|
for (const kind of declared) {
|
|
710
727
|
if (!allowed(caps, kind)) push({ code: "#CAP!", place: v.place, reason: capReason(v, kind, allowed), at: v.at });
|
|
@@ -718,16 +735,17 @@ function checkAll(machine: Machine, doc: unknown, options: CheckOptions, nodes:
|
|
|
718
735
|
// The run drives a runner under an evaluator, and only a real wait fails (RUN.DRIVE-NESTED). Its issue goes in
|
|
719
736
|
// here, in the order of the document, when an effect of its runners can wait.
|
|
720
737
|
const issue: Issue = { code: "#CALC!", place: v.place, reason: waitedReason(typeName(v.doc.type)), at: v.at };
|
|
721
|
-
chain = { issue, index: issues.length, node: current, wait:
|
|
738
|
+
chain = { issue, index: issues.length, node: current, wait: false };
|
|
722
739
|
drives.push(chain);
|
|
723
740
|
} else if (runner) {
|
|
724
741
|
chain = up ?? true;
|
|
725
742
|
}
|
|
726
743
|
waits.set(v, chain);
|
|
727
|
-
// A runner waits only for an effect that reaches the host (C6). A refused effect gives #CAP! without a wait.
|
|
744
|
+
// A runner waits only for an effect that reaches the host (C6). A refused effect gives #CAP! without a wait. A
|
|
745
|
+
// runner without a declaration for the verb can cause each effect, thus it can wait.
|
|
728
746
|
if (typeof chain === "object" && !chain.wait) {
|
|
729
747
|
const rule = allowed ?? permits;
|
|
730
|
-
chain.wait = declared.some((kind) => caps === undefined || rule(caps, kind));
|
|
748
|
+
chain.wait = stated === undefined || declared.some((kind) => caps === undefined || rule(caps, kind));
|
|
731
749
|
}
|
|
732
750
|
// In a run, the host does each effect, thus an evaluator cannot cause one.
|
|
733
751
|
const first = declared[0];
|
package/src/types.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { type Json, type Key, docEquals, isEnvelope, isRecord } from "./doc.ts";
|
|
|
9
9
|
/**
|
|
10
10
|
* A value type: a set of values that a result can have, as a JSON term.
|
|
11
11
|
*
|
|
12
|
-
* - A text names a base type: `unknown`, `never`, a domain of the kernel (`null`, `bool`, `number`,
|
|
13
|
-
* domain of a language.
|
|
12
|
+
* - A text names a base type: `unknown`, `never`, a domain of the kernel (`null`, `undefined`, `bool`, `number`,
|
|
13
|
+
* `text`), or a domain of a language.
|
|
14
14
|
* - `list`: a list whose items have the type.
|
|
15
15
|
* - `record`: a plain record with the named fields. `optional` names the fields that can be absent. A `closed`
|
|
16
16
|
* record has no other fields. The other fields of an open record can hold each value.
|
|
@@ -95,6 +95,8 @@ export interface TypeIssue {
|
|
|
95
95
|
*/
|
|
96
96
|
export const STD_DOMAINS: Readonly<Record<string, Domain>> = {
|
|
97
97
|
null: { is: (value) => value === null, summary: "The value null." },
|
|
98
|
+
// JSON has no undefined, thus a document holds it as ext.undefined (D48). The domain has no codec: the codec of values writes undefined first.
|
|
99
|
+
undefined: { is: (value) => value === undefined, summary: "The value undefined. A document holds it as the extension ext.undefined." },
|
|
98
100
|
bool: { is: (value) => typeof value === "boolean", summary: "A truth value: true or false." },
|
|
99
101
|
number: { is: (value) => typeof value === "number", summary: "A number of the host, also NaN, an infinity and -0." },
|
|
100
102
|
text: { is: (value) => typeof value === "string", summary: "A text." },
|
package/src/world.ts
CHANGED
|
@@ -172,7 +172,8 @@ export function equationType(kind: Kind): string | undefined {
|
|
|
172
172
|
/**
|
|
173
173
|
* A kind gives the name of an evaluator for each verb, and the contract of its value. The contract serves each verb.
|
|
174
174
|
* For each verb, the kernel splits the parts and narrows their capabilities by it, and `check` reads its scope rules.
|
|
175
|
-
* `side` and `effects` tell what the verb `eval` does (VERB.CONTRACT).
|
|
175
|
+
* `side` and `effects` tell what the verb `eval` does (VERB.CONTRACT). `verbEffects` tells the effects of the other
|
|
176
|
+
* verbs (VERB.EFFECTS).
|
|
176
177
|
*/
|
|
177
178
|
export interface Kind extends KindFacts {
|
|
178
179
|
/**
|
|
@@ -200,6 +201,13 @@ export interface Kind extends KindFacts {
|
|
|
200
201
|
* (CAP.LATENT).
|
|
201
202
|
*/
|
|
202
203
|
readonly effects?: "type" | readonly string[];
|
|
204
|
+
/**
|
|
205
|
+
* The effects that the other verbs of a document of this kind can cause, by verb, in the form of `effects`
|
|
206
|
+
* (VERB.EFFECTS, D49). An entry `view: []` declares no effect for `view`. A verb without an entry has no
|
|
207
|
+
* declaration, thus `check` does not test its effects. The field `effects` declares `eval`, thus this field has no
|
|
208
|
+
* entry `eval`.
|
|
209
|
+
*/
|
|
210
|
+
readonly verbEffects?: VerbEffects;
|
|
203
211
|
/**
|
|
204
212
|
* The value type of the result of the verb `eval`, when it is fixed (TYPE.SYNTH). A check of types gives it to each
|
|
205
213
|
* document of the kind, in the place of the type that the rule of the evaluator synthesizes.
|
|
@@ -214,6 +222,28 @@ export interface Kind extends KindFacts {
|
|
|
214
222
|
readonly summary?: string;
|
|
215
223
|
}
|
|
216
224
|
|
|
225
|
+
/**
|
|
226
|
+
* The effects that a contract declares for the verbs other than `eval`, by verb (D49). Each entry has the form of the
|
|
227
|
+
* field `effects` of a kind. The type has no entry `eval`, because the field `effects` declares `eval`.
|
|
228
|
+
*/
|
|
229
|
+
export type VerbEffects = Readonly<Record<string, "type" | readonly string[]>> & { readonly eval?: never };
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* This function gives the effects that the contract of a kind declares for a verb (VERB.EFFECTS, D49). For `eval`,
|
|
233
|
+
* the field `effects` declares them, and a kind without the field declares no effect. For another verb, the entry of
|
|
234
|
+
* `verbEffects` declares them. `check` and `conforms` read the declarations with this one function.
|
|
235
|
+
*
|
|
236
|
+
* @param kind - The kind.
|
|
237
|
+
* @param verb - The verb.
|
|
238
|
+
* @returns The declaration in the form of `effects`, or `undefined` when the contract does not declare the effects of
|
|
239
|
+
* the verb.
|
|
240
|
+
*/
|
|
241
|
+
export function effectsOfVerb(kind: Kind, verb: string): "type" | readonly string[] | undefined {
|
|
242
|
+
if (verb === "eval") return kind.effects ?? [];
|
|
243
|
+
const table = kind.verbEffects as Readonly<Record<string, "type" | readonly string[]>> | undefined;
|
|
244
|
+
return table !== undefined && Object.hasOwn(table, verb) ? table[verb] : undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
217
247
|
/** A namespace: a group of entries under one key of the world. `fallback` is the kind of each other key in it. */
|
|
218
248
|
export interface Namespace {
|
|
219
249
|
readonly kinds: Readonly<Record<string, WorldEntry>>;
|