0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev.js;
0017:
0018: import com.google.gwt.dev.js.ast.HasName;
0019: import com.google.gwt.dev.js.ast.JsArrayAccess;
0020: import com.google.gwt.dev.js.ast.JsArrayLiteral;
0021: import com.google.gwt.dev.js.ast.JsBinaryOperation;
0022: import com.google.gwt.dev.js.ast.JsBinaryOperator;
0023: import com.google.gwt.dev.js.ast.JsBlock;
0024: import com.google.gwt.dev.js.ast.JsBooleanLiteral;
0025: import com.google.gwt.dev.js.ast.JsBreak;
0026: import com.google.gwt.dev.js.ast.JsCase;
0027: import com.google.gwt.dev.js.ast.JsCatch;
0028: import com.google.gwt.dev.js.ast.JsConditional;
0029: import com.google.gwt.dev.js.ast.JsContext;
0030: import com.google.gwt.dev.js.ast.JsContinue;
0031: import com.google.gwt.dev.js.ast.JsDebugger;
0032: import com.google.gwt.dev.js.ast.JsDecimalLiteral;
0033: import com.google.gwt.dev.js.ast.JsDefault;
0034: import com.google.gwt.dev.js.ast.JsDoWhile;
0035: import com.google.gwt.dev.js.ast.JsEmpty;
0036: import com.google.gwt.dev.js.ast.JsExprStmt;
0037: import com.google.gwt.dev.js.ast.JsExpression;
0038: import com.google.gwt.dev.js.ast.JsFor;
0039: import com.google.gwt.dev.js.ast.JsForIn;
0040: import com.google.gwt.dev.js.ast.JsFunction;
0041: import com.google.gwt.dev.js.ast.JsIf;
0042: import com.google.gwt.dev.js.ast.JsIntegralLiteral;
0043: import com.google.gwt.dev.js.ast.JsInvocation;
0044: import com.google.gwt.dev.js.ast.JsLabel;
0045: import com.google.gwt.dev.js.ast.JsName;
0046: import com.google.gwt.dev.js.ast.JsNameRef;
0047: import com.google.gwt.dev.js.ast.JsNew;
0048: import com.google.gwt.dev.js.ast.JsNullLiteral;
0049: import com.google.gwt.dev.js.ast.JsObjectLiteral;
0050: import com.google.gwt.dev.js.ast.JsOperator;
0051: import com.google.gwt.dev.js.ast.JsParameter;
0052: import com.google.gwt.dev.js.ast.JsPostfixOperation;
0053: import com.google.gwt.dev.js.ast.JsPrefixOperation;
0054: import com.google.gwt.dev.js.ast.JsProgram;
0055: import com.google.gwt.dev.js.ast.JsPropertyInitializer;
0056: import com.google.gwt.dev.js.ast.JsRegExp;
0057: import com.google.gwt.dev.js.ast.JsReturn;
0058: import com.google.gwt.dev.js.ast.JsStatement;
0059: import com.google.gwt.dev.js.ast.JsStringLiteral;
0060: import com.google.gwt.dev.js.ast.JsSwitch;
0061: import com.google.gwt.dev.js.ast.JsSwitchMember;
0062: import com.google.gwt.dev.js.ast.JsThisRef;
0063: import com.google.gwt.dev.js.ast.JsThrow;
0064: import com.google.gwt.dev.js.ast.JsTry;
0065: import com.google.gwt.dev.js.ast.JsUnaryOperator;
0066: import com.google.gwt.dev.js.ast.JsVars;
0067: import com.google.gwt.dev.js.ast.JsVisitor;
0068: import com.google.gwt.dev.js.ast.JsWhile;
0069: import com.google.gwt.dev.js.ast.JsVars.JsVar;
0070: import com.google.gwt.dev.util.TextOutput;
0071:
0072: import java.util.Iterator;
0073: import java.util.regex.Pattern;
0074:
0075: /**
0076: * Produces text output from a JavaScript AST.
0077: */
0078: public class JsToStringGenerationVisitor extends JsVisitor {
0079:
0080: private static final char[] CHARS_BREAK = "break".toCharArray();
0081: private static final char[] CHARS_CASE = "case".toCharArray();
0082: private static final char[] CHARS_CATCH = "catch".toCharArray();
0083: private static final char[] CHARS_CONTINUE = "continue"
0084: .toCharArray();
0085: private static final char[] CHARS_DEBUGGER = "debugger"
0086: .toCharArray();
0087: private static final char[] CHARS_DEFAULT = "default".toCharArray();
0088: private static final char[] CHARS_DO = "do".toCharArray();
0089: private static final char[] CHARS_ELSE = "else".toCharArray();
0090: private static final char[] CHARS_FALSE = "false".toCharArray();
0091: private static final char[] CHARS_FINALLY = "finally".toCharArray();
0092: private static final char[] CHARS_FOR = "for".toCharArray();
0093: private static final char[] CHARS_FUNCTION = "function"
0094: .toCharArray();
0095: private static final char[] CHARS_IF = "if".toCharArray();
0096: private static final char[] CHARS_IN = "in".toCharArray();
0097: private static final char[] CHARS_NEW = "new".toCharArray();
0098: private static final char[] CHARS_NULL = "null".toCharArray();
0099: private static final char[] CHARS_RETURN = "return".toCharArray();
0100: private static final char[] CHARS_SWITCH = "switch".toCharArray();
0101: private static final char[] CHARS_THIS = "this".toCharArray();
0102: private static final char[] CHARS_THROW = "throw".toCharArray();
0103: private static final char[] CHARS_TRUE = "true".toCharArray();
0104: private static final char[] CHARS_TRY = "try".toCharArray();
0105: private static final char[] CHARS_VAR = "var".toCharArray();
0106: private static final char[] CHARS_WHILE = "while".toCharArray();
0107: private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4',
0108: '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
0109:
0110: /**
0111: * How many lines of code to print inside of a JsBlock when printing terse.
0112: */
0113: private static final int JSBLOCK_LINES_TO_PRINT = 3;
0114:
0115: /**
0116: * A variable name is valid if it contains only letters, numbers, _, $ and
0117: * does not begin with a number. There are actually other valid variable
0118: * names, such as ones that contain escaped Unicode characters, but we
0119: * surround those names with quotes in property initializers to be safe.
0120: */
0121: private static final Pattern VALID_NAME_PATTERN = Pattern
0122: .compile("[a-zA-Z_$][\\w$]*");
0123:
0124: protected boolean needSemi = true;
0125: private final TextOutput p;
0126:
0127: public JsToStringGenerationVisitor(TextOutput out) {
0128: this .p = out;
0129: }
0130:
0131: @Override
0132: public boolean visit(JsArrayAccess x, JsContext<JsExpression> ctx) {
0133: JsExpression arrayExpr = x.getArrayExpr();
0134: _parenPush(x, arrayExpr, false);
0135: accept(arrayExpr);
0136: _parenPop(x, arrayExpr, false);
0137: _lsquare();
0138: accept(x.getIndexExpr());
0139: _rsquare();
0140: return false;
0141: }
0142:
0143: @Override
0144: public boolean visit(JsArrayLiteral x, JsContext<JsExpression> ctx) {
0145: _lsquare();
0146: boolean sep = false;
0147: for (Object element : x.getExpressions()) {
0148: JsExpression arg = (JsExpression) element;
0149: sep = _sepCommaOptSpace(sep);
0150: _parenPushIfCommaExpr(arg);
0151: accept(arg);
0152: _parenPopIfCommaExpr(arg);
0153: }
0154: _rsquare();
0155: return false;
0156: }
0157:
0158: @Override
0159: public boolean visit(JsBinaryOperation x,
0160: JsContext<JsExpression> ctx) {
0161: JsBinaryOperator op = x.getOperator();
0162: JsExpression arg1 = x.getArg1();
0163: _parenPush(x, arg1, !op.isLeftAssociative());
0164: accept(arg1);
0165: if (op.isKeyword()) {
0166: _parenPopOrSpace(x, arg1, !op.isLeftAssociative());
0167: } else {
0168: _parenPop(x, arg1, !op.isLeftAssociative());
0169: _spaceOpt();
0170: }
0171: p.print(op.getSymbol());
0172: JsExpression arg2 = x.getArg2();
0173: if (_spaceCalc(op, arg2)) {
0174: _parenPushOrSpace(x, arg2, op.isLeftAssociative());
0175: } else {
0176: _spaceOpt();
0177: _parenPush(x, arg2, op.isLeftAssociative());
0178: }
0179: accept(arg2);
0180: _parenPop(x, arg2, op.isLeftAssociative());
0181: return false;
0182: }
0183:
0184: @Override
0185: public boolean visit(JsBlock x, JsContext<JsStatement> ctx) {
0186: printJsBlockOptionalTruncate(x, true);
0187: return false;
0188: }
0189:
0190: @Override
0191: public boolean visit(JsBooleanLiteral x, JsContext<JsExpression> ctx) {
0192: if (x.getValue()) {
0193: _true();
0194: } else {
0195: _false();
0196: }
0197: return false;
0198: }
0199:
0200: @Override
0201: public boolean visit(JsBreak x, JsContext<JsStatement> ctx) {
0202: _break();
0203:
0204: JsNameRef label = x.getLabel();
0205: if (label != null) {
0206: _space();
0207: _nameRef(label);
0208: }
0209:
0210: return false;
0211: }
0212:
0213: @Override
0214: public boolean visit(JsCase x, JsContext<JsSwitchMember> ctx) {
0215: _case();
0216: _space();
0217: accept(x.getCaseExpr());
0218: _colon();
0219: _newlineOpt();
0220:
0221: indent();
0222: for (Object element : x.getStmts()) {
0223: JsStatement stmt = (JsStatement) element;
0224: needSemi = true;
0225: accept(stmt);
0226: if (needSemi) {
0227: _semi();
0228: }
0229: _newlineOpt();
0230: }
0231: outdent();
0232: needSemi = false;
0233: return false;
0234: }
0235:
0236: @Override
0237: public boolean visit(JsCatch x, JsContext<JsCatch> ctx) {
0238: _spaceOpt();
0239: _catch();
0240: _spaceOpt();
0241: _lparen();
0242: _nameDef(x.getParameter().getName());
0243:
0244: // Optional catch condition.
0245: //
0246: JsExpression catchCond = x.getCondition();
0247: if (catchCond != null) {
0248: _space();
0249: _if();
0250: _space();
0251: accept(catchCond);
0252: }
0253:
0254: _rparen();
0255: _spaceOpt();
0256: accept(x.getBody());
0257:
0258: return false;
0259: }
0260:
0261: @Override
0262: public boolean visit(JsConditional x, JsContext<JsExpression> ctx) {
0263: // right associative
0264: {
0265: JsExpression testExpression = x.getTestExpression();
0266: _parenPush(x, testExpression, false);
0267: accept(testExpression);
0268: _parenPop(x, testExpression, false);
0269: }
0270: _questionMark();
0271: {
0272: JsExpression thenExpression = x.getThenExpression();
0273: _parenPush(x, thenExpression, false);
0274: accept(thenExpression);
0275: _parenPop(x, thenExpression, false);
0276: }
0277: _colon();
0278: {
0279: JsExpression elseExpression = x.getElseExpression();
0280: _parenPush(x, elseExpression, false);
0281: accept(elseExpression);
0282: _parenPop(x, elseExpression, false);
0283: }
0284: return false;
0285: }
0286:
0287: @Override
0288: public boolean visit(JsContinue x, JsContext<JsStatement> ctx) {
0289: _continue();
0290:
0291: JsNameRef label = x.getLabel();
0292: if (label != null) {
0293: _space();
0294: _nameRef(label);
0295: }
0296:
0297: return false;
0298: }
0299:
0300: @Override
0301: public boolean visit(JsDebugger x, JsContext<JsStatement> ctx) {
0302: _debugger();
0303: return false;
0304: }
0305:
0306: @Override
0307: public boolean visit(JsDecimalLiteral x, JsContext<JsExpression> ctx) {
0308: String s = x.getValue();
0309: // TODO: optimize this to only the cases that need it
0310: if (s.startsWith("-")) {
0311: _space();
0312: }
0313: p.print(s);
0314: return false;
0315: }
0316:
0317: @Override
0318: public boolean visit(JsDefault x, JsContext<JsSwitchMember> ctx) {
0319: _default();
0320: _colon();
0321:
0322: indent();
0323: for (Object element : x.getStmts()) {
0324: JsStatement stmt = (JsStatement) element;
0325: needSemi = true;
0326: accept(stmt);
0327: if (needSemi) {
0328: _semi();
0329: }
0330: _newlineOpt();
0331: }
0332: outdent();
0333: needSemi = false;
0334: return false;
0335: }
0336:
0337: @Override
0338: public boolean visit(JsDoWhile x, JsContext<JsStatement> ctx) {
0339: _do();
0340: _nestedPush(x.getBody(), true);
0341: accept(x.getBody());
0342: _nestedPop(x.getBody());
0343: if (needSemi) {
0344: _semi();
0345: _newlineOpt();
0346: } else {
0347: _spaceOpt();
0348: needSemi = true;
0349: }
0350: _while();
0351: _spaceOpt();
0352: _lparen();
0353: accept(x.getCondition());
0354: _rparen();
0355: return false;
0356: }
0357:
0358: @Override
0359: public boolean visit(JsEmpty x, JsContext<JsStatement> ctx) {
0360: return false;
0361: }
0362:
0363: @Override
0364: public boolean visit(JsExprStmt x, JsContext<JsStatement> ctx) {
0365: boolean surroundWithParentheses = JsFirstExpressionVisitor
0366: .exec(x);
0367: if (surroundWithParentheses) {
0368: _lparen();
0369: }
0370: JsExpression expr = x.getExpression();
0371: accept(expr);
0372: if (surroundWithParentheses) {
0373: _rparen();
0374: }
0375: return false;
0376: }
0377:
0378: @Override
0379: public boolean visit(JsFor x, JsContext<JsStatement> ctx) {
0380: _for();
0381: _spaceOpt();
0382: _lparen();
0383:
0384: // The init expressions or var decl.
0385: //
0386: if (x.getInitExpr() != null) {
0387: accept(x.getInitExpr());
0388: } else if (x.getInitVars() != null) {
0389: accept(x.getInitVars());
0390: }
0391:
0392: _semi();
0393:
0394: // The loop test.
0395: //
0396: if (x.getCondition() != null) {
0397: _spaceOpt();
0398: accept(x.getCondition());
0399: }
0400:
0401: _semi();
0402:
0403: // The incr expression.
0404: //
0405: if (x.getIncrExpr() != null) {
0406: _spaceOpt();
0407: accept(x.getIncrExpr());
0408: }
0409:
0410: _rparen();
0411: _nestedPush(x.getBody(), false);
0412: accept(x.getBody());
0413: _nestedPop(x.getBody());
0414: return false;
0415: }
0416:
0417: @Override
0418: public boolean visit(JsForIn x, JsContext<JsStatement> ctx) {
0419: _for();
0420: _spaceOpt();
0421: _lparen();
0422:
0423: if (x.getIterVarName() != null) {
0424: _var();
0425: _space();
0426: _nameDef(x.getIterVarName());
0427:
0428: if (x.getIterExpr() != null) {
0429: _spaceOpt();
0430: _assignment();
0431: _spaceOpt();
0432: accept(x.getIterExpr());
0433: }
0434: } else {
0435: // Just a name ref.
0436: //
0437: accept(x.getIterExpr());
0438: }
0439:
0440: _space();
0441: _in();
0442: _space();
0443: accept(x.getObjExpr());
0444:
0445: _rparen();
0446: _nestedPush(x.getBody(), false);
0447: accept(x.getBody());
0448: _nestedPop(x.getBody());
0449: return false;
0450: }
0451:
0452: // function foo(a, b) {
0453: // stmts...
0454: // }
0455: //
0456: @Override
0457: public boolean visit(JsFunction x, JsContext<JsExpression> ctx) {
0458: _function();
0459:
0460: // Functions can be anonymous.
0461: //
0462: if (x.getName() != null) {
0463: _space();
0464: _nameOf(x);
0465: }
0466:
0467: _lparen();
0468: boolean sep = false;
0469: for (Object element : x.getParameters()) {
0470: JsParameter param = (JsParameter) element;
0471: sep = _sepCommaOptSpace(sep);
0472: accept(param);
0473: }
0474: _rparen();
0475:
0476: accept(x.getBody());
0477: needSemi = true;
0478: return false;
0479: }
0480:
0481: @Override
0482: public boolean visit(JsIf x, JsContext<JsStatement> ctx) {
0483: _if();
0484: _spaceOpt();
0485: _lparen();
0486: accept(x.getIfExpr());
0487: _rparen();
0488: JsStatement thenStmt = x.getThenStmt();
0489: _nestedPush(thenStmt, false);
0490: accept(thenStmt);
0491: _nestedPop(thenStmt);
0492: JsStatement elseStmt = x.getElseStmt();
0493: if (elseStmt != null) {
0494: if (needSemi) {
0495: _semi();
0496: _newlineOpt();
0497: } else {
0498: _spaceOpt();
0499: needSemi = true;
0500: }
0501: _else();
0502: boolean elseIf = elseStmt instanceof JsIf;
0503: if (!elseIf) {
0504: _nestedPush(elseStmt, true);
0505: } else {
0506: _space();
0507: }
0508: accept(elseStmt);
0509: if (!elseIf) {
0510: _nestedPop(elseStmt);
0511: }
0512: }
0513: return false;
0514: }
0515:
0516: @Override
0517: public boolean visit(JsIntegralLiteral x,
0518: JsContext<JsExpression> ctx) {
0519: String s = x.getValue().toString();
0520: boolean needParens = s.startsWith("-");
0521: if (needParens) {
0522: _lparen();
0523: }
0524: p.print(s);
0525: if (needParens) {
0526: _rparen();
0527: }
0528: return false;
0529: }
0530:
0531: @Override
0532: public boolean visit(JsInvocation x, JsContext<JsExpression> ctx) {
0533: accept(x.getQualifier());
0534:
0535: _lparen();
0536: boolean sep = false;
0537: for (Object element : x.getArguments()) {
0538: JsExpression arg = (JsExpression) element;
0539: sep = _sepCommaOptSpace(sep);
0540: _parenPushIfCommaExpr(arg);
0541: accept(arg);
0542: _parenPopIfCommaExpr(arg);
0543: }
0544: _rparen();
0545: return false;
0546: }
0547:
0548: @Override
0549: public boolean visit(JsLabel x, JsContext<JsStatement> ctx) {
0550: _nameOf(x);
0551: _colon();
0552: _spaceOpt();
0553: accept(x.getStmt());
0554: return false;
0555: }
0556:
0557: @Override
0558: public boolean visit(JsNameRef x, JsContext<JsExpression> ctx) {
0559: JsExpression q = x.getQualifier();
0560: if (q != null) {
0561: _parenPush(x, q, false);
0562: accept(q);
0563: _parenPop(x, q, false);
0564: _dot();
0565: }
0566: _nameRef(x);
0567: return false;
0568: }
0569:
0570: @Override
0571: public boolean visit(JsNew x, JsContext<JsExpression> ctx) {
0572: _new();
0573: _space();
0574:
0575: JsExpression ctorExpr = x.getConstructorExpression();
0576: boolean needsParens = JsConstructExpressionVisitor
0577: .exec(ctorExpr);
0578: if (needsParens) {
0579: _lparen();
0580: }
0581: accept(ctorExpr);
0582: if (needsParens) {
0583: _rparen();
0584: }
0585:
0586: _lparen();
0587: boolean sep = false;
0588: for (Object element : x.getArguments()) {
0589: JsExpression arg = (JsExpression) element;
0590: sep = _sepCommaOptSpace(sep);
0591: _parenPushIfCommaExpr(arg);
0592: accept(arg);
0593: _parenPopIfCommaExpr(arg);
0594: }
0595: _rparen();
0596:
0597: return false;
0598: }
0599:
0600: @Override
0601: public boolean visit(JsNullLiteral x, JsContext<JsExpression> ctx) {
0602: _null();
0603: return false;
0604: }
0605:
0606: @Override
0607: public boolean visit(JsObjectLiteral x, JsContext<JsExpression> ctx) {
0608: _lbrace();
0609: boolean sep = false;
0610: for (Object element : x.getPropertyInitializers()) {
0611: sep = _sepCommaOptSpace(sep);
0612: JsPropertyInitializer propInit = (JsPropertyInitializer) element;
0613: printLabel: {
0614: JsExpression labelExpr = propInit.getLabelExpr();
0615: // labels can be either string, integral, or decimal literals
0616: if (labelExpr instanceof JsStringLiteral) {
0617: String propName = ((JsStringLiteral) labelExpr)
0618: .getValue();
0619: if (VALID_NAME_PATTERN.matcher(propName).matches()
0620: && !JsKeywords.isKeyword(propName)) {
0621: p.print(propName);
0622: break printLabel;
0623: }
0624: }
0625: accept(labelExpr);
0626: }
0627: _colon();
0628: JsExpression valueExpr = propInit.getValueExpr();
0629: _parenPushIfCommaExpr(valueExpr);
0630: accept(valueExpr);
0631: _parenPopIfCommaExpr(valueExpr);
0632: }
0633: _rbrace();
0634: return false;
0635: }
0636:
0637: @Override
0638: public boolean visit(JsParameter x, JsContext<JsParameter> ctx) {
0639: _nameOf(x);
0640: return false;
0641: }
0642:
0643: @Override
0644: public boolean visit(JsPostfixOperation x,
0645: JsContext<JsExpression> ctx) {
0646: JsUnaryOperator op = x.getOperator();
0647: JsExpression arg = x.getArg();
0648: // unary operators always associate correctly (I think)
0649: _parenPush(x, arg, false);
0650: accept(arg);
0651: _parenPop(x, arg, false);
0652: p.print(op.getSymbol());
0653: return false;
0654: }
0655:
0656: @Override
0657: public boolean visit(JsPrefixOperation x,
0658: JsContext<JsExpression> ctx) {
0659: JsUnaryOperator op = x.getOperator();
0660: p.print(op.getSymbol());
0661: JsExpression arg = x.getArg();
0662: if (_spaceCalc(op, arg)) {
0663: _space();
0664: }
0665: // unary operators always associate correctly (I think)
0666: _parenPush(x, arg, false);
0667: accept(arg);
0668: _parenPop(x, arg, false);
0669: return false;
0670: }
0671:
0672: @Override
0673: public boolean visit(JsProgram x, JsContext<JsProgram> ctx) {
0674: p.print("<JsProgram>");
0675: return false;
0676: }
0677:
0678: @Override
0679: public boolean visit(JsPropertyInitializer x,
0680: JsContext<JsPropertyInitializer> ctx) {
0681: // Since there are separators, we actually print the property init
0682: // in visit(JsObjectLiteral).
0683: //
0684: return false;
0685: }
0686:
0687: @Override
0688: public boolean visit(JsRegExp x, JsContext<JsExpression> ctx) {
0689: _slash();
0690: p.print(x.getPattern());
0691: _slash();
0692: String flags = x.getFlags();
0693: if (flags != null) {
0694: p.print(flags);
0695: }
0696: return false;
0697: }
0698:
0699: @Override
0700: public boolean visit(JsReturn x, JsContext<JsStatement> ctx) {
0701: _return();
0702: JsExpression expr = x.getExpr();
0703: if (expr != null) {
0704: _space();
0705: accept(expr);
0706: }
0707: return false;
0708: }
0709:
0710: @Override
0711: public boolean visit(JsStringLiteral x, JsContext<JsExpression> ctx) {
0712: printStringLiteral(x.getValue());
0713: return false;
0714: }
0715:
0716: @Override
0717: public boolean visit(JsSwitch x, JsContext<JsStatement> ctx) {
0718: _switch();
0719: _spaceOpt();
0720: _lparen();
0721: accept(x.getExpr());
0722: _rparen();
0723: _spaceOpt();
0724: _blockOpen();
0725: acceptList(x.getCases());
0726: _blockClose();
0727: return false;
0728: }
0729:
0730: @Override
0731: public boolean visit(JsThisRef x, JsContext<JsExpression> ctx) {
0732: _this ();
0733: return false;
0734: }
0735:
0736: @Override
0737: public boolean visit(JsThrow x, JsContext<JsStatement> ctx) {
0738: _throw();
0739: _space();
0740: accept(x.getExpr());
0741: return false;
0742: }
0743:
0744: @Override
0745: public boolean visit(JsTry x, JsContext<JsStatement> ctx) {
0746: _try();
0747: _spaceOpt();
0748: accept(x.getTryBlock());
0749:
0750: acceptList(x.getCatches());
0751:
0752: JsBlock finallyBlock = x.getFinallyBlock();
0753: if (finallyBlock != null) {
0754: _spaceOpt();
0755: _finally();
0756: _spaceOpt();
0757: accept(finallyBlock);
0758: }
0759:
0760: return false;
0761: }
0762:
0763: @Override
0764: public boolean visit(JsVar x, JsContext<JsVar> ctx) {
0765: _nameOf(x);
0766: JsExpression initExpr = x.getInitExpr();
0767: if (initExpr != null) {
0768: _spaceOpt();
0769: _assignment();
0770: _spaceOpt();
0771: _parenPushIfCommaExpr(initExpr);
0772: accept(initExpr);
0773: _parenPopIfCommaExpr(initExpr);
0774: }
0775: return false;
0776: }
0777:
0778: @Override
0779: public boolean visit(JsVars x, JsContext<JsStatement> ctx) {
0780: _var();
0781: _space();
0782: boolean sep = false;
0783: for (JsVar var : x) {
0784: sep = _sepCommaOptSpace(sep);
0785: accept(var);
0786: }
0787: return false;
0788: }
0789:
0790: @Override
0791: public boolean visit(JsWhile x, JsContext<JsStatement> ctx) {
0792: _while();
0793: _spaceOpt();
0794: _lparen();
0795: accept(x.getCondition());
0796: _rparen();
0797: _nestedPush(x.getBody(), false);
0798: accept(x.getBody());
0799: _nestedPop(x.getBody());
0800: return false;
0801: }
0802:
0803: // CHECKSTYLE_NAMING_OFF
0804: protected void _newline() {
0805: p.newline();
0806: }
0807:
0808: protected void _newlineOpt() {
0809: p.newlineOpt();
0810: }
0811:
0812: protected void printJsBlockOptionalTruncate(JsBlock x,
0813: boolean truncate) {
0814: boolean needBraces = !x.isGlobalBlock();
0815:
0816: if (needBraces) {
0817: // Open braces.
0818: //
0819: _blockOpen();
0820: }
0821:
0822: int count = 0;
0823: for (Iterator<JsStatement> iter = x.getStatements().iterator(); iter
0824: .hasNext(); ++count) {
0825: if (truncate && count > JSBLOCK_LINES_TO_PRINT) {
0826: p.print("[...]");
0827: _newlineOpt();
0828: break;
0829: }
0830: JsStatement stmt = iter.next();
0831: needSemi = true;
0832: accept(stmt);
0833: if (needSemi) {
0834: /*
0835: * Special treatment of function decls: function decls always set
0836: * needSemi back to true. But if they are the only item in a statement
0837: * (i.e. not part of an assignment operation), just give them a newline
0838: * instead of a semi since it makes obfuscated code so much "nicer"
0839: * (sic).
0840: */
0841: if (stmt instanceof JsExprStmt
0842: && ((JsExprStmt) stmt).getExpression() instanceof JsFunction) {
0843: _newline();
0844: } else {
0845: _semi();
0846: _newlineOpt();
0847: }
0848: }
0849: }
0850:
0851: if (needBraces) {
0852: // Close braces.
0853: //
0854: _blockClose();
0855: }
0856: needSemi = false;
0857: }
0858:
0859: private void _assignment() {
0860: p.print('=');
0861: }
0862:
0863: private void _blockClose() {
0864: p.indentOut();
0865: p.print('}');
0866: _newlineOpt();
0867: }
0868:
0869: private void _blockOpen() {
0870: p.print('{');
0871: p.indentIn();
0872: _newlineOpt();
0873: }
0874:
0875: private void _break() {
0876: p.print(CHARS_BREAK);
0877: }
0878:
0879: private void _case() {
0880: p.print(CHARS_CASE);
0881: }
0882:
0883: private void _catch() {
0884: p.print(CHARS_CATCH);
0885: }
0886:
0887: private void _colon() {
0888: p.print(':');
0889: }
0890:
0891: private void _continue() {
0892: p.print(CHARS_CONTINUE);
0893: }
0894:
0895: private void _debugger() {
0896: p.print(CHARS_DEBUGGER);
0897: }
0898:
0899: private void _default() {
0900: p.print(CHARS_DEFAULT);
0901: }
0902:
0903: private void _do() {
0904: p.print(CHARS_DO);
0905: }
0906:
0907: private void _dot() {
0908: p.print('.');
0909: }
0910:
0911: private void _else() {
0912: p.print(CHARS_ELSE);
0913: }
0914:
0915: private void _false() {
0916: p.print(CHARS_FALSE);
0917: }
0918:
0919: private void _finally() {
0920: p.print(CHARS_FINALLY);
0921: }
0922:
0923: private void _for() {
0924: p.print(CHARS_FOR);
0925: }
0926:
0927: private void _function() {
0928: p.print(CHARS_FUNCTION);
0929: }
0930:
0931: private void _if() {
0932: p.print(CHARS_IF);
0933: }
0934:
0935: private void _in() {
0936: p.print(CHARS_IN);
0937: }
0938:
0939: private void _lbrace() {
0940: p.print('{');
0941: }
0942:
0943: private void _lparen() {
0944: p.print('(');
0945: }
0946:
0947: private void _lsquare() {
0948: p.print('[');
0949: }
0950:
0951: private void _nameDef(JsName name) {
0952: p.print(name.getShortIdent());
0953: }
0954:
0955: private void _nameOf(HasName hasName) {
0956: _nameDef(hasName.getName());
0957: }
0958:
0959: private void _nameRef(JsNameRef nameRef) {
0960: p.print(nameRef.getShortIdent());
0961: }
0962:
0963: private boolean _nestedPop(JsStatement statement) {
0964: boolean pop = !(statement instanceof JsBlock);
0965: if (pop) {
0966: p.indentOut();
0967: }
0968: return pop;
0969: }
0970:
0971: private boolean _nestedPush(JsStatement statement, boolean needSpace) {
0972: boolean push = !(statement instanceof JsBlock);
0973: if (push) {
0974: if (needSpace) {
0975: _space();
0976: }
0977: p.indentIn();
0978: _newlineOpt();
0979: } else {
0980: _spaceOpt();
0981: }
0982: return push;
0983: }
0984:
0985: private void _new() {
0986: p.print(CHARS_NEW);
0987: }
0988:
0989: private void _null() {
0990: p.print(CHARS_NULL);
0991: }
0992:
0993: private boolean _parenCalc(JsExpression parent, JsExpression child,
0994: boolean wrongAssoc) {
0995: int parentPrec = JsPrecedenceVisitor.exec(parent);
0996: int childPrec = JsPrecedenceVisitor.exec(child);
0997: return (parentPrec > childPrec || (parentPrec == childPrec && wrongAssoc));
0998: }
0999:
1000: private boolean _parenPop(JsExpression parent, JsExpression child,
1001: boolean wrongAssoc) {
1002: boolean doPop = _parenCalc(parent, child, wrongAssoc);
1003: if (doPop) {
1004: _rparen();
1005: }
1006: return doPop;
1007: }
1008:
1009: private boolean _parenPopIfCommaExpr(JsExpression x) {
1010: boolean doPop = x instanceof JsBinaryOperation
1011: && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA;
1012: if (doPop) {
1013: _rparen();
1014: }
1015: return doPop;
1016: }
1017:
1018: private boolean _parenPopOrSpace(JsExpression parent,
1019: JsExpression child, boolean wrongAssoc) {
1020: boolean doPop = _parenCalc(parent, child, wrongAssoc);
1021: if (doPop) {
1022: _rparen();
1023: } else {
1024: _space();
1025: }
1026: return doPop;
1027: }
1028:
1029: private boolean _parenPush(JsExpression parent, JsExpression child,
1030: boolean wrongAssoc) {
1031: boolean doPush = _parenCalc(parent, child, wrongAssoc);
1032: if (doPush) {
1033: _lparen();
1034: }
1035: return doPush;
1036: }
1037:
1038: private boolean _parenPushIfCommaExpr(JsExpression x) {
1039: boolean doPush = x instanceof JsBinaryOperation
1040: && ((JsBinaryOperation) x).getOperator() == JsBinaryOperator.COMMA;
1041: if (doPush) {
1042: _lparen();
1043: }
1044: return doPush;
1045: }
1046:
1047: private boolean _parenPushOrSpace(JsExpression parent,
1048: JsExpression child, boolean wrongAssoc) {
1049: boolean doPush = _parenCalc(parent, child, wrongAssoc);
1050: if (doPush) {
1051: _lparen();
1052: } else {
1053: _space();
1054: }
1055: return doPush;
1056: }
1057:
1058: private void _questionMark() {
1059: p.print('?');
1060: }
1061:
1062: private void _rbrace() {
1063: p.print('}');
1064: }
1065:
1066: private void _return() {
1067: p.print(CHARS_RETURN);
1068: }
1069:
1070: private void _rparen() {
1071: p.print(')');
1072: }
1073:
1074: private void _rsquare() {
1075: p.print(']');
1076: }
1077:
1078: private void _semi() {
1079: p.print(';');
1080: }
1081:
1082: private boolean _sepCommaOptSpace(boolean sep) {
1083: if (sep) {
1084: p.print(',');
1085: _spaceOpt();
1086: }
1087: return true;
1088: }
1089:
1090: private void _slash() {
1091: p.print('/');
1092: }
1093:
1094: private void _space() {
1095: p.print(' ');
1096: }
1097:
1098: private boolean _spaceCalc(JsOperator op, JsExpression arg) {
1099: if (op.isKeyword()) {
1100: return true;
1101: }
1102: if (arg instanceof JsPrefixOperation) {
1103: JsOperator op2 = ((JsPrefixOperation) arg).getOperator();
1104: return (op == JsBinaryOperator.SUB || op == JsUnaryOperator.NEG)
1105: && (op2 == JsUnaryOperator.DEC || op2 == JsUnaryOperator.NEG)
1106: || (op == JsBinaryOperator.ADD && op2 == JsUnaryOperator.INC);
1107: }
1108: return false;
1109: }
1110:
1111: private void _spaceOpt() {
1112: p.printOpt(' ');
1113: }
1114:
1115: private void _switch() {
1116: p.print(CHARS_SWITCH);
1117: }
1118:
1119: private void _this () {
1120: p.print(CHARS_THIS);
1121: }
1122:
1123: private void _throw() {
1124: p.print(CHARS_THROW);
1125: }
1126:
1127: private void _true() {
1128: p.print(CHARS_TRUE);
1129: }
1130:
1131: private void _try() {
1132: p.print(CHARS_TRY);
1133: }
1134:
1135: private void _var() {
1136: p.print(CHARS_VAR);
1137: }
1138:
1139: private void _while() {
1140: p.print(CHARS_WHILE);
1141: }
1142:
1143: // CHECKSTYLE_NAMING_ON
1144:
1145: /**
1146: * Escapes any closing XML tags embedded in <code>str</code>, which could
1147: * potentially cause a parse failure in a browser, for example, embedding a
1148: * closing <code><script></code> tag.
1149: *
1150: * @param str an unescaped literal; May be null
1151: */
1152: private void escapeClosingTags(StringBuffer str) {
1153: if (str == null) {
1154: return;
1155: }
1156:
1157: int index = 0;
1158:
1159: while ((index = str.indexOf("</", index)) != -1) {
1160: str.insert(index + 1, '\\');
1161: }
1162: }
1163:
1164: private void indent() {
1165: p.indentIn();
1166: }
1167:
1168: private void outdent() {
1169: p.indentOut();
1170: }
1171:
1172: /**
1173: * Adapted from
1174: * {@link com.google.gwt.dev.js.rhino.ScriptRuntime#escapeString(String)}.
1175: * The difference is that we quote with either " or ' depending on
1176: * which one is used less inside the string.
1177: */
1178: private void printStringLiteral(String value) {
1179:
1180: char[] chars = value.toCharArray();
1181: final int n = chars.length;
1182: int quoteCount = 0;
1183: int aposCount = 0;
1184: for (int i = 0; i < n; ++i) {
1185: switch (chars[i]) {
1186: case '"':
1187: ++quoteCount;
1188: break;
1189: case '\'':
1190: ++aposCount;
1191: break;
1192: }
1193: }
1194:
1195: StringBuffer result = new StringBuffer(value.length() + 16);
1196:
1197: char quoteChar = (quoteCount < aposCount) ? '"' : '\'';
1198: p.print(quoteChar);
1199:
1200: for (int i = 0; i < n; ++i) {
1201: char c = chars[i];
1202:
1203: if (' ' <= c && c <= '~' && c != quoteChar && c != '\\') {
1204: // an ordinary print character (like C isprint())
1205: result.append(c);
1206: continue;
1207: }
1208:
1209: int escape = -1;
1210: switch (c) {
1211: case 0:
1212: escape = '0';
1213: break;
1214: case '\b':
1215: escape = 'b';
1216: break;
1217: case '\f':
1218: escape = 'f';
1219: break;
1220: case '\n':
1221: escape = 'n';
1222: break;
1223: case '\r':
1224: escape = 'r';
1225: break;
1226: case '\t':
1227: escape = 't';
1228: break;
1229: case '"':
1230: escape = '"';
1231: break; // only reach here if == quoteChar
1232: case '\'':
1233: escape = '\'';
1234: break; // only reach here if == quoteChar
1235: case '\\':
1236: escape = '\\';
1237: break;
1238: }
1239:
1240: if (escape >= 0) {
1241: // an \escaped sort of character
1242: result.append('\\');
1243: result.append((char) escape);
1244: } else {
1245: int hexSize;
1246: if (c < 256) {
1247: // 2-digit hex
1248: result.append("\\x");
1249: hexSize = 2;
1250: } else {
1251: // Unicode.
1252: result.append("\\u");
1253: hexSize = 4;
1254: }
1255: // append hexadecimal form of ch left-padded with 0
1256: for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
1257: int digit = 0xf & (c >> shift);
1258: result.append(HEX_DIGITS[digit]);
1259: }
1260: }
1261: }
1262: result.append(quoteChar);
1263: escapeClosingTags(result);
1264: p.print(result.toString());
1265: }
1266: }
|