@dallaylaen/ski-interpreter 2.5.1 → 2.6.0

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,26 @@ 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.6.0] - 2026-03-28
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ - `Alias`: `outdated` and `terminal` properties replaced by
13
+ `inline`: boolean.
14
+
15
+ ### Added
16
+
17
+ - Experimental `Alias.makeInline()` that sets the inline property
18
+ and adjusts the invocation accordingly
19
+ - (don't wait for more args if inline).
20
+
21
+ ## [2.5.2] - 2026-03-27
22
+
23
+ ### Changed
24
+
25
+ - Make `Expr` class abstract (as was always intended)
26
+ and mark its abstract/final/protected methods appropriately.
27
+
8
28
  ## [2.5.1] - 2026-03-26
9
29
 
10
30
  ### Changed
@@ -19,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
39
 
20
40
  ### BREAKING CHANGES
21
41
 
22
- - `affine: true` in quests now means "has no duplicating subterms" rather than "non-duplicating as a whole."
42
+ - `affine: true` in quests now means "has no duplicating subterms" rather than "non-duplicating as a whole."
23
43
  Aliases are exempted from this check, so a solution `SK`
24
44
  will not pass as S is duplicating, but `false=SK; false`
25
45
  will because `false` (as a whole term) _is_ affine.
@@ -74,7 +94,7 @@ will because `false` (as a whole term) _is_ affine.
74
94
  ### Changed
75
95
 
76
96
  - `SKI.extras.foldr` is now removed in favor of
77
- `Expr.foldBottomUp<T>(fun: (expr: Expr, args: T[]) => T): T`
97
+ `Expr.foldBottomUp<T>(fun: (expr: Expr, args: T[]) => T): T`
78
98
  with the same semantics but more descriptive name
79
99
  and simpler signature.
80
100
  _Was experimental (and still is), so not considered a breaking change._
@@ -13889,17 +13889,11 @@ var FormatOptionsSchema = external_exports.object({
13889
13889
  inventory: external_exports.record(external_exports.string(), external_exports.custom((v) => v instanceof Expr)).optional()
13890
13890
  });
13891
13891
  var Expr = class _Expr {
13892
- static {
13893
- this.control = control;
13894
- }
13895
- static {
13896
- this.native = native;
13897
- }
13898
13892
  /**
13899
13893
  *
13900
13894
  * @desc Define properties of the term based on user supplied options and/or inference results.
13901
13895
  * Typically useful for declaring Native and Alias terms.
13902
- * @private
13896
+ * @protected
13903
13897
  * @param {Object} options
13904
13898
  * @param {string} [options.note] - a brief description what the term does
13905
13899
  * @param {number} [options.arity] - number of arguments the term is waiting for (if known)
@@ -13974,6 +13968,7 @@ var Expr = class _Expr {
13974
13968
  * }} [options]
13975
13969
  * @param {(e:Expr) => TraverseValue<Expr>} change
13976
13970
  * @returns {Expr|null}
13971
+ * @final
13977
13972
  */
13978
13973
  traverse(options, change) {
13979
13974
  if (typeof options === "function") {
@@ -13987,7 +13982,8 @@ var Expr = class _Expr {
13987
13982
  return expr ?? null;
13988
13983
  }
13989
13984
  /**
13990
- * @private
13985
+ * @protected
13986
+ * @final
13991
13987
  * @param {Object} options
13992
13988
  * @param {(e:Expr) => TraverseValue<Expr>} change
13993
13989
  * @returns {TraverseValue<Expr>}
@@ -14006,7 +14002,7 @@ var Expr = class _Expr {
14006
14002
  return action ? action(expr) : expr;
14007
14003
  }
14008
14004
  /**
14009
- * @private
14005
+ * @protected
14010
14006
  * @param {Object} options
14011
14007
  * @param {(e:Expr) => TraverseValue<Expr>} change
14012
14008
  * @returns {TraverseValue<Expr>}
@@ -14036,7 +14032,11 @@ var Expr = class _Expr {
14036
14032
  *
14037
14033
  * This method is experimental and may change in the future.
14038
14034
  *
14035
+ * @example // count the number of nodes in the expression tree
14036
+ * expr.fold(0, (acc, e) => acc + 1);
14037
+ *
14039
14038
  * @experimental
14039
+ * @final
14040
14040
  * @template T
14041
14041
  * @param {T} initial
14042
14042
  * @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
@@ -14046,6 +14046,14 @@ var Expr = class _Expr {
14046
14046
  const [value] = unwrap(this._fold(initial, combine));
14047
14047
  return value ?? initial;
14048
14048
  }
14049
+ /**
14050
+ * @desc Internal method for fold(), which performs the actual folding.
14051
+ * Should be implemented in subclasses having any internal structure.
14052
+ *
14053
+ * @protected
14054
+ * @param initial
14055
+ * @param combine
14056
+ */
14049
14057
  _fold(initial, combine) {
14050
14058
  return combine(initial, this);
14051
14059
  }
@@ -14089,6 +14097,7 @@ var Expr = class _Expr {
14089
14097
  *
14090
14098
  * Use toLambda() if you want to get a lambda term in any case.
14091
14099
  *
14100
+ * @final
14092
14101
  * @param {{max?: number, maxArgs?: number}} options
14093
14102
  * @return {TermInfo}
14094
14103
  */
@@ -14181,6 +14190,7 @@ var Expr = class _Expr {
14181
14190
  *
14182
14191
  * See also Expr.walk() and Expr.toSKI().
14183
14192
  *
14193
+ * @final
14184
14194
  * @param {{
14185
14195
  * max?: number,
14186
14196
  * maxArgs?: number,
@@ -14227,6 +14237,7 @@ var Expr = class _Expr {
14227
14237
  *
14228
14238
  * See also Expr.walk() and Expr.toLambda().
14229
14239
  *
14240
+ * @final
14230
14241
  * @param {{max?: number}} [options]
14231
14242
  * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
14232
14243
  */
@@ -14257,12 +14268,15 @@ var Expr = class _Expr {
14257
14268
  }
14258
14269
  }
14259
14270
  /**
14260
- * Replace all instances of plug in the expression with value and return the resulting expression,
14271
+ * Replace all instances of `search` in the expression with `replace` and return the resulting expression,
14261
14272
  * or null if no changes could be made.
14273
+ *
14262
14274
  * Lambda terms and applications will never match if used as plug
14263
- * as they are impossible co compare without extensive computations.
14275
+ * as they are impossible to compare without extensive computations.
14276
+ *
14264
14277
  * Typically used on variables but can also be applied to other terms, e.g. aliases.
14265
- * See also Expr.traverse().
14278
+ * See also Expr.traverse() for more flexible replacement of subterms.
14279
+ *
14266
14280
  * @param {Expr} search
14267
14281
  * @param {Expr} replace
14268
14282
  * @return {Expr|null}
@@ -14303,6 +14317,7 @@ var Expr = class _Expr {
14303
14317
  * @desc Run uninterrupted sequence of step() applications
14304
14318
  * until the expression is irreducible, or max number of steps is reached.
14305
14319
  * Default number of steps = 1000.
14320
+ * @final
14306
14321
  * @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
14307
14322
  * @param {Expr} args
14308
14323
  * @return {{expr: Expr, steps: number, final: boolean}}
@@ -14333,7 +14348,9 @@ var Expr = class _Expr {
14333
14348
  }
14334
14349
  /**
14335
14350
  * Execute step() while possible, yielding a brief description of events after each step.
14351
+ *
14336
14352
  * Mnemonics: like run() but slower.
14353
+ * @final
14337
14354
  * @param {{max?: number}} options
14338
14355
  * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
14339
14356
  */
@@ -14363,6 +14380,7 @@ var Expr = class _Expr {
14363
14380
  *
14364
14381
  * @param {Expr} other
14365
14382
  * @return {boolean}
14383
+ * @final
14366
14384
  */
14367
14385
  equals(other) {
14368
14386
  return !this.diff(other);
@@ -14381,6 +14399,8 @@ var Expr = class _Expr {
14381
14399
  * To somewhat alleviate confusion, the output will include
14382
14400
  * the internal id of the variable in square brackets.
14383
14401
  *
14402
+ * Do not rely on the exact format of the output as it may change in the future.
14403
+ *
14384
14404
  * @example "K(S != I)" is the result of comparing "KS" and "KI"
14385
14405
  * @example "S(K([x[13] != x[14]]))K"
14386
14406
  *
@@ -14401,6 +14421,11 @@ var Expr = class _Expr {
14401
14421
  * `this` is the expected value and the argument is the actual one.
14402
14422
  * Mnemonic: the expected value is always a combinator, the actual one may be anything.
14403
14423
  *
14424
+ * In case of failure, an error is thrown with a message describing the first point of difference
14425
+ * and `expected` and `actual` properties like in AssertionError.
14426
+ * AssertionError is not used directly to because browsers don't recognize it.
14427
+ *
14428
+ * @final
14404
14429
  * @param {Expr} actual
14405
14430
  * @param {string} comment
14406
14431
  */
@@ -14420,7 +14445,10 @@ var Expr = class _Expr {
14420
14445
  /**
14421
14446
  * @desc Returns string representation of the expression.
14422
14447
  * Same as format() without options.
14448
+ *
14449
+ * Use formatImpl() to override in subclasses.
14423
14450
  * @return {string}
14451
+ * @final
14424
14452
  */
14425
14453
  toString() {
14426
14454
  return this.format();
@@ -14429,6 +14457,7 @@ var Expr = class _Expr {
14429
14457
  * @desc Whether the expression needs parentheses when printed.
14430
14458
  * @param {boolean} [first] - whether this is the first term in a sequence
14431
14459
  * @return {boolean}
14460
+ * @protected
14432
14461
  */
14433
14462
  _braced(_first) {
14434
14463
  return false;
@@ -14437,7 +14466,7 @@ var Expr = class _Expr {
14437
14466
  * @desc Whether the expression can be printed without a space when followed by arg.
14438
14467
  * @param {Expr} arg
14439
14468
  * @returns {boolean}
14440
- * @private
14469
+ * @protected
14441
14470
  */
14442
14471
  _unspaced(arg) {
14443
14472
  return this._braced(true);
@@ -14445,8 +14474,9 @@ var Expr = class _Expr {
14445
14474
  /**
14446
14475
  * @desc Stringify the expression with fancy formatting options.
14447
14476
  * Said options mostly include wrappers around various constructs in form of ['(', ')'],
14448
- * as well as terse and html flags that set up the defaults.
14477
+ * as well as `terse` and `html` flags that fill in appropriate defaults.
14449
14478
  * Format without options is equivalent to toString() and can be parsed back.
14479
+ * @final
14450
14480
  *
14451
14481
  * @param {Object} [options] - formatting options
14452
14482
  * @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
@@ -14504,16 +14534,6 @@ var Expr = class _Expr {
14504
14534
  html: options.html ?? false
14505
14535
  }, 0);
14506
14536
  }
14507
- /**
14508
- * @desc Internal method for format(), which performs the actual formatting.
14509
- * @param {Object} options
14510
- * @param {number} nargs
14511
- * @returns {string}
14512
- * @private
14513
- */
14514
- formatImpl(options, nargs) {
14515
- throw new Error("No formatImpl() method defined in class " + this.constructor.name);
14516
- }
14517
14537
  /**
14518
14538
  * @desc Returns a string representation of the expression tree, with indentation to show structure.
14519
14539
  *
@@ -14541,6 +14561,7 @@ var Expr = class _Expr {
14541
14561
  /**
14542
14562
  * @desc Convert the expression to a JSON-serializable format.
14543
14563
  * @returns {string}
14564
+ * @final
14544
14565
  */
14545
14566
  toJSON() {
14546
14567
  return this.format();
@@ -14809,8 +14830,8 @@ var Church = class _Church extends Expr {
14809
14830
  return nargs >= 2 ? options.redex[0] + this.n + options.redex[1] : this.n + "";
14810
14831
  }
14811
14832
  };
14812
- function waitn(expr, n) {
14813
- return (arg) => n <= 1 ? expr.apply(arg) : waitn(expr.apply(arg), n - 1);
14833
+ function waitn(n) {
14834
+ return n <= 1 ? (e) => (arg) => e.apply(arg) : (e) => (arg) => waitn(n - 1)(e.apply(arg));
14814
14835
  }
14815
14836
  var Alias = class extends Named {
14816
14837
  constructor(name, impl, options = {}) {
@@ -14819,17 +14840,28 @@ var Alias = class extends Named {
14819
14840
  throw new Error("Attempt to create an alias for a non-expression: " + impl);
14820
14841
  this.impl = impl;
14821
14842
  this._setup(options);
14822
- this.terminal = options.terminal ?? this.props?.proper;
14823
- this.invoke = waitn(impl, this.arity ?? 0);
14843
+ this.invoke = waitn(options.inline ? 0 : this.arity ?? 0)(impl);
14824
14844
  this.size = impl.size;
14825
- if (options.outdated)
14826
- this.outdated = true;
14845
+ if (options.inline)
14846
+ this.inline = true;
14847
+ }
14848
+ /**
14849
+ * @desc Make the alias inline, i.e. replace it with its implementation everywhere.
14850
+ *
14851
+ * Replaces the old `outdated` attribute.
14852
+ * Used by the parser when a term definition is removed or updated.
14853
+ *
14854
+ * May change in future versions, use with caution.
14855
+ *
14856
+ * @experimental
14857
+ * @returns {this}
14858
+ */
14859
+ makeInline() {
14860
+ this.invoke = waitn(0)(this.impl);
14861
+ this.inline = true;
14862
+ return this;
14827
14863
  }
14828
14864
  /**
14829
- * @property {boolean} [outdated] - whether the alias is outdated
14830
- * and should be replaced with its definition when encountered.
14831
- * @property {boolean} [terminal] - whether the alias should behave like a standalone term
14832
- * // TODO better name?
14833
14865
  * @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
14834
14866
  * @property {number} [arity] - the number of arguments the alias waits for before expanding
14835
14867
  * @property {Expr} [canonical] - equivalent lambda term.
@@ -14872,11 +14904,11 @@ var Alias = class extends Named {
14872
14904
  return other.diff(this.impl, !swap);
14873
14905
  }
14874
14906
  _braced(first) {
14875
- return this.outdated ? this.impl._braced(first) : false;
14907
+ return this.inline ? this.impl._braced(first) : false;
14876
14908
  }
14877
14909
  formatImpl(options, nargs) {
14878
- const outdated = options.inventory ? options.inventory[this.name] !== this : this.outdated;
14879
- return outdated ? this.impl.formatImpl(options, nargs) : super.formatImpl(options, nargs);
14910
+ const inline = options.inventory ? options.inventory[this.name] !== this : this.inline;
14911
+ return inline ? this.impl.formatImpl(options, nargs) : super.formatImpl(options, nargs);
14880
14912
  }
14881
14913
  diag(indent = "") {
14882
14914
  return `${indent}Alias (${this.name}): \\
@@ -14994,6 +15026,9 @@ var Empty = class extends Expr {
14994
15026
  postParse() {
14995
15027
  throw new Error("Attempt to use empty expression () as a term");
14996
15028
  }
15029
+ formatImpl(options, nargs) {
15030
+ return "()";
15031
+ }
14997
15032
  };
14998
15033
  var PartialLambda = class _PartialLambda extends Empty {
14999
15034
  // TODO mutable! rewrite ro when have time
@@ -15085,8 +15120,9 @@ var Parser = class {
15085
15120
  const named = this._named(term, impl);
15086
15121
  const opts = typeof options === "string" ? { note: options, canonize: false } : options ?? {};
15087
15122
  named._setup({ canonize: this.annotate, ...opts });
15088
- if (this.known[named.name])
15089
- this.known[named.name].outdated = true;
15123
+ const old = this.known[named.name];
15124
+ if (old instanceof Alias)
15125
+ old.makeInline();
15090
15126
  this.known[named.name] = named;
15091
15127
  this.allow.add(named.name);
15092
15128
  return this;
@@ -15186,7 +15222,9 @@ var Parser = class {
15186
15222
  * @return {SKI}
15187
15223
  */
15188
15224
  remove(name) {
15189
- this.known[name].outdated = true;
15225
+ const old = this.known[name];
15226
+ if (old instanceof Alias)
15227
+ old.makeInline();
15190
15228
  delete this.known[name];
15191
15229
  this.allow.delete(name);
15192
15230
  return this;
@@ -15271,7 +15309,7 @@ var Parser = class {
15271
15309
  let expr = new Empty();
15272
15310
  for (const item of lines) {
15273
15311
  if (expr instanceof Alias)
15274
- expr.outdated = true;
15312
+ expr.makeInline();
15275
15313
  const def = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s);
15276
15314
  if (def && def[2] === "")
15277
15315
  expr = new FreeVar(def[1], options.scope ?? FreeVar.global);
@@ -15285,7 +15323,7 @@ var Parser = class {
15285
15323
  }
15286
15324
  if (this.addContext) {
15287
15325
  if (expr instanceof Named)
15288
- expr = new Alias(expr.name, expr, { outdated: true });
15326
+ expr = new Alias(expr.name, expr, { inline: true });
15289
15327
  expr.context = {
15290
15328
  env: { ...this.getTerms(), ...jar },
15291
15329
  // also contains pre-parsed terms
@@ -15393,7 +15431,7 @@ var Quest = class {
15393
15431
  for (const term of env ?? []) {
15394
15432
  const expr = this.engineFull.parse(term, { env: jar, scope: this });
15395
15433
  if (expr instanceof Alias)
15396
- this.env[expr.name] = new Alias(expr.name, expr.impl, { terminal: true, canonize: false });
15434
+ this.env[expr.name] = new Alias(expr.name, expr.impl, { canonize: false });
15397
15435
  else if (expr instanceof FreeVar)
15398
15436
  this.env[expr.name] = expr;
15399
15437
  else
@@ -15482,7 +15520,7 @@ var Quest = class {
15482
15520
  if (e instanceof Named && arsenal[e.name] === e)
15483
15521
  return control.prune(a + 1);
15484
15522
  });
15485
- const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { terminal: true, canonize: false });
15523
+ const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { canonize: false });
15486
15524
  jar[spec.name] = expr;
15487
15525
  prepared.push(expr);
15488
15526
  }