summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2026-07-07 19:36:00 +0100
committerMichael Williamson <mike@zwobble.org>2026-07-07 19:36:00 +0100
commit9bc1704339c846de19f68543b05901ee18d1befc (patch)
tree08d609a10e972bc2a9765973bf945dcc22c0e389 /src
parent2536899969733a81a0001eb2da0d1d478c092e4b (diff)
Reorder Java writer body to match AST declarations
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriter.java670
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/output/lang/java/JavaWriterTests.java444
2 files changed, 603 insertions, 511 deletions
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 9eea1ca..38dde1f 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
@@ -39,48 +39,7 @@ public class JavaWriter implements AutoCloseable {
this.writer.close();
}
- private void writeBinaryOperation(JavaBinaryOperation binaryOperation) throws IOException {
- 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 -> "=";
- case BITWISE_AND -> "&";
- case BITWISE_OR -> "|";
- case EQUAL_TO -> "==";
- case LEFT_SHIFT -> "<<";
- case NOT_EQUAL_TO -> "!=";
- case UNSIGNED_RIGHT_SHIFT -> ">>>";
- });
- this.writer.write(" ");
- this.writeSubExpression(
- binaryOperation.right(),
- precedence,
- !isLeftAssociative
- );
- }
-
- private boolean isLeftAssociative(JavaBinaryOperator operator) {
- return switch (operator) {
- case ASSIGN -> false;
- case BITWISE_AND -> true;
- case BITWISE_OR -> true;
- case EQUAL_TO -> true;
- case LEFT_SHIFT -> true;
- case NOT_EQUAL_TO -> true;
- case UNSIGNED_RIGHT_SHIFT -> true;
- };
- }
-
- private void writeBooleanLiteral(JavaBooleanLiteral booleanLiteral) throws IOException {
- this.writer.write(booleanLiteral.value() ? "true" : "false");
- }
+ // == Compilation units ==
public void writeCompilationUnit(JavaCompilationUnit compilationUnit) throws IOException {
this.writer.writeHeader();
@@ -97,6 +56,8 @@ public class JavaWriter implements AutoCloseable {
this.writer.newLine();
}
+ // == Type declarations ==
+
void writeTypeDeclaration(JavaTypeDeclaration typeDeclaration) throws IOException {
switch (typeDeclaration) {
case JavaClassDeclaration classDeclaration -> {
@@ -117,71 +78,148 @@ public class JavaWriter implements AutoCloseable {
}
}
- void writeBlock(JavaBlock block) throws IOException {
- writeWithSeparator(
- block.statements(),
- this::writeBlockStatement,
- () -> this.writer.newLine()
- );
- }
+ private void writeClassDeclaration(JavaClassDeclaration classDeclaration) throws IOException {
+ writeDocComment(classDeclaration.docComment());
- void writeBlockStatement(JavaBlockStatement statement) throws IOException {
- switch (statement) {
- case JavaExpressionStatement expressionStatement -> {
- writeExpressionStatement(expressionStatement);
- }
+ this.writer.write("public class ");
+ this.writeIdentifier(classDeclaration.name());
+ this.writer.write(" ");
- case JavaLocalVariableDeclaration localVariableDeclaration -> {
- writeLocalVariableDeclaration(localVariableDeclaration);
- }
+ this.writeImplements(classDeclaration.implementsTypes());
- case JavaReturn returnStatement -> {
- writeReturnStatement(returnStatement);
- }
+ this.writer.write("{");
+ this.writer.indent();
+
+ for (var bodyDeclaration: classDeclaration.body()) {
+ writer.newLine();
+ writeClassBodyDeclaration(bodyDeclaration);
+ writer.newLine();
}
- }
- private void writeCast(JavaCast cast) throws IOException {
- this.writer.write("(");
- this.writeTypeRef(cast.type());
- this.writer.write(") ");
+ this.writer.newLine();
+ this.writeCustomArea(classDeclaration.customArea());
- this.writeSubExpression(cast.operand(), JavaPrecedence.PRIMARY, true);
+ this.writer.dedent();
+ this.writer.write("}");
}
- private void writeDocComment(DocComment docComment) throws IOException {
- for (var line : docComment.lines()) {
- this.writer.write("/// ");
- this.writer.write(line);
- this.writer.write("\n");
+ private void writeEnumDeclaration(JavaEnumDeclaration enumDeclaration) throws IOException {
+ this.writeDocComment(enumDeclaration.docComment());
+ this.writer.write("public enum ");
+ this.writeIdentifier(enumDeclaration.name());
+ this.writer.write(" {");
+ this.writer.indent();
+ this.writer.newLine();
+
+ if (!enumDeclaration.constants().isEmpty()) {
+ writeWithSeparator(
+ enumDeclaration.constants(),
+ constant -> this.writeIdentifier(constant.name()),
+ () -> {
+ this.writer.write(",");
+ this.writer.newLine();
+ }
+ );
+ this.writer.newLine();
+ this.writer.newLine();
}
+
+ this.writeCustomArea(enumDeclaration.customArea());
+ this.writer.dedent();
+ this.writer.write("}");
}
- private void writeClassDeclaration(JavaClassDeclaration classDeclaration) throws IOException {
- writeDocComment(classDeclaration.docComment());
+ private void writeInterfaceDeclaration(JavaInterfaceDeclaration interfaceDeclaration) throws IOException {
+ writeDocComment(interfaceDeclaration.docComment());
- this.writer.write("public class ");
- this.writeIdentifier(classDeclaration.name());
+ this.writer.write("public ");
+ switch (interfaceDeclaration.openness()) {
+ case OPEN -> {
+ }
+ case SEALED -> {
+ this.writer.write("sealed ");
+ }
+ }
+ this.writer.write("interface ");
+ this.writeIdentifier(interfaceDeclaration.name());
this.writer.write(" ");
- this.writeImplements(classDeclaration.implementsTypes());
+ if (!interfaceDeclaration.permitsTypes().isEmpty()) {
+ writer.write("permits ");
+ writeWithSeparator(
+ interfaceDeclaration.permitsTypes(),
+ this::writeTypeRef,
+ () -> writer.write(", ")
+ );
+ writer.write(" ");
+ }
this.writer.write("{");
this.writer.indent();
- for (var bodyDeclaration: classDeclaration.body()) {
+ for (var bodyDeclaration: interfaceDeclaration.body()) {
writer.newLine();
writeClassBodyDeclaration(bodyDeclaration);
writer.newLine();
}
this.writer.newLine();
- this.writeCustomArea(classDeclaration.customArea());
+ this.writeCustomArea(interfaceDeclaration.customArea());
+ this.writer.dedent();
+ this.writer.write("}");
+ }
+
+ private void writeRecordDeclaration(JavaRecordDeclaration recordDeclaration) throws IOException {
+ writeDocComment(recordDeclaration.docComment());
+
+ this.writer.write("public record ");
+ this.writeIdentifier(recordDeclaration.name());
+ this.writer.write("(");
+
+ writeWithSeparator(
+ recordDeclaration.components(),
+ component -> {
+ writeTypeRef(component.type());
+ writer.write(" ");
+ this.writeIdentifier(component.name());
+ },
+ () -> writer.write(", ")
+ );
+
+ writer.write(") ");
+
+ this.writeImplements(recordDeclaration.implementsTypes());
+
+ this.writer.write("{");
+ this.writer.indent();
+
+ for (var bodyDeclaration : recordDeclaration.body()) {
+ writer.newLine();
+ writeClassBodyDeclaration(bodyDeclaration);
+ writer.newLine();
+ }
+
+ this.writer.newLine();
+ this.writeCustomArea(recordDeclaration.customArea());
this.writer.dedent();
this.writer.write("}");
}
+ private void writeImplements(List<JavaTypeRef> implementsTypes) throws IOException {
+ if (!implementsTypes.isEmpty()) {
+ this.writer.write("implements ");
+ writeWithSeparator(
+ implementsTypes,
+ this::writeTypeRef,
+ () -> writer.write(", ")
+ );
+ writer.write(" ");
+ }
+ }
+
+ // == Type declaration bodies ==
+
void writeClassBodyDeclaration(JavaClassBodyDeclaration bodyDeclaration) throws IOException {
switch (bodyDeclaration) {
case JavaConstructorDeclaration constructorDeclaration -> {
@@ -231,40 +269,173 @@ public class JavaWriter implements AutoCloseable {
this.writer.write("}");
}
- private void writeCustomArea(JavaCustomArea customArea) throws IOException {
- var customAreaName = new StringWriter();
- writeTypeRef(customArea.type(), customAreaName::write);
- customAreaName.write(" ");
- customAreaName.write(customArea.name());
- this.writer.writeCustomArea(customAreaName.toString());
+ private void writeFieldDeclaration(JavaFieldDeclaration fieldDeclaration) throws IOException {
+ this.writeVisibility(fieldDeclaration.visibility());
+ this.writer.write(" final ");
+ this.writeTypeRef(fieldDeclaration.type());
+ this.writer.write(" ");
+ this.writeIdentifier(fieldDeclaration.name());
+ this.writer.write(";");
}
- private void writeEnumDeclaration(JavaEnumDeclaration enumDeclaration) throws IOException {
- this.writeDocComment(enumDeclaration.docComment());
- this.writer.write("public enum ");
- this.writeIdentifier(enumDeclaration.name());
- this.writer.write(" {");
- this.writer.indent();
- this.writer.newLine();
+ void writeMethodDeclaration(JavaMethodDeclaration method) throws IOException {
+ writeVisibility(method.visibility());
+ this.writer.write(" ");
- if (!enumDeclaration.constants().isEmpty()) {
+ switch (method.kind()) {
+ case STATIC -> {
+ this.writer.write("static ");
+ }
+ case INSTANCE -> {
+ }
+ }
+
+ writeTypeRef(method.returnType());
+ this.writer.write(" ");
+ this.writeIdentifier(method.name());
+ this.writer.write("(");
+
+ writeWithSeparator(
+ method.params(),
+ param -> {
+ writeTypeRef(param.type());
+ writer.write(" ");
+ this.writeIdentifier(param.name());
+ },
+ () -> writer.write(", ")
+ );
+
+ this.writer.write(")");
+
+ if (!method.throws_().isEmpty()) {
+ this.writer.write(" throws ");
writeWithSeparator(
- enumDeclaration.constants(),
- constant -> this.writeIdentifier(constant.name()),
- () -> {
- this.writer.write(",");
- this.writer.newLine();
- }
+ method.throws_(),
+ exceptionType -> writeTypeRef(exceptionType),
+ () -> this.writer.write(", ")
);
+ }
+
+ if (method.body().isEmpty()) {
+ this.writer.write(";");
+ } else {
+ this.writer.write(" {");
+ this.writer.indent();
+ if (!method.body().get().isEmpty()) {
+ this.writer.newLine();
+ writeBlock(method.body().get());
+ }
+ this.writer.dedent();
this.writer.newLine();
- this.writer.newLine();
+ this.writer.write("}");
}
+ }
- this.writeCustomArea(enumDeclaration.customArea());
- this.writer.dedent();
- this.writer.write("}");
+ // == Methods ==
+
+ // == Type references ==
+
+ void writeTypeRef(JavaTypeRef type) throws IOException {
+ writeTypeRef(type, writer::write);
+ }
+
+ private static void writeTypeRef(JavaTypeRef type, WriteString writer) throws IOException {
+ for (var part : type.packageName().parts()) {
+ writeIdentifier(part, writer);
+ writer.write(".");
+ }
+
+ writeWithSeparator(
+ type.typeNames(),
+ typeName -> writeIdentifier(typeName, writer),
+ () -> writer.write(".")
+ );
+
+ if (!type.args().isEmpty()) {
+ writer.write("<");
+ writeWithSeparator(
+ type.args(),
+ arg -> writeTypeArg(arg, writer),
+ () -> writer.write(", ")
+ );
+ writer.write(">");
+ }
}
+ private static void writeTypeArg(JavaTypeArg arg, WriteString writer) throws IOException {
+ switch (arg.variance()) {
+ case INVARIANT -> {
+ // Do nothing.
+ }
+ case SUPER -> {
+ writer.write("? super ");
+ }
+ }
+ writeTypeRef(arg.type(), writer);
+ }
+
+ // == Visibility ==
+
+ private void writeVisibility(JavaVisibility visibility) throws IOException {
+ this.writer.write(switch (visibility) {
+ case PUBLIC -> "public";
+ case PRIVATE -> "private";
+ });
+ }
+
+ // == Statements ==
+
+ void writeBlock(JavaBlock block) throws IOException {
+ writeWithSeparator(
+ block.statements(),
+ this::writeBlockStatement,
+ () -> this.writer.newLine()
+ );
+ }
+
+ void writeBlockStatement(JavaBlockStatement statement) throws IOException {
+ switch (statement) {
+ case JavaExpressionStatement expressionStatement -> {
+ writeExpressionStatement(expressionStatement);
+ }
+
+ case JavaLocalVariableDeclaration localVariableDeclaration -> {
+ writeLocalVariableDeclaration(localVariableDeclaration);
+ }
+
+ case JavaReturn returnStatement -> {
+ writeReturnStatement(returnStatement);
+ }
+ }
+ }
+
+ private void writeExpressionStatement(JavaExpressionStatement expressionStatement) throws IOException {
+ writeTopLevelExpression(expressionStatement.expression());
+ this.writer.write(";");
+ }
+
+ private void writeLocalVariableDeclaration(JavaLocalVariableDeclaration localVariableDeclaration) throws IOException {
+ if (localVariableDeclaration.type().isPresent()) {
+ this.writeTypeRef(localVariableDeclaration.type().get());
+ } else {
+ this.writer.write("var");
+ }
+
+ this.writer.write(" ");
+ this.writeIdentifier(localVariableDeclaration.name());
+ this.writer.write(" = ");
+ this.writeTopLevelExpression(localVariableDeclaration.initializer());
+ this.writer.write(";");
+ }
+
+ private void writeReturnStatement(JavaReturn returnStatement) throws IOException {
+ this.writer.write("return ");
+ writeTopLevelExpression(returnStatement.value());
+ this.writer.write(";");
+ }
+
+ // == Expressions ==
+
void writeTopLevelExpression(JavaExpression expression) throws IOException {
switch (expression) {
case JavaBinaryOperation binaryOperation -> {
@@ -378,102 +549,65 @@ public class JavaWriter implements AutoCloseable {
};
}
- private void writeExpressionStatement(JavaExpressionStatement expressionStatement) throws IOException {
- writeTopLevelExpression(expressionStatement.expression());
- this.writer.write(";");
- }
-
- private void writeFieldAccess(JavaFieldAccess fieldAccess) throws IOException {
- this.writeSubExpression(fieldAccess.receiver(), JavaPrecedence.PRIMARY, true);
- this.writer.write(".");
- this.writeIdentifier(fieldAccess.fieldName());
- }
+ private void writeBinaryOperation(JavaBinaryOperation binaryOperation) throws IOException {
+ var precedence = precedence(binaryOperation);
+ var isLeftAssociative = isLeftAssociative(binaryOperation.operator());
- private void writeFieldDeclaration(JavaFieldDeclaration fieldDeclaration) throws IOException {
- this.writeVisibility(fieldDeclaration.visibility());
- this.writer.write(" final ");
- this.writeTypeRef(fieldDeclaration.type());
+ this.writeSubExpression(
+ binaryOperation.left(),
+ precedence,
+ isLeftAssociative
+ );
this.writer.write(" ");
- this.writeIdentifier(fieldDeclaration.name());
- this.writer.write(";");
+ this.writer.write(switch (binaryOperation.operator()) {
+ case ASSIGN -> "=";
+ case BITWISE_AND -> "&";
+ case BITWISE_OR -> "|";
+ case EQUAL_TO -> "==";
+ case LEFT_SHIFT -> "<<";
+ case NOT_EQUAL_TO -> "!=";
+ case UNSIGNED_RIGHT_SHIFT -> ">>>";
+ });
+ this.writer.write(" ");
+ this.writeSubExpression(
+ binaryOperation.right(),
+ precedence,
+ !isLeftAssociative
+ );
}
- private void writeIdentifier(JavaIdentifier identifier) throws IOException {
- this.writer.write(identifier.value());
+ private boolean isLeftAssociative(JavaBinaryOperator operator) {
+ return switch (operator) {
+ case ASSIGN -> false;
+ case BITWISE_AND -> true;
+ case BITWISE_OR -> true;
+ case EQUAL_TO -> true;
+ case LEFT_SHIFT -> true;
+ case NOT_EQUAL_TO -> true;
+ case UNSIGNED_RIGHT_SHIFT -> true;
+ };
}
- private static void writeIdentifier(JavaIdentifier identifier, WriteString writer) throws IOException {
- writer.write(identifier.value());
+ private void writeBooleanLiteral(JavaBooleanLiteral booleanLiteral) throws IOException {
+ this.writer.write(booleanLiteral.value() ? "true" : "false");
}
- private void writeImplements(List<JavaTypeRef> implementsTypes) throws IOException {
- if (!implementsTypes.isEmpty()) {
- this.writer.write("implements ");
- writeWithSeparator(
- implementsTypes,
- this::writeTypeRef,
- () -> writer.write(", ")
- );
- writer.write(" ");
- }
- }
+ private void writeCast(JavaCast cast) throws IOException {
+ this.writer.write("(");
+ this.writeTypeRef(cast.type());
+ this.writer.write(") ");
- private void writeIntegerLiteral(JavaIntegerLiteral integerLiteral) throws IOException {
- this.writer.write(Long.toString(integerLiteral.value()));
+ this.writeSubExpression(cast.operand(), JavaPrecedence.PRIMARY, true);
}
- private void writeInterfaceDeclaration(JavaInterfaceDeclaration interfaceDeclaration) throws IOException {
- writeDocComment(interfaceDeclaration.docComment());
-
- this.writer.write("public ");
- switch (interfaceDeclaration.openness()) {
- case OPEN -> {
- }
- case SEALED -> {
- this.writer.write("sealed ");
- }
- }
- this.writer.write("interface ");
- this.writeIdentifier(interfaceDeclaration.name());
- this.writer.write(" ");
-
- if (!interfaceDeclaration.permitsTypes().isEmpty()) {
- writer.write("permits ");
- writeWithSeparator(
- interfaceDeclaration.permitsTypes(),
- this::writeTypeRef,
- () -> writer.write(", ")
- );
- writer.write(" ");
- }
-
- this.writer.write("{");
- this.writer.indent();
-
- for (var bodyDeclaration: interfaceDeclaration.body()) {
- writer.newLine();
- writeClassBodyDeclaration(bodyDeclaration);
- writer.newLine();
- }
-
- this.writer.newLine();
- this.writeCustomArea(interfaceDeclaration.customArea());
- this.writer.dedent();
- this.writer.write("}");
+ private void writeFieldAccess(JavaFieldAccess fieldAccess) throws IOException {
+ this.writeSubExpression(fieldAccess.receiver(), JavaPrecedence.PRIMARY, true);
+ this.writer.write(".");
+ this.writeIdentifier(fieldAccess.fieldName());
}
- private void writeLocalVariableDeclaration(JavaLocalVariableDeclaration localVariableDeclaration) throws IOException {
- if (localVariableDeclaration.type().isPresent()) {
- this.writeTypeRef(localVariableDeclaration.type().get());
- } else {
- this.writer.write("var");
- }
-
- this.writer.write(" ");
- this.writeIdentifier(localVariableDeclaration.name());
- this.writer.write(" = ");
- this.writeTopLevelExpression(localVariableDeclaration.initializer());
- this.writer.write(";");
+ private void writeIntegerLiteral(JavaIntegerLiteral integerLiteral) throws IOException {
+ this.writer.write(Long.toString(integerLiteral.value()));
}
private void writeMethodCall(JavaMethodCall call) throws IOException {
@@ -489,59 +623,6 @@ public class JavaWriter implements AutoCloseable {
this.writer.write(")");
}
- void writeMethodDeclaration(JavaMethodDeclaration method) throws IOException {
- writeVisibility(method.visibility());
- this.writer.write(" ");
-
- switch (method.kind()) {
- case STATIC -> {
- this.writer.write("static ");
- }
- case INSTANCE -> {
- }
- }
-
- writeTypeRef(method.returnType());
- this.writer.write(" ");
- this.writeIdentifier(method.name());
- this.writer.write("(");
-
- writeWithSeparator(
- method.params(),
- param -> {
- writeTypeRef(param.type());
- writer.write(" ");
- this.writeIdentifier(param.name());
- },
- () -> writer.write(", ")
- );
-
- this.writer.write(")");
-
- if (!method.throws_().isEmpty()) {
- this.writer.write(" throws ");
- writeWithSeparator(
- method.throws_(),
- exceptionType -> writeTypeRef(exceptionType),
- () -> this.writer.write(", ")
- );
- }
-
- if (method.body().isEmpty()) {
- this.writer.write(";");
- } else {
- this.writer.write(" {");
- this.writer.indent();
- if (!method.body().get().isEmpty()) {
- this.writer.newLine();
- writeBlock(method.body().get());
- }
- this.writer.dedent();
- this.writer.newLine();
- this.writer.write("}");
- }
- }
-
private void writeMethodRef(JavaMethodRef methodRef) throws IOException {
this.writeTypeRef(methodRef.type());
this.writer.write("::");
@@ -564,61 +645,10 @@ public class JavaWriter implements AutoCloseable {
this.writer.write("null");
}
- private void writePackageName(JavaPackageName packageName) throws IOException {
- writeWithSeparator(
- packageName.parts(),
- this::writeIdentifier,
- () -> this.writer.write(".")
- );
- }
-
- private void writeRecordDeclaration(JavaRecordDeclaration recordDeclaration) throws IOException {
- writeDocComment(recordDeclaration.docComment());
-
- this.writer.write("public record ");
- this.writeIdentifier(recordDeclaration.name());
- this.writer.write("(");
-
- writeWithSeparator(
- recordDeclaration.components(),
- component -> {
- writeTypeRef(component.type());
- writer.write(" ");
- this.writeIdentifier(component.name());
- },
- () -> writer.write(", ")
- );
-
- writer.write(") ");
-
- this.writeImplements(recordDeclaration.implementsTypes());
-
- this.writer.write("{");
- this.writer.indent();
-
- for (var bodyDeclaration : recordDeclaration.body()) {
- writer.newLine();
- writeClassBodyDeclaration(bodyDeclaration);
- writer.newLine();
- }
-
- this.writer.newLine();
- this.writeCustomArea(recordDeclaration.customArea());
-
- this.writer.dedent();
- this.writer.write("}");
- }
-
private void writeRef(JavaRef ref) throws IOException {
this.writeIdentifier(ref.name());
}
- private void writeReturnStatement(JavaReturn returnStatement) throws IOException {
- this.writer.write("return ");
- writeTopLevelExpression(returnStatement.value());
- this.writer.write(";");
- }
-
private void writeStaticFieldAccess(JavaStaticFieldAccess staticFieldAccess) throws IOException {
this.writeTypeRef(staticFieldAccess.type());
this.writer.write(".");
@@ -653,50 +683,42 @@ public class JavaWriter implements AutoCloseable {
this.writeSubExpression(ternaryConditional.ifFalse(), JavaPrecedence.TERNARY, true);
}
- private static void writeTypeArg(JavaTypeArg arg, WriteString writer) throws IOException {
- switch (arg.variance()) {
- case INVARIANT -> {
- // Do nothing.
- }
- case SUPER -> {
- writer.write("? super ");
- }
- }
- writeTypeRef(arg.type(), writer);
- }
+ // == Identifiers ==
- void writeTypeRef(JavaTypeRef type) throws IOException {
- writeTypeRef(type, writer::write);
+ private void writeIdentifier(JavaIdentifier identifier) throws IOException {
+ this.writer.write(identifier.value());
}
- private static void writeTypeRef(JavaTypeRef type, WriteString writer) throws IOException {
- for (var part : type.packageName().parts()) {
- writeIdentifier(part, writer);
- writer.write(".");
- }
+ private static void writeIdentifier(JavaIdentifier identifier, WriteString writer) throws IOException {
+ writer.write(identifier.value());
+ }
+ private void writePackageName(JavaPackageName packageName) throws IOException {
writeWithSeparator(
- type.typeNames(),
- typeName -> writeIdentifier(typeName, writer),
- () -> writer.write(".")
+ packageName.parts(),
+ this::writeIdentifier,
+ () -> this.writer.write(".")
);
+ }
- if (!type.args().isEmpty()) {
- writer.write("<");
- writeWithSeparator(
- type.args(),
- arg -> writeTypeArg(arg, writer),
- () -> writer.write(", ")
- );
- writer.write(">");
- }
+ // == Custom areas ==
+
+ private void writeCustomArea(JavaCustomArea customArea) throws IOException {
+ var customAreaName = new StringWriter();
+ writeTypeRef(customArea.type(), customAreaName::write);
+ customAreaName.write(" ");
+ customAreaName.write(customArea.name());
+ this.writer.writeCustomArea(customAreaName.toString());
}
- private void writeVisibility(JavaVisibility visibility) throws IOException {
- this.writer.write(switch (visibility) {
- case PUBLIC -> "public";
- case PRIVATE -> "private";
- });
+ // == Doc comments ==
+
+ private void writeDocComment(DocComment docComment) throws IOException {
+ for (var line : docComment.lines()) {
+ this.writer.write("/// ");
+ this.writer.write(line);
+ this.writer.write("\n");
+ }
}
interface WriteString {
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 ecdcaff..5464116 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
@@ -14,6 +14,8 @@ import static org.zwobble.precisely.AssertThat.assertThat;
import static org.zwobble.precisely.Matchers.equalTo;
public class JavaWriterTests {
+ // == Compilation units ==
+
@Test
public void compilationUnit() throws IOException {
var java = new JavaCompilationUnit(
@@ -43,6 +45,10 @@ public class JavaWriterTests {
));
}
+ // == Type declarations ==
+
+ // === Class declarations ===
+
@Test
public void emptyClassDeclaration() throws IOException {
var java = new JavaClassDeclaration(
@@ -168,6 +174,8 @@ public class JavaWriterTests {
));
}
+ // === Enum declarations ===
+
@Test
public void emptyEnumDeclaration() throws IOException {
var java = JavaEnumDeclaration.arbitrary()
@@ -227,6 +235,8 @@ public class JavaWriterTests {
));
}
+ // === Interface declarations ===
+
@Test
public void openInterfaceDeclaration() throws IOException {
var java = new JavaInterfaceDeclaration(
@@ -352,6 +362,8 @@ public class JavaWriterTests {
));
}
+ // === Record declarations ===
+
@Test
public void emptyRecordDeclaration() throws IOException {
var java = new JavaRecordDeclaration(
@@ -479,6 +491,118 @@ public class JavaWriterTests {
));
}
+ // == Type declaration bodies ==
+
+ // === Constructor declarations ===
+
+ @Test
+ public void emptyConstructor() throws IOException {
+ var java = new JavaConstructorDeclaration(
+ JavaIdentifier.of("Rectangle"),
+ JavaVisibility.PUBLIC,
+ List.of(),
+ JavaBlock.EMPTY
+ );
+
+ var string = write(writer -> writer.writeClassBodyDeclaration(java));
+
+ assertThat(string, equalTo("""
+ public Rectangle() {
+ }"""
+ ));
+ }
+
+ @Test
+ public void publicConstructor() throws IOException {
+ var java = new JavaConstructorDeclaration(
+ JavaIdentifier.of("Rectangle"),
+ JavaVisibility.PUBLIC,
+ List.of(),
+ JavaBlock.EMPTY
+ );
+
+ var string = write(writer -> writer.writeClassBodyDeclaration(java));
+
+ assertThat(string, equalTo("""
+ public Rectangle() {
+ }"""
+ ));
+ }
+
+ @Test
+ public void privateConstructor() throws IOException {
+ var java = new JavaConstructorDeclaration(
+ JavaIdentifier.of("Rectangle"),
+ JavaVisibility.PRIVATE,
+ List.of(),
+ JavaBlock.EMPTY
+ );
+
+ var string = write(writer -> writer.writeClassBodyDeclaration(java));
+
+ assertThat(string, equalTo("""
+ private Rectangle() {
+ }"""
+ ));
+ }
+
+ @Test
+ public void constructorWithParams() throws IOException {
+ var java = new JavaConstructorDeclaration(
+ JavaIdentifier.of("Rectangle"),
+ JavaVisibility.PUBLIC,
+ List.of(
+ new JavaParam(JavaTypeRef.INT, JavaIdentifier.of("b")),
+ new JavaParam(JavaTypeRef.LONG, JavaIdentifier.of("c"))
+ ),
+ JavaBlock.EMPTY
+ );
+
+ var string = write(writer -> writer.writeClassBodyDeclaration(java));
+
+ assertThat(string, equalTo("""
+ public Rectangle(int b, long c) {
+ }"""
+ ));
+ }
+
+ @Test
+ public void constructorWithBody() throws IOException {
+ var java = new JavaConstructorDeclaration(
+ JavaIdentifier.of("Rectangle"),
+ JavaVisibility.PUBLIC,
+ List.of(),
+ new JavaBlock(List.of(
+ new JavaReturn(new JavaIntegerLiteral(42))
+ ))
+ );
+
+ var string = write(writer -> writer.writeClassBodyDeclaration(java));
+
+ assertThat(string, equalTo("""
+ public Rectangle() {
+ return 42;
+ }"""
+ ));
+ }
+
+ // === Field declarations ===
+
+ @Test
+ public void fieldDeclaration() throws IOException {
+ var java = new JavaFieldDeclaration(
+ JavaVisibility.PRIVATE,
+ JavaTypeRef.INT,
+ JavaIdentifier.of("count")
+ );
+
+ var string = write(writer -> writer.writeClassBodyDeclaration(java));
+
+ assertThat(string, equalTo("private final int count;"));
+ }
+
+ // === Method declarations ===
+
@Test
public void emptyMethodWithoutBody() throws IOException {
var java = JavaMethodDeclaration.arbitrary()
@@ -633,109 +757,72 @@ public class JavaWriterTests {
));
}
- @Test
- public void emptyConstructor() throws IOException {
- var java = new JavaConstructorDeclaration(
- JavaIdentifier.of("Rectangle"),
- JavaVisibility.PUBLIC,
- List.of(),
- JavaBlock.EMPTY
- );
-
- var string = write(writer -> writer.writeClassBodyDeclaration(java));
+ // == Methods ==
- assertThat(string, equalTo("""
- public Rectangle() {
- }"""
- ));
- }
+ // == Type references ==
@Test
- public void publicConstructor() throws IOException {
- var java = new JavaConstructorDeclaration(
- JavaIdentifier.of("Rectangle"),
- JavaVisibility.PUBLIC,
- List.of(),
- JavaBlock.EMPTY
+ public void typeRefWithoutArgsHasNoAngleBrackets() throws IOException {
+ var java = JavaTypeRef.topLevel(
+ JavaPackageName.of("abc", "def"),
+ JavaIdentifier.of("One")
);
- var string = write(writer -> writer.writeClassBodyDeclaration(java));
+ var string = write(writer -> writer.writeTypeRef(java));
- assertThat(string, equalTo("""
- public Rectangle() {
- }"""
- ));
+ assertThat(string, equalTo("abc.def.One"));
}
@Test
- public void privateConstructor() throws IOException {
- var java = new JavaConstructorDeclaration(
- JavaIdentifier.of("Rectangle"),
- JavaVisibility.PRIVATE,
- List.of(),
- JavaBlock.EMPTY
+ public void typeRefWithArgsHasAngleBrackets() throws IOException {
+ var java = JavaTypeRef.topLevelGeneric(
+ JavaPackageName.of("abc", "def"),
+ JavaIdentifier.of("One"),
+ List.of(
+ JavaTypeArg.invariant(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Two"))),
+ JavaTypeArg.invariant(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Three")))
+ )
);
- var string = write(writer -> writer.writeClassBodyDeclaration(java));
+ var string = write(writer -> writer.writeTypeRef(java));
- assertThat(string, equalTo("""
- private Rectangle() {
- }"""
- ));
+ assertThat(string, equalTo("abc.def.One<Two, Three>"));
}
@Test
- public void constructorWithParams() throws IOException {
- var java = new JavaConstructorDeclaration(
- JavaIdentifier.of("Rectangle"),
- JavaVisibility.PUBLIC,
+ public void typeRefArgCanBeSuper() throws IOException {
+ var java = JavaTypeRef.topLevelGeneric(
+ JavaPackageName.of("abc", "def"),
+ JavaIdentifier.of("One"),
List.of(
- new JavaParam(JavaTypeRef.INT, JavaIdentifier.of("b")),
- new JavaParam(JavaTypeRef.LONG, JavaIdentifier.of("c"))
- ),
- JavaBlock.EMPTY
+ JavaTypeArg.superType(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Two"))),
+ JavaTypeArg.superType(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Three")))
+ )
);
- var string = write(writer -> writer.writeClassBodyDeclaration(java));
+ var string = write(writer -> writer.writeTypeRef(java));
- assertThat(string, equalTo("""
- public Rectangle(int b, long c) {
- }"""
- ));
+ assertThat(string, equalTo("abc.def.One<? super Two, ? super Three>"));
}
@Test
- public void constructorWithBody() throws IOException {
- var java = new JavaConstructorDeclaration(
- JavaIdentifier.of("Rectangle"),
- JavaVisibility.PUBLIC,
- List.of(),
- new JavaBlock(List.of(
- new JavaReturn(new JavaIntegerLiteral(42))
- ))
+ public void typeRefInnerTypeNameIsSeparatedByDot() throws IOException {
+ var java = JavaTypeRef.inner(
+ JavaPackageName.of("abc", "def"),
+ JavaIdentifier.of("One"),
+ JavaIdentifier.of("Two")
);
- var string = write(writer -> writer.writeClassBodyDeclaration(java));
+ var string = write(writer -> writer.writeTypeRef(java));
- assertThat(string, equalTo("""
- public Rectangle() {
- return 42;
- }"""
- ));
+ assertThat(string, equalTo("abc.def.One.Two"));
}
- @Test
- public void fieldDeclaration() throws IOException {
- var java = new JavaFieldDeclaration(
- JavaVisibility.PRIVATE,
- JavaTypeRef.INT,
- JavaIdentifier.of("count")
- );
+ // == Visibility ==
- var string = write(writer -> writer.writeClassBodyDeclaration(java));
+ // == Statements ==
- assertThat(string, equalTo("private final int count;"));
- }
+ // === Blocks ===
@Test
public void block() throws IOException {
@@ -749,6 +836,8 @@ public class JavaWriterTests {
assertThat(string, equalTo("return 42;\nreturn 47;"));
}
+ // === Expression statements ===
+
@Test
public void expressionStatement() throws IOException {
var java = new JavaExpressionStatement(new JavaIntegerLiteral(42));
@@ -758,6 +847,8 @@ public class JavaWriterTests {
assertThat(string, equalTo("42;"));
}
+ // === Local variable declarations ===
+
@Test
public void localVariableDeclarationUsingExplicitType() throws IOException {
var java = JavaLocalVariableDeclaration.arbitrary()
@@ -783,6 +874,8 @@ public class JavaWriterTests {
assertThat(string, equalTo("var count = 42;"));
}
+ // === Returns ===
+
@Test
public void returnStatement() throws IOException {
var java = new JavaReturn(new JavaIntegerLiteral(42));
@@ -792,59 +885,9 @@ public class JavaWriterTests {
assertThat(string, equalTo("return 42;"));
}
- @Test
- public void falseLiteral() throws IOException {
- var java = new JavaBooleanLiteral(false);
-
- var string = write(writer -> writer.writeTopLevelExpression(java));
-
- assertThat(string, equalTo("false"));
- }
-
- @Test
- public void trueLiteral() throws IOException {
- var java = new JavaBooleanLiteral(true);
-
- var string = write(writer -> writer.writeTopLevelExpression(java));
-
- assertThat(string, equalTo("true"));
- }
-
- @Test
- public void cast() throws IOException {
- var java = new JavaCast(JavaTypeRef.INT, new JavaIntegerLiteral(42));
-
- var string = write(writer -> writer.writeTopLevelExpression(java));
-
- assertThat(string, equalTo("(int) 42"));
- }
-
- @Test
- public void integerLiteral() throws IOException {
- var java = new JavaIntegerLiteral(42);
-
- var string = write(writer -> writer.writeTopLevelExpression(java));
-
- assertThat(string, equalTo("42"));
- }
-
- @Test
- public void nullLiteral() throws IOException {
- var java = new JavaNullLiteral();
-
- var string = write(writer -> writer.writeTopLevelExpression(java));
-
- assertThat(string, equalTo("null"));
- }
-
- @Test
- public void stringLiteral() throws IOException {
- var java = new JavaStringLiteral("hello");
-
- var string = write(writer -> writer.writeTopLevelExpression(java));
+ // == Expressions ==
- assertThat(string, equalTo("\"hello\""));
- }
+ // === Binary operations ===
@Test
public void binaryOperationAssignment() throws IOException {
@@ -937,6 +980,39 @@ public class JavaWriterTests {
assertThat(string, equalTo("x | y"));
}
+ // === Boolean literals ===
+
+ @Test
+ public void falseLiteral() throws IOException {
+ var java = new JavaBooleanLiteral(false);
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("false"));
+ }
+
+ @Test
+ public void trueLiteral() throws IOException {
+ var java = new JavaBooleanLiteral(true);
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("true"));
+ }
+
+ // === Casts ===
+
+ @Test
+ public void cast() throws IOException {
+ var java = new JavaCast(JavaTypeRef.INT, new JavaIntegerLiteral(42));
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("(int) 42"));
+ }
+
+ // === Field accesses ===
+
@Test
public void fieldAccess() throws IOException {
var java = new JavaFieldAccess(
@@ -949,18 +1025,19 @@ public class JavaWriterTests {
assertThat(string, equalTo("one.two"));
}
+ // === Integer literals ===
+
@Test
- public void staticFieldAccess() throws IOException {
- var java = new JavaStaticFieldAccess(
- JavaTypeRef.topLevel(JavaPackageName.of("one"), JavaIdentifier.of("Two")),
- JavaIdentifier.of("three")
- );
+ public void integerLiteral() throws IOException {
+ var java = new JavaIntegerLiteral(42);
var string = write(writer -> writer.writeTopLevelExpression(java));
- assertThat(string, equalTo("one.Two.three"));
+ assertThat(string, equalTo("42"));
}
+ // === Method refs ===
+
@Test
public void referenceToMethodOnType() throws IOException {
var java = new JavaMethodRef(
@@ -973,6 +1050,8 @@ public class JavaWriterTests {
assertThat(string, equalTo("one.Two::three"));
}
+ // === New expressions ===
+
@Test
public void newExpression() throws IOException {
var java = new JavaNewExpression(
@@ -985,6 +1064,19 @@ public class JavaWriterTests {
assertThat(string, equalTo("new one.Two(1, 2)"));
}
+ // === Null literals ===
+
+ @Test
+ public void nullLiteral() throws IOException {
+ var java = new JavaNullLiteral();
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("null"));
+ }
+
+ // === Refs ===
+
@Test
public void ref() throws IOException {
var java = new JavaRef(JavaIdentifier.of("x"));
@@ -994,6 +1086,22 @@ public class JavaWriterTests {
assertThat(string, equalTo("x"));
}
+ // === Static field accesses ===
+
+ @Test
+ public void staticFieldAccess() throws IOException {
+ var java = new JavaStaticFieldAccess(
+ JavaTypeRef.topLevel(JavaPackageName.of("one"), JavaIdentifier.of("Two")),
+ JavaIdentifier.of("three")
+ );
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("one.Two.three"));
+ }
+
+ // === Ternary conditionals ===
+
@Test
public void ternaryConditional() throws IOException {
var java = new JavaTernaryConditional(
@@ -1007,6 +1115,19 @@ public class JavaWriterTests {
assertThat(string, equalTo("x ? y : z"));
}
+ // === String literals ===
+
+ @Test
+ public void stringLiteral() throws IOException {
+ var java = new JavaStringLiteral("hello");
+
+ var string = write(writer -> writer.writeTopLevelExpression(java));
+
+ assertThat(string, equalTo("\"hello\""));
+ }
+
+ // === Precedence handling ===
+
@Test
public void whenSubExpressionHasLowerPrecedenceThenSubExpressionIsParenthesized() throws IOException {
var java = JavaBinaryOperation.arbitrary()
@@ -1071,62 +1192,9 @@ public class JavaWriterTests {
assertThat(string, equalTo("(a = b) = c = d"));
}
- @Test
- public void typeRefWithoutArgsHasNoAngleBrackets() throws IOException {
- var java = JavaTypeRef.topLevel(
- JavaPackageName.of("abc", "def"),
- JavaIdentifier.of("One")
- );
-
- var string = write(writer -> writer.writeTypeRef(java));
-
- assertThat(string, equalTo("abc.def.One"));
- }
-
- @Test
- public void typeRefWithArgsHasAngleBrackets() throws IOException {
- var java = JavaTypeRef.topLevelGeneric(
- JavaPackageName.of("abc", "def"),
- JavaIdentifier.of("One"),
- List.of(
- JavaTypeArg.invariant(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Two"))),
- JavaTypeArg.invariant(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Three")))
- )
- );
-
- var string = write(writer -> writer.writeTypeRef(java));
+ // == Identifiers ==
- assertThat(string, equalTo("abc.def.One<Two, Three>"));
- }
-
- @Test
- public void typeRefArgCanBeSuper() throws IOException {
- var java = JavaTypeRef.topLevelGeneric(
- JavaPackageName.of("abc", "def"),
- JavaIdentifier.of("One"),
- List.of(
- JavaTypeArg.superType(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Two"))),
- JavaTypeArg.superType(JavaTypeRef.topLevel(JavaPackageName.EMPTY, JavaIdentifier.of("Three")))
- )
- );
-
- var string = write(writer -> writer.writeTypeRef(java));
-
- assertThat(string, equalTo("abc.def.One<? super Two, ? super Three>"));
- }
-
- @Test
- public void typeRefInnerTypeNameIsSeparatedByDot() throws IOException {
- var java = JavaTypeRef.inner(
- JavaPackageName.of("abc", "def"),
- JavaIdentifier.of("One"),
- JavaIdentifier.of("Two")
- );
-
- var string = write(writer -> writer.writeTypeRef(java));
-
- assertThat(string, equalTo("abc.def.One.Two"));
- }
+ // == Custom areas ==
@Test
public void customAreaContentsArePreserved() throws IOException {
@@ -1158,6 +1226,8 @@ public class JavaWriterTests {
));
}
+ // == Doc comments ==
+
private String write(Write write) throws IOException {
return write(write, Optional.empty());
}