@dallaylaen/ski-interpreter 1.1.0 → 1.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.
- package/CHANGELOG.md +19 -1
- package/bin/ski.js +1 -1
- package/lib/expr.js +258 -119
- package/lib/parser.js +52 -14
- package/package.json +1 -1
- package/types/lib/expr.d.ts +103 -64
- package/types/lib/parser.d.ts +26 -14
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2025-12-14
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
- Remove `toString()` options, use `format()` instead.
|
|
13
|
+
- Make `needsParens()` private (should've been to begin with)
|
|
14
|
+
- Remove unused `renameVars()` method.
|
|
15
|
+
- Remove Expr.`toJSON()`
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- SKI: `toJSON()` now recreates declarations exactly, preserving named subexpressions.
|
|
20
|
+
- SKI: `declare()` / `bulkAdd()` methods to export/import term definitions.
|
|
21
|
+
- Expr: `format(options?)` method for pretty-printing expressions with various options: html, verbosity, custom lambdas, custom brackets etc.
|
|
22
|
+
- Expr: `subst(find, replace)` now works for any type of find except application and lambdas.
|
|
23
|
+
- Playground: permalinks now use #hash instead of a ?query string. (Old links still supported).
|
|
24
|
+
- Playground: togglable frames around subexpressions & variable/redex highlighting.
|
|
25
|
+
|
|
8
26
|
## [1.1.0] - 2025-12-07
|
|
9
27
|
|
|
10
28
|
### BREAKING CHANGES
|
|
@@ -21,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
21
39
|
- Expr: guess() method to normalize terms.
|
|
22
40
|
Returns an object with `normal`: boolean and `steps`:
|
|
23
41
|
number properties, as well as optional `expr`: Expr -
|
|
24
|
-
equivalent lambda expression; `arity`: number,
|
|
42
|
+
equivalent lambda expression; `arity`: number,
|
|
25
43
|
and other properties.
|
|
26
44
|
- Expr: replace(terms: Expr[], options: {}) replaces
|
|
27
45
|
subtrees with matching canonical form (if they have one).
|
package/bin/ski.js
CHANGED
|
@@ -69,7 +69,7 @@ function runLine(onErr) {
|
|
|
69
69
|
if (state.final && !options.q)
|
|
70
70
|
console.log(`// ${state.steps} step(s) in ${new Date() - t0}ms`);
|
|
71
71
|
if (options.v || state.final)
|
|
72
|
-
console.log('' + state.expr.
|
|
72
|
+
console.log('' + state.expr.format({terse: options.t}));
|
|
73
73
|
if (state.final && expr instanceof SKI.classes.Alias)
|
|
74
74
|
ski.add(expr.name, state.expr);
|
|
75
75
|
}
|
package/lib/expr.js
CHANGED
|
@@ -141,13 +141,13 @@ class Expr {
|
|
|
141
141
|
* @return {{
|
|
142
142
|
* normal: boolean,
|
|
143
143
|
* steps: number,
|
|
144
|
-
* expr
|
|
145
|
-
* arity
|
|
146
|
-
* proper
|
|
147
|
-
* discard
|
|
148
|
-
* duplicate
|
|
149
|
-
* skip
|
|
150
|
-
* dup
|
|
144
|
+
* expr?: Expr,
|
|
145
|
+
* arity?: number,
|
|
146
|
+
* proper?: boolean,
|
|
147
|
+
* discard?: boolean,
|
|
148
|
+
* duplicate?: boolean,
|
|
149
|
+
* skip?: Set<number>,
|
|
150
|
+
* dup?: Set<number>,
|
|
151
151
|
* }}
|
|
152
152
|
*/
|
|
153
153
|
guess (options = {}) {
|
|
@@ -202,12 +202,12 @@ class Expr {
|
|
|
202
202
|
* up to the provided computation steps limit,
|
|
203
203
|
* in decreasing weight order.
|
|
204
204
|
* @param {{
|
|
205
|
-
* max
|
|
206
|
-
* maxArgs
|
|
207
|
-
* varGen
|
|
208
|
-
* steps
|
|
209
|
-
* html
|
|
210
|
-
* latin
|
|
205
|
+
* max?: number,
|
|
206
|
+
* maxArgs?: number,
|
|
207
|
+
* varGen?: function(void): FreeVar,
|
|
208
|
+
* steps?: number,
|
|
209
|
+
* html?: boolean,
|
|
210
|
+
* latin?: number,
|
|
211
211
|
* }} options
|
|
212
212
|
* @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
|
|
213
213
|
* @return {IterableIterator<{expr: Expr, steps: number?, comment: string?}>}
|
|
@@ -238,17 +238,6 @@ class Expr {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/**
|
|
242
|
-
* @desc Rename free variables in the expression using the given sequence
|
|
243
|
-
* This is for eye-candy only, as the interpreter knows darn well hot to distinguish vars,
|
|
244
|
-
* regardless of names.
|
|
245
|
-
* @param {IterableIterator<string>} seq
|
|
246
|
-
* @return {Expr}
|
|
247
|
-
*/
|
|
248
|
-
renameVars (seq) {
|
|
249
|
-
return this;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
241
|
_rski (options) {
|
|
253
242
|
return this;
|
|
254
243
|
}
|
|
@@ -264,14 +253,18 @@ class Expr {
|
|
|
264
253
|
}
|
|
265
254
|
|
|
266
255
|
/**
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
256
|
+
* Replace all instances of plug in the expression with value and return the resulting expression,
|
|
257
|
+
* or null if no changes could be made.
|
|
258
|
+
* Lambda terms and applications will never match if used as plug
|
|
259
|
+
* as they are impossible co compare without extensive computations.
|
|
260
|
+
* Typically used on variables but can also be applied to other terms, e.g. aliases.
|
|
261
|
+
* See also Expr.replace().
|
|
262
|
+
* @param {Expr} search
|
|
263
|
+
* @param {Expr} replace
|
|
264
|
+
* @return {Expr|null}
|
|
265
|
+
*/
|
|
266
|
+
subst (search, replace) {
|
|
267
|
+
return this === search ? replace : null;
|
|
275
268
|
}
|
|
276
269
|
|
|
277
270
|
/**
|
|
@@ -371,19 +364,18 @@ class Expr {
|
|
|
371
364
|
// TODO wanna use AssertionError but webpack doesn't recognize it
|
|
372
365
|
// still the below hack works for mocha-based tests.
|
|
373
366
|
const poorMans = new Error(comment + 'found term ' + this + ' but expected ' + expected);
|
|
374
|
-
poorMans.expected = expected
|
|
375
|
-
poorMans.actual = this
|
|
367
|
+
poorMans.expected = expected + '';
|
|
368
|
+
poorMans.actual = this + '';
|
|
376
369
|
throw poorMans;
|
|
377
370
|
}
|
|
378
371
|
|
|
379
372
|
/**
|
|
380
|
-
* @
|
|
381
|
-
*
|
|
373
|
+
* @desc Returns string representation of the expression.
|
|
374
|
+
* Same as format() without options.
|
|
375
|
+
* @return {string}
|
|
382
376
|
*/
|
|
383
|
-
toString (
|
|
384
|
-
|
|
385
|
-
// return this.constructor.name
|
|
386
|
-
throw new Error( 'No toString() method defined in class ' + this.constructor.name );
|
|
377
|
+
toString () {
|
|
378
|
+
return this.format();
|
|
387
379
|
}
|
|
388
380
|
|
|
389
381
|
/**
|
|
@@ -391,17 +383,83 @@ class Expr {
|
|
|
391
383
|
* @param {boolean} [first] - whether this is the first term in a sequence
|
|
392
384
|
* @return {boolean}
|
|
393
385
|
*/
|
|
394
|
-
|
|
386
|
+
_braced (first) {
|
|
395
387
|
return false;
|
|
396
388
|
}
|
|
397
389
|
|
|
390
|
+
_unspaced (arg) {
|
|
391
|
+
return this._braced(true);
|
|
392
|
+
}
|
|
393
|
+
|
|
398
394
|
/**
|
|
395
|
+
* @desc Stringify the expression with fancy formatting options.
|
|
396
|
+
* Said options mostly include wrappers around various constructs in form of ['(', ')'],
|
|
397
|
+
* as well as terse and html flags that set up the defaults.
|
|
398
|
+
* Format without options is equivalent to toString() and can be parsed back.
|
|
399
|
+
*
|
|
400
|
+
* @param {Object} [options] - formatting options
|
|
401
|
+
* @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
|
|
402
|
+
* @param {boolean} [options.html] - whether to default to HTML tags & entities
|
|
403
|
+
* @param {[string, string]} [options.brackets] - wrappers for application arguments, typically ['(', ')']
|
|
404
|
+
* @param {[string, string]} [options.var] - wrappers for variable names
|
|
405
|
+
* (will default to <var> and </var> in html mode)
|
|
406
|
+
* @param {[string, string, string]} [options.lambda] - wrappers for lambda abstractions, e.g. ['λ', '.', '']
|
|
407
|
+
* where the middle string is placed between argument and body
|
|
408
|
+
* default is ['', '->', ''] or ['', '->', ''] for html
|
|
409
|
+
* @param {[string, string]} [options.around] - wrappers around (sub-)expressions.
|
|
410
|
+
* individual applications will not be wrapped, i.e. (a b c) but not ((a b) c)
|
|
411
|
+
* @param {[string, string]} [options.redex] - wrappers around the starting term(s) that have enough arguments to be reduced
|
|
412
|
+
* @param {Object<string, Expr>} [options.inventory] - if given, output aliases in the set as their names
|
|
413
|
+
* and any other aliases as the expansion of their definitions.
|
|
414
|
+
* The default is a cryptic and fragile mechanism dependent on a hidden mutable property.
|
|
415
|
+
* @returns {string}
|
|
416
|
+
*
|
|
417
|
+
* @example foo.format() // equivalent to foo.toString()
|
|
418
|
+
* @example foo.format({terse: false}) // spell out all parentheses
|
|
419
|
+
* @example foo.format({html: true}) // use HTML tags and entities
|
|
420
|
+
* @example foo.format({ around: ['(', ')'], brackets: ['', ''], lambda: ['(', '->', ')'] }) // lisp style, still back-parsable
|
|
421
|
+
* @exapmle foo.format({ lambda: ['λ', '.', ''] }) // pretty-print for the math department
|
|
422
|
+
* @example foo.format({ lambda: ['', '=>', ''], terse: false }) // make it javascript
|
|
423
|
+
* @example foo.format({ inventory: { T } }) // use T as a named term, expand all others
|
|
399
424
|
*
|
|
400
|
-
* @return {string}
|
|
401
425
|
*/
|
|
402
|
-
|
|
403
|
-
|
|
426
|
+
|
|
427
|
+
format (options = {}) {
|
|
428
|
+
const defaults = options.html
|
|
429
|
+
? {
|
|
430
|
+
brackets: ['(', ')'],
|
|
431
|
+
space: ' ',
|
|
432
|
+
var: ['<var>', '</var>'],
|
|
433
|
+
lambda: ['', '->', ''],
|
|
434
|
+
around: ['', ''],
|
|
435
|
+
redex: ['<b>', '</b>'],
|
|
436
|
+
}
|
|
437
|
+
: {
|
|
438
|
+
brackets: ['(', ')'],
|
|
439
|
+
space: ' ',
|
|
440
|
+
var: ['', ''],
|
|
441
|
+
lambda: ['', '->', ''],
|
|
442
|
+
around: ['', ''],
|
|
443
|
+
redex: ['', ''],
|
|
444
|
+
}
|
|
445
|
+
return this._format({
|
|
446
|
+
terse: options.terse ?? globalOptions.terse,
|
|
447
|
+
brackets: options.brackets ?? defaults.brackets,
|
|
448
|
+
space: options.space ?? defaults.space,
|
|
449
|
+
var: options.var ?? defaults.var,
|
|
450
|
+
lambda: options.lambda ?? defaults.lambda,
|
|
451
|
+
around: options.around ?? defaults.around,
|
|
452
|
+
redex: options.redex ?? defaults.redex,
|
|
453
|
+
inventory: options.inventory, // TODO better name
|
|
454
|
+
}, 0);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
_format (options, nargs) {
|
|
458
|
+
throw new Error( 'No _format() method defined in class ' + this.constructor.name );
|
|
404
459
|
}
|
|
460
|
+
|
|
461
|
+
// output: string[] /* appended */, inventory: { [key: string]: Expr }, seen: Set<Expr>
|
|
462
|
+
_declare (output, inventory, seen) {}
|
|
405
463
|
}
|
|
406
464
|
|
|
407
465
|
class App extends Expr {
|
|
@@ -504,13 +562,9 @@ class App extends Expr {
|
|
|
504
562
|
return (fun._replace(pairs, opt) ?? fun).apply(arg._replace(pairs, opt) ?? arg);
|
|
505
563
|
}
|
|
506
564
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
subst (plug, value) {
|
|
512
|
-
const fun = this.fun.subst(plug, value);
|
|
513
|
-
const arg = this.arg.subst(plug, value);
|
|
565
|
+
subst (search, replace) {
|
|
566
|
+
const fun = this.fun.subst(search, replace);
|
|
567
|
+
const arg = this.arg.subst(search, replace);
|
|
514
568
|
|
|
515
569
|
return (fun || arg) ? (fun ?? this.fun).apply(arg ?? this.arg) : null;
|
|
516
570
|
}
|
|
@@ -579,25 +633,28 @@ class App extends Expr {
|
|
|
579
633
|
return this.fun.contains(other) || this.arg.contains(other) || super.contains(other);
|
|
580
634
|
}
|
|
581
635
|
|
|
582
|
-
|
|
636
|
+
_braced (first) {
|
|
583
637
|
return !first;
|
|
584
638
|
}
|
|
585
639
|
|
|
586
|
-
|
|
587
|
-
const fun = this.fun.
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
640
|
+
_format (options, nargs) {
|
|
641
|
+
const fun = this.fun._format(options, nargs + 1);
|
|
642
|
+
const arg = this.arg._format(options, 0);
|
|
643
|
+
const wrap = nargs ? ['', ''] : options.around;
|
|
644
|
+
// TODO ignore terse for now
|
|
645
|
+
if (options.terse && !this.arg._braced(false))
|
|
646
|
+
return wrap[0] + fun + (this.fun._unspaced(this.arg) ? '' : options.space) + arg + wrap[1];
|
|
647
|
+
else
|
|
648
|
+
return wrap[0] + fun + options.brackets[0] + arg + options.brackets[1] + wrap[1];
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
_declare (output, inventory, seen) {
|
|
652
|
+
this.fun._declare(output, inventory, seen);
|
|
653
|
+
this.arg._declare(output, inventory, seen);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_unspaced (arg) {
|
|
657
|
+
return this.arg._braced(false) ? true : this.arg._unspaced(arg);
|
|
601
658
|
}
|
|
602
659
|
}
|
|
603
660
|
|
|
@@ -613,8 +670,19 @@ class Named extends Expr {
|
|
|
613
670
|
this.name = name;
|
|
614
671
|
}
|
|
615
672
|
|
|
616
|
-
|
|
617
|
-
return
|
|
673
|
+
_unspaced (arg) {
|
|
674
|
+
return !!(
|
|
675
|
+
(arg instanceof Named) && (
|
|
676
|
+
(this.name.match(/^[A-Z+]$/) && arg.name.match(/^[a-z+]/i))
|
|
677
|
+
|| (this.name.match(/^[a-z+]/i) && arg.name.match(/^[A-Z+]$/))
|
|
678
|
+
)
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
_format (options, nargs) {
|
|
683
|
+
return this.arity > 0 && this.arity <= nargs
|
|
684
|
+
? options.redex[0] + this.name + options.redex[1]
|
|
685
|
+
: this.name;
|
|
618
686
|
}
|
|
619
687
|
}
|
|
620
688
|
|
|
@@ -626,12 +694,6 @@ class FreeVar extends Named {
|
|
|
626
694
|
this.id = ++freeId;
|
|
627
695
|
}
|
|
628
696
|
|
|
629
|
-
subst (plug, value) {
|
|
630
|
-
if (this === plug)
|
|
631
|
-
return value;
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
697
|
weight () {
|
|
636
698
|
return 0;
|
|
637
699
|
}
|
|
@@ -640,8 +702,8 @@ class FreeVar extends Named {
|
|
|
640
702
|
return true;
|
|
641
703
|
}
|
|
642
704
|
|
|
643
|
-
|
|
644
|
-
return
|
|
705
|
+
_format (options, nargs) {
|
|
706
|
+
return options.var[0] + this.name + options.var[1];
|
|
645
707
|
}
|
|
646
708
|
}
|
|
647
709
|
|
|
@@ -674,7 +736,7 @@ class Native extends Named {
|
|
|
674
736
|
if (!opt.arity)
|
|
675
737
|
this.arity = guess.arity || 1;
|
|
676
738
|
|
|
677
|
-
this.note = opt.note ?? guess.expr?.
|
|
739
|
+
this.note = opt.note ?? guess.expr?.format({ terse: true, html: true, lambda: ['', ' ↦ ', ''] });
|
|
678
740
|
}
|
|
679
741
|
|
|
680
742
|
apply (...args) {
|
|
@@ -714,10 +776,6 @@ class Native extends Named {
|
|
|
714
776
|
throw new Error('Native combinator ' + this + ' reduced to a non-expression: ' + step);
|
|
715
777
|
return step.apply(...args.slice(egde));
|
|
716
778
|
}
|
|
717
|
-
|
|
718
|
-
toJSON () {
|
|
719
|
-
return 'Native:' + this.name;
|
|
720
|
-
}
|
|
721
779
|
}
|
|
722
780
|
|
|
723
781
|
const native = {};
|
|
@@ -788,25 +846,17 @@ class Lambda extends Expr {
|
|
|
788
846
|
return (this.impl.subst(this.arg, head) ?? this.impl).apply(...tail);
|
|
789
847
|
}
|
|
790
848
|
|
|
791
|
-
subst (
|
|
792
|
-
if (
|
|
849
|
+
subst (search, replace) {
|
|
850
|
+
if (search === this.arg)
|
|
793
851
|
return null;
|
|
794
|
-
const change = this.impl.subst(
|
|
795
|
-
|
|
796
|
-
return new Lambda(this.arg, change);
|
|
797
|
-
return null;
|
|
852
|
+
const change = this.impl.subst(search, replace);
|
|
853
|
+
return change ? new Lambda(this.arg, change) : null;
|
|
798
854
|
}
|
|
799
855
|
|
|
800
856
|
expand () {
|
|
801
857
|
return new Lambda(this.arg, this.impl.expand());
|
|
802
858
|
}
|
|
803
859
|
|
|
804
|
-
renameVars (seq) {
|
|
805
|
-
const arg = new FreeVar(seq.next().value);
|
|
806
|
-
const impl = this.impl.subst(this.arg, arg) ?? this.impl;
|
|
807
|
-
return new Lambda(arg, impl.renameVars(seq));
|
|
808
|
-
}
|
|
809
|
-
|
|
810
860
|
_rski (options) {
|
|
811
861
|
const impl = this.impl._rski(options);
|
|
812
862
|
if (options.steps >= options.max)
|
|
@@ -851,12 +901,20 @@ class Lambda extends Expr {
|
|
|
851
901
|
return this.equals(other) || this.impl.contains(other);
|
|
852
902
|
}
|
|
853
903
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
904
|
+
_format (options, nargs) {
|
|
905
|
+
return (nargs > 0 ? options.brackets[0] : '')
|
|
906
|
+
+ options.lambda[0]
|
|
907
|
+
+ this.arg._format(options, 0) // TODO highlight redex if nargs > 0
|
|
908
|
+
+ options.lambda[1]
|
|
909
|
+
+ this.impl._format(options, 0) + options.lambda[2]
|
|
910
|
+
+ (nargs > 0 ? options.brackets[1] : '');
|
|
857
911
|
}
|
|
858
912
|
|
|
859
|
-
|
|
913
|
+
_declare (output, inventory, seen) {
|
|
914
|
+
this.impl._declare(output, inventory, seen);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
_braced (first) {
|
|
860
918
|
return true;
|
|
861
919
|
}
|
|
862
920
|
}
|
|
@@ -885,6 +943,10 @@ class Church extends Native {
|
|
|
885
943
|
return this.n === other.n;
|
|
886
944
|
return super.equals(other);
|
|
887
945
|
}
|
|
946
|
+
|
|
947
|
+
_unspaced (arg) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
888
950
|
}
|
|
889
951
|
|
|
890
952
|
class Alias extends Named {
|
|
@@ -922,8 +984,10 @@ class Alias extends Named {
|
|
|
922
984
|
return this.impl.expand();
|
|
923
985
|
}
|
|
924
986
|
|
|
925
|
-
subst (
|
|
926
|
-
|
|
987
|
+
subst (search, replace) {
|
|
988
|
+
if (this === search)
|
|
989
|
+
return replace;
|
|
990
|
+
return this.impl.subst(search, replace);
|
|
927
991
|
}
|
|
928
992
|
|
|
929
993
|
_guess (options, preArgs = [], steps = 0) {
|
|
@@ -964,15 +1028,34 @@ class Alias extends Named {
|
|
|
964
1028
|
return this.impl._rski(options);
|
|
965
1029
|
}
|
|
966
1030
|
|
|
967
|
-
|
|
968
|
-
return this.outdated ? this.impl.
|
|
1031
|
+
_braced (first) {
|
|
1032
|
+
return this.outdated ? this.impl._braced(first) : false;
|
|
969
1033
|
}
|
|
970
1034
|
|
|
971
|
-
|
|
972
|
-
|
|
1035
|
+
_format (options, nargs) {
|
|
1036
|
+
const outdated = options.inventory
|
|
1037
|
+
? options.inventory[this.name] !== this
|
|
1038
|
+
: this.outdated;
|
|
1039
|
+
return outdated ? this.impl._format(options, nargs) : super._format(options, nargs);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
_declare (output, inventory, seen) {
|
|
1043
|
+
// only once
|
|
1044
|
+
if (seen.has(this))
|
|
1045
|
+
return;
|
|
1046
|
+
seen.add(this);
|
|
1047
|
+
|
|
1048
|
+
// topological order
|
|
1049
|
+
this.impl._declare(output, inventory, seen);
|
|
1050
|
+
|
|
1051
|
+
// only declare if in inventory and matches
|
|
1052
|
+
if (inventory[this.name] === this)
|
|
1053
|
+
output.push(this.name + '=' + this.impl.format({ terse: true, inventory }));
|
|
973
1054
|
}
|
|
974
1055
|
}
|
|
975
1056
|
|
|
1057
|
+
// ----- Expr* classes end here -----
|
|
1058
|
+
|
|
976
1059
|
// declare native combinators
|
|
977
1060
|
addNative('I', x => x);
|
|
978
1061
|
addNative('K', x => _ => x);
|
|
@@ -986,6 +1069,72 @@ addNative('+', x => y => z => y.apply(x.apply(y, z)), {
|
|
|
986
1069
|
apply: arg => arg instanceof Church ? new Church(arg.n + 1) : null
|
|
987
1070
|
});
|
|
988
1071
|
|
|
1072
|
+
// A global value meaning "lambda is used somewhere in this expression"
|
|
1073
|
+
// Can't be used (at least for now) to construct lambda expressions, or anything at all.
|
|
1074
|
+
// See also getSymbols().
|
|
1075
|
+
Expr.lambdaPlaceholder = new Native('->', x => x, {
|
|
1076
|
+
arity: 1,
|
|
1077
|
+
canonize: false,
|
|
1078
|
+
note: 'Lambda placeholder',
|
|
1079
|
+
apply: x => { throw new Error('Attempt to use a placeholder in expression') }
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
// utility functions dependent on Expr* classes, in alphabetical order
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
*
|
|
1086
|
+
* @param {Expr[]} inventory
|
|
1087
|
+
* @return {string[]}
|
|
1088
|
+
*/
|
|
1089
|
+
function declare (inventory) {
|
|
1090
|
+
const misnamed = Object.keys(inventory)
|
|
1091
|
+
.filter(s => !(inventory[s] instanceof Named && inventory[s].name === s))
|
|
1092
|
+
.map(s => s + ' = ' + inventory[s]);
|
|
1093
|
+
if (misnamed.length > 0)
|
|
1094
|
+
throw new Error('Inventory must be a hash of named terms with matching names: ' + misnamed.join(', '));
|
|
1095
|
+
|
|
1096
|
+
inventory = { ...inventory }; // shallow copy to avoid mutating input
|
|
1097
|
+
|
|
1098
|
+
// If any aliases mask native terms, those cannot be easily restored.
|
|
1099
|
+
// Moreover, subsequent terms may refer to both native term and and the conflicting alias.
|
|
1100
|
+
// Therefore, we will instead rename such aliases to something else
|
|
1101
|
+
// and only restore them at the end.
|
|
1102
|
+
const detour = [];
|
|
1103
|
+
let tmpId = 1;
|
|
1104
|
+
for (const name in native) {
|
|
1105
|
+
if (!(inventory[name] instanceof Alias))
|
|
1106
|
+
continue;
|
|
1107
|
+
while ('temp' + tmpId in inventory)
|
|
1108
|
+
tmpId++;
|
|
1109
|
+
const temp = 'temp' + tmpId;
|
|
1110
|
+
const orig = inventory[name];
|
|
1111
|
+
delete inventory[name];
|
|
1112
|
+
const masked = new Alias(temp, orig);
|
|
1113
|
+
for (const key in inventory)
|
|
1114
|
+
inventory[key] = inventory[key].subst(orig, masked) ?? inventory[key];
|
|
1115
|
+
|
|
1116
|
+
inventory[temp] = masked;
|
|
1117
|
+
detour.push([name, temp]);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// only want to declare aliases
|
|
1121
|
+
const terms = Object.values(inventory)
|
|
1122
|
+
.filter(s => s instanceof Alias)
|
|
1123
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1124
|
+
|
|
1125
|
+
const out = [];
|
|
1126
|
+
const seen = new Set();
|
|
1127
|
+
for (const term of terms)
|
|
1128
|
+
term._declare(out, inventory, seen);
|
|
1129
|
+
|
|
1130
|
+
for (const [name, temp] of detour) {
|
|
1131
|
+
out.push(name + '=' + temp); // rename
|
|
1132
|
+
out.push(temp + '='); // delete
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
return out;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
989
1138
|
function maybeLambda (args, expr, caps = {}) {
|
|
990
1139
|
const sym = expr.getSymbols();
|
|
991
1140
|
|
|
@@ -1019,6 +1168,10 @@ function naiveCanonize (expr) {
|
|
|
1019
1168
|
throw new Error('Failed to canonize expression: ' + expr);
|
|
1020
1169
|
}
|
|
1021
1170
|
|
|
1171
|
+
function nthvar (n) {
|
|
1172
|
+
return new FreeVar('abcdefgh'[n] ?? 'x' + n);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1022
1175
|
/**
|
|
1023
1176
|
*
|
|
1024
1177
|
* @param {Expr} expr
|
|
@@ -1078,18 +1231,4 @@ function * simplifyLambda (expr, options = {}, state = { steps: 0 }) {
|
|
|
1078
1231
|
yield { expr: canon.expr, steps: state.steps, comment: '(canonical)' };
|
|
1079
1232
|
}
|
|
1080
1233
|
|
|
1081
|
-
|
|
1082
|
-
return new FreeVar('abcdefgh'[n] ?? 'x' + n);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// A global value meaning "lambda is used somewhere in this expression"
|
|
1086
|
-
// Can't be used (at least for now) to construct lambda expressions, or anything at all.
|
|
1087
|
-
// See also getSymbols().
|
|
1088
|
-
Expr.lambdaPlaceholder = new Native('->', x => x, {
|
|
1089
|
-
arity: 1,
|
|
1090
|
-
canonize: false,
|
|
1091
|
-
note: 'Lambda placeholder',
|
|
1092
|
-
apply: x => { throw new Error('Attempt to use a placeholder in expression') }
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
module.exports = { Expr, App, FreeVar, Lambda, Native, Alias, Church, globalOptions, native };
|
|
1234
|
+
module.exports = { Expr, App, FreeVar, Lambda, Native, Alias, Church, globalOptions, native, declare };
|
package/lib/parser.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { Tokenizer, restrict } = require('./util');
|
|
6
|
-
const { globalOptions, Expr, App, FreeVar, Lambda, Native, Alias, Church, native } = require('./expr');
|
|
6
|
+
const { globalOptions, Expr, App, FreeVar, Lambda, Native, Alias, Church, native, declare } = require('./expr');
|
|
7
7
|
|
|
8
8
|
class Empty extends Expr {
|
|
9
9
|
apply (...args) {
|
|
@@ -55,11 +55,11 @@ class SKI {
|
|
|
55
55
|
/**
|
|
56
56
|
*
|
|
57
57
|
* @param {{
|
|
58
|
-
* allow
|
|
59
|
-
* numbers
|
|
60
|
-
* lambdas
|
|
61
|
-
* terms
|
|
62
|
-
* annotate
|
|
58
|
+
* allow?: string,
|
|
59
|
+
* numbers?: boolean,
|
|
60
|
+
* lambdas?: boolean,
|
|
61
|
+
* terms?: { [key: string]: Expr|string} | string[],
|
|
62
|
+
* annotate?: boolean,
|
|
63
63
|
* }} [options]
|
|
64
64
|
*/
|
|
65
65
|
constructor (options = {}) {
|
|
@@ -70,10 +70,14 @@ class SKI {
|
|
|
70
70
|
this.allow = new Set(Object.keys(this.known));
|
|
71
71
|
|
|
72
72
|
// Import terms, if any. Omit native ones
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
if (Array.isArray(options.terms))
|
|
74
|
+
this.bulkAdd(options.terms);
|
|
75
|
+
else if (options.terms) {
|
|
76
|
+
for (const name in options.terms) {
|
|
77
|
+
// Native terms already handled by allow
|
|
78
|
+
if (!options.terms[name].match(/^Native:/))
|
|
79
|
+
this.add(name, options.terms[name]);
|
|
80
|
+
}
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
// Finally, impose restrictions
|
|
@@ -107,12 +111,14 @@ class SKI {
|
|
|
107
111
|
throw new Error('add: term must be an Alias or a string (accompanied with an implementation)');
|
|
108
112
|
|
|
109
113
|
if (this.annotate && note === undefined && term.canonical)
|
|
110
|
-
note = term.canonical.
|
|
114
|
+
note = term.canonical.format({ terse: true, html: true, lambda: ['', ' ↦ ', ''] });
|
|
111
115
|
if (note !== undefined)
|
|
112
116
|
term.note = note;
|
|
113
117
|
|
|
114
|
-
this.known[
|
|
115
|
-
|
|
118
|
+
if (this.known[term.name])
|
|
119
|
+
this.known[term.name].outdated = true;
|
|
120
|
+
this.known[term.name] = term;
|
|
121
|
+
this.allow.add(term.name);
|
|
116
122
|
|
|
117
123
|
return this;
|
|
118
124
|
}
|
|
@@ -125,6 +131,28 @@ class SKI {
|
|
|
125
131
|
return this;
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
/**
|
|
135
|
+
* @desc Declare and remove multiple terms at once
|
|
136
|
+
* term=impl adds term
|
|
137
|
+
* term= removes term
|
|
138
|
+
* @param {string[]]} list
|
|
139
|
+
* @return {SKI} chainable
|
|
140
|
+
*/
|
|
141
|
+
bulkAdd (list) {
|
|
142
|
+
for (const item of list) {
|
|
143
|
+
const m = item.match(/^([A-Z]|[a-z][a-z_0-9]*)\s*=\s*(.*)$/s);
|
|
144
|
+
// TODO check all declarations before applying any (but we might need earlier terms for parsing later ones)
|
|
145
|
+
if (!m)
|
|
146
|
+
throw new Error('bulkAdd: invalid declaration: ' + item);
|
|
147
|
+
if (m[2] === '')
|
|
148
|
+
this.remove(m[1]);
|
|
149
|
+
else
|
|
150
|
+
this.add(m[1], this.parse(m[2]));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
128
156
|
/**
|
|
129
157
|
* Restrict the interpreter to given terms. Terms prepended with '+' will be added
|
|
130
158
|
* and terms preceeded with '-' will be removed.
|
|
@@ -183,6 +211,15 @@ class SKI {
|
|
|
183
211
|
return out;
|
|
184
212
|
}
|
|
185
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Export term declarations for use in bulkAdd().
|
|
216
|
+
* @returns {string[]}
|
|
217
|
+
*/
|
|
218
|
+
declare () {
|
|
219
|
+
// TODO accept argument to declare specific terms only
|
|
220
|
+
return declare(this.getTerms());
|
|
221
|
+
}
|
|
222
|
+
|
|
186
223
|
/**
|
|
187
224
|
*
|
|
188
225
|
* @param {string} source
|
|
@@ -290,11 +327,12 @@ class SKI {
|
|
|
290
327
|
|
|
291
328
|
toJSON () {
|
|
292
329
|
return {
|
|
330
|
+
version: '1.1.1', // set to incremented package.json version whenever SKI serialization changes
|
|
293
331
|
allow: this.showRestrict('+'),
|
|
294
332
|
numbers: this.hasNumbers,
|
|
295
333
|
lambdas: this.hasLambdas,
|
|
296
|
-
terms: this.getTerms(),
|
|
297
334
|
annotate: this.annotate,
|
|
335
|
+
terms: this.declare(),
|
|
298
336
|
}
|
|
299
337
|
}
|
|
300
338
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dallaylaen/ski-interpreter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
package/types/lib/expr.d.ts
CHANGED
|
@@ -65,13 +65,13 @@ export class Expr {
|
|
|
65
65
|
* @return {{
|
|
66
66
|
* normal: boolean,
|
|
67
67
|
* steps: number,
|
|
68
|
-
* expr
|
|
69
|
-
* arity
|
|
70
|
-
* proper
|
|
71
|
-
* discard
|
|
72
|
-
* duplicate
|
|
73
|
-
* skip
|
|
74
|
-
* dup
|
|
68
|
+
* expr?: Expr,
|
|
69
|
+
* arity?: number,
|
|
70
|
+
* proper?: boolean,
|
|
71
|
+
* discard?: boolean,
|
|
72
|
+
* duplicate?: boolean,
|
|
73
|
+
* skip?: Set<number>,
|
|
74
|
+
* dup?: Set<number>,
|
|
75
75
|
* }}
|
|
76
76
|
*/
|
|
77
77
|
guess(options?: {
|
|
@@ -80,13 +80,13 @@ export class Expr {
|
|
|
80
80
|
}): {
|
|
81
81
|
normal: boolean;
|
|
82
82
|
steps: number;
|
|
83
|
-
expr
|
|
84
|
-
arity
|
|
85
|
-
proper
|
|
86
|
-
discard
|
|
87
|
-
duplicate
|
|
88
|
-
skip
|
|
89
|
-
dup
|
|
83
|
+
expr?: Expr;
|
|
84
|
+
arity?: number;
|
|
85
|
+
proper?: boolean;
|
|
86
|
+
discard?: boolean;
|
|
87
|
+
duplicate?: boolean;
|
|
88
|
+
skip?: Set<number>;
|
|
89
|
+
dup?: Set<number>;
|
|
90
90
|
};
|
|
91
91
|
_guess(options: any, preArgs?: any[], steps?: number): any;
|
|
92
92
|
_aslist(): this[];
|
|
@@ -96,23 +96,23 @@ export class Expr {
|
|
|
96
96
|
* up to the provided computation steps limit,
|
|
97
97
|
* in decreasing weight order.
|
|
98
98
|
* @param {{
|
|
99
|
-
* max
|
|
100
|
-
* maxArgs
|
|
101
|
-
* varGen
|
|
102
|
-
* steps
|
|
103
|
-
* html
|
|
104
|
-
* latin
|
|
99
|
+
* max?: number,
|
|
100
|
+
* maxArgs?: number,
|
|
101
|
+
* varGen?: function(void): FreeVar,
|
|
102
|
+
* steps?: number,
|
|
103
|
+
* html?: boolean,
|
|
104
|
+
* latin?: number,
|
|
105
105
|
* }} options
|
|
106
106
|
* @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
|
|
107
107
|
* @return {IterableIterator<{expr: Expr, steps: number?, comment: string?}>}
|
|
108
108
|
*/
|
|
109
109
|
lambdify(options?: {
|
|
110
|
-
max
|
|
111
|
-
maxArgs
|
|
112
|
-
varGen
|
|
113
|
-
steps
|
|
114
|
-
html
|
|
115
|
-
latin
|
|
110
|
+
max?: number;
|
|
111
|
+
maxArgs?: number;
|
|
112
|
+
varGen?: (arg0: void) => FreeVar;
|
|
113
|
+
steps?: number;
|
|
114
|
+
html?: boolean;
|
|
115
|
+
latin?: number;
|
|
116
116
|
}): IterableIterator<{
|
|
117
117
|
expr: Expr;
|
|
118
118
|
steps: number | null;
|
|
@@ -130,14 +130,6 @@ export class Expr {
|
|
|
130
130
|
expr: Expr;
|
|
131
131
|
steps: number;
|
|
132
132
|
}>;
|
|
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
133
|
_rski(options: any): this;
|
|
142
134
|
/**
|
|
143
135
|
* Apply self to list of given args.
|
|
@@ -147,13 +139,17 @@ export class Expr {
|
|
|
147
139
|
*/
|
|
148
140
|
reduce(args: Expr[]): Expr | null;
|
|
149
141
|
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
142
|
+
* Replace all instances of plug in the expression with value and return the resulting expression,
|
|
143
|
+
* or null if no changes could be made.
|
|
144
|
+
* Lambda terms and applications will never match if used as plug
|
|
145
|
+
* as they are impossible co compare without extensive computations.
|
|
146
|
+
* Typically used on variables but can also be applied to other terms, e.g. aliases.
|
|
147
|
+
* See also Expr.replace().
|
|
148
|
+
* @param {Expr} search
|
|
149
|
+
* @param {Expr} replace
|
|
150
|
+
* @return {Expr|null}
|
|
151
|
+
*/
|
|
152
|
+
subst(search: Expr, replace: Expr): Expr | null;
|
|
157
153
|
/**
|
|
158
154
|
* @desc iterate one step of calculation in accordance with known rules.
|
|
159
155
|
* @return {{expr: Expr, steps: number, changed: boolean}}
|
|
@@ -207,24 +203,64 @@ export class Expr {
|
|
|
207
203
|
*/
|
|
208
204
|
expect(expected: Expr, comment?: string): void;
|
|
209
205
|
/**
|
|
210
|
-
* @
|
|
211
|
-
*
|
|
206
|
+
* @desc Returns string representation of the expression.
|
|
207
|
+
* Same as format() without options.
|
|
208
|
+
* @return {string}
|
|
212
209
|
*/
|
|
213
|
-
toString(
|
|
214
|
-
terse: boolean | null;
|
|
215
|
-
html: boolean | null;
|
|
216
|
-
}): string;
|
|
210
|
+
toString(): string;
|
|
217
211
|
/**
|
|
218
212
|
* @desc Whether the expression needs parentheses when printed.
|
|
219
213
|
* @param {boolean} [first] - whether this is the first term in a sequence
|
|
220
214
|
* @return {boolean}
|
|
221
215
|
*/
|
|
222
|
-
|
|
216
|
+
_braced(first?: boolean): boolean;
|
|
217
|
+
_unspaced(arg: any): boolean;
|
|
223
218
|
/**
|
|
219
|
+
* @desc Stringify the expression with fancy formatting options.
|
|
220
|
+
* Said options mostly include wrappers around various constructs in form of ['(', ')'],
|
|
221
|
+
* as well as terse and html flags that set up the defaults.
|
|
222
|
+
* Format without options is equivalent to toString() and can be parsed back.
|
|
223
|
+
*
|
|
224
|
+
* @param {Object} [options] - formatting options
|
|
225
|
+
* @param {boolean} [options.terse] - whether to use terse formatting (omitting unnecessary spaces and parentheses)
|
|
226
|
+
* @param {boolean} [options.html] - whether to default to HTML tags & entities
|
|
227
|
+
* @param {[string, string]} [options.brackets] - wrappers for application arguments, typically ['(', ')']
|
|
228
|
+
* @param {[string, string]} [options.var] - wrappers for variable names
|
|
229
|
+
* (will default to <var> and </var> in html mode)
|
|
230
|
+
* @param {[string, string, string]} [options.lambda] - wrappers for lambda abstractions, e.g. ['λ', '.', '']
|
|
231
|
+
* where the middle string is placed between argument and body
|
|
232
|
+
* default is ['', '->', ''] or ['', '->', ''] for html
|
|
233
|
+
* @param {[string, string]} [options.around] - wrappers around (sub-)expressions.
|
|
234
|
+
* individual applications will not be wrapped, i.e. (a b c) but not ((a b) c)
|
|
235
|
+
* @param {[string, string]} [options.redex] - wrappers around the starting term(s) that have enough arguments to be reduced
|
|
236
|
+
* @param {Object<string, Expr>} [options.inventory] - if given, output aliases in the set as their names
|
|
237
|
+
* and any other aliases as the expansion of their definitions.
|
|
238
|
+
* The default is a cryptic and fragile mechanism dependent on a hidden mutable property.
|
|
239
|
+
* @returns {string}
|
|
240
|
+
*
|
|
241
|
+
* @example foo.format() // equivalent to foo.toString()
|
|
242
|
+
* @example foo.format({terse: false}) // spell out all parentheses
|
|
243
|
+
* @example foo.format({html: true}) // use HTML tags and entities
|
|
244
|
+
* @example foo.format({ around: ['(', ')'], brackets: ['', ''], lambda: ['(', '->', ')'] }) // lisp style, still back-parsable
|
|
245
|
+
* @exapmle foo.format({ lambda: ['λ', '.', ''] }) // pretty-print for the math department
|
|
246
|
+
* @example foo.format({ lambda: ['', '=>', ''], terse: false }) // make it javascript
|
|
247
|
+
* @example foo.format({ inventory: { T } }) // use T as a named term, expand all others
|
|
224
248
|
*
|
|
225
|
-
* @return {string}
|
|
226
249
|
*/
|
|
227
|
-
|
|
250
|
+
format(options?: {
|
|
251
|
+
terse?: boolean;
|
|
252
|
+
html?: boolean;
|
|
253
|
+
brackets?: [string, string];
|
|
254
|
+
var?: [string, string];
|
|
255
|
+
lambda?: [string, string, string];
|
|
256
|
+
around?: [string, string];
|
|
257
|
+
redex?: [string, string];
|
|
258
|
+
inventory?: {
|
|
259
|
+
[x: string]: Expr;
|
|
260
|
+
};
|
|
261
|
+
}): string;
|
|
262
|
+
_format(options: any, nargs: any): void;
|
|
263
|
+
_declare(output: any, inventory: any, seen: any): void;
|
|
228
264
|
}
|
|
229
265
|
export namespace Expr {
|
|
230
266
|
let lambdaPlaceholder: Native;
|
|
@@ -245,8 +281,7 @@ export class App extends Expr {
|
|
|
245
281
|
_firstVar(): any;
|
|
246
282
|
apply(...args: any[]): App;
|
|
247
283
|
expand(): any;
|
|
248
|
-
|
|
249
|
-
subst(plug: any, value: any): any;
|
|
284
|
+
subst(search: any, replace: any): any;
|
|
250
285
|
/**
|
|
251
286
|
* @return {{expr: Expr, steps: number}}
|
|
252
287
|
*/
|
|
@@ -259,14 +294,13 @@ export class App extends Expr {
|
|
|
259
294
|
_aslist(): any[];
|
|
260
295
|
equals(other: any): any;
|
|
261
296
|
contains(other: any): any;
|
|
262
|
-
|
|
263
|
-
|
|
297
|
+
_braced(first: any): boolean;
|
|
298
|
+
_format(options: any, nargs: any): any;
|
|
299
|
+
_unspaced(arg: any): any;
|
|
264
300
|
}
|
|
265
301
|
export class FreeVar extends Named {
|
|
266
302
|
constructor(name: any);
|
|
267
303
|
id: number;
|
|
268
|
-
subst(plug: any, value: any): any;
|
|
269
|
-
toString(opt?: {}): string;
|
|
270
304
|
}
|
|
271
305
|
export class Lambda extends Expr {
|
|
272
306
|
/**
|
|
@@ -278,13 +312,12 @@ export class Lambda extends Expr {
|
|
|
278
312
|
impl: Expr;
|
|
279
313
|
arity: number;
|
|
280
314
|
reduce(input: any): Expr;
|
|
281
|
-
subst(
|
|
315
|
+
subst(search: any, replace: any): Lambda;
|
|
282
316
|
expand(): Lambda;
|
|
283
|
-
renameVars(seq: any): Lambda;
|
|
284
317
|
_rski(options: any): any;
|
|
285
318
|
equals(other: any): boolean;
|
|
286
|
-
|
|
287
|
-
|
|
319
|
+
_format(options: any, nargs: any): string;
|
|
320
|
+
_braced(first: any): boolean;
|
|
288
321
|
}
|
|
289
322
|
/**
|
|
290
323
|
* @typedef {function(Expr): Expr | AnyArity} AnyArity
|
|
@@ -334,7 +367,7 @@ export class Alias extends Named {
|
|
|
334
367
|
proper: any;
|
|
335
368
|
terminal: any;
|
|
336
369
|
canonical: any;
|
|
337
|
-
subst(
|
|
370
|
+
subst(search: any, replace: any): any;
|
|
338
371
|
/**
|
|
339
372
|
*
|
|
340
373
|
* @return {{expr: Expr, steps: number}}
|
|
@@ -346,8 +379,8 @@ export class Alias extends Named {
|
|
|
346
379
|
reduce(args: any): Expr;
|
|
347
380
|
equals(other: any): any;
|
|
348
381
|
_rski(options: any): Expr;
|
|
349
|
-
|
|
350
|
-
|
|
382
|
+
_braced(first: any): boolean;
|
|
383
|
+
_format(options: any, nargs: any): string | void;
|
|
351
384
|
}
|
|
352
385
|
export class Church extends Native {
|
|
353
386
|
constructor(n: any);
|
|
@@ -361,6 +394,12 @@ export namespace globalOptions {
|
|
|
361
394
|
let maxArgs: number;
|
|
362
395
|
}
|
|
363
396
|
export const native: {};
|
|
397
|
+
/**
|
|
398
|
+
*
|
|
399
|
+
* @param {Expr[]} inventory
|
|
400
|
+
* @return {string[]}
|
|
401
|
+
*/
|
|
402
|
+
export function declare(inventory: Expr[]): string[];
|
|
364
403
|
declare class Named extends Expr {
|
|
365
404
|
/**
|
|
366
405
|
* @desc a constant named 'name'
|
|
@@ -368,6 +407,6 @@ declare class Named extends Expr {
|
|
|
368
407
|
*/
|
|
369
408
|
constructor(name: string);
|
|
370
409
|
name: string;
|
|
371
|
-
|
|
410
|
+
_format(options: any, nargs: any): string;
|
|
372
411
|
}
|
|
373
412
|
export {};
|
package/types/lib/parser.d.ts
CHANGED
|
@@ -2,21 +2,21 @@ export class SKI {
|
|
|
2
2
|
/**
|
|
3
3
|
*
|
|
4
4
|
* @param {{
|
|
5
|
-
* allow
|
|
6
|
-
* numbers
|
|
7
|
-
* lambdas
|
|
8
|
-
* terms
|
|
9
|
-
* annotate
|
|
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
|
|
14
|
-
numbers
|
|
15
|
-
lambdas
|
|
16
|
-
terms
|
|
13
|
+
allow?: string;
|
|
14
|
+
numbers?: boolean;
|
|
15
|
+
lambdas?: boolean;
|
|
16
|
+
terms?: {
|
|
17
17
|
[key: string]: Expr | string;
|
|
18
|
-
} |
|
|
19
|
-
annotate
|
|
18
|
+
} | string[];
|
|
19
|
+
annotate?: boolean;
|
|
20
20
|
});
|
|
21
21
|
annotate: boolean;
|
|
22
22
|
known: {};
|
|
@@ -35,6 +35,14 @@ export class SKI {
|
|
|
35
35
|
fast: boolean | null;
|
|
36
36
|
}], note?: string): SKI;
|
|
37
37
|
maybeAdd(name: any, impl: any): this;
|
|
38
|
+
/**
|
|
39
|
+
* @desc Declare and remove multiple terms at once
|
|
40
|
+
* term=impl adds term
|
|
41
|
+
* term= removes term
|
|
42
|
+
* @param {string[]]} list
|
|
43
|
+
* @return {SKI} chainable
|
|
44
|
+
*/
|
|
45
|
+
bulkAdd(list: any): SKI;
|
|
38
46
|
/**
|
|
39
47
|
* Restrict the interpreter to given terms. Terms prepended with '+' will be added
|
|
40
48
|
* and terms preceeded with '-' will be removed.
|
|
@@ -65,6 +73,11 @@ export class SKI {
|
|
|
65
73
|
getTerms(): {
|
|
66
74
|
[key: string]: Native | Alias;
|
|
67
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Export term declarations for use in bulkAdd().
|
|
78
|
+
* @returns {string[]}
|
|
79
|
+
*/
|
|
80
|
+
declare(): string[];
|
|
68
81
|
/**
|
|
69
82
|
*
|
|
70
83
|
* @param {string} source
|
|
@@ -94,13 +107,12 @@ export class SKI {
|
|
|
94
107
|
allow: string | null;
|
|
95
108
|
}): Expr;
|
|
96
109
|
toJSON(): {
|
|
110
|
+
version: string;
|
|
97
111
|
allow: string;
|
|
98
112
|
numbers: boolean;
|
|
99
113
|
lambdas: boolean;
|
|
100
|
-
terms: {
|
|
101
|
-
[key: string]: Native | Alias;
|
|
102
|
-
};
|
|
103
114
|
annotate: boolean;
|
|
115
|
+
terms: string[];
|
|
104
116
|
};
|
|
105
117
|
}
|
|
106
118
|
export namespace SKI {
|