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.
@@ -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
+ }