@mark1russell7/coeval 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +40 -2
- package/build/index.d.ts +1600 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3378 -0
- package/build/index.js.map +1 -0
- package/package.json +49 -3
- package/src/classify.ts +54 -0
- package/src/context.ts +193 -0
- package/src/deref.ts +401 -0
- package/src/doc.ts +251 -0
- package/src/errors.ts +141 -0
- package/src/evaluate.ts +1069 -0
- package/src/formula.ts +409 -0
- package/src/index.ts +183 -0
- package/src/ladder.ts +141 -0
- package/src/order.ts +111 -0
- package/src/parts.ts +156 -0
- package/src/static.ts +364 -0
- package/src/stdlib.ts +310 -0
- package/src/world.ts +237 -0
package/src/formula.ts
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { type Json, isRecord } from "./doc.ts";
|
|
2
|
+
import type { Kind, WorldEntry } from "./world.ts";
|
|
3
|
+
import { valueOfThing } from "./deref.ts";
|
|
4
|
+
import { type EvalError, type Failed, type Result, Failure, fail, ok, thrown } from "./errors.ts";
|
|
5
|
+
import type { Code, CompileInput, CompiledEvaluator, CompiledPart, Evaluator, Machine } from "./evaluate.ts";
|
|
6
|
+
|
|
7
|
+
/** An operation of a formula language: a function of the values of its arguments. */
|
|
8
|
+
export type OpFn = (...args: never[]) => unknown;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A lazy operation: it gets one function for each argument. It evaluates only the arguments that it needs, for
|
|
12
|
+
* example a default for an error.
|
|
13
|
+
*/
|
|
14
|
+
export type LazyOp = (args: readonly (() => Result)[]) => Result;
|
|
15
|
+
|
|
16
|
+
/** The code of an extension: it gives the value of the data of an extension document. */
|
|
17
|
+
export type ExtFn = (data: Json) => Result;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The rules of one formula language: its operations, and the rules that differ between languages.
|
|
21
|
+
*
|
|
22
|
+
* - `ops`: the operations of the namespace `ops`, by their name (the keys after `ops`, with dots between them).
|
|
23
|
+
* - `lazy`: the operations that evaluate their arguments themselves.
|
|
24
|
+
* - `ext`: the extensions of the namespace `ext`, by their name. The kit has `ext.undefined` and `ext.number`.
|
|
25
|
+
* - `truthy`: the truth of a value for `if`, `and` and `or`. `undefined` is an error `#VALUE!`. The default
|
|
26
|
+
* accepts only `true` and `false`.
|
|
27
|
+
* - `otherwise`: the value of an `if` without `else`. The default is `null`.
|
|
28
|
+
* - `logic`: what `and` and `or` give: a truth value (the default), or the value that decided.
|
|
29
|
+
* - `omit`: the errors that leave a field out of a `record`, as an absent value.
|
|
30
|
+
* - `missing`: the values that a reference cannot give. Such a value gives `#N/A`.
|
|
31
|
+
* - `check`: a check of the value of an operation, for example "a number must be finite".
|
|
32
|
+
*/
|
|
33
|
+
export interface FormulaProfile {
|
|
34
|
+
readonly ops?: Readonly<Record<string, OpFn>> | ((name: string) => OpFn | undefined);
|
|
35
|
+
readonly lazy?: Readonly<Record<string, LazyOp>>;
|
|
36
|
+
readonly ext?: Readonly<Record<string, ExtFn>>;
|
|
37
|
+
readonly truthy?: (value: unknown) => boolean | undefined;
|
|
38
|
+
readonly otherwise?: unknown;
|
|
39
|
+
readonly logic?: "boolean" | "value";
|
|
40
|
+
readonly omit?: (error: EvalError) => boolean;
|
|
41
|
+
readonly missing?: (value: unknown) => boolean;
|
|
42
|
+
readonly check?: (value: unknown, op: string) => Result | undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The value of a name whose binding failed. A reference to the name gives the error. */
|
|
46
|
+
export class Held {
|
|
47
|
+
constructor(readonly error: EvalError) {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const CLOSURE: unique symbol = Symbol.for("coeval.closure") as never;
|
|
51
|
+
|
|
52
|
+
/** A function that the construct `fn` made. It throws a `Failure` when its body fails. */
|
|
53
|
+
export type Closure = ((...args: readonly unknown[]) => unknown) & { readonly [CLOSURE]: { readonly params: readonly string[] } };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* This function tells if a value is a function that the construct `fn` made.
|
|
57
|
+
*
|
|
58
|
+
* @param value - The value.
|
|
59
|
+
* @returns `true` for a closure.
|
|
60
|
+
*/
|
|
61
|
+
export function isClosure(value: unknown): value is Closure {
|
|
62
|
+
return typeof value === "function" && (value as Partial<Closure>)[CLOSURE] !== undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The standard extensions: the values that JSON cannot write. */
|
|
66
|
+
export const STD_EXT: Readonly<Record<string, ExtFn>> = {
|
|
67
|
+
undefined: () => ok(undefined),
|
|
68
|
+
number: (data) => {
|
|
69
|
+
if (data === "NaN") return ok(Number.NaN);
|
|
70
|
+
if (data === "Infinity") return ok(Number.POSITIVE_INFINITY);
|
|
71
|
+
if (data === "-Infinity") return ok(Number.NEGATIVE_INFINITY);
|
|
72
|
+
if (data === "-0") return ok(-0);
|
|
73
|
+
return fail("#VALUE!", "The value of ext.number is NaN, Infinity, -Infinity or -0.");
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** The truth of a value in the rules of JavaScript: `0`, `""`, `null`, `undefined` and `NaN` are false. */
|
|
78
|
+
export function jsTruthy(value: unknown): boolean {
|
|
79
|
+
return Boolean(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function strictTruthy(value: unknown): boolean | undefined {
|
|
83
|
+
return typeof value === "boolean" ? value : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function setOwn(target: Record<string, unknown>, key: string, value: unknown): void {
|
|
87
|
+
Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function compiled(compile: (input: CompileInput) => Code): CompiledEvaluator {
|
|
91
|
+
return { compile };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function partsOf(value: unknown): Readonly<Record<string, CompiledPart>> {
|
|
95
|
+
return isRecord(value) ? (value as unknown as Readonly<Record<string, CompiledPart>>) : {};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const LIT_SUMMARY = "A literal: its value is the result.";
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* This function makes the kinds of the formula kit, for a world. The kinds are the shared constructs of D13 and
|
|
102
|
+
* the namespaces `ops` and `ext`. A bare value in a position of a term is a literal.
|
|
103
|
+
*
|
|
104
|
+
* @returns The entries of the world, by their key.
|
|
105
|
+
*/
|
|
106
|
+
export function formulaKinds(): Readonly<Record<string, WorldEntry>> {
|
|
107
|
+
const term = { shape: "term", of: "lit" } as const;
|
|
108
|
+
const terms = { shape: "list", of: "lit" } as const;
|
|
109
|
+
const kinds: Record<string, Kind | WorldEntry> = {
|
|
110
|
+
lit: { verbs: { eval: "formula.lit" }, summary: LIT_SUMMARY },
|
|
111
|
+
ref: { verbs: { eval: "formula.ref" }, summary: "A path from a root of the context. Its result is what the path reaches." },
|
|
112
|
+
record: { verbs: { eval: "formula.record" }, parts: { shape: "map", of: "lit" }, summary: "A record: each field is a term." },
|
|
113
|
+
list: { verbs: { eval: "formula.list" }, parts: terms, summary: "A list: each item is a term." },
|
|
114
|
+
let: {
|
|
115
|
+
verbs: { eval: "formula.let" },
|
|
116
|
+
parts: { shape: "record", fields: { bind: { shape: "map", of: "lit" }, body: term }, required: ["bind", "body"] },
|
|
117
|
+
scope: [{ names: { keysAt: ["bind"] }, in: [["body"]] }],
|
|
118
|
+
summary: "Names for the body. Each binding sees the names around the let, not the other bindings.",
|
|
119
|
+
},
|
|
120
|
+
fn: {
|
|
121
|
+
verbs: { eval: "formula.fn" },
|
|
122
|
+
parts: { shape: "record", fields: { body: term }, required: ["params", "body"] },
|
|
123
|
+
scope: [{ names: { listAt: ["params"] }, in: [["body"]] }],
|
|
124
|
+
summary: "A function: its parameters are names for its body.",
|
|
125
|
+
},
|
|
126
|
+
call: {
|
|
127
|
+
verbs: { eval: "formula.call" },
|
|
128
|
+
parts: { shape: "record", fields: { fn: term, args: terms }, required: ["fn"] },
|
|
129
|
+
summary: "It calls a function with the values of its arguments.",
|
|
130
|
+
},
|
|
131
|
+
if: {
|
|
132
|
+
verbs: { eval: "formula.if" },
|
|
133
|
+
parts: { shape: "record", fields: { test: term, then: term, else: term }, required: ["test", "then"] },
|
|
134
|
+
summary: "It evaluates the test, then only the branch that the test selects.",
|
|
135
|
+
},
|
|
136
|
+
and: { verbs: { eval: "formula.and" }, parts: terms, summary: "True when each term is true. It stops at the first false term." },
|
|
137
|
+
or: { verbs: { eval: "formula.or" }, parts: terms, summary: "True when one term is true. It stops at the first true term." },
|
|
138
|
+
ops: { kinds: {}, fallback: { verbs: { eval: "formula.op" }, parts: terms, summary: "An operation of the language, applied to the values of its terms." } },
|
|
139
|
+
ext: { kinds: {}, fallback: { verbs: { eval: "formula.ext" }, summary: "An extension of the language. Its value is data." } },
|
|
140
|
+
};
|
|
141
|
+
return kinds as Readonly<Record<string, WorldEntry>>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* This function makes the evaluators of the formula kit for one language. Each evaluator compiles its document
|
|
146
|
+
* one time into code.
|
|
147
|
+
*
|
|
148
|
+
* @param profile - The operations and the rules of the language.
|
|
149
|
+
* @returns The evaluators, by their name.
|
|
150
|
+
*/
|
|
151
|
+
export function formulaEvaluators(profile: FormulaProfile = {}): Readonly<Record<string, Evaluator>> {
|
|
152
|
+
const truthy = profile.truthy ?? strictTruthy;
|
|
153
|
+
const otherwise = ok(profile.otherwise ?? null);
|
|
154
|
+
const logicValue = profile.logic === "value";
|
|
155
|
+
const omit = profile.omit;
|
|
156
|
+
const missing = profile.missing;
|
|
157
|
+
const check = profile.check;
|
|
158
|
+
const lookup = (name: string): OpFn | undefined => {
|
|
159
|
+
const ops = profile.ops;
|
|
160
|
+
if (ops === undefined) return undefined;
|
|
161
|
+
if (typeof ops === "function") return ops(name);
|
|
162
|
+
return Object.hasOwn(ops, name) ? ops[name] : undefined;
|
|
163
|
+
};
|
|
164
|
+
const notTruth = (what: string): Failed => fail("#VALUE!", `The ${what} must give a truth value.`);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"formula.lit": compiled(({ doc }) => {
|
|
168
|
+
const result = ok(doc.value);
|
|
169
|
+
return () => result;
|
|
170
|
+
}),
|
|
171
|
+
|
|
172
|
+
"formula.ref": compiled(({ doc }) => {
|
|
173
|
+
const path = doc.value;
|
|
174
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
175
|
+
const bad = fail("#VALUE!", 'The value of ref is a path: a list that starts with a root, for example ["names", "a"].');
|
|
176
|
+
return () => bad;
|
|
177
|
+
}
|
|
178
|
+
// A path through a name whose binding failed gives the error of the binding.
|
|
179
|
+
const head = path[0] === "names" && typeof path[1] === "string" ? path[1] : undefined;
|
|
180
|
+
return (ctx, rt) => {
|
|
181
|
+
if (head !== undefined && Object.hasOwn(ctx.names, head)) {
|
|
182
|
+
const bound = ctx.names[head];
|
|
183
|
+
if (bound instanceof Held) return { ok: false, error: bound.error };
|
|
184
|
+
}
|
|
185
|
+
const found = rt.resolve(ctx, path);
|
|
186
|
+
if (!found.ok) return fail(found.code, found.reason);
|
|
187
|
+
const value = valueOfThing(found.thing);
|
|
188
|
+
if (value instanceof Held) return { ok: false, error: value.error };
|
|
189
|
+
if (missing !== undefined && missing(value)) return fail("#N/A", "The path reaches no value.");
|
|
190
|
+
return ok(value);
|
|
191
|
+
};
|
|
192
|
+
}),
|
|
193
|
+
|
|
194
|
+
"formula.record": compiled(({ value }) => {
|
|
195
|
+
const fields = Object.entries(partsOf(value));
|
|
196
|
+
return (ctx, rt) => {
|
|
197
|
+
const out: Record<string, unknown> = {};
|
|
198
|
+
for (const [key, part] of fields) {
|
|
199
|
+
const result = part.code(ctx, rt);
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
if (omit !== undefined && omit(result.error)) continue;
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
setOwn(out, key, result.value);
|
|
205
|
+
}
|
|
206
|
+
return ok(out);
|
|
207
|
+
};
|
|
208
|
+
}),
|
|
209
|
+
|
|
210
|
+
"formula.list": compiled(({ parts }) => (ctx, rt) => {
|
|
211
|
+
const out = new Array<unknown>(parts.length);
|
|
212
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
213
|
+
const result = (parts[index] as CompiledPart).code(ctx, rt);
|
|
214
|
+
if (!result.ok) return result;
|
|
215
|
+
out[index] = result.value;
|
|
216
|
+
}
|
|
217
|
+
return ok(out);
|
|
218
|
+
}),
|
|
219
|
+
|
|
220
|
+
"formula.let": compiled(({ value }) => {
|
|
221
|
+
const record = partsOf(value);
|
|
222
|
+
const bindings = Object.entries(partsOf(record["bind"]));
|
|
223
|
+
const body = record["body"] as CompiledPart;
|
|
224
|
+
return (ctx, rt) => {
|
|
225
|
+
const names: Record<string, unknown> = { ...ctx.names };
|
|
226
|
+
for (const [name, part] of bindings) {
|
|
227
|
+
const result = part.code(ctx, rt);
|
|
228
|
+
setOwn(names, name, result.ok ? result.value : new Held(result.error));
|
|
229
|
+
}
|
|
230
|
+
return body.code({ ...ctx, names }, rt);
|
|
231
|
+
};
|
|
232
|
+
}),
|
|
233
|
+
|
|
234
|
+
"formula.fn": compiled(({ value, data }) => {
|
|
235
|
+
const params = isRecord(data) ? data["params"] : undefined;
|
|
236
|
+
if (!Array.isArray(params) || !params.every((param) => typeof param === "string") || new Set(params).size !== params.length) {
|
|
237
|
+
const bad = fail("#VALUE!", "The params of fn are a list of different texts.");
|
|
238
|
+
return () => bad;
|
|
239
|
+
}
|
|
240
|
+
const names = params as readonly string[];
|
|
241
|
+
const body = partsOf(value)["body"] as CompiledPart;
|
|
242
|
+
return (ctx, rt) => {
|
|
243
|
+
const closure = (...args: readonly unknown[]): unknown => {
|
|
244
|
+
const scope: Record<string, unknown> = { ...ctx.names };
|
|
245
|
+
names.forEach((name, index) => setOwn(scope, name, args[index]));
|
|
246
|
+
const result = body.code({ ...ctx, names: scope }, rt);
|
|
247
|
+
if (!result.ok) throw new Failure(result.error);
|
|
248
|
+
return result.value;
|
|
249
|
+
};
|
|
250
|
+
Object.defineProperty(closure, CLOSURE, { value: { params: names } });
|
|
251
|
+
return ok(closure);
|
|
252
|
+
};
|
|
253
|
+
}),
|
|
254
|
+
|
|
255
|
+
"formula.call": compiled(({ value }) => {
|
|
256
|
+
const record = partsOf(value);
|
|
257
|
+
const fnPart = record["fn"] as CompiledPart;
|
|
258
|
+
const args = Array.isArray(record["args"]) ? (record["args"] as unknown as readonly CompiledPart[]) : [];
|
|
259
|
+
return (ctx, rt) => {
|
|
260
|
+
const fn = fnPart.code(ctx, rt);
|
|
261
|
+
if (!fn.ok) return fn;
|
|
262
|
+
if (typeof fn.value !== "function") return fail("#VALUE!", "The fn of call must give a function.");
|
|
263
|
+
const values = new Array<unknown>(args.length);
|
|
264
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
265
|
+
const result = (args[index] as CompiledPart).code(ctx, rt);
|
|
266
|
+
if (!result.ok) return result;
|
|
267
|
+
values[index] = result.value;
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
return ok((fn.value as (...items: unknown[]) => unknown)(...values));
|
|
271
|
+
} catch (error) {
|
|
272
|
+
return thrown(error, "the function of call");
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}),
|
|
276
|
+
|
|
277
|
+
"formula.if": compiled(({ value }) => {
|
|
278
|
+
const record = partsOf(value);
|
|
279
|
+
const test = record["test"] as CompiledPart;
|
|
280
|
+
const then = record["then"] as CompiledPart;
|
|
281
|
+
const other = record["else"];
|
|
282
|
+
return (ctx, rt) => {
|
|
283
|
+
const chosen = test.code(ctx, rt);
|
|
284
|
+
if (!chosen.ok) return chosen;
|
|
285
|
+
const truth = truthy(chosen.value);
|
|
286
|
+
if (truth === undefined) return notTruth("test of if");
|
|
287
|
+
if (truth) return then.code(ctx, rt);
|
|
288
|
+
return other === undefined ? otherwise : other.code(ctx, rt);
|
|
289
|
+
};
|
|
290
|
+
}),
|
|
291
|
+
|
|
292
|
+
"formula.and": compiled(({ parts }) => (ctx, rt) => {
|
|
293
|
+
let last: Result = ok(true);
|
|
294
|
+
for (const part of parts) {
|
|
295
|
+
last = part.code(ctx, rt);
|
|
296
|
+
if (!last.ok) return last;
|
|
297
|
+
const truth = truthy(last.value);
|
|
298
|
+
if (truth === undefined) return notTruth("term of and");
|
|
299
|
+
if (!truth) return logicValue ? last : ok(false);
|
|
300
|
+
}
|
|
301
|
+
return logicValue ? last : ok(true);
|
|
302
|
+
}),
|
|
303
|
+
|
|
304
|
+
"formula.or": compiled(({ parts }) => (ctx, rt) => {
|
|
305
|
+
let last: Result = ok(false);
|
|
306
|
+
for (const part of parts) {
|
|
307
|
+
last = part.code(ctx, rt);
|
|
308
|
+
if (!last.ok) return last;
|
|
309
|
+
const truth = truthy(last.value);
|
|
310
|
+
if (truth === undefined) return notTruth("term of or");
|
|
311
|
+
if (truth) return logicValue ? last : ok(true);
|
|
312
|
+
}
|
|
313
|
+
return logicValue ? last : ok(false);
|
|
314
|
+
}),
|
|
315
|
+
|
|
316
|
+
"formula.op": compiled(({ typePath, parts }) => {
|
|
317
|
+
const name = typePath.slice(1).join(".");
|
|
318
|
+
const lazy = profile.lazy !== undefined && Object.hasOwn(profile.lazy, name) ? profile.lazy[name] : undefined;
|
|
319
|
+
if (lazy !== undefined) {
|
|
320
|
+
return (ctx, rt) => {
|
|
321
|
+
try {
|
|
322
|
+
return lazy(parts.map((part) => () => part.code(ctx, rt)));
|
|
323
|
+
} catch (error) {
|
|
324
|
+
return thrown(error, `the operation ${name}`);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const fn = lookup(name) as ((...args: unknown[]) => unknown) | undefined;
|
|
329
|
+
if (fn === undefined) {
|
|
330
|
+
const unknown = fail("#NAME?", `The language has no operation ${name}.`);
|
|
331
|
+
return () => unknown;
|
|
332
|
+
}
|
|
333
|
+
const done = (out: unknown): Result => (check === undefined ? ok(out) : (check(out, name) ?? ok(out)));
|
|
334
|
+
const failed = (error: unknown): Result => thrown(error, `the operation ${name}`);
|
|
335
|
+
if (parts.length === 1) {
|
|
336
|
+
const [a] = parts as readonly [CompiledPart];
|
|
337
|
+
return (ctx, rt) => {
|
|
338
|
+
const x = a.code(ctx, rt);
|
|
339
|
+
if (!x.ok) return x;
|
|
340
|
+
try {
|
|
341
|
+
return done(fn(x.value));
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return failed(error);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (parts.length === 2) {
|
|
348
|
+
const [a, b] = parts as readonly [CompiledPart, CompiledPart];
|
|
349
|
+
return (ctx, rt) => {
|
|
350
|
+
const x = a.code(ctx, rt);
|
|
351
|
+
if (!x.ok) return x;
|
|
352
|
+
const y = b.code(ctx, rt);
|
|
353
|
+
if (!y.ok) return y;
|
|
354
|
+
try {
|
|
355
|
+
return done(fn(x.value, y.value));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return failed(error);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return (ctx, rt) => {
|
|
362
|
+
const values = new Array<unknown>(parts.length);
|
|
363
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
364
|
+
const result = (parts[index] as CompiledPart).code(ctx, rt);
|
|
365
|
+
if (!result.ok) return result;
|
|
366
|
+
values[index] = result.value;
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
return done(fn(...values));
|
|
370
|
+
} catch (error) {
|
|
371
|
+
return failed(error);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}),
|
|
375
|
+
|
|
376
|
+
"formula.ext": compiled(({ typePath, doc }) => {
|
|
377
|
+
const name = typePath.slice(1).join(".");
|
|
378
|
+
const own = profile.ext !== undefined && Object.hasOwn(profile.ext, name) ? profile.ext[name] : undefined;
|
|
379
|
+
const handler = own ?? (Object.hasOwn(STD_EXT, name) ? STD_EXT[name] : undefined);
|
|
380
|
+
if (handler === undefined) {
|
|
381
|
+
const unknown = fail("#NAME?", `The language has no extension ${name}.`);
|
|
382
|
+
return () => unknown;
|
|
383
|
+
}
|
|
384
|
+
return () => {
|
|
385
|
+
try {
|
|
386
|
+
return handler(doc.value);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
return thrown(error, `the extension ${name}`);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* This function makes a machine for a formula language. The machine has the kinds and the evaluators of the
|
|
397
|
+
* formula kit. It also has the kinds, the evaluators and the walkers that the language adds.
|
|
398
|
+
*
|
|
399
|
+
* @param profile - The operations and the rules of the language.
|
|
400
|
+
* @param more - The parts of a machine that the language adds. Its kinds win over the kinds of the kit.
|
|
401
|
+
* @returns The machine.
|
|
402
|
+
*/
|
|
403
|
+
export function formulaMachine(profile: FormulaProfile = {}, more: Partial<Machine> = {}): Machine {
|
|
404
|
+
return {
|
|
405
|
+
...more,
|
|
406
|
+
world: { kinds: { ...formulaKinds(), ...more.world?.kinds }, defaults: { ...more.world?.defaults } },
|
|
407
|
+
evaluators: { ...formulaEvaluators(profile), ...more.evaluators },
|
|
408
|
+
};
|
|
409
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type Envelope,
|
|
3
|
+
type Json,
|
|
4
|
+
type JsonRecord,
|
|
5
|
+
type Key,
|
|
6
|
+
type Stored,
|
|
7
|
+
type TypeRef,
|
|
8
|
+
ENVELOPE_KEYS,
|
|
9
|
+
docEquals,
|
|
10
|
+
field,
|
|
11
|
+
fromJson,
|
|
12
|
+
isEnvelope,
|
|
13
|
+
isKey,
|
|
14
|
+
isRecord,
|
|
15
|
+
isTypeRef,
|
|
16
|
+
ref,
|
|
17
|
+
stored,
|
|
18
|
+
toJson,
|
|
19
|
+
typeName,
|
|
20
|
+
typePath,
|
|
21
|
+
typed,
|
|
22
|
+
} from "./doc.ts";
|
|
23
|
+
export {
|
|
24
|
+
type ErrorCode,
|
|
25
|
+
type EvalError,
|
|
26
|
+
type Failed,
|
|
27
|
+
type KernelCode,
|
|
28
|
+
type Result,
|
|
29
|
+
Failure,
|
|
30
|
+
describe,
|
|
31
|
+
fail,
|
|
32
|
+
isStackOverflow,
|
|
33
|
+
ok,
|
|
34
|
+
placed,
|
|
35
|
+
thrown,
|
|
36
|
+
} from "./errors.ts";
|
|
37
|
+
export {
|
|
38
|
+
type Alias,
|
|
39
|
+
type Kind,
|
|
40
|
+
type Lookup,
|
|
41
|
+
type Namespace,
|
|
42
|
+
type NamesSource,
|
|
43
|
+
type PartsSpec,
|
|
44
|
+
type ScopeRule,
|
|
45
|
+
type Shape as PartShape,
|
|
46
|
+
type Side,
|
|
47
|
+
type World,
|
|
48
|
+
type WorldEntry,
|
|
49
|
+
entriesAsJson,
|
|
50
|
+
isAlias,
|
|
51
|
+
isKind,
|
|
52
|
+
isNamespace,
|
|
53
|
+
kindDocument,
|
|
54
|
+
looksLikeKind,
|
|
55
|
+
lookupKind,
|
|
56
|
+
} from "./world.ts";
|
|
57
|
+
export {
|
|
58
|
+
type Answer,
|
|
59
|
+
type AskOptions,
|
|
60
|
+
type Header,
|
|
61
|
+
type KindSource,
|
|
62
|
+
type Outcome,
|
|
63
|
+
type Rung,
|
|
64
|
+
type Step,
|
|
65
|
+
RUNGS,
|
|
66
|
+
ask,
|
|
67
|
+
kindNamed,
|
|
68
|
+
kindOf,
|
|
69
|
+
} from "./ladder.ts";
|
|
70
|
+
export {
|
|
71
|
+
type Cap,
|
|
72
|
+
type Context,
|
|
73
|
+
type DerefCode,
|
|
74
|
+
type Hop,
|
|
75
|
+
type HopBy,
|
|
76
|
+
type HostThing,
|
|
77
|
+
type Navigator,
|
|
78
|
+
type Resolution,
|
|
79
|
+
type ResolveCode,
|
|
80
|
+
type ResolveOptions,
|
|
81
|
+
type Root,
|
|
82
|
+
type Thing,
|
|
83
|
+
type WalkInput,
|
|
84
|
+
type WalkResult,
|
|
85
|
+
type Walker,
|
|
86
|
+
ROOTS,
|
|
87
|
+
THING,
|
|
88
|
+
context,
|
|
89
|
+
derefStep,
|
|
90
|
+
isHostThing,
|
|
91
|
+
isRef,
|
|
92
|
+
resolve,
|
|
93
|
+
valueOfThing,
|
|
94
|
+
worldThing,
|
|
95
|
+
} from "./deref.ts";
|
|
96
|
+
export { type Classification, type Shape, classify, tag } from "./classify.ts";
|
|
97
|
+
export { type PartSlot, type Split, pathMatches, splitParts } from "./parts.ts";
|
|
98
|
+
export {
|
|
99
|
+
type Code,
|
|
100
|
+
type CompileInput,
|
|
101
|
+
type CompiledEvaluator,
|
|
102
|
+
type CompiledPart,
|
|
103
|
+
type CompiledRunner,
|
|
104
|
+
type Effect,
|
|
105
|
+
type EvalFn,
|
|
106
|
+
type EvalInput,
|
|
107
|
+
type EvalOptions,
|
|
108
|
+
type Evaluator,
|
|
109
|
+
type Machine,
|
|
110
|
+
type Part,
|
|
111
|
+
type Perform,
|
|
112
|
+
type Plan,
|
|
113
|
+
type PrepareOptions,
|
|
114
|
+
type Run,
|
|
115
|
+
type RunCode,
|
|
116
|
+
type RunFn,
|
|
117
|
+
type RunInput,
|
|
118
|
+
type RunOptions,
|
|
119
|
+
type RunPart,
|
|
120
|
+
type Runner,
|
|
121
|
+
type Runtime,
|
|
122
|
+
type Steps,
|
|
123
|
+
type StopSignal,
|
|
124
|
+
type TraceNode,
|
|
125
|
+
capKeys,
|
|
126
|
+
evaluate,
|
|
127
|
+
failures,
|
|
128
|
+
flatten,
|
|
129
|
+
makeContext,
|
|
130
|
+
narrow,
|
|
131
|
+
nodeAt,
|
|
132
|
+
permits,
|
|
133
|
+
planner,
|
|
134
|
+
prepare,
|
|
135
|
+
run,
|
|
136
|
+
} from "./evaluate.ts";
|
|
137
|
+
export { type NearestOptions, STD_CAPS, STD_EVALUATORS, STD_MACHINE, STD_RUNNERS, STD_WALKERS, STD_WORLD, nearestWalker } from "./stdlib.ts";
|
|
138
|
+
export {
|
|
139
|
+
type Edge,
|
|
140
|
+
type OrderProblem,
|
|
141
|
+
type StepSpec,
|
|
142
|
+
checkSteps,
|
|
143
|
+
dataEdges,
|
|
144
|
+
effectEdges,
|
|
145
|
+
isAllowedOrder,
|
|
146
|
+
layers,
|
|
147
|
+
} from "./order.ts";
|
|
148
|
+
export {
|
|
149
|
+
type Construct,
|
|
150
|
+
type Give,
|
|
151
|
+
type Part as ContextPart,
|
|
152
|
+
type PartInfo,
|
|
153
|
+
type Use,
|
|
154
|
+
CONSTRUCTS,
|
|
155
|
+
PARTS,
|
|
156
|
+
PART_INFO,
|
|
157
|
+
} from "./context.ts";
|
|
158
|
+
export {
|
|
159
|
+
type CheckOptions,
|
|
160
|
+
type DocVisit,
|
|
161
|
+
type Issue,
|
|
162
|
+
type RefUse,
|
|
163
|
+
type WalkOptions,
|
|
164
|
+
type WalkProblem,
|
|
165
|
+
check,
|
|
166
|
+
mapRefs,
|
|
167
|
+
refsOf,
|
|
168
|
+
walk,
|
|
169
|
+
} from "./static.ts";
|
|
170
|
+
export {
|
|
171
|
+
type Closure,
|
|
172
|
+
type ExtFn,
|
|
173
|
+
type FormulaProfile,
|
|
174
|
+
type LazyOp,
|
|
175
|
+
type OpFn,
|
|
176
|
+
Held,
|
|
177
|
+
STD_EXT,
|
|
178
|
+
formulaEvaluators,
|
|
179
|
+
formulaKinds,
|
|
180
|
+
formulaMachine,
|
|
181
|
+
isClosure,
|
|
182
|
+
jsTruthy,
|
|
183
|
+
} from "./formula.ts";
|