@dallaylaen/ski-interpreter 1.0.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 ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@dallaylaen/ski-interpreter",
3
+ "version": "1.0.0",
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
+ "keywords": [
6
+ "combinatory logic",
7
+ "lambda calculus",
8
+ "functional programming",
9
+ "education",
10
+ "interpreter"
11
+ ],
12
+ "main": "index.js",
13
+ "types": "types/index.d.ts",
14
+ "bin": {
15
+ "ski": "bin/ski.js"
16
+ },
17
+ "scripts": {
18
+ "test": "nyc mocha"
19
+ },
20
+ "files": [
21
+ "package.json",
22
+ "README.md",
23
+ "LICENSE",
24
+ "lib",
25
+ "types"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/dallaylaen/ski-interpreter.git"
30
+ },
31
+ "author": "Konstantin Uvarin",
32
+ "license": "MIT",
33
+ "bugs": {
34
+ "url": "https://github.com/dallaylaen/ski-interpreter/issues"
35
+ },
36
+ "homepage": "https://dallaylaen.github.io/ski-interpreter/index.html",
37
+ "devDependencies": {
38
+ "eslint": "^8.57.0",
39
+ "eslint-config-standard": "^17.1.0",
40
+ "eslint-plugin-align-assignments": "^1.1.2",
41
+ "eslint-plugin-import": "^2.29.1",
42
+ "typescript": "^5.8.2",
43
+ "mocha": "^10.2.0",
44
+ "nyc": "^15.1.0"
45
+ }
46
+ }
@@ -0,0 +1,7 @@
1
+ declare const _exports: {
2
+ Quest: typeof quest.Quest;
3
+ SKI: typeof ski.SKI;
4
+ };
5
+ export = _exports;
6
+ import quest = require("./lib/quest");
7
+ import ski = require("./lib/parser");
@@ -0,0 +1,344 @@
1
+ export type AnyArity = (arg0: Expr) => Expr | AnyArity;
2
+ export class Expr {
3
+ arity: number;
4
+ /**
5
+ * postprocess term after parsing. typically return self but may return other term or die
6
+ * @return {Expr}
7
+ */
8
+ postParse(): Expr;
9
+ /**
10
+ * @desc apply self to zero or more terms and return the resulting term,
11
+ * without performing any calculations whatsoever
12
+ * @param {Expr} args
13
+ * @return {Expr}
14
+ */
15
+ apply(...args: Expr): Expr;
16
+ /**
17
+ * expand all terms but don't perform any calculations
18
+ * @return {Expr}
19
+ */
20
+ expand(): Expr;
21
+ /**
22
+ * @desc return all free variables within the term
23
+ * @return {Set<FreeVar>}
24
+ */
25
+ freeVars(): Set<FreeVar>;
26
+ hasLambda(): any;
27
+ freeOnly(): boolean;
28
+ /**
29
+ * @desc return all terminal values within the term, that is, values not
30
+ * composed of other terms. For example, in S(KI)K, the terminals are S, K, I.
31
+ * @return {Map<Expr, number>}
32
+ */
33
+ getSymbols(): Map<Expr, number>;
34
+ /**
35
+ * @desc rought estimate of the complexity of the term
36
+ * @return {number}
37
+ */
38
+ weight(): number;
39
+ /**
40
+ *
41
+ * @param {{max: number?, maxArgs: number?, bestGuess?: Expr}} options
42
+ * @return {{
43
+ * found: boolean,
44
+ * proper: boolean,
45
+ * arity: number?,
46
+ * linear: boolean?,
47
+ * canonical?: Expr,
48
+ * steps: number?,
49
+ * skip: Set<number>?
50
+ * }}
51
+ */
52
+ canonize(options?: {
53
+ max: number | null;
54
+ maxArgs: number | null;
55
+ bestGuess?: Expr;
56
+ }): {
57
+ found: boolean;
58
+ proper: boolean;
59
+ arity: number | null;
60
+ linear: boolean | null;
61
+ canonical?: Expr;
62
+ steps: number | null;
63
+ skip: Set<number> | null;
64
+ };
65
+ /**
66
+ * @desc Returns a series of lambda terms equivalent to the given expression,
67
+ * up to the provided computation steps limit,
68
+ * in decreasing weight order.
69
+ * @param {{
70
+ * max: number?,
71
+ * maxArgs: number?,
72
+ * varGen: function(void): FreeVar?,
73
+ * steps: number?,
74
+ * html: boolean?,
75
+ * latin: number?,
76
+ * }} options
77
+ * @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
78
+ * @return {IterableIterator<{expr: Expr, steps: number?, comment: string?}>}
79
+ */
80
+ lambdify(options?: {
81
+ max: number | null;
82
+ maxArgs: number | null;
83
+ varGen: (arg0: void) => FreeVar | null;
84
+ steps: number | null;
85
+ html: boolean | null;
86
+ latin: number | null;
87
+ }): IterableIterator<{
88
+ expr: Expr;
89
+ steps: number | null;
90
+ comment: string | null;
91
+ }>;
92
+ /**
93
+ * @desc same semantics as walk() but rewrite step by step instead of computing
94
+ * @param {{max: number?}} options
95
+ * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
96
+ */
97
+ rewriteSKI(options?: {
98
+ max: number | null;
99
+ }): IterableIterator<{
100
+ final: boolean;
101
+ expr: Expr;
102
+ steps: number;
103
+ }>;
104
+ /**
105
+ * @desc Rename free variables in the expression using the given sequence
106
+ * This is for eye-candy only, as the interpreter knows darn well hot to distinguish vars,
107
+ * regardless of names.
108
+ * @param {IterableIterator<string>} seq
109
+ * @return {Expr}
110
+ */
111
+ renameVars(seq: IterableIterator<string>): Expr;
112
+ _rski(options: any): this;
113
+ /**
114
+ * @desc Whether the term will reduce further if given more arguments.
115
+ * In practice, equivalent to "starts with a FreeVar"
116
+ * Used by canonize (duh...)
117
+ * @return {boolean}
118
+ */
119
+ wantsArgs(): boolean;
120
+ /**
121
+ * Apply self to list of given args.
122
+ * Normally, only native combinators know how to do it.
123
+ * @param {Expr[]} args
124
+ * @return {Expr|null}
125
+ */
126
+ reduce(args: Expr[]): Expr | null;
127
+ /**
128
+ * Replace all instances of free vars with corresponding values and return the resulting expression.
129
+ * return null if no changes could be made.
130
+ * @param {FreeVar} plug
131
+ * @param {Expr} value
132
+ * @return {Expr|null}
133
+ */
134
+ subst(plug: FreeVar, value: Expr): Expr | null;
135
+ /**
136
+ * @desc iterate one step of calculation in accordance with known rules.
137
+ * @return {{expr: Expr, steps: number, changed: boolean}}
138
+ */
139
+ step(): {
140
+ expr: Expr;
141
+ steps: number;
142
+ changed: boolean;
143
+ };
144
+ /**
145
+ * @desc Run uninterrupted sequence of step() applications
146
+ * until the expression is irreducible, or max number of steps is reached.
147
+ * Default number of steps = 1000.
148
+ * @param {{max: number?, steps: number?, throw: boolean?}|Expr} [opt]
149
+ * @param {Expr} args
150
+ * @return {{expr: Expr, steps: number, final: boolean}}
151
+ */
152
+ run(opt?: {
153
+ max: number | null;
154
+ steps: number | null;
155
+ throw: boolean | null;
156
+ } | Expr, ...args: Expr): {
157
+ expr: Expr;
158
+ steps: number;
159
+ final: boolean;
160
+ };
161
+ /**
162
+ * Execute step() while possible, yielding a brief description of events after each step.
163
+ * Mnemonics: like run() but slower.
164
+ * @param {{max: number?}} options
165
+ * @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
166
+ */
167
+ walk(options?: {
168
+ max: number | null;
169
+ }): IterableIterator<{
170
+ final: boolean;
171
+ expr: Expr;
172
+ steps: number;
173
+ }>;
174
+ /**
175
+ *
176
+ * @param {Expr} other
177
+ * @return {boolean}
178
+ */
179
+ equals(other: Expr): boolean;
180
+ contains(other: any): boolean;
181
+ expect(other: any): void;
182
+ /**
183
+ * @param {{terse: boolean?, html: boolean?}} [options]
184
+ * @return {string} string representation of the expression
185
+ */
186
+ toString(options?: {
187
+ terse: boolean | null;
188
+ html: boolean | null;
189
+ }): string;
190
+ /**
191
+ *
192
+ * @return {boolean}
193
+ */
194
+ needsParens(): boolean;
195
+ /**
196
+ *
197
+ * @return {string}
198
+ */
199
+ toJSON(): string;
200
+ }
201
+ export namespace Expr {
202
+ let lambdaPlaceholder: Native;
203
+ }
204
+ export class App extends Expr {
205
+ /**
206
+ * @desc Application of fun() to args.
207
+ * Never ever use new App(fun, ...args) directly, use fun.apply(...args) instead.
208
+ * @param {Expr} fun
209
+ * @param {Expr} args
210
+ */
211
+ constructor(fun: Expr, ...args: Expr);
212
+ fun: Expr;
213
+ args: Expr;
214
+ final: boolean;
215
+ weight(): Expr;
216
+ apply(...args: any[]): any;
217
+ canonize(options?: {}): {
218
+ found: boolean;
219
+ proper: boolean;
220
+ arity: number | null;
221
+ linear: boolean | null;
222
+ canonical?: Expr;
223
+ steps: number | null;
224
+ skip: Set<number> | null;
225
+ };
226
+ renameVars(seq: any): Expr;
227
+ subst(plug: any, value: any): Expr;
228
+ /**
229
+ * @return {{expr: Expr, steps: number}}
230
+ */
231
+ step(): {
232
+ expr: Expr;
233
+ steps: number;
234
+ };
235
+ split(): any[];
236
+ equals(other: any): boolean;
237
+ toString(opt?: {}): string;
238
+ }
239
+ export class FreeVar extends Named {
240
+ constructor(name: any);
241
+ id: number;
242
+ subst(plug: any, value: any): any;
243
+ toString(opt?: {}): string;
244
+ }
245
+ export class Lambda extends Expr {
246
+ /**
247
+ * @param {FreeVar|FreeVar[]} arg
248
+ * @param {Expr} impl
249
+ */
250
+ constructor(arg: FreeVar | FreeVar[], impl: Expr);
251
+ arg: FreeVar;
252
+ impl: Expr;
253
+ reduce(input: any): Expr;
254
+ subst(plug: any, value: any): Lambda;
255
+ expand(): Lambda;
256
+ renameVars(seq: any): Lambda;
257
+ _rski(options: any): any;
258
+ equals(other: any): boolean;
259
+ toString(opt?: {}): string;
260
+ }
261
+ /**
262
+ * @typedef {function(Expr): Expr | AnyArity} AnyArity
263
+ */
264
+ export class Native extends Named {
265
+ /**
266
+ * @desc A term named 'name' that converts next 'arity' arguments into
267
+ * an expression returned by 'impl' function
268
+ * If an apply: Expr=>Expr|null function is given, it will be attempted upon application
269
+ * before building an App object. This allows to plug in argument coercions,
270
+ * e.g. instantly perform a numeric operation natively if the next term is a number.
271
+ * @param {String} name
272
+ * @param {AnyArity} impl
273
+ * @param {{note: string?, arity: number?, canonize: boolean?, apply: function(Expr):(Expr|null) }} [opt]
274
+ */
275
+ constructor(name: string, impl: AnyArity, opt?: {
276
+ note: string | null;
277
+ arity: number | null;
278
+ canonize: boolean | null;
279
+ apply: (arg0: Expr) => (Expr | null);
280
+ });
281
+ impl: AnyArity;
282
+ onApply: (arg0: Expr) => (Expr | null);
283
+ arity: any;
284
+ note: any;
285
+ apply(...args: any[]): Expr;
286
+ _rski(options: any): Expr | this;
287
+ reduce(args: any): any;
288
+ }
289
+ export class Alias extends Named {
290
+ /**
291
+ * @desc An existing expression under a different name.
292
+ * @param {String} name
293
+ * @param {Expr} impl
294
+ * @param {{canonize: boolean?, max: number?, maxArgs: number?, note: string?, terminal: boolean?}} [options]
295
+ */
296
+ constructor(name: string, impl: Expr, options?: {
297
+ canonize: boolean | null;
298
+ max: number | null;
299
+ maxArgs: number | null;
300
+ note: string | null;
301
+ terminal: boolean | null;
302
+ });
303
+ impl: Expr;
304
+ note: string;
305
+ arity: any;
306
+ proper: any;
307
+ terminal: any;
308
+ canonical: any;
309
+ subst(plug: any, value: any): Expr;
310
+ /**
311
+ *
312
+ * @return {{expr: Expr, steps: number}}
313
+ */
314
+ step(): {
315
+ expr: Expr;
316
+ steps: number;
317
+ };
318
+ reduce(args: any): Expr;
319
+ equals(other: any): any;
320
+ _rski(options: any): Expr;
321
+ toString(opt: any): string;
322
+ }
323
+ export class Church extends Native {
324
+ constructor(n: any);
325
+ n: any;
326
+ arity: number;
327
+ equals(other: any): boolean;
328
+ }
329
+ export namespace globalOptions {
330
+ let terse: boolean;
331
+ let max: number;
332
+ let maxArgs: number;
333
+ }
334
+ export const native: {};
335
+ declare class Named extends Expr {
336
+ /**
337
+ * @desc a constant named 'name'
338
+ * @param {String} name
339
+ */
340
+ constructor(name: string);
341
+ name: string;
342
+ toString(): string;
343
+ }
344
+ export {};
@@ -0,0 +1,138 @@
1
+ export class SKI {
2
+ /**
3
+ *
4
+ * @param {{
5
+ * allow: string?,
6
+ * numbers: boolean?,
7
+ * lambdas: boolean?,
8
+ * terms: { [key: string]: Expr|string}?,
9
+ * annotate: boolean?,
10
+ * }} [options]
11
+ */
12
+ constructor(options?: {
13
+ allow: string | null;
14
+ numbers: boolean | null;
15
+ lambdas: boolean | null;
16
+ terms: {
17
+ [key: string]: Expr | string;
18
+ } | null;
19
+ annotate: boolean | null;
20
+ });
21
+ annotate: boolean;
22
+ known: {};
23
+ hasNumbers: boolean;
24
+ hasLambdas: boolean;
25
+ allow: any;
26
+ /**
27
+ *
28
+ * @param {Alias|String} term
29
+ * @param {Expr|String|[number, function(...Expr): Expr, {note: string?, fast: boolean?}]} [impl]
30
+ * @param {String} [note]
31
+ * @return {SKI} chainable
32
+ */
33
+ add(term: Alias | string, impl?: Expr | string | [number, (...args: Expr[]) => Expr, {
34
+ note: string | null;
35
+ fast: boolean | null;
36
+ }], note?: string): SKI;
37
+ maybeAdd(name: any, impl: any): this;
38
+ /**
39
+ * Restrict the interpreter to given terms. Terms prepended with '+' will be added
40
+ * and terms preceeded with '-' will be removed.
41
+ * @example ski.restrict('SK') // use the basis
42
+ * @example ski.restrict('+I') // allow I now
43
+ * @example ski.restrict('-SKI +BCKW' ); // switch basis
44
+ * @example ski.restrict('-foo -bar'); // forbid some user functions
45
+ * @param {string} spec
46
+ * @return {SKI} chainable
47
+ */
48
+ restrict(spec: string): SKI;
49
+ /**
50
+ *
51
+ * @param {string} spec
52
+ * @return {string}
53
+ */
54
+ showRestrict(spec?: string): string;
55
+ /**
56
+ *
57
+ * @param {String} name
58
+ * @return {SKI}
59
+ */
60
+ remove(name: string): SKI;
61
+ /**
62
+ *
63
+ * @return {{[key:string]: Native|Alias}}
64
+ */
65
+ getTerms(): {
66
+ [key: string]: Native | Alias;
67
+ };
68
+ /**
69
+ *
70
+ * @param {string} source
71
+ * @param {{[keys: string]: Expr}} vars
72
+ * @param {{numbers: boolean?, lambdas: boolean?, allow: string?}} options
73
+ * @return {Expr}
74
+ */
75
+ parse(source: string, vars?: {
76
+ [keys: string]: Expr;
77
+ }, options?: {
78
+ numbers: boolean | null;
79
+ lambdas: boolean | null;
80
+ allow: string | null;
81
+ }): Expr;
82
+ /**
83
+ *
84
+ * @param {String} source S(KI)I
85
+ * @param {{[keys: string]: Expr}} vars
86
+ * @param {{numbers: boolean?, lambdas: boolean?, allow: string?}} options
87
+ * @return {Expr} parsed expression
88
+ */
89
+ parseLine(source: string, vars?: {
90
+ [keys: string]: Expr;
91
+ }, options?: {
92
+ numbers: boolean | null;
93
+ lambdas: boolean | null;
94
+ allow: string | null;
95
+ }): Expr;
96
+ toJSON(): {
97
+ allow: string;
98
+ numbers: boolean;
99
+ lambdas: boolean;
100
+ terms: {
101
+ [key: string]: Native | Alias;
102
+ };
103
+ annotate: boolean;
104
+ };
105
+ }
106
+ export namespace SKI {
107
+ /**
108
+ * Create free var(s) for subsequent use
109
+ * @param {String} names
110
+ * @return {FreeVar[]}
111
+ */
112
+ export function free(...names: string): FreeVar[];
113
+ /**
114
+ * Convert a number to Church encoding
115
+ * @param {number} n
116
+ * @return {Church}
117
+ */
118
+ export function church(n: number): Church;
119
+ export namespace classes {
120
+ export { Expr };
121
+ export { Native };
122
+ export { Alias };
123
+ export { FreeVar };
124
+ export { Lambda };
125
+ export { Church };
126
+ }
127
+ export { native };
128
+ export { globalOptions as options };
129
+ export let lambdaPlaceholder: Native;
130
+ }
131
+ import { Alias } from "./expr";
132
+ import { Expr } from "./expr";
133
+ import { Native } from "./expr";
134
+ import { FreeVar } from "./expr";
135
+ import { Church } from "./expr";
136
+ import { Lambda } from "./expr";
137
+ import { native } from "./expr";
138
+ import { globalOptions } from "./expr";
@@ -0,0 +1,139 @@
1
+ export type CaseResult = {
2
+ pass: boolean;
3
+ reason: string | null;
4
+ steps: number;
5
+ start: typeof import("./expr").Expr;
6
+ found: typeof import("./expr").Expr;
7
+ expected: typeof import("./expr").Expr;
8
+ note: string | null;
9
+ args: typeof import("./expr").Expr[];
10
+ case: Case;
11
+ };
12
+ /**
13
+ * @typedef {{
14
+ * pass: boolean,
15
+ * reason: string?,
16
+ * steps: number,
17
+ * start: Expr,
18
+ * found: Expr,
19
+ * expected: Expr,
20
+ * note: string?,
21
+ * args: Expr[],
22
+ * case: Case
23
+ * }} CaseResult
24
+ */
25
+ export class Quest {
26
+ /**
27
+ * @description A combinator problem with a set of test cases for the proposed solution.
28
+ * @param {{
29
+ * title: string?,
30
+ * descr: string?,
31
+ * subst: string?,
32
+ * allow: string?,
33
+ * numbers: boolean?,
34
+ * vars: string[]?,
35
+ * engine: SKI?,
36
+ * engineFull: SKI?,
37
+ * cases: [{max: number?, note: string?, feedInput: boolean, lambdas: boolean?}|string[], ...string[][]]?
38
+ * }} options
39
+ */
40
+ constructor(options?: {
41
+ title: string | null;
42
+ descr: string | null;
43
+ subst: string | null;
44
+ allow: string | null;
45
+ numbers: boolean | null;
46
+ vars: string[] | null;
47
+ engine: SKI | null;
48
+ engineFull: SKI | null;
49
+ cases: [{
50
+ max: number | null;
51
+ note: string | null;
52
+ feedInput: boolean;
53
+ lambdas: boolean | null;
54
+ } | string[], ...string[][]] | null;
55
+ });
56
+ engine: SKI;
57
+ engineFull: SKI;
58
+ restrict: {
59
+ allow: string;
60
+ numbers: boolean;
61
+ lambdas: any;
62
+ };
63
+ vars: {};
64
+ subst: string[] | (string & any[]);
65
+ input: any[];
66
+ varsFull: {};
67
+ cases: any[];
68
+ title: string;
69
+ descr: string;
70
+ meta: {
71
+ title: string | null;
72
+ descr: string | null;
73
+ };
74
+ /**
75
+ * Display allowed terms based on what engine thinks of this.vars + this.restrict.allow
76
+ * @return {string}
77
+ */
78
+ allowed(): string;
79
+ addInput(term: any): void;
80
+ /**
81
+ *
82
+ * @param {{} | string} opt
83
+ * @param {string} terms
84
+ * @return {Quest}
85
+ */
86
+ add(opt: {} | string, ...terms: string): Quest;
87
+ /**
88
+ * @description Statefully parse a list of strings into expressions or fancy aliases thereof.
89
+ * @param {string[]} input
90
+ * @return {{terms: Expr[], weight: number}}
91
+ */
92
+ prepare(...input: string[]): {
93
+ terms: typeof import("./expr").Expr[];
94
+ weight: number;
95
+ };
96
+ /**
97
+ *
98
+ * @param {string} input
99
+ * @return {{
100
+ * expr: Expr?,
101
+ * pass: boolean,
102
+ * details: CaseResult[],
103
+ * exception: Error?,
104
+ * steps: number,
105
+ * input: Expr[]|string[],
106
+ * weight: number?
107
+ * }}
108
+ */
109
+ check(...input: string): {
110
+ expr: typeof import("./expr").Expr | null;
111
+ pass: boolean;
112
+ details: CaseResult[];
113
+ exception: Error | null;
114
+ steps: number;
115
+ input: typeof import("./expr").Expr[] | string[];
116
+ weight: number | null;
117
+ };
118
+ /**
119
+ *
120
+ * @return {TestCase[]}
121
+ */
122
+ show(): TestCase[];
123
+ }
124
+ declare class Case {
125
+ constructor(input: any, options: any);
126
+ max: any;
127
+ note: any;
128
+ vars: any;
129
+ input: any;
130
+ engine: any;
131
+ parse(src: any): import("./expr").Lambda;
132
+ /**
133
+ * @param {Expr} expr
134
+ * @return {CaseResult}
135
+ */
136
+ check(...expr: typeof import("./expr").Expr): CaseResult;
137
+ }
138
+ import { SKI } from "./parser";
139
+ export {};
@@ -0,0 +1,13 @@
1
+ export class Tokenizer {
2
+ constructor(...terms: any[]);
3
+ rex: RegExp;
4
+ /**
5
+ *
6
+ * @param {string} str
7
+ * @return {string[]}
8
+ */
9
+ split(str: string): string[];
10
+ }
11
+ export function restrict(set: any, spec: any): any;
12
+ export function missingIndices(arr: any, set: any): any;
13
+ export function isSubset(a: any, b: any): boolean;