@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 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 plain JavaScript (with bolted on TypeScript support)
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 &mapsto; x z y</code> _// swapping_;
41
45
  * <code>W x y &mapsto; x y y</code> _//duplication_;
42
46
 
43
- The special combinator `+` will increment Church numerals, if they happen to come after it:
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._format({
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
- _format(options, nargs) {
14516
- throw new Error("No _format() method defined in class " + this.constructor.name);
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
- const rec = (e, indent) => {
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
- _format(options, nargs) {
14652
- const fun = this.fun._format(options, nargs + 1);
14653
- const arg = this.arg._format(options, 0);
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
- _format(options, nargs) {
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
- _format(options, nargs) {
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
- _format(options, nargs) {
14780
- return (nargs > 0 ? options.brackets[0] : "") + options.lambda[0] + this.arg._format(options, 0) + options.lambda[1] + this.impl._format(options, 0) + options.lambda[2] + (nargs > 0 ? options.brackets[1] : "");
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
- _format(options, nargs) {
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
- _format(options, nargs) {
14877
+ formatImpl(options, nargs) {
14881
14878
  const outdated = options.inventory ? options.inventory[this.name] !== this : this.outdated;
14882
- return outdated ? this.impl._format(options, nargs) : super._format(options, nargs);
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
- // variable generator shortcut
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, {