@dallaylaen/ski-interpreter 2.8.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 +12 -0
- package/bin/ski.js +17 -0
- package/lib/ski-interpreter.cjs.js +78 -61
- package/lib/ski-interpreter.cjs.js.map +2 -2
- package/lib/ski-interpreter.min.js +5 -5
- package/lib/ski-interpreter.min.js.map +3 -3
- package/lib/ski-interpreter.mjs +78 -61
- package/lib/ski-interpreter.mjs.map +2 -2
- package/lib/ski-quest.min.js +4 -4
- package/lib/ski-quest.min.js.map +3 -3
- package/lib/types/extras.d.ts +37 -13
- package/lib/types/index.d.ts +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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
|
+
|
|
8
20
|
## [2.8.0] - 2026-04-23
|
|
9
21
|
|
|
10
22
|
### 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...>')
|
|
@@ -357,11 +357,9 @@ var Expr = class _Expr {
|
|
|
357
357
|
let steps = 0;
|
|
358
358
|
let expr = this;
|
|
359
359
|
main: for (let i = 0; i < options.maxArgs; i++) {
|
|
360
|
-
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 });
|
|
361
361
|
steps += next.steps;
|
|
362
|
-
if (
|
|
363
|
-
break;
|
|
364
|
-
if (firstVar(next.expr)) {
|
|
362
|
+
if (next.final && firstVar(next.expr)) {
|
|
365
363
|
expr = next.expr;
|
|
366
364
|
if (!expr.any((e) => !(e instanceof FreeVar || e instanceof App)))
|
|
367
365
|
return maybeLambda(probe, expr, { steps });
|
|
@@ -2087,6 +2085,77 @@ function canonize(term, options = {}) {
|
|
|
2087
2085
|
}
|
|
2088
2086
|
|
|
2089
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
|
+
}
|
|
2090
2159
|
function search(seed, options, predicate) {
|
|
2091
2160
|
const {
|
|
2092
2161
|
depth = 16,
|
|
@@ -2150,65 +2219,13 @@ function search(seed, options, predicate) {
|
|
|
2150
2219
|
}
|
|
2151
2220
|
return { total, probed, gen: depth, ...options.retain ? { cache } : {} };
|
|
2152
2221
|
}
|
|
2153
|
-
function
|
|
2154
|
-
|
|
2155
|
-
return obj.format(options);
|
|
2156
|
-
if (obj instanceof Quest)
|
|
2157
|
-
return "Quest(" + obj.name + ")";
|
|
2158
|
-
if (obj instanceof Quest.Case)
|
|
2159
|
-
return "Quest.Case";
|
|
2160
|
-
if (Array.isArray(obj))
|
|
2161
|
-
return obj.map((item) => deepFormat(item, options));
|
|
2162
|
-
if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
|
|
2163
|
-
return obj;
|
|
2164
|
-
const out = {};
|
|
2165
|
-
for (const key in obj)
|
|
2166
|
-
out[key] = deepFormat(obj[key], options);
|
|
2167
|
-
return out;
|
|
2168
|
-
}
|
|
2169
|
-
function declare(expr, env) {
|
|
2170
|
-
return expr.declare({ inventory: env });
|
|
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";
|
|
2171
2224
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
var schema = {
|
|
2175
|
-
html: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2176
|
-
terse: (x) => typeof x === "boolean" ? void 0 : "must be a boolean",
|
|
2177
|
-
space: (x) => typeof x === "string" ? void 0 : "must be a string",
|
|
2178
|
-
brackets: isStringPair,
|
|
2179
|
-
var: isStringPair,
|
|
2180
|
-
around: isStringPair,
|
|
2181
|
-
redex: isStringPair,
|
|
2182
|
-
lambda: isStringTriple,
|
|
2183
|
-
inventory: (x) => {
|
|
2184
|
-
if (typeof x !== "object" || x === null || x.constructor !== Object)
|
|
2185
|
-
return "must be an object, not " + (x?.constructor?.name ?? typeof x);
|
|
2186
|
-
const refined = x;
|
|
2187
|
-
for (const key of Object.keys(refined)) {
|
|
2188
|
-
if (!(refined[key] instanceof Expr))
|
|
2189
|
-
return "key " + key + "is not an Expr";
|
|
2190
|
-
}
|
|
2191
|
-
return void 0;
|
|
2192
|
-
}
|
|
2193
|
-
};
|
|
2194
|
-
function checkFormatOptions(raw) {
|
|
2195
|
-
if (raw === null || raw === void 0)
|
|
2196
|
-
return { value: {} };
|
|
2197
|
-
if (typeof raw !== "object" || Array.isArray(raw) || raw.constructor !== Object)
|
|
2198
|
-
return { error: { object: "Format options must be an object, not " + (raw?.constructor?.name ?? typeof raw) } };
|
|
2199
|
-
const rec = raw;
|
|
2200
|
-
const error = {};
|
|
2201
|
-
for (const key in rec) {
|
|
2202
|
-
if (schema[key]) {
|
|
2203
|
-
const err = schema[key](rec[key]);
|
|
2204
|
-
if (err)
|
|
2205
|
-
error[key] = err;
|
|
2206
|
-
} else
|
|
2207
|
-
error[key] = "unknown option";
|
|
2208
|
-
}
|
|
2209
|
-
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";
|
|
2210
2227
|
}
|
|
2211
|
-
var extras = { search, deepFormat, declare, toposort, checkFormatOptions };
|
|
2228
|
+
var extras = { search, deepFormat, declare, toposort, checkFormatOptions, equiv };
|
|
2212
2229
|
|
|
2213
2230
|
// src/index.ts
|
|
2214
2231
|
extras.toposort = toposort;
|