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