@dallaylaen/ski-interpreter 2.5.0 → 2.5.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 +10 -0
- package/README.md +13 -7
- package/lib/ski-interpreter.cjs.js +40 -47
- package/lib/ski-interpreter.cjs.js.map +2 -2
- package/lib/ski-interpreter.esm.js +40 -47
- package/lib/ski-interpreter.esm.js.map +2 -2
- package/lib/ski-interpreter.min.js +10 -7
- package/lib/ski-interpreter.min.js.map +3 -3
- package/lib/ski-quest.min.js +10 -7
- package/lib/ski-quest.min.js.map +3 -3
- package/lib/types/expr.d.ts +32 -28
- package/lib/types/index.d.ts +12 -1
- package/lib/types/internal.d.ts +1 -4
- package/lib/types/quest.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ 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.5.1] - 2026-03-26
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- make diag() recursive (and simpler)
|
|
13
|
+
- better types, get rid of some type casts (esp in traverse/fold)
|
|
14
|
+
- some doc improvements
|
|
15
|
+
- some test improvements
|
|
16
|
+
- playground: add history removal
|
|
17
|
+
|
|
8
18
|
## [2.5.0] - 2026-03-15
|
|
9
19
|
|
|
10
20
|
### BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -7,9 +7,13 @@ This package contains a
|
|
|
7
7
|
and [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus)
|
|
8
8
|
parser and interpreter focused on traceability and inspectability.
|
|
9
9
|
|
|
10
|
-
It is written in
|
|
10
|
+
It is written in TypeScript and JavaScript
|
|
11
11
|
and can be used in Node.js or in the browser.
|
|
12
12
|
|
|
13
|
+
A [playground](https://dallaylaen.github.io/ski-interpreter/)
|
|
14
|
+
and a [quest page](https://dallaylaen.github.io/ski-interpreter/quest.html)
|
|
15
|
+
containing interactive combinatory logic exercises of increasing difficulty are incuded.
|
|
16
|
+
|
|
13
17
|
# Features:
|
|
14
18
|
|
|
15
19
|
* SKI and BCKW combinators
|
|
@@ -40,7 +44,8 @@ and can be used in Node.js or in the browser.
|
|
|
40
44
|
* <code>C x y z ↦ x z y</code> _// swapping_;
|
|
41
45
|
* <code>W x y ↦ x y y</code> _//duplication_;
|
|
42
46
|
|
|
43
|
-
The special combinator `+` will increment Church numerals,
|
|
47
|
+
The special combinator `+` will increment Church numerals,
|
|
48
|
+
if they happen to come directly after it:
|
|
44
49
|
|
|
45
50
|
* `+ 0` // 1
|
|
46
51
|
* `2 + 3` // -> `+(+(3))` -> `+(4)` -> `5`
|
|
@@ -99,14 +104,14 @@ npm install @dallaylaen/ski-interpreter
|
|
|
99
104
|
* `--verbose` - Show all evaluation steps
|
|
100
105
|
* Example: `ski file script.ski`
|
|
101
106
|
|
|
102
|
-
* **`infer <expression>`** - try to find equivalent lambda expression and display its properties if found.
|
|
107
|
+
* **`infer <expression>`** - try to find equivalent lambda expression and display its properties if found.
|
|
103
108
|
|
|
104
|
-
* **`extract <expression> <known term> ...`** -
|
|
109
|
+
* **`extract <expression> <known term> ...`** -
|
|
105
110
|
Replace parts of the expression that are equivalent to the known terms with the respective terms. Known terms must be normalizable.
|
|
106
111
|
|
|
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.
|
|
112
|
+
* **`search <expression> <known term> ...`** -
|
|
113
|
+
Attempt to brute force an equivalent of the _expression_ using only the _known terms_.
|
|
114
|
+
Only normalizable terms are currently supported.
|
|
110
115
|
|
|
111
116
|
* **`quest-lint <files...>`** - Validate quest definition files
|
|
112
117
|
* `--solution <file>` - Load solutions from a JSON file for verification
|
|
@@ -297,6 +302,7 @@ for building interactive quest pages from JSON-encoded quest data;
|
|
|
297
302
|
* [@ivanaxe](https://github.com/ivanaxe) for luring me into [icfpc 2011](http://icfpc2011.blogspot.com/2011/06/task-description-contest-starts-now.html) where I was introduced to combinators.
|
|
298
303
|
* [@akuklev](https://github.com/akuklev) for explaining functional programming to me so many times that I actually got some idea.
|
|
299
304
|
* [One happy fellow](https://github.com/happyfellow-one) whose [riddle](https://blog.happyfellow.dev/a-riddle/) trolled me into writing an early `traverse` prototype.
|
|
305
|
+
* [Darkwing3125](https://github.com/Darkwing3125) for posting multiple bug reports and feature requests.
|
|
300
306
|
|
|
301
307
|
# Prior art and inspiration
|
|
302
308
|
|
|
@@ -13895,7 +13895,6 @@ var Expr = class _Expr {
|
|
|
13895
13895
|
static {
|
|
13896
13896
|
this.native = native;
|
|
13897
13897
|
}
|
|
13898
|
-
// rough estimate of the number of nodes in the tree
|
|
13899
13898
|
/**
|
|
13900
13899
|
*
|
|
13901
13900
|
* @desc Define properties of the term based on user supplied options and/or inference results.
|
|
@@ -14492,7 +14491,7 @@ var Expr = class _Expr {
|
|
|
14492
14491
|
around: ["", ""],
|
|
14493
14492
|
redex: ["", ""]
|
|
14494
14493
|
};
|
|
14495
|
-
return this.
|
|
14494
|
+
return this.formatImpl({
|
|
14496
14495
|
terse: options.terse ?? true,
|
|
14497
14496
|
brackets: options.brackets ?? fallback.brackets,
|
|
14498
14497
|
space: options.space ?? fallback.space,
|
|
@@ -14512,8 +14511,8 @@ var Expr = class _Expr {
|
|
|
14512
14511
|
* @returns {string}
|
|
14513
14512
|
* @private
|
|
14514
14513
|
*/
|
|
14515
|
-
|
|
14516
|
-
throw new Error("No
|
|
14514
|
+
formatImpl(options, nargs) {
|
|
14515
|
+
throw new Error("No formatImpl() method defined in class " + this.constructor.name);
|
|
14517
14516
|
}
|
|
14518
14517
|
/**
|
|
14519
14518
|
* @desc Returns a string representation of the expression tree, with indentation to show structure.
|
|
@@ -14536,20 +14535,8 @@ var Expr = class _Expr {
|
|
|
14536
14535
|
* FreeVar: x[54]
|
|
14537
14536
|
* FreeVar: x[54]
|
|
14538
14537
|
*/
|
|
14539
|
-
diag() {
|
|
14540
|
-
|
|
14541
|
-
if (e instanceof App)
|
|
14542
|
-
return [indent + "App:", ...e.unroll().flatMap((s) => rec(s, indent + " "))];
|
|
14543
|
-
if (e instanceof Lambda)
|
|
14544
|
-
return [`${indent}Lambda (${e.arg}[${e.arg.id}]):`, ...rec(e.impl, indent + " ")];
|
|
14545
|
-
if (e instanceof Alias)
|
|
14546
|
-
return [`${indent}Alias (${e.name}): \\`, ...rec(e.impl, indent)];
|
|
14547
|
-
if (e instanceof FreeVar)
|
|
14548
|
-
return [`${indent}FreeVar: ${e.name}[${e.id}]`];
|
|
14549
|
-
return [`${indent}${e.constructor.name}: ${e}`];
|
|
14550
|
-
};
|
|
14551
|
-
const out = rec(this, "");
|
|
14552
|
-
return out.join("\n");
|
|
14538
|
+
diag(indent = "") {
|
|
14539
|
+
return indent + this.constructor.name + ": " + this;
|
|
14553
14540
|
}
|
|
14554
14541
|
/**
|
|
14555
14542
|
* @desc Convert the expression to a JSON-serializable format.
|
|
@@ -14648,15 +14635,18 @@ var App = class _App extends Expr {
|
|
|
14648
14635
|
_braced(first) {
|
|
14649
14636
|
return !first;
|
|
14650
14637
|
}
|
|
14651
|
-
|
|
14652
|
-
const fun = this.fun.
|
|
14653
|
-
const arg = this.arg.
|
|
14638
|
+
formatImpl(options, nargs) {
|
|
14639
|
+
const fun = this.fun.formatImpl(options, nargs + 1);
|
|
14640
|
+
const arg = this.arg.formatImpl(options, 0);
|
|
14654
14641
|
const wrap = nargs ? ["", ""] : options.around;
|
|
14655
14642
|
if (options.terse && !this.arg._braced(false))
|
|
14656
14643
|
return wrap[0] + fun + (this.fun._unspaced(this.arg) ? "" : options.space) + arg + wrap[1];
|
|
14657
14644
|
else
|
|
14658
14645
|
return wrap[0] + fun + options.brackets[0] + arg + options.brackets[1] + wrap[1];
|
|
14659
14646
|
}
|
|
14647
|
+
diag(indent = "") {
|
|
14648
|
+
return indent + "App:\n" + this.unroll().map((e) => e.diag(indent + " ")).join("\n");
|
|
14649
|
+
}
|
|
14660
14650
|
_unspaced(arg) {
|
|
14661
14651
|
return this.arg._braced(false) ? true : this.arg._unspaced(arg);
|
|
14662
14652
|
}
|
|
@@ -14671,7 +14661,7 @@ var Named = class _Named extends Expr {
|
|
|
14671
14661
|
_unspaced(arg) {
|
|
14672
14662
|
return !!(arg instanceof _Named && (this.name.match(/^[A-Z+]$/) && arg.name.match(/^[a-z+]/i) || this.name.match(/^[a-z+]/i) && arg.name.match(/^[A-Z+]$/)));
|
|
14673
14663
|
}
|
|
14674
|
-
|
|
14664
|
+
formatImpl(options, nargs) {
|
|
14675
14665
|
const name = options.html ? this.fancyName ?? this.name : this.name;
|
|
14676
14666
|
return this.arity !== void 0 && this.arity > 0 && this.arity <= nargs ? options.redex[0] + name + options.redex[1] : name;
|
|
14677
14667
|
}
|
|
@@ -14697,10 +14687,13 @@ var FreeVar = class _FreeVar extends Named {
|
|
|
14697
14687
|
return replace;
|
|
14698
14688
|
return null;
|
|
14699
14689
|
}
|
|
14700
|
-
|
|
14690
|
+
formatImpl(options, nargs) {
|
|
14701
14691
|
const name = options.html ? this.fancyName ?? this.name : this.name;
|
|
14702
14692
|
return options.var[0] + name + options.var[1];
|
|
14703
14693
|
}
|
|
14694
|
+
diag(indent = "") {
|
|
14695
|
+
return `${indent}FreeVar: ${this.name}[${this.id}]`;
|
|
14696
|
+
}
|
|
14704
14697
|
static {
|
|
14705
14698
|
this.global = ["global"];
|
|
14706
14699
|
}
|
|
@@ -14776,8 +14769,12 @@ var Lambda = class _Lambda extends Expr {
|
|
|
14776
14769
|
return "(t->" + diff + ")";
|
|
14777
14770
|
return null;
|
|
14778
14771
|
}
|
|
14779
|
-
|
|
14780
|
-
return (nargs > 0 ? options.brackets[0] : "") + options.lambda[0] + this.arg.
|
|
14772
|
+
formatImpl(options, nargs) {
|
|
14773
|
+
return (nargs > 0 ? options.brackets[0] : "") + options.lambda[0] + this.arg.formatImpl(options, 0) + options.lambda[1] + this.impl.formatImpl(options, 0) + options.lambda[2] + (nargs > 0 ? options.brackets[1] : "");
|
|
14774
|
+
}
|
|
14775
|
+
diag(indent = "") {
|
|
14776
|
+
return `${indent}Lambda (${this.arg.name}[${this.arg.id}]):
|
|
14777
|
+
` + this.impl.diag(indent + " ");
|
|
14781
14778
|
}
|
|
14782
14779
|
_braced(first) {
|
|
14783
14780
|
return true;
|
|
@@ -14808,7 +14805,7 @@ var Church = class _Church extends Expr {
|
|
|
14808
14805
|
_unspaced(arg) {
|
|
14809
14806
|
return false;
|
|
14810
14807
|
}
|
|
14811
|
-
|
|
14808
|
+
formatImpl(options, nargs) {
|
|
14812
14809
|
return nargs >= 2 ? options.redex[0] + this.n + options.redex[1] : this.n + "";
|
|
14813
14810
|
}
|
|
14814
14811
|
};
|
|
@@ -14877,9 +14874,13 @@ var Alias = class extends Named {
|
|
|
14877
14874
|
_braced(first) {
|
|
14878
14875
|
return this.outdated ? this.impl._braced(first) : false;
|
|
14879
14876
|
}
|
|
14880
|
-
|
|
14877
|
+
formatImpl(options, nargs) {
|
|
14881
14878
|
const outdated = options.inventory ? options.inventory[this.name] !== this : this.outdated;
|
|
14882
|
-
return outdated ? this.impl.
|
|
14879
|
+
return outdated ? this.impl.formatImpl(options, nargs) : super.formatImpl(options, nargs);
|
|
14880
|
+
}
|
|
14881
|
+
diag(indent = "") {
|
|
14882
|
+
return `${indent}Alias (${this.name}): \\
|
|
14883
|
+
` + this.impl.diag(indent);
|
|
14883
14884
|
}
|
|
14884
14885
|
};
|
|
14885
14886
|
function addNative(name, impl, opt = {}) {
|
|
@@ -15242,9 +15243,7 @@ var Parser = class {
|
|
|
15242
15243
|
list[j] = rework(list[j]);
|
|
15243
15244
|
detour.set(needDetour[list[j].name], list[j]);
|
|
15244
15245
|
env[list[j].name] = list[j];
|
|
15245
|
-
console.log(`list[${j}] = ${list[j].name}=${list[j].impl};`);
|
|
15246
15246
|
}
|
|
15247
|
-
console.log("detour:", detour);
|
|
15248
15247
|
}
|
|
15249
15248
|
const out = list.map(
|
|
15250
15249
|
(e) => needDetour[e.name] ? e.name + "=" + needDetour[e.name].name + "=" + e.impl.format({ inventory: env }) : e.name + "=" + e.impl.format({ inventory: env })
|
|
@@ -15372,22 +15371,6 @@ var Parser = class {
|
|
|
15372
15371
|
/**
|
|
15373
15372
|
* Public static shortcuts to common functions (see also ./extras.js)
|
|
15374
15373
|
*/
|
|
15375
|
-
/**
|
|
15376
|
-
* @desc Create a proxy object that generates variables on demand,
|
|
15377
|
-
* with names corresponding to the property accessed.
|
|
15378
|
-
* Different invocations will return distinct variables,
|
|
15379
|
-
* even if with the same name.
|
|
15380
|
-
*
|
|
15381
|
-
*
|
|
15382
|
-
* @example const {x, y, z} = SKI.vars();
|
|
15383
|
-
* x.name; // 'x'
|
|
15384
|
-
* x instanceof FreeVar; // true
|
|
15385
|
-
* x.apply(y).apply(z); // x(y)(z)
|
|
15386
|
-
*
|
|
15387
|
-
* @template T
|
|
15388
|
-
* @param {T} [scope] - optional context to bind the generated variables to
|
|
15389
|
-
* @return {{[key: string]: FreeVar}}
|
|
15390
|
-
*/
|
|
15391
15374
|
};
|
|
15392
15375
|
function maybeAlias(name, impl) {
|
|
15393
15376
|
while (impl instanceof Alias && impl.name === name)
|
|
@@ -15931,7 +15914,17 @@ var SKI = class extends Parser {
|
|
|
15931
15914
|
static {
|
|
15932
15915
|
this.W = native.W;
|
|
15933
15916
|
}
|
|
15934
|
-
|
|
15917
|
+
/**
|
|
15918
|
+
* @desc Create a proxy object that generates variables on demand,
|
|
15919
|
+
* with names corresponding to the property accessed.
|
|
15920
|
+
* Different invocations will return distinct variables,
|
|
15921
|
+
* even if with the same name.
|
|
15922
|
+
*
|
|
15923
|
+
* @example const {x, y, z} = SKI.vars();
|
|
15924
|
+
* x.name; // 'x'
|
|
15925
|
+
* x instanceof FreeVar; // true
|
|
15926
|
+
* x.apply(y).apply(z); // x(y)(z)
|
|
15927
|
+
*/
|
|
15935
15928
|
static vars(scope = {}) {
|
|
15936
15929
|
const vars = {};
|
|
15937
15930
|
return new Proxy(vars, {
|