@dallaylaen/ski-interpreter 2.0.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 +20 -0
- package/bin/ski.js +96 -97
- package/index.js +14 -6
- package/lib/expr.js +168 -67
- package/lib/extras.js +140 -0
- package/lib/internal.js +105 -0
- package/lib/parser.js +20 -4
- package/lib/quest.js +73 -43
- package/package.json +2 -2
- package/types/index.d.ts +2 -2
- package/types/lib/expr.d.ts +111 -38
- package/types/lib/extras.d.ts +57 -0
- package/types/lib/internal.d.ts +52 -0
- package/types/lib/parser.d.ts +5 -0
- package/types/lib/quest.d.ts +56 -39
- package/lib/util.js +0 -57
- package/types/lib/util.d.ts +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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
|
+
|
|
8
28
|
## [2.0.0] - 2026-02-06
|
|
9
29
|
|
|
10
30
|
### BREAKING CHANGES
|
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 };
|