summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaAssociativity.java6
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaPrecedence.java29
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java88
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java90
4 files changed, 189 insertions, 24 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaAssociativity.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaAssociativity.java
new file mode 100644
index 0000000..938c062
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaAssociativity.java
@@ -0,0 +1,6 @@
+package org.zwobble.hobgoblin.compiler.output.lang.java;
+
+public enum JavaAssociativity {
+ LEFT,
+ RIGHT
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaPrecedence.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaPrecedence.java
new file mode 100644
index 0000000..5318697
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaPrecedence.java
@@ -0,0 +1,29 @@
+package org.zwobble.hobgoblin.compiler.output.lang.java;
+
+public enum JavaPrecedence {
+ PRIMARY(14),
+ POSTFIX(13),
+ UNARY(12),
+ MULTIPLICATIVE(11),
+ ADDITIVE(10),
+ SHIFT(9),
+ RELATIONAL(8),
+ EQUALITY(7),
+ BITWISE_AND(6),
+ BITWISE_EXCLUSIVE_OR(5),
+ BITWISE_INCLUSIVE_OR(4),
+ LOGICAL_AND(3),
+ LOGICAL_OR(2),
+ TERNARY(1),
+ ASSIGNMENT(0);
+
+ private final int value;
+
+ JavaPrecedence(int value) {
+ this.value = value;
+ }
+
+ public int value() {
+ return this.value;
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java
index e8e0f40..95e3081 100644
--- a/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java
@@ -40,7 +40,14 @@ public class JavaWriter implements AutoCloseable {
}
private void writeBinaryOperation(JavaBinaryOperation binaryOperation) throws IOException {
- this.writeExpression(binaryOperation.left());
+ var precedence = precedence(binaryOperation);
+ var isLeftAssociative = isLeftAssociative(binaryOperation.operator());
+
+ this.writeSubExpression(
+ binaryOperation.left(),
+ precedence,
+ isLeftAssociative
+ );
this.writer.write(" ");
this.writer.write(switch (binaryOperation.operator()) {
case ASSIGN -> "=";
@@ -48,7 +55,19 @@ public class JavaWriter implements AutoCloseable {
case UNSIGNED_RIGHT_SHIFT -> ">>>";
});
this.writer.write(" ");
- this.writeExpression(binaryOperation.right());
+ this.writeSubExpression(
+ binaryOperation.right(),
+ precedence,
+ !isLeftAssociative
+ );
+ }
+
+ private boolean isLeftAssociative(JavaBinaryOperator operator) {
+ return switch (operator) {
+ case ASSIGN -> false;
+ case BITWISE_AND -> true;
+ case UNSIGNED_RIGHT_SHIFT -> true;
+ };
}
private void writeBooleanLiteral(JavaBooleanLiteral booleanLiteral) throws IOException {
@@ -230,7 +249,7 @@ public class JavaWriter implements AutoCloseable {
this.writer.write("}");
}
- void writeExpression(JavaExpression expression) throws IOException {
+ void writeTopLevelExpression(JavaExpression expression) throws IOException {
switch (expression) {
case JavaBinaryOperation binaryOperation -> {
writeBinaryOperation(binaryOperation);
@@ -282,13 +301,60 @@ public class JavaWriter implements AutoCloseable {
}
}
+ void writeSubExpression(
+ JavaExpression expression,
+ JavaPrecedence parentPrecedence,
+ boolean matchesAssociativity
+ ) throws IOException {
+ var precedence = precedence(expression);
+ var requiresParens = parentPrecedence != JavaPrecedence.PRIMARY && (
+ parentPrecedence.value() > precedence.value() ||
+ (parentPrecedence.value() == precedence.value() && !matchesAssociativity)
+ );
+
+ if (requiresParens) {
+ this.writer.write("(");
+ }
+
+ this.writeTopLevelExpression(expression);
+
+ if (requiresParens) {
+ this.writer.write(")");
+ }
+ }
+
+ private JavaPrecedence precedence(JavaExpression expression) {
+ return switch (expression) {
+ case JavaBinaryOperation binaryOperation -> precedence(binaryOperation);
+ case JavaBooleanLiteral booleanLiteral -> JavaPrecedence.PRIMARY;
+ case JavaFieldAccess fieldAccess -> JavaPrecedence.PRIMARY;
+ case JavaIntegerLiteral integerLiteral -> JavaPrecedence.PRIMARY;
+ case JavaMethodCall methodCall -> JavaPrecedence.PRIMARY;
+ case JavaMethodRef methodRef -> JavaPrecedence.PRIMARY;
+ case JavaNewExpression newExpression -> JavaPrecedence.PRIMARY;
+ case JavaNullLiteral nullLiteral -> JavaPrecedence.PRIMARY;
+ case JavaRef ref -> JavaPrecedence.PRIMARY;
+ case JavaStaticFieldAccess staticFieldAccess -> JavaPrecedence.PRIMARY;
+ case JavaStaticMethodCall staticMethodCall -> JavaPrecedence.PRIMARY;
+ case JavaStringLiteral stringLiteral -> JavaPrecedence.PRIMARY;
+ };
+ }
+
+ private static JavaPrecedence precedence(JavaBinaryOperation binaryOperation) {
+ return switch (binaryOperation.operator()) {
+ case ASSIGN -> JavaPrecedence.ASSIGNMENT;
+ case BITWISE_AND -> JavaPrecedence.BITWISE_AND;
+ case UNSIGNED_RIGHT_SHIFT -> JavaPrecedence.SHIFT;
+ };
+ }
+
private void writeExpressionStatement(JavaExpressionStatement expressionStatement) throws IOException {
- writeExpression(expressionStatement.expression());
+ writeTopLevelExpression(expressionStatement.expression());
this.writer.write(";");
}
private void writeFieldAccess(JavaFieldAccess fieldAccess) throws IOException {
- this.writeExpression(fieldAccess.receiver());
+ this.writeSubExpression(fieldAccess.receiver(), JavaPrecedence.PRIMARY, true);
this.writer.write(".");
this.writeIdentifier(fieldAccess.fieldName());
}
@@ -370,18 +436,18 @@ public class JavaWriter implements AutoCloseable {
this.writer.write("var ");
this.writeIdentifier(localVariableDeclaration.name());
this.writer.write(" = ");
- this.writeExpression(localVariableDeclaration.initializer());
+ this.writeTopLevelExpression(localVariableDeclaration.initializer());
this.writer.write(";");
}
private void writeMethodCall(JavaMethodCall call) throws IOException {
- this.writeExpression(call.receiver());
+ this.writeSubExpression(call.receiver(), JavaPrecedence.PRIMARY, true);
this.writer.write(".");
this.writeIdentifier(call.methodName());
this.writer.write("(");
writeWithSeparator(
call.args(),
- this::writeExpression,
+ this::writeTopLevelExpression,
() -> this.writer.write(", ")
);
this.writer.write(")");
@@ -443,7 +509,7 @@ public class JavaWriter implements AutoCloseable {
this.writer.write("(");
writeWithSeparator(
newExpression.args(),
- this::writeExpression,
+ this::writeTopLevelExpression,
() -> this.writer.write(", ")
);
this.writer.write(")");
@@ -504,7 +570,7 @@ public class JavaWriter implements AutoCloseable {
private void writeReturnStatement(JavaReturn returnStatement) throws IOException {
this.writer.write("return ");
- writeExpression(returnStatement.value());
+ writeTopLevelExpression(returnStatement.value());
this.writer.write(";");
}
@@ -521,7 +587,7 @@ public class JavaWriter implements AutoCloseable {
this.writer.write("(");
writeWithSeparator(
call.args(),
- this::writeExpression,
+ this::writeTopLevelExpression,
() -> this.writer.write(", ")
);
this.writer.write(")");
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java
index e1e43ad..25b2c9f 100644
--- a/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java
@@ -794,7 +794,7 @@ public class JavaWriterTests {
public void falseLiteral() throws IOException {
var java = new JavaBooleanLiteral(false);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("false"));
}
@@ -803,7 +803,7 @@ public class JavaWriterTests {
public void trueLiteral() throws IOException {
var java = new JavaBooleanLiteral(true);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("true"));
}
@@ -812,7 +812,7 @@ public class JavaWriterTests {
public void integerLiteral() throws IOException {
var java = new JavaIntegerLiteral(42);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("42"));
}
@@ -821,7 +821,7 @@ public class JavaWriterTests {
public void nullLiteral() throws IOException {
var java = new JavaNullLiteral();
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("null"));
}
@@ -830,7 +830,7 @@ public class JavaWriterTests {
public void stringLiteral() throws IOException {
var java = new JavaStringLiteral("hello");
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("\"hello\""));
}
@@ -843,7 +843,7 @@ public class JavaWriterTests {
new JavaIntegerLiteral(42)
);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("x = 42"));
}
@@ -856,7 +856,7 @@ public class JavaWriterTests {
.withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("y")))
.build();
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("x >>> y"));
}
@@ -869,7 +869,7 @@ public class JavaWriterTests {
.withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("y")))
.build();
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("x & y"));
}
@@ -881,7 +881,7 @@ public class JavaWriterTests {
JavaIdentifier.of("two")
);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("one.two"));
}
@@ -893,7 +893,7 @@ public class JavaWriterTests {
JavaIdentifier.of("three")
);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("one.Two.three"));
}
@@ -905,7 +905,7 @@ public class JavaWriterTests {
JavaIdentifier.of("three")
);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("one.Two::three"));
}
@@ -917,7 +917,7 @@ public class JavaWriterTests {
List.of(new JavaIntegerLiteral(1), new JavaIntegerLiteral(2))
);
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("new one.Two(1, 2)"));
}
@@ -926,12 +926,76 @@ public class JavaWriterTests {
public void ref() throws IOException {
var java = new JavaRef(JavaIdentifier.of("x"));
- var string = write(writer -> writer.writeExpression(java));
+ var string = write(writer -> writer.writeTopLevelExpression(java));
assertThat(string, equalTo("x"));
}
@Test
+ public void whenSubExpressionHasLowerPrecedenceThenSubExpressionIsParenthesized() throws IOException {
+ var java = JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.BITWISE_AND)
+ .withLeft(
+ JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.ASSIGN)
+ .withLeft(JavaRef.arbitrary().withName(JavaIdentifier.of("x")))
+ .withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("y")))
+ )
+ .withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("z")))
+ .build();
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("(x = y) & z"));
+ }
+
+ @Test
+ public void whenSubExpressionsOfLeftAssociativeBinaryOperationHaveSamePrecedenceThenRightExpressionIsParenthesized() throws IOException {
+ var java = JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.BITWISE_AND)
+ .withLeft(
+ JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.BITWISE_AND)
+ .withLeft(JavaRef.arbitrary().withName(JavaIdentifier.of("a")))
+ .withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("b")))
+ )
+ .withRight(
+ JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.BITWISE_AND)
+ .withLeft(JavaRef.arbitrary().withName(JavaIdentifier.of("c")))
+ .withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("d")))
+ )
+ .build();
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("a & b & (c & d)"));
+ }
+
+ @Test
+ public void whenSubExpressionsOfRightAssociativeBinaryOperationHaveSamePrecedenceThenLeftExpressionIsParenthesized() throws IOException {
+ var java = JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.ASSIGN)
+ .withLeft(
+ JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.ASSIGN)
+ .withLeft(JavaRef.arbitrary().withName(JavaIdentifier.of("a")))
+ .withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("b")))
+ )
+ .withRight(
+ JavaBinaryOperation.arbitrary()
+ .withOperator(JavaBinaryOperator.ASSIGN)
+ .withLeft(JavaRef.arbitrary().withName(JavaIdentifier.of("c")))
+ .withRight(JavaRef.arbitrary().withName(JavaIdentifier.of("d")))
+ )
+ .build();
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("(a = b) = c = d"));
+ }
+
+ @Test
public void typeRefWithoutArgsHasNoAngleBrackets() throws IOException {
var java = JavaTypeRef.topLevel(
JavaPackageName.of("abc", "def"),