@dallaylaen/ski-interpreter 2.2.0 → 2.2.1
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 +14 -0
- package/README.md +25 -4
- package/lib/ski-interpreter.cjs.js +160 -80
- package/lib/ski-interpreter.cjs.js.map +2 -2
- package/lib/ski-interpreter.esm.js +160 -80
- package/lib/ski-interpreter.esm.js.map +2 -2
- package/lib/ski-interpreter.min.js +4 -0
- package/lib/ski-interpreter.min.js.map +7 -0
- package/lib/ski-quest.min.js +4 -0
- package/lib/ski-quest.min.js.map +7 -0
- package/package.json +8 -4
- package/types/src/expr.d.ts +134 -48
- package/types/src/extras.d.ts +17 -1
- package/types/src/internal.d.ts +23 -10
- package/types/src/parser.d.ts +40 -17
- package/types/src/quest.d.ts +43 -41
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ 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
|
+
## [2.2.1] - 2026-02-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `Expr.diag()` outputs an expression as an indented tree (breadth-first) with class names and variables labeled for deduplication.
|
|
13
|
+
- `lib/ski-quest.min.js` bundle to create quest pages.
|
|
14
|
+
- The [playground](https://dallaylaen.github.io/ski-interpreter/playground.html) gets history!
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Greatly improved type definitions.
|
|
19
|
+
- Quests calculate solution complexity via `fold()`.
|
|
20
|
+
|
|
8
21
|
## [2.2.0] - 2026-02-14
|
|
9
22
|
|
|
10
23
|
### BREAKING CHANGES
|
|
@@ -20,6 +33,7 @@ that give the initial expression when applied
|
|
|
20
33
|
from left to right: `((a, b), (c, d)) => [a, b, (c, d)]`
|
|
21
34
|
- Parser: Support for chained assignments (`'foo=bar=baz'` expressions)
|
|
22
35
|
- Parser: Support for multi-line comment syntax (`/* comments */`)
|
|
36
|
+
- `SKI_REPL=1 node -r @dallaylaen/ski-interpreter` will now start a REPL with the `SKI` class available globally.
|
|
23
37
|
|
|
24
38
|
### Changed
|
|
25
39
|
|
package/README.md
CHANGED
|
@@ -68,16 +68,19 @@ all free variables are bound.
|
|
|
68
68
|
This page contains small tasks of increasing complexity.
|
|
69
69
|
Each task requires the user to build a combinator with specific properties.
|
|
70
70
|
|
|
71
|
-
# CLI
|
|
72
|
-
|
|
73
|
-
REPL comes with the package as [bin/ski.js](bin/ski.js).
|
|
74
|
-
|
|
75
71
|
# Installation
|
|
76
72
|
|
|
77
73
|
```bash
|
|
78
74
|
npm install @dallaylaen/ski-interpreter
|
|
79
75
|
```
|
|
80
76
|
|
|
77
|
+
# CLI
|
|
78
|
+
|
|
79
|
+
REPL comes with the package as [bin/ski.js](bin/ski.js).
|
|
80
|
+
|
|
81
|
+
Running `SKI_REPL=1 node -r @dallaylaen/ski-interpreter/bin/ski.js`
|
|
82
|
+
will start a node shell with the `SKI` class available as a global variable.
|
|
83
|
+
|
|
81
84
|
# Usage
|
|
82
85
|
|
|
83
86
|
## A minimal example
|
|
@@ -149,6 +152,10 @@ const lambdaSteps = [...skiExpr.toLambda()];
|
|
|
149
152
|
The `format` methods of the `Expr` class supports
|
|
150
153
|
a number of options, see [the source code](src/expr.js) for details.
|
|
151
154
|
|
|
155
|
+
`expr.diag()` will instead output indented
|
|
156
|
+
expression tree (breadth-first) with class information
|
|
157
|
+
and variables labeled for disambiguation.
|
|
158
|
+
|
|
152
159
|
## Variable scoping
|
|
153
160
|
|
|
154
161
|
By default, parsed free variables are global and equal to any other variable with the same name.
|
|
@@ -183,6 +190,9 @@ expr.traverse(e => e.equals(SKI.I) ? SKI.S.apply(SKI.K, SKI.K) : null);
|
|
|
183
190
|
// replaces all I's with S K K
|
|
184
191
|
// here a returned `Expr` object replaces the subexpression,
|
|
185
192
|
// whereas `null` means "leave it alone and descend if possible"
|
|
193
|
+
|
|
194
|
+
expr.fold(0, (acc, e) => acc + (e.equals(SKI.K) ? acc+1 : acc));
|
|
195
|
+
// counts the number of K's in the expression
|
|
186
196
|
```
|
|
187
197
|
|
|
188
198
|
## Test cases
|
|
@@ -209,6 +219,17 @@ q.check('K(K(y x))') // nope! the variable scopes won't match
|
|
|
209
219
|
|
|
210
220
|
See [quest page data](docs/quest-data/) for more examples.
|
|
211
221
|
|
|
222
|
+
# Package contents
|
|
223
|
+
|
|
224
|
+
* `lib/ski-interpreter.cjs.js` - main entry point for Node.js;
|
|
225
|
+
* `lib/ski-interpreter.esm.js` - main entry point for ES modules;
|
|
226
|
+
* `lib/ski-interpreter.min.js` - minified version for browsers;
|
|
227
|
+
* `lib/ski-quest.min.js` - script with the interpreter
|
|
228
|
+
plus `QuestBox`, `QuestChapter`, and `QuestPage` classes
|
|
229
|
+
for building interactive quest pages from JSON-encoded quest data;
|
|
230
|
+
* `bin/ski.js` - a CLI REPL;
|
|
231
|
+
* `types` - TypeScript type definitions.
|
|
232
|
+
|
|
212
233
|
# Thanks
|
|
213
234
|
|
|
214
235
|
* [@ivanaxe](https://github.com/ivanaxe) for luring me into [icfpc 2011](http://icfpc2011.blogspot.com/2011/06/task-description-contest-starts-now.html) where I was introduced to combinators.
|
|
@@ -59,24 +59,34 @@ var require_internal = __commonJS({
|
|
|
59
59
|
}
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
|
-
var
|
|
62
|
+
var TraverseControl = class {
|
|
63
63
|
/**
|
|
64
|
+
* @desc A wrapper for values returned by fold/traverse callbacks
|
|
65
|
+
* which instructs the traversal to alter its behavior while
|
|
66
|
+
* retaining the value in question.
|
|
67
|
+
*
|
|
68
|
+
* This class is instantiated internally be `SKI.control.*` functions,
|
|
69
|
+
* and is not intended to be used directly by client code.
|
|
70
|
+
*
|
|
64
71
|
* @template T
|
|
65
72
|
* @param {T} value
|
|
66
|
-
* @param {
|
|
73
|
+
* @param {function(T): TraverseControl<T>} decoration
|
|
67
74
|
*/
|
|
68
|
-
constructor(value,
|
|
75
|
+
constructor(value, decoration) {
|
|
69
76
|
this.value = value;
|
|
70
|
-
this.
|
|
77
|
+
this.decoration = decoration;
|
|
71
78
|
}
|
|
72
79
|
};
|
|
73
80
|
function unwrap(value) {
|
|
74
|
-
if (value instanceof
|
|
75
|
-
return [value.value ?? void 0, value.
|
|
81
|
+
if (value instanceof TraverseControl)
|
|
82
|
+
return [value.value ?? void 0, value.decoration];
|
|
76
83
|
return [value ?? void 0, void 0];
|
|
77
84
|
}
|
|
78
|
-
function prepareWrapper(
|
|
79
|
-
|
|
85
|
+
function prepareWrapper(label) {
|
|
86
|
+
const fun = (value) => new TraverseControl(value, fun);
|
|
87
|
+
fun.label = label;
|
|
88
|
+
fun.toString = () => "TraverseControl::" + label;
|
|
89
|
+
return fun;
|
|
80
90
|
}
|
|
81
91
|
module2.exports = { Tokenizer, restrict, unwrap, prepareWrapper };
|
|
82
92
|
}
|
|
@@ -94,6 +104,7 @@ var require_expr = __commonJS({
|
|
|
94
104
|
var control = {
|
|
95
105
|
descend: prepareWrapper("descend"),
|
|
96
106
|
prune: prepareWrapper("prune"),
|
|
107
|
+
redo: prepareWrapper("redo"),
|
|
97
108
|
stop: prepareWrapper("stop")
|
|
98
109
|
};
|
|
99
110
|
var Expr = class _Expr {
|
|
@@ -109,7 +120,7 @@ var require_expr = __commonJS({
|
|
|
109
120
|
* env?: { [key: string]: Expr },
|
|
110
121
|
* src?: string,
|
|
111
122
|
* parser: object,
|
|
112
|
-
* }} [context]
|
|
123
|
+
* }} [context]
|
|
113
124
|
*/
|
|
114
125
|
constructor() {
|
|
115
126
|
if (new.target === _Expr)
|
|
@@ -137,15 +148,21 @@ var require_expr = __commonJS({
|
|
|
137
148
|
return e.impl.expand();
|
|
138
149
|
}) ?? this;
|
|
139
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* @desc Returns true if the expression contains only free variables and applications, false otherwise.
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
140
155
|
freeOnly() {
|
|
141
156
|
return !this.any((e) => !(e instanceof FreeVar || e instanceof App));
|
|
142
157
|
}
|
|
143
158
|
/**
|
|
144
159
|
* @desc Traverse the expression tree, applying change() to each node.
|
|
145
160
|
* If change() returns an Expr, the node is replaced with that value.
|
|
146
|
-
* Otherwise, the node is
|
|
161
|
+
* Otherwise, the node is descended further (if applicable)
|
|
147
162
|
* or left unchanged.
|
|
148
163
|
*
|
|
164
|
+
* The traversal order is leftmost-outermost (LO), i.e. the same order as reduction steps are taken.
|
|
165
|
+
*
|
|
149
166
|
* Returns null if no changes were made, or the new expression otherwise.
|
|
150
167
|
*
|
|
151
168
|
* @param {(e:Expr) => (Expr|null)} change
|
|
@@ -179,18 +196,25 @@ var require_expr = __commonJS({
|
|
|
179
196
|
* @experimental
|
|
180
197
|
* @template T
|
|
181
198
|
* @param {T} initial
|
|
182
|
-
* @param {(acc: T, expr: Expr) =>
|
|
199
|
+
* @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
|
|
183
200
|
* @returns {T}
|
|
184
201
|
*/
|
|
185
202
|
fold(initial, combine) {
|
|
186
203
|
const [value, _] = unwrap(this._fold(initial, combine));
|
|
187
204
|
return value ?? initial;
|
|
188
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* @template T
|
|
208
|
+
* @param {T} initial
|
|
209
|
+
* @param {(acc: T, expr: Expr) => TraverseValue<T>} combine
|
|
210
|
+
* @returns {TraverseValue<T>}
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
189
213
|
_fold(initial, combine) {
|
|
190
214
|
return combine(initial, this);
|
|
191
215
|
}
|
|
192
216
|
/**
|
|
193
|
-
* @desc rough estimate of the complexity
|
|
217
|
+
* @desc rough estimate of the term's complexity
|
|
194
218
|
* @return {number}
|
|
195
219
|
*/
|
|
196
220
|
weight() {
|
|
@@ -232,7 +256,15 @@ var require_expr = __commonJS({
|
|
|
232
256
|
* @param {{max: number, maxArgs: number, index: number}} options
|
|
233
257
|
* @param {FreeVar[]} preArgs
|
|
234
258
|
* @param {number} steps
|
|
235
|
-
* @returns {{
|
|
259
|
+
* @returns {{
|
|
260
|
+
* normal: boolean,
|
|
261
|
+
* steps: number,
|
|
262
|
+
* expr?: Expr,
|
|
263
|
+
* arity?: number,
|
|
264
|
+
* skip?: Set<number>,
|
|
265
|
+
* dup?: Set<number>,
|
|
266
|
+
* duplicate, discard, proper: boolean
|
|
267
|
+
* }
|
|
236
268
|
* @private
|
|
237
269
|
*/
|
|
238
270
|
_infer(options, preArgs = [], steps = 0) {
|
|
@@ -289,7 +321,7 @@ var require_expr = __commonJS({
|
|
|
289
321
|
* latin?: number,
|
|
290
322
|
* }} options
|
|
291
323
|
* @param {number} [maxWeight] - maximum allowed weight of terms in the sequence
|
|
292
|
-
* @return {IterableIterator<{expr: Expr, steps
|
|
324
|
+
* @return {IterableIterator<{expr: Expr, steps?: number, comment?: string}>}
|
|
293
325
|
*/
|
|
294
326
|
*toLambda(options = {}) {
|
|
295
327
|
const expr = this.traverse((e) => {
|
|
@@ -382,7 +414,7 @@ var require_expr = __commonJS({
|
|
|
382
414
|
* @desc Run uninterrupted sequence of step() applications
|
|
383
415
|
* until the expression is irreducible, or max number of steps is reached.
|
|
384
416
|
* Default number of steps = 1000.
|
|
385
|
-
* @param {{max
|
|
417
|
+
* @param {{max?: number, steps?: number, throw?: boolean}|Expr} [opt]
|
|
386
418
|
* @param {Expr} args
|
|
387
419
|
* @return {{expr: Expr, steps: number, final: boolean}}
|
|
388
420
|
*/
|
|
@@ -411,7 +443,7 @@ var require_expr = __commonJS({
|
|
|
411
443
|
/**
|
|
412
444
|
* Execute step() while possible, yielding a brief description of events after each step.
|
|
413
445
|
* Mnemonics: like run() but slower.
|
|
414
|
-
* @param {{max
|
|
446
|
+
* @param {{max?: number}} options
|
|
415
447
|
* @return {IterableIterator<{final: boolean, expr: Expr, steps: number}>}
|
|
416
448
|
*/
|
|
417
449
|
*walk(options = {}) {
|
|
@@ -586,6 +618,42 @@ var require_expr = __commonJS({
|
|
|
586
618
|
_format(options, nargs) {
|
|
587
619
|
throw new Error("No _format() method defined in class " + this.constructor.name);
|
|
588
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* @desc Returns a string representation of the expression tree, with indentation to show structure.
|
|
623
|
+
*
|
|
624
|
+
* Applications are flattened to avoid excessive nesting.
|
|
625
|
+
* Variables include ids to distinguish different instances of the same variable name.
|
|
626
|
+
*
|
|
627
|
+
* May be useful for debugging.
|
|
628
|
+
*
|
|
629
|
+
* @returns {string}
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* > console.log(ski.parse('C 5 x (x->x x)').diag())
|
|
633
|
+
* App:
|
|
634
|
+
* Native: C
|
|
635
|
+
* Church: 5
|
|
636
|
+
* FreeVar: x[53]
|
|
637
|
+
* Lambda (x[54]):
|
|
638
|
+
* App:
|
|
639
|
+
* FreeVar: x[54]
|
|
640
|
+
* FreeVar: x[54]
|
|
641
|
+
*/
|
|
642
|
+
diag() {
|
|
643
|
+
const rec = (e, indent) => {
|
|
644
|
+
if (e instanceof App)
|
|
645
|
+
return [indent + "App:", ...e.unroll().flatMap((s) => rec(s, indent + " "))];
|
|
646
|
+
if (e instanceof Lambda)
|
|
647
|
+
return [`${indent}Lambda (${e.arg}[${e.arg.id}]):`, ...rec(e.impl, indent + " ")];
|
|
648
|
+
if (e instanceof Alias)
|
|
649
|
+
return [`Alias (${e.name}):`, ...rec(e.impl, indent + " ")];
|
|
650
|
+
if (e instanceof FreeVar)
|
|
651
|
+
return [`${indent}FreeVar: ${e.name}[${e.id}]`];
|
|
652
|
+
return [`${indent}${e.constructor.name}: ${e}`];
|
|
653
|
+
};
|
|
654
|
+
const out = rec(this, "");
|
|
655
|
+
return out.join("\n");
|
|
656
|
+
}
|
|
589
657
|
/**
|
|
590
658
|
* @desc Convert the expression to a JSON-serializable format.
|
|
591
659
|
* @returns {string}
|
|
@@ -662,15 +730,15 @@ var require_expr = __commonJS({
|
|
|
662
730
|
}
|
|
663
731
|
_fold(initial, combine) {
|
|
664
732
|
const [value = initial, action = "descend"] = unwrap(combine(initial, this));
|
|
665
|
-
if (action ===
|
|
733
|
+
if (action === control.prune)
|
|
666
734
|
return value;
|
|
667
|
-
if (action ===
|
|
735
|
+
if (action === control.stop)
|
|
668
736
|
return control.stop(value);
|
|
669
737
|
const [fValue = value, fAction = "descend"] = unwrap(this.fun._fold(value, combine));
|
|
670
|
-
if (fAction ===
|
|
738
|
+
if (fAction === control.stop)
|
|
671
739
|
return control.stop(fValue);
|
|
672
740
|
const [aValue = fValue, aAction = "descend"] = unwrap(this.arg._fold(fValue, combine));
|
|
673
|
-
if (aAction ===
|
|
741
|
+
if (aAction === control.stop)
|
|
674
742
|
return control.stop(aValue);
|
|
675
743
|
return aValue;
|
|
676
744
|
}
|
|
@@ -714,10 +782,6 @@ var require_expr = __commonJS({
|
|
|
714
782
|
unroll() {
|
|
715
783
|
return [...this.fun.unroll(), this.arg];
|
|
716
784
|
}
|
|
717
|
-
/**
|
|
718
|
-
* @desc Convert the expression to SKI combinatory logic
|
|
719
|
-
* @return {Expr}
|
|
720
|
-
*/
|
|
721
785
|
_rski(options) {
|
|
722
786
|
if (options.steps >= options.max)
|
|
723
787
|
return this;
|
|
@@ -915,12 +979,12 @@ var require_expr = __commonJS({
|
|
|
915
979
|
}
|
|
916
980
|
_fold(initial, combine) {
|
|
917
981
|
const [value = initial, action = "descend"] = unwrap(combine(initial, this));
|
|
918
|
-
if (action ===
|
|
982
|
+
if (action === control.prune)
|
|
919
983
|
return value;
|
|
920
|
-
if (action ===
|
|
984
|
+
if (action === control.stop)
|
|
921
985
|
return control.stop(value);
|
|
922
986
|
const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
|
|
923
|
-
if (iAction ===
|
|
987
|
+
if (iAction === control.stop)
|
|
924
988
|
return control.stop(iValue);
|
|
925
989
|
return iValue ?? value;
|
|
926
990
|
}
|
|
@@ -1016,7 +1080,7 @@ var require_expr = __commonJS({
|
|
|
1016
1080
|
*
|
|
1017
1081
|
* @param {String} name
|
|
1018
1082
|
* @param {Expr} impl
|
|
1019
|
-
* @param {{canonize
|
|
1083
|
+
* @param {{canonize?: boolean, max?: number, maxArgs?: number, note?: string, terminal?: boolean}} [options]
|
|
1020
1084
|
*/
|
|
1021
1085
|
constructor(name, impl, options = {}) {
|
|
1022
1086
|
super(name);
|
|
@@ -1032,6 +1096,15 @@ var require_expr = __commonJS({
|
|
|
1032
1096
|
this.canonical = guess.expr;
|
|
1033
1097
|
this.invoke = waitn(impl, this.arity);
|
|
1034
1098
|
}
|
|
1099
|
+
/**
|
|
1100
|
+
* @property {boolean} [outdated] - whether the alias is outdated
|
|
1101
|
+
* and should be replaced with its definition when encountered.
|
|
1102
|
+
* @property {boolean} [terminal] - whether the alias should behave like a standalone term
|
|
1103
|
+
* // TODO better name?
|
|
1104
|
+
* @property {boolean} [proper] - whether the alias is a proper combinator (i.e. contains no free variables or constants)
|
|
1105
|
+
* @property {number} [arity] - the number of arguments the alias waits for before expanding
|
|
1106
|
+
* @property {Expr} [canonical] - equivalent lambda term.
|
|
1107
|
+
*/
|
|
1035
1108
|
weight() {
|
|
1036
1109
|
return this.terminal ? 1 : this.impl.weight();
|
|
1037
1110
|
}
|
|
@@ -1042,13 +1115,13 @@ var require_expr = __commonJS({
|
|
|
1042
1115
|
return predicate(this) || this.impl.any(predicate);
|
|
1043
1116
|
}
|
|
1044
1117
|
_fold(initial, combine) {
|
|
1045
|
-
const [value = initial, action
|
|
1046
|
-
if (action ===
|
|
1118
|
+
const [value = initial, action] = unwrap(combine(initial, this));
|
|
1119
|
+
if (action === control.prune)
|
|
1047
1120
|
return value;
|
|
1048
|
-
if (action ===
|
|
1121
|
+
if (action === control.stop)
|
|
1049
1122
|
return control.stop(value);
|
|
1050
1123
|
const [iValue, iAction] = unwrap(this.impl._fold(value, combine));
|
|
1051
|
-
if (iAction ===
|
|
1124
|
+
if (iAction === control.stop)
|
|
1052
1125
|
return control.stop(iValue);
|
|
1053
1126
|
return iValue ?? value;
|
|
1054
1127
|
}
|
|
@@ -1060,9 +1133,10 @@ var require_expr = __commonJS({
|
|
|
1060
1133
|
_infer(options, preArgs = [], steps = 0) {
|
|
1061
1134
|
return this.impl._infer(options, preArgs, steps);
|
|
1062
1135
|
}
|
|
1136
|
+
// DO NOT REMOVE TYPE or tsc chokes with
|
|
1137
|
+
// TS2527: The inferred type of 'Alias' references an inaccessible 'this' type.
|
|
1063
1138
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
* @return {{expr: Expr, steps: number}}
|
|
1139
|
+
* @return {{expr: Expr, steps: number, changed: boolean}}
|
|
1066
1140
|
*/
|
|
1067
1141
|
step() {
|
|
1068
1142
|
if (this.arity > 0)
|
|
@@ -1337,6 +1411,13 @@ var require_parser = __commonJS({
|
|
|
1337
1411
|
this.allow.add(term.name);
|
|
1338
1412
|
return this;
|
|
1339
1413
|
}
|
|
1414
|
+
/**
|
|
1415
|
+
* @desc Internal helper for add() that creates an Alias or Native term from the given arguments.
|
|
1416
|
+
* @param {Alias|string} term
|
|
1417
|
+
* @param {string|Expr|function(Expr):Partial} impl
|
|
1418
|
+
* @returns {Native|Alias}
|
|
1419
|
+
* @private
|
|
1420
|
+
*/
|
|
1340
1421
|
_named(term, impl) {
|
|
1341
1422
|
if (term instanceof Alias)
|
|
1342
1423
|
return new Alias(term.name, term.impl, { canonize: true });
|
|
@@ -1352,6 +1433,16 @@ var require_parser = __commonJS({
|
|
|
1352
1433
|
return new Native(term, impl);
|
|
1353
1434
|
throw new Error("add(): impl must be an Expr, a string, or a function with a signature Expr => ... => Expr");
|
|
1354
1435
|
}
|
|
1436
|
+
/**
|
|
1437
|
+
* @desc Declare a new term if it is not known, otherwise just allow it.
|
|
1438
|
+
* Currently only used by quests.
|
|
1439
|
+
* Use with caution, this function may change its signature, behavior, or even be removed in the future.
|
|
1440
|
+
*
|
|
1441
|
+
* @experimental
|
|
1442
|
+
* @param {string|Alias} name
|
|
1443
|
+
* @param {string|Expr|function(Expr):Partial} impl
|
|
1444
|
+
* @returns {SKI}
|
|
1445
|
+
*/
|
|
1355
1446
|
maybeAdd(name, impl) {
|
|
1356
1447
|
if (this.known[name])
|
|
1357
1448
|
this.allow.add(name);
|
|
@@ -1363,7 +1454,7 @@ var require_parser = __commonJS({
|
|
|
1363
1454
|
* @desc Declare and remove multiple terms at once
|
|
1364
1455
|
* term=impl adds term
|
|
1365
1456
|
* term= removes term
|
|
1366
|
-
* @param {string[]
|
|
1457
|
+
* @param {string[]} list
|
|
1367
1458
|
* @return {SKI} chainable
|
|
1368
1459
|
*/
|
|
1369
1460
|
bulkAdd(list) {
|
|
@@ -1484,11 +1575,11 @@ var require_parser = __commonJS({
|
|
|
1484
1575
|
return out;
|
|
1485
1576
|
}
|
|
1486
1577
|
/**
|
|
1487
|
-
*
|
|
1578
|
+
* @template T
|
|
1488
1579
|
* @param {string} source
|
|
1489
1580
|
* @param {Object} [options]
|
|
1490
1581
|
* @param {{[keys: string]: Expr}} [options.env]
|
|
1491
|
-
* @param {
|
|
1582
|
+
* @param {T} [options.scope]
|
|
1492
1583
|
* @param {boolean} [options.numbers]
|
|
1493
1584
|
* @param {boolean} [options.lambdas]
|
|
1494
1585
|
* @param {string} [options.allow]
|
|
@@ -1524,12 +1615,14 @@ var require_parser = __commonJS({
|
|
|
1524
1615
|
return expr;
|
|
1525
1616
|
}
|
|
1526
1617
|
/**
|
|
1527
|
-
*
|
|
1618
|
+
* @desc Parse a single line of source code, without splitting it into declarations.
|
|
1619
|
+
* Internal, always use parse() instead.
|
|
1620
|
+
* @template T
|
|
1528
1621
|
* @param {String} source S(KI)I
|
|
1529
1622
|
* @param {{[keys: string]: Expr}} env
|
|
1530
1623
|
* @param {Object} [options]
|
|
1531
1624
|
* @param {{[keys: string]: Expr}} [options.env] - unused, see 'env' argument
|
|
1532
|
-
* @param {
|
|
1625
|
+
* @param {T} [options.scope]
|
|
1533
1626
|
* @param {boolean} [options.numbers]
|
|
1534
1627
|
* @param {boolean} [options.lambdas]
|
|
1535
1628
|
* @param {string} [options.allow]
|
|
@@ -1593,12 +1686,12 @@ var require_parser = __commonJS({
|
|
|
1593
1686
|
};
|
|
1594
1687
|
}
|
|
1595
1688
|
};
|
|
1596
|
-
SKI2.vars = function(
|
|
1689
|
+
SKI2.vars = function(scope = {}) {
|
|
1597
1690
|
const cache = {};
|
|
1598
1691
|
return new Proxy({}, {
|
|
1599
1692
|
get: (target, name) => {
|
|
1600
1693
|
if (!(name in cache))
|
|
1601
|
-
cache[name] = new FreeVar(name,
|
|
1694
|
+
cache[name] = new FreeVar(name, scope);
|
|
1602
1695
|
return cache[name];
|
|
1603
1696
|
}
|
|
1604
1697
|
});
|
|
@@ -1620,40 +1713,23 @@ var require_quest = __commonJS({
|
|
|
1620
1713
|
var { Expr, FreeVar, Alias, Lambda } = SKI2.classes;
|
|
1621
1714
|
var Quest2 = class {
|
|
1622
1715
|
/**
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
* }} options
|
|
1641
|
-
*
|
|
1642
|
-
* @example const quest = new Quest({
|
|
1643
|
-
* input: 'identity',
|
|
1644
|
-
* cases: [
|
|
1645
|
-
* ['identity x', 'x'],
|
|
1646
|
-
* ],
|
|
1647
|
-
* allow: 'SK',
|
|
1648
|
-
* intro: 'Find a combinator that behaves like the identity function.',
|
|
1649
|
-
* });
|
|
1650
|
-
* quest.check('S K K'); // { pass: true, details: [...], ... }
|
|
1651
|
-
* quest.check('K S'); // { pass: false, details: [...], ... }
|
|
1652
|
-
* quest.check('K x'); // fail! internal variable x is not equal to free variable x,
|
|
1653
|
-
* // despite having the same name.
|
|
1654
|
-
* quest.check('I'); // fail! I not in the allowed list.
|
|
1655
|
-
*/
|
|
1656
|
-
constructor(options = {}) {
|
|
1716
|
+
* @description A combinator problem with a set of test cases for the proposed solution.
|
|
1717
|
+
* @param {QuestSpec} options
|
|
1718
|
+
* @example const quest = new Quest({
|
|
1719
|
+
* input: 'identity',
|
|
1720
|
+
* cases: [
|
|
1721
|
+
* ['identity x', 'x'],
|
|
1722
|
+
* ],
|
|
1723
|
+
* allow: 'SK',
|
|
1724
|
+
* intro: 'Find a combinator that behaves like the identity function.',
|
|
1725
|
+
* });
|
|
1726
|
+
* quest.check('S K K'); // { pass: true, details: [...], ... }
|
|
1727
|
+
* quest.check('K S'); // { pass: false, details: [...], ... }
|
|
1728
|
+
* quest.check('K x'); // fail! internal variable x is not equal to free variable x,
|
|
1729
|
+
* // despite having the same name.
|
|
1730
|
+
* quest.check('I'); // fail! I not in the allowed list.
|
|
1731
|
+
*/
|
|
1732
|
+
constructor(options) {
|
|
1657
1733
|
const { input, cases, allow, numbers, lambdas, engine, engineFull, ...meta } = options;
|
|
1658
1734
|
const env = options.env ?? options.vars;
|
|
1659
1735
|
this.engine = engine ?? new SKI2();
|
|
@@ -1746,7 +1822,11 @@ var require_quest = __commonJS({
|
|
|
1746
1822
|
numbers: spec.numbers ?? this.restrict.numbers,
|
|
1747
1823
|
lambdas: spec.lambdas ?? this.restrict.lambdas
|
|
1748
1824
|
});
|
|
1749
|
-
|
|
1825
|
+
const arsenal = { ...this.engine.getTerms(), ...jar };
|
|
1826
|
+
weight += impl.fold(0, (a, e) => {
|
|
1827
|
+
if (e instanceof SKI2.classes.Named && arsenal[e.name] === e)
|
|
1828
|
+
return SKI2.control.prune(a + 1);
|
|
1829
|
+
});
|
|
1750
1830
|
const expr = impl instanceof FreeVar ? impl : new Alias(spec.fancy ?? spec.name, impl, { terminal: true, canonize: false });
|
|
1751
1831
|
jar[spec.name] = expr;
|
|
1752
1832
|
prepared.push(expr);
|
|
@@ -1819,10 +1899,10 @@ var require_quest = __commonJS({
|
|
|
1819
1899
|
/**
|
|
1820
1900
|
* @param {FreeVar[]} input
|
|
1821
1901
|
* @param {{
|
|
1822
|
-
* max
|
|
1823
|
-
* note
|
|
1824
|
-
* env
|
|
1825
|
-
* engine
|
|
1902
|
+
* max?: number,
|
|
1903
|
+
* note?: string,
|
|
1904
|
+
* env?: {string: Expr},
|
|
1905
|
+
* engine?: SKI
|
|
1826
1906
|
* }} options
|
|
1827
1907
|
* @param {[e1: string, e2: string]} terms
|
|
1828
1908
|
*/
|