diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fbc4c40..415abb8 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -29,6 +29,7 @@ jobs: uses: actions/setup-java@v4 with: java-version: | + 23-ea ${{ matrix.java }} distribution: 'temurin' - name: Grant execute permission for gradlew diff --git a/demo/src/main/kotlin/io/github/overrun/marshalgen/test/Test.kt b/demo/src/main/kotlin/io/github/overrun/marshalgen/test/Test.kt index d8a584d..8471b12 100644 --- a/demo/src/main/kotlin/io/github/overrun/marshalgen/test/Test.kt +++ b/demo/src/main/kotlin/io/github/overrun/marshalgen/test/Test.kt @@ -19,6 +19,20 @@ package io.github.overrun.marshalgen.test import io.github.overrun.marshalgen.* fun main() { + val MyStruct = struct("overrungl.gen.MyStruct", cType = "mystruct") { + int("Int") + address("Address") + void_pointer("VoidPointer", javadoc { +"A `void*` member." }) + c_int("CInt") + string("String") + const_char_pointer("ConstCharPointer", javadoc { +"A `const char*` member." }) + size_t("SizeT") + } + val MyStruct2 = struct("overrungl.gen.MyStruct2", javadoc = javadoc { +"Javadoc" }) { + MyStruct("MyStruct") + } + StructRegistration.generate("overrungl.gen.MyStructRegistration") + downcall("overrungl.gen.MyDowncall", javadoc = javadoc { +"Paragraph 1" +"Paragraph 2" @@ -72,6 +86,8 @@ fun main() { "Parameter1" param "The first parameter" "Parameter2" param "The second parameter" returns("The returned value") + see("#SkippedFunction()") + see("#ReturnAddress()") }) string("StringFunction", string * "Parameter1") void("DefaultFunction1") { @@ -88,6 +104,13 @@ fun main() { """.trimIndent() ) } + void("DefaultFunction3", string * "Parameter1") { + default( + """ + System.out.println("default operation 3"); + """.trimIndent() + ) + } const_char_pointer("NativeType", const_char_pointer * "Parameter1") handle("ReturnMethodHandle") string("StringCharset", (string * "Parameter1") { charset("UTF-16") }) { @@ -104,11 +127,13 @@ fun main() { int * "Parameter1", (int * "Parameter2") { default("42") }, int * "Parameter3", + void_pointer * "Parameter4", javadoc = javadoc { +"Default parameters" "Parameter1" param "The first parameter" "Parameter2" param "The second parameter" "Parameter3" param "The third parameter" + "Parameter4" param "The fourth parameter" }) string("TestDefaultOverload", (int * "Parameter1") { default("42") }, string * "Parameter2") string("TestReturnOverload") diff --git a/generated/build.gradle.kts b/generated/build.gradle.kts index 45d1fbe..441b0af 100644 --- a/generated/build.gradle.kts +++ b/generated/build.gradle.kts @@ -18,3 +18,21 @@ java { languageVersion.set(JavaLanguageVersion.of(23)) } } + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} + +tasks.withType { + options { + if (this is CoreJavadocOptions) { + // TODO + addBooleanOption("-enable-preview", true) + addStringOption("source", "23") + if (this is StandardJavadocDocletOptions) { + links("https://over-run.github.io/memstack/", "https://over-run.github.io/marshal/") + } + } + jFlags("-Dstdout.encoding=UTF-8", "-Dstderr.encoding=UTF-8") + } +} diff --git a/generated/src/main/java/overrungl/gen/MyDowncall.java b/generated/src/main/java/overrungl/gen/MyDowncall.java index 88de020..70ae2b4 100644 --- a/generated/src/main/java/overrungl/gen/MyDowncall.java +++ b/generated/src/main/java/overrungl/gen/MyDowncall.java @@ -61,6 +61,8 @@ static void StaticMethod() { /// @param Parameter1 The first parameter /// @param Parameter2 The second parameter /// @return The returned value + /// @see #SkippedFunction() + /// @see #ReturnAddress() int WithParameter(int Parameter1, double Parameter2); String StringFunction(String Parameter1); @@ -76,17 +78,25 @@ default void DefaultFunction2() { System.out.println("default operation"); } + default void DefaultFunction3(String Parameter1) { + System.out.println("default operation 3"); + } + + void DefaultFunction3(MemorySegment Parameter1); + @CType("const char*") String NativeType(@CType("const char*") String Parameter1); - MemorySegment NativeType(MemorySegment Parameter1); + @CType("const char*") + MemorySegment NativeType(@CType("const char*") MemorySegment Parameter1); MethodHandle ReturnMethodHandle(); @StrCharset("UTF-16") String StringCharset(@StrCharset("UTF-16") String Parameter1); - MemorySegment StringCharset(MemorySegment Parameter1); + @StrCharset("UTF-16") + MemorySegment StringCharset(@StrCharset("UTF-16") MemorySegment Parameter1); @Critical(allowHeapAccess = true) void CriticalFunction(); @@ -100,14 +110,16 @@ default void DefaultFunction2() { /// @param Parameter1 The first parameter /// @param Parameter2 The second parameter /// @param Parameter3 The third parameter - void DefaultParamFunction(int Parameter1, int Parameter2, int Parameter3); + /// @param Parameter4 The fourth parameter + void DefaultParamFunction(int Parameter1, int Parameter2, int Parameter3, @CType("void*") @CanonicalType("void*") MemorySegment Parameter4); /// Default parameters /// @param Parameter1 The first parameter /// @param Parameter3 The third parameter + /// @param Parameter4 The fourth parameter @Skip - default void DefaultParamFunction(int Parameter1, int Parameter3) { - this.DefaultParamFunction(Parameter1, 42, Parameter3); + default void DefaultParamFunction(int Parameter1, int Parameter3, @CType("void*") @CanonicalType("void*") MemorySegment Parameter4) { + this.DefaultParamFunction(Parameter1, 42, Parameter3, Parameter4); } String TestDefaultOverload(int Parameter1, String Parameter2); @@ -125,7 +137,7 @@ default String TestDefaultOverload(String Parameter2) { int[] TestIntArray(int[] Parameter1, @Ref int[] Parameter2); - MemorySegment TestIntArray(MemorySegment Parameter1, MemorySegment Parameter2); + MemorySegment TestIntArray(MemorySegment Parameter1, @Ref MemorySegment Parameter2); MemorySegment[] TestAddressArray(MemorySegment[] Parameter1); @@ -140,6 +152,6 @@ default String TestDefaultOverload(String Parameter2) { void TestAllocator3(Arena Parameter1); - void CanonicalLayouts(@CanonicalType("bool") boolean p0, @CanonicalType("char") byte p1, @CanonicalType("short") short p2, @CanonicalType("int") int p3, @CanonicalType("float") float p4, @CanonicalType("long") long p5, @CanonicalType("long long") long p6, @CanonicalType("double") double p7, @CanonicalType("size_t") long p8, @CanonicalType("wchar_t") int p9); + void CanonicalLayouts(@CType("bool") @CanonicalType("bool") boolean p0, @CType("char") @CanonicalType("char") byte p1, @CType("short") @CanonicalType("short") short p2, @CType("int") @CanonicalType("int") int p3, @CType("float") @CanonicalType("float") float p4, @CType("long") @CanonicalType("long") long p5, @CType("long long") @CanonicalType("long long") long p6, @CType("double") @CanonicalType("double") double p7, @CType("size_t") @CanonicalType("size_t") long p8, @CType("wchar_t") @CanonicalType("wchar_t") int p9); } diff --git a/generated/src/main/java/overrungl/gen/MyStruct.java b/generated/src/main/java/overrungl/gen/MyStruct.java new file mode 100644 index 0000000..93a544b --- /dev/null +++ b/generated/src/main/java/overrungl/gen/MyStruct.java @@ -0,0 +1,142 @@ +// This file is auto-generated. DO NOT EDIT! +package overrungl.gen; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandles; +import overrun.marshal.LayoutBuilder; +import overrun.marshal.Unmarshal; +import overrun.marshal.gen.CType; +import overrun.marshal.gen.CanonicalType; +import overrun.marshal.struct.Struct; +import overrun.marshal.struct.StructAllocator; + +/// ## Members +/// +/// ### Int +/// +/// [Getter](#Int()) - [Setter](#Int(int)) +/// +/// ### Address +/// +/// [Getter](#Address()) - [Setter](#Address(java.lang.foreign.MemorySegment)) +/// +/// ### VoidPointer +/// +/// [Getter](#VoidPointer()) - [Setter](#VoidPointer(java.lang.foreign.MemorySegment)) +/// +/// A `void*` member. +/// +/// ### CInt +/// +/// [Getter](#CInt()) - [Setter](#CInt(int)) +/// +/// ### String +/// +/// [Getter](#String()) - [Setter](#String(java.lang.foreign.MemorySegment)) +/// +/// ### ConstCharPointer +/// +/// [Getter](#ConstCharPointer()) - [Setter](#ConstCharPointer(java.lang.foreign.MemorySegment)) +/// +/// A `const char*` member. +/// +/// ### SizeT +/// +/// [Getter](#SizeT()) - [Setter](#SizeT(long)) +/// +/// ## Layout +/// +/// ``` +/// typedef struct mystruct { +/// int Int; +/// MemorySegment Address; +/// void* VoidPointer; +/// int CInt; +/// String String; +/// const char* ConstCharPointer; +/// size_t SizeT; +/// } MyStruct; +/// ``` +/// +public interface MyStruct extends Struct { + /// The struct allocator. + StructAllocator OF = new StructAllocator<>(MethodHandles.lookup(), LayoutBuilder.struct() + .add(ValueLayout.JAVA_INT, "Int") + .add(ValueLayout.ADDRESS, "Address") + .add(ValueLayout.ADDRESS, "VoidPointer") + .add(ValueLayout.JAVA_INT, "CInt") + .add(Unmarshal.STR_LAYOUT, "String") + .add(Unmarshal.STR_LAYOUT, "ConstCharPointer") + .add(Linker.nativeLinker().canonicalLayouts().get("size_t"), "SizeT") + .build()); + + @Override + MyStruct slice(long index, long count); + + @Override + MyStruct slice(long index); + + /// {@return `Int`} + int Int(); + + /// Sets `Int` with the given value. + /// @param Int the value + /// @return `this` + MyStruct Int(int Int); + + /// {@return `Address`} + MemorySegment Address(); + + /// Sets `Address` with the given value. + /// @param Address the value + /// @return `this` + MyStruct Address(MemorySegment Address); + + /// {@return `VoidPointer`} + @CType("void*") + MemorySegment VoidPointer(); + + /// Sets `VoidPointer` with the given value. + /// @param VoidPointer the value + /// @return `this` + MyStruct VoidPointer(@CType("void*") MemorySegment VoidPointer); + + /// {@return `CInt`} + @CType("int") + @CanonicalType("int") + int CInt(); + + /// Sets `CInt` with the given value. + /// @param CInt the value + /// @return `this` + MyStruct CInt(@CType("int") @CanonicalType("int") int CInt); + + /// {@return `String`} + MemorySegment String(); + + /// Sets `String` with the given value. + /// @param String the value + /// @return `this` + MyStruct String(MemorySegment String); + + /// {@return `ConstCharPointer`} + @CType("const char*") + MemorySegment ConstCharPointer(); + + /// Sets `ConstCharPointer` with the given value. + /// @param ConstCharPointer the value + /// @return `this` + MyStruct ConstCharPointer(@CType("const char*") MemorySegment ConstCharPointer); + + /// {@return `SizeT`} + @CType("size_t") + @CanonicalType("size_t") + long SizeT(); + + /// Sets `SizeT` with the given value. + /// @param SizeT the value + /// @return `this` + MyStruct SizeT(@CType("size_t") @CanonicalType("size_t") long SizeT); + +} diff --git a/generated/src/main/java/overrungl/gen/MyStruct2.java b/generated/src/main/java/overrungl/gen/MyStruct2.java new file mode 100644 index 0000000..ebc71af --- /dev/null +++ b/generated/src/main/java/overrungl/gen/MyStruct2.java @@ -0,0 +1,48 @@ +// This file is auto-generated. DO NOT EDIT! +package overrungl.gen; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandles; +import overrun.marshal.LayoutBuilder; +import overrun.marshal.gen.CType; +import overrun.marshal.struct.Struct; +import overrun.marshal.struct.StructAllocator; +import overrungl.gen.MyStruct; + +/// Javadoc +/// +/// ## Members +/// +/// ### MyStruct +/// +/// [Getter](#MyStruct()) - [Setter](#MyStruct(java.lang.foreign.MemorySegment)) +/// +/// ## Layout +/// +/// ``` +/// typedef struct { +/// mystruct MyStruct; +/// } MyStruct2; +/// ``` +/// +public interface MyStruct2 extends Struct { + /// The struct allocator. + StructAllocator OF = new StructAllocator<>(MethodHandles.lookup(), LayoutBuilder.struct() + .add(MyStruct.OF.layout(), "MyStruct") + .build()); + + @Override + MyStruct2 slice(long index, long count); + + @Override + MyStruct2 slice(long index); + + /// {@return `MyStruct`} + @CType("mystruct") + MemorySegment MyStruct(); + + /// Sets `MyStruct` with the given value. + /// @param MyStruct the value + /// @return `this` + MyStruct2 MyStruct(@CType("mystruct") MemorySegment MyStruct); + +} diff --git a/generated/src/main/java/overrungl/gen/MyStructRegistration.java b/generated/src/main/java/overrungl/gen/MyStructRegistration.java new file mode 100644 index 0000000..74270f2 --- /dev/null +++ b/generated/src/main/java/overrungl/gen/MyStructRegistration.java @@ -0,0 +1,10 @@ +// This file is auto-generated. DO NOT EDIT! +package overrungl.gen; +import static overrun.marshal.gen.processor.ProcessorTypes.registerStruct; +final class MyStructRegistration { + private MyStructRegistration() { } + static void registerAll() { + registerStruct(MyStruct.class, MyStruct.OF); + registerStruct(MyStruct2.class, MyStruct2.OF); + } +} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt index cc3d61c..838fc33 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt @@ -16,14 +16,14 @@ package io.github.overrun.marshalgen -typealias AnnotationKV = Pair List> +typealias AnnotationKV = Pair List> -class AnnotationSpec(val type: ClassRefSupplier, vararg val values: AnnotationKV) { - fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { +class AnnotationSpec(val type: ClassRef, vararg val values: AnnotationKV) { + fun appendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) { builder.append(" ".repeat(indent)) - builder.append("@${type.get(factory)}") + builder.append("@${type.simpleName(classRefs)}") values.map { (first, second1) -> - second1.invoke(factory).let { second -> + second1.invoke(classRefs).let { second -> first to if (second.size == 1) second.first() else second.joinToString(", ", prefix = "{", postfix = "}") @@ -39,10 +39,14 @@ class AnnotationSpec(val type: ClassRefSupplier, vararg val values: AnnotationKV } interface AnnotatedSpec { - fun at(classRef: ClassRefSupplier, vararg values: AnnotationKV) + fun at(classRef: ClassRef, vararg values: AnnotationKV) + + fun byValue() { + at(ByValue) + } fun convert(target: BoolConvert) { - at(Convert, "value" to { listOf("${ProcessorType_BoolConvert.get(it)}.${target.name}") }) + at(Convert, "value" to { listOf("${ProcessorType_BoolConvert.simpleName(it)}.${target.name}") }) } fun charset(name: String) { diff --git a/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt b/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt index c964a07..ddf2237 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt @@ -19,57 +19,83 @@ package io.github.overrun.marshalgen import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles -internal class ClassRefs { +class ClassRefs { val importedClasses = mutableListOf() + + fun appendString(builder: StringBuilder, selfName: String) { + importedClasses + .filterNot { + (it.startsWith("java.lang.") && it.lastIndexOf('.') == 9) || + it == selfName + } + .sorted() + .forEach { + builder.appendLine("import $it;") + } + } } -interface ClassRef : ClassRefSupplier { +interface ClassRef { val qualifiedName: String - val simpleName: String val cType: String? val canonicalType: String? val carrier: ClassRef? - - override fun get(factory: ClassRefFactory): ClassRef = this -} - -fun interface ClassRefSupplier { - fun get(factory: ClassRefFactory): ClassRef - - infix fun c(cType: String): ClassRefSupplier = - ClassRefSupplier { - get(it).let { ref -> - it.classRef( - ref.qualifiedName, - ref.carrier, - cType = cType, - canonicalType = ref.canonicalType - ) - } - } - - infix fun canonical(canonicalType: String): ClassRefSupplier = - ClassRefSupplier { - get(it).let { ref -> - it.classRef(ref.qualifiedName, ref.carrier, cType = ref.cType, canonicalType = canonicalType) - } - } - - fun array(nativeType: String, carrier: ClassRef?): ClassRefSupplier = - ClassRefSupplier { ArrayClassRef(get(it), nativeType, carrier) } - - fun array(): ClassRefSupplier = - ClassRefSupplier { ArrayClassRef(get(it), null, address.get(it)) } + val memoryLayout: ((ClassRefs) -> String)? + + fun simpleName(classRefs: ClassRefs): String + + infix fun c(cType: String?): ClassRef = + if (cType == this.cType) this + else classRef( + qualifiedName, + carrier, + cType, + canonicalType, + memoryLayout + ) + + infix fun canonical(canonicalType: String?): ClassRef = + if (canonicalType == this.canonicalType) this + else classRef( + qualifiedName, + carrier, + cType, + canonicalType, + memoryLayout + ) + + infix fun layout(memoryLayout: ((ClassRefs) -> String)?): ClassRef = + classRef( + qualifiedName, + carrier, + cType, + canonicalType, + memoryLayout + ) + + fun array(cType: String?, carrier: ClassRef?, memoryLayout: ((ClassRefs) -> String)?): ClassRef = + ArrayClassRef(this, cType, carrier, memoryLayout) + + fun array(): ClassRef = + array(null, address, address.memoryLayout) + + fun T(vararg classRefs: ClassRef): ClassRef = + GenericClassRef(this, classRefs.toList(), cType = cType, carrier = carrier, memoryLayout = memoryLayout) + + fun isValueClassRef(): Boolean = when (qualifiedName) { + "boolean", "char", "byte", "short", "int", "long", "float", "double", "java.lang.foreign.MemorySegment" -> true + else -> false + } } internal open class ObjectClassRef( - classRefs: ClassRefs, override val qualifiedName: String, override val cType: String?, override val canonicalType: String?, - override val carrier: ClassRef? + override val carrier: ClassRef?, + override val memoryLayout: ((ClassRefs) -> String)? ) : ClassRef { - override val simpleName by lazy { + override fun simpleName(classRefs: ClassRefs): String = if (classRefs.importedClasses.contains(qualifiedName)) { qualifiedName.substringAfterLast('.') } else if (classRefs.importedClasses.any { it.substringAfterLast('.') == qualifiedName.substringAfterLast('.') }) { @@ -78,7 +104,6 @@ internal open class ObjectClassRef( classRefs.importedClasses.add(qualifiedName) qualifiedName.substringAfterLast('.') } - } override fun equals(other: Any?): Boolean { if (this === other) return true @@ -95,18 +120,16 @@ internal open class ObjectClassRef( result = 31 * result + carrier.hashCode() return result } - - override fun toString(): String = simpleName } internal class PrimitiveClassRef( - name: String, + override val qualifiedName: String, override val cType: String?, - override val canonicalType: String? + override val canonicalType: String?, + override val memoryLayout: ((ClassRefs) -> String)? ) : ClassRef { - override val qualifiedName: String = name - override val simpleName: String = name override val carrier: ClassRef = this + override fun simpleName(classRefs: ClassRefs): String = qualifiedName override fun equals(other: Any?): Boolean { if (this === other) return true @@ -124,17 +147,20 @@ internal class PrimitiveClassRef( return result } - override fun toString(): String = simpleName + override fun toString(): String = qualifiedName } internal class ArrayClassRef( - componentType: ClassRef, + private val componentType: ClassRef, override val cType: String?, - override val carrier: ClassRef? + override val carrier: ClassRef?, + override val memoryLayout: ((ClassRefs) -> String)? ) : ClassRef { override val canonicalType: String? = null override val qualifiedName: String = "${componentType.qualifiedName}[]" - override val simpleName: String = "${componentType}[]" + override fun simpleName(classRefs: ClassRefs): String { + return "${componentType.simpleName(classRefs)}[]" + } override fun equals(other: Any?): Boolean { if (this === other) return true @@ -151,52 +177,93 @@ internal class ArrayClassRef( result = 31 * result + qualifiedName.hashCode() return result } - - override fun toString(): String = simpleName } -interface ClassRefFactory { - fun classRef(name: String, carrier: ClassRef?, cType: String? = null, canonicalType: String? = null): ClassRef +internal class GenericClassRef( + private val type: ClassRef, + private val typeArgument: List, + override val cType: String?, + override val carrier: ClassRef?, + override val memoryLayout: ((ClassRefs) -> String)? +) : ClassRef { + override val canonicalType: String? = null + override val qualifiedName: String = + "${type.qualifiedName}<${typeArgument.joinToString(", ") { it.qualifiedName }}>" + + override fun simpleName(classRefs: ClassRefs): String { + return "${type.simpleName(classRefs)}<${typeArgument.joinToString(", ") { it.simpleName(classRefs) }}>" + } } -fun classRefSupplier(name: String, carrier: ClassRefSupplier? = null): ClassRefSupplier = - ClassRefSupplier { it.classRef(name, carrier?.get(it)) } +fun findPrimitiveRef( + name: String, + cType: String?, + canonicalType: String?, + memoryLayout: ((ClassRefs) -> String)? +): ClassRef? = when (name) { + "void" -> void + "boolean" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "char" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "byte" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "short" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "int" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "long" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "float" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + "double" -> PrimitiveClassRef(name, cType, canonicalType, memoryLayout) + else -> null +} -inline fun classRefSupplier(carrier: ClassRefSupplier? = null): ClassRefSupplier = - ClassRefSupplier { it.classRef(T::class.java.name, carrier?.get(it)) } +fun classRef( + name: String, + carrier: ClassRef? = null, + cType: String? = null, + canonicalType: String? = null, + memoryLayout: ((ClassRefs) -> String)? = null +): ClassRef = + findPrimitiveRef(name, cType, canonicalType, memoryLayout) ?: ObjectClassRef( + name, + cType, + canonicalType, + carrier, + memoryLayout + ) + +inline fun classRef( + carrier: ClassRef? = null, + noinline memoryLayout: ((ClassRefs) -> String)? = carrier?.memoryLayout +): ClassRef = + classRef(T::class.java.name, carrier, memoryLayout = memoryLayout) // Java types -val void: ClassRef = PrimitiveClassRef("void", null, null) -val boolean: ClassRef = PrimitiveClassRef("boolean", null, null) -val char: ClassRef = PrimitiveClassRef("char", null, null) -val byte: ClassRef = PrimitiveClassRef("byte", null, null) -val short: ClassRef = PrimitiveClassRef("short", null, null) -val int: ClassRef = PrimitiveClassRef("int", null, null) -val long: ClassRef = PrimitiveClassRef("long", null, null) -val float: ClassRef = PrimitiveClassRef("float", null, null) -val double: ClassRef = PrimitiveClassRef("double", null, null) - -val address = ClassRefSupplier { - object : ObjectClassRef( - when (it) { - is DowncallSpec -> it.classRefs - else -> error("Cannot access ClassRefs") - }, - "java.lang.foreign.MemorySegment", - null, - null, - null - ) { - override val carrier: ClassRef = this - } +val void: ClassRef = PrimitiveClassRef("void", null, null, null) +val boolean: ClassRef = PrimitiveClassRef("boolean", null, null) { "${valueLayout.simpleName(it)}.JAVA_BOOLEAN" } +val char: ClassRef = PrimitiveClassRef("char", null, null) { "${valueLayout.simpleName(it)}.JAVA_CHAR" } +val byte: ClassRef = PrimitiveClassRef("byte", null, null) { "${valueLayout.simpleName(it)}.JAVA_BYTE" } +val short: ClassRef = PrimitiveClassRef("short", null, null) { "${valueLayout.simpleName(it)}.JAVA_SHORT" } +val int: ClassRef = PrimitiveClassRef("int", null, null) { "${valueLayout.simpleName(it)}.JAVA_INT" } +val long: ClassRef = PrimitiveClassRef("long", null, null) { "${valueLayout.simpleName(it)}.JAVA_LONG" } +val float: ClassRef = PrimitiveClassRef("float", null, null) { "${valueLayout.simpleName(it)}.JAVA_FLOAT" } +val double: ClassRef = PrimitiveClassRef("double", null, null) { "${valueLayout.simpleName(it)}.JAVA_DOUBLE" } + +val address: ClassRef = object : ObjectClassRef( + "java.lang.foreign.MemorySegment", + null, + null, + null, + memoryLayout = { "${valueLayout.simpleName(it)}.ADDRESS" } +) { + override val carrier: ClassRef = this } -val string = classRefSupplier(address) +val string = classRef(address) { "${Unmarshal.simpleName(it)}.STR_LAYOUT" } // TODO: JDK 22 -val arena = classRefSupplier("java.lang.foreign.Arena") -val allocator = classRefSupplier("java.lang.foreign.SegmentAllocator") -val handle = classRefSupplier() -val handles = classRefSupplier() +val arena = classRef("java.lang.foreign.Arena") +val allocator = classRef("java.lang.foreign.SegmentAllocator") +val handle = classRef() +val handles = classRef() +val linker = classRef("java.lang.foreign.Linker") +val override = classRef() +val valueLayout = classRef("java.lang.foreign.ValueLayout") val boolean_array = boolean.array() val char_array = char.array() @@ -210,38 +277,47 @@ val address_array = address.array() val string_array = string.array() // C types -val c_bool = boolean canonical "bool" -val c_char = byte canonical "char" -val c_short = short canonical "short" -val c_int = int canonical "int" -val c_float = float canonical "float" -val c_long = long canonical "long" -val c_long_long = long canonical "long long" -val c_double = double canonical "double" -val size_t = long canonical "size_t" -val wchar_t = int canonical "wchar_t" +val c_bool = boolean c "bool" canonical "bool" +val c_char = byte c "char" canonical "char" +val c_short = short c "short" canonical "short" +val c_int = int c "int" canonical "int" +val c_float = float c "float" canonical "float" +val c_long = canonicalType(long, "long") +val c_long_long = long c "long long" canonical "long long" +val c_double = double c "double" canonical "double" +val size_t = canonicalType(long, "size_t") +val wchar_t = canonicalType(int, "wchar_t") + +private fun canonicalType(from: ClassRef, type: String) = + from c type canonical type layout { """${linker.simpleName(it)}.nativeLinker().canonicalLayouts().get("$type")""" } val void_pointer = address c "void*" canonical "void*" val const_char_pointer = string c "const char*" // objects -val MemoryStack = classRefSupplier("io.github.overrun.memstack.MemoryStack") +val MemoryStack = classRef("io.github.overrun.memstack.MemoryStack") + +val Convert = classRef("overrun.marshal.gen.Convert") +val Critical = classRef("overrun.marshal.gen.Critical") +val CType = classRef("overrun.marshal.gen.CType") +val CanonicalType = classRef("overrun.marshal.gen.CanonicalType") +val Entrypoint = classRef("overrun.marshal.gen.Entrypoint") +val Ref = classRef("overrun.marshal.gen.Ref") +val Sized = classRef("overrun.marshal.gen.Sized") +val Skip = classRef("overrun.marshal.gen.Skip") +val StrCharset = classRef("overrun.marshal.gen.StrCharset") -val Convert = classRefSupplier("overrun.marshal.gen.Convert") -val Critical = classRefSupplier("overrun.marshal.gen.Critical") -val CType = classRefSupplier("overrun.marshal.gen.CType") -val CanonicalType = classRefSupplier("overrun.marshal.gen.CanonicalType") -val Entrypoint = classRefSupplier("overrun.marshal.gen.Entrypoint") -val Ref = classRefSupplier("overrun.marshal.gen.Ref") -val Sized = classRefSupplier("overrun.marshal.gen.Sized") -val Skip = classRefSupplier("overrun.marshal.gen.Skip") -val StrCharset = classRefSupplier("overrun.marshal.gen.StrCharset") +val ProcessorType_BoolConvert = classRef("overrun.marshal.gen.processor.ProcessorType.BoolConvert") -val ProcessorType_BoolConvert = classRefSupplier("overrun.marshal.gen.processor.ProcessorType.BoolConvert") +val DirectAccess = classRef("overrun.marshal.DirectAccess") +val Downcall = classRef("overrun.marshal.Downcall") +val LayoutBuilder = classRef("overrun.marshal.LayoutBuilder") +val Unmarshal = classRef("overrun.marshal.Unmarshal") -val DirectAccess = classRefSupplier("overrun.marshal.DirectAccess") -val Downcall = classRefSupplier("overrun.marshal.Downcall") +val ByValue = classRef("overrun.marshal.struct.ByValue") +val Struct = classRef("overrun.marshal.struct.Struct") +val StructAllocator = classRef("overrun.marshal.struct.StructAllocator") enum class BoolConvert { CHAR, diff --git a/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt index f6ddbe4..a54b059 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt @@ -24,48 +24,30 @@ import kotlin.io.path.createDirectories annotation class MarshalGen @MarshalGen -class DowncallSpec(private val qualifiedName: String, private var javadoc: JavadocSpec?) : ClassRefFactory { +class DowncallSpec(private val qualifiedName: String, private var javadoc: JavadocSpec?) { private val packageName = qualifiedName.substringBeforeLast('.') private val simpleName = qualifiedName.substringAfterLast('.') - internal val classRefs = ClassRefs() - private val superclasses = mutableListOf() + private val classRefs = ClassRefs() + private val superclasses = mutableListOf() private val specs = mutableListOf() init { classRefs.importedClasses.add(qualifiedName) } - private fun findPrimitiveRef(name: String, cType: String?, canonicalType: String?): ClassRef? { - return when (name) { - "void" -> void - "boolean" -> PrimitiveClassRef("boolean", cType, canonicalType) - "char" -> PrimitiveClassRef("char", cType, canonicalType) - "byte" -> PrimitiveClassRef("byte", cType, canonicalType) - "short" -> PrimitiveClassRef("short", cType, canonicalType) - "int" -> PrimitiveClassRef("int", cType, canonicalType) - "long" -> PrimitiveClassRef("long", cType, canonicalType) - "float" -> PrimitiveClassRef("float", cType, canonicalType) - "double" -> PrimitiveClassRef("double", cType, canonicalType) - else -> null - } - } - - override fun classRef(name: String, carrier: ClassRef?, cType: String?, canonicalType: String?): ClassRef = - findPrimitiveRef(name, cType, canonicalType) ?: ObjectClassRef(classRefs, name, cType, canonicalType, carrier) - - fun extends(vararg superclasses: ClassRefSupplier) { + fun extends(vararg superclasses: ClassRef) { superclasses.forEach { this.superclasses.add(it) } } - operator fun ClassRefSupplier.invoke(declaration: Pair, javadoc: JavadocSpec? = null) { + operator fun ClassRef.invoke(declaration: Pair, javadoc: JavadocSpec? = null) { specs.add(FieldSpec(this, declaration.first, declaration.second, javadoc)) } - operator fun ClassRefSupplier.invoke(javadoc: JavadocSpec? = null, action: FieldListSpec.() -> Unit) { + operator fun ClassRef.invoke(javadoc: JavadocSpec? = null, action: FieldListSpec.() -> Unit) { specs.add(FieldListSpec(this, mutableListOf(), javadoc).also(action)) } - operator fun ClassRefSupplier.invoke( + operator fun ClassRef.invoke( methodName: String, vararg parameters: ParameterSpec, javadoc: JavadocSpec? = null, @@ -74,29 +56,29 @@ class DowncallSpec(private val qualifiedName: String, private var javadoc: Javad specs.add(MethodSpec(this, methodName, parameters.toList(), javadoc).also { action?.invoke(it) }) } - operator fun ClassRefSupplier.times(name: String): ParameterSpec = + operator fun ClassRef.times(name: String): ParameterSpec = ParameterSpec(this, name) - fun ClassRefSupplier.get(): ClassRef = get(this@DowncallSpec) - operator fun Spec.unaryPlus() { this@DowncallSpec.specs.add(this) } private fun downcallLoadMethod(symbolLookup: String, downcallOptions: String?): String = - "${Downcall.get()}.load(${handles.get()}.lookup(), $symbolLookup${if (downcallOptions != null) ", $downcallOptions" else ""})" + "${Downcall.simpleName(classRefs)}.load(${handles.simpleName(classRefs)}.lookup(), $symbolLookup${if (downcallOptions != null) ", $downcallOptions" else ""})" fun instanceField(symbolLookup: String, downcallOptions: String? = null) { - classRef(qualifiedName, null)("INSTANCE" to downcallLoadMethod(symbolLookup, downcallOptions)) + classRef(qualifiedName)("INSTANCE" to downcallLoadMethod(symbolLookup, downcallOptions)) } fun instanceGetter(symbolLookup: String, downcallOptions: String? = null) { - classRef(qualifiedName, null).also { + classRef(qualifiedName).also { it("getInstance") { static( """ final class Holder { - static final $it INSTANCE = ${this@DowncallSpec.downcallLoadMethod(symbolLookup, downcallOptions)}; + static final ${it.simpleName(this@DowncallSpec.classRefs)} INSTANCE = ${ + this@DowncallSpec.downcallLoadMethod(symbolLookup, downcallOptions) + }; } return Holder.INSTANCE; """.trimIndent() @@ -120,23 +102,15 @@ class DowncallSpec(private val qualifiedName: String, private var javadoc: Javad append("public interface $simpleName") if (superclasses.isNotEmpty()) { append(" extends ") - append(superclasses.joinToString(separator = ", ") { it.get().simpleName }) + append(superclasses.joinToString(separator = ", ") { it.simpleName(classRefs) }) } appendLine(" {") - specs.forEach { it.appendString(4, this, this@DowncallSpec) } + specs.forEach { it.appendString(4, this, classRefs) } appendLine("}") } // imports - classRefs.importedClasses - .filterNot { - (it.startsWith("java.lang.") && it.lastIndexOf('.') == 9) || - it == qualifiedName - } - .sorted() - .forEach { - appendLine("import $it;") - } + classRefs.appendString(this, qualifiedName) appendLine() append(s) } diff --git a/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt index 01de5d9..281d405 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt @@ -17,34 +17,34 @@ package io.github.overrun.marshalgen data class FieldSpec( - val type: ClassRefSupplier, + val type: ClassRef, val name: String, val value: String, val javadoc: JavadocSpec? ) : Spec { - override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + override fun appendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) { val indentStr = " ".repeat(indent) builder.apply { javadoc?.also { appendLine(it.build(indent)) } - appendLine("$indentStr${type.get(factory)} $name = $value;") + appendLine("$indentStr${type.simpleName(classRefs)} $name = $value;") } } } class FieldListSpec( - private val type: ClassRefSupplier, + private val type: ClassRef, private val pairs: MutableList>, private val javadoc: JavadocSpec? ) : Spec { - override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + override fun appendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) { val indentStr = " ".repeat(indent) builder.apply { javadoc?.also { appendLine(it.build(indent)) } - append("$indentStr${type.get(factory)} ") + append("$indentStr${type.simpleName(classRefs)} ") appendLine( pairs.joinToString( ",\n$indentStr$indentStr", diff --git a/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt index 1412964..5595d49 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt @@ -22,6 +22,7 @@ typealias JavadocParam = Pair data class JavadocSpec( private val paragraphs: MutableList = mutableListOf(), private val params: MutableList = mutableListOf(), + private val sees: MutableList = mutableListOf(), private var returns: String? = null ) { operator fun String.unaryPlus() { @@ -32,6 +33,10 @@ data class JavadocSpec( params.add(this to string) } + fun see(string: String) { + sees.add(string) + } + fun returns(string: String) { returns = string } @@ -39,6 +44,7 @@ data class JavadocSpec( fun withParams(params: (List) -> List): JavadocSpec = copy( paragraphs = paragraphs.toMutableList(), params = params.invoke(this.params).toMutableList(), + sees = sees.toMutableList() ) fun build(indent: Int): String = buildString { @@ -56,6 +62,12 @@ data class JavadocSpec( appendLine() append("$indentStr/// @return $returns") } + if (sees.isNotEmpty()) { + appendLine() + append(sees.joinToString("\n") { + "@see $it" + }.prependIndent("$indentStr/// ")) + } } } diff --git a/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt index ec389a4..15eb4fa 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt @@ -16,24 +16,26 @@ package io.github.overrun.marshalgen -class MethodSpec( - private val returnType: ClassRefSupplier, +data class MethodSpec( + private val returnType: ClassRef, private val name: String, private val parameters: List, - private val javadocSpec: JavadocSpec? -) : AnnotatedSpec, Spec { - private val annotations = mutableListOf() - private var defaultCode: String? = null + private val javadocSpec: JavadocSpec?, + private val annotations: MutableList = mutableListOf(), + private var defaultCode: String? = null, private var static: Boolean = false +) : AnnotatedSpec, Spec { + var returnCarrierOverloads: Boolean = true + var returnShouldAnnotateCType: Boolean = true - private fun internalAppendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + private fun internalAppendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) { builder.apply { javadocSpec?.also { appendLine(it.build(indent)) } - returnType.get(factory).also { - if (it.cType != null) { + returnType.also { + if (returnShouldAnnotateCType && it.cType != null) { at(CType, "value" to { _ -> listOf(""""${it.cType}"""") }) } if (it.canonicalType != null) { @@ -42,9 +44,9 @@ class MethodSpec( } // annotations - annotations.sortedBy { it.type.get(factory).simpleName } + annotations.sortedBy { it.type.simpleName(classRefs) } .forEach { - it.appendString(indent, this, factory) + it.appendString(indent, this, classRefs) appendLine() } @@ -57,24 +59,17 @@ class MethodSpec( append("default ") } } - append("${returnType.get(factory)} ${name}(") + append("${returnType.simpleName(classRefs)} ${name}(") append(parameters.joinToString(", ") { param -> buildString { - param.type.get(factory).also { - if (it.cType != null) { - param.at(CType, "value" to { _ -> listOf(""""${it.cType}"""") }) - } - if (it.canonicalType != null) { - param.at(CanonicalType, "value" to { _ -> listOf(""""${it.canonicalType}"""") }) - } - - param.annotations.sortedBy { annotation -> annotation.type.get(factory).simpleName } + param.type.also { + param.annotations.sortedBy { annotation -> annotation.type.simpleName(classRefs) } .forEach { annotation -> - annotation.appendString(0, this, factory) + annotation.appendString(0, this, classRefs) append(" ") } - append("${it.get(factory)} ${param.name}") + append("${it.simpleName(classRefs)} ${param.name}") } } }) @@ -91,44 +86,57 @@ class MethodSpec( } } - override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { - internalAppendString(indent, builder, factory) + override fun appendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) { + parameters.forEach { param -> + param.type.also { + if (it.cType != null) { + param.at(CType, "value" to { _ -> listOf(""""${it.cType}"""") }) + } + if (it.canonicalType != null) { + param.at(CanonicalType, "value" to { _ -> listOf(""""${it.canonicalType}"""") }) + } + } + } + + internalAppendString(indent, builder, classRefs) - if (!static && !annotations.any { it.type.get(factory) == Skip }) { + if (!static && !annotations.any { it.type == Skip }) { // generate carrier overload - if (returnType.get(factory).let { it.carrier != null && it.carrier != it } || - parameters.any { it.type.get(factory).let { ref -> ref.carrier != null && ref.carrier != ref } }) { - MethodSpec( - returnType.get(factory).let { it.carrier ?: it }, - if (parameters.isEmpty()) "${name}_" else name, - parameters.map { ParameterSpec(it.type.get(factory).let { ref -> ref.carrier ?: ref }, it.name) }, - javadocSpec - ).internalAppendString(indent, builder, factory) + if ((returnCarrierOverloads && returnType.let { it.carrier != null && it.carrier != it }) || + parameters.any { it.type.let { ref -> ref.carrier != null && ref.carrier != ref } } + ) { + val pList = parameters.map { it.copy(type = it.type.carrier ?: it.type) } + copy( + returnType = if (returnCarrierOverloads) returnType.let { it.carrier ?: it } else returnType, + name = if (parameters == pList) "${name}_" else name, + parameters = pList, + javadocSpec = javadocSpec, + defaultCode = null + ).internalAppendString(indent, builder, classRefs) } // generate default parameter overload if (parameters.any { it.defaultValue != null }) { - MethodSpec( - returnType, - name, - parameters.filter { it.defaultValue == null }, - javadocSpec?.withParams { + copy( + parameters = parameters.filter { it.defaultValue == null }, + javadocSpec = javadocSpec?.withParams { it.filter { p -> parameters.filter { s -> s.defaultValue == null }.any { s -> s.name == p.first } } - } + }, + defaultCode = null ).also { it.skip( """ - ${if (returnType.get(factory) == void) "" else "return "}this.$name(${parameters.joinToString(", ") { param -> param.defaultValue ?: param.name }}); + ${if (returnType == void) "" else "return "}this.$name(${parameters.joinToString(", ") { param -> param.defaultValue ?: param.name }}); """.trimIndent() ) - }.internalAppendString(indent, builder, factory) + }.internalAppendString(indent, builder, classRefs) } } } - override fun at(classRef: ClassRefSupplier, vararg values: AnnotationKV) { + override fun at(classRef: ClassRef, vararg values: AnnotationKV) { annotations.add(AnnotationSpec(classRef, *values)) } @@ -140,6 +148,10 @@ class MethodSpec( at(Entrypoint, "value" to { listOf(""""$name"""") }) } + fun override() { + at(override) + } + fun skip() { at(Skip) } @@ -160,11 +172,14 @@ class MethodSpec( } @MarshalGen -class ParameterSpec(val type: ClassRefSupplier, val name: String) : AnnotatedSpec { - internal val annotations = mutableListOf() +data class ParameterSpec( + val type: ClassRef, + val name: String, + internal val annotations: MutableList = mutableListOf(), internal var defaultValue: String? = null +) : AnnotatedSpec { - override fun at(classRef: ClassRefSupplier, vararg values: AnnotationKV) { + override fun at(classRef: ClassRef, vararg values: AnnotationKV) { annotations.add(AnnotationSpec(classRef, *values)) } diff --git a/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt b/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt index 576e748..7f75216 100644 --- a/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt +++ b/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt @@ -18,11 +18,11 @@ package io.github.overrun.marshalgen @MarshalGen interface Spec { - fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) + fun appendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) } fun literal(string: String): Spec = object : Spec { - override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + override fun appendString(indent: Int, builder: StringBuilder, classRefs: ClassRefs) { builder.appendLine(string.prependIndent(" ".repeat(indent))) } } diff --git a/src/main/kotlin/io/github/overrun/marshalgen/StructSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/StructSpec.kt new file mode 100644 index 0000000..53402c5 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/StructSpec.kt @@ -0,0 +1,216 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package io.github.overrun.marshalgen + +import java.nio.file.Files +import kotlin.io.path.Path +import kotlin.io.path.createDirectories + +class StructSpec(private val qualifiedName: String, private val cType: String?, private var javadoc: JavadocSpec?) { + private val packageName = qualifiedName.substringBeforeLast('.') + private val simpleName = qualifiedName.substringAfterLast('.') + private val classRefs = ClassRefs() + private val superclasses = mutableListOf() + private val specs = mutableListOf() + internal val selfClassRef: ClassRef = + classRef(qualifiedName, address, cType, null).let { it layout { refs -> "${it.simpleName(refs)}.OF.layout()" } } + private val members = mutableListOf() + + init { + classRefs.importedClasses.add(qualifiedName) + StructRegistration.structs.add(selfClassRef) + } + + fun extends(vararg superclasses: ClassRef) { + superclasses.forEach { this.superclasses.add(it) } + } + + operator fun ClassRef.invoke(name: String, javadoc: JavadocSpec? = null) { + members.add(StructMemberSpec(this, name, javadoc)) + } + + operator fun Spec.unaryPlus() { + specs.add(this) + } + + fun generate() { + Files.writeString( + Path(packageName.replace('.', '/')) + .createDirectories() + .resolve("$simpleName.java"), + buildString { + appendLine(fileHeader) + appendLine("package $packageName;") + val s = buildString { + if (javadoc == null) { + javadoc = JavadocSpec() + } + // javadoc + javadoc?.apply { + +"## Members" + members.forEach { + +"### ${it.name}" + +"[Getter](#${it.name}()) - [Setter](#${it.name}(${(it.type.carrier ?: it.type).qualifiedName}))" + if (it.javadoc != null) { + +it.javadoc.build(0).trimMargin("/// ") + } + } + +"## Layout" + +buildString { + appendLine("```") + appendLine("typedef struct${if (cType != null) " $cType" else ""} {") + members.forEach { + appendLine(" ${it.type.cType ?: it.type.canonicalType ?: it.type.simpleName(classRefs)} ${it.name};") + } + appendLine("} $simpleName;") + appendLine("```") + } + } + extends(Struct.T(selfClassRef)) + + // allocator + specs.add(FieldSpec(StructAllocator.T(selfClassRef), "OF", buildString { + append("new ") + append(StructAllocator.T().simpleName(classRefs)) + append("(") + append(handles.simpleName(classRefs)) + append(".lookup(), ") + append(LayoutBuilder.simpleName(classRefs)) + appendLine(".struct()") + members.forEach { + appendLine( + """ .add(${ + it.type.let { type -> + type.memoryLayout?.invoke(classRefs) ?: throw IllegalStateException( + "No memory layout for ${type.qualifiedName} c ${type.cType} canonical ${type.canonicalType}" + ) + } + }, "${it.name}")""" + ) + } + append(" .build())") + }, javadoc { +"The struct allocator." })) + + specs.add(literal("")) + + // slice + specs.add(MethodSpec( + selfClassRef, + "slice", + listOf(ParameterSpec(long, "index"), ParameterSpec(long, "count")), + null + ).apply { + override() + returnCarrierOverloads = false + returnShouldAnnotateCType = false + }) + specs.add(MethodSpec( + selfClassRef, + "slice", + listOf(ParameterSpec(long, "index")), + null + ).apply { + override() + returnCarrierOverloads = false + returnShouldAnnotateCType = false + }) + + // accessors + members.forEach { + // getter + specs.add(MethodSpec( + it.type.carrier?.c(it.type.cType) ?: it.type, + it.name, + listOf(), + javadoc { + +"{@return `${it.name}`}" + } + )) + + // setter + specs.add(MethodSpec( + selfClassRef, + it.name, + listOf(ParameterSpec(it.type.carrier?.c(it.type.cType) ?: it.type, it.name)), + javadoc { + +"Sets `${it.name}` with the given value." + it.name param "the value" + returns("`this`") + } + ).apply { + returnCarrierOverloads = false + returnShouldAnnotateCType = false + }) + } + + javadoc?.also { + appendLine(it.build(0)) + } + append("public interface $simpleName") + if (superclasses.isNotEmpty()) { + append(" extends ") + append(superclasses.joinToString(separator = ", ") { it.simpleName(classRefs) }) + } + appendLine(" {") + specs.forEach { it.appendString(4, this, classRefs) } + appendLine("}") + } + + // imports + classRefs.appendString(this, qualifiedName) + appendLine() + append(s) + } + ) + } +} + +fun struct(name: String, cType: String? = null, javadoc: JavadocSpec? = null, action: StructSpec.() -> Unit): ClassRef { + val s = StructSpec(name, cType, javadoc).apply { + action() + generate() + } + return s.selfClassRef +} + +data class StructMemberSpec(val type: ClassRef, val name: String, val javadoc: JavadocSpec?) + +object StructRegistration { + internal val structs = mutableListOf() + + fun generate(qualifiedName: String) { + val classRefs = ClassRefs() + val packageName = qualifiedName.substringBeforeLast('.') + val javaName = qualifiedName.substringAfterLast('.') + Files.writeString(Path(packageName.replace('.', '/')) + .createDirectories() + .resolve("$javaName.java"), buildString { + appendLine(fileHeader) + appendLine("package $packageName;") + appendLine("import static overrun.marshal.gen.processor.ProcessorTypes.registerStruct;") + appendLine("final class $javaName {") + appendLine(" private $javaName() { }") + appendLine(" static void registerAll() {") + structs.forEach { + val simpleName = it.simpleName(classRefs) + appendLine(" registerStruct($simpleName.class, $simpleName.OF);") + } + appendLine(" }") + appendLine("}") + }) + } +}