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,620 @@
1
+ // the parser for pure-dango
2
+ // handles syntax rules
3
+ // turns the tokens from tokenizer in to objects the compiler can read
4
+
5
+ import {parseErrors} from "../../runtime/errors";
6
+ import {errorTemplate} from "../../runtime/stdlib";
7
+ import * as handlers from "./handlers";
8
+ import * as helpers from "./helpers";
9
+
10
+ // the operators of pure-dango
11
+ export const OPERATORS : Operators =
12
+ {
13
+ // operators
14
+ "+": {prec: 10, assoc: "left", type: "binary"},
15
+ "-": {prec: 10, assoc: "left", type: "binary"},
16
+ "*": {prec: 20, assoc: "left", type: "binary"},
17
+ "/": {prec: 20, assoc: "left", type: "binary"},
18
+ "%": {prec: 20, assoc: "left", type: "binary"},
19
+
20
+ // assignment operators
21
+ "=": {prec: 5, assoc: "right", type: "assignment"},
22
+ "-=": {prec: 5, assoc: "right", type: "assignment"},
23
+ "+=": {prec: 5, assoc: "right", type: "assignment"},
24
+ "*=": {prec: 5, assoc: "right", type: "assignment"},
25
+ "/=": {prec: 5, assoc: "right", type: "assignment"},
26
+
27
+ // equality operators
28
+ "==": {prec: 7, assoc: "left", type: "binary"},
29
+ "!=": {prec: 7, assoc: "left", type: "binary"},
30
+ ">": {prec: 8, assoc: "left", type: "binary"},
31
+ "<": {prec: 8, assoc: "left", type: "binary"},
32
+ ">=": {prec: 8, assoc: "left", type: "binary"},
33
+ "<=": {prec: 8, assoc: "left", type: "binary"},
34
+
35
+ // logical operators
36
+ "&&": {prec: 3, assoc: "left", type: "logical"},
37
+ "||": {prec: 2, assoc: "left", type: "logical"},
38
+ "??": {prec: 1, assoc: "left", type: "logical"},
39
+
40
+ // unary operators
41
+ "!": {prec: 30, assoc: "right", type: "unary", fix: "prefix"},
42
+ "~": {prec: 30, assoc: "right", type: "unary", fix: "prefix"},
43
+ "+u": {prec: 30, assoc: "right", type: "unary", fix: "prefix"},
44
+ "-u": {prec: 30, assoc: "right", type: "unary", fix: "prefix"},
45
+ "++": {prec: 30, assoc: "right", type: "unary", fix: "both"},
46
+ "--": {prec: 30, assoc: "right", type: "unary", fix: "both"},
47
+
48
+ // bitwise operators
49
+ "&": {prec: 9, assoc: "left", type: "binary"},
50
+ "^": {prec: 8, assoc: "left", type: "binary"},
51
+ "|": {prec: 7, assoc: "left", type: "binary"},
52
+ "<<": {prec: 12, assoc: "left", type: "binary"},
53
+ ">>": {prec: 12, assoc: "left", type: "binary"},
54
+ ">>>": {prec: 12, assoc: "left", type: "binary"},
55
+ "&=": {prec: 5, assoc: "right", type: "assignment"},
56
+ "|=": {prec: 5, assoc: "right", type: "assignment"},
57
+ "^=": {prec: 5, assoc: "right", type: "assignment"},
58
+ "<<=": {prec: 5, assoc: "right", type: "assignment"},
59
+ ">>=": {prec: 5, assoc: "right", type: "assignment"},
60
+ ">>>=": {prec: 5, assoc: "right", type: "assignment"},
61
+ }
62
+
63
+ export function peek(tokens : Tokens, state : State) : BaseToken
64
+ {
65
+ return tokens[state.position];
66
+ }
67
+
68
+ export function next(tokens : Tokens, state : State) : BaseToken | null
69
+ {
70
+ if (state.position >= tokens.length)
71
+ return null;
72
+ const token : BaseToken = tokens[state.position++];
73
+ state.lastToken = token;
74
+ return token;
75
+ }
76
+
77
+ function isUnaryPrefix(operator : string) : boolean
78
+ {
79
+ return OPERATORS[operator]?.type === "unary" &&
80
+ OPERATORS[operator].fix !== "postfix";
81
+ }
82
+
83
+ function parseStartingToken(token : BaseToken, tokens : Tokens, state : State) : ParserToken | null
84
+ {
85
+ if (!token)
86
+ errorTemplate("parseStartingToken", `Unexpected end of input`);
87
+
88
+ let
89
+ {
90
+ type,
91
+ value,
92
+ row,
93
+ column
94
+ } = token;
95
+
96
+ if (type === "Keyword" && value === "function")
97
+ return handlers.parseFunctionNode(tokens, state, true);
98
+
99
+ if (type === "Keyword" && value === "inst")
100
+ return handlers.parseInstantationExpression(token, tokens, state);
101
+
102
+ if (type === "Operator" && (value === "+" || value === "-"))
103
+ {
104
+ const previousToken : BaseToken | null = state.lastToken;
105
+
106
+ const isUnaryContext : boolean =
107
+ !previousToken ||
108
+ previousToken.type === "Keyword" ||
109
+ previousToken.value === "(" ||
110
+ previousToken.value === "," ||
111
+ (
112
+ previousToken.type === "Operator" &&
113
+ previousToken.value !== ")" &&
114
+ previousToken.value !== "++" &&
115
+ previousToken.value !== "--"
116
+ );
117
+
118
+ if (isUnaryContext)
119
+ value += "u"
120
+ }
121
+
122
+ if (type === "Literal" || type === "StringLiteral")
123
+ {
124
+ return {
125
+ type,
126
+ value,
127
+ row,
128
+ column
129
+ }
130
+ }
131
+
132
+ if (isUnaryPrefix(value))
133
+ {
134
+ const valueObject = OPERATORS[value];
135
+ const operand = parseExpression(tokens, valueObject.prec, state)!;
136
+
137
+ if (valueObject.fix === "both")
138
+ {
139
+ return {
140
+ type: "UnaryExpression",
141
+ value: value.toString(),
142
+ argument: operand,
143
+ row,
144
+ column
145
+ };
146
+ }
147
+
148
+ if (valueObject.fix === "prefix")
149
+ {
150
+ return {
151
+ type: "UnaryExpression",
152
+ value,
153
+ argument: operand,
154
+ row,
155
+ column
156
+ }
157
+ }
158
+
159
+ throw new parseErrors.UnaryOperatorError(value, row, column);
160
+ }
161
+
162
+ if (type === "Identifier")
163
+ {
164
+ const peekToken = peek(tokens, state);
165
+
166
+ // if the first item of tokens is "(" then treat it as a function
167
+ if (peekToken && peekToken.value === "(")
168
+ return parseFunctionCall(value, tokens, state);
169
+
170
+ return {
171
+ type: "VariableReference",
172
+ value,
173
+ row,
174
+ column
175
+ }
176
+ }
177
+
178
+ if (value === "(")
179
+ {
180
+ const expression = parseExpression(tokens, 0, state)!;
181
+
182
+ const peekToken = peek(tokens, state);
183
+ if (!peekToken || peekToken.value !== ")")
184
+ throw new parseErrors.MissingTokenError(")", expression.row, expression.column);
185
+
186
+ next(tokens, state); // eat )
187
+
188
+ return expression;
189
+ }
190
+
191
+ // handle ArrayAccesses
192
+ if (value === "[")
193
+ {
194
+ const elements : ParserToken[] = [];
195
+
196
+ while (peek(tokens, state) && peek(tokens, state).value !== "]")
197
+ {
198
+ const element = parseExpression(tokens, 0, state, true);
199
+ if (element)
200
+ elements.push(element);
201
+
202
+ const peekToken = peek(tokens, state);
203
+ if (peekToken && peekToken.value === ",")
204
+ next(tokens, state);
205
+ else if (peekToken && peekToken.value !== "]")
206
+ throw new parseErrors.UnexpectedTokenError(peekToken.value, peekToken.row, peekToken.column);
207
+ }
208
+
209
+ if (!peek(tokens, state) || peek(tokens, state).value !== "]")
210
+ throw new parseErrors.MissingTokenError("]", token.row, token.column);
211
+
212
+ next(tokens, state); // eat ]
213
+
214
+ return {
215
+ type: "ArrayLiteral",
216
+ elements,
217
+ row,
218
+ column
219
+ };
220
+ }
221
+
222
+ // handle Objects
223
+ if (value === "{")
224
+ {
225
+ const properties : {key : string, value : ParserToken | null}[] = [];
226
+
227
+ while (peek(tokens, state) && peek(tokens, state).value !== "}")
228
+ {
229
+ const keyToken = next(tokens, state)!;
230
+ const isInvalid : boolean = !keyToken || !(keyToken.type === "Identifier" || keyToken.type === "StringLiteral" || keyToken.type === "Literal");
231
+
232
+ if (isInvalid)
233
+ throw new parseErrors.UnexpectedTokenError(keyToken?.value, keyToken?.row ?? row, keyToken?.column ?? column);
234
+
235
+ if (!peek(tokens, state) || peek(tokens, state).value !== ":")
236
+ throw new parseErrors.MissingTokenError(":", keyToken.row, keyToken.column);
237
+ next(tokens, state); // eat :
238
+
239
+ const value = parseExpression(tokens, 0, state, true);
240
+
241
+ properties.unshift({key: keyToken.value, value});
242
+
243
+ const peekToken = peek(tokens, state);
244
+ if (peekToken && peekToken.value === ",")
245
+ next(tokens, state);
246
+ else if (peekToken && peekToken.value !== "}")
247
+ throw new parseErrors.UnexpectedTokenError(peekToken.value, peekToken.row, peekToken.column);
248
+ }
249
+
250
+ if (!peek(tokens, state) || peek(tokens, state).value !== "}")
251
+ throw new parseErrors.MissingTokenError("}", token.row, token.column);
252
+
253
+ next(tokens, state); // eat }
254
+
255
+ const node =
256
+ {
257
+ type: "ObjectLiteral",
258
+ properties,
259
+ row,
260
+ column
261
+ };
262
+
263
+ return node;
264
+ }
265
+
266
+ if (value === ";")
267
+ return null;
268
+
269
+ throw new parseErrors.UnexpectedTokenError(value, row, column);
270
+ }
271
+
272
+ // parse the expression using shunting-yard
273
+ export function parseExpression(tokens : Tokens, minimumPrecedence = 0, state : State, stopAtComma = false) : ParserToken | null
274
+ {
275
+ let operatorStack : Tokens = [];
276
+ let outputStack : ParserToken[] = [];
277
+
278
+ let token = next(tokens, state);
279
+ if (!token)
280
+ return null;
281
+
282
+ let node = parseStartingToken(token, tokens, state)!;
283
+ node = helpers.attach(node, tokens, state, stopAtComma);
284
+ outputStack.push(node);
285
+ while (true)
286
+ {
287
+ let token = peek(tokens, state);
288
+ if (helpers.stoppingCheck(token, stopAtComma))
289
+ break;
290
+
291
+ const operatorInfo = OPERATORS[token.value];
292
+ if (!operatorInfo || operatorInfo.prec < minimumPrecedence)
293
+ break;
294
+
295
+ const operator = next(tokens, state)!;
296
+
297
+ while (outputStack.length > 0)
298
+ {
299
+ const topOperator : BaseToken = operatorStack[operatorStack.length - 1]!;
300
+ const topInfo : Operator | null = OPERATORS[topOperator?.value] ?? null;
301
+ if (!topInfo)
302
+ break;
303
+
304
+ if (
305
+ topInfo.type !== "assignment" && (
306
+ (operatorInfo.assoc === "left" && operatorInfo.prec <= topInfo.prec) ||
307
+ (operatorInfo.assoc === "right" && operatorInfo.prec < topInfo.prec)
308
+ )
309
+ )
310
+ {
311
+ if (topInfo.type === "unary")
312
+ {
313
+ const operand = outputStack.pop()!;
314
+ outputStack.push
315
+ (
316
+ {
317
+ type: "UnaryExpression",
318
+ value: topOperator.value,
319
+ argument: operand,
320
+ row: topOperator.row,
321
+ column: topOperator.column
322
+ }
323
+ );
324
+ }
325
+ else
326
+ {
327
+ const rightNode : ParserToken = outputStack.pop()!;
328
+ const leftNode : ParserToken = outputStack.pop()!;
329
+
330
+ outputStack.push
331
+ (
332
+ {
333
+ type: topInfo.type === "logical" ? "LogicalExpression" : "BinaryExpression",
334
+ operator: topOperator.value,
335
+ left: leftNode,
336
+ right: rightNode,
337
+ row: topOperator.row,
338
+ column: topOperator.column
339
+ }
340
+ );
341
+ }
342
+
343
+ operatorStack.pop();
344
+ }
345
+
346
+ else
347
+ break;
348
+ }
349
+
350
+ if (operatorInfo.type === "assignment")
351
+ {
352
+ const leftNode = outputStack.pop()!;
353
+ if (leftNode.type !== "VariableReference" && leftNode.type !== "ArrayAccess" && leftNode.type !== "MemberExpression")
354
+ throw new parseErrors.AssignmentError(operator.value, operator.row, operator.column);
355
+
356
+ const rightNode = parseExpression(tokens, operatorInfo.prec, state, stopAtComma);
357
+
358
+ const baseOperators : Record<string, string> =
359
+ {
360
+ "+=": "+",
361
+ "-=": "-",
362
+ "*=": "*",
363
+ "/=": "/",
364
+ "%=": "%",
365
+ "&=": "&",
366
+ "|=": "|",
367
+ "^=": "^",
368
+ "<<=": "<<",
369
+ ">>=": ">>",
370
+ ">>>=": ">>>",
371
+ };
372
+
373
+ const name = leftNode.type === "ArrayAccess" || leftNode.type === "MemberExpression"
374
+ ? leftNode
375
+ : leftNode.value
376
+ const valueType = OPERATORS[baseOperators[operator.value]]?.type === "logical"
377
+ ? "LogicalExpression"
378
+ : "BinaryExpression";
379
+ outputStack.push
380
+ (
381
+ {
382
+ type: "Assignment",
383
+ name,
384
+ value: operator.value === "="
385
+ ? rightNode
386
+ : {
387
+ type: valueType,
388
+ operator: baseOperators[operator.value],
389
+ left: leftNode,
390
+ right: rightNode
391
+ },
392
+ row: operator.row,
393
+ column: operator.column
394
+ }
395
+ );
396
+
397
+ break;
398
+ }
399
+
400
+ operatorStack.push(operator);
401
+
402
+ let nextToken = next(tokens, state);
403
+ if (!nextToken)
404
+ throw new parseErrors.UnexpectedTokenError("end of input", operator.row, operator.column);
405
+
406
+ let node = parseStartingToken(nextToken, tokens, state);
407
+ if (!node)
408
+ throw new parseErrors.UnexpectedTokenError(nextToken.value, nextToken.row, nextToken.column);
409
+
410
+ node = helpers.attach(node, tokens, state, stopAtComma);
411
+
412
+ outputStack.push(node);
413
+ }
414
+
415
+ while (operatorStack.length)
416
+ {
417
+ const operator = operatorStack.pop()!;
418
+ const operatorInfo = OPERATORS[operator.value];
419
+ if (operatorInfo.type === "unary")
420
+ {
421
+ const operand = outputStack.pop(); // take one value
422
+ outputStack.push
423
+ (
424
+ {
425
+ type: "UnaryExpression",
426
+ value: operator.value,
427
+ argument: operand,
428
+ row: operator.row,
429
+ column: operator.column
430
+ }
431
+ );
432
+ }
433
+ else
434
+ {
435
+ const rightNode : ParserToken = outputStack.pop()!; // take 2 values
436
+ const leftNode : ParserToken = outputStack.pop()!;
437
+ outputStack.push
438
+ (
439
+ {
440
+ type: operatorInfo.type === "logical" ? "LogicalExpression" : "BinaryExpression",
441
+ operator: operator.value,
442
+ left: leftNode,
443
+ right: rightNode,
444
+ row: operator.row,
445
+ column: operator.column
446
+ }
447
+ );
448
+ }
449
+ }
450
+
451
+ const maybeTernary = peek(tokens, state);
452
+ if (maybeTernary && maybeTernary.value === "?")
453
+ {
454
+ next(tokens, state); // eat ?
455
+
456
+ const thenBranch = parseExpression(tokens, 0, state, stopAtComma)!;
457
+
458
+ const elseColon = peek(tokens, state);
459
+ if (!elseColon || elseColon.value !== ":")
460
+ throw new parseErrors.MissingTokenError(":", elseColon?.row ?? 0, elseColon?.column ?? 0);
461
+ next(tokens, state); // eat :
462
+
463
+ const elseBranch = parseExpression(tokens, 0, state, stopAtComma)!;
464
+
465
+ return {
466
+ type: "TernaryExpression",
467
+ condition: outputStack[0],
468
+ then: thenBranch,
469
+ else: elseBranch,
470
+ row: maybeTernary.row,
471
+ column: maybeTernary.column
472
+ }
473
+ }
474
+
475
+ return outputStack[0];
476
+ }
477
+
478
+ function parseFunctionCall(name : string, tokens : Tokens, state : State) : ParserToken
479
+ {
480
+ const row = state.lastToken?.row ?? 0;
481
+ const column = state.lastToken?.column ?? 0;
482
+
483
+ next(tokens, state); // eat (
484
+
485
+ let args : ParserToken[] = [];
486
+
487
+ const peekToken = peek(tokens, state);
488
+
489
+ if (peekToken && peekToken.value === ")")
490
+ {
491
+ next(tokens, state); // eat )
492
+ return {
493
+ type: "FunctionCall",
494
+ name,
495
+ args,
496
+ row,
497
+ column
498
+ }
499
+ }
500
+
501
+ // get all the arguments
502
+ while (peek(tokens, state) && peek(tokens, state).value !== ")")
503
+ {
504
+ if (peek(tokens, state)?.value === "...")
505
+ {
506
+ next(tokens, state);
507
+ const expression = parseExpression(tokens, 0, state, true);
508
+ args.push
509
+ (
510
+ {
511
+ type: "SpreadElement",
512
+ argument: expression,
513
+ row,
514
+ column
515
+ }
516
+ )
517
+ }
518
+ else
519
+ {
520
+ const expression = parseExpression(tokens, 0, state, true);
521
+
522
+ if (!expression)
523
+ {
524
+ console.warn(`\n Expected expression in function call "${name}"`);
525
+ break;
526
+ }
527
+
528
+ args.push(expression);
529
+ }
530
+
531
+ const peekToken = peek(tokens, state);
532
+
533
+ if (peekToken && peekToken.value === ",")
534
+ next(tokens, state);
535
+ else if (peekToken && peekToken.value !== ")")
536
+ throw new parseErrors.UnexpectedTokenError(peekToken.value, peekToken.row, peekToken.column);
537
+ }
538
+
539
+ if (!peek(tokens, state) || peek(tokens, state).value !== ")")
540
+ {
541
+ const lastToken = tokens[state.position - 1];
542
+ const row = lastToken?.row ?? 0;
543
+ const column = lastToken?.column ?? 0;
544
+
545
+ throw new parseErrors.FunctionCallError(`Missing ")" in function call for name "${name}"`, row, column);
546
+ }
547
+
548
+ next(tokens, state); // eat )
549
+
550
+ if (peek(tokens, state) && peek(tokens, state).value === "(")
551
+ throw new parseErrors.ChainedFunctionCallError(name, peek(tokens, state).row, peek(tokens, state).column);
552
+
553
+ return {
554
+ type: "FunctionCall",
555
+ name,
556
+ args,
557
+ row,
558
+ column
559
+ };
560
+ }
561
+
562
+ export function parseStatement(ast : AST, tokens : Tokens, state : State) : true | ParserToken | null
563
+ {
564
+ const token = peek(tokens, state);
565
+ if (!token)
566
+ return null;
567
+
568
+ // run all the handlers for keywords
569
+ if (token.type === "Keyword")
570
+ {
571
+ if (handlers.variableHandler(ast, token, tokens, state)) return true;
572
+ else if (handlers.ifHandler(ast, token, tokens, state)) return true;
573
+ else if (handlers.doWhileHandler(ast, token, tokens, state)) return true;
574
+ else if (handlers.whileHandler(ast, token, tokens, state)) return true;
575
+ else if (handlers.loopControlHandler(ast, token, tokens, state)) return true;
576
+ else if (handlers.forHandler(ast, token, tokens, state)) return true;
577
+ else if (handlers.functionHandler(ast, token, tokens, state)) return true;
578
+ else if (handlers.returnHandler(ast, token, tokens, state)) return true;
579
+ else if (handlers.importHandler(ast, token, tokens, state)) return true;
580
+ else if (handlers.classHandler(ast, token, tokens, state)) return true;
581
+ else if (handlers.tryHandler(ast, token, tokens, state)) return true;
582
+ else if (handlers.switchHandler(ast, token, tokens, state)) return true;
583
+ }
584
+
585
+ const expression = parseExpression(tokens, 0, state)!;
586
+ if (expression)
587
+ ast.body.push(expression);
588
+
589
+ return expression;
590
+ }
591
+
592
+ export function parser(tokens : Tokens) : AST
593
+ {
594
+ let state : State =
595
+ {
596
+ position: 0,
597
+ time: 0,
598
+ lastToken: null
599
+ };
600
+
601
+ let ast : AST =
602
+ {
603
+ type: "Program",
604
+ body:
605
+ [
606
+
607
+ ]
608
+ };
609
+
610
+ while (state.position < tokens.length)
611
+ {
612
+ const token = peek(tokens, state);
613
+ if (!token)
614
+ break;
615
+
616
+ parseStatement(ast, tokens, state);
617
+ }
618
+
619
+ return ast;
620
+ }
@@ -0,0 +1,43 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const counterFile = path.join(process.env.LOCALAPPDATA || process.cwd(), "pure-dango", "quote_crimes.json");
5
+
6
+ export function getQuoteCrimes() : number
7
+ {
8
+ try
9
+ {
10
+ const data = fs.readFileSync(counterFile, "utf8");
11
+ return JSON.parse(data).count ?? 0;
12
+ }
13
+ catch
14
+ {
15
+ return 0;
16
+ }
17
+ }
18
+
19
+ export function resetQuoteCrimes()
20
+ {
21
+ const zero = 0;
22
+ fs.mkdirSync(path.dirname(counterFile), {recursive: true});
23
+ fs.writeFileSync(counterFile, JSON.stringify({zero}));
24
+ return zero;
25
+ }
26
+
27
+ export function incrementQuoteCrimes() : number
28
+ {
29
+ const count = getQuoteCrimes() + 1;
30
+ fs.mkdirSync(path.dirname(counterFile), {recursive: true});
31
+ fs.writeFileSync(counterFile, JSON.stringify({count}));
32
+ return count;
33
+ }
34
+
35
+ export function QuoteCrimesMessage(count: number, opening : string) : string
36
+ {
37
+ if (count <= 1) return `Oops you broke the quotes! Quote was opened with ${opening}, but closed with another ${opening}.`
38
+ if (count <= 3) return `Oops you broke the quotes, again. Quote was opened with ${opening}, but closed with another ${opening}.`;
39
+ if (count <= 5) return `You broke the quotes AGAIN. This is the ${count}th time. Do you even read the errors?`;
40
+ if (count <= 9) return `I'm starting to think you're doing this on purpose`;
41
+ if (count <= 14) return `At this point just use straight quotes. It's gonna benefit both of us`;
42
+ return `${count} times.`;
43
+ }