@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/lib/parser.js CHANGED
@@ -3,10 +3,10 @@
3
3
  */
4
4
  'use strict';
5
5
 
6
- const { Tokenizer, restrict } = require('./util');
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 = this.parseLine(str, jar, options);
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 = new Alias(save, 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
- * title: string?,
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, vars, cases, allow, numbers, lambdas, subst, engine, engineFull, ...meta } = options;
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.vars = {};
73
- this.subst = Array.isArray(subst) ? subst : [subst ?? 'phi'];
97
+ this.env = {};
74
98
 
75
99
  const jar = {};
76
100
 
77
- // options.vars is a list of expressions.
78
- // we suck all free variables + all term declarations from there into this.vars
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 vars ?? []) {
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.vars[expr.name] = new Alias(expr.name, expr.impl, { terminal: true, canonize: false });
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.vars[expr.name] = expr;
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.varsFull = { ...this.vars, ...jar };
122
+ this.envFull = { ...this.env, ...jar };
101
123
  for (const term of this.input) {
102
- if (term.name in this.varsFull)
103
- throw new Error('input placeholder name is duplicated or clashes with vars: ' + term.name);
104
- this.varsFull[term.name] = term.placeholder;
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.title = meta.title;
109
- meta.descr = list2str(meta.descr);
110
- this.descr = meta.descr;
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.vars + this.restrict.allow
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 vars = Object.keys(this.vars).sort();
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 + '+' + vars.join(' '))
127
- : vars.map( s => '+' + s).join(' ');
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.vars = opt.vars ?? this.varsFull;
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.vars };
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
- * vars?: {[key:string]: Expr},
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.vars = { ...(options.vars ?? {}) }; // note: scope already contains input placeholders
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.vars, scope: this }), this.input);
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
- * vars: {string: Expr}?,
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[]} vars
401
+ * @param {FreeVar[]} env
377
402
  */
378
- constructor (expr, vars) {
403
+ constructor (expr, env) {
379
404
  this.expr = expr;
380
- this.vars = vars;
405
+ this.env = env;
381
406
  }
382
407
 
383
408
  apply (list) {
384
- if (list.length !== this.vars.length)
385
- throw new Error('Subst: expected ' + this.vars.length + ' terms, got ' + list.length);
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.vars.length; i++)
389
- expr = expr.subst(this.vars[i], list[i]) ?? expr;
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.0.0",
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 lib --ext .js,.ts",
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
@@ -1,7 +1,7 @@
1
1
  declare const _exports: {
2
2
  Quest: typeof quest.Quest;
3
- SKI: typeof ski.SKI;
3
+ SKI: typeof main.SKI;
4
4
  };
5
5
  export = _exports;
6
6
  import quest = require("./lib/quest");
7
- import ski = require("./lib/parser");
7
+ import main = require("./lib/parser");
@@ -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 rought estimate of the complexity of the term
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: number?, maxArgs: number?}} options
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: number | null;
70
- maxArgs: number | null;
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
- _infer(options: any, preArgs?: any[], steps?: number): any;
83
- _aslist(): this[];
84
- _firstVar(): boolean;
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: number?}} options
168
+ * @param {{max?: number}} [options]
126
169
  * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
127
170
  */
128
171
  toSKI(options?: {
129
- max: number | null;
172
+ max?: number;
130
173
  }): IterableIterator<{
131
174
  final: boolean;
132
175
  expr: Expr;
133
176
  steps: number;
134
177
  }>;
135
- _rski(options: any): this;
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
- _unspaced(arg: any): boolean;
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
- _format(options: any, nargs: any): void;
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, ...args) directly, use fun.apply(...args) instead.
379
+ * Never ever use new App(fun, arg) directly, use fun.apply(...args) instead.
316
380
  * @param {Expr} fun
317
- * @param {Expr} args
381
+ * @param {Expr} arg
318
382
  */
319
- constructor(fun: Expr, ...args: Expr);
320
- arg: any;
321
- fun: any;
383
+ constructor(fun: Expr, arg: Expr);
384
+ arg: Expr;
385
+ fun: Expr;
322
386
  final: boolean;
323
- arity: any;
324
- weight(): any;
325
- _firstVar(): any;
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): 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): any;
338
- split(): any[];
339
- _aslist(): any[];
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): 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): 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 class Named extends Expr {
495
- /**
496
- * @desc An abstract class representing a term named 'name'.
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";