@dallaylaen/ski-interpreter 2.7.0 → 2.8.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/CHANGELOG.md +39 -0
- package/bin/ski.js +24 -10
- package/lib/ski-interpreter.cjs.js +150 -119
- package/lib/ski-interpreter.cjs.js.map +3 -3
- package/lib/ski-interpreter.min.js +5 -5
- package/lib/ski-interpreter.min.js.map +4 -4
- package/lib/ski-interpreter.mjs +150 -119
- package/lib/ski-interpreter.mjs.map +3 -3
- package/lib/ski-quest.min.js +5 -5
- package/lib/ski-quest.min.js.map +4 -4
- package/lib/types/expr.d.ts +33 -6
- package/lib/types/extras.d.ts +41 -23
- package/lib/types/index.d.ts +3 -5
- package/lib/types/parser.d.ts +11 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.8.1] - 2026-04-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `SKI.extras.equiv(expr1, expr2, options: InferOptions?)` to check if two expressions are computationally equivalent.
|
|
13
|
+
- `./bin/ski.js compare <expr1> <expr2>` command to compare two expressions for equivalence.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `Expr.infer()` keeps adding variables to nonterminating expressions
|
|
18
|
+
to catch e.g. `CK(WWW)` (never terminates but eqivalent to I).
|
|
19
|
+
|
|
20
|
+
## [2.8.0] - 2026-04-23
|
|
21
|
+
|
|
22
|
+
### BREAKING CHANGES
|
|
23
|
+
|
|
24
|
+
- `parse()` no longer auto-inlines aliases unless they are redefined; known aliases are preserved by name.
|
|
25
|
+
- `toposort()` signature changed to use options object instead of positional parameters.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- `Expr.declare()` — emits a complete, round-trippable declaration of an expression including its dependencies.
|
|
30
|
+
- `parse(..., { canonize: true })` — calculates properties of intermediate aliases during parsing.
|
|
31
|
+
- `annotate()` — public rename of the semi-official `_setup()` method, with added documentation.
|
|
32
|
+
- Quest UI: "reveal solution" button added.
|
|
33
|
+
- Quest UI: raw user input is now saved when a quest is solved.
|
|
34
|
+
- CLI (`bin/ski.js`): outputs full declarations instead of bare expressions.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- `toJSON()` now outputs back-parsable declaration instead of just string.
|
|
39
|
+
- `toposort` moved into `expr.ts`.
|
|
40
|
+
- `extras.ts` delegates to `Expr.declare()` instead of re-implementing the logic.
|
|
41
|
+
- Internal type `{ ... }` renamed to `Record<string, Named>` for clarity.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- `Expr.toLambda` was incorrectly preparing term (#24).
|
|
46
|
+
|
|
8
47
|
## [2.7.0] - 2026-04-03
|
|
9
48
|
|
|
10
49
|
### BREAKING CHANGES
|
package/bin/ski.js
CHANGED
|
@@ -71,6 +71,23 @@ program
|
|
|
71
71
|
inferExpression(expression);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
program
|
|
75
|
+
.command('compare <expr1> <expr2>')
|
|
76
|
+
.description('Check if two expressions are equivalent')
|
|
77
|
+
.action((expr1, expr2) => {
|
|
78
|
+
const ski = new SKI();
|
|
79
|
+
const e1 = ski.parse(expr1);
|
|
80
|
+
const e2 = ski.parse(expr2);
|
|
81
|
+
const res = SKI.extras.equiv(e1, e2, runOptions);
|
|
82
|
+
if (res.equal)
|
|
83
|
+
console.log('Both expressions are equivalent to ' + res.canonical[0].format(format));
|
|
84
|
+
else
|
|
85
|
+
console.log(`Expressions differ:\n${res.canonical[0].format(format)}\n vs \n${res.canonical[1].format(format)}`);
|
|
86
|
+
|
|
87
|
+
console.log(`// ${res.steps} step(s)`);
|
|
88
|
+
process.exit(res.equal ? 0 : 1);
|
|
89
|
+
});
|
|
90
|
+
|
|
74
91
|
// Extract subcommand
|
|
75
92
|
program
|
|
76
93
|
.command('extract <target> <terms...>')
|
|
@@ -166,20 +183,17 @@ function processLine (source, ski, onErr) {
|
|
|
166
183
|
return; // nothing to see here
|
|
167
184
|
|
|
168
185
|
try {
|
|
169
|
-
const expr = ski.parse(source);
|
|
186
|
+
const expr = ski.parse(source, { canonize: true });
|
|
187
|
+
if (expr instanceof SKI.classes.Alias)
|
|
188
|
+
ski.add(expr);
|
|
170
189
|
const t0 = new Date();
|
|
171
|
-
const isAlias = expr instanceof SKI.classes.Alias;
|
|
172
|
-
const aliasName = isAlias ? expr.name : null;
|
|
173
190
|
|
|
174
191
|
for (const state of expr.walk(runOptions)) {
|
|
175
|
-
if (state.final)
|
|
192
|
+
if (state.final) {
|
|
176
193
|
console.log(`// ${state.steps} step(s) in ${new Date() - t0}ms`);
|
|
177
|
-
|
|
178
|
-
if (verbose
|
|
179
|
-
console.log(state.expr.format(format));
|
|
180
|
-
|
|
181
|
-
if (state.final && isAlias && aliasName)
|
|
182
|
-
ski.add(aliasName, state.expr);
|
|
194
|
+
console.log(state.expr.declare({ ...format, inventory: ski.getTerms() }));
|
|
195
|
+
} else if (verbose)
|
|
196
|
+
console.log(state.expr.format(format) + ';');
|
|
183
197
|
}
|
|
184
198
|
} catch (err) {
|
|
185
199
|
onErr(err);
|
|
@@ -111,10 +111,13 @@ var control = {
|
|
|
111
111
|
var native = {};
|
|
112
112
|
var Expr = class _Expr {
|
|
113
113
|
/**
|
|
114
|
+
* Add metadata based on user-supplied values and/or the properties of the term itself.
|
|
114
115
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
116
|
+
* Typically applied to named terms shortly after instantiation.
|
|
117
|
+
*
|
|
118
|
+
* Experimental. Name and meaning may change in the future.
|
|
119
|
+
*
|
|
120
|
+
* @experimental
|
|
118
121
|
* @param {Object} options
|
|
119
122
|
* @param {string} [options.note] - a brief description what the term does
|
|
120
123
|
* @param {number} [options.arity] - number of arguments the term is waiting for (if known)
|
|
@@ -124,7 +127,7 @@ var Expr = class _Expr {
|
|
|
124
127
|
* @param {number} [options.maxArgs] - maximum number of arguments for inference, if canonize is true
|
|
125
128
|
* @return {this}
|
|
126
129
|
*/
|
|
127
|
-
|
|
130
|
+
annotate(options = {}) {
|
|
128
131
|
if (options.fancy !== void 0 && this instanceof Named)
|
|
129
132
|
this.fancyName = options.fancy;
|
|
130
133
|
if (options.note !== void 0)
|
|
@@ -354,11 +357,9 @@ var Expr = class _Expr {
|
|
|
354
357
|
let steps = 0;
|
|
355
358
|
let expr = this;
|
|
356
359
|
main: for (let i = 0; i < options.maxArgs; i++) {
|
|
357
|
-
const next = expr.run({ max: options.max - steps, maxSize: options.maxSize });
|
|
360
|
+
const next = expr.run({ max: Math.max((options.max - steps) / 2, 10), maxSize: options.maxSize });
|
|
358
361
|
steps += next.steps;
|
|
359
|
-
if (
|
|
360
|
-
break;
|
|
361
|
-
if (firstVar(next.expr)) {
|
|
362
|
+
if (next.final && firstVar(next.expr)) {
|
|
362
363
|
expr = next.expr;
|
|
363
364
|
if (!expr.any((e) => !(e instanceof FreeVar || e instanceof App)))
|
|
364
365
|
return maybeLambda(probe, expr, { steps });
|
|
@@ -428,7 +429,9 @@ var Expr = class _Expr {
|
|
|
428
429
|
*/
|
|
429
430
|
*toLambda(options = {}) {
|
|
430
431
|
let expr = this.traverse((e) => {
|
|
431
|
-
if (e instanceof FreeVar
|
|
432
|
+
if (e instanceof FreeVar)
|
|
433
|
+
return e;
|
|
434
|
+
if (e instanceof App || e instanceof Lambda || e instanceof Alias)
|
|
432
435
|
return null;
|
|
433
436
|
const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
|
|
434
437
|
if (!guess.normal)
|
|
@@ -794,16 +797,26 @@ var Expr = class _Expr {
|
|
|
794
797
|
diag(indent = "") {
|
|
795
798
|
return indent + this.constructor.name + ": " + this;
|
|
796
799
|
}
|
|
800
|
+
declare(options = {}) {
|
|
801
|
+
const { declaration: d = ["", "=", "; "], ...format } = options;
|
|
802
|
+
const res = toposort({ list: [this], env: format.inventory });
|
|
803
|
+
return res.list.map((s) => {
|
|
804
|
+
if (s instanceof Alias)
|
|
805
|
+
return d[0] + s.name + d[1] + s.impl.format({ ...format, inventory: res.env });
|
|
806
|
+
if (s instanceof FreeVar)
|
|
807
|
+
return d[0] + s.name + d[1];
|
|
808
|
+
return s.format({ ...format, inventory: res.env });
|
|
809
|
+
}).join(d[2]);
|
|
810
|
+
}
|
|
797
811
|
/**
|
|
798
812
|
* Convert the expression to a JSON-serializable format.
|
|
799
813
|
* Sadly the format is not yet finalized and may change in the future.
|
|
800
814
|
*
|
|
801
815
|
* @experimental
|
|
802
|
-
* @returns {string}
|
|
803
816
|
* @sealed
|
|
804
817
|
*/
|
|
805
818
|
toJSON() {
|
|
806
|
-
return this.
|
|
819
|
+
return this.declare();
|
|
807
820
|
}
|
|
808
821
|
};
|
|
809
822
|
var App = class _App extends Expr {
|
|
@@ -970,7 +983,7 @@ var Native = class extends Named {
|
|
|
970
983
|
constructor(name, impl, opt = {}) {
|
|
971
984
|
super(name);
|
|
972
985
|
this.invoke = impl;
|
|
973
|
-
this.
|
|
986
|
+
this.annotate({ canonize: true, ...opt });
|
|
974
987
|
}
|
|
975
988
|
};
|
|
976
989
|
var Lambda = class _Lambda extends Expr {
|
|
@@ -1077,7 +1090,7 @@ var Alias = class extends Named {
|
|
|
1077
1090
|
if (!(impl instanceof Expr))
|
|
1078
1091
|
throw new Error("Attempt to create an alias for a non-expression: " + impl);
|
|
1079
1092
|
this.impl = impl;
|
|
1080
|
-
this.
|
|
1093
|
+
this.annotate(options);
|
|
1081
1094
|
this.invoke = waitn(options.inline ? 0 : this.arity ?? 0)(impl);
|
|
1082
1095
|
this.size = impl.size;
|
|
1083
1096
|
if (options.inline)
|
|
@@ -1215,40 +1228,43 @@ function maybeLambda(args, expr, caps) {
|
|
|
1215
1228
|
function nthvar(n) {
|
|
1216
1229
|
return new FreeVar("abcdefgh"[n] ?? "x" + n);
|
|
1217
1230
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
if (
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if (!list)
|
|
1229
|
-
return { list: [], env: {} };
|
|
1230
|
-
env = {};
|
|
1231
|
-
for (const item of list) {
|
|
1232
|
-
if (!(item instanceof Named))
|
|
1233
|
-
continue;
|
|
1234
|
-
if (env[item.name])
|
|
1235
|
-
throw new Error("duplicate name " + item);
|
|
1236
|
-
env[item.name] = item;
|
|
1231
|
+
function toposort(options) {
|
|
1232
|
+
if (typeof options !== "object" || options === null || Array.isArray(options) || options instanceof Expr)
|
|
1233
|
+
throw new Error("positional arguments to toposort are deprecated, use { list: ..., env: ... } instead");
|
|
1234
|
+
const allow = options.allow ? { ...options.allow } : null;
|
|
1235
|
+
const env = { ...options.env ?? {} };
|
|
1236
|
+
const list = options.list instanceof Expr ? [options.list] : options.list ?? [];
|
|
1237
|
+
if (allow) {
|
|
1238
|
+
for (const term of list) {
|
|
1239
|
+
if (term instanceof Named)
|
|
1240
|
+
allow[term.name] = term;
|
|
1237
1241
|
}
|
|
1238
1242
|
}
|
|
1243
|
+
for (const term of list) {
|
|
1244
|
+
if (term instanceof Named && env[term.name] === term)
|
|
1245
|
+
delete env[term.name];
|
|
1246
|
+
}
|
|
1239
1247
|
const out = [];
|
|
1240
|
-
const seen =
|
|
1248
|
+
const seen = new Set(Object.values(env));
|
|
1241
1249
|
const rec = (term) => {
|
|
1242
1250
|
if (seen.has(term))
|
|
1243
1251
|
return;
|
|
1244
|
-
term.fold(
|
|
1245
|
-
if (e
|
|
1252
|
+
term.fold(void 0, (_, e) => {
|
|
1253
|
+
if (!(e instanceof Named))
|
|
1254
|
+
return;
|
|
1255
|
+
if (allow && allow[e.name] !== e)
|
|
1256
|
+
return;
|
|
1257
|
+
if (!allow && e instanceof Alias && e.inline)
|
|
1258
|
+
return;
|
|
1259
|
+
if (e !== term) {
|
|
1246
1260
|
rec(e);
|
|
1247
|
-
return control.prune(
|
|
1261
|
+
return control.prune();
|
|
1248
1262
|
}
|
|
1249
1263
|
});
|
|
1250
1264
|
out.push(term);
|
|
1251
1265
|
seen.add(term);
|
|
1266
|
+
if (term instanceof Named)
|
|
1267
|
+
env[term.name] ||= term;
|
|
1252
1268
|
};
|
|
1253
1269
|
for (const term of list)
|
|
1254
1270
|
rec(term);
|
|
@@ -1257,6 +1273,7 @@ function toposort(list, env) {
|
|
|
1257
1273
|
env
|
|
1258
1274
|
};
|
|
1259
1275
|
}
|
|
1276
|
+
var classes = { Expr, App, Named, FreeVar, Native, Lambda, Church, Alias };
|
|
1260
1277
|
|
|
1261
1278
|
// src/parser.ts
|
|
1262
1279
|
var Empty = class extends Expr {
|
|
@@ -1359,7 +1376,7 @@ var Parser = class {
|
|
|
1359
1376
|
add(term, impl, options) {
|
|
1360
1377
|
const named = this._named(term, impl);
|
|
1361
1378
|
const opts = typeof options === "string" ? { note: options, canonize: false } : options ?? {};
|
|
1362
|
-
named.
|
|
1379
|
+
named.annotate({ canonize: this.annotate, ...opts });
|
|
1363
1380
|
const old = this.known[named.name];
|
|
1364
1381
|
if (old instanceof Alias)
|
|
1365
1382
|
old.makeInline();
|
|
@@ -1504,7 +1521,7 @@ var Parser = class {
|
|
|
1504
1521
|
env[temp.name] = temp;
|
|
1505
1522
|
delete env[name];
|
|
1506
1523
|
}
|
|
1507
|
-
const list = toposort(
|
|
1524
|
+
const list = toposort({ list: Object.values(env), allow: {} }).list;
|
|
1508
1525
|
const detour = /* @__PURE__ */ new Map();
|
|
1509
1526
|
if (Object.keys(needDetour).length) {
|
|
1510
1527
|
const rework = (expr) => {
|
|
@@ -1531,14 +1548,15 @@ var Parser = class {
|
|
|
1531
1548
|
return out;
|
|
1532
1549
|
}
|
|
1533
1550
|
/**
|
|
1534
|
-
* @template T
|
|
1535
1551
|
* @param {string} source
|
|
1536
1552
|
* @param {Object} [options]
|
|
1537
|
-
* @param
|
|
1538
|
-
* @param
|
|
1539
|
-
* @param {boolean} [options.numbers]
|
|
1540
|
-
* @param {boolean} [options.lambdas]
|
|
1541
|
-
* @param {string} [options.allow]
|
|
1553
|
+
* @param [options.env] - additional
|
|
1554
|
+
* @param [options.scope] - assign this scope to unknown free variables
|
|
1555
|
+
* @param {boolean} [options.numbers] - whether numbers are allowed
|
|
1556
|
+
* @param {boolean} [options.lambdas] - whether lambdas are allowed
|
|
1557
|
+
* @param {string} [options.allow] - restrict known terms
|
|
1558
|
+
* @param [options.canonize] - whether to calculate canonical form, arity, and properties
|
|
1559
|
+
* of intermediate aliases
|
|
1542
1560
|
* @return {Expr}
|
|
1543
1561
|
*/
|
|
1544
1562
|
parse(source, options = {}) {
|
|
@@ -1548,18 +1566,19 @@ var Parser = class {
|
|
|
1548
1566
|
const jar = { ...options.env };
|
|
1549
1567
|
let expr = new Empty();
|
|
1550
1568
|
for (const item of lines) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1569
|
+
const [_, name, def] = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s) || [];
|
|
1570
|
+
if (name !== void 0) {
|
|
1571
|
+
if (jar[name] instanceof Alias && jar[name] !== options.env?.[name]) {
|
|
1572
|
+
jar[name].makeInline();
|
|
1573
|
+
}
|
|
1574
|
+
delete jar[name];
|
|
1575
|
+
}
|
|
1576
|
+
if (def === "")
|
|
1577
|
+
expr = new FreeVar(name, options.scope ?? FreeVar.global);
|
|
1556
1578
|
else
|
|
1557
1579
|
expr = this.parseLine(item, jar, options);
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1560
|
-
throw new Error("Attempt to redefine a known term: " + def[1]);
|
|
1561
|
-
jar[def[1]] = expr;
|
|
1562
|
-
}
|
|
1580
|
+
if (name)
|
|
1581
|
+
jar[name] = expr;
|
|
1563
1582
|
}
|
|
1564
1583
|
if (this.addContext) {
|
|
1565
1584
|
if (expr instanceof Named)
|
|
@@ -1591,7 +1610,7 @@ var Parser = class {
|
|
|
1591
1610
|
parseLine(source, env = {}, options = {}) {
|
|
1592
1611
|
const aliased = source.match(/^\s*([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
|
|
1593
1612
|
if (aliased)
|
|
1594
|
-
return new Alias(aliased[1], this.parseLine(aliased[2], env, options));
|
|
1613
|
+
return new Alias(aliased[1], this.parseLine(aliased[2], env, options), { canonize: options.canonize });
|
|
1595
1614
|
const opt = {
|
|
1596
1615
|
numbers: options.numbers ?? this.hasNumbers,
|
|
1597
1616
|
lambdas: options.lambdas ?? this.hasLambdas,
|
|
@@ -2066,6 +2085,77 @@ function canonize(term, options = {}) {
|
|
|
2066
2085
|
}
|
|
2067
2086
|
|
|
2068
2087
|
// src/extras.ts
|
|
2088
|
+
var formatSchema = {
|
|
2089
|
+
html: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2090
|
+
terse: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2091
|
+
space: (x) => typeof x === "string" ? void 0 : "must be a string",
|
|
2092
|
+
brackets: isStringPair,
|
|
2093
|
+
var: isStringPair,
|
|
2094
|
+
around: isStringPair,
|
|
2095
|
+
redex: isStringPair,
|
|
2096
|
+
lambda: isStringTriple,
|
|
2097
|
+
inventory: (x) => {
|
|
2098
|
+
if (typeof x !== "object" || x === null || x.constructor !== Object)
|
|
2099
|
+
return "must be an object, not " + (x?.constructor?.name ?? typeof x);
|
|
2100
|
+
const refined = x;
|
|
2101
|
+
for (const key of Object.keys(refined)) {
|
|
2102
|
+
if (!(refined[key] instanceof Expr))
|
|
2103
|
+
return "key " + key + "is not an Expr";
|
|
2104
|
+
}
|
|
2105
|
+
return void 0;
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
function checkFormatOptions(raw) {
|
|
2109
|
+
if (raw === null || raw === void 0)
|
|
2110
|
+
return { value: {} };
|
|
2111
|
+
if (typeof raw !== "object" || Array.isArray(raw) || raw.constructor !== Object)
|
|
2112
|
+
return { error: { object: "Format options must be an object, not " + (raw?.constructor?.name ?? typeof raw) } };
|
|
2113
|
+
const rec = raw;
|
|
2114
|
+
const error = {};
|
|
2115
|
+
for (const key in rec) {
|
|
2116
|
+
if (formatSchema[key]) {
|
|
2117
|
+
const err = formatSchema[key](rec[key]);
|
|
2118
|
+
if (err)
|
|
2119
|
+
error[key] = err;
|
|
2120
|
+
} else
|
|
2121
|
+
error[key] = "unknown option";
|
|
2122
|
+
}
|
|
2123
|
+
return Object.keys(error).length > 0 ? { error } : { value: rec };
|
|
2124
|
+
}
|
|
2125
|
+
function equiv(e1, e2, options = {}) {
|
|
2126
|
+
let steps = 0;
|
|
2127
|
+
const [n1, n2] = [e1, e2].map((x) => x.traverse((e) => {
|
|
2128
|
+
const props = e.infer(options);
|
|
2129
|
+
steps += props.steps ?? 0;
|
|
2130
|
+
return props.expr;
|
|
2131
|
+
}));
|
|
2132
|
+
const normal = !!(n1 && n2);
|
|
2133
|
+
return {
|
|
2134
|
+
steps,
|
|
2135
|
+
normal,
|
|
2136
|
+
equal: normal ? n1.equals(n2) : false,
|
|
2137
|
+
canonical: [n1, n2]
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
function declare(expr, env) {
|
|
2141
|
+
return expr.declare({ inventory: env });
|
|
2142
|
+
}
|
|
2143
|
+
function deepFormat(obj, options = {}) {
|
|
2144
|
+
if (obj instanceof Expr)
|
|
2145
|
+
return obj.format(options);
|
|
2146
|
+
if (obj instanceof Quest)
|
|
2147
|
+
return "Quest(" + obj.name + ")";
|
|
2148
|
+
if (obj instanceof Quest.Case)
|
|
2149
|
+
return "Quest.Case";
|
|
2150
|
+
if (Array.isArray(obj))
|
|
2151
|
+
return obj.map((item) => deepFormat(item, options));
|
|
2152
|
+
if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
|
|
2153
|
+
return obj;
|
|
2154
|
+
const out = {};
|
|
2155
|
+
for (const key in obj)
|
|
2156
|
+
out[key] = deepFormat(obj[key], options);
|
|
2157
|
+
return out;
|
|
2158
|
+
}
|
|
2069
2159
|
function search(seed, options, predicate) {
|
|
2070
2160
|
const {
|
|
2071
2161
|
depth = 16,
|
|
@@ -2129,72 +2219,13 @@ function search(seed, options, predicate) {
|
|
|
2129
2219
|
}
|
|
2130
2220
|
return { total, probed, gen: depth, ...options.retain ? { cache } : {} };
|
|
2131
2221
|
}
|
|
2132
|
-
function
|
|
2133
|
-
|
|
2134
|
-
return obj.format(options);
|
|
2135
|
-
if (obj instanceof Quest)
|
|
2136
|
-
return "Quest(" + obj.name + ")";
|
|
2137
|
-
if (obj instanceof Quest.Case)
|
|
2138
|
-
return "Quest.Case";
|
|
2139
|
-
if (Array.isArray(obj))
|
|
2140
|
-
return obj.map((item) => deepFormat(item, options));
|
|
2141
|
-
if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
|
|
2142
|
-
return obj;
|
|
2143
|
-
const out = {};
|
|
2144
|
-
for (const key in obj)
|
|
2145
|
-
out[key] = deepFormat(obj[key], options);
|
|
2146
|
-
return out;
|
|
2222
|
+
function isStringPair(x) {
|
|
2223
|
+
return Array.isArray(x) && x.length === 2 && typeof x[0] === "string" && typeof x[1] === "string" ? void 0 : "must be a pair of strings";
|
|
2147
2224
|
}
|
|
2148
|
-
function
|
|
2149
|
-
|
|
2150
|
-
return res.list.map((s) => {
|
|
2151
|
-
if (s instanceof Alias)
|
|
2152
|
-
return s.name + "=" + s.impl.format({ inventory: res.env });
|
|
2153
|
-
if (s instanceof FreeVar)
|
|
2154
|
-
return s.name + "=";
|
|
2155
|
-
return s.format({ inventory: res.env });
|
|
2156
|
-
}).join("; ");
|
|
2157
|
-
}
|
|
2158
|
-
var isStringPair = (x) => Array.isArray(x) && x.length === 2 && typeof x[0] === "string" && typeof x[1] === "string" ? void 0 : "must be a pair of strings";
|
|
2159
|
-
var isStringTriple = (x) => Array.isArray(x) && x.length === 3 && typeof x[0] === "string" && typeof x[1] === "string" && typeof x[2] === "string" ? void 0 : "must be a triplet of strings";
|
|
2160
|
-
var schema = {
|
|
2161
|
-
html: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2162
|
-
terse: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2163
|
-
space: (x) => typeof x === "string" ? void 0 : "must be a string",
|
|
2164
|
-
brackets: isStringPair,
|
|
2165
|
-
var: isStringPair,
|
|
2166
|
-
around: isStringPair,
|
|
2167
|
-
redex: isStringPair,
|
|
2168
|
-
lambda: isStringTriple,
|
|
2169
|
-
inventory: (x) => {
|
|
2170
|
-
if (typeof x !== "object" || x === null || x.constructor !== Object)
|
|
2171
|
-
return "must be an object, not " + (x?.constructor?.name ?? typeof x);
|
|
2172
|
-
const refined = x;
|
|
2173
|
-
for (const key of Object.keys(refined)) {
|
|
2174
|
-
if (!(refined[key] instanceof Expr))
|
|
2175
|
-
return "key " + key + "is not an Expr";
|
|
2176
|
-
}
|
|
2177
|
-
return void 0;
|
|
2178
|
-
}
|
|
2179
|
-
};
|
|
2180
|
-
function checkFormatOptions(raw) {
|
|
2181
|
-
if (raw === null || raw === void 0)
|
|
2182
|
-
return { value: {} };
|
|
2183
|
-
if (typeof raw !== "object" || Array.isArray(raw) || raw.constructor !== Object)
|
|
2184
|
-
return { error: { object: "Format options must be an object, not " + (raw?.constructor?.name ?? typeof raw) } };
|
|
2185
|
-
const rec = raw;
|
|
2186
|
-
const error = {};
|
|
2187
|
-
for (const key in rec) {
|
|
2188
|
-
if (schema[key]) {
|
|
2189
|
-
const err = schema[key](rec[key]);
|
|
2190
|
-
if (err)
|
|
2191
|
-
error[key] = err;
|
|
2192
|
-
} else
|
|
2193
|
-
error[key] = "unknown option";
|
|
2194
|
-
}
|
|
2195
|
-
return Object.keys(error).length > 0 ? { error } : { value: rec };
|
|
2225
|
+
function isStringTriple(x) {
|
|
2226
|
+
return Array.isArray(x) && x.length === 3 && typeof x[0] === "string" && typeof x[1] === "string" && typeof x[2] === "string" ? void 0 : "must be a triplet of strings";
|
|
2196
2227
|
}
|
|
2197
|
-
var extras = { search, deepFormat, declare, toposort, checkFormatOptions };
|
|
2228
|
+
var extras = { search, deepFormat, declare, toposort, checkFormatOptions, equiv };
|
|
2198
2229
|
|
|
2199
2230
|
// src/index.ts
|
|
2200
2231
|
extras.toposort = toposort;
|