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/grammar.js
CHANGED
|
@@ -1,172 +1,194 @@
|
|
|
1
|
-
|
|
1
|
+
import Scope from './scope.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const start = (story, path, base) => {
|
|
4
|
+
const scope = new Scope(story, path, base);
|
|
5
|
+
const stop = new Stop(scope);
|
|
6
|
+
const start = scope.create('goto', 'RET', '1:1');
|
|
7
|
+
return new Thread(scope.zerothChild(), stop, [start], []);
|
|
8
|
+
};
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
var scope = new Scope(story, path, base);
|
|
10
|
-
var stop = new Stop(scope);
|
|
11
|
-
var start = scope.create('goto', 'RET', '1:1');
|
|
12
|
-
return new Thread(scope.zerothChild(), stop, [start], []);
|
|
13
|
-
}
|
|
10
|
+
export default start;
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
class Stop {
|
|
13
|
+
constructor(scope) {
|
|
16
14
|
this.scope = scope;
|
|
17
15
|
Object.freeze(this);
|
|
18
|
-
}
|
|
16
|
+
}
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
Stop.prototype.next = function next(type, space, text, scanner) {
|
|
18
|
+
next(type, _space, text, scanner) {
|
|
22
19
|
// The only way to reach this method is for there to be a bug in the
|
|
23
20
|
// outline lexer, or a bug in the grammar.
|
|
24
21
|
if (type !== 'stop') {
|
|
25
|
-
|
|
22
|
+
this.scope.error(
|
|
23
|
+
`${scanner.position()}: Expected end of file, got ${tokenName(type, text)}.`
|
|
24
|
+
);
|
|
26
25
|
}
|
|
27
26
|
return new End();
|
|
28
|
-
}
|
|
27
|
+
}
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
return(_scope, rets, escs, _scanner) {
|
|
31
30
|
Scope.tie(rets, 'RET');
|
|
32
31
|
Scope.tie(escs, 'ESC');
|
|
33
32
|
return this;
|
|
34
|
-
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
class End {
|
|
37
|
+
constructor() {
|
|
37
38
|
Object.freeze(this);
|
|
38
|
-
}
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
End.prototype.next = function next(type, space, text, scanner) {
|
|
41
|
+
next(_type, _space, _text, _scanner) {
|
|
42
42
|
return this;
|
|
43
|
-
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
44
45
|
|
|
45
46
|
// rets are tied to the next instruction
|
|
46
47
|
// escs are tied off after the next encountered prompt
|
|
47
|
-
|
|
48
|
+
class Thread {
|
|
49
|
+
constructor(scope, parent, rets, escs) {
|
|
48
50
|
this.scope = scope;
|
|
49
51
|
this.parent = parent;
|
|
50
52
|
this.rets = rets;
|
|
51
53
|
this.escs = escs;
|
|
52
54
|
Object.freeze(this);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
} else if (type === 'start') {
|
|
86
|
-
if (text === '+' || text === '*') {
|
|
87
|
-
return new MaybeOption(this.scope, new ThenExpect('stop', '', this), this.rets, [], text);
|
|
88
|
-
} else if (text === '-') {
|
|
89
|
-
return new MaybeThread(this.scope, new ThenExpect('stop', '', this), this.rets, [], [], ' ');
|
|
90
|
-
} else if (text === '>') {
|
|
91
|
-
// tie off rets to the prompt.
|
|
92
|
-
this.scope.tie(this.rets);
|
|
93
|
-
// promote escs to rets, tying them off after the prompt.
|
|
94
|
-
var rets = this.escs.slice();
|
|
95
|
-
this.escs.length = 0;
|
|
96
|
-
return new Ask(this.scope, new ThenExpect('stop', '', this), rets, []);
|
|
97
|
-
} else { // if text === '!') {
|
|
98
|
-
return new Program(this.scope, new ThenExpect('stop', '', this), this.rets, []);
|
|
99
|
-
}
|
|
100
|
-
} else if (type === 'dash') {
|
|
101
|
-
var node = this.scope.create('rule', null, scanner.position());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
next(type, space, text, scanner) {
|
|
58
|
+
if (
|
|
59
|
+
type === 'symbol' ||
|
|
60
|
+
type === 'alphanum' ||
|
|
61
|
+
type === 'number' ||
|
|
62
|
+
type === 'literal' ||
|
|
63
|
+
text === '--' ||
|
|
64
|
+
text === '---'
|
|
65
|
+
) {
|
|
66
|
+
return new Text(this.scope, space, text, this, this.rets);
|
|
67
|
+
} else if (type === 'token') {
|
|
68
|
+
if (text === '{') {
|
|
69
|
+
return new Block(this.scope, new ThenExpect('token', '}', this), this.rets);
|
|
70
|
+
} else if (text === '@') {
|
|
71
|
+
return label(this.scope, new Label(this, this.rets));
|
|
72
|
+
} else if (text === '->') {
|
|
73
|
+
return label(this.scope, new Goto(this, this.rets));
|
|
74
|
+
} else if (text === '<-') {
|
|
75
|
+
// Explicitly tie rets to null by dropping them.
|
|
76
|
+
Scope.tie(this.rets, 'RET');
|
|
77
|
+
// Continue carrying escs to the next encountered prompt.
|
|
78
|
+
// Advance the path so that option thread don't appear empty.
|
|
79
|
+
return new Thread(this.scope.next(), this.parent, [], this.escs);
|
|
80
|
+
} else if (text === '/') {
|
|
81
|
+
const node = this.scope.create('break', null, scanner.position());
|
|
82
|
+
this.scope.tie(this.rets);
|
|
83
|
+
return new Thread(this.scope.next(), this.parent, [node], this.escs);
|
|
84
|
+
} else if (text === '//') {
|
|
85
|
+
const node = this.scope.create('paragraph', null, scanner.position());
|
|
102
86
|
this.scope.tie(this.rets);
|
|
103
87
|
return new Thread(this.scope.next(), this.parent, [node], this.escs);
|
|
88
|
+
} else if (text === '{"' || text === "{'" || text === '"}' || text === "'}") {
|
|
89
|
+
return new Text(this.scope, space, '', this, this.rets).next(type, '', text, scanner);
|
|
90
|
+
} else if (text === '<') {
|
|
91
|
+
return label(this.scope, new ThenExpect('token', '>', new Cue(this, this.rets, this.escs)));
|
|
92
|
+
}
|
|
93
|
+
} else if (type === 'start') {
|
|
94
|
+
if (text === '+' || text === '*') {
|
|
95
|
+
return new MaybeOption(this.scope, new ThenExpect('stop', '', this), this.rets, [], text);
|
|
96
|
+
} else if (text === '-') {
|
|
97
|
+
return new MaybeThread(
|
|
98
|
+
this.scope,
|
|
99
|
+
new ThenExpect('stop', '', this),
|
|
100
|
+
this.rets,
|
|
101
|
+
[],
|
|
102
|
+
[],
|
|
103
|
+
' '
|
|
104
|
+
);
|
|
105
|
+
} else if (text === '>') {
|
|
106
|
+
// tie off rets to the prompt.
|
|
107
|
+
this.scope.tie(this.rets);
|
|
108
|
+
// promote escs to rets, tying them off after the prompt.
|
|
109
|
+
const rets = this.escs.slice();
|
|
110
|
+
this.escs.length = 0;
|
|
111
|
+
return new Ask(this.scope, new ThenExpect('stop', '', this), rets, []);
|
|
112
|
+
} else {
|
|
113
|
+
// if text === '!') {
|
|
114
|
+
return new Program(this.scope, new ThenExpect('stop', '', this), this.rets, []);
|
|
115
|
+
}
|
|
116
|
+
} else if (type === 'dash') {
|
|
117
|
+
const node = this.scope.create('rule', null, scanner.position());
|
|
118
|
+
this.scope.tie(this.rets);
|
|
119
|
+
return new Thread(this.scope.next(), this.parent, [node], this.escs);
|
|
104
120
|
} else if (type === 'break') {
|
|
105
|
-
|
|
121
|
+
return this;
|
|
106
122
|
}
|
|
107
123
|
if (type === 'stop' || text === '|' || text === ']' || text === '[' || text === '}') {
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
return this.parent
|
|
125
|
+
.return(this.scope, this.rets, this.escs, scanner)
|
|
126
|
+
.next(type, space, text, scanner);
|
|
110
127
|
}
|
|
111
128
|
return new Text(this.scope, space, text, this, this.rets);
|
|
112
|
-
}
|
|
129
|
+
}
|
|
113
130
|
|
|
114
|
-
|
|
131
|
+
return(scope, rets, escs, _scanner) {
|
|
115
132
|
// All rules above (in next) guarantee that this.rets has been passed to
|
|
116
133
|
// any rule that might use them. If the rule fails to use them, they must
|
|
117
134
|
// return them. However, escs are never passed to any rule that returns.
|
|
118
135
|
return new Thread(scope, this.parent, rets, this.escs.concat(escs));
|
|
119
|
-
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
120
138
|
|
|
121
|
-
|
|
139
|
+
class Text {
|
|
140
|
+
constructor(scope, lift, text, parent, rets) {
|
|
122
141
|
this.scope = scope;
|
|
123
142
|
this.lift = lift;
|
|
124
143
|
this.text = text;
|
|
125
144
|
this.parent = parent;
|
|
126
145
|
this.rets = rets;
|
|
127
146
|
Object.seal(this);
|
|
128
|
-
}
|
|
147
|
+
}
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
next(type, space, text, scanner) {
|
|
131
150
|
if (type === 'alphanum' || type === 'number' || type === 'symbol' || type === 'literal') {
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
this.text += space + text;
|
|
152
|
+
return this;
|
|
134
153
|
} else if (type === 'token') {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return this;
|
|
138
|
-
} else if (text === '"}') {
|
|
139
|
-
this.text += space + '”';
|
|
140
|
-
return this;
|
|
141
|
-
} else if (text === '{\'') {
|
|
142
|
-
this.text += space + '‘';
|
|
143
|
-
return this;
|
|
144
|
-
} else if (text === '\'}') {
|
|
145
|
-
this.text += space + '’';
|
|
146
|
-
return this;
|
|
147
|
-
} else if ((text === '/' || text === '//') && space === '') {
|
|
148
|
-
// This is an exception to accommodate hyperlinks.
|
|
149
|
-
// Paragraphs and line breaks must be expressed by the / and //
|
|
150
|
-
// tokens and must be preceded by space.
|
|
151
|
-
this.text += text;
|
|
152
|
-
return this;
|
|
153
|
-
}
|
|
154
|
-
} else if (text === '--') {
|
|
155
|
-
this.text += space + '–'; // en-dash
|
|
154
|
+
if (text === '{"') {
|
|
155
|
+
this.text += `${space}“`;
|
|
156
156
|
return this;
|
|
157
|
-
|
|
158
|
-
this.text += space
|
|
157
|
+
} else if (text === '"}') {
|
|
158
|
+
this.text += `${space}”`;
|
|
159
|
+
return this;
|
|
160
|
+
} else if (text === "{'") {
|
|
161
|
+
this.text += `${space}‘`;
|
|
159
162
|
return this;
|
|
163
|
+
} else if (text === "'}") {
|
|
164
|
+
this.text += `${space}’`;
|
|
165
|
+
return this;
|
|
166
|
+
} else if ((text === '/' || text === '//') && space === '') {
|
|
167
|
+
// This is an exception to accommodate hyperlinks.
|
|
168
|
+
// Paragraphs and line breaks must be expressed by the / and //
|
|
169
|
+
// tokens and must be preceded by space.
|
|
170
|
+
this.text += text;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
} else if (text === '--') {
|
|
174
|
+
this.text += `${space}–`; // en-dash
|
|
175
|
+
return this;
|
|
176
|
+
} else if (text === '---') {
|
|
177
|
+
this.text += `${space}—`; // em-dash
|
|
178
|
+
return this;
|
|
160
179
|
}
|
|
161
180
|
this.scope.tie(this.rets);
|
|
162
|
-
|
|
181
|
+
const node = this.scope.create('text', this.text, scanner.position());
|
|
163
182
|
node.lift = this.lift;
|
|
164
183
|
node.drop = space;
|
|
165
|
-
return this.parent
|
|
166
|
-
|
|
167
|
-
|
|
184
|
+
return this.parent
|
|
185
|
+
.return(this.scope.next(), [node], [], scanner)
|
|
186
|
+
.next(type, space, text, scanner);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
168
189
|
|
|
169
|
-
|
|
190
|
+
class MaybeThread {
|
|
191
|
+
constructor(scope, parent, rets, escs, skips, space) {
|
|
170
192
|
this.scope = scope;
|
|
171
193
|
this.parent = parent;
|
|
172
194
|
this.rets = rets;
|
|
@@ -174,40 +196,59 @@ function MaybeThread(scope, parent, rets, escs, skips, space) {
|
|
|
174
196
|
this.skips = skips;
|
|
175
197
|
this.space = space || '';
|
|
176
198
|
Object.freeze(this);
|
|
177
|
-
}
|
|
199
|
+
}
|
|
178
200
|
|
|
179
|
-
|
|
201
|
+
next(type, space, text, scanner) {
|
|
180
202
|
if (type === 'token') {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
203
|
+
if (text === '{') {
|
|
204
|
+
return expression(
|
|
205
|
+
this.scope,
|
|
206
|
+
new ThenExpect(
|
|
207
|
+
'token',
|
|
208
|
+
'}',
|
|
209
|
+
new ThreadCondition(this.parent, this.rets, this.escs, this.skips)
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
186
213
|
}
|
|
187
|
-
return new Thread(this.scope, this, this.rets, this.escs)
|
|
188
|
-
|
|
189
|
-
|
|
214
|
+
return new Thread(this.scope, this, this.rets, this.escs).next(
|
|
215
|
+
type,
|
|
216
|
+
this.space || space,
|
|
217
|
+
text,
|
|
218
|
+
scanner
|
|
219
|
+
);
|
|
220
|
+
}
|
|
190
221
|
|
|
191
|
-
|
|
222
|
+
return(scope, rets, escs, scanner) {
|
|
192
223
|
return this.parent.return(scope, rets.concat(this.skips), escs, scanner);
|
|
193
|
-
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
194
226
|
|
|
195
|
-
|
|
227
|
+
class ThreadCondition {
|
|
228
|
+
constructor(parent, rets, escs, skips) {
|
|
196
229
|
this.parent = parent;
|
|
197
230
|
this.rets = rets;
|
|
198
231
|
this.escs = escs;
|
|
199
232
|
this.skips = skips;
|
|
200
233
|
Object.freeze(this);
|
|
201
|
-
}
|
|
234
|
+
}
|
|
202
235
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
236
|
+
return(scope, args, scanner) {
|
|
237
|
+
const node = scope.create('jump', invertExpression(args), scanner.position());
|
|
238
|
+
const branch = new Branch(node);
|
|
206
239
|
scope.tie(this.rets);
|
|
207
|
-
return new MaybeThread(
|
|
208
|
-
|
|
240
|
+
return new MaybeThread(
|
|
241
|
+
scope.next(),
|
|
242
|
+
this.parent,
|
|
243
|
+
[node],
|
|
244
|
+
this.escs,
|
|
245
|
+
this.skips.concat([branch])
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
209
249
|
|
|
210
|
-
|
|
250
|
+
class MaybeOption {
|
|
251
|
+
constructor(scope, parent, rets, escs, leader) {
|
|
211
252
|
this.scope = scope;
|
|
212
253
|
this.at = scope;
|
|
213
254
|
this.parent = parent;
|
|
@@ -219,158 +260,183 @@ function MaybeOption(scope, parent, rets, escs, leader) {
|
|
|
219
260
|
this.keywords = {};
|
|
220
261
|
this.descended = false;
|
|
221
262
|
Object.seal(this);
|
|
222
|
-
}
|
|
263
|
+
}
|
|
223
264
|
|
|
224
|
-
|
|
265
|
+
next(type, space, text, scanner) {
|
|
225
266
|
if (type === 'token') {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
267
|
+
if (text === '{') {
|
|
268
|
+
return new OptionOperator(this.scope, new ThenExpect('token', '}', this));
|
|
269
|
+
}
|
|
270
|
+
// Recognize the inequality token as individual keyword tokens with an
|
|
271
|
+
// empty string amid them in this context.
|
|
272
|
+
if (text === '<>') {
|
|
273
|
+
return this.return(this.scope, 'keyword', '');
|
|
274
|
+
}
|
|
275
|
+
if (text === '<') {
|
|
276
|
+
return new Keyword(this.scope, this);
|
|
277
|
+
}
|
|
238
278
|
}
|
|
239
279
|
return this.option(scanner).next(type, space, text, scanner);
|
|
240
|
-
}
|
|
280
|
+
}
|
|
241
281
|
|
|
242
|
-
|
|
282
|
+
return(_scope, operator, expression, modifier, _scanner) {
|
|
243
283
|
if (operator === '+' || operator === '-' || operator === '!') {
|
|
244
|
-
|
|
284
|
+
modifier = modifier || ['val', 1];
|
|
245
285
|
}
|
|
246
286
|
if (operator === '?') {
|
|
247
|
-
|
|
287
|
+
modifier = modifier || ['val', 0];
|
|
248
288
|
}
|
|
249
289
|
if (operator === '+' || operator === '-') {
|
|
250
|
-
|
|
290
|
+
this.consequences.push([expression, [operator, expression, modifier]]);
|
|
251
291
|
}
|
|
252
292
|
if (operator === '-') {
|
|
253
|
-
|
|
293
|
+
this.conditions.push(['>=', expression, modifier]);
|
|
254
294
|
}
|
|
255
295
|
if (operator === '') {
|
|
256
|
-
|
|
296
|
+
this.conditions.push(expression);
|
|
257
297
|
}
|
|
258
298
|
if (operator === '=' || operator === '!' || operator === '?') {
|
|
259
|
-
|
|
260
|
-
|
|
299
|
+
this.conditions.push(['<>', expression, modifier]);
|
|
300
|
+
this.consequences.push([expression, modifier]);
|
|
261
301
|
}
|
|
262
302
|
if (operator === 'keyword') {
|
|
263
|
-
|
|
303
|
+
this.keywords[expression] = true;
|
|
264
304
|
}
|
|
265
305
|
return this;
|
|
266
|
-
}
|
|
306
|
+
}
|
|
267
307
|
|
|
268
|
-
|
|
308
|
+
advance() {
|
|
269
309
|
if (this.descended) {
|
|
270
|
-
|
|
310
|
+
this.at = this.at.next();
|
|
271
311
|
} else {
|
|
272
|
-
|
|
273
|
-
|
|
312
|
+
this.at = this.at.firstChild();
|
|
313
|
+
this.descended = true;
|
|
274
314
|
}
|
|
275
|
-
}
|
|
315
|
+
}
|
|
276
316
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
317
|
+
option(scanner) {
|
|
318
|
+
const variable = this.scope.name();
|
|
319
|
+
const rets = [];
|
|
280
320
|
|
|
281
321
|
this.at.tie(this.rets);
|
|
282
322
|
|
|
283
323
|
if (this.leader === '*') {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
324
|
+
this.consequences.push([
|
|
325
|
+
['get', variable],
|
|
326
|
+
['+', ['get', variable], ['val', 1]],
|
|
327
|
+
]);
|
|
328
|
+
const jump = this.at.create(
|
|
329
|
+
'jump',
|
|
330
|
+
['<>', ['get', variable], ['val', 0]],
|
|
331
|
+
scanner.position()
|
|
332
|
+
);
|
|
333
|
+
const jumpBranch = new Branch(jump);
|
|
334
|
+
rets.push(jumpBranch);
|
|
335
|
+
this.advance();
|
|
336
|
+
this.at.tie([jump]);
|
|
290
337
|
}
|
|
291
338
|
|
|
292
|
-
for (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
this.at.tie([jump]);
|
|
339
|
+
for (const condition of this.conditions) {
|
|
340
|
+
const jump = this.at.create('jump', ['==', condition, ['val', 0]], scanner.position());
|
|
341
|
+
const jumpBranch = new Branch(jump);
|
|
342
|
+
rets.push(jumpBranch);
|
|
343
|
+
this.advance();
|
|
344
|
+
this.at.tie([jump]);
|
|
299
345
|
}
|
|
300
346
|
|
|
301
|
-
|
|
347
|
+
const option = new Option(
|
|
348
|
+
this.scope,
|
|
349
|
+
this.parent,
|
|
350
|
+
rets,
|
|
351
|
+
this.escs,
|
|
352
|
+
this.leader,
|
|
353
|
+
this.consequences
|
|
354
|
+
);
|
|
302
355
|
option.node = this.at.create('option', null, scanner.position());
|
|
303
356
|
option.node.keywords = Object.keys(this.keywords).sort();
|
|
304
357
|
this.advance();
|
|
305
358
|
|
|
306
359
|
option.next = this.at;
|
|
307
|
-
return option.thread(
|
|
308
|
-
|
|
309
|
-
|
|
360
|
+
return option.thread(
|
|
361
|
+
scanner,
|
|
362
|
+
new OptionThread(this.at, option, [], option, 'qa', AfterInitialQA)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
310
366
|
|
|
311
367
|
// Captures <keyword> annotations on options.
|
|
312
|
-
|
|
368
|
+
class Keyword {
|
|
369
|
+
constructor(scope, parent) {
|
|
313
370
|
this.scope = scope;
|
|
314
371
|
this.parent = parent;
|
|
315
372
|
this.keyword = '';
|
|
316
373
|
this.space = '';
|
|
317
374
|
Object.seal(this);
|
|
318
|
-
}
|
|
375
|
+
}
|
|
319
376
|
|
|
320
|
-
|
|
377
|
+
next(_type, space, text, _scanner) {
|
|
321
378
|
if (text === '>') {
|
|
322
|
-
|
|
379
|
+
return this.parent.return(this.scope, 'keyword', this.keyword);
|
|
323
380
|
}
|
|
324
381
|
this.keyword += (this.space && space) + text;
|
|
325
382
|
this.space = ' ';
|
|
326
383
|
return this;
|
|
327
|
-
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
328
386
|
|
|
329
387
|
// {+x}, {-x}, {!x}, {+n x}, {-n x}, {=n x} or simply {x}
|
|
330
|
-
|
|
388
|
+
class OptionOperator {
|
|
389
|
+
constructor(scope, parent) {
|
|
331
390
|
this.scope = scope;
|
|
332
391
|
this.parent = parent;
|
|
333
392
|
Object.freeze(this);
|
|
334
|
-
}
|
|
393
|
+
}
|
|
335
394
|
|
|
336
|
-
|
|
395
|
+
next(type, space, text, scanner) {
|
|
337
396
|
if (text === '+' || text === '-' || text === '=' || text === '!' || text === '?') {
|
|
338
|
-
|
|
339
|
-
new OptionArgument(this.parent, text));
|
|
397
|
+
return expression(this.scope, new OptionArgument(this.parent, text));
|
|
340
398
|
} else {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
399
|
+
return expression(this.scope, new OptionArgument2(this.parent, '')).next(
|
|
400
|
+
type,
|
|
401
|
+
space,
|
|
402
|
+
text,
|
|
403
|
+
scanner
|
|
404
|
+
);
|
|
344
405
|
}
|
|
345
|
-
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
346
408
|
|
|
347
|
-
|
|
409
|
+
class OptionArgument {
|
|
410
|
+
constructor(parent, operator) {
|
|
348
411
|
this.parent = parent;
|
|
349
412
|
this.operator = operator;
|
|
350
413
|
Object.freeze(this);
|
|
351
|
-
}
|
|
414
|
+
}
|
|
352
415
|
|
|
353
|
-
|
|
416
|
+
return(scope, args, scanner) {
|
|
354
417
|
if (args[0] === 'get' || args[0] === 'var') {
|
|
355
|
-
|
|
418
|
+
return this.parent.return(scope, this.operator, args, this.args, scanner);
|
|
356
419
|
} else {
|
|
357
|
-
|
|
358
|
-
new OptionArgument2(this.parent, this.operator, args));
|
|
420
|
+
return expression(scope, new OptionArgument2(this.parent, this.operator, args));
|
|
359
421
|
}
|
|
360
|
-
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
361
424
|
|
|
362
|
-
|
|
425
|
+
class OptionArgument2 {
|
|
426
|
+
constructor(parent, operator, args) {
|
|
363
427
|
this.parent = parent;
|
|
364
428
|
this.operator = operator;
|
|
365
429
|
this.args = args;
|
|
366
430
|
Object.freeze(this);
|
|
367
|
-
}
|
|
431
|
+
}
|
|
368
432
|
|
|
369
|
-
|
|
433
|
+
return(scope, args, scanner) {
|
|
370
434
|
return this.parent.return(scope, this.operator, args, this.args, scanner);
|
|
371
|
-
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
372
437
|
|
|
373
|
-
|
|
438
|
+
class Option {
|
|
439
|
+
constructor(scope, parent, rets, escs, leader, consequences) {
|
|
374
440
|
this.scope = scope;
|
|
375
441
|
this.parent = parent;
|
|
376
442
|
this.rets = rets; // to tie off to the next option
|
|
@@ -382,52 +448,54 @@ function Option(scope, parent, rets, escs, leader, consequences) {
|
|
|
382
448
|
this.mode = '';
|
|
383
449
|
this.branch = null;
|
|
384
450
|
Object.seal(this);
|
|
385
|
-
}
|
|
451
|
+
}
|
|
386
452
|
|
|
387
|
-
|
|
453
|
+
return(scope, rets, escs, scanner) {
|
|
388
454
|
// Create a jump from the end of the answer.
|
|
389
455
|
if (this.mode !== 'a') {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
456
|
+
// If the answer is reused in the question, create a dedicated jump and
|
|
457
|
+
// add it to the end of the answer.
|
|
458
|
+
const jump = scope.create('goto', 'RET', scanner.position());
|
|
459
|
+
this.node.answer.push(scope.name());
|
|
460
|
+
rets.push(jump);
|
|
395
461
|
}
|
|
396
462
|
|
|
397
463
|
return this.parent.return(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
464
|
+
this.scope.next(),
|
|
465
|
+
this.rets.concat([this.node]),
|
|
466
|
+
this.escs.concat(rets, escs),
|
|
467
|
+
scanner
|
|
402
468
|
);
|
|
403
|
-
}
|
|
469
|
+
}
|
|
404
470
|
|
|
405
|
-
|
|
471
|
+
thread(scanner, parent) {
|
|
406
472
|
// Creat a dummy node, to replace if necessary, for arcs that begin with a
|
|
407
473
|
// goto/divert arrow that otherwise would have loose rets to forward.
|
|
408
|
-
|
|
474
|
+
const placeholder = this.next.create('goto', 'RET', scanner.position());
|
|
409
475
|
return new Thread(this.next, parent, [placeholder], []);
|
|
410
|
-
}
|
|
476
|
+
}
|
|
411
477
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
478
|
+
push(scope, mode) {
|
|
479
|
+
const next = this.next.name();
|
|
480
|
+
const end = scope.name();
|
|
415
481
|
if (next !== end) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
482
|
+
if (mode === 'q' || mode === 'qa') {
|
|
483
|
+
this.node.question.push(next);
|
|
484
|
+
}
|
|
485
|
+
if (mode === 'a' || mode === 'qa') {
|
|
486
|
+
this.node.answer.push(next);
|
|
487
|
+
}
|
|
488
|
+
this.next = scope;
|
|
489
|
+
this.mode = mode;
|
|
424
490
|
}
|
|
425
|
-
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
426
493
|
|
|
427
494
|
// An option thread captures the end of an arc, and if the path has advanced,
|
|
428
495
|
// adds that arc to the option's questions and/or answer depending on the
|
|
429
496
|
// "mode" ("q", "a", or "qa") and proceeds to the following state.
|
|
430
|
-
|
|
497
|
+
class OptionThread {
|
|
498
|
+
constructor(scope, parent, rets, option, mode, Next) {
|
|
431
499
|
this.scope = scope;
|
|
432
500
|
this.parent = parent;
|
|
433
501
|
this.rets = rets;
|
|
@@ -435,43 +503,46 @@ function OptionThread(scope, parent, rets, option, mode, Next) {
|
|
|
435
503
|
this.mode = mode;
|
|
436
504
|
this.Next = Next;
|
|
437
505
|
Object.freeze(this);
|
|
438
|
-
}
|
|
506
|
+
}
|
|
439
507
|
|
|
440
|
-
|
|
508
|
+
return(scope, rets, escs, _scanner) {
|
|
441
509
|
this.option.push(scope, this.mode);
|
|
442
|
-
// TODO investigate whether we can consistently tie
|
|
510
|
+
// TODO investigate whether we can consistently tie off received rets
|
|
443
511
|
// instead of passing them forward to OptionThread, which consistently
|
|
444
512
|
// just terminates them on their behalf.
|
|
445
513
|
Scope.tie(this.rets, 'RET');
|
|
446
514
|
// TODO no test exercises this kind of jump.
|
|
447
515
|
Scope.tie(escs, 'ESC');
|
|
448
516
|
return new this.Next(scope, this.parent, rets, this.option);
|
|
449
|
-
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
450
519
|
|
|
451
520
|
// Every option begins with a (potentially empty) thread before the first open
|
|
452
521
|
// backet that will contribute both to the question and the answer.
|
|
453
|
-
|
|
522
|
+
class AfterInitialQA {
|
|
523
|
+
constructor(scope, parent, rets, option) {
|
|
454
524
|
this.scope = scope;
|
|
455
525
|
this.parent = parent;
|
|
456
526
|
this.rets = rets;
|
|
457
527
|
this.option = option;
|
|
458
528
|
Object.freeze(this);
|
|
459
|
-
}
|
|
529
|
+
}
|
|
460
530
|
|
|
461
|
-
|
|
462
|
-
// istanbul ignore else
|
|
531
|
+
next(type, _space, text, scanner) {
|
|
463
532
|
if (type === 'token' && text === '[') {
|
|
464
|
-
|
|
533
|
+
return this.option.thread(scanner, new AfterQorA(this.scope, this, this.rets, this.option));
|
|
465
534
|
} else {
|
|
466
|
-
|
|
467
|
-
|
|
535
|
+
this.scope.error(
|
|
536
|
+
`${scanner.position()}: Expected "[]" brackets in option but got ${tokenName(type, text)}.`
|
|
537
|
+
);
|
|
538
|
+
return this.return(this.scope, this.rets, [], scanner);
|
|
468
539
|
}
|
|
469
|
-
}
|
|
540
|
+
}
|
|
470
541
|
|
|
471
|
-
// The thread returns to this level after capturing the bracketed terms,
|
|
472
|
-
// which anything and everything to the end of the block contributes
|
|
473
|
-
// answer.
|
|
474
|
-
|
|
542
|
+
// The thread returns to this level after capturing the bracketed terms,
|
|
543
|
+
// after which anything and everything to the end of the block contributes
|
|
544
|
+
// to the answer.
|
|
545
|
+
return(scope, rets, escs, scanner) {
|
|
475
546
|
Scope.tie(rets, 'RET');
|
|
476
547
|
// TODO no test exercises these escs.
|
|
477
548
|
Scope.tie(escs, 'ESC');
|
|
@@ -479,419 +550,497 @@ AfterInitialQA.prototype.return = function _return(scope, rets, escs, scanner) {
|
|
|
479
550
|
rets = [];
|
|
480
551
|
|
|
481
552
|
// Thread consequences, including incrementing the option variable name
|
|
482
|
-
|
|
553
|
+
const consequences = this.option.consequences;
|
|
483
554
|
if (consequences.length) {
|
|
484
|
-
|
|
555
|
+
this.option.node.answer.push(scope.name());
|
|
485
556
|
}
|
|
486
|
-
for (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
rets = [node];
|
|
557
|
+
for (const consequence of consequences) {
|
|
558
|
+
const node = scope.create('move', null, scanner.position());
|
|
559
|
+
node.source = consequence[1];
|
|
560
|
+
node.target = consequence[0];
|
|
561
|
+
scope.tie(rets);
|
|
562
|
+
scope = scope.next();
|
|
563
|
+
rets = [node];
|
|
494
564
|
}
|
|
495
565
|
|
|
496
566
|
this.option.next = scope;
|
|
497
|
-
return this.option.thread(
|
|
498
|
-
|
|
499
|
-
|
|
567
|
+
return this.option.thread(
|
|
568
|
+
scanner,
|
|
569
|
+
new OptionThread(scope, this.parent, rets, this.option, 'a', AfterFinalA)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
500
573
|
|
|
501
574
|
// After capturing the first arc within brackets, which may either contribute
|
|
502
575
|
// to the question or the answer, we decide which based on whether there is a
|
|
503
576
|
// following bracket.
|
|
504
|
-
|
|
577
|
+
class DecideQorA {
|
|
578
|
+
constructor(scope, parent, rets, option) {
|
|
505
579
|
this.scope = scope;
|
|
506
580
|
this.parent = parent;
|
|
507
581
|
this.rets = rets;
|
|
508
582
|
this.option = option;
|
|
509
583
|
Object.freeze(this);
|
|
510
|
-
}
|
|
584
|
+
}
|
|
511
585
|
|
|
512
|
-
|
|
513
|
-
if (type === 'token' && text === '[') {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
this.
|
|
519
|
-
|
|
586
|
+
next(type, _space, text, scanner) {
|
|
587
|
+
if (type === 'token' && text === '[') {
|
|
588
|
+
// A
|
|
589
|
+
this.option.push(this.scope, 'a');
|
|
590
|
+
return this.option.thread(
|
|
591
|
+
scanner,
|
|
592
|
+
new OptionThread(this.scope, this, this.rets, this.option, 'q', ExpectFinalBracket)
|
|
593
|
+
);
|
|
594
|
+
} else if (type === 'token' && text === ']') {
|
|
595
|
+
// Q
|
|
596
|
+
this.option.push(this.scope, 'q');
|
|
597
|
+
return this.parent.return(this.scope, this.rets, [], scanner);
|
|
520
598
|
} else {
|
|
521
|
-
|
|
522
|
-
|
|
599
|
+
this.scope.error(
|
|
600
|
+
`${scanner.position()}: Expected "]" to end option but got ${tokenName(type, text)}.`
|
|
601
|
+
);
|
|
602
|
+
return this.parent.return(this.scope, this.rets, [], scanner);
|
|
523
603
|
}
|
|
524
|
-
}
|
|
604
|
+
}
|
|
525
605
|
|
|
526
|
-
// If the brackets contain a sequence of question thread like [A [Q] QA [Q]
|
|
527
|
-
// QA...], then after each [question], we return here for continuing QA arcs.
|
|
528
|
-
|
|
606
|
+
// If the brackets contain a sequence of question thread like [A [Q] QA [Q]
|
|
607
|
+
// QA...], then after each [question], we return here for continuing QA arcs.
|
|
608
|
+
return(scope, rets, escs, scanner) {
|
|
529
609
|
// TODO no test exercises these escs.
|
|
530
610
|
Scope.tie(escs, 'ESC');
|
|
531
|
-
return this.option.thread(
|
|
532
|
-
|
|
533
|
-
|
|
611
|
+
return this.option.thread(
|
|
612
|
+
scanner,
|
|
613
|
+
new OptionThread(scope, this.parent, rets, this.option, 'qa', AfterQA)
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
534
617
|
|
|
535
618
|
// After a Question/Answer thread, there can always be another [Q] thread
|
|
536
619
|
// ad nauseam. Here we check whether this is the end of the bracketed
|
|
537
620
|
// expression or continue after a [Question].
|
|
538
|
-
|
|
621
|
+
class AfterQA {
|
|
622
|
+
constructor(scope, parent, rets, option) {
|
|
539
623
|
this.scope = scope;
|
|
540
624
|
this.parent = parent;
|
|
541
625
|
this.rets = rets;
|
|
542
626
|
this.option = option;
|
|
543
627
|
Object.freeze(this);
|
|
544
|
-
}
|
|
628
|
+
}
|
|
545
629
|
|
|
546
|
-
|
|
630
|
+
next(type, _space, text, scanner) {
|
|
547
631
|
if (type === 'token' && text === '[') {
|
|
548
|
-
|
|
549
|
-
|
|
632
|
+
return this.option.thread(
|
|
633
|
+
scanner,
|
|
634
|
+
new OptionThread(this.scope, this, this.rets, this.option, 'q', ExpectFinalBracket)
|
|
635
|
+
);
|
|
550
636
|
} else if (type === 'token' && text === ']') {
|
|
551
|
-
|
|
637
|
+
return this.parent.return(this.scope, this.rets, [], scanner);
|
|
552
638
|
} else {
|
|
553
|
-
|
|
554
|
-
|
|
639
|
+
this.scope.error(
|
|
640
|
+
`${scanner.position()}: Expected "]" to end option but got ${tokenName(type, text)}.`
|
|
641
|
+
);
|
|
642
|
+
return this.parent.return(this.scope, this.rets, [], scanner);
|
|
555
643
|
}
|
|
556
|
-
}
|
|
644
|
+
}
|
|
557
645
|
|
|
558
|
-
|
|
646
|
+
return(_scope, rets, escs, scanner) {
|
|
559
647
|
// TODO terminate returned scope
|
|
560
648
|
// TODO no test exercises these escapes.
|
|
561
649
|
Scope.tie(escs, 'ESC');
|
|
562
|
-
return this.option.thread(
|
|
563
|
-
|
|
564
|
-
|
|
650
|
+
return this.option.thread(
|
|
651
|
+
scanner,
|
|
652
|
+
new OptionThread(this.scope, this.parent, rets, this.option, 'qa', ExpectFinalBracket)
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
565
656
|
|
|
566
657
|
// The bracketed terms may either take the form [Q] or [A, ([Q] QA)*].
|
|
567
658
|
// This captures the first arc without committing to either Q or A until we
|
|
568
659
|
// know whether it is followed by a bracketed term.
|
|
569
|
-
|
|
660
|
+
class AfterQorA {
|
|
661
|
+
constructor(scope, parent, rets, option) {
|
|
570
662
|
this.scope = scope;
|
|
571
663
|
this.parent = parent;
|
|
572
664
|
this.rets = rets;
|
|
573
665
|
this.option = option;
|
|
574
666
|
Object.freeze(this);
|
|
575
|
-
}
|
|
667
|
+
}
|
|
576
668
|
|
|
577
|
-
// Just capture the path and proceed.
|
|
578
|
-
|
|
669
|
+
// Just capture the path and proceed.
|
|
670
|
+
return(scope, rets, escs, _scanner) {
|
|
579
671
|
// TODO consider whether this could have been done earlier.
|
|
580
672
|
Scope.tie(this.rets, 'RET');
|
|
581
673
|
// TODO no test exercises these escapes.
|
|
582
674
|
Scope.tie(escs, 'ESC');
|
|
583
675
|
return new DecideQorA(scope, this.parent, rets, this.option);
|
|
584
|
-
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
585
678
|
|
|
586
679
|
// After a [Q] or [A [Q] QA...] block, there must be a closing bracket and we
|
|
587
680
|
// return to the parent arc of the option.
|
|
588
|
-
|
|
681
|
+
class ExpectFinalBracket {
|
|
682
|
+
constructor(scope, parent, rets, option) {
|
|
589
683
|
this.scope = scope;
|
|
590
684
|
this.parent = parent;
|
|
591
685
|
this.rets = rets;
|
|
592
686
|
this.option = option;
|
|
593
687
|
Object.freeze(this);
|
|
594
|
-
}
|
|
688
|
+
}
|
|
595
689
|
|
|
596
|
-
|
|
690
|
+
next(type, space, text, scanner) {
|
|
597
691
|
if (type !== 'token' || text !== ']') {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
692
|
+
this.scope.error(`${scanner.position()}: Expected "]" to end option.`);
|
|
693
|
+
return this.parent
|
|
694
|
+
.return(this.scope, this.rets, [], scanner)
|
|
695
|
+
.next('token', space, ']', scanner);
|
|
601
696
|
}
|
|
602
697
|
return this.parent.return(this.scope, this.rets, [], scanner);
|
|
603
|
-
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
604
700
|
|
|
605
701
|
// After the closing bracket in an option], everything that remains is the last
|
|
606
702
|
// node of the answer. After that thread has been submitted, we expect the
|
|
607
703
|
// block to end.
|
|
608
|
-
|
|
704
|
+
class AfterFinalA {
|
|
705
|
+
constructor(scope, parent, rets, _option) {
|
|
609
706
|
this.scope = scope;
|
|
610
707
|
this.parent = parent;
|
|
611
708
|
this.rets = rets;
|
|
612
709
|
Object.freeze(this);
|
|
613
|
-
}
|
|
710
|
+
}
|
|
614
711
|
|
|
615
|
-
|
|
616
|
-
return this.parent.return(this.scope, this.rets, [], scanner)
|
|
617
|
-
|
|
618
|
-
}
|
|
712
|
+
next(type, space, text, scanner) {
|
|
713
|
+
return this.parent.return(this.scope, this.rets, [], scanner).next(type, space, text, scanner);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
619
716
|
|
|
620
717
|
// This concludes the portion dedicated to parsing options
|
|
621
718
|
|
|
622
719
|
// Branch is a fake story node. It serves to mark that the wrapped node's
|
|
623
720
|
// "branch" label should be tied instead of its "next" label.
|
|
624
|
-
|
|
721
|
+
class Branch {
|
|
722
|
+
constructor(node) {
|
|
625
723
|
this.type = 'branch';
|
|
626
724
|
this.node = node;
|
|
627
725
|
Object.freeze(this);
|
|
726
|
+
}
|
|
628
727
|
}
|
|
629
728
|
|
|
630
|
-
|
|
729
|
+
class Label {
|
|
730
|
+
constructor(parent, rets) {
|
|
631
731
|
this.parent = parent;
|
|
632
732
|
this.rets = rets;
|
|
633
733
|
Object.freeze(this);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return(scope, expression, scanner) {
|
|
737
|
+
const [head, ...tail] = expression;
|
|
738
|
+
if (head === 'get') {
|
|
739
|
+
const [label] = tail;
|
|
740
|
+
if (label === '...') {
|
|
741
|
+
const node = scope.create('goto', 'RET', scanner.position());
|
|
742
|
+
scope.tie(this.rets);
|
|
743
|
+
return new Thread(scope, new Loop(scope, this.parent), [node], []);
|
|
744
|
+
} else {
|
|
745
|
+
const labelScope = scope.label(label);
|
|
746
|
+
// place-holder goto thunk
|
|
747
|
+
const node = labelScope.create('goto', 'RET', scanner.position());
|
|
748
|
+
scope.tie(this.rets);
|
|
749
|
+
// rets also forwarded so they can be tied off if the goto is replaced.
|
|
750
|
+
return this.parent.return(labelScope, this.rets.concat([node]), [], scanner);
|
|
751
|
+
}
|
|
752
|
+
} else if (head === 'call') {
|
|
753
|
+
const [label, ...args] = tail;
|
|
754
|
+
const labelScope = scope.label(label[1]);
|
|
755
|
+
const node = labelScope.create('def', null, scanner.position());
|
|
756
|
+
const params = [];
|
|
757
|
+
for (const arg of args) {
|
|
758
|
+
if (arg[0] === 'get') {
|
|
759
|
+
params.push(arg[1]);
|
|
643
760
|
} else {
|
|
644
|
-
|
|
645
|
-
// place-holder goto thunk
|
|
646
|
-
var node = labelScope.create('goto', 'RET', scanner.position());
|
|
647
|
-
scope.tie(this.rets);
|
|
648
|
-
// rets also forwarded so they can be tied off if the goto is replaced.
|
|
649
|
-
return this.parent.return(labelScope, this.rets.concat([node]), [], scanner);
|
|
650
|
-
}
|
|
651
|
-
} else if (expression[0] === 'call') {
|
|
652
|
-
var label = expression[1][1];
|
|
653
|
-
var labelScope = scope.label(label);
|
|
654
|
-
var node = labelScope.create('def', null, scanner.position());
|
|
655
|
-
var params = [];
|
|
656
|
-
for (var i = 2; i < expression.length; i++) {
|
|
657
|
-
var arg = expression[i];
|
|
658
|
-
if (arg[0] === 'get') {
|
|
659
|
-
params.push(arg[1]);
|
|
660
|
-
} else {
|
|
661
|
-
scope.error(scanner.position() + ': Expected parameter name but got expression.');
|
|
662
|
-
}
|
|
761
|
+
scope.error(`${scanner.position()}: Expected parameter name but got expression.`);
|
|
663
762
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
763
|
+
}
|
|
764
|
+
node.locals = params;
|
|
765
|
+
return new Thread(
|
|
766
|
+
labelScope.next(),
|
|
767
|
+
new ConcludeProcedure(scope, this.parent, this.rets),
|
|
768
|
+
[node],
|
|
769
|
+
[]
|
|
770
|
+
);
|
|
668
771
|
} else {
|
|
669
|
-
|
|
670
|
-
|
|
772
|
+
scope.error(`${scanner.position()}: Expected label after "@".`);
|
|
773
|
+
return new Thread(scope, this.parent, this.rets, []);
|
|
671
774
|
}
|
|
672
|
-
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
673
777
|
|
|
674
|
-
|
|
778
|
+
class Loop {
|
|
779
|
+
constructor(scope, parent) {
|
|
675
780
|
this.scope = scope;
|
|
676
781
|
this.parent = parent;
|
|
677
782
|
this.label = scope.name();
|
|
678
783
|
Object.freeze(this);
|
|
679
|
-
}
|
|
784
|
+
}
|
|
680
785
|
|
|
681
|
-
|
|
786
|
+
return(scope, rets, _escs, scanner) {
|
|
682
787
|
// tie back rets
|
|
683
788
|
this.scope.tie(rets);
|
|
684
789
|
// TODO tie back escs
|
|
685
790
|
return this.parent.return(scope, [], [], scanner);
|
|
686
|
-
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
687
793
|
|
|
688
|
-
|
|
794
|
+
class ConcludeProcedure {
|
|
795
|
+
constructor(scope, parent, rets) {
|
|
689
796
|
this.scope = scope;
|
|
690
797
|
this.parent = parent;
|
|
691
798
|
this.rets = rets;
|
|
692
799
|
Object.freeze(this);
|
|
693
|
-
}
|
|
800
|
+
}
|
|
694
801
|
|
|
695
|
-
|
|
802
|
+
return(_scope, rets, escs, scanner) {
|
|
696
803
|
// After a procedure, connect prior rets.
|
|
697
804
|
Scope.tie(rets, 'RET');
|
|
698
805
|
// Dangling escs go to an escape instruction, to follow the jump path in
|
|
699
806
|
// the parent scope, determined at run time.
|
|
700
807
|
Scope.tie(escs, 'ESC');
|
|
701
808
|
return this.parent.return(this.scope, this.rets, [], scanner);
|
|
702
|
-
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
703
811
|
|
|
704
|
-
|
|
812
|
+
class Goto {
|
|
813
|
+
constructor(parent, rets) {
|
|
705
814
|
this.parent = parent;
|
|
706
815
|
this.rets = rets;
|
|
707
|
-
}
|
|
816
|
+
}
|
|
708
817
|
|
|
709
|
-
|
|
818
|
+
return(scope, args, scanner) {
|
|
710
819
|
if (args[0] === 'get') {
|
|
711
|
-
|
|
712
|
-
|
|
820
|
+
Scope.tie(this.rets, args[1]);
|
|
821
|
+
return this.parent.return(scope.next(), [], [], scanner);
|
|
713
822
|
} else if (args[0] === 'call') {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
823
|
+
const label = args[1][1];
|
|
824
|
+
const node = scope.create('call', label, scanner.position());
|
|
825
|
+
node.args = args.slice(2);
|
|
826
|
+
scope.tie(this.rets);
|
|
827
|
+
return this.parent.return(scope.next(), [node], [new Branch(node)], scanner);
|
|
719
828
|
} else {
|
|
720
|
-
|
|
721
|
-
|
|
829
|
+
scope.error(`${scanner.position()}: Expected label after goto arrow but got expression.`);
|
|
830
|
+
return new Thread(scope, this.parent, this.rets, []);
|
|
722
831
|
}
|
|
723
|
-
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
724
834
|
|
|
725
|
-
|
|
835
|
+
class Cue {
|
|
836
|
+
constructor(parent, rets, escs) {
|
|
726
837
|
this.parent = parent;
|
|
727
838
|
this.rets = rets;
|
|
728
839
|
this.escs = escs;
|
|
729
840
|
Object.freeze(this);
|
|
730
|
-
}
|
|
841
|
+
}
|
|
731
842
|
|
|
732
|
-
|
|
843
|
+
return(scope, expression, scanner) {
|
|
733
844
|
if (expression.length === 0 || expression[0] !== 'get') {
|
|
734
|
-
|
|
735
|
-
|
|
845
|
+
scope.error(`${scanner.position()}: Expected cue.`);
|
|
846
|
+
return this.parent.return(scope, this.rets, this.escs, scanner);
|
|
736
847
|
} else {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
848
|
+
const cue = expression[1];
|
|
849
|
+
const node = scope.create('cue', cue, scanner.position());
|
|
850
|
+
scope.tie(this.rets);
|
|
851
|
+
return this.parent.return(scope.next(), [node], this.escs, scanner);
|
|
741
852
|
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
function Block(scope, parent, rets) {
|
|
745
|
-
this.scope = scope;
|
|
746
|
-
this.parent = parent;
|
|
747
|
-
this.rets = rets;
|
|
748
|
-
Object.freeze(this);
|
|
853
|
+
}
|
|
749
854
|
}
|
|
750
855
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
856
|
+
const mutators = {
|
|
857
|
+
'=': true,
|
|
858
|
+
'+': true,
|
|
859
|
+
'-': true,
|
|
860
|
+
'*': true,
|
|
861
|
+
'/': true,
|
|
757
862
|
};
|
|
758
863
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
864
|
+
const toggles = {
|
|
865
|
+
'!': ['val', 1],
|
|
866
|
+
'?': ['val', 0],
|
|
762
867
|
};
|
|
763
868
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
869
|
+
const variables = {
|
|
870
|
+
'@': 'loop',
|
|
871
|
+
'#': 'hash',
|
|
872
|
+
'^': 'pick',
|
|
768
873
|
};
|
|
769
874
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
875
|
+
const switches = {
|
|
876
|
+
'&': 'loop',
|
|
877
|
+
'~': 'rand',
|
|
773
878
|
};
|
|
774
879
|
|
|
775
|
-
Block
|
|
880
|
+
class Block {
|
|
881
|
+
constructor(scope, parent, rets) {
|
|
882
|
+
this.scope = scope;
|
|
883
|
+
this.parent = parent;
|
|
884
|
+
this.rets = rets;
|
|
885
|
+
Object.freeze(this);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
next(type, space, text, scanner) {
|
|
776
889
|
if (type !== 'start') {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
890
|
+
if (text === '(') {
|
|
891
|
+
return expression(this.scope, new ExpressionBlock(this.parent, this.rets, 'walk')).next(
|
|
892
|
+
type,
|
|
893
|
+
space,
|
|
894
|
+
text,
|
|
895
|
+
scanner
|
|
896
|
+
);
|
|
897
|
+
} else if (mutators[text]) {
|
|
898
|
+
return expression(this.scope, new SetBlock(this.parent, this.rets, text));
|
|
899
|
+
} else if (toggles[text]) {
|
|
900
|
+
return expression(this.scope, new ToggleBlock(this.parent, this.rets, toggles[text]));
|
|
901
|
+
} else if (variables[text]) {
|
|
902
|
+
return expression(this.scope, new ExpressionBlock(this.parent, this.rets, variables[text]));
|
|
903
|
+
} else if (switches[text]) {
|
|
904
|
+
return new SwitchBlock(this.scope, this.parent, this.rets).start(
|
|
905
|
+
scanner,
|
|
906
|
+
null,
|
|
907
|
+
this.scope.name(),
|
|
908
|
+
null,
|
|
909
|
+
switches[text]
|
|
910
|
+
);
|
|
911
|
+
}
|
|
790
912
|
}
|
|
791
913
|
return new SwitchBlock(this.scope, this.parent, this.rets)
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
914
|
+
.start(scanner, null, this.scope.name(), 1, 'walk') // with variable and value, waiting for case to start
|
|
915
|
+
.next(type, space, text, scanner);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
795
918
|
|
|
796
|
-
|
|
919
|
+
class SetBlock {
|
|
920
|
+
constructor(parent, rets, op) {
|
|
797
921
|
this.op = op;
|
|
798
922
|
this.parent = parent;
|
|
799
923
|
this.rets = rets;
|
|
800
924
|
Object.freeze(this);
|
|
801
|
-
}
|
|
925
|
+
}
|
|
802
926
|
|
|
803
|
-
|
|
927
|
+
return(scope, expression, _scanner) {
|
|
804
928
|
return new MaybeSetVariable(scope, this.parent, this.rets, this.op, expression);
|
|
805
|
-
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
806
931
|
|
|
807
|
-
|
|
932
|
+
class MaybeSetVariable {
|
|
933
|
+
constructor(scope, parent, rets, op, expression) {
|
|
808
934
|
this.scope = scope;
|
|
809
935
|
this.parent = parent;
|
|
810
936
|
this.rets = rets;
|
|
811
937
|
this.op = op;
|
|
812
938
|
this.expression = expression;
|
|
813
939
|
Object.freeze(this);
|
|
814
|
-
}
|
|
940
|
+
}
|
|
815
941
|
|
|
816
|
-
|
|
942
|
+
next(type, space, text, scanner) {
|
|
817
943
|
if (type === 'token' && text === '}') {
|
|
818
|
-
|
|
819
|
-
.next(type, space, text, scanner);
|
|
944
|
+
return this.set(['val', 1], this.expression, scanner).next(type, space, text, scanner);
|
|
820
945
|
}
|
|
821
|
-
return expression(this.scope, this)
|
|
822
|
-
|
|
823
|
-
};
|
|
946
|
+
return expression(this.scope, this).next(type, space, text, scanner);
|
|
947
|
+
}
|
|
824
948
|
|
|
825
|
-
|
|
826
|
-
|
|
949
|
+
set(source, target, scanner) {
|
|
950
|
+
const node = this.scope.create('move', null, scanner.position());
|
|
827
951
|
if (this.op === '=') {
|
|
828
|
-
|
|
952
|
+
node.source = source;
|
|
829
953
|
} else {
|
|
830
|
-
|
|
954
|
+
node.source = [this.op, target, source];
|
|
831
955
|
}
|
|
832
956
|
node.target = target;
|
|
833
957
|
this.scope.tie(this.rets);
|
|
834
958
|
return this.parent.return(this.scope.next(), [node], [], scanner);
|
|
835
|
-
}
|
|
959
|
+
}
|
|
836
960
|
|
|
837
|
-
|
|
961
|
+
return(_scope, target, scanner) {
|
|
838
962
|
return this.set(this.expression, target, scanner);
|
|
839
|
-
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
840
965
|
|
|
841
|
-
|
|
966
|
+
class ToggleBlock {
|
|
967
|
+
constructor(parent, rets, source) {
|
|
842
968
|
this.parent = parent;
|
|
843
969
|
this.rets = rets;
|
|
844
970
|
this.source = source;
|
|
845
971
|
Object.freeze(this);
|
|
846
|
-
}
|
|
972
|
+
}
|
|
847
973
|
|
|
848
|
-
|
|
849
|
-
|
|
974
|
+
return(scope, expression, scanner) {
|
|
975
|
+
const node = scope.create('move', null, scanner.position());
|
|
850
976
|
node.source = this.source;
|
|
851
977
|
node.target = expression;
|
|
852
978
|
scope.tie(this.rets);
|
|
853
979
|
return this.parent.return(scope.next(), [node], [], scanner);
|
|
854
|
-
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
855
982
|
|
|
856
|
-
|
|
983
|
+
class ExpressionBlock {
|
|
984
|
+
constructor(parent, rets, mode) {
|
|
857
985
|
this.parent = parent;
|
|
858
986
|
this.rets = rets;
|
|
859
987
|
this.mode = mode;
|
|
860
988
|
Object.freeze(this);
|
|
861
|
-
}
|
|
989
|
+
}
|
|
862
990
|
|
|
863
|
-
|
|
991
|
+
return(scope, expression, _scanner) {
|
|
864
992
|
return new AfterExpressionBlock(scope, this.parent, this.rets, this.mode, expression);
|
|
865
|
-
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
866
995
|
|
|
867
|
-
|
|
996
|
+
class AfterExpressionBlock {
|
|
997
|
+
constructor(scope, parent, rets, mode, expression) {
|
|
868
998
|
this.scope = scope;
|
|
869
999
|
this.parent = parent;
|
|
870
1000
|
this.rets = rets;
|
|
871
1001
|
this.mode = mode;
|
|
872
1002
|
this.expression = expression;
|
|
873
1003
|
Object.freeze(this);
|
|
874
|
-
}
|
|
1004
|
+
}
|
|
875
1005
|
|
|
876
|
-
|
|
1006
|
+
next(type, space, text, scanner) {
|
|
877
1007
|
if (text === '|') {
|
|
878
|
-
|
|
879
|
-
|
|
1008
|
+
return new SwitchBlock(this.scope, this.parent, this.rets).start(
|
|
1009
|
+
scanner,
|
|
1010
|
+
this.expression,
|
|
1011
|
+
null,
|
|
1012
|
+
0,
|
|
1013
|
+
this.mode
|
|
1014
|
+
);
|
|
880
1015
|
} else if (text === '?') {
|
|
881
|
-
|
|
882
|
-
|
|
1016
|
+
return new SwitchBlock(this.scope, this.parent, this.rets).start(
|
|
1017
|
+
scanner,
|
|
1018
|
+
invertExpression(this.expression),
|
|
1019
|
+
null,
|
|
1020
|
+
0,
|
|
1021
|
+
this.mode,
|
|
1022
|
+
2
|
|
1023
|
+
);
|
|
883
1024
|
} else if (text === '}') {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1025
|
+
const node = this.scope.create('echo', this.expression, scanner.position());
|
|
1026
|
+
this.scope.tie(this.rets);
|
|
1027
|
+
return this.parent
|
|
1028
|
+
.return(this.scope.next(), [node], [], scanner)
|
|
1029
|
+
.next(type, space, text, scanner);
|
|
888
1030
|
} else {
|
|
889
|
-
|
|
890
|
-
|
|
1031
|
+
this.scope.error(
|
|
1032
|
+
`${scanner.position()}: Expected "|", "?", or "}" after expression but got ${tokenName(
|
|
1033
|
+
type,
|
|
1034
|
+
text
|
|
1035
|
+
)}.`
|
|
1036
|
+
);
|
|
1037
|
+
return this.parent.return(this.scope, [], [], scanner);
|
|
891
1038
|
}
|
|
892
|
-
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
893
1041
|
|
|
894
|
-
|
|
1042
|
+
class SwitchBlock {
|
|
1043
|
+
constructor(scope, parent, rets) {
|
|
895
1044
|
this.scope = scope;
|
|
896
1045
|
this.parent = parent;
|
|
897
1046
|
this.rets = rets;
|
|
@@ -899,15 +1048,15 @@ function SwitchBlock(scope, parent, rets) {
|
|
|
899
1048
|
this.branches = [];
|
|
900
1049
|
this.weights = [];
|
|
901
1050
|
Object.seal(this);
|
|
902
|
-
}
|
|
1051
|
+
}
|
|
903
1052
|
|
|
904
|
-
|
|
1053
|
+
start(scanner, expression, variable, value, mode, min) {
|
|
905
1054
|
value = value || 0;
|
|
906
1055
|
if (mode === 'loop' && !expression) {
|
|
907
|
-
|
|
1056
|
+
value = 1;
|
|
908
1057
|
}
|
|
909
1058
|
expression = expression || ['get', this.scope.name()];
|
|
910
|
-
|
|
1059
|
+
const node = this.scope.create('switch', expression, scanner.position());
|
|
911
1060
|
this.node = node;
|
|
912
1061
|
node.variable = variable;
|
|
913
1062
|
node.value = value;
|
|
@@ -915,183 +1064,219 @@ SwitchBlock.prototype.start = function start(scanner, expression, variable, valu
|
|
|
915
1064
|
this.scope.tie(this.rets);
|
|
916
1065
|
node.branches = this.branches;
|
|
917
1066
|
node.weights = this.weights;
|
|
918
|
-
return new MaybeWeightedCase(
|
|
919
|
-
|
|
1067
|
+
return new MaybeWeightedCase(
|
|
1068
|
+
this.scope,
|
|
1069
|
+
new Case(this.scope.firstChild(), this, [], this.branches, min || 0)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
920
1072
|
|
|
921
|
-
|
|
1073
|
+
return(_scope, rets, escs, scanner) {
|
|
922
1074
|
if (this.node.mode === 'pick') {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1075
|
+
Scope.tie(rets, 'RET');
|
|
1076
|
+
rets = [this.node];
|
|
1077
|
+
// TODO think about what to do with escs.
|
|
926
1078
|
} else {
|
|
927
|
-
|
|
1079
|
+
this.node.next = 'RET';
|
|
928
1080
|
}
|
|
929
1081
|
return this.parent.return(this.scope.next(), rets, escs, scanner);
|
|
930
|
-
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
931
1084
|
|
|
932
|
-
|
|
1085
|
+
class Case {
|
|
1086
|
+
constructor(scope, parent, rets, branches, min) {
|
|
933
1087
|
this.scope = scope;
|
|
934
1088
|
this.parent = parent;
|
|
935
1089
|
this.rets = rets;
|
|
936
1090
|
this.branches = branches;
|
|
937
1091
|
this.min = min;
|
|
938
1092
|
Object.freeze(this);
|
|
939
|
-
}
|
|
1093
|
+
}
|
|
940
1094
|
|
|
941
|
-
|
|
1095
|
+
next(type, space, text, scanner) {
|
|
942
1096
|
if (text === '|') {
|
|
943
|
-
|
|
1097
|
+
return new MaybeWeightedCase(this.scope, this);
|
|
944
1098
|
} else {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
.next(type, space, text, scanner);
|
|
1099
|
+
let scope = this.scope;
|
|
1100
|
+
while (this.branches.length < this.min) {
|
|
1101
|
+
const node = scope.create('goto', 'RET', scanner.position());
|
|
1102
|
+
this.rets.push(node);
|
|
1103
|
+
this.branches.push(scope.name());
|
|
1104
|
+
scope = scope.next();
|
|
1105
|
+
}
|
|
1106
|
+
return this.parent.return(scope, this.rets, [], scanner).next(type, space, text, scanner);
|
|
954
1107
|
}
|
|
955
|
-
}
|
|
1108
|
+
}
|
|
956
1109
|
|
|
957
|
-
|
|
1110
|
+
case(args, scanner) {
|
|
958
1111
|
this.parent.weights.push(args || ['val', 1]);
|
|
959
|
-
|
|
960
|
-
|
|
1112
|
+
const scope = this.scope.zerothChild();
|
|
1113
|
+
const node = scope.create('goto', 'RET', scanner.position());
|
|
961
1114
|
this.branches.push(scope.name());
|
|
962
1115
|
return new Thread(scope, this, [node], []);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return new Case(
|
|
967
|
-
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return(_scope, rets, escs, _scanner) {
|
|
1119
|
+
return new Case(
|
|
1120
|
+
this.scope.next(),
|
|
1121
|
+
this.parent,
|
|
1122
|
+
this.rets.concat(rets, escs),
|
|
1123
|
+
this.branches,
|
|
1124
|
+
this.min
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
968
1128
|
|
|
969
|
-
|
|
1129
|
+
class MaybeWeightedCase {
|
|
1130
|
+
constructor(scope, parent) {
|
|
970
1131
|
this.scope = scope;
|
|
971
1132
|
this.parent = parent;
|
|
972
1133
|
Object.freeze(this);
|
|
973
|
-
}
|
|
1134
|
+
}
|
|
974
1135
|
|
|
975
|
-
|
|
1136
|
+
next(type, space, text, scanner) {
|
|
976
1137
|
if (text === '(') {
|
|
977
|
-
|
|
978
|
-
.next(type, space, text, scanner);
|
|
1138
|
+
return expression(this.scope, this).next(type, space, text, scanner);
|
|
979
1139
|
} else {
|
|
980
|
-
|
|
981
|
-
.next(type, space, text, scanner);
|
|
1140
|
+
return this.parent.case(null, scanner).next(type, space, text, scanner);
|
|
982
1141
|
}
|
|
983
|
-
}
|
|
1142
|
+
}
|
|
984
1143
|
|
|
985
|
-
|
|
1144
|
+
return(_scope, args, scanner) {
|
|
986
1145
|
return this.parent.case(args, scanner);
|
|
987
|
-
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
988
1148
|
|
|
989
|
-
|
|
1149
|
+
class Ask {
|
|
1150
|
+
constructor(scope, parent, rets, escs) {
|
|
990
1151
|
this.scope = scope;
|
|
991
1152
|
this.parent = parent;
|
|
992
1153
|
this.rets = rets;
|
|
993
1154
|
this.escs = escs;
|
|
994
1155
|
Object.freeze(this);
|
|
995
|
-
}
|
|
1156
|
+
}
|
|
996
1157
|
|
|
997
|
-
|
|
1158
|
+
next(type, space, text, scanner) {
|
|
998
1159
|
if (type == 'alphanum') {
|
|
999
|
-
|
|
1160
|
+
return new Read(text, this.scope, this.parent, this.rets, this.escs);
|
|
1000
1161
|
}
|
|
1001
|
-
|
|
1002
|
-
return new Thread(this.scope.next(), this.parent, this.rets, this.escs)
|
|
1003
|
-
|
|
1004
|
-
|
|
1162
|
+
this.scope.create('ask', null, scanner.position());
|
|
1163
|
+
return new Thread(this.scope.next(), this.parent, this.rets, this.escs).next(
|
|
1164
|
+
type,
|
|
1165
|
+
space,
|
|
1166
|
+
text,
|
|
1167
|
+
scanner
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1005
1171
|
|
|
1006
|
-
|
|
1172
|
+
class Read {
|
|
1173
|
+
constructor(variable, scope, parent, rets, escs) {
|
|
1007
1174
|
this.variable = variable;
|
|
1008
1175
|
this.scope = scope;
|
|
1009
1176
|
this.parent = parent;
|
|
1010
1177
|
this.rets = rets;
|
|
1011
1178
|
this.escs = escs;
|
|
1012
1179
|
Object.freeze(this);
|
|
1013
|
-
}
|
|
1180
|
+
}
|
|
1014
1181
|
|
|
1015
|
-
|
|
1182
|
+
next(type, space, text, scanner) {
|
|
1016
1183
|
if (type == 'alphanum') {
|
|
1017
|
-
|
|
1184
|
+
return this.return(text, scanner);
|
|
1018
1185
|
}
|
|
1019
1186
|
return this.return(null, scanner).next(type, space, text, scanner);
|
|
1020
|
-
}
|
|
1187
|
+
}
|
|
1021
1188
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1189
|
+
return(cue, scanner) {
|
|
1190
|
+
const node = this.scope.create('read', this.variable, scanner.position());
|
|
1024
1191
|
node.cue = cue;
|
|
1025
1192
|
return new Thread(this.scope.next(), this.parent, this.rets.concat([node]), this.escs);
|
|
1026
|
-
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1027
1195
|
|
|
1028
|
-
|
|
1196
|
+
class Program {
|
|
1197
|
+
constructor(scope, parent, rets, escs) {
|
|
1029
1198
|
this.scope = scope;
|
|
1030
1199
|
this.parent = parent;
|
|
1031
1200
|
this.rets = rets;
|
|
1032
1201
|
this.escs = escs;
|
|
1033
1202
|
Object.freeze(this);
|
|
1034
|
-
}
|
|
1203
|
+
}
|
|
1035
1204
|
|
|
1036
|
-
|
|
1205
|
+
next(type, space, text, scanner) {
|
|
1037
1206
|
if (type === 'stop' || text === '}') {
|
|
1038
|
-
|
|
1039
|
-
|
|
1207
|
+
return this.parent
|
|
1208
|
+
.return(this.scope, this.rets, this.escs, scanner)
|
|
1209
|
+
.next(type, space, text, scanner);
|
|
1040
1210
|
} else if (text === ',' || type === 'break') {
|
|
1041
|
-
|
|
1042
|
-
// istanbul ignore if
|
|
1211
|
+
return this;
|
|
1043
1212
|
} else if (type === 'error') {
|
|
1044
|
-
|
|
1045
|
-
|
|
1213
|
+
// Break out of recursive error loops
|
|
1214
|
+
return this.parent.return(this.scope, this.rets, this.escs, scanner);
|
|
1046
1215
|
} else {
|
|
1047
|
-
|
|
1048
|
-
|
|
1216
|
+
return variable(this.scope, new Assignment(this.scope, this, this.rets, this.escs)).next(
|
|
1217
|
+
type,
|
|
1218
|
+
space,
|
|
1219
|
+
text,
|
|
1220
|
+
scanner
|
|
1221
|
+
);
|
|
1049
1222
|
}
|
|
1050
|
-
}
|
|
1223
|
+
}
|
|
1051
1224
|
|
|
1052
|
-
|
|
1225
|
+
return(scope, rets, escs, _scanner) {
|
|
1053
1226
|
return new Program(scope, this.parent, rets, escs);
|
|
1054
|
-
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1055
1229
|
|
|
1056
|
-
|
|
1230
|
+
class Assignment {
|
|
1231
|
+
constructor(scope, parent, rets, escs) {
|
|
1057
1232
|
this.scope = scope;
|
|
1058
1233
|
this.parent = parent;
|
|
1059
1234
|
this.rets = rets;
|
|
1060
1235
|
this.escs = escs;
|
|
1061
1236
|
Object.freeze(this);
|
|
1062
|
-
}
|
|
1237
|
+
}
|
|
1063
1238
|
|
|
1064
|
-
|
|
1065
|
-
// istanbul ignore else
|
|
1239
|
+
return(_scope, expression, scanner) {
|
|
1066
1240
|
if (expression[0] === 'get' || expression[0] === 'var') {
|
|
1067
|
-
|
|
1241
|
+
return new ExpectOperator(this.scope, this.parent, this.rets, this.escs, expression);
|
|
1068
1242
|
} else {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1243
|
+
this.scope.error(
|
|
1244
|
+
`${scanner.position()}: Expected variable to assign but got ${JSON.stringify(expression)}.`
|
|
1245
|
+
);
|
|
1246
|
+
return this.parent
|
|
1247
|
+
.return(this.scope, this.rets, this.escs, scanner)
|
|
1248
|
+
.next('error', '', '', scanner);
|
|
1072
1249
|
}
|
|
1073
|
-
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1074
1252
|
|
|
1075
|
-
|
|
1253
|
+
class ExpectOperator {
|
|
1254
|
+
constructor(scope, parent, rets, escs, left) {
|
|
1076
1255
|
this.scope = scope;
|
|
1077
1256
|
this.parent = parent;
|
|
1078
1257
|
this.rets = rets;
|
|
1079
1258
|
this.escs = escs;
|
|
1080
1259
|
this.left = left;
|
|
1081
1260
|
Object.freeze(this);
|
|
1082
|
-
}
|
|
1261
|
+
}
|
|
1083
1262
|
|
|
1084
|
-
|
|
1085
|
-
// istanbul ignore else
|
|
1263
|
+
next(type, _space, text, scanner) {
|
|
1086
1264
|
if (text === '=') {
|
|
1087
|
-
|
|
1265
|
+
return expression(
|
|
1266
|
+
this.scope,
|
|
1267
|
+
new ExpectExpression(this.scope, this.parent, this.rets, this.escs, this.left, text)
|
|
1268
|
+
);
|
|
1088
1269
|
} else {
|
|
1089
|
-
|
|
1090
|
-
|
|
1270
|
+
this.scope.error(
|
|
1271
|
+
`${scanner.position()}: Expected "=" operator but got ${tokenName(type, text)}.`
|
|
1272
|
+
);
|
|
1273
|
+
return this.parent.return(this.scope, this.rets, this.escs, scanner);
|
|
1091
1274
|
}
|
|
1092
|
-
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1093
1277
|
|
|
1094
|
-
|
|
1278
|
+
class ExpectExpression {
|
|
1279
|
+
constructor(scope, parent, rets, escs, left, operator) {
|
|
1095
1280
|
this.scope = scope;
|
|
1096
1281
|
this.parent = parent;
|
|
1097
1282
|
this.rets = rets;
|
|
@@ -1099,322 +1284,360 @@ function ExpectExpression(scope, parent, rets, escs, left, operator) {
|
|
|
1099
1284
|
this.left = left;
|
|
1100
1285
|
this.operator = operator;
|
|
1101
1286
|
Object.freeze(this);
|
|
1102
|
-
}
|
|
1287
|
+
}
|
|
1103
1288
|
|
|
1104
|
-
|
|
1105
|
-
var node;
|
|
1289
|
+
return(_scope, right, scanner) {
|
|
1106
1290
|
// TODO validate this.left as a valid move target
|
|
1107
1291
|
this.scope.tie(this.rets);
|
|
1108
|
-
node = this.scope.create('move', null, scanner.position());
|
|
1292
|
+
const node = this.scope.create('move', null, scanner.position());
|
|
1109
1293
|
node.target = this.left;
|
|
1110
1294
|
node.source = right;
|
|
1111
1295
|
return this.parent.return(this.scope.next(), [node], this.escs, scanner);
|
|
1112
|
-
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1113
1298
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1299
|
+
const unary = {
|
|
1300
|
+
not: true,
|
|
1301
|
+
'-': true,
|
|
1302
|
+
'~': true,
|
|
1303
|
+
'#': true,
|
|
1119
1304
|
};
|
|
1120
1305
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1306
|
+
const exponential = {
|
|
1307
|
+
'**': true, // x ** y
|
|
1123
1308
|
};
|
|
1124
1309
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1310
|
+
const multiplicative = {
|
|
1311
|
+
'*': true,
|
|
1312
|
+
'/': true,
|
|
1313
|
+
'%': true,
|
|
1314
|
+
rem: true,
|
|
1315
|
+
'~': true,
|
|
1131
1316
|
};
|
|
1132
1317
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1318
|
+
const arithmetic = {
|
|
1319
|
+
'+': true,
|
|
1320
|
+
'-': true,
|
|
1136
1321
|
};
|
|
1137
1322
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1323
|
+
const comparison = {
|
|
1324
|
+
'<': true,
|
|
1325
|
+
'<=': true,
|
|
1326
|
+
'==': true,
|
|
1327
|
+
'<>': true,
|
|
1328
|
+
'>=': true,
|
|
1329
|
+
'>': true,
|
|
1330
|
+
'#': true,
|
|
1146
1331
|
};
|
|
1147
1332
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1333
|
+
const intersection = {
|
|
1334
|
+
and: true,
|
|
1150
1335
|
};
|
|
1151
1336
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1337
|
+
const union = {
|
|
1338
|
+
or: true,
|
|
1154
1339
|
};
|
|
1155
1340
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1341
|
+
const precedence = [
|
|
1342
|
+
// from low to high
|
|
1343
|
+
union,
|
|
1344
|
+
intersection,
|
|
1345
|
+
comparison,
|
|
1346
|
+
arithmetic,
|
|
1347
|
+
multiplicative,
|
|
1348
|
+
exponential,
|
|
1163
1349
|
];
|
|
1164
1350
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1351
|
+
const expression = (scope, parent) => {
|
|
1352
|
+
for (const operators of precedence) {
|
|
1353
|
+
parent = new BinaryExpression(operators, parent);
|
|
1354
|
+
}
|
|
1355
|
+
return new Unary(scope, parent);
|
|
1356
|
+
};
|
|
1172
1357
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
}
|
|
1358
|
+
const variable = (scope, parent) => {
|
|
1359
|
+
return new GetStaticVariable(scope, parent, [], [], '', true);
|
|
1360
|
+
};
|
|
1176
1361
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1362
|
+
const label = (scope, parent) => {
|
|
1363
|
+
return new GetStaticVariable(scope, new AfterVariable(parent), [], [], '', true);
|
|
1364
|
+
};
|
|
1180
1365
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1366
|
+
const inversions = {
|
|
1367
|
+
'==': '<>',
|
|
1368
|
+
'<>': '==',
|
|
1369
|
+
'>': '<=',
|
|
1370
|
+
'<': '>=',
|
|
1371
|
+
'>=': '<',
|
|
1372
|
+
'<=': '>',
|
|
1188
1373
|
};
|
|
1189
1374
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1375
|
+
const invertExpression = expression => {
|
|
1376
|
+
if (expression[0] === 'not') {
|
|
1377
|
+
return expression[1];
|
|
1378
|
+
} else if (inversions[expression[0]]) {
|
|
1379
|
+
return [inversions[expression[0]], expression[1], expression[2]];
|
|
1380
|
+
} else {
|
|
1381
|
+
return ['not', expression];
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1199
1384
|
|
|
1200
|
-
|
|
1385
|
+
class Open {
|
|
1386
|
+
constructor(parent) {
|
|
1201
1387
|
this.parent = parent;
|
|
1202
1388
|
Object.seal(this);
|
|
1203
|
-
}
|
|
1389
|
+
}
|
|
1204
1390
|
|
|
1205
|
-
|
|
1391
|
+
return(scope, expression, _scanner) {
|
|
1206
1392
|
return new Close(scope, this.parent, expression);
|
|
1207
|
-
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1208
1395
|
|
|
1209
|
-
|
|
1396
|
+
class Close {
|
|
1397
|
+
constructor(scope, parent, expression) {
|
|
1210
1398
|
this.scope = scope;
|
|
1211
1399
|
this.parent = parent;
|
|
1212
1400
|
this.expression = expression;
|
|
1213
1401
|
Object.seal(this);
|
|
1214
|
-
}
|
|
1402
|
+
}
|
|
1215
1403
|
|
|
1216
|
-
|
|
1217
|
-
// istanbul ignore else
|
|
1404
|
+
next(type, _space, text, scanner) {
|
|
1218
1405
|
if (type === 'symbol' && text === ')') {
|
|
1219
|
-
|
|
1406
|
+
return this.parent.return(this.scope, this.expression, scanner);
|
|
1220
1407
|
} else {
|
|
1221
|
-
|
|
1222
|
-
|
|
1408
|
+
this.scope.error(
|
|
1409
|
+
`${scanner.position()}: Expected parenthetical expression to end with ")" or continue with operator but got ${tokenName(
|
|
1410
|
+
type,
|
|
1411
|
+
text
|
|
1412
|
+
)}.`
|
|
1413
|
+
);
|
|
1414
|
+
return this.parent.return(this.scope, this.expression, scanner);
|
|
1223
1415
|
}
|
|
1224
|
-
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1225
1418
|
|
|
1226
|
-
|
|
1419
|
+
class Value {
|
|
1420
|
+
constructor(scope, parent) {
|
|
1227
1421
|
this.scope = scope;
|
|
1228
1422
|
this.parent = parent;
|
|
1229
1423
|
Object.seal(this);
|
|
1230
|
-
}
|
|
1424
|
+
}
|
|
1231
1425
|
|
|
1232
|
-
|
|
1426
|
+
next(type, space, text, scanner) {
|
|
1233
1427
|
if (type === 'number') {
|
|
1234
|
-
|
|
1428
|
+
return this.parent.return(this.scope, ['val', +text], scanner);
|
|
1235
1429
|
} else if (text === '(') {
|
|
1236
|
-
|
|
1430
|
+
return expression(this.scope, new Open(this.parent));
|
|
1237
1431
|
} else if (text === '{') {
|
|
1238
|
-
|
|
1239
|
-
// istanbul ignore else
|
|
1432
|
+
return expression(this.scope, new GetDynamicVariable(this.parent, [''], []));
|
|
1240
1433
|
} else if (type === 'alphanum') {
|
|
1241
|
-
|
|
1434
|
+
return new GetStaticVariable(this.scope, new AfterVariable(this.parent), [], [], text, false);
|
|
1242
1435
|
} else {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1436
|
+
this.scope.error(
|
|
1437
|
+
`${scanner.position()}: Expected expression but got ${tokenName(type, text)}.`
|
|
1438
|
+
);
|
|
1439
|
+
return this.parent.return(this.scope, ['val', 0], scanner).next(type, space, text, scanner);
|
|
1246
1440
|
}
|
|
1247
|
-
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1248
1443
|
|
|
1249
|
-
|
|
1444
|
+
class AfterVariable {
|
|
1445
|
+
constructor(parent) {
|
|
1250
1446
|
this.parent = parent;
|
|
1251
1447
|
Object.seal(this);
|
|
1252
|
-
}
|
|
1448
|
+
}
|
|
1253
1449
|
|
|
1254
|
-
|
|
1450
|
+
return(scope, expression, _scanner) {
|
|
1255
1451
|
return new MaybeCall(scope, this.parent, expression);
|
|
1256
|
-
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1257
1454
|
|
|
1258
|
-
|
|
1455
|
+
class MaybeCall {
|
|
1456
|
+
constructor(scope, parent, expression) {
|
|
1259
1457
|
this.scope = scope;
|
|
1260
1458
|
this.parent = parent;
|
|
1261
1459
|
this.expression = expression;
|
|
1262
1460
|
Object.seal(this);
|
|
1263
|
-
}
|
|
1461
|
+
}
|
|
1264
1462
|
|
|
1265
|
-
|
|
1463
|
+
next(type, space, text, scanner) {
|
|
1266
1464
|
if (space === '' && text === '(') {
|
|
1267
|
-
|
|
1465
|
+
return new Arguments(this.scope, this.parent, this.expression);
|
|
1268
1466
|
} else {
|
|
1269
|
-
|
|
1270
|
-
|
|
1467
|
+
return this.parent
|
|
1468
|
+
.return(this.scope, this.expression, scanner)
|
|
1469
|
+
.next(type, space, text, scanner);
|
|
1271
1470
|
}
|
|
1272
|
-
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1273
1473
|
|
|
1274
|
-
|
|
1474
|
+
class Arguments {
|
|
1475
|
+
constructor(scope, parent, expression) {
|
|
1275
1476
|
this.scope = scope;
|
|
1276
1477
|
this.parent = parent;
|
|
1277
1478
|
this.args = ['call', expression];
|
|
1278
|
-
}
|
|
1479
|
+
}
|
|
1279
1480
|
|
|
1280
|
-
|
|
1481
|
+
next(type, space, text, scanner) {
|
|
1281
1482
|
if (text === ')') {
|
|
1282
|
-
|
|
1483
|
+
return this.parent.return(this.scope, this.args, scanner);
|
|
1283
1484
|
} else {
|
|
1284
|
-
|
|
1285
|
-
.next(type, space, text, scanner);
|
|
1485
|
+
return expression(this.scope, this).next(type, space, text, scanner);
|
|
1286
1486
|
}
|
|
1287
|
-
}
|
|
1487
|
+
}
|
|
1288
1488
|
|
|
1289
|
-
|
|
1489
|
+
return(scope, expression, _scanner) {
|
|
1290
1490
|
this.args.push(expression);
|
|
1291
1491
|
return new MaybeArgument(scope, this);
|
|
1292
|
-
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1293
1494
|
|
|
1294
|
-
|
|
1495
|
+
class MaybeArgument {
|
|
1496
|
+
constructor(scope, parent) {
|
|
1295
1497
|
this.scope = scope;
|
|
1296
1498
|
this.parent = parent;
|
|
1297
1499
|
Object.seal(this);
|
|
1298
|
-
}
|
|
1500
|
+
}
|
|
1299
1501
|
|
|
1300
|
-
|
|
1502
|
+
next(type, space, text, scanner) {
|
|
1301
1503
|
if (text === ',') {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
return this.parent.next(type, space, text, scanner);
|
|
1504
|
+
return expression(this.scope, this.parent);
|
|
1505
|
+
} else if (text === ')') {
|
|
1506
|
+
return this.parent.next(type, space, text, scanner);
|
|
1306
1507
|
} else {
|
|
1307
|
-
|
|
1308
|
-
|
|
1508
|
+
this.scope.error(
|
|
1509
|
+
`${scanner.position()}: Expected "," or ")" to end or argument list but got ${tokenName(
|
|
1510
|
+
type,
|
|
1511
|
+
text
|
|
1512
|
+
)}.`
|
|
1513
|
+
);
|
|
1514
|
+
return this.parent;
|
|
1309
1515
|
}
|
|
1310
|
-
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1311
1518
|
|
|
1312
|
-
|
|
1519
|
+
class Unary {
|
|
1520
|
+
constructor(scope, parent) {
|
|
1313
1521
|
this.scope = scope;
|
|
1314
1522
|
this.parent = parent;
|
|
1315
1523
|
Object.seal(this);
|
|
1316
|
-
}
|
|
1524
|
+
}
|
|
1317
1525
|
|
|
1318
|
-
|
|
1526
|
+
next(type, space, text, scanner) {
|
|
1319
1527
|
if (unary[text] === true) {
|
|
1320
|
-
|
|
1321
|
-
new UnaryOperator(this.parent, text));
|
|
1528
|
+
return new Unary(this.scope, new UnaryOperator(this.parent, text));
|
|
1322
1529
|
} else {
|
|
1323
|
-
|
|
1324
|
-
.next(type, space, text, scanner);
|
|
1530
|
+
return new Value(this.scope, this.parent).next(type, space, text, scanner);
|
|
1325
1531
|
}
|
|
1326
|
-
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1327
1534
|
|
|
1328
|
-
|
|
1535
|
+
class UnaryOperator {
|
|
1536
|
+
constructor(parent, op) {
|
|
1329
1537
|
this.parent = parent;
|
|
1330
1538
|
this.op = op;
|
|
1331
|
-
}
|
|
1539
|
+
}
|
|
1332
1540
|
|
|
1333
|
-
|
|
1541
|
+
return(scope, expression, scanner) {
|
|
1334
1542
|
return this.parent.return(scope, [this.op, expression], scanner);
|
|
1335
|
-
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1336
1545
|
|
|
1337
|
-
|
|
1546
|
+
class MaybeOperator {
|
|
1547
|
+
constructor(scope, parent, expression, operators) {
|
|
1338
1548
|
this.scope = scope;
|
|
1339
1549
|
this.parent = parent;
|
|
1340
1550
|
this.expression = expression;
|
|
1341
1551
|
this.operators = operators;
|
|
1342
1552
|
Object.seal(this);
|
|
1343
|
-
}
|
|
1553
|
+
}
|
|
1344
1554
|
|
|
1345
|
-
|
|
1555
|
+
next(type, space, text, scanner) {
|
|
1346
1556
|
if (this.operators[text] === true) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1557
|
+
let parent = new MaybeExpression(this.parent, this.operators);
|
|
1558
|
+
parent = new PartialExpression(parent, text, this.expression);
|
|
1559
|
+
for (let i = precedence.indexOf(this.operators) + 1; i < precedence.length; i++) {
|
|
1560
|
+
parent = new MaybeExpression(parent, precedence[i]);
|
|
1561
|
+
}
|
|
1562
|
+
return new Unary(this.scope, parent);
|
|
1353
1563
|
} else {
|
|
1354
|
-
|
|
1355
|
-
|
|
1564
|
+
return this.parent
|
|
1565
|
+
.return(this.scope, this.expression, scanner)
|
|
1566
|
+
.next(type, space, text, scanner);
|
|
1356
1567
|
}
|
|
1357
|
-
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1358
1570
|
|
|
1359
|
-
|
|
1571
|
+
class MaybeExpression {
|
|
1572
|
+
constructor(parent, operators) {
|
|
1360
1573
|
this.parent = parent;
|
|
1361
1574
|
this.operators = operators;
|
|
1362
1575
|
Object.seal(this);
|
|
1363
|
-
}
|
|
1576
|
+
}
|
|
1364
1577
|
|
|
1365
|
-
|
|
1578
|
+
return(scope, expression, _scanner) {
|
|
1366
1579
|
return new MaybeOperator(scope, this.parent, expression, this.operators);
|
|
1367
|
-
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1368
1582
|
|
|
1369
|
-
|
|
1583
|
+
class PartialExpression {
|
|
1584
|
+
constructor(parent, operator, expression) {
|
|
1370
1585
|
this.parent = parent;
|
|
1371
1586
|
this.operator = operator;
|
|
1372
1587
|
this.expression = expression;
|
|
1373
|
-
}
|
|
1588
|
+
}
|
|
1374
1589
|
|
|
1375
|
-
|
|
1590
|
+
return(scope, expression, scanner) {
|
|
1376
1591
|
return this.parent.return(scope, [this.operator, this.expression, expression], scanner);
|
|
1377
|
-
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1378
1594
|
|
|
1379
|
-
|
|
1595
|
+
class BinaryExpression {
|
|
1596
|
+
constructor(operators, parent) {
|
|
1380
1597
|
this.parent = parent;
|
|
1381
1598
|
this.operators = operators;
|
|
1382
1599
|
Object.seal(this);
|
|
1383
|
-
}
|
|
1600
|
+
}
|
|
1384
1601
|
|
|
1385
|
-
|
|
1602
|
+
return(scope, expression, _scanner) {
|
|
1386
1603
|
return new MaybeOperator(scope, this.parent, expression, this.operators);
|
|
1387
|
-
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1388
1606
|
|
|
1389
|
-
|
|
1607
|
+
class GetDynamicVariable {
|
|
1608
|
+
constructor(parent, literals, expressions) {
|
|
1390
1609
|
this.parent = parent;
|
|
1391
1610
|
this.literals = literals;
|
|
1392
1611
|
this.expressions = expressions;
|
|
1393
1612
|
Object.seal(this);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
return(scope, expression, _scanner) {
|
|
1616
|
+
return new Expect(
|
|
1617
|
+
'token',
|
|
1618
|
+
'}',
|
|
1619
|
+
scope,
|
|
1620
|
+
new ContinueVariable(scope, this.parent, this.literals, this.expressions.concat([expression]))
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1394
1623
|
}
|
|
1395
1624
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
scope,
|
|
1399
|
-
this.parent,
|
|
1400
|
-
this.literals,
|
|
1401
|
-
this.expressions.concat([expression])
|
|
1402
|
-
));
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
|
-
function ContinueVariable(scope, parent, literals, expressions) {
|
|
1625
|
+
class ContinueVariable {
|
|
1626
|
+
constructor(scope, parent, literals, expressions) {
|
|
1406
1627
|
this.scope = scope;
|
|
1407
1628
|
this.parent = parent;
|
|
1408
1629
|
this.literals = literals;
|
|
1409
1630
|
this.expressions = expressions;
|
|
1410
1631
|
Object.freeze(this);
|
|
1411
|
-
}
|
|
1632
|
+
}
|
|
1412
1633
|
|
|
1413
|
-
|
|
1634
|
+
return() {
|
|
1414
1635
|
return new GetStaticVariable(this.scope, this.parent, this.literals, this.expressions, '');
|
|
1415
|
-
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1416
1638
|
|
|
1417
|
-
|
|
1639
|
+
class GetStaticVariable {
|
|
1640
|
+
constructor(scope, parent, literals, expressions, literal, fresh) {
|
|
1418
1641
|
this.scope = scope;
|
|
1419
1642
|
this.parent = parent;
|
|
1420
1643
|
this.literals = literals;
|
|
@@ -1422,81 +1645,98 @@ function GetStaticVariable(scope, parent, literals, expressions, literal, fresh)
|
|
|
1422
1645
|
this.literal = literal;
|
|
1423
1646
|
this.fresh = fresh;
|
|
1424
1647
|
Object.seal(this);
|
|
1425
|
-
}
|
|
1648
|
+
}
|
|
1426
1649
|
|
|
1427
|
-
|
|
1650
|
+
next(type, space, text, scanner) {
|
|
1428
1651
|
if (type !== 'literal' && (space === '' || this.fresh)) {
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
)
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1652
|
+
this.fresh = false;
|
|
1653
|
+
if (text === '{') {
|
|
1654
|
+
return expression(
|
|
1655
|
+
this.scope,
|
|
1656
|
+
new GetDynamicVariable(
|
|
1657
|
+
this.parent,
|
|
1658
|
+
this.literals.concat([this.literal]),
|
|
1659
|
+
this.expressions
|
|
1660
|
+
)
|
|
1661
|
+
);
|
|
1662
|
+
} else if (text === '.') {
|
|
1663
|
+
this.literal += text;
|
|
1664
|
+
return this;
|
|
1665
|
+
} else if (type === 'alphanum' || type === 'number') {
|
|
1666
|
+
this.literal += text;
|
|
1667
|
+
return this;
|
|
1668
|
+
}
|
|
1443
1669
|
}
|
|
1444
1670
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1454
|
-
} else {
|
|
1455
|
-
state = this.parent.return(this.scope, ['var', this.literals.concat([this.literal]), this.expressions], scanner);
|
|
1671
|
+
if (this.literals.length !== 0 || this.expressions.length !== 0) {
|
|
1672
|
+
return this.parent
|
|
1673
|
+
.return(
|
|
1674
|
+
this.scope,
|
|
1675
|
+
['var', this.literals.concat([this.literal]), this.expressions],
|
|
1676
|
+
scanner
|
|
1677
|
+
)
|
|
1678
|
+
.next(type, space, text, scanner);
|
|
1456
1679
|
}
|
|
1457
|
-
return state.next(type, space, text, scanner);
|
|
1458
|
-
};
|
|
1459
1680
|
|
|
1460
|
-
|
|
1681
|
+
if (this.literal === '') {
|
|
1682
|
+
this.scope.error(`${scanner.position()}: Expected name but got ${tokenName(type, text)}`);
|
|
1683
|
+
return this.parent.return(this.scope, [], scanner);
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
return this.parent
|
|
1687
|
+
.return(this.scope, ['get', this.literal], scanner)
|
|
1688
|
+
.next(type, space, text, scanner);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
class ThenExpect {
|
|
1693
|
+
constructor(expect, text, parent) {
|
|
1461
1694
|
this.expect = expect;
|
|
1462
1695
|
this.text = text;
|
|
1463
1696
|
this.parent = parent;
|
|
1464
1697
|
Object.freeze(this);
|
|
1465
|
-
}
|
|
1698
|
+
}
|
|
1466
1699
|
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
for (
|
|
1470
|
-
|
|
1700
|
+
return(scope) {
|
|
1701
|
+
const args = [];
|
|
1702
|
+
for (const arg of arguments) {
|
|
1703
|
+
args.push(arg);
|
|
1471
1704
|
}
|
|
1472
1705
|
return new Expect(this.expect, this.text, scope, this.parent, args);
|
|
1473
|
-
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1474
1708
|
|
|
1475
|
-
|
|
1709
|
+
class Expect {
|
|
1710
|
+
constructor(expect, text, scope, parent, args) {
|
|
1476
1711
|
this.expect = expect;
|
|
1477
1712
|
this.text = text;
|
|
1478
1713
|
this.scope = scope;
|
|
1479
1714
|
this.parent = parent;
|
|
1480
1715
|
this.args = args;
|
|
1481
1716
|
Object.freeze(this);
|
|
1482
|
-
}
|
|
1717
|
+
}
|
|
1483
1718
|
|
|
1484
|
-
|
|
1719
|
+
next(type, _space, text, scanner) {
|
|
1485
1720
|
if (type !== this.expect || text !== this.text) {
|
|
1486
|
-
|
|
1721
|
+
this.scope.error(
|
|
1722
|
+
`${scanner.position()}: Expected ${tokenName(this.expect, this.text)} but got ${tokenName(
|
|
1723
|
+
type,
|
|
1724
|
+
text
|
|
1725
|
+
)}.`
|
|
1726
|
+
);
|
|
1487
1727
|
}
|
|
1488
1728
|
return this.parent.return.apply(this.parent, this.args);
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
function tokenName(type, text) {
|
|
1492
|
-
// It might not be possible to provoke an error at the beginning of a new
|
|
1493
|
-
// block.
|
|
1494
|
-
// istanbul ignore if
|
|
1495
|
-
if (type === 'start') {
|
|
1496
|
-
return 'beginning of a new ' + JSON.stringify(text) + ' block';
|
|
1497
|
-
} else if (type === 'stop') {
|
|
1498
|
-
return 'to the end of the block';
|
|
1499
|
-
} else {
|
|
1500
|
-
return JSON.stringify(text);
|
|
1501
|
-
}
|
|
1729
|
+
}
|
|
1502
1730
|
}
|
|
1731
|
+
|
|
1732
|
+
const tokenName = (type, text) => {
|
|
1733
|
+
// It might not be possible to provoke an error at the beginning of a new
|
|
1734
|
+
// block.
|
|
1735
|
+
if (type === 'start') {
|
|
1736
|
+
return `beginning of a new ${JSON.stringify(text)} block`;
|
|
1737
|
+
} else if (type === 'stop') {
|
|
1738
|
+
return 'to the end of the block';
|
|
1739
|
+
} else {
|
|
1740
|
+
return JSON.stringify(text);
|
|
1741
|
+
}
|
|
1742
|
+
};
|