diff options
| author | Michael Williamson <mike@zwobble.org> | 2026-07-04 22:55:19 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2026-07-04 22:55:19 +0100 |
| commit | 444c252a1f345f392a9cb8238597ea7bcb96e6fa (patch) | |
| tree | 80e5394713a3ae3cb689dc0e685dd048475692be /src/main | |
| parent | aad987d6a5f8360af1d20316e2fbed56f418796c (diff) | |
Handle parenthesized sub expressions in Java AST
Diffstat (limited to 'src/main')
3 files changed, 112 insertions, 11 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(")"); |
