@dallaylaen/ski-interpreter 1.1.0 → 1.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.
@@ -1,4 +1,7 @@
1
- export type AnyArity = (arg0: Expr) => Expr | AnyArity;
1
+ export type Partial = Expr | ((arg0: Expr) => Partial);
2
+ /**
3
+ * @typedef {Expr | function(Expr): Partial} Partial
4
+ */
2
5
  export class Expr {
3
6
  /**
4
7
  * postprocess term after parsing. typically return self but may return other term or die
@@ -65,13 +68,13 @@ export class Expr {
65
68
  * @return {{
66
69
  * normal: boolean,
67
70
  * steps: number,
68
- * expr: Expr?,
69
- * arity: number?,
70
- * proper: boolean?,
71
- * discard: boolean?,
72
- * duplicate: boolean?,
73
- * skip: Set<number>?,
74
- * dup: Set<number>?
71
+ * expr?: Expr,
72
+ * arity?: number,
73
+ * proper?: boolean,
74
+ * discard?: boolean,
75
+ * duplicate?: boolean,
76
+ * skip?: Set<number>,
77
+ * dup?: Set<number>,
75
78
  * }}
76
79
  */
77
80
  guess(options?: {
@@ -80,13 +83,13 @@ export class Expr {
80
83
  }): {
81
84
  normal: boolean;
82
85
  steps: number;
83
- expr: Expr | null;
84
- arity: number | null;
85
- proper: boolean | null;
86
- discard: boolean | null;
87
- duplicate: boolean | null;
88
- skip: Set<number> | null;
89
- dup: Set<number> | null;
86
+ expr?: Expr;
87
+ arity?: number;
88
+ proper?: boolean;
89
+ discard?: boolean;
90
+ duplicate?: boolean;
91
+ skip?: Set<number>;
92
+ dup?: Set<number>;
90
93
  };
91
94
  _guess(options: any, preArgs?: any[], steps?: number): any;
92
95
  _aslist(): this[];
@@ -96,23 +99,23 @@ export class Expr {
96
99
  * up to the provided computation steps limit,
97
100
  * in decreasing weight order.
98
101
  * @param {{
99
- * max: number?,
100
- * maxArgs: number?,
101
- * varGen: function(void): FreeVar?,
102
- * steps: number?,
103
- * html: boolean?,
104
- * latin: number?,
102
+ * max?: number,
103
+ * maxArgs?: number,
104
+ * varGen?: function(void): FreeVar,
105
+ * steps?: number,
106
+ * html?: boolean,
107
+ * latin?: number,
105
108
  * }} options
106
109
  * @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
107
110
  * @return {IterableIterator<{expr: Expr, steps: number?, comment: string?}>}
108
111
  */
109
112
  lambdify(options?: {
110
- max: number | null;
111
- maxArgs: number | null;
112
- varGen: (arg0: void) => FreeVar | null;
113
- steps: number | null;
114
- html: boolean | null;
115
- latin: number | null;
113
+ max?: number;
114
+ maxArgs?: number;
115
+ varGen?: (arg0: void) => FreeVar;
116
+ steps?: number;
117
+ html?: boolean;
118
+ latin?: number;
116
119
  }): IterableIterator<{
117
120
  expr: Expr;
118
121
  steps: number | null;
@@ -130,32 +133,41 @@ export class Expr {
130
133
  expr: Expr;
131
134
  steps: number;
132
135
  }>;
133
- /**
134
- * @desc Rename free variables in the expression using the given sequence
135
- * This is for eye-candy only, as the interpreter knows darn well hot to distinguish vars,
136
- * regardless of names.
137
- * @param {IterableIterator<string>} seq
138
- * @return {Expr}
139
- */
140
- renameVars(seq: IterableIterator<string>): Expr;
141
136
  _rski(options: any): this;
142
137
  /**
143
- * Apply self to list of given args.
144
- * Normally, only native combinators know how to do it.
145
- * @param {Expr[]} args
146
- * @return {Expr|null}
147
- */
148
- reduce(args: Expr[]): Expr | null;
138
+ * Replace all instances of plug in the expression with value and return the resulting expression,
139
+ * or null if no changes could be made.
140
+ * Lambda terms and applications will never match if used as plug
141
+ * as they are impossible co compare without extensive computations.
142
+ * Typically used on variables but can also be applied to other terms, e.g. aliases.
143
+ * See also Expr.replace().
144
+ * @param {Expr} search
145
+ * @param {Expr} replace
146
+ * @return {Expr|null}
147
+ */
148
+ subst(search: Expr, replace: Expr): Expr | null;
149
149
  /**
150
- * Replace all instances of free vars with corresponding values and return the resulting expression.
151
- * return null if no changes could be made.
152
- * @param {FreeVar} plug
153
- * @param {Expr} value
154
- * @return {Expr|null}
155
- */
156
- subst(plug: FreeVar, value: Expr): Expr | null;
150
+ * @desc Apply term reduction rules, if any, to the given argument.
151
+ * A returned value of null means no reduction is possible.
152
+ * A returned value of Expr means the reduction is complete and the application
153
+ * of this and arg can be replaced with the result.
154
+ * A returned value of a function means that further arguments are needed,
155
+ * and can be cached for when they arrive.
156
+ *
157
+ * This method is between apply() which merely glues terms together,
158
+ * and step() which reduces the whole expression.
159
+ *
160
+ * foo.invoke(bar) is what happens inside foo.apply(bar).step() before
161
+ * reduction of either foo or bar is attempted.
162
+ *
163
+ * The name 'invoke' was chosen to avoid confusion with either 'apply' or 'reduce'.
164
+ *
165
+ * @param {Expr} arg
166
+ * @returns {Partial | null}
167
+ */
168
+ invoke(arg: Expr): Partial | null;
157
169
  /**
158
- * @desc iterate one step of calculation in accordance with known rules.
170
+ * @desc iterate one step of a calculation.
159
171
  * @return {{expr: Expr, steps: number, changed: boolean}}
160
172
  */
161
173
  step(): {
@@ -207,24 +219,64 @@ export class Expr {
207
219
  */
208
220
  expect(expected: Expr, comment?: string): void;
209
221
  /**
210
- * @param {{terse: boolean?, html: boolean?}} [options]
211
- * @return {string} string representation of the expression
222
+ * @desc Returns string representation of the expression.
223
+ * Same as format() without options.
224
+ * @return {string}
212
225
  */
213
- toString(options?: {
214
- terse: boolean | null;
215
- html: boolean | null;
216
- }): string;
226
+ toString(): string;
217
227
  /**
218
228
  * @desc Whether the expression needs parentheses when printed.
219
229
  * @param {boolean} [first] - whether this is the first term in a sequence
220
230
  * @return {boolean}
221
231
  */
222
- needsParens(first?: boolean): boolean;
232
+ _braced(first?: boolean): boolean;
233
+ _unspaced(arg: any): boolean;
223
234
  /**
235
+ * @desc Stringify the expression with fancy formatting options.
236
+ * Said options mostly include wrappers around various constructs in form of ['(', ')'],
237
+ * as well as terse and html flags that set up the defaults.
238
+ * Format without options is equivalent to toString() and can be parsed back.
239
+ *
240
+ * @param {Object} [options] - formatting options
241
+ * @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
242
+ * @param {boolean} [options.html] - whether to default to HTML tags & entities
243
+ * @param {[string, string]} [options.brackets] - wrappers for application arguments, typically ['(', ')']
244
+ * @param {[string, string]} [options.var] - wrappers for variable names
245
+ * (will default to &lt;var&gt; and &lt;/var&gt; in html mode)
246
+ * @param {[string, string, string]} [options.lambda] - wrappers for lambda abstractions, e.g. ['&lambda;', '.', '']
247
+ * where the middle string is placed between argument and body
248
+ * default is ['', '->', ''] or ['', '-&gt;', ''] for html
249
+ * @param {[string, string]} [options.around] - wrappers around (sub-)expressions.
250
+ * individual applications will not be wrapped, i.e. (a b c) but not ((a b) c)
251
+ * @param {[string, string]} [options.redex] - wrappers around the starting term(s) that have enough arguments to be reduced
252
+ * @param {Object<string, Expr>} [options.inventory] - if given, output aliases in the set as their names
253
+ * and any other aliases as the expansion of their definitions.
254
+ * The default is a cryptic and fragile mechanism dependent on a hidden mutable property.
255
+ * @returns {string}
256
+ *
257
+ * @example foo.format() // equivalent to foo.toString()
258
+ * @example foo.format({terse: false}) // spell out all parentheses
259
+ * @example foo.format({html: true}) // use HTML tags and entities
260
+ * @example foo.format({ around: ['(', ')'], brackets: ['', ''], lambda: ['(', '->', ')'] }) // lisp style, still back-parsable
261
+ * @exapmle foo.format({ lambda: ['&lambda;', '.', ''] }) // pretty-print for the math department
262
+ * @example foo.format({ lambda: ['', '=>', ''], terse: false }) // make it javascript
263
+ * @example foo.format({ inventory: { T } }) // use T as a named term, expand all others
224
264
  *
225
- * @return {string}
226
265
  */
227
- toJSON(): string;
266
+ format(options?: {
267
+ terse?: boolean;
268
+ html?: boolean;
269
+ brackets?: [string, string];
270
+ var?: [string, string];
271
+ lambda?: [string, string, string];
272
+ around?: [string, string];
273
+ redex?: [string, string];
274
+ inventory?: {
275
+ [x: string]: Expr;
276
+ };
277
+ }): string;
278
+ _format(options: any, nargs: any): void;
279
+ _declare(output: any, inventory: any, seen: any): void;
228
280
  }
229
281
  export namespace Expr {
230
282
  let lambdaPlaceholder: Native;
@@ -243,10 +295,8 @@ export class App extends Expr {
243
295
  arity: any;
244
296
  weight(): any;
245
297
  _firstVar(): any;
246
- apply(...args: any[]): App;
247
298
  expand(): any;
248
- renameVars(seq: any): any;
249
- subst(plug: any, value: any): any;
299
+ subst(search: any, replace: any): any;
250
300
  /**
251
301
  * @return {{expr: Expr, steps: number}}
252
302
  */
@@ -254,69 +304,88 @@ export class App extends Expr {
254
304
  expr: Expr;
255
305
  steps: number;
256
306
  };
257
- reduce(args: any): any;
307
+ invoke(arg: any): any;
258
308
  split(): any[];
259
309
  _aslist(): any[];
260
310
  equals(other: any): any;
261
311
  contains(other: any): any;
262
- needsParens(first: any): boolean;
263
- toString(opt?: {}): string;
312
+ _braced(first: any): boolean;
313
+ _format(options: any, nargs: any): any;
314
+ _unspaced(arg: any): any;
264
315
  }
265
316
  export class FreeVar extends Named {
266
317
  constructor(name: any);
267
318
  id: number;
268
- subst(plug: any, value: any): any;
269
- toString(opt?: {}): string;
270
319
  }
271
320
  export class Lambda extends Expr {
272
321
  /**
273
- * @param {FreeVar|FreeVar[]} arg
274
- * @param {Expr} impl
275
- */
276
- constructor(arg: FreeVar | FreeVar[], impl: Expr);
322
+ * @desc Lambda abstraction of arg over impl.
323
+ * Upon evaluation, all occurrences of 'arg' within 'impl' will be replaced
324
+ * with the provided argument.
325
+ *
326
+ * Note that 'arg' will be replaced by a localized placeholder, so the original
327
+ * variable can be used elsewhere without interference.
328
+ * Listing symbols contained in the lambda will omit such placeholder.
329
+ *
330
+ * Legacy ([FreeVar], impl) constructor is supported but deprecated.
331
+ * It will create a nested lambda expression.
332
+ *
333
+ * @param {FreeVar} arg
334
+ * @param {Expr} impl
335
+ */
336
+ constructor(arg: FreeVar, impl: Expr);
277
337
  arg: FreeVar;
278
338
  impl: Expr;
279
339
  arity: number;
280
- reduce(input: any): Expr;
281
- subst(plug: any, value: any): Lambda;
340
+ invoke(arg: any): Expr;
341
+ subst(search: any, replace: any): Lambda;
282
342
  expand(): Lambda;
283
- renameVars(seq: any): Lambda;
284
343
  _rski(options: any): any;
285
344
  equals(other: any): boolean;
286
- toString(opt?: {}): string;
287
- needsParens(first: any): boolean;
345
+ _format(options: any, nargs: any): string;
346
+ _braced(first: any): boolean;
288
347
  }
289
- /**
290
- * @typedef {function(Expr): Expr | AnyArity} AnyArity
291
- */
292
348
  export class Native extends Named {
293
349
  /**
294
- * @desc A term named 'name' that converts next 'arity' arguments into
295
- * an expression returned by 'impl' function
296
- * If an apply: Expr=>Expr|null function is given, it will be attempted upon application
297
- * before building an App object. This allows to plug in argument coercions,
298
- * e.g. instantly perform a numeric operation natively if the next term is a number.
350
+ * @desc A named term with a known rewriting rule.
351
+ * 'impl' is a function with signature Expr => Expr => ... => Expr
352
+ * (see typedef Partial).
353
+ * This is how S, K, I, and company are implemented.
354
+ *
355
+ * Note that as of current something like a=>b=>b(a) is not possible,
356
+ * use full form instead: a=>b=>b.apply(a).
357
+ *
358
+ * @example new Native('K', x => y => x); // constant
359
+ * @example new Native('Y', function(f) { return f.apply(this.apply(f)); }); // self-application
360
+ *
299
361
  * @param {String} name
300
- * @param {AnyArity} impl
301
- * @param {{note: string?, arity: number?, canonize: boolean?, apply: function(Expr):(Expr|null) }} [opt]
362
+ * @param {Partial} impl
363
+ * @param {{note?: string, arity?: number, canonize?: boolean, apply?: function(Expr):(Expr|null) }} [opt]
302
364
  */
303
- constructor(name: string, impl: AnyArity, opt?: {
304
- note: string | null;
305
- arity: number | null;
306
- canonize: boolean | null;
307
- apply: (arg0: Expr) => (Expr | null);
365
+ constructor(name: string, impl: Partial, opt?: {
366
+ note?: string;
367
+ arity?: number;
368
+ canonize?: boolean;
369
+ apply?: (arg0: Expr) => (Expr | null);
308
370
  });
309
- impl: AnyArity;
310
- onApply: (arg0: Expr) => (Expr | null);
371
+ invoke: Partial;
311
372
  arity: any;
312
373
  note: any;
313
- apply(...args: any[]): Expr;
314
374
  _rski(options: any): any;
315
- reduce(args: any): any;
316
375
  }
317
376
  export class Alias extends Named {
318
377
  /**
319
- * @desc An existing expression under a different name.
378
+ * @desc A named alias for an existing expression.
379
+ *
380
+ * Upon evaluation, the alias expands into the original expression,
381
+ * unless it has a known arity > 0 and is marked terminal,
382
+ * in which case it waits for enough arguments before expanding.
383
+ *
384
+ * A hidden mutable property 'outdated' is used to silently
385
+ * replace the alias with its definition in all contexts.
386
+ * This is used when declaring named terms in an interpreter,
387
+ * to avoid confusion between old and new terms with the same name.
388
+ *
320
389
  * @param {String} name
321
390
  * @param {Expr} impl
322
391
  * @param {{canonize: boolean?, max: number?, maxArgs: number?, note: string?, terminal: boolean?}} [options]
@@ -334,7 +403,8 @@ export class Alias extends Named {
334
403
  proper: any;
335
404
  terminal: any;
336
405
  canonical: any;
337
- subst(plug: any, value: any): Expr;
406
+ invoke: (arg: any) => any;
407
+ subst(search: any, replace: any): any;
338
408
  /**
339
409
  *
340
410
  * @return {{expr: Expr, steps: number}}
@@ -343,14 +413,18 @@ export class Alias extends Named {
343
413
  expr: Expr;
344
414
  steps: number;
345
415
  };
346
- reduce(args: any): Expr;
347
416
  equals(other: any): any;
348
417
  _rski(options: any): Expr;
349
- toString(opt: any): string;
350
- needsParens(first: any): boolean;
418
+ _braced(first: any): boolean;
419
+ _format(options: any, nargs: any): string | void;
351
420
  }
352
421
  export class Church extends Native {
353
- constructor(n: any);
422
+ /**
423
+ * @desc Church numeral representing non-negative integer n:
424
+ * n f x = f(f(...(f x)...)) with f applied n times.
425
+ * @param {number} n
426
+ */
427
+ constructor(n: number);
354
428
  n: any;
355
429
  arity: number;
356
430
  equals(other: any): boolean;
@@ -361,6 +435,12 @@ export namespace globalOptions {
361
435
  let maxArgs: number;
362
436
  }
363
437
  export const native: {};
438
+ /**
439
+ *
440
+ * @param {Expr[]} inventory
441
+ * @return {string[]}
442
+ */
443
+ export function declare(inventory: Expr[]): string[];
364
444
  declare class Named extends Expr {
365
445
  /**
366
446
  * @desc a constant named 'name'
@@ -368,6 +448,6 @@ declare class Named extends Expr {
368
448
  */
369
449
  constructor(name: string);
370
450
  name: string;
371
- toString(): string;
451
+ _format(options: any, nargs: any): string;
372
452
  }
373
453
  export {};
@@ -2,21 +2,21 @@ export class SKI {
2
2
  /**
3
3
  *
4
4
  * @param {{
5
- * allow: string?,
6
- * numbers: boolean?,
7
- * lambdas: boolean?,
8
- * terms: { [key: string]: Expr|string}?,
9
- * annotate: boolean?,
5
+ * allow?: string,
6
+ * numbers?: boolean,
7
+ * lambdas?: boolean,
8
+ * terms?: { [key: string]: Expr|string} | string[],
9
+ * annotate?: boolean,
10
10
  * }} [options]
11
11
  */
12
12
  constructor(options?: {
13
- allow: string | null;
14
- numbers: boolean | null;
15
- lambdas: boolean | null;
16
- terms: {
13
+ allow?: string;
14
+ numbers?: boolean;
15
+ lambdas?: boolean;
16
+ terms?: {
17
17
  [key: string]: Expr | string;
18
- } | null;
19
- annotate: boolean | null;
18
+ } | string[];
19
+ annotate?: boolean;
20
20
  });
21
21
  annotate: boolean;
22
22
  known: {};
@@ -24,17 +24,35 @@ export class SKI {
24
24
  hasLambdas: boolean;
25
25
  allow: any;
26
26
  /**
27
+ * @desc Declare a new term
28
+ * If the first argument is an Alias, it is added as is.
29
+ * Otherwise, a new Alias or Native term (depending on impl type) is created.
30
+ * If note is not provided and this.annotate is true, an automatic note is generated.
31
+ *
32
+ * If impl is a function, it should have signature (Expr) => ... => Expr
33
+ * (see typedef Partial at top of expr.js)
34
+ *
35
+ * @example ski.add('T', 'S(K(SI))K', 'swap combinator')
36
+ * @example ski.add( ski.parse('T = S(K(SI))K') ) // ditto but one-arg form
37
+ * @example ski.add('T', x => y => y.apply(x), 'swap combinator') // heavy artillery
38
+ * @example ski.add('Y', function (f) { return f.apply(this.apply(f)); }, 'Y combinator')
27
39
  *
28
40
  * @param {Alias|String} term
29
- * @param {Expr|String|[number, function(...Expr): Expr, {note: string?, fast: boolean?}]} [impl]
41
+ * @param {String|Expr|function(Expr):Partial} [impl]
30
42
  * @param {String} [note]
31
43
  * @return {SKI} chainable
32
44
  */
33
- add(term: Alias | string, impl?: Expr | string | [number, (...args: Expr[]) => Expr, {
34
- note: string | null;
35
- fast: boolean | null;
36
- }], note?: string): SKI;
45
+ add(term: Alias | string, impl?: string | Expr | ((arg0: Expr) => Partial), note?: string): SKI;
46
+ _named(term: any, impl: any): Native | Alias;
37
47
  maybeAdd(name: any, impl: any): this;
48
+ /**
49
+ * @desc Declare and remove multiple terms at once
50
+ * term=impl adds term
51
+ * term= removes term
52
+ * @param {string[]]} list
53
+ * @return {SKI} chainable
54
+ */
55
+ bulkAdd(list: any): SKI;
38
56
  /**
39
57
  * Restrict the interpreter to given terms. Terms prepended with '+' will be added
40
58
  * and terms preceeded with '-' will be removed.
@@ -65,6 +83,11 @@ export class SKI {
65
83
  getTerms(): {
66
84
  [key: string]: Native | Alias;
67
85
  };
86
+ /**
87
+ * Export term declarations for use in bulkAdd().
88
+ * @returns {string[]}
89
+ */
90
+ declare(): string[];
68
91
  /**
69
92
  *
70
93
  * @param {string} source
@@ -94,13 +117,12 @@ export class SKI {
94
117
  allow: string | null;
95
118
  }): Expr;
96
119
  toJSON(): {
120
+ version: string;
97
121
  allow: string;
98
122
  numbers: boolean;
99
123
  lambdas: boolean;
100
- terms: {
101
- [key: string]: Native | Alias;
102
- };
103
124
  annotate: boolean;
125
+ terms: string[];
104
126
  };
105
127
  }
106
128
  export namespace SKI {
@@ -144,13 +144,31 @@ export class Quest {
144
144
  show(): TestCase[];
145
145
  }
146
146
  declare class Case {
147
- constructor(input: any, options: any);
148
- max: any;
149
- note: any;
150
- vars: any;
151
- input: any;
152
- engine: any;
153
- parse(src: any): import("./expr").Lambda;
147
+ /**
148
+ * @param {FreeVar[]} input
149
+ * @param {{
150
+ * max?: number,
151
+ * note?: string,
152
+ * vars?: {string: Expr},
153
+ * engine: SKI
154
+ * }} options
155
+ */
156
+ constructor(input: typeof import("./expr").FreeVar[], options: {
157
+ max?: number;
158
+ note?: string;
159
+ vars?: {
160
+ string: typeof import("./expr").Expr;
161
+ };
162
+ engine: SKI;
163
+ });
164
+ max: number;
165
+ note: string;
166
+ vars: {
167
+ string?: typeof import("./expr").Expr;
168
+ };
169
+ input: typeof import("./expr").FreeVar[];
170
+ engine: SKI;
171
+ parse(src: any): Subst;
154
172
  /**
155
173
  * @param {Expr} expr
156
174
  * @return {CaseResult}
@@ -158,4 +176,15 @@ declare class Case {
158
176
  check(...expr: typeof import("./expr").Expr): CaseResult;
159
177
  }
160
178
  import { SKI } from "./parser";
179
+ declare class Subst {
180
+ /**
181
+ * @descr A placeholder object with exactly n free variables to be substituted later.
182
+ * @param {Expr} expr
183
+ * @param {FreeVar[]} vars
184
+ */
185
+ constructor(expr: typeof import("./expr").Expr, vars: typeof import("./expr").FreeVar[]);
186
+ expr: typeof import("./expr").Expr;
187
+ vars: typeof import("./expr").FreeVar[];
188
+ apply(list: any): typeof import("./expr").Expr;
189
+ }
161
190
  export {};