@dallaylaen/ski-interpreter 1.3.0 → 2.1.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 +55 -0
- package/README.md +132 -45
- package/bin/ski.js +96 -97
- package/index.js +14 -6
- package/lib/expr.js +449 -287
- package/lib/extras.js +140 -0
- package/lib/internal.js +105 -0
- package/lib/parser.js +78 -31
- package/lib/quest.js +93 -59
- package/package.json +2 -2
- package/types/index.d.ts +2 -2
- package/types/lib/expr.d.ts +232 -108
- package/types/lib/extras.d.ts +57 -0
- package/types/lib/internal.d.ts +52 -0
- package/types/lib/parser.d.ts +68 -45
- package/types/lib/quest.d.ts +80 -59
- package/lib/util.js +0 -78
- package/types/lib/util.d.ts +0 -13
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,61 @@ 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.1.0] - 2026-02-12
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
- Quest: rename `vars` -> `env`, `title` -> `name`, and `descr` -> `intro`.
|
|
13
|
+
- Quest: remove `subst` for good, use input: `{ name, fancy }` instead
|
|
14
|
+
- App: remove `split()` method, use `foo.fun` and `foo.arg` directly or better yet `foo.unroll()` instead.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `SKI.extras.search(seed: Expr[], options:{...}, predicate: (e: Expr, props: infer()) => 1|0|-1)` that brute forces all possible applications of `seed` (with some heuristics) returns the first matching expr (if can)
|
|
19
|
+
- Add experimental `Expr.fold<T>(initial : T, combine: (acc : T, expr) => T?)` that folds an expression tree in LO order. Composite nodes are first checked as is and then descended into. If `combine` returns null, it is discarded and the previous value is kept.
|
|
20
|
+
- Add `SKI.control.{descend, prune, stop}` helper functions for more precise `fold` control.
|
|
21
|
+
- Add optional `Expr.context{ parser, scope, env, src }` which is filled by the parser.
|
|
22
|
+
- Add `SKI.extras.deepFormat({}|[]|Expr|..., options={})` that recursively formats a deep structure with given options, leaving non-Expr values as is. Useful for debugging.
|
|
23
|
+
- Add `Expr.toJSON()`, currently just `format()` with no options
|
|
24
|
+
- Add `Expr.unroll()` that returns a list of terms
|
|
25
|
+
that give the initial expression when applied
|
|
26
|
+
from left to right: `((a, b), (c, d)) => [a, b, (c, d)]`
|
|
27
|
+
|
|
28
|
+
## [2.0.0] - 2026-02-06
|
|
29
|
+
|
|
30
|
+
### BREAKING CHANGES
|
|
31
|
+
|
|
32
|
+
- Rename `guess()` to `infer()` for clarity;
|
|
33
|
+
- Remove `replace()` method, use `subst()` or `traverse()` instead;
|
|
34
|
+
- Rename `rewriteSKI` to `toSKI`, `lambdify` to `toLambda` for clarity and consistence;
|
|
35
|
+
- Remove `getSymbols()` method. Partially replaced by traverse();
|
|
36
|
+
- Remove `freeVars()` method (enumeration of vars is problematic with current scope impl);
|
|
37
|
+
- Remove `contains()` method, use `any(e=>e.equals(other))` instead;
|
|
38
|
+
- Remove `Expr.hasLambda()`, use `any(e => e instanceof Lambda)` instead;
|
|
39
|
+
- Remove `postParse()` method (did nothing anyway and was only used in the parser);
|
|
40
|
+
- Replace `parse(src, jar, options)` with `parse(src, options = { ..., env: jar })`;
|
|
41
|
+
- Remove global `SKI.options`;
|
|
42
|
+
- Remove `SKI.free()`, use `const {x, y} = SKI.vars()` instead.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- `expr.traverse(transform: Expr->Expr|null): Expr|null` for term traversal and replacement;
|
|
47
|
+
- `expr.any(predicate: Expr->boolean)` method for matching expressions;
|
|
48
|
+
- expr.diff(expr2) shows exactly where the terms begin differing (or returns null);
|
|
49
|
+
- Parse now has 2 arguments: `ski.parse(src, options={})`:
|
|
50
|
+
- All `parse()` arguments are now immutable;
|
|
51
|
+
- Passing extra terms: `ski.parse(src, { env: { myterm } })`;
|
|
52
|
+
- Variable scope restriction: `ski.parse('x(y)', { scope: myObject })`;
|
|
53
|
+
- `SKI.vars(scope?)` returns a magic proxy for variable declarations;
|
|
54
|
+
- Added semi-official `Named.fancyName` property
|
|
55
|
+
- `@typedef QuestResult` for better type definitions.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- Parsing without context produces global free vars;
|
|
60
|
+
- Better variable handling with scope/context distinction;
|
|
61
|
+
- Quest system now uses `diff()` instead of `equals()` for more detailed comparisons.
|
|
62
|
+
|
|
8
63
|
## [1.3.0] - 2026-01-25
|
|
9
64
|
|
|
10
65
|
### BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Simple Kombinator Interpreter
|
|
2
2
|
|
|
3
|
-
This package contains a
|
|
4
|
-
[combinatory logic](https://en.wikipedia.org/wiki/Combinatory_logic)
|
|
5
|
-
and [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus)
|
|
3
|
+
This package contains a
|
|
4
|
+
[combinatory logic](https://en.wikipedia.org/wiki/Combinatory_logic)
|
|
5
|
+
and [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus)
|
|
6
6
|
parser and interpreter focused on traceability and inspectability.
|
|
7
7
|
|
|
8
8
|
It is written in plain JavaScript (with bolted on TypeScript support)
|
|
@@ -25,7 +25,7 @@ and can be used in Node.js or in the browser.
|
|
|
25
25
|
* Whole non-negative numbers are interpreted as Church numerals, e.g. `5 x y` evaluates to `x(x(x(x(x y))))`. They must also be space-separated from other terms;
|
|
26
26
|
* `x y z` is the same as `(x y) z` or `x(y)(z)` but **not** `x (y z)`;
|
|
27
27
|
* Unknown terms are assumed to be free variables;
|
|
28
|
-
* Lambda terms are written as `x->y->z->expr`, which is equivalent to
|
|
28
|
+
* Lambda terms are written as `x->y->z->expr`, which is equivalent to
|
|
29
29
|
`x->(y->(z->expr))` (aka right associative). Free variables in a lambda expression ~~stay in Vegas~~ are isolated from terms with the same name outside it;
|
|
30
30
|
* X = y z defines a new term.
|
|
31
31
|
|
|
@@ -38,6 +38,15 @@ and can be used in Node.js or in the browser.
|
|
|
38
38
|
* <code>C x y z ↦ x z y</code> _// swapping_;
|
|
39
39
|
* <code>W x y ↦ x y y</code> _//duplication_;
|
|
40
40
|
|
|
41
|
+
The special combinator `+` will increment Church numerals, if they happen to come after it:
|
|
42
|
+
|
|
43
|
+
* `+ 0` // 1
|
|
44
|
+
* `2 + 3` // -> `+(+(3))` -> `+(4)` -> `5`
|
|
45
|
+
|
|
46
|
+
The `term + 0` idiom may be used to convert
|
|
47
|
+
numbers obtained via computation (e.g. factorials)
|
|
48
|
+
back to human readable form.
|
|
49
|
+
|
|
41
50
|
# Execution strategy
|
|
42
51
|
|
|
43
52
|
Applications and native terms use normal strategy, i.e. the first term in the tree
|
|
@@ -48,15 +57,13 @@ all free variables are bound.
|
|
|
48
57
|
|
|
49
58
|
# Playground
|
|
50
59
|
|
|
51
|
-
https://dallaylaen.github.io/ski-interpreter/
|
|
60
|
+
* [Interactive interpreter](https://dallaylaen.github.io/ski-interpreter/)
|
|
52
61
|
|
|
53
|
-
* all of the above features (except comparison and JS-native terms) in your browser
|
|
54
|
-
* expressions have permalinks
|
|
55
|
-
* can configure verbosity
|
|
62
|
+
* all of the above features (except comparison and JS-native terms) in your browser
|
|
63
|
+
* expressions have permalinks
|
|
64
|
+
* can configure verbosity and execution speed
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
https://dallaylaen.github.io/ski-interpreter/quest.html
|
|
66
|
+
* [Quests](https://dallaylaen.github.io/ski-interpreter/quest.html)
|
|
60
67
|
|
|
61
68
|
This page contains small tasks of increasing complexity.
|
|
62
69
|
Each task requires the user to build a combinator with specific properties.
|
|
@@ -73,54 +80,134 @@ npm install @dallaylaen/ski-interpreter
|
|
|
73
80
|
|
|
74
81
|
# Usage
|
|
75
82
|
|
|
83
|
+
## A minimal example
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
#!node
|
|
87
|
+
|
|
88
|
+
const { SKI } = require('@dallaylaen/ski-interpreter');
|
|
89
|
+
|
|
90
|
+
// Create a parser instance
|
|
91
|
+
const ski = new SKI();
|
|
92
|
+
|
|
93
|
+
// Parse an expression
|
|
94
|
+
const expr = ski.parse(process.argv[2]);
|
|
95
|
+
|
|
96
|
+
// Evaluate it step by step
|
|
97
|
+
for (const step of expr.walk({max: 100})) {
|
|
98
|
+
console.log(`[${step.steps}] ${step.expr}`);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Main features
|
|
103
|
+
|
|
76
104
|
```javascript
|
|
77
|
-
const {SKI} = require('@dallaylaen/ski-interpreter');
|
|
78
|
-
const ski = new SKI();
|
|
105
|
+
const { SKI } = require('@dallaylaen/ski-interpreter');
|
|
106
|
+
const ski = new SKI();
|
|
107
|
+
|
|
108
|
+
const expr = ski.parse(src);
|
|
109
|
+
|
|
110
|
+
// evaluating expressions
|
|
111
|
+
const next = expr.step(); // { steps: 1, expr: '...' }
|
|
112
|
+
const final = expr.run({max: 1000}); // { steps: 42, expr: '...' }
|
|
113
|
+
const iterator = expr.walk();
|
|
114
|
+
|
|
115
|
+
// applying expressions
|
|
116
|
+
const result = expr.run({max: 1000}, arg1, arg2 ...);
|
|
117
|
+
// same sa
|
|
118
|
+
expr.apply(arg1).apply(arg2).run();
|
|
119
|
+
// or simply
|
|
120
|
+
expr.apply(arg1, arg2).run();
|
|
121
|
+
|
|
122
|
+
// equality check
|
|
123
|
+
ski.parse('x->y->x').equals(ski.parse('a->b->a')); // true
|
|
124
|
+
ski.parse('S').equals(SKI.S); // true
|
|
125
|
+
ski.parse('x').apply(ski.parse('y')).equals(ski.parse('x y')); // also true
|
|
126
|
+
|
|
127
|
+
// defining new terms
|
|
128
|
+
ski.add('T', 'CI'); // T x y = C I x y = I y x = y
|
|
129
|
+
ski.add('M', 'x->x x'); // M x = x x
|
|
130
|
+
|
|
131
|
+
// also with native JavaScript implementations:
|
|
132
|
+
ski.add('V', x=>y=>f=>f.apply(x, y), 'pair constructor');
|
|
133
|
+
|
|
134
|
+
ski.getTerms(); // all of the above as an object
|
|
135
|
+
|
|
136
|
+
// converting lambda expressions to SKI
|
|
137
|
+
const lambdaExpr = ski.parse('x->y->x y');
|
|
138
|
+
const steps = [...lambdaExpr.toSKI()];
|
|
139
|
+
// steps[steps.length - 1].expr only contains S, K, I, and free variables, if any
|
|
140
|
+
|
|
141
|
+
// converting SKI expressions to lambda
|
|
142
|
+
const skiExpr = ski.parse('S K K');
|
|
143
|
+
const lambdaSteps = [...skiExpr.toLambda()];
|
|
144
|
+
// lambdaSteps[lambdaSteps.length - 1].expr only contains lambda abstractions and applications
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Fancy formatting
|
|
79
148
|
|
|
80
|
-
|
|
81
|
-
|
|
149
|
+
The `format` methods of the `Expr` class supports
|
|
150
|
+
a number of options, see [the source code](lib/expr.js) for details.
|
|
82
151
|
|
|
83
|
-
|
|
84
|
-
const result = expr.run({max: 100, throw: true});
|
|
85
|
-
console.log('reached '+ result.expr + ' after ' + result.steps + ' steps.');
|
|
152
|
+
## Variable scoping
|
|
86
153
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log(step.expr.toString());
|
|
154
|
+
By default, parsed free variables are global and equal to any other variable with the same name.
|
|
155
|
+
Variables inside lambdas are local to said lambda and will not be equal to anything except themselves.
|
|
90
156
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
157
|
+
A special `scope` argument may be given to parse to limit the scope. It can be any object.
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
const scope1 = {};
|
|
161
|
+
const scope2 = {};
|
|
162
|
+
const expr1 = ski.parse('x y', {scope: scope1});
|
|
163
|
+
const expr2 = ski.parse('x y', {scope: scope2}); // not equal
|
|
164
|
+
const expr3 = ski.parse('x y'); // equal to neither
|
|
165
|
+
const expr4 = ski.parse('x', {scope: scope1}).apply(ski.parse('y', {scope: scope1})); // equal to expr1
|
|
166
|
+
```
|
|
95
167
|
|
|
96
|
-
|
|
97
|
-
const combinator = ski.parse('BSC');
|
|
98
|
-
for (const step of combinator.lambdify())
|
|
99
|
-
console.log(step.steps + ': ' + step.expr);
|
|
168
|
+
Variables can also be created using magic `SKI.vars(scope)` method:
|
|
100
169
|
|
|
101
|
-
|
|
102
|
-
|
|
170
|
+
```javascript
|
|
171
|
+
const scope = {};
|
|
172
|
+
const {x, y, z} = SKI.vars(scope); // no need to specify names
|
|
173
|
+
```
|
|
103
174
|
|
|
104
|
-
|
|
105
|
-
ski.parse('a->b->f a').equals(ski.parse('x->y->f x')); // false
|
|
106
|
-
ski.parse('a->b->f a', jar).equals(ski.parse('x->y->f x', jar)); // true
|
|
175
|
+
## Querying the expressions
|
|
107
176
|
|
|
108
|
-
|
|
109
|
-
ski.add('T', 'S(K(SI))K');
|
|
110
|
-
console.log(ski.parse('T x y').run().expr); // prints 'x(y)'
|
|
177
|
+
Expressions are trees, so they can be traversed.
|
|
111
178
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
ski.add('J', jay);
|
|
179
|
+
```javascript
|
|
180
|
+
expr.any(e => e.equals(SKI.S)); // true if any subexpression is S
|
|
115
181
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
182
|
+
expr.traverse(e => e.equals(SKI.I) ? SKI.S.apply(SKI.K, SKI.K) : null);
|
|
183
|
+
// replaces all I's with S K K
|
|
184
|
+
// here a returned `Expr` object replaces the subexpression,
|
|
185
|
+
// whereas `null` means "leave it alone and descend if possible"
|
|
120
186
|
```
|
|
121
187
|
|
|
188
|
+
## Test cases
|
|
122
189
|
|
|
190
|
+
The `Quest` class may be used to build and execute test cases for combinators.
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
const { Quest } = require('@dallaylaen/ski-interpreter');
|
|
194
|
+
|
|
195
|
+
const q = new Quest({
|
|
196
|
+
name: 'Test combinator T',
|
|
197
|
+
description: 'T x y should equal y x',
|
|
198
|
+
input: 'T',
|
|
199
|
+
cases: [
|
|
200
|
+
['T x y', 'y x'],
|
|
201
|
+
],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
q.check('CI'); // pass
|
|
205
|
+
q.check('a->b->b a'); // ditto
|
|
206
|
+
q.check('K'); // fail
|
|
207
|
+
q.check('K(K(y x))') // nope! the variable scopes won't match
|
|
208
|
+
```
|
|
123
209
|
|
|
210
|
+
See [quest page data](docs/quest-data/) for more examples.
|
|
124
211
|
|
|
125
212
|
# Thanks
|
|
126
213
|
|
|
@@ -138,4 +225,4 @@ SKI.church(5).apply(x, y).run().expr + ''; // 'x(x(x(x(x y))))'
|
|
|
138
225
|
|
|
139
226
|
This software is free and available under the MIT license.
|
|
140
227
|
|
|
141
|
-
© Konstantin Uvarin 2024
|
|
228
|
+
© Konstantin Uvarin 2024–2026
|
package/bin/ski.js
CHANGED
|
@@ -2,121 +2,120 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs/promises');
|
|
4
4
|
|
|
5
|
-
const {SKI} = require('../index');
|
|
5
|
+
const { SKI } = require('../index');
|
|
6
6
|
|
|
7
7
|
const [myname, options, positional] = parseArgs(process.argv);
|
|
8
8
|
|
|
9
9
|
if (options.help) {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
console.error(myname + ': usage: ' + myname + '[-q | -v ] -e <expression>');
|
|
11
|
+
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (typeof options.e === 'string' && positional.length > 0 || positional.length > 1) {
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if ((typeof options.e === 'string' && positional.length) > 0 || positional.length > 1) {
|
|
15
|
+
console.error(myname + ': either -e <expr> or exactly one filename must be given');
|
|
16
|
+
process.exit(1);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const ski = new SKI();
|
|
20
20
|
|
|
21
21
|
if (options.e === undefined && !positional.length) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
rl.prompt();
|
|
42
|
-
});
|
|
43
|
-
rl.once('close', () => {
|
|
44
|
-
if (!options.q)
|
|
45
|
-
console.log('Bye, and may your bird fly high!');
|
|
46
|
-
process.exit(0)
|
|
47
|
-
});
|
|
22
|
+
// interactive console
|
|
23
|
+
const readline = require('readline');
|
|
24
|
+
const rl = readline.createInterface({
|
|
25
|
+
input: process.stdin,
|
|
26
|
+
output: process.stdout,
|
|
27
|
+
prompt: '> ',
|
|
28
|
+
terminal: true,
|
|
29
|
+
});
|
|
30
|
+
if (!options.q)
|
|
31
|
+
console.log('Welcome to SKI interactive shell. Known combinators: ' + ski.showRestrict());
|
|
32
|
+
rl.on('line', str => {
|
|
33
|
+
const flag = str.match(/^\s*([-+])([qvt])\s*$/);
|
|
34
|
+
if (flag)
|
|
35
|
+
options[flag[2]] = flag[1] === '+';
|
|
36
|
+
else {
|
|
37
|
+
runLine(err => {
|
|
38
|
+
console.log('' + err)
|
|
39
|
+
})(str);
|
|
40
|
+
}
|
|
48
41
|
rl.prompt();
|
|
42
|
+
});
|
|
43
|
+
rl.once('close', () => {
|
|
44
|
+
if (!options.q)
|
|
45
|
+
console.log('Bye, and may your bird fly high!');
|
|
46
|
+
process.exit(0)
|
|
47
|
+
});
|
|
48
|
+
rl.prompt();
|
|
49
49
|
} else {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
const prom = positional.length > 0
|
|
51
|
+
? fs.readFile(positional[0], 'utf8')
|
|
52
|
+
: Promise.resolve(options.e);
|
|
53
|
+
|
|
54
|
+
prom.then(runLine(err => { console.error('' + err); process.exit(3) })).catch(err => {
|
|
55
|
+
console.error(myname + ': ' + err);
|
|
56
|
+
process.exit(2);
|
|
57
|
+
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function runLine(onErr) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
60
|
+
function runLine (onErr) {
|
|
61
|
+
return function (source) {
|
|
62
|
+
if (!source.match(/\S/))
|
|
63
|
+
return 0; // nothing to see here
|
|
64
|
+
try {
|
|
65
|
+
const expr = ski.parse(source);
|
|
66
|
+
|
|
67
|
+
const t0 = new Date();
|
|
68
|
+
for (const state of expr.walk()) {
|
|
69
|
+
if (state.final && !options.q)
|
|
70
|
+
console.log(`// ${state.steps} step(s) in ${new Date() - t0}ms`);
|
|
71
|
+
if (options.v || state.final)
|
|
72
|
+
console.log('' + state.expr.format({ terse: options.t }));
|
|
73
|
+
if (state.final && expr instanceof SKI.classes.Alias)
|
|
74
|
+
ski.add(expr.name, state.expr);
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
onErr(err);
|
|
79
|
+
return 1;
|
|
81
80
|
}
|
|
81
|
+
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function parseArgs(argv) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const action = todo[next];
|
|
114
|
-
if (typeof action !== "function")
|
|
115
|
-
throw new Error('Unknown option ' + next + '; see ski.js --help');
|
|
116
|
-
|
|
117
|
-
action();
|
|
84
|
+
function parseArgs (argv) {
|
|
85
|
+
const [_, script, ...list] = argv;
|
|
86
|
+
|
|
87
|
+
const todo = {
|
|
88
|
+
'--': () => { pos.push(...list) },
|
|
89
|
+
'--help': () => { opt.help = true },
|
|
90
|
+
'-q': () => { opt.q = true },
|
|
91
|
+
'-v': () => { opt.v = true },
|
|
92
|
+
'-c': () => { opt.t = false },
|
|
93
|
+
'-t': () => { opt.t = true },
|
|
94
|
+
'-e': () => {
|
|
95
|
+
if (list.length < 1)
|
|
96
|
+
throw new Error('option -e requires an argument');
|
|
97
|
+
opt.e = list.shift();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// TODO replace with a relevant dependency
|
|
102
|
+
const pos = [];
|
|
103
|
+
const opt = {};
|
|
104
|
+
|
|
105
|
+
while (list.length > 0) {
|
|
106
|
+
const next = list.shift();
|
|
107
|
+
|
|
108
|
+
if (!next.match(/^-/)) {
|
|
109
|
+
pos.push(next);
|
|
110
|
+
continue;
|
|
118
111
|
}
|
|
119
112
|
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
const action = todo[next];
|
|
114
|
+
if (typeof action !== 'function')
|
|
115
|
+
throw new Error('Unknown option ' + next + '; see ski.js --help');
|
|
116
|
+
|
|
117
|
+
action();
|
|
118
|
+
}
|
|
122
119
|
|
|
120
|
+
return [script, opt, pos];
|
|
121
|
+
}
|
package/index.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
const
|
|
1
|
+
const main = require('./lib/parser');
|
|
2
2
|
const quest = require('./lib/quest');
|
|
3
|
+
const extras = require('./lib/extras');
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
main.SKI.Quest = quest.Quest;
|
|
6
|
+
main.SKI.extras = extras;
|
|
7
|
+
|
|
8
|
+
// SKI_REPL=1 node -r ./index.js
|
|
9
|
+
if (typeof process === 'object' && process.env.SKI_REPL && typeof global !== 'undefined')
|
|
10
|
+
global.SKI = main.SKI;
|
|
11
|
+
|
|
12
|
+
// we're in a browser
|
|
13
|
+
if (typeof window !== 'undefined')
|
|
14
|
+
window.SKI = main.SKI;
|
|
15
|
+
|
|
16
|
+
module.exports = { ...main, ...quest };
|