pure-dango 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/package.json +45 -0
- package/src/core/compiler.ts +1269 -0
- package/src/core/interpreter.ts +2042 -0
- package/src/core/parser/handlers.ts +1091 -0
- package/src/core/parser/helpers.ts +157 -0
- package/src/core/parser/main.ts +620 -0
- package/src/core/tokenizer/funnies.ts +43 -0
- package/src/core/tokenizer/tokenizer.ts +334 -0
- package/src/core/utils.ts +534 -0
- package/src/index.ts +190 -0
- package/src/runtime/errors.ts +239 -0
- package/src/runtime/globals.ts +11 -0
- package/src/runtime/libs/ai.pds +56 -0
- package/src/runtime/libs/errors.pds +74 -0
- package/src/runtime/libs/gambling.pds +93 -0
- package/src/runtime/libs/io.pds +371 -0
- package/src/runtime/libs/math.pds +86 -0
- package/src/runtime/libs/std.pds +2 -0
- package/src/runtime/libs/types.pds +1232 -0
- package/src/runtime/stdlib.ts +1483 -0
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import {parseErrors} from "../../runtime/errors";
|
|
3
|
+
import {peek, next, parseExpression, parseStatement} from "./main";
|
|
4
|
+
import {errorTemplate} from "../../runtime/stdlib";
|
|
5
|
+
|
|
6
|
+
export function parseInstantationExpression(token : BaseToken, tokens : Tokens, state : State) : ParserToken
|
|
7
|
+
{
|
|
8
|
+
const row = token?.row ?? state.lastToken?.row ?? 0;
|
|
9
|
+
const column = token?.column ?? state.lastToken?.column ?? 0;
|
|
10
|
+
|
|
11
|
+
const classToken = peek(tokens, state);
|
|
12
|
+
if (!classToken || classToken.type !== "Identifier")
|
|
13
|
+
throw new Error(`Expected class name after "inst" at line ${row}:${column}`);
|
|
14
|
+
|
|
15
|
+
const className : string = next(tokens, state)!.value;
|
|
16
|
+
|
|
17
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "(")
|
|
18
|
+
throw new parseErrors.MissingTokenError("(", row, column);
|
|
19
|
+
next(tokens, state); // eat (
|
|
20
|
+
|
|
21
|
+
// collect arguments
|
|
22
|
+
const args : ParserToken[] = [];
|
|
23
|
+
while (peek(tokens, state) && peek(tokens, state).value !== ")")
|
|
24
|
+
{
|
|
25
|
+
const argument = parseExpression(tokens, 0, state, true);
|
|
26
|
+
if (argument)
|
|
27
|
+
args.push(argument);
|
|
28
|
+
|
|
29
|
+
if (peek(tokens, state)?.value === ",")
|
|
30
|
+
next(tokens, state);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
34
|
+
throw new parseErrors.MissingTokenError(")", row, column);
|
|
35
|
+
next(tokens, state); // eat )
|
|
36
|
+
|
|
37
|
+
const node : ParserToken =
|
|
38
|
+
{
|
|
39
|
+
type: "ClassInstantiation",
|
|
40
|
+
className,
|
|
41
|
+
args,
|
|
42
|
+
row,
|
|
43
|
+
column
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return node;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function variableHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
50
|
+
{
|
|
51
|
+
if (!token || token.type !== "Keyword" || (token.value !== "new" && token.value !== "const"))
|
|
52
|
+
return false;
|
|
53
|
+
|
|
54
|
+
const {row, column} = next(tokens, state)!;
|
|
55
|
+
|
|
56
|
+
const isConstant = token.value === "const";
|
|
57
|
+
|
|
58
|
+
const gatherNames = function(names : Array<string>, typeAnnotations : Array<string | null>, breakOnNoComma? : boolean) : boolean
|
|
59
|
+
{
|
|
60
|
+
const nextToken = peek(tokens, state);
|
|
61
|
+
if (!nextToken || nextToken.type !== "Identifier")
|
|
62
|
+
errorTemplate("variableHandler", `expected a variable after keyword "${token.value}" at line ${row}:${column}`);
|
|
63
|
+
|
|
64
|
+
names.push(next(tokens, state)!.value);
|
|
65
|
+
|
|
66
|
+
if (peek(tokens, state)?.value === ":" && tokens[state.position + 1]?.type === "Identifier")
|
|
67
|
+
{
|
|
68
|
+
next(tokens, state); // eat :
|
|
69
|
+
typeAnnotations.push(next(tokens, state)!.value);
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
typeAnnotations.push(null);
|
|
73
|
+
|
|
74
|
+
if (peek(tokens, state)?.value === ",")
|
|
75
|
+
next(tokens, state);
|
|
76
|
+
else if (breakOnNoComma)
|
|
77
|
+
return true;
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// array destructuring like uhh new [a, b] = expression()
|
|
83
|
+
if (peek(tokens, state)?.value === "[")
|
|
84
|
+
{
|
|
85
|
+
next(tokens, state); // eat [
|
|
86
|
+
|
|
87
|
+
const names : string[] = [];
|
|
88
|
+
const typeAnnotations0 : Array<string | null> = [];
|
|
89
|
+
while (peek(tokens, state) && peek(tokens, state).value !== "]")
|
|
90
|
+
gatherNames(names, typeAnnotations0);
|
|
91
|
+
|
|
92
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "]")
|
|
93
|
+
errorTemplate("variableHandler", `expected "]" to close array destructure at line ${row}:${column}`);
|
|
94
|
+
next(tokens, state); // eat ]
|
|
95
|
+
|
|
96
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "=")
|
|
97
|
+
errorTemplate("variableHandler", `inner array destructure must be followed by "=" at line ${row}:${column}`);
|
|
98
|
+
next(tokens, state); // eat =
|
|
99
|
+
|
|
100
|
+
const value = parseExpression(tokens, 0, state, true);
|
|
101
|
+
ast.body.push({type : "ArrayDestructure", names, value, row, column, constant : isConstant});
|
|
102
|
+
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// object destructuring like new {a, b} = expr() or new {[a, b] = expr()}
|
|
107
|
+
if (peek(tokens, state)?.value === "{")
|
|
108
|
+
{
|
|
109
|
+
next(tokens, state); // eat {
|
|
110
|
+
|
|
111
|
+
// detect inner array destructure shorthand: {[a, b] = expr()}
|
|
112
|
+
if (peek(tokens, state)?.value === "[")
|
|
113
|
+
{
|
|
114
|
+
next(tokens, state); // eat [
|
|
115
|
+
|
|
116
|
+
const names : string[] = [];
|
|
117
|
+
const typeAnnotations1 : Array<string | null> = [];
|
|
118
|
+
while (peek(tokens, state) && peek(tokens, state).value !== "]")
|
|
119
|
+
gatherNames(names, typeAnnotations1);
|
|
120
|
+
|
|
121
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "]")
|
|
122
|
+
errorTemplate("variableHandler", `expected "]" to close inner array destructure at line ${row}:${column}`);
|
|
123
|
+
next(tokens, state); // eat ]
|
|
124
|
+
|
|
125
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "=")
|
|
126
|
+
errorTemplate("variableHandler", `inner array destructure must be followed by "=" at line ${row}:${column}`);
|
|
127
|
+
next(tokens, state); // eat =
|
|
128
|
+
|
|
129
|
+
const value = parseExpression(tokens, 0, state);
|
|
130
|
+
|
|
131
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "}")
|
|
132
|
+
errorTemplate("variableHandler", `expected "}" to close object destructure at line ${row}:${column}`);
|
|
133
|
+
next(tokens, state); // eat }
|
|
134
|
+
|
|
135
|
+
ast.body.push({type : "ObjectDestructure", names, value, row, column, constant : isConstant});
|
|
136
|
+
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// normal object destructure: new {a, b} = expr()
|
|
141
|
+
const names : string[] = [];
|
|
142
|
+
const typeAnnotations2 : Array<string | null> = [];
|
|
143
|
+
while (peek(tokens, state) && peek(tokens, state)?.value !== "}")
|
|
144
|
+
gatherNames(names, typeAnnotations2);
|
|
145
|
+
|
|
146
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "}")
|
|
147
|
+
errorTemplate("variableHandler", `expected "}" to close object destructure at line ${row}:${column}`);
|
|
148
|
+
next(tokens, state); // eat }
|
|
149
|
+
|
|
150
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "=")
|
|
151
|
+
errorTemplate("variableHandler", `object destructure must be initialized with "=" at line ${row}:${column}`);
|
|
152
|
+
next(tokens, state); // eat =
|
|
153
|
+
|
|
154
|
+
const value = parseExpression(tokens, 0, state, true);
|
|
155
|
+
ast.body.push({type : "ObjectDestructure", names, value, row, column, constant : isConstant});
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// collect all names
|
|
161
|
+
const names : string[] = [];
|
|
162
|
+
const typeAnnotations : Array<string | null> = [];
|
|
163
|
+
do
|
|
164
|
+
{
|
|
165
|
+
if (gatherNames(names, typeAnnotations, true))
|
|
166
|
+
break;
|
|
167
|
+
} while (true);
|
|
168
|
+
|
|
169
|
+
// collect values if = is here
|
|
170
|
+
const values : (ParserToken | null)[] = [];
|
|
171
|
+
if (peek(tokens, state)?.value === "=")
|
|
172
|
+
{
|
|
173
|
+
next(tokens, state); // eat =
|
|
174
|
+
do
|
|
175
|
+
{
|
|
176
|
+
const value = parseExpression(tokens, 0, state, true)!;
|
|
177
|
+
values.push(value);
|
|
178
|
+
|
|
179
|
+
if (peek(tokens, state)?.value === ",")
|
|
180
|
+
next(tokens, state);
|
|
181
|
+
else
|
|
182
|
+
break;
|
|
183
|
+
} while (true);
|
|
184
|
+
}
|
|
185
|
+
else if (isConstant)
|
|
186
|
+
errorTemplate("variableHandler", `const must be initialized at line ${row}:${column}`);
|
|
187
|
+
|
|
188
|
+
// create nodes
|
|
189
|
+
for (let i = 0; i < names.length; i++)
|
|
190
|
+
{
|
|
191
|
+
const name = names[i];
|
|
192
|
+
const value = values[i] ?? null;
|
|
193
|
+
const type = value ? "NewAssignment" : "NewDeclaration";
|
|
194
|
+
ast.body.push({type, name, value, row, column, constant : isConstant});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function parseBlock(tokens : Tokens, state : State) : ParserToken[]
|
|
201
|
+
{
|
|
202
|
+
const openingBracket = peek(tokens, state);
|
|
203
|
+
if (!openingBracket || openingBracket.value !== "{")
|
|
204
|
+
throw new parseErrors.MissingTokenError("{", openingBracket?.row ?? 0, openingBracket?.column ?? 0);
|
|
205
|
+
next(tokens, state); // remove {
|
|
206
|
+
|
|
207
|
+
let block : ParserToken[] = [];
|
|
208
|
+
|
|
209
|
+
// parse body
|
|
210
|
+
while (peek(tokens, state) && peek(tokens, state).value !== "}")
|
|
211
|
+
{
|
|
212
|
+
const temporaryAST : AST = {type: "Program", body : []};
|
|
213
|
+
parseStatement(temporaryAST, tokens, state);
|
|
214
|
+
block.push(...temporaryAST.body);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const closingBracket = peek(tokens, state);
|
|
218
|
+
if (!closingBracket || closingBracket.value !== "}")
|
|
219
|
+
throw new parseErrors.MissingTokenError("}", closingBracket?.row ?? 0, closingBracket?.column ?? 0);
|
|
220
|
+
next(tokens, state); // remove "}"
|
|
221
|
+
|
|
222
|
+
return block;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function parseNextToken(tokens : Tokens, state : State) : ParserToken[]
|
|
226
|
+
{
|
|
227
|
+
const temporaryAst : AST =
|
|
228
|
+
{
|
|
229
|
+
type: "Program",
|
|
230
|
+
body: []
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
parseStatement(temporaryAst, tokens, state);
|
|
234
|
+
return temporaryAst.body;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getCondition(keyword : string, token : BaseToken, tokens : Tokens, state : State) : ParserToken | false
|
|
238
|
+
{
|
|
239
|
+
if (!token || token.type !== "Keyword" || token.value !== keyword)
|
|
240
|
+
return false;
|
|
241
|
+
|
|
242
|
+
const {row, column} = next(tokens, state)!; // delete the keyword
|
|
243
|
+
|
|
244
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "(")
|
|
245
|
+
throw new parseErrors.MissingTokenError("(", row, column);
|
|
246
|
+
next(tokens, state); // remove (
|
|
247
|
+
|
|
248
|
+
const condition = parseExpression(tokens, 0, state)!;
|
|
249
|
+
if (!condition)
|
|
250
|
+
throw new Error(`Expected condition after keyword "${keyword}" at line ${token.row}:${token.column}`);
|
|
251
|
+
|
|
252
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
253
|
+
throw new parseErrors.MissingTokenError(")", condition.row, condition.column);
|
|
254
|
+
next(tokens, state); // remove )
|
|
255
|
+
|
|
256
|
+
return condition;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function loopControlHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
260
|
+
{
|
|
261
|
+
if (!token ||
|
|
262
|
+
token.type !== "Keyword" ||
|
|
263
|
+
(
|
|
264
|
+
token.value !== "continue" &&
|
|
265
|
+
token.value !== "break"
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
return false;
|
|
269
|
+
next(tokens, state); // delete the keyword
|
|
270
|
+
|
|
271
|
+
const node : ParserToken =
|
|
272
|
+
{
|
|
273
|
+
type : token.value === "continue" ? "ContinueStatement": "BreakStatement",
|
|
274
|
+
row : token.row,
|
|
275
|
+
column : token.column
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
ast.body.push(node);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function ifHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
283
|
+
{
|
|
284
|
+
const condition : ParserToken | false = getCondition("if", token, tokens, state);
|
|
285
|
+
if (!condition)
|
|
286
|
+
return false;
|
|
287
|
+
|
|
288
|
+
let thenBlock : ParserToken[];
|
|
289
|
+
const nextToken = peek(tokens, state);
|
|
290
|
+
if (nextToken && nextToken.value === "{")
|
|
291
|
+
thenBlock = parseBlock(tokens, state);
|
|
292
|
+
else
|
|
293
|
+
thenBlock = parseNextToken(tokens, state);
|
|
294
|
+
|
|
295
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
296
|
+
next(tokens, state);
|
|
297
|
+
|
|
298
|
+
// if followed by "else if", parse using a temporary AST
|
|
299
|
+
let elseBlock : ParserToken[] | null = null;
|
|
300
|
+
const maybeElse = peek(tokens, state);
|
|
301
|
+
if (maybeElse && maybeElse.type === "Keyword" && maybeElse.value === "else")
|
|
302
|
+
{
|
|
303
|
+
next(tokens, state); // eat the else
|
|
304
|
+
const elseToken = peek(tokens, state);
|
|
305
|
+
|
|
306
|
+
if (elseToken && elseToken.type === "Keyword" && elseToken.value === "if")
|
|
307
|
+
{
|
|
308
|
+
const elseIfAST : AST =
|
|
309
|
+
{
|
|
310
|
+
type: "Program",
|
|
311
|
+
body: []
|
|
312
|
+
};
|
|
313
|
+
ifHandler(elseIfAST, elseToken, tokens, state);
|
|
314
|
+
elseBlock = [elseIfAST.body[0]]!;
|
|
315
|
+
}
|
|
316
|
+
else
|
|
317
|
+
{
|
|
318
|
+
const maybeOpeningBracket = peek(tokens, state);
|
|
319
|
+
|
|
320
|
+
if (maybeOpeningBracket && maybeOpeningBracket.value === "{")
|
|
321
|
+
elseBlock = parseBlock(tokens, state)!;
|
|
322
|
+
else
|
|
323
|
+
elseBlock = parseNextToken(tokens, state)!;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const node : ParserToken =
|
|
328
|
+
{
|
|
329
|
+
type : "IfStatement",
|
|
330
|
+
condition,
|
|
331
|
+
body : thenBlock,
|
|
332
|
+
else : elseBlock,
|
|
333
|
+
row : token.row,
|
|
334
|
+
column : token.column
|
|
335
|
+
}!;
|
|
336
|
+
|
|
337
|
+
ast.body.push(node);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function checkForSemicolon(tokens : Tokens, state : State, row : number, column : number) : void
|
|
342
|
+
{
|
|
343
|
+
const peekToken = peek(tokens, state);
|
|
344
|
+
if (!peekToken || peekToken.value !== ";")
|
|
345
|
+
throw new parseErrors.MissingTokenError(";", row, column);
|
|
346
|
+
next(tokens, state); // consume the semicolon
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function forHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
350
|
+
{
|
|
351
|
+
if (!token || token.type !== "Keyword" || token.value !== "for")
|
|
352
|
+
return false;
|
|
353
|
+
|
|
354
|
+
const {row, column} = next(tokens, state)!; // delete for
|
|
355
|
+
|
|
356
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "(")
|
|
357
|
+
throw new parseErrors.MissingTokenError("(", row, column);
|
|
358
|
+
|
|
359
|
+
const savedPosition = state.position;
|
|
360
|
+
next(tokens, state);
|
|
361
|
+
|
|
362
|
+
let variableToken = peek(tokens, state);
|
|
363
|
+
let isDeclaration = false;
|
|
364
|
+
let variableName : string | null = null;
|
|
365
|
+
|
|
366
|
+
if (variableToken?.value === "new")
|
|
367
|
+
{
|
|
368
|
+
isDeclaration = true;
|
|
369
|
+
next(tokens, state);
|
|
370
|
+
variableToken = peek(tokens, state);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (variableToken?.value === "[")
|
|
374
|
+
{
|
|
375
|
+
next(tokens, state); // consume [
|
|
376
|
+
const names: string[] = [];
|
|
377
|
+
while (peek(tokens, state)?.value !== "]")
|
|
378
|
+
{
|
|
379
|
+
const nextToken = next(tokens, state)!;
|
|
380
|
+
if (nextToken.type !== "Identifier")
|
|
381
|
+
errorTemplate("forHandler", `expected identifier in destructure pattern, got "${nextToken.value}" at line ${row}:${column}`);
|
|
382
|
+
names.push(nextToken.value);
|
|
383
|
+
|
|
384
|
+
if (peek(tokens, state)?.value === ",")
|
|
385
|
+
next(tokens, state);
|
|
386
|
+
}
|
|
387
|
+
next(tokens, state); // consume ]
|
|
388
|
+
|
|
389
|
+
const nextToken = peek(tokens, state);
|
|
390
|
+
if (nextToken?.type === "Keyword" && nextToken.value === "of")
|
|
391
|
+
{
|
|
392
|
+
next(tokens, state); // consume of
|
|
393
|
+
const iterable = parseExpression(tokens, 0, state)!;
|
|
394
|
+
|
|
395
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
396
|
+
throw new parseErrors.MissingTokenError(")", row, column);
|
|
397
|
+
next(tokens, state);
|
|
398
|
+
|
|
399
|
+
let body: ParserToken[];
|
|
400
|
+
const openingBracket = peek(tokens, state);
|
|
401
|
+
if (openingBracket?.value === "{")
|
|
402
|
+
body = parseBlock(tokens, state);
|
|
403
|
+
else
|
|
404
|
+
body = parseNextToken(tokens, state);
|
|
405
|
+
|
|
406
|
+
ast.body.push(
|
|
407
|
+
{
|
|
408
|
+
type : "ForOfStatement",
|
|
409
|
+
left : names,
|
|
410
|
+
right : iterable,
|
|
411
|
+
body,
|
|
412
|
+
isDeclaration : true,
|
|
413
|
+
row,
|
|
414
|
+
column
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (variableToken?.type === "Identifier")
|
|
422
|
+
{
|
|
423
|
+
variableName = variableToken.value;
|
|
424
|
+
next(tokens, state); // consume the identifier
|
|
425
|
+
|
|
426
|
+
const nextToken = peek(tokens, state);
|
|
427
|
+
|
|
428
|
+
// check for "in" or "of" keywords
|
|
429
|
+
if (nextToken?.type === "Keyword" && (nextToken.value === "in" || nextToken.value === "of"))
|
|
430
|
+
{
|
|
431
|
+
const loopType = nextToken.value;
|
|
432
|
+
next(tokens, state); // consume in or of
|
|
433
|
+
|
|
434
|
+
const iterable = parseExpression(tokens, 0, state)!;
|
|
435
|
+
|
|
436
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
437
|
+
throw new parseErrors.MissingTokenError(")", row, column);
|
|
438
|
+
next(tokens, state);
|
|
439
|
+
|
|
440
|
+
// parse the body
|
|
441
|
+
let body : ParserToken[];
|
|
442
|
+
const openingBracket = peek(tokens, state);
|
|
443
|
+
if (openingBracket && openingBracket.value === "{")
|
|
444
|
+
body = parseBlock(tokens, state);
|
|
445
|
+
else
|
|
446
|
+
body = parseNextToken(tokens, state);
|
|
447
|
+
|
|
448
|
+
const node : ParserToken =
|
|
449
|
+
{
|
|
450
|
+
type : loopType === "in" ? "ForInStatement" : "ForOfStatement",
|
|
451
|
+
left : variableName,
|
|
452
|
+
right : iterable,
|
|
453
|
+
body,
|
|
454
|
+
isDeclaration,
|
|
455
|
+
row,
|
|
456
|
+
column
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
ast.body.push(node);
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
state.position = savedPosition;
|
|
465
|
+
next(tokens, state); // re-consume the (
|
|
466
|
+
|
|
467
|
+
// parse initializer as a temporary AST
|
|
468
|
+
const temporaryAST : AST =
|
|
469
|
+
{
|
|
470
|
+
type: "Program",
|
|
471
|
+
body: []
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
if (peek(tokens, state)?.value !== ";")
|
|
475
|
+
parseStatement(temporaryAST, tokens, state);
|
|
476
|
+
|
|
477
|
+
const initial : ParserToken = temporaryAST.body[0] ?? null;
|
|
478
|
+
checkForSemicolon(tokens, state, row, column);
|
|
479
|
+
|
|
480
|
+
const condition = parseExpression(tokens, 0, state)!;
|
|
481
|
+
checkForSemicolon(tokens, state, row, column);
|
|
482
|
+
|
|
483
|
+
let update = undefined;
|
|
484
|
+
if (peek(tokens, state)?.value !== ")")
|
|
485
|
+
update = parseExpression(tokens, 0, state)!;
|
|
486
|
+
|
|
487
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
488
|
+
throw new parseErrors.MissingTokenError(")", row, column);
|
|
489
|
+
next(tokens, state); // remove )
|
|
490
|
+
|
|
491
|
+
let thenBlock : ParserToken[];
|
|
492
|
+
const openingBracket = peek(tokens, state);
|
|
493
|
+
if (openingBracket && openingBracket.value === "{")
|
|
494
|
+
thenBlock = parseBlock(tokens, state);
|
|
495
|
+
else
|
|
496
|
+
thenBlock = parseNextToken(tokens, state);
|
|
497
|
+
|
|
498
|
+
const node : ParserToken =
|
|
499
|
+
{
|
|
500
|
+
type: "ForStatement",
|
|
501
|
+
initial,
|
|
502
|
+
condition,
|
|
503
|
+
update,
|
|
504
|
+
body: thenBlock,
|
|
505
|
+
row: token.row,
|
|
506
|
+
column: token.column
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
ast.body.push(node);
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function doWhileHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
514
|
+
{
|
|
515
|
+
if (!token || token.type !== "Keyword" || token.value !== "do")
|
|
516
|
+
return false;
|
|
517
|
+
|
|
518
|
+
const {row, column} = next(tokens, state)!; // eat do
|
|
519
|
+
|
|
520
|
+
// parse the body block
|
|
521
|
+
const openingBracket = peek(tokens, state);
|
|
522
|
+
if (!openingBracket || openingBracket.value !== "{")
|
|
523
|
+
throw new parseErrors.MissingTokenError("{", row, column);
|
|
524
|
+
|
|
525
|
+
const body : ParserToken[] = parseBlock(tokens, state);
|
|
526
|
+
|
|
527
|
+
// skip separators between } and while
|
|
528
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
529
|
+
next(tokens, state);
|
|
530
|
+
|
|
531
|
+
// expect while keyword
|
|
532
|
+
const whileToken = peek(tokens, state);
|
|
533
|
+
if (!whileToken || whileToken.type !== "Keyword" || whileToken.value !== "while")
|
|
534
|
+
throw new parseErrors.MissingTokenError("while", row, column);
|
|
535
|
+
next(tokens, state); // eat while
|
|
536
|
+
|
|
537
|
+
// expect (condition)
|
|
538
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "(")
|
|
539
|
+
throw new parseErrors.MissingTokenError("(", row, column);
|
|
540
|
+
next(tokens, state); // eat (
|
|
541
|
+
|
|
542
|
+
const condition = parseExpression(tokens, 0, state)!;
|
|
543
|
+
if (!condition)
|
|
544
|
+
throw new Error(`Expected condition after "do...while" at line ${row}:${column}`);
|
|
545
|
+
|
|
546
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
547
|
+
throw new parseErrors.MissingTokenError(")", condition.row, condition.column);
|
|
548
|
+
next(tokens, state); // eat )
|
|
549
|
+
|
|
550
|
+
const node : ParserToken =
|
|
551
|
+
{
|
|
552
|
+
type : "DoWhileStatement",
|
|
553
|
+
body,
|
|
554
|
+
condition,
|
|
555
|
+
row,
|
|
556
|
+
column
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
ast.body.push(node);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
export function whileHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
565
|
+
{
|
|
566
|
+
const condition : ParserToken | false = getCondition("while", token, tokens, state);
|
|
567
|
+
if (!condition)
|
|
568
|
+
return false;
|
|
569
|
+
|
|
570
|
+
let thenBlock : ParserToken[];
|
|
571
|
+
const openingBracket = peek(tokens, state);
|
|
572
|
+
if (openingBracket && openingBracket.value === "{")
|
|
573
|
+
thenBlock = parseBlock(tokens, state);
|
|
574
|
+
else
|
|
575
|
+
thenBlock = parseNextToken(tokens, state);
|
|
576
|
+
|
|
577
|
+
const node : ParserToken =
|
|
578
|
+
{
|
|
579
|
+
type : "WhileStatement",
|
|
580
|
+
body : thenBlock,
|
|
581
|
+
row : token.row,
|
|
582
|
+
column : token.column,
|
|
583
|
+
condition
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
ast.body.push(node);
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function parseFunctionNode(tokens : Tokens, state : State, alreadyConsumed : boolean = false) : ParserToken
|
|
591
|
+
{
|
|
592
|
+
let row : number, column : number;
|
|
593
|
+
if (alreadyConsumed)
|
|
594
|
+
{
|
|
595
|
+
const previousToken = tokens[state.position - 1];
|
|
596
|
+
row = previousToken.row;
|
|
597
|
+
column = previousToken.column;
|
|
598
|
+
}
|
|
599
|
+
else
|
|
600
|
+
({row, column} = next(tokens, state)!);
|
|
601
|
+
|
|
602
|
+
let name : string | null = null;
|
|
603
|
+
const nextToken = peek(tokens, state);
|
|
604
|
+
if (nextToken && nextToken.type === "Identifier")
|
|
605
|
+
name = next(tokens, state)!.value;
|
|
606
|
+
|
|
607
|
+
const openingParentheses = peek(tokens, state);
|
|
608
|
+
if (!openingParentheses || openingParentheses.value !== "(")
|
|
609
|
+
errorTemplate("parseFunctionNode", `expected "(" after function name "${name}", got "${openingParentheses}" at line ${row}:${column}`);
|
|
610
|
+
next(tokens, state); // eat the (
|
|
611
|
+
|
|
612
|
+
// get the parameter names
|
|
613
|
+
const parameters : Array<{name: string, default: ParserToken | null, rest: boolean, typeAnnotation: string | null}> = [];
|
|
614
|
+
while (peek(tokens, state) && peek(tokens, state).value !== ")")
|
|
615
|
+
{
|
|
616
|
+
const parameter = peek(tokens, state);
|
|
617
|
+
|
|
618
|
+
// handle SpreadElement (...name)
|
|
619
|
+
if (parameter?.value === "...")
|
|
620
|
+
{
|
|
621
|
+
next(tokens, state); // eat ...
|
|
622
|
+
const restParameter = peek(tokens, state);
|
|
623
|
+
if (!restParameter || restParameter.type !== "Identifier")
|
|
624
|
+
errorTemplate("parseFunctionNode", `expected parameter name after "...", got "${restParameter}" at line ${parameter.row}:${parameter.column}`);
|
|
625
|
+
|
|
626
|
+
const restName = next(tokens, state)!.value;
|
|
627
|
+
let restTypeAnnotation: string | null = null;
|
|
628
|
+
if (peek(tokens, state)?.value === ":" && tokens[state.position + 1]?.type === "Identifier")
|
|
629
|
+
{
|
|
630
|
+
next(tokens, state); // eat :
|
|
631
|
+
let baseType = next(tokens, state)!.value;
|
|
632
|
+
if (peek(tokens, state)?.value === "<")
|
|
633
|
+
{
|
|
634
|
+
next(tokens, state); // eat
|
|
635
|
+
const union: string[] = [];
|
|
636
|
+
while (peek(tokens, state)?.value !== ">")
|
|
637
|
+
{
|
|
638
|
+
union.push(next(tokens, state)!.value);
|
|
639
|
+
if (peek(tokens, state)?.value === ",") next(tokens, state);
|
|
640
|
+
}
|
|
641
|
+
next(tokens, state); // eat >
|
|
642
|
+
baseType = `${baseType}<${union.join(",")}>`;
|
|
643
|
+
}
|
|
644
|
+
restTypeAnnotation = baseType;
|
|
645
|
+
}
|
|
646
|
+
parameters.push({name: restName, default: null, rest: true, typeAnnotation : restTypeAnnotation});
|
|
647
|
+
|
|
648
|
+
break; // rest param must be last
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (!parameter)
|
|
652
|
+
errorTemplate("parseFunctionNode", `expected parameter in function name "${name}", got "${parameter}"`);
|
|
653
|
+
if (parameters.some(param => param.name === parameter.value))
|
|
654
|
+
errorTemplate("parseFunctionNode", `duplicate parameter "${parameter.value}" at function name "${name}" at line ${parameter.row}:${parameter.column}`)
|
|
655
|
+
if (parameter.value === ")")
|
|
656
|
+
errorTemplate("parseFunctionNode", `trailing comma in parameters of function name "${name}"`);
|
|
657
|
+
if (parameter.type !== "Identifier")
|
|
658
|
+
errorTemplate("parseFunctionNode", `expected parameter in function name "${name}" with type "Identifier", got "${parameter.value}" at line ${parameter.row}:${parameter.column}`);
|
|
659
|
+
|
|
660
|
+
const parameterName = next(tokens, state)!.value;
|
|
661
|
+
|
|
662
|
+
let parameterTypeAnnotation : string | null = null;
|
|
663
|
+
if (peek(tokens, state)?.value === ":" && tokens[state.position + 1]?.type === "Identifier")
|
|
664
|
+
{
|
|
665
|
+
next(tokens, state); // eat :
|
|
666
|
+
let baseType = next(tokens, state)!.value;
|
|
667
|
+
if (peek(tokens, state)?.value === "<")
|
|
668
|
+
{
|
|
669
|
+
next(tokens, state); // eat
|
|
670
|
+
const union: string[] = [];
|
|
671
|
+
while (peek(tokens, state)?.value !== ">")
|
|
672
|
+
{
|
|
673
|
+
union.push(next(tokens, state)!.value);
|
|
674
|
+
if (peek(tokens, state)?.value === ",") next(tokens, state);
|
|
675
|
+
}
|
|
676
|
+
next(tokens, state); // eat >
|
|
677
|
+
baseType = `${baseType}<${union.join(",")}>`;
|
|
678
|
+
}
|
|
679
|
+
parameterTypeAnnotation = baseType;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (peek(tokens, state)?.value === "=")
|
|
683
|
+
{
|
|
684
|
+
next(tokens, state); // eat =
|
|
685
|
+
const defaultValue = parseExpression(tokens, 0, state, true);
|
|
686
|
+
parameters.push({name: parameterName, default: defaultValue, rest: false, typeAnnotation: parameterTypeAnnotation});
|
|
687
|
+
}
|
|
688
|
+
else``
|
|
689
|
+
parameters.push({name: parameterName, default: null, rest: false, typeAnnotation: parameterTypeAnnotation});
|
|
690
|
+
|
|
691
|
+
const nextToken = peek(tokens, state);
|
|
692
|
+
if (nextToken && nextToken.value === ",")
|
|
693
|
+
next(tokens, state);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const closingParentheses = peek(tokens, state);
|
|
697
|
+
if (!closingParentheses || closingParentheses.value !== ")")
|
|
698
|
+
errorTemplate("parseFunctionNode", `expected ")" after parameters in function declaration "${name}", got "${closingParentheses}"`);
|
|
699
|
+
next(tokens, state); // eat )
|
|
700
|
+
|
|
701
|
+
let returnType : string | null = null;
|
|
702
|
+
if (peek(tokens, state)?.value === ":" && tokens[state.position + 1]?.type === "Identifier")
|
|
703
|
+
{
|
|
704
|
+
next(tokens, state); // eat :
|
|
705
|
+
returnType = next(tokens, state)!.value;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
let body : ParserToken[];
|
|
709
|
+
const openingBracket = peek(tokens, state);
|
|
710
|
+
if (openingBracket && openingBracket.value === "{")
|
|
711
|
+
body = parseBlock(tokens, state);
|
|
712
|
+
else
|
|
713
|
+
{
|
|
714
|
+
body = parseNextToken(tokens, state);
|
|
715
|
+
if (peek(tokens, state)?.value === ";")
|
|
716
|
+
next(tokens, state);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const node : ParserToken =
|
|
720
|
+
{
|
|
721
|
+
type: name ? "FunctionDeclaration" : "FunctionExpression",
|
|
722
|
+
name,
|
|
723
|
+
parameters,
|
|
724
|
+
body,
|
|
725
|
+
returnType,
|
|
726
|
+
row,
|
|
727
|
+
column
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return node;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function functionHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
734
|
+
{
|
|
735
|
+
if (!token || token.type !== "Keyword" || token.value !== "function")
|
|
736
|
+
return false;
|
|
737
|
+
|
|
738
|
+
const node = parseFunctionNode(tokens, state);
|
|
739
|
+
|
|
740
|
+
ast.body.push(node);
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export function returnHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
745
|
+
{
|
|
746
|
+
if (!token || token.type !== "Keyword" || token.value !== "return")
|
|
747
|
+
return false;
|
|
748
|
+
|
|
749
|
+
next(tokens, state); // nom nom return
|
|
750
|
+
|
|
751
|
+
const peekToken = peek(tokens, state);
|
|
752
|
+
|
|
753
|
+
let argument : ParserToken | null = null;
|
|
754
|
+
if (peekToken && peekToken.value !== ";")
|
|
755
|
+
argument = parseExpression(tokens, 0, state)!;
|
|
756
|
+
|
|
757
|
+
checkForSemicolon(tokens, state, token.row, token.column);
|
|
758
|
+
|
|
759
|
+
const node : ParserToken =
|
|
760
|
+
{
|
|
761
|
+
type: "ReturnStatement",
|
|
762
|
+
argument,
|
|
763
|
+
row: token.row,
|
|
764
|
+
column: token.column
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
ast.body.push(node);
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export function importHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
772
|
+
{
|
|
773
|
+
if (!token || token.type !== "Keyword" || token.value !== "import")
|
|
774
|
+
return false;
|
|
775
|
+
|
|
776
|
+
const
|
|
777
|
+
{
|
|
778
|
+
row,
|
|
779
|
+
column
|
|
780
|
+
} = next(tokens, state)!;
|
|
781
|
+
|
|
782
|
+
let importPath : string = "";
|
|
783
|
+
|
|
784
|
+
// "internal" redirects the import path to src/runtime/libs
|
|
785
|
+
const maybeInternal = peek(tokens, state);
|
|
786
|
+
if (maybeInternal && maybeInternal.type === "Keyword" && maybeInternal.value === "internal")
|
|
787
|
+
{
|
|
788
|
+
next(tokens, state); // eat internal
|
|
789
|
+
const root : string = process.pkg
|
|
790
|
+
? path.resolve(path.dirname(process.execPath), "..")
|
|
791
|
+
: path.resolve(path.dirname(process.argv[1]), ".."); // pure-dango
|
|
792
|
+
|
|
793
|
+
const src : string = path.resolve(root, "src"); // pure-dango/src
|
|
794
|
+
const runtime : string = path.resolve(src, "runtime"); // pure-dango/src/runtime
|
|
795
|
+
|
|
796
|
+
importPath = path.resolve(runtime, "libs"); // pure-dango/src/runtime/libs
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const pathToken = peek(tokens, state);
|
|
800
|
+
if (!pathToken || pathToken.type !== "StringLiteral")
|
|
801
|
+
errorTemplate("importHandler", `expected a file path with type String after keyword "import", got "${pathToken}" at line ${row}:${column}`);
|
|
802
|
+
|
|
803
|
+
const fileName = next(tokens, state)!.value;
|
|
804
|
+
|
|
805
|
+
const node : ParserToken =
|
|
806
|
+
{
|
|
807
|
+
type : "ImportStatement",
|
|
808
|
+
path : importPath ? path.join(importPath, fileName) : fileName,
|
|
809
|
+
row,
|
|
810
|
+
column
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
ast.body.push(node);
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
export function classHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
818
|
+
{
|
|
819
|
+
if (!token || token.type !== "Keyword" || token.value !== "class")
|
|
820
|
+
return false;
|
|
821
|
+
|
|
822
|
+
const {row, column} = next(tokens, state)!;
|
|
823
|
+
|
|
824
|
+
const nameToken = peek(tokens, state);
|
|
825
|
+
if (!nameToken || nameToken.type !== "Identifier")
|
|
826
|
+
errorTemplate("classHandler", `expected class name at line ${row}:${column}`);
|
|
827
|
+
const name = next(tokens, state)!.value;
|
|
828
|
+
|
|
829
|
+
// if the next token's value is "extends", the value of the token after extends must be an identifier
|
|
830
|
+
let superclass : string | null = null;
|
|
831
|
+
const maybeExtends = peek(tokens, state);
|
|
832
|
+
if (maybeExtends && maybeExtends.type === "Keyword" && maybeExtends.value === "extends")
|
|
833
|
+
{
|
|
834
|
+
next(tokens, state); // eat extends
|
|
835
|
+
const superToken = peek(tokens, state);
|
|
836
|
+
if (!superToken || superToken.type !== "Identifier")
|
|
837
|
+
errorTemplate("classHandler", `expected superclass after keyword "extends", got "${superToken}"`);
|
|
838
|
+
|
|
839
|
+
superclass = next(tokens, state)!.value;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "{")
|
|
843
|
+
throw new parseErrors.MissingTokenError("{", row, column);
|
|
844
|
+
next(tokens, state); // eat {
|
|
845
|
+
|
|
846
|
+
// parse the method until it hits a closing curly brace
|
|
847
|
+
const methods : ParserToken[] = [];
|
|
848
|
+
while (peek(tokens, state) && peek(tokens, state).value !== "}")
|
|
849
|
+
{
|
|
850
|
+
const methodToken = peek(tokens, state);
|
|
851
|
+
if (!methodToken || methodToken.type !== "Identifier")
|
|
852
|
+
throw new parseErrors.UnexpectedTokenError(methodToken?.value, methodToken?.row, methodToken?.column);
|
|
853
|
+
|
|
854
|
+
const methodName : string = next(tokens, state)!.value;
|
|
855
|
+
const methodNode : ParserToken = parseFunctionNode(tokens, state, true);
|
|
856
|
+
methodNode.name = methodName;
|
|
857
|
+
|
|
858
|
+
methods.push(methodNode);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "}")
|
|
862
|
+
throw new parseErrors.MissingTokenError("}", row, column);
|
|
863
|
+
next(tokens, state); // eat }
|
|
864
|
+
|
|
865
|
+
const node : ParserToken =
|
|
866
|
+
{
|
|
867
|
+
type: "ClassDeclaration",
|
|
868
|
+
name,
|
|
869
|
+
superclass,
|
|
870
|
+
methods,
|
|
871
|
+
row,
|
|
872
|
+
column
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
ast.body.push(node);
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export function tryHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
880
|
+
{
|
|
881
|
+
if (!token || token.type !== "Keyword" || token.value !== "try")
|
|
882
|
+
return false;
|
|
883
|
+
|
|
884
|
+
const {row, column} = next(tokens, state)!;
|
|
885
|
+
|
|
886
|
+
// parse try {...}
|
|
887
|
+
const openingBracket = peek(tokens, state);
|
|
888
|
+
if (!openingBracket || openingBracket.value !== "{")
|
|
889
|
+
throw new parseErrors.MissingTokenError("{", row, column);
|
|
890
|
+
|
|
891
|
+
const tryBlock = parseBlock(tokens, state);
|
|
892
|
+
|
|
893
|
+
// skip separators
|
|
894
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
895
|
+
next(tokens, state);
|
|
896
|
+
|
|
897
|
+
// parse catch
|
|
898
|
+
const maybeCatch = peek(tokens, state);
|
|
899
|
+
if (!maybeCatch || maybeCatch.type !== "Keyword" || maybeCatch.value !== "catch")
|
|
900
|
+
throw new parseErrors.MissingTokenError("catch", row, column);
|
|
901
|
+
|
|
902
|
+
next(tokens, state); // eat catch
|
|
903
|
+
|
|
904
|
+
// parse catch error variable name
|
|
905
|
+
let errorVariable : string | null = null;
|
|
906
|
+
if (peek(tokens, state)?.value === "(")
|
|
907
|
+
{
|
|
908
|
+
next(tokens, state); // eat (
|
|
909
|
+
|
|
910
|
+
const errorToken = peek(tokens, state);
|
|
911
|
+
if (!errorToken || errorToken.type !== "Identifier")
|
|
912
|
+
errorTemplate("tryHandler", `expected identifier for catch parameter at line ${row}:${column}`);
|
|
913
|
+
|
|
914
|
+
errorVariable = next(tokens, state)!.value;
|
|
915
|
+
|
|
916
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
917
|
+
throw new parseErrors.MissingTokenError(")", row, column);
|
|
918
|
+
|
|
919
|
+
next(tokens, state); // eat )
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// parse catch body
|
|
923
|
+
const catchOpeningBracket = peek(tokens, state);
|
|
924
|
+
if (!catchOpeningBracket || catchOpeningBracket.value !== "{")
|
|
925
|
+
throw new parseErrors.MissingTokenError("{", row, column);
|
|
926
|
+
|
|
927
|
+
const catchBlock = parseBlock(tokens, state);
|
|
928
|
+
|
|
929
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
930
|
+
next(tokens, state);
|
|
931
|
+
|
|
932
|
+
let finallyBlock : ParserToken[] | null = null;
|
|
933
|
+
const maybeFinally = peek(tokens, state);
|
|
934
|
+
if (maybeFinally && maybeFinally.type === "Keyword" && maybeFinally.value === "finally")
|
|
935
|
+
{
|
|
936
|
+
next(tokens, state); // eat finally
|
|
937
|
+
|
|
938
|
+
const finallyOpeningBracket = peek(tokens, state);
|
|
939
|
+
if (!finallyOpeningBracket || finallyOpeningBracket.value !== "{")
|
|
940
|
+
throw new parseErrors.MissingTokenError("{", row, column);
|
|
941
|
+
|
|
942
|
+
finallyBlock = parseBlock(tokens, state);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const node : ParserToken =
|
|
946
|
+
{
|
|
947
|
+
type: "TryStatement",
|
|
948
|
+
tryBlock,
|
|
949
|
+
catchBlock,
|
|
950
|
+
errorVariable : errorVariable ?? "error",
|
|
951
|
+
finallyBlock,
|
|
952
|
+
row,
|
|
953
|
+
column
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
ast.body.push(node);
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export function switchHandler(ast : AST, token : BaseToken, tokens : Tokens, state : State) : boolean
|
|
961
|
+
{
|
|
962
|
+
if (!token || token.type !== "Keyword" || token.value !== "switch")
|
|
963
|
+
return false;
|
|
964
|
+
|
|
965
|
+
const {row, column} = next(tokens, state)!;
|
|
966
|
+
|
|
967
|
+
// parse the expression to match
|
|
968
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "(")
|
|
969
|
+
throw new parseErrors.MissingTokenError("(", row, column);
|
|
970
|
+
next(tokens, state); // eat (
|
|
971
|
+
|
|
972
|
+
const discriminant = parseExpression(tokens, 0, state, true);
|
|
973
|
+
if (!discriminant)
|
|
974
|
+
errorTemplate("switchHandler", `Expected expression after "switch" at line ${row}:${column}`);
|
|
975
|
+
|
|
976
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ")")
|
|
977
|
+
throw new parseErrors.MissingTokenError(")", row, column);
|
|
978
|
+
next(tokens, state); // eat )
|
|
979
|
+
|
|
980
|
+
// parse the opening brace
|
|
981
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "{")
|
|
982
|
+
throw new parseErrors.MissingTokenError("{", row, column);
|
|
983
|
+
next(tokens, state); // eat {
|
|
984
|
+
|
|
985
|
+
// parse cases
|
|
986
|
+
const cases : any[] = [];
|
|
987
|
+
let defaultCase : any = null;
|
|
988
|
+
|
|
989
|
+
while (peek(tokens, state) && peek(tokens, state).value !== "}")
|
|
990
|
+
{
|
|
991
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
992
|
+
next(tokens, state);
|
|
993
|
+
|
|
994
|
+
const caseToken = peek(tokens, state);
|
|
995
|
+
if (!caseToken || caseToken.type !== "Keyword")
|
|
996
|
+
break;
|
|
997
|
+
|
|
998
|
+
if (caseToken.value === "case")
|
|
999
|
+
{
|
|
1000
|
+
next(tokens, state); // eat case
|
|
1001
|
+
const test = parseExpression(tokens, 0, state, true);
|
|
1002
|
+
if (!test)
|
|
1003
|
+
errorTemplate("switchHandler", `Expected expression after "case" at line ${caseToken.row}:${caseToken.column}`);
|
|
1004
|
+
|
|
1005
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ":")
|
|
1006
|
+
throw new parseErrors.MissingTokenError(":", caseToken.row, caseToken.column);
|
|
1007
|
+
next(tokens, state); // eat :
|
|
1008
|
+
|
|
1009
|
+
// parse consequent statements until next case, default, or }
|
|
1010
|
+
const consequent : ParserToken[] = [];
|
|
1011
|
+
while (peek(tokens, state))
|
|
1012
|
+
{
|
|
1013
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
1014
|
+
next(tokens, state);
|
|
1015
|
+
|
|
1016
|
+
const nextToken = peek(tokens, state);
|
|
1017
|
+
if (!nextToken || nextToken.value === "}" ||
|
|
1018
|
+
(nextToken.type === "Keyword" && (nextToken.value === "case" || nextToken.value === "default")))
|
|
1019
|
+
break;
|
|
1020
|
+
|
|
1021
|
+
const temporaryAST : AST = {type: "Program", body: []};
|
|
1022
|
+
parseStatement(temporaryAST, tokens, state);
|
|
1023
|
+
consequent.push(...temporaryAST.body);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
cases.push(
|
|
1027
|
+
{
|
|
1028
|
+
type: "SwitchCase",
|
|
1029
|
+
test,
|
|
1030
|
+
consequent,
|
|
1031
|
+
row: caseToken.row,
|
|
1032
|
+
column: caseToken.column
|
|
1033
|
+
}
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
else if (caseToken.value === "default")
|
|
1037
|
+
{
|
|
1038
|
+
next(tokens, state); // eat default
|
|
1039
|
+
|
|
1040
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== ":")
|
|
1041
|
+
throw new parseErrors.MissingTokenError(":", caseToken.row, caseToken.column);
|
|
1042
|
+
next(tokens, state); // eat :
|
|
1043
|
+
|
|
1044
|
+
// parse consequent statements until next case or }
|
|
1045
|
+
const consequent : ParserToken[] = [];
|
|
1046
|
+
while (peek(tokens, state))
|
|
1047
|
+
{
|
|
1048
|
+
while (peek(tokens, state)?.type === "Separator")
|
|
1049
|
+
next(tokens, state);
|
|
1050
|
+
|
|
1051
|
+
const nextToken = peek(tokens, state);
|
|
1052
|
+
if (!nextToken || nextToken.value === "}" ||
|
|
1053
|
+
(nextToken.type === "Keyword" && nextToken.value === "case"))
|
|
1054
|
+
break;
|
|
1055
|
+
|
|
1056
|
+
const temporaryAST : AST = {type: "Program", body: []};
|
|
1057
|
+
parseStatement(temporaryAST, tokens, state);
|
|
1058
|
+
consequent.push(...temporaryAST.body);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
defaultCase =
|
|
1062
|
+
{
|
|
1063
|
+
type: "SwitchCase",
|
|
1064
|
+
test: null,
|
|
1065
|
+
consequent,
|
|
1066
|
+
row: caseToken.row,
|
|
1067
|
+
column: caseToken.column
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
else
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// parse closing brace
|
|
1075
|
+
if (!peek(tokens, state) || peek(tokens, state).value !== "}")
|
|
1076
|
+
throw new parseErrors.MissingTokenError("}", row, column);
|
|
1077
|
+
next(tokens, state); // eat }
|
|
1078
|
+
|
|
1079
|
+
const node : ParserToken =
|
|
1080
|
+
{
|
|
1081
|
+
type: "SwitchStatement",
|
|
1082
|
+
discriminant,
|
|
1083
|
+
cases,
|
|
1084
|
+
defaultCase,
|
|
1085
|
+
row,
|
|
1086
|
+
column
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
ast.body.push(node);
|
|
1090
|
+
return true;
|
|
1091
|
+
}
|