From 9f6c9b42073373b18c8a92a9428f35986effc77b Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sun, 26 Apr 2026 18:03:39 +0100 Subject: Hack together basic Java types generator --- .../src/main/java/org/zwobble/example/Main.java | 6 +- .../hobgoblin/compiler/HobgoblinCompiler.java | 8 +-- .../ast/typed/TypedStructDefinitionNode.java | 3 + .../hobgoblin/compiler/builtins/BuiltinTypes.java | 7 ++ .../hobgoblin/compiler/generators/Generator.java | 9 --- .../compiler/generators/java/JavaGenerator.java | 22 ------ .../hobgoblin/compiler/output/CodeWriter.java | 34 +++++++++ .../compiler/output/generators/Generator.java | 10 +++ .../generators/javatypes/JavaTypesGenerator.java | 81 ++++++++++++++++++++++ .../hobgoblin/compiler/output/java/JavaWriter.java | 76 ++++++++++++++++++++ .../output/java/ast/JavaCompilationUnit.java | 7 ++ .../output/java/ast/JavaRecordComponent.java | 4 ++ .../output/java/ast/JavaRecordDeclaration.java | 9 +++ .../output/java/ast/JavaTypeDeclaration.java | 4 ++ .../compiler/output/java/ast/JavaTypeRef.java | 6 ++ .../zwobble/hobgoblin/compiler/ExampleTests.java | 11 ++- 16 files changed, 259 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/builtins/BuiltinTypes.java delete mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/generators/Generator.java delete mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/generators/java/JavaGenerator.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/generators/Generator.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/java/JavaWriter.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaCompilationUnit.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordComponent.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordDeclaration.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeDeclaration.java create mode 100644 src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeRef.java diff --git a/examples/01-struct/output/java/src/main/java/org/zwobble/example/Main.java b/examples/01-struct/output/java/src/main/java/org/zwobble/example/Main.java index 5e0e111..5a48480 100644 --- a/examples/01-struct/output/java/src/main/java/org/zwobble/example/Main.java +++ b/examples/01-struct/output/java/src/main/java/org/zwobble/example/Main.java @@ -1,7 +1,11 @@ package org.zwobble.example; +import org.zwobble.example.types.point.Point; + public class Main { public static void main() { - + Point point = new Point(10, 25); + int x = point.x(); + int y = point.y(); } } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java b/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java index 5d59d96..698d68f 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/HobgoblinCompiler.java @@ -1,12 +1,12 @@ package org.zwobble.hobgoblin.compiler; import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode; -import org.zwobble.hobgoblin.compiler.generators.Generator; +import org.zwobble.hobgoblin.compiler.builtins.BuiltinTypes; +import org.zwobble.hobgoblin.compiler.output.generators.Generator; import org.zwobble.hobgoblin.compiler.parser.Parser; import org.zwobble.hobgoblin.compiler.typechecker.TypeChecker; import org.zwobble.hobgoblin.compiler.typechecker.TypeCheckerGlobalContext; import org.zwobble.hobgoblin.compiler.types.NamespaceName; -import org.zwobble.hobgoblin.compiler.types.ScalarType; import org.zwobble.sourcetext.SourceText; import java.io.IOException; @@ -22,7 +22,7 @@ public class HobgoblinCompiler { private HobgoblinCompiler() { } - public static void compile(Path projectRoot, List generators) throws IOException { + public static void compile(Path projectRoot, List generators) throws IOException { var sourceDirectory = projectRoot.resolve("src"); var sourceFiles = sourceDirectory.toFile() .listFiles(file -> file.toPath().toString().endsWith(FILE_NAME_EXTENSION)); @@ -35,7 +35,7 @@ public class HobgoblinCompiler { var untypedNamespaceNode = Parser.parseNamespace(sourceText, namespaceName); var typeCheckerContext = TypeCheckerGlobalContext.initial(); - typeCheckerContext.addBuiltinScalarType(new ScalarType("Int32")); + typeCheckerContext.addBuiltinScalarType(BuiltinTypes.INT_32); var typedNamespaceNode = TypeChecker.typeCheckNamespace(untypedNamespaceNode, typeCheckerContext); typedNamespaceNodes.add(typedNamespaceNode); diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedStructDefinitionNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedStructDefinitionNode.java index 0f966ab..d1666c2 100644 --- a/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedStructDefinitionNode.java +++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedStructDefinitionNode.java @@ -10,4 +10,7 @@ public record TypedStructDefinitionNode( List fields, Source source ) implements TypedNamespaceStatementNode { + public String name() { + return this.type.name(); + } } diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/builtins/BuiltinTypes.java b/src/main/java/org/zwobble/hobgoblin/compiler/builtins/BuiltinTypes.java new file mode 100644 index 0000000..9ed5fcf --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/builtins/BuiltinTypes.java @@ -0,0 +1,7 @@ +package org.zwobble.hobgoblin.compiler.builtins; + +import org.zwobble.hobgoblin.compiler.types.ScalarType; + +public class BuiltinTypes { + public static ScalarType INT_32 = new ScalarType("Int32"); +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/generators/Generator.java b/src/main/java/org/zwobble/hobgoblin/compiler/generators/Generator.java deleted file mode 100644 index 2457609..0000000 --- a/src/main/java/org/zwobble/hobgoblin/compiler/generators/Generator.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.zwobble.hobgoblin.compiler.generators; - -import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode; - -import java.util.List; - -public interface Generator { - void generate(List namespaces); -} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/generators/java/JavaGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/generators/java/JavaGenerator.java deleted file mode 100644 index ced3d81..0000000 --- a/src/main/java/org/zwobble/hobgoblin/compiler/generators/java/JavaGenerator.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.zwobble.hobgoblin.compiler.generators.java; - -import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode; -import org.zwobble.hobgoblin.compiler.generators.Generator; - -import java.nio.file.Path; -import java.util.List; - -public class JavaGenerator implements Generator { - private final Path root; - private final String packageName; - - public JavaGenerator(Path root, String packageName) { - this.root = root; - this.packageName = packageName; - } - - @Override - public void generate(List namespaces) { - - } -} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java new file mode 100644 index 0000000..6a5f784 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/CodeWriter.java @@ -0,0 +1,34 @@ +package org.zwobble.hobgoblin.compiler.output; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; + +public class CodeWriter implements AutoCloseable { + public static CodeWriter file(Path path) throws IOException { + Files.createDirectories(path.getParent()); + var writer = new FileWriter(path.toFile()); + return new CodeWriter(writer); + } + + private final Writer writer; + + public CodeWriter(Writer writer) { + this.writer = writer; + } + + public void write(String value) throws IOException { + this.writer.write(value); + } + + public void newLine() throws IOException { + this.writer.write("\n"); + } + + @Override + public void close() throws Exception { + this.writer.close(); + } +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/Generator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/Generator.java new file mode 100644 index 0000000..67f8d91 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/Generator.java @@ -0,0 +1,10 @@ +package org.zwobble.hobgoblin.compiler.output.generators; + +import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode; + +import java.io.IOException; +import java.util.List; + +public interface Generator { + void generate(List namespaces) throws IOException; +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java new file mode 100644 index 0000000..54796e2 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/generators/javatypes/JavaTypesGenerator.java @@ -0,0 +1,81 @@ +package org.zwobble.hobgoblin.compiler.output.generators.javatypes; + +import org.zwobble.hobgoblin.compiler.ast.typed.TypedNamespaceNode; +import org.zwobble.hobgoblin.compiler.ast.typed.TypedStructDefinitionNode; +import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelExpressionNode; +import org.zwobble.hobgoblin.compiler.builtins.BuiltinTypes; +import org.zwobble.hobgoblin.compiler.output.CodeWriter; +import org.zwobble.hobgoblin.compiler.output.generators.Generator; +import org.zwobble.hobgoblin.compiler.output.java.JavaWriter; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaRecordComponent; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaRecordDeclaration; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaCompilationUnit; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaTypeRef; +import org.zwobble.hobgoblin.compiler.types.Type; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class JavaTypesGenerator implements Generator { + private final Path sourceRootDirectory; + private final List packageName; + + public JavaTypesGenerator(Path sourceRootDirectory, String packageName) { + this.sourceRootDirectory = sourceRootDirectory; + this.packageName = List.of(packageName.split("\\.")); + } + + @Override + public void generate(List namespaces) throws IOException { + for (var namespace : namespaces) { + generateNamespace(namespace); + } + } + + private void generateNamespace(TypedNamespaceNode namespace) throws IOException { + var packageParts = new ArrayList<>(this.packageName); + packageParts.addAll(namespace.namespaceName().parts()); + + for (var statement : namespace.body()) { + switch (statement) { + case TypedStructDefinitionNode structDefinition -> { + var javaCompilationUnit = new JavaCompilationUnit( + String.join(".", packageParts), + new JavaRecordDeclaration( + structDefinition.name(), + structDefinition.fields().stream() + .map(field -> new JavaRecordComponent( + generateTypeRef(field.type()), + field.name() + )) + .toList() + ) + ); + + var javaCompilationUnitPath = this.sourceRootDirectory + .resolve(String.join(File.separator, packageParts)) + .resolve(structDefinition.name() + ".java"); + try (var writer = new JavaWriter(CodeWriter.file(javaCompilationUnitPath))) { + writer.writeCompilationUnit(javaCompilationUnit); + } catch (Exception e) { + // TODO: better handling + throw new RuntimeException(e); + } + } + } + } + } + + private JavaTypeRef generateTypeRef(TypedTypeLevelExpressionNode typeNode) { + var type = typeNode.value(); + + if (type.equals(BuiltinTypes.INT_32)) { + return new JavaTypeRef(List.of(), "int"); + } else { + throw new UnsupportedOperationException("TODO"); + } + } +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/java/JavaWriter.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/JavaWriter.java new file mode 100644 index 0000000..5e169c5 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/JavaWriter.java @@ -0,0 +1,76 @@ +package org.zwobble.hobgoblin.compiler.output.java; + +import org.zwobble.hobgoblin.compiler.output.CodeWriter; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaRecordDeclaration; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaCompilationUnit; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaTypeDeclaration; +import org.zwobble.hobgoblin.compiler.output.java.ast.JavaTypeRef; + +import java.io.IOException; + +public class JavaWriter implements AutoCloseable { + private final CodeWriter writer; + + public JavaWriter(CodeWriter writer) { + this.writer = writer; + } + + @Override + public void close() throws Exception { + this.writer.close(); + } + + public void writeCompilationUnit(JavaCompilationUnit compilationUnit) throws IOException { + this.writer.write("package "); + this.writer.write(compilationUnit.packageName()); + this.writer.write(";"); + this.writer.newLine(); + this.writer.newLine(); + + writeTypeDeclaration(compilationUnit.typeDeclaration()); + } + + private void writeTypeDeclaration(JavaTypeDeclaration typeDeclaration) throws IOException { + switch (typeDeclaration) { + case JavaRecordDeclaration recordDeclaration -> { + writeRecordDeclaration(recordDeclaration); + } + } + } + + private void writeRecordDeclaration(JavaRecordDeclaration recordDeclaration) throws IOException { + this.writer.write("public record "); + this.writer.write(recordDeclaration.name()); + this.writer.write("("); + + var isFirst = true; + for (var component : recordDeclaration.components()) { + if (!isFirst) { + writer.write(", "); + } + + writeTypeRef(component.type()); + writer.write(" "); + writer.write(component.name()); + + if (isFirst) { + isFirst = false; + } + } + + this.writer.write(") {"); + this.writer.newLine(); + + this.writer.write("}"); + this.writer.newLine(); + } + + private void writeTypeRef(JavaTypeRef type) throws IOException { + for (var part : type.packageName()) { + writer.write(part); + writer.write("."); + } + + writer.write(type.name()); + } +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaCompilationUnit.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaCompilationUnit.java new file mode 100644 index 0000000..de88d7b --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaCompilationUnit.java @@ -0,0 +1,7 @@ +package org.zwobble.hobgoblin.compiler.output.java.ast; + +public record JavaCompilationUnit( + String packageName, + JavaTypeDeclaration typeDeclaration +) { +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordComponent.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordComponent.java new file mode 100644 index 0000000..6954753 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordComponent.java @@ -0,0 +1,4 @@ +package org.zwobble.hobgoblin.compiler.output.java.ast; + +public record JavaRecordComponent(JavaTypeRef type, String name) { +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordDeclaration.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordDeclaration.java new file mode 100644 index 0000000..cbebf74 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaRecordDeclaration.java @@ -0,0 +1,9 @@ +package org.zwobble.hobgoblin.compiler.output.java.ast; + +import java.util.List; + +public record JavaRecordDeclaration( + String name, + List components +) implements JavaTypeDeclaration { +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeDeclaration.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeDeclaration.java new file mode 100644 index 0000000..5f0a3a5 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeDeclaration.java @@ -0,0 +1,4 @@ +package org.zwobble.hobgoblin.compiler.output.java.ast; + +public sealed interface JavaTypeDeclaration permits JavaRecordDeclaration { +} diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeRef.java b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeRef.java new file mode 100644 index 0000000..b657162 --- /dev/null +++ b/src/main/java/org/zwobble/hobgoblin/compiler/output/java/ast/JavaTypeRef.java @@ -0,0 +1,6 @@ +package org.zwobble.hobgoblin.compiler.output.java.ast; + +import java.util.List; + +public record JavaTypeRef(List packageName, String name) { +} diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/ExampleTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/ExampleTests.java index 8e68c09..fb5cc55 100644 --- a/src/test/java/org/zwobble/hobgoblin/compiler/ExampleTests.java +++ b/src/test/java/org/zwobble/hobgoblin/compiler/ExampleTests.java @@ -2,6 +2,7 @@ package org.zwobble.hobgoblin.compiler; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; +import org.zwobble.hobgoblin.compiler.output.generators.javatypes.JavaTypesGenerator; import org.zwobble.hobgoblin.compiler.testing.TestProcessRunner; import java.io.IOException; @@ -19,9 +20,15 @@ public class ExampleTests { } private void runTest(ExampleSet exampleSet) throws IOException, InterruptedException { - HobgoblinCompiler.compile(exampleSet.path(), List.of()); - var javaProjectPath = exampleSet.path().resolve("output/java"); + + var javaTypesGenerator = new JavaTypesGenerator( + javaProjectPath.resolve("src/gen/java"), + "org.zwobble.example.types" + ); + var generators = List.of(javaTypesGenerator); + HobgoblinCompiler.compile(exampleSet.path(), generators); + TestProcessRunner.command(List.of("mvn", "compile")) .cwd(javaProjectPath) .runCaptureOutput() -- cgit v1.2.3