kni 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -7
- package/console.js +33 -35
- package/describe.js +54 -89
- package/document.js +103 -72
- package/engine.js +436 -407
- package/entry.js +88 -0
- package/evaluate.js +221 -228
- package/excerpt.js +117 -115
- package/grammar.js +1025 -785
- package/html.js +174 -167
- package/inline-lexer.js +155 -125
- package/kni.js +286 -279
- package/link.js +50 -52
- package/outline-lexer.js +64 -37
- package/package.json +27 -34
- package/parser.js +32 -20
- package/path.js +34 -46
- package/readline.js +89 -79
- package/scanner.js +101 -78
- package/scope.js +32 -36
- package/story.js +174 -165
- package/test.js +6 -0
- package/translate-json.js +3 -5
- package/tsconfig.json +11 -0
- package/verify.js +121 -117
- package/wrapper.js +37 -41
- package/template.js +0 -69
package/readline.js
CHANGED
|
@@ -1,143 +1,153 @@
|
|
|
1
|
-
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
module.exports = Readline;
|
|
7
|
-
|
|
8
|
-
function Readline(transcript, filename) {
|
|
9
|
-
var self = this;
|
|
4
|
+
export default class Readline {
|
|
5
|
+
constructor(transcript, filename) {
|
|
6
|
+
const self = this;
|
|
10
7
|
this.readline = readline.createInterface({
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout,
|
|
13
10
|
});
|
|
14
11
|
this.engine = null;
|
|
15
|
-
this.boundAnswer =
|
|
12
|
+
this.boundAnswer = text => {
|
|
13
|
+
self.answer(text);
|
|
14
|
+
};
|
|
16
15
|
this.transcript = transcript;
|
|
17
16
|
this.history = [];
|
|
18
17
|
this.state = new Play(this, filename);
|
|
19
18
|
Object.seal(this);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
meterFault() {
|
|
22
|
+
this.readline.question(`Enter any command to continue... `, (answer) => {
|
|
23
|
+
if (answer === 'quit') {
|
|
24
|
+
this.close();
|
|
25
|
+
} else {
|
|
26
|
+
this.engine.clearMeterFault();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
this.readline.question(
|
|
27
|
-
}
|
|
31
|
+
ask(cue) {
|
|
32
|
+
this.readline.question(`${cue || ''}> `, this.boundAnswer);
|
|
33
|
+
}
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
answer(text) {
|
|
30
36
|
if (this.transcript) {
|
|
31
|
-
|
|
37
|
+
this.transcript.write(`> ${text}\n`);
|
|
32
38
|
}
|
|
33
39
|
this.state = this.state.answer(text);
|
|
34
|
-
}
|
|
40
|
+
}
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
close() {
|
|
37
43
|
if (this.transcript) {
|
|
38
|
-
|
|
44
|
+
this.transcript.write('\n');
|
|
39
45
|
}
|
|
40
46
|
this.readline.close();
|
|
41
|
-
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
42
49
|
|
|
43
|
-
|
|
50
|
+
class Play {
|
|
51
|
+
constructor(readline, filename) {
|
|
44
52
|
this.readline = readline;
|
|
45
53
|
this.filename = filename || 'kni.waypoint';
|
|
46
|
-
}
|
|
54
|
+
}
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
answer(text) {
|
|
57
|
+
const engine = this.readline.engine;
|
|
50
58
|
|
|
51
59
|
if (text === 'quit') {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// istanbul ignore next
|
|
60
|
+
console.log('');
|
|
61
|
+
engine.dialog.close();
|
|
55
62
|
} else if (text === 'bt' || text === 'trace') {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// istanbul ignore next
|
|
63
|
+
engine.log();
|
|
64
|
+
engine.ask();
|
|
59
65
|
} else if (text === 'capture' || text === 'cap') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// istanbul ignore next
|
|
66
|
+
console.log(JSON.stringify(engine.waypoint));
|
|
67
|
+
console.log('');
|
|
68
|
+
engine.ask();
|
|
64
69
|
} else if (text === 'save') {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
console.log('');
|
|
71
|
+
engine.dialog.ask(`file name [${this.filename}]> `);
|
|
72
|
+
return new Save(this, engine.waypoint, this.filename);
|
|
68
73
|
} else if (text === 'load') {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
console.log('');
|
|
75
|
+
engine.dialog.ask(`file name [${this.filename}]> `);
|
|
76
|
+
return new Load(this, this.filename);
|
|
72
77
|
} else if (text === 'back') {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
console.log('');
|
|
79
|
+
if (this.readline.transcript) {
|
|
80
|
+
this.readline.transcript.write('\n');
|
|
81
|
+
}
|
|
82
|
+
if (this.readline.history.length <= 1) {
|
|
83
|
+
console.log('Meanwhile, at the beginning of recorded history...');
|
|
84
|
+
}
|
|
85
|
+
engine.waypoint = this.readline.history.pop();
|
|
86
|
+
engine.resume(engine.waypoint);
|
|
82
87
|
} else if (text === 'replay') {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
console.log('');
|
|
89
|
+
if (this.readline.transcript) {
|
|
90
|
+
this.readline.transcript.write('\n');
|
|
91
|
+
}
|
|
92
|
+
engine.resume(engine.waypoint);
|
|
88
93
|
} else {
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
this.readline.history.push(engine.waypoint);
|
|
95
|
+
engine.answer(text);
|
|
91
96
|
}
|
|
92
97
|
return this;
|
|
93
|
-
}
|
|
98
|
+
}
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
saved(filename) {
|
|
101
|
+
const engine = this.readline.engine;
|
|
97
102
|
|
|
98
103
|
this.filename = filename;
|
|
99
104
|
engine.ask();
|
|
100
105
|
return this;
|
|
101
|
-
}
|
|
106
|
+
}
|
|
102
107
|
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
loaded(waypoint) {
|
|
109
|
+
const engine = this.readline.engine;
|
|
105
110
|
|
|
106
111
|
engine.resume(waypoint);
|
|
107
112
|
return this;
|
|
108
|
-
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
109
115
|
|
|
110
|
-
|
|
116
|
+
class Save {
|
|
117
|
+
constructor(parent, waypoint, filename) {
|
|
111
118
|
this.parent = parent;
|
|
112
119
|
this.waypoint = waypoint;
|
|
113
120
|
this.filename = filename;
|
|
114
|
-
}
|
|
121
|
+
}
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
answer(filename) {
|
|
124
|
+
const waypoint = JSON.stringify(this.waypoint);
|
|
118
125
|
filename = filename || this.filename;
|
|
119
126
|
fs.writeFileSync(filename, waypoint, 'utf8');
|
|
120
127
|
|
|
121
128
|
console.log('');
|
|
122
|
-
console.log(
|
|
129
|
+
console.log(`Waypoint written to ${filename}`);
|
|
123
130
|
console.log(waypoint);
|
|
124
131
|
console.log('');
|
|
125
132
|
return this.parent.saved(filename);
|
|
126
|
-
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
127
135
|
|
|
128
|
-
|
|
136
|
+
class Load {
|
|
137
|
+
constructor(parent, filename) {
|
|
129
138
|
this.parent = parent;
|
|
130
139
|
this.filename = filename;
|
|
131
|
-
}
|
|
140
|
+
}
|
|
132
141
|
|
|
133
|
-
|
|
142
|
+
answer(filename) {
|
|
134
143
|
filename = filename || this.filename;
|
|
135
144
|
|
|
136
|
-
|
|
145
|
+
const waypoint = fs.readFileSync(filename, 'utf8');
|
|
137
146
|
console.log('');
|
|
138
|
-
console.log(
|
|
147
|
+
console.log(`Loaded from ${filename}`);
|
|
139
148
|
console.log(waypoint);
|
|
140
149
|
console.log('');
|
|
141
150
|
|
|
142
151
|
return this.parent.loaded(JSON.parse(waypoint));
|
|
143
|
-
}
|
|
152
|
+
}
|
|
153
|
+
}
|
package/scanner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
2
|
|
|
3
3
|
// Transforms a stream of text into a sequence of 'lines', tracking each line's
|
|
4
4
|
// level of indentation.
|
|
@@ -7,86 +7,111 @@
|
|
|
7
7
|
//
|
|
8
8
|
// The scanner feeds into an outline lexer.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
var leaders = '-+*!>';
|
|
12
|
-
var debug = typeof process === 'object' && process.env.DEBUG_SCANNER;
|
|
10
|
+
const tabWidth = 4;
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param {number} columnNo -- screen column number (logical X coord)
|
|
14
|
+
* @returns {number} -- column number where the next tab stop starts
|
|
15
|
+
*/
|
|
16
|
+
const nextTabStop = columnNo => {
|
|
17
|
+
// TODO simplify with modulo arithmetic
|
|
18
|
+
return Math.floor((columnNo + tabWidth) / tabWidth) * tabWidth;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const leaders = '-+*!>';
|
|
22
|
+
|
|
23
|
+
export default class Scanner {
|
|
24
|
+
debug = typeof process === 'object' && process.env.DEBUG_SCANNER;
|
|
25
|
+
|
|
26
|
+
indent = 0;
|
|
27
|
+
lineStart = 0;
|
|
28
|
+
indentStart = 0;
|
|
29
|
+
itemStart = 0;
|
|
30
|
+
lineNo = 0;
|
|
31
|
+
columnNo = 0;
|
|
32
|
+
columnStart = 0;
|
|
33
|
+
leading = true;
|
|
34
|
+
leader = '';
|
|
35
|
+
|
|
36
|
+
/** An Iterator-like object that has text pushed into it by a Scanner.
|
|
37
|
+
*
|
|
38
|
+
* Its biggest difference from an Iterator<string> is that the Scanner
|
|
39
|
+
* object itself is passed along as an additional next agument
|
|
40
|
+
*
|
|
41
|
+
* @typedef {object} ScanIt
|
|
42
|
+
* @prop {(text: string, sc: Scanner) => void} next
|
|
43
|
+
* @prop {(sc: Scanner) => void} return
|
|
44
|
+
*/
|
|
15
45
|
|
|
16
|
-
|
|
46
|
+
/**
|
|
47
|
+
* @param {ScanIt} generator
|
|
48
|
+
* @param {string} fileName
|
|
49
|
+
*/
|
|
50
|
+
constructor(generator, fileName) {
|
|
17
51
|
this.generator = generator;
|
|
18
52
|
this.fileName = fileName || '-';
|
|
19
|
-
|
|
20
|
-
this.lineStart = 0;
|
|
21
|
-
this.indentStart = 0;
|
|
22
|
-
this.itemStart = 0;
|
|
23
|
-
this.lineNo = 0;
|
|
24
|
-
this.columnNo = 0;
|
|
25
|
-
this.columnStart = 0;
|
|
26
|
-
this.leading = true;
|
|
27
|
-
this.leader = '';
|
|
28
|
-
this.debug = debug;
|
|
29
|
-
Object.seal(this);
|
|
30
|
-
}
|
|
53
|
+
}
|
|
31
54
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} else if (c === '\t') {
|
|
52
|
-
this.columnNo = nextTabStop(this.columnNo);
|
|
53
|
-
} else if (c === '\n') {
|
|
54
|
-
this.newLine(text, i);
|
|
55
|
-
} else if (c === ' ') {
|
|
56
|
-
this.columnNo++;
|
|
57
|
-
} else if (
|
|
58
|
-
this.leading && leaders.indexOf(c) >= 0 &&
|
|
59
|
-
(d === ' ' || d === '\t')
|
|
60
|
-
) {
|
|
61
|
-
this.leader += c;
|
|
62
|
-
this.columnNo++;
|
|
63
|
-
} else if (this.leading && leaders.indexOf(c) >= 0 && d === '\n') {
|
|
64
|
-
this.leader += c;
|
|
65
|
-
this.indentStart = i;
|
|
66
|
-
this.columnStart = this.columnNo;
|
|
67
|
-
this.lineStart = this.lineNo;
|
|
68
|
-
this.indent = this.columnNo + 2;
|
|
69
|
-
} else if (this.leading) {
|
|
70
|
-
this.indent = this.columnNo;
|
|
71
|
-
this.indentStart = i;
|
|
72
|
-
this.columnStart = this.columnNo;
|
|
73
|
-
this.lineStart = this.lineNo;
|
|
74
|
-
this.columnNo++;
|
|
75
|
-
this.leading = false;
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} text
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
next(text) {
|
|
60
|
+
let i = 0;
|
|
61
|
+
for (; i < text.length; i++) {
|
|
62
|
+
let c = text[i];
|
|
63
|
+
const d = text[i + 1];
|
|
64
|
+
if (this.debug) {
|
|
65
|
+
console.error('SCN', `${this.position()}:${i}`, JSON.stringify(c + (d || '')));
|
|
66
|
+
}
|
|
67
|
+
if (((c === '\t' || c === ' ') && d === '#') || (this.columnNo === 0 && c === '#')) {
|
|
68
|
+
this.newLine(text, i);
|
|
69
|
+
for (i++; i < text.length; i++) {
|
|
70
|
+
c = text[i];
|
|
71
|
+
if (c === '\n') {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
76
74
|
}
|
|
75
|
+
} else if (c === '\t') {
|
|
76
|
+
this.columnNo = nextTabStop(this.columnNo);
|
|
77
|
+
} else if (c === '\n') {
|
|
78
|
+
this.newLine(text, i);
|
|
79
|
+
} else if (c === ' ') {
|
|
80
|
+
this.columnNo++;
|
|
81
|
+
} else if (this.leading && leaders.indexOf(c) >= 0 && (d === ' ' || d === '\t')) {
|
|
82
|
+
this.leader += c;
|
|
83
|
+
this.columnNo++;
|
|
84
|
+
} else if (this.leading && leaders.indexOf(c) >= 0 && d === '\n') {
|
|
85
|
+
this.leader += c;
|
|
86
|
+
this.indentStart = i;
|
|
87
|
+
this.columnStart = this.columnNo;
|
|
88
|
+
this.lineStart = this.lineNo;
|
|
89
|
+
this.indent = this.columnNo + 2;
|
|
90
|
+
} else if (this.leading) {
|
|
91
|
+
this.indent = this.columnNo;
|
|
92
|
+
this.indentStart = i;
|
|
93
|
+
this.columnStart = this.columnNo;
|
|
94
|
+
this.lineStart = this.lineNo;
|
|
95
|
+
this.columnNo++;
|
|
96
|
+
this.leading = false;
|
|
97
|
+
}
|
|
77
98
|
}
|
|
78
99
|
|
|
79
100
|
// TODO To exercise the following block, you need a file with no final
|
|
80
101
|
// newline.
|
|
81
|
-
// istanbul ignore if
|
|
82
102
|
if (!this.leading) {
|
|
83
|
-
|
|
103
|
+
this.generator.next(text.slice(this.indentStart, i), this);
|
|
84
104
|
}
|
|
85
|
-
}
|
|
105
|
+
}
|
|
86
106
|
|
|
87
|
-
|
|
107
|
+
/**
|
|
108
|
+
* @param {string} text
|
|
109
|
+
* @param {number} i
|
|
110
|
+
* @returns {void}
|
|
111
|
+
*/
|
|
112
|
+
newLine(text, i) {
|
|
88
113
|
if (this.leading) {
|
|
89
|
-
|
|
114
|
+
this.indentStart = i;
|
|
90
115
|
}
|
|
91
116
|
this.leading = true;
|
|
92
117
|
this.generator.next(text.slice(this.indentStart, i), this);
|
|
@@ -94,18 +119,16 @@ Scanner.prototype.newLine = function newLine(text, i) {
|
|
|
94
119
|
this.lineNo++;
|
|
95
120
|
this.lineStart = i + 1;
|
|
96
121
|
this.leader = '';
|
|
97
|
-
}
|
|
122
|
+
}
|
|
98
123
|
|
|
99
|
-
|
|
124
|
+
/**
|
|
125
|
+
* @returns {void}
|
|
126
|
+
*/
|
|
127
|
+
return() {
|
|
100
128
|
this.generator.return(this);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// istanbul ignore next
|
|
104
|
-
Scanner.prototype.position = function position() {
|
|
105
|
-
return this.fileName + ':' + (this.lineNo + 1) + ':' + (this.columnStart + 1);
|
|
106
|
-
};
|
|
129
|
+
}
|
|
107
130
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
position() {
|
|
132
|
+
return `${this.fileName}:${this.lineNo + 1}:${this.columnStart + 1}`;
|
|
133
|
+
}
|
|
111
134
|
}
|
package/scope.js
CHANGED
|
@@ -1,57 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import * as Path from './path.js';
|
|
2
|
+
|
|
3
|
+
export default class Scope {
|
|
4
|
+
static tie(ends, name) {
|
|
5
|
+
for (let i = 0; i < ends.length; i++) {
|
|
6
|
+
const end = ends[i];
|
|
7
|
+
if (end.type === 'branch') {
|
|
8
|
+
end.node.branch = name;
|
|
9
|
+
} else {
|
|
10
|
+
end.next = name;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
constructor(story, path, base) {
|
|
8
16
|
this.story = story;
|
|
9
17
|
this.path = path;
|
|
10
18
|
this.base = base;
|
|
11
19
|
Object.seal(this);
|
|
12
|
-
}
|
|
20
|
+
}
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
name() {
|
|
15
23
|
return Path.toName(this.path);
|
|
16
|
-
}
|
|
24
|
+
}
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
create(type, arg, position) {
|
|
19
27
|
return this.story.create(this.path, type, arg, position);
|
|
20
|
-
}
|
|
28
|
+
}
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
next() {
|
|
23
31
|
return new Scope(this.story, Path.next(this.path), this.base);
|
|
24
|
-
}
|
|
32
|
+
}
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
zerothChild() {
|
|
27
35
|
return new Scope(this.story, Path.zerothChild(this.path), this.base);
|
|
28
|
-
}
|
|
36
|
+
}
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
firstChild() {
|
|
31
39
|
return new Scope(this.story, Path.firstChild(this.path), this.base);
|
|
32
|
-
}
|
|
40
|
+
}
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
label(label) {
|
|
35
43
|
return new Scope(this.story, this.base.concat([label, 0]), this.base);
|
|
36
|
-
}
|
|
44
|
+
}
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
tie(nodes, this.name());
|
|
40
|
-
}
|
|
46
|
+
tie(nodes) {
|
|
47
|
+
Scope.tie(nodes, this.name());
|
|
48
|
+
}
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
Scope.prototype.error = function (message) {
|
|
50
|
+
error(message) {
|
|
44
51
|
this.story.error(message);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
Scope.tie = tie;
|
|
48
|
-
function tie(ends, name) {
|
|
49
|
-
for (var i = 0; i < ends.length; i++) {
|
|
50
|
-
var end = ends[i];
|
|
51
|
-
if (end.type === 'branch') {
|
|
52
|
-
end.node.branch = name;
|
|
53
|
-
} else {
|
|
54
|
-
end.next = name;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
52
|
+
}
|
|
57
53
|
}
|