package de.skuzzle.polly.core.parser.ast.visitor;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import de.skuzzle.polly.core.parser.ast.Identifier;
import de.skuzzle.polly.core.parser.ast.Node;
import de.skuzzle.polly.core.parser.ast.ResolvableIdentifier;
import de.skuzzle.polly.core.parser.ast.Root;
import de.skuzzle.polly.core.parser.ast.declarations.Declaration;
import de.skuzzle.polly.core.parser.ast.expressions.Assignment;
import de.skuzzle.polly.core.parser.ast.expressions.Braced;
import de.skuzzle.polly.core.parser.ast.expressions.Call;
import de.skuzzle.polly.core.parser.ast.expressions.Delete;
import de.skuzzle.polly.core.parser.ast.expressions.Expression;
import de.skuzzle.polly.core.parser.ast.expressions.Inspect;
import de.skuzzle.polly.core.parser.ast.expressions.NamespaceAccess;
import de.skuzzle.polly.core.parser.ast.expressions.OperatorCall;
import de.skuzzle.polly.core.parser.ast.expressions.VarAccess;
import de.skuzzle.polly.core.parser.ast.expressions.Delete.DeleteableIdentifier;
import de.skuzzle.polly.core.parser.ast.expressions.literals.FunctionLiteral;
import de.skuzzle.polly.core.parser.ast.expressions.literals.ListLiteral;
import de.skuzzle.polly.core.parser.ast.expressions.literals.Literal;
import de.skuzzle.polly.core.parser.ast.expressions.literals.LiteralFormatter;
import de.skuzzle.polly.core.parser.ast.expressions.literals.ProductLiteral;
import de.skuzzle.polly.core.parser.ast.lang.Operator.OpType;
import de.skuzzle.polly.tools.io.StringBuilderWriter;
/**
* The unparser traverses the AST to re-generate the string that the AST was created from.
* If using the default literal formatter, the generated string is guaranteed to be
* parseable again. If the AST was not transformed in any way, the resulting string will
* be syntactical equal (disregarding whitespaces) to the string that the AST was created
* from.
*
* @author Simon Taddiken
*/
public class Unparser extends DepthFirstVisitor {
/**
* Formats an AST (or just a sub tree) into a string using a
* {@link LiteralFormatter#DEFAULT default literal formatter}. This string is
* guaranteed to be parseable as a valid polly command again.
*
* @param node AST node to format.
* @return The formatted string.
*/
public static String toString(Node node) {
return toString(node, LiteralFormatter.DEFAULT);
}
/**
* Formats an AST (or just a sub tree) into a string using a custom
* {@link LiteralFormatter}. Depending on the output of this formatter, the resulting
* string might not bot parseable again as a valid polly command.
*
* @param node AST node to format.
* @param formatter Formatter to format occurring literals with.
* @return The formatted string.
*/
public static String toString(Node node, LiteralFormatter formatter) {
final StringBuilderWriter sbw = new StringBuilderWriter();
final Unparser unp = new Unparser(new PrintWriter(sbw), formatter);
try {
node.visit(unp);
} catch (ASTTraversalException e) {
e.printStackTrace();
}
return sbw.getBuilder().toString();
}
private final PrintWriter out;
private final LiteralFormatter literalFormatter;
/**
* Creates a new Unparser.
*
* @param out Writer to write the output to.
* @param formatter Formatter to format occuring literals with.
*/
public Unparser(PrintWriter out, LiteralFormatter formatter) {
this.out = out;
this.literalFormatter = formatter;
}
/**
* Creates a new Unparser which uses a default literal formatter.
* @param out Writer to write the output to.
*/
public Unparser(PrintWriter out) {
this(out, LiteralFormatter.DEFAULT);
}
@Override
public int before(Braced node) throws ASTTraversalException {
this.out.print("(");
return CONTINUE;
}
@Override
public int after(Braced node) throws ASTTraversalException {
this.out.print(")");
return CONTINUE;
}
@Override
public int before(Declaration node) throws ASTTraversalException {
node.getType().getName().visit(this);
this.out.print(" ");
node.getName().visit(this);
return CONTINUE;
}
@Override
public boolean visit(Root node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
this.out.print(":");
if (!node.getCommand().visit(this)) {
return false;
}
if (!node.getExpressions().isEmpty()) {
this.out.print(" ");
final Iterator<Expression> it = node.getExpressions().iterator();
while (it.hasNext()) {
if (!it.next().visit(this)) {
return false;
}
if (it.hasNext()) {
this.out.print(" ");
}
}
}
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(FunctionLiteral node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
this.out.print(node.format(this.literalFormatter));
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(Literal node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
this.out.print(node.format(this.literalFormatter));
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(ListLiteral node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
this.out.print(node.format(this.literalFormatter));
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(ProductLiteral node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
final Iterator<Expression> it = node.getContent().iterator();
while (it.hasNext()) {
if (!it.next().visit(this)) {
return false;
}
if (it.hasNext()) {
this.out.print(",");
}
}
return this.after(node) == CONTINUE;
}
@Override
public int before(Identifier node) throws ASTTraversalException {
if (node.wasEscaped()) {
this.out.print("\\");
}
this.out.print(node.getId());
return CONTINUE;
}
@Override
public int before(ResolvableIdentifier node) throws ASTTraversalException {
this.before((Identifier) node);
return CONTINUE;
}
@Override
public boolean visit(OperatorCall node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
final List<Expression> content = node.getRhs().getContent();
final Iterator<Expression> it = content.iterator();
if (content.size() == 1) {
if (node.isPostfix()) {
if (!it.next().visit(this)) {
return false;
}
this.out.print(node.getOperator().getId());
} else {
this.out.print(node.getOperator().getId());
if (!it.next().visit(this)) {
return false;
}
}
} else if (content.size() == 2) {
// HACK: special treatment for certain operators
if (node.getOperator() == OpType.INDEX) {
if (!it.next().visit(this)) {
return false;
}
this.out.print("[");
if (!it.next().visit(this)) {
return false;
}
this.out.print("]");
} else {
if (!it.next().visit(this)) {
return false;
}
this.out.print(node.getOperator().getId());
if (!it.next().visit(this)) {
return false;
}
}
} else if (content.size() == 3) {
if (node.getOperator() == OpType.IF) {
this.out.print("if ");
if (!it.next().visit(this)) {
return false;
}
this.out.print(" ? ");
if (!it.next().visit(this)) {
return false;
}
this.out.print(" : ");
if (!it.next().visit(this)) {
return false;
}
} else if (node.getOperator() == OpType.DOTDOT) {
if (!it.next().visit(this)) {
return false;
}
this.out.print("..");
if (!it.next().visit(this)) {
return false;
}
this.out.print("$");
if (!it.next().visit(this)) {
return false;
}
}
}
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(Call node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
if (!node.getLhs().visit(this)) {
return false;
}
this.out.print("(");
if (!node.getRhs().visit(this)) {
return false;
}
this.out.print(")");
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(NamespaceAccess node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
if (!node.getLhs().visit(this)) {
return false;
}
this.out.print(".");
if (!node.getRhs().visit(this)) {
return false;
}
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(VarAccess node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
if (!node.getIdentifier().visit(this)) {
return false;
}
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(Assignment node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
if (!node.getExpression().visit(this)) {
return false;
}
this.out.print("->");
if (!node.getName().visit(this)) {
return false;
}
return this.after(node) == CONTINUE;
}
@Override
public boolean visit(Delete node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
this.out.print("del(");
final Iterator<DeleteableIdentifier> it = node.getIdentifiers().iterator();
while (it.hasNext()) {
final DeleteableIdentifier next = it.next();
if (next.isGlobal()) {
this.out.print("public ");
}
if (!next.visit(this)) {
return false;
}
if (it.hasNext()) {
this.out.print(",");
}
}
this.out.print(")");
return this.after(node) == CONTINUE;
}
@Override
public int before(Inspect node) throws ASTTraversalException {
this.out.print("inspect(");
return CONTINUE;
}
@Override
public int after(Inspect node) throws ASTTraversalException {
this.out.print(")");
return CONTINUE;
}
}