1 /** 2 This module implements the Parser struct, which generates Nodes from tokens. The resulting syntax tree 3 is processed by the Compiler. 4 5 ──────────────────────────────────────────────────────────────────────────────── 6 7 Copyright (C) 2021 pillager86.rf.gd 8 9 This program is free software: you can redistribute it and/or modify it under 10 the terms of the GNU General Public License as published by the Free Software 11 Foundation, either version 3 of the License, or (at your option) any later 12 version. 13 14 This program is distributed in the hope that it will be useful, but WITHOUT ANY 15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 16 PARTICULAR PURPOSE. See the GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <https://www.gnu.org/licenses/>. 20 */ 21 module mildew.parser; 22 23 import std.conv: to, parse; 24 debug 25 { 26 import std.stdio; 27 } 28 29 import mildew.exceptions: ScriptCompileException; 30 import mildew.lexer: Token; 31 import mildew.nodes; 32 import mildew.types.any: ScriptAny; 33 import mildew.util.stack; 34 35 private int unaryOpPrecedence(Token opToken, bool isPost = false) 36 { 37 if(opToken.isKeyword("typeof")) 38 { 39 if(!isPost) 40 return 17; 41 } 42 43 // see grammar.txt for explanation of magic constants 44 switch(opToken.type) 45 { 46 case Token.Type.BIT_NOT: 47 case Token.Type.NOT: 48 case Token.Type.PLUS: 49 case Token.Type.DASH: 50 if(!isPost) 51 return 17; 52 else 53 return 0; 54 case Token.Type.INC: 55 case Token.Type.DEC: 56 if(isPost) 57 return 18; 58 else 59 return 17; 60 default: 61 return 0; 62 } 63 } 64 65 private int binaryOpPrecedence(Token opToken) 66 { 67 // TODO handle keywords in as 12 here (???) 68 if(opToken.isKeyword("instanceof")) 69 return 12; 70 71 // see grammar.txt for explanation of magic constants 72 switch(opToken.type) 73 { 74 case Token.Type.LBRACKET: 75 case Token.Type.DOT: 76 case Token.Type.LPAREN: 77 return 20; 78 case Token.Type.POW: 79 return 16; 80 case Token.Type.STAR: 81 case Token.Type.FSLASH: 82 case Token.Type.PERCENT: 83 return 15; 84 case Token.Type.PLUS: 85 case Token.Type.DASH: 86 return 14; 87 case Token.Type.BIT_LSHIFT: 88 case Token.Type.BIT_RSHIFT: 89 case Token.Type.BIT_URSHIFT: 90 return 13; 91 case Token.Type.LT: 92 case Token.Type.LE: 93 case Token.Type.GT: 94 case Token.Type.GE: 95 return 12; 96 case Token.Type.EQUALS: 97 case Token.Type.NEQUALS: 98 case Token.Type.STRICT_EQUALS: 99 case Token.Type.STRICT_NEQUALS: 100 return 11; 101 case Token.Type.BIT_AND: 102 return 10; 103 case Token.Type.BIT_XOR: 104 return 9; 105 case Token.Type.BIT_OR: 106 return 8; 107 case Token.Type.AND: 108 return 7; 109 case Token.Type.OR: 110 return 6; 111 case Token.Type.NULLC: 112 return 5; 113 case Token.Type.QUESTION: 114 return 4; 115 case Token.Type.ASSIGN: 116 case Token.Type.POW_ASSIGN: 117 case Token.Type.STAR_ASSIGN: 118 case Token.Type.FSLASH_ASSIGN: 119 case Token.Type.PERCENT_ASSIGN: 120 case Token.Type.PLUS_ASSIGN: 121 case Token.Type.DASH_ASSIGN: 122 case Token.Type.BAND_ASSIGN: 123 case Token.Type.BXOR_ASSIGN: 124 case Token.Type.BOR_ASSIGN: 125 case Token.Type.BLS_ASSIGN: 126 case Token.Type.BRS_ASSIGN: 127 case Token.Type.BURS_ASSIGN: 128 return 3; 129 default: 130 return 0; 131 } 132 // yield and commma are already handled differently 133 } 134 135 private bool isBinaryOpLeftAssociative(in Token opToken) 136 { 137 if(opToken.isKeyword("instanceof")) 138 return true; 139 140 switch(opToken.type) 141 { 142 case Token.Type.LBRACKET: 143 case Token.Type.DOT: 144 case Token.Type.LPAREN: 145 return true; 146 case Token.Type.POW: 147 return false; 148 case Token.Type.STAR: 149 case Token.Type.FSLASH: 150 case Token.Type.PERCENT: 151 return true; 152 case Token.Type.PLUS: 153 case Token.Type.DASH: 154 return true; 155 case Token.Type.BIT_LSHIFT: 156 case Token.Type.BIT_RSHIFT: 157 case Token.Type.BIT_URSHIFT: 158 return true; 159 case Token.Type.LT: 160 case Token.Type.LE: 161 case Token.Type.GT: 162 case Token.Type.GE: 163 return true; 164 case Token.Type.EQUALS: 165 case Token.Type.NEQUALS: 166 case Token.Type.STRICT_EQUALS: 167 case Token.Type.STRICT_NEQUALS: 168 return true; 169 case Token.Type.BIT_AND: 170 return true; 171 case Token.Type.BIT_XOR: 172 return true; 173 case Token.Type.BIT_OR: 174 return true; 175 case Token.Type.AND: 176 return true; 177 case Token.Type.OR: 178 return true; 179 case Token.Type.QUESTION: 180 return false; 181 case Token.Type.NULLC: 182 return true; 183 case Token.Type.ASSIGN: 184 case Token.Type.POW_ASSIGN: 185 case Token.Type.STAR_ASSIGN: 186 case Token.Type.FSLASH_ASSIGN: 187 case Token.Type.PERCENT_ASSIGN: 188 case Token.Type.PLUS_ASSIGN: 189 case Token.Type.DASH_ASSIGN: 190 case Token.Type.BAND_ASSIGN: 191 case Token.Type.BXOR_ASSIGN: 192 case Token.Type.BOR_ASSIGN: 193 case Token.Type.BLS_ASSIGN: 194 case Token.Type.BRS_ASSIGN: 195 case Token.Type.BURS_ASSIGN: 196 return false; 197 default: 198 return false; 199 } 200 } 201 202 private bool tokenBeginsLoops(const Token tok) 203 { 204 return tok.type == Token.Type.LABEL 205 || tok.isKeyword("while") 206 || tok.isKeyword("do") 207 || tok.isKeyword("for"); 208 } 209 210 /** 211 * The parser is used by the Compiler to generate a syntax tree out of tokens. 212 */ 213 struct Parser 214 { 215 /** 216 * The constructor takes all tokens so that looking ahead for specific tokens 217 * can allow support for lambdas and other complex language features. 218 */ 219 this(Token[] tokens) 220 { 221 _tokens = tokens; 222 nextToken(); // prime 223 } 224 225 /** 226 * The main starting point. Also the "program" grammar rule. This method generates a block statement 227 * node where the Compiler iterates through each statement and emits bytecode for it. 228 */ 229 BlockStatementNode parseProgram() 230 { 231 immutable lineNo = _currentToken.position.line; 232 _functionContextStack.push(FunctionContext(FunctionContextType.NORMAL, 0, 0, [])); 233 auto statements = parseStatements(Token.Type.EOF); 234 _functionContextStack.pop(); 235 assert(_functionContextStack.size == 0, "Sanity check failed: _functionContextStack"); 236 return new BlockStatementNode(lineNo, statements); 237 } 238 239 package: 240 241 /// parse a single expression. See https://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing 242 /// for algorithm. 243 ExpressionNode parseExpression(int minPrec = 1) 244 { 245 ExpressionNode primaryLeft = null; 246 247 immutable unOpPrec = _currentToken.unaryOpPrecedence; 248 if(unOpPrec > minPrec) 249 { 250 auto opToken = _currentToken; 251 nextToken(); 252 // TODO handle nested unary operations such as -++foo. This will require building an array 253 // of unary ops before parsing the primary expression. and further complicate unary operations on 254 // member access, array access, and function call nodes. 255 primaryLeft = parsePrimaryExpression(); 256 primaryLeft = new UnaryOpNode(opToken, primaryLeft); 257 } 258 else 259 { 260 primaryLeft = parsePrimaryExpression(); 261 } 262 263 while(_currentToken.binaryOpPrecedence >= minPrec || _currentToken.unaryOpPrecedence(true) >= minPrec) 264 { 265 if(_currentToken.unaryOpPrecedence(true) >= minPrec) 266 { 267 // if primary left is a unary op node, we need to apply the postfix and recreate the uop 268 if(auto uopNode = cast(UnaryOpNode)primaryLeft) 269 primaryLeft = new UnaryOpNode(uopNode.opToken, new PostfixOpNode(_currentToken, 270 uopNode.operandNode)); 271 else 272 primaryLeft = new PostfixOpNode(_currentToken, primaryLeft); 273 nextToken(); 274 } 275 else 276 { 277 auto opToken = _currentToken; 278 immutable prec = opToken.binaryOpPrecedence; 279 immutable isLeftAssoc = opToken.isBinaryOpLeftAssociative; 280 immutable nextMinPrec = isLeftAssoc? prec + 1 : prec; 281 nextToken(); 282 if(opToken.type == Token.Type.QUESTION) 283 { 284 // primaryLeft is our condition node 285 auto onTrue = parseExpression(); 286 if(_currentToken.type != Token.Type.COLON) 287 throw new ScriptCompileException("Expected ':' in terniary operator expression", _currentToken); 288 nextToken(); 289 auto onFalse = parseExpression(); 290 primaryLeft = new TerniaryOpNode(primaryLeft, onTrue, onFalse); 291 } 292 // this is monstrous. There has to be a better way to do this 293 else if(opToken.type == Token.Type.DOT) 294 { 295 auto right = parsePrimaryExpression(); 296 if(!cast(VarAccessNode)right) 297 throw new ScriptCompileException("Right hand side of `.` operator must be identifier", opToken); 298 if(cast(VarAccessNode)right is null) 299 throw new ScriptCompileException("Object members must be valid identifiers", _currentToken); 300 if(unOpPrec != 0 && prec > unOpPrec) 301 { 302 auto uon = cast(UnaryOpNode)primaryLeft; 303 primaryLeft = new UnaryOpNode(uon.opToken, 304 new MemberAccessNode(uon.operandNode, opToken, right)); 305 } 306 else 307 primaryLeft = new MemberAccessNode(primaryLeft, opToken, right); 308 } 309 else if(opToken.type == Token.Type.LBRACKET) 310 { 311 auto index = parseExpression(); 312 if(_currentToken.type != Token.Type.RBRACKET) 313 throw new ScriptCompileException("Missing ']'", _currentToken); 314 nextToken(); 315 if(unOpPrec != 0 && prec > unOpPrec) 316 { 317 auto uon = cast(UnaryOpNode)primaryLeft; 318 primaryLeft = new UnaryOpNode(uon.opToken, new ArrayIndexNode(uon.operandNode, index)); 319 } 320 else 321 primaryLeft = new ArrayIndexNode(primaryLeft, index); 322 } 323 else if(opToken.type == Token.Type.LPAREN) 324 { 325 auto params = parseCommaSeparatedExpressions(Token.Type.RPAREN); 326 nextToken(); 327 if(unOpPrec != 0 && prec > unOpPrec) 328 { 329 auto uon = cast(UnaryOpNode)primaryLeft; 330 primaryLeft = new UnaryOpNode(uon.opToken, new FunctionCallNode(uon.operandNode, params)); 331 } 332 else 333 primaryLeft = new FunctionCallNode(primaryLeft, params); 334 } 335 else 336 { 337 ExpressionNode primaryRight = parseExpression(nextMinPrec); 338 // catch invalid assignments 339 if(opToken.isAssignmentOperator) 340 { 341 if(!(cast(VarAccessNode)primaryLeft 342 || cast(MemberAccessNode)primaryLeft 343 || cast(ArrayIndexNode)primaryLeft)) 344 { 345 throw new ScriptCompileException("Invalid left hand operand for assignment " 346 ~ primaryLeft.toString(), opToken); 347 } 348 } 349 primaryLeft = new BinaryOpNode(opToken, primaryLeft, primaryRight); 350 } 351 } 352 } 353 354 return primaryLeft; 355 } 356 357 private: 358 359 // TODO: parser needs to take an optional compiler object so that lambdas can be switch-case 360 // expressions and work at runtime. 361 ScriptAny evaluateCTFE(ExpressionNode expr) 362 { 363 import mildew.environment: Environment; 364 import mildew.compiler: Compiler; 365 import mildew.vm.program: Program; 366 import mildew.vm.virtualmachine: VirtualMachine; 367 auto ret = new ReturnStatementNode(0, expr); 368 auto compiler = new Compiler(); 369 auto program = compiler.compile([ret]); 370 auto vm = new VirtualMachine(new Environment(null, "<ctfe>")); 371 try 372 { 373 return vm.runProgram(program, []); 374 } 375 catch(Exception ex) 376 { 377 return ScriptAny.UNDEFINED; 378 } 379 } 380 381 /// parses a single statement 382 StatementNode parseStatement() 383 { 384 StatementNode statement; 385 immutable lineNumber = _currentToken.position.line; 386 // check for var declaration 387 if(_currentToken.isKeyword("var") || _currentToken.isKeyword("let") || _currentToken.isKeyword("const")) 388 { 389 statement = parseVarDeclarationStatement(); 390 } 391 // check for {} block 392 else if(_currentToken.type == Token.Type.LBRACE) 393 { 394 // TODO: peek two tokens ahead for a ':' to indicate whether or not this is an object literal expression 395 nextToken(); 396 auto statements = parseStatements(Token.Type.RBRACE); 397 nextToken(); 398 statement = new BlockStatementNode(lineNumber, statements); 399 } 400 // check for if statement 401 else if(_currentToken.isKeyword("if")) 402 { 403 statement = parseIfStatement(); 404 } 405 // check for switch 406 else if(_currentToken.isKeyword("switch")) 407 { 408 statement = parseSwitchStatement(); 409 } 410 // check for loops 411 else if(_currentToken.tokenBeginsLoops()) 412 { 413 statement = parseLoopStatement(); 414 } 415 // break statement? 416 else if(_currentToken.isKeyword("break")) 417 { 418 if(_functionContextStack.top.loopStack == 0 && _functionContextStack.top.switchStack == 0) 419 throw new ScriptCompileException("Break statements only allowed in loops or switch-case bodies", 420 _currentToken); 421 nextToken(); 422 string label = ""; 423 if(_currentToken.type == Token.Type.IDENTIFIER) 424 { 425 label = _currentToken.text; 426 bool valid = false; 427 // label must exist on stack 428 for(size_t i = _functionContextStack.top.labelStack.length; i > 0; --i) 429 { 430 if(_functionContextStack.top.labelStack[i-1] == label) 431 { 432 valid = true; 433 break; 434 } 435 } 436 if(!valid) 437 throw new ScriptCompileException("Break label does not refer to valid label", _currentToken); 438 nextToken(); 439 } 440 if(_currentToken.type != Token.Type.SEMICOLON) 441 throw new ScriptCompileException("Expected ';' after break", _currentToken); 442 nextToken(); 443 statement = new BreakStatementNode(lineNumber, label); 444 } 445 // continue statement 446 else if(_currentToken.isKeyword("continue")) 447 { 448 if(_functionContextStack.top.loopStack == 0) 449 throw new ScriptCompileException("Continue statements only allowed in loops", _currentToken); 450 nextToken(); 451 string label = ""; 452 if(_currentToken.type == Token.Type.IDENTIFIER) 453 { 454 label = _currentToken.text; 455 bool valid = false; 456 for(size_t i = _functionContextStack.top.labelStack.length; i > 0; --i) 457 { 458 if(_functionContextStack.top.labelStack[i-1] == label) 459 { 460 valid = true; 461 break; 462 } 463 } 464 if(!valid) 465 throw new ScriptCompileException("Continue label does not refer to valid label", _currentToken); 466 nextToken(); 467 } 468 if(_currentToken.type != Token.Type.SEMICOLON) 469 throw new ScriptCompileException("Expected ';' after continue", _currentToken); 470 nextToken(); 471 statement = new ContinueStatementNode(lineNumber, label); 472 } 473 // return statement with optional expression 474 else if(_currentToken.isKeyword("return")) 475 { 476 nextToken(); 477 ExpressionNode expression = null; 478 if(_currentToken.type != Token.Type.SEMICOLON) 479 expression = parseExpression(); 480 if(_currentToken.type != Token.Type.SEMICOLON) 481 throw new ScriptCompileException("Expected ';' after return", _currentToken); 482 nextToken(); 483 statement = new ReturnStatementNode(lineNumber, expression); 484 } 485 else if(_currentToken.isKeyword("function")) 486 { 487 statement = parseFunctionDeclarationStatement(); 488 } 489 else if(_currentToken.isKeyword("throw")) 490 { 491 nextToken(); 492 auto expr = parseExpression(); 493 if(_currentToken.type != Token.Type.SEMICOLON) 494 throw new ScriptCompileException("Expected ';' after throw expression", _currentToken); 495 nextToken(); 496 statement = new ThrowStatementNode(lineNumber, expr); 497 } 498 else if(_currentToken.isKeyword("try")) 499 { 500 statement = parseTryCatchBlockStatement(); 501 } 502 else if(_currentToken.isKeyword("delete")) 503 { 504 immutable delToken = _currentToken; 505 nextToken(); 506 auto tok = _currentToken; 507 auto expression = parseExpression(); 508 if(cast(MemberAccessNode)expression is null && cast(ArrayIndexNode)expression is null) 509 throw new ScriptCompileException("Invalid operand for delete operation", tok); 510 statement = new DeleteStatementNode(lineNumber, delToken, expression); 511 } 512 else if(_currentToken.isKeyword("class")) 513 { 514 statement = parseClassDeclaration(); 515 } 516 else // for now has to be one expression followed by semicolon or EOF 517 { 518 if(_currentToken.type == Token.Type.SEMICOLON) 519 { 520 // empty statement 521 statement = new ExpressionStatementNode(lineNumber, null); 522 nextToken(); 523 } 524 else 525 { 526 auto expression = parseExpression(); 527 if(_currentToken.type != Token.Type.SEMICOLON && _currentToken.type != Token.Type.EOF) 528 throw new ScriptCompileException("Expected semicolon after expression", _currentToken); 529 nextToken(); // eat semicolon 530 statement = new ExpressionStatementNode(lineNumber, expression); 531 } 532 } 533 return statement; 534 } 535 536 ExpressionNode parsePrimaryExpression() 537 { 538 import std.conv: to, ConvException; 539 540 ExpressionNode left = null; 541 switch(_currentToken.type) 542 { 543 case Token.Type.LPAREN: { 544 // first check if this is a lambda 545 auto lookahead = peekTokens(3); 546 if((lookahead[1].type == Token.Type.COMMA || 547 lookahead[1].type == Token.Type.ARROW || 548 lookahead[2].type == Token.Type.ARROW) && lookahead[0].type != Token.Type.LPAREN) 549 { 550 left = parseLambda(true); 551 } 552 else 553 { 554 nextToken(); 555 left = parseExpression(); 556 if(_currentToken.type != Token.Type.RPAREN) 557 throw new ScriptCompileException("Missing ')' in primary expression", _currentToken); 558 nextToken(); 559 } 560 break; 561 } 562 case Token.Type.LBRACE: 563 left = parseObjectLiteral(); 564 break; 565 case Token.Type.DOUBLE: 566 if(_currentToken.literalFlag == Token.LiteralFlag.NONE) 567 left = new LiteralNode(_currentToken, ScriptAny(to!double(_currentToken.text))); 568 else 569 throw new ScriptCompileException("Malformed decimal number token", _currentToken); 570 nextToken(); 571 break; 572 case Token.Type.INTEGER: 573 try 574 { 575 if(_currentToken.literalFlag == Token.LiteralFlag.NONE) 576 left = new LiteralNode(_currentToken, ScriptAny(to!long(_currentToken.text))); 577 else if(_currentToken.literalFlag == Token.LiteralFlag.HEXADECIMAL) 578 left = new LiteralNode(_currentToken, ScriptAny(_currentToken.text[2..$].to!long(16))); 579 else if(_currentToken.literalFlag == Token.LiteralFlag.OCTAL) 580 left = new LiteralNode(_currentToken, ScriptAny(_currentToken.text[2..$].to!long(8))); 581 else if(_currentToken.literalFlag == Token.LiteralFlag.BINARY) 582 left = new LiteralNode(_currentToken, ScriptAny(_currentToken.text[2..$].to!long(2))); 583 } 584 catch(ConvException ex) 585 { 586 throw new ScriptCompileException("Integer literal is too long", _currentToken); 587 } 588 nextToken(); 589 break; 590 case Token.Type.STRING: 591 if(_currentToken.literalFlag != Token.LiteralFlag.TEMPLATE_STRING) 592 left = new LiteralNode(_currentToken, ScriptAny(_currentToken.text)); 593 else 594 left = parseTemplateStringNode(); 595 nextToken(); 596 break; 597 case Token.Type.REGEX: 598 left = new LiteralNode(_currentToken, ScriptAny(_currentToken.text)); 599 nextToken(); 600 break; 601 case Token.Type.KEYWORD: 602 if(_currentToken.text == "true" || _currentToken.text == "false") 603 { 604 left = new LiteralNode(_currentToken, ScriptAny(to!bool(_currentToken.text))); 605 nextToken(); 606 } 607 else if(_currentToken.text == "null") 608 { 609 left = new LiteralNode(_currentToken, ScriptAny(null)); 610 nextToken(); 611 } 612 else if(_currentToken.text == "undefined") 613 { 614 left = new LiteralNode(_currentToken, ScriptAny.UNDEFINED); 615 nextToken(); 616 } 617 else if(_currentToken.text == "function") // function literal 618 { 619 bool isGenerator = false; 620 immutable token = _currentToken; 621 nextToken(); 622 if(_currentToken.type == Token.Type.STAR) 623 { 624 isGenerator = true; 625 nextToken(); 626 } 627 628 string optionalName = ""; 629 if(_currentToken.type == Token.Type.IDENTIFIER) 630 { 631 optionalName = _currentToken.text; 632 nextToken(); 633 } 634 635 if(_currentToken.type != Token.Type.LPAREN) 636 throw new ScriptCompileException("Argument list expected after anonymous function", 637 _currentToken); 638 nextToken(); 639 string[] argNames = []; 640 ExpressionNode[] defaultArgs; 641 argNames = parseArgumentList(defaultArgs); 642 nextToken(); // eat the ) 643 if(_currentToken.type != Token.Type.LBRACE) 644 throw new ScriptCompileException("Expected '{' before anonymous function body", _currentToken); 645 nextToken(); // eat the { 646 _functionContextStack.push(FunctionContext( 647 isGenerator? FunctionContextType.GENERATOR : FunctionContextType.NORMAL, 648 0, 0, [])); 649 auto statements = parseStatements(Token.Type.RBRACE); 650 _functionContextStack.pop(); 651 nextToken(); 652 // auto func = new ScriptFunction(name, argNames, statements, null); 653 left = new FunctionLiteralNode(token, argNames, defaultArgs, statements, optionalName, 654 false, isGenerator); 655 } 656 else if(_currentToken.text == "class") 657 { 658 left = parseClassExpression(); 659 } 660 else if(_currentToken.text == "new") 661 { 662 immutable newToken = _currentToken; 663 nextToken(); 664 auto expression = parseExpression(); 665 auto fcn = cast(FunctionCallNode)expression; 666 if(fcn is null) 667 { 668 // if this isn't a function call, turn it into one 669 fcn = new FunctionCallNode(expression, [], true); 670 } 671 fcn.returnThis = true; 672 left = new NewExpressionNode(fcn); 673 } 674 else if(_currentToken.text == "super") 675 { 676 immutable stoken = _currentToken; 677 if(_baseClassStack.length < 1) 678 throw new ScriptCompileException("Super expression only allowed in derived classes", stoken); 679 left = new SuperNode(stoken, _baseClassStack[$-1]); 680 nextToken(); 681 } 682 else if(_currentToken.text == "yield") 683 { 684 // check context stack 685 if(_functionContextStack.top.fct != FunctionContextType.GENERATOR) 686 throw new ScriptCompileException("Yield may only be used in Generator functions", 687 _currentToken); 688 689 immutable ytoken = _currentToken; 690 nextToken(); 691 ExpressionNode expr; 692 if(_currentToken.type != Token.Type.RBRACE && _currentToken.type != Token.Type.SEMICOLON) 693 expr = parseExpression(); 694 left = new YieldNode(ytoken, expr); 695 } 696 else 697 throw new ScriptCompileException("Unexpected keyword in primary expression", _currentToken); 698 break; 699 // TODO function 700 case Token.Type.IDENTIFIER: { 701 immutable lookahead = peekToken(); 702 if(lookahead.type == Token.Type.ARROW) 703 { 704 left = parseLambda(false); 705 } 706 else 707 { 708 left = new VarAccessNode(_currentToken); 709 nextToken(); 710 } 711 break; 712 } 713 case Token.Type.LBRACKET: // an array 714 { 715 nextToken(); // eat the [ 716 auto values = parseCommaSeparatedExpressions(Token.Type.RBRACKET); 717 nextToken(); // eat the ] 718 left = new ArrayLiteralNode(values); 719 break; 720 } 721 default: 722 throw new ScriptCompileException("Unexpected token in primary expression", _currentToken); 723 } 724 return left; 725 } 726 727 // TODO optimize this to use indexing instead of string building. 728 TemplateStringNode parseTemplateStringNode() 729 { 730 import mildew.lexer: Lexer; 731 732 enum TSState { LIT, EXPR } 733 734 size_t textIndex; 735 string currentExpr = ""; 736 string currentLiteral = ""; 737 ExpressionNode[] nodes; 738 TSState state = TSState.LIT; 739 int bracketStack = 0; 740 741 string peekTwo(in string text, size_t i) 742 { 743 immutable char first = i >= text.length ? '\0' : text[i]; 744 immutable char second = i+1 >= text.length ? '\0' : text[i+1]; 745 return cast(string)[first,second]; 746 } 747 748 while(textIndex < _currentToken.text.length) 749 { 750 if(state == TSState.LIT) 751 { 752 if(peekTwo(_currentToken.text, textIndex) == "${") 753 { 754 currentExpr = ""; 755 textIndex += 2; 756 state = TSState.EXPR; 757 if(currentLiteral.length > 0) 758 nodes ~= new LiteralNode(_currentToken, ScriptAny(currentLiteral)); 759 } 760 else 761 { 762 currentLiteral ~= _currentToken.text[textIndex++]; 763 } 764 } 765 else 766 { 767 if(_currentToken.text[textIndex] == '}') 768 { 769 if(bracketStack) 770 { 771 currentExpr ~= _currentToken.text[textIndex++]; 772 --bracketStack; 773 } 774 else 775 { 776 currentLiteral = ""; 777 textIndex++; 778 state = TSState.LIT; 779 if(currentExpr.length > 0) 780 { 781 auto lexer = Lexer(currentExpr); 782 auto tokens = lexer.tokenize(); 783 auto parser = Parser(tokens); 784 nodes ~= parser.parseExpression(); 785 if(parser._currentToken.type != Token.Type.EOF) 786 { 787 parser._currentToken.position = _currentToken.position; 788 throw new ScriptCompileException("Unexpected token in template expression", 789 parser._currentToken); 790 } 791 } 792 } 793 } 794 else 795 { 796 if(_currentToken.text[textIndex] == '{') 797 ++bracketStack; 798 currentExpr ~= _currentToken.text[textIndex++]; 799 } 800 } 801 } 802 if(state == TSState.EXPR) 803 throw new ScriptCompileException("Unclosed template expression", _currentToken); 804 if(currentLiteral.length > 0) 805 nodes ~= new LiteralNode(_currentToken, ScriptAny(currentLiteral)); 806 return new TemplateStringNode(nodes); 807 } 808 809 /// after class ? extend base this can begin 810 ClassDefinition parseClassDefinition(Token classToken, string className, ExpressionNode baseClass) 811 { 812 if(_currentToken.type != Token.Type.LBRACE) 813 throw new ScriptCompileException("Expected '{' after class", _currentToken); 814 nextToken(); 815 FunctionLiteralNode constructor; 816 string[] methodNames; 817 FunctionLiteralNode[] methods; 818 string[] getMethodNames; 819 FunctionLiteralNode[] getMethods; 820 string[] setMethodNames; 821 FunctionLiteralNode[] setMethods; 822 string[] staticMethodNames; 823 FunctionLiteralNode[] staticMethods; 824 enum PropertyType { NONE, GET, SET, STATIC } 825 while(_currentToken.type != Token.Type.RBRACE && _currentToken.type != Token.Type.EOF) 826 { 827 PropertyType ptype = PropertyType.NONE; 828 string currentMethodName = ""; 829 // could be a get or set 830 if(_currentToken.isIdentifier("get")) 831 { 832 ptype = PropertyType.GET; 833 nextToken(); 834 } 835 else if(_currentToken.isIdentifier("set")) 836 { 837 ptype = PropertyType.SET; 838 nextToken(); 839 } 840 else if(_currentToken.isIdentifier("static")) 841 { 842 ptype = PropertyType.STATIC; 843 nextToken(); 844 } 845 // then an identifier 846 immutable idToken = _currentToken; 847 if(_currentToken.type != Token.Type.IDENTIFIER) 848 throw new ScriptCompileException("Method names must be valid identifiers", _currentToken); 849 currentMethodName = _currentToken.text; 850 nextToken(); 851 // then a ( 852 if(_currentToken.type != Token.Type.LPAREN) 853 throw new ScriptCompileException("Expected '(' after method name", _currentToken); 854 nextToken(); // eat the ( 855 string[] argNames; 856 ExpressionNode[] defaultArgs; 857 argNames = parseArgumentList(defaultArgs); 858 nextToken(); // eat the ) 859 // then a { 860 if(_currentToken.type != Token.Type.LBRACE) 861 throw new ScriptCompileException("Method bodies must begin with '{'", _currentToken); 862 nextToken(); 863 if(currentMethodName != "constructor") 864 _functionContextStack.push(FunctionContext(FunctionContextType.METHOD, 0, 0, [])); 865 else 866 _functionContextStack.push(FunctionContext(FunctionContextType.CONSTRUCTOR, 0, 0, [])); 867 auto statements = parseStatements(Token.Type.RBRACE); 868 _functionContextStack.pop(); 869 nextToken(); // eat } 870 // now we have a method but if this is the constructor 871 if(currentMethodName == "constructor") 872 { 873 if(ptype != PropertyType.NONE) 874 throw new ScriptCompileException("Get, set, or static not allowed for constructor", classToken); 875 if(constructor !is null) 876 throw new ScriptCompileException("Classes may only have one constructor", classToken); 877 // if this is extending a class it MUST have ONE super call 878 if(baseClass !is null) 879 { 880 ulong numSupers = 0; 881 foreach(stmt ; statements) 882 { 883 if(auto exprStmt = cast(ExpressionStatementNode)stmt) 884 { 885 if(auto fcn = cast(FunctionCallNode)exprStmt.expressionNode) 886 { 887 if(auto supernode = cast(SuperNode)fcn.functionToCall) 888 numSupers++; 889 } 890 } 891 } 892 if(numSupers != 1) 893 throw new ScriptCompileException( 894 "Derived class constructors must have one super call at the top level", 895 classToken 896 ); 897 } 898 constructor = new FunctionLiteralNode(idToken, argNames, defaultArgs, statements, className, true); 899 } 900 else // it's a normal method or getter/setter 901 { 902 if(ptype == PropertyType.NONE) 903 { 904 auto trueName = currentMethodName; 905 if(className != "<anonymous class>" && className != "") 906 trueName = className ~ ".prototype." ~ currentMethodName; 907 methods ~= new FunctionLiteralNode(idToken, argNames, defaultArgs, statements, trueName); 908 methodNames ~= currentMethodName; 909 } 910 else if(ptype == PropertyType.GET) 911 { 912 getMethods ~= new FunctionLiteralNode(idToken, argNames, defaultArgs, statements, 913 currentMethodName); 914 getMethodNames ~= currentMethodName; 915 } 916 else if(ptype == PropertyType.SET) 917 { 918 setMethods ~= new FunctionLiteralNode(idToken, argNames, defaultArgs, statements, 919 currentMethodName); 920 setMethodNames ~= currentMethodName; 921 } 922 else if(ptype == PropertyType.STATIC) 923 { 924 auto trueName = currentMethodName; 925 if(className != "<anonymous class>" && className != "") 926 trueName = className ~ "." ~ currentMethodName; 927 staticMethods ~= new FunctionLiteralNode(idToken, argNames, defaultArgs, statements, trueName); 928 staticMethodNames ~= currentMethodName; 929 } 930 } 931 } 932 nextToken(); // eat the class body } 933 934 // check for duplicate methods 935 bool[string] mnameMap; 936 foreach(mname ; methodNames) 937 { 938 if(mname in mnameMap) 939 throw new ScriptCompileException("Duplicate methods are not allowed", classToken); 940 mnameMap[mname] = true; 941 } 942 943 if(constructor is null) 944 { 945 // probably should enforce required super when base class exists. or not 946 constructor = new FunctionLiteralNode(classToken, [], [], [], className, true); 947 } 948 949 if(baseClass !is null) 950 _baseClassStack = _baseClassStack[0..$-1]; 951 return new ClassDefinition(className, constructor, methodNames, methods, getMethodNames, getMethods, 952 setMethodNames, setMethods, staticMethodNames, staticMethods, baseClass); 953 } 954 955 ClassLiteralNode parseClassExpression() 956 { 957 immutable classToken = _currentToken; 958 nextToken(); 959 string className = "<anonymous class>"; 960 if(_currentToken.type == Token.Type.IDENTIFIER) 961 { 962 className = _currentToken.text; 963 nextToken(); 964 } 965 ExpressionNode baseClass = null; 966 if(_currentToken.isKeyword("extends")) 967 { 968 nextToken(); 969 baseClass = parseExpression(); // let's hope this is an expression that results in a ScriptFunction value 970 _baseClassStack ~= baseClass; 971 } 972 auto classDef = parseClassDefinition(classToken, className, baseClass); 973 return new ClassLiteralNode(classToken, classDef); 974 } 975 976 /// parses multiple statements until reaching stop 977 StatementNode[] parseStatements(in Token.Type stop) 978 { 979 StatementNode[] statements; 980 while(_currentToken.type != stop && _currentToken.type != Token.Type.EOF) 981 { 982 statements ~= parseStatement(); 983 // each statement parse should eat the semicolon or } so there's nothing to do here 984 } 985 return statements; 986 } 987 988 VarDeclarationStatementNode parseVarDeclarationStatement(bool consumeSemicolon = true) 989 { 990 auto specifier = _currentToken; 991 nextToken(); 992 // auto expressions = parseCommaSeparatedExpressions(Token.Type.SEMICOLON); 993 ExpressionNode[] expressions; 994 while(_currentToken.type != Token.Type.SEMICOLON && _currentToken.type != Token.Type.EOF 995 && !_currentToken.isIdentifier("of") && !_currentToken.isKeyword("in")) 996 { 997 if(_currentToken.type == Token.Type.IDENTIFIER) 998 { 999 auto varName = _currentToken.text; 1000 nextToken(); 1001 if(_currentToken.type == Token.Type.ASSIGN) 1002 { 1003 nextToken(); 1004 auto assignment = parseExpression(); 1005 expressions ~= new BinaryOpNode(Token.createFakeToken(Token.Type.ASSIGN, "="), 1006 new VarAccessNode(Token.createFakeToken(Token.Type.IDENTIFIER, varName)), 1007 assignment); 1008 } 1009 else 1010 { 1011 expressions ~= new VarAccessNode(Token.createFakeToken(Token.Type.IDENTIFIER, varName)); 1012 } 1013 } 1014 else if(_currentToken.type == Token.Type.LBRACE || _currentToken.type == Token.Type.LBRACKET) 1015 { 1016 // TODO: replace destructure node with [name|/{name| variables 1017 immutable isObj = _currentToken.type == Token.Type.LBRACE; 1018 immutable endTokenType = isObj ? Token.Type.RBRACE : Token.Type.RBRACKET; 1019 nextToken(); 1020 string[] names; 1021 string remainderName; 1022 while(_currentToken.type != endTokenType && _currentToken.type != Token.Type.EOF) 1023 { 1024 if(_currentToken.type == Token.Type.TDOT) 1025 { 1026 nextToken(); 1027 if(_currentToken.type != Token.Type.IDENTIFIER) 1028 throw new ScriptCompileException("Spread variable must be identifier", _currentToken); 1029 if(remainderName != "") 1030 throw new ScriptCompileException("Only one spread variable allowed", _currentToken); 1031 remainderName = _currentToken.text; 1032 } 1033 else if(_currentToken.type == Token.Type.IDENTIFIER) 1034 { 1035 auto currentName = _currentToken.text; 1036 names ~= currentName; 1037 } 1038 nextToken(); 1039 if(_currentToken.type == Token.Type.COMMA) 1040 nextToken(); // eat the , 1041 else if(_currentToken.type != endTokenType && _currentToken.type != Token.Type.EOF) 1042 throw new ScriptCompileException("Destructuring variable names must be separated by comma", 1043 _currentToken); 1044 } 1045 if(names.length == 0 && remainderName == "") 1046 throw new ScriptCompileException("Destructuring declaration cannot be empty", _currentToken); 1047 nextToken(); // eat the } or ] 1048 if(_currentToken.type != Token.Type.ASSIGN) 1049 throw new ScriptCompileException("Destructuring declarations must be assignments", _currentToken); 1050 immutable assignToken = _currentToken; 1051 nextToken(); // eat the = 1052 auto assignment = parseExpression(); 1053 expressions ~= new BinaryOpNode(assignToken, new DestructureTargetNode(names, remainderName, isObj), 1054 assignment); 1055 } 1056 1057 if(_currentToken.type == Token.Type.COMMA) 1058 { 1059 nextToken(); 1060 } 1061 else if(_currentToken.type != Token.Type.SEMICOLON && _currentToken.type != Token.Type.EOF 1062 && !_currentToken.isIdentifier("of") && !_currentToken.isKeyword("in")) 1063 { 1064 throw new ScriptCompileException("Expected ',' between variable declarations (or missing ';')", 1065 _currentToken); 1066 } 1067 } 1068 // make sure all expressions are valid BinaryOpNodes or VarAccessNodes 1069 foreach(expression; expressions) 1070 { 1071 if(auto node = cast(BinaryOpNode)expression) 1072 { 1073 if(!(cast(VarAccessNode)node.leftNode || cast(DestructureTargetNode)node.leftNode)) 1074 throw new ScriptCompileException("Invalid assignment node", _currentToken); 1075 if(node.opToken.type != Token.Type.ASSIGN) 1076 throw new ScriptCompileException("Invalid assignment statement", node.opToken); 1077 } 1078 else if(!cast(VarAccessNode)expression) 1079 { 1080 throw new ScriptCompileException("Invalid variable name in declaration", _currentToken); 1081 } 1082 } 1083 if(consumeSemicolon) 1084 nextToken(); // eat semicolon 1085 return new VarDeclarationStatementNode(specifier, expressions); 1086 } 1087 1088 IfStatementNode parseIfStatement() 1089 { 1090 immutable lineNumber = _currentToken.position.line; 1091 nextToken(); 1092 if(_currentToken.type != Token.Type.LPAREN) 1093 throw new ScriptCompileException("Expected '(' after if keyword", _currentToken); 1094 nextToken(); 1095 auto condition = parseExpression(); 1096 if(_currentToken.type != Token.Type.RPAREN) 1097 throw new ScriptCompileException("Expected ')' after if condition", _currentToken); 1098 nextToken(); 1099 auto ifTrueStatement = parseStatement(); 1100 StatementNode elseStatement = null; 1101 if(_currentToken.isKeyword("else")) 1102 { 1103 nextToken(); 1104 elseStatement = parseStatement(); 1105 } 1106 return new IfStatementNode(lineNumber, condition, ifTrueStatement, elseStatement); 1107 } 1108 1109 StatementNode parseLoopStatement() 1110 { 1111 string label = ""; 1112 if(_currentToken.type == Token.Type.LABEL) 1113 { 1114 label = _currentToken.text; 1115 _functionContextStack.top.labelStack ~= label; 1116 nextToken(); 1117 } 1118 StatementNode statement; 1119 if(_currentToken.isKeyword("while")) 1120 { 1121 ++_functionContextStack.top.loopStack; 1122 statement = parseWhileStatement(label); 1123 --_functionContextStack.top.loopStack; 1124 } 1125 // check for do-while statement TODO check for label 1126 else if(_currentToken.isKeyword("do")) 1127 { 1128 ++_functionContextStack.top.loopStack; 1129 statement = parseDoWhileStatement(label); 1130 --_functionContextStack.top.loopStack; 1131 } 1132 // check for for loop TODO check label 1133 else if(_currentToken.isKeyword("for")) 1134 { 1135 ++_functionContextStack.top.loopStack; 1136 statement = parseForStatement(label); 1137 --_functionContextStack.top.loopStack; 1138 } 1139 else 1140 { 1141 throw new ScriptCompileException("Labels may only be used before loop statements", 1142 _currentToken); 1143 } 1144 if(label != "") 1145 { 1146 _functionContextStack.top.labelStack = _functionContextStack.top.labelStack[0..$-1]; 1147 } 1148 return statement; 1149 } 1150 1151 WhileStatementNode parseWhileStatement(string label = "") 1152 { 1153 immutable lineNumber = _currentToken.position.line; 1154 nextToken(); 1155 if(_currentToken.type != Token.Type.LPAREN) 1156 throw new ScriptCompileException("Expected '(' after while keyword", _currentToken); 1157 nextToken(); 1158 auto condition = parseExpression(); 1159 if(_currentToken.type != Token.Type.RPAREN) 1160 throw new ScriptCompileException("Expected ')' after while condition", _currentToken); 1161 nextToken(); 1162 auto loopBody = parseStatement(); 1163 return new WhileStatementNode(lineNumber, condition, loopBody, label); 1164 } 1165 1166 DoWhileStatementNode parseDoWhileStatement(string label = "") 1167 { 1168 immutable lineNumber = _currentToken.position.line; 1169 nextToken(); 1170 auto loopBody = parseStatement(); 1171 if(!_currentToken.isKeyword("while")) 1172 throw new ScriptCompileException("Expected while keyword after do statement", _currentToken); 1173 nextToken(); 1174 if(_currentToken.type != Token.Type.LPAREN) 1175 throw new ScriptCompileException("Expected '(' before do-while condition", _currentToken); 1176 nextToken(); 1177 auto condition = parseExpression(); 1178 if(_currentToken.type != Token.Type.RPAREN) 1179 throw new ScriptCompileException("Expected ')' after do-while condition", _currentToken); 1180 nextToken(); 1181 if(_currentToken.type != Token.Type.SEMICOLON) 1182 throw new ScriptCompileException("Expected ';' after do-while statement", _currentToken); 1183 nextToken(); 1184 return new DoWhileStatementNode(lineNumber, loopBody, condition, label); 1185 } 1186 1187 StatementNode parseForStatement(string label = "") 1188 { 1189 immutable lineNumber = _currentToken.position.line; 1190 nextToken(); 1191 if(_currentToken.type != Token.Type.LPAREN) 1192 throw new ScriptCompileException("Expected '(' after for keyword", _currentToken); 1193 nextToken(); 1194 VarDeclarationStatementNode decl = null; 1195 if(_currentToken.type != Token.Type.SEMICOLON) 1196 decl = parseVarDeclarationStatement(false); 1197 if(_currentToken.isKeyword("in") || _currentToken.isIdentifier("of")) 1198 { 1199 immutable ofInToken = _currentToken; 1200 // first we need to validate the VarDeclarationStatementNode to make sure it only consists 1201 // of let or const and VarAccessNodes 1202 if(decl is null) 1203 throw new ScriptCompileException("Invalid for in statement", _currentToken); 1204 Token qualifier; 1205 VarAccessNode[] vans; 1206 if(decl.qualifier.text != "const" && decl.qualifier.text != "let") 1207 throw new ScriptCompileException("Global variable declaration invalid in for in statement", 1208 decl.qualifier); 1209 int vanCount = 0; 1210 foreach(va ; decl.varAccessOrAssignmentNodes) 1211 { 1212 auto valid = cast(VarAccessNode)va; 1213 if(valid is null) 1214 throw new ScriptCompileException("Invalid variable declaration in for in statement", 1215 _currentToken); 1216 vans ~= valid; 1217 ++vanCount; 1218 } 1219 if(vanCount > 2) 1220 throw new ScriptCompileException("For in loops may only have one or two variable declarations", 1221 _currentToken); 1222 nextToken(); 1223 auto objToIterateExpr = parseExpression(); 1224 if(_currentToken.type != Token.Type.RPAREN) 1225 throw new ScriptCompileException("Expected ')' after array or object", _currentToken); 1226 nextToken(); 1227 auto bodyStatement = parseStatement(); 1228 return new ForOfStatementNode(lineNumber, qualifier, ofInToken, vans, objToIterateExpr, 1229 bodyStatement, label); 1230 } 1231 else if(_currentToken.type == Token.Type.SEMICOLON) 1232 { 1233 nextToken(); 1234 ExpressionNode condition = null; 1235 if(_currentToken.type != Token.Type.SEMICOLON) 1236 { 1237 condition = parseExpression(); 1238 if(_currentToken.type != Token.Type.SEMICOLON) 1239 throw new ScriptCompileException("Expected ';' after for condition", _currentToken); 1240 } 1241 else 1242 { 1243 condition = new LiteralNode(_currentToken, ScriptAny(true)); 1244 } 1245 nextToken(); 1246 ExpressionNode increment = null; 1247 if(_currentToken.type != Token.Type.RPAREN) 1248 { 1249 increment = parseExpression(); 1250 } 1251 else 1252 { 1253 increment = new LiteralNode(_currentToken, ScriptAny(true)); 1254 } 1255 if(_currentToken.type != Token.Type.RPAREN) 1256 throw new ScriptCompileException("Expected ')' before for loop body", _currentToken); 1257 nextToken(); 1258 auto bodyNode = parseStatement(); 1259 return new ForStatementNode(lineNumber, decl, condition, increment, bodyNode, label); 1260 } 1261 else 1262 throw new ScriptCompileException("Invalid for statement", _currentToken); 1263 } 1264 1265 FunctionDeclarationStatementNode parseFunctionDeclarationStatement() 1266 { 1267 import std.algorithm: uniq, count; 1268 immutable lineNumber = _currentToken.position.line; 1269 bool isGenerator = false; 1270 nextToken(); // eat the function keyword 1271 1272 if(_currentToken.type == Token.Type.STAR) 1273 { 1274 isGenerator = true; 1275 nextToken(); 1276 } 1277 1278 if(_currentToken.type != Token.Type.IDENTIFIER) 1279 throw new ScriptCompileException("Expected identifier after function keyword", _currentToken); 1280 string name = _currentToken.text; 1281 nextToken(); 1282 1283 if(_currentToken.type == Token.Type.STAR) 1284 { 1285 if(isGenerator) 1286 throw new ScriptCompileException("Only one asterisk allowed to described Generators", _currentToken); 1287 isGenerator = true; 1288 nextToken(); 1289 } 1290 1291 if(_currentToken.type != Token.Type.LPAREN) 1292 throw new ScriptCompileException("Expected '(' after function name", _currentToken); 1293 nextToken(); // eat the ( 1294 string[] argNames = []; 1295 ExpressionNode[] defaultArgs; 1296 argNames = parseArgumentList(defaultArgs); 1297 nextToken(); // eat the ) 1298 1299 // make sure there are no duplicate parameter names 1300 if(argNames.uniq.count != argNames.length) 1301 throw new ScriptCompileException("Function argument names must be unique", _currentToken); 1302 1303 if(_currentToken.type != Token.Type.LBRACE) 1304 throw new ScriptCompileException("Function definition must begin with '{'", _currentToken); 1305 nextToken(); 1306 _functionContextStack.push(FunctionContext( 1307 isGenerator? FunctionContextType.GENERATOR: FunctionContextType.NORMAL, 1308 0, 0, [])); 1309 auto statements = parseStatements(Token.Type.RBRACE); 1310 _functionContextStack.pop(); 1311 nextToken(); // eat the } 1312 return new FunctionDeclarationStatementNode(lineNumber, name, argNames, defaultArgs, statements, isGenerator); 1313 } 1314 1315 TryCatchBlockStatementNode parseTryCatchBlockStatement() 1316 { 1317 immutable lineNumber = _currentToken.position.line; 1318 auto tryToken = _currentToken; 1319 nextToken(); // eat the 'try' 1320 auto tryBlock = parseStatement(); 1321 StatementNode catchBlock = null; 1322 StatementNode finallyBlock = null; 1323 auto name = ""; 1324 if(_currentToken.isKeyword("catch")) 1325 { 1326 nextToken(); // eat the catch 1327 if(_currentToken.type == Token.Type.LPAREN) 1328 { 1329 nextToken(); // eat ( 1330 if(_currentToken.type != Token.Type.IDENTIFIER) 1331 throw new ScriptCompileException("Name of exception required after '('", _currentToken); 1332 name = _currentToken.text; 1333 nextToken(); 1334 if(_currentToken.type != Token.Type.RPAREN) 1335 throw new ScriptCompileException("')' required after exception name", _currentToken); 1336 nextToken(); 1337 } 1338 catchBlock = parseStatement(); 1339 } 1340 if(_currentToken.isKeyword("finally")) 1341 { 1342 nextToken(); 1343 finallyBlock = parseStatement(); 1344 } 1345 // can't be missing both catch and finally 1346 if(catchBlock is null && finallyBlock is null) 1347 throw new ScriptCompileException("Try statements must have catch and/or finally block", tryToken); 1348 return new TryCatchBlockStatementNode(lineNumber, tryBlock, name, catchBlock, finallyBlock); 1349 } 1350 1351 ObjectLiteralNode parseObjectLiteral() 1352 { 1353 nextToken(); // eat the { 1354 string[] keys = []; 1355 ExpressionNode[] valueExpressions = []; 1356 while(_currentToken.type != Token.Type.RBRACE) 1357 { 1358 // first must be an identifier token or string literal token 1359 immutable idToken = _currentToken; 1360 if(_currentToken.type != Token.Type.IDENTIFIER && _currentToken.type != Token.Type.STRING 1361 && _currentToken.type != Token.Type.LABEL) 1362 throw new ScriptCompileException("Invalid key for object literal", _currentToken); 1363 keys ~= _currentToken.text; 1364 1365 nextToken(); 1366 // next must be a : 1367 if(idToken.type != Token.Type.LABEL) 1368 { 1369 if(_currentToken.type != Token.Type.COLON) 1370 throw new ScriptCompileException("Expected ':' after key in object literal", _currentToken); 1371 nextToken(); 1372 } 1373 // next can be any valid expression 1374 valueExpressions ~= parseExpression(); 1375 // if next is not a comma it must be a closing brace to exit 1376 if(_currentToken.type == Token.Type.COMMA) 1377 nextToken(); 1378 else if(_currentToken.type != Token.Type.RBRACE) 1379 throw new ScriptCompileException("Key value pairs must be separated by ','", _currentToken); 1380 } 1381 nextToken(); // eat the } 1382 if(keys.length != valueExpressions.length) 1383 throw new ScriptCompileException("Number of keys must match values in object literal", _currentToken); 1384 return new ObjectLiteralNode(keys, valueExpressions); 1385 } 1386 1387 ClassDeclarationStatementNode parseClassDeclaration() 1388 { 1389 immutable lineNumber = _currentToken.position.line; 1390 immutable classToken = _currentToken; 1391 nextToken(); 1392 if(_currentToken.type != Token.Type.IDENTIFIER) 1393 throw new ScriptCompileException("Class name must be valid identifier", _currentToken); 1394 auto className = _currentToken.text; 1395 nextToken(); 1396 ExpressionNode baseClass = null; 1397 if(_currentToken.isKeyword("extends")) 1398 { 1399 nextToken(); 1400 baseClass = parseExpression(); // let's hope this is an expression that results in a ScriptFunction value 1401 _baseClassStack ~= baseClass; 1402 } 1403 auto classDef = parseClassDefinition(classToken, className, baseClass); 1404 return new ClassDeclarationStatementNode(lineNumber, classToken, classDef); 1405 } 1406 1407 SwitchStatementNode parseSwitchStatement() 1408 { 1409 ++_functionContextStack.top.switchStack; 1410 immutable lineNumber = _currentToken.position.line; 1411 immutable switchToken = _currentToken; 1412 nextToken(); 1413 if(_currentToken.type != Token.Type.LPAREN) 1414 throw new ScriptCompileException("Expected '(' after switch keyword", _currentToken); 1415 nextToken(); 1416 auto expression = parseExpression(); 1417 if(_currentToken.type != Token.Type.RPAREN) 1418 throw new ScriptCompileException("Expected ')' after switch expression", _currentToken); 1419 nextToken(); 1420 if(_currentToken.type != Token.Type.LBRACE) 1421 throw new ScriptCompileException("Expected '{' to begin switch body", _currentToken); 1422 nextToken(); 1423 bool caseStarted = false; 1424 size_t statementCounter = 0; 1425 StatementNode[] statementNodes; 1426 size_t defaultStatementID = size_t.max; 1427 size_t[ScriptAny] jumpTable; 1428 while(_currentToken.type != Token.Type.RBRACE) 1429 { 1430 if(_currentToken.isKeyword("case")) 1431 { 1432 nextToken(); 1433 caseStarted = true; 1434 auto caseExpression = parseExpression(); 1435 // it has to be evaluatable at compile time 1436 auto result = evaluateCTFE(caseExpression); 1437 if(result == ScriptAny.UNDEFINED) 1438 throw new ScriptCompileException( 1439 "Case expression must be determined at compile time and cannot be undefined", 1440 switchToken); 1441 if(_currentToken.type != Token.Type.COLON) 1442 throw new ScriptCompileException("Expected ':' after case expression", _currentToken); 1443 nextToken(); 1444 if(result in jumpTable) 1445 throw new ScriptCompileException("Duplicate case entries not allowed", switchToken); 1446 jumpTable[result] = statementCounter; 1447 } 1448 else if(_currentToken.isKeyword("default")) 1449 { 1450 caseStarted = true; 1451 nextToken(); 1452 if(_currentToken.type != Token.Type.COLON) 1453 throw new ScriptCompileException("':' expected after default keyword", _currentToken); 1454 nextToken(); 1455 defaultStatementID = statementCounter; 1456 } 1457 else 1458 { 1459 if(!caseStarted) 1460 throw new ScriptCompileException("Case condition required before any statements", _currentToken); 1461 statementNodes ~= parseStatement(); 1462 ++statementCounter; 1463 } 1464 } 1465 nextToken(); // consume } 1466 --_functionContextStack.top.switchStack; 1467 return new SwitchStatementNode(lineNumber, expression, new SwitchBody(statementNodes, defaultStatementID, 1468 jumpTable)); 1469 } 1470 1471 LambdaNode parseLambda(bool hasParentheses) 1472 { 1473 string[] argList; 1474 ExpressionNode[] defaultArgs; 1475 if(hasParentheses) 1476 { 1477 nextToken(); // eat the ( 1478 argList = parseArgumentList(defaultArgs); 1479 nextToken(); // eat the ) 1480 } 1481 else 1482 { 1483 if(_currentToken.type != Token.Type.IDENTIFIER) 1484 throw new ScriptCompileException("Lambda argument name must be valid identifier", _currentToken); 1485 argList ~= _currentToken.text; 1486 nextToken(); 1487 } 1488 // make sure arrow 1489 if(_currentToken.type != Token.Type.ARROW) 1490 throw new ScriptCompileException("Arrow expected after lambda argument list", _currentToken); 1491 auto arrow = _currentToken; 1492 nextToken(); 1493 // either a single expression, or body marked by { 1494 if(_currentToken.type == Token.Type.LBRACE) 1495 { 1496 nextToken(); // eat the { 1497 auto stmts = parseStatements(Token.Type.RBRACE); 1498 nextToken(); // eat the } 1499 return new LambdaNode(arrow, argList, defaultArgs, stmts); 1500 } 1501 else 1502 { 1503 auto expr = parseExpression(); 1504 return new LambdaNode(arrow, argList, defaultArgs, expr); 1505 } 1506 } 1507 1508 ExpressionNode[] parseCommaSeparatedExpressions(in Token.Type stop) 1509 { 1510 ExpressionNode[] expressions; 1511 1512 while(_currentToken.type != stop && _currentToken.type != Token.Type.EOF && !_currentToken.isIdentifier("of") 1513 && !_currentToken.isKeyword("in")) 1514 { 1515 auto expression = parseExpression(); 1516 expressions ~= expression; 1517 if(_currentToken.type == Token.Type.COMMA) 1518 nextToken(); 1519 else if(_currentToken.type != stop 1520 && !_currentToken.isIdentifier("of") 1521 && !_currentToken.isKeyword("in")) 1522 throw new ScriptCompileException("Comma separated list items must be separated by ','" 1523 ~ " (or missing '" ~ Token.createFakeToken(stop, "").symbol ~ "')", 1524 _currentToken); 1525 } 1526 1527 return expressions; 1528 } 1529 1530 string[] parseArgumentList(out ExpressionNode[] defaultArgs) 1531 { 1532 string[] argList; 1533 defaultArgs = []; 1534 while(_currentToken.type != Token.Type.RPAREN && _currentToken.type != Token.Type.EOF) 1535 { 1536 if(_currentToken.type != Token.Type.IDENTIFIER) 1537 throw new ScriptCompileException("Argument name must be identifier", _currentToken); 1538 argList ~= _currentToken.text; 1539 nextToken(); // eat the identifier 1540 1541 if(_currentToken.type == Token.Type.ASSIGN) 1542 { 1543 nextToken(); // eat = 1544 defaultArgs ~= parseExpression(); 1545 } 1546 else if(defaultArgs.length != 0) 1547 { 1548 throw new ScriptCompileException("Default arguments must be last arguments", _currentToken); 1549 } 1550 1551 if(_currentToken.type == Token.Type.COMMA) 1552 nextToken(); // eat , 1553 else if(_currentToken.type != Token.Type.RPAREN) 1554 throw new ScriptCompileException("Argument names must be separated by comma", _currentToken); 1555 } 1556 return argList; 1557 } 1558 1559 void nextToken() 1560 { 1561 if(_tokenIndex >= _tokens.length) 1562 _currentToken = Token(Token.Type.EOF); 1563 else 1564 _currentToken = _tokens[_tokenIndex++]; 1565 } 1566 1567 void putbackToken() 1568 { 1569 if(_tokenIndex > 0) 1570 _currentToken = _tokens[--_tokenIndex]; 1571 } 1572 1573 Token peekToken() 1574 { 1575 return peekTokens(1)[0]; 1576 } 1577 1578 Token[] peekTokens(int numToPeek) 1579 { 1580 Token[] list; 1581 for(size_t i = _tokenIndex; i < _tokenIndex+numToPeek; ++i) 1582 { 1583 if(i < _tokens.length) 1584 list ~= _tokens[i]; 1585 else 1586 list ~= Token.createFakeToken(Token.Type.EOF, ""); 1587 } 1588 return list; 1589 } 1590 1591 enum FunctionContextType {NORMAL, CONSTRUCTOR, METHOD, GENERATOR} 1592 1593 struct FunctionContext 1594 { 1595 FunctionContextType fct; 1596 int loopStack; 1597 int switchStack; 1598 string[] labelStack; 1599 } 1600 1601 Token[] _tokens; 1602 size_t _tokenIndex = 0; 1603 Token _currentToken; 1604 Stack!FunctionContext _functionContextStack; 1605 ExpressionNode[] _baseClassStack; // in case we have nested class declarations 1606 }