@dallaylaen/ski-interpreter 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dallaylaen/ski-interpreter",
3
- "version": "2.2.1",
3
+ "version": "2.3.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",
@@ -61,5 +61,8 @@
61
61
  "mocha": "^10.2.0",
62
62
  "nyc": "^15.1.0",
63
63
  "typescript": "^5.8.2"
64
+ },
65
+ "dependencies": {
66
+ "commander": "^14.0.3"
64
67
  }
65
68
  }
@@ -1,9 +1,77 @@
1
1
  export type TraverseValue<T_1> = T_1 | TraverseControl<T_1> | null;
2
2
  export type Partial = Expr | ((arg0: Expr) => Partial);
3
+ /**
4
+ * : boolean, // whether the irreducible form is only contains its arguments. implies normal.
5
+ * arity?: number, // the number of arguments that is sufficient to reach the normal form
6
+ * // absent unless normal.
7
+ * discard?: boolean, // whether the term (or subterms, unless proper) can discard arguments.
8
+ * duplicate?: boolean, // whether the term (or subterms, unless proper) can duplicate arguments.
9
+ * skip?: Set<number>, // indices of arguments that are discarded. nonempty inplies discard.
10
+ * dup?: Set<number>, // indices of arguments that are duplicated. nonempty implies duplicate.
11
+ * expr?: Expr, // canonical form containing lambdas, applications, and variables, if any
12
+ * steps?: number, // number of steps taken to obtain the aforementioned information, if applicable
13
+ * }} TermInfo
14
+ */
15
+ export type proper = {
16
+ normal: boolean;
17
+ };
3
18
  /**
4
19
  * @typedef {Expr | function(Expr): Partial} Partial
5
20
  */
