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