@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 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 (!next.final)
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 deepFormat(obj, options = {}) {
2154
- if (obj instanceof Expr)
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
- 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";
2173
- 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";
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;