driftjs-compiler 0.0.4 → 0.0.6
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 +54 -0
- package/dist/index-cjs.js +14 -0
- package/package.json +5 -1
- package/src/generator.ts +0 -1029
- package/src/index.ts +0 -36
- package/src/lexer.ts +0 -954
- package/src/parser.ts +0 -533
- package/src/transformer.ts +0 -288
- package/tests/generator.test.ts +0 -149
- package/tests/if_conditionals.test.ts +0 -371
- package/tests/lexer.test.ts +0 -221
- package/tests/parser.test.ts +0 -302
- package/tests/transformer.test.ts +0 -110
- package/tsconfig.json +0 -8
- package/types/ast.ts +0 -88
- package/types/error.ts +0 -29
- package/types/index.ts +0 -6
- package/types/lexer-state.ts +0 -118
- package/types/opcodes.ts +0 -66
- package/types/token.ts +0 -61
- package/vite.config.ts +0 -20
package/src/parser.ts
DELETED
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
import { DriftLexer } from './lexer.js';
|
|
2
|
-
import type {
|
|
3
|
-
Token,
|
|
4
|
-
TokenSource,
|
|
5
|
-
ProgramNode,
|
|
6
|
-
TemplateChildNode,
|
|
7
|
-
ElementNode,
|
|
8
|
-
AttributeNode,
|
|
9
|
-
InterpolationNode,
|
|
10
|
-
IfNode,
|
|
11
|
-
ForNode,
|
|
12
|
-
SwitchNode,
|
|
13
|
-
CaseBranch,
|
|
14
|
-
} from '../types/index.js';
|
|
15
|
-
import {
|
|
16
|
-
TokenType,
|
|
17
|
-
ASTNodeType,
|
|
18
|
-
DriftParserError,
|
|
19
|
-
} from '../types/index.js';
|
|
20
|
-
|
|
21
|
-
const VOID_ELEMENTS = new Set([
|
|
22
|
-
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
23
|
-
'link', 'meta', 'param', 'source', 'track', 'wbr'
|
|
24
|
-
]);
|
|
25
|
-
|
|
26
|
-
class ArrayTokenSource implements TokenSource {
|
|
27
|
-
private readonly tokens: readonly Token[];
|
|
28
|
-
private current = 0;
|
|
29
|
-
|
|
30
|
-
constructor(tokens: readonly Token[]) {
|
|
31
|
-
this.tokens = tokens;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public nextToken(): Token {
|
|
35
|
-
if (this.current >= this.tokens.length) {
|
|
36
|
-
return this.tokens[this.tokens.length - 1]!;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const token = this.tokens[this.current]!;
|
|
40
|
-
this.current++;
|
|
41
|
-
return token;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Parser for Drift template tokens producing a structured AST.
|
|
47
|
-
*
|
|
48
|
-
* The parser lazily pulls tokens from the lexer on demand and keeps a small
|
|
49
|
-
* lookahead buffer for local decisions.
|
|
50
|
-
*/
|
|
51
|
-
export class DriftParser {
|
|
52
|
-
private readonly tokenSource: TokenSource;
|
|
53
|
-
private readonly lookahead: Token[] = [];
|
|
54
|
-
|
|
55
|
-
constructor(input: DriftLexer | readonly Token[]) {
|
|
56
|
-
this.tokenSource = Array.isArray(input) ? new ArrayTokenSource(input) : (input as DriftLexer);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public parse(): ProgramNode {
|
|
60
|
-
this.lookahead.length = 0;
|
|
61
|
-
return this.parseProgram();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private parseProgram(): ProgramNode {
|
|
65
|
-
const startLoc = this.peek().loc.start;
|
|
66
|
-
const body: TemplateChildNode[] = [];
|
|
67
|
-
|
|
68
|
-
while (!this.isAtEnd()) {
|
|
69
|
-
body.push(this.parseChild());
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const endLoc = this.peek().loc.end;
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
type: ASTNodeType.Program,
|
|
76
|
-
body,
|
|
77
|
-
loc: { start: startLoc, end: endLoc },
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private parseChild(): TemplateChildNode {
|
|
82
|
-
const token = this.peek();
|
|
83
|
-
|
|
84
|
-
if (token.type === TokenType.Comment) {
|
|
85
|
-
this.advance();
|
|
86
|
-
return {
|
|
87
|
-
type: ASTNodeType.Comment,
|
|
88
|
-
content: token.value,
|
|
89
|
-
loc: token.loc,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (token.type === TokenType.Interpolation) {
|
|
94
|
-
this.advance();
|
|
95
|
-
return {
|
|
96
|
-
type: ASTNodeType.Interpolation,
|
|
97
|
-
expression: token.value,
|
|
98
|
-
loc: token.loc,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (token.type === TokenType.TagOpen) {
|
|
103
|
-
return this.parseElement();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (token.type === TokenType.DirectiveIf) {
|
|
107
|
-
return this.parseIfDirective();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (token.type === TokenType.DirectiveFor) {
|
|
111
|
-
return this.parseForDirective();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (token.type === TokenType.DirectiveSwitch) {
|
|
115
|
-
return this.parseSwitchDirective();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (token.type === TokenType.Text) {
|
|
119
|
-
this.advance();
|
|
120
|
-
return {
|
|
121
|
-
type: ASTNodeType.Text,
|
|
122
|
-
content: token.value,
|
|
123
|
-
loc: token.loc,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (token.type === TokenType.TagOpenSlash) {
|
|
128
|
-
throw new DriftParserError(
|
|
129
|
-
`Unexpected closing tag '</${this.peek(1).value}>' without opening tag`,
|
|
130
|
-
token.loc.start.line,
|
|
131
|
-
token.loc.start.column,
|
|
132
|
-
token.loc.start.offset
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
throw new DriftParserError(
|
|
137
|
-
`Unexpected token '${token.value}' of type '${token.type}'`,
|
|
138
|
-
token.loc.start.line,
|
|
139
|
-
token.loc.start.column,
|
|
140
|
-
token.loc.start.offset
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
private parseElement(): ElementNode {
|
|
145
|
-
const openToken = this.consume(TokenType.TagOpen, 'Expected opening tag bracket');
|
|
146
|
-
const startLoc = openToken.loc.start;
|
|
147
|
-
|
|
148
|
-
const tagToken = this.consume(TokenType.Identifier, 'Expected tag name after opening bracket');
|
|
149
|
-
const tagName = tagToken.value;
|
|
150
|
-
|
|
151
|
-
const attributes: AttributeNode[] = [];
|
|
152
|
-
while (
|
|
153
|
-
!this.check(TokenType.TagClose) &&
|
|
154
|
-
!this.check(TokenType.TagSelfClose) &&
|
|
155
|
-
!this.isAtEnd()
|
|
156
|
-
) {
|
|
157
|
-
attributes.push(this.parseAttribute());
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (this.check(TokenType.TagSelfClose)) {
|
|
161
|
-
const selfCloseToken = this.advance();
|
|
162
|
-
return {
|
|
163
|
-
type: ASTNodeType.Element,
|
|
164
|
-
tagName,
|
|
165
|
-
attributes,
|
|
166
|
-
children: [],
|
|
167
|
-
isSelfClosing: true,
|
|
168
|
-
loc: { start: startLoc, end: selfCloseToken.loc.end },
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const closeToken = this.consume(TokenType.TagClose, 'Expected closing bracket after attributes');
|
|
173
|
-
|
|
174
|
-
if (VOID_ELEMENTS.has(tagName.toLowerCase())) {
|
|
175
|
-
return {
|
|
176
|
-
type: ASTNodeType.Element,
|
|
177
|
-
tagName,
|
|
178
|
-
attributes,
|
|
179
|
-
children: [],
|
|
180
|
-
isSelfClosing: true,
|
|
181
|
-
loc: { start: startLoc, end: closeToken.loc.end },
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const children: TemplateChildNode[] = [];
|
|
186
|
-
while (!this.check(TokenType.TagOpenSlash) && !this.isAtEnd()) {
|
|
187
|
-
children.push(this.parseChild());
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (this.isAtEnd()) {
|
|
191
|
-
throw new DriftParserError(
|
|
192
|
-
`Unclosed element '<${tagName}>', expected closing tag '</${tagName}>'`,
|
|
193
|
-
startLoc.line,
|
|
194
|
-
startLoc.column,
|
|
195
|
-
startLoc.offset
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
this.consume(TokenType.TagOpenSlash, `Expected closing tag '</${tagName}>'`);
|
|
200
|
-
const closingTagToken = this.consume(
|
|
201
|
-
TokenType.Identifier,
|
|
202
|
-
'Expected closing tag name'
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
if (closingTagToken.value !== tagName) {
|
|
206
|
-
throw new DriftParserError(
|
|
207
|
-
`Mismatched closing tag. Expected '</${tagName}>' but got '</${closingTagToken.value}>'`,
|
|
208
|
-
closingTagToken.loc.start.line,
|
|
209
|
-
closingTagToken.loc.start.column,
|
|
210
|
-
closingTagToken.loc.start.offset
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const closeBracketToken = this.consume(
|
|
215
|
-
TokenType.TagClose,
|
|
216
|
-
`Expected '>' after closing tag name`
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
type: ASTNodeType.Element,
|
|
221
|
-
tagName,
|
|
222
|
-
attributes,
|
|
223
|
-
children,
|
|
224
|
-
isSelfClosing: false,
|
|
225
|
-
loc: { start: startLoc, end: closeBracketToken.loc.end },
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private parseAttribute(): AttributeNode {
|
|
230
|
-
const nameToken = this.consume(TokenType.Identifier, 'Expected attribute name');
|
|
231
|
-
const startLoc = nameToken.loc.start;
|
|
232
|
-
const name = nameToken.value;
|
|
233
|
-
|
|
234
|
-
if (this.matchToken(TokenType.Equals)) {
|
|
235
|
-
const valueToken = this.peek();
|
|
236
|
-
|
|
237
|
-
if (valueToken.type === TokenType.StringLiteral) {
|
|
238
|
-
this.advance();
|
|
239
|
-
return {
|
|
240
|
-
type: ASTNodeType.Attribute,
|
|
241
|
-
name,
|
|
242
|
-
value: valueToken.value,
|
|
243
|
-
loc: { start: startLoc, end: valueToken.loc.end },
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (valueToken.type === TokenType.Interpolation) {
|
|
248
|
-
this.advance();
|
|
249
|
-
const interpNode: InterpolationNode = {
|
|
250
|
-
type: ASTNodeType.Interpolation,
|
|
251
|
-
expression: valueToken.value,
|
|
252
|
-
loc: valueToken.loc,
|
|
253
|
-
};
|
|
254
|
-
return {
|
|
255
|
-
type: ASTNodeType.Attribute,
|
|
256
|
-
name,
|
|
257
|
-
value: interpNode,
|
|
258
|
-
loc: { start: startLoc, end: valueToken.loc.end },
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
throw new DriftParserError(
|
|
263
|
-
`Expected string literal or interpolation after '=' for attribute '${name}'`,
|
|
264
|
-
valueToken.loc.start.line,
|
|
265
|
-
valueToken.loc.start.column,
|
|
266
|
-
valueToken.loc.start.offset
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
type: ASTNodeType.Attribute,
|
|
272
|
-
name,
|
|
273
|
-
value: null,
|
|
274
|
-
loc: { start: startLoc, end: nameToken.loc.end },
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private parseIfDirective(): IfNode {
|
|
279
|
-
const ifToken = this.consume(TokenType.DirectiveIf, 'Expected @if directive');
|
|
280
|
-
const startLoc = ifToken.loc.start;
|
|
281
|
-
const test = ifToken.value;
|
|
282
|
-
|
|
283
|
-
const consequent: TemplateChildNode[] = [];
|
|
284
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
285
|
-
consequent.push(this.parseChild());
|
|
286
|
-
}
|
|
287
|
-
const endBlockToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @if block');
|
|
288
|
-
|
|
289
|
-
let alternate: TemplateChildNode[] | IfNode | null = null;
|
|
290
|
-
let endLoc = endBlockToken.loc.end;
|
|
291
|
-
|
|
292
|
-
this.skipWhitespaceTokens();
|
|
293
|
-
|
|
294
|
-
if (this.check(TokenType.DirectiveElseIf)) {
|
|
295
|
-
alternate = this.parseElseIfChain();
|
|
296
|
-
endLoc = alternate.loc.end;
|
|
297
|
-
} else if (this.check(TokenType.DirectiveElse)) {
|
|
298
|
-
this.advance(); // consume @else
|
|
299
|
-
const elseBody: TemplateChildNode[] = [];
|
|
300
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
301
|
-
elseBody.push(this.parseChild());
|
|
302
|
-
}
|
|
303
|
-
const elseCloseToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @else block');
|
|
304
|
-
alternate = elseBody;
|
|
305
|
-
endLoc = elseCloseToken.loc.end;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
type: ASTNodeType.If,
|
|
310
|
-
test,
|
|
311
|
-
consequent,
|
|
312
|
-
alternate,
|
|
313
|
-
loc: { start: startLoc, end: endLoc },
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
private parseElseIfChain(): IfNode {
|
|
318
|
-
const elseIfToken = this.consume(TokenType.DirectiveElseIf, 'Expected @else if directive');
|
|
319
|
-
const startLoc = elseIfToken.loc.start;
|
|
320
|
-
const test = elseIfToken.value;
|
|
321
|
-
|
|
322
|
-
const consequent: TemplateChildNode[] = [];
|
|
323
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
324
|
-
consequent.push(this.parseChild());
|
|
325
|
-
}
|
|
326
|
-
const endBlockToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @else if block');
|
|
327
|
-
|
|
328
|
-
let alternate: TemplateChildNode[] | IfNode | null = null;
|
|
329
|
-
let endLoc = endBlockToken.loc.end;
|
|
330
|
-
|
|
331
|
-
this.skipWhitespaceTokens();
|
|
332
|
-
|
|
333
|
-
if (this.check(TokenType.DirectiveElseIf)) {
|
|
334
|
-
alternate = this.parseElseIfChain();
|
|
335
|
-
endLoc = alternate.loc.end;
|
|
336
|
-
} else if (this.check(TokenType.DirectiveElse)) {
|
|
337
|
-
this.advance(); // consume @else
|
|
338
|
-
const elseBody: TemplateChildNode[] = [];
|
|
339
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
340
|
-
elseBody.push(this.parseChild());
|
|
341
|
-
}
|
|
342
|
-
const elseCloseToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @else block');
|
|
343
|
-
alternate = elseBody;
|
|
344
|
-
endLoc = elseCloseToken.loc.end;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return {
|
|
348
|
-
type: ASTNodeType.If,
|
|
349
|
-
test,
|
|
350
|
-
consequent,
|
|
351
|
-
alternate,
|
|
352
|
-
loc: { start: startLoc, end: endLoc },
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
private parseForDirective(): ForNode {
|
|
357
|
-
const forToken = this.consume(TokenType.DirectiveFor, 'Expected @for directive');
|
|
358
|
-
const startLoc = forToken.loc.start;
|
|
359
|
-
const header = forToken.value; // e.g. "item in items" or "(item, index) in items"
|
|
360
|
-
|
|
361
|
-
const inIndex = (() => {
|
|
362
|
-
let parenDepth = 0;
|
|
363
|
-
for (let i = 0; i <= header.length - 4; i++) {
|
|
364
|
-
const ch = header[i];
|
|
365
|
-
if (ch === '(') parenDepth++;
|
|
366
|
-
else if (ch === ')') parenDepth--;
|
|
367
|
-
else if (parenDepth === 0 && header.slice(i, i + 4) === ' in ') {
|
|
368
|
-
return i;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return -1;
|
|
372
|
-
})();
|
|
373
|
-
if (inIndex === -1) {
|
|
374
|
-
throw new DriftParserError(
|
|
375
|
-
`Invalid @for header syntax '${header}'. Expected format: 'item in list' or '(item, index) in list'`,
|
|
376
|
-
forToken.loc.start.line,
|
|
377
|
-
forToken.loc.start.column,
|
|
378
|
-
forToken.loc.start.offset
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const lhs = header.slice(0, inIndex).trim();
|
|
383
|
-
let rawIterable = header.slice(inIndex + 4).trim();
|
|
384
|
-
let key: string | null = null;
|
|
385
|
-
|
|
386
|
-
const keyMatch = rawIterable.match(/\s+key\s+(.+)$/);
|
|
387
|
-
if (keyMatch) {
|
|
388
|
-
key = keyMatch[1]!.trim();
|
|
389
|
-
rawIterable = rawIterable.slice(0, keyMatch.index).trim();
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const iterable = rawIterable;
|
|
393
|
-
|
|
394
|
-
let item = lhs;
|
|
395
|
-
let index: string | null = null;
|
|
396
|
-
|
|
397
|
-
if (lhs.startsWith('(') && lhs.endsWith(')')) {
|
|
398
|
-
const parts = lhs.slice(1, -1).split(',').map(s => s.trim());
|
|
399
|
-
item = parts[0] || '';
|
|
400
|
-
index = parts[1] || null;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const body: TemplateChildNode[] = [];
|
|
404
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
405
|
-
body.push(this.parseChild());
|
|
406
|
-
}
|
|
407
|
-
const endBlockToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @for block');
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
type: ASTNodeType.For,
|
|
411
|
-
item,
|
|
412
|
-
index,
|
|
413
|
-
iterable,
|
|
414
|
-
key,
|
|
415
|
-
body,
|
|
416
|
-
loc: { start: startLoc, end: endBlockToken.loc.end },
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private parseSwitchDirective(): SwitchNode {
|
|
421
|
-
const switchToken = this.consume(TokenType.DirectiveSwitch, 'Expected @switch directive');
|
|
422
|
-
const startLoc = switchToken.loc.start;
|
|
423
|
-
const discriminant = switchToken.value;
|
|
424
|
-
const cases: CaseBranch[] = [];
|
|
425
|
-
|
|
426
|
-
this.skipWhitespaceTokens();
|
|
427
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
428
|
-
if (this.check(TokenType.DirectiveCase)) {
|
|
429
|
-
const caseToken = this.advance();
|
|
430
|
-
const caseBody: TemplateChildNode[] = [];
|
|
431
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
432
|
-
caseBody.push(this.parseChild());
|
|
433
|
-
}
|
|
434
|
-
const closeToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @case block');
|
|
435
|
-
cases.push({
|
|
436
|
-
expression: caseToken.value,
|
|
437
|
-
body: caseBody,
|
|
438
|
-
loc: { start: caseToken.loc.start, end: closeToken.loc.end },
|
|
439
|
-
});
|
|
440
|
-
} else if (this.check(TokenType.DirectiveDefault)) {
|
|
441
|
-
const defaultToken = this.advance();
|
|
442
|
-
const defaultBody: TemplateChildNode[] = [];
|
|
443
|
-
while (!this.check(TokenType.BlockClose) && !this.isAtEnd()) {
|
|
444
|
-
defaultBody.push(this.parseChild());
|
|
445
|
-
}
|
|
446
|
-
const closeToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @default block');
|
|
447
|
-
cases.push({
|
|
448
|
-
expression: null,
|
|
449
|
-
body: defaultBody,
|
|
450
|
-
loc: { start: defaultToken.loc.start, end: closeToken.loc.end },
|
|
451
|
-
});
|
|
452
|
-
} else {
|
|
453
|
-
throw new DriftParserError(
|
|
454
|
-
`Unexpected token '${this.peek().value}' inside @switch block. Expected @case or @default.`,
|
|
455
|
-
this.peek().loc.start.line,
|
|
456
|
-
this.peek().loc.start.column,
|
|
457
|
-
this.peek().loc.start.offset
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
this.skipWhitespaceTokens();
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const endBlockToken = this.consume(TokenType.BlockClose, 'Expected closing brace } for @switch block');
|
|
464
|
-
|
|
465
|
-
return {
|
|
466
|
-
type: ASTNodeType.Switch,
|
|
467
|
-
discriminant,
|
|
468
|
-
cases,
|
|
469
|
-
loc: { start: startLoc, end: endBlockToken.loc.end },
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
private skipWhitespaceTokens(): void {
|
|
474
|
-
while (this.check(TokenType.Text) && this.peek().value.trim().length === 0) {
|
|
475
|
-
this.advance();
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
private isAtEnd(): boolean {
|
|
480
|
-
return this.peek().type === TokenType.EOF;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
private peek(relativeOffset = 0): Token {
|
|
484
|
-
this.ensureLookahead(relativeOffset);
|
|
485
|
-
return this.lookahead[Math.min(relativeOffset, this.lookahead.length - 1)]!;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
private advance(): Token {
|
|
489
|
-
const token = this.peek();
|
|
490
|
-
if (token.type !== TokenType.EOF) {
|
|
491
|
-
this.lookahead.shift();
|
|
492
|
-
}
|
|
493
|
-
return token;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
private check(type: TokenType): boolean {
|
|
497
|
-
return this.peek().type === type;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
private matchToken(type: TokenType): boolean {
|
|
501
|
-
if (!this.check(type)) {
|
|
502
|
-
return false;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
this.advance();
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
private consume(type: TokenType, errorMessage: string): Token {
|
|
510
|
-
if (this.check(type)) {
|
|
511
|
-
return this.advance();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const token = this.peek();
|
|
515
|
-
throw new DriftParserError(
|
|
516
|
-
errorMessage,
|
|
517
|
-
token.loc.start.line,
|
|
518
|
-
token.loc.start.column,
|
|
519
|
-
token.loc.start.offset
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
private ensureLookahead(relativeOffset: number): void {
|
|
524
|
-
while (this.lookahead.length <= relativeOffset) {
|
|
525
|
-
const nextToken = this.tokenSource.nextToken();
|
|
526
|
-
this.lookahead.push(nextToken);
|
|
527
|
-
|
|
528
|
-
if (nextToken.type === TokenType.EOF) {
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|