@mark1russell7/coeval 0.21.0 → 0.23.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 +23 -5
- package/build/index.d.ts +420 -116
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5669 -3655
- package/build/index.js.map +1 -1
- package/package.json +4 -4
- package/src/codec-laws.ts +152 -0
- package/src/codec.ts +2 -2
- package/src/conform.ts +662 -70
- package/src/deref.ts +17 -3
- package/src/durable.ts +21 -9
- package/src/errors.ts +17 -1
- package/src/evaluate.ts +55 -21
- package/src/formula.ts +82 -22
- package/src/index.ts +16 -0
- package/src/instances.ts +61 -6
- package/src/meaning.ts +43 -10
- package/src/parts.ts +7 -4
- package/src/providers.ts +4 -1
- package/src/reference.ts +134 -9
- package/src/schedule.ts +286 -45
- package/src/static.ts +116 -23
- package/src/stdlib.ts +229 -6
- package/src/syntax.ts +430 -48
- package/src/types.ts +2 -1
- package/src/world.ts +106 -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.23.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",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"json"
|
|
36
36
|
],
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@mark1russell7/cue": "^1.
|
|
39
|
-
"@mark1russell7/rigor-bench": "^0.2.
|
|
40
|
-
"@mark1russell7/rigor-statistics": "^0.1.
|
|
38
|
+
"@mark1russell7/cue": "^2.1.1",
|
|
39
|
+
"@mark1russell7/rigor-bench": "^0.2.15",
|
|
40
|
+
"@mark1russell7/rigor-statistics": "^0.1.12",
|
|
41
41
|
"@types/node": "^26.6.4",
|
|
42
42
|
"esbuild": "^0.28.2",
|
|
43
43
|
"typescript": "^7.0.2",
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { sameValue } from "./agree.ts";
|
|
2
|
+
import { codecOf, decodeDomain } from "./codec.ts";
|
|
3
|
+
import { type Json, docEquals } from "./doc.ts";
|
|
4
|
+
import { type Result, describe, fail, ok } from "./errors.ts";
|
|
5
|
+
import type { Machine } from "./evaluate.ts";
|
|
6
|
+
import { domainOf, membersOf } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* The check of the laws of a codec (K141, D130). D40 states the laws, and `codecLaws` examines them for one domain of
|
|
10
|
+
* a machine. It stays apart from `conforms`: a codec has no document and no parts, thus no probe of `conforms` fits it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A law of a codec that `codecLaws` checks.
|
|
15
|
+
*
|
|
16
|
+
* - `samples`: the check has at least one member of the domain. Without a member, the check proves nothing.
|
|
17
|
+
* - `encode`: `encode` writes each member, and it does not throw.
|
|
18
|
+
* - `json`: the data of `encode` is JSON. `JSON.parse(JSON.stringify(data))` gives equal data, thus the data has no
|
|
19
|
+
* `NaN`, no `-0`, no infinity, no `undefined`, no function, no host object and no cycle.
|
|
20
|
+
* - `stable`: the same member has one document. A second `encode` of the member gives equal data, and so does an
|
|
21
|
+
* `encode` of the decoded member. The check compares with `docEquals`.
|
|
22
|
+
* - `decode`: `decodeDomain` reads the JSON copy of the data, and it gives a member of the domain.
|
|
23
|
+
* - `roundtrip`: the decoded member equals the member by `sameValue`.
|
|
24
|
+
*/
|
|
25
|
+
export type CodecLaw = "samples" | "encode" | "json" | "stable" | "decode" | "roundtrip";
|
|
26
|
+
|
|
27
|
+
/** The probe of a finding of `codecLaws`: the domain, the member and the data of the member. */
|
|
28
|
+
export interface CodecProbe {
|
|
29
|
+
/** The name of the domain. */
|
|
30
|
+
readonly domain: string;
|
|
31
|
+
/** The member that shows the finding. The law `samples` has no member, thus the value is `undefined`. */
|
|
32
|
+
readonly value: unknown;
|
|
33
|
+
/** The data that `encode` gave for the member. The laws `samples` and `encode` have no data. */
|
|
34
|
+
readonly data?: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One law that a codec breaks, with the reason and the first probe that shows it. The form is the form of `Finding`. */
|
|
38
|
+
export interface CodecFinding {
|
|
39
|
+
readonly obligation: CodecLaw;
|
|
40
|
+
/** One sentence that tells what the codec did. */
|
|
41
|
+
readonly reason: string;
|
|
42
|
+
readonly probe: CodecProbe;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The options of `codecLaws`. */
|
|
46
|
+
export interface CodecLawsOptions {
|
|
47
|
+
/** More members of the domain. They come first. A value that is not a member of the domain gets no test. */
|
|
48
|
+
readonly samples?: readonly unknown[];
|
|
49
|
+
/**
|
|
50
|
+
* This function compares two values that the rules of `sameValue` do not know, as in `agree`. A member that is an
|
|
51
|
+
* object needs this rule, for example a date, because `sameValue` compares an object only with itself.
|
|
52
|
+
*
|
|
53
|
+
* @param a - The first value.
|
|
54
|
+
* @param b - The second value.
|
|
55
|
+
* @returns `true` or `false`, or `undefined` for the rules of `sameValue`.
|
|
56
|
+
*/
|
|
57
|
+
readonly equal?: (a: unknown, b: unknown) => boolean | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The order of the laws in the list of findings. */
|
|
61
|
+
const ORDER: readonly CodecLaw[] = ["samples", "encode", "json", "stable", "decode", "roundtrip"];
|
|
62
|
+
|
|
63
|
+
/** This function gives the result of a function, or a failure with the text of the error that it throws. */
|
|
64
|
+
function attempt<V>(act: () => V): Result<V> {
|
|
65
|
+
try {
|
|
66
|
+
return ok(act());
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return fail("#VALUE!", describe(error));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* This function gives the edge values of each base type: the values that a codec loses first. `codecLaws` tests the
|
|
74
|
+
* values that are members of the domain. The function makes new values at each call, thus a codec that changes a
|
|
75
|
+
* value changes no later test.
|
|
76
|
+
*/
|
|
77
|
+
function edgeValues(): readonly unknown[] {
|
|
78
|
+
const proto = JSON.parse('{ "__proto__": 1 }') as unknown;
|
|
79
|
+
const numbers = [0, -0, 1, -1, 0.1, Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
|
80
|
+
const limits = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, 2 ** 53, Number.MAX_VALUE, Number.MIN_VALUE];
|
|
81
|
+
const texts = ["", " ", "a", "\u0000", "\ud800", "\u{1f600}", "__proto__"];
|
|
82
|
+
const wholes = [0n, -1n, 2n ** 53n, 2n ** 70n];
|
|
83
|
+
const lists = [[], [[]], [undefined], [Number.NaN], [-0]];
|
|
84
|
+
const records = [{}, { a: {} }, { a: undefined }, { a: Number.NaN }, proto];
|
|
85
|
+
const hosts = [new Date(0), new Date(Number.NaN), new Map(), new Set()];
|
|
86
|
+
return [null, undefined, true, false, ...numbers, ...limits, ...texts, ...wholes, ...lists, ...records, ...hosts];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** This function gives the JSON copy of data, or a failure that tells why the data is not JSON. */
|
|
90
|
+
function jsonCopy(data: unknown): Result<Json> {
|
|
91
|
+
const copy = attempt(() => {
|
|
92
|
+
const text = JSON.stringify(data) as string | undefined;
|
|
93
|
+
return text === undefined ? undefined : (JSON.parse(text) as Json);
|
|
94
|
+
});
|
|
95
|
+
if (!copy.ok) return fail("#VALUE!", `JSON cannot write the data of encode: ${copy.error.reason}`);
|
|
96
|
+
if (copy.value !== undefined && docEquals(copy.value, data)) return ok(copy.value);
|
|
97
|
+
return fail("#VALUE!", "The data of encode is not JSON: JSON.parse(JSON.stringify(data)) does not give equal data.");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** This function tells if a function of a codec gives data equal to `data`. A throw gives `false`. */
|
|
101
|
+
function sameData(act: () => unknown, data: unknown): boolean {
|
|
102
|
+
const again = attempt(act);
|
|
103
|
+
return again.ok && docEquals(again.value, data);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* This function examines the laws of the codec of one domain of a machine (K141, D130). A test of a language starts it
|
|
108
|
+
* for each domain with a codec. The members are the list `values` of the domain, or else the given samples, the edge
|
|
109
|
+
* values and the lists `samples` of the domains. Each value that is not a member gets no test.
|
|
110
|
+
*
|
|
111
|
+
* The law `decode` reads the JSON copy of the data through `decodeDomain`, as an evaluation of `ext.domain` does. Thus
|
|
112
|
+
* a codec that reads a data object by its identity breaks a law. The finding of a law names the first member that
|
|
113
|
+
* breaks it. An empty list means that each member keeps each law.
|
|
114
|
+
*
|
|
115
|
+
* @param machine - The machine, or only its domains.
|
|
116
|
+
* @param name - The name of the domain.
|
|
117
|
+
* @param options - The samples and the rule of equality.
|
|
118
|
+
* @returns The first finding of each law that the codec breaks, in the order of the laws.
|
|
119
|
+
* @throws {Error} The machine has no domain with the name, or the domain has no codec.
|
|
120
|
+
*/
|
|
121
|
+
export function codecLaws(machine: Pick<Machine, "domains">, name: string, options: CodecLawsOptions = {}): readonly CodecFinding[] {
|
|
122
|
+
const domains = machine.domains;
|
|
123
|
+
const domain = domainOf(name, domains);
|
|
124
|
+
const codec = domain === undefined ? undefined : codecOf(domain);
|
|
125
|
+
if (codec === undefined) throw new Error(`The machine has no domain ${name} with a codec.`);
|
|
126
|
+
const found = new Map<CodecLaw, CodecFinding>();
|
|
127
|
+
const report = (obligation: CodecLaw, reason: string, value: unknown, more: { readonly data?: unknown } = {}): void => {
|
|
128
|
+
if (!found.has(obligation)) found.set(obligation, { obligation, reason, probe: { domain: name, value, ...more } });
|
|
129
|
+
};
|
|
130
|
+
const members = membersOf(name, domains, [...(options.samples ?? []), ...edgeValues()]);
|
|
131
|
+
if (members.length === 0) report("samples", `The check has no member of the domain ${name}. Give members in the option samples.`, undefined);
|
|
132
|
+
for (const value of members) {
|
|
133
|
+
const written = attempt(() => codec.encode(value));
|
|
134
|
+
if (!written.ok) {
|
|
135
|
+
report("encode", `encode throws for a member: ${written.error.reason}`, value);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const data = written.value;
|
|
139
|
+
const probe = { data };
|
|
140
|
+
const copy = jsonCopy(data);
|
|
141
|
+
if (!copy.ok) report("json", copy.error.reason, value, probe);
|
|
142
|
+
if (!sameData(() => codec.encode(value), data)) report("stable", "A second encode of the member does not give equal data.", value, probe);
|
|
143
|
+
const back = decodeDomain({ domain: name, data: copy.ok ? copy.value : data }, domains);
|
|
144
|
+
if (!back.ok) {
|
|
145
|
+
report("decode", back.error.reason, value, probe);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!sameValue(back.value, value, options.equal)) report("roundtrip", "The decoded member does not equal the member by sameValue. An object needs the option equal.", value, probe);
|
|
149
|
+
if (!sameData(() => codec.encode(back.value), data)) report("stable", "An encode of the decoded member does not give equal data.", value, probe);
|
|
150
|
+
}
|
|
151
|
+
return ORDER.filter((law) => found.has(law)).map((law) => found.get(law) as CodecFinding);
|
|
152
|
+
}
|
package/src/codec.ts
CHANGED
|
@@ -12,8 +12,8 @@ import { type Domain, type DomainCodec, domainOf, lineOf, member } from "./types
|
|
|
12
12
|
/** The domains of a machine, by their names. */
|
|
13
13
|
type Domains = Readonly<Record<string, Domain>> | undefined;
|
|
14
14
|
|
|
15
|
-
/** This function gives the codec of a domain, or `undefined` when the domain has no codec. */
|
|
16
|
-
function codecOf(domain: Domain): DomainCodec | undefined {
|
|
15
|
+
/** This function gives the codec of a domain, or `undefined` when the domain has no codec. The function is internal: `codecLaws` uses it too. */
|
|
16
|
+
export function codecOf(domain: Domain): DomainCodec | undefined {
|
|
17
17
|
const codec: unknown = domain.codec;
|
|
18
18
|
if (typeof codec !== "object" || codec === null) return undefined;
|
|
19
19
|
const { encode, decode } = codec as Partial<DomainCodec>;
|