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/grammar.js CHANGED
@@ -1,172 +1,194 @@
1
- 'use strict';
1
+ import Scope from './scope.js';
2
2
 
3
- var Scope = require('./scope');
4
- var story = require('./story');
5
-
6
- exports.start = start;
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
- function start(story, path, base) {
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
- function Stop(scope) {
12
+ class Stop {
13
+ constructor(scope) {
16
14
  this.scope = scope;
17
15
  Object.freeze(this);
18
- }
16
+ }
19
17
 
20
- // istanbul ignore next
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
- this.scope.error(scanner.position() + ': Expected end of file, got ' + tokenName(type, text) + '.');
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
- Stop.prototype.return = function _return(scope, rets, escs, scanner) {
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
- function End() {
36
+ class End {
37
+ constructor() {
37
38
  Object.freeze(this);
38
- }
39
+ }
39
40
 
40
- // istanbul ignore next
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
- function Thread(scope, parent, rets, escs) {
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
- Thread.prototype.next = function next(type, space, text, scanner) {
56
- if (type === 'symbol'|| type === 'alphanum' || type === 'number' || type === 'literal' || text === '--' || text === '---') {
57
- return new Text(this.scope, space, text, this, this.rets);
58
- } else if (type === 'token') {
59
- if (text === '{') {
60
- return new Block(this.scope, new ThenExpect('token', '}', this), this.rets);
61
- } else if (text === '@') {
62
- return label(this.scope, new Label(this, this.rets));
63
- } else if (text === '->') {
64
- return label(this.scope, new Goto(this, this.rets));
65
- } else if (text === '<-') {
66
- // Explicitly tie rets to null by dropping them.
67
- Scope.tie(this.rets, 'RET');
68
- // Continue carrying escs to the next encountered prompt.
69
- // Advance the path so that option thread don't appear empty.
70
- return new Thread(this.scope.next(), this.parent, [], this.escs);
71
- } else if (text === '/') {
72
- var node = this.scope.create('break', null, scanner.position());
73
- this.scope.tie(this.rets);
74
- return new Thread(this.scope.next(), this.parent, [node], this.escs);
75
- } else if (text === '//') {
76
- var node = this.scope.create('paragraph', null, scanner.position());
77
- this.scope.tie(this.rets);
78
- return new Thread(this.scope.next(), this.parent, [node], this.escs);
79
- } else if (text === '{"' || text === '{\'' || text === '"}' || text === '\'}') {
80
- return new Text(this.scope, space, '', this, this.rets)
81
- .next(type, '', text, scanner);
82
- } else if (text === '<') {
83
- return label(this.scope, new ThenExpect('token', '>', new Cue(this, this.rets, this.escs)));
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
- return this;
121
+ return this;
106
122
  }
107
123
  if (type === 'stop' || text === '|' || text === ']' || text === '[' || text === '}') {
108
- return this.parent.return(this.scope, this.rets, this.escs, scanner)
109
- .next(type, space, text, scanner);
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
- Thread.prototype.return = function _return(scope, rets, escs, scanner) {
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
- function Text(scope, lift, text, parent, rets) {
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
- Text.prototype.next = function next(type, space, text, scanner) {
149
+ next(type, space, text, scanner) {
131
150
  if (type === 'alphanum' || type === 'number' || type === 'symbol' || type === 'literal') {
132
- this.text += space + text;
133
- return this;
151
+ this.text += space + text;
152
+ return this;
134
153
  } else if (type === 'token') {
135
- if (text === '{"') {
136
- this.text += space + '“';
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
- } else if (text === '---') {
158
- this.text += space + '—'; // em-dash
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
- var node = this.scope.create('text', this.text, scanner.position());
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.return(this.scope.next(), [node], [], scanner)
166
- .next(type, space, text, scanner);
167
- };
184
+ return this.parent
185
+ .return(this.scope.next(), [node], [], scanner)
186
+ .next(type, space, text, scanner);
187
+ }
188
+ }
168
189
 
169
- function MaybeThread(scope, parent, rets, escs, skips, space) {
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
- MaybeThread.prototype.next = function next(type, space, text, scanner) {
201
+ next(type, space, text, scanner) {
180
202
  if (type === 'token') {
181
- if (text === '{') {
182
- return expression(this.scope,
183
- new ThenExpect('token', '}',
184
- new ThreadCondition(this.parent, this.rets, this.escs, this.skips)));
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
- .next(type, this.space || space, text, scanner);
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
- MaybeThread.prototype.return = function _return(scope, rets, escs, scanner) {
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
- function ThreadCondition(parent, rets, escs, skips) {
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
- ThreadCondition.prototype.return = function _return(scope, args, scanner) {
204
- var node = scope.create('jump', invertExpression(args), scanner.position());
205
- var branch = new Branch(node);
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(scope.next(), this.parent, [node], this.escs, this.skips.concat([branch]));
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
- function MaybeOption(scope, parent, rets, escs, leader) {
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
- MaybeOption.prototype.next = function next(type, space, text, scanner) {
265
+ next(type, space, text, scanner) {
225
266
  if (type === 'token') {
226
- if (text === '{') {
227
- return new OptionOperator(this.scope,
228
- new ThenExpect('token', '}', this));
229
- }
230
- // Recognize the inequality token as individual keyword tokens with an
231
- // empty string amid them in this context.
232
- if (text === '<>') {
233
- return this.return(this.scope, 'keyword', '');
234
- }
235
- if (text === '<') {
236
- return new Keyword(this.scope, this);
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
- MaybeOption.prototype.return = function _return(scope, operator, expression, modifier, scanner) {
282
+ return(_scope, operator, expression, modifier, _scanner) {
243
283
  if (operator === '+' || operator === '-' || operator === '!') {
244
- modifier = modifier || ['val', 1];
284
+ modifier = modifier || ['val', 1];
245
285
  }
246
286
  if (operator === '?') {
247
- modifier = modifier || ['val', 0];
287
+ modifier = modifier || ['val', 0];
248
288
  }
249
289
  if (operator === '+' || operator === '-') {
250
- this.consequences.push([expression, [operator, expression, modifier]]);
290
+ this.consequences.push([expression, [operator, expression, modifier]]);
251
291
  }
252
292
  if (operator === '-') {
253
- this.conditions.push(['>=', expression, modifier]);
293
+ this.conditions.push(['>=', expression, modifier]);
254
294
  }
255
295
  if (operator === '') {
256
- this.conditions.push(expression);
296
+ this.conditions.push(expression);
257
297
  }
258
298
  if (operator === '=' || operator === '!' || operator === '?') {
259
- this.conditions.push(['<>', expression, modifier]);
260
- this.consequences.push([expression, modifier]);
299
+ this.conditions.push(['<>', expression, modifier]);
300
+ this.consequences.push([expression, modifier]);
261
301
  }
262
302
  if (operator === 'keyword') {
263
- this.keywords[expression] = true;
303
+ this.keywords[expression] = true;
264
304
  }
265
305
  return this;
266
- };
306
+ }
267
307
 
268
- MaybeOption.prototype.advance = function advance() {
308
+ advance() {
269
309
  if (this.descended) {
270
- this.at = this.at.next();
310
+ this.at = this.at.next();
271
311
  } else {
272
- this.at = this.at.firstChild();
273
- this.descended = true;
312
+ this.at = this.at.firstChild();
313
+ this.descended = true;
274
314
  }
275
- };
315
+ }
276
316
 
277
- MaybeOption.prototype.option = function option(scanner) {
278
- var variable = this.scope.name();
279
- var rets = [];
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
- this.consequences.push([['get', variable], ['+', ['get', variable], ['val', 1]]]);
285
- var jump = this.at.create('jump', ['<>', ['get', variable], ['val', 0]], scanner.position());
286
- var jumpBranch = new Branch(jump);
287
- rets.push(jumpBranch);
288
- this.advance();
289
- this.at.tie([jump]);
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 (var i = 0; i < this.conditions.length; i++) {
293
- var condition = this.conditions[i];
294
- var jump = this.at.create('jump', ['==', condition, ['val', 0]], scanner.position());
295
- var jumpBranch = new Branch(jump);
296
- rets.push(jumpBranch);
297
- this.advance();
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
- var option = new Option(this.scope, this.parent, rets, this.escs, this.leader, this.consequences);
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(scanner,
308
- new OptionThread(this.at, option, [], option, 'qa', AfterInitialQA));
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
- function Keyword(scope, parent) {
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
- Keyword.prototype.next = function next(type, space, text, scanner) {
377
+ next(_type, space, text, _scanner) {
321
378
  if (text === '>') {
322
- return this.parent.return(this.scope, 'keyword', this.keyword);
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
- function OptionOperator(scope, parent) {
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
- OptionOperator.prototype.next = function next(type, space, text, scanner) {
395
+ next(type, space, text, scanner) {
337
396
  if (text === '+' || text === '-' || text === '=' || text === '!' || text === '?') {
338
- return expression(this.scope,
339
- new OptionArgument(this.parent, text));
397
+ return expression(this.scope, new OptionArgument(this.parent, text));
340
398
  } else {
341
- return expression(this.scope,
342
- new OptionArgument2(this.parent, ''))
343
- .next(type, space, text, scanner);
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
- function OptionArgument(parent, operator) {
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
- OptionArgument.prototype.return = function _return(scope, args, scanner) {
416
+ return(scope, args, scanner) {
354
417
  if (args[0] === 'get' || args[0] === 'var') {
355
- return this.parent.return(scope, this.operator, args, this.args, scanner);
418
+ return this.parent.return(scope, this.operator, args, this.args, scanner);
356
419
  } else {
357
- return expression(scope,
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
- function OptionArgument2(parent, operator, args) {
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
- OptionArgument2.prototype.return = function _return(scope, args, scanner) {
433
+ return(scope, args, scanner) {
370
434
  return this.parent.return(scope, this.operator, args, this.args, scanner);
371
- };
435
+ }
436
+ }
372
437
 
373
- function Option(scope, parent, rets, escs, leader, consequences) {
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
- Option.prototype.return = function _return(scope, rets, escs, scanner) {
453
+ return(scope, rets, escs, scanner) {
388
454
  // Create a jump from the end of the answer.
389
455
  if (this.mode !== 'a') {
390
- // If the answer is reused in the question, create a dedicated jump and
391
- // add it to the end of the answer.
392
- var jump = scope.create('goto', 'RET', scanner.position());
393
- this.node.answer.push(scope.name());
394
- rets.push(jump);
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
- this.scope.next(),
399
- this.rets.concat([this.node]),
400
- this.escs.concat(rets, escs),
401
- scanner
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
- Option.prototype.thread = function thread(scanner, parent) {
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
- var placeholder = this.next.create('goto', 'RET', scanner.position());
474
+ const placeholder = this.next.create('goto', 'RET', scanner.position());
409
475
  return new Thread(this.next, parent, [placeholder], []);
410
- };
476
+ }
411
477
 
412
- Option.prototype.push = function push(scope, mode) {
413
- var next = this.next.name();
414
- var end = scope.name();
478
+ push(scope, mode) {
479
+ const next = this.next.name();
480
+ const end = scope.name();
415
481
  if (next !== end) {
416
- if (mode === 'q' || mode === 'qa') {
417
- this.node.question.push(next);
418
- }
419
- if (mode === 'a' || mode === 'qa') {
420
- this.node.answer.push(next);
421
- }
422
- this.next = scope;
423
- this.mode = mode;
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
- function OptionThread(scope, parent, rets, option, mode, Next) {
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
- OptionThread.prototype.return = function _return(scope, rets, escs, scanner) {
508
+ return(scope, rets, escs, _scanner) {
441
509
  this.option.push(scope, this.mode);
442
- // TODO investigate whether we can consistently tie of received rets
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
- function AfterInitialQA(scope, parent, rets, option) {
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
- AfterInitialQA.prototype.next = function next(type, space, text, scanner) {
462
- // istanbul ignore else
531
+ next(type, _space, text, scanner) {
463
532
  if (type === 'token' && text === '[') {
464
- return this.option.thread(scanner, new AfterQorA(this.scope, this, this.rets, this.option));
533
+ return this.option.thread(scanner, new AfterQorA(this.scope, this, this.rets, this.option));
465
534
  } else {
466
- this.scope.error(scanner.position() + ': Expected "[]" brackets in option but got ' + tokenName(type, text) + '.');
467
- return this.return(this.scope, this.rets, [], scanner);
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, after
472
- // which anything and everything to the end of the block contributes to the
473
- // answer.
474
- AfterInitialQA.prototype.return = function _return(scope, rets, escs, scanner) {
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
- var consequences = this.option.consequences;
553
+ const consequences = this.option.consequences;
483
554
  if (consequences.length) {
484
- this.option.node.answer.push(scope.name());
555
+ this.option.node.answer.push(scope.name());
485
556
  }
486
- for (var i = 0; i < consequences.length; i++) {
487
- var consequence = consequences[i];
488
- var node = scope.create('move', null, scanner.position());
489
- node.source = consequence[1];
490
- node.target = consequence[0];
491
- scope.tie(rets);
492
- scope = scope.next();
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(scanner,
498
- new OptionThread(scope, this.parent, rets, this.option, 'a', AfterFinalA));
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
- function DecideQorA(scope, parent, rets, option) {
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
- DecideQorA.prototype.next = function next(type, space, text, scanner) {
513
- if (type === 'token' && text === '[') { // A
514
- this.option.push(this.scope, 'a');
515
- return this.option.thread(scanner,
516
- new OptionThread(this.scope, this, this.rets, this.option, 'q', ExpectFinalBracket));
517
- } else if (type === 'token' && text === ']') { // Q
518
- this.option.push(this.scope, 'q');
519
- return this.parent.return(this.scope, this.rets, [], scanner);
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
- this.scope.error(scanner.position() + ': Expected "]" to end option but got ' + tokenName(type, text) + '.');
522
- return this.parent.return(this.scope, this.rets, [], scanner);
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
- DecideQorA.prototype.return = function _return(scope, rets, escs, scanner) {
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(scanner,
532
- new OptionThread(scope, this.parent, rets, this.option, 'qa', AfterQA));
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
- function AfterQA(scope, parent, rets, option) {
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
- AfterQA.prototype.next = function next(type, space, text, scanner) {
630
+ next(type, _space, text, scanner) {
547
631
  if (type === 'token' && text === '[') {
548
- return this.option.thread(scanner,
549
- new OptionThread(this.scope, this, this.rets, this.option, 'q', ExpectFinalBracket));
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
- return this.parent.return(this.scope, this.rets, [], scanner);
637
+ return this.parent.return(this.scope, this.rets, [], scanner);
552
638
  } else {
553
- this.scope.error(scanner.position() + ': Expected "]" to end option but got ' + tokenName(type, text) + '.');
554
- return this.parent.return(this.scope, this.rets, [], scanner);
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
- AfterQA.prototype.return = function _return(scope, rets, escs, scanner) {
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(scanner,
563
- new OptionThread(this.scope, this.parent, rets, this.option, 'qa', ExpectFinalBracket));
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
- function AfterQorA(scope, parent, rets, option) {
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
- AfterQorA.prototype.return = function _return(scope, rets, escs, scanner) {
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
- function ExpectFinalBracket(scope, parent, rets, option) {
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
- ExpectFinalBracket.prototype.next = function next(type, space, text, scanner) {
690
+ next(type, space, text, scanner) {
597
691
  if (type !== 'token' || text !== ']') {
598
- this.scope.error(scanner.position() + ': Expected "]" to end option.');
599
- return this.parent.return(this.scope, this.rets, [], scanner)
600
- .next('token', space, ']', scanner);
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
- function AfterFinalA(scope, parent, rets, option) {
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
- AfterFinalA.prototype.next = function next(type, space, text, scanner) {
616
- return this.parent.return(this.scope, this.rets, [], scanner)
617
- .next(type, space, text, scanner);
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
- function Branch(node) {
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
- function Label(parent, rets) {
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
- Label.prototype.return = function _return(scope, expression, scanner) {
637
- if (expression[0] === 'get') {
638
- var label = expression[1];
639
- if (label === '...') {
640
- var node = scope.create('goto', 'RET', scanner.position());
641
- scope.tie(this.rets);
642
- return new Thread(scope, new Loop(scope, this.parent), [node], []);
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
- var labelScope = scope.label(label);
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
- node.locals = params;
665
- return new Thread(labelScope.next(),
666
- new ConcludeProcedure(scope, this.parent, this.rets),
667
- [node], []);
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
- scope.error(scanner.position() + ': Expected label after "@".');
670
- return new Thread(scope, this.parent, this.rets, []);
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
- function Loop(scope, parent) {
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
- Loop.prototype.return = function _return(scope, rets, escs, scanner) {
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
- function ConcludeProcedure(scope, parent, rets) {
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
- ConcludeProcedure.prototype.return = function _return(scope, rets, escs, scanner) {
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
- function Goto(parent, rets) {
812
+ class Goto {
813
+ constructor(parent, rets) {
705
814
  this.parent = parent;
706
815
  this.rets = rets;
707
- }
816
+ }
708
817
 
709
- Goto.prototype.return = function _return(scope, args, scanner) {
818
+ return(scope, args, scanner) {
710
819
  if (args[0] === 'get') {
711
- Scope.tie(this.rets, args[1]);
712
- return this.parent.return(scope.next(), [], [], scanner);
820
+ Scope.tie(this.rets, args[1]);
821
+ return this.parent.return(scope.next(), [], [], scanner);
713
822
  } else if (args[0] === 'call') {
714
- var label = args[1][1];
715
- var node = scope.create('call', label, scanner.position());
716
- node.args = args.slice(2);
717
- scope.tie(this.rets);
718
- return this.parent.return(scope.next(), [node], [new Branch(node)], scanner);
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
- scope.error(scanner.position() + ': Expected label after goto arrow but got expression.');
721
- return new Thread(scope, this.parent, this.rets, []);
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
- function Cue(parent, rets, escs) {
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
- Cue.prototype.return = function _return(scope, expression, scanner) {
843
+ return(scope, expression, scanner) {
733
844
  if (expression.length === 0 || expression[0] !== 'get') {
734
- scope.error(scanner.position() + ': Expected cue.');
735
- return this.parent.return(scope, this.rets, this.escs, scanner);
845
+ scope.error(`${scanner.position()}: Expected cue.`);
846
+ return this.parent.return(scope, this.rets, this.escs, scanner);
736
847
  } else {
737
- var cue = expression[1];
738
- var node = scope.create('cue', cue, scanner.position());
739
- scope.tie(this.rets);
740
- return this.parent.return(scope.next(), [node], this.escs, scanner);
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
- var mutators = {
752
- '=': true,
753
- '+': true,
754
- '-': true,
755
- '*': true,
756
- '/': true,
856
+ const mutators = {
857
+ '=': true,
858
+ '+': true,
859
+ '-': true,
860
+ '*': true,
861
+ '/': true,
757
862
  };
758
863
 
759
- var toggles = {
760
- '!': ['val', 1],
761
- '?': ['val', 0],
864
+ const toggles = {
865
+ '!': ['val', 1],
866
+ '?': ['val', 0],
762
867
  };
763
868
 
764
- var variables = {
765
- '@': 'loop',
766
- '#': 'hash',
767
- '^': 'pick'
869
+ const variables = {
870
+ '@': 'loop',
871
+ '#': 'hash',
872
+ '^': 'pick',
768
873
  };
769
874
 
770
- var switches = {
771
- '&': 'loop',
772
- '~': 'rand'
875
+ const switches = {
876
+ '&': 'loop',
877
+ '~': 'rand',
773
878
  };
774
879
 
775
- Block.prototype.next = function next(type, space, text, scanner) {
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
- if (text === '(') {
778
- return expression(this.scope, new ExpressionBlock(this.parent, this.rets, 'walk'))
779
- .next(type, space, text, scanner);
780
- } else if (mutators[text]) {
781
- return expression(this.scope, new SetBlock(this.parent, this.rets, text));
782
- } else if (toggles[text]) {
783
- return expression(this.scope, new ToggleBlock(this.parent, this.rets, toggles[text]));
784
- } else if (variables[text]) {
785
- return expression(this.scope, new ExpressionBlock(this.parent, this.rets, variables[text]));
786
- } else if (switches[text]) {
787
- return new SwitchBlock(this.scope, this.parent, this.rets)
788
- .start(scanner, null, this.scope.name(), null, switches[text]);
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
- .start(scanner, null, this.scope.name(), 1, 'walk') // with variable and value, waiting for case to start
793
- .next(type, space, text, scanner);
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
- function SetBlock(parent, rets, op) {
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
- SetBlock.prototype.return = function _return(scope, expression, scanner) {
927
+ return(scope, expression, _scanner) {
804
928
  return new MaybeSetVariable(scope, this.parent, this.rets, this.op, expression);
805
- };
929
+ }
930
+ }
806
931
 
807
- function MaybeSetVariable(scope, parent, rets, op, expression) {
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
- MaybeSetVariable.prototype.next = function next(type, space, text, scanner) {
942
+ next(type, space, text, scanner) {
817
943
  if (type === 'token' && text === '}') {
818
- return this.set(['val', 1], this.expression, scanner)
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
- .next(type, space, text, scanner);
823
- };
946
+ return expression(this.scope, this).next(type, space, text, scanner);
947
+ }
824
948
 
825
- MaybeSetVariable.prototype.set = function set(source, target, scanner) {
826
- var node = this.scope.create('move', null, scanner.position());
949
+ set(source, target, scanner) {
950
+ const node = this.scope.create('move', null, scanner.position());
827
951
  if (this.op === '=') {
828
- node.source = source;
952
+ node.source = source;
829
953
  } else {
830
- node.source = [this.op, target, source];
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
- MaybeSetVariable.prototype.return = function _return(scope, target, scanner) {
961
+ return(_scope, target, scanner) {
838
962
  return this.set(this.expression, target, scanner);
839
- };
963
+ }
964
+ }
840
965
 
841
- function ToggleBlock(parent, rets, source) {
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
- ToggleBlock.prototype.return = function _return(scope, expression, scanner) {
849
- var node = scope.create('move', null, scanner.position());
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
- function ExpressionBlock(parent, rets, mode) {
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
- ExpressionBlock.prototype.return = function _return(scope, expression, scanner) {
991
+ return(scope, expression, _scanner) {
864
992
  return new AfterExpressionBlock(scope, this.parent, this.rets, this.mode, expression);
865
- };
993
+ }
994
+ }
866
995
 
867
- function AfterExpressionBlock(scope, parent, rets, mode, expression) {
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
- AfterExpressionBlock.prototype.next = function next(type, space, text, scanner) {
1006
+ next(type, space, text, scanner) {
877
1007
  if (text === '|') {
878
- return new SwitchBlock(this.scope, this.parent, this.rets)
879
- .start(scanner, this.expression, null, 0, this.mode);
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
- return new SwitchBlock(this.scope, this.parent, this.rets)
882
- .start(scanner, invertExpression(this.expression), null, 0, this.mode, 2);
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
- var node = this.scope.create('echo', this.expression, scanner.position());
885
- this.scope.tie(this.rets);
886
- return this.parent.return(this.scope.next(), [node], [], scanner)
887
- .next(type, space, text, scanner);
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
- this.scope.error(scanner.position() + ': Expected "|", "?", or "}" after expression but got ' + tokenName(type, text) + '.');
890
- return this.parent.return(this.scope, [], [], scanner);
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
- function SwitchBlock(scope, parent, rets) {
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
- SwitchBlock.prototype.start = function start(scanner, expression, variable, value, mode, min) {
1053
+ start(scanner, expression, variable, value, mode, min) {
905
1054
  value = value || 0;
906
1055
  if (mode === 'loop' && !expression) {
907
- value = 1;
1056
+ value = 1;
908
1057
  }
909
1058
  expression = expression || ['get', this.scope.name()];
910
- var node = this.scope.create('switch', expression, scanner.position());
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(this.scope, new Case(this.scope.firstChild(), this, [], this.branches, min || 0));
919
- };
1067
+ return new MaybeWeightedCase(
1068
+ this.scope,
1069
+ new Case(this.scope.firstChild(), this, [], this.branches, min || 0)
1070
+ );
1071
+ }
920
1072
 
921
- SwitchBlock.prototype.return = function _return(scope, rets, escs, scanner) {
1073
+ return(_scope, rets, escs, scanner) {
922
1074
  if (this.node.mode === 'pick') {
923
- Scope.tie(rets, 'RET');
924
- rets = [this.node];
925
- // TODO think about what to do with escs.
1075
+ Scope.tie(rets, 'RET');
1076
+ rets = [this.node];
1077
+ // TODO think about what to do with escs.
926
1078
  } else {
927
- this.node.next = 'RET';
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
- function Case(scope, parent, rets, branches, min) {
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
- Case.prototype.next = function next(type, space, text, scanner) {
1095
+ next(type, space, text, scanner) {
942
1096
  if (text === '|') {
943
- return new MaybeWeightedCase(this.scope, this);
1097
+ return new MaybeWeightedCase(this.scope, this);
944
1098
  } else {
945
- var scope = this.scope;
946
- while (this.branches.length < this.min) {
947
- var node = scope.create('goto', 'RET', scanner.position());
948
- this.rets.push(node);
949
- this.branches.push(scope.name());
950
- scope = scope.next();
951
- }
952
- return this.parent.return(scope, this.rets, [], scanner)
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
- Case.prototype.case = function _case(args, scanner) {
1110
+ case(args, scanner) {
958
1111
  this.parent.weights.push(args || ['val', 1]);
959
- var scope = this.scope.zerothChild();
960
- var node = scope.create('goto', 'RET', scanner.position());
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
- Case.prototype.return = function _return(scope, rets, escs, scanner) {
966
- return new Case(this.scope.next(), this.parent, this.rets.concat(rets, escs), this.branches, this.min);
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
- function MaybeWeightedCase(scope, parent) {
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
- MaybeWeightedCase.prototype.next = function next(type, space, text, scanner) {
1136
+ next(type, space, text, scanner) {
976
1137
  if (text === '(') {
977
- return expression(this.scope, this)
978
- .next(type, space, text, scanner);
1138
+ return expression(this.scope, this).next(type, space, text, scanner);
979
1139
  } else {
980
- return this.parent.case(null, scanner)
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
- MaybeWeightedCase.prototype.return = function _return(scope, args, scanner) {
1144
+ return(_scope, args, scanner) {
986
1145
  return this.parent.case(args, scanner);
987
- };
1146
+ }
1147
+ }
988
1148
 
989
- function Ask(scope, parent, rets, escs) {
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
- Ask.prototype.next = function next(type, space, text, scanner) {
1158
+ next(type, space, text, scanner) {
998
1159
  if (type == 'alphanum') {
999
- return new Read(text, this.scope, this.parent, this.rets, this.escs);
1160
+ return new Read(text, this.scope, this.parent, this.rets, this.escs);
1000
1161
  }
1001
- var node = this.scope.create('ask', null, scanner.position());
1002
- return new Thread(this.scope.next(), this.parent, this.rets, this.escs)
1003
- .next(type, space, text, scanner);
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
- function Read(variable, scope, parent, rets, escs) {
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
- Read.prototype.next = function next(type, space, text, scanner) {
1182
+ next(type, space, text, scanner) {
1016
1183
  if (type == 'alphanum') {
1017
- return this.return(text, scanner);
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
- Read.prototype.return = function _return(cue, scanner) {
1023
- var node = this.scope.create('read', this.variable, scanner.position());
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
- function Program(scope, parent, rets, escs) {
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
- Program.prototype.next = function next(type, space, text, scanner) {
1205
+ next(type, space, text, scanner) {
1037
1206
  if (type === 'stop' || text === '}') {
1038
- return this.parent.return(this.scope, this.rets, this.escs, scanner)
1039
- .next(type, space, text, scanner);
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
- return this;
1042
- // istanbul ignore if
1211
+ return this;
1043
1212
  } else if (type === 'error') {
1044
- // Break out of recursive error loops
1045
- return this.parent.return(this.scope, this.rets, this.escs, scanner);
1213
+ // Break out of recursive error loops
1214
+ return this.parent.return(this.scope, this.rets, this.escs, scanner);
1046
1215
  } else {
1047
- return variable(this.scope, new Assignment(this.scope, this, this.rets, this.escs))
1048
- .next(type, space, text, scanner);
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
- Program.prototype.return = function _return(scope, rets, escs, scanner) {
1225
+ return(scope, rets, escs, _scanner) {
1053
1226
  return new Program(scope, this.parent, rets, escs);
1054
- };
1227
+ }
1228
+ }
1055
1229
 
1056
- function Assignment(scope, parent, rets, escs) {
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
- Assignment.prototype.return = function _return(scope, expression, scanner) {
1065
- // istanbul ignore else
1239
+ return(_scope, expression, scanner) {
1066
1240
  if (expression[0] === 'get' || expression[0] === 'var') {
1067
- return new ExpectOperator(this.scope, this.parent, this.rets, this.escs, expression);
1241
+ return new ExpectOperator(this.scope, this.parent, this.rets, this.escs, expression);
1068
1242
  } else {
1069
- this.scope.error(scanner.position() + ': Expected variable to assign but got ' + JSON.stringify(expression) + '.');
1070
- return this.parent.return(this.scope, this.rets, this.escs, scanner)
1071
- .next('error', '', '', scanner);
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
- function ExpectOperator(scope, parent, rets, escs, left) {
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
- ExpectOperator.prototype.next = function next(type, space, text, scanner) {
1085
- // istanbul ignore else
1263
+ next(type, _space, text, scanner) {
1086
1264
  if (text === '=') {
1087
- return expression(this.scope, new ExpectExpression(this.scope, this.parent, this.rets, this.escs, this.left, text));
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
- this.scope.error(scanner.position() + ': Expected "=" operator but got ' + tokenName(type, text) + '.');
1090
- return this.parent.return(this.scope, this.rets, this.escs, scanner);
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
- function ExpectExpression(scope, parent, rets, escs, left, operator) {
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
- ExpectExpression.prototype.return = function _return(scope, right, scanner) {
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
- var unary = {
1115
- 'not': true,
1116
- '-': true,
1117
- '~': true,
1118
- '#': true
1299
+ const unary = {
1300
+ not: true,
1301
+ '-': true,
1302
+ '~': true,
1303
+ '#': true,
1119
1304
  };
1120
1305
 
1121
- var exponential = {
1122
- '**': true // x ** y
1306
+ const exponential = {
1307
+ '**': true, // x ** y
1123
1308
  };
1124
1309
 
1125
- var multiplicative = {
1126
- '*': true,
1127
- '/': true,
1128
- '%': true,
1129
- 'rem': true,
1130
- '~': true,
1310
+ const multiplicative = {
1311
+ '*': true,
1312
+ '/': true,
1313
+ '%': true,
1314
+ rem: true,
1315
+ '~': true,
1131
1316
  };
1132
1317
 
1133
- var arithmetic = {
1134
- '+': true,
1135
- '-': true,
1318
+ const arithmetic = {
1319
+ '+': true,
1320
+ '-': true,
1136
1321
  };
1137
1322
 
1138
- var comparison = {
1139
- '<': true,
1140
- '<=': true,
1141
- '==': true,
1142
- '<>': true,
1143
- '>=': true,
1144
- '>': true,
1145
- '#': true
1323
+ const comparison = {
1324
+ '<': true,
1325
+ '<=': true,
1326
+ '==': true,
1327
+ '<>': true,
1328
+ '>=': true,
1329
+ '>': true,
1330
+ '#': true,
1146
1331
  };
1147
1332
 
1148
- var intersection = {
1149
- 'and': true
1333
+ const intersection = {
1334
+ and: true,
1150
1335
  };
1151
1336
 
1152
- var union = {
1153
- 'or': true
1337
+ const union = {
1338
+ or: true,
1154
1339
  };
1155
1340
 
1156
- var precedence = [ // from low to high
1157
- union,
1158
- intersection,
1159
- comparison,
1160
- arithmetic,
1161
- multiplicative,
1162
- exponential,
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
- function expression(scope, parent) {
1166
- for (var i = 0; i < precedence.length; i++) {
1167
- var operators = precedence[i];
1168
- parent = new BinaryExpression(operators, parent);
1169
- }
1170
- return new Unary(scope, parent);
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
- function variable(scope, parent) {
1174
- return new GetStaticVariable(scope, parent, [], [], '', true);
1175
- }
1358
+ const variable = (scope, parent) => {
1359
+ return new GetStaticVariable(scope, parent, [], [], '', true);
1360
+ };
1176
1361
 
1177
- function label(scope, parent) {
1178
- return new GetStaticVariable(scope, new AfterVariable(parent), [], [], '', true);
1179
- }
1362
+ const label = (scope, parent) => {
1363
+ return new GetStaticVariable(scope, new AfterVariable(parent), [], [], '', true);
1364
+ };
1180
1365
 
1181
- var inversions = {
1182
- '==': '<>',
1183
- '<>': '==',
1184
- '>': '<=',
1185
- '<': '>=',
1186
- '>=': '<',
1187
- '<=': '>'
1366
+ const inversions = {
1367
+ '==': '<>',
1368
+ '<>': '==',
1369
+ '>': '<=',
1370
+ '<': '>=',
1371
+ '>=': '<',
1372
+ '<=': '>',
1188
1373
  };
1189
1374
 
1190
- function invertExpression(expression) {
1191
- if (expression[0] === 'not') {
1192
- return expression[1];
1193
- } else if (inversions[expression[0]]) {
1194
- return [inversions[expression[0]], expression[1], expression[2]];
1195
- } else {
1196
- return ['not', expression];
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
- function Open(parent) {
1385
+ class Open {
1386
+ constructor(parent) {
1201
1387
  this.parent = parent;
1202
1388
  Object.seal(this);
1203
- }
1389
+ }
1204
1390
 
1205
- Open.prototype.return = function _return(scope, expression, scanner) {
1391
+ return(scope, expression, _scanner) {
1206
1392
  return new Close(scope, this.parent, expression);
1207
- };
1393
+ }
1394
+ }
1208
1395
 
1209
- function Close(scope, parent, expression) {
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
- Close.prototype.next = function next(type, space, text, scanner) {
1217
- // istanbul ignore else
1404
+ next(type, _space, text, scanner) {
1218
1405
  if (type === 'symbol' && text === ')') {
1219
- return this.parent.return(this.scope, this.expression, scanner);
1406
+ return this.parent.return(this.scope, this.expression, scanner);
1220
1407
  } else {
1221
- this.scope.error(scanner.position() + ': Expected parenthetical expression to end with ")" or continue with operator but got ' + tokenName(type, text) + '.');
1222
- return this.parent.return(this.scope, this.expression, scanner);
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
- function Value(scope, parent) {
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
- Value.prototype.next = function next(type, space, text, scanner) {
1426
+ next(type, space, text, scanner) {
1233
1427
  if (type === 'number') {
1234
- return this.parent.return(this.scope, ['val', +text], scanner);
1428
+ return this.parent.return(this.scope, ['val', +text], scanner);
1235
1429
  } else if (text === '(') {
1236
- return expression(this.scope, new Open(this.parent));
1430
+ return expression(this.scope, new Open(this.parent));
1237
1431
  } else if (text === '{') {
1238
- return expression(this.scope, new GetDynamicVariable(this.parent, [''], []));
1239
- // istanbul ignore else
1432
+ return expression(this.scope, new GetDynamicVariable(this.parent, [''], []));
1240
1433
  } else if (type === 'alphanum') {
1241
- return new GetStaticVariable(this.scope, new AfterVariable(this.parent), [], [], text, false);
1434
+ return new GetStaticVariable(this.scope, new AfterVariable(this.parent), [], [], text, false);
1242
1435
  } else {
1243
- this.scope.error(scanner.position() + ': Expected expression but got ' + tokenName(type, text) + '.');
1244
- return this.parent.return(this.scope, ['val', 0], scanner)
1245
- .next(type, space, text, scanner);
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
- function AfterVariable(parent) {
1444
+ class AfterVariable {
1445
+ constructor(parent) {
1250
1446
  this.parent = parent;
1251
1447
  Object.seal(this);
1252
- }
1448
+ }
1253
1449
 
1254
- AfterVariable.prototype.return = function _return(scope, expression, scanner) {
1450
+ return(scope, expression, _scanner) {
1255
1451
  return new MaybeCall(scope, this.parent, expression);
1256
- };
1452
+ }
1453
+ }
1257
1454
 
1258
- function MaybeCall(scope, parent, expression) {
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
- MaybeCall.prototype.next = function next(type, space, text, scanner) {
1463
+ next(type, space, text, scanner) {
1266
1464
  if (space === '' && text === '(') {
1267
- return new Arguments(this.scope, this.parent, this.expression);
1465
+ return new Arguments(this.scope, this.parent, this.expression);
1268
1466
  } else {
1269
- return this.parent.return(this.scope, this.expression, scanner)
1270
- .next(type, space, text, scanner);
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
- function Arguments(scope, parent, expression) {
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
- Arguments.prototype.next = function next(type, space, text, scanner) {
1481
+ next(type, space, text, scanner) {
1281
1482
  if (text === ')') {
1282
- return this.parent.return(this.scope, this.args, scanner);
1483
+ return this.parent.return(this.scope, this.args, scanner);
1283
1484
  } else {
1284
- return expression(this.scope, this)
1285
- .next(type, space, text, scanner);
1485
+ return expression(this.scope, this).next(type, space, text, scanner);
1286
1486
  }
1287
- };
1487
+ }
1288
1488
 
1289
- Arguments.prototype.return = function _return(scope, expression, scanner) {
1489
+ return(scope, expression, _scanner) {
1290
1490
  this.args.push(expression);
1291
1491
  return new MaybeArgument(scope, this);
1292
- };
1492
+ }
1493
+ }
1293
1494
 
1294
- function MaybeArgument(scope, parent) {
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
- MaybeArgument.prototype.next = function next(type, space, text, scanner) {
1502
+ next(type, space, text, scanner) {
1301
1503
  if (text === ',') {
1302
- return expression(this.scope, this.parent);
1303
- // istanbul ignore else
1304
- } else if (text === ')') {
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
- this.scope.error(scanner.position() + ': Expected "," or ")" to end or argument list but got ' + tokenName(type, text) + '.');
1308
- return this.parent;
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
- function Unary(scope, parent) {
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
- Unary.prototype.next = function next(type, space, text, scanner) {
1526
+ next(type, space, text, scanner) {
1319
1527
  if (unary[text] === true) {
1320
- return new Unary(this.scope,
1321
- new UnaryOperator(this.parent, text));
1528
+ return new Unary(this.scope, new UnaryOperator(this.parent, text));
1322
1529
  } else {
1323
- return new Value(this.scope, this.parent)
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
- function UnaryOperator(parent, op) {
1535
+ class UnaryOperator {
1536
+ constructor(parent, op) {
1329
1537
  this.parent = parent;
1330
1538
  this.op = op;
1331
- }
1539
+ }
1332
1540
 
1333
- UnaryOperator.prototype.return = function _return(scope, expression, scanner) {
1541
+ return(scope, expression, scanner) {
1334
1542
  return this.parent.return(scope, [this.op, expression], scanner);
1335
- };
1543
+ }
1544
+ }
1336
1545
 
1337
- function MaybeOperator(scope, parent, expression, operators) {
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
- MaybeOperator.prototype.next = function next(type, space, text, scanner) {
1555
+ next(type, space, text, scanner) {
1346
1556
  if (this.operators[text] === true) {
1347
- var parent = new MaybeExpression(this.parent, this.operators);
1348
- parent = new PartialExpression(parent, text, this.expression);
1349
- for (var i = precedence.indexOf(this.operators) + 1; i < precedence.length; i++) {
1350
- parent = new MaybeExpression(parent, precedence[i]);
1351
- }
1352
- return new Unary(this.scope, parent);
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
- return this.parent.return(this.scope, this.expression, scanner)
1355
- .next(type, space, text, scanner);
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
- function MaybeExpression(parent, operators) {
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
- MaybeExpression.prototype.return = function _return(scope, expression, scanner) {
1578
+ return(scope, expression, _scanner) {
1366
1579
  return new MaybeOperator(scope, this.parent, expression, this.operators);
1367
- };
1580
+ }
1581
+ }
1368
1582
 
1369
- function PartialExpression(parent, operator, expression) {
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
- PartialExpression.prototype.return = function _return(scope, expression, scanner) {
1590
+ return(scope, expression, scanner) {
1376
1591
  return this.parent.return(scope, [this.operator, this.expression, expression], scanner);
1377
- };
1592
+ }
1593
+ }
1378
1594
 
1379
- function BinaryExpression(operators, parent) {
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
- BinaryExpression.prototype.return = function _return(scope, expression, scanner) {
1602
+ return(scope, expression, _scanner) {
1386
1603
  return new MaybeOperator(scope, this.parent, expression, this.operators);
1387
- };
1604
+ }
1605
+ }
1388
1606
 
1389
- function GetDynamicVariable(parent, literals, expressions) {
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
- GetDynamicVariable.prototype.return = function _return(scope, expression, scanner) {
1397
- return new Expect('token', '}', scope, new ContinueVariable(
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
- ContinueVariable.prototype.return = function _return() {
1634
+ return() {
1414
1635
  return new GetStaticVariable(this.scope, this.parent, this.literals, this.expressions, '');
1415
- };
1636
+ }
1637
+ }
1416
1638
 
1417
- function GetStaticVariable(scope, parent, literals, expressions, literal, fresh) {
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
- GetStaticVariable.prototype.next = function next(type, space, text, scanner) {
1650
+ next(type, space, text, scanner) {
1428
1651
  if (type !== 'literal' && (space === '' || this.fresh)) {
1429
- this.fresh = false;
1430
- if (text === '{') {
1431
- return expression(this.scope, new GetDynamicVariable(
1432
- this.parent,
1433
- this.literals.concat([this.literal]),
1434
- this.expressions
1435
- ));
1436
- } else if (text === '.') {
1437
- this.literal += text;
1438
- return this;
1439
- } else if (type === 'alphanum' || type === 'number') {
1440
- this.literal += text;
1441
- return this;
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
- var state;
1446
- if (this.literals.length === 0 && this.expressions.length === 0) {
1447
- // istanbul ignore if
1448
- if (this.literal === '') {
1449
- this.scope.error(scanner.position() + ': Expected name but got ' + tokenName(type, text));
1450
- return this.parent.return(this.scope, [], scanner);
1451
- } else {
1452
- state = this.parent.return(this.scope, ['get', this.literal], scanner);
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
- function ThenExpect(expect, text, parent) {
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
- ThenExpect.prototype.return = function _return(scope) {
1468
- var args = [];
1469
- for (var i = 0; i < arguments.length; i++) {
1470
- args.push(arguments[i]);
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
- function Expect(expect, text, scope, parent, args) {
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
- Expect.prototype.next = function next(type, space, text, scanner) {
1719
+ next(type, _space, text, scanner) {
1485
1720
  if (type !== this.expect || text !== this.text) {
1486
- this.scope.error(scanner.position() + ': Expected ' + tokenName(this.expect, this.text) + ' but got ' + tokenName(type, text) + '.');
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
+ };