com.google.gwt.dev.js.JsAstMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* 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.js.parserExceptions.JsParserException;
import com.google.gwt.dev.js.rhino.CodePosition;
import com.google.gwt.dev.js.rhino.Comment;
import com.google.gwt.dev.js.rhino.Node;
import com.google.gwt.dev.js.rhino.TokenStream;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.js.backend.ast.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class JsAstMapper {
private final ScopeContext scopeContext;
@NotNull
private final String fileName;
public JsAstMapper(@NotNull JsScope scope, @NotNull String fileName) {
scopeContext = new ScopeContext(scope);
this.fileName = fileName;
}
private static JsParserException createParserException(String msg, Node offender) {
return new JsParserException("Parser encountered internal error: " + msg, offender.getPosition());
}
private JsNode map(Node node) throws JsParserException {
return withLocation(mapWithComments(node), node);
}
private JsNode mapWithComments(Node node) throws JsParserException {
JsNode jsNode = mapWithoutLocation(node);
if (jsNode != null) {
jsNode.setCommentsBeforeNode(mapComments(node.getCommentsBeforeNode()));
jsNode.setCommentsAfterNode(mapComments(node.getCommentsAfterNode()));
}
return jsNode;
}
private JsNode mapWithoutLocation(Node node) throws JsParserException {
switch (node.getType()) {
case TokenStream.SCRIPT: {
JsBlock block = new JsBlock();
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 mapExpressionStatement(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:
return new JsStringLiteral(node.getString());
case TokenStream.NUMBER_INT:
return mapIntNumber(node);
case TokenStream.NUMBER:
return mapDoubleNumber(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:
case TokenStream.GENERATOR:
return mapFunction(node);
case TokenStream.BLOCK:
return mapBlock(node);
case TokenStream.SETNAME:
return mapBinaryOperation(JsBinaryOperator.ASG, node);
case TokenStream.NAME:
case TokenStream.BINDNAME:
return scopeContext.globalNameFor(node.getString()).makeRef();
case TokenStream.RETURN:
return mapReturn(node);
case TokenStream.BREAK:
return mapBreak(node);
case TokenStream.CONTINUE:
return mapContinue(node);
case TokenStream.YIELD:
return mapYield(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();
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;
return scopeContext.referenceFor(lit.getValue());
}
else {
throw createParserException("Expecting a name reference", nameRefNode);
}
}
private JsExpression mapAssignmentVariant(Node asgNode)
throws JsParserException {
switch (asgNode.getOperation()) {
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.getOperation(), 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(op, to1, to2);
}
private JsBlock mapBlock(Node nodeStmts) throws JsParserException {
JsBlock block = new JsBlock();
mapStatements(block.getStatements(), nodeStmts);
return block;
}
private JsBreak mapBreak(Node breakNode) {
return new JsBreak(getTargetLabel(breakNode));
}
@Nullable
private JsNameRef getTargetLabel(@NotNull Node statementWithLabel) {
int type = statementWithLabel.getType();
if (type != TokenStream.BREAK && type != TokenStream.CONTINUE) {
String tokenTypeName = TokenStream.tokenToName(statementWithLabel.getType());
throw new AssertionError("Unexpected node type with label: " + tokenTypeName);
}
Node label = statementWithLabel.getFirstChild();
if (label == null) return null;
String identifier = label.getString();
assert identifier != null: "If label exists identifier should not be null";
JsName labelName = scopeContext.labelFor(identifier);
assert labelName != null: "Unknown label name: " + identifier;
return labelName.makeRef();
}
private JsInvocation mapCall(Node callNode) throws JsParserException {
// Map the target expression.
//
Node from = callNode.getFirstChild();
JsExpression qualifier = mapExpression(from);
// Iterate over and map the arguments.
//
List arguments = new SmartList<>();
from = from.getNext();
while (from != null) {
arguments.add(mapExpression(from));
from = from.getNext();
}
return new JsInvocation(qualifier, arguments);
}
private JsExpression mapConditional(Node condNode) throws JsParserException {
JsConditional toCond = new JsConditional();
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) {
return new JsContinue(getTargetLabel(contNode));
}
private JsYield mapYield(Node yieldNode) {
return new JsYield(mapExpression(yieldNode.getFirstChild()));
}
private JsStatement mapDebuggerStatement(Node node) {
// Calls an optional method to invoke the debugger.
//
return new JsDebugger();
}
private JsExpression mapDeleteProp(Node node) throws JsParserException {
Node from = node.getFirstChild();
JsExpression to = mapExpression(from);
if (to instanceof JsNameRef) {
return new JsPrefixOperation(
JsUnaryOperator.DELETE, to);
}
else if (to instanceof JsArrayAccess) {
return new JsPrefixOperation(
JsUnaryOperator.DELETE, to);
}
else {
return new JsNullLiteral();
}
}
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();
}
// Map the test expression.
//
JsExpression toTestExpr = mapExpression(fromTestExpr);
// Map the body block.
//
JsStatement toBody = mapStatement(fromBody);
// Create and attach the "while" or "do" statement we're mapping to.
//
if (isWhile) {
return new JsWhile(toTestExpr, toBody);
}
else {
return new JsDoWhile(toTestExpr, toBody);
}
}
private JsExpression mapEqualityVariant(Node eqNode) throws JsParserException {
switch (eqNode.getOperation()) {
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.getOperation(), eqNode);
}
}
public 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 JsStatement mapExpressionStatement(Node node) throws JsParserException {
JsExpression expr = mapExpression(node.getFirstChild());
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();
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 = scopeContext.localNameFor(fromName);
toForIn = new JsForIn(toName);
Node fromIterInit = fromIterVarName.getFirstChild();
if (fromIterInit != null) {
// That has an initializer expression (useful only for side effects).
//
toForIn.setIterExpression(mapOptionalExpression(fromIterInit));
}
}
else {
// An unnamed iterator var.
//
toForIn = new JsForIn();
toForIn.setIterExpression(mapExpression(fromIter));
}
toForIn.setObjectExpression(mapExpression(fromObjExpr));
// The body stmt.
//
JsStatement bodyStmt = mapStatement(fromBody);
if (bodyStmt != null) {
toForIn.setBody(bodyStmt);
}
else {
toForIn.setBody(JsEmpty.INSTANCE);
}
return toForIn;
}
else {
// Regular ol' for loop.
//
JsFor toFor;
// The first item is either an expression or a JsVars.
JsNode init = map(fromInit);
JsExpression condition = mapOptionalExpression(fromTest);
JsExpression increment = mapOptionalExpression(fromIncr);
if (init instanceof JsVars) {
toFor = new JsFor((JsVars) init, condition, increment);
}
else {
assert (init == null || init instanceof JsExpression);
toFor = new JsFor((JsExpression) init, condition, increment);
}
JsStatement bodyStmt = mapStatement(fromBody);
if (bodyStmt != null) {
toFor.setBody(bodyStmt);
}
else {
toFor.setBody(JsEmpty.INSTANCE);
}
return toFor;
}
}
public JsFunction mapFunction(Node fnNode) throws JsParserException {
int nodeType = fnNode.getType();
assert nodeType == TokenStream.FUNCTION || nodeType == TokenStream.GENERATOR: "Expected function node, got: " + TokenStream.tokenToName(nodeType);
Node fromFnNameNode = fnNode.getFirstChild();
Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild();
Node fromBodyNode = fnNode.getFirstChild().getNext().getNext();
// Decide the function's name, if any.
//
String fnNameIdent = fromFnNameNode.getString();
JsName functionName = null;
if (fnNameIdent != null && fnNameIdent.length() > 0) {
functionName = scopeContext.localNameFor(fnNameIdent);
}
JsFunction toFn = scopeContext.enterFunction();
toFn.setName(functionName);
if (nodeType == TokenStream.GENERATOR) {
toFn.getModifiers().add(JsFunction.Modifier.GENERATOR);
}
while (fromParamNode != null) {
String fromParamName = fromParamNode.getString();
JsName name = scopeContext.localNameFor(fromParamName);
toFn.getParameters().add(withLocation(new JsParameter(name), fromParamNode));
fromParamNode = fromParamNode.getNext();
}
// Map the function's body.
//
JsBlock toBody = mapBlock(fromBodyNode);
toFn.setBody(toBody);
scopeContext.exitFunction();
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(to1, to2);
}
private JsNameRef mapGetProp(Node getPropNode) throws JsParserException {
Node from1 = getPropNode.getFirstChild();
Node from2 = from1.getNext();
JsExpression toQualifier = mapExpression(from1);
JsNameRef toNameRef;
toNameRef = mapAsPropertyNameRef(from2);
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(mapExpression(fromTestExpr), mapStatement(fromThenBlock));
// Map the "else" block.
//
if (fromElseBlock != null) {
toIf.setElseStatement(mapStatement(fromElseBlock));
}
return toIf;
}
private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node)
throws JsParserException {
switch (node.getOperation()) {
case TokenStream.PRE:
return mapPrefixOperation(op, node);
case TokenStream.POST:
return mapPostfixOperation(op, node);
default:
throw createParserException(
"Unknown prefix/postfix variant: " + node.getOperation(), node);
}
}
private JsLabel mapLabel(Node labelNode) throws JsParserException {
String fromName = labelNode.getFirstChild().getString();
JsName toName = scopeContext.enterLabel(fromName, fromName);
Node fromStmt = labelNode.getFirstChild().getNext();
JsLabel toLabel = new JsLabel(toName);
toLabel.setStatement(mapStatement(fromStmt));
scopeContext.exitLabel();
return toLabel;
}
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(
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 static JsExpression mapIntNumber(Node numberNode) {
double value = numberNode.getDouble();
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
return new JsIntLiteral((int) numberNode.getDouble());
}
else {
return new JsDoubleLiteral(numberNode.getDouble());
}
}
private static JsExpression mapDoubleNumber(Node numberNode) {
return new JsDoubleLiteral(numberNode.getDouble());
}
private JsExpression mapObjectLit(Node objLitNode) throws JsParserException {
JsObjectLiteral toLit = new JsObjectLiteral();
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(
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(op, to);
}
private JsExpression mapPrefixOperation(JsUnaryOperator op, Node node)
throws JsParserException {
Node from = node.getFirstChild();
JsExpression to = mapExpression(from);
return new JsPrefixOperation(op, to);
}
private static JsExpression mapPrimary(Node node) throws JsParserException {
switch (node.getOperation()) {
case TokenStream.THIS:
return new JsThisRef();
case TokenStream.TRUE:
return new JsBooleanLiteral(true);
case TokenStream.FALSE:
return new JsBooleanLiteral(false);
case TokenStream.NULL:
return new JsNullLiteral();
case TokenStream.UNDEFINED:
return new JsNameRef("undefined");
default:
throw createParserException("Unknown primary: " + node.getOperation(),
node);
}
}
private JsNode mapRegExp(Node regExpNode) {
JsRegExp toRegExp = new JsRegExp();
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.getOperation()) {
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.getOperation(), relNode);
}
}
private JsReturn mapReturn(Node returnNode) throws JsParserException {
JsReturn toReturn = new JsReturn();
Node from = returnNode.getFirstChild();
if (from != null) {
JsExpression to = mapExpression(from);
toReturn.setExpression(to);
}
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(
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(
JsBinaryOperator.ASG, lhs, toRhs);
}
private JsExpression mapShiftVariant(Node shiftNode) throws JsParserException {
switch (shiftNode.getOperation()) {
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.getOperation(), 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 JsEmpty.INSTANCE;
}
}
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();
}
}
public List mapStatements(Node nodeStmts)
throws JsParserException {
List stmts = new ArrayList<>();
mapStatements(stmts, nodeStmts);
return stmts;
}
private JsSwitch mapSwitchStatement(Node switchNode) throws JsParserException {
JsSwitch toSwitch = new JsSwitch();
// The switch expression.
//
Node fromSwitchExpr = switchNode.getFirstChild();
toSwitch.setExpression(mapExpression(fromSwitchExpr));
// The members.
//
Node fromMember = fromSwitchExpr.getNext();
while (fromMember != null) {
if (fromMember.getType() == TokenStream.CASE) {
JsCase toCase = new JsCase();
// Set the case expression. In JS, this can be any expression.
//
Node fromCaseExpr = fromMember.getFirstChild();
toCase.setCaseExpression(mapExpression(fromCaseExpr));
// Set the case statements.
//
Node fromCaseBlock = fromCaseExpr.getNext();
mapStatements(toCase.getStatements(), 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();
// Set the default statements.
//
Node fromDefaultBlock = fromMember.getFirstChild();
mapStatements(toDefault.getStatements(), fromDefaultBlock);
// Attach the default to the switch.
//
toSwitch.getCases().add(toDefault);
}
fromMember = fromMember.getNext();
}
return toSwitch;
}
private JsThrow mapThrowStatement(Node throwNode) throws JsParserException {
// Create, map, and attach.
//
Node fromExpr = throwNode.getFirstChild();
JsThrow toThrow = new JsThrow(mapExpression(fromExpr));
return toThrow;
}
private JsTry mapTryStatement(Node tryNode) throws JsParserException {
JsTry toTry = new JsTry();
// 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 = scopeContext.enterCatch(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();
catchBlock.setBody(mapBlock(fromCatchBody));
// Attach it.
//
toTry.getCatches().add(catchBlock);
scopeContext.exitCatch();
}
Node fromFinallyNode = fromCatchNodes.getNext();
if (fromFinallyNode != null) {
toTry.setFinallyBlock(mapBlock(fromFinallyNode));
}
return toTry;
}
private JsExpression mapUnaryVariant(Node unOp) throws JsParserException {
switch (unOp.getOperation()) {
case TokenStream.SUB: {
Node operand = unOp.getFirstChild();
if (operand.getType() == TokenStream.NUMBER_INT) {
double value = operand.getDouble();
if (-value >= Integer.MIN_VALUE) {
return new JsIntLiteral((int) -value);
}
}
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:
return mapPrefixOperation(JsUnaryOperator.POS, unOp);
case TokenStream.VOID:
return mapPrefixOperation(JsUnaryOperator.VOID, unOp);
default:
throw createParserException(
"Unknown unary operator variant: " + unOp.getOperation(), unOp);
}
}
private JsVars mapVar(Node varNode) throws JsParserException {
JsVars toVars = new JsVars();
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 = scopeContext.localNameFor(fromName);
JsVars.JsVar toVar = withLocation(new JsVars.JsVar(toName), fromVar);
Node fromInit = fromVar.getFirstChild();
if (fromInit != null) {
JsExpression toInit = mapExpression(fromInit);
toVar.setInitExpression(toInit);
}
toVars.add(toVar);
fromVar = fromVar.getNext();
}
return toVars;
}
private JsSingleLineComment mapSingleLineComment(Node node) {
return new JsSingleLineComment(node.getString());
}
private JsMultiLineComment mapMultiLineComment(Node node) {
return new JsMultiLineComment(node.getString());
}
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 T withLocation(T astNode, Node node) {
if (astNode == null) return null;
CodePosition location = node.getPosition();
switch (node.getType()) {
case TokenStream.FUNCTION:
// For functions, consider their location to be at the opening parenthesis.
Node c = node.getFirstChild();
while (c != null && c.getType() != TokenStream.LP)
c = c.getNext();
if (c != null && c.getPosition() != null)
location = c.getPosition();
break;
case TokenStream.GETPROP:
// For dot-qualified references, consider their position to be at the rightmost name reference.
location = node.getLastChild().getPosition();
break;
}
if (location != null) {
String originalName = null;
if (astNode instanceof JsFunction || astNode instanceof JsVars.JsVar || astNode instanceof JsParameter) {
JsName name = ((HasName) astNode).getName();
originalName = name != null ? name.toString() : null;
}
JsLocation jsLocation = new JsLocation(fileName, location.getLine(), location.getOffset(), originalName);
if (astNode instanceof SourceInfoAwareJsNode) {
astNode.setSource(jsLocation);
}
else if (astNode instanceof JsExpressionStatement) {
JsExpression expression = ((JsExpressionStatement) astNode).getExpression();
if (expression.getSource() == null) {
expression.setSource(jsLocation);
}
}
}
return astNode;
}
private List mapComments(Comment comment) {
if (comment == null) return null;
List comments = new LinkedList<>();
while (comment != null) {
String text = comment.getText();
JsComment jsComment = comment.isMultiLine() ? new JsMultiLineComment(text) : new JsSingleLineComment(text);
comments.add(jsComment);
comment = comment.getNext();
}
return comments;
}
}