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/link.js
CHANGED
|
@@ -1,58 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
const link = story => {
|
|
2
|
+
const labels = Object.keys(story.states);
|
|
3
|
+
for (let i = 0; i < labels.length; i++) {
|
|
4
|
+
const label = labels[i];
|
|
5
|
+
const state = story.states[label];
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
const link = linker(story, label, state);
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
if (state.label != null) {
|
|
10
|
+
state.label = link('label')(state.label);
|
|
11
|
+
}
|
|
12
|
+
if (state.next != null) {
|
|
13
|
+
state.next = link('next')(state.next);
|
|
14
|
+
}
|
|
15
|
+
if (state.branch != null) {
|
|
16
|
+
state.branch = link('branch')(state.branch);
|
|
17
|
+
}
|
|
18
|
+
if (state.question != null) {
|
|
19
|
+
state.question = state.question.map(link('question'));
|
|
20
|
+
}
|
|
21
|
+
if (state.answer != null) {
|
|
22
|
+
state.answer = state.answer.map(link('answer'));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
10
26
|
|
|
11
|
-
|
|
27
|
+
export default link;
|
|
12
28
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
const linker = (story, context, state) => {
|
|
30
|
+
const parts = context.split('.');
|
|
31
|
+
const ancestry = [];
|
|
32
|
+
while (parts.length > 0) {
|
|
33
|
+
ancestry.push(parts.slice());
|
|
34
|
+
parts.pop();
|
|
35
|
+
}
|
|
36
|
+
ancestry.push([]);
|
|
37
|
+
return role => {
|
|
38
|
+
return label => {
|
|
39
|
+
if (label === 'RET' || label === 'ESC') {
|
|
40
|
+
return label;
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < ancestry.length; i++) {
|
|
43
|
+
let candidate = ancestry[i].slice();
|
|
44
|
+
candidate.push(label);
|
|
45
|
+
candidate = candidate.join('.');
|
|
46
|
+
if (story.states[candidate] != null) {
|
|
47
|
+
return candidate;
|
|
18
48
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
if (state.answer != null) {
|
|
26
|
-
state.answer = state.answer.map(link('answer'));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function linker(story, context, state) {
|
|
32
|
-
var parts = context.split('.');
|
|
33
|
-
var ancestry = [];
|
|
34
|
-
while (parts.length > 0) {
|
|
35
|
-
ancestry.push(parts.slice());
|
|
36
|
-
parts.pop();
|
|
37
|
-
}
|
|
38
|
-
ancestry.push([]);
|
|
39
|
-
return function (role) {
|
|
40
|
-
return function (label) {
|
|
41
|
-
if (label === 'RET' || label === 'ESC') {
|
|
42
|
-
return label;
|
|
43
|
-
}
|
|
44
|
-
for (var i = 0; i < ancestry.length; i++) {
|
|
45
|
-
var candidate = ancestry[i].slice();
|
|
46
|
-
candidate.push(label);
|
|
47
|
-
candidate = candidate.join('.');
|
|
48
|
-
if (story.states[candidate] != null) {
|
|
49
|
-
return candidate;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// istanbul ignore next
|
|
53
|
-
story.error('Could not link ' + role + ' label ' + JSON.stringify(label) + ' at position ' + state.position);
|
|
54
|
-
// istanbul ignore next
|
|
55
|
-
return label;
|
|
56
|
-
};
|
|
49
|
+
}
|
|
50
|
+
story.error(
|
|
51
|
+
`Could not link ${role} label ${JSON.stringify(label)} at position ${state.position}`
|
|
52
|
+
);
|
|
53
|
+
return label;
|
|
57
54
|
};
|
|
58
|
-
}
|
|
55
|
+
};
|
|
56
|
+
};
|
package/outline-lexer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
2
|
|
|
3
3
|
// Transforms a stream of lines with known indentation levels and leaders like
|
|
4
4
|
// bullets, and transforms these into a stream of lines with start and stop
|
|
@@ -9,59 +9,86 @@
|
|
|
9
9
|
|
|
10
10
|
// TODO remove the break emission feature
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
export default class OutlineLexer {
|
|
13
|
+
debug = typeof process === 'object' && process.env.DEBUG_OUTLINE_LEXER;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
top = 0;
|
|
16
|
+
broken = false;
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
/** @typedef {import('./scanner')} Scanner */
|
|
19
|
+
|
|
20
|
+
/** Outline lexer state object, which receives typed text tokens, along
|
|
21
|
+
* with the current scanner. It is expected to return a subsequent state
|
|
22
|
+
* object, which will be retained by the lexer, and receive the subsequent
|
|
23
|
+
* token.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {object} State
|
|
26
|
+
* @prop {(type: string, text: string, sc: Scanner) => State} next
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {State} generator
|
|
31
|
+
*/
|
|
32
|
+
constructor(generator) {
|
|
17
33
|
this.generator = generator;
|
|
18
|
-
this.top = 0;
|
|
19
34
|
this.stack = [this.top];
|
|
20
|
-
|
|
21
|
-
this.debug = debug;
|
|
22
|
-
}
|
|
35
|
+
}
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
/**
|
|
38
|
+
* @param {string} line
|
|
39
|
+
* @param {Scanner} scanner
|
|
40
|
+
* @returns {OutlineLexer}
|
|
41
|
+
*/
|
|
42
|
+
next(line, scanner) {
|
|
26
43
|
if (this.debug) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
44
|
+
console.error(
|
|
45
|
+
'OLL',
|
|
46
|
+
scanner.position(),
|
|
47
|
+
JSON.stringify(line),
|
|
48
|
+
'indent',
|
|
49
|
+
scanner.indent,
|
|
50
|
+
'leader',
|
|
51
|
+
JSON.stringify(scanner.leader),
|
|
52
|
+
'stack',
|
|
53
|
+
this.stack,
|
|
54
|
+
'top',
|
|
55
|
+
this.top
|
|
56
|
+
);
|
|
35
57
|
}
|
|
36
58
|
while (scanner.indent < this.top) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
this.generator = this.generator.next('stop', '', scanner);
|
|
60
|
+
this.stack.pop();
|
|
61
|
+
this.top = this.stack[this.stack.length - 1];
|
|
40
62
|
}
|
|
41
63
|
if (scanner.leader.length !== 0 && scanner.indent > this.top) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
this.generator = this.generator.next('start', scanner.leader, scanner);
|
|
65
|
+
this.stack.push(scanner.indent);
|
|
66
|
+
this.top = scanner.indent;
|
|
67
|
+
this.broken = false;
|
|
46
68
|
} else if (scanner.leader.length !== 0 && scanner.indent === this.top) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
this.generator = this.generator.next('stop', '', scanner);
|
|
70
|
+
this.generator = this.generator.next('start', scanner.leader, scanner);
|
|
71
|
+
this.top = scanner.indent;
|
|
72
|
+
this.broken = false;
|
|
51
73
|
}
|
|
52
74
|
if (line.length) {
|
|
53
|
-
|
|
75
|
+
this.generator = this.generator.next('text', line, scanner);
|
|
54
76
|
} else if (!this.broken) {
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
this.broken = true;
|
|
78
|
+
this.generator = this.generator.next('break', '', scanner);
|
|
57
79
|
}
|
|
58
80
|
return this;
|
|
59
|
-
}
|
|
81
|
+
}
|
|
60
82
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
/**
|
|
84
|
+
* @param {Scanner} scanner
|
|
85
|
+
* @returns {OutlineLexer}
|
|
86
|
+
*/
|
|
87
|
+
return(scanner) {
|
|
88
|
+
for (let i = 0; i < this.stack.length; i++) {
|
|
89
|
+
this.generator = this.generator.next('stop', '', scanner);
|
|
64
90
|
}
|
|
65
91
|
this.stack.length = 0;
|
|
66
92
|
return this;
|
|
67
|
-
}
|
|
93
|
+
}
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,56 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kni",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "A text adventure language and runtime inspired by inkle/ink",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"bin": {
|
|
6
7
|
"kni": "./kni.js"
|
|
7
8
|
},
|
|
8
9
|
"dependencies": {
|
|
9
10
|
"pop-equals": "^1.0.0",
|
|
10
11
|
"shon": "^4.0.0",
|
|
11
|
-
"system": "^1.0.6",
|
|
12
12
|
"table": "^3.7.8",
|
|
13
13
|
"tee": "^0.2.0",
|
|
14
14
|
"xorshift": "^1.1.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
17
|
+
"@babel/core": "^7.13.1",
|
|
18
|
+
"@babel/eslint-parser": "^7.13.4",
|
|
19
|
+
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
|
20
|
+
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
|
21
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
22
|
+
"@types/node": "^14.14.31",
|
|
23
|
+
"eslint": "^7.20.0",
|
|
24
|
+
"nyc": "^15.1.0",
|
|
25
|
+
"open": "^7.4.2",
|
|
26
|
+
"prettier": "^3.7.1",
|
|
27
|
+
"rimraf": "^3.0.2",
|
|
28
|
+
"rollup": "^4.53.3",
|
|
29
|
+
"strip-ansi": "^3.0.1",
|
|
30
|
+
"typescript": "^4.2.2"
|
|
21
31
|
},
|
|
22
32
|
"files": [
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"evaluate.js",
|
|
28
|
-
"excerpt.js",
|
|
29
|
-
"grammar.js",
|
|
30
|
-
"html.js",
|
|
31
|
-
"inline-lexer.js",
|
|
32
|
-
"kni.js",
|
|
33
|
-
"kni.json",
|
|
34
|
-
"link.js",
|
|
35
|
-
"outline-lexer.js",
|
|
36
|
-
"parser.js",
|
|
37
|
-
"path.js",
|
|
38
|
-
"readline.js",
|
|
39
|
-
"scanner.js",
|
|
40
|
-
"scope.js",
|
|
41
|
-
"story.js",
|
|
42
|
-
"template.js",
|
|
43
|
-
"translate-json.js",
|
|
44
|
-
"verify.js",
|
|
45
|
-
"wrapper.js"
|
|
33
|
+
"*.js",
|
|
34
|
+
"!*-test.js",
|
|
35
|
+
"*.json",
|
|
36
|
+
"!.*.json"
|
|
46
37
|
],
|
|
47
38
|
"scripts": {
|
|
48
|
-
"
|
|
39
|
+
"format": "prettier --write \"**/*.js\"",
|
|
40
|
+
"format:check": "prettier --check \"**/*.js\"",
|
|
49
41
|
"lint": "eslint .",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
42
|
+
"lint-fix": "eslint . --fix",
|
|
43
|
+
"test": "node -- test.js",
|
|
44
|
+
"cover": "nyc npm test",
|
|
45
|
+
"check-cover": "nyc check-coverage",
|
|
46
|
+
"view-cover": "npm run cover && open ./coverage/index.html",
|
|
54
47
|
"usage": "usage2json kni.usage > kni.json"
|
|
55
48
|
},
|
|
56
49
|
"translators": {
|
package/parser.js
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export default class Parser {
|
|
4
|
+
debug = typeof process === 'object' && process.env.DEBUG_PARSER;
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
/** @typedef {import('./scanner')} Scanner */
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} State
|
|
10
|
+
* @prop {(type: string, space: string, text: string, sc: Scanner) => State} next
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {State} generator
|
|
15
|
+
*/
|
|
16
|
+
constructor(generator) {
|
|
8
17
|
this.generator = generator;
|
|
9
|
-
|
|
10
|
-
}
|
|
18
|
+
}
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} type
|
|
22
|
+
* @param {string} space
|
|
23
|
+
* @param {string} text
|
|
24
|
+
* @param {Scanner} scanner
|
|
25
|
+
* @returns {Parser}
|
|
26
|
+
*/
|
|
27
|
+
next(type, space, text, scanner) {
|
|
28
|
+
const prior = this.generator.constructor.name;
|
|
14
29
|
this.generator = this.generator.next(type, space, text, scanner);
|
|
15
|
-
// istanbul ignore if
|
|
16
|
-
if (!this.generator) {
|
|
17
|
-
throw new Error(prior + ' returned undefined next state given ' + type + '/' + text + ' at ' + scanner.position());
|
|
18
|
-
}
|
|
19
|
-
// istanbul ignore if
|
|
20
30
|
if (this.debug) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
console.error(
|
|
32
|
+
'PAR',
|
|
33
|
+
scanner.position(),
|
|
34
|
+
type,
|
|
35
|
+
JSON.stringify(text),
|
|
36
|
+
`${prior}->${this.generator.constructor.name}`
|
|
37
|
+
);
|
|
27
38
|
}
|
|
28
39
|
return this;
|
|
29
|
-
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/path.js
CHANGED
|
@@ -1,46 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
path = path.slice();
|
|
36
|
-
path.push(1);
|
|
37
|
-
return path;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
exports.zerothChild = zerothChildPath;
|
|
41
|
-
|
|
42
|
-
function zerothChildPath(path) {
|
|
43
|
-
path = path.slice();
|
|
44
|
-
path.push(0);
|
|
45
|
-
return path;
|
|
46
|
-
}
|
|
1
|
+
export const start = () => {
|
|
2
|
+
return ['start'];
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const toName = path => {
|
|
6
|
+
let name = path[0];
|
|
7
|
+
let i;
|
|
8
|
+
for (i = 1; i < path.length - 1; i++) {
|
|
9
|
+
name += `.${path[i]}`;
|
|
10
|
+
}
|
|
11
|
+
const last = path[i];
|
|
12
|
+
if (path.length > 1 && last !== 0) {
|
|
13
|
+
name += `.${last}`;
|
|
14
|
+
}
|
|
15
|
+
return name;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const next = path => {
|
|
19
|
+
path = path.slice();
|
|
20
|
+
path[path.length - 1]++;
|
|
21
|
+
return path;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const firstChild = path => {
|
|
25
|
+
path = path.slice();
|
|
26
|
+
path.push(1);
|
|
27
|
+
return path;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const zerothChild = path => {
|
|
31
|
+
path = path.slice();
|
|
32
|
+
path.push(0);
|
|
33
|
+
return path;
|
|
34
|
+
};
|