21
+ /**
22
+ * @typedef {{
23
+ * normal: boolean, // whether the term becomes irreducible after receiving a number of arguments.
24
+ * // if false, other properties may be missing.
25
+ * proper: boolean, // whether the irreducible form is only contains its arguments. implies normal.
26
+ * arity?: number, // the number of arguments that is sufficient to reach the normal form
27
+ * // absent unless normal.
28
+ * discard?: boolean, // whether the term (or subterms, unless proper) can discard arguments.
29
+ * duplicate?: boolean, // whether the term (or subterms, unless proper) can duplicate arguments.
30
+ * skip?: Set<number>, // indices of arguments that are discarded. nonempty inplies discard.
31
+ * dup?: Set<number>, // indices of arguments that are duplicated. nonempty implies duplicate.
32
+ * expr?: Expr, // canonical form containing lambdas, applications, and variables, if any
33
+ * steps?: number, // number of steps taken to obtain the aforementioned information, if applicable
34
+ * }} TermInfo
35
+ */
6
36
  export class Expr {
37
+ /**
38
+ * @descr A combinatory logic expression.
39
+ *
40
+ * Applications, variables, and other terms like combinators per se
41
+ * are subclasses of this class.
42
+ *
43
+ * @abstract
44
+ * @property {{
45
+ * scope?: any,
46
+ * env?: { [key: string]: Expr },
47
+ * src?: string,
48
+ * parser: object,
49
+ * }} [context]
50
+ * @property {number} [arity] - number of arguments the term is waiting for (if known)
51
+ * @property {string} [note] - a brief description what the term does
52
+ * @property {string} [fancyName] - how to display in html mode, e.g. &phi; instead of 'f'
53
+ * Typically only applicable to descendants of Named.
54
+ * @property {TermInfo} [props] - properties inferred from the term's behavior
55
+ */
56
+ /**
57
+ *
58
+ * @desc Define properties of the term based on user supplied options and/or inference results.
59
+ * Typically useful for declaring Native and Alias terms.
60
+ * @private
61
+ * @param {Object} options
62
+ * @param {string} [options.note] - a brief description what the term does
63
+ * @param {number} [options.arity] - number of arguments the term is waiting for (if known)
64
+ * @param {string} [options.fancy] - how to display in html mode, e.g. &phi; instead of 'f'
65
+ * @param {boolean} [options.canonize] - whether to try to infer the properties
66
+ * @param {number} [options.max] - maximum number of steps for inference, if canonize is true
67
+ * @param {number} [options.maxArgs] - maximum number of arguments for inference, if canonize is true
68
+ * @return {this}
69
+ */
70
+ private _setup;
71
+ fancyName: any;
72
+ note: any;
73
+ arity: any;
74
+ props: TermInfo;
7
75
  /**
8
76
  * @desc apply self to zero or more terms and return the resulting term,
9
77
  * without performing any calculations whatsoever
@@ -24,17 +92,46 @@ export class Expr {
24
92
  /**
25
93
  * @desc Traverse the expression tree, applying change() to each node.
26
94
  * If change() returns an Expr, the node is replaced with that value.
27
- * Otherwise, the node is descended further (if applicable)
28
- * or left unchanged.
95
+ * A null/undefined value is interpreted as
96
+ * "descend further if applicable, or leave the node unchanged".
97
+ *
98
+ * Returned values may be decorated:
29
99
  *
30
- * The traversal order is leftmost-outermost (LO), i.e. the same order as reduction steps are taken.
100
+ * SKI.control.prune will suppress further descending even if nothing was returned
101
+ * SKI.control.stop will terminate further changes.
102
+ * SKI.control.redo will apply the callback to the returned subtree, recursively.
103
+ *
104
+ * Note that if redo was applied at least once to a subtree, a null return from the same subtree
105
+ * will be replaced by the last non-null value returned.
106
+ *
107
+ * The traversal order is leftmost-outermost, unless options.order = 'leftmost-innermost' is specified.
108
+ * Short aliases 'LO' and 'LI' (case-sensitive) are also accepted.
31
109
  *
32
110
  * Returns null if no changes were made, or the new expression otherwise.
33
111
  *
34
- * @param {(e:Expr) => (Expr|null)} change
112
+ * @param {{
113
+ * order?: 'LO' | 'LI' | 'leftmost-outermost' | 'leftmost-innermost',
114
+ * }} [options]
115
+ * @param {(e:Expr) => TraverseValue<Expr>} change
35
116
  * @returns {Expr|null}
36
117
  */
37
- traverse(change: (e: Expr) => (Expr | null)): Expr | null;
118
+ traverse(options?: {
119
+ order?: "LO" | "LI" | "leftmost-outermost" | "leftmost-innermost";
120
+ }, change: (e: Expr) => TraverseValue<Expr>): Expr | null;
121
+ /**
122
+ * @private
123
+ * @param {Object} options
124
+ * @param {(e:Expr) => TraverseValue<Expr>} change
125
+ * @returns {TraverseValue<Expr>}
126
+ */
127
+ private _traverse_redo;
128
+ /**
129
+ * @private
130
+ * @param {Object} options
131
+ * @param {(e:Expr) => TraverseValue<Expr>} change
132
+ * @returns {TraverseValue<Expr>}
133
+ */
134
+ private _traverse_descend;
38
135
  /**
39
136
  * @desc Returns true if predicate() is true for any subterm of the expression, false otherwise.
40
137
  *
@@ -88,46 +185,17 @@ export class Expr {
88
185
  * Use toLambda() if you want to get a lambda term in any case.
89
186
  *
90
187
  * @param {{max?: number, maxArgs?: number}} options
91
- * @return {{
92
- * normal: boolean,
93
- * steps: number,
94
- * expr?: Expr,
95
- * arity?: number,
96
- * proper?: boolean,
97
- * discard?: boolean,
98
- * duplicate?: boolean,
99
- * skip?: Set<number>,
100
- * dup?: Set<number>,
101
- * }}
188
+ * @return {TermInfo}
102
189
  */
103
190
  infer(options?: {
104
191
  max?: number;
105
192
  maxArgs?: number;
106
- }): {
107
- normal: boolean;
108
- steps: number;
109
- expr?: Expr;
110
- arity?: number;
111
- proper?: boolean;
112
- discard?: boolean;
113
- duplicate?: boolean;
114
- skip?: Set<number>;
115
- dup?: Set<number>;
116
- };
193
+ }): TermInfo;
117
194
  /**
118
- *
119
- * @param {{max: number, maxArgs: number, index: number}} options
120
- * @param {FreeVar[]} preArgs
121
- * @param {number} steps
122
- * @returns {{
123
- * normal: boolean,
124
- * steps: number,
125
- * expr?: Expr,
126
- * arity?: number,
127
- * skip?: Set<number>,
128
- * dup?: Set<number>,
129
- * duplicate, discard, proper: boolean
130
- * }
195
+ * @desc Internal method for infer(), which performs the actual inference.
196
+ * @param {{max: number, maxArgs: number}} options
197
+ * @param {number} nargs - var index to avoid name clashes
198
+ * @returns {TermInfo}
131
199
  * @private
132
200
  */
