@dallaylaen/ski-interpreter 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -11,7 +15,7 @@ export class Expr {
11
15
  */
12
16
  apply(...args: Expr): Expr;
13
17
  /**
14
- * expand all terms but don't perform any calculations
18
+ * @desc Replace all aliases in the expression with their definitions, recursively.
15
19
  * @return {Expr}
16
20
  */
17
21
  expand(): Expr;
@@ -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,43 @@ export class Expr {
302
357
  [x: string]: Expr;
303
358
  };
304
359
  }): string;
305
- _format(options: any, nargs: any): void;
306
- _declare(output: any, inventory: any, seen: 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;
368
+ /**
369
+ * @desc Convert the expression to a JSON-serializable format.
370
+ * @returns {string}
371
+ */
372
+ toJSON(): string;
307
373
  }
308
374
  export namespace Expr {
309
- export { declare };
310
375
  export { native };
376
+ export { control };
377
+ export namespace extras {
378
+ export { toposort };
379
+ }
311
380
  }
312
381
  export class App extends Expr {
313
382
  /**
314
383
  * @desc Application of fun() to args.
315
- * Never ever use new App(fun, ...args) directly, use fun.apply(...args) instead.
384
+ * Never ever use new App(fun, arg) directly, use fun.apply(...args) instead.
316
385
  * @param {Expr} fun
317
- * @param {Expr} args
386
+ * @param {Expr} arg
318
387
  */
319
- constructor(fun: Expr, ...args: Expr);
320
- arg: any;
321
- fun: any;
388
+ constructor(fun: Expr, arg: Expr);
389
+ arg: Expr;
390
+ fun: Expr;
322
391
  final: boolean;
323
- arity: any;
324
- weight(): any;
325
- _firstVar(): any;
326
- expand(): any;
327
- traverse(change: any): any;
392
+ arity: number;
393
+ _infer(options: any, preArgs?: any[], steps?: number): any;
394
+ traverse(change: any): Expr;
328
395
  any(predicate: any): any;
329
- subst(search: any, replace: any): any;
396
+ subst(search: any, replace: any): Expr;
330
397
  /**
331
398
  * @return {{expr: Expr, steps: number}}
332
399
  */
@@ -334,13 +401,27 @@ export class App extends Expr {
334
401
  expr: Expr;
335
402
  steps: number;
336
403
  };
337
- invoke(arg: any): any;
338
- split(): any[];
339
- _aslist(): any[];
404
+ invoke(arg: any): Partial;
405
+ /**
406
+ * @desc Convert the expression to SKI combinatory logic
407
+ * @return {Expr}
408
+ */
409
+ _rski(options: any): Expr;
340
410
  diff(other: any, swap?: boolean): string;
341
411
  _braced(first: any): boolean;
412
+ _format(options: any, nargs: any): string;
413
+ _unspaced(arg: any): boolean;
414
+ }
415
+ export class Named extends Expr {
416
+ /**
417
+ * @desc An abstract class representing a term named 'name'.
418
+ *
419
+ * @param {String} name
420
+ */
421
+ constructor(name: string);
422
+ name: string;
423
+ _unspaced(arg: any): boolean;
342
424
  _format(options: any, nargs: any): any;
343
- _unspaced(arg: any): any;
344
425
  }
