public StringBuffer printSymbolTree(int linebreakpos, boolean preserveAllSemiColons)
      throws IOException {

    offset = 0;
    braceNesting = 0;
    scopes.clear();

    String symbol;
    JavaScriptToken token;
    // begin
    if (tokens.size() == 0) {
      StringBuffer result = new StringBuffer();
      return result;
    }
    // end
    JavaScriptToken lastToken = getToken(0);
    ScriptOrFnScope currentScope;
    JavaScriptIdentifier identifier;

    int length = tokens.size(); // 文本的长度
    StringBuffer result = new StringBuffer();

    int linestartpos = 0;

    enterScope(globalScope); // 将globalScope压入栈scopes中

    while (offset < length) {

      token = consumeToken();
      symbol = token.getValue();
      currentScope = getCurrentScope();
      switch (token.getType()) {
        case Token.GET:
        case Token.SET:
          lastToken = token; // 注意没有break;

        case Token.NAME:
          if (offset >= 2 && getToken(-2).getType() == Token.DOT
              || getToken(0).getType() == Token.OBJECTLIT) {

            result.append(symbol);

          } else {
            identifier = getIdentifier(symbol, currentScope);
            if (identifier != null) {
              if (identifier.getMungedValue() != null) {
                result.append(identifier.getMungedValue());
              } else {
                result.append(symbol);
              }
              if (currentScope != globalScope // 全局域中的变量可能被HTML文档使用,不需要发出警告
                  && identifier.getRefcount() == 0) {
                warn("标识符" + symbol + "已经声明但未使用 ", true);
              }
            } else {
              result.append(symbol);
            }
          }
          break;

        case Token.REGEXP:
        case Token.STRING:
          result.append(symbol);
          break;

        case Token.NUMBER:
          if (getToken(0).getType() == Token.DOT) {
            // calling methods on int requires a leading dot so JS
            // doesn't
            // treat the method as the decimal component of a float
            result.append('(');
            result.append(symbol);
            result.append(')');
          } else {
            result.append(symbol);
          }
          break;

        case Token.ADD:
        case Token.SUB:
          result.append(literals.get(new Integer(token.getType())));
          if (offset < length) {
            token = getToken(0);
            if (token.getType() == Token.INC
                || token.getType() == Token.DEC
                || token.getType() == Token.ADD
                || token.getType() == Token.DEC) {
              // Handle the case x +/- ++/-- y
              // We must keep a white space here. Otherwise, x +++ y
              // would be
              // interpreted as x ++ + y by the compiler, which is a
              // bug (due
              // to the implicit(隐式的) assignment being done on the
              // wrong variable)
              result.append(' ');
            } else if (token.getType() == Token.POS && getToken(-1).getType() == Token.ADD
                || token.getType() == Token.NEG && getToken(-1).getType() == Token.SUB) {
              // Handle the case x + + y and x - - y
              result.append(' ');
            }
          }
          break;

        case Token.FUNCTION:
          if (lastToken.getType() != Token.GET && lastToken.getType() != Token.SET) {
            result.append("function");
          }
          lastToken = token;
          token = consumeToken();
          if (token.getType() == Token.NAME) {
            result.append(' ');
            symbol = token.getValue();
            identifier = getIdentifier(symbol, currentScope);
            assert identifier != null;
            if (identifier.getMungedValue() != null) {
              result.append(identifier.getMungedValue());
            } else {
              result.append(symbol);
            }
            if (currentScope != globalScope && identifier.getRefcount() == 0) {
              warn("标识符" + symbol + "已经声明但未使用", true);
            }
            token = consumeToken();
          }
          assert token.getType() == Token.LP;
          result.append('(');
          currentScope = indexedScopes.get(new Integer(offset)); // 根据左圆括号的下一个索引映射得到函数作用域
          enterScope(currentScope);
          while ((token = consumeToken()).getType() != Token.RP) {
            assert token.getType() == Token.NAME || token.getType() == Token.COMMA;
            if (token.getType() == Token.NAME) {
              symbol = token.getValue();
              identifier = getIdentifier(symbol, currentScope);
              assert identifier != null;
              if (identifier.getMungedValue() != null) {
                result.append(identifier.getMungedValue());
              } else {
                result.append(symbol);
              }
            } else if (token.getType() == Token.COMMA) {
              result.append(',');
            }
          }
          result.append(')');
          token = consumeToken(); // 得到左花括号
          assert token.getType() == Token.LC;
          result.append("{"); // nomodify
          braceNesting++;
          token = getToken(0);
          if (token.getType() == Token.STRING && getToken(1).getType() == Token.SEMI) {
            // This is a hint. Skip it!
            consumeToken();
            consumeToken();
          }
          break;

        case Token.RETURN:
        case Token.TYPEOF:
          result.append(literals.get(new Integer(token.getType())));
          // No space needed after 'return' and 'typeof' when followed
          // by '(', '[', '{', a string or a regexp.
          if (offset < length) {
            token = getToken(0);
            if (token.getType() != Token.LP
                && token.getType() != Token.LB
                && token.getType() != Token.LC
                && token.getType() != Token.STRING
                && token.getType() != Token.REGEXP
                && token.getType() != Token.SEMI) {
              result.append(' ');
            }
          }
          break;

        case Token.CASE:
        case Token.THROW:
          result.append(literals.get(new Integer(token.getType())));
          // White-space needed after 'case' and 'throw' when not followed
          // by a string.
          if (offset < length && getToken(0).getType() != Token.STRING) {
            result.append(' ');
          }
          break;

        case Token.BREAK:
        case Token.CONTINUE:
          result.append(literals.get(new Integer(token.getType())));
          if (offset < length && getToken(0).getType() != Token.SEMI) {
            // If 'break' or 'continue' is not followed by a
            // semi-colon(分号), it must
            // be followed by a label, hence(因此) the need for a white
            // space.
            result.append(' ');
          }
          break;

        case Token.LC:
          result.append("{"); // nomodify
          braceNesting++;
          break;

        case Token.RC:
          result.append('}');
          braceNesting--;
          assert braceNesting >= currentScope.getBraceNesting();
          if (braceNesting == currentScope.getBraceNesting()) {
            leaveCurrentScope();
          }
          break;

        case Token.SEMI:
          // No need to output a semi-colon if the next character is a
          // right-curly...
          if (preserveAllSemiColons || offset < length && getToken(0).getType() != Token.RC) {
            result.append(';');
          }

          if (linebreakpos >= 0 && result.length() - linestartpos > linebreakpos) {
            // Some source control tools don't like it when files
            // containing lines longer
            // than, say 8000 characters, are checked in. The linebreak
            // option is used in
            // that case to split long lines after a specific column.
            result.append('\n');
            linestartpos = result.length();
          }
          break;

        case Token.COMMA:
          // No need to output a comma if the next character is a
          // right-curly or a right-square bracket
          if (offset < length
              && getToken(0).getType() != Token.RC
              && getToken(0).getType() != Token.RB) {
            result.append(',');
          }
          break;

        case Token.CONDCOMMENT:
        case Token.KEEPCOMMENT:
          if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') {
            result.append("\n");
          }
          result.append("/*");
          if (token.getType() == Token.KEEPCOMMENT) {
            result.append("!");
          }
          result.append(symbol);
          result.append("*/\n");
          break;

          // begin--这个分支用于实现压扁控制流
          // 此分支将if(expression){...}else if(expression){...}else{...}转化为
          // switch(expression){case true:原if块;break;case false:原else块;break;}
        case Token.IF:
          if (!EditOptionPanel.checkBoxFlatten.isSelected()) {
            result.append(symbol);
            break;
          }
          FlattenIF flattenIF = new FlattenIF(offset, length, tokens, currentScope);
          String switchBlock = flattenIF.flattenIF();
          result.append(switchBlock);
          offset = flattenIF.offset;
          break;
        case Token.WHILE:
          if (!EditOptionPanel.checkBoxOpacity.isSelected()) {
            result.append(symbol);
            break;
          }
          result.append(symbol);
          OpacityPredicate opacityPredicateWhile =
              new OpacityPredicate(offset, tokens, currentScope);
          String whileBlock = opacityPredicateWhile.opacityPredicateWhile();
          result.append(whileBlock);
          offset = opacityPredicateWhile.offset;
          break;
        case Token.DO:
          if (!EditOptionPanel.checkBoxOpacity.isSelected()) {
            result.append(symbol);
            break;
          }
          result.append(symbol);
          OpacityPredicate opacityPredicateDoWhile =
              new OpacityPredicate(offset, tokens, currentScope);
          String doWhileBlock = opacityPredicateDoWhile.opacityPredicateDoWhile();
          result.append(doWhileBlock);
          offset = opacityPredicateDoWhile.offset;
          break;
          // end
        default:
          String literal = literals.get(new Integer(token.getType()));
          if (literal != null) {
            result.append(literal);
          } else {
            warn("此标志符不能被打印出来:" + symbol, true);
          }
          break;
      }
    }

    // Append a semi-colon at the end, even if unnecessary semi-colons are
    // supposed to be removed. This is especially useful when concatenating
    // several minified files (the absence of an ending semi-colon at the
    // end of one file may very likely cause a syntax error)
    /*if (!preserveAllSemiColons && result.length() > 0
    		&& getToken(-1).getType() != Token.CONDCOMMENT
    		&& getToken(-1).getType() != Token.KEEPCOMMENT) {
    	if (result.charAt(result.length() - 1) == '\n') {
    		result.setCharAt(result.length() - 1, ';');
    	} else {
    		result.append(';');
    	}
    }*/
    // 暂时用不上,注释掉

    return result;
  }