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