@dallaylaen/ski-interpreter 2.4.0 → 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 +12 -0
- package/README.md +4 -2
- package/bin/ski.js +48 -5
- package/lib/ski-interpreter.cjs.js +13 -3
- package/lib/ski-interpreter.cjs.js.map +2 -2
- package/lib/ski-interpreter.esm.js +13 -3
- package/lib/ski-interpreter.esm.js.map +2 -2
- package/lib/ski-interpreter.min.js +3 -3
- package/lib/ski-interpreter.min.js.map +3 -3
- package/lib/ski-quest.min.js +3 -3
- package/lib/ski-quest.min.js.map +3 -3
- package/lib/types/expr.d.ts +4 -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.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
|
+
|
|
8
20
|
## [2.4.0] - 2026-03-06
|
|
9
21
|
|
|
10
22
|
### BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -99,12 +99,14 @@ npm install @dallaylaen/ski-interpreter
|
|
|
99
99
|
* `--verbose` - Show all evaluation steps
|
|
100
100
|
* Example: `ski file script.ski`
|
|
101
101
|
|
|
102
|
+
* **`infer <expression>`** - try to find equivalent lambda expression and display its properties if found.
|
|
103
|
+
|
|
102
104
|
* **`extract <expression> <known term> ...`** -
|
|
103
105
|
Replace parts of the expression that are equivalent to the known terms with the respective terms. Known terms must be normalizable.
|
|
104
106
|
|
|
105
107
|
* **`search <expression> <known term> ...`** -
|
|
106
108
|
Attempt to brute force an equivalent of the _expression_ using only the _known terms_.
|
|
107
|
-
Only
|
|
109
|
+
Only normalizable terms are currently supported.
|
|
108
110
|
|
|
109
111
|
* **`quest-lint <files...>`** - Validate quest definition files
|
|
110
112
|
* `--solution <file>` - Load solutions from a JSON file for verification
|
|
@@ -150,7 +152,7 @@ const final = expr.run({max: 1000}); // { steps: 42, expr: '...' }
|
|
|
150
152
|
const iterator = expr.walk();
|
|
151
153
|
|
|
152
154
|
// applying expressions
|
|
153
|
-
const result = expr.run({max: 1000}, arg1, arg2 ...);
|
|
155
|
+
const result = expr.run({max: 1000}, arg1, arg2, ...);
|
|
154
156
|
// same sa
|
|
155
157
|
expr.apply(arg1).apply(arg2).run();
|
|
156
158
|
// or simply
|
package/bin/ski.js
CHANGED
|
@@ -41,12 +41,12 @@ program
|
|
|
41
41
|
evaluateFile(filepath, options.verbose);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
//
|
|
44
|
+
// Infer subcommand
|
|
45
45
|
program
|
|
46
|
-
.command('
|
|
47
|
-
.description('
|
|
48
|
-
.action((
|
|
49
|
-
|
|
46
|
+
.command('infer <expression>')
|
|
47
|
+
.description('Find a canonical form of the expression and its properties')
|
|
48
|
+
.action((expression) => {
|
|
49
|
+
inferExpression(expression);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
// Extract subcommand
|
|
@@ -57,6 +57,14 @@ program
|
|
|
57
57
|
extractExpression(target, terms);
|
|
58
58
|
});
|
|
59
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
|
+
|
|
60
68
|
// Quest-check subcommand
|
|
61
69
|
program
|
|
62
70
|
.command('quest-lint <files...>')
|
|
@@ -156,6 +164,41 @@ function processLine (source, ski, verbose, onErr) {
|
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
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
|
+
|
|
159
202
|
async function questCheck (files, solutionFile) {
|
|
160
203
|
try {
|
|
161
204
|
// Load solutions if provided
|
|
@@ -93,7 +93,8 @@ function prepareWrapper(label) {
|
|
|
93
93
|
// src/expr.ts
|
|
94
94
|
var DEFAULTS = {
|
|
95
95
|
max: 1e3,
|
|
96
|
-
maxArgs: 32
|
|
96
|
+
maxArgs: 32,
|
|
97
|
+
maxSize: 1e6
|
|
97
98
|
};
|
|
98
99
|
var ORDER = {
|
|
99
100
|
"leftmost-outermost": "LO",
|
|
@@ -115,6 +116,7 @@ var Expr = class _Expr {
|
|
|
115
116
|
static {
|
|
116
117
|
this.native = native;
|
|
117
118
|
}
|
|
119
|
+
// rough estimate of the number of nodes in the tree
|
|
118
120
|
/**
|
|
119
121
|
*
|
|
120
122
|
* @desc Define properties of the term based on user supplied options and/or inference results.
|
|
@@ -314,9 +316,12 @@ var Expr = class _Expr {
|
|
|
314
316
|
*/
|
|
315
317
|
infer(options = {}) {
|
|
316
318
|
const skipNames = {};
|
|
319
|
+
const skipSkip = /* @__PURE__ */ new Set();
|
|
317
320
|
this.traverse((e) => {
|
|
318
|
-
if (e instanceof Named)
|
|
321
|
+
if (e instanceof Named && !skipSkip.has(e))
|
|
319
322
|
skipNames[e.name] = true;
|
|
323
|
+
if (e instanceof Lambda)
|
|
324
|
+
skipSkip.add(e.arg);
|
|
320
325
|
return void 0;
|
|
321
326
|
});
|
|
322
327
|
return this._infer({
|
|
@@ -539,6 +544,8 @@ var Expr = class _Expr {
|
|
|
539
544
|
final = true;
|
|
540
545
|
break;
|
|
541
546
|
}
|
|
547
|
+
if ((next.expr.size ?? 1) > (opt.maxSize ?? DEFAULTS.maxSize))
|
|
548
|
+
break;
|
|
542
549
|
steps += next.steps;
|
|
543
550
|
expr = next.expr;
|
|
544
551
|
}
|
|
@@ -557,7 +564,7 @@ var Expr = class _Expr {
|
|
|
557
564
|
let steps = 0;
|
|
558
565
|
let expr = this;
|
|
559
566
|
let final = false;
|
|
560
|
-
while (steps < max) {
|
|
567
|
+
while (steps < max && (expr.size ?? 1) < (options.maxSize ?? DEFAULTS.maxSize)) {
|
|
561
568
|
const next = expr.step();
|
|
562
569
|
if (!next.changed)
|
|
563
570
|
final = true;
|
|
@@ -778,6 +785,7 @@ var App = class _App extends Expr {
|
|
|
778
785
|
super();
|
|
779
786
|
this.arg = arg;
|
|
780
787
|
this.fun = fun;
|
|
788
|
+
this.size = (fun.size ?? 1) + (arg.size ?? 1);
|
|
781
789
|
}
|
|
782
790
|
/** @property {boolean} [final] */
|
|
783
791
|
_traverse_descend(options, change) {
|
|
@@ -950,6 +958,7 @@ var Lambda = class _Lambda extends Expr {
|
|
|
950
958
|
this.arg = local;
|
|
951
959
|
this.impl = impl.subst(arg, local) ?? impl;
|
|
952
960
|
this.arity = 1;
|
|
961
|
+
this.size = (impl.size ?? 1) + 1;
|
|
953
962
|
}
|
|
954
963
|
invoke(arg) {
|
|
955
964
|
return this.impl.subst(this.arg, arg) ?? this.impl;
|
|
@@ -1036,6 +1045,7 @@ var Alias = class extends Named {
|
|
|
1036
1045
|
this._setup(options);
|
|
1037
1046
|
this.terminal = options.terminal ?? this.props?.proper;
|
|
1038
1047
|
this.invoke = waitn(impl, this.arity ?? 0);
|
|
1048
|
+
this.size = impl.size;
|
|
1039
1049
|
}
|
|
1040
1050
|
/**
|
|
1041
1051
|
* @property {boolean} [outdated] - whether the alias is outdated
|