133
201
  private _infer;
@@ -193,13 +261,6 @@ export class Expr {
193
261
  expr: Expr;
194
262
  steps: number;
195
263
  }>;
196
- /**
197
- * @desc Internal method for toSKI, which performs one step of the conversion.
198
- * @param {{max: number, steps: number}} options
199
- * @returns {Expr}
200
- * @private
201
- */
202
- private _rski;
203
264
  /**
204
265
  * Replace all instances of plug in the expression with value and return the resulting expression,
205
266
  * or null if no changes could be made.
@@ -307,10 +368,14 @@ export class Expr {
307
368
  diff(other: Expr, swap?: boolean): string | null;
308
369
  /**
309
370
  * @desc Assert expression equality. Can be used in tests.
310
- * @param {Expr} expected
371
+ *
372
+ * `this` is the expected value and the argument is the actual one.
373
+ * Mnemonic: the expected value is always a combinator, the actual one may be anything.
374
+ *
375
+ * @param {Expr} actual
311
376
  * @param {string} comment
312
377
  */
313
- expect(expected: Expr, comment?: string): void;
378
+ expect(actual: Expr, comment?: string): void;
314
379
  /**
315
380
  * @desc Returns string representation of the expression.
316
381
  * Same as format() without options.
@@ -428,33 +493,7 @@ export class App extends Expr {
428
493
  constructor(fun: Expr, arg: Expr);
429
494
  arg: Expr;
430
495
  fun: Expr;
431
- final: boolean;
432
- arity: number;
433
- _infer(options: any, preArgs?: any[], steps?: number): {
434
- normal: boolean;
435
- steps: number;
436
- expr?: Expr;
437
- arity?: number;
438
- skip?: Set<number>;
439
- dup?: Set<number>;
440
- duplicate: any;
441
- discard: any;
442
- proper: boolean;
443
- } | {
444
- normal: boolean;
445
- steps: number;
446
- } | {
447
- expr: Expr;
448
- arity?: number;
449
- skip?: Set<number>;
450
- dup?: Set<number>;
451
- duplicate?: any;
452
- discard?: any;
453
- proper: boolean;
454
- normal: boolean;
455
- steps: number;
456
- };
457
- traverse(change: any): Expr;
496
+ _traverse_descend(options: any, change: any): any;
458
497
  any(predicate: any): any;
459
498
  _fold(initial: any, combine: any): any;
460
499
  subst(search: any, replace: any): Expr;
@@ -466,7 +505,7 @@ export class App extends Expr {
466
505
  steps: number;
467
506
  };
468
507
  invoke(arg: any): Partial;
469
- _rski(options: any): Expr | this;
508
+ final: boolean;
470
509
  diff(other: any, swap?: boolean): string;
471
510
  _braced(first: any): boolean;
472
511
  _format(options: any, nargs: any): string;
@@ -496,6 +535,8 @@ export class FreeVar extends Named {
496
535
  * If a scope object is given, however, two variables with the same name and scope
497
536
  * are considered identical.
498
537
  *
538
+ * By convention, FreeVar.global is a constant denoting a global unbound variable.
539
+ *
499
540
  * @param {string} name - name of the variable
500
541
  * @param {any} scope - an object representing where the variable belongs to.
501
542
  */
