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