diff --git a/LICENSE b/LICENSE index 0e259d4..0532fc2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,121 +1,21 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. +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. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 22c9291..473d198 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,3 @@ -# Example Project +# marshal-gen -This is an example project. - -You can change the options in gradle.properties and build.gradle.kts. - -You can use this template by clicking "Use this template" or download ZIP. +Generator for [Marshal](https://github.com/Over-Run/marshal). diff --git a/build.gradle.kts b/build.gradle.kts index 542c2dc..38dd3b7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { `java-library` signing `maven-publish` + kotlin("jvm") version "2.0.20" //application } @@ -33,7 +34,7 @@ val jdkEarlyAccessDoc: String? by rootProject val targetJavaVersion = jdkVersion.toInt() val projDevelopers = arrayOf( - Developer("example") + Developer("squid233") ) data class Organization( diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts new file mode 100644 index 0000000..25324c3 --- /dev/null +++ b/demo/build.gradle.kts @@ -0,0 +1,31 @@ +plugins { + kotlin("jvm") +} + +val jdkVersion: String by rootProject + +repositories { + mavenCentral() +} + +dependencies { + implementation(rootProject) +} + +tasks.register("runGenTest") { + classpath = sourceSets.main.get().runtimeClasspath + mainClass = "io.github.overrun.marshalgen.test.TestKt" + + val runDir = project(":generated").projectDir.resolve("src/main/java/") + workingDir = runDir + + javaLauncher = javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(jdkVersion)) + } + + doFirst { + if (!runDir.exists()) { + runDir.mkdirs() + } + } +} 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 new file mode 100644 index 0000000..d8a584d --- /dev/null +++ b/demo/src/main/kotlin/io/github/overrun/marshalgen/test/Test.kt @@ -0,0 +1,134 @@ +/* + * 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.test + +import io.github.overrun.marshalgen.* + +fun main() { + downcall("overrungl.gen.MyDowncall", javadoc = javadoc { + +"Paragraph 1" + +"Paragraph 2" + +"My downcall `code`" + }) { + extends(DirectAccess) + + int("INT" to "1", javadoc = javadoc { + +"An integer." + +"Paragraph 2" + }) + double("DOUBLE" to "2") + + int(javadoc = javadoc { + +"The javadoc." + }) { + "int1"("1") + "int2"("2") + } + + +literal( + """ + + // This is a comment. + + """.trimIndent() + ) + + instanceField(""""example.dll"""") + instanceGetter(""""example.dll"""") + + void("StaticMethod") { + static( + """ + System.out.println("Static method"); + """.trimIndent() + ) + } + + void("SkippedFunction", javadoc = javadoc { + +"Skipped" + }) { + skip() + } + address("ReturnAddress") + void("SetEntrypoint") { + entrypoint("_entrypoint") + } + int("WithParameter", int * "Parameter1", double * "Parameter2", javadoc = javadoc { + +"A method with parameters." + "Parameter1" param "The first parameter" + "Parameter2" param "The second parameter" + returns("The returned value") + }) + string("StringFunction", string * "Parameter1") + void("DefaultFunction1") { + skip( + """ + System.out.println("Hello world"); + """.trimIndent() + ) + } + void("DefaultFunction2") { + default( + """ + System.out.println("default operation"); + """.trimIndent() + ) + } + const_char_pointer("NativeType", const_char_pointer * "Parameter1") + handle("ReturnMethodHandle") + string("StringCharset", (string * "Parameter1") { charset("UTF-16") }) { + charset("UTF-16") + } + void("CriticalFunction") { + critical(true) + } + void_pointer("SizedFunction", (void_pointer * "Parameter1") { sized(1L) }) { + sized(1L) + } + void( + "DefaultParamFunction", + int * "Parameter1", + (int * "Parameter2") { default("42") }, + int * "Parameter3", + javadoc = javadoc { + +"Default parameters" + "Parameter1" param "The first parameter" + "Parameter2" param "The second parameter" + "Parameter3" param "The third parameter" + }) + string("TestDefaultOverload", (int * "Parameter1") { default("42") }, string * "Parameter2") + string("TestReturnOverload") + int_array("TestIntArray", int_array * "Parameter1", (int_array * "Parameter2") { ref() }) + address_array("TestAddressArray", address_array * "Parameter1") + boolean("TestConvert", (boolean * "Parameter1") { convert(BoolConvert.INT) }) { convert(BoolConvert.INT) } + void("TestAllocator1", MemoryStack * "Parameter1") + void("TestAllocator2", allocator * "Parameter1") + void("TestAllocator3", arena * "Parameter1") + void( + "CanonicalLayouts", c_bool * "p0", + c_char * "p1", + c_short * "p2", + c_int * "p3", + c_float * "p4", + c_long * "p5", + c_long_long * "p6", + c_double * "p7", + size_t * "p8", + wchar_t * "p9" + ) + } +} diff --git a/generated/build.gradle.kts b/generated/build.gradle.kts new file mode 100644 index 0000000..45d1fbe --- /dev/null +++ b/generated/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `java-library` +} + +val marshalVersion: String by rootProject + +repositories { + mavenCentral() + maven("https://s01.oss.sonatype.org/content/repositories/releases") +} + +dependencies { + implementation("io.github.over-run:marshal:$marshalVersion") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(23)) + } +} diff --git a/generated/src/main/java/overrungl/gen/MyDowncall.java b/generated/src/main/java/overrungl/gen/MyDowncall.java new file mode 100644 index 0000000..88de020 --- /dev/null +++ b/generated/src/main/java/overrungl/gen/MyDowncall.java @@ -0,0 +1,145 @@ +// This file is auto-generated. DO NOT EDIT! +package overrungl.gen; +import io.github.overrun.memstack.MemoryStack; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import overrun.marshal.DirectAccess; +import overrun.marshal.Downcall; +import overrun.marshal.gen.CType; +import overrun.marshal.gen.CanonicalType; +import overrun.marshal.gen.Convert; +import overrun.marshal.gen.Critical; +import overrun.marshal.gen.Entrypoint; +import overrun.marshal.gen.Ref; +import overrun.marshal.gen.Sized; +import overrun.marshal.gen.Skip; +import overrun.marshal.gen.StrCharset; +import overrun.marshal.gen.processor.ProcessorType.BoolConvert; + +/// Paragraph 1 +/// +/// Paragraph 2 +/// +/// My downcall `code` +public interface MyDowncall extends DirectAccess { + /// An integer. + /// + /// Paragraph 2 + int INT = 1; + double DOUBLE = 2; + /// The javadoc. + int int1 = 1, + int2 = 2; + + // This is a comment. + + MyDowncall INSTANCE = Downcall.load(MethodHandles.lookup(), "example.dll"); + static MyDowncall getInstance() { + final class Holder { + static final MyDowncall INSTANCE = Downcall.load(MethodHandles.lookup(), "example.dll"); + } + return Holder.INSTANCE; + } + + static void StaticMethod() { + System.out.println("Static method"); + } + + /// Skipped + @Skip + void SkippedFunction(); + + MemorySegment ReturnAddress(); + + @Entrypoint("_entrypoint") + void SetEntrypoint(); + + /// A method with parameters. + /// @param Parameter1 The first parameter + /// @param Parameter2 The second parameter + /// @return The returned value + int WithParameter(int Parameter1, double Parameter2); + + String StringFunction(String Parameter1); + + MemorySegment StringFunction(MemorySegment Parameter1); + + @Skip + default void DefaultFunction1() { + System.out.println("Hello world"); + } + + default void DefaultFunction2() { + System.out.println("default operation"); + } + + @CType("const char*") + String NativeType(@CType("const char*") String Parameter1); + + MemorySegment NativeType(MemorySegment Parameter1); + + MethodHandle ReturnMethodHandle(); + + @StrCharset("UTF-16") + String StringCharset(@StrCharset("UTF-16") String Parameter1); + + MemorySegment StringCharset(MemorySegment Parameter1); + + @Critical(allowHeapAccess = true) + void CriticalFunction(); + + @CType("void*") + @CanonicalType("void*") + @Sized(1L) + MemorySegment SizedFunction(@CType("void*") @CanonicalType("void*") @Sized(1L) MemorySegment Parameter1); + + /// Default parameters + /// @param Parameter1 The first parameter + /// @param Parameter2 The second parameter + /// @param Parameter3 The third parameter + void DefaultParamFunction(int Parameter1, int Parameter2, int Parameter3); + + /// Default parameters + /// @param Parameter1 The first parameter + /// @param Parameter3 The third parameter + @Skip + default void DefaultParamFunction(int Parameter1, int Parameter3) { + this.DefaultParamFunction(Parameter1, 42, Parameter3); + } + + String TestDefaultOverload(int Parameter1, String Parameter2); + + MemorySegment TestDefaultOverload(int Parameter1, MemorySegment Parameter2); + + @Skip + default String TestDefaultOverload(String Parameter2) { + return this.TestDefaultOverload(42, Parameter2); + } + + String TestReturnOverload(); + + MemorySegment TestReturnOverload_(); + + int[] TestIntArray(int[] Parameter1, @Ref int[] Parameter2); + + MemorySegment TestIntArray(MemorySegment Parameter1, MemorySegment Parameter2); + + MemorySegment[] TestAddressArray(MemorySegment[] Parameter1); + + MemorySegment TestAddressArray(MemorySegment Parameter1); + + @Convert(BoolConvert.INT) + boolean TestConvert(@Convert(BoolConvert.INT) boolean Parameter1); + + void TestAllocator1(MemoryStack Parameter1); + + void TestAllocator2(SegmentAllocator Parameter1); + + 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); + +} diff --git a/gradle.properties b/gradle.properties index 49cf0d1..a752856 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,29 +1,29 @@ # Gradle Options org.gradle.jvmargs=-Dfile.encoding=UTF-8 -hasPublication=false -publicationSigning=false +hasPublication=true +publicationSigning=true -hasJavadocJar=false -hasSourcesJar=false +hasJavadocJar=true +hasSourcesJar=true # Project Information -projGroupId=org.example -projArtifactId=example +projGroupId=io.github.over-run +projArtifactId=marshal-gen # The project name should only contain lowercase letters, numbers and hyphen. -projName=project-template +projName=marshal-gen projVersion=0.1.0-SNAPSHOT -projDesc=An example project. +projDesc=Marshal generator # Uncomment them if you want to publish to maven repository. -#projUrl=https://github.com/Over-Run/project-template -#projLicenseUrl=https://raw.githubusercontent.com/Over-Run/project-template/main/LICENSE -#projScmConnection=scm:git:https://github.com/Over-Run/project-template.git -#projScmUrl=https://github.com/Over-Run/project-template.git -projLicense=CC0-1.0 +projUrl=https://github.com/Over-Run/marshal-gen +projLicenseUrl=https://raw.githubusercontent.com/Over-Run/marshal-gen/main/LICENSE +projScmConnection=scm:git:https://github.com/Over-Run/marshal-gen.git +projScmUrl=https://github.com/Over-Run/marshal-gen.git +projLicense=MIT projLicenseFileName=LICENSE # Organization Information -orgName=Example +orgName=Overrun Organization orgUrl=https://example.org/ # JDK Options @@ -33,3 +33,5 @@ jdkEnablePreview=false # https://download.java.net/java/early_access/$jdkEarlyAccessDoc/docs/api/ # Uncomment it if you need to use EA build of JDK. #jdkEarlyAccessDoc=jdk22 + +marshalVersion=0.1.0-alpha.34-jdk23 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9355b41..0aaefbc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index e3e4378..69833ce 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,3 +9,5 @@ pluginManagement { val projName: String by settings rootProject.name = projName + +include("demo", "generated") diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java deleted file mode 100644 index c34d46a..0000000 --- a/src/main/java/org/example/Main.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.example; - -/** - * An example - * - * @author You - * @since 0.1.0 - */ -public class Main { - public static void main(String[] args) { - System.out.println("Hello world"); - } -} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt new file mode 100644 index 0000000..cc3d61c --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/AnnotationSpec.kt @@ -0,0 +1,55 @@ +/* + * 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 + +typealias AnnotationKV = Pair List> + +class AnnotationSpec(val type: ClassRefSupplier, vararg val values: AnnotationKV) { + fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + builder.append(" ".repeat(indent)) + builder.append("@${type.get(factory)}") + values.map { (first, second1) -> + second1.invoke(factory).let { second -> + first to + if (second.size == 1) second.first() + else second.joinToString(", ", prefix = "{", postfix = "}") + } + }.also { + if (it.size == 1 && it.first().first == "value") { + builder.append("(${it.first().second})") + } else if (it.isNotEmpty()) { + builder.append("(${it.joinToString(", ") { (first, second) -> "$first = $second" }})") + } + } + } +} + +interface AnnotatedSpec { + fun at(classRef: ClassRefSupplier, vararg values: AnnotationKV) + + fun convert(target: BoolConvert) { + at(Convert, "value" to { listOf("${ProcessorType_BoolConvert.get(it)}.${target.name}") }) + } + + fun charset(name: String) { + at(StrCharset, "value" to { listOf(""""$name"""") }) + } + + fun sized(size: Long) { + at(Sized, "value" to { listOf("${size}L") }) + } +} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt b/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt new file mode 100644 index 0000000..c964a07 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/ClassRef.kt @@ -0,0 +1,254 @@ +/* + * 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.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles + +internal class ClassRefs { + val importedClasses = mutableListOf() +} + +interface ClassRef : ClassRefSupplier { + 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)) } +} + +internal open class ObjectClassRef( + classRefs: ClassRefs, + override val qualifiedName: String, + override val cType: String?, + override val canonicalType: String?, + override val carrier: ClassRef? +) : ClassRef { + override val simpleName by lazy { + if (classRefs.importedClasses.contains(qualifiedName)) { + qualifiedName.substringAfterLast('.') + } else if (classRefs.importedClasses.any { it.substringAfterLast('.') == qualifiedName.substringAfterLast('.') }) { + qualifiedName + } else { + classRefs.importedClasses.add(qualifiedName) + qualifiedName.substringAfterLast('.') + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ObjectClassRef) return false + + if (qualifiedName != other.qualifiedName) return false + if (carrier != other.carrier) return false + + return true + } + + override fun hashCode(): Int { + var result = qualifiedName.hashCode() + result = 31 * result + carrier.hashCode() + return result + } + + override fun toString(): String = simpleName +} + +internal class PrimitiveClassRef( + name: String, + override val cType: String?, + override val canonicalType: String? +) : ClassRef { + override val qualifiedName: String = name + override val simpleName: String = name + override val carrier: ClassRef = this + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PrimitiveClassRef) return false + + if (qualifiedName != other.qualifiedName) return false + if (carrier != other.carrier) return false + + return true + } + + override fun hashCode(): Int { + var result = qualifiedName.hashCode() + result = 31 * result + carrier.hashCode() + return result + } + + override fun toString(): String = simpleName +} + +internal class ArrayClassRef( + componentType: ClassRef, + override val cType: String?, + override val carrier: ClassRef? +) : ClassRef { + override val canonicalType: String? = null + override val qualifiedName: String = "${componentType.qualifiedName}[]" + override val simpleName: String = "${componentType}[]" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ArrayClassRef) return false + + if (carrier != other.carrier) return false + if (qualifiedName != other.qualifiedName) return false + + return true + } + + override fun hashCode(): Int { + var result = carrier?.hashCode() ?: 0 + 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 +} + +fun classRefSupplier(name: String, carrier: ClassRefSupplier? = null): ClassRefSupplier = + ClassRefSupplier { it.classRef(name, carrier?.get(it)) } + +inline fun classRefSupplier(carrier: ClassRefSupplier? = null): ClassRefSupplier = + ClassRefSupplier { it.classRef(T::class.java.name, carrier?.get(it)) } + +// 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 string = classRefSupplier(address) + +// TODO: JDK 22 +val arena = classRefSupplier("java.lang.foreign.Arena") +val allocator = classRefSupplier("java.lang.foreign.SegmentAllocator") +val handle = classRefSupplier() +val handles = classRefSupplier() + +val boolean_array = boolean.array() +val char_array = char.array() +val byte_array = byte.array() +val short_array = short.array() +val int_array = int.array() +val long_array = long.array() +val float_array = float.array() +val double_array = double.array() +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 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 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 = classRefSupplier("overrun.marshal.gen.processor.ProcessorType.BoolConvert") + +val DirectAccess = classRefSupplier("overrun.marshal.DirectAccess") +val Downcall = classRefSupplier("overrun.marshal.Downcall") + +enum class BoolConvert { + CHAR, + BYTE, + SHORT, + INT, + LONG, + FLOAT, + DOUBLE, +} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/Constants.kt b/src/main/kotlin/io/github/overrun/marshalgen/Constants.kt new file mode 100644 index 0000000..c5f56fc --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/Constants.kt @@ -0,0 +1,20 @@ +/* + * 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 + +const val AUTO_GENERATED_NOTICE = "// This file is auto-generated. DO NOT EDIT!" +var fileHeader = AUTO_GENERATED_NOTICE diff --git a/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt new file mode 100644 index 0000000..f6ddbe4 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/DowncallSpec.kt @@ -0,0 +1,152 @@ +/* + * 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 + +@DslMarker +annotation class MarshalGen + +@MarshalGen +class DowncallSpec(private val qualifiedName: String, private var javadoc: JavadocSpec?) : ClassRefFactory { + private val packageName = qualifiedName.substringBeforeLast('.') + private val simpleName = qualifiedName.substringAfterLast('.') + internal 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) { + superclasses.forEach { this.superclasses.add(it) } + } + + operator fun ClassRefSupplier.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) { + specs.add(FieldListSpec(this, mutableListOf(), javadoc).also(action)) + } + + operator fun ClassRefSupplier.invoke( + methodName: String, + vararg parameters: ParameterSpec, + javadoc: JavadocSpec? = null, + action: (MethodSpec.() -> Unit)? = null + ) { + specs.add(MethodSpec(this, methodName, parameters.toList(), javadoc).also { action?.invoke(it) }) + } + + operator fun ClassRefSupplier.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 ""})" + + fun instanceField(symbolLookup: String, downcallOptions: String? = null) { + classRef(qualifiedName, null)("INSTANCE" to downcallLoadMethod(symbolLookup, downcallOptions)) + } + + fun instanceGetter(symbolLookup: String, downcallOptions: String? = null) { + classRef(qualifiedName, null).also { + it("getInstance") { + static( + """ + final class Holder { + static final $it INSTANCE = ${this@DowncallSpec.downcallLoadMethod(symbolLookup, downcallOptions)}; + } + return Holder.INSTANCE; + """.trimIndent() + ) + } + } + } + + fun generate() { + Files.writeString( + Path(packageName.replace('.', '/')) + .createDirectories() + .resolve("$simpleName.java"), + buildString { + appendLine(fileHeader) + appendLine("package $packageName;") + val s = buildString { + javadoc?.also { + appendLine(it.build(0)) + } + append("public interface $simpleName") + if (superclasses.isNotEmpty()) { + append(" extends ") + append(superclasses.joinToString(separator = ", ") { it.get().simpleName }) + } + appendLine(" {") + specs.forEach { it.appendString(4, this, this@DowncallSpec) } + appendLine("}") + } + + // imports + classRefs.importedClasses + .filterNot { + (it.startsWith("java.lang.") && it.lastIndexOf('.') == 9) || + it == qualifiedName + } + .sorted() + .forEach { + appendLine("import $it;") + } + appendLine() + append(s) + } + ) + } +} + +fun downcall(name: String, javadoc: JavadocSpec? = null, action: DowncallSpec.() -> Unit) { + DowncallSpec(name, javadoc).also { + it.action() + it.generate() + } +} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt new file mode 100644 index 0000000..01de5d9 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/FieldSpec.kt @@ -0,0 +1,59 @@ +/* + * 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 + +data class FieldSpec( + val type: ClassRefSupplier, + val name: String, + val value: String, + val javadoc: JavadocSpec? +) : Spec { + override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + val indentStr = " ".repeat(indent) + builder.apply { + javadoc?.also { + appendLine(it.build(indent)) + } + appendLine("$indentStr${type.get(factory)} $name = $value;") + } + } +} + +class FieldListSpec( + private val type: ClassRefSupplier, + private val pairs: MutableList>, + private val javadoc: JavadocSpec? +) : Spec { + override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + val indentStr = " ".repeat(indent) + builder.apply { + javadoc?.also { + appendLine(it.build(indent)) + } + append("$indentStr${type.get(factory)} ") + appendLine( + pairs.joinToString( + ",\n$indentStr$indentStr", + postfix = ";" + ) { (name, value) -> "$name = $value" }) + } + } + + operator fun String.invoke(value: String) { + pairs.add(this to value) + } +} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt new file mode 100644 index 0000000..1412964 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/JavadocSpec.kt @@ -0,0 +1,63 @@ +/* + * 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 + +typealias JavadocParam = Pair + +@MarshalGen +data class JavadocSpec( + private val paragraphs: MutableList = mutableListOf(), + private val params: MutableList = mutableListOf(), + private var returns: String? = null +) { + operator fun String.unaryPlus() { + paragraphs.add(this) + } + + infix fun String.param(string: String) { + params.add(this to string) + } + + fun returns(string: String) { + returns = string + } + + fun withParams(params: (List) -> List): JavadocSpec = copy( + paragraphs = paragraphs.toMutableList(), + params = params.invoke(this.params).toMutableList(), + ) + + fun build(indent: Int): String = buildString { + val indentStr = " ".repeat(indent) + append( + paragraphs.joinToString("\n\n").prependIndent("$indentStr/// ") + ) + if (params.isNotEmpty()) { + appendLine() + append(params.joinToString("\n") { (name, string) -> + "@param $name $string" + }.prependIndent("$indentStr/// ")) + } + returns?.also { + appendLine() + append("$indentStr/// @return $returns") + } + } +} + +fun javadoc(action: JavadocSpec.() -> Unit): JavadocSpec = + JavadocSpec().apply(action) diff --git a/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt b/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt new file mode 100644 index 0000000..ec389a4 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/MethodSpec.kt @@ -0,0 +1,181 @@ +/* + * 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 + +class MethodSpec( + private val returnType: ClassRefSupplier, + private val name: String, + private val parameters: List, + private val javadocSpec: JavadocSpec? +) : AnnotatedSpec, Spec { + private val annotations = mutableListOf() + private var defaultCode: String? = null + private var static: Boolean = false + + private fun internalAppendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + builder.apply { + javadocSpec?.also { + appendLine(it.build(indent)) + } + + returnType.get(factory).also { + if (it.cType != null) { + at(CType, "value" to { _ -> listOf(""""${it.cType}"""") }) + } + if (it.canonicalType != null) { + at(CanonicalType, "value" to { _ -> listOf(""""${it.canonicalType}"""") }) + } + } + + // annotations + annotations.sortedBy { it.type.get(factory).simpleName } + .forEach { + it.appendString(indent, this, factory) + appendLine() + } + + // signature + append(" ") + if (defaultCode != null) { + if (static) { + append("static ") + } else { + append("default ") + } + } + append("${returnType.get(factory)} ${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 } + .forEach { annotation -> + annotation.appendString(0, this, factory) + append(" ") + } + + append("${it.get(factory)} ${param.name}") + } + } + }) + append(")") + // body + if (defaultCode != null) { + appendLine(" {") + appendLine(defaultCode?.prependIndent(" ")) + appendLine(" }") + } else { + appendLine(";") + } + appendLine() + } + } + + override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + internalAppendString(indent, builder, factory) + + if (!static && !annotations.any { it.type.get(factory) == 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) + } + + // generate default parameter overload + if (parameters.any { it.defaultValue != null }) { + MethodSpec( + returnType, + name, + parameters.filter { it.defaultValue == null }, + javadocSpec?.withParams { + it.filter { p -> + parameters.filter { s -> s.defaultValue == null }.any { s -> s.name == p.first } + } + } + ).also { + it.skip( + """ + ${if (returnType.get(factory) == void) "" else "return "}this.$name(${parameters.joinToString(", ") { param -> param.defaultValue ?: param.name }}); + """.trimIndent() + ) + }.internalAppendString(indent, builder, factory) + } + } + } + + override fun at(classRef: ClassRefSupplier, vararg values: AnnotationKV) { + annotations.add(AnnotationSpec(classRef, *values)) + } + + fun critical(allowHeapAccess: Boolean) { + at(Critical, "allowHeapAccess" to { listOf(allowHeapAccess.toString()) }) + } + + fun entrypoint(name: String) { + at(Entrypoint, "value" to { listOf(""""$name"""") }) + } + + fun skip() { + at(Skip) + } + + fun default(string: String) { + defaultCode = string + } + + fun static(string: String) { + defaultCode = string + static = true + } + + fun skip(string: String) { + skip() + default(string) + } +} + +@MarshalGen +class ParameterSpec(val type: ClassRefSupplier, val name: String) : AnnotatedSpec { + internal val annotations = mutableListOf() + internal var defaultValue: String? = null + + override fun at(classRef: ClassRefSupplier, vararg values: AnnotationKV) { + annotations.add(AnnotationSpec(classRef, *values)) + } + + fun default(value: String) { + defaultValue = value + } + + fun ref() { + at(Ref) + } + + operator fun invoke(action: ParameterSpec.() -> Unit): ParameterSpec = + apply(action) +} diff --git a/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt b/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt new file mode 100644 index 0000000..576e748 --- /dev/null +++ b/src/main/kotlin/io/github/overrun/marshalgen/Spec.kt @@ -0,0 +1,28 @@ +/* + * 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 + +@MarshalGen +interface Spec { + fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) +} + +fun literal(string: String): Spec = object : Spec { + override fun appendString(indent: Int, builder: StringBuilder, factory: ClassRefFactory) { + builder.appendLine(string.prependIndent(" ".repeat(indent))) + } +}