@dallaylaen/ski-interpreter 2.0.0 → 2.1.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 +20 -0
- package/bin/ski.js +96 -97
- package/index.js +14 -6
- package/lib/expr.js +168 -67
- package/lib/extras.js +140 -0
- package/lib/internal.js +105 -0
- package/lib/parser.js +20 -4
- package/lib/quest.js +73 -43
- package/package.json +2 -2
- package/types/index.d.ts +2 -2
- package/types/lib/expr.d.ts +111 -38
- package/types/lib/extras.d.ts +57 -0
- package/types/lib/internal.d.ts +52 -0
- package/types/lib/parser.d.ts +5 -0
- package/types/lib/quest.d.ts +56 -39
- package/lib/util.js +0 -57
- package/types/lib/util.d.ts +0 -11
package/lib/parser.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
const { Tokenizer, restrict } = require('./
|
|
6
|
+
const { Tokenizer, restrict } = require('./internal');
|
|
7
7
|
const classes = require('./expr');
|
|
8
8
|
|
|
9
|
-
const { Expr, Native, Alias, FreeVar, Lambda, Church } = classes;
|
|
9
|
+
const { Expr, Named, Native, Alias, FreeVar, Lambda, Church } = classes;
|
|
10
10
|
const { native, declare } = Expr;
|
|
11
11
|
|
|
12
12
|
class Empty extends Expr {
|
|
@@ -271,20 +271,29 @@ class SKI {
|
|
|
271
271
|
let expr = new Empty();
|
|
272
272
|
for (const item of lines) {
|
|
273
273
|
const [_, save, str] = item.match(/^(?:\s*([A-Z]|[a-z][a-z_0-9]*)\s*=\s*)?(.*)$/s);
|
|
274
|
+
|
|
274
275
|
if (expr instanceof Alias)
|
|
275
276
|
expr.outdated = true;
|
|
276
|
-
expr =
|
|
277
|
+
expr = (str === '' && save !== undefined)
|
|
278
|
+
? new FreeVar(save, options.scope)
|
|
279
|
+
: this.parseLine(str, jar, options);
|
|
277
280
|
|
|
278
281
|
if (save !== undefined) {
|
|
279
282
|
if (jar[save] !== undefined)
|
|
280
283
|
throw new Error('Attempt to redefine a known term: ' + save);
|
|
281
|
-
expr =
|
|
284
|
+
expr = maybeAlias(save, expr);
|
|
282
285
|
jar[save] = expr;
|
|
283
286
|
}
|
|
284
287
|
|
|
285
288
|
// console.log('parsed line:', item, '; got:', expr,'; jar now: ', jar);
|
|
286
289
|
}
|
|
287
290
|
|
|
291
|
+
expr.context = {
|
|
292
|
+
env: jar, // also contains pre-parsed terms
|
|
293
|
+
scope: options.scope,
|
|
294
|
+
src: source,
|
|
295
|
+
parser: this,
|
|
296
|
+
};
|
|
288
297
|
return expr;
|
|
289
298
|
}
|
|
290
299
|
|
|
@@ -368,6 +377,12 @@ class SKI {
|
|
|
368
377
|
}
|
|
369
378
|
}
|
|
370
379
|
|
|
380
|
+
function maybeAlias (name, expr) {
|
|
381
|
+
if (expr instanceof Named && expr.name === name)
|
|
382
|
+
return expr;
|
|
383
|
+
return new Alias(name, expr);
|
|
384
|
+
}
|
|
385
|
+
|
|
371
386
|
// Create shortcuts for common terms
|
|
372
387
|
|
|
373
388
|
SKI.classes = classes;
|
|
@@ -414,5 +429,6 @@ for (const name in native)
|
|
|
414
429
|
SKI[name] = native[name];
|
|
415
430
|
SKI.native = native;
|
|
416
431
|
SKI.declare = declare;
|
|
432
|
+
SKI.control = Expr.control;
|
|
417
433
|
|
|
418
434
|
module.exports = { SKI };
|
package/lib/quest.js
CHANGED
|
@@ -35,6 +35,10 @@ const { Expr, FreeVar, Alias, Lambda } = SKI.classes;
|
|
|
35
35
|
* } TestCase
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {string | {name: string, fancy?: string, allow?: string, numbers?: boolean, lambdas?: boolean}} InputSpec
|
|
40
|
+
*/
|
|
41
|
+
|
|
38
42
|
/**
|
|
39
43
|
* @typedef {{
|
|
40
44
|
* pass: boolean,
|
|
@@ -51,40 +55,60 @@ class Quest {
|
|
|
51
55
|
/**
|
|
52
56
|
* @description A combinator problem with a set of test cases for the proposed solution.
|
|
53
57
|
* @param {{
|
|
54
|
-
*
|
|
55
|
-
* descr: string?,
|
|
56
|
-
* subst: string?,
|
|
57
|
-
* allow: string?,
|
|
58
|
-
* numbers: boolean?,
|
|
59
|
-
* vars: string[]?,
|
|
60
|
-
* engine: SKI?,
|
|
61
|
-
* engineFull: SKI?,
|
|
58
|
+
* input: InputSpec | InputSpec[],
|
|
62
59
|
* cases: TestCase[],
|
|
60
|
+
*
|
|
61
|
+
* // the rest is optional
|
|
62
|
+
|
|
63
|
+
* allow?: string,
|
|
64
|
+
* numbers?: boolean,
|
|
65
|
+
* env?: string[],
|
|
66
|
+
* engine?: SKI,
|
|
67
|
+
* engineFull?: SKI,
|
|
68
|
+
*
|
|
69
|
+
* // metadata, also any fields not listed here will go to quest.meta.???
|
|
70
|
+
* id?: string|number,
|
|
71
|
+
* name?: string,
|
|
72
|
+
* intro?: string|string[], // multiple strings will be concatenated with spaces
|
|
63
73
|
* }} options
|
|
74
|
+
*
|
|
75
|
+
* @example const quest = new Quest({
|
|
76
|
+
* input: 'identity',
|
|
77
|
+
* cases: [
|
|
78
|
+
* ['identity x', 'x'],
|
|
79
|
+
* ],
|
|
80
|
+
* allow: 'SK',
|
|
81
|
+
* intro: 'Find a combinator that behaves like the identity function.',
|
|
82
|
+
* });
|
|
83
|
+
* quest.check('S K K'); // { pass: true, details: [...], ... }
|
|
84
|
+
* quest.check('K S'); // { pass: false, details: [...], ... }
|
|
85
|
+
* quest.check('K x'); // fail! internal variable x is not equal to free variable x,
|
|
86
|
+
* // despite having the same name.
|
|
87
|
+
* quest.check('I'); // fail! I not in the allowed list.
|
|
64
88
|
*/
|
|
65
89
|
constructor (options = {}) {
|
|
66
|
-
const { input,
|
|
90
|
+
const { input, cases, allow, numbers, lambdas, engine, engineFull, ...meta } = options;
|
|
91
|
+
const env = options.env ?? options.vars; // backwards compatibility
|
|
67
92
|
|
|
68
93
|
//
|
|
69
94
|
this.engine = engine ?? new SKI();
|
|
70
95
|
this.engineFull = engineFull ?? new SKI();
|
|
71
96
|
this.restrict = { allow, numbers: numbers ?? false, lambdas: lambdas ?? false };
|
|
72
|
-
this.
|
|
73
|
-
this.subst = Array.isArray(subst) ? subst : [subst ?? 'phi'];
|
|
97
|
+
this.env = {};
|
|
74
98
|
|
|
75
99
|
const jar = {};
|
|
76
100
|
|
|
77
|
-
//
|
|
78
|
-
// we suck all free variables + all term declarations from there into this.
|
|
101
|
+
// option.env is a list of expressions.
|
|
102
|
+
// we suck all free variables + all term declarations from there into this.env
|
|
79
103
|
// to feed it later to every case's parser.
|
|
80
|
-
for (const term of
|
|
104
|
+
for (const term of env ?? []) {
|
|
81
105
|
const expr = this.engineFull.parse(term, { env: jar, scope: this });
|
|
82
106
|
if (expr instanceof SKI.classes.Alias)
|
|
83
|
-
this.
|
|
107
|
+
this.env[expr.name] = new Alias(expr.name, expr.impl, { terminal: true, canonize: false });
|
|
84
108
|
// Canonized aliases won't expand with insufficient arguments,
|
|
85
109
|
// causing correct solutions to fail, so alas...
|
|
86
110
|
else if (expr instanceof SKI.classes.FreeVar)
|
|
87
|
-
this.
|
|
111
|
+
this.env[expr.name] = expr;
|
|
88
112
|
else
|
|
89
113
|
throw new Error('Unsupported given variable type: ' + term);
|
|
90
114
|
}
|
|
@@ -94,20 +118,21 @@ class Quest {
|
|
|
94
118
|
this.addInput(term);
|
|
95
119
|
if (!this.input.length)
|
|
96
120
|
throw new Error('Quest needs at least one input placeholder');
|
|
97
|
-
if (subst)
|
|
98
|
-
this.input[0].fancy = this.subst[0];
|
|
99
121
|
|
|
100
|
-
this.
|
|
122
|
+
this.envFull = { ...this.env, ...jar };
|
|
101
123
|
for (const term of this.input) {
|
|
102
|
-
if (term.name in this.
|
|
103
|
-
throw new Error('input placeholder name is duplicated or clashes with
|
|
104
|
-
this.
|
|
124
|
+
if (term.name in this.envFull)
|
|
125
|
+
throw new Error('input placeholder name is duplicated or clashes with env: ' + term.name);
|
|
126
|
+
this.envFull[term.name] = term.placeholder;
|
|
105
127
|
}
|
|
106
128
|
|
|
129
|
+
// NOTE meta is a local variable, can mutate
|
|
130
|
+
// NOTE title/descr are old name/intro respectively, kept for backwards compatibility
|
|
107
131
|
this.cases = [];
|
|
108
|
-
this.
|
|
109
|
-
meta.
|
|
110
|
-
this.
|
|
132
|
+
this.name = meta.name ?? meta.title;
|
|
133
|
+
meta.intro = list2str(meta.intro ?? meta.descr);
|
|
134
|
+
this.intro = meta.intro;
|
|
135
|
+
this.id = meta.id;
|
|
111
136
|
this.meta = meta;
|
|
112
137
|
|
|
113
138
|
for (const c of cases ?? [])
|
|
@@ -115,16 +140,16 @@ class Quest {
|
|
|
115
140
|
}
|
|
116
141
|
|
|
117
142
|
/**
|
|
118
|
-
* Display allowed terms based on what engine thinks of this.
|
|
143
|
+
* Display allowed terms based on what engine thinks of this.env + this.restrict.allow
|
|
119
144
|
* @return {string}
|
|
120
145
|
*/
|
|
121
146
|
allowed () {
|
|
122
147
|
const allow = this.restrict.allow ?? '';
|
|
123
|
-
const
|
|
148
|
+
const env = Object.keys(this.env).sort();
|
|
124
149
|
// In case vars are present and restrictions aren't, don't clutter the output with all the known terms
|
|
125
150
|
return allow
|
|
126
|
-
? this.engine.showRestrict(allow + '+' +
|
|
127
|
-
:
|
|
151
|
+
? this.engine.showRestrict(allow + '+' + env.join(' '))
|
|
152
|
+
: env.map( s => '+' + s).join(' ');
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
addInput (term) {
|
|
@@ -152,7 +177,7 @@ class Quest {
|
|
|
152
177
|
opt = { ...opt };
|
|
153
178
|
|
|
154
179
|
opt.engine = opt.engine ?? this.engineFull;
|
|
155
|
-
opt.
|
|
180
|
+
opt.env = opt.env ?? this.envFull;
|
|
156
181
|
|
|
157
182
|
const input = this.input.map( t => t.placeholder );
|
|
158
183
|
this.cases.push(
|
|
@@ -174,7 +199,7 @@ class Quest {
|
|
|
174
199
|
|
|
175
200
|
let weight = 0;
|
|
176
201
|
const prepared = [];
|
|
177
|
-
const jar = { ...this.
|
|
202
|
+
const jar = { ...this.env };
|
|
178
203
|
for (let i = 0; i < input.length; i++) {
|
|
179
204
|
const spec = this.input[i];
|
|
180
205
|
const impl = this.engine.parse(input[i], {
|
|
@@ -235,20 +260,20 @@ class Case {
|
|
|
235
260
|
* @param {{
|
|
236
261
|
* max?: number,
|
|
237
262
|
* note?: string,
|
|
238
|
-
*
|
|
263
|
+
* env?: {[key:string]: Expr},
|
|
239
264
|
* engine: SKI
|
|
240
265
|
* }} options
|
|
241
266
|
*/
|
|
242
267
|
constructor (input, options) {
|
|
243
268
|
this.max = options.max ?? 1000;
|
|
244
269
|
this.note = options.note;
|
|
245
|
-
this.
|
|
270
|
+
this.env = { ...(options.env ?? {}) }; // note: env already contains input placeholders
|
|
246
271
|
this.input = input;
|
|
247
272
|
this.engine = options.engine;
|
|
248
273
|
}
|
|
249
274
|
|
|
250
275
|
parse (src) {
|
|
251
|
-
return new Subst(this.engine.parse(src, { env: this.
|
|
276
|
+
return new Subst(this.engine.parse(src, { env: this.env, scope: this }), this.input);
|
|
252
277
|
}
|
|
253
278
|
|
|
254
279
|
/**
|
|
@@ -266,7 +291,7 @@ class ExprCase extends Case {
|
|
|
266
291
|
* @param {{
|
|
267
292
|
* max: number?,
|
|
268
293
|
* note: string?,
|
|
269
|
-
*
|
|
294
|
+
* env: {string: Expr}?,
|
|
270
295
|
* engine: SKI?
|
|
271
296
|
* }} options
|
|
272
297
|
* @param {[e1: string, e2: string]} terms
|
|
@@ -373,27 +398,32 @@ class Subst {
|
|
|
373
398
|
/**
|
|
374
399
|
* @descr A placeholder object with exactly n free variables to be substituted later.
|
|
375
400
|
* @param {Expr} expr
|
|
376
|
-
* @param {FreeVar[]}
|
|
401
|
+
* @param {FreeVar[]} env
|
|
377
402
|
*/
|
|
378
|
-
constructor (expr,
|
|
403
|
+
constructor (expr, env) {
|
|
379
404
|
this.expr = expr;
|
|
380
|
-
this.
|
|
405
|
+
this.env = env;
|
|
381
406
|
}
|
|
382
407
|
|
|
383
408
|
apply (list) {
|
|
384
|
-
if (list.length !== this.
|
|
385
|
-
throw new Error('Subst: expected ' + this.
|
|
409
|
+
if (list.length !== this.env.length)
|
|
410
|
+
throw new Error('Subst: expected ' + this.env.length + ' terms, got ' + list.length);
|
|
386
411
|
|
|
387
412
|
let expr = this.expr;
|
|
388
|
-
for (let i = 0; i < this.
|
|
389
|
-
expr = expr.subst(this.
|
|
413
|
+
for (let i = 0; i < this.env.length; i++)
|
|
414
|
+
expr = expr.subst(this.env[i], list[i]) ?? expr;
|
|
390
415
|
|
|
391
416
|
return expr;
|
|
392
417
|
}
|
|
393
418
|
}
|
|
394
419
|
|
|
420
|
+
/**
|
|
421
|
+
* @desc Concatenate long strings represented as arrays, or just pass along if already string or undefined.
|
|
422
|
+
* @param {string|Array<string>|undefined} str
|
|
423
|
+
* @returns {string|undefined}
|
|
424
|
+
*/
|
|
395
425
|
function list2str (str) {
|
|
396
|
-
if (str === undefined)
|
|
426
|
+
if (str === undefined || typeof str === 'string')
|
|
397
427
|
return str;
|
|
398
428
|
return Array.isArray(str) ? str.join(' ') : '' + str;
|
|
399
429
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dallaylaen/ski-interpreter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Simple Kombinator Interpreter - a combinatory logic & lambda calculus parser and interpreter. Supports SKI, BCKW, Church numerals, and setting up assertions ('quests') involving all of the above.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"combinatory logic",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"ski": "bin/ski.js"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"lint": "npx eslint
|
|
18
|
+
"lint": "npx eslint --ext .js,.ts lib test index.js bin",
|
|
19
19
|
"types": "npx -p typescript tsc index.js --declaration --allowJs --emitDeclarationOnly --outDir types",
|
|
20
20
|
"test": "npx nyc mocha",
|
|
21
21
|
"minify": "npx esbuild --bundle ./index.js --outfile=docs/build/js/ski-interpreter.min.js --minify --sourcemap",
|
package/types/index.d.ts
CHANGED
package/types/lib/expr.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
export type ActionWrapper<T> = T | {
|
|
2
|
+
value: T | null;
|
|
3
|
+
action: string;
|
|
4
|
+
} | null;
|
|
1
5
|
export type Partial = Expr | ((arg0: Expr) => Partial);
|
|
2
6
|
/**
|
|
3
7
|
* @typedef {Expr | function(Expr): Partial} Partial
|
|
@@ -36,7 +40,28 @@ export class Expr {
|
|
|
36
40
|
*/
|
|
37
41
|
any(predicate: (e: Expr) => boolean): boolean;
|
|
38
42
|
/**
|
|
39
|
-
* @desc
|
|
43
|
+
* @desc Fold the expression into a single value by recursively applying combine() to its subterms.
|
|
44
|
+
* Nodes are traversed in leftmost-outermost order, i.e. the same order as reduction steps are taken.
|
|
45
|
+
*
|
|
46
|
+
* null or undefined return value from combine() means "keep current value and descend further".
|
|
47
|
+
*
|
|
48
|
+
* SKI.control provides primitives to control the folding flow:
|
|
49
|
+
* - SKI.control.prune(value) means "use value and don't descend further into this branch";
|
|
50
|
+
* - SKI.control.stop(value) means "stop folding immediately and return value".
|
|
51
|
+
* - SKI.control.descend(value) is the default behavior, meaning "use value and descend further".
|
|
52
|
+
*
|
|
53
|
+
* This method is experimental and may change in the future.
|
|
54
|
+
*
|
|
55
|
+
* @experimental
|
|
56
|
+
* @template T
|
|
57
|
+
* @param {T} initial
|
|
58
|
+
* @param {(acc: T, expr: Expr) => ActionWrapper<T>} combine
|
|
59
|
+
* @returns {T}
|
|
60
|
+
*/
|
|
61
|
+
fold<T>(initial: T, combine: (acc: T, expr: Expr) => ActionWrapper<T>): T;
|
|
62
|
+
_fold(initial: any, combine: any): any;
|
|
63
|
+
/**
|
|
64
|
+
* @desc rough estimate of the complexity of the term
|
|
40
65
|
* @return {number}
|
|
41
66
|
*/
|
|
42
67
|
weight(): number;
|
|
@@ -52,7 +77,7 @@ export class Expr {
|
|
|
52
77
|
*
|
|
53
78
|
* Use toLambda() if you want to get a lambda term in any case.
|
|
54
79
|
*
|
|
55
|
-
* @param {{max
|
|
80
|
+
* @param {{max?: number, maxArgs?: number}} options
|
|
56
81
|
* @return {{
|
|
57
82
|
* normal: boolean,
|
|
58
83
|
* steps: number,
|
|
@@ -66,8 +91,8 @@ export class Expr {
|
|
|
66
91
|
* }}
|
|
67
92
|
*/
|
|
68
93
|
infer(options?: {
|
|
69
|
-
max
|
|
70
|
-
maxArgs
|
|
94
|
+
max?: number;
|
|
95
|
+
maxArgs?: number;
|
|
71
96
|
}): {
|
|
72
97
|
normal: boolean;
|
|
73
98
|
steps: number;
|
|
@@ -79,9 +104,27 @@ export class Expr {
|
|
|
79
104
|
skip?: Set<number>;
|
|
80
105
|
dup?: Set<number>;
|
|
81
106
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
107
|
+
/**
|
|
108
|
+
*
|
|
109
|
+
* @param {{max: number, maxArgs: number, index: number}} options
|
|
110
|
+
* @param {FreeVar[]} preArgs
|
|
111
|
+
* @param {number} steps
|
|
112
|
+
* @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}}
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
private _infer;
|
|
116
|
+
/**
|
|
117
|
+
* @desc Expand an expression into a list of terms
|
|
118
|
+
* that give the initial expression when applied from left to right:
|
|
119
|
+
* ((a, b), (c, d)) => [a, b, (c, d)]
|
|
120
|
+
*
|
|
121
|
+
* This can be thought of as an opposite of apply:
|
|
122
|
+
* fun.apply(...arg).unroll() is exactly [fun, ...args]
|
|
123
|
+
* (even if ...arg is in fact empty).
|
|
124
|
+
*
|
|
125
|
+
* @returns {Expr[]}
|
|
126
|
+
*/
|
|
127
|
+
unroll(): Expr[];
|
|
85
128
|
/**
|
|
86
129
|
* @desc Returns a series of lambda terms equivalent to the given expression,
|
|
87
130
|
* up to the provided computation steps limit,
|
|
@@ -122,17 +165,23 @@ export class Expr {
|
|
|
122
165
|
*
|
|
123
166
|
* See also Expr.walk() and Expr.toLambda().
|
|
124
167
|
*
|
|
125
|
-
* @param {{max
|
|
168
|
+
* @param {{max?: number}} [options]
|
|
126
169
|
* @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
|
|
127
170
|
*/
|
|
128
171
|
toSKI(options?: {
|
|
129
|
-
max
|
|
172
|
+
max?: number;
|
|
130
173
|
}): IterableIterator<{
|
|
131
174
|
final: boolean;
|
|
132
175
|
expr: Expr;
|
|
133
176
|
steps: number;
|
|
134
177
|
}>;
|
|
135
|
-
|
|
178
|
+
/**
|
|
179
|
+
* @desc Internal method for toSKI, which performs one step of the conversion.
|
|
180
|
+
* @param {{max: number, steps: number}} options
|
|
181
|
+
* @returns {Expr}
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
private _rski;
|
|
136
185
|
/**
|
|
137
186
|
* Replace all instances of plug in the expression with value and return the resulting expression,
|
|
138
187
|
* or null if no changes could be made.
|
|
@@ -256,7 +305,13 @@ export class Expr {
|
|
|
256
305
|
* @return {boolean}
|
|
257
306
|
*/
|
|
258
307
|
_braced(first?: boolean): boolean;
|
|
259
|
-
|
|
308
|
+
/**
|
|
309
|
+
* @desc Whether the expression can be printed without a space when followed by arg.
|
|
310
|
+
* @param {Expr} arg
|
|
311
|
+
* @returns {boolean}
|
|
312
|
+
* @private
|
|
313
|
+
*/
|
|
314
|
+
private _unspaced;
|
|
260
315
|
/**
|
|
261
316
|
* @desc Stringify the expression with fancy formatting options.
|
|
262
317
|
* Said options mostly include wrappers around various constructs in form of ['(', ')'],
|
|
@@ -302,31 +357,38 @@ export class Expr {
|
|
|
302
357
|
[x: string]: Expr;
|
|
303
358
|
};
|
|
304
359
|
}): string;
|
|
305
|
-
|
|
360
|
+
/**
|
|
361
|
+
* @desc Internal method for format(), which performs the actual formatting.
|
|
362
|
+
* @param {Object} options
|
|
363
|
+
* @param {number} nargs
|
|
364
|
+
* @returns {string}
|
|
365
|
+
* @private
|
|
366
|
+
*/
|
|
367
|
+
private _format;
|
|
306
368
|
_declare(output: any, inventory: any, seen: any): void;
|
|
369
|
+
toJSON(): string;
|
|
307
370
|
}
|
|
308
371
|
export namespace Expr {
|
|
309
372
|
export { declare };
|
|
310
373
|
export { native };
|
|
374
|
+
export { control };
|
|
311
375
|
}
|
|
312
376
|
export class App extends Expr {
|
|
313
377
|
/**
|
|
314
378
|
* @desc Application of fun() to args.
|
|
315
|
-
* Never ever use new App(fun,
|
|
379
|
+
* Never ever use new App(fun, arg) directly, use fun.apply(...args) instead.
|
|
316
380
|
* @param {Expr} fun
|
|
317
|
-
* @param {Expr}
|
|
381
|
+
* @param {Expr} arg
|
|
318
382
|
*/
|
|
319
|
-
constructor(fun: Expr,
|
|
320
|
-
arg:
|
|
321
|
-
fun:
|
|
383
|
+
constructor(fun: Expr, arg: Expr);
|
|
384
|
+
arg: Expr;
|
|
385
|
+
fun: Expr;
|
|
322
386
|
final: boolean;
|
|
323
|
-
arity:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
expand(): any;
|
|
327
|
-
traverse(change: any): any;
|
|
387
|
+
arity: number;
|
|
388
|
+
_infer(options: any, preArgs?: any[], steps?: number): any;
|
|
389
|
+
traverse(change: any): Expr;
|
|
328
390
|
any(predicate: any): any;
|
|
329
|
-
subst(search: any, replace: any):
|
|
391
|
+
subst(search: any, replace: any): Expr;
|
|
330
392
|
/**
|
|
331
393
|
* @return {{expr: Expr, steps: number}}
|
|
332
394
|
*/
|
|
@@ -334,13 +396,27 @@ export class App extends Expr {
|
|
|
334
396
|
expr: Expr;
|
|
335
397
|
steps: number;
|
|
336
398
|
};
|
|
337
|
-
invoke(arg: any):
|
|
338
|
-
|
|
339
|
-
|
|
399
|
+
invoke(arg: any): Partial;
|
|
400
|
+
/**
|
|
401
|
+
* @desc Convert the expression to SKI combinatory logic
|
|
402
|
+
* @return {Expr}
|
|
403
|
+
*/
|
|
404
|
+
_rski(options: any): Expr;
|
|
340
405
|
diff(other: any, swap?: boolean): string;
|
|
341
406
|
_braced(first: any): boolean;
|
|
407
|
+
_format(options: any, nargs: any): string;
|
|
408
|
+
_unspaced(arg: any): boolean;
|
|
409
|
+
}
|
|
410
|
+
export class Named extends Expr {
|
|
411
|
+
/**
|
|
412
|
+
* @desc An abstract class representing a term named 'name'.
|
|
413
|
+
*
|
|
414
|
+
* @param {String} name
|
|
415
|
+
*/
|
|
416
|
+
constructor(name: string);
|
|
417
|
+
name: string;
|
|
418
|
+
_unspaced(arg: any): boolean;
|
|
342
419
|
_format(options: any, nargs: any): any;
|
|
343
|
-
_unspaced(arg: any): any;
|
|
344
420
|
}
|
|
345
421
|
export class FreeVar extends Named {
|
|
346
422
|
/**
|
|
@@ -384,6 +460,7 @@ export class Lambda extends Expr {
|
|
|
384
460
|
arg: FreeVar;
|
|
385
461
|
impl: Expr;
|
|
386
462
|
arity: number;
|
|
463
|
+
_infer(options: any, preArgs?: any[], steps?: number): any;
|
|
387
464
|
invoke(arg: any): Expr;
|
|
388
465
|
traverse(change: any): Expr | Lambda;
|
|
389
466
|
any(predicate: any): any;
|
|
@@ -391,7 +468,7 @@ export class Lambda extends Expr {
|
|
|
391
468
|
expand(): Lambda;
|
|
392
469
|
_rski(options: any): any;
|
|
393
470
|
diff(other: any, swap?: boolean): string;
|
|
394
|
-
_format(options: any, nargs: any):
|
|
471
|
+
_format(options: any, nargs: any): string;
|
|
395
472
|
_braced(first: any): boolean;
|
|
396
473
|
}
|
|
397
474
|
export class Native extends Named {
|
|
@@ -420,7 +497,7 @@ export class Native extends Named {
|
|
|
420
497
|
invoke: Partial;
|
|
421
498
|
arity: any;
|
|
422
499
|
note: any;
|
|
423
|
-
_rski(options: any):
|
|
500
|
+
_rski(options: any): Expr | this;
|
|
424
501
|
}
|
|
425
502
|
export class Alias extends Named {
|
|
426
503
|
/**
|
|
@@ -456,6 +533,7 @@ export class Alias extends Named {
|
|
|
456
533
|
traverse(change: any): any;
|
|
457
534
|
any(predicate: any): any;
|
|
458
535
|
subst(search: any, replace: any): any;
|
|
536
|
+
_infer(options: any, preArgs?: any[], steps?: number): any;
|
|
459
537
|
/**
|
|
460
538
|
*
|
|
461
539
|
* @return {{expr: Expr, steps: number}}
|
|
@@ -491,14 +569,9 @@ declare function declare(inventory: Expr[]): string[];
|
|
|
491
569
|
declare const native: {
|
|
492
570
|
[key: string]: Native;
|
|
493
571
|
};
|
|
494
|
-
declare
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* @param {String} name
|
|
499
|
-
*/
|
|
500
|
-
constructor(name: string);
|
|
501
|
-
name: string;
|
|
502
|
-
_format(options: any, nargs: any): any;
|
|
572
|
+
declare namespace control {
|
|
573
|
+
let descend: (arg0: any) => any;
|
|
574
|
+
let prune: (arg0: any) => any;
|
|
575
|
+
let stop: (arg0: any) => any;
|
|
503
576
|
}
|
|
504
577
|
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @desc Extra utilities that do not belong in the core.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @experimental
|
|
6
|
+
* @desc Look for an expression that matches the predicate,
|
|
7
|
+
* starting with the seed and applying the terms to one another.
|
|
8
|
+
*
|
|
9
|
+
* A predicate returning 0 (or nothing) means "keep looking",
|
|
10
|
+
* a positive number stands for "found",
|
|
11
|
+
* and a negative means "discard this term from further applications".
|
|
12
|
+
*
|
|
13
|
+
* The order of search is from shortest to longest expressions.
|
|
14
|
+
*
|
|
15
|
+
* @param {Expr[]} seed
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {number} [options.depth] - maximum generation to search for
|
|
18
|
+
* @param {number} [options.tries] - maximum number of tries before giving up
|
|
19
|
+
* @param {boolean} [options.infer] - whether to call infer(), default true.
|
|
20
|
+
* @param {number} [options.maxArgs] - arguments in infer()
|
|
21
|
+
* @param {number} [options.max] - step limit in infer()
|
|
22
|
+
* @param {boolean} [options.noskip] - prevents skipping equivalent terms. Always true if infer is false.
|
|
23
|
+
* @param {boolean} [retain] - if true. also add the whole cache to returned value
|
|
24
|
+
* @param {({gen: number, total: number, probed: number, step: boolean}) => void} [options.progress]
|
|
25
|
+
* @param {number} [options.progressInterval] - minimum number of tries between calls to options.progress, default 1000.
|
|
26
|
+
* @param {(e: Expr, props: {}) => number?} predicate
|
|
27
|
+
* @return {{expr?: Expr, total: number, probed: number, gen: number, cache?: Expr[][]}}
|
|
28
|
+
*/
|
|
29
|
+
export function search(seed: Expr[], options: {
|
|
30
|
+
depth?: number;
|
|
31
|
+
tries?: number;
|
|
32
|
+
infer?: boolean;
|
|
33
|
+
maxArgs?: number;
|
|
34
|
+
max?: number;
|
|
35
|
+
noskip?: boolean;
|
|
36
|
+
}, predicate: (e: Expr, props: {}) => number | null): {
|
|
37
|
+
expr?: Expr;
|
|
38
|
+
total: number;
|
|
39
|
+
probed: number;
|
|
40
|
+
gen: number;
|
|
41
|
+
cache?: Expr[][];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* @desc Recursively replace all instances of Expr in a data structure with
|
|
45
|
+
* respective string representation using the format() options.
|
|
46
|
+
* Objects of other types and primitive values are eft as is.
|
|
47
|
+
*
|
|
48
|
+
* May be useful for debugging or diagnostic output.
|
|
49
|
+
*
|
|
50
|
+
* @experimental
|
|
51
|
+
*
|
|
52
|
+
* @param {any} obj
|
|
53
|
+
* @param {object} [options] - see Expr.format()
|
|
54
|
+
* @returns {any}
|
|
55
|
+
*/
|
|
56
|
+
export function deepFormat(obj: any, options?: object): any;
|
|
57
|
+
import { Expr } from "./expr";
|