diff --git a/js/js.parser/src/com/google/gwt/dev/js/JsParser.java b/js/js.parser/src/com/google/gwt/dev/js/JsParser.java index 46586e0f5a8..4e3bb6b02a4 100644 --- a/js/js.parser/src/com/google/gwt/dev/js/JsParser.java +++ b/js/js.parser/src/com/google/gwt/dev/js/JsParser.java @@ -84,1207 +84,1238 @@ import java.util.Stack; */ public class JsParser { - public static List 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 childStmts = parse(rootSourceInfo, scope, r); - List parentStmts = block.getStatements(); - parentStmts.addAll(childStmts); - } - - private final Stack scopeStack = new Stack(); - private final Stack sourceInfoStack = new Stack(); - - private JsParser() { - } - - List 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() { - @Override - public void error(String msg, String loc, int ln, String src, int col) { - throw new UncheckedJsParserException(new JsParserException(msg, ln, - src, col, rootSourceInfo.getFileName())); - } - - @Override - 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())); - } - - @Override - 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 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 args = invocation.getArguments(); - from = from.getNext(); - while (from != null) { - to = mapExpression(from); - args.add(to); - from = from.getNext(); + public static List parse( + SourceInfo rootSourceInfo, + JsScope scope, Reader r + ) throws IOException, JsParserException { + return new JsParser().parseImpl(rootSourceInfo, scope, r); } - 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(); + public static void parseInto( + SourceInfo rootSourceInfo, JsScope scope, + JsBlock block, Reader r + ) throws IOException, JsParserException { + List childStmts = parse(rootSourceInfo, scope, r); + List parentStmts = block.getStatements(); + parentStmts.addAll(childStmts); } - SourceInfo info = makeSourceInfo(ifNode); - pushSourceInfo(info); + private final Stack scopeStack = new Stack(); + private final Stack sourceInfoStack = new Stack(); - // 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); + private JsParser() { } - // 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 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 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 mapStatements(Node nodeStmts) - throws JsParserException { - List stmts = new ArrayList(); - 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. + List 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() { + @Override + public void error(String msg, String loc, int ln, String src, int col) { + throw new UncheckedJsParserException(new JsParserException(msg, ln, + src, col, rootSourceInfo.getFileName())); + } + + @Override + 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())); + } + + @Override + public void warning(String msg, String loc, int ln, String src, int col) { + // Ignore warnings. + } + }); + try { + // Parse using the Rhino parser. // - catchBlock.setCondition(null); - } + 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 stmts = mapStatements(topNode); + popScope(); + return stmts; + } + catch (UncheckedJsParserException e) { + throw e.getParserException(); + } + finally { + Context.exit(); } - } - - // 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)); + private JsParserException createParserException(String msg, Node offender) { + return new JsParserException(msg, offender.getLineno(), null, 0, + sourceInfoStack.peek().getFileName()); } - return toTry; - } + private JsScope getScope() { + return scopeStack.peek(); + } - private JsExpression mapUnaryVariant(Node unOp) throws JsParserException { - switch (unOp.getIntDatum()) { - case TokenStream.SUB: - return mapPrefixOperation(JsUnaryOperator.NEG, unOp); + 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())); + } - case TokenStream.NOT: - return mapPrefixOperation(JsUnaryOperator.NOT, unOp); + /** + * 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())); + } - case TokenStream.BITNOT: - return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp); + private JsNode map(Node node) throws JsParserException { - case TokenStream.TYPEOF: - return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp); + switch (node.getType()) { + case TokenStream.SCRIPT: { + JsBlock block = new JsBlock(makeSourceInfo(node)); + mapStatements(block.getStatements(), node); + return block; + } - case TokenStream.ADD: - if (unOp.getFirstChild().getType() != TokenStream.NUMBER) { - return mapPrefixOperation(JsUnaryOperator.POS, unOp); - } else { - // Pretend we didn't see it. - return mapExpression(unOp.getFirstChild()); + 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 args = invocation.getArguments(); + from = from.getNext(); + while (from != null) { + to = mapExpression(from); + args.add(to); + from = from.getNext(); } - 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(); + return invocation; } - popSourceInfo(); - return toVars; - } + private JsExpression mapConditional(Node condNode) throws JsParserException { + JsConditional toCond = new JsConditional(makeSourceInfo(condNode)); - 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); - } + Node fromTest = condNode.getFirstChild(); + toCond.setTestExpression(mapExpression(fromTest)); - private void popScope() { - scopeStack.pop(); - sourceInfoStack.pop(); - } + Node fromThen = fromTest.getNext(); + toCond.setThenExpression(mapExpression(fromThen)); - private void popSourceInfo() { - sourceInfoStack.pop(); - } + Node fromElse = fromThen.getNext(); + toCond.setElseExpression(mapExpression(fromElse)); - private void pushScope(JsScope scope, SourceInfo sourceInfo) { - scopeStack.push(scope); - sourceInfoStack.push(sourceInfo); - } + return toCond; + } - /** - * 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); - } + 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 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 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 mapStatements(Node nodeStmts) + throws JsParserException { + List stmts = new ArrayList(); + 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: + if (unOp.getFirstChild().getType() != TokenStream.NUMBER) { + return mapPrefixOperation(JsUnaryOperator.POS, unOp); + } + else { + // 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); + } }