345
426
  export class FreeVar extends Named {
346
427
  /**
@@ -384,14 +465,14 @@ export class Lambda extends Expr {
384
465
  arg: FreeVar;
385
466
  impl: Expr;
386
467
  arity: number;
468
+ _infer(options: any, preArgs?: any[], steps?: number): any;
387
469
  invoke(arg: any): Expr;
388
470
  traverse(change: any): Expr | Lambda;
389
471
  any(predicate: any): any;
390
472
  subst(search: any, replace: any): Lambda;
391
- expand(): Lambda;
392
473
  _rski(options: any): any;
393
474
  diff(other: any, swap?: boolean): string;
394
- _format(options: any, nargs: any): any;
475
+ _format(options: any, nargs: any): string;
395
476
  _braced(first: any): boolean;
396
477
  }
397
478
  export class Native extends Named {
@@ -420,7 +501,7 @@ export class Native extends Named {
420
501
  invoke: Partial;
421
502
  arity: any;
422
503
  note: any;
423
- _rski(options: any): any;
504
+ _rski(options: any): Expr | this;
424
505
  }
425
506
  export class Alias extends Named {
426
507
  /**
@@ -456,6 +537,7 @@ export class Alias extends Named {
456
537
  traverse(change: any): any;
457
538
  any(predicate: any): any;
458
539
  subst(search: any, replace: any): any;
540
+ _infer(options: any, preArgs?: any[], steps?: number): any;
459
541
  /**
460
542
  *
461
543
  * @return {{expr: Expr, steps: number}}
@@ -479,26 +561,42 @@ export class Church extends Native {
479
561
  arity: number;
480
562
  diff(other: any, swap?: boolean): string;
481
563
  }
482
- /**
483
- *
484
- * @param {Expr[]} inventory
485
- * @return {string[]}
486
- */
487
- declare function declare(inventory: Expr[]): string[];
488
564
  /**
489
565
  * @type {{[key: string]: Native}}
490
566
  */
491
567
  declare const native: {
492
568
  [key: string]: Native;
493
569
  };
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;
570
+ declare namespace control {
571
+ let descend: (arg0: any) => any;
572
+ let prune: (arg0: any) => any;
573
+ let stop: (arg0: any) => any;
503
574
  }
575
+ /**
576
+ * @desc Sort a list in such a way that dependent terms come after the (named) terms they depend on.
577
+ * If env is given, only terms listed there are taken into account.
578
+ * If env is omitted, it will be implied from the list.
579
+ * If list is omitted, it will default to values of env.
580
+ * If just one term is given instead of a list, it will be coerced into a list.
581
+ *
582
+ * No terms outside env + list may ever appear in the result.
583
+ *
584
+ * The terms in env must be named and their names must match their keys.
585
+ *
586
+ * @param {Expr|Expr[]} list
587
+ * @param {{[s:string]: Named}} env
588
+ * @returns {{list: Expr[], env: {[s:string]: Named}}}
589
+ *
590
+ * @example
591
+ * const expr = ski.parse(src);
592
+ * toposort([expr], ski.getTerms()); // returns all terms appearing in Expr in correct order
593
+ */
594
+ declare function toposort(list: Expr | Expr[], env: {
595
+ [s: string]: Named;
596
+ }): {
597
+ list: Expr[];
598
+ env: {
599
+ [s: string]: Named;
600
+ };
601
+ };
504
602
  export {};
@@ -0,0 +1,58 @@
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
+ export function declare(expr: any, env: any): string;
58
+ import { Expr } from "./expr";
@@ -0,0 +1,52 @@
1
+ export class Tokenizer {
2
+ /**
3
+ * @desc Create a tokenizer that splits strings into tokens according to the given terms.
4
+ * The terms are interpreted as regular expressions, and are sorted by length
5
+ * to ensure that longer matches are preferred over shorter ones.
6
+ * @param {...string|RegExp} terms
7
+ */
8
+ constructor(...terms: (string | RegExp)[]);
9
+ rex: RegExp;
10
+ /**
11
+ * @desc Split the given string into tokens according to the terms specified in the constructor.
12
+ * @param {string} str
13
+ * @return {string[]}
14
+ */
15
+ split(str: string): string[];
16
+ }
17
+ /**
18
+ * @desc Add ot remove tokens from a set according to a spec string.
19
+ * The spec string is a sequence of tokens, with each group optionally prefixed
20
+ * by one of the operators '=', '+', or '-'.
21
+ * The '=' operator resets the set to contain only the following token(s).
22
+ * @param {Set<string>} set
23
+ * @param {string} [spec]
24
+ * @returns {Set<string>}
25
+ */
26
+ export function restrict(set: Set<string>, spec?: string): Set<string>;
27
+ /**
28
+ * @private
29
+ * @template T
30
+ * @param {T|ActionWrapper<T>} value
31
+ * @returns {[T?, string|undefined]}
32
+ */
33
+ export function unwrap<T>(value: T | ActionWrapper<T>): [T?, string | undefined];
34
+ /**
35
+ *
36
+ * @private
37
+ * @template T
38
+ * @param {string} action
39
+ * @returns {function(T): ActionWrapper<T>}
40
+ */
41
+ export function prepareWrapper<T>(action: string): (arg0: T) => ActionWrapper<T>;
42
+ declare class ActionWrapper {
43
+ /**
44
+ * @template T
45
+ * @param {T} value
46
+ * @param {string} action
47
+ */
48
+ constructor(value: T, action: string);
49
+ value: T;
50
+ action: string;
51
+ }
52
+ export {};
@@ -86,7 +86,8 @@ export class SKI {
86
86
  [key: string]: typeof classes.Native | typeof classes.Alias;
87
87
  };
88
88
  /**
89
- * Export term declarations for use in bulkAdd().
89
+ * @desc Export term declarations for use in bulkAdd().
90
+ * Currently only Alias terms are serialized.
90
91
  * @returns {string[]}
91
92
  */
92
93
  declare(): string[];
@@ -143,7 +144,9 @@ export class SKI {
143
144
  };
144
145
  }