@@ -505,6 +546,9 @@ export class FreeVar extends Named {
505
546
  diff(other: any, swap?: boolean): string;
506
547
  subst(search: any, replace: any): any;
507
548
  }
549
+ export namespace FreeVar {
550
+ let global: string[];
551
+ }
508
552
  export class Lambda extends Expr {
509
553
  /**
510
554
  * @desc Lambda abstraction of arg over impl.
@@ -525,26 +569,11 @@ export class Lambda extends Expr {
525
569
  arg: FreeVar;
526
570
  impl: Expr;
527
571
  arity: number;
528
- _infer(options: any, preArgs?: any[], steps?: number): {
529
- normal: boolean;
530
- steps: number;
531
- expr?: Expr;
532
- arity?: number;
533
- skip?: Set<number>;
534
- dup?: Set<number>;
535
- duplicate: any;
536
- discard: any;
537
- proper: boolean;
538
- } | {
539
- normal: boolean;
540
- steps: number;
541
- };
542
572
  invoke(arg: any): Expr;
543
- traverse(change: any): Expr | Lambda;
573
+ _traverse_descend(options: any, change: any): any;
544
574
  any(predicate: any): any;
545
575
  _fold(initial: any, combine: any): any;
546
576
  subst(search: any, replace: any): Lambda;
547
- _rski(options: any): any;
548
577
  diff(other: any, swap?: boolean): string;
549
578
  _format(options: any, nargs: any): string;
550
579
  _braced(first: any): boolean;
@@ -564,20 +593,14 @@ export class Native extends Named {
564
593
  *
565
594
  * @param {String} name
566
595
  * @param {Partial} impl
567
- * @param {{note?: string, arity?: number, canonize?: boolean, apply?: function(Expr):(Expr|null) }} [opt]
596
+ * @param {{note?: string, arity?: number, canonize?: boolean }} [opt]
568
597
  */
569
598
  constructor(name: string, impl: Partial, opt?: {
570
599
  note?: string;
571
600
  arity?: number;
572
601
  canonize?: boolean;
573
- apply?: (arg0: Expr) => (Expr | null);
574
602
  });
575
603
  invoke: Partial;
576
- /** @type {number} */
577
- arity: number;
578
- /** @type {string} */
579
- note: string;
580
- _rski(options: any): Expr | this;
581
604
  }
582
605
  export class Alias extends Named {
583
606
  /**
@@ -604,43 +627,33 @@ export class Alias extends Named {
604
627
  terminal?: boolean;
605
628
  });
606
629
  impl: Expr;
607
- note: string;
608
- arity: any;
609
- proper: any;
610
630
  terminal: any;
611
- canonical: any;
612
631
  invoke: (arg: any) => any;
613
- traverse(change: any): any;
632
+ _traverse_descend(options: any, change: any): any;
614
633
  any(predicate: any): any;
615
634
  _fold(initial: any, combine: any): any;
616
635
  subst(search: any, replace: any): any;
617
- _infer(options: any, preArgs?: any[], steps?: number): {
618
- normal: boolean;
619
- steps: number;
620
- expr?: Expr;
621
- arity?: number;
622
- skip?: Set<number>;
623
- dup?: Set<number>;
624
- duplicate: any;
625
- discard: any;
626
- proper: boolean;
627
- };
628
636
  diff(other: any, swap?: boolean): any;
629
- _rski(options: any): Expr;
630
637
  _braced(first: any): boolean;
631
638
  }
632
- export class Church extends Native {
639
+ export class Church extends Expr {
633
640
  /**
634
641
  * @desc Church numeral representing non-negative integer n:
635
642
  * n f x = f(f(...(f x)...)) with f applied n times.
636
643
  * @param {number} n
637
644
  */
638
645
  constructor(n: number);
646
+ invoke: (x: any) => (y: any) => any;
639
647
  /** @type {number} */
640
648
  n: number;
649
+ arity: number;
641
650
  diff(other: any, swap?: boolean): string;
651
+ _unspaced(arg: any): boolean;
652
+ _format(options: any, nargs: any): any;
642
653
  }
