/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.util.nodes; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import kodkod.ast.BinaryExpression; import kodkod.ast.BinaryFormula; import kodkod.ast.BinaryIntExpression; import kodkod.ast.ComparisonFormula; import kodkod.ast.Comprehension; import kodkod.ast.ConstantExpression; import kodkod.ast.ConstantFormula; import kodkod.ast.Decl; import kodkod.ast.Decls; import kodkod.ast.ExprToIntCast; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.IfExpression; import kodkod.ast.IfIntExpression; import kodkod.ast.IntComparisonFormula; import kodkod.ast.IntConstant; import kodkod.ast.IntExpression; import kodkod.ast.IntToExprCast; import kodkod.ast.LeafExpression; import kodkod.ast.MultiplicityFormula; import kodkod.ast.NaryExpression; import kodkod.ast.NaryFormula; import kodkod.ast.NaryIntExpression; import kodkod.ast.Node; import kodkod.ast.NotFormula; import kodkod.ast.ProjectExpression; import kodkod.ast.QuantifiedFormula; import kodkod.ast.Relation; import kodkod.ast.RelationPredicate; import kodkod.ast.SumExpression; import kodkod.ast.UnaryExpression; import kodkod.ast.UnaryIntExpression; import kodkod.ast.Variable; import kodkod.ast.operator.ExprOperator; import kodkod.ast.operator.FormulaOperator; import kodkod.ast.operator.IntOperator; import kodkod.ast.operator.Multiplicity; import kodkod.ast.visitor.VoidVisitor; /** * Pretty-prints Kodkod nodes. * * @author Emina Torlak */ public final class PrettyPrinter { /** * Returns a dot representation of the given node that can be visualized with GraphViz. * @return a dot representation of the given node that can be visualized with GraphViz. */ public static String dotify(Node node) { return Dotifier.apply(node); } /** * Returns a pretty-printed string representation of the * given node, with each line offset by at least the given * number of whitespaces. The line parameter determines the * length of each pretty-printed line, including the offset. * @requires 0 <= offset < line * @return a pretty-printed string representation of the * given node */ public static String print(Node node, int offset, int line) { final Formatter formatter = new Formatter(offset,line); node.accept(formatter); return formatter.tokens.toString(); } /** * Returns a pretty-printed string representation of the * given node, with each line offset by at least the given * number of whitespaces. * @requires 0 <= offset < 80 * @return a pretty-printed string representation of the * given node */ public static String print(Node node, int offset) { return print(node,offset,80); } /** * Returns a pretty-printed string representation of the * given formulas, with each line offset by at least the given * number of whitespaces. * @requires 0 <= offset < 80 * @return a pretty-printed string representation of the * given formulas */ public static String print(Set<Formula> formulas, int offset) { return print(formulas,offset,80); } /** * Returns a pretty-printed string representation of the * given formulas, with each line offset by at least the given * number of whitespaces. The line parameter determines the * length of each pretty-printed line, including the offset. * @requires 0 <= offset < line * @return a pretty-printed string representation of the * given formulas */ public static String print(Set<Formula> formulas, int offset, int line) { final Formatter formatter = new Formatter(offset,line); for(Formula f : formulas) { f.accept(formatter); formatter.newline(); } return formatter.tokens.toString(); } /** * Generates a buffer of tokens comprising the string representation * of a given node. The buffer contains at least the parentheses * needed to correctly represent the node's tree structure. * * @specfield tokens: seq<Object> * @author Emina Torlak */ private static class Formatter implements VoidVisitor { final StringBuilder tokens ; //final int offset; private final int lineLength; private int indent, lineStart; /** * Constructs a new tokenizer. * @ensures no this.tokens */ Formatter(int offset, int line) { assert offset >= 0 && offset < line; this.tokens = new StringBuilder(); //this.offset = offset; this.lineLength = line; this.lineStart = 0; this.indent = offset; indent(); } /*--------------FORMATTERS---------------*/ /** @ensures this.tokens' = concat [ this.tokens, " ", token, " " ]*/ private void infix(Object token) { space(); tokens.append(token); space(); } /** @ensures this.tokens' = concat [ this.tokens, token, " " ]*/ private void keyword(Object token) { append(token); space(); } /** @ensures this.tokens' = concat [ this.tokens, ", " ]*/ private void comma() { tokens.append(","); space(); } /** @ensures this.tokens' = concat [ this.tokens, ": " ]*/ private void colon() { tokens.append(":"); space(); } /** @ensures adds this.indent spaces to this.tokens */ private void indent() { for(int i = 0; i < indent; i++) { space(); } } /** @ensures adds newline plus this.indent spaces to this.tokens */ private void newline() { tokens.append("\n"); lineStart = tokens.length(); indent(); } /** @ensures this.tokens' = concat[ this.tokens, " " ] **/ private void space() { tokens.append(" "); } /** @ensures this.tokens' = concat [ this.tokens, token ]*/ private void append(Object token) { final String str = String.valueOf(token); if ((tokens.length() - lineStart + str.length()) > lineLength) { newline(); } tokens.append(str); } /*--------------LEAVES---------------*/ /** @ensures this.tokens' = concat[ this.tokens, node ] */ public void visit(Relation node) { append(node); } /** @ensures this.tokens' = concat[ this.tokens, node ] */ public void visit(Variable node) { append(node); } /** @ensures this.tokens' = concat[ this.tokens, node ] */ public void visit(ConstantExpression node) { append(node); } /** @ensures this.tokens' = concat[ this.tokens, node ] */ public void visit(IntConstant node) { append(node); } /** @ensures this.tokens' = concat[ this.tokens, node ] */ public void visit(ConstantFormula node) { append(node); } /*--------------DECLARATIONS---------------*/ /** * @ensures this.tokens' = * concat[ this.tokens, tokenize[ node.variable ], ":", tokenize[ node.expression ] **/ public void visit(Decl node) { node.variable().accept(this); colon(); if (node.multiplicity()!=Multiplicity.ONE) { append(node.multiplicity()); space(); } node.expression().accept(this); } /** * @ensures this.tokens' = * concat[ this.tokens, tokenize[ node.declarations[0] ], ",", * ..., tokenize[ node.declarations[ node.size() - 1 ] ] ] **/ public void visit(Decls node) { Iterator<Decl> decls = node.iterator(); decls.next().accept(this); while(decls.hasNext()) { comma(); decls.next().accept(this); } } /*--------------UNARY NODES---------------*/ /** @ensures this.tokenize' = * (parenthesize => concat [ this.tokens, "(", tokenize[child], ")" ] else * concat [ this.tokens, tokenize[child] ]*/ private void visitChild(Node child, boolean parenthesize) { if (parenthesize) { append("("); } child.accept(this); if (parenthesize) { append(")"); } } /** @return true if the given expression should be parenthesized when a * child of a compound parent */ private boolean parenthesize(Expression child) { return child instanceof BinaryExpression || child instanceof IfExpression; } /** @return true if the given expression should be parenthesized when a * child of a compound parent */ private boolean parenthesize(IntExpression child) { return !(child instanceof UnaryIntExpression || child instanceof IntConstant || child instanceof ExprToIntCast); } /** @return true if the given formula should be parenthesized when a * child of a compound parent */ private boolean parenthesize(Formula child) { return !(child instanceof NotFormula || child instanceof ConstantFormula || child instanceof RelationPredicate); } /** @ensures appends the given op and child to this.tokens; the child is * parenthesized if it's an instance of binary expression or an if expression. **/ public void visit(UnaryExpression node) { append(node.op()); visitChild(node.expression(), parenthesize(node.expression())); } /** @ensures appends the given op and child to this.tokens; the child is * parenthesized if it's not an instance of unary int expression or int constant. **/ public void visit(UnaryIntExpression node) { final IntExpression child = node.intExpr(); final IntOperator op = node.op(); final boolean parens = (op==IntOperator.ABS) || (op==IntOperator.SGN) || parenthesize(child); append(node.op()); visitChild(child, parens); } /** @ensures appends the given op and child to this.tokens; the child is * parenthesized if it's not an instance of not formula, constant formula, or * relation predicate. **/ public void visit(NotFormula node) { append("!"); final boolean pchild = parenthesize(node.formula()); indent += pchild ? 2 : 1; visitChild(node.formula(), parenthesize(node.formula())); indent -= pchild ? 2 : 1; } /** @ensures appends the given op and child to this.tokens; the child is * parenthesized if it's an instance of binary expression or an if expression. **/ public void visit(MultiplicityFormula node) { keyword(node.multiplicity()); visitChild(node.expression(), parenthesize(node.expression())); } /*--------------BINARY NODES---------------*/ /** @return true if the given expression needs to be parenthesized if a * child of a binary expression with the given operator */ private boolean parenthesize(ExprOperator op, Expression child) { return child instanceof IfExpression || child instanceof NaryExpression || (child instanceof BinaryExpression && (op==ExprOperator.JOIN || ((BinaryExpression)child).op()!=op)); } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(BinaryExpression node) { final ExprOperator op = node.op(); visitChild(node.left(), parenthesize(op, node.left())); infix(op); visitChild(node.right(), parenthesize(op, node.right())); } /** @return true if the given operator is associative */ private boolean associative(IntOperator op) { switch(op) { case DIVIDE : case MODULO : case SHA : case SHL : case SHR : return false; default : return true; } } /** @return true if the given int expression needs to be parenthesized if a * child of a binary int expression with the given operator */ private boolean parenthesize(IntOperator op, IntExpression child) { return child instanceof SumExpression || child instanceof IfIntExpression || child instanceof NaryIntExpression || (child instanceof BinaryIntExpression && (!associative(op) || ((BinaryIntExpression)child).op()!=op)); } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(BinaryIntExpression node) { final IntOperator op = node.op(); visitChild(node.left(), parenthesize(op, node.left())); infix(op); visitChild(node.right(), parenthesize(op, node.right())); } /** @return true if the given formula needs to be parenthesized if a * child of a binary formula with the given operator */ private boolean parenthesize(FormulaOperator op, Formula child) { return child instanceof QuantifiedFormula || //child instanceof NaryFormula || (child instanceof BinaryFormula && (op==FormulaOperator.IMPLIES || ((BinaryFormula)child).op()!=op)); } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(BinaryFormula node) { final FormulaOperator op = node.op(); final boolean pleft = parenthesize(op, node.left()); if (pleft) indent++; visitChild(node.left(), pleft); if (pleft) indent--; infix(op); newline(); final boolean pright = parenthesize(op, node.right()); if (pright) indent++; visitChild(node.right(), pright); if (pright) indent--; } /** @ensures this.tokens' = concat[ this.tokens, tokenize[node.left], node.op, tokenize[node.right] */ public void visit(ComparisonFormula node) { visitChild(node.left(), parenthesize(node.left())); infix(node.op()); visitChild(node.right(), parenthesize(node.right())); } /** @ensures this.tokens' = concat[ this.tokens, tokenize[node.left], node.op, tokenize[node.right] */ public void visit(IntComparisonFormula node) { visitChild(node.left(), parenthesize(node.left())); infix(node.op()); visitChild(node.right(), parenthesize(node.right())); } /*--------------TERNARY NODES---------------*/ /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(IfExpression node) { visitChild(node.condition(), parenthesize(node.condition())); infix("=>"); indent++; newline(); visitChild(node.thenExpr(), parenthesize(node.thenExpr())); infix("else"); newline(); visitChild(node.elseExpr(), parenthesize(node.elseExpr())); indent--; } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(IfIntExpression node) { visitChild(node.condition(), parenthesize(node.condition())); infix("=>"); indent++; newline(); visitChild(node.thenExpr(), parenthesize(node.thenExpr())); infix("else"); newline(); visitChild(node.elseExpr(), parenthesize(node.elseExpr())); indent--; } /*--------------VARIABLE CREATOR NODES---------------*/ /** @ensures this.tokens' = concat[ this.tokens, * "{", tokenize[node.decls], "|", tokenize[ node.formula ], "}" ]*/ public void visit(Comprehension node) { append("{"); node.decls().accept(this); infix("|"); node.formula().accept(this); append("}"); } /** @ensures this.tokens' = concat[ this.tokens, "sum", * tokenize[node.decls], "|", tokenize[ node.intExpr ], ]*/ public void visit(SumExpression node) { keyword("sum"); node.decls().accept(this); infix("|"); node.intExpr().accept(this); } /** @ensures this.tokens' = concat[ this.tokens, node.quantifier, * tokenize[node.decls], "|", tokenize[ node.formula ] ]*/ public void visit(QuantifiedFormula node) { keyword(node.quantifier()); node.decls().accept(this); infix("|"); indent++; newline(); node.formula().accept(this); indent--; } /*--------------NARY NODES---------------*/ /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(NaryExpression node) { final ExprOperator op = node.op(); visitChild(node.child(0), parenthesize(op, node.child(0))); for(int i = 1, size = node.size(); i < size; i++) { infix(op); visitChild(node.child(i), parenthesize(op, node.child(i))); } } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(NaryIntExpression node) { final IntOperator op = node.op(); visitChild(node.child(0), parenthesize(op, node.child(0))); for(int i = 1, size = node.size(); i < size; i++) { infix(op); visitChild(node.child(i), parenthesize(op, node.child(i))); } } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(NaryFormula node) { final FormulaOperator op = node.op(); boolean parens = parenthesize(op, node.child(0)); if (parens) indent++; visitChild(node.child(0), parens); if (parens) indent--; for(int i = 1, size = node.size(); i < size; i++) { infix(op); newline(); parens = parenthesize(op, node.child(i)); if (parens) indent++; visitChild(node.child(i), parens); if (parens) indent--; } } /*--------------OTHER NODES---------------*/ /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(ProjectExpression node) { append("project"); append("["); node.expression().accept(this); comma(); append("<"); final Iterator<IntExpression> cols = node.columns(); cols.next().accept(this); while(cols.hasNext()) { comma(); cols.next().accept(this); } append(">"); append("]"); } /** @ensures this.tokens' = concat[ this.tokens, "Int","[", * tokenize[node.intExpr], "]" ] **/ public void visit(IntToExprCast node) { append("Int"); append("["); node.intExpr().accept(this); append("]"); } /** @ensures this.tokens' = concat[ this.tokens, "int","[", * tokenize[node.expression], "]" ] **/ public void visit(ExprToIntCast node) { switch(node.op()) { case SUM: append("int"); append("["); node.expression().accept(this); append("]"); break; case CARDINALITY : append("#"); append("("); node.expression().accept(this); append(")"); break; default : throw new IllegalArgumentException("unknown operator: " + node.op()); } } /** @ensures appends the tokenization of the given node to this.tokens */ public void visit(RelationPredicate node) { switch(node.name()) { case ACYCLIC : append("acyclic"); append("["); node.relation().accept(this); append("]"); break; case FUNCTION : RelationPredicate.Function func = (RelationPredicate.Function)node; append("function"); append("["); func.relation().accept(this); colon(); func.domain().accept(this); infix("->"); keyword(func.targetMult()); func.range().accept(this); append("]"); break; case TOTAL_ORDERING : RelationPredicate.TotalOrdering ord = (RelationPredicate.TotalOrdering)node; append("ord"); append("["); ord.relation().accept(this); comma(); ord.ordered().accept(this); comma(); ord.first().accept(this); comma(); ord.last().accept(this); append("]"); break; default: throw new AssertionError("unreachable"); } } } private static class Dotifier implements VoidVisitor { private final StringBuilder graph = new StringBuilder(); private final Map<Node,Integer> ids = new LinkedHashMap<Node, Integer>(); static String apply(Node node) { final Dotifier dot = new Dotifier(); dot.graph.append("digraph {\n"); node.accept(dot); dot.graph.append("}"); return dot.graph.toString(); } private boolean visited(Node n) { if (ids.containsKey(n)) return true; ids.put(n, ids.size()); return false; } private String id(Node n) { return "N" + ids.get(n); } private void node(Node n, String label) { graph.append(id(n)); graph.append("[ label=\"" ); graph.append(ids.get(n)); graph.append("("); graph.append(label); graph.append(")\"];\n"); } private void edge(Node n1, Node n2) { if (n2 instanceof LeafExpression || n2 instanceof ConstantFormula || n2 instanceof IntConstant) { } graph.append(id(n1)); graph.append("->"); graph.append(id(n2)); graph.append(";\n"); } private void visit(Node parent, Object label) { if (visited(parent)) return; node(parent, label.toString()); } private void visit(Node parent, Object label, Node child) { if (visited(parent)) return; node(parent, label.toString()); child.accept(this); edge(parent, child); } private void visit(Node parent, Object label, Node left, Node right) { if (visited(parent)) return; node(parent, label.toString()); left.accept(this); right.accept(this); edge(parent, left); edge(parent, right); } private void visit(Node parent, Object label, Node left, Node middle, Node right) { if (visited(parent)) return; node(parent, label.toString()); left.accept(this); middle.accept(this); right.accept(this); edge(parent, left); edge(parent, middle); edge(parent, right); } private void visit(Node parent, Object label, Iterator<? extends Node> children) { if (visited(parent)) return; node(parent, label.toString()); while(children.hasNext()) { Node child = children.next(); child.accept(this); edge(parent, child); } } private void visit(Node parent, Object label, Node child, Iterator<? extends Node> children) { if (visited(parent)) return; node(parent, label.toString()); child.accept(this); edge(parent, child); while(children.hasNext()) { Node other = children.next(); other.accept(this); edge(parent, other); } } public void visit(Decls decls) { visit(decls, "decls", decls.iterator()); } public void visit(Decl decl) { visit(decl, "decl", decl.variable(), decl.expression()); } public void visit(Relation relation) { visit(relation, relation.name()); } public void visit(Variable variable) { visit(variable, variable.name()); } public void visit(ConstantExpression constExpr) { visit(constExpr, constExpr.name()); } public void visit(NaryExpression expr) { visit(expr, expr.op(), expr.iterator()); } public void visit(BinaryExpression binExpr) { visit(binExpr, binExpr.op(), binExpr.left(), binExpr.right()); } public void visit(UnaryExpression unaryExpr) { visit(unaryExpr, unaryExpr.op(), unaryExpr.expression()); } public void visit(Comprehension comprehension) { visit(comprehension, "setcomp", comprehension.decls(), comprehension.formula()); } public void visit(IfExpression ifExpr) { visit(ifExpr, "ite", ifExpr.condition(), ifExpr.thenExpr(), ifExpr.elseExpr()); } public void visit(ProjectExpression project) { visit(project, "proj", project.expression(), project.columns()); } public void visit(IntToExprCast castExpr) { visit(castExpr, castExpr.op(), castExpr.intExpr()); } public void visit(IntConstant intConst) { visit(intConst, intConst.value()); } public void visit(IfIntExpression intExpr) { visit(intExpr, "ite", intExpr.condition(), intExpr.thenExpr(), intExpr.elseExpr()); } public void visit(ExprToIntCast intExpr) { visit(intExpr, intExpr.op(), intExpr.expression()); } public void visit(NaryIntExpression intExpr) { visit(intExpr, intExpr.op(), intExpr.iterator());} public void visit(BinaryIntExpression intExpr) { visit(intExpr, intExpr.op(), intExpr.left(), intExpr.right()); } public void visit(UnaryIntExpression intExpr) { visit(intExpr, intExpr.op(), intExpr.intExpr());} public void visit(SumExpression intExpr) { visit(intExpr, "sum", intExpr.decls(), intExpr.intExpr()); } public void visit(IntComparisonFormula intComp) { visit(intComp, intComp.op(), intComp.left(), intComp.right());} public void visit(QuantifiedFormula quantFormula) { visit(quantFormula, quantFormula.quantifier(), quantFormula.decls(), quantFormula.formula()); } public void visit(NaryFormula formula) { visit(formula, formula.op(), formula.iterator()); } public void visit(BinaryFormula binFormula) { visit(binFormula, binFormula.op(), binFormula.left(), binFormula.right()); } public void visit(NotFormula not) { visit(not, "not", not.formula()); } public void visit(ConstantFormula constant) { visit(constant, constant.booleanValue()); } public void visit(ComparisonFormula compFormula) { visit(compFormula, compFormula.op(), compFormula.left(), compFormula.right());} public void visit(MultiplicityFormula multFormula) { visit(multFormula, multFormula.multiplicity(), multFormula.expression()); } public void visit(RelationPredicate pred) { if (visited(pred)) return; if (pred.name()==RelationPredicate.Name.FUNCTION) { final RelationPredicate.Function fp = (RelationPredicate.Function) pred; visit(fp, fp.name(), fp.domain(), fp.range() ); } else if (pred.name()==RelationPredicate.Name.TOTAL_ORDERING) { final RelationPredicate.TotalOrdering tp = (RelationPredicate.TotalOrdering) pred; visit(tp, tp.name(), tp.ordered(), tp.first(), tp.last() ); } else { throw new IllegalArgumentException("Unknown predicate: " + pred); } } } }