@dallaylaen/ski-interpreter 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +86 -11
- package/bin/ski.js +195 -82
- package/lib/ski-interpreter.cjs.js +522 -329
- package/lib/ski-interpreter.cjs.js.map +3 -3
- package/lib/ski-interpreter.esm.js +522 -329
- package/lib/ski-interpreter.esm.js.map +2 -2
- package/lib/ski-interpreter.min.js +4 -0
- package/lib/ski-interpreter.min.js.map +7 -0
- package/lib/ski-quest.min.js +4 -0
- package/lib/ski-quest.min.js.map +7 -0
- package/package.json +11 -4
- package/types/src/expr.d.ts +201 -102
- package/types/src/extras.d.ts +17 -1
- package/types/src/internal.d.ts +23 -10
- package/types/src/parser.d.ts +46 -19
- package/types/src/quest.d.ts +87 -41
|
@@ -59,24 +59,34 @@ var require_internal = __commonJS({
|
|
|
59
59
|
}
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
|
-
var
|
|
62
|
+
var TraverseControl = class {
|
|
63
63
|
/**
|
|
64
|
+
* @desc A wrapper for values returned by fold/traverse callbacks
|
|
65
|
+
* which instructs the traversal to alter its behavior while
|
|
66
|
+
* retaining the value in question.
|
|
67
|
+
*
|
|
68
|
+
* This class is instantiated internally be `SKI.control.*` functions,
|
|
69
|
+
* and is not intended to be used directly by client code.
|
|
70
|
+
*
|
|
64
71
|
* @template T
|
|
65
72
|
* @param {T} value
|
|
66
|
-
* @param {
|
|
73
|
+
* @param {function(T): TraverseControl<T>} decoration
|
|
67
74
|
*/
|
|
68
|
-
constructor(value,
|
|
75
|
+
constructor(value, decoration) {
|
|
69
76
|
this.value = value;
|
|
70
|
-
this.
|
|
77
|
+
this.decoration = decoration;
|
|
71
78
|
}
|
|
72
79
|
};
|
|
73
80
|
function unwrap(value) {
|
|
74
|
-
if (value instanceof
|
|
75
|
-
return [value.value ?? void 0, value.
|
|
81
|
+
if (value instanceof TraverseControl)
|
|
82
|
+
return [value.value ?? void 0, value.decoration];
|
|
76
83
|
return [value ?? void 0, void 0];
|
|
77
84
|
}
|
|
78
|
-
function prepareWrapper(
|
|
79
|
-
|
|
85
|
+
function prepareWrapper(label) {
|
|
86
|
+
const fun = (value) => new TraverseControl(value, fun);
|
|
87
|
+
fun.label = label;
|
|
88
|
+
fun.toString = () => "TraverseControl::" + label;
|
|
89
|
+
return fun;
|
|
80
90
|
}
|
|
81
91
|
module2.exports = { Tokenizer, restrict, unwrap, prepareWrapper };
|
|
82
92
|
}
|
|
@@ -91,11 +101,19 @@ var require_expr = __commonJS({
|
|
|
91
101
|
max: 1e3,
|
|
92
102
|
maxArgs: 32
|
|
93
103
|
};
|
|
104
|
+
var ORDER = {
|
|
105
|
+
"leftmost-outermost": "LO",
|
|
106
|
+
"leftmost-innermost": "LI",
|
|
107
|
+
LO: "LO",
|
|
108
|
+
LI: "LI"
|
|
109
|
+
};
|
|
94
110
|
var control = {
|
|
95
111
|
descend: prepareWrapper("descend"),
|
|
96
112
|
prune: prepareWrapper("prune"),
|
|
113
|
+
redo: prepareWrapper("redo"),
|
|
97
114
|
stop: prepareWrapper("stop")
|
|
98
115
|
};
|
|
116
|
+
var native = {};
|
|
99
117
|
var Expr = class _Expr {
|
|
100
118
|
/**
|
|
101
119
|
* @descr A combinatory logic expression.
|
|
@@ -109,11 +127,44 @@ var require_expr = __commonJS({
|
|
|
109
127
|
* env?: { [key: string]: Expr },
|
|
110
128
|
* src?: string,
|
|
111
129
|
* parser: object,
|
|
112
|
-
* }} [context]
|
|
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. φ instead of 'f'
|
|
134
|
+
* Typically only applicable to descendants of Named.
|
|
135
|
+
* @property {TermInfo} [props] - properties inferred from the term's behavior
|
|
113
136
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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. φ 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: ["", " ↦ ", ""] });
|
|
163
|
+
delete guess.steps;
|
|
164
|
+
this.props = guess;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
117
168
|
}
|
|
118
169
|
/**
|
|
119
170
|
* @desc apply self to zero or more terms and return the resulting term,
|
|
@@ -137,22 +188,77 @@ var require_expr = __commonJS({
|
|
|
137
188
|
return e.impl.expand();
|
|
138
189
|
}) ?? this;
|
|
139
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* @desc Returns true if the expression contains only free variables and applications, false otherwise.
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
140
195
|
freeOnly() {
|
|
141
196
|
return !this.any((e) => !(e instanceof FreeVar || e instanceof App));
|
|
142
197
|
}
|
|
143
198
|
/**
|
|
144
199
|
* @desc Traverse the expression tree, applying change() to each node.
|
|
145
200
|
* If change() returns an Expr, the node is replaced with that value.
|
|
146
|
-
*
|
|
147
|
-
* or
|
|
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.
|
|
148
215
|
*
|
|
149
216
|
* Returns null if no changes were made, or the new expression otherwise.
|
|
150
217
|
*
|
|
151
|
-
* @param {
|
|
218
|
+
* @param {{
|
|
219
|
+
* order?: 'LO' | 'LI' | 'leftmost-outermost' | 'leftmost-innermost',
|
|
220
|
+
* }} [options]
|
|
221
|
+
* @param {(e:Expr) => TraverseValue<Expr>} change
|
|
152
222
|
* @returns {Expr|null}
|
|
153
223
|
*/
|
|
154
|
-
traverse(change) {
|
|
155
|
-
|
|
224
|
+
traverse(options, change) {
|
|
225
|
+
if (typeof options === "function") {
|
|
226
|
+
change = options;
|
|
227
|
+
options = {};
|
|
228
|
+
}
|
|
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;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* @private
|
|
256
|
+
* @param {Object} options
|
|
257
|
+
* @param {(e:Expr) => TraverseValue<Expr>} change
|
|
258
|
+
* @returns {TraverseValue<Expr>}
|
|
259
|
+
*/
|
|
260
|
+
_traverse_descend(options, change) {
|
|
261
|
+
return null;
|
|
156
262
|
}
|
|
157
263
|
/**
|
|
158
264
|
* @desc Returns true if predicate() is true for any subterm of the expression, false otherwise.
|
|
@@ -179,18 +285,25 @@ var require_expr = __commonJS({
|
|
|
179
285
|
* @experimental
|
|
180
286
|
* @template T
|
|
181
287
|
* @param {T} initial
|
|
182
|
-
* @param {(acc: T, expr: Expr) =>
|
|
288
|
+
* @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
|
|
183
289
|
* @returns {T}
|
|
184
290
|
*/
|
|
185
291
|
fold(initial, combine) {
|
|
186
292
|
const [value, _] = unwrap(this._fold(initial, combine));
|
|
187
293
|
return value ?? initial;
|
|
188
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* @template T
|
|
297
|
+
* @param {T} initial
|
|
298
|
+
* @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
|
|
299
|
+
* @returns {TraverseValue<T>}
|
|
300
|
+
* @private
|
|
301
|
+
*/
|
|
189
302
|
_fold(initial, combine) {
|
|
190
303
|
return combine(initial, this);
|
|
191
304
|
}
|
|
192
305
|
/**
|
|
193
|
-
* @desc rough estimate of the complexity
|
|
306
|
+
* @desc rough estimate of the term's complexity
|
|
194
307
|
* @return {number}
|
|
195
308
|
*/
|
|
196
309
|
weight() {
|
|
@@ -209,52 +322,61 @@ var require_expr = __commonJS({
|
|
|
209
322
|
* Use toLambda() if you want to get a lambda term in any case.
|
|
210
323
|
*
|
|
211
324
|
* @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
|
-
* }}
|
|
325
|
+
* @return {TermInfo}
|
|
223
326
|
*/
|
|
224
327
|
infer(options = {}) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
328
|
+
return this._infer({
|
|
329
|
+
max: options.max ?? DEFAULTS.max,
|
|
330
|
+
maxArgs: options.maxArgs ?? DEFAULTS.maxArgs
|
|
331
|
+
}, 0);
|
|
229
332
|
}
|
|
230
333
|
/**
|
|
231
|
-
*
|
|
232
|
-
* @param {{max: number, maxArgs: number
|
|
233
|
-
* @param {
|
|
234
|
-
* @
|
|
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}}
|
|
334
|
+
* @desc Internal method for infer(), which performs the actual inference.
|
|
335
|
+
* @param {{max: number, maxArgs: number}} options
|
|
336
|
+
* @param {number} nargs - var index to avoid name clashes
|
|
337
|
+
* @returns {TermInfo}
|
|
236
338
|
* @private
|
|
237
339
|
*/
|
|
238
|
-
_infer(options,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
340
|
+
_infer(options, nargs) {
|
|
341
|
+
const probe = [];
|
|
342
|
+
let steps = 0;
|
|
343
|
+
let expr = this;
|
|
344
|
+
main: for (let i = 0; i < options.maxArgs; i++) {
|
|
345
|
+
const next = expr.run({ max: options.max - steps });
|
|
346
|
+
steps += next.steps;
|
|
347
|
+
if (!next.final)
|
|
348
|
+
break;
|
|
349
|
+
if (firstVar(next.expr)) {
|
|
350
|
+
expr = next.expr;
|
|
351
|
+
if (!expr.any((e) => !(e instanceof FreeVar || e instanceof App)))
|
|
352
|
+
return maybeLambda(probe, expr, { steps });
|
|
353
|
+
const list = expr.unroll();
|
|
354
|
+
let discard = false;
|
|
355
|
+
let duplicate = false;
|
|
356
|
+
const acc = [];
|
|
357
|
+
for (let j = 1; j < list.length; j++) {
|
|
358
|
+
const sub = list[j]._infer(
|
|
359
|
+
{ maxArgs: options.maxArgs - nargs, max: options.max - steps },
|
|
360
|
+
// limit recursion
|
|
361
|
+
nargs + i
|
|
362
|
+
// avoid variable name clashes
|
|
363
|
+
);
|
|
364
|
+
steps += sub.steps;
|
|
365
|
+
if (!sub.expr)
|
|
366
|
+
break main;
|
|
367
|
+
if (sub.discard)
|
|
368
|
+
discard = true;
|
|
369
|
+
if (sub.duplicate)
|
|
370
|
+
duplicate = true;
|
|
371
|
+
acc.push(sub.expr);
|
|
372
|
+
}
|
|
373
|
+
return maybeLambda(probe, list[0].apply(...acc), { discard, duplicate, steps });
|
|
374
|
+
}
|
|
375
|
+
const push = nthvar(nargs + i);
|
|
376
|
+
probe.push(push);
|
|
377
|
+
expr = next.expr.apply(push);
|
|
247
378
|
}
|
|
248
|
-
|
|
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);
|
|
379
|
+
return { normal: false, proper: false, steps };
|
|
258
380
|
}
|
|
259
381
|
/**
|
|
260
382
|
* @desc Expand an expression into a list of terms
|
|
@@ -289,10 +411,10 @@ var require_expr = __commonJS({
|
|
|
289
411
|
* latin?: number,
|
|
290
412
|
* }} options
|
|
291
413
|
* @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
|
|
292
|
-
* @return {IterableIterator<{expr: Expr, steps
|
|
414
|
+
* @return {IterableIterator<{expr: Expr, steps?: number, comment?: string}>}
|
|
293
415
|
*/
|
|
294
416
|
*toLambda(options = {}) {
|
|
295
|
-
|
|
417
|
+
let expr = this.traverse((e) => {
|
|
296
418
|
if (e instanceof FreeVar || e instanceof App || e instanceof Lambda || e instanceof Alias)
|
|
297
419
|
return null;
|
|
298
420
|
const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
|
|
@@ -300,7 +422,25 @@ var require_expr = __commonJS({
|
|
|
300
422
|
throw new Error("Failed to infer an equivalent lambda term for " + e);
|
|
301
423
|
return guess.expr;
|
|
302
424
|
}) ?? this;
|
|
303
|
-
|
|
425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
426
|
+
let steps = 0;
|
|
427
|
+
while (expr) {
|
|
428
|
+
const next = expr.traverse({ order: "LI" }, (e) => {
|
|
429
|
+
if (seen.has(e))
|
|
430
|
+
return null;
|
|
431
|
+
if (e instanceof App && e.fun instanceof Lambda) {
|
|
432
|
+
const guess = e.infer({ max: options.max, maxArgs: options.maxArgs });
|
|
433
|
+
steps += guess.steps;
|
|
434
|
+
if (!guess.normal) {
|
|
435
|
+
seen.add(e);
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
return control.stop(guess.expr);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
yield { expr, steps };
|
|
442
|
+
expr = next;
|
|
443
|
+
}
|
|
304
444
|
}
|
|
305
445
|
/**
|
|
306
446
|
* @desc Rewrite the expression into S, K, and I combinators step by step.
|
|
@@ -313,28 +453,31 @@ var require_expr = __commonJS({
|
|
|
313
453
|
* @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
|
|
314
454
|
*/
|
|
315
455
|
*toSKI(options = {}) {
|
|
456
|
+
let expr = this.traverse((e) => {
|
|
457
|
+
if (e instanceof FreeVar || e instanceof App || e instanceof Lambda || e instanceof Alias)
|
|
458
|
+
return null;
|
|
459
|
+
return e.infer().expr;
|
|
460
|
+
}) ?? this;
|
|
316
461
|
let steps = 0;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
462
|
+
while (expr) {
|
|
463
|
+
const next = expr.traverse({ order: "LI" }, (e) => {
|
|
464
|
+
if (!(e instanceof Lambda) || e.impl instanceof Lambda)
|
|
465
|
+
return null;
|
|
466
|
+
if (e.impl === e.arg)
|
|
467
|
+
return control.stop(native.I);
|
|
468
|
+
if (!e.impl.any((t) => t === e.arg))
|
|
469
|
+
return control.stop(native.K.apply(e.impl));
|
|
470
|
+
if (!(e.impl instanceof App))
|
|
471
|
+
throw new Error("toSKI: assert failed: lambda body is of unexpected type " + e.impl.constructor.name);
|
|
472
|
+
if (e.impl.arg === e.arg && !e.impl.fun.any((t) => t === e.arg))
|
|
473
|
+
return control.stop(e.impl.fun);
|
|
474
|
+
return control.stop(native.S.apply(new Lambda(e.arg, e.impl.fun), new Lambda(e.arg, e.impl.arg)));
|
|
475
|
+
});
|
|
476
|
+
yield { expr, steps, final: !next };
|
|
477
|
+
steps++;
|
|
325
478
|
expr = next;
|
|
326
|
-
steps += opt.steps;
|
|
327
479
|
}
|
|
328
480
|
}
|
|
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
481
|
/**
|
|
339
482
|
* Replace all instances of plug in the expression with value and return the resulting expression,
|
|
340
483
|
* or null if no changes could be made.
|
|
@@ -382,7 +525,7 @@ var require_expr = __commonJS({
|
|
|
382
525
|
* @desc Run uninterrupted sequence of step() applications
|
|
383
526
|
* until the expression is irreducible, or max number of steps is reached.
|
|
384
527
|
* Default number of steps = 1000.
|
|
385
|
-
* @param {{max
|
|
528
|
+
* @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
|
|
386
529
|
* @param {Expr} args
|
|
387
530
|
* @return {{expr: Expr, steps: number, final: boolean}}
|
|
388
531
|
*/
|
|
@@ -411,7 +554,7 @@ var require_expr = __commonJS({
|
|
|
411
554
|
/**
|
|
412
555
|
* Execute step() while possible, yielding a brief description of events after each step.
|
|
413
556
|
* Mnemonics: like run() but slower.
|
|
414
|
-
* @param {{max
|
|
557
|
+
* @param {{max?: number}} options
|
|
415
558
|
* @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
|
|
416
559
|
*/
|
|
417
560
|
*walk(options = {}) {
|
|
@@ -474,19 +617,24 @@ var require_expr = __commonJS({
|
|
|
474
617
|
}
|
|
475
618
|
/**
|
|
476
619
|
* @desc Assert expression equality. Can be used in tests.
|
|
477
|
-
*
|
|
620
|
+
*
|
|
621
|
+
* `this` is the expected value and the argument is the actual one.
|
|
622
|
+
* Mnemonic: the expected value is always a combinator, the actual one may be anything.
|
|
623
|
+
*
|
|
624
|
+
* @param {Expr} actual
|
|
478
625
|
* @param {string} comment
|
|
479
626
|
*/
|
|
480
|
-
expect(
|
|
627
|
+
expect(actual, comment = "") {
|
|
481
628
|
comment = comment ? comment + ": " : "";
|
|
482
|
-
if (!(
|
|
483
|
-
throw new Error(comment + "
|
|
484
|
-
|
|
629
|
+
if (!(actual instanceof _Expr)) {
|
|
630
|
+
throw new Error(comment + "Expected a combinator but found " + (actual?.constructor?.name ?? typeof actual));
|
|
631
|
+
}
|
|
632
|
+
const diff = this.diff(actual);
|
|
485
633
|
if (!diff)
|
|
486
634
|
return;
|
|
487
635
|
const poorMans = new Error(comment + diff);
|
|
488
|
-
poorMans.expected =
|
|
489
|
-
poorMans.actual =
|
|
636
|
+
poorMans.expected = this.diag();
|
|
637
|
+
poorMans.actual = actual.diag();
|
|
490
638
|
throw poorMans;
|
|
491
639
|
}
|
|
492
640
|
/**
|
|
@@ -586,6 +734,42 @@ var require_expr = __commonJS({
|
|
|
586
734
|
_format(options, nargs) {
|
|
587
735
|
throw new Error("No _format() method defined in class " + this.constructor.name);
|
|
588
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* @desc Returns a string representation of the expression tree, with indentation to show structure.
|
|
739
|
+
*
|
|
740
|
+
* Applications are flattened to avoid excessive nesting.
|
|
741
|
+
* Variables include ids to distinguish different instances of the same variable name.
|
|
742
|
+
*
|
|
743
|
+
* May be useful for debugging.
|
|
744
|
+
*
|
|
745
|
+
* @returns {string}
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* > console.log(ski.parse('C 5 x (x->x x)').diag())
|
|
749
|
+
* App:
|
|
750
|
+
* Native: C
|
|
751
|
+
* Church: 5
|
|
752
|
+
* FreeVar: x[53]
|
|
753
|
+
* Lambda (x[54]):
|
|
754
|
+
* App:
|
|
755
|
+
* FreeVar: x[54]
|
|
756
|
+
* FreeVar: x[54]
|
|
757
|
+
*/
|
|
758
|
+
diag() {
|
|
759
|
+
const rec = (e, indent) => {
|
|
760
|
+
if (e instanceof App)
|
|
761
|
+
return [indent + "App:", ...e.unroll().flatMap((s) => rec(s, indent + " "))];
|
|
762
|
+
if (e instanceof Lambda)
|
|
763
|
+
return [`${indent}Lambda (${e.arg}[${e.arg.id}]):`, ...rec(e.impl, indent + " ")];
|
|
764
|
+
if (e instanceof Alias)
|
|
765
|
+
return [`${indent}Alias (${e.name}): \\`, ...rec(e.impl, indent)];
|
|
766
|
+
if (e instanceof FreeVar)
|
|
767
|
+
return [`${indent}FreeVar: ${e.name}[${e.id}]`];
|
|
768
|
+
return [`${indent}${e.constructor.name}: ${e}`];
|
|
769
|
+
};
|
|
770
|
+
const out = rec(this, "");
|
|
771
|
+
return out.join("\n");
|
|
772
|
+
}
|
|
589
773
|
/**
|
|
590
774
|
* @desc Convert the expression to a JSON-serializable format.
|
|
591
775
|
* @returns {string}
|
|
@@ -605,72 +789,35 @@ var require_expr = __commonJS({
|
|
|
605
789
|
super();
|
|
606
790
|
this.arg = arg;
|
|
607
791
|
this.fun = fun;
|
|
608
|
-
this.final = false;
|
|
609
|
-
this.arity = this.fun.arity > 0 ? this.fun.arity - 1 : 0;
|
|
610
792
|
}
|
|
793
|
+
/** @property {boolean} [final] */
|
|
611
794
|
weight() {
|
|
612
795
|
return this.fun.weight() + this.arg.weight();
|
|
613
796
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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);
|
|
797
|
+
_traverse_descend(options, change) {
|
|
798
|
+
const [fun, fAction] = unwrap(this.fun._traverse_redo(options, change));
|
|
799
|
+
if (fAction === control.stop)
|
|
800
|
+
return control.stop(fun ? fun.apply(this.arg) : null);
|
|
801
|
+
const [arg, aAction] = unwrap(this.arg._traverse_redo(options, change));
|
|
802
|
+
const final = fun || arg ? (fun ?? this.fun).apply(arg ?? this.arg) : null;
|
|
803
|
+
if (aAction === control.stop)
|
|
804
|
+
return control.stop(final);
|
|
805
|
+
return final;
|
|
659
806
|
}
|
|
660
807
|
any(predicate) {
|
|
661
808
|
return predicate(this) || this.fun.any(predicate) || this.arg.any(predicate);
|
|
662
809
|
}
|
|
663
810
|
_fold(initial, combine) {
|
|
664
811
|
const [value = initial, action = "descend"] = unwrap(combine(initial, this));
|
|
665
|
-
if (action ===
|
|
812
|
+
if (action === control.prune)
|
|
666
813
|
return value;
|
|
667
|
-
if (action ===
|
|
814
|
+
if (action === control.stop)
|
|
668
815
|
return control.stop(value);
|
|
669
816
|
const [fValue = value, fAction = "descend"] = unwrap(this.fun._fold(value, combine));
|
|
670
|
-
if (fAction ===
|
|
817
|
+
if (fAction === control.stop)
|
|
671
818
|
return control.stop(fValue);
|
|
672
819
|
const [aValue = fValue, aAction = "descend"] = unwrap(this.arg._fold(fValue, combine));
|
|
673
|
-
if (aAction ===
|
|
820
|
+
if (aAction === control.stop)
|
|
674
821
|
return control.stop(aValue);
|
|
675
822
|
return aValue;
|
|
676
823
|
}
|
|
@@ -714,15 +861,6 @@ var require_expr = __commonJS({
|
|
|
714
861
|
unroll() {
|
|
715
862
|
return [...this.fun.unroll(), this.arg];
|
|
716
863
|
}
|
|
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
864
|
diff(other, swap = false) {
|
|
727
865
|
if (!(other instanceof _App))
|
|
728
866
|
return super.diff(other, swap);
|
|
@@ -784,6 +922,8 @@ var require_expr = __commonJS({
|
|
|
784
922
|
* If a scope object is given, however, two variables with the same name and scope
|
|
785
923
|
* are considered identical.
|
|
786
924
|
*
|
|
925
|
+
* By convention, FreeVar.global is a constant denoting a global unbound variable.
|
|
926
|
+
*
|
|
787
927
|
* @param {string} name - name of the variable
|
|
788
928
|
* @param {any} scope - an object representing where the variable belongs to.
|
|
789
929
|
*/
|
|
@@ -814,6 +954,7 @@ var require_expr = __commonJS({
|
|
|
814
954
|
return options.var[0] + name + options.var[1];
|
|
815
955
|
}
|
|
816
956
|
};
|
|
957
|
+
FreeVar.global = ["global"];
|
|
817
958
|
var Native = class extends Named {
|
|
818
959
|
/**
|
|
819
960
|
* @desc A named term with a known rewriting rule.
|
|
@@ -829,29 +970,14 @@ var require_expr = __commonJS({
|
|
|
829
970
|
*
|
|
830
971
|
* @param {String} name
|
|
831
972
|
* @param {Partial} impl
|
|
832
|
-
* @param {{note?: string, arity?: number, canonize?: boolean
|
|
973
|
+
* @param {{note?: string, arity?: number, canonize?: boolean }} [opt]
|
|
833
974
|
*/
|
|
834
975
|
constructor(name, impl, opt = {}) {
|
|
835
976
|
super(name);
|
|
836
977
|
this.invoke = impl;
|
|
837
|
-
|
|
838
|
-
this.arity = opt.arity || guess.arity || 1;
|
|
839
|
-
this.note = opt.note ?? guess.expr?.format({ terse: true, html: true, lambda: ["", " ↦ ", ""] });
|
|
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);
|
|
978
|
+
this._setup({ canonize: true, ...opt });
|
|
849
979
|
}
|
|
850
980
|
};
|
|
851
|
-
var native = {};
|
|
852
|
-
function addNative(name, impl, opt) {
|
|
853
|
-
native[name] = new Native(name, impl, opt);
|
|
854
|
-
}
|
|
855
981
|
var Lambda = class _Lambda extends Expr {
|
|
856
982
|
/**
|
|
857
983
|
* @desc Lambda abstraction of arg over impl.
|
|
@@ -892,35 +1018,25 @@ var require_expr = __commonJS({
|
|
|
892
1018
|
weight() {
|
|
893
1019
|
return this.impl.weight() + 1;
|
|
894
1020
|
}
|
|
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
1021
|
invoke(arg) {
|
|
902
1022
|
return this.impl.subst(this.arg, arg) ?? this.impl;
|
|
903
1023
|
}
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const impl = this.impl.traverse(change);
|
|
909
|
-
if (!impl)
|
|
910
|
-
return null;
|
|
911
|
-
return new _Lambda(this.arg, impl);
|
|
1024
|
+
_traverse_descend(options, change) {
|
|
1025
|
+
const [impl, iAction] = unwrap(this.impl._traverse_redo(options, change));
|
|
1026
|
+
const final = impl ? new _Lambda(this.arg, impl) : null;
|
|
1027
|
+
return iAction === control.stop ? control.stop(final) : final;
|
|
912
1028
|
}
|
|
913
1029
|
any(predicate) {
|
|
914
1030
|
return predicate(this) || this.impl.any(predicate);
|
|
915
1031
|
}
|
|
916
1032
|
_fold(initial, combine) {
|
|
917
1033
|
const [value = initial, action = "descend"] = unwrap(combine(initial, this));
|
|
918
|
-
if (action ===
|
|
1034
|
+
if (action === control.prune)
|
|
919
1035
|
return value;
|
|
920
|
-
if (action ===
|
|
1036
|
+
if (action === control.stop)
|
|
921
1037
|
return control.stop(value);
|
|
922
1038
|
const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
|
|
923
|
-
if (iAction ===
|
|
1039
|
+
if (iAction === control.stop)
|
|
924
1040
|
return control.stop(iValue);
|
|
925
1041
|
return iValue ?? value;
|
|
926
1042
|
}
|
|
@@ -930,26 +1046,6 @@ var require_expr = __commonJS({
|
|
|
930
1046
|
const change = this.impl.subst(search, replace);
|
|
931
1047
|
return change ? new _Lambda(this.arg, change) : null;
|
|
932
1048
|
}
|
|
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
1049
|
diff(other, swap = false) {
|
|
954
1050
|
if (!(other instanceof _Lambda))
|
|
955
1051
|
return super.diff(other, swap);
|
|
@@ -966,25 +1062,24 @@ var require_expr = __commonJS({
|
|
|
966
1062
|
return true;
|
|
967
1063
|
}
|
|
968
1064
|
};
|
|
969
|
-
var Church = class _Church extends
|
|
1065
|
+
var Church = class _Church extends Expr {
|
|
970
1066
|
/**
|
|
971
1067
|
* @desc Church numeral representing non-negative integer n:
|
|
972
1068
|
* n f x = f(f(...(f x)...)) with f applied n times.
|
|
973
1069
|
* @param {number} n
|
|
974
1070
|
*/
|
|
975
1071
|
constructor(n) {
|
|
976
|
-
|
|
977
|
-
if (!(
|
|
1072
|
+
n = Number.parseInt(n);
|
|
1073
|
+
if (!(n >= 0))
|
|
978
1074
|
throw new Error("Church number must be a non-negative integer");
|
|
979
|
-
|
|
980
|
-
|
|
1075
|
+
super();
|
|
1076
|
+
this.invoke = (x) => (y) => {
|
|
981
1077
|
let expr = y;
|
|
982
|
-
for (let i =
|
|
1078
|
+
for (let i = n; i-- > 0; )
|
|
983
1079
|
expr = x.apply(expr);
|
|
984
1080
|
return expr;
|
|
985
1081
|
};
|
|
986
|
-
|
|
987
|
-
this.n = p;
|
|
1082
|
+
this.n = n;
|
|
988
1083
|
this.arity = 2;
|
|
989
1084
|
}
|
|
990
1085
|
diff(other, swap = false) {
|
|
@@ -997,6 +1092,9 @@ var require_expr = __commonJS({
|
|
|
997
1092
|
_unspaced(arg) {
|
|
998
1093
|
return false;
|
|
999
1094
|
}
|
|
1095
|
+
_format(options, nargs) {
|
|
1096
|
+
return nargs >= 2 ? options.redex[0] + this.n + options.redex[1] : this.n + "";
|
|
1097
|
+
}
|
|
1000
1098
|
};
|
|
1001
1099
|
function waitn(expr, n) {
|
|
1002
1100
|
return (arg) => n <= 1 ? expr.apply(arg) : waitn(expr.apply(arg), n - 1);
|
|
@@ -1016,39 +1114,43 @@ var require_expr = __commonJS({
|
|
|
1016
1114
|
*
|
|
1017
1115
|
* @param {String} name
|
|
1018
1116
|
* @param {Expr} impl
|
|
1019
|
-
* @param {{canonize
|
|
1117
|
+
* @param {{canonize?: boolean, max?: number, maxArgs?: number, note?: string, terminal?: boolean}} [options]
|
|
1020
1118
|
*/
|
|
1021
1119
|
constructor(name, impl, options = {}) {
|
|
1022
1120
|
super(name);
|
|
1023
1121
|
if (!(impl instanceof Expr))
|
|
1024
1122
|
throw new Error("Attempt to create an alias for a non-expression: " + impl);
|
|
1025
1123
|
this.impl = impl;
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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);
|
|
1124
|
+
this._setup(options);
|
|
1125
|
+
this.terminal = options.terminal ?? this.props?.proper;
|
|
1126
|
+
this.invoke = waitn(impl, this.arity ?? 0);
|
|
1034
1127
|
}
|
|
1128
|
+
/**
|
|
1129
|
+
* @property {boolean} [outdated] - whether the alias is outdated
|
|
1130
|
+
* and should be replaced with its definition when encountered.
|
|
1131
|
+
* @property {boolean} [terminal] - whether the alias should behave like a standalone term
|
|
1132
|
+
* // TODO better name?
|
|
1133
|
+
* @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
|
|
1134
|
+
* @property {number} [arity] - the number of arguments the alias waits for before expanding
|
|
1135
|
+
* @property {Expr} [canonical] - equivalent lambda term.
|
|
1136
|
+
*/
|
|
1035
1137
|
weight() {
|
|
1036
1138
|
return this.terminal ? 1 : this.impl.weight();
|
|
1037
1139
|
}
|
|
1038
|
-
|
|
1039
|
-
return
|
|
1140
|
+
_traverse_descend(options, change) {
|
|
1141
|
+
return this.impl._traverse_redo(options, change);
|
|
1040
1142
|
}
|
|
1041
1143
|
any(predicate) {
|
|
1042
1144
|
return predicate(this) || this.impl.any(predicate);
|
|
1043
1145
|
}
|
|
1044
1146
|
_fold(initial, combine) {
|
|
1045
|
-
const [value = initial, action
|
|
1046
|
-
if (action ===
|
|
1147
|
+
const [value = initial, action] = unwrap(combine(initial, this));
|
|
1148
|
+
if (action === control.prune)
|
|
1047
1149
|
return value;
|
|
1048
|
-
if (action ===
|
|
1150
|
+
if (action === control.stop)
|
|
1049
1151
|
return control.stop(value);
|
|
1050
1152
|
const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
|
|
1051
|
-
if (iAction ===
|
|
1153
|
+
if (iAction === control.stop)
|
|
1052
1154
|
return control.stop(iValue);
|
|
1053
1155
|
return iValue ?? value;
|
|
1054
1156
|
}
|
|
@@ -1057,12 +1159,10 @@ var require_expr = __commonJS({
|
|
|
1057
1159
|
return replace;
|
|
1058
1160
|
return this.impl.subst(search, replace);
|
|
1059
1161
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1162
|
+
// DO NOT REMOVE TYPE or tsc chokes with
|
|
1163
|
+
// TS2527: The inferred type of 'Alias' references an inaccessible 'this' type.
|
|
1063
1164
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
* @return {{expr: Expr, steps: number}}
|
|
1165
|
+
* @return {{expr: Expr, steps: number, changed: boolean}}
|
|
1066
1166
|
*/
|
|
1067
1167
|
step() {
|
|
1068
1168
|
if (this.arity > 0)
|
|
@@ -1074,9 +1174,6 @@ var require_expr = __commonJS({
|
|
|
1074
1174
|
return null;
|
|
1075
1175
|
return other.diff(this.impl, !swap);
|
|
1076
1176
|
}
|
|
1077
|
-
_rski(options) {
|
|
1078
|
-
return this.impl._rski(options);
|
|
1079
|
-
}
|
|
1080
1177
|
_braced(first) {
|
|
1081
1178
|
return this.outdated ? this.impl._braced(first) : false;
|
|
1082
1179
|
}
|
|
@@ -1085,6 +1182,9 @@ var require_expr = __commonJS({
|
|
|
1085
1182
|
return outdated ? this.impl._format(options, nargs) : super._format(options, nargs);
|
|
1086
1183
|
}
|
|
1087
1184
|
};
|
|
1185
|
+
function addNative(name, impl, opt) {
|
|
1186
|
+
native[name] = new Native(name, impl, opt);
|
|
1187
|
+
}
|
|
1088
1188
|
addNative("I", (x) => x);
|
|
1089
1189
|
addNative("K", (x) => (_) => x);
|
|
1090
1190
|
addNative("S", (x) => (y) => (z) => x.apply(z, y.apply(z)));
|
|
@@ -1098,6 +1198,11 @@ var require_expr = __commonJS({
|
|
|
1098
1198
|
note: "Increase a Church numeral argument by 1, otherwise n => f => x => f(n f x)"
|
|
1099
1199
|
}
|
|
1100
1200
|
);
|
|
1201
|
+
function firstVar(expr) {
|
|
1202
|
+
while (expr instanceof App)
|
|
1203
|
+
expr = expr.fun;
|
|
1204
|
+
return expr instanceof FreeVar;
|
|
1205
|
+
}
|
|
1101
1206
|
function maybeLambda(args, expr, caps = {}) {
|
|
1102
1207
|
const count = new Array(args.length).fill(0);
|
|
1103
1208
|
let proper = true;
|
|
@@ -1121,8 +1226,10 @@ var require_expr = __commonJS({
|
|
|
1121
1226
|
dup.add(i);
|
|
1122
1227
|
}
|
|
1123
1228
|
return {
|
|
1229
|
+
normal: true,
|
|
1230
|
+
steps: caps.steps,
|
|
1124
1231
|
expr: args.length ? new Lambda(args, expr) : expr,
|
|
1125
|
-
|
|
1232
|
+
arity: args.length,
|
|
1126
1233
|
...skip.size ? { skip } : {},
|
|
1127
1234
|
...dup.size ? { dup } : {},
|
|
1128
1235
|
duplicate: !!dup.size || caps.duplicate || false,
|
|
@@ -1133,43 +1240,6 @@ var require_expr = __commonJS({
|
|
|
1133
1240
|
function nthvar(n) {
|
|
1134
1241
|
return new FreeVar("abcdefgh"[n] ?? "x" + n);
|
|
1135
1242
|
}
|
|
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
1243
|
function toposort(list, env) {
|
|
1174
1244
|
if (list instanceof Expr)
|
|
1175
1245
|
list = [list];
|
|
@@ -1273,7 +1343,7 @@ var require_parser = __commonJS({
|
|
|
1273
1343
|
"->",
|
|
1274
1344
|
"\\+"
|
|
1275
1345
|
);
|
|
1276
|
-
var SKI2 = class
|
|
1346
|
+
var SKI2 = class {
|
|
1277
1347
|
/**
|
|
1278
1348
|
*
|
|
1279
1349
|
* @param {{
|
|
@@ -1319,24 +1389,31 @@ var require_parser = __commonJS({
|
|
|
1319
1389
|
*
|
|
1320
1390
|
* @param {Alias|String} term
|
|
1321
1391
|
* @param {String|Expr|function(Expr):Partial} [impl]
|
|
1322
|
-
* @param {
|
|
1392
|
+
* @param {object|string} [options]
|
|
1393
|
+
* @param {string} [options.note] - optional annotation for the term, default is auto-generated if this.annotate is true
|
|
1394
|
+
* @param {boolean} [options.canonize] - whether to canonize the term's implementation, default is this.annotate
|
|
1395
|
+
* @param {boolean} [options.fancy] - alternative HTML-friendly name for the term
|
|
1396
|
+
* @param {number} [options.arity] - custom arity for the term, default is inferred from the implementation
|
|
1323
1397
|
* @return {SKI} chainable
|
|
1324
1398
|
*/
|
|
1325
|
-
add(term, impl,
|
|
1399
|
+
add(term, impl, options) {
|
|
1326
1400
|
term = this._named(term, impl);
|
|
1327
|
-
if (
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
note = guess.expr.format({ terse: true, html: true, lambda: ["", " ↦ ", ""] });
|
|
1331
|
-
}
|
|
1332
|
-
if (note !== void 0)
|
|
1333
|
-
term.note = note;
|
|
1401
|
+
if (typeof options === "string")
|
|
1402
|
+
options = { note: options, canonize: false };
|
|
1403
|
+
term._setup({ canonize: this.annotate, ...options });
|
|
1334
1404
|
if (this.known[term.name])
|
|
1335
1405
|
this.known[term.name].outdated = true;
|
|
1336
1406
|
this.known[term.name] = term;
|
|
1337
1407
|
this.allow.add(term.name);
|
|
1338
1408
|
return this;
|
|
1339
1409
|
}
|
|
1410
|
+
/**
|
|
1411
|
+
* @desc Internal helper for add() that creates an Alias or Native term from the given arguments.
|
|
1412
|
+
* @param {Alias|string} term
|
|
1413
|
+
* @param {string|Expr|function(Expr):Partial} impl
|
|
1414
|
+
* @returns {Native|Alias}
|
|
1415
|
+
* @private
|
|
1416
|
+
*/
|
|
1340
1417
|
_named(term, impl) {
|
|
1341
1418
|
if (term instanceof Alias)
|
|
1342
1419
|
return new Alias(term.name, term.impl, { canonize: true });
|
|
@@ -1352,6 +1429,16 @@ var require_parser = __commonJS({
|
|
|
1352
1429
|
return new Native(term, impl);
|
|
1353
1430
|
throw new Error("add(): impl must be an Expr, a string, or a function with a signature Expr => ... => Expr");
|
|
1354
1431
|
}
|
|
1432
|
+
/**
|
|
1433
|
+
* @desc Declare a new term if it is not known, otherwise just allow it.
|
|
1434
|
+
* Currently only used by quests.
|
|
1435
|
+
* Use with caution, this function may change its signature, behavior, or even be removed in the future.
|
|
1436
|
+
*
|
|
1437
|
+
* @experimental
|
|
1438
|
+
* @param {string|Alias} name
|
|
1439
|
+
* @param {string|Expr|function(Expr):Partial} impl
|
|
1440
|
+
* @returns {SKI}
|
|
1441
|
+
*/
|
|
1355
1442
|
maybeAdd(name, impl) {
|
|
1356
1443
|
if (this.known[name])
|
|
1357
1444
|
this.allow.add(name);
|
|
@@ -1363,7 +1450,7 @@ var require_parser = __commonJS({
|
|
|
1363
1450
|
* @desc Declare and remove multiple terms at once
|
|
1364
1451
|
* term=impl adds term
|
|
1365
1452
|
* term= removes term
|
|
1366
|
-
* @param {string[]
|
|
1453
|
+
* @param {string[]} list
|
|
1367
1454
|
* @return {SKI} chainable
|
|
1368
1455
|
*/
|
|
1369
1456
|
bulkAdd(list) {
|
|
@@ -1484,11 +1571,11 @@ var require_parser = __commonJS({
|
|
|
1484
1571
|
return out;
|
|
1485
1572
|
}
|
|
1486
1573
|
/**
|
|
1487
|
-
*
|
|
1574
|
+
* @template T
|
|
1488
1575
|
* @param {string} source
|
|
1489
1576
|
* @param {Object} [options]
|
|
1490
1577
|
* @param {{[keys: string]: Expr}} [options.env]
|
|
1491
|
-
* @param {
|
|
1578
|
+
* @param {T} [options.scope]
|
|
1492
1579
|
* @param {boolean} [options.numbers]
|
|
1493
1580
|
* @param {boolean} [options.lambdas]
|
|
1494
1581
|
* @param {string} [options.allow]
|
|
@@ -1505,7 +1592,7 @@ var require_parser = __commonJS({
|
|
|
1505
1592
|
expr.outdated = true;
|
|
1506
1593
|
const def = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=(.*)$/s);
|
|
1507
1594
|
if (def && def[2] === "")
|
|
1508
|
-
expr = new FreeVar(def[1], options.scope ??
|
|
1595
|
+
expr = new FreeVar(def[1], options.scope ?? FreeVar.global);
|
|
1509
1596
|
else
|
|
1510
1597
|
expr = this.parseLine(item, jar, options);
|
|
1511
1598
|
if (def) {
|
|
@@ -1524,12 +1611,14 @@ var require_parser = __commonJS({
|
|
|
1524
1611
|
return expr;
|
|
1525
1612
|
}
|
|
1526
1613
|
/**
|
|
1527
|
-
*
|
|
1614
|
+
* @desc Parse a single line of source code, without splitting it into declarations.
|
|
1615
|
+
* Internal, always use parse() instead.
|
|
1616
|
+
* @template T
|
|
1528
1617
|
* @param {String} source S(KI)I
|
|
1529
1618
|
* @param {{[keys: string]: Expr}} env
|
|
1530
1619
|
* @param {Object} [options]
|
|
1531
1620
|
* @param {{[keys: string]: Expr}} [options.env] - unused, see 'env' argument
|
|
1532
|
-
* @param {
|
|
1621
|
+
* @param {T} [options.scope]
|
|
1533
1622
|
* @param {boolean} [options.numbers]
|
|
1534
1623
|
* @param {boolean} [options.lambdas]
|
|
1535
1624
|
* @param {string} [options.allow]
|
|
@@ -1548,7 +1637,7 @@ var require_parser = __commonJS({
|
|
|
1548
1637
|
const tokens = combChars.split(source);
|
|
1549
1638
|
const empty = new Empty();
|
|
1550
1639
|
const stack = [empty];
|
|
1551
|
-
const context = options.scope ||
|
|
1640
|
+
const context = options.scope || FreeVar.global;
|
|
1552
1641
|
for (const c of tokens) {
|
|
1553
1642
|
if (c === "(")
|
|
1554
1643
|
stack.push(empty);
|
|
@@ -1593,12 +1682,12 @@ var require_parser = __commonJS({
|
|
|
1593
1682
|
};
|
|
1594
1683
|
}
|
|
1595
1684
|
};
|
|
1596
|
-
SKI2.vars = function(
|
|
1685
|
+
SKI2.vars = function(scope = {}) {
|
|
1597
1686
|
const cache = {};
|
|
1598
1687
|
return new Proxy({}, {
|
|
1599
1688
|
get: (target, name) => {
|
|
1600
1689
|
if (!(name in cache))
|
|
1601
|
-
cache[name] = new FreeVar(name,
|
|
1690
|
+
cache[name] = new FreeVar(name, scope);
|
|
1602
1691
|
return cache[name];
|
|
1603
1692
|
}
|
|
1604
1693
|
});
|
|
@@ -1620,40 +1709,23 @@ var require_quest = __commonJS({
|
|
|
1620
1709
|
var { Expr, FreeVar, Alias, Lambda } = SKI2.classes;
|
|
1621
1710
|
var Quest2 = class {
|
|
1622
1711
|
/**
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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 = {}) {
|
|
1712
|
+
* @description A combinator problem with a set of test cases for the proposed solution.
|
|
1713
|
+
* @param {QuestSpec} options
|
|
1714
|
+
* @example const quest = new Quest({
|
|
1715
|
+
* input: 'identity',
|
|
1716
|
+
* cases: [
|
|
1717
|
+
* ['identity x', 'x'],
|
|
1718
|
+
* ],
|
|
1719
|
+
* allow: 'SK',
|
|
1720
|
+
* intro: 'Find a combinator that behaves like the identity function.',
|
|
1721
|
+
* });
|
|
1722
|
+
* quest.check('S K K'); // { pass: true, details: [...], ... }
|
|
1723
|
+
* quest.check('K S'); // { pass: false, details: [...], ... }
|
|
1724
|
+
* quest.check('K x'); // fail! internal variable x is not equal to free variable x,
|
|
1725
|
+
* // despite having the same name.
|
|
1726
|
+
* quest.check('I'); // fail! I not in the allowed list.
|
|
1727
|
+
*/
|
|
1728
|
+
constructor(options) {
|
|
1657
1729
|
const { input, cases, allow, numbers, lambdas, engine, engineFull, ...meta } = options;
|
|
1658
1730
|
const env = options.env ?? options.vars;
|
|
1659
1731
|
this.engine = engine ?? new SKI2();
|
|
@@ -1746,7 +1818,11 @@ var require_quest = __commonJS({
|
|
|
1746
1818
|
numbers: spec.numbers ?? this.restrict.numbers,
|
|
1747
1819
|
lambdas: spec.lambdas ?? this.restrict.lambdas
|
|
1748
1820
|
});
|
|
1749
|
-
|
|
1821
|
+
const arsenal = { ...this.engine.getTerms(), ...jar };
|
|
1822
|
+
weight += impl.fold(0, (a, e) => {
|
|
1823
|
+
if (e instanceof SKI2.classes.Named && arsenal[e.name] === e)
|
|
1824
|
+
return SKI2.control.prune(a + 1);
|
|
1825
|
+
});
|
|
1750
1826
|
const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { terminal: true, canonize: false });
|
|
1751
1827
|
jar[spec.name] = expr;
|
|
1752
1828
|
prepared.push(expr);
|
|
@@ -1779,6 +1855,62 @@ var require_quest = __commonJS({
|
|
|
1779
1855
|
return { pass: false, details: [], exception: e, steps: 0, input };
|
|
1780
1856
|
}
|
|
1781
1857
|
}
|
|
1858
|
+
verify(options) {
|
|
1859
|
+
const findings = this.verifyMeta(options);
|
|
1860
|
+
if (options.solutions) {
|
|
1861
|
+
const solCheck = this.verifySolutions(options.solutions);
|
|
1862
|
+
if (solCheck)
|
|
1863
|
+
findings.solutions = solCheck;
|
|
1864
|
+
}
|
|
1865
|
+
if (options.seen) {
|
|
1866
|
+
if (!this.id)
|
|
1867
|
+
findings.seen = "No id in quest " + (this.name ?? "(unnamed)");
|
|
1868
|
+
if (options.seen.has(this.id))
|
|
1869
|
+
findings.seen = "Duplicate quest id " + this.id;
|
|
1870
|
+
options.seen.add(this.id);
|
|
1871
|
+
}
|
|
1872
|
+
return Object.keys(findings).length ? findings : null;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* @desc Verify that solutions that are expected to pass/fail do so.
|
|
1876
|
+
* @param {SelfCheck|{[key: string]: SelfCheck}} dataset
|
|
1877
|
+
* @return {{shouldPass: {input: string[], result: QuestResult}[], shouldFail: {input: string[], result: QuestResult}[]} | null}
|
|
1878
|
+
*/
|
|
1879
|
+
verifySolutions(dataset) {
|
|
1880
|
+
if (typeof dataset === "object" && !Array.isArray(dataset?.accepted) && !Array.isArray(dataset?.rejected)) {
|
|
1881
|
+
if (!this.id || !dataset[this.id])
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
const { accepted = [], rejected = [] } = dataset[this.id] ?? dataset;
|
|
1885
|
+
const ret = { shouldPass: [], shouldFail: [] };
|
|
1886
|
+
for (const input of accepted) {
|
|
1887
|
+
const result = this.check(...input);
|
|
1888
|
+
if (!result.pass)
|
|
1889
|
+
ret.shouldPass.push({ input, result });
|
|
1890
|
+
}
|
|
1891
|
+
for (const input of rejected) {
|
|
1892
|
+
const result = this.check(...input);
|
|
1893
|
+
if (result.pass)
|
|
1894
|
+
ret.shouldFail.push({ input, result });
|
|
1895
|
+
}
|
|
1896
|
+
return ret.shouldFail.length + ret.shouldPass.length ? ret : null;
|
|
1897
|
+
}
|
|
1898
|
+
verifyMeta(options = {}) {
|
|
1899
|
+
const findings = {};
|
|
1900
|
+
for (const field of ["name", "intro"]) {
|
|
1901
|
+
const found = checkHtml(this[field]);
|
|
1902
|
+
if (found)
|
|
1903
|
+
findings[field] = found;
|
|
1904
|
+
}
|
|
1905
|
+
if (options.date) {
|
|
1906
|
+
const date = new Date(this.meta.created_at);
|
|
1907
|
+
if (isNaN(date))
|
|
1908
|
+
findings.date = "invalid date format: " + this.meta.created_at;
|
|
1909
|
+
else if (date < /* @__PURE__ */ new Date("2024-07-15") || date > /* @__PURE__ */ new Date())
|
|
1910
|
+
findings.date = "date out of range: " + this.meta.created_at;
|
|
1911
|
+
}
|
|
1912
|
+
return findings;
|
|
1913
|
+
}
|
|
1782
1914
|
/**
|
|
1783
1915
|
*
|
|
1784
1916
|
* @return {TestCase[]}
|
|
@@ -1819,10 +1951,10 @@ var require_quest = __commonJS({
|
|
|
1819
1951
|
/**
|
|
1820
1952
|
* @param {FreeVar[]} input
|
|
1821
1953
|
* @param {{
|
|
1822
|
-
* max
|
|
1823
|
-
* note
|
|
1824
|
-
* env
|
|
1825
|
-
* engine
|
|
1954
|
+
* max?: number,
|
|
1955
|
+
* note?: string,
|
|
1956
|
+
* env?: {string: Expr},
|
|
1957
|
+
* engine?: SKI
|
|
1826
1958
|
* }} options
|
|
1827
1959
|
* @param {[e1: string, e2: string]} terms
|
|
1828
1960
|
*/
|
|
@@ -1929,11 +2061,67 @@ var require_quest = __commonJS({
|
|
|
1929
2061
|
return expr;
|
|
1930
2062
|
}
|
|
1931
2063
|
};
|
|
2064
|
+
var Group = class {
|
|
2065
|
+
constructor(options) {
|
|
2066
|
+
this.name = options.name;
|
|
2067
|
+
this.intro = list2str(options.intro);
|
|
2068
|
+
this.id = options.id;
|
|
2069
|
+
if (options.content)
|
|
2070
|
+
this.content = options.content.map((c) => c instanceof Quest2 ? c : new Quest2(c));
|
|
2071
|
+
}
|
|
2072
|
+
verify(options) {
|
|
2073
|
+
const findings = {};
|
|
2074
|
+
const id = checkId(this.id, options.seen);
|
|
2075
|
+
if (id)
|
|
2076
|
+
findings[this.id] = id;
|
|
2077
|
+
for (const field of ["name", "intro"]) {
|
|
2078
|
+
const found = checkHtml(this[field]);
|
|
2079
|
+
if (found)
|
|
2080
|
+
findings[field] = found;
|
|
2081
|
+
}
|
|
2082
|
+
findings.content = this.content.map((q) => q.verify(options));
|
|
2083
|
+
return findings;
|
|
2084
|
+
}
|
|
2085
|
+
};
|
|
1932
2086
|
function list2str(str) {
|
|
1933
2087
|
if (str === void 0 || typeof str === "string")
|
|
1934
2088
|
return str;
|
|
1935
2089
|
return Array.isArray(str) ? str.join(" ") : "" + str;
|
|
1936
2090
|
}
|
|
2091
|
+
function checkId(id, seen) {
|
|
2092
|
+
if (id === void 0)
|
|
2093
|
+
return "missing";
|
|
2094
|
+
if (typeof id !== "string" && typeof id !== "number")
|
|
2095
|
+
return "is a " + typeof id;
|
|
2096
|
+
if (seen) {
|
|
2097
|
+
if (seen.has(id))
|
|
2098
|
+
return "duplicate id " + id;
|
|
2099
|
+
seen.add(id);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
function checkHtml(str) {
|
|
2103
|
+
if (str === void 0)
|
|
2104
|
+
return "missing";
|
|
2105
|
+
if (typeof str !== "string")
|
|
2106
|
+
return "not a string but " + typeof str;
|
|
2107
|
+
const tagStack = [];
|
|
2108
|
+
const tagRegex = /<\/?([a-z]+)(?:\s[^>]*)?>/gi;
|
|
2109
|
+
let match;
|
|
2110
|
+
while ((match = tagRegex.exec(str)) !== null) {
|
|
2111
|
+
const [fullTag, tagName] = match;
|
|
2112
|
+
if (fullTag.startsWith("</")) {
|
|
2113
|
+
if (tagStack.length === 0 || tagStack.pop() !== tagName)
|
|
2114
|
+
return `Unmatched closing tag: </${tagName}>`;
|
|
2115
|
+
} else {
|
|
2116
|
+
tagStack.push(tagName);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (tagStack.length > 0)
|
|
2120
|
+
return `Unclosed tags: ${tagStack.join(", ")}`;
|
|
2121
|
+
return null;
|
|
2122
|
+
}
|
|
2123
|
+
Quest2.Group = Group;
|
|
2124
|
+
Quest2.Case = Case;
|
|
1937
2125
|
module2.exports = { Quest: Quest2 };
|
|
1938
2126
|
}
|
|
1939
2127
|
});
|
|
@@ -1943,6 +2131,7 @@ var require_extras = __commonJS({
|
|
|
1943
2131
|
"src/extras.js"(exports2, module2) {
|
|
1944
2132
|
"use strict";
|
|
1945
2133
|
var { Expr, Alias, FreeVar } = require_expr();
|
|
2134
|
+
var { Quest: Quest2 } = require_quest();
|
|
1946
2135
|
function search(seed, options, predicate) {
|
|
1947
2136
|
const {
|
|
1948
2137
|
depth = 16,
|
|
@@ -2008,6 +2197,10 @@ var require_extras = __commonJS({
|
|
|
2008
2197
|
function deepFormat(obj, options = {}) {
|
|
2009
2198
|
if (obj instanceof Expr)
|
|
2010
2199
|
return obj.format(options);
|
|
2200
|
+
if (obj instanceof Quest2)
|
|
2201
|
+
return "Quest(" + obj.name + ")";
|
|
2202
|
+
if (obj instanceof Quest2.Case)
|
|
2203
|
+
return "Quest.Case";
|
|
2011
2204
|
if (Array.isArray(obj))
|
|
2012
2205
|
return obj.map(deepFormat);
|
|
2013
2206
|
if (typeof obj !== "object" || obj === null || obj.constructor !== Object)
|