@dallaylaen/ski-interpreter 2.3.3 → 2.4.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.
@@ -1,2268 +1,2139 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
2
- var __commonJS = (cb, mod) => function __require() {
3
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
4
9
  };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
5
19
 
6
- // src/internal.js
7
- var require_internal = __commonJS({
8
- "src/internal.js"(exports2, module2) {
9
- var Tokenizer = class {
10
- /**
11
- * @desc Create a tokenizer that splits strings into tokens according to the given terms.
12
- * The terms are interpreted as regular expressions, and are sorted by length
13
- * to ensure that longer matches are preferred over shorter ones.
14
- * @param {...string|RegExp} terms
15
- */
16
- constructor(...terms) {
17
- const src = "$|(\\s+)|" + terms.map((s) => "(?:" + s + ")").sort((a, b) => b.length - a.length).join("|");
18
- this.rex = new RegExp(src, "gys");
19
- }
20
- /**
21
- * @desc Split the given string into tokens according to the terms specified in the constructor.
22
- * @param {string} str
23
- * @return {string[]}
24
- */
25
- split(str) {
26
- this.rex.lastIndex = 0;
27
- const list = [...str.matchAll(this.rex)];
28
- const eol = list.pop();
29
- const last = eol?.index ?? 0;
30
- if (last !== str.length) {
31
- throw new Error("Unknown tokens at pos " + last + "/" + str.length + " starting with " + str.substring(last));
32
- }
33
- return list.filter((x) => x[1] === void 0).map((x) => x[0]);
34
- }
35
- };
36
- var tokRestrict = new Tokenizer("[-=+]", "[A-Z]", "\\b[a-z_][a-z_0-9]*\\b");
37
- function restrict(set, spec) {
38
- if (!spec)
39
- return set;
40
- let out = /* @__PURE__ */ new Set([...set]);
41
- const act = {
42
- "=": (sym) => {
43
- out = /* @__PURE__ */ new Set([sym]);
44
- mode = "+";
45
- },
46
- "+": (sym) => {
47
- out.add(sym);
48
- },
49
- "-": (sym) => {
50
- out.delete(sym);
51
- }
52
- };
53
- let mode = "=";
54
- for (const sym of tokRestrict.split(spec)) {
55
- if (act[sym])
56
- mode = sym;
57
- else
58
- act[mode](sym);
59
- }
60
- return out;
61
- }
62
- var TraverseControl = class {
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
- *
71
- * @template T
72
- * @param {T} value
73
- * @param {function(T): TraverseControl<T>} decoration
74
- */
75
- constructor(value, decoration) {
76
- this.value = value;
77
- this.decoration = decoration;
78
- }
79
- };
80
- function unwrap(value) {
81
- if (value instanceof TraverseControl)
82
- return [value.value ?? void 0, value.decoration];
83
- return [value ?? void 0, void 0];
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SKI: () => SKI
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/internal.ts
28
+ var Tokenizer = class {
29
+ constructor(...terms) {
30
+ const src = "$|(\\s+)|" + terms.map((s) => "(?:" + s + ")").sort((a, b) => b.length - a.length).join("|");
31
+ this.rex = new RegExp(src, "gys");
32
+ }
33
+ /**
34
+ * @desc Split the given string into tokens according to the terms specified in the constructor.
35
+ * @param {string} str
36
+ * @return {string[]}
37
+ */
38
+ split(str) {
39
+ this.rex.lastIndex = 0;
40
+ const list = [...str.matchAll(this.rex)];
41
+ const eol = list.pop();
42
+ const last = eol?.index ?? 0;
43
+ if (last !== str.length) {
44
+ throw new Error("Unknown tokens at pos " + last + "/" + str.length + " starting with " + str.substring(last));
84
45
  }
85
- function prepareWrapper(label) {
86
- const fun = (value) => new TraverseControl(value, fun);
87
- fun.label = label;
88
- fun.toString = () => "TraverseControl::" + label;
89
- return fun;
46
+ return list.filter((x) => x[1] === void 0).map((x) => x[0]);
47
+ }
48
+ };
49
+ var tokRestrict = new Tokenizer("[-=+]", "[A-Z]", "\\b[a-z_][a-z_0-9]*\\b");
50
+ function restrict(set, spec) {
51
+ if (!spec)
52
+ return set;
53
+ let out = /* @__PURE__ */ new Set([...set]);
54
+ const act = {
55
+ "=": (sym) => {
56
+ out = /* @__PURE__ */ new Set([sym]);
57
+ mode = "+";
58
+ },
59
+ "+": (sym) => {
60
+ out.add(sym);
61
+ },
62
+ "-": (sym) => {
63
+ out.delete(sym);
90
64
  }
91
- module2.exports = { Tokenizer, restrict, unwrap, prepareWrapper };
65
+ };
66
+ let mode = "=";
67
+ for (const sym of tokRestrict.split(spec)) {
68
+ if (act[sym])
69
+ mode = sym;
70
+ else
71
+ act[mode](sym);
92
72
  }
93
- });
73
+ return out;
74
+ }
75
+ var TraverseControl = class {
76
+ constructor(value, decoration) {
77
+ this.value = value;
78
+ this.decoration = decoration;
79
+ }
80
+ };
81
+ function unwrap(value) {
82
+ if (value instanceof TraverseControl)
83
+ return [value.value ?? void 0, value.decoration];
84
+ return [value ?? void 0, void 0];
85
+ }
86
+ function prepareWrapper(label) {
87
+ const fun = (value) => new TraverseControl(value, fun);
88
+ fun.label = label;
89
+ fun.toString = () => "TraverseControl::" + label;
90
+ return fun;
91
+ }
94
92
 
95
- // src/expr.js
96
- var require_expr = __commonJS({
97
- "src/expr.js"(exports2, module2) {
98
- "use strict";
99
- var { unwrap, prepareWrapper } = require_internal();
100
- var DEFAULTS = {
101
- max: 1e3,
102
- maxArgs: 32
103
- };
104
- var ORDER = {
105
- "leftmost-outermost": "LO",
106
- "leftmost-innermost": "LI",
107
- LO: "LO",
108
- LI: "LI"
109
- };
110
- var control = {
111
- descend: prepareWrapper("descend"),
112
- prune: prepareWrapper("prune"),
113
- redo: prepareWrapper("redo"),
114
- stop: prepareWrapper("stop")
115
- };
116
- var native = {};
117
- var Expr = class _Expr {
118
- /**
119
- * @descr A combinatory logic expression.
120
- *
121
- * Applications, variables, and other terms like combinators per se
122
- * are subclasses of this class.
123
- *
124
- * @abstract
125
- * @property {{
126
- * scope?: any,
127
- * env?: { [key: string]: Expr },
128
- * src?: string,
129
- * parser: object,
130
- * }} [context]
131
- * @property {number} [arity] - number of arguments the term is waiting for (if known)
132
- * @property {string} [note] - a brief description what the term does
133
- * @property {string} [fancyName] - how to display in html mode, e.g. &phi; instead of 'f'
134
- * Typically only applicable to descendants of Named.
135
- * @property {TermInfo} [props] - properties inferred from the term's behavior
136
- */
137
- /**
138
- *
139
- * @desc Define properties of the term based on user supplied options and/or inference results.
140
- * Typically useful for declaring Native and Alias terms.
141
- * @private
142
- * @param {Object} options
143
- * @param {string} [options.note] - a brief description what the term does
144
- * @param {number} [options.arity] - number of arguments the term is waiting for (if known)
145
- * @param {string} [options.fancy] - how to display in html mode, e.g. &phi; instead of 'f'
146
- * @param {boolean} [options.canonize] - whether to try to infer the properties
147
- * @param {number} [options.max] - maximum number of steps for inference, if canonize is true
148
- * @param {number} [options.maxArgs] - maximum number of arguments for inference, if canonize is true
149
- * @return {this}
150
- */
151
- _setup(options = {}) {
152
- if (options.fancy !== void 0)
153
- this.fancyName = options.fancyName;
154
- if (options.note !== void 0)
155
- this.note = options.note;
156
- if (options.arity !== void 0)
157
- this.arity = options.arity;
158
- if (options.canonize) {
159
- const guess = this.infer(options);
160
- if (guess.normal) {
161
- this.arity = this.arity ?? guess.arity;
162
- this.note = this.note ?? guess.expr.format({ html: true, lambda: ["", " &mapsto; ", ""] });
163
- delete guess.steps;
164
- this.props = guess;
165
- }
166
- }
167
- return this;
168
- }
169
- /**
170
- * @desc apply self to zero or more terms and return the resulting term,
171
- * without performing any calculations whatsoever
172
- * @param {Expr} args
173
- * @return {Expr}
174
- */
175
- apply(...args) {
176
- let expr = this;
177
- for (const arg of args)
178
- expr = new App(expr, arg);
179
- return expr;
180
- }
181
- /**
182
- * @desc Replace all aliases in the expression with their definitions, recursively.
183
- * @return {Expr}
184
- */
185
- expand() {
186
- return this.traverse((e) => {
187
- if (e instanceof Alias)
188
- return e.impl.expand();
189
- }) ?? this;
190
- }
191
- /**
192
- * @desc Returns true if the expression contains only free variables and applications, false otherwise.
193
- * @returns {boolean}
194
- */
195
- freeOnly() {
196
- return !this.any((e) => !(e instanceof FreeVar || e instanceof App));
93
+ // src/expr.ts
94
+ var DEFAULTS = {
95
+ max: 1e3,
96
+ maxArgs: 32
97
+ };
98
+ var ORDER = {
99
+ "leftmost-outermost": "LO",
100
+ "leftmost-innermost": "LI",
101
+ LO: "LO",
102
+ LI: "LI"
103
+ };
104
+ var control = {
105
+ descend: prepareWrapper("descend"),
106
+ prune: prepareWrapper("prune"),
107
+ redo: prepareWrapper("redo"),
108
+ stop: prepareWrapper("stop")
109
+ };
110
+ var native = {};
111
+ var Expr = class _Expr {
112
+ static {
113
+ this.control = control;
114
+ }
115
+ static {
116
+ this.native = native;
117
+ }
118
+ /**
119
+ *
120
+ * @desc Define properties of the term based on user supplied options and/or inference results.
121
+ * Typically useful for declaring Native and Alias terms.
122
+ * @private
123
+ * @param {Object} options
124
+ * @param {string} [options.note] - a brief description what the term does
125
+ * @param {number} [options.arity] - number of arguments the term is waiting for (if known)
126
+ * @param {string} [options.fancy] - how to display in html mode, e.g. &phi; instead of 'f'
127
+ * @param {boolean} [options.canonize] - whether to try to infer the properties
128
+ * @param {number} [options.max] - maximum number of steps for inference, if canonize is true
129
+ * @param {number} [options.maxArgs] - maximum number of arguments for inference, if canonize is true
130
+ * @return {this}
131
+ */
132
+ _setup(options = {}) {
133
+ if (options.fancy !== void 0 && this instanceof Named)
134
+ this.fancyName = options.fancy;
135
+ if (options.note !== void 0)
136
+ this.note = options.note;
137
+ if (options.arity !== void 0)
138
+ this.arity = options.arity;
139
+ if (options.canonize) {
140
+ const guess = this.infer(options);
141
+ if (guess.normal) {
142
+ this.arity = this.arity ?? guess.arity;
143
+ this.note = this.note ?? guess.expr.format({ html: true, lambda: ["", " &mapsto; ", ""] });
144
+ delete guess.steps;
145
+ this.props = guess;
197
146
  }
198
- /**
199
- * @desc Traverse the expression tree, applying change() to each node.
200
- * If change() returns an Expr, the node is replaced with that value.
201
- * A null/undefined value is interpreted as
202
- * "descend further if applicable, or leave the node unchanged".
203
- *
204
- * Returned values may be decorated:
205
- *
206
- * SKI.control.prune will suppress further descending even if nothing was returned
207
- * SKI.control.stop will terminate further changes.
208
- * SKI.control.redo will apply the callback to the returned subtree, recursively.
209
- *
210
- * Note that if redo was applied at least once to a subtree, a null return from the same subtree
211
- * will be replaced by the last non-null value returned.
212
- *
213
- * The traversal order is leftmost-outermost, unless options.order = 'leftmost-innermost' is specified.
214
- * Short aliases 'LO' and 'LI' (case-sensitive) are also accepted.
215
- *
216
- * Returns null if no changes were made, or the new expression otherwise.
217
- *
218
- * @param {{
219
- * order?: 'LO' | 'LI' | 'leftmost-outermost' | 'leftmost-innermost',
220
- * }} [options]
221
- * @param {(e:Expr) => TraverseValue<Expr>} change
222
- * @returns {Expr|null}
223
- */
224
- traverse(options, change) {
225
- if (typeof options === "function") {
226
- change = options;
227
- options = {};
147
+ }
148
+ return this;
149
+ }
150
+ /**
151
+ * @desc apply self to zero or more terms and return the resulting term,
152
+ * without performing any calculations whatsoever
153
+ * @param {Expr} args
154
+ * @return {Expr}
155
+ */
156
+ apply(...args) {
157
+ let expr = this;
158
+ for (const arg of args)
159
+ expr = new App(expr, arg);
160
+ return expr;
161
+ }
162
+ /**
163
+ * @desc Replace all aliases in the expression with their definitions, recursively.
164
+ * @return {Expr}
165
+ */
166
+ expand() {
167
+ return this.traverse((e) => {
168
+ if (e instanceof Alias)
169
+ return e.impl.expand();
170
+ }) ?? this;
171
+ }
172
+ /**
173
+ * @desc Traverse the expression tree, applying change() to each node.
174
+ * If change() returns an Expr, the node is replaced with that value.
175
+ * A null/undefined value is interpreted as
176
+ * "descend further if applicable, or leave the node unchanged".
177
+ *
178
+ * Returned values may be decorated:
179
+ *
180
+ * SKI.control.prune will suppress further descending even if nothing was returned
181
+ * SKI.control.stop will terminate further changes.
182
+ * SKI.control.redo will apply the callback to the returned subtree, recursively.
183
+ *
184
+ * Note that if redo was applied at least once to a subtree, a null return from the same subtree
185
+ * will be replaced by the last non-null value returned.
186
+ *
187
+ * The traversal order is leftmost-outermost, unless options.order = 'leftmost-innermost' is specified.
188
+ * Short aliases 'LO' and 'LI' (case-sensitive) are also accepted.
189
+ *
190
+ * Returns null if no changes were made, or the new expression otherwise.
191
+ *
192
+ * @param {{
193
+ * order?: 'LO' | 'LI' | 'leftmost-outermost' | 'leftmost-innermost',
194
+ * }} [options]
195
+ * @param {(e:Expr) => TraverseValue<Expr>} change
196
+ * @returns {Expr|null}
197
+ */
198
+ traverse(options, change) {
199
+ if (typeof options === "function") {
200
+ change = options;
201
+ options = {};
202
+ }
203
+ const order = ORDER[options.order ?? "LO"];
204
+ if (order === void 0)
205
+ throw new Error("Unknown traversal order: " + options.order);
206
+ const [expr] = unwrap(this._traverse_redo({ order }, change));
207
+ return expr ?? null;
208
+ }
209
+ /**
210
+ * @private
211
+ * @param {Object} options
212
+ * @param {(e:Expr) => TraverseValue<Expr>} change
213
+ * @returns {TraverseValue<Expr>}
214
+ */
215
+ _traverse_redo(options, change) {
216
+ let action;
217
+ let expr = this;
218
+ let prev;
219
+ do {
220
+ prev = expr;
221
+ const next = options.order === "LI" ? expr._traverse_descend(options, change) ?? change(expr) : change(expr) ?? expr._traverse_descend(options, change);
222
+ [expr, action] = unwrap(next);
223
+ } while (expr && action === control.redo);
224
+ if (!expr && prev !== this)
225
+ expr = prev;
226
+ return action ? action(expr) : expr;
227
+ }
228
+ /**
229
+ * @private
230
+ * @param {Object} options
231
+ * @param {(e:Expr) => TraverseValue<Expr>} change
232
+ * @returns {TraverseValue<Expr>}
233
+ */
234
+ _traverse_descend(options, change) {
235
+ return null;
236
+ }
237
+ /**
238
+ * @desc Returns true if predicate() is true for any subterm of the expression, false otherwise.
239
+ *
240
+ * @param {(e: Expr) => boolean} predicate
241
+ * @returns {boolean}
242
+ */
243
+ any(predicate) {
244
+ return predicate(this);
245
+ }
246
+ /**
247
+ * @desc Fold the expression into a single value by recursively applying combine() to its subterms.
248
+ * Nodes are traversed in leftmost-outermost order, i.e. the same order as reduction steps are taken.
249
+ *
250
+ * null or undefined return value from combine() means "keep current value and descend further".
251
+ *
252
+ * SKI.control provides primitives to control the folding flow:
253
+ * - SKI.control.prune(value) means "use value and don't descend further into this branch";
254
+ * - SKI.control.stop(value) means "stop folding immediately and return value".
255
+ * - SKI.control.descend(value) is the default behavior, meaning "use value and descend further".
256
+ *
257
+ * This method is experimental and may change in the future.
258
+ *
259
+ * @experimental
260
+ * @template T
261
+ * @param {T} initial
262
+ * @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
263
+ * @returns {T}
264
+ */
265
+ fold(initial, combine) {
266
+ const [value] = unwrap(this._fold(initial, combine));
267
+ return value ?? initial;
268
+ }
269
+ _fold(initial, combine) {
270
+ return combine(initial, this);
271
+ }
272
+ /**
273
+ * @experimental
274
+ * @desc Fold an application tree bottom to top.
275
+ * For each subtree, the function is given the term in the root position and
276
+ * a list of the results of folding its arguments.
277
+ *
278
+ * E.g. fold('x y (z t)', f) results in f(x, [f(y, []), f(z, [f(t, [])])])
279
+ *
280
+ * @example expr.foldBottomUp((head, tail) => {
281
+ * if (head.arity && head.arity <= tail.length) {
282
+ * return '(<span class="redex">'
283
+ * + head + ' '
284
+ * + tail.slice(0, head.arity).join(' ')
285
+ * + '</span>'
286
+ * + tail.slice(head.arity).join(' ')
287
+ * + ')';
288
+ * } else {
289
+ * return '(' + head + ' ' + tail.join(' ') + ')';
290
+ * }
291
+ * });
292
+ * @template T
293
+ * @param {(head: Expr, tail: T[]) => T} fun
294
+ * @return {T}
295
+ */
296
+ foldBottomUp(fun) {
297
+ const [head, ...tail] = this.unroll();
298
+ return fun(head, tail.map((e) => e.foldBottomUp(fun)));
299
+ }
300
+ /**
301
+ * @desc Try to empirically find an equivalent lambda term for the expression,
302
+ * returning also the term's arity and some other properties.
303
+ *
304
+ * This is used internally when declaring a Native / Alias term,
305
+ * unless {canonize: false} is used.
306
+ *
307
+ * As of current it only recognizes terms that have a normal form,
308
+ * perhaps after adding some variables. This may change in the future.
309
+ *
310
+ * Use toLambda() if you want to get a lambda term in any case.
311
+ *
312
+ * @param {{max?: number, maxArgs?: number}} options
313
+ * @return {TermInfo}
314
+ */
315
+ infer(options = {}) {
316
+ const skipNames = {};
317
+ this.traverse((e) => {
318
+ if (e instanceof Named)
319
+ skipNames[e.name] = true;
320
+ return void 0;
321
+ });
322
+ return this._infer({
323
+ max: options.max ?? DEFAULTS.max,
324
+ maxArgs: options.maxArgs ?? DEFAULTS.maxArgs,
325
+ skipNames
326
+ }, 0);
327
+ }
328
+ /**
329
+ * @desc Internal method for infer(), which performs the actual inference.
330
+ * @param {{max: number, maxArgs: number}} options
331
+ * @param {number} nargs - var index to avoid name clashes
332
+ * @returns {TermInfo}
333
+ * @private
334
+ */
335
+ _infer(options, nargs) {
336
+ const probe = [];
337
+ let steps = 0;
338
+ let expr = this;
339
+ main: for (let i = 0; i < options.maxArgs; i++) {
340
+ const next = expr.run({ max: options.max - steps });
341
+ steps += next.steps;
342
+ if (!next.final)
343
+ break;
344
+ if (firstVar(next.expr)) {
345
+ expr = next.expr;
346
+ if (!expr.any((e) => !(e instanceof FreeVar || e instanceof App)))
347
+ return maybeLambda(probe, expr, { steps });
348
+ const list = expr.unroll();
349
+ let discard = false;
350
+ let duplicate = false;
351
+ const acc = [];
352
+ for (let j = 1; j < list.length; j++) {
353
+ const sub = list[j]._infer(
354
+ { maxArgs: options.maxArgs - nargs, max: options.max - steps, skipNames: options.skipNames },
355
+ // limit recursion
356
+ nargs + i
357
+ // avoid variable name clashes
358
+ );
359
+ steps += sub.steps ?? 0;
360
+ if (!sub.expr)
361
+ break main;
362
+ if (sub.discard)
363
+ discard = true;
364
+ if (sub.duplicate)
365
+ duplicate = true;
366
+ acc.push(sub.expr);
228
367
  }
229
- const order = ORDER[options.order ?? "LO"];
230
- if (order === void 0)
231
- throw new Error("Unknown traversal order: " + options.order);
232
- const [expr, _] = unwrap(this._traverse_redo({ order }, change));
233
- return expr;
234
- }
235
- /**
236
- * @private
237
- * @param {Object} options
238
- * @param {(e:Expr) => TraverseValue<Expr>} change
239
- * @returns {TraverseValue<Expr>}
240
- */
241
- _traverse_redo(options, change) {
242
- let action;
243
- let expr = this;
244
- let prev;
245
- do {
246
- prev = expr;
247
- const next = options.order === "LI" ? expr._traverse_descend(options, change) ?? change(expr) : change(expr) ?? expr._traverse_descend(options, change);
248
- [expr, action] = unwrap(next);
249
- } while (expr && action === control.redo);
250
- if (!expr && prev !== this)
251
- expr = prev;
252
- return action ? action(expr) : expr;
368
+ return maybeLambda(probe, list[0].apply(...acc), { discard, duplicate, steps });
253
369
  }
254
- /**
255
- * @private
256
- * @param {Object} options
257
- * @param {(e:Expr) => TraverseValue<Expr>} change
258
- * @returns {TraverseValue<Expr>}
259
- */
260
- _traverse_descend(options, change) {
370
+ while (options.skipNames[nthvar(nargs + i).name])
371
+ nargs++;
372
+ const push = nthvar(nargs + i);
373
+ probe.push(push);
374
+ expr = next.expr.apply(push);
375
+ }
376
+ return { normal: false, proper: false, steps };
377
+ }
378
+ /**
379
+ * @desc Expand an expression into a list of terms
380
+ * that give the initial expression when applied from left to right:
381
+ * ((a, b), (c, d)) => [a, b, (c, d)]
382
+ *
383
+ * This can be thought of as an opposite of apply:
384
+ * fun.apply(...arg).unroll() is exactly [fun, ...args]
385
+ * (even if ...arg is in fact empty).
386
+ *
387
+ * @returns {Expr[]}
388
+ */
389
+ unroll() {
390
+ return [this];
391
+ }
392
+ /**
393
+ * @desc Returns a series of lambda terms equivalent to the given expression,
394
+ * up to the provided computation steps limit.
395
+ *
396
+ * Unlike infer(), this method will always return something,
397
+ * even if the expression has no normal form.
398
+ *
399
+ * See also Expr.walk() and Expr.toSKI().
400
+ *
401
+ * @param {{
402
+ * max?: number,
403
+ * maxArgs?: number,
404
+ * varGen?: function(void): FreeVar,
405
+ * steps?: number,
406
+ * html?: boolean,
407
+ * latin?: number,
408
+ * }} options
409
+ * @return {IterableIterator<{expr: Expr, steps?: number, comment?: string}>}
410
+ */
411
+ *toLambda(options = {}) {
412
+ let expr = this.traverse((e) => {
413
+ if (e instanceof FreeVar || e instanceof App || e instanceof Lambda || e instanceof Alias)
261
414
  return null;
262
- }
263
- /**
264
- * @desc Returns true if predicate() is true for any subterm of the expression, false otherwise.
265
- *
266
- * @param {(e: Expr) => boolean} predicate
267
- * @returns {boolean}
268
- */
269
- any(predicate) {
270
- return predicate(this);
271
- }
272
- /**
273
- * @desc Fold the expression into a single value by recursively applying combine() to its subterms.
274
- * Nodes are traversed in leftmost-outermost order, i.e. the same order as reduction steps are taken.
275
- *
276
- * null or undefined return value from combine() means "keep current value and descend further".
277
- *
278
- * SKI.control provides primitives to control the folding flow:
279
- * - SKI.control.prune(value) means "use value and don't descend further into this branch";
280
- * - SKI.control.stop(value) means "stop folding immediately and return value".
281
- * - SKI.control.descend(value) is the default behavior, meaning "use value and descend further".
282
- *
283
- * This method is experimental and may change in the future.
284
- *
285
- * @experimental
286
- * @template T
287
- * @param {T} initial
288
- * @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
289
- * @returns {T}
290
- */
291
- fold(initial, combine) {
292
- const [value, _] = unwrap(this._fold(initial, combine));
293
- return value ?? initial;
294
- }
295
- /**
296
- * @experimental
297
- * @desc Fold an application tree bottom to top.
298
- * For each subtree, the function is given the term in the root position and
299
- * a list of the results of folding its arguments.
300
- *
301
- * E.g. fold('x y (z t)', f) results in f(x, [f(y, []), f(z, [f(t, [])])])
302
- *
303
- * @example expr.foldBottomUp((head, tail) => {
304
- * if (head.arity && head.arity <= tail.length) {
305
- * return '(<span class="redex">'
306
- * + head + ' '
307
- * + tail.slice(0, head.arity).join(' ')
308
- * + '</span>'
309
- * + tail.slice(head.arity).join(' ')
310
- * + ')';
311
- * } else {
312
- * return '(' + head + ' ' + tail.join(' ') + ')';
313
- * }
314
- * });
315
- * @template T
316
- * @param {(head: Expr, tail: T[]) => T} fun
317
- * @return {T}
318
- */
319
- foldBottomUp(fun) {
320
- const [head, ...tail] = this.unroll();
321
- return fun(head, tail.map((e) => e.foldBottomUp(fun)));
322
- }
323
- /**
324
- * @template T
325
- * @param {T} initial
326
- * @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
327
- * @returns {TraverseValue<T>}
328
- * @private
329
- */
330
- _fold(initial, combine) {
331
- return combine(initial, this);
332
- }
333
- /**
334
- * @desc rough estimate of the term's complexity
335
- * @return {number}
336
- */
337
- weight() {
338
- return 1;
339
- }
340
- /**
341
- * @desc Try to empirically find an equivalent lambda term for the expression,
342
- * returning also the term's arity and some other properties.
343
- *
344
- * This is used internally when declaring a Native / Alias term,
345
- * unless {canonize: false} is used.
346
- *
347
- * As of current it only recognizes terms that have a normal form,
348
- * perhaps after adding some variables. This may change in the future.
349
- *
350
- * Use toLambda() if you want to get a lambda term in any case.
351
- *
352
- * @param {{max?: number, maxArgs?: number}} options
353
- * @return {TermInfo}
354
- */
355
- infer(options = {}) {
356
- return this._infer({
357
- max: options.max ?? DEFAULTS.max,
358
- maxArgs: options.maxArgs ?? DEFAULTS.maxArgs
359
- }, 0);
360
- }
361
- /**
362
- * @desc Internal method for infer(), which performs the actual inference.
363
- * @param {{max: number, maxArgs: number}} options
364
- * @param {number} nargs - var index to avoid name clashes
365
- * @returns {TermInfo}
366
- * @private
367
- */
368
- _infer(options, nargs) {
369
- const probe = [];
370
- let steps = 0;
371
- let expr = this;
372
- main: for (let i = 0; i < options.maxArgs; i++) {
373
- const next = expr.run({ max: options.max - steps });
374
- steps += next.steps;
375
- if (!next.final)
376
- break;
377
- if (firstVar(next.expr)) {
378
- expr = next.expr;
379
- if (!expr.any((e) => !(e instanceof FreeVar || e instanceof App)))
380
- return maybeLambda(probe, expr, { steps });
381
- const list = expr.unroll();
382
- let discard = false;
383
- let duplicate = false;
384
- const acc = [];
385
- for (let j = 1; j < list.length; j++) {
386
- const sub = list[j]._infer(
387
- { maxArgs: options.maxArgs - nargs, max: options.max - steps },
388
- // limit recursion
389
- nargs + i
390
- // avoid variable name clashes
391
- );
392
- steps += sub.steps;
393
- if (!sub.expr)
394
- break main;
395
- if (sub.discard)
396
- discard = true;
397
- if (sub.duplicate)
398
- duplicate = true;
399
- acc.push(sub.expr);
400
- }
401
- return maybeLambda(probe, list[0].apply(...acc), { discard, duplicate, steps });
402
- }
403
- const push = nthvar(nargs + i);
404
- probe.push(push);
405
- expr = next.expr.apply(push);
406
- }
407
- return { normal: false, proper: false, steps };
408
- }
409
- /**
410
- * @desc Expand an expression into a list of terms
411
- * that give the initial expression when applied from left to right:
412
- * ((a, b), (c, d)) => [a, b, (c, d)]
413
- *
414
- * This can be thought of as an opposite of apply:
415
- * fun.apply(...arg).unroll() is exactly [fun, ...args]
416
- * (even if ...arg is in fact empty).
417
- *
418
- * @returns {Expr[]}
419
- */
420
- unroll() {
421
- return [this];
422
- }
423
- /**
424
- * @desc Returns a series of lambda terms equivalent to the given expression,
425
- * up to the provided computation steps limit,
426
- * in decreasing weight order.
427
- *
428
- * Unlike infer(), this method will always return something,
429
- * even if the expression has no normal form.
430
- *
431
- * See also Expr.walk() and Expr.toSKI().
432
- *
433
- * @param {{
434
- * max?: number,
435
- * maxArgs?: number,
436
- * varGen?: function(void): FreeVar,
437
- * steps?: number,
438
- * html?: boolean,
439
- * latin?: number,
440
- * }} options
441
- * @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
442
- * @return {IterableIterator<{expr: Expr, steps?: number, comment?: string}>}
443
- */
444
- *toLambda(options = {}) {
445
- let expr = this.traverse((e) => {
446
- if (e instanceof FreeVar || e instanceof App || e instanceof Lambda || e instanceof Alias)
447
- return null;
415
+ const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
416
+ if (!guess.normal)
417
+ throw new Error("Failed to infer an equivalent lambda term for " + e);
418
+ return guess.expr;
419
+ }) ?? this;
420
+ const seen = /* @__PURE__ */ new Set();
421
+ let steps = 0;
422
+ while (expr) {
423
+ const next = expr.traverse({ order: "LI" }, (e) => {
424
+ if (seen.has(e))
425
+ return null;
426
+ if (e instanceof App && e.fun instanceof Lambda) {
448
427
  const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
449
- if (!guess.normal)
450
- throw new Error("Failed to infer an equivalent lambda term for " + e);
451
- return guess.expr;
452
- }) ?? this;
453
- const seen = /* @__PURE__ */ new Set();
454
- let steps = 0;
455
- while (expr) {
456
- const next = expr.traverse({ order: "LI" }, (e) => {
457
- if (seen.has(e))
458
- return null;
459
- if (e instanceof App && e.fun instanceof Lambda) {
460
- const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
461
- steps += guess.steps;
462
- if (!guess.normal) {
463
- seen.add(e);
464
- return null;
465
- }
466
- return control.stop(guess.expr);
467
- }
468
- });
469
- yield { expr, steps };
470
- expr = next;
471
- }
472
- }
473
- /**
474
- * @desc Rewrite the expression into S, K, and I combinators step by step.
475
- * Returns an iterator yielding the intermediate expressions,
476
- * along with the number of steps taken to reach them.
477
- *
478
- * See also Expr.walk() and Expr.toLambda().
479
- *
480
- * @param {{max?: number}} [options]
481
- * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
482
- */
483
- *toSKI(options = {}) {
484
- let expr = this.traverse((e) => {
485
- if (e instanceof FreeVar || e instanceof App || e instanceof Lambda || e instanceof Alias)
428
+ steps += guess.steps ?? 0;
429
+ if (!guess.normal) {
430
+ seen.add(e);
486
431
  return null;
487
- return e.infer().expr;
488
- }) ?? this;
489
- let steps = 0;
490
- while (expr) {
491
- const next = expr.traverse({ order: "LI" }, (e) => {
492
- if (!(e instanceof Lambda) || e.impl instanceof Lambda)
493
- return null;
494
- if (e.impl === e.arg)
495
- return control.stop(native.I);
496
- if (!e.impl.any((t) => t === e.arg))
497
- return control.stop(native.K.apply(e.impl));
498
- if (!(e.impl instanceof App))
499
- throw new Error("toSKI: assert failed: lambda body is of unexpected type " + e.impl.constructor.name);
500
- if (e.impl.arg === e.arg && !e.impl.fun.any((t) => t === e.arg))
501
- return control.stop(e.impl.fun);
502
- return control.stop(native.S.apply(new Lambda(e.arg, e.impl.fun), new Lambda(e.arg, e.impl.arg)));
503
- });
504
- yield { expr, steps, final: !next };
505
- steps++;
506
- expr = next;
507
- }
508
- }
509
- /**
510
- * Replace all instances of plug in the expression with value and return the resulting expression,
511
- * or null if no changes could be made.
512
- * Lambda terms and applications will never match if used as plug
513
- * as they are impossible co compare without extensive computations.
514
- * Typically used on variables but can also be applied to other terms, e.g. aliases.
515
- * See also Expr.traverse().
516
- * @param {Expr} search
517
- * @param {Expr} replace
518
- * @return {Expr|null}
519
- */
520
- subst(search, replace) {
521
- return this === search ? replace : null;
522
- }
523
- /**
524
- * @desc Apply term reduction rules, if any, to the given argument.
525
- * A returned value of null means no reduction is possible.
526
- * A returned value of Expr means the reduction is complete and the application
527
- * of this and arg can be replaced with the result.
528
- * A returned value of a function means that further arguments are needed,
529
- * and can be cached for when they arrive.
530
- *
531
- * This method is between apply() which merely glues terms together,
532
- * and step() which reduces the whole expression.
533
- *
534
- * foo.invoke(bar) is what happens inside foo.apply(bar).step() before
535
- * reduction of either foo or bar is attempted.
536
- *
537
- * The name 'invoke' was chosen to avoid confusion with either 'apply' or 'reduce'.
538
- *
539
- * @param {Expr} arg
540
- * @returns {Partial | null}
541
- */
542
- invoke(arg) {
543
- return null;
544
- }
545
- /**
546
- * @desc iterate one step of a calculation.
547
- * @return {{expr: Expr, steps: number, changed: boolean}}
548
- */
549
- step() {
550
- return { expr: this, steps: 0, changed: false };
551
- }
552
- /**
553
- * @desc Run uninterrupted sequence of step() applications
554
- * until the expression is irreducible, or max number of steps is reached.
555
- * Default number of steps = 1000.
556
- * @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
557
- * @param {Expr} args
558
- * @return {{expr: Expr, steps: number, final: boolean}}
559
- */
560
- run(opt = {}, ...args) {
561
- if (opt instanceof _Expr) {
562
- args.unshift(opt);
563
- opt = {};
564
- }
565
- let expr = args ? this.apply(...args) : this;
566
- let steps = opt.steps ?? 0;
567
- const max = Math.max(opt.max ?? DEFAULTS.max, 1) + steps;
568
- let final = false;
569
- for (; steps < max; ) {
570
- const next = expr.step();
571
- if (!next.changed) {
572
- final = true;
573
- break;
574
432
  }
575
- steps += next.steps;
576
- expr = next.expr;
577
- }
578
- if (opt.throw && !final)
579
- throw new Error("Failed to compute expression in " + max + " steps");
580
- return { final, steps, expr };
581
- }
582
- /**
583
- * Execute step() while possible, yielding a brief description of events after each step.
584
- * Mnemonics: like run() but slower.
585
- * @param {{max?: number}} options
586
- * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
587
- */
588
- *walk(options = {}) {
589
- const max = options.max ?? Infinity;
590
- let steps = 0;
591
- let expr = this;
592
- let final = false;
593
- while (steps < max) {
594
- const next = expr.step();
595
- if (!next.changed)
596
- final = true;
597
- yield { expr, steps, final };
598
- if (final)
599
- break;
600
- steps += next.steps;
601
- expr = next.expr;
602
- }
603
- }
604
- /**
605
- * @desc True is the expressions are identical, false otherwise.
606
- * Aliases are expanded.
607
- * Bound variables in lambda terms are renamed consistently.
608
- * However, no reductions are attempted.
609
- *
610
- * E.g. a->b->a == x->y->x is true, but a->b->a == K is false.
611
- *
612
- * @param {Expr} other
613
- * @return {boolean}
614
- */
615
- equals(other) {
616
- return !this.diff(other);
617
- }
618
- /**
619
- * @desc Recursively compare two expressions and return a string
620
- * describing the first point of difference.
621
- * Returns null if expressions are identical.
622
- *
623
- * Aliases are expanded.
624
- * Bound variables in lambda terms are renamed consistently.
625
- * However, no reductions are attempted.
626
- *
627
- * Members of the FreeVar class are considered different
628
- * even if they have the same name, unless they are the same object.
629
- * To somewhat alleviate confusion, the output will include
630
- * the internal id of the variable in square brackets.
631
- *
632
- * @example "K(S != I)" is the result of comparing "KS" and "KI"
633
- * @example "S(K([x[13] != x[14]]))K"
634
- *
635
- * @param {Expr} other
636
- * @param {boolean} [swap] If true, the order of expressions is reversed in the output.
637
- * @returns {string|null}
638
- */
639
- diff(other, swap = false) {
640
- if (this === other)
641
- return null;
642
- if (other instanceof Alias)
643
- return other.impl.diff(this, !swap);
644
- return swap ? "[" + other + " != " + this + "]" : "[" + this + " != " + other + "]";
645
- }
646
- /**
647
- * @desc Assert expression equality. Can be used in tests.
648
- *
649
- * `this` is the expected value and the argument is the actual one.
650
- * Mnemonic: the expected value is always a combinator, the actual one may be anything.
651
- *
652
- * @param {Expr} actual
653
- * @param {string} comment
654
- */
655
- expect(actual, comment = "") {
656
- comment = comment ? comment + ": " : "";
657
- if (!(actual instanceof _Expr)) {
658
- throw new Error(comment + "Expected a combinator but found " + (actual?.constructor?.name ?? typeof actual));
659
- }
660
- const diff = this.diff(actual);
661
- if (!diff)
662
- return;
663
- const poorMans = new Error(comment + diff);
664
- poorMans.expected = this.diag();
665
- poorMans.actual = actual.diag();
666
- throw poorMans;
667
- }
668
- /**
669
- * @desc Returns string representation of the expression.
670
- * Same as format() without options.
671
- * @return {string}
672
- */
673
- toString() {
674
- return this.format();
675
- }
676
- /**
677
- * @desc Whether the expression needs parentheses when printed.
678
- * @param {boolean} [first] - whether this is the first term in a sequence
679
- * @return {boolean}
680
- */
681
- _braced(first) {
682
- return false;
683
- }
684
- /**
685
- * @desc Whether the expression can be printed without a space when followed by arg.
686
- * @param {Expr} arg
687
- * @returns {boolean}
688
- * @private
689
- */
690
- _unspaced(arg) {
691
- return this._braced(true);
692
- }
693
- /**
694
- * @desc Stringify the expression with fancy formatting options.
695
- * Said options mostly include wrappers around various constructs in form of ['(', ')'],
696
- * as well as terse and html flags that set up the defaults.
697
- * Format without options is equivalent to toString() and can be parsed back.
698
- *
699
- * @param {Object} [options] - formatting options
700
- * @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
701
- * @param {boolean} [options.html] - whether to default to HTML tags & entities.
702
- * If a named term has fancyName property set, it will be used instead of name in this mode.
703
- * @param {[string, string]} [options.brackets] - wrappers for application arguments, typically ['(', ')']
704
- * @param {[string, string]} [options.var] - wrappers for variable names
705
- * (will default to &lt;var&gt; and &lt;/var&gt; in html mode).
706
- * @param {[string, string, string]} [options.lambda] - wrappers for lambda abstractions, e.g. ['&lambda;', '.', '']
707
- * where the middle string is placed between argument and body
708
- * default is ['', '->', ''] or ['', '-&gt;', ''] for html
709
- * @param {[string, string]} [options.around] - wrappers around (sub-)expressions.
710
- * individual applications will not be wrapped, i.e. (a b c) but not ((a b) c)
711
- * @param {[string, string]} [options.redex] - wrappers around the starting term(s) that have enough arguments to be reduced
712
- * @param {Object<string, Expr>} [options.inventory] - if given, output aliases in the set as their names
713
- * and any other aliases as the expansion of their definitions.
714
- * The default is a cryptic and fragile mechanism dependent on a hidden mutable property.
715
- * @returns {string}
716
- *
717
- * @example foo.format() // equivalent to foo.toString()
718
- * @example foo.format({terse: false}) // spell out all parentheses
719
- * @example foo.format({html: true}) // use HTML tags and entities
720
- * @example foo.format({ around: ['(', ')'], brackets: ['', ''], lambda: ['(', '->', ')'] }) // lisp style, still back-parsable
721
- * @exapmle foo.format({ lambda: ['&lambda;', '.', ''] }) // pretty-print for the math department
722
- * @example foo.format({ lambda: ['', '=>', ''], terse: false }) // make it javascript
723
- * @example foo.format({ inventory: { T } }) // use T as a named term, expand all others
724
- *
725
- */
726
- format(options = {}) {
727
- const fallback = options.html ? {
728
- brackets: ["(", ")"],
729
- space: " ",
730
- var: ["<var>", "</var>"],
731
- lambda: ["", "-&gt;", ""],
732
- around: ["", ""],
733
- redex: ["", ""]
734
- } : {
735
- brackets: ["(", ")"],
736
- space: " ",
737
- var: ["", ""],
738
- lambda: ["", "->", ""],
739
- around: ["", ""],
740
- redex: ["", ""]
741
- };
742
- return this._format({
743
- terse: options.terse ?? true,
744
- brackets: options.brackets ?? fallback.brackets,
745
- space: options.space ?? fallback.space,
746
- var: options.var ?? fallback.var,
747
- lambda: options.lambda ?? fallback.lambda,
748
- around: options.around ?? fallback.around,
749
- redex: options.redex ?? fallback.redex,
750
- inventory: options.inventory,
751
- // TODO better name
752
- html: options.html ?? false
753
- }, 0);
754
- }
755
- /**
756
- * @desc Internal method for format(), which performs the actual formatting.
757
- * @param {Object} options
758
- * @param {number} nargs
759
- * @returns {string}
760
- * @private
761
- */
762
- _format(options, nargs) {
763
- throw new Error("No _format() method defined in class " + this.constructor.name);
764
- }
765
- /**
766
- * @desc Returns a string representation of the expression tree, with indentation to show structure.
767
- *
768
- * Applications are flattened to avoid excessive nesting.
769
- * Variables include ids to distinguish different instances of the same variable name.
770
- *
771
- * May be useful for debugging.
772
- *
773
- * @returns {string}
774
- *
775
- * @example
776
- * > console.log(ski.parse('C 5 x (x->x x)').diag())
777
- * App:
778
- * Native: C
779
- * Church: 5
780
- * FreeVar: x[53]
781
- * Lambda (x[54]):
782
- * App:
783
- * FreeVar: x[54]
784
- * FreeVar: x[54]
785
- */
786
- diag() {
787
- const rec = (e, indent) => {
788
- if (e instanceof App)
789
- return [indent + "App:", ...e.unroll().flatMap((s) => rec(s, indent + " "))];
790
- if (e instanceof Lambda)
791
- return [`${indent}Lambda (${e.arg}[${e.arg.id}]):`, ...rec(e.impl, indent + " ")];
792
- if (e instanceof Alias)
793
- return [`${indent}Alias (${e.name}): \\`, ...rec(e.impl, indent)];
794
- if (e instanceof FreeVar)
795
- return [`${indent}FreeVar: ${e.name}[${e.id}]`];
796
- return [`${indent}${e.constructor.name}: ${e}`];
797
- };
798
- const out = rec(this, "");
799
- return out.join("\n");
800
- }
801
- /**
802
- * @desc Convert the expression to a JSON-serializable format.
803
- * @returns {string}
804
- */
805
- toJSON() {
806
- return this.format();
807
- }
808
- };
809
- var App = class _App extends Expr {
810
- /**
811
- * @desc Application of fun() to args.
812
- * Never ever use new App(fun, arg) directly, use fun.apply(...args) instead.
813
- * @param {Expr} fun
814
- * @param {Expr} arg
815
- */
816
- constructor(fun, arg) {
817
- super();
818
- this.arg = arg;
819
- this.fun = fun;
820
- }
821
- /** @property {boolean} [final] */
822
- weight() {
823
- return this.fun.weight() + this.arg.weight();
824
- }
825
- _traverse_descend(options, change) {
826
- const [fun, fAction] = unwrap(this.fun._traverse_redo(options, change));
827
- if (fAction === control.stop)
828
- return control.stop(fun ? fun.apply(this.arg) : null);
829
- const [arg, aAction] = unwrap(this.arg._traverse_redo(options, change));
830
- const final = fun || arg ? (fun ?? this.fun).apply(arg ?? this.arg) : null;
831
- if (aAction === control.stop)
832
- return control.stop(final);
833
- return final;
834
- }
835
- any(predicate) {
836
- return predicate(this) || this.fun.any(predicate) || this.arg.any(predicate);
837
- }
838
- _fold(initial, combine) {
839
- const [value = initial, action = "descend"] = unwrap(combine(initial, this));
840
- if (action === control.prune)
841
- return value;
842
- if (action === control.stop)
843
- return control.stop(value);
844
- const [fValue = value, fAction = "descend"] = unwrap(this.fun._fold(value, combine));
845
- if (fAction === control.stop)
846
- return control.stop(fValue);
847
- const [aValue = fValue, aAction = "descend"] = unwrap(this.arg._fold(fValue, combine));
848
- if (aAction === control.stop)
849
- return control.stop(aValue);
850
- return aValue;
851
- }
852
- subst(search, replace) {
853
- const fun = this.fun.subst(search, replace);
854
- const arg = this.arg.subst(search, replace);
855
- return fun || arg ? (fun ?? this.fun).apply(arg ?? this.arg) : null;
856
- }
857
- /**
858
- * @return {{expr: Expr, steps: number}}
859
- */
860
- step() {
861
- if (!this.final) {
862
- const partial = this.fun.invoke(this.arg);
863
- if (partial instanceof Expr)
864
- return { expr: partial, steps: 1, changed: true };
865
- else if (typeof partial === "function")
866
- this.invoke = partial;
867
- const fun = this.fun.step();
868
- if (fun.changed)
869
- return { expr: fun.expr.apply(this.arg), steps: fun.steps, changed: true };
870
- const arg = this.arg.step();
871
- if (arg.changed)
872
- return { expr: this.fun.apply(arg.expr), steps: arg.steps, changed: true };
873
- this.final = true;
874
- }
875
- return { expr: this, steps: 0, changed: false };
876
- }
877
- invoke(arg) {
878
- const partial = this.fun.invoke(this.arg);
879
- if (partial instanceof Expr)
880
- return partial.apply(arg);
881
- else if (typeof partial === "function") {
882
- this.invoke = partial;
883
- return partial(arg);
884
- } else {
885
- this.invoke = (_) => null;
886
- return null;
433
+ return control.stop(guess.expr);
887
434
  }
888
- }
889
- unroll() {
890
- return [...this.fun.unroll(), this.arg];
891
- }
892
- diff(other, swap = false) {
893
- if (!(other instanceof _App))
894
- return super.diff(other, swap);
895
- const fun = this.fun.diff(other.fun, swap);
896
- if (fun)
897
- return fun + "(...)";
898
- const arg = this.arg.diff(other.arg, swap);
899
- if (arg)
900
- return this.fun + "(" + arg + ")";
435
+ });
436
+ yield { expr, steps };
437
+ expr = next;
438
+ }
439
+ }
440
+ /**
441
+ * @desc Rewrite the expression into S, K, and I combinators step by step.
442
+ * Returns an iterator yielding the intermediate expressions,
443
+ * along with the number of steps taken to reach them.
444
+ *
445
+ * See also Expr.walk() and Expr.toLambda().
446
+ *
447
+ * @param {{max?: number}} [options]
448
+ * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
449
+ */
450
+ *toSKI(_options = {}) {
451
+ let expr = this.traverse((e) => {
452
+ if (e instanceof FreeVar || e instanceof App || e instanceof Lambda || e instanceof Alias)
901
453
  return null;
902
- }
903
- _braced(first) {
904
- return !first;
905
- }
906
- _format(options, nargs) {
907
- const fun = this.fun._format(options, nargs + 1);
908
- const arg = this.arg._format(options, 0);
909
- const wrap = nargs ? ["", ""] : options.around;
910
- if (options.terse && !this.arg._braced(false))
911
- return wrap[0] + fun + (this.fun._unspaced(this.arg) ? "" : options.space) + arg + wrap[1];
912
- else
913
- return wrap[0] + fun + options.brackets[0] + arg + options.brackets[1] + wrap[1];
914
- }
915
- _unspaced(arg) {
916
- return this.arg._braced(false) ? true : this.arg._unspaced(arg);
917
- }
918
- };
919
- var Named = class _Named extends Expr {
920
- /**
921
- * @desc An abstract class representing a term named 'name'.
922
- *
923
- * @param {String} name
924
- */
925
- constructor(name) {
926
- super();
927
- if (typeof name !== "string" || name.length === 0)
928
- throw new Error("Attempt to create a named term with improper name");
929
- this.name = name;
930
- }
931
- _unspaced(arg) {
932
- 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+]$/)));
933
- }
934
- _format(options, nargs) {
935
- const name = options.html ? this.fancyName ?? this.name : this.name;
936
- return this.arity > 0 && this.arity <= nargs ? options.redex[0] + name + options.redex[1] : name;
937
- }
938
- };
939
- var freeId = 0;
940
- var FreeVar = class _FreeVar extends Named {
941
- /**
942
- * @desc A named variable.
943
- *
944
- * Given the functional nature of combinatory logic, variables are treated
945
- * as functions that we don't know how to evaluate just yet.
946
- *
947
- * By default, two different variables even with the same name are considered different.
948
- * They display it via a hidden id property.
949
- *
950
- * If a scope object is given, however, two variables with the same name and scope
951
- * are considered identical.
952
- *
953
- * By convention, FreeVar.global is a constant denoting a global unbound variable.
954
- *
955
- * @param {string} name - name of the variable
956
- * @param {any} scope - an object representing where the variable belongs to.
957
- */
958
- constructor(name, scope) {
959
- super(name);
960
- this.id = ++freeId;
961
- this.scope = scope === void 0 ? this : scope;
962
- }
963
- weight() {
964
- return 0;
965
- }
966
- diff(other, swap = false) {
967
- if (!(other instanceof _FreeVar))
968
- return super.diff(other, swap);
969
- if (this.name === other.name && this.scope === other.scope)
454
+ return e.infer().expr;
455
+ }) ?? this;
456
+ let steps = 0;
457
+ while (expr) {
458
+ const next = expr.traverse({ order: "LI" }, (e) => {
459
+ if (!(e instanceof Lambda) || e.impl instanceof Lambda)
970
460
  return null;
971
- const lhs = this.name + "[" + this.id + "]";
972
- const rhs = other.name + "[" + other.id + "]";
973
- return swap ? "[" + rhs + " != " + lhs + "]" : "[" + lhs + " != " + rhs + "]";
974
- }
975
- subst(search, replace) {
976
- if (search instanceof _FreeVar && search.name === this.name && search.scope === this.scope)
977
- return replace;
978
- return null;
979
- }
980
- _format(options, nargs) {
981
- const name = options.html ? this.fancyName ?? this.name : this.name;
982
- return options.var[0] + name + options.var[1];
983
- }
461
+ if (e.impl === e.arg)
462
+ return control.stop(native.I);
463
+ if (!e.impl.any((t) => t === e.arg))
464
+ return control.stop(native.K.apply(e.impl));
465
+ if (!(e.impl instanceof App))
466
+ throw new Error("toSKI: assert failed: lambda body is of unexpected type " + e.impl.constructor.name);
467
+ if (e.impl.arg === e.arg && !e.impl.fun.any((t) => t === e.arg))
468
+ return control.stop(e.impl.fun);
469
+ return control.stop(native.S.apply(new Lambda(e.arg, e.impl.fun), new Lambda(e.arg, e.impl.arg)));
470
+ });
471
+ yield { expr, steps, final: !next };
472
+ steps++;
473
+ expr = next;
474
+ }
475
+ }
476
+ /**
477
+ * Replace all instances of plug in the expression with value and return the resulting expression,
478
+ * or null if no changes could be made.
479
+ * Lambda terms and applications will never match if used as plug
480
+ * as they are impossible co compare without extensive computations.
481
+ * Typically used on variables but can also be applied to other terms, e.g. aliases.
482
+ * See also Expr.traverse().
483
+ * @param {Expr} search
484
+ * @param {Expr} replace
485
+ * @return {Expr|null}
486
+ */
487
+ subst(search2, replace) {
488
+ return this === search2 ? replace : null;
489
+ }
490
+ /**
491
+ * @desc Apply term reduction rules, if any, to the given argument.
492
+ * A returned value of null means no reduction is possible.
493
+ * A returned value of Expr means the reduction is complete and the application
494
+ * of this and arg can be replaced with the result.
495
+ * A returned value of a function means that further arguments are needed,
496
+ * and can be cached for when they arrive.
497
+ *
498
+ * This method is between apply() which merely glues terms together,
499
+ * and step() which reduces the whole expression.
500
+ *
501
+ * foo.invoke(bar) is what happens inside foo.apply(bar).step() before
502
+ * reduction of either foo or bar is attempted.
503
+ *
504
+ * The name 'invoke' was chosen to avoid confusion with either 'apply' or 'reduce'.
505
+ *
506
+ * @param {Expr} arg
507
+ * @returns {Partial | null}
508
+ */
509
+ invoke(arg) {
510
+ return null;
511
+ }
512
+ /**
513
+ * @desc iterate one step of a calculation.
514
+ * @return {{expr: Expr, steps: number, changed: boolean}}
515
+ */
516
+ step() {
517
+ return { expr: this, steps: 0, changed: false };
518
+ }
519
+ /**
520
+ * @desc Run uninterrupted sequence of step() applications
521
+ * until the expression is irreducible, or max number of steps is reached.
522
+ * Default number of steps = 1000.
523
+ * @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
524
+ * @param {Expr} args
525
+ * @return {{expr: Expr, steps: number, final: boolean}}
526
+ */
527
+ run(opt = {}, ...args) {
528
+ if (opt instanceof _Expr) {
529
+ args.unshift(opt);
530
+ opt = {};
531
+ }
532
+ let expr = args ? this.apply(...args) : this;
533
+ let steps = opt.steps ?? 0;
534
+ const max = Math.max(opt.max ?? DEFAULTS.max, 1) + steps;
535
+ let final = false;
536
+ for (; steps < max; ) {
537
+ const next = expr.step();
538
+ if (!next.changed) {
539
+ final = true;
540
+ break;
541
+ }
542
+ steps += next.steps;
543
+ expr = next.expr;
544
+ }
545
+ if (opt.throw && !final)
546
+ throw new Error("Failed to compute expression in " + max + " steps");
547
+ return { final, steps, expr };
548
+ }
549
+ /**
550
+ * Execute step() while possible, yielding a brief description of events after each step.
551
+ * Mnemonics: like run() but slower.
552
+ * @param {{max?: number}} options
553
+ * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
554
+ */
555
+ *walk(options = {}) {
556
+ const max = options.max ?? Infinity;
557
+ let steps = 0;
558
+ let expr = this;
559
+ let final = false;
560
+ while (steps < max) {
561
+ const next = expr.step();
562
+ if (!next.changed)
563
+ final = true;
564
+ yield { expr, steps, final };
565
+ if (final)
566
+ break;
567
+ steps += next.steps;
568
+ expr = next.expr;
569
+ }
570
+ }
571
+ /**
572
+ * @desc True is the expressions are identical, false otherwise.
573
+ * Aliases are expanded.
574
+ * Bound variables in lambda terms are renamed consistently.
575
+ * However, no reductions are attempted.
576
+ *
577
+ * E.g. a->b->a == x->y->x is true, but a->b->a == K is false.
578
+ *
579
+ * @param {Expr} other
580
+ * @return {boolean}
581
+ */
582
+ equals(other) {
583
+ return !this.diff(other);
584
+ }
585
+ /**
586
+ * @desc Recursively compare two expressions and return a string
587
+ * describing the first point of difference.
588
+ * Returns null if expressions are identical.
589
+ *
590
+ * Aliases are expanded.
591
+ * Bound variables in lambda terms are renamed consistently.
592
+ * However, no reductions are attempted.
593
+ *
594
+ * Members of the FreeVar class are considered different
595
+ * even if they have the same name, unless they are the same object.
596
+ * To somewhat alleviate confusion, the output will include
597
+ * the internal id of the variable in square brackets.
598
+ *
599
+ * @example "K(S != I)" is the result of comparing "KS" and "KI"
600
+ * @example "S(K([x[13] != x[14]]))K"
601
+ *
602
+ * @param {Expr} other
603
+ * @param {boolean} [swap] If true, the order of expressions is reversed in the output.
604
+ * @returns {string|null}
605
+ */
606
+ diff(other, swap = false) {
607
+ if (this === other)
608
+ return null;
609
+ if (other instanceof Alias)
610
+ return other.impl.diff(this, !swap);
611
+ return swap ? "[" + other + " != " + this + "]" : "[" + this + " != " + other + "]";
612
+ }
613
+ /**
614
+ * @desc Assert expression equality. Can be used in tests.
615
+ *
616
+ * `this` is the expected value and the argument is the actual one.
617
+ * Mnemonic: the expected value is always a combinator, the actual one may be anything.
618
+ *
619
+ * @param {Expr} actual
620
+ * @param {string} comment
621
+ */
622
+ expect(actual, comment = "") {
623
+ comment = comment ? comment + ": " : "";
624
+ if (!(actual instanceof _Expr)) {
625
+ throw new Error(comment + "Expected a combinator but found " + (actual?.constructor?.name ?? typeof actual));
626
+ }
627
+ const diff = this.diff(actual);
628
+ if (!diff)
629
+ return;
630
+ const poorMans = new Error(comment + diff);
631
+ poorMans.expected = this.diag();
632
+ poorMans.actual = actual.diag();
633
+ throw poorMans;
634
+ }
635
+ /**
636
+ * @desc Returns string representation of the expression.
637
+ * Same as format() without options.
638
+ * @return {string}
639
+ */
640
+ toString() {
641
+ return this.format();
642
+ }
643
+ /**
644
+ * @desc Whether the expression needs parentheses when printed.
645
+ * @param {boolean} [first] - whether this is the first term in a sequence
646
+ * @return {boolean}
647
+ */
648
+ _braced(_first) {
649
+ return false;
650
+ }
651
+ /**
652
+ * @desc Whether the expression can be printed without a space when followed by arg.
653
+ * @param {Expr} arg
654
+ * @returns {boolean}
655
+ * @private
656
+ */
657
+ _unspaced(arg) {
658
+ return this._braced(true);
659
+ }
660
+ /**
661
+ * @desc Stringify the expression with fancy formatting options.
662
+ * Said options mostly include wrappers around various constructs in form of ['(', ')'],
663
+ * as well as terse and html flags that set up the defaults.
664
+ * Format without options is equivalent to toString() and can be parsed back.
665
+ *
666
+ * @param {Object} [options] - formatting options
667
+ * @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
668
+ * @param {boolean} [options.html] - whether to default to HTML tags & entities.
669
+ * If a named term has fancyName property set, it will be used instead of name in this mode.
670
+ * @param {[string, string]} [options.brackets] - wrappers for application arguments, typically ['(', ')']
671
+ * @param {[string, string]} [options.var] - wrappers for variable names
672
+ * (will default to &lt;var&gt; and &lt;/var&gt; in html mode).
673
+ * @param {[string, string, string]} [options.lambda] - wrappers for lambda abstractions, e.g. ['&lambda;', '.', '']
674
+ * where the middle string is placed between argument and body
675
+ * default is ['', '->', ''] or ['', '-&gt;', ''] for html
676
+ * @param {[string, string]} [options.around] - wrappers around (sub-)expressions.
677
+ * individual applications will not be wrapped, i.e. (a b c) but not ((a b) c)
678
+ * @param {[string, string]} [options.redex] - wrappers around the starting term(s) that have enough arguments to be reduced
679
+ * @param {Object<string, Expr>} [options.inventory] - if given, output aliases in the set as their names
680
+ * and any other aliases as the expansion of their definitions.
681
+ * The default is a cryptic and fragile mechanism dependent on a hidden mutable property.
682
+ * @returns {string}
683
+ *
684
+ * @example foo.format() // equivalent to foo.toString()
685
+ * @example foo.format({terse: false}) // spell out all parentheses
686
+ * @example foo.format({html: true}) // use HTML tags and entities
687
+ * @example foo.format({ around: ['(', ')'], brackets: ['', ''], lambda: ['(', '->', ')'] }) // lisp style, still back-parsable
688
+ * @exapmle foo.format({ lambda: ['&lambda;', '.', ''] }) // pretty-print for the math department
689
+ * @example foo.format({ lambda: ['', '=>', ''], terse: false }) // make it javascript
690
+ * @example foo.format({ inventory: { T } }) // use T as a named term, expand all others
691
+ *
692
+ */
693
+ format(options = {}) {
694
+ const fallback = options.html ? {
695
+ brackets: ["(", ")"],
696
+ space: " ",
697
+ var: ["<var>", "</var>"],
698
+ lambda: ["", "-&gt;", ""],
699
+ around: ["", ""],
700
+ redex: ["", ""]
701
+ } : {
702
+ brackets: ["(", ")"],
703
+ space: " ",
704
+ var: ["", ""],
705
+ lambda: ["", "->", ""],
706
+ around: ["", ""],
707
+ redex: ["", ""]
984
708
  };
985
- FreeVar.global = ["global"];
986
- var Native = class extends Named {
987
- /**
988
- * @desc A named term with a known rewriting rule.
989
- * 'impl' is a function with signature Expr => Expr => ... => Expr
990
- * (see typedef Partial).
991
- * This is how S, K, I, and company are implemented.
992
- *
993
- * Note that as of current something like a=>b=>b(a) is not possible,
994
- * use full form instead: a=>b=>b.apply(a).
995
- *
996
- * @example new Native('K', x => y => x); // constant
997
- * @example new Native('Y', function(f) { return f.apply(this.apply(f)); }); // self-application
998
- *
999
- * @param {String} name
1000
- * @param {Partial} impl
1001
- * @param {{note?: string, arity?: number, canonize?: boolean }} [opt]
1002
- */
1003
- constructor(name, impl, opt = {}) {
1004
- super(name);
1005
- this.invoke = impl;
1006
- this._setup({ canonize: true, ...opt });
1007
- }
709
+ return this._format({
710
+ terse: options.terse ?? true,
711
+ brackets: options.brackets ?? fallback.brackets,
712
+ space: options.space ?? fallback.space,
713
+ var: options.var ?? fallback.var,
714
+ lambda: options.lambda ?? fallback.lambda,
715
+ around: options.around ?? fallback.around,
716
+ redex: options.redex ?? fallback.redex,
717
+ inventory: options.inventory,
718
+ // TODO better name
719
+ html: options.html ?? false
720
+ }, 0);
721
+ }
722
+ /**
723
+ * @desc Internal method for format(), which performs the actual formatting.
724
+ * @param {Object} options
725
+ * @param {number} nargs
726
+ * @returns {string}
727
+ * @private
728
+ */
729
+ _format(options, nargs) {
730
+ throw new Error("No _format() method defined in class " + this.constructor.name);
731
+ }
732
+ /**
733
+ * @desc Returns a string representation of the expression tree, with indentation to show structure.
734
+ *
735
+ * Applications are flattened to avoid excessive nesting.
736
+ * Variables include ids to distinguish different instances of the same variable name.
737
+ *
738
+ * May be useful for debugging.
739
+ *
740
+ * @returns {string}
741
+ *
742
+ * @example
743
+ * > console.log(ski.parse('C 5 x (x->x x)').diag())
744
+ * App:
745
+ * Native: C
746
+ * Church: 5
747
+ * FreeVar: x[53]
748
+ * Lambda (x[54]):
749
+ * App:
750
+ * FreeVar: x[54]
751
+ * FreeVar: x[54]
752
+ */
753
+ diag() {
754
+ const rec = (e, indent) => {
755
+ if (e instanceof App)
756
+ return [indent + "App:", ...e.unroll().flatMap((s) => rec(s, indent + " "))];
757
+ if (e instanceof Lambda)
758
+ return [`${indent}Lambda (${e.arg}[${e.arg.id}]):`, ...rec(e.impl, indent + " ")];
759
+ if (e instanceof Alias)
760
+ return [`${indent}Alias (${e.name}): \\`, ...rec(e.impl, indent)];
761
+ if (e instanceof FreeVar)
762
+ return [`${indent}FreeVar: ${e.name}[${e.id}]`];
763
+ return [`${indent}${e.constructor.name}: ${e}`];
1008
764
  };
1009
- var Lambda = class _Lambda extends Expr {
1010
- /**
1011
- * @desc Lambda abstraction of arg over impl.
1012
- * Upon evaluation, all occurrences of 'arg' within 'impl' will be replaced
1013
- * with the provided argument.
1014
- *
1015
- * Note that 'arg' will be replaced by a localized placeholder, so the original
1016
- * variable can be used elsewhere without interference.
1017
- * Listing symbols contained in the lambda will omit such placeholder.
1018
- *
1019
- * Legacy ([FreeVar], impl) constructor is supported but deprecated.
1020
- * It will create a nested lambda expression.
1021
- *
1022
- * @param {FreeVar} arg
1023
- * @param {Expr} impl
1024
- */
1025
- constructor(arg, impl) {
1026
- if (Array.isArray(arg)) {
1027
- if (arg.length === 0)
1028
- throw new Error("empty argument list in lambda constructor");
1029
- const [my, ...tail] = arg;
1030
- const known = /* @__PURE__ */ new Set([my.name]);
1031
- while (tail.length > 0) {
1032
- const last = tail.pop();
1033
- if (known.has(last.name))
1034
- throw new Error("Duplicate free var name " + last + " in lambda expression");
1035
- known.add(last.name);
1036
- impl = new _Lambda(last, impl);
1037
- }
1038
- arg = my;
1039
- }
1040
- super();
1041
- const local = new FreeVar(arg.name, this);
1042
- this.arg = local;
1043
- this.impl = impl.subst(arg, local) ?? impl;
1044
- this.arity = 1;
1045
- }
1046
- weight() {
1047
- return this.impl.weight() + 1;
1048
- }
1049
- invoke(arg) {
1050
- return this.impl.subst(this.arg, arg) ?? this.impl;
1051
- }
1052
- _traverse_descend(options, change) {
1053
- const [impl, iAction] = unwrap(this.impl._traverse_redo(options, change));
1054
- const final = impl ? new _Lambda(this.arg, impl) : null;
1055
- return iAction === control.stop ? control.stop(final) : final;
1056
- }
1057
- any(predicate) {
1058
- return predicate(this) || this.impl.any(predicate);
1059
- }
1060
- _fold(initial, combine) {
1061
- const [value = initial, action = "descend"] = unwrap(combine(initial, this));
1062
- if (action === control.prune)
1063
- return value;
1064
- if (action === control.stop)
1065
- return control.stop(value);
1066
- const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
1067
- if (iAction === control.stop)
1068
- return control.stop(iValue);
1069
- return iValue ?? value;
1070
- }
1071
- subst(search, replace) {
1072
- if (search === this.arg)
1073
- return null;
1074
- const change = this.impl.subst(search, replace);
1075
- return change ? new _Lambda(this.arg, change) : null;
1076
- }
1077
- diff(other, swap = false) {
1078
- if (!(other instanceof _Lambda))
1079
- return super.diff(other, swap);
1080
- const t = new FreeVar("t");
1081
- const diff = this.invoke(t).diff(other.invoke(t), swap);
1082
- if (diff)
1083
- return "(t->" + diff + ")";
1084
- return null;
1085
- }
1086
- _format(options, nargs) {
1087
- 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] : "");
1088
- }
1089
- _braced(first) {
1090
- return true;
1091
- }
765
+ const out = rec(this, "");
766
+ return out.join("\n");
767
+ }
768
+ /**
769
+ * @desc Convert the expression to a JSON-serializable format.
770
+ * @returns {string}
771
+ */
772
+ toJSON() {
773
+ return this.format();
774
+ }
775
+ };
776
+ var App = class _App extends Expr {
777
+ constructor(fun, arg) {
778
+ super();
779
+ this.arg = arg;
780
+ this.fun = fun;
781
+ }
782
+ /** @property {boolean} [final] */
783
+ _traverse_descend(options, change) {
784
+ const [fun, fAction] = unwrap(this.fun._traverse_redo(options, change));
785
+ if (fAction === control.stop)
786
+ return control.stop(fun ? fun.apply(this.arg) : null);
787
+ const [arg, aAction] = unwrap(this.arg._traverse_redo(options, change));
788
+ const final = fun || arg ? (fun ?? this.fun).apply(arg ?? this.arg) : null;
789
+ if (aAction === control.stop)
790
+ return control.stop(final);
791
+ return final;
792
+ }
793
+ any(predicate) {
794
+ return predicate(this) || this.fun.any(predicate) || this.arg.any(predicate);
795
+ }
796
+ _fold(initial, combine) {
797
+ const [value = initial, action = "descend"] = unwrap(combine(initial, this));
798
+ if (action === control.prune)
799
+ return value;
800
+ if (action === control.stop)
801
+ return control.stop(value);
802
+ const [fValue = value, fAction = "descend"] = unwrap(this.fun._fold(value, combine));
803
+ if (fAction === control.stop)
804
+ return control.stop(fValue);
805
+ const [aValue = fValue, aAction = "descend"] = unwrap(this.arg._fold(fValue, combine));
806
+ if (aAction === control.stop)
807
+ return control.stop(aValue);
808
+ return aValue;
809
+ }
810
+ subst(search2, replace) {
811
+ const fun = this.fun.subst(search2, replace);
812
+ const arg = this.arg.subst(search2, replace);
813
+ return fun || arg ? (fun ?? this.fun).apply(arg ?? this.arg) : null;
814
+ }
815
+ /**
816
+ * @return {{expr: Expr, steps: number}}
817
+ */
818
+ step() {
819
+ if (!this.final) {
820
+ const partial = this.fun.invoke(this.arg);
821
+ if (partial instanceof Expr)
822
+ return { expr: partial, steps: 1, changed: true };
823
+ else if (typeof partial === "function")
824
+ this.invoke = partial;
825
+ const fun = this.fun.step();
826
+ if (fun.changed)
827
+ return { expr: fun.expr.apply(this.arg), steps: fun.steps, changed: true };
828
+ const arg = this.arg.step();
829
+ if (arg.changed)
830
+ return { expr: this.fun.apply(arg.expr), steps: arg.steps, changed: true };
831
+ this.final = true;
832
+ }
833
+ return { expr: this, steps: 0, changed: false };
834
+ }
835
+ invoke(arg) {
836
+ const partial = this.fun.invoke(this.arg);
837
+ if (partial instanceof Expr)
838
+ return partial.apply(arg);
839
+ else if (typeof partial === "function") {
840
+ this.invoke = partial;
841
+ return partial(arg);
842
+ } else {
843
+ this.invoke = (arg2) => null;
844
+ return null;
845
+ }
846
+ }
847
+ unroll() {
848
+ return [...this.fun.unroll(), this.arg];
849
+ }
850
+ diff(other, swap = false) {
851
+ if (!(other instanceof _App))
852
+ return super.diff(other, swap);
853
+ const fun = this.fun.diff(other.fun, swap);
854
+ if (fun)
855
+ return fun + "(...)";
856
+ const arg = this.arg.diff(other.arg, swap);
857
+ if (arg)
858
+ return this.fun + "(" + arg + ")";
859
+ return null;
860
+ }
861
+ _braced(first) {
862
+ return !first;
863
+ }
864
+ _format(options, nargs) {
865
+ const fun = this.fun._format(options, nargs + 1);
866
+ const arg = this.arg._format(options, 0);
867
+ const wrap = nargs ? ["", ""] : options.around;
868
+ if (options.terse && !this.arg._braced(false))
869
+ return wrap[0] + fun + (this.fun._unspaced(this.arg) ? "" : options.space) + arg + wrap[1];
870
+ else
871
+ return wrap[0] + fun + options.brackets[0] + arg + options.brackets[1] + wrap[1];
872
+ }
873
+ _unspaced(arg) {
874
+ return this.arg._braced(false) ? true : this.arg._unspaced(arg);
875
+ }
876
+ };
877
+ var Named = class _Named extends Expr {
878
+ constructor(name) {
879
+ super();
880
+ if (typeof name !== "string" || name.length === 0)
881
+ throw new Error("Attempt to create a named term with improper name");
882
+ this.name = name;
883
+ }
884
+ _unspaced(arg) {
885
+ 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+]$/)));
886
+ }
887
+ _format(options, nargs) {
888
+ const name = options.html ? this.fancyName ?? this.name : this.name;
889
+ return this.arity !== void 0 && this.arity > 0 && this.arity <= nargs ? options.redex[0] + name + options.redex[1] : name;
890
+ }
891
+ };
892
+ var freeId = 0;
893
+ var FreeVar = class _FreeVar extends Named {
894
+ constructor(name, scope) {
895
+ super(name);
896
+ this.id = ++freeId;
897
+ this.scope = scope === void 0 ? this : scope;
898
+ }
899
+ diff(other, swap = false) {
900
+ if (!(other instanceof _FreeVar))
901
+ return super.diff(other, swap);
902
+ if (this.name === other.name && this.scope === other.scope)
903
+ return null;
904
+ const lhs = this.name + "[" + this.id + "]";
905
+ const rhs = other.name + "[" + other.id + "]";
906
+ return swap ? "[" + rhs + " != " + lhs + "]" : "[" + lhs + " != " + rhs + "]";
907
+ }
908
+ subst(search2, replace) {
909
+ if (search2 instanceof _FreeVar && search2.name === this.name && search2.scope === this.scope)
910
+ return replace;
911
+ return null;
912
+ }
913
+ _format(options, nargs) {
914
+ const name = options.html ? this.fancyName ?? this.name : this.name;
915
+ return options.var[0] + name + options.var[1];
916
+ }
917
+ static {
918
+ this.global = ["global"];
919
+ }
920
+ };
921
+ var Native = class extends Named {
922
+ /**
923
+ * @desc A named term with a known rewriting rule.
924
+ * 'impl' is a function with signature Expr => Expr => ... => Expr
925
+ * (see typedef Partial).
926
+ * This is how S, K, I, and company are implemented.
927
+ *
928
+ * Note that as of current something like a=>b=>b(a) is not possible,
929
+ * use full form instead: a=>b=>b.apply(a).
930
+ *
931
+ * @example new Native('K', x => y => x); // constant
932
+ * @example new Native('Y', function(f) { return f.apply(this.apply(f)); }); // self-application
933
+ *
934
+ * @param {String} name
935
+ * @param {Partial} impl
936
+ * @param {{note?: string, arity?: number, canonize?: boolean }} [opt]
937
+ */
938
+ constructor(name, impl, opt = {}) {
939
+ super(name);
940
+ this.invoke = impl;
941
+ this._setup({ canonize: true, ...opt });
942
+ }
943
+ };
944
+ var Lambda = class _Lambda extends Expr {
945
+ constructor(arg, impl) {
946
+ super();
947
+ if (!(arg instanceof FreeVar))
948
+ throw new Error("Lambda argument must be a FreeVar");
949
+ const local = new FreeVar(arg.name, this);
950
+ this.arg = local;
951
+ this.impl = impl.subst(arg, local) ?? impl;
952
+ this.arity = 1;
953
+ }
954
+ invoke(arg) {
955
+ return this.impl.subst(this.arg, arg) ?? this.impl;
956
+ }
957
+ _traverse_descend(options, change) {
958
+ const [impl, iAction] = unwrap(this.impl._traverse_redo(options, change));
959
+ const final = impl ? new _Lambda(this.arg, impl) : null;
960
+ return iAction === control.stop ? control.stop(final) : final;
961
+ }
962
+ any(predicate) {
963
+ return predicate(this) || this.impl.any(predicate);
964
+ }
965
+ _fold(initial, combine) {
966
+ const [value = initial, action = "descend"] = unwrap(combine(initial, this));
967
+ if (action === control.prune)
968
+ return value;
969
+ if (action === control.stop)
970
+ return control.stop(value);
971
+ const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
972
+ if (iAction === control.stop)
973
+ return control.stop(iValue);
974
+ return iValue ?? value;
975
+ }
976
+ subst(search2, replace) {
977
+ if (search2 === this.arg)
978
+ return null;
979
+ const change = this.impl.subst(search2, replace);
980
+ return change ? new _Lambda(this.arg, change) : null;
981
+ }
982
+ diff(other, swap = false) {
983
+ if (!(other instanceof _Lambda))
984
+ return super.diff(other, swap);
985
+ const t = new FreeVar("t");
986
+ const diff = this.invoke(t).diff(other.invoke(t), swap);
987
+ if (diff)
988
+ return "(t->" + diff + ")";
989
+ return null;
990
+ }
991
+ _format(options, nargs) {
992
+ 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] : "");
993
+ }
994
+ _braced(first) {
995
+ return true;
996
+ }
997
+ };
998
+ var Church = class _Church extends Expr {
999
+ constructor(n) {
1000
+ n = Number.parseInt(String(n));
1001
+ if (!(n >= 0))
1002
+ throw new Error("Church number must be a non-negative integer");
1003
+ super();
1004
+ this.invoke = (x) => (y) => {
1005
+ let expr = y;
1006
+ for (let i = n; i-- > 0; )
1007
+ expr = x.apply(expr);
1008
+ return expr;
1092
1009
  };
1093
- var Church = class _Church extends Expr {
1094
- /**
1095
- * @desc Church numeral representing non-negative integer n:
1096
- * n f x = f(f(...(f x)...)) with f applied n times.
1097
- * @param {number} n
1098
- */
1099
- constructor(n) {
1100
- n = Number.parseInt(n);
1101
- if (!(n >= 0))
1102
- throw new Error("Church number must be a non-negative integer");
1103
- super();
1104
- this.invoke = (x) => (y) => {
1105
- let expr = y;
1106
- for (let i = n; i-- > 0; )
1107
- expr = x.apply(expr);
1108
- return expr;
1109
- };
1110
- this.n = n;
1111
- this.arity = 2;
1112
- }
1113
- diff(other, swap = false) {
1114
- if (!(other instanceof _Church))
1115
- return super.diff(other, swap);
1116
- if (this.n === other.n)
1117
- return null;
1118
- return swap ? "[" + other.n + " != " + this.n + "]" : "[" + this.n + " != " + other.n + "]";
1119
- }
1120
- _unspaced(arg) {
1121
- return false;
1122
- }
1123
- _format(options, nargs) {
1124
- return nargs >= 2 ? options.redex[0] + this.n + options.redex[1] : this.n + "";
1010
+ this.n = n;
1011
+ this.arity = 2;
1012
+ }
1013
+ diff(other, swap = false) {
1014
+ if (!(other instanceof _Church))
1015
+ return super.diff(other, swap);
1016
+ if (this.n === other.n)
1017
+ return null;
1018
+ return swap ? "[" + other.n + " != " + this.n + "]" : "[" + this.n + " != " + other.n + "]";
1019
+ }
1020
+ _unspaced(arg) {
1021
+ return false;
1022
+ }
1023
+ _format(options, nargs) {
1024
+ return nargs >= 2 ? options.redex[0] + this.n + options.redex[1] : this.n + "";
1025
+ }
1026
+ };
1027
+ function waitn(expr, n) {
1028
+ return (arg) => n <= 1 ? expr.apply(arg) : waitn(expr.apply(arg), n - 1);
1029
+ }
1030
+ var Alias = class extends Named {
1031
+ constructor(name, impl, options = {}) {
1032
+ super(name);
1033
+ if (!(impl instanceof Expr))
1034
+ throw new Error("Attempt to create an alias for a non-expression: " + impl);
1035
+ this.impl = impl;
1036
+ this._setup(options);
1037
+ this.terminal = options.terminal ?? this.props?.proper;
1038
+ this.invoke = waitn(impl, this.arity ?? 0);
1039
+ }
1040
+ /**
1041
+ * @property {boolean} [outdated] - whether the alias is outdated
1042
+ * and should be replaced with its definition when encountered.
1043
+ * @property {boolean} [terminal] - whether the alias should behave like a standalone term
1044
+ * // TODO better name?
1045
+ * @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
1046
+ * @property {number} [arity] - the number of arguments the alias waits for before expanding
1047
+ * @property {Expr} [canonical] - equivalent lambda term.
1048
+ */
1049
+ _traverse_descend(options, change) {
1050
+ return this.impl._traverse_redo(options, change);
1051
+ }
1052
+ any(predicate) {
1053
+ return predicate(this) || this.impl.any(predicate);
1054
+ }
1055
+ _fold(initial, combine) {
1056
+ const [value = initial, action] = unwrap(combine(initial, this));
1057
+ if (action === control.prune)
1058
+ return value;
1059
+ if (action === control.stop)
1060
+ return control.stop(value);
1061
+ const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
1062
+ if (iAction === control.stop)
1063
+ return control.stop(iValue);
1064
+ return iValue ?? value;
1065
+ }
1066
+ subst(search2, replace) {
1067
+ if (this === search2)
1068
+ return replace;
1069
+ return this.impl.subst(search2, replace);
1070
+ }
1071
+ // DO NOT REMOVE TYPE or tsc chokes with
1072
+ // TS2527: The inferred type of 'Alias' references an inaccessible 'this' type.
1073
+ /**
1074
+ * @return {{expr: Expr, steps: number, changed: boolean}}
1075
+ */
1076
+ step() {
1077
+ if ((this.arity ?? 0) > 0)
1078
+ return { expr: this, steps: 0, changed: false };
1079
+ return { expr: this.impl, steps: 0, changed: true };
1080
+ }
1081
+ diff(other, swap = false) {
1082
+ if (this === other)
1083
+ return null;
1084
+ return other.diff(this.impl, !swap);
1085
+ }
1086
+ _braced(first) {
1087
+ return this.outdated ? this.impl._braced(first) : false;
1088
+ }
1089
+ _format(options, nargs) {
1090
+ const outdated = options.inventory ? options.inventory[this.name] !== this : this.outdated;
1091
+ return outdated ? this.impl._format(options, nargs) : super._format(options, nargs);
1092
+ }
1093
+ };
1094
+ function addNative(name, impl, opt = {}) {
1095
+ native[name] = new Native(name, impl, opt);
1096
+ }
1097
+ addNative("I", (x) => x);
1098
+ addNative("K", (x) => (_y) => x);
1099
+ addNative("S", (x) => (y) => (z) => x.apply(z, y.apply(z)));
1100
+ addNative("B", (x) => (y) => (z) => x.apply(y.apply(z)));
1101
+ addNative("C", (x) => (y) => (z) => x.apply(z).apply(y));
1102
+ addNative("W", (x) => (y) => x.apply(y).apply(y));
1103
+ addNative(
1104
+ "+",
1105
+ (n) => n instanceof Church ? new Church(n.n + 1) : (f) => (x) => f.apply(n.apply(f, x)),
1106
+ {
1107
+ note: "Increase a Church numeral argument by 1, otherwise n => f => x => f(n f x)"
1108
+ }
1109
+ );
1110
+ function firstVar(expr) {
1111
+ while (expr instanceof App)
1112
+ expr = expr.fun;
1113
+ return expr instanceof FreeVar;
1114
+ }
1115
+ function maybeLambda(args, expr, caps) {
1116
+ const count = new Array(args.length).fill(0);
1117
+ let proper = true;
1118
+ expr.traverse((e) => {
1119
+ if (e instanceof FreeVar) {
1120
+ const index = args.findIndex((a) => a.name === e.name);
1121
+ if (index >= 0) {
1122
+ count[index]++;
1123
+ return;
1125
1124
  }
1126
- };
1127
- function waitn(expr, n) {
1128
- return (arg) => n <= 1 ? expr.apply(arg) : waitn(expr.apply(arg), n - 1);
1129
1125
  }
1130
- var Alias = class extends Named {
1131
- /**
1132
- * @desc A named alias for an existing expression.
1133
- *
1134
- * Upon evaluation, the alias expands into the original expression,
1135
- * unless it has a known arity > 0 and is marked terminal,
1136
- * in which case it waits for enough arguments before expanding.
1137
- *
1138
- * A hidden mutable property 'outdated' is used to silently
1139
- * replace the alias with its definition in all contexts.
1140
- * This is used when declaring named terms in an interpreter,
1141
- * to avoid confusion between old and new terms with the same name.
1142
- *
1143
- * @param {String} name
1144
- * @param {Expr} impl
1145
- * @param {{canonize?: boolean, max?: number, maxArgs?: number, note?: string, terminal?: boolean}} [options]
1146
- */
1147
- constructor(name, impl, options = {}) {
1148
- super(name);
1149
- if (!(impl instanceof Expr))
1150
- throw new Error("Attempt to create an alias for a non-expression: " + impl);
1151
- this.impl = impl;
1152
- this._setup(options);
1153
- this.terminal = options.terminal ?? this.props?.proper;
1154
- this.invoke = waitn(impl, this.arity ?? 0);
1155
- }
1156
- /**
1157
- * @property {boolean} [outdated] - whether the alias is outdated
1158
- * and should be replaced with its definition when encountered.
1159
- * @property {boolean} [terminal] - whether the alias should behave like a standalone term
1160
- * // TODO better name?
1161
- * @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
1162
- * @property {number} [arity] - the number of arguments the alias waits for before expanding
1163
- * @property {Expr} [canonical] - equivalent lambda term.
1164
- */
1165
- weight() {
1166
- return this.terminal ? 1 : this.impl.weight();
1167
- }
1168
- _traverse_descend(options, change) {
1169
- return this.impl._traverse_redo(options, change);
1170
- }
1171
- any(predicate) {
1172
- return predicate(this) || this.impl.any(predicate);
1173
- }
1174
- _fold(initial, combine) {
1175
- const [value = initial, action] = unwrap(combine(initial, this));
1176
- if (action === control.prune)
1177
- return value;
1178
- if (action === control.stop)
1179
- return control.stop(value);
1180
- const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
1181
- if (iAction === control.stop)
1182
- return control.stop(iValue);
1183
- return iValue ?? value;
1184
- }
1185
- subst(search, replace) {
1186
- if (this === search)
1187
- return replace;
1188
- return this.impl.subst(search, replace);
1189
- }
1190
- // DO NOT REMOVE TYPE or tsc chokes with
1191
- // TS2527: The inferred type of 'Alias' references an inaccessible 'this' type.
1192
- /**
1193
- * @return {{expr: Expr, steps: number, changed: boolean}}
1194
- */
1195
- step() {
1196
- if (this.arity > 0)
1197
- return { expr: this, steps: 0, changed: false };
1198
- return { expr: this.impl, steps: 0, changed: true };
1199
- }
1200
- diff(other, swap = false) {
1201
- if (this === other)
1202
- return null;
1203
- return other.diff(this.impl, !swap);
1204
- }
1205
- _braced(first) {
1206
- return this.outdated ? this.impl._braced(first) : false;
1207
- }
1208
- _format(options, nargs) {
1209
- const outdated = options.inventory ? options.inventory[this.name] !== this : this.outdated;
1210
- return outdated ? this.impl._format(options, nargs) : super._format(options, nargs);
1211
- }
1212
- };
1213
- function addNative(name, impl, opt) {
1214
- native[name] = new Native(name, impl, opt);
1126
+ if (!(e instanceof App))
1127
+ proper = false;
1128
+ return void 0;
1129
+ });
1130
+ const skip = /* @__PURE__ */ new Set();
1131
+ const dup = /* @__PURE__ */ new Set();
1132
+ for (let i = 0; i < args.length; i++) {
1133
+ if (count[i] === 0)
1134
+ skip.add(i);
1135
+ else if (count[i] > 1)
1136
+ dup.add(i);
1137
+ }
1138
+ for (let i = args.length; i-- > 0; )
1139
+ expr = new Lambda(args[i], expr);
1140
+ return {
1141
+ normal: true,
1142
+ steps: caps.steps,
1143
+ expr,
1144
+ arity: args.length,
1145
+ ...skip.size ? { skip } : {},
1146
+ ...dup.size ? { dup } : {},
1147
+ duplicate: !!dup.size || caps.duplicate || false,
1148
+ discard: !!skip.size || caps.discard || false,
1149
+ proper
1150
+ };
1151
+ }
1152
+ function nthvar(n) {
1153
+ return new FreeVar("abcdefgh"[n] ?? "x" + n);
1154
+ }
1155
+ var classes = { Expr, App, Named, FreeVar, Native, Lambda, Church, Alias };
1156
+
1157
+ // src/toposort.ts
1158
+ function toposort(list, env) {
1159
+ if (list instanceof Expr)
1160
+ list = [list];
1161
+ if (env) {
1162
+ if (!list)
1163
+ list = Object.keys(env).sort().map((k) => env[k]);
1164
+ } else {
1165
+ if (!list)
1166
+ return { list: [], env: {} };
1167
+ env = {};
1168
+ for (const item of list) {
1169
+ if (!(item instanceof Named))
1170
+ continue;
1171
+ if (env[item.name])
1172
+ throw new Error("duplicate name " + item);
1173
+ env[item.name] = item;
1215
1174
  }
1216
- addNative("I", (x) => x);
1217
- addNative("K", (x) => (_) => x);
1218
- addNative("S", (x) => (y) => (z) => x.apply(z, y.apply(z)));
1219
- addNative("B", (x) => (y) => (z) => x.apply(y.apply(z)));
1220
- addNative("C", (x) => (y) => (z) => x.apply(z).apply(y));
1221
- addNative("W", (x) => (y) => x.apply(y).apply(y));
1222
- addNative(
1223
- "+",
1224
- (n) => n instanceof Church ? new Church(n.n + 1) : (f) => (x) => f.apply(n.apply(f, x)),
1225
- {
1226
- note: "Increase a Church numeral argument by 1, otherwise n => f => x => f(n f x)"
1175
+ }
1176
+ const out = [];
1177
+ const seen = /* @__PURE__ */ new Set();
1178
+ const rec = (term) => {
1179
+ if (seen.has(term))
1180
+ return;
1181
+ term.fold(false, (acc, e) => {
1182
+ if (e !== term && e instanceof Named && env[e.name] === e) {
1183
+ rec(e);
1184
+ return control.prune(false);
1185
+ }
1186
+ });
1187
+ out.push(term);
1188
+ seen.add(term);
1189
+ };
1190
+ for (const term of list)
1191
+ rec(term);
1192
+ return {
1193
+ list: out,
1194
+ env
1195
+ };
1196
+ }
1197
+
1198
+ // src/parser.ts
1199
+ var Empty = class extends Expr {
1200
+ apply(...args) {
1201
+ return args.length > 0 ? args.shift().apply(...args) : this;
1202
+ }
1203
+ postParse() {
1204
+ throw new Error("Attempt to use empty expression () as a term");
1205
+ }
1206
+ };
1207
+ var PartialLambda = class _PartialLambda extends Empty {
1208
+ // TODO mutable! rewrite ro when have time
1209
+ constructor(term, _known = {}) {
1210
+ super();
1211
+ this.impl = new Empty();
1212
+ if (term instanceof FreeVar)
1213
+ this.terms = [term];
1214
+ else if (term instanceof _PartialLambda) {
1215
+ if (!(term.impl instanceof FreeVar))
1216
+ throw new Error("Expected FreeVar->...->FreeVar->Expr");
1217
+ this.terms = [...term.terms, term.impl];
1218
+ } else
1219
+ throw new Error("Expected FreeVar or PartialLambda");
1220
+ }
1221
+ apply(term, ...tail) {
1222
+ if (term === null || tail.length !== 0)
1223
+ throw new Error("bad syntax in partial lambda expr");
1224
+ this.impl = this.impl.apply(term);
1225
+ return this;
1226
+ }
1227
+ postParse() {
1228
+ let expr = this.impl;
1229
+ for (let i = this.terms.length; i-- > 0; )
1230
+ expr = new Lambda(this.terms[i], expr);
1231
+ return expr;
1232
+ }
1233
+ // uncomment if debugging with prints
1234
+ /* toString () {
1235
+ return this.terms.join('->') + '->' + (this.impl ?? '???');
1236
+ } */
1237
+ };
1238
+ function postParse(expr) {
1239
+ return expr.postParse ? expr.postParse() : expr;
1240
+ }
1241
+ var combChars = new Tokenizer(
1242
+ "[()]",
1243
+ "[A-Z]",
1244
+ "[a-z_][a-z_0-9]*",
1245
+ "\\b[0-9]+\\b",
1246
+ "->",
1247
+ "\\+"
1248
+ );
1249
+ var Parser = class {
1250
+ constructor(options = {}) {
1251
+ this.annotate = !!options.annotate;
1252
+ this.known = { ...native };
1253
+ this.hasNumbers = true;
1254
+ this.hasLambdas = true;
1255
+ this.allow = new Set(Object.keys(this.known));
1256
+ if (Array.isArray(options.terms))
1257
+ this.bulkAdd(options.terms);
1258
+ else if (options.terms) {
1259
+ for (const name in options.terms) {
1260
+ if (typeof options.terms[name] !== "string" || !options.terms[name].match(/^Native:/))
1261
+ this.add(name, options.terms[name]);
1227
1262
  }
1228
- );
1229
- function firstVar(expr) {
1230
- while (expr instanceof App)
1231
- expr = expr.fun;
1232
- return expr instanceof FreeVar;
1233
1263
  }
1234
- function maybeLambda(args, expr, caps = {}) {
1235
- const count = new Array(args.length).fill(0);
1236
- let proper = true;
1237
- expr.traverse((e) => {
1238
- if (e instanceof FreeVar) {
1239
- const index = args.findIndex((a) => a.name === e.name);
1240
- if (index >= 0) {
1241
- count[index]++;
1242
- return;
1243
- }
1244
- }
1245
- if (!(e instanceof App))
1246
- proper = false;
1247
- });
1248
- const skip = /* @__PURE__ */ new Set();
1249
- const dup = /* @__PURE__ */ new Set();
1250
- for (let i = 0; i < args.length; i++) {
1251
- if (count[i] === 0)
1252
- skip.add(i);
1253
- else if (count[i] > 1)
1254
- dup.add(i);
1255
- }
1256
- return {
1257
- normal: true,
1258
- steps: caps.steps,
1259
- expr: args.length ? new Lambda(args, expr) : expr,
1260
- arity: args.length,
1261
- ...skip.size ? { skip } : {},
1262
- ...dup.size ? { dup } : {},
1263
- duplicate: !!dup.size || caps.duplicate || false,
1264
- discard: !!skip.size || caps.discard || false,
1265
- proper
1266
- };
1264
+ this.hasNumbers = options.numbers ?? true;
1265
+ this.hasLambdas = options.lambdas ?? true;
1266
+ if (options.allow)
1267
+ this.restrict(options.allow);
1268
+ }
1269
+ /**
1270
+ * @desc Declare a new term
1271
+ * If the first argument is an Alias, it is added as is.
1272
+ * Otherwise, a new Alias or Native term (depending on impl type) is created.
1273
+ * If note is not provided and this.annotate is true, an automatic note is generated.
1274
+ *
1275
+ * If impl is a function, it should have signature (Expr) => ... => Expr
1276
+ * (see typedef Partial at top of expr.js)
1277
+ *
1278
+ * @example ski.add('T', 'S(K(SI))K', 'swap combinator')
1279
+ * @example ski.add( ski.parse('T = S(K(SI))K') ) // ditto but one-arg form
1280
+ * @example ski.add('T', x => y => y.apply(x), 'swap combinator') // heavy artillery
1281
+ * @example ski.add('Y', function (f) { return f.apply(this.apply(f)); }, 'Y combinator')
1282
+ *
1283
+ * @param {Alias|String} term
1284
+ * @param {String|Expr|function(Expr):Partial} [impl]
1285
+ * @param {object|string} [options]
1286
+ * @param {string} [options.note] - optional annotation for the term, default is auto-generated if this.annotate is true
1287
+ * @param {boolean} [options.canonize] - whether to canonize the term's implementation, default is this.annotate
1288
+ * @param {boolean} [options.fancy] - alternative HTML-friendly name for the term
1289
+ * @param {number} [options.arity] - custom arity for the term, default is inferred from the implementation
1290
+ * @return {SKI} chainable
1291
+ */
1292
+ add(term, impl, options) {
1293
+ const named = this._named(term, impl);
1294
+ const opts = typeof options === "string" ? { note: options, canonize: false } : options ?? {};
1295
+ named._setup({ canonize: this.annotate, ...opts });
1296
+ if (this.known[named.name])
1297
+ this.known[named.name].outdated = true;
1298
+ this.known[named.name] = named;
1299
+ this.allow.add(named.name);
1300
+ return this;
1301
+ }
1302
+ /**
1303
+ * @desc Internal helper for add() that creates an Alias or Native term from the given arguments.
1304
+ * @param {Alias|string} term
1305
+ * @param {string|Expr|function(Expr):Partial} impl
1306
+ * @returns {Native|Alias}
1307
+ * @private
1308
+ */
1309
+ _named(term, impl) {
1310
+ if (term instanceof Alias)
1311
+ return new Alias(term.name, term.impl, { canonize: true });
1312
+ if (typeof term !== "string")
1313
+ throw new Error("add(): term must be an Alias or a string");
1314
+ if (impl === void 0)
1315
+ throw new Error("add(): impl must be provided when term is a string");
1316
+ if (typeof impl === "string")
1317
+ return new Alias(term, this.parse(impl), { canonize: true });
1318
+ if (impl instanceof Expr)
1319
+ return new Alias(term, impl, { canonize: true });
1320
+ if (typeof impl === "function")
1321
+ return new Native(term, impl);
1322
+ throw new Error("add(): impl must be an Expr, a string, or a function with a signature Expr => ... => Expr");
1323
+ }
1324
+ /**
1325
+ * @desc Declare a new term if it is not known, otherwise just allow it.
1326
+ * Currently only used by quests.
1327
+ * Use with caution, this function may change its signature, behavior, or even be removed in the future.
1328
+ *
1329
+ * @experimental
1330
+ * @param {string|Alias} name
1331
+ * @param {string|Expr|function(Expr):Partial} impl
1332
+ * @returns {SKI}
1333
+ */
1334
+ maybeAdd(name, impl) {
1335
+ if (this.known[name])
1336
+ this.allow.add(name);
1337
+ else
1338
+ this.add(name, impl);
1339
+ return this;
1340
+ }
1341
+ /**
1342
+ * @desc Declare and remove multiple terms at once
1343
+ * term=impl adds term
1344
+ * term= removes term
1345
+ * @param {string[]} list
1346
+ * @return {SKI} chainable
1347
+ */
1348
+ bulkAdd(list) {
1349
+ for (const item of list) {
1350
+ const m = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
1351
+ if (!m)
1352
+ throw new Error("bulkAdd: invalid declaration: " + item);
1353
+ if (m[2] === "")
1354
+ this.remove(m[1]);
1355
+ else
1356
+ this.add(m[1], this.parse(m[2]));
1267
1357
  }
1268
- function nthvar(n) {
1269
- return new FreeVar("abcdefgh"[n] ?? "x" + n);
1358
+ return this;
1359
+ }
1360
+ /**
1361
+ * Restrict the interpreter to given terms. Terms prepended with '+' will be added
1362
+ * and terms preceeded with '-' will be removed.
1363
+ * @example ski.restrict('SK') // use the basis
1364
+ * @example ski.restrict('+I') // allow I now
1365
+ * @example ski.restrict('-SKI +BCKW' ); // switch basis
1366
+ * @example ski.restrict('-foo -bar'); // forbid some user functions
1367
+ * @param {string} spec
1368
+ * @return {SKI} chainable
1369
+ */
1370
+ restrict(spec) {
1371
+ this.allow = restrict(this.allow, spec);
1372
+ return this;
1373
+ }
1374
+ /**
1375
+ *
1376
+ * @param {string} spec
1377
+ * @return {string}
1378
+ */
1379
+ showRestrict(spec = "+") {
1380
+ const out = [];
1381
+ let prevShort = true;
1382
+ for (const term of [...restrict(this.allow, spec)].sort()) {
1383
+ const nextShort = !!term.match(/^[A-Z]$/);
1384
+ if (out.length && !(prevShort && nextShort))
1385
+ out.push(" ");
1386
+ out.push(term);
1387
+ prevShort = nextShort;
1270
1388
  }
1271
- function toposort(list, env) {
1272
- if (list instanceof Expr)
1273
- list = [list];
1274
- if (env) {
1275
- if (!list)
1276
- list = Object.keys(env).sort().map((k) => env[k]);
1277
- } else {
1278
- if (!list)
1279
- return [];
1280
- if (!env) {
1281
- env = {};
1282
- for (const item of list) {
1283
- if (!(item instanceof Named))
1284
- continue;
1285
- if (env[item.name])
1286
- throw new Error("duplicate name " + item);
1287
- env[item.name] = item;
1288
- }
1289
- }
1290
- }
1291
- const out = [];
1292
- const seen = /* @__PURE__ */ new Set();
1293
- const rec = (term) => {
1294
- if (seen.has(term))
1295
- return;
1296
- term.fold(null, (acc, e) => {
1297
- if (e !== term && e instanceof Named && env[e.name] === e) {
1298
- rec(e);
1299
- return Expr.control.prune(null);
1300
- }
1301
- });
1302
- out.push(term);
1303
- seen.add(term);
1304
- };
1305
- for (const term of list)
1306
- rec(term);
1307
- return {
1308
- list: out,
1309
- env
1310
- };
1389
+ return out.join("");
1390
+ }
1391
+ /**
1392
+ *
1393
+ * @param {String} name
1394
+ * @return {SKI}
1395
+ */
1396
+ remove(name) {
1397
+ this.known[name].outdated = true;
1398
+ delete this.known[name];
1399
+ this.allow.delete(name);
1400
+ return this;
1401
+ }
1402
+ /**
1403
+ *
1404
+ * @return {{[key:string]: Native|Alias}}
1405
+ */
1406
+ getTerms() {
1407
+ const out = {};
1408
+ for (const name of Object.keys(this.known)) {
1409
+ if (this.allow.has(name))
1410
+ out[name] = this.known[name];
1311
1411
  }
1312
- Expr.native = native;
1313
- Expr.control = control;
1314
- Expr.extras = { toposort };
1315
- module2.exports = { Expr, App, Named, FreeVar, Lambda, Native, Alias, Church };
1412
+ return out;
1316
1413
  }
1317
- });
1318
-
1319
- // src/parser.js
1320
- var require_parser = __commonJS({
1321
- "src/parser.js"(exports2, module2) {
1322
- "use strict";
1323
- var { Tokenizer, restrict } = require_internal();
1324
- var classes = require_expr();
1325
- var { Expr, Named, Native, Alias, FreeVar, Lambda, Church } = classes;
1326
- var { native } = Expr;
1327
- var Empty = class extends Expr {
1328
- apply(...args) {
1329
- return args.length ? args.shift().apply(...args) : this;
1330
- }
1331
- postParse() {
1332
- throw new Error("Attempt to use empty expression () as a term");
1333
- }
1334
- };
1335
- var PartialLambda = class _PartialLambda extends Empty {
1336
- // TODO mutable! rewrite ro when have time
1337
- constructor(term, known = {}) {
1338
- super();
1339
- this.impl = new Empty();
1340
- if (term instanceof FreeVar)
1341
- this.terms = [term];
1342
- else if (term instanceof _PartialLambda) {
1343
- if (!(term.impl instanceof FreeVar))
1344
- throw new Error("Expected FreeVar->...->FreeVar->Expr");
1345
- this.terms = [...term.terms, term.impl];
1346
- } else
1347
- throw new Error("Expected FreeVar or PartialLambda");
1348
- }
1349
- apply(term, ...tail) {
1350
- if (term === null || tail.length !== 0)
1351
- throw new Error("bad syntax in partial lambda expr");
1352
- this.impl = this.impl.apply(term);
1353
- return this;
1354
- }
1355
- postParse() {
1356
- return new Lambda(this.terms, this.impl);
1414
+ /**
1415
+ * @desc Export term declarations for use in bulkAdd().
1416
+ * Currently only Alias terms are serialized.
1417
+ * @returns {string[]}
1418
+ */
1419
+ declare() {
1420
+ const env = {};
1421
+ for (const [name, term] of Object.entries(this.getTerms())) {
1422
+ if (term instanceof Alias)
1423
+ env[name] = term;
1424
+ }
1425
+ const needDetour = {};
1426
+ let i = 1;
1427
+ for (const name in native) {
1428
+ if (!(env[name] instanceof Alias))
1429
+ continue;
1430
+ while ("tmp" + i in env)
1431
+ i++;
1432
+ const temp = new Alias("tmp" + i, env[name]);
1433
+ needDetour[temp.name] = env[name];
1434
+ env[temp.name] = temp;
1435
+ delete env[name];
1436
+ }
1437
+ const list = toposort(void 0, env).list;
1438
+ const detour = /* @__PURE__ */ new Map();
1439
+ if (Object.keys(needDetour).length) {
1440
+ const rework = (expr) => {
1441
+ return expr.traverse((e) => {
1442
+ if (!(e instanceof Alias))
1443
+ return null;
1444
+ const newAlias = detour.get(e);
1445
+ if (newAlias)
1446
+ return newAlias;
1447
+ return new Alias(e.name, rework(e.impl));
1448
+ }) ?? expr;
1449
+ };
1450
+ for (let j = 0; j < list.length; j++) {
1451
+ list[j] = rework(list[j]);
1452
+ detour.set(needDetour[list[j].name], list[j]);
1453
+ env[list[j].name] = list[j];
1454
+ console.log(`list[${j}] = ${list[j].name}=${list[j].impl};`);
1357
1455
  }
1358
- // uncomment if debugging with prints
1359
- /* toString () {
1360
- return this.terms.join('->') + '->' + (this.impl ?? '???');
1361
- } */
1362
- };
1363
- function postParse(expr) {
1364
- return expr.postParse ? expr.postParse() : expr;
1456
+ console.log("detour:", detour);
1365
1457
  }
1366
- var combChars = new Tokenizer(
1367
- "[()]",
1368
- "[A-Z]",
1369
- "[a-z_][a-z_0-9]*",
1370
- "\\b[0-9]+\\b",
1371
- "->",
1372
- "\\+"
1458
+ const out = list.map(
1459
+ (e) => needDetour[e.name] ? e.name + "=" + needDetour[e.name].name + "=" + e.impl.format({ inventory: env }) : e.name + "=" + e.impl.format({ inventory: env })
1373
1460
  );
1374
- var SKI2 = class {
1375
- /**
1376
- *
1377
- * @param {{
1378
- * allow?: string,
1379
- * numbers?: boolean,
1380
- * lambdas?: boolean,
1381
- * terms?: { [key: string]: Expr|string} | string[],
1382
- * annotate?: boolean,
1383
- * }} [options]
1384
- */
1385
- constructor(options = {}) {
1386
- this.annotate = options.annotate ?? false;
1387
- this.known = { ...native };
1388
- this.hasNumbers = true;
1389
- this.hasLambdas = true;
1390
- this.allow = new Set(Object.keys(this.known));
1391
- if (Array.isArray(options.terms))
1392
- this.bulkAdd(options.terms);
1393
- else if (options.terms) {
1394
- for (const name in options.terms) {
1395
- if (!options.terms[name].match(/^Native:/))
1396
- this.add(name, options.terms[name]);
1397
- }
1398
- }
1399
- this.hasNumbers = options.numbers ?? true;
1400
- this.hasLambdas = options.lambdas ?? true;
1401
- if (options.allow)
1402
- this.restrict(options.allow);
1403
- }
1404
- /**
1405
- * @desc Declare a new term
1406
- * If the first argument is an Alias, it is added as is.
1407
- * Otherwise, a new Alias or Native term (depending on impl type) is created.
1408
- * If note is not provided and this.annotate is true, an automatic note is generated.
1409
- *
1410
- * If impl is a function, it should have signature (Expr) => ... => Expr
1411
- * (see typedef Partial at top of expr.js)
1412
- *
1413
- * @example ski.add('T', 'S(K(SI))K', 'swap combinator')
1414
- * @example ski.add( ski.parse('T = S(K(SI))K') ) // ditto but one-arg form
1415
- * @example ski.add('T', x => y => y.apply(x), 'swap combinator') // heavy artillery
1416
- * @example ski.add('Y', function (f) { return f.apply(this.apply(f)); }, 'Y combinator')
1417
- *
1418
- * @param {Alias|String} term
1419
- * @param {String|Expr|function(Expr):Partial} [impl]
1420
- * @param {object|string} [options]
1421
- * @param {string} [options.note] - optional annotation for the term, default is auto-generated if this.annotate is true
1422
- * @param {boolean} [options.canonize] - whether to canonize the term's implementation, default is this.annotate
1423
- * @param {boolean} [options.fancy] - alternative HTML-friendly name for the term
1424
- * @param {number} [options.arity] - custom arity for the term, default is inferred from the implementation
1425
- * @return {SKI} chainable
1426
- */
1427
- add(term, impl, options) {
1428
- term = this._named(term, impl);
1429
- if (typeof options === "string")
1430
- options = { note: options, canonize: false };
1431
- term._setup({ canonize: this.annotate, ...options });
1432
- if (this.known[term.name])
1433
- this.known[term.name].outdated = true;
1434
- this.known[term.name] = term;
1435
- this.allow.add(term.name);
1436
- return this;
1437
- }
1438
- /**
1439
- * @desc Internal helper for add() that creates an Alias or Native term from the given arguments.
1440
- * @param {Alias|string} term
1441
- * @param {string|Expr|function(Expr):Partial} impl
1442
- * @returns {Native|Alias}
1443
- * @private
1444
- */
1445
- _named(term, impl) {
1446
- if (term instanceof Alias)
1447
- return new Alias(term.name, term.impl, { canonize: true });
1448
- if (typeof term !== "string")
1449
- throw new Error("add(): term must be an Alias or a string");
1450
- if (impl === void 0)
1451
- throw new Error("add(): impl must be provided when term is a string");
1452
- if (typeof impl === "string")
1453
- return new Alias(term, this.parse(impl), { canonize: true });
1454
- if (impl instanceof Expr)
1455
- return new Alias(term, impl, { canonize: true });
1456
- if (typeof impl === "function")
1457
- return new Native(term, impl);
1458
- throw new Error("add(): impl must be an Expr, a string, or a function with a signature Expr => ... => Expr");
1459
- }
1460
- /**
1461
- * @desc Declare a new term if it is not known, otherwise just allow it.
1462
- * Currently only used by quests.
1463
- * Use with caution, this function may change its signature, behavior, or even be removed in the future.
1464
- *
1465
- * @experimental
1466
- * @param {string|Alias} name
1467
- * @param {string|Expr|function(Expr):Partial} impl
1468
- * @returns {SKI}
1469
- */
1470
- maybeAdd(name, impl) {
1471
- if (this.known[name])
1472
- this.allow.add(name);
1473
- else
1474
- this.add(name, impl);
1475
- return this;
1476
- }
1477
- /**
1478
- * @desc Declare and remove multiple terms at once
1479
- * term=impl adds term
1480
- * term= removes term
1481
- * @param {string[]} list
1482
- * @return {SKI} chainable
1483
- */
1484
- bulkAdd(list) {
1485
- for (const item of list) {
1486
- const m = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
1487
- if (!m)
1488
- throw new Error("bulkAdd: invalid declaration: " + item);
1489
- if (m[2] === "")
1490
- this.remove(m[1]);
1491
- else
1492
- this.add(m[1], this.parse(m[2]));
1493
- }
1494
- return this;
1495
- }
1496
- /**
1497
- * Restrict the interpreter to given terms. Terms prepended with '+' will be added
1498
- * and terms preceeded with '-' will be removed.
1499
- * @example ski.restrict('SK') // use the basis
1500
- * @example ski.restrict('+I') // allow I now
1501
- * @example ski.restrict('-SKI +BCKW' ); // switch basis
1502
- * @example ski.restrict('-foo -bar'); // forbid some user functions
1503
- * @param {string} spec
1504
- * @return {SKI} chainable
1505
- */
1506
- restrict(spec) {
1507
- this.allow = restrict(this.allow, spec);
1508
- return this;
1509
- }
1510
- /**
1511
- *
1512
- * @param {string} spec
1513
- * @return {string}
1514
- */
1515
- showRestrict(spec = "+") {
1516
- const out = [];
1517
- let prevShort = true;
1518
- for (const term of [...restrict(this.allow, spec)].sort()) {
1519
- const nextShort = term.match(/^[A-Z]$/);
1520
- if (out.length && !(prevShort && nextShort))
1521
- out.push(" ");
1522
- out.push(term);
1523
- prevShort = nextShort;
1524
- }
1525
- return out.join("");
1526
- }
1527
- /**
1528
- *
1529
- * @param {String} name
1530
- * @return {SKI}
1531
- */
1532
- remove(name) {
1533
- this.known[name].outdated = true;
1534
- delete this.known[name];
1535
- this.allow.delete(name);
1536
- return this;
1537
- }
1538
- /**
1539
- *
1540
- * @return {{[key:string]: Native|Alias}}
1541
- */
1542
- getTerms() {
1543
- const out = {};
1544
- for (const name of Object.keys(this.known)) {
1545
- if (this.allow.has(name))
1546
- out[name] = this.known[name];
1547
- }
1548
- return out;
1549
- }
1550
- /**
1551
- * @desc Export term declarations for use in bulkAdd().
1552
- * Currently only Alias terms are serialized.
1553
- * @returns {string[]}
1554
- */
1555
- declare() {
1556
- const env = this.getTerms();
1557
- for (const name in env) {
1558
- if (!(env[name] instanceof Alias))
1559
- delete env[name];
1560
- }
1561
- const needDetour = {};
1562
- let i = 1;
1563
- for (const name in native) {
1564
- if (!(env[name] instanceof Alias))
1565
- continue;
1566
- while ("tmp" + i in env)
1567
- i++;
1568
- const temp = new Alias("tmp" + i, env[name]);
1569
- needDetour[temp] = env[name];
1570
- env[temp] = temp;
1571
- delete env[name];
1572
- }
1573
- const list = Expr.extras.toposort(void 0, env).list;
1574
- const detour = /* @__PURE__ */ new Map();
1575
- if (Object.keys(needDetour).length) {
1576
- const rework = (expr) => {
1577
- return expr.traverse((e) => {
1578
- if (!(e instanceof Alias))
1579
- return null;
1580
- const newAlias = detour.get(e);
1581
- if (newAlias)
1582
- return newAlias;
1583
- return new Alias(e.name, rework(e.impl));
1584
- }) ?? expr;
1585
- };
1586
- for (let i2 = 0; i2 < list.length; i2++) {
1587
- list[i2] = rework(list[i2], detour);
1588
- detour.set(needDetour[list[i2].name], list[i2]);
1589
- env[list[i2].name] = list[i2];
1590
- console.log(`list[${i2}] = ${list[i2].name}=${list[i2].impl};`);
1591
- }
1592
- console.log("detour:", detour);
1593
- }
1594
- const out = list.map(
1595
- (e) => needDetour[e] ? e.name + "=" + needDetour[e].name + "=" + e.impl.format({ inventory: env }) : e.name + "=" + e.impl.format({ inventory: env })
1596
- );
1597
- for (const [name, temp] of detour)
1598
- out.push(name + "=" + temp, temp + "=");
1599
- return out;
1600
- }
1601
- /**
1602
- * @template T
1603
- * @param {string} source
1604
- * @param {Object} [options]
1605
- * @param {{[keys: string]: Expr}} [options.env]
1606
- * @param {T} [options.scope]
1607
- * @param {boolean} [options.numbers]
1608
- * @param {boolean} [options.lambdas]
1609
- * @param {string} [options.allow]
1610
- * @return {Expr}
1611
- */
1612
- parse(source, options = {}) {
1613
- if (typeof source !== "string")
1614
- throw new Error("parse: source must be a string, got " + typeof source);
1615
- const lines = source.replace(/\/\/[^\n]*$/gm, " ").replace(/\/\*.*?\*\//gs, " ").trim().split(/\s*;[\s;]*/).filter((s) => s.match(/\S/));
1616
- const jar = { ...options.env };
1617
- let expr = new Empty();
1618
- for (const item of lines) {
1619
- if (expr instanceof Alias)
1620
- expr.outdated = true;
1621
- const def = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s);
1622
- if (def && def[2] === "")
1623
- expr = new FreeVar(def[1], options.scope ?? FreeVar.global);
1624
- else
1625
- expr = this.parseLine(item, jar, options);
1626
- if (def) {
1627
- if (jar[def[1]] !== void 0)
1628
- throw new Error("Attempt to redefine a known term: " + def[1]);
1629
- jar[def[1]] = expr;
1630
- }
1631
- }
1632
- expr.context = {
1633
- env: { ...this.getTerms(), ...jar },
1634
- // also contains pre-parsed terms
1635
- scope: options.scope,
1636
- src: source,
1637
- parser: this
1638
- };
1639
- return expr;
1640
- }
1641
- /**
1642
- * @desc Parse a single line of source code, without splitting it into declarations.
1643
- * Internal, always use parse() instead.
1644
- * @template T
1645
- * @param {String} source S(KI)I
1646
- * @param {{[keys: string]: Expr}} env
1647
- * @param {Object} [options]
1648
- * @param {{[keys: string]: Expr}} [options.env] - unused, see 'env' argument
1649
- * @param {T} [options.scope]
1650
- * @param {boolean} [options.numbers]
1651
- * @param {boolean} [options.lambdas]
1652
- * @param {string} [options.allow]
1653
- * @return {Expr} parsed expression
1654
- */
1655
- parseLine(source, env = {}, options = {}) {
1656
- const aliased = source.match(/^\s*([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
1657
- if (aliased)
1658
- return new Alias(aliased[1], this.parseLine(aliased[2], env, options));
1659
- const opt = {
1660
- numbers: options.numbers ?? this.hasNumbers,
1661
- lambdas: options.lambdas ?? this.hasLambdas,
1662
- allow: restrict(this.allow, options.allow)
1663
- };
1664
- opt.numbers ? opt.allow.add("+") : opt.allow.delete("+");
1665
- const tokens = combChars.split(source);
1666
- const empty = new Empty();
1667
- const stack = [empty];
1668
- const context = options.scope || FreeVar.global;
1669
- for (const c of tokens) {
1670
- if (c === "(")
1671
- stack.push(empty);
1672
- else if (c === ")") {
1673
- if (stack.length < 2)
1674
- throw new Error("unbalanced input: extra closing parenthesis" + source);
1675
- const x = postParse(stack.pop());
1676
- const f = stack.pop();
1677
- stack.push(f.apply(x));
1678
- } else if (c === "->") {
1679
- if (!opt.lambdas)
1680
- throw new Error("Lambdas not supported, allow them explicitly");
1681
- stack.push(new PartialLambda(stack.pop(), env));
1682
- } else if (c.match(/^[0-9]+$/)) {
1683
- if (!opt.numbers)
1684
- throw new Error("Church numbers not supported, allow them explicitly");
1685
- const f = stack.pop();
1686
- stack.push(f.apply(new Church(c)));
1687
- } else {
1688
- const f = stack.pop();
1689
- if (!env[c] && this.known[c] && !opt.allow.has(c)) {
1690
- throw new Error("Term '" + c + "' is not in the restricted set " + [...opt.allow].sort().join(" "));
1691
- }
1692
- const x = env[c] ?? this.known[c] ?? (env[c] = new FreeVar(c, context));
1693
- stack.push(f.apply(x));
1694
- }
1695
- }
1696
- if (stack.length !== 1) {
1697
- throw new Error("unbalanced input: missing " + (stack.length - 1) + " closing parenthesis:" + source);
1698
- }
1699
- return postParse(stack.pop());
1700
- }
1701
- toJSON() {
1702
- return {
1703
- version: "1.1.1",
1704
- // set to incremented package.json version whenever SKI serialization changes
1705
- allow: this.showRestrict("+"),
1706
- numbers: this.hasNumbers,
1707
- lambdas: this.hasLambdas,
1708
- annotate: this.annotate,
1709
- terms: this.declare()
1710
- };
1711
- }
1712
- };
1713
- SKI2.vars = function(scope = {}) {
1714
- const cache = {};
1715
- return new Proxy({}, {
1716
- get: (target, name) => {
1717
- if (!(name in cache))
1718
- cache[name] = new FreeVar(name, scope);
1719
- return cache[name];
1720
- }
1721
- });
1722
- };
1723
- SKI2.church = (n) => new Church(n);
1724
- for (const name in native)
1725
- SKI2[name] = native[name];
1726
- SKI2.classes = classes;
1727
- SKI2.native = native;
1728
- SKI2.control = Expr.control;
1729
- module2.exports = { SKI: SKI2 };
1461
+ for (const [name, temp] of detour)
1462
+ out.push(name.name + "=" + temp, temp + "=");
1463
+ return out;
1730
1464
  }
1731
- });
1732
-
1733
- // src/quest.js
1734
- var require_quest = __commonJS({
1735
- "src/quest.js"(exports2, module2) {
1736
- var { SKI: SKI2 } = require_parser();
1737
- var { Expr, FreeVar, Alias, Lambda } = SKI2.classes;
1738
- var Quest2 = class {
1739
- /**
1740
- * @description A combinator problem with a set of test cases for the proposed solution.
1741
- * @param {QuestSpec} options
1742
- * @example const quest = new Quest({
1743
- * input: 'identity',
1744
- * cases: [
1745
- * ['identity x', 'x'],
1746
- * ],
1747
- * allow: 'SK',
1748
- * intro: 'Find a combinator that behaves like the identity function.',
1749
- * });
1750
- * quest.check('S K K'); // { pass: true, details: [...], ... }
1751
- * quest.check('K S'); // { pass: false, details: [...], ... }
1752
- * quest.check('K x'); // fail! internal variable x is not equal to free variable x,
1753
- * // despite having the same name.
1754
- * quest.check('I'); // fail! I not in the allowed list.
1755
- */
1756
- constructor(options) {
1757
- const { input, cases, allow, numbers, lambdas, engine, engineFull, ...meta } = options;
1758
- const env = options.env ?? options.vars;
1759
- this.engine = engine ?? new SKI2();
1760
- this.engineFull = engineFull ?? new SKI2();
1761
- this.restrict = { allow, numbers: numbers ?? false, lambdas: lambdas ?? false };
1762
- this.env = {};
1763
- const jar = {};
1764
- for (const term of env ?? []) {
1765
- const expr = this.engineFull.parse(term, { env: jar, scope: this });
1766
- if (expr instanceof SKI2.classes.Alias)
1767
- this.env[expr.name] = new Alias(expr.name, expr.impl, { terminal: true, canonize: false });
1768
- else if (expr instanceof SKI2.classes.FreeVar)
1769
- this.env[expr.name] = expr;
1770
- else
1771
- throw new Error("Unsupported given variable type: " + term);
1772
- }
1773
- this.input = [];
1774
- for (const term of Array.isArray(input) ? input : [input])
1775
- this.addInput(term);
1776
- if (!this.input.length)
1777
- throw new Error("Quest needs at least one input placeholder");
1778
- this.envFull = { ...this.env, ...jar };
1779
- for (const term of this.input) {
1780
- if (term.name in this.envFull)
1781
- throw new Error("input placeholder name is duplicated or clashes with env: " + term.name);
1782
- this.envFull[term.name] = term.placeholder;
1783
- }
1784
- this.cases = [];
1785
- this.name = meta.name ?? meta.title;
1786
- meta.intro = list2str(meta.intro ?? meta.descr);
1787
- this.intro = meta.intro;
1788
- this.id = meta.id;
1789
- this.meta = meta;
1790
- for (const c of cases ?? [])
1791
- this.add(...c);
1792
- }
1793
- /**
1794
- * Display allowed terms based on what engine thinks of this.env + this.restrict.allow
1795
- * @return {string}
1796
- */
1797
- allowed() {
1798
- const allow = this.restrict.allow ?? "";
1799
- const env = Object.keys(this.env).sort();
1800
- return allow ? this.engine.showRestrict(allow + "+" + env.join(" ")) : env.map((s) => "+" + s).join(" ");
1801
- }
1802
- addInput(term) {
1803
- if (typeof term !== "object")
1804
- term = { name: term };
1805
- if (typeof term.name !== "string")
1806
- throw new Error("quest 'input' field must be a string or a {name: string, ...} object");
1807
- term.placeholder = new SKI2.classes.FreeVar(term.name);
1808
- this.input.push(term);
1809
- }
1810
- /**
1811
- *
1812
- * @param {{} | string} opt
1813
- * @param {string} terms
1814
- * @return {Quest}
1815
- */
1816
- add(opt, ...terms) {
1817
- if (typeof opt === "string") {
1818
- terms.unshift(opt);
1819
- opt = {};
1820
- } else
1821
- opt = { ...opt };
1822
- opt.engine = opt.engine ?? this.engineFull;
1823
- opt.env = opt.env ?? this.envFull;
1824
- const input = this.input.map((t) => t.placeholder);
1825
- this.cases.push(
1826
- opt.caps ? new PropertyCase(input, opt, terms) : new ExprCase(input, opt, terms)
1827
- );
1828
- return this;
1829
- }
1830
- /**
1831
- * @description Statefully parse a list of strings into expressions or fancy aliases thereof.
1832
- * @param {string[]} input
1833
- * @return {{terms: Expr[], weight: number}}
1834
- */
1835
- prepare(...input) {
1836
- if (input.length !== this.input.length)
1837
- throw new Error("Solutions provided " + input.length + " terms where " + this.input.length + " are expected");
1838
- let weight = 0;
1839
- const prepared = [];
1840
- const jar = { ...this.env };
1841
- for (let i = 0; i < input.length; i++) {
1842
- const spec = this.input[i];
1843
- const impl = this.engine.parse(input[i], {
1844
- env: jar,
1845
- allow: spec.allow ?? this.restrict.allow,
1846
- numbers: spec.numbers ?? this.restrict.numbers,
1847
- lambdas: spec.lambdas ?? this.restrict.lambdas
1848
- });
1849
- const arsenal = { ...this.engine.getTerms(), ...jar };
1850
- weight += impl.fold(0, (a, e) => {
1851
- if (e instanceof SKI2.classes.Named && arsenal[e.name] === e)
1852
- return SKI2.control.prune(a + 1);
1853
- });
1854
- const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { terminal: true, canonize: false });
1855
- jar[spec.name] = expr;
1856
- prepared.push(expr);
1857
- }
1858
- return {
1859
- prepared,
1860
- weight
1861
- };
1862
- }
1863
- /**
1864
- *
1865
- * @param {string} input
1866
- * @return {QuestResult}
1867
- */
1868
- check(...input) {
1869
- try {
1870
- const { prepared, weight } = this.prepare(...input);
1871
- const details = this.cases.map((c) => c.check(...prepared));
1872
- const pass = details.reduce((acc, val) => acc && val.pass, true);
1873
- const steps = details.reduce((acc, val) => acc + val.steps, 0);
1874
- return {
1875
- expr: prepared[0],
1876
- input: prepared,
1877
- pass,
1878
- steps,
1879
- details,
1880
- weight
1881
- };
1882
- } catch (e) {
1883
- return { pass: false, details: [], exception: e, steps: 0, input };
1884
- }
1885
- }
1886
- verify(options) {
1887
- const findings = this.verifyMeta(options);
1888
- if (options.solutions) {
1889
- const solCheck = this.verifySolutions(options.solutions);
1890
- if (solCheck)
1891
- findings.solutions = solCheck;
1892
- }
1893
- if (options.seen) {
1894
- if (!this.id)
1895
- findings.seen = "No id in quest " + (this.name ?? "(unnamed)");
1896
- if (options.seen.has(this.id))
1897
- findings.seen = "Duplicate quest id " + this.id;
1898
- options.seen.add(this.id);
1899
- }
1900
- return Object.keys(findings).length ? findings : null;
1901
- }
1902
- /**
1903
- * @desc Verify that solutions that are expected to pass/fail do so.
1904
- * @param {SelfCheck|{[key: string]: SelfCheck}} dataset
1905
- * @return {{shouldPass: {input: string[], result: QuestResult}[], shouldFail: {input: string[], result: QuestResult}[]} | null}
1906
- */
1907
- verifySolutions(dataset) {
1908
- if (typeof dataset === "object" && !Array.isArray(dataset?.accepted) && !Array.isArray(dataset?.rejected)) {
1909
- if (!this.id || !dataset[this.id])
1910
- return null;
1911
- }
1912
- const { accepted = [], rejected = [] } = dataset[this.id] ?? dataset;
1913
- const ret = { shouldPass: [], shouldFail: [] };
1914
- for (const input of accepted) {
1915
- const result = this.check(...input);
1916
- if (!result.pass)
1917
- ret.shouldPass.push({ input, result });
1918
- }
1919
- for (const input of rejected) {
1920
- const result = this.check(...input);
1921
- if (result.pass)
1922
- ret.shouldFail.push({ input, result });
1923
- }
1924
- return ret.shouldFail.length + ret.shouldPass.length ? ret : null;
1925
- }
1926
- verifyMeta(options = {}) {
1927
- const findings = {};
1928
- for (const field of ["name", "intro"]) {
1929
- const found = checkHtml(this[field]);
1930
- if (found)
1931
- findings[field] = found;
1932
- }
1933
- if (options.date) {
1934
- const date = new Date(this.meta.created_at);
1935
- if (isNaN(date))
1936
- findings.date = "invalid date format: " + this.meta.created_at;
1937
- else if (date < /* @__PURE__ */ new Date("2024-07-15") || date > /* @__PURE__ */ new Date())
1938
- findings.date = "date out of range: " + this.meta.created_at;
1939
- }
1940
- return findings;
1941
- }
1942
- /**
1943
- *
1944
- * @return {TestCase[]}
1945
- */
1946
- show() {
1947
- return [...this.cases];
1948
- }
1949
- };
1950
- var Case = class {
1951
- /**
1952
- * @param {FreeVar[]} input
1953
- * @param {{
1954
- * max?: number,
1955
- * note?: string,
1956
- * env?: {[key:string]: Expr},
1957
- * engine: SKI
1958
- * }} options
1959
- */
1960
- constructor(input, options) {
1961
- this.max = options.max ?? 1e3;
1962
- this.note = options.note;
1963
- this.env = { ...options.env ?? {} };
1964
- this.input = input;
1965
- this.engine = options.engine;
1966
- }
1967
- parse(src) {
1968
- return new Subst(this.engine.parse(src, { env: this.env, scope: this }), this.input);
1969
- }
1970
- /**
1971
- * @param {Expr} expr
1972
- * @return {CaseResult}
1973
- */
1974
- check(...expr) {
1975
- throw new Error("not implemented");
1976
- }
1977
- };
1978
- var ExprCase = class extends Case {
1979
- /**
1980
- * @param {FreeVar[]} input
1981
- * @param {{
1982
- * max?: number,
1983
- * note?: string,
1984
- * env?: {string: Expr},
1985
- * engine?: SKI
1986
- * }} options
1987
- * @param {[e1: string, e2: string]} terms
1988
- */
1989
- constructor(input, options, terms) {
1990
- if (terms.length !== 2)
1991
- throw new Error("Case accepts exactly 2 strings");
1992
- super(input, options);
1993
- [this.e1, this.e2] = terms.map((s) => this.parse(s));
1994
- }
1995
- check(...args) {
1996
- const e1 = this.e1.apply(args);
1997
- const r1 = e1.run({ max: this.max });
1998
- const e2 = this.e2.apply(args);
1999
- const r2 = e2.run({ max: this.max });
2000
- let reason = null;
2001
- if (!r1.final || !r2.final)
2002
- reason = "failed to reach normal form in " + this.max + " steps";
2003
- else
2004
- reason = r1.expr.diff(r2.expr);
2005
- return {
2006
- pass: !reason,
2007
- reason,
2008
- steps: r1.steps,
2009
- start: e1,
2010
- found: r1.expr,
2011
- expected: r2.expr,
2012
- note: this.note,
2013
- args,
2014
- case: this
2015
- };
1465
+ /**
1466
+ * @template T
1467
+ * @param {string} source
1468
+ * @param {Object} [options]
1469
+ * @param {{[keys: string]: Expr}} [options.env]
1470
+ * @param {T} [options.scope]
1471
+ * @param {boolean} [options.numbers]
1472
+ * @param {boolean} [options.lambdas]
1473
+ * @param {string} [options.allow]
1474
+ * @return {Expr}
1475
+ */
1476
+ parse(source, options = {}) {
1477
+ if (typeof source !== "string")
1478
+ throw new Error("parse: source must be a string, got " + typeof source);
1479
+ const lines = source.replace(/\/\/[^\n]*$/gm, " ").replace(/\/\*.*?\*\//gs, " ").trim().split(/\s*;[\s;]*/).filter((s) => s.match(/\S/));
1480
+ const jar = { ...options.env };
1481
+ let expr = new Empty();
1482
+ for (const item of lines) {
1483
+ if (expr instanceof Alias)
1484
+ expr.outdated = true;
1485
+ const def = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s);
1486
+ if (def && def[2] === "")
1487
+ expr = new FreeVar(def[1], options.scope ?? FreeVar.global);
1488
+ else
1489
+ expr = this.parseLine(item, jar, options);
1490
+ if (def) {
1491
+ if (jar[def[1]] !== void 0)
1492
+ throw new Error("Attempt to redefine a known term: " + def[1]);
1493
+ jar[def[1]] = expr;
2016
1494
  }
1495
+ }
1496
+ expr.context = {
1497
+ env: { ...this.getTerms(), ...jar },
1498
+ // also contains pre-parsed terms
1499
+ scope: options.scope,
1500
+ src: source,
1501
+ parser: this
2017
1502
  };
2018
- var knownCaps = {
2019
- normal: true,
2020
- proper: true,
2021
- discard: true,
2022
- duplicate: true,
2023
- linear: true,
2024
- affine: true,
2025
- arity: true
1503
+ return expr;
1504
+ }
1505
+ /**
1506
+ * @desc Parse a single line of source code, without splitting it into declarations.
1507
+ * Internal, always use parse() instead.
1508
+ * @template T
1509
+ * @param {String} source S(KI)I
1510
+ * @param {{[keys: string]: Expr}} env
1511
+ * @param {Object} [options]
1512
+ * @param {{[keys: string]: Expr}} [options.env] - unused, see 'env' argument
1513
+ * @param {T} [options.scope]
1514
+ * @param {boolean} [options.numbers]
1515
+ * @param {boolean} [options.lambdas]
1516
+ * @param {string} [options.allow]
1517
+ * @return {Expr} parsed expression
1518
+ */
1519
+ parseLine(source, env = {}, options = {}) {
1520
+ const aliased = source.match(/^\s*([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
1521
+ if (aliased)
1522
+ return new Alias(aliased[1], this.parseLine(aliased[2], env, options));
1523
+ const opt = {
1524
+ numbers: options.numbers ?? this.hasNumbers,
1525
+ lambdas: options.lambdas ?? this.hasLambdas,
1526
+ allow: restrict(this.allow, options.allow)
2026
1527
  };
2027
- var PropertyCase = class extends Case {
2028
- // test that an expression uses all of its inputs exactly once
2029
- constructor(input, options, terms) {
2030
- super(input, options);
2031
- if (terms.length > 1)
2032
- throw new Error("PropertyCase accepts exactly 1 string");
2033
- if (!options.caps || typeof options.caps !== "object" || !Object.keys(options.caps).length)
2034
- throw new Error("PropertyCase requires a caps object with at least one capability");
2035
- const unknown = Object.keys(options.caps).filter((c) => !knownCaps[c]);
2036
- if (unknown.length)
2037
- throw new Error("PropertyCase: don't know how to test these capabilities: " + unknown.join(", "));
2038
- this.expr = this.parse(terms[0]);
2039
- this.caps = options.caps;
2040
- if (this.caps.linear) {
2041
- delete this.caps.linear;
2042
- this.caps.duplicate = false;
2043
- this.caps.discard = false;
2044
- this.caps.normal = true;
2045
- }
2046
- if (this.caps.affine) {
2047
- delete this.caps.affine;
2048
- this.caps.normal = true;
2049
- this.caps.duplicate = false;
2050
- }
2051
- }
2052
- check(...expr) {
2053
- const start = this.expr.apply(expr);
2054
- const r = start.run({ max: this.max });
2055
- const guess = r.expr.infer({ max: this.max });
2056
- const reason = [];
2057
- for (const cap in this.caps) {
2058
- if (guess[cap] !== this.caps[cap])
2059
- reason.push("expected property " + cap + " to be " + this.caps[cap] + ", found " + guess[cap]);
1528
+ if (opt.numbers) opt.allow.add("+");
1529
+ else opt.allow.delete("+");
1530
+ const tokens = combChars.split(source);
1531
+ const empty = new Empty();
1532
+ const stack = [empty];
1533
+ const context = options.scope || FreeVar.global;
1534
+ for (const c of tokens) {
1535
+ if (c === "(")
1536
+ stack.push(empty);
1537
+ else if (c === ")") {
1538
+ if (stack.length < 2)
1539
+ throw new Error("unbalanced input: extra closing parenthesis" + source);
1540
+ const x = postParse(stack.pop());
1541
+ const f = stack.pop();
1542
+ stack.push(f.apply(x));
1543
+ } else if (c === "->") {
1544
+ if (!opt.lambdas)
1545
+ throw new Error("Lambdas not supported, allow them explicitly");
1546
+ stack.push(new PartialLambda(stack.pop()));
1547
+ } else if (c.match(/^[0-9]+$/)) {
1548
+ if (!opt.numbers)
1549
+ throw new Error("Church numbers not supported, allow them explicitly");
1550
+ const f = stack.pop();
1551
+ stack.push(f.apply(new Church(Number.parseInt(c))));
1552
+ } else {
1553
+ const f = stack.pop();
1554
+ if (!env[c] && this.known[c] && !opt.allow.has(c)) {
1555
+ throw new Error("Term '" + c + "' is not in the restricted set " + [...opt.allow].sort().join(" "));
2060
1556
  }
2061
- return {
2062
- pass: !reason.length,
2063
- reason: reason ? reason.join("\n") : null,
2064
- steps: r.steps,
2065
- start,
2066
- found: r.expr,
2067
- case: this,
2068
- note: this.note,
2069
- args: expr
2070
- };
1557
+ const x = env[c] ?? this.known[c] ?? (env[c] = new FreeVar(c, context));
1558
+ stack.push(f.apply(x));
2071
1559
  }
1560
+ }
1561
+ if (stack.length !== 1) {
1562
+ throw new Error("unbalanced input: missing " + (stack.length - 1) + " closing parenthesis:" + source);
1563
+ }
1564
+ return postParse(stack.pop());
1565
+ }
1566
+ toJSON() {
1567
+ return {
1568
+ version: "1.1.1",
1569
+ // set to incremented package.json version whenever SKI serialization changes
1570
+ allow: this.showRestrict("+"),
1571
+ numbers: this.hasNumbers,
1572
+ lambdas: this.hasLambdas,
1573
+ annotate: this.annotate,
1574
+ terms: this.declare()
2072
1575
  };
2073
- var Subst = class {
2074
- /**
2075
- * @descr A placeholder object with exactly n free variables to be substituted later.
2076
- * @param {Expr} expr
2077
- * @param {FreeVar[]} env
2078
- */
2079
- constructor(expr, env) {
2080
- this.expr = expr;
2081
- this.env = env;
2082
- }
2083
- apply(list) {
2084
- if (list.length !== this.env.length)
2085
- throw new Error("Subst: expected " + this.env.length + " terms, got " + list.length);
2086
- let expr = this.expr;
2087
- for (let i = 0; i < this.env.length; i++)
2088
- expr = expr.subst(this.env[i], list[i]) ?? expr;
2089
- return expr;
2090
- }
1576
+ }
1577
+ /**
1578
+ * Public static shortcuts to common functions (see also ./extras.js)
1579
+ */
1580
+ /**
1581
+ * @desc Create a proxy object that generates variables on demand,
1582
+ * with names corresponding to the property accessed.
1583
+ * Different invocations will return distinct variables,
1584
+ * even if with the same name.
1585
+ *
1586
+ *
1587
+ * @example const {x, y, z} = SKI.vars();
1588
+ * x.name; // 'x'
1589
+ * x instanceof FreeVar; // true
1590
+ * x.apply(y).apply(z); // x(y)(z)
1591
+ *
1592
+ * @template T
1593
+ * @param {T} [scope] - optional context to bind the generated variables to
1594
+ * @return {{[key: string]: FreeVar}}
1595
+ */
1596
+ };
1597
+
1598
+ // src/quest.ts
1599
+ var Quest = class {
1600
+ constructor(options) {
1601
+ const { input, cases, allow, numbers, lambdas, engine, engineFull, ...meta } = options;
1602
+ const env = options.env ?? [];
1603
+ this.engineFull = engineFull ?? new Parser();
1604
+ this.engine = engine ?? this.engineFull;
1605
+ this.restrict = { allow, numbers: numbers ?? false, lambdas: lambdas ?? false };
1606
+ this.env = {};
1607
+ const jar = {};
1608
+ for (const term of env ?? []) {
1609
+ const expr = this.engineFull.parse(term, { env: jar, scope: this });
1610
+ if (expr instanceof Alias)
1611
+ this.env[expr.name] = new Alias(expr.name, expr.impl, { terminal: true, canonize: false });
1612
+ else if (expr instanceof FreeVar)
1613
+ this.env[expr.name] = expr;
1614
+ else
1615
+ throw new Error("Unsupported given variable type: " + term);
1616
+ }
1617
+ this.input = [];
1618
+ for (const term of Array.isArray(input) ? input : [input])
1619
+ this.addInput(term);
1620
+ if (!this.input.length)
1621
+ throw new Error("Quest needs at least one input placeholder");
1622
+ this.envFull = { ...this.env, ...jar };
1623
+ for (const term of this.input) {
1624
+ if (term.name in this.envFull)
1625
+ throw new Error("input placeholder name is duplicated or clashes with env: " + term.name);
1626
+ this.envFull[term.name] = term.placeholder;
1627
+ }
1628
+ this.cases = [];
1629
+ this.name = meta.name ?? meta.title;
1630
+ meta.intro = list2str(meta.intro ?? meta.descr);
1631
+ this.intro = meta.intro;
1632
+ this.id = meta.id;
1633
+ this.meta = meta;
1634
+ for (const c of cases ?? [])
1635
+ this.add(...c);
1636
+ }
1637
+ /**
1638
+ * Display allowed terms based on what engine thinks of this.env + this.restrict.allow
1639
+ * @return {string}
1640
+ */
1641
+ allowed() {
1642
+ const allow = this.restrict.allow ?? "";
1643
+ const env = Object.keys(this.env).sort();
1644
+ return allow ? this.engine.showRestrict(allow + "+" + env.join(" ")) : env.map((s) => "+" + s).join(" ");
1645
+ }
1646
+ addInput(term) {
1647
+ if (typeof term !== "object")
1648
+ term = { name: term };
1649
+ if (typeof term.name !== "string")
1650
+ throw new Error("quest 'input' field must be a string or a {name: string, ...} object");
1651
+ const full = term;
1652
+ full.placeholder = new FreeVar(term.name);
1653
+ this.input.push(full);
1654
+ }
1655
+ /**
1656
+ *
1657
+ * @param {{} | string} opt
1658
+ * @param {string} terms
1659
+ * @return {Quest}
1660
+ */
1661
+ add(opt, ...terms) {
1662
+ let o;
1663
+ if (typeof opt === "string") {
1664
+ terms.unshift(opt);
1665
+ o = {};
1666
+ } else
1667
+ o = { ...opt };
1668
+ o.engine = o.engine ?? this.engineFull;
1669
+ o.env = o.env ?? this.envFull;
1670
+ const input = this.input.map((t) => t.placeholder);
1671
+ this.cases.push(
1672
+ o.caps ? new PropertyCase(input, o, terms) : new ExprCase(input, o, terms)
1673
+ );
1674
+ return this;
1675
+ }
1676
+ /**
1677
+ * @description Statefully parse a list of strings into expressions or fancy aliases thereof.
1678
+ * @param {string[]} input
1679
+ * @return {{terms: Expr[], weight: number}}
1680
+ */
1681
+ prepare(...input) {
1682
+ if (input.length !== this.input.length)
1683
+ throw new Error("Solutions provided " + input.length + " terms where " + this.input.length + " are expected");
1684
+ let weight = 0;
1685
+ const prepared = [];
1686
+ const jar = { ...this.env };
1687
+ for (let i = 0; i < input.length; i++) {
1688
+ const spec = this.input[i];
1689
+ const impl = this.engine.parse(input[i], {
1690
+ env: jar,
1691
+ allow: spec.allow ?? this.restrict.allow,
1692
+ numbers: spec.numbers ?? this.restrict.numbers,
1693
+ lambdas: spec.lambdas ?? this.restrict.lambdas
1694
+ });
1695
+ const arsenal = { ...this.engine.getTerms(), ...jar };
1696
+ weight += impl.fold(0, (a, e) => {
1697
+ if (e instanceof Named && arsenal[e.name] === e)
1698
+ return control.prune(a + 1);
1699
+ });
1700
+ const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { terminal: true, canonize: false });
1701
+ jar[spec.name] = expr;
1702
+ prepared.push(expr);
1703
+ }
1704
+ return {
1705
+ prepared,
1706
+ weight
2091
1707
  };
2092
- var Group = class {
2093
- constructor(options) {
2094
- this.name = options.name;
2095
- this.intro = list2str(options.intro);
2096
- this.id = options.id;
2097
- if (options.content)
2098
- this.content = options.content.map((c) => c instanceof Quest2 ? c : new Quest2(c));
2099
- }
2100
- verify(options) {
2101
- const findings = {};
2102
- const id = checkId(this.id, options.seen);
2103
- if (id)
2104
- findings[this.id] = id;
2105
- for (const field of ["name", "intro"]) {
2106
- const found = checkHtml(this[field]);
2107
- if (found)
2108
- findings[field] = found;
2109
- }
2110
- findings.content = this.content.map((q) => q.verify(options));
2111
- return findings;
2112
- }
1708
+ }
1709
+ /**
1710
+ *
1711
+ * @param {string} input
1712
+ * @return {QuestResult}
1713
+ */
1714
+ check(...input) {
1715
+ try {
1716
+ const { prepared, weight } = this.prepare(...input);
1717
+ const details = this.cases.map((c) => c.check(...prepared));
1718
+ const pass = details.reduce((acc, val) => acc && val.pass, true);
1719
+ const steps = details.reduce((acc, val) => acc + val.steps, 0);
1720
+ return {
1721
+ expr: prepared[0],
1722
+ input: prepared,
1723
+ pass,
1724
+ steps,
1725
+ details,
1726
+ weight
1727
+ };
1728
+ } catch (e) {
1729
+ return { pass: false, details: [], exception: e, steps: 0, input };
1730
+ }
1731
+ }
1732
+ verify(options) {
1733
+ const findings = this.verifyMeta(options);
1734
+ if (options.solutions) {
1735
+ const solCheck = this.verifySolutions(options.solutions);
1736
+ if (solCheck)
1737
+ findings.solutions = solCheck;
1738
+ }
1739
+ if (options.seen) {
1740
+ if (!this.id)
1741
+ findings.seen = "No id in quest " + (this.name ?? "(unnamed)");
1742
+ if (options.seen.has(this.id))
1743
+ findings.seen = "Duplicate quest id " + this.id;
1744
+ options.seen.add(this.id);
1745
+ }
1746
+ return Object.keys(findings).length ? findings : null;
1747
+ }
1748
+ /**
1749
+ * @desc Verify that solutions that are expected to pass/fail do so.
1750
+ * @param {SelfCheck|{[key: string]: SelfCheck}} dataset
1751
+ * @return {{shouldPass: {input: string[], result: QuestResult}[], shouldFail: {input: string[], result: QuestResult}[]} | null}
1752
+ */
1753
+ verifySolutions(dataset) {
1754
+ if (typeof dataset === "object" && !Array.isArray(dataset.accepted) && !Array.isArray(dataset.rejected)) {
1755
+ if (!this.id || !dataset[this.id])
1756
+ return null;
1757
+ }
1758
+ const byId = this.id !== void 0 ? dataset[this.id] : void 0;
1759
+ const { accepted = [], rejected = [] } = byId ?? dataset;
1760
+ const ret = { shouldPass: [], shouldFail: [] };
1761
+ for (const input of accepted) {
1762
+ const result = this.check(...input);
1763
+ if (!result.pass)
1764
+ ret.shouldPass.push({ input, result });
1765
+ }
1766
+ for (const input of rejected) {
1767
+ const result = this.check(...input);
1768
+ if (result.pass)
1769
+ ret.shouldFail.push({ input, result });
1770
+ }
1771
+ return ret.shouldFail.length + ret.shouldPass.length ? ret : null;
1772
+ }
1773
+ verifyMeta(options = {}) {
1774
+ const findings = {};
1775
+ for (const field of ["name", "intro"]) {
1776
+ const found = checkHtml(this[field]);
1777
+ if (found)
1778
+ findings[field] = found;
1779
+ }
1780
+ if (options.date) {
1781
+ const date = new Date(this.meta?.created_at);
1782
+ if (isNaN(date.getTime()))
1783
+ findings.date = "invalid date format: " + this.meta?.created_at;
1784
+ else if (date.getTime() < (/* @__PURE__ */ new Date("2024-07-15")).getTime() || date.getTime() > (/* @__PURE__ */ new Date()).getTime())
1785
+ findings.date = "date out of range: " + this.meta?.created_at;
1786
+ }
1787
+ return findings;
1788
+ }
1789
+ /**
1790
+ *
1791
+ * @return {TestCase[]}
1792
+ */
1793
+ show() {
1794
+ return [...this.cases];
1795
+ }
1796
+ };
1797
+ var Case = class {
1798
+ constructor(input, options) {
1799
+ this.max = options.max ?? 1e3;
1800
+ this.note = options.note;
1801
+ this.env = { ...options.env ?? {} };
1802
+ this.input = input;
1803
+ this.engine = options.engine;
1804
+ }
1805
+ parse(src) {
1806
+ return new Subst(this.engine.parse(src, { env: this.env, scope: this }), this.input);
1807
+ }
1808
+ /**
1809
+ * @param {Expr} expr
1810
+ * @return {CaseResult}
1811
+ */
1812
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1813
+ check(..._expr) {
1814
+ throw new Error("not implemented");
1815
+ }
1816
+ };
1817
+ var ExprCase = class extends Case {
1818
+ /**
1819
+ * @param {FreeVar[]} input
1820
+ * @param {{
1821
+ * max?: number,
1822
+ * note?: string,
1823
+ * env?: {string: Expr},
1824
+ * engine?: Parser
1825
+ * }} options
1826
+ * @param {[e1: string, e2: string]} terms
1827
+ */
1828
+ constructor(input, options, terms) {
1829
+ if (terms.length !== 2)
1830
+ throw new Error("Case accepts exactly 2 strings");
1831
+ super(input, options);
1832
+ [this.e1, this.e2] = terms.map((s) => this.parse(s));
1833
+ }
1834
+ check(...args) {
1835
+ const e1 = this.e1.apply(args);
1836
+ const r1 = e1.run({ max: this.max });
1837
+ const e2 = this.e2.apply(args);
1838
+ const r2 = e2.run({ max: this.max });
1839
+ let reason = null;
1840
+ if (!r1.final || !r2.final)
1841
+ reason = "failed to reach normal form in " + this.max + " steps";
1842
+ else
1843
+ reason = r1.expr.diff(r2.expr);
1844
+ return {
1845
+ pass: !reason,
1846
+ reason: reason ?? void 0,
1847
+ steps: r1.steps,
1848
+ start: e1,
1849
+ found: r1.expr,
1850
+ expected: r2.expr,
1851
+ note: this.note,
1852
+ args,
1853
+ case: this
2113
1854
  };
2114
- function list2str(str) {
2115
- if (str === void 0 || typeof str === "string")
2116
- return str;
2117
- return Array.isArray(str) ? str.join(" ") : "" + str;
1855
+ }
1856
+ };
1857
+ var knownCaps = {
1858
+ normal: true,
1859
+ proper: true,
1860
+ discard: true,
1861
+ duplicate: true,
1862
+ linear: true,
1863
+ affine: true,
1864
+ arity: true
1865
+ };
1866
+ var PropertyCase = class extends Case {
1867
+ // test that an expression uses all of its inputs exactly once
1868
+ constructor(input, options, terms) {
1869
+ super(input, options);
1870
+ if (terms.length > 1)
1871
+ throw new Error("PropertyCase accepts exactly 1 string");
1872
+ if (!options.caps || typeof options.caps !== "object" || !Object.keys(options.caps).length)
1873
+ throw new Error("PropertyCase requires a caps object with at least one capability");
1874
+ const unknown = Object.keys(options.caps).filter((c) => !knownCaps[c]);
1875
+ if (unknown.length)
1876
+ throw new Error("PropertyCase: don't know how to test these capabilities: " + unknown.join(", "));
1877
+ this.expr = this.parse(terms[0]);
1878
+ this.caps = { ...options.caps };
1879
+ if (this.caps.linear) {
1880
+ delete this.caps.linear;
1881
+ this.caps.duplicate = false;
1882
+ this.caps.discard = false;
1883
+ this.caps.normal = true;
2118
1884
  }
2119
- function checkId(id, seen) {
2120
- if (id === void 0)
2121
- return "missing";
2122
- if (typeof id !== "string" && typeof id !== "number")
2123
- return "is a " + typeof id;
2124
- if (seen) {
2125
- if (seen.has(id))
2126
- return "duplicate id " + id;
2127
- seen.add(id);
2128
- }
1885
+ if (this.caps.affine) {
1886
+ delete this.caps.affine;
1887
+ this.caps.normal = true;
1888
+ this.caps.duplicate = false;
2129
1889
  }
2130
- function checkHtml(str) {
2131
- if (str === void 0)
2132
- return "missing";
2133
- if (typeof str !== "string")
2134
- return "not a string but " + typeof str;
2135
- const tagStack = [];
2136
- const tagRegex = /<\/?([a-z]+)(?:\s[^>]*)?>/gi;
2137
- let match;
2138
- while ((match = tagRegex.exec(str)) !== null) {
2139
- const [fullTag, tagName] = match;
2140
- if (fullTag.startsWith("</")) {
2141
- if (tagStack.length === 0 || tagStack.pop() !== tagName)
2142
- return `Unmatched closing tag: </${tagName}>`;
2143
- } else {
2144
- tagStack.push(tagName);
2145
- }
2146
- }
2147
- if (tagStack.length > 0)
2148
- return `Unclosed tags: ${tagStack.join(", ")}`;
2149
- return null;
1890
+ }
1891
+ check(...expr) {
1892
+ const start = this.expr.apply(expr);
1893
+ const r = start.run({ max: this.max });
1894
+ const guess = r.expr.infer({ max: this.max });
1895
+ const reason = [];
1896
+ for (const cap in this.caps) {
1897
+ if (guess[cap] !== this.caps[cap])
1898
+ reason.push("expected property " + cap + " to be " + this.caps[cap] + ", found " + guess[cap]);
2150
1899
  }
2151
- Quest2.Group = Group;
2152
- Quest2.Case = Case;
2153
- module2.exports = { Quest: Quest2 };
1900
+ return {
1901
+ pass: !reason.length,
1902
+ reason: reason.length ? reason.join("\n") : void 0,
1903
+ steps: r.steps,
1904
+ start,
1905
+ found: r.expr,
1906
+ case: this,
1907
+ note: this.note,
1908
+ args: expr
1909
+ };
2154
1910
  }
2155
- });
1911
+ };
1912
+ var Subst = class {
1913
+ constructor(expr, env) {
1914
+ this.expr = expr;
1915
+ this.env = env;
1916
+ }
1917
+ apply(list) {
1918
+ if (list.length !== this.env.length)
1919
+ throw new Error("Subst: expected " + this.env.length + " terms, got " + list.length);
1920
+ let expr = this.expr;
1921
+ for (let i = 0; i < this.env.length; i++)
1922
+ expr = expr.subst(this.env[i], list[i]) ?? expr;
1923
+ return expr;
1924
+ }
1925
+ };
1926
+ var Group = class {
1927
+ constructor(options) {
1928
+ this.name = options.name;
1929
+ this.intro = list2str(options.intro);
1930
+ this.id = options.id;
1931
+ if (options.content)
1932
+ this.content = options.content.map((c) => c instanceof Quest ? c : new Quest(c));
1933
+ }
1934
+ verify(options) {
1935
+ const findings = {};
1936
+ const id = checkId(this.id, options.seen);
1937
+ if (id)
1938
+ findings.id = id;
1939
+ for (const field of ["name", "intro"]) {
1940
+ const found = checkHtml(this[field]);
1941
+ if (found)
1942
+ findings[field] = found;
1943
+ }
1944
+ findings.content = this.content.map((q) => q.verify(options));
1945
+ return findings;
1946
+ }
1947
+ };
1948
+ function list2str(str) {
1949
+ if (str === void 0 || typeof str === "string")
1950
+ return str;
1951
+ return Array.isArray(str) ? str.join(" ") : "" + str;
1952
+ }
1953
+ function checkId(id, seen) {
1954
+ if (id === void 0)
1955
+ return "missing";
1956
+ if (typeof id !== "string" && typeof id !== "number")
1957
+ return "is a " + typeof id;
1958
+ if (seen) {
1959
+ if (seen.has(id))
1960
+ return "duplicate id " + id;
1961
+ seen.add(id);
1962
+ }
1963
+ }
1964
+ function checkHtml(str) {
1965
+ if (str === void 0)
1966
+ return "missing";
1967
+ if (typeof str !== "string")
1968
+ return "not a string but " + typeof str;
1969
+ const tagStack = [];
1970
+ const tagRegex = /<\/?([a-z]+)(?:\s[^>]*)?>/gi;
1971
+ let match;
1972
+ while ((match = tagRegex.exec(str)) !== null) {
1973
+ const [fullTag, tagName] = match;
1974
+ if (fullTag.startsWith("</")) {
1975
+ if (tagStack.length === 0 || tagStack.pop() !== tagName)
1976
+ return `Unmatched closing tag: </${tagName}>`;
1977
+ } else {
1978
+ tagStack.push(tagName);
1979
+ }
1980
+ }
1981
+ if (tagStack.length > 0)
1982
+ return `Unclosed tags: ${tagStack.join(", ")}`;
1983
+ return null;
1984
+ }
1985
+ Quest.Group = Group;
1986
+ Quest.Case = Case;
2156
1987
 
2157
- // src/extras.js
2158
- var require_extras = __commonJS({
2159
- "src/extras.js"(exports2, module2) {
2160
- "use strict";
2161
- var { Expr, Alias, FreeVar } = require_expr();
2162
- var { Quest: Quest2 } = require_quest();
2163
- function search(seed, options, predicate) {
2164
- const {
2165
- depth = 16,
2166
- infer = true,
2167
- progressInterval = 1e3
2168
- } = options;
2169
- const hasSeen = infer && !options.noskip;
2170
- const cache = [[]];
2171
- let total = 0;
2172
- let probed = 0;
2173
- const seen = {};
2174
- const maybeProbe = (term) => {
2175
- total++;
2176
- const props = infer ? term.infer({ max: options.max, maxArgs: options.maxArgs }) : null;
2177
- if (hasSeen && props.expr) {
2178
- if (seen[props.expr])
2179
- return { res: -1 };
2180
- seen[props.expr] = true;
2181
- }
2182
- probed++;
2183
- const res = predicate(term, props);
2184
- return { res, props };
2185
- };
2186
- for (const term of seed) {
2187
- const { res } = maybeProbe(term);
2188
- if (res > 0)
2189
- return { expr: term, total, probed, gen: 1 };
2190
- else if (res < 0)
2191
- continue;
2192
- cache[0].push(term);
2193
- }
2194
- let lastProgress;
2195
- for (let gen = 1; gen < depth; gen++) {
2196
- if (options.progress) {
2197
- options.progress({ gen, total, probed, step: true });
2198
- lastProgress = total;
2199
- }
2200
- for (let i = 0; i < gen; i++) {
2201
- for (const a of cache[gen - i - 1] || []) {
2202
- for (const b of cache[i] || []) {
2203
- if (total >= options.tries)
2204
- return { total, probed, gen, ...options.retain ? { cache } : {} };
2205
- if (options.progress && total - lastProgress >= progressInterval) {
2206
- options.progress({ gen, total, probed, step: false });
2207
- lastProgress = total;
2208
- }
2209
- const term = a.apply(b);
2210
- const { res, props } = maybeProbe(term);
2211
- if (res > 0)
2212
- return { expr: term, total, probed, gen, ...options.retain ? { cache } : {} };
2213
- else if (res < 0)
2214
- continue;
2215
- const offset = infer ? (props.expr ? 0 : 3) + (props.dup ? 1 : 0) + (props.proper ? 0 : 1) : 0;
2216
- if (!cache[gen + offset])
2217
- cache[gen + offset] = [];
2218
- cache[gen + offset].push(term);
2219
- }
1988
+ // src/extras.ts
1989
+ function search(seed, options, predicate) {
1990
+ const {
1991
+ depth = 16,
1992
+ infer = true,
1993
+ progressInterval = 1e3
1994
+ } = options;
1995
+ const hasSeen = infer && !options.noskip;
1996
+ const cache = [[]];
1997
+ let total = 0;
1998
+ let probed = 0;
1999
+ const seen = {};
2000
+ const maybeProbe = (term) => {
2001
+ total++;
2002
+ const props = infer ? term.infer({ max: options.max, maxArgs: options.maxArgs }) : null;
2003
+ if (hasSeen && props && props.expr) {
2004
+ const key = String(props.expr);
2005
+ if (seen[key])
2006
+ return { res: -1, props };
2007
+ seen[key] = true;
2008
+ }
2009
+ probed++;
2010
+ const res = predicate(term, props);
2011
+ return { res, props };
2012
+ };
2013
+ for (const term of seed) {
2014
+ const { res = 0 } = maybeProbe(term);
2015
+ if (res > 0)
2016
+ return { expr: term, total, probed, gen: 1 };
2017
+ else if (res < 0)
2018
+ continue;
2019
+ cache[0].push(term);
2020
+ }
2021
+ let lastProgress = 0;
2022
+ for (let gen = 1; gen < depth; gen++) {
2023
+ if (options.progress) {
2024
+ options.progress({ gen, total, probed, step: true });
2025
+ lastProgress = total;
2026
+ }
2027
+ for (let i = 0; i < gen; i++) {
2028
+ for (const a of cache[gen - i - 1] || []) {
2029
+ for (const b of cache[i] || []) {
2030
+ if (total >= (options.tries ?? Infinity))
2031
+ return { total, probed, gen, ...options.retain ? { cache } : {} };
2032
+ if (options.progress && total - lastProgress >= progressInterval) {
2033
+ options.progress({ gen, total, probed, step: false });
2034
+ lastProgress = total;
2220
2035
  }
2036
+ const term = a.apply(b);
2037
+ const { res, props } = maybeProbe(term);
2038
+ if ((res ?? 0) > 0)
2039
+ return { expr: term, total, probed, gen, ...options.retain ? { cache } : {} };
2040
+ else if ((res ?? 0) < 0)
2041
+ continue;
2042
+ const offset = infer && props ? (props.expr ? 0 : 3) + (props.dup ? 1 : 0) + (props.proper ? 0 : 1) : 0;
2043
+ if (!cache[gen + offset])
2044
+ cache[gen + offset] = [];
2045
+ cache[gen + offset].push(term);
2221
2046
  }
2222
2047
  }
2223
- return { total, probed, gen: depth, ...options.retain ? { cache } : {} };
2224
- }
2225
- function deepFormat(obj, options = {}) {
2226
- if (obj instanceof Expr)
2227
- return obj.format(options);
2228
- if (obj instanceof Quest2)
2229
- return "Quest(" + obj.name + ")";
2230
- if (obj instanceof Quest2.Case)
2231
- return "Quest.Case";
2232
- if (Array.isArray(obj))
2233
- return obj.map(deepFormat);
2234
- if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
2235
- return obj;
2236
- const out = {};
2237
- for (const key in obj)
2238
- out[key] = deepFormat(obj[key]);
2239
- return out;
2240
2048
  }
2241
- function declare(expr, env) {
2242
- const res = Expr.extras.toposort([expr], env);
2243
- return res.list.map((s) => {
2244
- if (s instanceof Alias)
2245
- return s.name + "=" + s.impl.format({ inventory: res.env });
2246
- if (s instanceof FreeVar)
2247
- return s.name + "=";
2248
- return s.format({ inventory: res.env });
2249
- }).join("; ");
2250
- }
2251
- module2.exports = { search, deepFormat, declare };
2252
2049
  }
2253
- });
2050
+ return { total, probed, gen: depth, ...options.retain ? { cache } : {} };
2051
+ }
2052
+ function deepFormat(obj, options = {}) {
2053
+ if (obj instanceof Expr)
2054
+ return obj.format(options);
2055
+ if (obj instanceof Quest)
2056
+ return "Quest(" + obj.name + ")";
2057
+ if (obj instanceof Quest.Case)
2058
+ return "Quest.Case";
2059
+ if (Array.isArray(obj))
2060
+ return obj.map((item) => deepFormat(item, options));
2061
+ if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
2062
+ return obj;
2063
+ const out = {};
2064
+ for (const key in obj)
2065
+ out[key] = deepFormat(obj[key], options);
2066
+ return out;
2067
+ }
2068
+ function declare(expr, env = {}) {
2069
+ const res = toposort([expr], env);
2070
+ return res.list.map((s) => {
2071
+ if (s instanceof Alias)
2072
+ return s.name + "=" + s.impl.format({ inventory: res.env });
2073
+ if (s instanceof FreeVar)
2074
+ return s.name + "=";
2075
+ return s.format({ inventory: res.env });
2076
+ }).join("; ");
2077
+ }
2078
+ var extras = { search, deepFormat, declare, toposort };
2254
2079
 
2255
- // index.js
2256
- var { SKI } = require_parser();
2257
- var { Quest } = require_quest();
2258
- var extras = require_extras();
2259
- SKI.Quest = Quest;
2260
- SKI.extras = { ...extras, ...SKI.classes.Expr.extras };
2261
- if (typeof process === "object" && process.env.SKI_REPL && typeof global !== "undefined") {
2262
- global.SKI = SKI;
2080
+ // src/index.ts
2081
+ extras.toposort = toposort;
2082
+ var SKI = class extends Parser {
2083
+ static {
2084
+ this.native = native;
2085
+ }
2086
+ static {
2087
+ this.control = control;
2088
+ }
2089
+ static {
2090
+ this.classes = classes;
2091
+ }
2092
+ static {
2093
+ // TODO declare in a loop?
2094
+ this.B = native.B;
2095
+ }
2096
+ static {
2097
+ this.C = native.C;
2098
+ }
2099
+ static {
2100
+ this.I = native.I;
2101
+ }
2102
+ static {
2103
+ this.K = native.K;
2104
+ }
2105
+ static {
2106
+ this.S = native.S;
2107
+ }
2108
+ static {
2109
+ this.W = native.W;
2110
+ }
2111
+ // variable generator shortcut
2112
+ static vars(scope = {}) {
2113
+ const vars = {};
2114
+ return new Proxy(vars, {
2115
+ get(target, prop) {
2116
+ if (!(prop in target))
2117
+ target[prop] = new FreeVar(prop, scope);
2118
+ return target[prop];
2119
+ }
2120
+ });
2121
+ }
2122
+ static church(n) {
2123
+ return new Church(n);
2124
+ }
2125
+ static {
2126
+ this.extras = extras;
2127
+ }
2128
+ static {
2129
+ this.Quest = Quest;
2130
+ }
2131
+ };
2132
+ var g = globalThis;
2133
+ if (g.process?.env.SKI_REPL) {
2134
+ g.SKI = SKI;
2263
2135
  console.log("SKI_REPL activated, try `new SKI();`");
2264
2136
  }
2265
2137
  if (typeof window !== "undefined")
2266
2138
  window.SKI = SKI;
2267
- module.exports = { SKI, Quest };
2268
2139
  //# sourceMappingURL=ski-interpreter.cjs.js.map