/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.dev.js; import com.google.gwt.dev.jjs.Correlation.Literal; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.js.ast.JsArrayAccess; import com.google.gwt.dev.js.ast.JsArrayLiteral; import com.google.gwt.dev.js.ast.JsBinaryOperation; import com.google.gwt.dev.js.ast.JsBinaryOperator; import com.google.gwt.dev.js.ast.JsBlock; import com.google.gwt.dev.js.ast.JsBooleanLiteral; import com.google.gwt.dev.js.ast.JsBreak; import com.google.gwt.dev.js.ast.JsCase; import com.google.gwt.dev.js.ast.JsCatch; import com.google.gwt.dev.js.ast.JsConditional; import com.google.gwt.dev.js.ast.JsContinue; import com.google.gwt.dev.js.ast.JsDebugger; import com.google.gwt.dev.js.ast.JsDefault; import com.google.gwt.dev.js.ast.JsDoWhile; import com.google.gwt.dev.js.ast.JsEmpty; import com.google.gwt.dev.js.ast.JsExprStmt; import com.google.gwt.dev.js.ast.JsExpression; import com.google.gwt.dev.js.ast.JsFor; import com.google.gwt.dev.js.ast.JsForIn; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsIf; import com.google.gwt.dev.js.ast.JsInvocation; import com.google.gwt.dev.js.ast.JsLabel; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsNameRef; import com.google.gwt.dev.js.ast.JsNew; import com.google.gwt.dev.js.ast.JsNode; import com.google.gwt.dev.js.ast.JsNullLiteral; import com.google.gwt.dev.js.ast.JsNumberLiteral; import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsParameter; import com.google.gwt.dev.js.ast.JsPostfixOperation; import com.google.gwt.dev.js.ast.JsPrefixOperation; import com.google.gwt.dev.js.ast.JsPropertyInitializer; import com.google.gwt.dev.js.ast.JsRegExp; import com.google.gwt.dev.js.ast.JsReturn; import com.google.gwt.dev.js.ast.JsRootScope; import com.google.gwt.dev.js.ast.JsScope; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsStringLiteral; import com.google.gwt.dev.js.ast.JsSwitch; import com.google.gwt.dev.js.ast.JsThisRef; import com.google.gwt.dev.js.ast.JsThrow; import com.google.gwt.dev.js.ast.JsTry; import com.google.gwt.dev.js.ast.JsUnaryOperator; import com.google.gwt.dev.js.ast.JsVars; import com.google.gwt.dev.js.ast.JsWhile; import com.google.gwt.dev.js.rhino.Context; import com.google.gwt.dev.js.rhino.ErrorReporter; import com.google.gwt.dev.js.rhino.EvaluatorException; import com.google.gwt.dev.js.rhino.IRFactory; import com.google.gwt.dev.js.rhino.Node; import com.google.gwt.dev.js.rhino.Parser; import com.google.gwt.dev.js.rhino.TokenStream; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * Parses JavaScript source. */ public class JsParser { public static List<JsStatement> parse(SourceInfo rootSourceInfo, JsScope scope, Reader r) throws IOException, JsParserException { return new JsParser().parseImpl(rootSourceInfo, scope, r); } public static void parseInto(SourceInfo rootSourceInfo, JsScope scope, JsBlock block, Reader r) throws IOException, JsParserException { List<JsStatement> childStmts = parse(rootSourceInfo, scope, r); List<JsStatement> parentStmts = block.getStatements(); parentStmts.addAll(childStmts); } private final Stack<JsScope> scopeStack = new Stack<JsScope>(); private final Stack<SourceInfo> sourceInfoStack = new Stack<SourceInfo>(); private JsParser() { } List<JsStatement> parseImpl(final SourceInfo rootSourceInfo, JsScope scope, Reader r) throws JsParserException, IOException { // Create a custom error handler so that we can throw our own exceptions. Context.enter().setErrorReporter(new ErrorReporter() { public void error(String msg, String loc, int ln, String src, int col) { throw new UncheckedJsParserException(new JsParserException(msg, ln, src, col, rootSourceInfo.getFileName())); } public EvaluatorException runtimeError(String msg, String loc, int ln, String src, int col) { // Never called, but just in case. throw new UncheckedJsParserException(new JsParserException(msg, ln, src, col, rootSourceInfo.getFileName())); } public void warning(String msg, String loc, int ln, String src, int col) { // Ignore warnings. } }); try { // Parse using the Rhino parser. // TokenStream ts = new TokenStream(r, rootSourceInfo.getFileName(), rootSourceInfo.getStartLine()); Parser parser = new Parser(new IRFactory(ts)); Node topNode = (Node) parser.parse(ts); // Map the Rhino AST to ours. pushScope(scope, rootSourceInfo); List<JsStatement> stmts = mapStatements(topNode); popScope(); return stmts; } catch (UncheckedJsParserException e) { throw e.getParserException(); } finally { Context.exit(); } } private JsParserException createParserException(String msg, Node offender) { return new JsParserException(msg, offender.getLineno(), null, 0, sourceInfoStack.peek().getFileName()); } private JsScope getScope() { return scopeStack.peek(); } private SourceInfo makeSourceInfo(Node node) { SourceInfo parent = sourceInfoStack.peek(); int lineno = node.getLineno(); if (lineno == -1) { // Rhino only reports line numbers for statement nodes, not expressions return parent; } return parent.makeChild(SourceOrigin.create(lineno, parent.getFileName())); } /** * Force a distinct child to be created, so correlations can be added. */ private SourceInfo makeSourceInfoDistinct(Node node) { SourceInfo parent = sourceInfoStack.peek(); int lineno = node.getLineno(); if (lineno == -1) { // Rhino only reports line numbers for statement nodes, not expressions lineno = parent.getStartLine(); } return parent.makeChild(SourceOrigin.create(lineno, parent.getFileName())); } private JsNode map(Node node) throws JsParserException { switch (node.getType()) { case TokenStream.SCRIPT: { JsBlock block = new JsBlock(makeSourceInfo(node)); mapStatements(block.getStatements(), node); return block; } case TokenStream.DEBUGGER: return mapDebuggerStatement(node); case TokenStream.VOID: // VOID = nothing was parsed for this node return null; case TokenStream.EXPRSTMT: return mapExprStmt(node); case TokenStream.REGEXP: return mapRegExp(node); case TokenStream.ADD: return mapBinaryOperation(JsBinaryOperator.ADD, node); case TokenStream.SUB: return mapBinaryOperation(JsBinaryOperator.SUB, node); case TokenStream.MUL: return mapBinaryOperation(JsBinaryOperator.MUL, node); case TokenStream.DIV: return mapBinaryOperation(JsBinaryOperator.DIV, node); case TokenStream.MOD: return mapBinaryOperation(JsBinaryOperator.MOD, node); case TokenStream.AND: return mapBinaryOperation(JsBinaryOperator.AND, node); case TokenStream.OR: return mapBinaryOperation(JsBinaryOperator.OR, node); case TokenStream.BITAND: return mapBinaryOperation(JsBinaryOperator.BIT_AND, node); case TokenStream.BITOR: return mapBinaryOperation(JsBinaryOperator.BIT_OR, node); case TokenStream.BITXOR: return mapBinaryOperation(JsBinaryOperator.BIT_XOR, node); case TokenStream.ASSIGN: return mapAssignmentVariant(node); case TokenStream.RELOP: return mapRelationalVariant(node); case TokenStream.EQOP: return mapEqualityVariant(node); case TokenStream.SHOP: return mapShiftVariant(node); case TokenStream.UNARYOP: return mapUnaryVariant(node); case TokenStream.INC: return mapIncDecFixity(JsUnaryOperator.INC, node); case TokenStream.DEC: return mapIncDecFixity(JsUnaryOperator.DEC, node); case TokenStream.HOOK: return mapConditional(node); case TokenStream.STRING: { SourceInfo info = makeSourceInfoDistinct(node); info.addCorrelation(info.getCorrelator().by(Literal.STRING)); return new JsStringLiteral(info, node.getString()); } case TokenStream.NUMBER: return mapNumber(node); case TokenStream.CALL: return mapCall(node); case TokenStream.GETPROP: return mapGetProp(node); case TokenStream.SETPROP: return mapSetProp(node); case TokenStream.DELPROP: return mapDeleteProp(node); case TokenStream.IF: return mapIfStatement(node); case TokenStream.WHILE: return mapDoOrWhileStatement(true, node); case TokenStream.DO: return mapDoOrWhileStatement(false, node); case TokenStream.FOR: return mapForStatement(node); case TokenStream.WITH: return mapWithStatement(node); case TokenStream.GETELEM: return mapGetElem(node); case TokenStream.SETELEM: return mapSetElem(node); case TokenStream.FUNCTION: return mapFunction(node); case TokenStream.BLOCK: return mapBlock(node); case TokenStream.SETNAME: return mapBinaryOperation(JsBinaryOperator.ASG, node); case TokenStream.NAME: case TokenStream.BINDNAME: return mapName(node); case TokenStream.RETURN: return mapReturn(node); case TokenStream.BREAK: return mapBreak(node); case TokenStream.CONTINUE: return mapContinue(node); case TokenStream.OBJLIT: return mapObjectLit(node); case TokenStream.ARRAYLIT: return mapArrayLit(node); case TokenStream.VAR: return mapVar(node); case TokenStream.PRIMARY: return mapPrimary(node); case TokenStream.COMMA: return mapBinaryOperation(JsBinaryOperator.COMMA, node); case TokenStream.NEW: return mapNew(node); case TokenStream.THROW: return mapThrowStatement(node); case TokenStream.TRY: return mapTryStatement(node); case TokenStream.SWITCH: return mapSwitchStatement(node); case TokenStream.LABEL: return mapLabel(node); default: int tokenType = node.getType(); throw createParserException("Unexpected top-level token type: " + tokenType, node); } } private JsArrayLiteral mapArrayLit(Node node) throws JsParserException { JsArrayLiteral toLit = new JsArrayLiteral(makeSourceInfo(node)); Node from = node.getFirstChild(); while (from != null) { toLit.getExpressions().add(mapExpression(from)); from = from.getNext(); } return toLit; } /** * Produces a {@link JsNameRef}. */ private JsNameRef mapAsPropertyNameRef(Node nameRefNode) throws JsParserException { JsNode unknown = map(nameRefNode); // This is weird, but for "a.b", the rhino AST calls "b" a string literal. // However, since we know it's for a PROPGET, we can unstringliteralize it. // if (unknown instanceof JsStringLiteral) { JsStringLiteral lit = (JsStringLiteral) unknown; String litName = lit.getValue(); return new JsNameRef(makeSourceInfo(nameRefNode), litName); } else { throw createParserException("Expecting a name reference", nameRefNode); } } private JsExpression mapAssignmentVariant(Node asgNode) throws JsParserException { switch (asgNode.getIntDatum()) { case TokenStream.NOP: return mapBinaryOperation(JsBinaryOperator.ASG, asgNode); case TokenStream.ADD: return mapBinaryOperation(JsBinaryOperator.ASG_ADD, asgNode); case TokenStream.SUB: return mapBinaryOperation(JsBinaryOperator.ASG_SUB, asgNode); case TokenStream.MUL: return mapBinaryOperation(JsBinaryOperator.ASG_MUL, asgNode); case TokenStream.DIV: return mapBinaryOperation(JsBinaryOperator.ASG_DIV, asgNode); case TokenStream.MOD: return mapBinaryOperation(JsBinaryOperator.ASG_MOD, asgNode); case TokenStream.BITAND: return mapBinaryOperation(JsBinaryOperator.ASG_BIT_AND, asgNode); case TokenStream.BITOR: return mapBinaryOperation(JsBinaryOperator.ASG_BIT_OR, asgNode); case TokenStream.BITXOR: return mapBinaryOperation(JsBinaryOperator.ASG_BIT_XOR, asgNode); case TokenStream.LSH: return mapBinaryOperation(JsBinaryOperator.ASG_SHL, asgNode); case TokenStream.RSH: return mapBinaryOperation(JsBinaryOperator.ASG_SHR, asgNode); case TokenStream.URSH: return mapBinaryOperation(JsBinaryOperator.ASG_SHRU, asgNode); default: throw createParserException("Unknown assignment operator variant: " + asgNode.getIntDatum(), asgNode); } } private JsExpression mapBinaryOperation(JsBinaryOperator op, Node node) throws JsParserException { Node from1 = node.getFirstChild(); Node from2 = from1.getNext(); JsExpression to1 = mapExpression(from1); JsExpression to2 = mapExpression(from2); return new JsBinaryOperation(makeSourceInfo(node), op, to1, to2); } private JsBlock mapBlock(Node nodeStmts) throws JsParserException { SourceInfo info = makeSourceInfo(nodeStmts); JsBlock block = new JsBlock(info); pushSourceInfo(info); mapStatements(block.getStatements(), nodeStmts); popSourceInfo(); return block; } private JsBreak mapBreak(Node breakNode) { Node fromLabel = breakNode.getFirstChild(); if (fromLabel != null) { return new JsBreak(makeSourceInfo(breakNode), mapName(fromLabel)); } else { return new JsBreak(makeSourceInfo(breakNode)); } } private JsInvocation mapCall(Node callNode) throws JsParserException { JsInvocation invocation = new JsInvocation(makeSourceInfo(callNode)); // Map the target expression. // Node from = callNode.getFirstChild(); JsExpression to = mapExpression(from); invocation.setQualifier(to); // Iterate over and map the arguments. // List<JsExpression> args = invocation.getArguments(); from = from.getNext(); while (from != null) { to = mapExpression(from); args.add(to); from = from.getNext(); } return invocation; } private JsExpression mapConditional(Node condNode) throws JsParserException { JsConditional toCond = new JsConditional(makeSourceInfo(condNode)); Node fromTest = condNode.getFirstChild(); toCond.setTestExpression(mapExpression(fromTest)); Node fromThen = fromTest.getNext(); toCond.setThenExpression(mapExpression(fromThen)); Node fromElse = fromThen.getNext(); toCond.setElseExpression(mapExpression(fromElse)); return toCond; } private JsContinue mapContinue(Node contNode) { Node fromLabel = contNode.getFirstChild(); if (fromLabel != null) { return new JsContinue(makeSourceInfo(contNode), mapName(fromLabel)); } else { return new JsContinue(makeSourceInfo(contNode)); } } private JsStatement mapDebuggerStatement(Node node) { // Calls an optional method to invoke the debugger. // return new JsDebugger(makeSourceInfo(node)); } private JsExpression mapDeleteProp(Node node) throws JsParserException { Node from = node.getFirstChild(); JsExpression to = mapExpression(from); if (to instanceof JsNameRef) { return new JsPrefixOperation(makeSourceInfo(node), JsUnaryOperator.DELETE, to); } else if (to instanceof JsArrayAccess) { return new JsPrefixOperation(makeSourceInfo(node), JsUnaryOperator.DELETE, to); } else { throw createParserException( "'delete' can only operate on property names and array elements", from); } } private JsStatement mapDoOrWhileStatement(boolean isWhile, Node ifNode) throws JsParserException { // Pull out the pieces we want to map. // Node fromTestExpr; Node fromBody; if (isWhile) { fromTestExpr = ifNode.getFirstChild(); fromBody = ifNode.getFirstChild().getNext(); } else { fromBody = ifNode.getFirstChild(); fromTestExpr = ifNode.getFirstChild().getNext(); } SourceInfo info = makeSourceInfo(ifNode); pushSourceInfo(info); // Map the test expression. // JsExpression toTestExpr = mapExpression(fromTestExpr); // Map the body block. // JsStatement toBody = mapStatement(fromBody); popSourceInfo(); // Create and attach the "while" or "do" statement we're mapping to. // if (isWhile) { return new JsWhile(info, toTestExpr, toBody); } else { return new JsDoWhile(info, toTestExpr, toBody); } } private JsExpression mapEqualityVariant(Node eqNode) throws JsParserException { switch (eqNode.getIntDatum()) { case TokenStream.EQ: return mapBinaryOperation(JsBinaryOperator.EQ, eqNode); case TokenStream.NE: return mapBinaryOperation(JsBinaryOperator.NEQ, eqNode); case TokenStream.SHEQ: return mapBinaryOperation(JsBinaryOperator.REF_EQ, eqNode); case TokenStream.SHNE: return mapBinaryOperation(JsBinaryOperator.REF_NEQ, eqNode); case TokenStream.LT: return mapBinaryOperation(JsBinaryOperator.LT, eqNode); case TokenStream.LE: return mapBinaryOperation(JsBinaryOperator.LTE, eqNode); case TokenStream.GT: return mapBinaryOperation(JsBinaryOperator.GT, eqNode); case TokenStream.GE: return mapBinaryOperation(JsBinaryOperator.GTE, eqNode); default: throw createParserException("Unknown equality operator variant: " + eqNode.getIntDatum(), eqNode); } } private JsExpression mapExpression(Node exprNode) throws JsParserException { JsNode unknown = map(exprNode); if (unknown instanceof JsExpression) { return (JsExpression) unknown; } else { throw createParserException("Expecting an expression", exprNode); } } private JsExprStmt mapExprStmt(Node node) throws JsParserException { pushSourceInfo(makeSourceInfo(node)); JsExpression expr = mapExpression(node.getFirstChild()); popSourceInfo(); return expr.makeStmt(); } private JsStatement mapForStatement(Node forNode) throws JsParserException { Node fromInit = forNode.getFirstChild(); Node fromTest = fromInit.getNext(); Node fromIncr = fromTest.getNext(); Node fromBody = fromIncr.getNext(); SourceInfo info = makeSourceInfo(forNode); if (fromBody == null) { // This could be a "for...in" structure. // We could based on the different child layout. // Node fromIter = forNode.getFirstChild(); Node fromObjExpr = fromIter.getNext(); fromBody = fromObjExpr.getNext(); JsForIn toForIn; if (fromIter.getType() == TokenStream.VAR) { // A named iterator var. // Node fromIterVarName = fromIter.getFirstChild(); String fromName = fromIterVarName.getString(); JsName toName = getScope().declareName(fromName); toForIn = new JsForIn(info, toName); Node fromIterInit = fromIterVarName.getFirstChild(); if (fromIterInit != null) { // That has an initializer expression (useful only for side effects). // toForIn.setIterExpr(mapOptionalExpression(fromIterInit)); } } else { // An unnamed iterator var. // toForIn = new JsForIn(info); toForIn.setIterExpr(mapExpression(fromIter)); } toForIn.setObjExpr(mapExpression(fromObjExpr)); // The body stmt. // JsStatement bodyStmt = mapStatement(fromBody); if (bodyStmt != null) { toForIn.setBody(bodyStmt); } else { toForIn.setBody(new JsEmpty(info)); } return toForIn; } else { // Regular ol' for loop. // JsFor toFor = new JsFor(info); // The first item is either an expression or a JsVars. JsNode initThingy = map(fromInit); if (initThingy != null) { if (initThingy instanceof JsVars) { toFor.setInitVars((JsVars) initThingy); } else { assert (initThingy instanceof JsExpression); toFor.setInitExpr((JsExpression) initThingy); } } toFor.setCondition(mapOptionalExpression(fromTest)); toFor.setIncrExpr(mapOptionalExpression(fromIncr)); JsStatement bodyStmt = mapStatement(fromBody); if (bodyStmt != null) { toFor.setBody(bodyStmt); } else { toFor.setBody(new JsEmpty(info)); } return toFor; } } private JsExpression mapFunction(Node fnNode) throws JsParserException { Node fromFnNameNode = fnNode.getFirstChild(); Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild(); Node fromBodyNode = fnNode.getFirstChild().getNext().getNext(); // Decide the function's name, if any. // String fromFnName = fromFnNameNode.getString(); JsName toFnName = null; if (fromFnName != null && fromFnName.length() > 0) { toFnName = getScope().declareName(fromFnName); } // Create it, and set the params. // SourceInfo fnSourceInfo = makeSourceInfo(fnNode); JsFunction toFn = new JsFunction(fnSourceInfo, getScope(), toFnName); // Creating a function also creates a new scope, which we push onto // the scope stack. // pushScope(toFn.getScope(), fnSourceInfo); while (fromParamNode != null) { String fromParamName = fromParamNode.getString(); // should this be unique? I think not since you can have dup args. JsName paramName = toFn.getScope().declareName(fromParamName); toFn.getParameters().add(new JsParameter(fnSourceInfo, paramName)); fromParamNode = fromParamNode.getNext(); } // Map the function's body. // JsBlock toBody = mapBlock(fromBodyNode); toFn.setBody(toBody); // Pop the new function's scope off of the scope stack. // popScope(); return toFn; } private JsArrayAccess mapGetElem(Node getElemNode) throws JsParserException { Node from1 = getElemNode.getFirstChild(); Node from2 = from1.getNext(); JsExpression to1 = mapExpression(from1); JsExpression to2 = mapExpression(from2); return new JsArrayAccess(makeSourceInfo(getElemNode), to1, to2); } private JsNameRef mapGetProp(Node getPropNode) throws JsParserException { Node from1 = getPropNode.getFirstChild(); Node from2 = from1.getNext(); JsExpression toQualifier = mapExpression(from1); JsNameRef toNameRef; if (from2 != null) { toNameRef = mapAsPropertyNameRef(from2); } else { // Special properties don't have a second expression. // Object obj = getPropNode.getProp(Node.SPECIAL_PROP_PROP); assert (obj instanceof String); toNameRef = new JsNameRef(makeSourceInfo(getPropNode), (String) obj); } toNameRef.setQualifier(toQualifier); return toNameRef; } private JsIf mapIfStatement(Node ifNode) throws JsParserException { // Pull out the pieces we want to map. // Node fromTestExpr = ifNode.getFirstChild(); Node fromThenBlock = ifNode.getFirstChild().getNext(); Node fromElseBlock = ifNode.getFirstChild().getNext().getNext(); // Create the "if" statement we're mapping to. // JsIf toIf = new JsIf(makeSourceInfo(ifNode)); // Map the test expression. // JsExpression toTestExpr = mapExpression(fromTestExpr); toIf.setIfExpr(toTestExpr); // Map the "then" block. // toIf.setThenStmt(mapStatement(fromThenBlock)); // Map the "else" block. // if (fromElseBlock != null) { toIf.setElseStmt(mapStatement(fromElseBlock)); } return toIf; } private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node) throws JsParserException { switch (node.getIntDatum()) { case TokenStream.PRE: return mapPrefixOperation(op, node); case TokenStream.POST: return mapPostfixOperation(op, node); default: throw createParserException( "Unknown prefix/postfix variant: " + node.getIntDatum(), node); } } private JsLabel mapLabel(Node labelNode) throws JsParserException { String fromName = labelNode.getFirstChild().getString(); JsName toName = getScope().declareName(fromName); Node fromStmt = labelNode.getFirstChild().getNext(); JsLabel toLabel = new JsLabel(makeSourceInfo(labelNode), toName); toLabel.setStmt(mapStatement(fromStmt)); return toLabel; } /** * Creates a reference to a name that may or may not be obfuscatable, based on * whether it matches a known name in the scope. */ private JsNameRef mapName(Node node) { String ident = node.getString(); return new JsNameRef(makeSourceInfo(node), ident); } private JsNew mapNew(Node newNode) throws JsParserException { // Map the constructor expression, which is often just the name of // some lambda. // Node fromCtorExpr = newNode.getFirstChild(); JsNew newExpr = new JsNew(makeSourceInfo(newNode), mapExpression(fromCtorExpr)); // Iterate over and map the arguments. // List<JsExpression> args = newExpr.getArguments(); Node fromArg = fromCtorExpr.getNext(); while (fromArg != null) { args.add(mapExpression(fromArg)); fromArg = fromArg.getNext(); } return newExpr; } private JsExpression mapNumber(Node numberNode) { return new JsNumberLiteral(makeSourceInfo(numberNode), numberNode.getDouble()); } private JsExpression mapObjectLit(Node objLitNode) throws JsParserException { JsObjectLiteral toLit = new JsObjectLiteral(makeSourceInfo(objLitNode)); Node fromPropInit = objLitNode.getFirstChild(); while (fromPropInit != null) { Node fromLabelExpr = fromPropInit; JsExpression toLabelExpr = mapExpression(fromLabelExpr); // Advance to the initializer expression. // fromPropInit = fromPropInit.getNext(); Node fromValueExpr = fromPropInit; if (fromValueExpr == null) { throw createParserException("Expected an init expression for: " + toLabelExpr, objLitNode); } JsExpression toValueExpr = mapExpression(fromValueExpr); JsPropertyInitializer toPropInit = new JsPropertyInitializer( makeSourceInfo(fromLabelExpr), toLabelExpr, toValueExpr); toLit.getPropertyInitializers().add(toPropInit); // Begin the next property initializer, if there is one. // fromPropInit = fromPropInit.getNext(); } return toLit; } private JsExpression mapOptionalExpression(Node exprNode) throws JsParserException { JsNode unknown = map(exprNode); if (unknown != null) { if (unknown instanceof JsExpression) { return (JsExpression) unknown; } else { throw createParserException("Expecting an expression or null", exprNode); } } return null; } private JsExpression mapPostfixOperation(JsUnaryOperator op, Node node) throws JsParserException { Node from = node.getFirstChild(); JsExpression to = mapExpression(from); return new JsPostfixOperation(makeSourceInfo(node), op, to); } private JsExpression mapPrefixOperation(JsUnaryOperator op, Node node) throws JsParserException { Node from = node.getFirstChild(); JsExpression to = mapExpression(from); return new JsPrefixOperation(makeSourceInfo(node), op, to); } private JsExpression mapPrimary(Node node) throws JsParserException { switch (node.getIntDatum()) { case TokenStream.THIS: return new JsThisRef(makeSourceInfo(node)); case TokenStream.TRUE: return JsBooleanLiteral.TRUE; case TokenStream.FALSE: return JsBooleanLiteral.FALSE; case TokenStream.NULL: return JsNullLiteral.INSTANCE; case TokenStream.UNDEFINED: return new JsNameRef(makeSourceInfo(node), JsRootScope.INSTANCE.getUndefined()); default: throw createParserException("Unknown primary: " + node.getIntDatum(), node); } } private JsNode mapRegExp(Node regExpNode) { JsRegExp toRegExp = new JsRegExp(makeSourceInfo(regExpNode)); Node fromPattern = regExpNode.getFirstChild(); toRegExp.setPattern(fromPattern.getString()); Node fromFlags = fromPattern.getNext(); if (fromFlags != null) { toRegExp.setFlags(fromFlags.getString()); } return toRegExp; } private JsExpression mapRelationalVariant(Node relNode) throws JsParserException { switch (relNode.getIntDatum()) { case TokenStream.LT: return mapBinaryOperation(JsBinaryOperator.LT, relNode); case TokenStream.LE: return mapBinaryOperation(JsBinaryOperator.LTE, relNode); case TokenStream.GT: return mapBinaryOperation(JsBinaryOperator.GT, relNode); case TokenStream.GE: return mapBinaryOperation(JsBinaryOperator.GTE, relNode); case TokenStream.INSTANCEOF: return mapBinaryOperation(JsBinaryOperator.INSTANCEOF, relNode); case TokenStream.IN: return mapBinaryOperation(JsBinaryOperator.INOP, relNode); default: throw createParserException("Unknown relational operator variant: " + relNode.getIntDatum(), relNode); } } private JsReturn mapReturn(Node returnNode) throws JsParserException { SourceInfo info = makeSourceInfo(returnNode); JsReturn toReturn = new JsReturn(info); pushSourceInfo(info); Node from = returnNode.getFirstChild(); if (from != null) { JsExpression to = mapExpression(from); toReturn.setExpr(to); } popSourceInfo(); return toReturn; } private JsExpression mapSetElem(Node setElemNode) throws JsParserException { // Reuse the get elem code. // JsArrayAccess lhs = mapGetElem(setElemNode); // Map the RHS. // Node fromRhs = setElemNode.getFirstChild().getNext().getNext(); JsExpression toRhs = mapExpression(fromRhs); return new JsBinaryOperation(makeSourceInfo(setElemNode), JsBinaryOperator.ASG, lhs, toRhs); } private JsExpression mapSetProp(Node getPropNode) throws JsParserException { // Reuse the get prop code. // JsNameRef lhs = mapGetProp(getPropNode); // Map the RHS. // Node fromRhs = getPropNode.getFirstChild().getNext().getNext(); JsExpression toRhs = mapExpression(fromRhs); return new JsBinaryOperation(makeSourceInfo(getPropNode), JsBinaryOperator.ASG, lhs, toRhs); } private JsExpression mapShiftVariant(Node shiftNode) throws JsParserException { switch (shiftNode.getIntDatum()) { case TokenStream.LSH: return mapBinaryOperation(JsBinaryOperator.SHL, shiftNode); case TokenStream.RSH: return mapBinaryOperation(JsBinaryOperator.SHR, shiftNode); case TokenStream.URSH: return mapBinaryOperation(JsBinaryOperator.SHRU, shiftNode); default: throw createParserException("Unknown equality operator variant: " + shiftNode.getIntDatum(), shiftNode); } } private JsStatement mapStatement(Node nodeStmt) throws JsParserException { JsNode unknown = map(nodeStmt); if (unknown != null) { if (unknown instanceof JsStatement) { return (JsStatement) unknown; } else if (unknown instanceof JsExpression) { return ((JsExpression) unknown).makeStmt(); } else { throw createParserException("Expecting a statement", nodeStmt); } } else { // When map() returns null, we return an empty statement. // return new JsEmpty(makeSourceInfo(nodeStmt)); } } private void mapStatements(List<JsStatement> stmts, Node nodeStmts) throws JsParserException { Node curr = nodeStmts.getFirstChild(); while (curr != null) { JsStatement stmt = mapStatement(curr); if (stmt != null) { stmts.add(stmt); } else { // When mapStatement() returns null, we just ignore it. // } curr = curr.getNext(); } } private List<JsStatement> mapStatements(Node nodeStmts) throws JsParserException { List<JsStatement> stmts = new ArrayList<JsStatement>(); mapStatements(stmts, nodeStmts); return stmts; } private JsSwitch mapSwitchStatement(Node switchNode) throws JsParserException { SourceInfo info = makeSourceInfo(switchNode); JsSwitch toSwitch = new JsSwitch(info); pushSourceInfo(info); // The switch expression. // Node fromSwitchExpr = switchNode.getFirstChild(); toSwitch.setExpr(mapExpression(fromSwitchExpr)); // The members. // Node fromMember = fromSwitchExpr.getNext(); while (fromMember != null) { if (fromMember.getType() == TokenStream.CASE) { JsCase toCase = new JsCase(makeSourceInfo(fromMember)); // Set the case expression. In JS, this can be any expression. // Node fromCaseExpr = fromMember.getFirstChild(); toCase.setCaseExpr(mapExpression(fromCaseExpr)); // Set the case statements. // Node fromCaseBlock = fromCaseExpr.getNext(); mapStatements(toCase.getStmts(), fromCaseBlock); // Attach the case to the switch. // toSwitch.getCases().add(toCase); } else { // This should be the only default statement. // If more than one is present, we keep the last one. // assert (fromMember.getType() == TokenStream.DEFAULT); JsDefault toDefault = new JsDefault(makeSourceInfo(fromMember)); // Set the default statements. // Node fromDefaultBlock = fromMember.getFirstChild(); mapStatements(toDefault.getStmts(), fromDefaultBlock); // Attach the default to the switch. // toSwitch.getCases().add(toDefault); } fromMember = fromMember.getNext(); } popSourceInfo(); return toSwitch; } private JsThrow mapThrowStatement(Node throwNode) throws JsParserException { SourceInfo info = makeSourceInfo(throwNode); pushSourceInfo(info); // Create, map, and attach. // Node fromExpr = throwNode.getFirstChild(); JsThrow toThrow = new JsThrow(info, mapExpression(fromExpr)); popSourceInfo(); return toThrow; } private JsTry mapTryStatement(Node tryNode) throws JsParserException { JsTry toTry = new JsTry(makeSourceInfo(tryNode)); // Map the "try" body. // Node fromTryBody = tryNode.getFirstChild(); toTry.setTryBlock(mapBlock(fromTryBody)); // Map zero or more catch blocks. // Node fromCatchNodes = fromTryBody.getNext(); Node fromCatchNode = fromCatchNodes.getFirstChild(); while (fromCatchNode != null) { assert (fromCatchNode.getType() == TokenStream.CATCH); // Map the catch variable. // Node fromCatchVarName = fromCatchNode.getFirstChild(); JsCatch catchBlock = new JsCatch(makeSourceInfo(fromCatchNode), getScope(), fromCatchVarName.getString()); // Pre-advance to the next catch block, if any. // We do this here to decide whether or not this is the last one. // fromCatchNode = fromCatchNode.getNext(); // Map the condition, with a little fixup based on whether or not // this is the last catch block. // Node fromCondition = fromCatchVarName.getNext(); JsExpression toCondition = mapExpression(fromCondition); catchBlock.setCondition(toCondition); if (fromCatchNode == null) { if (toCondition instanceof JsBooleanLiteral) { if (((JsBooleanLiteral) toCondition).getValue()) { // Actually, this is an unconditional catch block. // Indicate that by nulling the condition. // catchBlock.setCondition(null); } } } // Map the catch body. // Node fromCatchBody = fromCondition.getNext(); pushScope(catchBlock.getScope(), catchBlock.getSourceInfo()); catchBlock.setBody(mapBlock(fromCatchBody)); popScope(); // Attach it. // toTry.getCatches().add(catchBlock); } Node fromFinallyNode = fromCatchNodes.getNext(); if (fromFinallyNode != null) { toTry.setFinallyBlock(mapBlock(fromFinallyNode)); } return toTry; } private JsExpression mapUnaryVariant(Node unOp) throws JsParserException { switch (unOp.getIntDatum()) { case TokenStream.SUB: return mapPrefixOperation(JsUnaryOperator.NEG, unOp); case TokenStream.NOT: return mapPrefixOperation(JsUnaryOperator.NOT, unOp); case TokenStream.BITNOT: return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp); case TokenStream.TYPEOF: return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp); case TokenStream.ADD: // Pretend we didn't see it. return mapExpression(unOp.getFirstChild()); case TokenStream.VOID: return mapPrefixOperation(JsUnaryOperator.VOID, unOp); default: throw createParserException( "Unknown unary operator variant: " + unOp.getIntDatum(), unOp); } } private JsVars mapVar(Node varNode) throws JsParserException { SourceInfo info = makeSourceInfo(varNode); pushSourceInfo(info); JsVars toVars = new JsVars(info); Node fromVar = varNode.getFirstChild(); while (fromVar != null) { // Use a conservative name allocation strategy that allocates all names // from the function's scope, even the names of properties in field // literals. // String fromName = fromVar.getString(); JsName toName = getScope().declareName(fromName); JsVars.JsVar toVar = new JsVars.JsVar(makeSourceInfo(fromVar), toName); Node fromInit = fromVar.getFirstChild(); if (fromInit != null) { JsExpression toInit = mapExpression(fromInit); toVar.setInitExpr(toInit); } toVars.add(toVar); fromVar = fromVar.getNext(); } popSourceInfo(); return toVars; } private JsNode mapWithStatement(Node withNode) throws JsParserException { // The "with" statement is unsupported because it introduces ambiguity // related to whether or not a name is obfuscatable that we cannot resolve // statically. This is modified in our copy of the Rhino Parser to provide // detailed source & line info. So, this method should never actually be // called. // throw createParserException("Internal error: unexpected token 'with'", withNode); } private void popScope() { scopeStack.pop(); sourceInfoStack.pop(); } private void popSourceInfo() { sourceInfoStack.pop(); } private void pushScope(JsScope scope, SourceInfo sourceInfo) { scopeStack.push(scope); sourceInfoStack.push(sourceInfo); } /** * This should be called when processing any Rhino statement Node that has * line number data so that enclosed expressions will have a useful source * location. * * @see Node#hasLineno */ private void pushSourceInfo(SourceInfo sourceInfo) { assert sourceInfo.getStartLine() >= 0 : "Bad SourceInfo line number"; sourceInfoStack.push(sourceInfo); } }