145
146
  export namespace SKI {
146
- export { classes };
147
+ /**
148
+ * Public static shortcuts to common functions (see also ./extras.js)
149
+ */
147
150
  /**
148
151
  * @desc Create a proxy object that generates variables on demand,
149
152
  * with names corresponding to the property accessed.
@@ -167,12 +170,16 @@ export namespace SKI {
167
170
  * @return {Church}
168
171
  */
169
172
  export function church(n: number): typeof import("./expr").Church;
173
+ export { classes };
170
174
  export { native };
171
- export { declare };
175
+ export let control: {
176
+ descend: (arg0: any) => any;
177
+ prune: (arg0: any) => any;
178
+ stop: (arg0: any) => any;
179
+ };
172
180
  }
173
181
  import classes = require("./expr");
174
182
  declare const native: {
175
183
  [key: string]: classes.Native;
176
184
  };
177
- declare const declare: (inventory: Expr[]) => string[];
178
185
  export {};
@@ -18,6 +18,13 @@ export type Capability = {
18
18
  duplicate: boolean | null;
19
19
  arity: number | null;
20
20
  };
21
+ export type InputSpec = string | {
22
+ name: string;
23
+ fancy?: string;
24
+ allow?: string;
25
+ numbers?: boolean;
26
+ lambdas?: boolean;
27
+ };
21
28
  export type QuestResult = {
22
29
  pass: boolean;
23
30
  details: CaseResult[];
@@ -58,6 +65,9 @@ export type QuestResult = {
58
65
  * | [{caps: Capability, max: number?}, string]
59
66
  * } TestCase
60
67
  */
68
+ /**
69
+ * @typedef {string | {name: string, fancy?: string, allow?: string, numbers?: boolean, lambdas?: boolean}} InputSpec
70
+ */
61
71
  /**
62
72
  * @typedef {{
63
73
  * pass: boolean,
@@ -73,48 +83,55 @@ export class Quest {
73
83
  /**
74
84
  * @description A combinator problem with a set of test cases for the proposed solution.
75
85
  * @param {{
76
- * title: string?,
77
- * descr: string?,
78
- * subst: string?,
79
- * allow: string?,
80
- * numbers: boolean?,
81
- * vars: string[]?,
82
- * engine: SKI?,
83
- * engineFull: SKI?,
86
+ * input: InputSpec | InputSpec[],
84
87
  * cases: TestCase[],
88
+ *
89
+ * // the rest is optional
90
+
91
+ * allow?: string,
92
+ * numbers?: boolean,
93
+ * env?: string[],
94
+ * engine?: SKI,
95
+ * engineFull?: SKI,
96
+ *
97
+ * // metadata, also any fields not listed here will go to quest.meta.???
98
+ * id?: string|number,
99
+ * name?: string,
100
+ * intro?: string|string[], // multiple strings will be concatenated with spaces
85
101
  * }} options
102
+ *
103
+ * @example const quest = new Quest({
104
+ * input: 'identity',
105
+ * cases: [
106
+ * ['identity x', 'x'],
107
+ * ],
108
+ * allow: 'SK',
109
+ * intro: 'Find a combinator that behaves like the identity function.',
110
+ * });
111
+ * quest.check('S K K'); // { pass: true, details: [...], ... }
112
+ * quest.check('K S'); // { pass: false, details: [...], ... }
113
+ * quest.check('K x'); // fail! internal variable x is not equal to free variable x,
114
+ * // despite having the same name.
115
+ * quest.check('I'); // fail! I not in the allowed list.
86
116
  */
87
- constructor(options?: {
88
- title: string | null;
89
- descr: string | null;
90
- subst: string | null;
91
- allow: string | null;
92
- numbers: boolean | null;
93
- vars: string[] | null;
94
- engine: SKI | null;
95
- engineFull: SKI | null;
96
- cases: TestCase[];
97
- });
98
- engine: SKI;
99
- engineFull: SKI;
117
+ constructor(options?: {});
118
+ engine: any;
119
+ engineFull: any;
100
120
  restrict: {
101
- allow: string;
102
- numbers: boolean;
121
+ allow: any;
122
+ numbers: any;
103
123
  lambdas: any;
104
124
  };
105
- vars: {};
106
- subst: string[] | (string & any[]);
125
+ env: {};
107
126
  input: any[];
108
- varsFull: {};
127
+ envFull: {};
109
128
  cases: any[];
110
- title: string;
111
- descr: string;
112
- meta: {
113
- title: string | null;
114
- descr: string | null;
115
- };
129
+ name: any;
130
+ intro: any;
131
+ id: any;
132
+ meta: {};
116
133
  /**
117
- * Display allowed terms based on what engine thinks of this.vars + this.restrict.allow
134
+ * Display allowed terms based on what engine thinks of this.env + this.restrict.allow
118
135
  * @return {string}
119
136
  */
120
137
  allowed(): string;
@@ -153,21 +170,21 @@ declare class Case {
153
170
  * @param {{
154
171
  * max?: number,
155
172
  * note?: string,
156
- * vars?: {[key:string]: Expr},
173
+ * env?: {[key:string]: Expr},
157
174
  * engine: SKI
158
175
  * }} options
159
176
  */
160
177
  constructor(input: typeof import("./expr").FreeVar[], options: {
161
178
  max?: number;
162
179
  note?: string;
163
- vars?: {
180
+ env?: {
164
181
  [key: string]: typeof import("./expr").Expr;
165
182
  };
166
183
  engine: SKI;
167
184
  });
168
185
  max: number;
169
186
  note: string;
170
- vars: {
187
+ env: {
171
188
  [key: string]: typeof import("./expr").Expr;
172
189
  };
173
190
  input: typeof import("./expr").FreeVar[];
@@ -184,11 +201,11 @@ declare class Subst {
184
201
  /**
185
202
  * @descr A placeholder object with exactly n free variables to be substituted later.
186
203
  * @param {Expr} expr
187
- * @param {FreeVar[]} vars
204
+ * @param {FreeVar[]} env
188
205
  */
189
- constructor(expr: typeof import("./expr").Expr, vars: typeof import("./expr").FreeVar[]);
206
+ constructor(expr: typeof import("./expr").Expr, env: typeof import("./expr").FreeVar[]);
190
207
  expr: typeof import("./expr").Expr;
191
- vars: typeof import("./expr").FreeVar[];
208
+ env: typeof import("./expr").FreeVar[];
192
209
  apply(list: any): typeof import("./expr").Expr;
193
210
  }
194
211
  export {};
package/index.js DELETED
@@ -1,8 +0,0 @@
1
- const ski = require('./lib/parser');
2
- const quest = require('./lib/quest');
3
-
4
- module.exports = { ...ski, ...quest };
5
- if (typeof window !== 'undefined') {
6
- window.SKI = ski.SKI;
7
- window.SKI.Quest = quest.Quest;
8
- }