643
654
  /**
655
+ * @desc List of predefined native combinators.
656
+ * This is required for toSKI() to work, otherwise could as well have been in parser.js.
644
657
  * @type {{[key: string]: Native}}
645
658
  */
646
659
  declare const native: {
@@ -42,10 +42,14 @@ export class SKI {
42
42
  *
43
43
  * @param {Alias|String} term
44
44
  * @param {String|Expr|function(Expr):Partial} [impl]
45
- * @param {String} [note]
45
+ * @param {object|string} [options]
46
+ * @param {string} [options.note] - optional annotation for the term, default is auto-generated if this.annotate is true
47
+ * @param {boolean} [options.canonize] - whether to canonize the term's implementation, default is this.annotate
48
+ * @param {boolean} [options.fancy] - alternative HTML-friendly name for the term
49
+ * @param {number} [options.arity] - custom arity for the term, default is inferred from the implementation
46
50
  * @return {SKI} chainable
47
51
  */
48
- add(term: typeof classes.Alias | string, impl?: string | typeof classes.Expr | ((arg0: typeof classes.Expr) => Partial), note?: string): SKI;
52
+ add(term: typeof classes.Alias | string, impl?: string | typeof classes.Expr | ((arg0: typeof classes.Expr) => Partial), options?: object | string): SKI;
49
53
  /**
50
54
  * @desc Internal helper for add() that creates an Alias or Native term from the given arguments.
51
55
  * @param {Alias|string} term
@@ -34,6 +34,10 @@ export type QuestResult = {
34
34
  steps: number;
35
35
  weight?: number;
36
36
  };
37
+ export type SelfCheck = {
38
+ accepted?: string[][];
39
+ rejected?: string[][];
40
+ };
37
41
  /**
38
42
  * @typedef {{
39
43
  * pass: boolean,
@@ -98,6 +102,9 @@ export type QuestResult = {
98
102
  * intro?: string|string[], // multiple strings will be concatenated with spaces
99
103
  * }} QuestSpec
100
104
  */
105
+ /**
106
+ * @typedef {{ accepted?: string[][], rejected?: string[][] }} SelfCheck
107
+ */
101
108
  export class Quest {
102
109
  /**
103
110
  * @description A combinator problem with a set of test cases for the proposed solution.
@@ -160,12 +167,39 @@ export class Quest {
160
167
  * @return {QuestResult}
161
168
  */
162
169
  check(...input: string): QuestResult;
170
+ verify(options: any): {
171
+ date: string;
172
+ };
173
+ /**
174
+ * @desc Verify that solutions that are expected to pass/fail do so.
175
+ * @param {SelfCheck|{[key: string]: SelfCheck}} dataset
176
+ * @return {{shouldPass: {input: string[], result: QuestResult}[], shouldFail: {input: string[], result: QuestResult}[]} | null}
177
+ */
178
+ verifySolutions(dataset: SelfCheck | {
179
+ [key: string]: SelfCheck;
180
+ }): {
181
+ shouldPass: {
182
+ input: string[];
183
+ result: QuestResult;
184
+ }[];
185
+ shouldFail: {
186
+ input: string[];
187
+ result: QuestResult;
188
+ }[];
189
+ } | null;
190
+ verifyMeta(options?: {}): {
191
+ date: string;
192
+ };
163
193
  /**
164
194
  *
165
195
  * @return {TestCase[]}
166
196
  */
167
197
  show(): TestCase[];
168
198
  }
199
+ export namespace Quest {
200
+ export { Group };
201
+ export { Case };
202
+ }
169
203
  declare class Case {
170
204
  /**
171
205
  * @param {FreeVar[]} input
@@ -198,6 +232,16 @@ declare class Case {
198
232
  */
199
233
  check(...expr: typeof import("./expr").Expr): CaseResult;
200
234
  }
235
+ declare class Group {
236
+ constructor(options: any);
237
+ name: any;
238
+ intro: string;
239
+ id: any;
240
+ content: any;
241
+ verify(options: any): {
242
+ content: any;
243
+ };
244
+ }
201
245
  import { SKI } from "./parser";
202
246
  declare class Subst {
203
247
  /**