/* * Copyright 2011 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.HasSourceInfo; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.js.ast.HasName; 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.JsNameOf; 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.JsProgram; import com.google.gwt.dev.js.ast.JsProgramFragment; 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.JsSeedIdOf; 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.JsSwitchMember; 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.JsVars.JsVar; import com.google.gwt.dev.js.ast.JsWhile; import com.google.gwt.dev.js.ast.NodeKind; import com.google.gwt.thirdparty.guava.common.base.Preconditions; import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.javascript.jscomp.AstValidator; import com.google.gwt.thirdparty.javascript.rhino.IR; import com.google.gwt.thirdparty.javascript.rhino.InputId; import com.google.gwt.thirdparty.javascript.rhino.Node; import com.google.gwt.thirdparty.javascript.rhino.Token; import com.google.gwt.thirdparty.javascript.rhino.jstype.SimpleSourceFile; import com.google.gwt.thirdparty.javascript.rhino.jstype.StaticSourceFile; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * Translate a Dart JS AST to a Closure Compiler AST. */ public class ClosureJsAstTranslator { private static String getStringValue(double value) { long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString(value); } } private final Map<String, StaticSourceFile> sourceCache = new HashMap<String, StaticSourceFile>(); private final boolean validate; private final Set<String> globalVars = Sets.newHashSet(); private final Set<String> externalProperties = Sets.newHashSet(); private final Set<String> externalVars = Sets.newHashSet(); private final JsProgram program; ClosureJsAstTranslator(boolean validate, JsProgram program) { this.program = program; this.validate = validate; } public Node translate(JsProgramFragment fragment, InputId inputId, String source) { Node script = IR.script(); script.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); script.setInputId(inputId); script.putProp(Node.SOURCENAME_PROP, source); script.setStaticSourceFile(getClosureSourceFile(source)); for (JsStatement s : fragment.getGlobalBlock().getStatements()) { script.addChildToBack(transform(s)); } // Validate the structural integrity of the AST. if (validate) { new AstValidator().validateScript(script); } return script; } Set<String> getExternalPropertyReferences() { return externalProperties; } Set<String> getExternalVariableReferences() { return externalVars; } Set<String> getGlobalVariableNames() { return globalVars; } private Node applyOriginalName(Node n, JsNode x) { /* * if (x instanceof HasSymbol) { Symbol symbol = ((HasSymbol)x).getSymbol(); if (symbol != null) * { String originalName = symbol.getOriginalSymbolName(); n.putProp(Node.ORIGINALNAME_PROP, * originalName); } } */ return n; } private Node applySourceInfo(Node n, HasSourceInfo srcNode) { if (n != null && srcNode != null) { SourceInfo info = srcNode.getSourceInfo(); if (info != null && info.getFileName() != null) { n.setStaticSourceFile(getClosureSourceFile(info.getFileName())); n.setLineno(info.getStartLine()); n.setCharno(0); } } return n; } private StaticSourceFile getClosureSourceFile(String source) { StaticSourceFile closureSourceFile = sourceCache.get(source); if (closureSourceFile == null) { closureSourceFile = new SimpleSourceFile(source, false); sourceCache.put(source, closureSourceFile); } return closureSourceFile; } private String getName(JsName name) { return name.getShortIdent(); } private String getName(JsNameRef name) { return name.getShortIdent(); } private Node getNameNodeFor(HasName hasName) { Node n = IR.name(getName(hasName.getName())); applyOriginalName(n, (JsNode) hasName); return applySourceInfo(n, (HasSourceInfo) hasName); } private int getTokenForOp(JsBinaryOperator op) { switch (op) { case MUL: return Token.MUL; case DIV: return Token.DIV; case MOD: return Token.MOD; case ADD: return Token.ADD; case SUB: return Token.SUB; case SHL: return Token.LSH; case SHR: return Token.RSH; case SHRU: return Token.URSH; case LT: return Token.LT; case LTE: return Token.LE; case GT: return Token.GT; case GTE: return Token.GE; case INSTANCEOF: return Token.INSTANCEOF; case INOP: return Token.IN; case EQ: return Token.EQ; case NEQ: return Token.NE; case REF_EQ: return Token.SHEQ; case REF_NEQ: return Token.SHNE; case BIT_AND: return Token.BITAND; case BIT_XOR: return Token.BITXOR; case BIT_OR: return Token.BITOR; case AND: return Token.AND; case OR: return Token.OR; case ASG: return Token.ASSIGN; case ASG_ADD: return Token.ASSIGN_ADD; case ASG_SUB: return Token.ASSIGN_SUB; case ASG_MUL: return Token.ASSIGN_MUL; case ASG_DIV: return Token.ASSIGN_DIV; case ASG_MOD: return Token.ASSIGN_MOD; case ASG_SHL: return Token.ASSIGN_LSH; case ASG_SHR: return Token.ASSIGN_RSH; case ASG_SHRU: return Token.ASSIGN_URSH; case ASG_BIT_AND: return Token.ASSIGN_BITAND; case ASG_BIT_OR: return Token.ASSIGN_BITOR; case ASG_BIT_XOR: return Token.ASSIGN_BITXOR; case COMMA: return Token.COMMA; } return 0; } private int getTokenForOp(JsUnaryOperator op) { switch (op) { case BIT_NOT: return Token.BITNOT; case DEC: return Token.DEC; case DELETE: return Token.DELPROP; case INC: return Token.INC; case NEG: return Token.NEG; case POS: return Token.POS; case NOT: return Token.NOT; case TYPEOF: return Token.TYPEOF; case VOID: return Token.VOID; } throw new IllegalStateException(); } private Node transform(JsArrayAccess x) { Node n = IR.getelem(transform(x.getArrayExpr()), transform(x.getIndexExpr())); return applySourceInfo(n, x); } private Node transform(JsArrayLiteral x) { Node n = IR.arraylit(); for (Object element : x.getExpressions()) { JsExpression arg = (JsExpression) element; n.addChildToBack(transform(arg)); } return applySourceInfo(n, x); } private Node transform(JsBinaryOperation x) { JsBinaryOperator op = x.getOperator(); Node n = new Node(getTokenForOp(op), transform(x.getArg1()), transform(x.getArg2())); return applySourceInfo(n, x); } private Node transform(JsBlock x) { Node n = IR.block(); for (JsStatement s : x.getStatements()) { n.addChildToBack(transform(s)); } return applySourceInfo(n, x); } private Node transform(JsBooleanLiteral x) { Node n = x.getValue() ? IR.trueNode() : IR.falseNode(); return applySourceInfo(n, x); } private Node transform(JsBreak x) { Node n; JsNameRef label = x.getLabel(); if (label == null) { n = IR.breakNode(); } else { n = IR.breakNode(transformLabel(label)); } return applySourceInfo(n, x); } private Node transform(JsCase x) { Node expr = transform(x.getCaseExpr()); Node body = IR.block(); body.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); applySourceInfo(body, x); for (Object element : x.getStmts()) { JsStatement stmt = (JsStatement) element; body.addChildToBack(transform(stmt)); } Node n = IR.caseNode(expr, body); return applySourceInfo(n, x); } private Node transform(JsCatch x) { Node n = IR.catchNode(transformName(x.getParameter().getName()), transform(x.getBody())); Preconditions.checkState(x.getCondition() == null); return applySourceInfo(n, x); } private Node transform(JsConditional x) { Node n = IR.hook(transform(x.getTestExpression()), transform(x.getThenExpression()), transform(x .getElseExpression())); return applySourceInfo(n, x); } private Node transform(JsContinue x) { Node n; JsNameRef label = x.getLabel(); if (label == null) { n = IR.continueNode(); } else { n = IR.continueNode(transformLabel(label)); } return applySourceInfo(n, x); } private Node transform(JsDebugger x) { Node n = new Node(Token.DEBUGGER); return applySourceInfo(n, x); } private Node transform(JsDefault x) { Node body = IR.block(); body.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); applySourceInfo(body, x); for (Object element : x.getStmts()) { JsStatement stmt = (JsStatement) element; body.addChildToBack(transform(stmt)); } Node n = IR.defaultCase(body); return applySourceInfo(n, x); } private Node transform(JsDoWhile x) { Node n = IR.doNode(transformBody(x.getBody(), x), transform(x.getCondition())); return applySourceInfo(n, x); } private Node transform(JsEmpty x) { return IR.empty(); } private Node transform(JsExpression x) { assert x != null; switch (x.getKind()) { case ARRAY: return transform((JsArrayLiteral) x); case ARRAY_ACCESS: return transform((JsArrayAccess) x); case BINARY_OP: return transform((JsBinaryOperation) x); case CONDITIONAL: return transform((JsConditional) x); case INVOKE: return transform((JsInvocation) x); case FUNCTION: return transform((JsFunction) x); case OBJECT: return transform((JsObjectLiteral) x); case BOOLEAN: return transform((JsBooleanLiteral) x); case NULL: return transform((JsNullLiteral) x); case NUMBER: return transform((JsNumberLiteral) x); case REGEXP: return transform((JsRegExp) x); case STRING: return transform((JsStringLiteral) x); case THIS: return transform((JsThisRef) x); case NAME_OF: return transform((JsNameOf) x); case SEED_ID_OF: return transform((JsSeedIdOf) x); case NAME_REF: return transform((JsNameRef) x); case NEW: return transform((JsNew) x); case POSTFIX_OP: return transform((JsPostfixOperation) x); case PREFIX_OP: return transform((JsPrefixOperation) x); default: throw new IllegalStateException("Unexpected expression type: " + x.getClass().getSimpleName()); } } private Node transform(JsExprStmt x) { // The GWT JS AST doesn't produce function declarations, instead // they are expressions statements: Node expr = transform(x.getExpression()); if (!expr.isFunction()) { return IR.exprResult(expr); } else { return expr; } } private Node transform(JsFor x) { // The init expressions or var decl. // Node init; if (x.getInitExpr() != null) { init = transform(x.getInitExpr()); } else if (x.getInitVars() != null) { init = transform(x.getInitVars()); } else { init = IR.empty(); } // The loop test. // Node cond; if (x.getCondition() != null) { cond = transform(x.getCondition()); } else { cond = IR.empty(); } // The incr expression. // Node incr; if (x.getIncrExpr() != null) { incr = transform(x.getIncrExpr()); } else { incr = IR.empty(); } Node body = transformBody(x.getBody(), x); Node n = IR.forNode(init, cond, incr, body); return applySourceInfo(n, x); } private Node transform(JsForIn x) { Node valueExpr; if (x.getIterVarName() != null) { valueExpr = new Node(Token.VAR, transformName(x.getIterVarName())); } else { // Just a name ref. // valueExpr = transform(x.getIterExpr()); } Node n = IR.forIn(valueExpr, transform(x.getObjExpr()), transformBody(x.getBody(), x)); return applySourceInfo(n, x); } private Node transform(JsFunction x) { Node name; if (x.getName() != null) { name = getNameNodeFor(x); } else { name = IR.name(""); } applySourceInfo(name, x); Node params = IR.paramList(); for (Object element : x.getParameters()) { JsParameter param = (JsParameter) element; params.addChildToBack(transform(param)); } applySourceInfo(params, x); Node n = IR.function(name, params, transform(x.getBody())); if (name.getString().isEmpty()) { n.putProp(Node.ORIGINALNAME_PROP, ""); } else { applyOriginalName(n, x); } /* * if (x.isConstructor()) { JSDocInfoBuilder builder = new JSDocInfoBuilder(false); * builder.recordConstructor(); n.setJSDocInfo(builder.build(n)); } */ return applySourceInfo(n, x); } private Node transform(JsIf x) { Node n = IR.ifNode(transform(x.getIfExpr()), transformBody(x.getThenStmt(), x)); if (x.getElseStmt() != null) { n.addChildToBack(transformBody(x.getElseStmt(), x)); } return applySourceInfo(n, x); } private Node transform(JsInvocation x) { Node n = IR.call(transform(x.getQualifier())); for (Object element : x.getArguments()) { JsExpression arg = (JsExpression) element; n.addChildToBack(transform(arg)); } return applySourceInfo(n, x); } private Node transform(JsLabel x) { Node n = IR.label(transformLabel(x.getName()), transform(x.getStmt())); return applySourceInfo(n, x); } private Node transform(JsNameOf x) { Node n = transformName(x.getName().getShortIdent(), x); applyOriginalName(n, x); return applySourceInfo(n, x); } private Node transform(JsNameRef x) { Node n; JsName name = x.getName(); boolean isExternal = name == null || !name.isObfuscatable(); if (x.getQualifier() != null) { n = IR.getprop(transform(x.getQualifier()), transformNameAsString(x.getShortIdent(), x)); if (isExternal) { this.externalProperties.add(x.getShortIdent()); } } else { n = transformName(x.getShortIdent(), x); if (isExternal) { this.externalVars.add(x.getShortIdent()); } else if (name.getEnclosing() == program.getScope()) { this.globalVars.add(x.getShortIdent()); } } applyOriginalName(n, x); return applySourceInfo(n, x); } private Node transform(JsNew x) { Node n = IR.newNode(transform(x.getConstructorExpression())); for (Object element : x.getArguments()) { JsExpression arg = (JsExpression) element; n.addChildToBack(transform(arg)); } return applySourceInfo(n, x); } private Node transform(JsNullLiteral x) { return IR.nullNode(); } private Node transform(JsNumberLiteral x) { return IR.number(x.getValue()); } private Node transform(JsObjectLiteral x) { Node n = IR.objectlit(); for (Object element : x.getPropertyInitializers()) { JsPropertyInitializer propInit = (JsPropertyInitializer) element; Node key; if (propInit.getLabelExpr().getKind() == NodeKind.NUMBER) { key = transformNumberAsString((JsNumberLiteral) propInit.getLabelExpr()); key.putBooleanProp(Node.QUOTED_PROP, true); } else if (propInit.getLabelExpr().getKind() == NodeKind.NAME_REF) { key = transformNameAsString(((JsNameRef) propInit.getLabelExpr()).getShortIdent(), propInit .getLabelExpr()); } else { key = transform(propInit.getLabelExpr()); } Preconditions.checkState(key.isString(), key); n.addChildToBack(IR.propdef(key, transform(propInit.getValueExpr()))); } return applySourceInfo(n, x); } private Node transform(JsParameter x) { return getNameNodeFor(x); } private Node transform(JsPostfixOperation x) { Node n = new Node(getTokenForOp(x.getOperator()), transform(x.getArg())); n.putBooleanProp(Node.INCRDECR_PROP, true); return applySourceInfo(n, x); } private Node transform(JsPrefixOperation x) { Node n = new Node(getTokenForOp(x.getOperator()), transform(x.getArg())); return applySourceInfo(n, x); } private Node transform(JsRegExp x) { String flags = x.getFlags(); Node n = IR.regexp(Node.newString(x.getPattern()), Node.newString(flags != null ? x.getFlags() : "")); return applySourceInfo(n, x); } private Node transform(JsReturn x) { Node n = IR.returnNode(); JsExpression result = x.getExpr(); if (result != null) { n.addChildToBack(transform(x.getExpr())); } return applySourceInfo(n, x); } private Node transform(JsSeedIdOf x) { Node n = Node.newNumber(x.getSeedId()); return applySourceInfo(n, x); } private Node transform(JsStatement x) { switch (x.getKind()) { case BLOCK: return transform((JsBlock) x); case BREAK: return transform((JsBreak) x); case CONTINUE: return transform((JsContinue) x); case DEBUGGER: return transform((JsDebugger) x); case DO: return transform((JsDoWhile) x); case EMPTY: return transform((JsEmpty) x); case EXPR_STMT: return transform((JsExprStmt) x); case FOR: return transform((JsFor) x); case FOR_IN: return transform((JsForIn) x); case IF: return transform((JsIf) x); case LABEL: return transform((JsLabel) x); case RETURN: return transform((JsReturn) x); case SWITCH: return transform((JsSwitch) x); case THROW: return transform((JsThrow) x); case TRY: return transform((JsTry) x); case VARS: return transform((JsVars) x); case WHILE: return transform((JsWhile) x); default: throw new IllegalStateException("Unexpected statement type: " + x.getClass().getSimpleName()); } } private Node transform(JsStringLiteral x) { return IR.string(x.getValue()); } private Node transform(JsSwitch x) { Node n = IR.switchNode(transform(x.getExpr())); for (JsSwitchMember member : x.getCases()) { n.addChildToBack(transform(member)); } return applySourceInfo(n, x); } private Node transform(JsSwitchMember x) { switch (x.getKind()) { case CASE: return transform((JsCase) x); case DEFAULT: return transform((JsDefault) x); default: throw new IllegalStateException("Unexpected switch member type: " + x.getClass().getSimpleName()); } } private Node transform(JsThisRef x) { Node n = new Node(Token.THIS); return applySourceInfo(n, x); } private Node transform(JsThrow x) { Node n = IR.throwNode(transform(x.getExpr())); return applySourceInfo(n, x); } private Node transform(JsTry x) { Node n = new Node(Token.TRY, transform(x.getTryBlock())); Node catches = new Node(Token.BLOCK); for (JsCatch catchBlock : x.getCatches()) { catches.addChildToBack(transform(catchBlock)); } n.addChildToBack(catches); JsBlock finallyBlock = x.getFinallyBlock(); if (finallyBlock != null) { n.addChildToBack(transform(finallyBlock)); } return applySourceInfo(n, x); } private Node transform(JsVar x) { Node n = getNameNodeFor(x); JsExpression initExpr = x.getInitExpr(); if (initExpr != null) { n.addChildToBack(transform(initExpr)); } return applySourceInfo(n, x); } private Node transform(JsVars x) { Node n = new Node(Token.VAR); for (JsVar var : x) { n.addChildToBack(transform(var)); } return applySourceInfo(n, x); } private Node transform(JsWhile x) { Node n = IR.forNode(IR.empty(), transform(x.getCondition()), IR.empty(), transformBody(x.getBody(), x)); return applySourceInfo(n, x); } private Node transformBody(JsStatement x, HasSourceInfo parent) { Node n = transform(x); if (!n.isBlock()) { Node stmt = n; n = IR.block(); if (!stmt.isEmpty()) { n.addChildToBack(stmt); } applySourceInfo(n, parent); } return n; } private Node transformLabel(JsName label) { Node n = IR.labelName(getName(label)); return applySourceInfo(n, label.getStaticRef()); } private Node transformLabel(JsNameRef label) { Node n = IR.labelName(getName(label)); return applySourceInfo(n, label); } private Node transformName(JsName name) { Node n = IR.name(getName(name)); return applySourceInfo(n, name.getStaticRef()); } private Node transformName(String name, HasSourceInfo info) { Node n = IR.name(name); return applySourceInfo(n, info); } private Node transformNameAsString(String name, HasSourceInfo info) { Node n = IR.string(name); return applySourceInfo(n, info); } private Node transformNumberAsString(JsNumberLiteral literalNode) { Node irNode = Node.newString(getStringValue(literalNode.getValue())); return irNode; } }