0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev.js;
0017:
0018: import com.google.gwt.dev.js.ast.JsArrayAccess;
0019: import com.google.gwt.dev.js.ast.JsArrayLiteral;
0020: import com.google.gwt.dev.js.ast.JsBinaryOperation;
0021: import com.google.gwt.dev.js.ast.JsBinaryOperator;
0022: import com.google.gwt.dev.js.ast.JsBlock;
0023: import com.google.gwt.dev.js.ast.JsBooleanLiteral;
0024: import com.google.gwt.dev.js.ast.JsBreak;
0025: import com.google.gwt.dev.js.ast.JsCase;
0026: import com.google.gwt.dev.js.ast.JsCatch;
0027: import com.google.gwt.dev.js.ast.JsConditional;
0028: import com.google.gwt.dev.js.ast.JsContinue;
0029: import com.google.gwt.dev.js.ast.JsDefault;
0030: import com.google.gwt.dev.js.ast.JsDoWhile;
0031: import com.google.gwt.dev.js.ast.JsExpression;
0032: import com.google.gwt.dev.js.ast.JsFor;
0033: import com.google.gwt.dev.js.ast.JsForIn;
0034: import com.google.gwt.dev.js.ast.JsFunction;
0035: import com.google.gwt.dev.js.ast.JsIf;
0036: import com.google.gwt.dev.js.ast.JsInvocation;
0037: import com.google.gwt.dev.js.ast.JsLabel;
0038: import com.google.gwt.dev.js.ast.JsName;
0039: import com.google.gwt.dev.js.ast.JsNameRef;
0040: import com.google.gwt.dev.js.ast.JsNew;
0041: import com.google.gwt.dev.js.ast.JsNode;
0042: import com.google.gwt.dev.js.ast.JsObjectLiteral;
0043: import com.google.gwt.dev.js.ast.JsParameter;
0044: import com.google.gwt.dev.js.ast.JsPostfixOperation;
0045: import com.google.gwt.dev.js.ast.JsPrefixOperation;
0046: import com.google.gwt.dev.js.ast.JsProgram;
0047: import com.google.gwt.dev.js.ast.JsPropertyInitializer;
0048: import com.google.gwt.dev.js.ast.JsRegExp;
0049: import com.google.gwt.dev.js.ast.JsReturn;
0050: import com.google.gwt.dev.js.ast.JsScope;
0051: import com.google.gwt.dev.js.ast.JsStatement;
0052: import com.google.gwt.dev.js.ast.JsStringLiteral;
0053: import com.google.gwt.dev.js.ast.JsSwitch;
0054: import com.google.gwt.dev.js.ast.JsThisRef;
0055: import com.google.gwt.dev.js.ast.JsThrow;
0056: import com.google.gwt.dev.js.ast.JsTry;
0057: import com.google.gwt.dev.js.ast.JsUnaryOperator;
0058: import com.google.gwt.dev.js.ast.JsVars;
0059: import com.google.gwt.dev.js.ast.JsWhile;
0060: import com.google.gwt.dev.js.rhino.Context;
0061: import com.google.gwt.dev.js.rhino.ErrorReporter;
0062: import com.google.gwt.dev.js.rhino.EvaluatorException;
0063: import com.google.gwt.dev.js.rhino.IRFactory;
0064: import com.google.gwt.dev.js.rhino.Node;
0065: import com.google.gwt.dev.js.rhino.Parser;
0066: import com.google.gwt.dev.js.rhino.TokenStream;
0067:
0068: import java.io.IOException;
0069: import java.io.Reader;
0070: import java.math.BigInteger;
0071: import java.util.ArrayList;
0072: import java.util.Iterator;
0073: import java.util.List;
0074: import java.util.Stack;
0075:
0076: /**
0077: * Parses JavaScript source.
0078: */
0079: public class JsParser {
0080:
0081: private JsProgram program;
0082: private final Stack<JsScope> scopeStack = new Stack<JsScope>();
0083:
0084: public JsParser() {
0085: // Create a custom error handler so that we can throw our own exceptions.
0086: //
0087: Context.enter().setErrorReporter(new ErrorReporter() {
0088: public void error(String msg, String loc, int ln,
0089: String src, int col) {
0090: throw new UncheckedJsParserException(
0091: new JsParserException(msg, ln, src, col));
0092: }
0093:
0094: public EvaluatorException runtimeError(String msg,
0095: String loc, int ln, String src, int col) {
0096: // Never called, but just in case.
0097: throw new UncheckedJsParserException(
0098: new JsParserException(msg, ln, src, col));
0099: }
0100:
0101: public void warning(String msg, String loc, int ln,
0102: String src, int col) {
0103: // Ignore warnings.
0104: }
0105: });
0106: }
0107:
0108: public List<JsStatement> parse(JsScope scope, Reader r,
0109: int startLine) throws IOException, JsParserException {
0110: try {
0111: // Parse using the Rhino parser.
0112: //
0113: TokenStream ts = new TokenStream(r, "", startLine);
0114: Parser parser = new Parser(new IRFactory(ts));
0115: Node topNode = (Node) parser.parse(ts);
0116:
0117: // Map the Rhino AST to ours.
0118: //
0119: program = scope.getProgram();
0120: pushScope(scope);
0121: List<JsStatement> stmts = mapStatements(topNode);
0122: popScope();
0123:
0124: return stmts;
0125: } catch (UncheckedJsParserException e) {
0126: throw e.getParserException();
0127: }
0128: }
0129:
0130: public void parseInto(JsScope scope, JsBlock block, Reader r,
0131: int startLine) throws IOException, JsParserException {
0132: List<JsStatement> childStmts = parse(scope, r, startLine);
0133: List<JsStatement> parentStmts = block.getStatements();
0134: for (Iterator<JsStatement> iter = childStmts.iterator(); iter
0135: .hasNext();) {
0136: parentStmts.add(iter.next());
0137: }
0138: }
0139:
0140: private JsParserException createParserException(String msg,
0141: Node offender) {
0142: // TODO: get source info
0143: offender.getLineno();
0144: return new JsParserException(msg);
0145: }
0146:
0147: private JsScope getScope() {
0148: return scopeStack.peek();
0149: }
0150:
0151: private JsNode<?> map(Node node) throws JsParserException {
0152:
0153: switch (node.getType()) {
0154: case TokenStream.SCRIPT: {
0155: JsBlock block = new JsBlock();
0156: mapStatements(block.getStatements(), node);
0157: return block;
0158: }
0159:
0160: case TokenStream.DEBUGGER:
0161: return mapDebuggerStatement();
0162:
0163: case TokenStream.VOID:
0164: // VOID = nothing was parsed for this node
0165: return null;
0166:
0167: case TokenStream.EXPRSTMT:
0168: return mapExpression(node.getFirstChild()).makeStmt();
0169:
0170: case TokenStream.REGEXP:
0171: return mapRegExp(node);
0172:
0173: case TokenStream.ADD:
0174: return mapBinaryOperation(JsBinaryOperator.ADD, node);
0175:
0176: case TokenStream.SUB:
0177: return mapBinaryOperation(JsBinaryOperator.SUB, node);
0178:
0179: case TokenStream.MUL:
0180: return mapBinaryOperation(JsBinaryOperator.MUL, node);
0181:
0182: case TokenStream.DIV:
0183: return mapBinaryOperation(JsBinaryOperator.DIV, node);
0184:
0185: case TokenStream.MOD:
0186: return mapBinaryOperation(JsBinaryOperator.MOD, node);
0187:
0188: case TokenStream.AND:
0189: return mapBinaryOperation(JsBinaryOperator.AND, node);
0190:
0191: case TokenStream.OR:
0192: return mapBinaryOperation(JsBinaryOperator.OR, node);
0193:
0194: case TokenStream.BITAND:
0195: return mapBinaryOperation(JsBinaryOperator.BIT_AND, node);
0196:
0197: case TokenStream.BITOR:
0198: return mapBinaryOperation(JsBinaryOperator.BIT_OR, node);
0199:
0200: case TokenStream.BITXOR:
0201: return mapBinaryOperation(JsBinaryOperator.BIT_XOR, node);
0202:
0203: case TokenStream.ASSIGN:
0204: return mapAssignmentVariant(node);
0205:
0206: case TokenStream.RELOP:
0207: return mapRelationalVariant(node);
0208:
0209: case TokenStream.EQOP:
0210: return mapEqualityVariant(node);
0211:
0212: case TokenStream.SHOP:
0213: return mapShiftVariant(node);
0214:
0215: case TokenStream.UNARYOP:
0216: return mapUnaryVariant(node);
0217:
0218: case TokenStream.INC:
0219: return mapIncDecFixity(JsUnaryOperator.INC, node);
0220:
0221: case TokenStream.DEC:
0222: return mapIncDecFixity(JsUnaryOperator.DEC, node);
0223:
0224: case TokenStream.HOOK:
0225: return mapConditional(node);
0226:
0227: case TokenStream.NAME:
0228: return mapName(node);
0229:
0230: case TokenStream.STRING:
0231: return program.getStringLiteral(node.getString());
0232:
0233: case TokenStream.NUMBER:
0234: return mapNumber(node);
0235:
0236: case TokenStream.CALL:
0237: return mapCall(node);
0238:
0239: case TokenStream.GETPROP:
0240: return mapGetProp(node);
0241:
0242: case TokenStream.SETPROP:
0243: return mapSetProp(node);
0244:
0245: case TokenStream.DELPROP:
0246: return mapDeleteProp(node);
0247:
0248: case TokenStream.IF:
0249: return mapIfStatement(node);
0250:
0251: case TokenStream.WHILE:
0252: return mapDoOrWhileStatement(true, node);
0253:
0254: case TokenStream.DO:
0255: return mapDoOrWhileStatement(false, node);
0256:
0257: case TokenStream.FOR:
0258: return mapForStatement(node);
0259:
0260: case TokenStream.WITH:
0261: return mapWithStatement(node);
0262:
0263: case TokenStream.GETELEM:
0264: return mapGetElem(node);
0265:
0266: case TokenStream.SETELEM:
0267: return mapSetElem(node);
0268:
0269: case TokenStream.FUNCTION:
0270: return mapFunction(node);
0271:
0272: case TokenStream.BLOCK:
0273: return mapBlock(node);
0274:
0275: case TokenStream.SETNAME:
0276: return mapBinaryOperation(JsBinaryOperator.ASG, node);
0277:
0278: case TokenStream.BINDNAME:
0279: return mapName(node);
0280:
0281: case TokenStream.RETURN:
0282: return mapReturn(node);
0283:
0284: case TokenStream.BREAK:
0285: return mapBreak(node);
0286:
0287: case TokenStream.CONTINUE:
0288: return mapContinue(node);
0289:
0290: case TokenStream.OBJLIT:
0291: return mapObjectLit(node);
0292:
0293: case TokenStream.ARRAYLIT:
0294: return mapArrayLit(node);
0295:
0296: case TokenStream.VAR:
0297: return mapVar(node);
0298:
0299: case TokenStream.PRIMARY:
0300: return mapPrimary(node);
0301:
0302: case TokenStream.COMMA:
0303: return mapBinaryOperation(JsBinaryOperator.COMMA, node);
0304:
0305: case TokenStream.NEW:
0306: return mapNew(node);
0307:
0308: case TokenStream.THROW:
0309: return mapThrowStatement(node);
0310:
0311: case TokenStream.TRY:
0312: return mapTryStatement(node);
0313:
0314: case TokenStream.SWITCH:
0315: return mapSwitchStatement(node);
0316:
0317: case TokenStream.LABEL:
0318: return mapLabel(node);
0319:
0320: default:
0321: int tokenType = node.getType();
0322: throw new JsParserException(
0323: "Unexpected top-level token type: " + tokenType);
0324: }
0325: }
0326:
0327: private JsArrayLiteral mapArrayLit(Node node)
0328: throws JsParserException {
0329: JsArrayLiteral toLit = new JsArrayLiteral();
0330: Node from = node.getFirstChild();
0331: while (from != null) {
0332: toLit.getExpressions().add(mapExpression(from));
0333: from = from.getNext();
0334: }
0335: return toLit;
0336: }
0337:
0338: /**
0339: * Produces a {@link JsNameRef}.
0340: */
0341: private JsNameRef mapAsPropertyNameRef(Node nameRefNode)
0342: throws JsParserException {
0343: JsNode unknown = map(nameRefNode);
0344: // This is weird, but for "a.b", the rhino AST calls "b" a string literal.
0345: // However, since we know it's for a PROPGET, we can unstringliteralize it.
0346: //
0347: if (unknown instanceof JsStringLiteral) {
0348: JsStringLiteral lit = (JsStringLiteral) unknown;
0349: String litName = lit.getValue();
0350: return new JsNameRef(litName);
0351: } else {
0352: throw createParserException("Expecting a name reference",
0353: nameRefNode);
0354: }
0355: }
0356:
0357: private JsExpression mapAssignmentVariant(Node asgNode)
0358: throws JsParserException {
0359: switch (asgNode.getIntDatum()) {
0360: case TokenStream.NOP:
0361: return mapBinaryOperation(JsBinaryOperator.ASG, asgNode);
0362:
0363: case TokenStream.ADD:
0364: return mapBinaryOperation(JsBinaryOperator.ASG_ADD, asgNode);
0365:
0366: case TokenStream.SUB:
0367: return mapBinaryOperation(JsBinaryOperator.ASG_SUB, asgNode);
0368:
0369: case TokenStream.MUL:
0370: return mapBinaryOperation(JsBinaryOperator.ASG_MUL, asgNode);
0371:
0372: case TokenStream.DIV:
0373: return mapBinaryOperation(JsBinaryOperator.ASG_DIV, asgNode);
0374:
0375: case TokenStream.MOD:
0376: return mapBinaryOperation(JsBinaryOperator.ASG_MOD, asgNode);
0377:
0378: case TokenStream.BITAND:
0379: return mapBinaryOperation(JsBinaryOperator.ASG_BIT_AND,
0380: asgNode);
0381:
0382: case TokenStream.BITOR:
0383: return mapBinaryOperation(JsBinaryOperator.ASG_BIT_OR,
0384: asgNode);
0385:
0386: case TokenStream.BITXOR:
0387: return mapBinaryOperation(JsBinaryOperator.ASG_BIT_XOR,
0388: asgNode);
0389:
0390: case TokenStream.LSH:
0391: return mapBinaryOperation(JsBinaryOperator.ASG_SHL, asgNode);
0392:
0393: case TokenStream.RSH:
0394: return mapBinaryOperation(JsBinaryOperator.ASG_SHR, asgNode);
0395:
0396: case TokenStream.URSH:
0397: return mapBinaryOperation(JsBinaryOperator.ASG_SHRU,
0398: asgNode);
0399:
0400: default:
0401: throw new JsParserException(
0402: "Unknown assignment operator variant: "
0403: + asgNode.getIntDatum());
0404: }
0405: }
0406:
0407: private JsExpression mapBinaryOperation(JsBinaryOperator op,
0408: Node node) throws JsParserException {
0409: Node from1 = node.getFirstChild();
0410: Node from2 = from1.getNext();
0411:
0412: JsExpression to1 = mapExpression(from1);
0413: JsExpression to2 = mapExpression(from2);
0414:
0415: return new JsBinaryOperation(op, to1, to2);
0416: }
0417:
0418: private JsBlock mapBlock(Node nodeStmts) throws JsParserException {
0419: JsBlock block = new JsBlock();
0420: mapStatements(block.getStatements(), nodeStmts);
0421: return block;
0422: }
0423:
0424: private JsBreak mapBreak(Node breakNode) {
0425: Node fromLabel = breakNode.getFirstChild();
0426: if (fromLabel != null) {
0427: return new JsBreak(mapName(fromLabel));
0428: } else {
0429: return new JsBreak();
0430: }
0431: }
0432:
0433: private JsInvocation mapCall(Node callNode)
0434: throws JsParserException {
0435: JsInvocation invocation = new JsInvocation();
0436:
0437: // Map the target expression.
0438: //
0439: Node from = callNode.getFirstChild();
0440: JsExpression to = mapExpression(from);
0441: invocation.setQualifier(to);
0442:
0443: // Iterate over and map the arguments.
0444: //
0445: List<JsExpression> args = invocation.getArguments();
0446: from = from.getNext();
0447: while (from != null) {
0448: to = mapExpression(from);
0449: args.add(to);
0450: from = from.getNext();
0451: }
0452:
0453: return invocation;
0454: }
0455:
0456: private JsExpression mapConditional(Node condNode)
0457: throws JsParserException {
0458: JsConditional toCond = new JsConditional();
0459:
0460: Node fromTest = condNode.getFirstChild();
0461: toCond.setTestExpression(mapExpression(fromTest));
0462:
0463: Node fromThen = fromTest.getNext();
0464: toCond.setThenExpression(mapExpression(fromThen));
0465:
0466: Node fromElse = fromThen.getNext();
0467: toCond.setElseExpression(mapExpression(fromElse));
0468:
0469: return toCond;
0470: }
0471:
0472: private JsContinue mapContinue(Node contNode) {
0473: Node fromLabel = contNode.getFirstChild();
0474: if (fromLabel != null) {
0475: return new JsContinue(mapName(fromLabel));
0476: } else {
0477: return new JsContinue();
0478: }
0479: }
0480:
0481: private JsStatement mapDebuggerStatement() {
0482: // Calls an optional method to invoke the debugger.
0483: //
0484: return program.getDebuggerStmt();
0485: }
0486:
0487: private JsExpression mapDeleteProp(Node node)
0488: throws JsParserException {
0489: Node from = node.getFirstChild();
0490: JsExpression to = mapExpression(from);
0491: if (to instanceof JsNameRef) {
0492: return new JsPrefixOperation(JsUnaryOperator.DELETE, to);
0493: } else if (to instanceof JsArrayAccess) {
0494: return new JsPrefixOperation(JsUnaryOperator.DELETE, to);
0495: } else {
0496: throw createParserException(
0497: "'delete' can only operate on property names and array elements",
0498: from);
0499: }
0500: }
0501:
0502: private JsStatement mapDoOrWhileStatement(boolean isWhile,
0503: Node ifNode) throws JsParserException {
0504:
0505: // Pull out the pieces we want to map.
0506: //
0507: Node fromTestExpr;
0508: Node fromBody;
0509: if (isWhile) {
0510: fromTestExpr = ifNode.getFirstChild();
0511: fromBody = ifNode.getFirstChild().getNext();
0512: } else {
0513: fromBody = ifNode.getFirstChild();
0514: fromTestExpr = ifNode.getFirstChild().getNext();
0515: }
0516:
0517: // Map the test expression.
0518: //
0519: JsExpression toTestExpr = mapExpression(fromTestExpr);
0520:
0521: // Map the body block.
0522: //
0523: JsStatement toBody = mapStatement(fromBody);
0524:
0525: // Create and attach the "while" or "do" statement we're mapping to.
0526: //
0527: if (isWhile) {
0528: return new JsWhile(toTestExpr, toBody);
0529: } else {
0530: return new JsDoWhile(toTestExpr, toBody);
0531: }
0532: }
0533:
0534: private JsExpression mapEqualityVariant(Node eqNode)
0535: throws JsParserException {
0536: switch (eqNode.getIntDatum()) {
0537: case TokenStream.EQ:
0538: return mapBinaryOperation(JsBinaryOperator.EQ, eqNode);
0539:
0540: case TokenStream.NE:
0541: return mapBinaryOperation(JsBinaryOperator.NEQ, eqNode);
0542:
0543: case TokenStream.SHEQ:
0544: return mapBinaryOperation(JsBinaryOperator.REF_EQ, eqNode);
0545:
0546: case TokenStream.SHNE:
0547: return mapBinaryOperation(JsBinaryOperator.REF_NEQ, eqNode);
0548:
0549: case TokenStream.LT:
0550: return mapBinaryOperation(JsBinaryOperator.LT, eqNode);
0551:
0552: case TokenStream.LE:
0553: return mapBinaryOperation(JsBinaryOperator.LTE, eqNode);
0554:
0555: case TokenStream.GT:
0556: return mapBinaryOperation(JsBinaryOperator.GT, eqNode);
0557:
0558: case TokenStream.GE:
0559: return mapBinaryOperation(JsBinaryOperator.GTE, eqNode);
0560:
0561: default:
0562: throw new JsParserException(
0563: "Unknown equality operator variant: "
0564: + eqNode.getIntDatum());
0565: }
0566: }
0567:
0568: private JsExpression mapExpression(Node exprNode)
0569: throws JsParserException {
0570: JsNode unknown = map(exprNode);
0571: if (unknown instanceof JsExpression) {
0572: return (JsExpression) unknown;
0573: } else {
0574: throw createParserException("Expecting an expression",
0575: exprNode);
0576: }
0577: }
0578:
0579: private JsStatement mapForStatement(Node forNode)
0580: throws JsParserException {
0581: Node fromInit = forNode.getFirstChild();
0582: Node fromTest = fromInit.getNext();
0583: Node fromIncr = fromTest.getNext();
0584: Node fromBody = fromIncr.getNext();
0585:
0586: if (fromBody == null) {
0587: // This could be a "for...in" structure.
0588: // We could based on the different child layout.
0589: //
0590: Node fromIter = forNode.getFirstChild();
0591: Node fromObjExpr = fromIter.getNext();
0592: fromBody = fromObjExpr.getNext();
0593:
0594: JsForIn toForIn;
0595: if (fromIter.getType() == TokenStream.VAR) {
0596: // A named iterator var.
0597: //
0598: Node fromIterVarName = fromIter.getFirstChild();
0599: String fromName = fromIterVarName.getString();
0600: JsName toName = getScope().declareName(fromName);
0601: toForIn = new JsForIn(toName);
0602: Node fromIterInit = fromIterVarName.getFirstChild();
0603: if (fromIterInit != null) {
0604: // That has an initializer expression (useful only for side effects).
0605: //
0606: toForIn
0607: .setIterExpr(mapOptionalExpression(fromIterInit));
0608: }
0609: } else {
0610: // An unnamed iterator var.
0611: //
0612: toForIn = new JsForIn();
0613: toForIn.setIterExpr(mapExpression(fromIter));
0614: }
0615: toForIn.setObjExpr(mapExpression(fromObjExpr));
0616:
0617: // The body stmt.
0618: //
0619: JsStatement bodyStmt = mapStatement(fromBody);
0620: if (bodyStmt != null) {
0621: toForIn.setBody(bodyStmt);
0622: } else {
0623: toForIn.setBody(program.getEmptyStmt());
0624: }
0625:
0626: return toForIn;
0627: } else {
0628: // Regular ol' for loop.
0629: //
0630: JsFor toFor = new JsFor();
0631:
0632: // The first item is either an expression or a JsVars.
0633: //
0634: JsNode initThingy = map(fromInit);
0635: if (initThingy != null) {
0636: if (initThingy instanceof JsVars) {
0637: toFor.setInitVars((JsVars) initThingy);
0638: } else {
0639: assert (initThingy instanceof JsExpression);
0640: toFor.setInitExpr((JsExpression) initThingy);
0641: }
0642: }
0643: toFor.setCondition(mapOptionalExpression(fromTest));
0644: toFor.setIncrExpr(mapOptionalExpression(fromIncr));
0645:
0646: JsStatement bodyStmt = mapStatement(fromBody);
0647: if (bodyStmt != null) {
0648: toFor.setBody(bodyStmt);
0649: } else {
0650: toFor.setBody(program.getEmptyStmt());
0651: }
0652: return toFor;
0653: }
0654: }
0655:
0656: private JsExpression mapFunction(Node fnNode)
0657: throws JsParserException {
0658:
0659: Node fromFnNameNode = fnNode.getFirstChild();
0660: Node fromParamNode = fnNode.getFirstChild().getNext()
0661: .getFirstChild();
0662: Node fromBodyNode = fnNode.getFirstChild().getNext().getNext();
0663:
0664: // Decide the function's name, if any.
0665: //
0666: String fromFnName = fromFnNameNode.getString();
0667: JsName toFnName = null;
0668: if (fromFnName != null && fromFnName.length() > 0) {
0669: toFnName = getScope().declareName(fromFnName);
0670: }
0671:
0672: // Create it, and set the params.
0673: //
0674: JsFunction toFn = new JsFunction(getScope(), toFnName);
0675:
0676: // Creating a function also creates a new scope, which we push onto
0677: // the scope stack.
0678: //
0679: pushScope(toFn.getScope());
0680:
0681: while (fromParamNode != null) {
0682: String fromParamName = fromParamNode.getString();
0683: // should this be unique? I think not since you can have dup args.
0684: JsName paramName = toFn.getScope().declareName(
0685: fromParamName);
0686: toFn.getParameters().add(new JsParameter(paramName));
0687: fromParamNode = fromParamNode.getNext();
0688: }
0689:
0690: // Map the function's body.
0691: //
0692: JsBlock toBody = mapBlock(fromBodyNode);
0693: toFn.setBody(toBody);
0694:
0695: // Pop the new function's scope off of the scope stack.
0696: //
0697: popScope();
0698:
0699: return toFn;
0700: }
0701:
0702: private JsArrayAccess mapGetElem(Node getElemNode)
0703: throws JsParserException {
0704: Node from1 = getElemNode.getFirstChild();
0705: Node from2 = from1.getNext();
0706:
0707: JsExpression to1 = mapExpression(from1);
0708: JsExpression to2 = mapExpression(from2);
0709:
0710: return new JsArrayAccess(to1, to2);
0711: }
0712:
0713: private JsNameRef mapGetProp(Node getPropNode)
0714: throws JsParserException {
0715: Node from1 = getPropNode.getFirstChild();
0716: Node from2 = from1.getNext();
0717:
0718: JsExpression toQualifier = mapExpression(from1);
0719: JsNameRef toNameRef;
0720: if (from2 != null) {
0721: toNameRef = mapAsPropertyNameRef(from2);
0722: } else {
0723: // Special properties don't have a second expression.
0724: //
0725: Object obj = getPropNode.getProp(Node.SPECIAL_PROP_PROP);
0726: assert (obj instanceof String);
0727: toNameRef = new JsNameRef((String) obj);
0728: }
0729: toNameRef.setQualifier(toQualifier);
0730:
0731: return toNameRef;
0732: }
0733:
0734: private JsIf mapIfStatement(Node ifNode) throws JsParserException {
0735:
0736: // Pull out the pieces we want to map.
0737: //
0738: Node fromTestExpr = ifNode.getFirstChild();
0739: Node fromThenBlock = ifNode.getFirstChild().getNext();
0740: Node fromElseBlock = ifNode.getFirstChild().getNext().getNext();
0741:
0742: // Create the "if" statement we're mapping to.
0743: //
0744: JsIf toIf = new JsIf();
0745:
0746: // Map the test expression.
0747: //
0748: JsExpression toTestExpr = mapExpression(fromTestExpr);
0749: toIf.setIfExpr(toTestExpr);
0750:
0751: // Map the "then" block.
0752: //
0753: toIf.setThenStmt(mapStatement(fromThenBlock));
0754:
0755: // Map the "else" block.
0756: //
0757: if (fromElseBlock != null) {
0758: toIf.setElseStmt(mapStatement(fromElseBlock));
0759: }
0760:
0761: return toIf;
0762: }
0763:
0764: private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node)
0765: throws JsParserException {
0766: switch (node.getIntDatum()) {
0767: case TokenStream.PRE:
0768: return mapPrefixOperation(op, node);
0769: case TokenStream.POST:
0770: return mapPostfixOperation(op, node);
0771: default:
0772: throw new JsParserException(
0773: "Unknown prefix/postfix variant: "
0774: + node.getIntDatum());
0775: }
0776: }
0777:
0778: private JsLabel mapLabel(Node labelNode) throws JsParserException {
0779: String fromName = labelNode.getFirstChild().getString();
0780: JsName toName = getScope().declareName(fromName);
0781: Node fromStmt = labelNode.getFirstChild().getNext();
0782: JsLabel toLabel = new JsLabel(toName);
0783: toLabel.setStmt(mapStatement(fromStmt));
0784: return toLabel;
0785: }
0786:
0787: /**
0788: * Creates a reference to a name that may or may not be obfuscatable, based on
0789: * whether it matches a known name in the scope.
0790: */
0791: private JsNameRef mapName(Node node) {
0792: String ident = node.getString();
0793: return new JsNameRef(ident);
0794: }
0795:
0796: private JsNew mapNew(Node newNode) throws JsParserException {
0797:
0798: JsNew newExpr = new JsNew();
0799:
0800: // Map the constructor expression, which is often just the name of
0801: // some lambda.
0802: //
0803: Node fromCtorExpr = newNode.getFirstChild();
0804: newExpr.setConstructorExpression(mapExpression(fromCtorExpr));
0805:
0806: // Iterate over and map the arguments.
0807: //
0808: List<JsExpression> args = newExpr.getArguments();
0809: Node fromArg = fromCtorExpr.getNext();
0810: while (fromArg != null) {
0811: args.add(mapExpression(fromArg));
0812: fromArg = fromArg.getNext();
0813: }
0814:
0815: return newExpr;
0816: }
0817:
0818: private JsExpression mapNumber(Node numberNode) {
0819: double x = numberNode.getDouble();
0820: long j = (long) x;
0821: if (x == j) {
0822: return program.getIntegralLiteral(BigInteger.valueOf(j));
0823: } else {
0824: return program.getDecimalLiteral(String.valueOf(x));
0825: }
0826: }
0827:
0828: private JsExpression mapObjectLit(Node objLitNode)
0829: throws JsParserException {
0830: JsObjectLiteral toLit = new JsObjectLiteral();
0831: Node fromPropInit = objLitNode.getFirstChild();
0832: while (fromPropInit != null) {
0833:
0834: Node fromLabelExpr = fromPropInit;
0835: JsExpression toLabelExpr = mapExpression(fromLabelExpr);
0836:
0837: // Advance to the initializer expression.
0838: //
0839: fromPropInit = fromPropInit.getNext();
0840: Node fromValueExpr = fromPropInit;
0841: if (fromValueExpr == null) {
0842: throw createParserException(
0843: "Expected an init expression for: "
0844: + toLabelExpr, objLitNode);
0845: }
0846: JsExpression toValueExpr = mapExpression(fromValueExpr);
0847:
0848: JsPropertyInitializer toPropInit = new JsPropertyInitializer(
0849: toLabelExpr, toValueExpr);
0850: toLit.getPropertyInitializers().add(toPropInit);
0851:
0852: // Begin the next property initializer, if there is one.
0853: //
0854: fromPropInit = fromPropInit.getNext();
0855: }
0856:
0857: return toLit;
0858: }
0859:
0860: private JsExpression mapOptionalExpression(Node exprNode)
0861: throws JsParserException {
0862: JsNode unknown = map(exprNode);
0863: if (unknown != null) {
0864: if (unknown instanceof JsExpression) {
0865: return (JsExpression) unknown;
0866: } else {
0867: throw createParserException(
0868: "Expecting an expression or null", exprNode);
0869: }
0870: }
0871: return null;
0872: }
0873:
0874: private JsExpression mapPostfixOperation(JsUnaryOperator op,
0875: Node node) throws JsParserException {
0876: Node from = node.getFirstChild();
0877: JsExpression to = mapExpression(from);
0878: return new JsPostfixOperation(op, to);
0879: }
0880:
0881: private JsExpression mapPrefixOperation(JsUnaryOperator op,
0882: Node node) throws JsParserException {
0883: Node from = node.getFirstChild();
0884: JsExpression to = mapExpression(from);
0885: return new JsPrefixOperation(op, to);
0886: }
0887:
0888: private JsExpression mapPrimary(Node node) throws JsParserException {
0889: switch (node.getIntDatum()) {
0890: case TokenStream.THIS:
0891: return new JsThisRef();
0892:
0893: case TokenStream.TRUE:
0894: return program.getTrueLiteral();
0895:
0896: case TokenStream.FALSE:
0897: return program.getFalseLiteral();
0898:
0899: case TokenStream.NULL:
0900: return program.getNullLiteral();
0901:
0902: default:
0903: throw new JsParserException("Unknown primary: "
0904: + node.getIntDatum());
0905: }
0906: }
0907:
0908: private JsNode<?> mapRegExp(Node regExpNode) {
0909: JsRegExp toRegExp = new JsRegExp();
0910:
0911: Node fromPattern = regExpNode.getFirstChild();
0912: toRegExp.setPattern(fromPattern.getString());
0913:
0914: Node fromFlags = fromPattern.getNext();
0915: if (fromFlags != null) {
0916: toRegExp.setFlags(fromFlags.getString());
0917: }
0918:
0919: return toRegExp;
0920: }
0921:
0922: private JsExpression mapRelationalVariant(Node relNode)
0923: throws JsParserException {
0924: switch (relNode.getIntDatum()) {
0925: case TokenStream.LT:
0926: return mapBinaryOperation(JsBinaryOperator.LT, relNode);
0927:
0928: case TokenStream.LE:
0929: return mapBinaryOperation(JsBinaryOperator.LTE, relNode);
0930:
0931: case TokenStream.GT:
0932: return mapBinaryOperation(JsBinaryOperator.GT, relNode);
0933:
0934: case TokenStream.GE:
0935: return mapBinaryOperation(JsBinaryOperator.GTE, relNode);
0936:
0937: case TokenStream.INSTANCEOF:
0938: return mapBinaryOperation(JsBinaryOperator.INSTANCEOF,
0939: relNode);
0940:
0941: case TokenStream.IN:
0942: return mapBinaryOperation(JsBinaryOperator.INOP, relNode);
0943:
0944: default:
0945: throw new JsParserException(
0946: "Unknown relational operator variant: "
0947: + relNode.getIntDatum());
0948: }
0949: }
0950:
0951: private JsReturn mapReturn(Node returnNode)
0952: throws JsParserException {
0953: JsReturn toReturn = new JsReturn();
0954: Node from = returnNode.getFirstChild();
0955: if (from != null) {
0956: JsExpression to = mapExpression(from);
0957: toReturn.setExpr(to);
0958: }
0959:
0960: return toReturn;
0961: }
0962:
0963: private JsExpression mapSetElem(Node setElemNode)
0964: throws JsParserException {
0965: // Reuse the get elem code.
0966: //
0967: JsArrayAccess lhs = mapGetElem(setElemNode);
0968:
0969: // Map the RHS.
0970: //
0971: Node fromRhs = setElemNode.getFirstChild().getNext().getNext();
0972: JsExpression toRhs = mapExpression(fromRhs);
0973:
0974: return new JsBinaryOperation(JsBinaryOperator.ASG, lhs, toRhs);
0975: }
0976:
0977: private JsExpression mapSetProp(Node getPropNode)
0978: throws JsParserException {
0979: // Reuse the get prop code.
0980: //
0981: JsNameRef lhs = mapGetProp(getPropNode);
0982:
0983: // Map the RHS.
0984: //
0985: Node fromRhs = getPropNode.getFirstChild().getNext().getNext();
0986: JsExpression toRhs = mapExpression(fromRhs);
0987:
0988: return new JsBinaryOperation(JsBinaryOperator.ASG, lhs, toRhs);
0989: }
0990:
0991: private JsExpression mapShiftVariant(Node shiftNode)
0992: throws JsParserException {
0993: switch (shiftNode.getIntDatum()) {
0994: case TokenStream.LSH:
0995: return mapBinaryOperation(JsBinaryOperator.SHL, shiftNode);
0996:
0997: case TokenStream.RSH:
0998: return mapBinaryOperation(JsBinaryOperator.SHR, shiftNode);
0999:
1000: case TokenStream.URSH:
1001: return mapBinaryOperation(JsBinaryOperator.SHRU, shiftNode);
1002:
1003: default:
1004: throw new JsParserException(
1005: "Unknown equality operator variant: "
1006: + shiftNode.getIntDatum());
1007: }
1008: }
1009:
1010: private JsStatement mapStatement(Node nodeStmt)
1011: throws JsParserException {
1012: JsNode unknown = map(nodeStmt);
1013: if (unknown != null) {
1014: if (unknown instanceof JsStatement) {
1015: return (JsStatement) unknown;
1016: } else if (unknown instanceof JsExpression) {
1017: return ((JsExpression) unknown).makeStmt();
1018: } else {
1019: throw createParserException("Expecting a statement",
1020: nodeStmt);
1021: }
1022: } else {
1023: // When map() returns null, we return an empty statement.
1024: //
1025: return program.getEmptyStmt();
1026: }
1027: }
1028:
1029: private void mapStatements(List<JsStatement> stmts, Node nodeStmts)
1030: throws JsParserException {
1031: Node curr = nodeStmts.getFirstChild();
1032: while (curr != null) {
1033: JsStatement stmt = mapStatement(curr);
1034: if (stmt != null) {
1035: stmts.add(stmt);
1036: } else {
1037: // When mapStatement() returns null, we just ignore it.
1038: //
1039: }
1040: curr = curr.getNext();
1041: }
1042: }
1043:
1044: private List<JsStatement> mapStatements(Node nodeStmts)
1045: throws JsParserException {
1046: List<JsStatement> stmts = new ArrayList<JsStatement>();
1047: mapStatements(stmts, nodeStmts);
1048: return stmts;
1049: }
1050:
1051: private JsSwitch mapSwitchStatement(Node switchNode)
1052: throws JsParserException {
1053: JsSwitch toSwitch = new JsSwitch();
1054:
1055: // The switch expression.
1056: //
1057: Node fromSwitchExpr = switchNode.getFirstChild();
1058: toSwitch.setExpr(mapExpression(fromSwitchExpr));
1059:
1060: // The members.
1061: //
1062: Node fromMember = fromSwitchExpr.getNext();
1063: while (fromMember != null) {
1064: if (fromMember.getType() == TokenStream.CASE) {
1065: JsCase toCase = new JsCase();
1066:
1067: // Set the case expression. In JS, this can be any expression.
1068: //
1069: Node fromCaseExpr = fromMember.getFirstChild();
1070: toCase.setCaseExpr(mapExpression(fromCaseExpr));
1071:
1072: // Set the case statements.
1073: //
1074: Node fromCaseBlock = fromCaseExpr.getNext();
1075: mapStatements(toCase.getStmts(), fromCaseBlock);
1076:
1077: // Attach the case to the switch.
1078: //
1079: toSwitch.getCases().add(toCase);
1080: } else {
1081: // This should be the only default statement.
1082: // If more than one is present, we keep the last one.
1083: //
1084: assert (fromMember.getType() == TokenStream.DEFAULT);
1085: JsDefault toDefault = new JsDefault();
1086:
1087: // Set the default statements.
1088: //
1089: Node fromDefaultBlock = fromMember.getFirstChild();
1090: mapStatements(toDefault.getStmts(), fromDefaultBlock);
1091:
1092: // Attach the default to the switch.
1093: //
1094: toSwitch.getCases().add(toDefault);
1095: }
1096: fromMember = fromMember.getNext();
1097: }
1098:
1099: return toSwitch;
1100: }
1101:
1102: private JsThrow mapThrowStatement(Node throwNode)
1103: throws JsParserException {
1104: // Create, map, and attach.
1105: //
1106: Node fromExpr = throwNode.getFirstChild();
1107: JsThrow toThrow = new JsThrow(mapExpression(fromExpr));
1108: return toThrow;
1109: }
1110:
1111: private JsTry mapTryStatement(Node tryNode)
1112: throws JsParserException {
1113: JsTry toTry = new JsTry();
1114:
1115: // Map the "try" body.
1116: //
1117: Node fromTryBody = tryNode.getFirstChild();
1118: toTry.setTryBlock(mapBlock(fromTryBody));
1119:
1120: // Map zero or more catch blocks.
1121: //
1122: Node fromCatchNodes = fromTryBody.getNext();
1123: Node fromCatchNode = fromCatchNodes.getFirstChild();
1124: while (fromCatchNode != null) {
1125: assert (fromCatchNode.getType() == TokenStream.CATCH);
1126: // Map the catch variable.
1127: //
1128: Node fromCatchVarName = fromCatchNode.getFirstChild();
1129: JsCatch catchBlock = new JsCatch(getScope(),
1130: fromCatchVarName.getString());
1131:
1132: // Pre-advance to the next catch block, if any.
1133: // We do this here to decide whether or not this is the last one.
1134: //
1135: fromCatchNode = fromCatchNode.getNext();
1136:
1137: // Map the condition, with a little fixup based on whether or not
1138: // this is the last catch block.
1139: //
1140: Node fromCondition = fromCatchVarName.getNext();
1141: JsExpression toCondition = mapExpression(fromCondition);
1142: catchBlock.setCondition(toCondition);
1143: if (fromCatchNode == null) {
1144: if (toCondition instanceof JsBooleanLiteral) {
1145: if (((JsBooleanLiteral) toCondition).getValue()) {
1146: // Actually, this is an unconditional catch block.
1147: // Indicate that by nulling the condition.
1148: //
1149: catchBlock.setCondition(null);
1150: }
1151: }
1152: }
1153:
1154: // Map the catch body.
1155: //
1156: Node fromCatchBody = fromCondition.getNext();
1157: pushScope(catchBlock.getScope());
1158: catchBlock.setBody(mapBlock(fromCatchBody));
1159: popScope();
1160:
1161: // Attach it.
1162: //
1163: toTry.getCatches().add(catchBlock);
1164: }
1165:
1166: Node fromFinallyNode = fromCatchNodes.getNext();
1167: if (fromFinallyNode != null) {
1168: toTry.setFinallyBlock(mapBlock(fromFinallyNode));
1169: }
1170:
1171: return toTry;
1172: }
1173:
1174: private JsExpression mapUnaryVariant(Node unOp)
1175: throws JsParserException {
1176: switch (unOp.getIntDatum()) {
1177: case TokenStream.SUB:
1178: return mapPrefixOperation(JsUnaryOperator.NEG, unOp);
1179:
1180: case TokenStream.NOT:
1181: return mapPrefixOperation(JsUnaryOperator.NOT, unOp);
1182:
1183: case TokenStream.BITNOT:
1184: return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp);
1185:
1186: case TokenStream.TYPEOF:
1187: return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp);
1188:
1189: case TokenStream.ADD:
1190: // Pretend we didn't see it.
1191: return mapExpression(unOp.getFirstChild());
1192:
1193: case TokenStream.VOID:
1194: return mapPrefixOperation(JsUnaryOperator.VOID, unOp);
1195:
1196: default:
1197: throw new JsParserException(
1198: "Unknown unary operator variant: "
1199: + unOp.getIntDatum());
1200: }
1201: }
1202:
1203: private JsVars mapVar(Node varNode) throws JsParserException {
1204: JsVars toVars = new JsVars();
1205: Node fromVar = varNode.getFirstChild();
1206: while (fromVar != null) {
1207: // Use a conservative name allocation strategy that allocates all names
1208: // from the function's scope, even the names of properties in field
1209: // literals.
1210: //
1211: String fromName = fromVar.getString();
1212: JsName toName = getScope().declareName(fromName);
1213: JsVars.JsVar toVar = new JsVars.JsVar(toName);
1214:
1215: Node fromInit = fromVar.getFirstChild();
1216: if (fromInit != null) {
1217: JsExpression toInit = mapExpression(fromInit);
1218: toVar.setInitExpr(toInit);
1219: }
1220: toVars.add(toVar);
1221:
1222: fromVar = fromVar.getNext();
1223: }
1224:
1225: return toVars;
1226: }
1227:
1228: private JsNode<?> mapWithStatement(Node withNode)
1229: throws JsParserException {
1230: // The "with" statement is unsupported because it introduces ambiguity
1231: // related to whether or not a name is obfuscatable that we cannot resolve
1232: // statically. This is modified in our copy of the Rhino Parser to provide
1233: // detailed source & line info. So, this method should never actually be
1234: // called.
1235: //
1236: throw createParserException(
1237: "Internal error: unexpected token 'with'", withNode);
1238: }
1239:
1240: private void popScope() {
1241: scopeStack.pop();
1242: }
1243:
1244: private void pushScope(JsScope scope) {
1245: scopeStack.push(scope);
1246: }
1247: }
|