@dallaylaen/ski-interpreter 2.3.3 → 2.4.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 +28 -0
- package/README.md +12 -3
- package/bin/ski.js +58 -12
- package/lib/ski-interpreter.cjs.js +2092 -2211
- package/lib/ski-interpreter.cjs.js.map +4 -4
- package/lib/ski-interpreter.esm.js +2075 -2222
- package/lib/ski-interpreter.esm.js.map +4 -4
- package/lib/ski-interpreter.min.js +3 -3
- package/lib/ski-interpreter.min.js.map +4 -4
- package/lib/ski-quest.min.js +3 -3
- package/lib/ski-quest.min.js.map +4 -4
- package/{types/src → lib/types}/expr.d.ts +221 -252
- package/{types/src → lib/types}/extras.d.ts +24 -9
- package/lib/types/index.d.ts +68 -0
- package/{types/src → lib/types}/internal.d.ts +27 -23
- package/{types/src → lib/types}/parser.d.ts +40 -83
- package/lib/types/quest.d.ts +232 -0
- package/lib/types/toposort.d.ts +30 -0
- package/package.json +13 -12
- package/types/index.d.ts +0 -3
- package/types/src/quest.d.ts +0 -257
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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.4.1] - 2026-03-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- expr.run({maxSize: number ?? 1_000_000}) to prevent runaway reductions (fixes #14).
|
|
13
|
+
- `ski.js infer <expression>` command to infer properties of an expression.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- quests use shorter Y combinator form internally (#13)
|
|
18
|
+
- history in the playground is scrolled to the bottom.
|
|
19
|
+
|
|
20
|
+
## [2.4.0] - 2026-03-06
|
|
21
|
+
|
|
22
|
+
### BREAKING CHANGES
|
|
23
|
+
|
|
24
|
+
- Remove Expr.freeOnly method, use `!this.any(e => !(e instanceof FreeVar || e instanceof App))` instead;
|
|
25
|
+
- Remove Expr.weight() function because it's too ambiguous, use `fold (...)` instead.
|
|
26
|
+
- Remove `{ Quest }` from exports, use `SKI.Quest` instead.
|
|
27
|
+
- Rename `ski.js quest-check` to `ski.js quest-lint` for clarity.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Rewrite the whole thing in TypeScript.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Avoid name clashes with existing variables in infer()
|
|
34
|
+
([#15](https://github.com/dallaylaen/ski-interpreter/issues/15))
|
|
35
|
+
|
|
8
36
|
## [2.3.3] - 2026-03-01
|
|
9
37
|
|
|
10
38
|
### Changed
|
package/README.md
CHANGED
|
@@ -99,9 +99,18 @@ npm install @dallaylaen/ski-interpreter
|
|
|
99
99
|
* `--verbose` - Show all evaluation steps
|
|
100
100
|
* Example: `ski file script.ski`
|
|
101
101
|
|
|
102
|
-
* **`
|
|
102
|
+
* **`infer <expression>`** - try to find equivalent lambda expression and display its properties if found.
|
|
103
|
+
|
|
104
|
+
* **`extract <expression> <known term> ...`** -
|
|
105
|
+
Replace parts of the expression that are equivalent to the known terms with the respective terms. Known terms must be normalizable.
|
|
106
|
+
|
|
107
|
+
* **`search <expression> <known term> ...`** -
|
|
108
|
+
Attempt to brute force an equivalent of the _expression_ using only the _known terms_.
|
|
109
|
+
Only normalizable terms are currently supported.
|
|
110
|
+
|
|
111
|
+
* **`quest-lint <files...>`** - Validate quest definition files
|
|
103
112
|
* `--solution <file>` - Load solutions from a JSON file for verification
|
|
104
|
-
* Example: `ski quest-
|
|
113
|
+
* Example: `ski quest-lint quest1.json quest2.json --solution solutions.json`
|
|
105
114
|
|
|
106
115
|
If no subcommand is provided, help is displayed.
|
|
107
116
|
|
|
@@ -143,7 +152,7 @@ const final = expr.run({max: 1000}); // { steps: 42, expr: '...' }
|
|
|
143
152
|
const iterator = expr.walk();
|
|
144
153
|
|
|
145
154
|
// applying expressions
|
|
146
|
-
const result = expr.run({max: 1000}, arg1, arg2 ...);
|
|
155
|
+
const result = expr.run({max: 1000}, arg1, arg2, ...);
|
|
147
156
|
// same sa
|
|
148
157
|
expr.apply(arg1).apply(arg2).run();
|
|
149
158
|
// or simply
|
package/bin/ski.js
CHANGED
|
@@ -4,14 +4,15 @@ const fs = require('node:fs/promises');
|
|
|
4
4
|
const { Command } = require('commander');
|
|
5
5
|
|
|
6
6
|
const { SKI } = require('../lib/ski-interpreter.cjs');
|
|
7
|
-
const { Quest } =
|
|
7
|
+
const { Quest } = SKI;
|
|
8
|
+
const { version } = require('../package.json');
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
11
12
|
program
|
|
12
13
|
.name('ski')
|
|
13
14
|
.description('Simple Kombinator Interpreter - a combinatory logic & lambda calculus parser and interpreter')
|
|
14
|
-
.version(
|
|
15
|
+
.version(version);
|
|
15
16
|
|
|
16
17
|
// REPL subcommand
|
|
17
18
|
program
|
|
@@ -40,12 +41,12 @@ program
|
|
|
40
41
|
evaluateFile(filepath, options.verbose);
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
+
// Infer subcommand
|
|
44
45
|
program
|
|
45
|
-
.command('
|
|
46
|
-
.description('
|
|
47
|
-
.action((
|
|
48
|
-
|
|
46
|
+
.command('infer <expression>')
|
|
47
|
+
.description('Find a canonical form of the expression and its properties')
|
|
48
|
+
.action((expression) => {
|
|
49
|
+
inferExpression(expression);
|
|
49
50
|
});
|
|
50
51
|
|
|
51
52
|
// Extract subcommand
|
|
@@ -56,9 +57,17 @@ program
|
|
|
56
57
|
extractExpression(target, terms);
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
// Search subcommand
|
|
61
|
+
program
|
|
62
|
+
.command('search <target> <terms...>')
|
|
63
|
+
.description('Search for an expression equivalent to target using known terms')
|
|
64
|
+
.action((target, terms) => {
|
|
65
|
+
searchExpression(target, terms);
|
|
66
|
+
});
|
|
67
|
+
|
|
59
68
|
// Quest-check subcommand
|
|
60
69
|
program
|
|
61
|
-
.command('quest-
|
|
70
|
+
.command('quest-lint <files...>')
|
|
62
71
|
.description('Check quest files for validity')
|
|
63
72
|
.option('--solution <file>', 'Load solutions from file')
|
|
64
73
|
.action((files, options) => {
|
|
@@ -155,6 +164,41 @@ function processLine (source, ski, verbose, onErr) {
|
|
|
155
164
|
}
|
|
156
165
|
}
|
|
157
166
|
|
|
167
|
+
function inferExpression (expression) {
|
|
168
|
+
const ski = new SKI();
|
|
169
|
+
|
|
170
|
+
const expr = ski.parse(expression);
|
|
171
|
+
const guess = expr.infer();
|
|
172
|
+
|
|
173
|
+
if (guess.normal) {
|
|
174
|
+
displayInfer(guess);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// hard case...
|
|
178
|
+
let steps = guess.steps;
|
|
179
|
+
const canon = expr.traverse(e => {
|
|
180
|
+
const g = e.infer();
|
|
181
|
+
steps += g.steps;
|
|
182
|
+
return g.expr;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
displayInfer({ expr: canon, steps, normal: false, proper: false });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
* @param {TermInfo} guess
|
|
191
|
+
*/
|
|
192
|
+
function displayInfer (guess) {
|
|
193
|
+
if (guess.expr)
|
|
194
|
+
console.log(guess.expr.format());
|
|
195
|
+
|
|
196
|
+
for (const key of ['normal', 'proper', 'arity', 'discard', 'duplicate', 'steps']) {
|
|
197
|
+
if (guess[key] !== undefined)
|
|
198
|
+
console.log(`// ${key}: ${guess[key]}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
158
202
|
async function questCheck (files, solutionFile) {
|
|
159
203
|
try {
|
|
160
204
|
// Load solutions if provided
|
|
@@ -214,7 +258,7 @@ async function questCheck (files, solutionFile) {
|
|
|
214
258
|
// Exit with appropriate code
|
|
215
259
|
process.exit(hasErrors ? 1 : 0);
|
|
216
260
|
} catch (err) {
|
|
217
|
-
console.error('Error in quest-
|
|
261
|
+
console.error('Error in quest-lint:', err.message);
|
|
218
262
|
process.exit(2);
|
|
219
263
|
}
|
|
220
264
|
}
|
|
@@ -264,9 +308,11 @@ function extractExpression (targetStr, termStrs) {
|
|
|
264
308
|
|
|
265
309
|
const replaced = expr.traverse(e => {
|
|
266
310
|
const canon = e.infer().expr;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
311
|
+
if (canon) {
|
|
312
|
+
for (const [lambda, term] of pairs) {
|
|
313
|
+
if (canon.equals(lambda))
|
|
314
|
+
return term;
|
|
315
|
+
}
|
|
270
316
|
}
|
|
271
317
|
return null;
|
|
272
318
|
});
|