@dallaylaen/ski-interpreter 2.2.0 → 2.2.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,19 @@ 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.2.1] - 2026-02-22
9
+
10
+ ### Added
11
+
12
+ - `Expr.diag()` outputs an expression as an indented tree (breadth-first) with class names and variables labeled for deduplication.
13
+ - `lib/ski-quest.min.js` bundle to create quest pages.
14
+ - The [playground](https://dallaylaen.github.io/ski-interpreter/playground.html) gets history!
15
+
16
+ ### Fixed
17
+
18
+ - Greatly improved type definitions.
19
+ - Quests calculate solution complexity via `fold()`.
20
+
8
21
  ## [2.2.0] - 2026-02-14
9
22
 
10
23
  ### BREAKING CHANGES
@@ -20,6 +33,7 @@ that give the initial expression when applied
20
33
  from left to right: `((a, b), (c, d)) => [a, b, (c, d)]`
21
34
  - Parser: Support for chained assignments (`'foo=bar=baz'` expressions)
22
35
  - Parser: Support for multi-line comment syntax (`/* comments */`)
36
+ - `SKI_REPL=1 node -r @dallaylaen/ski-interpreter` will now start a REPL with the `SKI` class available globally.
23
37
 
24
38
  ### Changed
25
39
 
package/README.md CHANGED
@@ -68,16 +68,19 @@ all free variables are bound.
68
68
  This page contains small tasks of increasing complexity.
69
69
  Each task requires the user to build a combinator with specific properties.
70
70
 
71
- # CLI
72
-
73
- REPL comes with the package as [bin/ski.js](bin/ski.js).
74
-
75
71
  # Installation
76
72
 
77
73
  ```bash
78
74
  npm install @dallaylaen/ski-interpreter
79
75
  ```
80
76
 
77
+ # CLI
78
+
79
+ REPL comes with the package as [bin/ski.js](bin/ski.js).
80
+
81
+ Running `SKI_REPL=1 node -r @dallaylaen/ski-interpreter/bin/ski.js`
82
+ will start a node shell with the `SKI` class available as a global variable.
83
+
81
84
  # Usage
82
85
 
83
86
  ## A minimal example
@@ -149,6 +152,10 @@ const lambdaSteps = [...skiExpr.toLambda()];
149
152
  The `format` methods of the `Expr` class supports
150
153
  a number of options, see [the source code](src/expr.js) for details.
151
154
 
155
+ `expr.diag()` will instead output indented
156
+ expression tree (breadth-first) with class information
157
+ and variables labeled for disambiguation.
158
+
152
159
  ## Variable scoping
153
160
 
154
161
  By default, parsed free variables are global and equal to any other variable with the same name.
@@ -183,6 +190,9 @@ expr.traverse(e => e.equals(SKI.I) ? SKI.S.apply(SKI.K, SKI.K) : null);
183
190
  // replaces all I's with S K K
184
191
  // here a returned `Expr` object replaces the subexpression,
185
192
  // whereas `null` means "leave it alone and descend if possible"
193
+
194
+ expr.fold(0, (acc, e) => acc + (e.equals(SKI.K) ? acc+1 : acc));
195
+ // counts the number of K's in the expression
186
196
  ```
187
197
 
188
198
  ## Test cases
@@ -209,6 +219,17 @@ q.check('K(K(y x))') // nope! the variable scopes won't match
209
219
 
210
220
  See [quest page data](docs/quest-data/) for more examples.
211
221
 
222
+ # Package contents
223
+
224
+ * `lib/ski-interpreter.cjs.js` - main entry point for Node.js;
225
+ * `lib/ski-interpreter.esm.js` - main entry point for ES modules;
226
+ * `lib/ski-interpreter.min.js` - minified version for browsers;
227
+ * `lib/ski-quest.min.js` - script with the interpreter
228
+ plus `QuestBox`, `QuestChapter`, and `QuestPage` classes
229
+ for building interactive quest pages from JSON-encoded quest data;
230
+ * `bin/ski.js` - a CLI REPL;
231
+ * `types` - TypeScript type definitions.
232
+
212
233
  # Thanks
213
234
 
214
235
  * [@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.
@@ -59,24 +59,34 @@ var require_internal = __commonJS({
59
59
  }
60
60
  return out;
61
61
  }
62
- var ActionWrapper = class {
62
+ var TraverseControl = class {
63
63
  /**
64
+ * @desc A wrapper for values returned by fold/traverse callbacks
65
+ * which instructs the traversal to alter its behavior while
66
+ * retaining the value in question.
67
+ *
68
+ * This class is instantiated internally be `SKI.control.*` functions,
69
+ * and is not intended to be used directly by client code.
70
+ *
64
71
  * @template T
65
72
  * @param {T} value
66
- * @param {string} action
73
+ * @param {function(T): TraverseControl<T>} decoration
67
74
  */
68
- constructor(value, action) {
75
+ constructor(value, decoration) {
69
76
  this.value = value;
70
- this.action = action;
77
+ this.decoration = decoration;
71
78
  }
72
79
  };
73
80
  function unwrap(value) {
74
- if (value instanceof ActionWrapper)
75
- return [value.value ?? void 0, value.action];
81
+ if (value instanceof TraverseControl)
82
+ return [value.value ?? void 0, value.decoration];
76
83
  return [value ?? void 0, void 0];
77
84
  }
78
- function prepareWrapper(action) {
79
- return (value) => new ActionWrapper(value, action);
85
+ function prepareWrapper(label) {
86
+ const fun = (value) => new TraverseControl(value, fun);
87
+ fun.label = label;
88
+ fun.toString = () => "TraverseControl::" + label;
89
+ return fun;
80
90
  }
81
91
  module2.exports = { Tokenizer, restrict, unwrap, prepareWrapper };
82
92
  }
@@ -94,6 +104,7 @@ var require_expr = __commonJS({
94
104
  var control = {
95
105
  descend: prepareWrapper("descend"),
96
106
  prune: prepareWrapper("prune"),
107
+ redo: prepareWrapper("redo"),
97
108
  stop: prepareWrapper("stop")
98
109
  };
99
110
  var Expr = class _Expr {
@@ -109,7 +120,7 @@ var require_expr = __commonJS({
109
120
  * env?: { [key: string]: Expr },
110
121
  * src?: string,
111
122
  * parser: object,
112
- * }} [context] // TODO proper type
123
+ * }} [context]
113
124
  */
114
125
  constructor() {
115
126
  if (new.target === _Expr)
@@ -137,15 +148,21 @@ var require_expr = __commonJS({
137
148
  return e.impl.expand();
138
149
  }) ?? this;
139
150
  }
151
+ /**
152
+ * @desc Returns true if the expression contains only free variables and applications, false otherwise.
153
+ * @returns {boolean}
154
+ */
140
155
  freeOnly() {
141
156
  return !this.any((e) => !(e instanceof FreeVar || e instanceof App));
142
157
  }
143
158
  /**
144
159
  * @desc Traverse the expression tree, applying change() to each node.
145
160
  * If change() returns an Expr, the node is replaced with that value.
146
- * Otherwise, the node is left descended further (if applicable)
161
+ * Otherwise, the node is descended further (if applicable)
147
162
  * or left unchanged.
148
163
  *
164
+ * The traversal order is leftmost-outermost (LO), i.e. the same order as reduction steps are taken.
165
+ *
149
166
  * Returns null if no changes were made, or the new expression otherwise.
150
167
  *
151
168
  * @param {(e:Expr) => (Expr|null)} change
@@ -179,18 +196,25 @@ var require_expr = __commonJS({
179
196
  * @experimental
180
197
  * @template T
181
198
  * @param {T} initial
182
- * @param {(acc: T, expr: Expr) => ActionWrapper<T>} combine
199
+ * @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
183
200
  * @returns {T}
184
201
  */
185
202
  fold(initial, combine) {
186
203
  const [value, _] = unwrap(this._fold(initial, combine));
187
204
  return value ?? initial;
188
205
  }
206
+ /**
207
+ * @template T
208
+ * @param {T} initial
209
+ * @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
210
+ * @returns {TraverseValue<T>}
211
+ * @private
212
+ */
189
213
  _fold(initial, combine) {
190
214
  return combine(initial, this);
191
215
  }
192
216
  /**
193
- * @desc rough estimate of the complexity of the term
217
+ * @desc rough estimate of the term's complexity
194
218
  * @return {number}
195
219
  */
196
220
  weight() {
@@ -232,7 +256,15 @@ var require_expr = __commonJS({
232
256
  * @param {{max: number, maxArgs: number, index: number}} options
233
257
  * @param {FreeVar[]} preArgs
234
258
  * @param {number} steps
235
- * @returns {{normal: boolean, steps: number}|{normal: boolean, steps: number}|{normal: boolean, steps: number, expr: Lambda|*, arity?: *, skip?: Set<any>, dup?: Set<any>, duplicate, discard, proper: boolean}|*|{normal: boolean, steps: number}}
259
+ * @returns {{
260
+ * normal: boolean,
261
+ * steps: number,
262
+ * expr?: Expr,
263
+ * arity?: number,
264
+ * skip?: Set<number>,
265
+ * dup?: Set<number>,
266
+ * duplicate, discard, proper: boolean
267
+ * }
236
268
  * @private
237
269
  */
238
270
  _infer(options, preArgs = [], steps = 0) {
@@ -289,7 +321,7 @@ var require_expr = __commonJS({
289
321
  * latin?: number,
290
322
  * }} options
291
323
  * @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
292
- * @return {IterableIterator<{expr: Expr, steps: number?, comment: string?}>}
324
+ * @return {IterableIterator<{expr: Expr, steps?: number, comment?: string}>}
293
325
  */
294
326
  *toLambda(options = {}) {
295
327
  const expr = this.traverse((e) => {
@@ -382,7 +414,7 @@ var require_expr = __commonJS({
382
414
  * @desc Run uninterrupted sequence of step() applications
383
415
  * until the expression is irreducible, or max number of steps is reached.
384
416
  * Default number of steps = 1000.
385
- * @param {{max: number?, steps: number?, throw: boolean?}|Expr} [opt]
417
+ * @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
386
418
  * @param {Expr} args
387
419
  * @return {{expr: Expr, steps: number, final: boolean}}
388
420
  */
@@ -411,7 +443,7 @@ var require_expr = __commonJS({
411
443
  /**
412
444
  * Execute step() while possible, yielding a brief description of events after each step.
413
445
  * Mnemonics: like run() but slower.
414
- * @param {{max: number?}} options
446
+ * @param {{max?: number}} options
415
447
  * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
416
448
  */
417
449
  *walk(options = {}) {
@@ -586,6 +618,42 @@ var require_expr = __commonJS({
586
618
  _format(options, nargs) {
587
619
  throw new Error("No _format() method defined in class " + this.constructor.name);
588
620
  }
621
+ /**
622
+ * @desc Returns a string representation of the expression tree, with indentation to show structure.
623
+ *
624
+ * Applications are flattened to avoid excessive nesting.
625
+ * Variables include ids to distinguish different instances of the same variable name.
626
+ *
627
+ * May be useful for debugging.
628
+ *
629
+ * @returns {string}
630
+ *
631
+ * @example
632
+ * > console.log(ski.parse('C 5 x (x->x x)').diag())
633
+ * App:
634
+ * Native: C
635
+ * Church: 5
636
+ * FreeVar: x[53]
637
+ * Lambda (x[54]):
638
+ * App:
639
+ * FreeVar: x[54]
640
+ * FreeVar: x[54]
641
+ */
642
+ diag() {
643
+ const rec = (e, indent) => {
644
+ if (e instanceof App)
645
+ return [indent + "App:", ...e.unroll().flatMap((s) => rec(s, indent + " "))];
646
+ if (e instanceof Lambda)
647
+ return [`${indent}Lambda (${e.arg}[${e.arg.id}]):`, ...rec(e.impl, indent + " ")];
648
+ if (e instanceof Alias)
649
+ return [`Alias (${e.name}):`, ...rec(e.impl, indent + " ")];
650
+ if (e instanceof FreeVar)
651
+ return [`${indent}FreeVar: ${e.name}[${e.id}]`];
652
+ return [`${indent}${e.constructor.name}: ${e}`];
653
+ };
654
+ const out = rec(this, "");
655
+ return out.join("\n");
656
+ }
589
657
  /**
590
658
  * @desc Convert the expression to a JSON-serializable format.
591
659
  * @returns {string}
@@ -662,15 +730,15 @@ var require_expr = __commonJS({
662
730
  }
663
731
  _fold(initial, combine) {
664
732
  const [value = initial, action = "descend"] = unwrap(combine(initial, this));
665
- if (action === "prune")
733
+ if (action === control.prune)
666
734
  return value;
667
- if (action === "stop")
735
+ if (action === control.stop)
668
736
  return control.stop(value);
669
737
  const [fValue = value, fAction = "descend"] = unwrap(this.fun._fold(value, combine));
670
- if (fAction === "stop")
738
+ if (fAction === control.stop)
671
739
  return control.stop(fValue);
672
740
  const [aValue = fValue, aAction = "descend"] = unwrap(this.arg._fold(fValue, combine));
673
- if (aAction === "stop")
741
+ if (aAction === control.stop)
674
742
  return control.stop(aValue);
675
743
  return aValue;
676
744
  }
@@ -714,10 +782,6 @@ var require_expr = __commonJS({
714
782
  unroll() {
715
783
  return [...this.fun.unroll(), this.arg];
716
784
  }
717
- /**
718
- * @desc Convert the expression to SKI combinatory logic
719
- * @return {Expr}
720
- */
721
785
  _rski(options) {
722
786
  if (options.steps >= options.max)
723
787
  return this;
@@ -915,12 +979,12 @@ var require_expr = __commonJS({
915
979
  }
916
980
  _fold(initial, combine) {
917
981
  const [value = initial, action = "descend"] = unwrap(combine(initial, this));
918
- if (action === "prune")
982
+ if (action === control.prune)
919
983
  return value;
920
- if (action === "stop")
984
+ if (action === control.stop)
921
985
  return control.stop(value);
922
986
  const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
923
- if (iAction === "stop")
987
+ if (iAction === control.stop)
924
988
  return control.stop(iValue);
925
989
  return iValue ?? value;
926
990
  }
@@ -1016,7 +1080,7 @@ var require_expr = __commonJS({
1016
1080
  *
1017
1081
  * @param {String} name
1018
1082
  * @param {Expr} impl
1019
- * @param {{canonize: boolean?, max: number?, maxArgs: number?, note: string?, terminal: boolean?}} [options]
1083
+ * @param {{canonize?: boolean, max?: number, maxArgs?: number, note?: string, terminal?: boolean}} [options]
1020
1084
  */
1021
1085
  constructor(name, impl, options = {}) {
1022
1086
  super(name);
@@ -1032,6 +1096,15 @@ var require_expr = __commonJS({
1032
1096
  this.canonical = guess.expr;
1033
1097
  this.invoke = waitn(impl, this.arity);
1034
1098
  }
1099
+ /**
1100
+ * @property {boolean} [outdated] - whether the alias is outdated
1101
+ * and should be replaced with its definition when encountered.
1102
+ * @property {boolean} [terminal] - whether the alias should behave like a standalone term
1103
+ * // TODO better name?
1104
+ * @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
1105
+ * @property {number} [arity] - the number of arguments the alias waits for before expanding
1106
+ * @property {Expr} [canonical] - equivalent lambda term.
1107
+ */
1035
1108
  weight() {
1036
1109
  return this.terminal ? 1 : this.impl.weight();
1037
1110
  }
@@ -1042,13 +1115,13 @@ var require_expr = __commonJS({
1042
1115
  return predicate(this) || this.impl.any(predicate);
1043
1116
  }
1044
1117
  _fold(initial, combine) {
1045
- const [value = initial, action = "descend"] = unwrap(combine(initial, this));
1046
- if (action === "prune")
1118
+ const [value = initial, action] = unwrap(combine(initial, this));
1119
+ if (action === control.prune)
1047
1120
  return value;
1048
- if (action === "stop")
1121
+ if (action === control.stop)
1049
1122
  return control.stop(value);
1050
1123
  const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
1051
- if (iAction === "stop")
1124
+ if (iAction === control.stop)
1052
1125
  return control.stop(iValue);
1053
1126
  return iValue ?? value;
1054
1127
  }
@@ -1060,9 +1133,10 @@ var require_expr = __commonJS({
1060
1133
  _infer(options, preArgs = [], steps = 0) {
1061
1134
  return this.impl._infer(options, preArgs, steps);
1062
1135
  }
1136
+ // DO NOT REMOVE TYPE or tsc chokes with
1137
+ // TS2527: The inferred type of 'Alias' references an inaccessible 'this' type.
1063
1138
  /**
1064
- *
1065
- * @return {{expr: Expr, steps: number}}
1139
+ * @return {{expr: Expr, steps: number, changed: boolean}}
1066
1140
  */
1067
1141
  step() {
1068
1142
  if (this.arity > 0)
@@ -1337,6 +1411,13 @@ var require_parser = __commonJS({
1337
1411
  this.allow.add(term.name);
1338
1412
  return this;
1339
1413
  }
1414
+ /**
1415
+ * @desc Internal helper for add() that creates an Alias or Native term from the given arguments.
1416
+ * @param {Alias|string} term
1417
+ * @param {string|Expr|function(Expr):Partial} impl
1418
+ * @returns {Native|Alias}
1419
+ * @private
1420
+ */
1340
1421
  _named(term, impl) {
1341
1422
  if (term instanceof Alias)
1342
1423
  return new Alias(term.name, term.impl, { canonize: true });
@@ -1352,6 +1433,16 @@ var require_parser = __commonJS({
1352
1433
  return new Native(term, impl);
1353
1434
  throw new Error("add(): impl must be an Expr, a string, or a function with a signature Expr => ... => Expr");
1354
1435
  }
1436
+ /**
1437
+ * @desc Declare a new term if it is not known, otherwise just allow it.
1438
+ * Currently only used by quests.
1439
+ * Use with caution, this function may change its signature, behavior, or even be removed in the future.
1440
+ *
1441
+ * @experimental
1442
+ * @param {string|Alias} name
1443
+ * @param {string|Expr|function(Expr):Partial} impl
1444
+ * @returns {SKI}
1445
+ */
1355
1446
  maybeAdd(name, impl) {
1356
1447
  if (this.known[name])
1357
1448
  this.allow.add(name);
@@ -1363,7 +1454,7 @@ var require_parser = __commonJS({
1363
1454
  * @desc Declare and remove multiple terms at once
1364
1455
  * term=impl adds term
1365
1456
  * term= removes term
1366
- * @param {string[]]} list
1457
+ * @param {string[]} list
1367
1458
  * @return {SKI} chainable
1368
1459
  */
1369
1460
  bulkAdd(list) {
@@ -1484,11 +1575,11 @@ var require_parser = __commonJS({
1484
1575
  return out;
1485
1576
  }
1486
1577
  /**
1487
- *
1578
+ * @template T
1488
1579
  * @param {string} source
1489
1580
  * @param {Object} [options]
1490
1581
  * @param {{[keys: string]: Expr}} [options.env]
1491
- * @param {any} [options.scope]
1582
+ * @param {T} [options.scope]
1492
1583
  * @param {boolean} [options.numbers]
1493
1584
  * @param {boolean} [options.lambdas]
1494
1585
  * @param {string} [options.allow]
@@ -1524,12 +1615,14 @@ var require_parser = __commonJS({
1524
1615
  return expr;
1525
1616
  }
1526
1617
  /**
1527
- *
1618
+ * @desc Parse a single line of source code, without splitting it into declarations.
1619
+ * Internal, always use parse() instead.
1620
+ * @template T
1528
1621
  * @param {String} source S(KI)I
1529
1622
  * @param {{[keys: string]: Expr}} env
1530
1623
  * @param {Object} [options]
1531
1624
  * @param {{[keys: string]: Expr}} [options.env] - unused, see 'env' argument
1532
- * @param {any} [options.scope]
1625
+ * @param {T} [options.scope]
1533
1626
  * @param {boolean} [options.numbers]
1534
1627
  * @param {boolean} [options.lambdas]
1535
1628
  * @param {string} [options.allow]
@@ -1593,12 +1686,12 @@ var require_parser = __commonJS({
1593
1686
  };
1594
1687
  }
1595
1688
  };
1596
- SKI2.vars = function(context = {}) {
1689
+ SKI2.vars = function(scope = {}) {
1597
1690
  const cache = {};
1598
1691
  return new Proxy({}, {
1599
1692
  get: (target, name) => {
1600
1693
  if (!(name in cache))
1601
- cache[name] = new FreeVar(name, context);
1694
+ cache[name] = new FreeVar(name, scope);
1602
1695
  return cache[name];
1603
1696
  }
1604
1697
  });
@@ -1620,40 +1713,23 @@ var require_quest = __commonJS({
1620
1713
  var { Expr, FreeVar, Alias, Lambda } = SKI2.classes;
1621
1714
  var Quest2 = class {
1622
1715
  /**
1623
- * @description A combinator problem with a set of test cases for the proposed solution.
1624
- * @param {{
1625
- * input: InputSpec | InputSpec[],
1626
- * cases: TestCase[],
1627
- *
1628
- * // the rest is optional
1629
-
1630
- * allow?: string,
1631
- * numbers?: boolean,
1632
- * env?: string[],
1633
- * engine?: SKI,
1634
- * engineFull?: SKI,
1635
- *
1636
- * // metadata, also any fields not listed here will go to quest.meta.???
1637
- * id?: string|number,
1638
- * name?: string,
1639
- * intro?: string|string[], // multiple strings will be concatenated with spaces
1640
- * }} options
1641
- *
1642
- * @example const quest = new Quest({
1643
- * input: 'identity',
1644
- * cases: [
1645
- * ['identity x', 'x'],
1646
- * ],
1647
- * allow: 'SK',
1648
- * intro: 'Find a combinator that behaves like the identity function.',
1649
- * });
1650
- * quest.check('S K K'); // { pass: true, details: [...], ... }
1651
- * quest.check('K S'); // { pass: false, details: [...], ... }
1652
- * quest.check('K x'); // fail! internal variable x is not equal to free variable x,
1653
- * // despite having the same name.
1654
- * quest.check('I'); // fail! I not in the allowed list.
1655
- */
1656
- constructor(options = {}) {
1716
+ * @description A combinator problem with a set of test cases for the proposed solution.
1717
+ * @param {QuestSpec} options
1718
+ * @example const quest = new Quest({
1719
+ * input: 'identity',
1720
+ * cases: [
1721
+ * ['identity x', 'x'],
1722
+ * ],
1723
+ * allow: 'SK',
1724
+ * intro: 'Find a combinator that behaves like the identity function.',
1725
+ * });
1726
+ * quest.check('S K K'); // { pass: true, details: [...], ... }
1727
+ * quest.check('K S'); // { pass: false, details: [...], ... }
1728
+ * quest.check('K x'); // fail! internal variable x is not equal to free variable x,
1729
+ * // despite having the same name.
1730
+ * quest.check('I'); // fail! I not in the allowed list.
1731
+ */
1732
+ constructor(options) {
1657
1733
  const { input, cases, allow, numbers, lambdas, engine, engineFull, ...meta } = options;
1658
1734
  const env = options.env ?? options.vars;
1659
1735
  this.engine = engine ?? new SKI2();
@@ -1746,7 +1822,11 @@ var require_quest = __commonJS({
1746
1822
  numbers: spec.numbers ?? this.restrict.numbers,
1747
1823
  lambdas: spec.lambdas ?? this.restrict.lambdas
1748
1824
  });
1749
- weight += impl.weight();
1825
+ const arsenal = { ...this.engine.getTerms(), ...jar };
1826
+ weight += impl.fold(0, (a, e) => {
1827
+ if (e instanceof SKI2.classes.Named && arsenal[e.name] === e)
1828
+ return SKI2.control.prune(a + 1);
1829
+ });
1750
1830
  const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { terminal: true, canonize: false });
1751
1831
  jar[spec.name] = expr;
1752
1832
  prepared.push(expr);
@@ -1819,10 +1899,10 @@ var require_quest = __commonJS({
1819
1899
  /**
1820
1900
  * @param {FreeVar[]} input
1821
1901
  * @param {{
1822
- * max: number?,
1823
- * note: string?,
1824
- * env: {string: Expr}?,
1825
- * engine: SKI?
1902
+ * max?: number,
1903
+ * note?: string,
1904
+ * env?: {string: Expr},
1905
+ * engine?: SKI
1826
1906
  * }} options
1827
1907
  * @param {[e1: string, e2: string]} terms
1828
1908
  */