Skip to content

Commit 0aa749a

Browse files
committed
Add ExpressionPrinter for Pretty Printing
1 parent 9c7ed97 commit 0aa749a

10 files changed

Lines changed: 188 additions & 83 deletions

File tree

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import java.util.stream.Collectors;
6-
75
import liquidjava.diagnostics.errors.LJError;
86
import liquidjava.rj_language.visitors.ExpressionVisitor;
97

@@ -31,7 +29,7 @@ public <T> T accept(ExpressionVisitor<T> visitor) throws LJError {
3129

3230
@Override
3331
public String toString() {
34-
return name + "(" + getArgs().stream().map(Expression::toString).collect(Collectors.joining(", ")) + ")";
32+
return ExpressionPrinter.print(this);
3533
}
3634

3735
@Override

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,9 @@ public boolean isArithmeticOperation() {
4040
return !isLogicOperation() && !isBooleanOperation();
4141
}
4242

43-
private boolean isAssociative() {
44-
return op.equals("&&") || op.equals("||") || op.equals("+") || op.equals("*");
45-
}
46-
47-
@Override
48-
protected Precedence getPrecedence() {
49-
return Precedence.fromOperator(op);
50-
}
51-
52-
private String formatRightOperand(Expression operand) {
53-
if (operand.getPrecedence() == getPrecedence() && operand instanceof BinaryExpression right)
54-
if (!isAssociative() || !op.equals(right.getOperator()))
55-
return parenthesize(operand);
56-
57-
return formatChild(operand);
58-
}
59-
6043
@Override
6144
public String toString() {
62-
return formatChild(getFirstOperand()) + " " + op + " " + formatRightOperand(getSecondOperand());
45+
return ExpressionPrinter.print(this);
6346
}
6447

6548
@Override

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,39 +36,6 @@ public abstract class Expression {
3636

3737
public abstract String toString();
3838

39-
protected enum Precedence {
40-
TERNARY, IMPLICATION, OR, AND, COMPARISON, ADDITIVE, MULTIPLICATIVE, PREFIX, ATOMIC;
41-
42-
private boolean isLowerThan(Precedence other) {
43-
return ordinal() < other.ordinal();
44-
}
45-
46-
protected static Precedence fromOperator(String op) {
47-
return switch (op) {
48-
case "-->" -> IMPLICATION;
49-
case "||" -> OR;
50-
case "&&" -> AND;
51-
case "==", "!=", ">=", ">", "<=", "<" -> COMPARISON;
52-
case "+", "-" -> ADDITIVE;
53-
case "*", "/", "%" -> MULTIPLICATIVE;
54-
default -> ATOMIC;
55-
};
56-
}
57-
}
58-
59-
protected Precedence getPrecedence() {
60-
return Precedence.ATOMIC;
61-
}
62-
63-
protected String parenthesize(Expression expression) {
64-
return "(" + expression.toString() + ")";
65-
}
66-
67-
protected String formatChild(Expression child) {
68-
boolean needsParentheses = child.getPrecedence().isLowerThan(getPrecedence());
69-
return needsParentheses ? parenthesize(child) : child.toString();
70-
}
71-
7239
List<Expression> children = new ArrayList<>();
7340

7441
public void addChild(Expression e) {
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package liquidjava.rj_language.ast;
2+
3+
import java.util.stream.Collectors;
4+
5+
import liquidjava.diagnostics.errors.LJError;
6+
import liquidjava.rj_language.visitors.ExpressionVisitor;
7+
import liquidjava.utils.Utils;
8+
9+
public class ExpressionPrinter implements ExpressionVisitor<String> {
10+
11+
private enum Precedence {
12+
TERNARY, IMPLICATION, OR, AND, COMPARISON, ADDITIVE, MULTIPLICATIVE, PREFIX, ATOMIC;
13+
14+
private boolean isLowerThan(Precedence other) {
15+
return ordinal() < other.ordinal();
16+
}
17+
}
18+
19+
public static String print(Expression expression) {
20+
return new ExpressionPrinter().render(expression);
21+
}
22+
23+
private String render(Expression expression) {
24+
return expression.accept(this);
25+
}
26+
27+
private String renderChild(Expression child) {
28+
if (child instanceof GroupExpression group)
29+
return "(" + render(group.getExpression()) + ")";
30+
return render(child);
31+
}
32+
33+
private String renderOperand(Expression parent, Expression child) {
34+
if (needsParentheses(parent, child))
35+
return "(" + render(child) + ")";
36+
return renderChild(child);
37+
}
38+
39+
private String renderRightOperand(BinaryExpression parent, Expression child) {
40+
if (needsRightParentheses(parent, child))
41+
return "(" + render(child) + ")";
42+
return renderChild(child);
43+
}
44+
45+
private String renderConditionOperand(Expression child) {
46+
if (child instanceof Ite)
47+
return "(" + render(child) + ")";
48+
return renderChild(child);
49+
}
50+
51+
private boolean needsParentheses(Expression parent, Expression child) {
52+
if (precedence(child).isLowerThan(precedence(parent)))
53+
return true;
54+
return false;
55+
}
56+
57+
private boolean needsRightParentheses(BinaryExpression parent, Expression child) {
58+
if (needsParentheses(parent, child))
59+
return true;
60+
61+
if (precedence(child) != precedence(parent))
62+
return false;
63+
64+
if (child instanceof BinaryExpression right)
65+
return !isAssociative(parent.getOperator()) || !parent.getOperator().equals(right.getOperator());
66+
67+
return false;
68+
}
69+
70+
private Precedence precedence(Expression expression) {
71+
if (expression instanceof GroupExpression group)
72+
return precedence(group.getExpression());
73+
if (expression instanceof Ite)
74+
return Precedence.TERNARY;
75+
if (expression instanceof UnaryExpression)
76+
return Precedence.PREFIX;
77+
if (expression instanceof BinaryExpression binary)
78+
return precedence(binary.getOperator());
79+
return Precedence.ATOMIC;
80+
}
81+
82+
private Precedence precedence(String operator) {
83+
return switch (operator) {
84+
case "-->" -> Precedence.IMPLICATION;
85+
case "||" -> Precedence.OR;
86+
case "&&" -> Precedence.AND;
87+
case "==", "!=", ">=", ">", "<=", "<" -> Precedence.COMPARISON;
88+
case "+", "-" -> Precedence.ADDITIVE;
89+
case "*", "/", "%" -> Precedence.MULTIPLICATIVE;
90+
default -> Precedence.ATOMIC;
91+
};
92+
}
93+
94+
private boolean isAssociative(String operator) {
95+
return operator.equals("&&") || operator.equals("||") || operator.equals("+") || operator.equals("*");
96+
}
97+
98+
@Override
99+
public String visitAliasInvocation(AliasInvocation alias) {
100+
return alias.getName() + "(" + alias.getArgs().stream().map(this::renderChild).collect(Collectors.joining(", "))
101+
+ ")";
102+
}
103+
104+
@Override
105+
public String visitBinaryExpression(BinaryExpression exp) {
106+
return renderOperand(exp, exp.getFirstOperand()) + " " + exp.getOperator() + " "
107+
+ renderRightOperand(exp, exp.getSecondOperand());
108+
}
109+
110+
@Override
111+
public String visitFunctionInvocation(FunctionInvocation fun) {
112+
return Utils.getSimpleName(fun.getName()) + "("
113+
+ fun.getArgs().stream().map(this::renderChild).collect(Collectors.joining(",")) + ")";
114+
}
115+
116+
@Override
117+
public String visitGroupExpression(GroupExpression exp) {
118+
return "(" + render(exp.getExpression()) + ")";
119+
}
120+
121+
@Override
122+
public String visitIte(Ite ite) {
123+
return renderConditionOperand(ite.getCondition()) + " ? " + renderConditionOperand(ite.getThen()) + " : "
124+
+ renderOperand(ite, ite.getElse());
125+
}
126+
127+
@Override
128+
public String visitLiteralInt(LiteralInt lit) {
129+
return Integer.toString(lit.getValue());
130+
}
131+
132+
@Override
133+
public String visitLiteralLong(LiteralLong lit) {
134+
return Long.toString(lit.getValue());
135+
}
136+
137+
@Override
138+
public String visitLiteralBoolean(LiteralBoolean lit) {
139+
return Boolean.toString(lit.value);
140+
}
141+
142+
@Override
143+
public String visitLiteralChar(LiteralChar lit) {
144+
return "'" + lit.getValue() + "'";
145+
}
146+
147+
@Override
148+
public String visitLiteralReal(LiteralReal lit) {
149+
return Double.toString(lit.getValue());
150+
}
151+
152+
@Override
153+
public String visitLiteralString(LiteralString lit) {
154+
return lit.toString();
155+
}
156+
157+
@Override
158+
public String visitUnaryExpression(UnaryExpression exp) {
159+
return exp.getOp() + renderOperand(exp, exp.getExpression());
160+
}
161+
162+
@Override
163+
public String visitVar(Var var) {
164+
return var.getName();
165+
}
166+
}

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import java.util.ArrayList;
44
import java.util.List;
5-
import java.util.stream.Collectors;
6-
75
import liquidjava.diagnostics.errors.LJError;
86
import liquidjava.rj_language.visitors.ExpressionVisitor;
97
import liquidjava.utils.Utils;
@@ -37,8 +35,7 @@ public <T> T accept(ExpressionVisitor<T> visitor) throws LJError {
3735

3836
@Override
3937
public String toString() {
40-
return Utils.getSimpleName(name) + "("
41-
+ getArgs().stream().map(Expression::toString).collect(Collectors.joining(",")) + ")";
38+
return ExpressionPrinter.print(this);
4239
}
4340

4441
@Override

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,7 @@ public <T> T accept(ExpressionVisitor<T> visitor) throws LJError {
2121
}
2222

2323
public String toString() {
24-
return getExpression().toString();
25-
}
26-
27-
@Override
28-
protected Precedence getPrecedence() {
29-
return getExpression().getPrecedence();
24+
return ExpressionPrinter.print(this);
3025
}
3126

3227
@Override

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,9 @@ public <T> T accept(ExpressionVisitor<T> visitor) throws LJError {
3030
return visitor.visitIte(this);
3131
}
3232

33-
@Override
34-
protected Precedence getPrecedence() {
35-
return Precedence.TERNARY;
36-
}
37-
38-
private String formatCondition(Expression operand) {
39-
if (operand instanceof Ite)
40-
return parenthesize(operand);
41-
42-
return formatChild(operand);
43-
}
44-
4533
@Override
4634
public String toString() {
47-
return formatCondition(getCondition()) + " ? " + formatCondition(getThen()) + " : " + formatChild(getElse());
35+
return ExpressionPrinter.print(this);
4836
}
4937

5038
@Override

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public <T> T accept(ExpressionVisitor<T> visitor) throws LJError {
1818
return visitor.visitLiteralString(this);
1919
}
2020

21+
@Override
2122
public String toString() {
2223
return value;
2324
}

liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,9 @@ public <T> T accept(ExpressionVisitor<T> visitor) throws LJError {
2727
return visitor.visitUnaryExpression(this);
2828
}
2929

30-
@Override
31-
protected Precedence getPrecedence() {
32-
return Precedence.PREFIX;
33-
}
34-
3530
@Override
3631
public String toString() {
37-
return op + formatChild(getExpression());
32+
return ExpressionPrinter.print(this);
3833
}
3934

4035
@Override

liquidjava-verifier/src/test/java/liquidjava/rj_language/ast/ExpressionPrinterTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ void printsUnaryWithParenthesesForCompoundOperands() {
1818

1919
assertEquals("x > 0", comparison.toString());
2020
assertEquals("!(x > 0)", new UnaryExpression("!", comparison).toString());
21+
assertEquals("-(-x)",
22+
new UnaryExpression("-", new GroupExpression(new UnaryExpression("-", new Var("x")))).toString());
2123
}
2224

2325
@Test
@@ -33,6 +35,16 @@ void printsBinaryExpressionsWithOperatorPrecedence() {
3335
assertEquals("b * c * c", new BinaryExpression(product, "*", new Var("c")).toString());
3436
}
3537

38+
@Test
39+
void preservesExplicitGroupingOnRightHandSide() {
40+
Expression groupedSum = new GroupExpression(new BinaryExpression(new Var("b"), "+", new Var("c")));
41+
Expression groupedComparison = new GroupExpression(
42+
new BinaryExpression(new LiteralInt(1), ">", new LiteralInt(0)));
43+
44+
assertEquals("a - (b + c)", new BinaryExpression(new Var("a"), "-", groupedSum).toString());
45+
assertEquals("x == (1 > 0)", new BinaryExpression(new Var("x"), "==", groupedComparison).toString());
46+
}
47+
3648
@Test
3749
void printsLogicalExpressionsWithNeededParentheses() {
3850
Expression andExpression = new BinaryExpression(new Var("a"), "&&", new Var("b"));
@@ -58,6 +70,9 @@ void printsTernaryExpressionsWithNeededParentheses() {
5870
assertEquals("a ? (b ? c : d) : e",
5971
new Ite(new Var("a"), new Ite(new Var("b"), new Var("c"), new Var("d")), new Var("e")).toString());
6072
assertEquals("a ? b : c ? d : e", new Ite(new Var("a"), new Var("b"), nestedElse).toString());
73+
assertEquals("(a ? b : c) ? d : e", new Ite(new GroupExpression(ite), new Var("d"), new Var("e")).toString());
74+
assertEquals("a ? b : (c ? d : e)",
75+
new Ite(new Var("a"), new Var("b"), new GroupExpression(nestedElse)).toString());
6176
assertEquals("a ? b : c", new Ite(new Var("a"), new Var("b"), new Var("c")).toString());
6277
}
6378
}

0 commit comments

Comments
 (0)