Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.dart.compiler.backend.js.JsToStringGenerationVisitor Maven / Gradle / Ivy
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.backend.js;
import com.google.dart.compiler.backend.js.ast.*;
import com.google.dart.compiler.backend.js.ast.JsVars.JsVar;
import com.google.dart.compiler.util.TextOutput;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsDoubleLiteral;
import static com.google.dart.compiler.backend.js.ast.JsNumberLiteral.JsIntLiteral;
/**
* Produces text output from a JavaScript AST.
*/
public class JsToStringGenerationVisitor extends JsVisitor {
private static final char[] CHARS_BREAK = "break".toCharArray();
private static final char[] CHARS_CASE = "case".toCharArray();
private static final char[] CHARS_CATCH = "catch".toCharArray();
private static final char[] CHARS_CONTINUE = "continue".toCharArray();
private static final char[] CHARS_DEBUGGER = "debugger".toCharArray();
private static final char[] CHARS_DEFAULT = "default".toCharArray();
private static final char[] CHARS_DO = "do".toCharArray();
private static final char[] CHARS_ELSE = "else".toCharArray();
private static final char[] CHARS_FALSE = "false".toCharArray();
private static final char[] CHARS_FINALLY = "finally".toCharArray();
private static final char[] CHARS_FOR = "for".toCharArray();
private static final char[] CHARS_FUNCTION = "function".toCharArray();
private static final char[] CHARS_IF = "if".toCharArray();
private static final char[] CHARS_IN = "in".toCharArray();
private static final char[] CHARS_NEW = "new".toCharArray();
private static final char[] CHARS_NULL = "null".toCharArray();
private static final char[] CHARS_RETURN = "return".toCharArray();
private static final char[] CHARS_SWITCH = "switch".toCharArray();
private static final char[] CHARS_THIS = "this".toCharArray();
private static final char[] CHARS_THROW = "throw".toCharArray();
private static final char[] CHARS_TRUE = "true".toCharArray();
private static final char[] CHARS_TRY = "try".toCharArray();
private static final char[] CHARS_VAR = "var".toCharArray();
private static final char[] CHARS_WHILE = "while".toCharArray();
private static final char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static CharSequence javaScriptString(String value) {
return javaScriptString(value, false);
}
/**
* Generate JavaScript code that evaluates to the supplied string. Adapted
* from {@link org.mozilla.javascript.ScriptRuntime#escapeString(String)}
* . The difference is that we quote with either " or ' depending on
* which one is used less inside the string.
*/
@SuppressWarnings({"ConstantConditions", "UnnecessaryFullyQualifiedName", "JavadocReference"})
public static CharSequence javaScriptString(CharSequence chars, boolean forceDoubleQuote) {
final int n = chars.length();
int quoteCount = 0;
int aposCount = 0;
for (int i = 0; i < n; i++) {
switch (chars.charAt(i)) {
case '"':
++quoteCount;
break;
case '\'':
++aposCount;
break;
}
}
StringBuilder result = new StringBuilder(n + 16);
char quoteChar = (quoteCount < aposCount || forceDoubleQuote) ? '"' : '\'';
result.append(quoteChar);
for (int i = 0; i < n; i++) {
char c = chars.charAt(i);
if (' ' <= c && c <= '~' && c != quoteChar && c != '\\') {
// an ordinary print character (like C isprint())
result.append(c);
continue;
}
int escape = -1;
switch (c) {
case '\b':
escape = 'b';
break;
case '\f':
escape = 'f';
break;
case '\n':
escape = 'n';
break;
case '\r':
escape = 'r';
break;
case '\t':
escape = 't';
break;
case '"':
escape = '"';
break; // only reach here if == quoteChar
case '\'':
escape = '\'';
break; // only reach here if == quoteChar
case '\\':
escape = '\\';
break;
}
if (escape >= 0) {
// an \escaped sort of character
result.append('\\');
result.append((char) escape);
}
else {
int hexSize;
if (c < 256) {
// 2-digit hex
result.append("\\x");
hexSize = 2;
}
else {
// Unicode.
result.append("\\u");
hexSize = 4;
}
// append hexadecimal form of ch left-padded with 0
for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
int digit = 0xf & (c >> shift);
result.append(HEX_DIGITS[digit]);
}
}
}
result.append(quoteChar);
escapeClosingTags(result);
return result;
}
/**
* Escapes any closing XML tags embedded in str
, which could
* potentially cause a parse failure in a browser, for example, embedding a
* closing <script>
tag.
*
* @param str an unescaped literal; May be null
*/
private static void escapeClosingTags(StringBuilder str) {
if (str == null) {
return;
}
int index = 0;
while ((index = str.indexOf("", index)) != -1) {
str.insert(index + 1, '\\');
}
}
protected boolean needSemi = true;
private boolean lineBreakAfterBlock = true;
/**
* "Global" blocks are either the global block of a fragment, or a block
* nested directly within some other global block. This definition matters
* because the statements designated by statementEnds and statementStarts are
* those that appear directly within these global blocks.
*/
private Set globalBlocks = new THashSet();
protected final TextOutput p;
public JsToStringGenerationVisitor(TextOutput out) {
p = out;
}
@Override
public void visitArrayAccess(@NotNull JsArrayAccess x) {
printPair(x, x.getArrayExpression());
leftSquare();
accept(x.getIndexExpression());
rightSquare();
}
@Override
public void visitArray(@NotNull JsArrayLiteral x) {
leftSquare();
printExpressions(x.getExpressions());
rightSquare();
}
private void printExpressions(List expressions) {
boolean notFirst = false;
for (JsExpression expression : expressions) {
notFirst = sepCommaOptSpace(notFirst) && !(expression instanceof JsDocComment);
boolean isEnclosed = parenPushIfCommaExpression(expression);
accept(expression);
if (isEnclosed) {
rightParen();
}
}
}
@Override
public void visitBinaryExpression(@NotNull JsBinaryOperation binaryOperation) {
JsBinaryOperator operator = binaryOperation.getOperator();
JsExpression arg1 = binaryOperation.getArg1();
boolean isExpressionEnclosed = parenPush(binaryOperation, arg1, !operator.isLeftAssociative());
accept(arg1);
if (operator.isKeyword()) {
_parenPopOrSpace(binaryOperation, arg1, !operator.isLeftAssociative());
}
else if (operator != JsBinaryOperator.COMMA) {
if (isExpressionEnclosed) {
rightParen();
}
spaceOpt();
}
p.print(operator.getSymbol());
JsExpression arg2 = binaryOperation.getArg2();
boolean isParenOpened;
if (operator == JsBinaryOperator.COMMA) {
isParenOpened = false;
spaceOpt();
}
else if (arg2 instanceof JsBinaryOperation && ((JsBinaryOperation) arg2).getOperator() == JsBinaryOperator.AND) {
spaceOpt();
leftParen();
isParenOpened = true;
}
else {
if (spaceCalc(operator, arg2)) {
isParenOpened = _parenPushOrSpace(binaryOperation, arg2, operator.isLeftAssociative());
}
else {
spaceOpt();
isParenOpened = parenPush(binaryOperation, arg2, operator.isLeftAssociative());
}
}
accept(arg2);
if (isParenOpened) {
rightParen();
}
}
@Override
public void visitBlock(@NotNull JsBlock x) {
printJsBlock(x, true);
}
@Override
public void visitBoolean(@NotNull JsLiteral.JsBooleanLiteral x) {
if (x.getValue()) {
p.print(CHARS_TRUE);
}
else {
p.print(CHARS_FALSE);
}
}
@Override
public void visitBreak(@NotNull JsBreak x) {
p.print(CHARS_BREAK);
continueOrBreakLabel(x);
}
@Override
public void visitContinue(@NotNull JsContinue x) {
p.print(CHARS_CONTINUE);
continueOrBreakLabel(x);
}
private void continueOrBreakLabel(JsContinue x) {
JsNameRef label = x.getLabel();
if (label != null && label.getIdent() != null) {
space();
p.print(label.getIdent());
}
}
@Override
public void visitCase(@NotNull JsCase x) {
p.print(CHARS_CASE);
space();
accept(x.getCaseExpression());
_colon();
newlineOpt();
printSwitchMemberStatements(x);
}
private void printSwitchMemberStatements(JsSwitchMember x) {
p.indentIn();
for (JsStatement stmt : x.getStatements()) {
needSemi = true;
accept(stmt);
if (needSemi) {
semi();
}
newlineOpt();
}
p.indentOut();
needSemi = false;
}
@Override
public void visitCatch(@NotNull JsCatch x) {
spaceOpt();
p.print(CHARS_CATCH);
spaceOpt();
leftParen();
nameDef(x.getParameter().getName());
// Optional catch condition.
//
JsExpression catchCond = x.getCondition();
if (catchCond != null) {
space();
_if();
space();
accept(catchCond);
}
rightParen();
spaceOpt();
accept(x.getBody());
}
@Override
public void visitConditional(@NotNull JsConditional x) {
// Associativity: for the then and else branches, it is safe to insert
// another
// ternary expression, but if the test expression is a ternary, it should
// get parentheses around it.
printPair(x, x.getTestExpression(), true);
spaceOpt();
p.print('?');
spaceOpt();
printPair(x, x.getThenExpression());
spaceOpt();
_colon();
spaceOpt();
printPair(x, x.getElseExpression());
}
private void printPair(JsExpression parent, JsExpression expression, boolean wrongAssoc) {
boolean isNeedParen = parenCalc(parent, expression, wrongAssoc);
if (isNeedParen) {
leftParen();
}
accept(expression);
if (isNeedParen) {
rightParen();
}
}
private void printPair(JsExpression parent, JsExpression expression) {
printPair(parent, expression, false);
}
@Override
public void visitDebugger(@NotNull JsDebugger x) {
p.print(CHARS_DEBUGGER);
}
@Override
public void visitDefault(@NotNull JsDefault x) {
p.print(CHARS_DEFAULT);
_colon();
printSwitchMemberStatements(x);
}
@Override
public void visitWhile(@NotNull JsWhile x) {
_while();
spaceOpt();
leftParen();
accept(x.getCondition());
rightParen();
nestedPush(x.getBody());
accept(x.getBody());
nestedPop(x.getBody());
}
@Override
public void visitDoWhile(@NotNull JsDoWhile x) {
p.print(CHARS_DO);
nestedPush(x.getBody());
accept(x.getBody());
nestedPop(x.getBody());
if (needSemi) {
semi();
newlineOpt();
}
else {
spaceOpt();
needSemi = true;
}
_while();
spaceOpt();
leftParen();
accept(x.getCondition());
rightParen();
}
@Override
public void visitEmpty(@NotNull JsEmpty x) {
}
@Override
public void visitExpressionStatement(@NotNull JsExpressionStatement x) {
boolean surroundWithParentheses = JsFirstExpressionVisitor.exec(x);
if (surroundWithParentheses) {
leftParen();
}
accept(x.getExpression());
if (surroundWithParentheses) {
rightParen();
}
}
@Override
public void visitFor(@NotNull JsFor x) {
_for();
spaceOpt();
leftParen();
// The init expressions or var decl.
//
if (x.getInitExpression() != null) {
accept(x.getInitExpression());
}
else if (x.getInitVars() != null) {
accept(x.getInitVars());
}
semi();
// The loop test.
//
if (x.getCondition() != null) {
spaceOpt();
accept(x.getCondition());
}
semi();
// The incr expression.
//
if (x.getIncrementExpression() != null) {
spaceOpt();
accept(x.getIncrementExpression());
}
rightParen();
nestedPush(x.getBody());
accept(x.getBody());
nestedPop(x.getBody());
}
@Override
public void visitForIn(@NotNull JsForIn x) {
_for();
spaceOpt();
leftParen();
if (x.getIterVarName() != null) {
var();
space();
nameDef(x.getIterVarName());
if (x.getIterExpression() != null) {
spaceOpt();
assignment();
spaceOpt();
accept(x.getIterExpression());
}
}
else {
// Just a name ref.
//
accept(x.getIterExpression());
}
space();
p.print(CHARS_IN);
space();
accept(x.getObjectExpression());
rightParen();
nestedPush(x.getBody());
accept(x.getBody());
nestedPop(x.getBody());
}
@Override
public void visitFunction(@NotNull JsFunction x) {
p.print(CHARS_FUNCTION);
space();
if (x.getName() != null) {
nameOf(x);
}
leftParen();
boolean notFirst = false;
for (Object element : x.getParameters()) {
JsParameter param = (JsParameter) element;
notFirst = sepCommaOptSpace(notFirst);
accept(param);
}
rightParen();
space();
lineBreakAfterBlock = false;
accept(x.getBody());
needSemi = true;
}
@Override
public void visitIf(@NotNull JsIf x) {
_if();
spaceOpt();
leftParen();
accept(x.getIfExpression());
rightParen();
JsStatement thenStmt = x.getThenStatement();
JsStatement elseStatement = x.getElseStatement();
if (elseStatement != null && thenStmt instanceof JsIf && ((JsIf)thenStmt).getElseStatement() == null) {
thenStmt = new JsBlock(thenStmt);
}
nestedPush(thenStmt);
accept(thenStmt);
nestedPop(thenStmt);
if (elseStatement != null) {
if (needSemi) {
semi();
newlineOpt();
}
else {
spaceOpt();
needSemi = true;
}
p.print(CHARS_ELSE);
boolean elseIf = elseStatement instanceof JsIf;
if (!elseIf) {
nestedPush(elseStatement);
}
else {
space();
}
accept(elseStatement);
if (!elseIf) {
nestedPop(elseStatement);
}
}
}
@Override
public void visitInvocation(@NotNull JsInvocation invocation) {
printPair(invocation, invocation.getQualifier());
leftParen();
printExpressions(invocation.getArguments());
rightParen();
}
@Override
public void visitLabel(@NotNull JsLabel x) {
nameOf(x);
_colon();
spaceOpt();
accept(x.getStatement());
}
@Override
public void visitNameRef(@NotNull JsNameRef nameRef) {
JsExpression qualifier = nameRef.getQualifier();
if (qualifier != null) {
final boolean enclose;
if (qualifier instanceof JsLiteral.JsValueLiteral) {
// "42.foo" is not allowed, but "(42).foo" is.
enclose = qualifier instanceof JsNumberLiteral;
}
else {
enclose = parenCalc(nameRef, qualifier, false);
}
if (enclose) {
leftParen();
}
accept(qualifier);
if (enclose) {
rightParen();
}
p.print('.');
}
p.maybeIndent();
beforeNodePrinted(nameRef);
p.print(nameRef.getIdent());
}
protected void beforeNodePrinted(JsNode node) {
}
@Override
public void visitNew(@NotNull JsNew x) {
p.print(CHARS_NEW);
space();
JsExpression constructorExpression = x.getConstructorExpression();
boolean needsParens = JsConstructExpressionVisitor.exec(constructorExpression);
if (needsParens) {
leftParen();
}
accept(constructorExpression);
if (needsParens) {
rightParen();
}
leftParen();
printExpressions(x.getArguments());
rightParen();
}
@Override
public void visitNull(@NotNull JsNullLiteral x) {
p.print(CHARS_NULL);
}
@Override
public void visitInt(@NotNull JsIntLiteral x) {
p.print(x.value);
}
@Override
public void visitDouble(@NotNull JsDoubleLiteral x) {
p.print(x.value);
}
@Override
public void visitObjectLiteral(@NotNull JsObjectLiteral objectLiteral) {
p.print('{');
if (objectLiteral.isMultiline()) {
p.indentIn();
}
boolean notFirst = false;
for (JsPropertyInitializer item : objectLiteral.getPropertyInitializers()) {
if (notFirst) {
p.print(',');
}
if (objectLiteral.isMultiline()) {
newlineOpt();
}
else if (notFirst) {
spaceOpt();
}
notFirst = true;
JsExpression labelExpr = item.getLabelExpr();
// labels can be either string, integral, or decimal literals
if (labelExpr instanceof JsNameRef) {
p.print(((JsNameRef) labelExpr).getIdent());
}
else if (labelExpr instanceof JsStringLiteral) {
p.print(((JsStringLiteral) labelExpr).getValue());
}
else {
accept(labelExpr);
}
_colon();
space();
JsExpression valueExpr = item.getValueExpr();
boolean wasEnclosed = parenPushIfCommaExpression(valueExpr);
accept(valueExpr);
if (wasEnclosed) {
rightParen();
}
}
if (objectLiteral.isMultiline()) {
p.indentOut();
newlineOpt();
}
p.print('}');
}
@Override
public void visitParameter(@NotNull JsParameter x) {
nameOf(x);
}
@Override
public void visitPostfixOperation(@NotNull JsPostfixOperation x) {
JsUnaryOperator op = x.getOperator();
JsExpression arg = x.getArg();
// unary operators always associate correctly (I think)
printPair(x, arg);
p.print(op.getSymbol());
}
@Override
public void visitPrefixOperation(@NotNull JsPrefixOperation x) {
JsUnaryOperator op = x.getOperator();
p.print(op.getSymbol());
JsExpression arg = x.getArg();
if (spaceCalc(op, arg)) {
space();
}
// unary operators always associate correctly (I think)
printPair(x, arg);
}
@Override
public void visitProgram(@NotNull JsProgram x) {
p.print("");
}
@Override
public void visitProgramFragment(@NotNull JsProgramFragment x) {
p.print("");
}
@Override
public void visitRegExp(@NotNull JsRegExp x) {
slash();
p.print(x.getPattern());
slash();
String flags = x.getFlags();
if (flags != null) {
p.print(flags);
}
}
@Override
public void visitReturn(@NotNull JsReturn x) {
p.print(CHARS_RETURN);
JsExpression expr = x.getExpression();
if (expr != null) {
space();
accept(expr);
}
}
@Override
public void visitString(@NotNull JsStringLiteral x) {
p.print(javaScriptString(x.getValue()));
}
@Override
public void visit(@NotNull JsSwitch x) {
p.print(CHARS_SWITCH);
spaceOpt();
leftParen();
accept(x.getExpression());
rightParen();
spaceOpt();
blockOpen();
acceptList(x.getCases());
blockClose();
}
@Override
public void visitThis(@NotNull JsLiteral.JsThisRef x) {
p.print(CHARS_THIS);
}
@Override
public void visitThrow(@NotNull JsThrow x) {
p.print(CHARS_THROW);
space();
accept(x.getExpression());
}
@Override
public void visitTry(@NotNull JsTry x) {
p.print(CHARS_TRY);
spaceOpt();
accept(x.getTryBlock());
acceptList(x.getCatches());
JsBlock finallyBlock = x.getFinallyBlock();
if (finallyBlock != null) {
p.print(CHARS_FINALLY);
spaceOpt();
accept(finallyBlock);
}
}
@Override
public void visit(@NotNull JsVar var) {
nameOf(var);
JsExpression initExpr = var.getInitExpression();
if (initExpr != null) {
spaceOpt();
assignment();
spaceOpt();
boolean isEnclosed = parenPushIfCommaExpression(initExpr);
accept(initExpr);
if (isEnclosed) {
rightParen();
}
}
}
@Override
public void visitVars(@NotNull JsVars vars) {
var();
space();
boolean sep = false;
for (JsVar var : vars) {
if (sep) {
if (vars.isMultiline()) {
newlineOpt();
}
p.print(',');
spaceOpt();
}
else {
sep = true;
}
accept(var);
}
}
@Override
public void visitDocComment(@NotNull JsDocComment comment) {
boolean asSingleLine = comment.getTags().size() == 1;
if (!asSingleLine) {
newlineOpt();
}
p.print("/**");
if (asSingleLine) {
space();
}
else {
p.newline();
}
boolean notFirst = false;
for (Map.Entry entry : comment.getTags().entrySet()) {
if (notFirst) {
p.newline();
p.print(' ');
p.print('*');
}
else {
notFirst = true;
}
p.print('@');
p.print(entry.getKey());
Object value = entry.getValue();
if (value != null) {
space();
if (value instanceof CharSequence) {
p.print((CharSequence) value);
}
else {
visitNameRef((JsNameRef) value);
}
}
if (!asSingleLine) {
p.newline();
}
}
if (asSingleLine) {
space();
}
else {
newlineOpt();
}
p.print('*');
p.print('/');
if (asSingleLine) {
spaceOpt();
}
}
protected final void newlineOpt() {
if (!p.isCompact()) {
p.newline();
}
}
protected void printJsBlock(JsBlock x, boolean finalNewline) {
if (!lineBreakAfterBlock) {
finalNewline = false;
lineBreakAfterBlock = true;
}
boolean needBraces = !x.isGlobalBlock();
if (needBraces) {
blockOpen();
}
Iterator iterator = x.getStatements().iterator();
while (iterator.hasNext()) {
boolean isGlobal = x.isGlobalBlock() || globalBlocks.contains(x);
JsStatement statement = iterator.next();
if (statement instanceof JsEmpty) {
continue;
}
needSemi = true;
boolean stmtIsGlobalBlock = false;
if (isGlobal) {
if (statement instanceof JsBlock) {
// A block inside a global block is still considered global
stmtIsGlobalBlock = true;
globalBlocks.add((JsBlock) statement);
}
}
accept(statement);
if (stmtIsGlobalBlock) {
//noinspection SuspiciousMethodCalls
globalBlocks.remove(statement);
}
if (needSemi) {
/*
* Special treatment of function declarations: If they are the only item in a
* statement (i.e. not part of an assignment operation), just give them
* a newline instead of a semi.
*/
boolean functionStmt =
statement instanceof JsExpressionStatement && ((JsExpressionStatement) statement).getExpression() instanceof JsFunction;
/*
* Special treatment of the last statement in a block: only a few
* statements at the end of a block require semicolons.
*/
boolean lastStatement = !iterator.hasNext() && needBraces && !JsRequiresSemiVisitor.exec(statement);
if (functionStmt) {
if (lastStatement) {
newlineOpt();
}
else {
p.newline();
}
}
else {
if (lastStatement) {
p.printOpt(';');
}
else {
semi();
}
newlineOpt();
}
}
}
if (needBraces) {
// _blockClose() modified
p.indentOut();
p.print('}');
if (finalNewline) {
newlineOpt();
}
}
needSemi = false;
}
private void assignment() {
p.print('=');
}
private void blockClose() {
p.indentOut();
p.print('}');
newlineOpt();
}
private void blockOpen() {
p.print('{');
p.indentIn();
newlineOpt();
}
private void _colon() {
p.print(':');
}
private void _for() {
p.print(CHARS_FOR);
}
private void _if() {
p.print(CHARS_IF);
}
private void leftParen() {
p.print('(');
}
private void leftSquare() {
p.print('[');
}
private void nameDef(JsName name) {
p.print(name.getIdent());
}
private void nameOf(HasName hasName) {
nameDef(hasName.getName());
}
private boolean nestedPop(JsStatement statement) {
boolean pop = !(statement instanceof JsBlock);
if (pop) {
p.indentOut();
}
return pop;
}
private boolean nestedPush(JsStatement statement) {
boolean push = !(statement instanceof JsBlock);
if (push) {
newlineOpt();
p.indentIn();
}
else {
spaceOpt();
}
return push;
}
private static boolean parenCalc(JsExpression parent, JsExpression child, boolean wrongAssoc) {
int parentPrec = JsPrecedenceVisitor.exec(parent);
int childPrec = JsPrecedenceVisitor.exec(child);
return parentPrec > childPrec || parentPrec == childPrec && wrongAssoc;
}
private boolean _parenPopOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) {
boolean doPop = parenCalc(parent, child, wrongAssoc);
if (doPop) {
rightParen();
}
else {
space();
}
return doPop;
}
private boolean parenPush(JsExpression parent, JsExpression child, boolean wrongAssoc) {
boolean doPush = parenCalc(parent, child, wrongAssoc);
if (doPush) {
leftParen();
}
return doPush;
}
private boolean parenPushIfCommaExpression(JsExpression x) {
boolean doPush = x instanceof JsBinaryOperation && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA;
if (doPush) {
leftParen();
}
return doPush;
}
private boolean _parenPushOrSpace(JsExpression parent, JsExpression child, boolean wrongAssoc) {
boolean doPush = parenCalc(parent, child, wrongAssoc);
if (doPush) {
leftParen();
}
else {
space();
}
return doPush;
}
private void rightParen() {
p.print(')');
}
private void rightSquare() {
p.print(']');
}
private void semi() {
p.print(';');
}
private boolean sepCommaOptSpace(boolean sep) {
if (sep) {
p.print(',');
spaceOpt();
}
return true;
}
private void slash() {
p.print('/');
}
private void space() {
p.print(' ');
}
/**
* Decide whether, if op
is printed followed by arg
,
* there needs to be a space between the operator and expression.
*
* @return true
if a space needs to be printed
*/
private static boolean spaceCalc(JsOperator op, JsExpression arg) {
if (op.isKeyword()) {
return true;
}
if (arg instanceof JsBinaryOperation) {
JsBinaryOperation binary = (JsBinaryOperation) arg;
/*
* If the binary operation has a higher precedence than op, then it won't
* be parenthesized, so check the first argument of the binary operation.
*/
return binary.getOperator().getPrecedence() > op.getPrecedence() && spaceCalc(op, binary.getArg1());
}
if (arg instanceof JsPrefixOperation) {
JsOperator op2 = ((JsPrefixOperation) arg).getOperator();
return (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG)
&& (op2 == JsUnaryOperator.DEC || op2 == JsUnaryOperator.NEG)
|| (op == JsBinaryOperator.ADD && op2 == JsUnaryOperator.INC);
}
if (arg instanceof JsNumberLiteral && (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG)) {
if (arg instanceof JsIntLiteral) {
return ((JsIntLiteral) arg).value < 0;
}
else {
assert arg instanceof JsDoubleLiteral;
//noinspection CastConflictsWithInstanceof
return ((JsDoubleLiteral) arg).value < 0;
}
}
return false;
}
private void spaceOpt() {
p.printOpt(' ');
}
private void var() {
p.print(CHARS_VAR);
}
private void _while() {
p.print(CHARS_WHILE);
}
}