001 /* 002 * Copyright 2008 Google Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package com.google.gwt.dev.js; 017 018 import com.google.dart.compiler.backend.js.ast.*; 019 import com.google.dart.compiler.backend.js.ast.JsLiteral.JsBooleanLiteral; 020 import com.google.dart.compiler.common.SourceInfo; 021 import com.google.gwt.dev.js.parserExceptions.JsParserException; 022 import com.google.gwt.dev.js.rhino.*; 023 import com.intellij.util.SmartList; 024 import org.jetbrains.annotations.NotNull; 025 import org.jetbrains.annotations.Nullable; 026 027 import java.io.IOException; 028 import java.io.Reader; 029 import java.util.ArrayList; 030 import java.util.List; 031 032 import static com.google.dart.compiler.util.AstUtil.toFunctionScope; 033 034 /** 035 * Parses JavaScript source. 036 */ 037 public class JsParser { 038 039 private final JsProgram program; 040 private final ScopeContext scopeContext; 041 042 public static List<JsStatement> parse( 043 SourceInfo rootSourceInfo, 044 JsScope scope, Reader r, 045 ErrorReporter errorReporter, 046 boolean insideFunction 047 ) throws IOException, JsParserException { 048 return new JsParser(scope).parseImpl(rootSourceInfo, r, errorReporter, insideFunction); 049 } 050 051 /** 052 * since source maps are not mapped to kotlin source maps 053 */ 054 private static final String SOURCE_NAME_STUB = "jsCode"; 055 056 private JsParser(@NotNull JsScope scope) { 057 scopeContext = new ScopeContext(scope); 058 program = scope.getProgram(); 059 } 060 061 List<JsStatement> parseImpl( 062 final SourceInfo rootSourceInfo, 063 Reader r, 064 ErrorReporter errorReporter, 065 boolean insideFunction 066 ) throws JsParserException, IOException { 067 // Create a custom error handler so that we can throw our own exceptions. 068 Context.enter().setErrorReporter(errorReporter); 069 try { 070 // Parse using the Rhino parser. 071 // 072 TokenStream ts = new TokenStream(r, SOURCE_NAME_STUB, rootSourceInfo.getLine()); 073 Parser parser = new Parser(new IRFactory(ts), insideFunction); 074 Node topNode = (Node) parser.parse(ts); 075 return mapStatements(topNode); 076 } 077 finally { 078 Context.exit(); 079 } 080 } 081 082 private static JsParserException createParserException(String msg, Node offender) { 083 CodePosition position = new CodePosition(offender.getLineno(), 0); 084 return new JsParserException("Parser encountered internal error: " + msg, position); 085 } 086 087 private JsNode map(Node node) throws JsParserException { 088 switch (node.getType()) { 089 case TokenStream.SCRIPT: { 090 JsBlock block = new JsBlock(); 091 mapStatements(block.getStatements(), node); 092 return block; 093 } 094 095 case TokenStream.DEBUGGER: 096 return mapDebuggerStatement(node); 097 098 case TokenStream.VOID: 099 // VOID = nothing was parsed for this node 100 return null; 101 102 case TokenStream.EXPRSTMT: 103 return mapExpressionStatement(node); 104 105 case TokenStream.REGEXP: 106 return mapRegExp(node); 107 108 case TokenStream.ADD: 109 return mapBinaryOperation(JsBinaryOperator.ADD, node); 110 111 case TokenStream.SUB: 112 return mapBinaryOperation(JsBinaryOperator.SUB, node); 113 114 case TokenStream.MUL: 115 return mapBinaryOperation(JsBinaryOperator.MUL, node); 116 117 case TokenStream.DIV: 118 return mapBinaryOperation(JsBinaryOperator.DIV, node); 119 120 case TokenStream.MOD: 121 return mapBinaryOperation(JsBinaryOperator.MOD, node); 122 123 case TokenStream.AND: 124 return mapBinaryOperation(JsBinaryOperator.AND, node); 125 126 case TokenStream.OR: 127 return mapBinaryOperation(JsBinaryOperator.OR, node); 128 129 case TokenStream.BITAND: 130 return mapBinaryOperation(JsBinaryOperator.BIT_AND, node); 131 132 case TokenStream.BITOR: 133 return mapBinaryOperation(JsBinaryOperator.BIT_OR, node); 134 135 case TokenStream.BITXOR: 136 return mapBinaryOperation(JsBinaryOperator.BIT_XOR, node); 137 138 case TokenStream.ASSIGN: 139 return mapAssignmentVariant(node); 140 141 case TokenStream.RELOP: 142 return mapRelationalVariant(node); 143 144 case TokenStream.EQOP: 145 return mapEqualityVariant(node); 146 147 case TokenStream.SHOP: 148 return mapShiftVariant(node); 149 150 case TokenStream.UNARYOP: 151 return mapUnaryVariant(node); 152 153 case TokenStream.INC: 154 return mapIncDecFixity(JsUnaryOperator.INC, node); 155 156 case TokenStream.DEC: 157 return mapIncDecFixity(JsUnaryOperator.DEC, node); 158 159 case TokenStream.HOOK: 160 return mapConditional(node); 161 162 case TokenStream.STRING: 163 return program.getStringLiteral(node.getString()); 164 165 case TokenStream.NUMBER_INT: 166 return mapIntNumber(node); 167 168 case TokenStream.NUMBER: 169 return mapDoubleNumber(node); 170 171 case TokenStream.CALL: 172 return mapCall(node); 173 174 case TokenStream.GETPROP: 175 return mapGetProp(node); 176 177 case TokenStream.SETPROP: 178 return mapSetProp(node); 179 180 case TokenStream.DELPROP: 181 return mapDeleteProp(node); 182 183 case TokenStream.IF: 184 return mapIfStatement(node); 185 186 case TokenStream.WHILE: 187 return mapDoOrWhileStatement(true, node); 188 189 case TokenStream.DO: 190 return mapDoOrWhileStatement(false, node); 191 192 case TokenStream.FOR: 193 return mapForStatement(node); 194 195 case TokenStream.WITH: 196 return mapWithStatement(node); 197 198 case TokenStream.GETELEM: 199 return mapGetElem(node); 200 201 case TokenStream.SETELEM: 202 return mapSetElem(node); 203 204 case TokenStream.FUNCTION: 205 return mapFunction(node); 206 207 case TokenStream.BLOCK: 208 return mapBlock(node); 209 210 case TokenStream.SETNAME: 211 return mapBinaryOperation(JsBinaryOperator.ASG, node); 212 213 case TokenStream.NAME: 214 case TokenStream.BINDNAME: 215 return scopeContext.globalNameFor(node.getString()).makeRef(); 216 217 case TokenStream.RETURN: 218 return mapReturn(node); 219 220 case TokenStream.BREAK: 221 return mapBreak(node); 222 223 case TokenStream.CONTINUE: 224 return mapContinue(node); 225 226 case TokenStream.OBJLIT: 227 return mapObjectLit(node); 228 229 case TokenStream.ARRAYLIT: 230 return mapArrayLit(node); 231 232 case TokenStream.VAR: 233 return mapVar(node); 234 235 case TokenStream.PRIMARY: 236 return mapPrimary(node); 237 238 case TokenStream.COMMA: 239 return mapBinaryOperation(JsBinaryOperator.COMMA, node); 240 241 case TokenStream.NEW: 242 return mapNew(node); 243 244 case TokenStream.THROW: 245 return mapThrowStatement(node); 246 247 case TokenStream.TRY: 248 return mapTryStatement(node); 249 250 case TokenStream.SWITCH: 251 return mapSwitchStatement(node); 252 253 case TokenStream.LABEL: 254 return mapLabel(node); 255 256 default: 257 int tokenType = node.getType(); 258 throw createParserException("Unexpected top-level token type: " 259 + tokenType, node); 260 } 261 } 262 263 private JsArrayLiteral mapArrayLit(Node node) throws JsParserException { 264 JsArrayLiteral toLit = new JsArrayLiteral(); 265 Node from = node.getFirstChild(); 266 while (from != null) { 267 toLit.getExpressions().add(mapExpression(from)); 268 from = from.getNext(); 269 } 270 return toLit; 271 } 272 273 /** 274 * Produces a {@link JsNameRef}. 275 */ 276 private JsNameRef mapAsPropertyNameRef(Node nameRefNode) 277 throws JsParserException { 278 JsNode unknown = map(nameRefNode); 279 // This is weird, but for "a.b", the rhino AST calls "b" a string literal. 280 // However, since we know it's for a PROPGET, we can unstringliteralize it. 281 // 282 if (unknown instanceof JsStringLiteral) { 283 JsStringLiteral lit = (JsStringLiteral) unknown; 284 return scopeContext.referenceFor(lit.getValue()); 285 } 286 else { 287 throw createParserException("Expecting a name reference", nameRefNode); 288 } 289 } 290 291 private JsExpression mapAssignmentVariant(Node asgNode) 292 throws JsParserException { 293 switch (asgNode.getIntDatum()) { 294 case TokenStream.NOP: 295 return mapBinaryOperation(JsBinaryOperator.ASG, asgNode); 296 297 case TokenStream.ADD: 298 return mapBinaryOperation(JsBinaryOperator.ASG_ADD, asgNode); 299 300 case TokenStream.SUB: 301 return mapBinaryOperation(JsBinaryOperator.ASG_SUB, asgNode); 302 303 case TokenStream.MUL: 304 return mapBinaryOperation(JsBinaryOperator.ASG_MUL, asgNode); 305 306 case TokenStream.DIV: 307 return mapBinaryOperation(JsBinaryOperator.ASG_DIV, asgNode); 308 309 case TokenStream.MOD: 310 return mapBinaryOperation(JsBinaryOperator.ASG_MOD, asgNode); 311 312 case TokenStream.BITAND: 313 return mapBinaryOperation(JsBinaryOperator.ASG_BIT_AND, asgNode); 314 315 case TokenStream.BITOR: 316 return mapBinaryOperation(JsBinaryOperator.ASG_BIT_OR, asgNode); 317 318 case TokenStream.BITXOR: 319 return mapBinaryOperation(JsBinaryOperator.ASG_BIT_XOR, asgNode); 320 321 case TokenStream.LSH: 322 return mapBinaryOperation(JsBinaryOperator.ASG_SHL, asgNode); 323 324 case TokenStream.RSH: 325 return mapBinaryOperation(JsBinaryOperator.ASG_SHR, asgNode); 326 327 case TokenStream.URSH: 328 return mapBinaryOperation(JsBinaryOperator.ASG_SHRU, asgNode); 329 330 default: 331 throw createParserException("Unknown assignment operator variant: " 332 + asgNode.getIntDatum(), asgNode); 333 } 334 } 335 336 private JsExpression mapBinaryOperation(JsBinaryOperator op, Node node) 337 throws JsParserException { 338 Node from1 = node.getFirstChild(); 339 Node from2 = from1.getNext(); 340 341 JsExpression to1 = mapExpression(from1); 342 JsExpression to2 = mapExpression(from2); 343 344 return new JsBinaryOperation(op, to1, to2); 345 } 346 347 private JsBlock mapBlock(Node nodeStmts) throws JsParserException { 348 JsBlock block = new JsBlock(); 349 mapStatements(block.getStatements(), nodeStmts); 350 return block; 351 } 352 353 private JsBreak mapBreak(Node breakNode) { 354 return new JsBreak(getTargetLabel(breakNode)); 355 } 356 357 @Nullable 358 private JsNameRef getTargetLabel(@NotNull Node statementWithLabel) { 359 int type = statementWithLabel.getType(); 360 if (type != TokenStream.BREAK && type != TokenStream.CONTINUE) { 361 String tokenTypeName = TokenStream.tokenToName(statementWithLabel.getType()); 362 throw new AssertionError("Unexpected node type with label: " + tokenTypeName); 363 } 364 365 Node label = statementWithLabel.getFirstChild(); 366 if (label == null) return null; 367 368 String identifier = label.getString(); 369 assert identifier != null: "If label exists identifier should not be null"; 370 371 JsName labelName = scopeContext.labelFor(identifier); 372 assert labelName != null: "Unknown label name: " + identifier; 373 374 return labelName.makeRef(); 375 } 376 377 private JsInvocation mapCall(Node callNode) throws JsParserException { 378 // Map the target expression. 379 // 380 Node from = callNode.getFirstChild(); 381 JsExpression qualifier = mapExpression(from); 382 383 // Iterate over and map the arguments. 384 // 385 List<JsExpression> arguments = new SmartList<JsExpression>(); 386 from = from.getNext(); 387 while (from != null) { 388 arguments.add(mapExpression(from)); 389 from = from.getNext(); 390 } 391 392 return new JsInvocation(qualifier, arguments); 393 } 394 395 private JsExpression mapConditional(Node condNode) throws JsParserException { 396 JsConditional toCond = new JsConditional(); 397 398 Node fromTest = condNode.getFirstChild(); 399 toCond.setTestExpression(mapExpression(fromTest)); 400 401 Node fromThen = fromTest.getNext(); 402 toCond.setThenExpression(mapExpression(fromThen)); 403 404 Node fromElse = fromThen.getNext(); 405 toCond.setElseExpression(mapExpression(fromElse)); 406 407 return toCond; 408 } 409 410 private JsContinue mapContinue(Node contNode) { 411 return new JsContinue(getTargetLabel(contNode)); 412 } 413 414 private JsStatement mapDebuggerStatement(Node node) { 415 // Calls an optional method to invoke the debugger. 416 // 417 return new JsDebugger(); 418 } 419 420 private JsExpression mapDeleteProp(Node node) throws JsParserException { 421 Node from = node.getFirstChild(); 422 JsExpression to = mapExpression(from); 423 if (to instanceof JsNameRef) { 424 return new JsPrefixOperation( 425 JsUnaryOperator.DELETE, to); 426 } 427 else if (to instanceof JsArrayAccess) { 428 return new JsPrefixOperation( 429 JsUnaryOperator.DELETE, to); 430 } 431 else { 432 throw createParserException( 433 "'delete' can only operate on property names and array elements", 434 from); 435 } 436 } 437 438 private JsStatement mapDoOrWhileStatement(boolean isWhile, Node ifNode) 439 throws JsParserException { 440 441 // Pull out the pieces we want to map. 442 // 443 Node fromTestExpr; 444 Node fromBody; 445 if (isWhile) { 446 fromTestExpr = ifNode.getFirstChild(); 447 fromBody = ifNode.getFirstChild().getNext(); 448 } 449 else { 450 fromBody = ifNode.getFirstChild(); 451 fromTestExpr = ifNode.getFirstChild().getNext(); 452 } 453 454 // Map the test expression. 455 // 456 JsExpression toTestExpr = mapExpression(fromTestExpr); 457 458 // Map the body block. 459 // 460 JsStatement toBody = mapStatement(fromBody); 461 462 // Create and attach the "while" or "do" statement we're mapping to. 463 // 464 if (isWhile) { 465 return new JsWhile(toTestExpr, toBody); 466 } 467 else { 468 return new JsDoWhile(toTestExpr, toBody); 469 } 470 } 471 472 private JsExpression mapEqualityVariant(Node eqNode) throws JsParserException { 473 switch (eqNode.getIntDatum()) { 474 case TokenStream.EQ: 475 return mapBinaryOperation(JsBinaryOperator.EQ, eqNode); 476 477 case TokenStream.NE: 478 return mapBinaryOperation(JsBinaryOperator.NEQ, eqNode); 479 480 case TokenStream.SHEQ: 481 return mapBinaryOperation(JsBinaryOperator.REF_EQ, eqNode); 482 483 case TokenStream.SHNE: 484 return mapBinaryOperation(JsBinaryOperator.REF_NEQ, eqNode); 485 486 case TokenStream.LT: 487 return mapBinaryOperation(JsBinaryOperator.LT, eqNode); 488 489 case TokenStream.LE: 490 return mapBinaryOperation(JsBinaryOperator.LTE, eqNode); 491 492 case TokenStream.GT: 493 return mapBinaryOperation(JsBinaryOperator.GT, eqNode); 494 495 case TokenStream.GE: 496 return mapBinaryOperation(JsBinaryOperator.GTE, eqNode); 497 498 default: 499 throw createParserException("Unknown equality operator variant: " 500 + eqNode.getIntDatum(), eqNode); 501 } 502 } 503 504 private JsExpression mapExpression(Node exprNode) throws JsParserException { 505 JsNode unknown = map(exprNode); 506 if (unknown instanceof JsExpression) { 507 return (JsExpression) unknown; 508 } 509 else { 510 throw createParserException("Expecting an expression", exprNode); 511 } 512 } 513 514 private JsStatement mapExpressionStatement(Node node) throws JsParserException { 515 JsExpression expr = mapExpression(node.getFirstChild()); 516 return expr.makeStmt(); 517 } 518 519 private JsStatement mapForStatement(Node forNode) throws JsParserException { 520 Node fromInit = forNode.getFirstChild(); 521 Node fromTest = fromInit.getNext(); 522 Node fromIncr = fromTest.getNext(); 523 Node fromBody = fromIncr.getNext(); 524 525 if (fromBody == null) { 526 // This could be a "for...in" structure. 527 // We could based on the different child layout. 528 // 529 Node fromIter = forNode.getFirstChild(); 530 Node fromObjExpr = fromIter.getNext(); 531 fromBody = fromObjExpr.getNext(); 532 533 JsForIn toForIn; 534 if (fromIter.getType() == TokenStream.VAR) { 535 // A named iterator var. 536 // 537 Node fromIterVarName = fromIter.getFirstChild(); 538 String fromName = fromIterVarName.getString(); 539 JsName toName = scopeContext.localNameFor(fromName); 540 toForIn = new JsForIn(toName); 541 Node fromIterInit = fromIterVarName.getFirstChild(); 542 if (fromIterInit != null) { 543 // That has an initializer expression (useful only for side effects). 544 // 545 toForIn.setIterExpression(mapOptionalExpression(fromIterInit)); 546 } 547 } 548 else { 549 // An unnamed iterator var. 550 // 551 toForIn = new JsForIn(); 552 toForIn.setIterExpression(mapExpression(fromIter)); 553 } 554 toForIn.setObjectExpression(mapExpression(fromObjExpr)); 555 556 // The body stmt. 557 // 558 JsStatement bodyStmt = mapStatement(fromBody); 559 if (bodyStmt != null) { 560 toForIn.setBody(bodyStmt); 561 } 562 else { 563 toForIn.setBody(program.getEmptyStatement()); 564 } 565 566 return toForIn; 567 } 568 else { 569 // Regular ol' for loop. 570 // 571 JsFor toFor; 572 573 // The first item is either an expression or a JsVars. 574 JsNode init = map(fromInit); 575 JsExpression condition = mapOptionalExpression(fromTest); 576 JsExpression increment = mapOptionalExpression(fromIncr); 577 assert (init != null); 578 if (init instanceof JsVars) { 579 toFor = new JsFor((JsVars) init, condition, increment); 580 } 581 else { 582 assert (init instanceof JsExpression); 583 toFor = new JsFor((JsExpression) init, condition, increment); 584 } 585 586 JsStatement bodyStmt = mapStatement(fromBody); 587 if (bodyStmt != null) { 588 toFor.setBody(bodyStmt); 589 } 590 else { 591 toFor.setBody(program.getEmptyStatement()); 592 } 593 return toFor; 594 } 595 } 596 597 private JsExpression mapFunction(Node fnNode) throws JsParserException { 598 599 Node fromFnNameNode = fnNode.getFirstChild(); 600 Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild(); 601 Node fromBodyNode = fnNode.getFirstChild().getNext().getNext(); 602 JsFunction toFn = scopeContext.enterFunction(); 603 604 // Decide the function's name, if any. 605 // 606 String fnNameIdent = fromFnNameNode.getString(); 607 if (fnNameIdent != null && fnNameIdent.length() > 0) { 608 scopeContext.globalNameFor(fnNameIdent); 609 } 610 611 while (fromParamNode != null) { 612 String fromParamName = fromParamNode.getString(); 613 JsName name = scopeContext.localNameFor(fromParamName); 614 toFn.getParameters().add(new JsParameter(name)); 615 fromParamNode = fromParamNode.getNext(); 616 } 617 618 // Map the function's body. 619 // 620 JsBlock toBody = mapBlock(fromBodyNode); 621 toFn.setBody(toBody); 622 623 scopeContext.exitFunction(); 624 return toFn; 625 } 626 627 private JsArrayAccess mapGetElem(Node getElemNode) throws JsParserException { 628 Node from1 = getElemNode.getFirstChild(); 629 Node from2 = from1.getNext(); 630 631 JsExpression to1 = mapExpression(from1); 632 JsExpression to2 = mapExpression(from2); 633 634 return new JsArrayAccess(to1, to2); 635 } 636 637 private JsNameRef mapGetProp(Node getPropNode) throws JsParserException { 638 Node from1 = getPropNode.getFirstChild(); 639 Node from2 = from1.getNext(); 640 641 JsExpression toQualifier = mapExpression(from1); 642 JsNameRef toNameRef; 643 if (from2 != null) { 644 toNameRef = mapAsPropertyNameRef(from2); 645 } 646 else { 647 // Special properties don't have a second expression. 648 // 649 Object obj = getPropNode.getProp(Node.SPECIAL_PROP_PROP); 650 assert (obj instanceof String); 651 toNameRef = scopeContext.referenceFor((String) obj); 652 } 653 toNameRef.setQualifier(toQualifier); 654 655 return toNameRef; 656 } 657 658 private JsIf mapIfStatement(Node ifNode) throws JsParserException { 659 660 // Pull out the pieces we want to map. 661 // 662 Node fromTestExpr = ifNode.getFirstChild(); 663 Node fromThenBlock = ifNode.getFirstChild().getNext(); 664 Node fromElseBlock = ifNode.getFirstChild().getNext().getNext(); 665 666 // Create the "if" statement we're mapping to. 667 // 668 JsIf toIf = new JsIf(); 669 670 // Map the test expression. 671 // 672 JsExpression toTestExpr = mapExpression(fromTestExpr); 673 toIf.setIfExpression(toTestExpr); 674 675 // Map the "then" block. 676 // 677 toIf.setThenStatement(mapStatement(fromThenBlock)); 678 679 // Map the "else" block. 680 // 681 if (fromElseBlock != null) { 682 toIf.setElseStatement(mapStatement(fromElseBlock)); 683 } 684 685 return toIf; 686 } 687 688 private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node) 689 throws JsParserException { 690 switch (node.getIntDatum()) { 691 case TokenStream.PRE: 692 return mapPrefixOperation(op, node); 693 case TokenStream.POST: 694 return mapPostfixOperation(op, node); 695 default: 696 throw createParserException( 697 "Unknown prefix/postfix variant: " + node.getIntDatum(), node); 698 } 699 } 700 701 private JsLabel mapLabel(Node labelNode) throws JsParserException { 702 String fromName = labelNode.getFirstChild().getString(); 703 704 JsName toName = scopeContext.enterLabel(fromName); 705 706 Node fromStmt = labelNode.getFirstChild().getNext(); 707 JsLabel toLabel = new JsLabel(toName); 708 toLabel.setStatement(mapStatement(fromStmt)); 709 710 scopeContext.exitLabel(); 711 712 return toLabel; 713 } 714 715 private JsNew mapNew(Node newNode) throws JsParserException { 716 // Map the constructor expression, which is often just the name of 717 // some lambda. 718 // 719 Node fromCtorExpr = newNode.getFirstChild(); 720 JsNew newExpr = new JsNew( 721 mapExpression(fromCtorExpr)); 722 723 // Iterate over and map the arguments. 724 // 725 List<JsExpression> args = newExpr.getArguments(); 726 Node fromArg = fromCtorExpr.getNext(); 727 while (fromArg != null) { 728 args.add(mapExpression(fromArg)); 729 fromArg = fromArg.getNext(); 730 } 731 732 return newExpr; 733 } 734 735 private JsExpression mapIntNumber(Node numberNode) { 736 return program.getNumberLiteral((int) numberNode.getDouble()); 737 } 738 739 private JsExpression mapDoubleNumber(Node numberNode) { 740 return program.getNumberLiteral(numberNode.getDouble()); 741 } 742 743 private JsExpression mapObjectLit(Node objLitNode) throws JsParserException { 744 JsObjectLiteral toLit = new JsObjectLiteral(); 745 Node fromPropInit = objLitNode.getFirstChild(); 746 while (fromPropInit != null) { 747 748 Node fromLabelExpr = fromPropInit; 749 JsExpression toLabelExpr = mapExpression(fromLabelExpr); 750 751 // Advance to the initializer expression. 752 // 753 fromPropInit = fromPropInit.getNext(); 754 Node fromValueExpr = fromPropInit; 755 if (fromValueExpr == null) { 756 throw createParserException("Expected an init expression for: " 757 + toLabelExpr, objLitNode); 758 } 759 JsExpression toValueExpr = mapExpression(fromValueExpr); 760 761 JsPropertyInitializer toPropInit = new JsPropertyInitializer( 762 toLabelExpr, toValueExpr); 763 toLit.getPropertyInitializers().add(toPropInit); 764 765 // Begin the next property initializer, if there is one. 766 // 767 fromPropInit = fromPropInit.getNext(); 768 } 769 770 return toLit; 771 } 772 773 private JsExpression mapOptionalExpression(Node exprNode) 774 throws JsParserException { 775 JsNode unknown = map(exprNode); 776 if (unknown != null) { 777 if (unknown instanceof JsExpression) { 778 return (JsExpression) unknown; 779 } 780 else { 781 throw createParserException("Expecting an expression or null", exprNode); 782 } 783 } 784 return null; 785 } 786 787 private JsExpression mapPostfixOperation(JsUnaryOperator op, Node node) 788 throws JsParserException { 789 Node from = node.getFirstChild(); 790 JsExpression to = mapExpression(from); 791 return new JsPostfixOperation(op, to); 792 } 793 794 private JsExpression mapPrefixOperation(JsUnaryOperator op, Node node) 795 throws JsParserException { 796 Node from = node.getFirstChild(); 797 JsExpression to = mapExpression(from); 798 return new JsPrefixOperation(op, to); 799 } 800 801 private JsExpression mapPrimary(Node node) throws JsParserException { 802 switch (node.getIntDatum()) { 803 case TokenStream.THIS: 804 return JsLiteral.THIS; 805 806 case TokenStream.TRUE: 807 return JsBooleanLiteral.TRUE; 808 809 case TokenStream.FALSE: 810 return JsBooleanLiteral.FALSE; 811 812 case TokenStream.NULL: 813 return JsNullLiteral.NULL; 814 815 case TokenStream.UNDEFINED: 816 return JsLiteral.UNDEFINED; 817 818 default: 819 throw createParserException("Unknown primary: " + node.getIntDatum(), 820 node); 821 } 822 } 823 824 private JsNode mapRegExp(Node regExpNode) { 825 JsRegExp toRegExp = new JsRegExp(); 826 827 Node fromPattern = regExpNode.getFirstChild(); 828 toRegExp.setPattern(fromPattern.getString()); 829 830 Node fromFlags = fromPattern.getNext(); 831 if (fromFlags != null) { 832 toRegExp.setFlags(fromFlags.getString()); 833 } 834 835 return toRegExp; 836 } 837 838 private JsExpression mapRelationalVariant(Node relNode) 839 throws JsParserException { 840 switch (relNode.getIntDatum()) { 841 case TokenStream.LT: 842 return mapBinaryOperation(JsBinaryOperator.LT, relNode); 843 844 case TokenStream.LE: 845 return mapBinaryOperation(JsBinaryOperator.LTE, relNode); 846 847 case TokenStream.GT: 848 return mapBinaryOperation(JsBinaryOperator.GT, relNode); 849 850 case TokenStream.GE: 851 return mapBinaryOperation(JsBinaryOperator.GTE, relNode); 852 853 case TokenStream.INSTANCEOF: 854 return mapBinaryOperation(JsBinaryOperator.INSTANCEOF, relNode); 855 856 case TokenStream.IN: 857 return mapBinaryOperation(JsBinaryOperator.INOP, relNode); 858 859 default: 860 throw createParserException("Unknown relational operator variant: " 861 + relNode.getIntDatum(), relNode); 862 } 863 } 864 865 private JsReturn mapReturn(Node returnNode) throws JsParserException { 866 JsReturn toReturn = new JsReturn(); 867 Node from = returnNode.getFirstChild(); 868 if (from != null) { 869 JsExpression to = mapExpression(from); 870 toReturn.setExpression(to); 871 } 872 873 return toReturn; 874 } 875 876 private JsExpression mapSetElem(Node setElemNode) throws JsParserException { 877 // Reuse the get elem code. 878 // 879 JsArrayAccess lhs = mapGetElem(setElemNode); 880 881 // Map the RHS. 882 // 883 Node fromRhs = setElemNode.getFirstChild().getNext().getNext(); 884 JsExpression toRhs = mapExpression(fromRhs); 885 886 return new JsBinaryOperation( 887 JsBinaryOperator.ASG, lhs, toRhs); 888 } 889 890 private JsExpression mapSetProp(Node getPropNode) throws JsParserException { 891 // Reuse the get prop code. 892 // 893 JsNameRef lhs = mapGetProp(getPropNode); 894 895 // Map the RHS. 896 // 897 Node fromRhs = getPropNode.getFirstChild().getNext().getNext(); 898 JsExpression toRhs = mapExpression(fromRhs); 899 900 return new JsBinaryOperation( 901 JsBinaryOperator.ASG, lhs, toRhs); 902 } 903 904 private JsExpression mapShiftVariant(Node shiftNode) throws JsParserException { 905 switch (shiftNode.getIntDatum()) { 906 case TokenStream.LSH: 907 return mapBinaryOperation(JsBinaryOperator.SHL, shiftNode); 908 909 case TokenStream.RSH: 910 return mapBinaryOperation(JsBinaryOperator.SHR, shiftNode); 911 912 case TokenStream.URSH: 913 return mapBinaryOperation(JsBinaryOperator.SHRU, shiftNode); 914 915 default: 916 throw createParserException("Unknown equality operator variant: " 917 + shiftNode.getIntDatum(), shiftNode); 918 } 919 } 920 921 private JsStatement mapStatement(Node nodeStmt) throws JsParserException { 922 JsNode unknown = map(nodeStmt); 923 if (unknown != null) { 924 if (unknown instanceof JsStatement) { 925 return (JsStatement) unknown; 926 } 927 else if (unknown instanceof JsExpression) { 928 return ((JsExpression) unknown).makeStmt(); 929 } 930 else { 931 throw createParserException("Expecting a statement", nodeStmt); 932 } 933 } 934 else { 935 // When map() returns null, we return an empty statement. 936 // 937 return program.getEmptyStatement(); 938 } 939 } 940 941 private void mapStatements(List<JsStatement> stmts, Node nodeStmts) 942 throws JsParserException { 943 Node curr = nodeStmts.getFirstChild(); 944 while (curr != null) { 945 JsStatement stmt = mapStatement(curr); 946 if (stmt != null) { 947 stmts.add(stmt); 948 } 949 else { 950 // When mapStatement() returns null, we just ignore it. 951 // 952 } 953 curr = curr.getNext(); 954 } 955 } 956 957 private List<JsStatement> mapStatements(Node nodeStmts) 958 throws JsParserException { 959 List<JsStatement> stmts = new ArrayList<JsStatement>(); 960 mapStatements(stmts, nodeStmts); 961 return stmts; 962 } 963 964 private JsSwitch mapSwitchStatement(Node switchNode) throws JsParserException { 965 JsSwitch toSwitch = new JsSwitch(); 966 967 // The switch expression. 968 // 969 Node fromSwitchExpr = switchNode.getFirstChild(); 970 toSwitch.setExpression(mapExpression(fromSwitchExpr)); 971 972 // The members. 973 // 974 Node fromMember = fromSwitchExpr.getNext(); 975 while (fromMember != null) { 976 if (fromMember.getType() == TokenStream.CASE) { 977 JsCase toCase = new JsCase(); 978 979 // Set the case expression. In JS, this can be any expression. 980 // 981 Node fromCaseExpr = fromMember.getFirstChild(); 982 toCase.setCaseExpression(mapExpression(fromCaseExpr)); 983 984 // Set the case statements. 985 // 986 Node fromCaseBlock = fromCaseExpr.getNext(); 987 mapStatements(toCase.getStatements(), fromCaseBlock); 988 989 // Attach the case to the switch. 990 // 991 toSwitch.getCases().add(toCase); 992 } 993 else { 994 // This should be the only default statement. 995 // If more than one is present, we keep the last one. 996 // 997 assert (fromMember.getType() == TokenStream.DEFAULT); 998 JsDefault toDefault = new JsDefault(); 999 1000 // Set the default statements. 1001 // 1002 Node fromDefaultBlock = fromMember.getFirstChild(); 1003 mapStatements(toDefault.getStatements(), fromDefaultBlock); 1004 1005 // Attach the default to the switch. 1006 // 1007 toSwitch.getCases().add(toDefault); 1008 } 1009 fromMember = fromMember.getNext(); 1010 } 1011 1012 return toSwitch; 1013 } 1014 1015 private JsThrow mapThrowStatement(Node throwNode) throws JsParserException { 1016 // Create, map, and attach. 1017 // 1018 Node fromExpr = throwNode.getFirstChild(); 1019 JsThrow toThrow = new JsThrow(mapExpression(fromExpr)); 1020 1021 return toThrow; 1022 } 1023 1024 private JsTry mapTryStatement(Node tryNode) throws JsParserException { 1025 JsTry toTry = new JsTry(); 1026 1027 // Map the "try" body. 1028 // 1029 Node fromTryBody = tryNode.getFirstChild(); 1030 toTry.setTryBlock(mapBlock(fromTryBody)); 1031 1032 // Map zero or more catch blocks. 1033 // 1034 Node fromCatchNodes = fromTryBody.getNext(); 1035 Node fromCatchNode = fromCatchNodes.getFirstChild(); 1036 while (fromCatchNode != null) { 1037 assert (fromCatchNode.getType() == TokenStream.CATCH); 1038 // Map the catch variable. 1039 // 1040 Node fromCatchVarName = fromCatchNode.getFirstChild(); 1041 JsCatch catchBlock = scopeContext.enterCatch(fromCatchVarName.getString()); 1042 1043 // Pre-advance to the next catch block, if any. 1044 // We do this here to decide whether or not this is the last one. 1045 // 1046 fromCatchNode = fromCatchNode.getNext(); 1047 1048 // Map the condition, with a little fixup based on whether or not 1049 // this is the last catch block. 1050 // 1051 Node fromCondition = fromCatchVarName.getNext(); 1052 JsExpression toCondition = mapExpression(fromCondition); 1053 catchBlock.setCondition(toCondition); 1054 if (fromCatchNode == null) { 1055 if (toCondition instanceof JsBooleanLiteral) { 1056 if (((JsBooleanLiteral) toCondition).getValue()) { 1057 // Actually, this is an unconditional catch block. 1058 // Indicate that by nulling the condition. 1059 // 1060 catchBlock.setCondition(null); 1061 } 1062 } 1063 } 1064 1065 // Map the catch body. 1066 // 1067 Node fromCatchBody = fromCondition.getNext(); 1068 catchBlock.setBody(mapBlock(fromCatchBody)); 1069 1070 // Attach it. 1071 // 1072 toTry.getCatches().add(catchBlock); 1073 scopeContext.exitCatch(); 1074 } 1075 1076 Node fromFinallyNode = fromCatchNodes.getNext(); 1077 if (fromFinallyNode != null) { 1078 toTry.setFinallyBlock(mapBlock(fromFinallyNode)); 1079 } 1080 1081 return toTry; 1082 } 1083 1084 private JsExpression mapUnaryVariant(Node unOp) throws JsParserException { 1085 switch (unOp.getIntDatum()) { 1086 case TokenStream.SUB: 1087 return mapPrefixOperation(JsUnaryOperator.NEG, unOp); 1088 1089 case TokenStream.NOT: 1090 return mapPrefixOperation(JsUnaryOperator.NOT, unOp); 1091 1092 case TokenStream.BITNOT: 1093 return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp); 1094 1095 case TokenStream.TYPEOF: 1096 return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp); 1097 1098 case TokenStream.ADD: 1099 if (!isJsNumber(unOp.getFirstChild())) { 1100 return mapPrefixOperation(JsUnaryOperator.POS, unOp); 1101 } 1102 else { 1103 // Pretend we didn't see it. 1104 return mapExpression(unOp.getFirstChild()); 1105 } 1106 1107 case TokenStream.VOID: 1108 return mapPrefixOperation(JsUnaryOperator.VOID, unOp); 1109 1110 default: 1111 throw createParserException( 1112 "Unknown unary operator variant: " + unOp.getIntDatum(), unOp); 1113 } 1114 } 1115 1116 private JsVars mapVar(Node varNode) throws JsParserException { 1117 JsVars toVars = new JsVars(); 1118 Node fromVar = varNode.getFirstChild(); 1119 while (fromVar != null) { 1120 // Use a conservative name allocation strategy that allocates all names 1121 // from the function's scope, even the names of properties in field 1122 // literals. 1123 // 1124 String fromName = fromVar.getString(); 1125 JsName toName = scopeContext.localNameFor(fromName); 1126 JsVars.JsVar toVar = new JsVars.JsVar(toName); 1127 1128 Node fromInit = fromVar.getFirstChild(); 1129 if (fromInit != null) { 1130 JsExpression toInit = mapExpression(fromInit); 1131 toVar.setInitExpression(toInit); 1132 } 1133 toVars.add(toVar); 1134 1135 fromVar = fromVar.getNext(); 1136 } 1137 1138 return toVars; 1139 } 1140 1141 private JsNode mapWithStatement(Node withNode) throws JsParserException { 1142 // The "with" statement is unsupported because it introduces ambiguity 1143 // related to whether or not a name is obfuscatable that we cannot resolve 1144 // statically. This is modified in our copy of the Rhino Parser to provide 1145 // detailed source & line info. So, this method should never actually be 1146 // called. 1147 // 1148 throw createParserException("Internal error: unexpected token 'with'", 1149 withNode); 1150 } 1151 1152 private boolean isJsNumber(Node jsNode) { 1153 int type = jsNode.getType(); 1154 return type == TokenStream.NUMBER || type == TokenStream.NUMBER; 1155 } 1156 }