From 53663253206379c5b1d0cac7196cc342524a9e93 Mon Sep 17 00:00:00 2001 From: Kyle Mayes Date: Thu, 28 Mar 2024 00:27:52 -0400 Subject: [PATCH] Satisfy ktlint --- .../kotlin/com/kylemayes/generator/Input.kt | 30 +- .../kotlin/com/kylemayes/generator/Main.kt | 32 +- .../kylemayes/generator/generate/Generate.kt | 43 +- .../generator/generate/file/Bitmasks.kt | 47 +- .../generator/generate/file/Builders.kt | 117 +++-- .../generator/generate/file/Chains.kt | 2 +- .../generator/generate/file/Commands.kt | 30 +- .../generator/generate/file/Constants.kt | 6 +- .../generator/generate/file/Enums.kt | 119 +++-- .../generator/generate/file/Extensions.kt | 44 +- .../generator/generate/file/Handles.kt | 2 +- .../generator/generate/file/Headers.kt | 27 +- .../generator/generate/file/Structs.kt | 15 +- .../generator/generate/file/Versions.kt | 69 +-- .../generator/generate/support/Alias.kt | 9 +- .../generator/generate/support/Command.kt | 27 +- .../generator/generate/support/Extension.kt | 7 +- .../generator/generate/support/Manual.kt | 3 +- .../generator/generate/support/Struct.kt | 79 +-- .../generator/generate/support/Type.kt | 12 +- .../generator/generate/support/Wrapper.kt | 122 +++-- .../kylemayes/generator/registry/Extend.kt | 7 +- .../kylemayes/generator/registry/Extract.kt | 482 ++++++++++-------- .../kylemayes/generator/registry/Filter.kt | 26 +- .../com/kylemayes/generator/registry/Index.kt | 11 +- .../com/kylemayes/generator/registry/Parse.kt | 9 +- .../kylemayes/generator/registry/Rename.kt | 73 +-- .../com/kylemayes/generator/support/Case.kt | 5 +- .../kylemayes/generator/support/Changelog.kt | 9 +- .../kylemayes/generator/support/Command.kt | 60 ++- .../com/kylemayes/generator/support/Logger.kt | 30 +- .../generator/support/PeekableIterator.kt | 3 + .../com/kylemayes/generator/support/Xml.kt | 31 +- .../generator/support/ChangelogTest.kt | 3 +- 34 files changed, 891 insertions(+), 700 deletions(-) diff --git a/generator/src/main/kotlin/com/kylemayes/generator/Input.kt b/generator/src/main/kotlin/com/kylemayes/generator/Input.kt index 3355cc47..c26e63ad 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/Input.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/Input.kt @@ -86,9 +86,12 @@ private fun getRepositoryInput( } /** Fetches the contents of a file for a generator input pulled from a GitHub repository. */ -private fun getFile(commit: GHCommit, path: RepositoryPath): String { +private fun getFile( + commit: GHCommit, + path: RepositoryPath, +): String { val content = commit.owner.getFileContent(path.path, commit.shA1) - return if (content.size <= 1024 * 1024 /* 1 MiB */) { + return if (content.size <= 1024 * 1024) { content.read().readAllBytes().decodeToString() } else { commit.owner.getBlob(content.sha).read().readAllBytes().decodeToString() @@ -96,7 +99,10 @@ private fun getFile(commit: GHCommit, path: RepositoryPath): String { } /** Fetches the contents of the files in a directory for a generator input pulled from a GitHub repository. */ -private fun getDirectory(commit: GHCommit, path: RepositoryPath): Map { +private fun getDirectory( + commit: GHCommit, + path: RepositoryPath, +): Map { return commit .owner .getDirectoryContent(path.path, commit.shA1) @@ -119,11 +125,13 @@ private fun getLocalCommitHashes(context: GeneratorContext): Map>) = - Files.writeString( - context.directory.resolve(".commits"), - inputs.joinToString("\n") { - val (name, branch, path) = it.path - "$name/$branch/$path => ${it.latest.commit.shA1}" - }, - ) +private fun setLocalCommitHashes( + context: GeneratorContext, + inputs: List>, +) = Files.writeString( + context.directory.resolve(".commits"), + inputs.joinToString("\n") { + val (name, branch, path) = it.path + "$name/$branch/$path => ${it.latest.commit.shA1}" + }, +) diff --git a/generator/src/main/kotlin/com/kylemayes/generator/Main.kt b/generator/src/main/kotlin/com/kylemayes/generator/Main.kt index 22796511..54bb72ea 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/Main.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/Main.kt @@ -31,9 +31,10 @@ import kotlin.system.exitProcess private val log = KotlinLogging.logger { /* */ } -fun main(args: Array) = Generator() - .subcommands(Check(), Index(), Update()) - .main(args) +fun main(args: Array) = + Generator() + .subcommands(Check(), Index(), Update()) + .main(args) data class GeneratorContext( val directory: Path, @@ -147,13 +148,13 @@ class Update : CliktCommand(help = "Updates generated Vulkan bindings") { // Parse - val xmlVersion = if (skipUpgrade) { inputs.registry.local } else { inputs.registry.latest } + val xmlVersion = if (skipUpgrade) inputs.registry.local else inputs.registry.latest val xml = log.time("Fetch Registry") { xmlVersion.lazy.value } val registry = log.time("Parse Registry") { parseRegistry(xml) } // Headers (video) - val videoVersion = if (skipUpgrade) { inputs.video.local } else { inputs.video.latest } + val videoVersion = if (skipUpgrade) inputs.video.local else inputs.video.latest val video = log.time("Fetch Video Headers") { videoVersion.lazy.value } // Generate @@ -187,10 +188,11 @@ class Update : CliktCommand(help = "Updates generated Vulkan bindings") { val markdown = context.directory.resolve("CHANGELOG.md") val changelog = parseMarkdown(Files.readString(markdown)) - val commits = inputs.list - .flatMap { it.getIntermediateCommits() } - .toSet() - .sortedBy { it.commitDate } + val commits = + inputs.list + .flatMap { it.getIntermediateCommits() } + .toSet() + .sortedBy { it.commitDate } for (commit in commits) { log.info { "Intermediate commit hash = ${commit.shA1}" } @@ -224,17 +226,17 @@ class Update : CliktCommand(help = "Updates generated Vulkan bindings") { val head = "vk-$hash" val base = "master" - val existing = repo.queryPullRequests() - .head(head) - .base(base) - .list() - .firstOrNull() + val existing = + repo.queryPullRequests() + .head(head) + .base(base) + .list() + .firstOrNull() if (existing != null) { log.info { "Pull request already exists (#${existing.number})!" } return } - log.info { "Creating branch, committing changes, and pushing branch..." } val git = Git(FileRepositoryBuilder.create(context.directory.resolve(".git").toFile())) git.checkout() diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/Generate.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/Generate.kt index 7c3adceb..9b4020b4 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/Generate.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/Generate.kt @@ -29,16 +29,20 @@ import java.nio.file.Path private val log = KotlinLogging.logger { /* */ } /** The additional `bindgen` options for the Vulkan video headers. */ -private val videoOptions = listOf( - "--allowlist-item", "StdVideo.*", - "--allowlist-item", "STD_VIDEO_.*", - "--no-prepend-enum-name", - "--default-enum-style", "newtype_global", - "--with-derive-custom-enum", ".*=Default", -) +private val videoOptions = + listOf( + "--allowlist-item", "StdVideo.*", + "--allowlist-item", "STD_VIDEO_.*", + "--no-prepend-enum-name", + "--default-enum-style", "newtype_global", + "--with-derive-custom-enum", ".*=Default", + ) /** Generates Rust files for a Vulkan API registry and Vulkan video headers. */ -fun generateRustFiles(registry: Registry, video: Map) = listOf( +fun generateRustFiles( + registry: Registry, + video: Map, +) = listOf( generateRustFile("vulkanalia-sys", "bitmasks.rs", registry.generateBitmasks()), generateRustFile("vulkanalia-sys", "commands.rs", registry.generateCommands()), generateRustFile("vulkanalia-sys", "constants.rs", registry.generateConstants()), @@ -81,15 +85,16 @@ data class File( fun matches(directory: Path): Boolean { val full = directory.resolve(path) - matches = if (Files.exists(full)) { - val contents = Files.readString(full) - val matches = contents == this.contents - if (!matches) log.info { "$path does not match file on disk." } - matches - } else { - log.info { "$path does not exist on disk." } - false - } + matches = + if (Files.exists(full)) { + val contents = Files.readString(full) + val matches = contents == this.contents + if (!matches) log.info { "$path does not match file on disk." } + matches + } else { + log.info { "$path does not exist on disk." } + false + } return matches!! } @@ -111,11 +116,11 @@ private fun generateRustFile( ): File { val path = Path.of(crate).resolve("src").resolve(name) log.info { "Generating $path..." } - return File(path, "$prefix\n$contents".replace(Regex("\\r\\n?"), "\n")) + return File(path, "$PREFIX\n$contents".replace(Regex("\\r\\n?"), "\n")) } /** The Rust file prefix. */ -private const val prefix = +private const val PREFIX = """ // SPDX-License-Identifier: Apache-2.0 diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Bitmasks.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Bitmasks.kt index 37d1282e..79593d62 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Bitmasks.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Bitmasks.kt @@ -26,15 +26,16 @@ ${generateAliases(bitmasks.keys)} /** Generates a Rust `bitflags!` struct for a Vulkan bitmask. */ private fun Registry.generateBitmask(bitmask: Bitmask): String { val long = bitmask.bitflags.any { it.value.bitLength() > 32 } - val repr = "Flags" + (if (long) { "64" } else { "" }) + val repr = "Flags" + (if (long) "64" else "") val values = bitmask.bitflags.associateBy { it.value } val flags = generateBitflags(values, bitmask.bitflags).joinToString("\n ") - val block = if (flags.isNotBlank()) { - "{\n $flags\n }" - } else { - "{ }" - } + val block = + if (flags.isNotBlank()) { + "{\n $flags\n }" + } else { + "{ }" + } return """ bitflags! { @@ -47,27 +48,33 @@ bitflags! { } /** Generates Rust `bitflags!` bitflags for a list of Vulkan bitflags. */ -private fun generateBitflags(values: Map, bitflags: List) = - if (bitflags.isNotEmpty()) { - bitflags - .sortedBy { it.value } - .map { "const ${it.name} = ${generateExpr(values, it.value)};" } - } else { - emptyList() - } +private fun generateBitflags( + values: Map, + bitflags: List, +) = if (bitflags.isNotEmpty()) { + bitflags + .sortedBy { it.value } + .map { "const ${it.name} = ${generateExpr(values, it.value)};" } +} else { + emptyList() +} /** Generates a Rust expression for a Vulkan bitflag value. */ -private fun generateExpr(values: Map, value: BigInteger): String { - val bits = (0..value.bitLength()) - .map { it to value.testBit(it) } - .filter { it.second } - .map { it.first } +private fun generateExpr( + values: Map, + value: BigInteger, +): String { + val bits = + (0..value.bitLength()) + .map { it to value.testBit(it) } + .filter { it.second } + .map { it.first } return if (bits.isEmpty()) { "0" } else if (bits.size == 1) { val bit = bits[0] - if (bit == 0) { "1" } else { "1 << $bit" } + if (bit == 0) "1" else "1 << $bit" } else if (bits.size == 31) { return "i32::MAX as u32" } else { diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Builders.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Builders.kt index a5834339..dfe14465 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Builders.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Builders.kt @@ -98,9 +98,9 @@ ${structs.values /** Generates a Rust struct to build a Vulkan struct. */ fun Registry.generateBuilder(struct: Structure): String { - val lifetime = if (getStructLifetime(struct)) { "<'b>" } else { "" } - val traitLifetime = if (lifetime.isNotEmpty()) { "<'b>" } else { "<'static>" } - val marker = if (lifetime.isNotEmpty()) { "_marker: PhantomData<&'b ()>," } else { "" } + val lifetime = if (getStructLifetime(struct)) "<'b>" else "" + val traitLifetime = if (lifetime.isNotEmpty()) "<'b>" else "<'static>" + val marker = if (lifetime.isNotEmpty()) "_marker: PhantomData<&'b ()>," else "" val methods = generateMethods(struct) return """ ${generateExtends(struct)} @@ -193,9 +193,10 @@ private fun Registry.generateMethods(struct: Structure): String { // Filter out the fields that do not require builder methods since they will // be set by other builder methods (i.e., array length fields for // non-optional array fields). - val requireBuilders = members.values.filter { - arraysByLength[it.name].let { a -> a?.optional ?: true } - } + val requireBuilders = + members.values.filter { + arraysByLength[it.name].let { a -> a?.optional ?: true } + } // Generate the builder methods. val methods = ArrayList() @@ -237,23 +238,27 @@ private fun Registry.generateMethods(struct: Structure): String { } /** Generates a Rust builder method for an array value. */ -private fun Registry.generateArrayMethod(member: Member, length: Pair? = null): String { +private fun Registry.generateArrayMethod( + member: Member, + length: Pair? = null, +): String { val pointer = member.type as PointerType val identifier = pointer.pointee.getIdentifier() - val (item, cast) = when { - structs.containsKey(identifier) -> "impl Cast" to ".cast()" - identifier?.value == "void" -> "u8" to ".cast()" - else -> pointer.pointee.generate() to "" - } + val (item, cast) = + when { + structs.containsKey(identifier) -> "impl Cast" to ".cast()" + identifier?.value == "void" -> "u8" to ".cast()" + else -> pointer.pointee.generate() to "" + } val type = "[$item]".generateRef(pointer.const, lifetime = "b") - val method = if (pointer.const) { "as_ptr" } else { "as_mut_ptr" } + val method = if (pointer.const) "as_ptr" else "as_mut_ptr" return """ #[inline] pub fn ${member.name}(mut self, ${member.name}: $type) -> Self { - ${if (length != null) { "self.value.${length.first} = ${member.name}.len() as ${length.second};" } else { "" }} + ${if (length != null) "self.value.${length.first} = ${member.name}.len() as ${length.second};" else ""} self.value.${member.name} = ${member.name}.$method()$cast; self } @@ -261,7 +266,10 @@ pub fn ${member.name}(mut self, ${member.name}: $type) -> Self { } /** Generates a Rust builder method for adjacent bitfield values. */ -private fun Registry.generateBitfieldMethods(bf24: Member, bf8: Member): String { +private fun Registry.generateBitfieldMethods( + bf24: Member, + bf8: Member, +): String { val combined = "${bf24.name}_and_${bf8.name}" return """ #[inline] @@ -313,47 +321,48 @@ pub fn ${member.name}(mut self, ${member.name}: $ref) -> Self { """ } - val (type, cast) = when { - // Boolean. - member.type.getIdentifier()?.value == "Bool32" -> Pair("bool") { m: String -> "$m as Bool32" } - // Array (byte). - member.type.isByteArray() -> Pair("impl Into<${member.type.generate()}>") { m: String -> "$m.into()" } - // Array (byte). - member.type.isStringArray() -> Pair("impl Into<${member.type.generate()}>") { m: String -> "$m.into()" } - // Struct. - structs.containsKey(member.type.getIdentifier()) -> - Pair("impl Cast") { m: String -> "$m.into()" } - // Pointer to struct. - structs.containsKey(member.type.getPointee()?.getIdentifier()) -> { - val pointer = member.type as PointerType - val type = "impl Cast".generateRef(pointer.const, lifetime = "b") - val cast = if (pointer.const) { ".as_ref()" } else { ".as_mut()" } - Pair(type) { m: String -> "$m$cast" } - } - // Pointer to string. - member.type.isStringPointer() -> Pair("&'b [u8]") { m: String -> "$m.as_ptr().cast()" } - // Pointer to pointer. - member.type is PointerType && member.type.pointee is PointerType -> { - if (structs.containsKey(member.type.pointee.pointee.getIdentifier())) { - // Pointer to pointer to struct. - val item = member.type.pointee.pointee.generate() - Pair("&'b [&'b impl Cast]") { m: String -> "$m.as_ptr().cast()" } - } else { - // Pointer to pointer to other type. - val item = member.type.pointee.pointee.generate() - Pair("&'b [&'b $item]") { m: String -> "$m.as_ptr().cast()" } + val (type, cast) = + when { + // Boolean. + member.type.getIdentifier()?.value == "Bool32" -> Pair("bool") { m: String -> "$m as Bool32" } + // Array (byte). + member.type.isByteArray() -> Pair("impl Into<${member.type.generate()}>") { m: String -> "$m.into()" } + // Array (byte). + member.type.isStringArray() -> Pair("impl Into<${member.type.generate()}>") { m: String -> "$m.into()" } + // Struct. + structs.containsKey(member.type.getIdentifier()) -> + Pair("impl Cast") { m: String -> "$m.into()" } + // Pointer to struct. + structs.containsKey(member.type.getPointee()?.getIdentifier()) -> { + val pointer = member.type as PointerType + val type = "impl Cast".generateRef(pointer.const, lifetime = "b") + val cast = if (pointer.const) ".as_ref()" else ".as_mut()" + Pair(type) { m: String -> "$m$cast" } } + // Pointer to string. + member.type.isStringPointer() -> Pair("&'b [u8]") { m: String -> "$m.as_ptr().cast()" } + // Pointer to pointer. + member.type is PointerType && member.type.pointee is PointerType -> { + if (structs.containsKey(member.type.pointee.pointee.getIdentifier())) { + // Pointer to pointer to struct. + val item = member.type.pointee.pointee.generate() + Pair("&'b [&'b impl Cast]") { m: String -> "$m.as_ptr().cast()" } + } else { + // Pointer to pointer to other type. + val item = member.type.pointee.pointee.generate() + Pair("&'b [&'b $item]") { m: String -> "$m.as_ptr().cast()" } + } + } + // Pointer to other type (non-opaque). + member.type is PointerType && !member.type.isOpaquePointer() -> { + val item = member.type.pointee.generate() + val type = item.generateRef(member.type.const, lifetime = "b") + val cast = item.generatePtr(member.type.const) + Pair(type) { m: String -> "$m as $cast" } + } + // Other. + else -> Pair(member.type.generate()) { m: String -> m } } - // Pointer to other type (non-opaque). - member.type is PointerType && !member.type.isOpaquePointer() -> { - val item = member.type.pointee.generate() - val type = item.generateRef(member.type.const, lifetime = "b") - val cast = item.generatePtr(member.type.const) - Pair(type) { m: String -> "$m as $cast" } - } - // Other. - else -> Pair(member.type.generate()) { m: String -> m } - } return """ #[inline] diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Chains.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Chains.kt index 180b691c..40c97af9 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Chains.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Chains.kt @@ -57,7 +57,7 @@ unsafe impl InputChainStruct for ${struct.name} { } } -${if (output) { generateOutputChainStruct(struct) } else { "" }} +${if (output) generateOutputChainStruct(struct) else ""} """ } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Commands.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Commands.kt index 95d3df33..d2f89b75 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Commands.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Commands.kt @@ -32,14 +32,15 @@ pub type PFN_${command.name.original} = $type; /** Generates Rust structs for Vulkan commands. */ fun Registry.generateCommandStructs(): String { - val structs = commands.values - .groupBy { getCommandType(it) } - .entries - .sortedBy { it.key.display } - .joinToString("") { - val supported = it.value.sortedBy { c -> c.name } - generateCommandStruct(it.key, supported) - } + val structs = + commands.values + .groupBy { getCommandType(it) } + .entries + .sortedBy { it.key.display } + .joinToString("") { + val supported = it.value.sortedBy { c -> c.name } + generateCommandStruct(it.key, supported) + } return """ use core::mem; use core::ffi::{c_char, c_int, c_void}; @@ -51,8 +52,10 @@ $structs } /** Generates a Rust struct for a group of Vulkan commands of the same type. */ -private fun Registry.generateCommandStruct(type: CommandType, commands: List) = - """ +private fun Registry.generateCommandStruct( + type: CommandType, + commands: List, +) = """ /// Loaded Vulkan ${type.display.lowercase()} commands. #[derive(Copy, Clone)] pub struct ${type.display}Commands { @@ -84,9 +87,12 @@ ${command.name}: { """ /** Generates a Rust function signature for a Vulkan command. */ -private fun generateSignature(command: Command, name: String = ""): String { +private fun generateSignature( + command: Command, + name: String = "", +): String { val params = command.params.joinToString { "_${it.name.value.removePrefix("_")}: ${it.type.generateForCommand()}" } val actual = command.result.generateForCommand() - val result = if (actual != "c_void") { " -> $actual" } else { "" } + val result = if (actual != "c_void") " -> $actual" else "" return "unsafe extern \"system\" fn $name($params)$result" } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Constants.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Constants.kt index dfca1841..6ce02205 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Constants.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Constants.kt @@ -6,9 +6,7 @@ import com.kylemayes.generator.registry.Constant import com.kylemayes.generator.registry.Registry /** Generates Rust constants for Vulkan constants. */ -fun Registry.generateConstants() = - constants.values.sortedBy { it.name }.joinToString("\n") { generateConstant(it) } +fun Registry.generateConstants() = constants.values.sortedBy { it.name }.joinToString("\n") { generateConstant(it) } /** Generates a Rust constant for a Vulkan constant. */ -private fun generateConstant(constant: Constant) = - "pub const ${constant.name}: ${constant.type.generate()} = ${constant.expr};" +private fun generateConstant(constant: Constant) = "pub const ${constant.name}: ${constant.type.generate()} = ${constant.expr};" diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Enums.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Enums.kt index bc29d9a4..11c7b907 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Enums.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Enums.kt @@ -40,13 +40,18 @@ ${generateResultEnum("ErrorCode", "Result codes that indicate errors.") { it.val """ /** Generates a struct for a success enum for a Vulkan command with non-`SUCCESS` success codes. */ -private fun Registry.generateResultEnum(name: String, documentation: String, predicate: (Variant) -> Boolean): String { +private fun Registry.generateResultEnum( + name: String, + documentation: String, + predicate: (Variant) -> Boolean, +): String { val result = enums["VkResult".intern()] ?: error("Missing Result enum.") - val variants = result.variants - .filter(predicate) - .map { it.copy(name = it.name.value.replace(Regex("^ERROR_"), "").intern()) } - .toMutableList() + val variants = + result.variants + .filter(predicate) + .map { it.copy(name = it.name.value.replace(Regex("^ERROR_"), "").intern()) } + .toMutableList() val enum = Enum(name = name.intern(), variants = variants) @@ -70,16 +75,20 @@ impl From<$name> for Result { } /** Generates a Rust struct for a Vulkan enum. */ -private fun Registry.generateEnum(enum: Enum, documentation: String? = null): String { +private fun Registry.generateEnum( + enum: Enum, + documentation: String? = null, +): String { val debug = generateFmtImpl(enum, "Debug", "self.0.fmt(f)") { "\"${it.name}\"" } - val (display, error) = if (enum.name.value == "Result" || enum.name.value == "ErrorCode") { - val default = "write!(f, \"unknown Vulkan result (code = {})\", self.0)" - val display = generateFmtImpl(enum, "Display", default) { "\"${results[it.value] ?: it.name.value}\"" } - display to "#[cfg(any(feature=\"std\", feature=\"no_std_error\"))] impl error::Error for ${enum.name} { }" - } else { - "" to "" - } + val (display, error) = + if (enum.name.value == "Result" || enum.name.value == "ErrorCode") { + val default = "write!(f, \"unknown Vulkan result (code = {})\", self.0)" + val display = generateFmtImpl(enum, "Display", default) { "\"${results[it.value] ?: it.name.value}\"" } + display to "#[cfg(any(feature=\"std\", feature=\"no_std_error\"))] impl error::Error for ${enum.name} { }" + } else { + "" to "" + } return """ /// ${documentation ?: "<${generateManualUrl(enum)}>"} @@ -110,8 +119,12 @@ $error } /** Generates a Rust `Debug` or `Display` trait implementation for an enum. */ -private fun generateFmtImpl(enum: Enum, trait: String, default: String, f: (Variant) -> String) = - """ +private fun generateFmtImpl( + enum: Enum, + trait: String, + default: String, + f: (Variant) -> String, +) = """ impl fmt::$trait for ${enum.name} { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { @@ -123,40 +136,42 @@ impl fmt::$trait for ${enum.name} { """ /** The descriptions for Vulkan result codes. */ -private val results = mapOf( - 0L to "Command successfully completed.", - 1L to "A fence or query has not yet completed.", - 2L to "A wait operation has not completed in the specified time.", - 3L to "An event is signaled.", - 4L to "An event is unsignaled.", - 5L to "A return array was too small for the result.", - -1L to "A host memory allocation has failed.", - -2L to "A device memory allocation has failed.", - -3L to "Initialization of an object could not be completed for implementation-specific reasons.", - -4L to "The logical or physical device has been lost. See Lost Device.", - -5L to "Mapping of a memory object has failed.", - -6L to "A requested layer is not present or could not be loaded.", - -7L to "A requested extension is not supported.", - -8L to "A requested feature is not supported.", - -9L to "The requested version of Vulkan is not supported by the driver or is otherwise incompatible for implementation-specific reasons.", - -10L to "Too many objects of the type have already been created.", - -11L to "A requested format is not supported on this device.", - -12L to "A pool allocation has failed due to fragmentation of the pool's memory. This must only be returned if no attempt to allocate host or device memory was made to accommodate the new allocation. This should be returned in preference to VK_ERROR_OUT_OF_POOL_MEMORY, but only if the implementation is certain that the pool allocation failure was due to fragmentation.", - -13L to "An unknown error has occurred; either the application has provided invalid input, or an implementation failure has occurred.", - 1000001003L to "A swapchain no longer matches the surface properties exactly, but can still be used to present to the surface successfully.", - 1000268000L to "A deferred operation is not complete but there is currently no work for this thread to do at the time of this call.", - 1000268001L to "A deferred operation is not complete but there is no work remaining to assign to additional threads.", - 1000268002L to "A deferred operation was requested and at least some of the work was deferred.", - 1000268003L to "A deferred operation was requested and no operations were deferred.", - 1000297000L to "A requested pipeline creation would have required compilation, but the application requested compilation to not be performed.", - -1000000000L to "A surface is no longer available.", - -1000000001L to "The requested window is already in use by Vulkan or another API in a manner which prevents it from being used again.", - -1000001004L to "A surface has changed in such a way that it is no longer compatible with the swapchain, and further presentation requests using the swapchain will fail. Applications must query the new surface properties and recreate their swapchain if they wish to continue presenting to the surface.", - -1000003001L to "The display used by a swapchain does not use the same presentable image layout, or is incompatible in a way that prevents sharing an image.", - -1000012000L to "One or more shaders failed to compile or link. More details are reported back to the application via VK_EXT_debug_report if enabled.", - -1000069000L to "A pool memory allocation has failed. This must only be returned if no attempt to allocate host or device memory was made to accommodate the new allocation. If the failure was definitely due to fragmentation of the pool, VK_ERROR_FRAGMENTED_POOL should be returned instead.", - -1000072003L to "An external handle is not a valid handle of the specified type.", - -1000161000L to "A descriptor pool creation has failed due to fragmentation.", - -1000255000L to "An operation on a swapchain created with VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT failed as it did not have exclusive full-screen access. This may occur due to implementation-dependent reasons, outside of the application's control.", - -1000257000L to "A buffer creation or memory allocation failed because the requested address is not available. A shader group handle assignment failed because the requested shader group handle information is no longer valid.", -) +@Suppress("ktlint:standard:max-line-length") +private val results = + mapOf( + 0L to "Command successfully completed.", + 1L to "A fence or query has not yet completed.", + 2L to "A wait operation has not completed in the specified time.", + 3L to "An event is signaled.", + 4L to "An event is unsignaled.", + 5L to "A return array was too small for the result.", + -1L to "A host memory allocation has failed.", + -2L to "A device memory allocation has failed.", + -3L to "Initialization of an object could not be completed for implementation-specific reasons.", + -4L to "The logical or physical device has been lost. See Lost Device.", + -5L to "Mapping of a memory object has failed.", + -6L to "A requested layer is not present or could not be loaded.", + -7L to "A requested extension is not supported.", + -8L to "A requested feature is not supported.", + -9L to "The requested version of Vulkan is not supported by the driver or is otherwise incompatible for implementation-specific reasons.", + -10L to "Too many objects of the type have already been created.", + -11L to "A requested format is not supported on this device.", + -12L to "A pool allocation has failed due to fragmentation of the pool's memory. This must only be returned if no attempt to allocate host or device memory was made to accommodate the new allocation. This should be returned in preference to VK_ERROR_OUT_OF_POOL_MEMORY, but only if the implementation is certain that the pool allocation failure was due to fragmentation.", + -13L to "An unknown error has occurred; either the application has provided invalid input, or an implementation failure has occurred.", + 1000001003L to "A swapchain no longer matches the surface properties exactly, but can still be used to present to the surface successfully.", + 1000268000L to "A deferred operation is not complete but there is currently no work for this thread to do at the time of this call.", + 1000268001L to "A deferred operation is not complete but there is no work remaining to assign to additional threads.", + 1000268002L to "A deferred operation was requested and at least some of the work was deferred.", + 1000268003L to "A deferred operation was requested and no operations were deferred.", + 1000297000L to "A requested pipeline creation would have required compilation, but the application requested compilation to not be performed.", + -1000000000L to "A surface is no longer available.", + -1000000001L to "The requested window is already in use by Vulkan or another API in a manner which prevents it from being used again.", + -1000001004L to "A surface has changed in such a way that it is no longer compatible with the swapchain, and further presentation requests using the swapchain will fail. Applications must query the new surface properties and recreate their swapchain if they wish to continue presenting to the surface.", + -1000003001L to "The display used by a swapchain does not use the same presentable image layout, or is incompatible in a way that prevents sharing an image.", + -1000012000L to "One or more shaders failed to compile or link. More details are reported back to the application via VK_EXT_debug_report if enabled.", + -1000069000L to "A pool memory allocation has failed. This must only be returned if no attempt to allocate host or device memory was made to accommodate the new allocation. If the failure was definitely due to fragmentation of the pool, VK_ERROR_FRAGMENTED_POOL should be returned instead.", + -1000072003L to "An external handle is not a valid handle of the specified type.", + -1000161000L to "A descriptor pool creation has failed due to fragmentation.", + -1000255000L to "An operation on a swapchain created with VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT failed as it did not have exclusive full-screen access. This may occur due to implementation-dependent reasons, outside of the application's control.", + -1000257000L to "A buffer creation or memory allocation failed because the requested address is not available. A shader group handle assignment failed because the requested shader group handle information is no longer valid.", + ) diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Extensions.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Extensions.kt index 89a1f28b..2cf4596d 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Extensions.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Extensions.kt @@ -71,17 +71,19 @@ ${getExtensionGroups().values /** Generates a Rust constant for a Vulkan extension. */ private fun Registry.generateExtension(extension: Extension): String { - val provisional = if (extension.provisional) { PROVISIONAL } else { "" } + val provisional = if (extension.provisional) PROVISIONAL else "" val deprecation = generateDeprecation(extension)?.let { "\n$it" } ?: "" - val extensions = if (extension.requires != null) { - val names = extension.requires.split(",") - .map { extensions[it.intern()]!!.name } - .joinToString { "${it}_EXTENSION.name" } - "Some(&[$names])" - } else { - "None" - } + val extensions = + if (extension.requires != null) { + val names = + extension.requires.split(",") + .map { extensions[it.intern()]!!.name } + .joinToString { "${it}_EXTENSION.name" } + "Some(&[$names])" + } else { + "None" + } return """ /// <${generateManualUrl(extension)}>$provisional$deprecation @@ -120,7 +122,7 @@ ${getExtensionGroups().values /** Generates a Rust trait and implementation for a Vulkan extension. */ private fun Registry.generateExtensionTrait(extension: Extension): String { - val provisional = if (extension.provisional) { PROVISIONAL } else { "" } + val provisional = if (extension.provisional) PROVISIONAL else "" val deprecation = generateDeprecation(extension)?.let { "\n$it" } ?: "" val name = "${extension.name.value.toPascalCase()}Extension" @@ -128,10 +130,11 @@ private fun Registry.generateExtensionTrait(extension: Extension): String { val commands = extension.require.commands.mapNotNull { commands[it] }.sortedBy { it.name } - val implAttributes = listOf( - if (extension.provisional) { "#[cfg(feature = \"provisional\")]" } else { "" }, - if (deprecation.isNotEmpty()) { "#[allow(deprecated)]" } else { "" }, - ).filter { it.isNotBlank() }.joinToString("\n") + val implAttributes = + listOf( + if (extension.provisional) "#[cfg(feature = \"provisional\")]" else "", + if (deprecation.isNotEmpty()) "#[allow(deprecated)]" else "", + ).filter { it.isNotBlank() }.joinToString("\n") return """ /// <${generateManualUrl(extension)}>$provisional$deprecation @@ -149,10 +152,11 @@ impl $name for crate::$type { } } /** Generates a Rust deprecation annotation for a Vulkan extension. */ -private fun generateDeprecation(extension: Extension) = extension.deprecatedby?.let { - if (it.isNotBlank()) { - """#[deprecated(note = "deprecated in favor of `$it`")]""" - } else { - "#[deprecated]" +private fun generateDeprecation(extension: Extension) = + extension.deprecatedby?.let { + if (it.isNotBlank()) { + """#[deprecated(note = "deprecated in favor of `$it`")]""" + } else { + "#[deprecated]" + } } -} diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Handles.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Handles.kt index d7e5886c..8a12711f 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Handles.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Handles.kt @@ -46,7 +46,7 @@ ${generateAliases(handles.keys)} /** Generates a Rust struct for a Vulkan handle. */ private fun Registry.generateHandle(handle: Handle): String { - val repr = if (handle.dispatchable) { "usize" } else { "u64" } + val repr = if (handle.dispatchable) "usize" else "u64" val type = handle.name.value.toSnakeCase().uppercase() return """ /// <${generateManualUrl(handle)}> diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Headers.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Headers.kt index 9b1cd6d0..0be3f865 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Headers.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Headers.kt @@ -14,7 +14,11 @@ private val log = KotlinLogging.logger { /* */ } /** Generates a Rust module using `bindgen` for a collection of C/C++ headers. */ @OptIn(ExperimentalPathApi::class) -fun generateHeaders(name: String, headers: Map, options: List = emptyList()): String { +fun generateHeaders( + name: String, + headers: Map, + options: List = emptyList(), +): String { if (headers.isEmpty()) { return "" } @@ -25,16 +29,17 @@ fun generateHeaders(name: String, headers: Map, options: List "StructureType::${member.values}" - member.type.isPlatformPointer() -> "ptr::null_mut()" - else -> member.type.generateDefault() -} +private fun Registry.generateDefaultField(member: Member) = + when { + member.name.value == "s_type" && member.values != null -> "StructureType::${member.values}" + member.type.isPlatformPointer() -> "ptr::null_mut()" + else -> member.type.generateDefault() + } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Versions.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Versions.kt index 4e8283c1..1d074b49 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Versions.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/file/Versions.kt @@ -24,36 +24,40 @@ use super::*; var previousSuffix: String? = null for (version in this.versions.values) { val suffix = version.number.toString().replace('.', '_') - val commands = commands.values - .filter { version.require.commands.contains(it.name) } - .groupBy { getCommandType(it) } + val commands = + commands.values + .filter { version.require.commands.contains(it.name) } + .groupBy { getCommandType(it) } - versions += generateVersionTrait( - version, - commands[CommandType.ENTRY]?.sortedBy { it.name } ?: emptyList(), - CommandType.ENTRY, - "EntryV$suffix", - previousSuffix?.let { "EntryV$previousSuffix" }, - false, - ) + versions += + generateVersionTrait( + version, + commands[CommandType.ENTRY]?.sortedBy { it.name } ?: emptyList(), + CommandType.ENTRY, + "EntryV$suffix", + previousSuffix?.let { "EntryV$previousSuffix" }, + false, + ) - versions += generateVersionTrait( - version, - commands[CommandType.INSTANCE]?.sortedBy { it.name } ?: emptyList(), - CommandType.INSTANCE, - "InstanceV$suffix", - previousSuffix?.let { "InstanceV$previousSuffix" }, - true, - ) + versions += + generateVersionTrait( + version, + commands[CommandType.INSTANCE]?.sortedBy { it.name } ?: emptyList(), + CommandType.INSTANCE, + "InstanceV$suffix", + previousSuffix?.let { "InstanceV$previousSuffix" }, + true, + ) - versions += generateVersionTrait( - version, - commands[CommandType.DEVICE]?.sortedBy { it.name } ?: emptyList(), - CommandType.DEVICE, - "DeviceV$suffix", - previousSuffix?.let { "DeviceV$previousSuffix" }, - true, - ) + versions += + generateVersionTrait( + version, + commands[CommandType.DEVICE]?.sortedBy { it.name } ?: emptyList(), + CommandType.DEVICE, + "DeviceV$suffix", + previousSuffix?.let { "DeviceV$previousSuffix" }, + true, + ) previousSuffix = suffix } @@ -69,17 +73,16 @@ private fun Registry.generateVersionTrait( name: String, extends: String?, handle: Boolean, -) = - """ +) = """ /// Vulkan ${version.number} ${type.display.lowercase()} command wrappers. pub trait $name${extends?.let { ": $it" } ?: ""} { - ${if (extends == null) { "fn commands(&self) -> &${type.display}Commands;\n" } else { "" }} - ${if (handle && extends == null) { "fn handle(&self) -> ${type.display};\n" } else { "" }} + ${if (extends == null) "fn commands(&self) -> &${type.display}Commands;\n" else ""} + ${if (handle && extends == null) "fn handle(&self) -> ${type.display};\n" else ""} ${commands.joinToString("") { generateCommandWrapper(it) }} } impl $name for crate::${type.display} { - ${if (extends == null) { "#[inline] fn commands(&self) -> &${type.display}Commands { &self.commands }\n" } else { "" }} - ${if (handle && extends == null) { "#[inline] fn handle(&self) -> ${type.display} { self.handle }\n" } else { "" }} + ${if (extends == null) "#[inline] fn commands(&self) -> &${type.display}Commands { &self.commands }\n" else ""} + ${if (handle && extends == null) "#[inline] fn handle(&self) -> ${type.display} { self.handle }\n" else ""} } """ diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Alias.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Alias.kt index f6af3966..bd32ad98 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Alias.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Alias.kt @@ -7,10 +7,11 @@ import com.kylemayes.generator.registry.Registry import com.kylemayes.generator.registry.Typedef /** Generates Rust type aliases for Vulkan aliases. */ -fun Registry.generateAliases(filter: Set) = aliases.values - .filter { filter.contains(it.type.identifier) } - .sortedBy { it.name } - .joinToString("\n") { generateAlias(it) } +fun Registry.generateAliases(filter: Set) = + aliases.values + .filter { filter.contains(it.type.identifier) } + .sortedBy { it.name } + .joinToString("\n") { generateAlias(it) } /** Generates a Rust type alias for a Vulkan alias. */ private fun Registry.generateAlias(alias: Typedef) = diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Command.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Command.kt index e14a1617..57b8cf21 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Command.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Command.kt @@ -8,7 +8,8 @@ import com.kylemayes.generator.registry.getIdentifier // The commands whose types cannot be determined by their extension membership or first parameter. private val STATIC = setOf("vkGetInstanceProcAddr", "vkGetDeviceProcAddr") -private val ENTRY = setOf("vkCreateInstance", "vkEnumerateInstanceExtensionProperties", "vkEnumerateInstanceLayerProperties", "vkEnumerateInstanceVersion") +private val ENTRY = + setOf("vkCreateInstance", "vkEnumerateInstanceExtensionProperties", "vkEnumerateInstanceLayerProperties", "vkEnumerateInstanceVersion") private val INSTANCE = setOf("vkCreateDevice") /** A type of command which indicates how it is loaded. */ @@ -47,16 +48,18 @@ fun Registry.getCommandType(command: Command): CommandType { } /** Gets the command types for the Vulkan extension commands. */ -private val getExtensionCommandTypes = thunk { -> - extensions.values - .filter { e -> e.type != null } - .flatMap { e -> e.require.commands.map { c -> Pair(e, c) } } - .associate { (e, c) -> c to CommandType.valueOf(e.type!!.uppercase()) } -} +private val getExtensionCommandTypes = + thunk { -> + extensions.values + .filter { e -> e.type != null } + .flatMap { e -> e.require.commands.map { c -> Pair(e, c) } } + .associate { (e, c) -> c to CommandType.valueOf(e.type!!.uppercase()) } + } /** Gets the non-`SUCCESS` and non-`INCOMPLETE` success codes for a Vulkan command. */ -val getCommandSuccessCodes = thunk { command: Command -> - command.successcodes - .filter { it.value != "SUCCESS" && it.value != "INCOMPLETE" } - .toSet() -} +val getCommandSuccessCodes = + thunk { command: Command -> + command.successcodes + .filter { it.value != "SUCCESS" && it.value != "INCOMPLETE" } + .toSet() + } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Extension.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Extension.kt index 04c4f63c..4ccc213a 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Extension.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Extension.kt @@ -3,6 +3,7 @@ package com.kylemayes.generator.generate.support /** Gets the extensions grouped by author. */ -val getExtensionGroups = thunk { -> - extensions.values.groupBy { it.name.value.split('_')[0] } -} +val getExtensionGroups = + thunk { -> + extensions.values.groupBy { it.name.value.split('_')[0] } + } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Manual.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Manual.kt index 992bb71a..3791a3e2 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Manual.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Manual.kt @@ -11,8 +11,7 @@ val manualUrls = mutableListOf>() private val getLatestVersionNumber = thunk { -> versions.values.maxOfOrNull { it.number }!! } /** Generates a URL for a Vulkan manual page for a Vulkan entity. */ -fun Registry.generateManualUrl(entity: Entity) = - generateManualUrl(entity.name.original) +fun Registry.generateManualUrl(entity: Entity) = generateManualUrl(entity.name.original) /** Generates a URL for a Vulkan manual page for a Vulkan entity. */ fun Registry.generateManualUrl(name: String): String { diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Struct.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Struct.kt index be612cb8..6e8404e5 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Struct.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Struct.kt @@ -14,12 +14,13 @@ import com.kylemayes.generator.registry.isPointer import kotlin.math.max /** Gets the non-pointer dependencies of a Vulkan struct on other Vulkan structs. */ -val getStructDependencies = thunk { struct: Structure -> - struct.members - .mapNotNull { m -> m.type.getBaseIdentifier() } - .filter { d -> structs.containsKey(d) } - .toSet() -} +val getStructDependencies = + thunk { struct: Structure -> + struct.members + .mapNotNull { m -> m.type.getBaseIdentifier() } + .filter { d -> structs.containsKey(d) } + .toSet() + } private val getStructDerivesResults = HashMap>() @@ -50,7 +51,10 @@ fun Registry.getStructDerives(struct: Structure): Set { val optional = HashSet() val partialEq = !functions && !unions if (partialEq) optional.add("PartialEq") - if (partialEq && !floats) { optional.add("Eq"); optional.add("Hash") } + if (partialEq && !floats) { + optional.add("Eq") + optional.add("Hash") + } for (dependency in getStructDependencies(struct)) { val derives = getStructDerives(structs[dependency] ?: error("Missing struct.")) @@ -63,9 +67,10 @@ fun Registry.getStructDerives(struct: Structure): Set { } /** Gets the length of the longest array in a Vulkan struct. */ -private fun Registry.getMaxArrayLength(struct: Structure) = struct.members - .mapNotNull { getMaxArrayLength(it.type) } - .maxOrNull() +private fun Registry.getMaxArrayLength(struct: Structure) = + struct.members + .mapNotNull { getMaxArrayLength(it.type) } + .maxOrNull() /** Gets the length of the longest array in a Vulkan type. */ private fun Registry.getMaxArrayLength(type: Type): Long? { @@ -75,12 +80,13 @@ private fun Registry.getMaxArrayLength(type: Type): Long? { } /** Gets the Vulkan structs that can be used to extend other Vulkan structs. */ -val getStructExtensions = thunk { -> - structs.values - .filter { it.structextends != null } - .flatMap { it.structextends!!.map { e -> e to it.name } } - .groupBy({ it.first }, { it.second }) -} +val getStructExtensions = + thunk { -> + structs.values + .filter { it.structextends != null } + .flatMap { it.structextends!!.map { e -> e to it.name } } + .groupBy({ it.first }, { it.second }) + } private val getStructLifetimeResults = HashMap() @@ -95,15 +101,17 @@ fun Registry.getStructLifetime(struct: Structure): Boolean { // so any struct that contains a pointer will need a lifetime. An exception // is made for the `next` member if there are no extending structs since // the corresponding builder method will be omitted in this case. - val members = struct.members.any { - it.type is PointerType && - (it.name.value != "next" || getStructExtensions()[struct.name]?.isNotEmpty() ?: false) - } + val members = + struct.members.any { + it.type is PointerType && + (it.name.value != "next" || getStructExtensions()[struct.name]?.isNotEmpty() ?: false) + } // Builder method for struct members will use - val dependencies = getStructDependencies(struct).any { - getStructLifetime(structs[it] ?: error("Missing struct.")) - } + val dependencies = + getStructDependencies(struct).any { + getStructLifetime(structs[it] ?: error("Missing struct.")) + } val result = members || dependencies getStructLifetimeResults[struct.name] = result @@ -111,17 +119,18 @@ fun Registry.getStructLifetime(struct: Structure): Boolean { } /** Gets the Vulkan structs that can be part of a pointer chain. */ -val getChainStructs = thunk { -> - structs.filter { - val name = it.key.original - if (name == "VkBaseInStructure" || name == "VkBaseOutStructure") { - // These are helper structs used for iterating through pointer - // chains, not pointer chain structs themselves. - false - } else { - val type = it.value.members.getOrNull(0)?.name?.original == "sType" - val next = it.value.members.getOrNull(1)?.name?.original == "pNext" - type && next +val getChainStructs = + thunk { -> + structs.filter { + val name = it.key.original + if (name == "VkBaseInStructure" || name == "VkBaseOutStructure") { + // These are helper structs used for iterating through pointer + // chains, not pointer chain structs themselves. + false + } else { + val type = it.value.members.getOrNull(0)?.name?.original == "sType" + val next = it.value.members.getOrNull(1)?.name?.original == "pNext" + type && next + } } } -} diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Type.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Type.kt index 4a217748..a17a71c1 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Type.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Type.kt @@ -7,13 +7,13 @@ import com.kylemayes.generator.registry.Type import com.kylemayes.generator.registry.getLength /** Gets the length of this Vulkan array type as an integer. */ -fun Registry.getLengthValue(type: Type) = - type.getLength()?.let { constants[it]?.expr?.toLong() ?: it.value.toLong() } +fun Registry.getLengthValue(type: Type) = type.getLength()?.let { constants[it]?.expr?.toLong() ?: it.value.toLong() } /** Generates a Rust pointer type for this type string. */ -fun String.generatePtr(const: Boolean) = - "* ${if (const) { "const" } else { "mut" }} $this" +fun String.generatePtr(const: Boolean) = "* ${if (const) "const" else "mut"} $this" /** Generates a Rust reference type for this type string. */ -fun String.generateRef(const: Boolean, lifetime: String? = null) = - "&${lifetime?.let { "'$it" } ?: ""} ${if (const) { "" } else { "mut" }} $this" +fun String.generateRef( + const: Boolean, + lifetime: String? = null, +) = "&${lifetime?.let { "'$it" } ?: ""} ${if (const) "" else "mut"} $this" diff --git a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Wrapper.kt b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Wrapper.kt index 310f7943..f2b32294 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Wrapper.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/generate/support/Wrapper.kt @@ -42,7 +42,10 @@ fun Registry.generateCommandWrapper(command: Command): String { resultExprs.add("__result") } - fun addArgument(actual: String, setup: String? = null) { + fun addArgument( + actual: String, + setup: String? = null, + ) { setupArgs.add(setup ?: actual) actualArgs.add(actual) } @@ -109,11 +112,12 @@ fun Registry.generateCommandWrapper(command: Command): String { if (outputWithLength || outputWithoutLength) { // Output slice parameter. - val length = if (outputWithLength) { - current.name.value - } else { - "${slices[0].name}.len()" - } + val length = + if (outputWithLength) { + current.name.value + } else { + "${slices[0].name}.len()" + } if (pointee.getIdentifier()?.value == "void") { resultTypes.add("Vec") @@ -178,11 +182,12 @@ fun Registry.generateCommandWrapper(command: Command): String { // Output pointer parameter (uninit-provided). val pointeeType = pointee.generate() - val (resultType, exprCast) = if (pointeeType == "Bool32") { - Pair("bool", " == TRUE") - } else { - Pair(pointeeType, "") - } + val (resultType, exprCast) = + if (pointeeType == "Bool32") { + Pair("bool", " == TRUE") + } else { + Pair(pointeeType, "") + } preActualStmts.add("let mut ${current.name} = MaybeUninit::<$pointeeType>::uninit();") resultTypes.add(resultType) @@ -228,49 +233,52 @@ fun Registry.generateCommandWrapper(command: Command): String { // Generate method signature components. val resultType = resultTypes.joinTuple() - val outputType = when { - hasSuccessCodes && resultType == "()" -> "-> crate::VkResult" - hasSuccessCodes -> "-> crate::VkSuccessResult<$resultType>" - hasErrorCodes -> "-> crate::VkResult<$resultType>" - resultType != "()" -> " -> $resultType" - else -> "" - } + val outputType = + when { + hasSuccessCodes && resultType == "()" -> "-> crate::VkResult" + hasSuccessCodes -> "-> crate::VkSuccessResult<$resultType>" + hasErrorCodes -> "-> crate::VkResult<$resultType>" + resultType != "()" -> " -> $resultType" + else -> "" + } // Generate setup command invocation, if required. - val setup = if (preSetupStmts.isNotEmpty()) { - """ + val setup = + if (preSetupStmts.isNotEmpty()) { + """ ${preSetupStmts.joinToString("")} ${generateInvocation(command, setupArgs)}; """ - } else { - "" - } + } else { + "" + } // Generate actual command invocation. val resultExpr = resultExprs.joinTuple() - val outputExpr = when { - hasSuccessCodes -> - """ + val outputExpr = + when { + hasSuccessCodes -> + """ if __result >= Result::SUCCESS { - Ok(${if (resultExpr != "()") { "($resultExpr, __result.into())" } else { "__result.into()" }}) + Ok(${if (resultExpr != "()") "($resultExpr, __result.into())" else "__result.into()"}) } else { Err(__result.into()) } """ - hasErrorCodes -> - """ + hasErrorCodes -> + """ if __result == Result::SUCCESS { Ok($resultExpr) } else { Err(__result.into()) } """ - resultExpr != "()" -> resultExpr - else -> "" - } + resultExpr != "()" -> resultExpr + else -> "" + } val actual = """ @@ -296,30 +304,38 @@ unsafe fn ${command.name}(&self, ${params.joinToString()})$outputType { } /** Generates the Rust type and cast expression suffix for an input slice parameter. */ -private fun Registry.generateInputSliceTypeAndCast(pointer: PointerType): Pair = when { - structs.containsKey(pointer.pointee.getIdentifier()) -> Pair( - "impl Cast", - ".cast()", - ) - pointer.pointee.getIdentifier()?.value == "void" -> Pair( - "u8", - "as ${"c_void".generatePtr(pointer.const)}", - ) - pointer.pointee is PointerType -> Pair( - "&${pointer.pointee.pointee.generate()}", - ".cast()", - ) - else -> Pair(pointer.pointee.generate(), "") -} +private fun Registry.generateInputSliceTypeAndCast(pointer: PointerType): Pair = + when { + structs.containsKey(pointer.pointee.getIdentifier()) -> + Pair( + "impl Cast", + ".cast()", + ) + pointer.pointee.getIdentifier()?.value == "void" -> + Pair( + "u8", + "as ${"c_void".generatePtr(pointer.const)}", + ) + pointer.pointee is PointerType -> + Pair( + "&${pointer.pointee.pointee.generate()}", + ".cast()", + ) + else -> Pair(pointer.pointee.generate(), "") + } /** Generates a Rust expression which invokes a command in a version or extension trait method. */ -private fun generateInvocation(command: Command, arguments: List): String { +private fun generateInvocation( + command: Command, + arguments: List, +): String { return "(self.commands().${command.name})(${arguments.joinToString()})" } /** Joins a list of strings as a Rust tuple expression or type. */ -private fun List.joinTuple() = when (size) { - 0 -> "()" - 1 -> this[0] - else -> joinToString(prefix = "(", postfix = ")") -} +private fun List.joinTuple() = + when (size) { + 0 -> "()" + 1 -> this[0] + else -> joinToString(prefix = "(", postfix = ")") + } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/registry/Extend.kt b/generator/src/main/kotlin/com/kylemayes/generator/registry/Extend.kt index dae2b38f..43f1efe1 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/registry/Extend.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/registry/Extend.kt @@ -58,7 +58,10 @@ private fun extendEnum( } /** Gets the value for a version/extension variant. */ -private fun getVariantValue(ext: RequireValue, extnumber: Long?): Long? { +private fun getVariantValue( + ext: RequireValue, + extnumber: Long?, +): Long? { if (ext.value != null) { return ext.value } @@ -67,5 +70,5 @@ private fun getVariantValue(ext: RequireValue, extnumber: Long?): Long? { val number = ext.extnumber ?: extnumber ?: return null val offset = ext.offset ?: return null val value = 1_000_000_000L + (1_000 * (number - 1)) + offset - return if (ext.negative) { -value } else { value } + return if (ext.negative) -value else value } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/registry/Extract.kt b/generator/src/main/kotlin/com/kylemayes/generator/registry/Extract.kt index 56f122c2..61a75428 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/registry/Extract.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/registry/Extract.kt @@ -51,13 +51,14 @@ fun extractEntities(e: Element): Registry { // Extract the commands and command aliases. val commands = e.queryEntities("commands/command[not(@alias) and (not(@api) or @api='vulkan')]", ::extractCommand).toMutableMap() - val commandAliases = e.queryElements("commands/command[@alias]").associate { - val name = it.getAttribute("name").intern() - val alias = it.getAttribute("alias").intern() - val command = commands[alias] ?: error("Missing aliased command.") - commands[name] = command.copy(name = name) - name to alias - } + val commandAliases = + e.queryElements("commands/command[@alias]").associate { + val name = it.getAttribute("name").intern() + val alias = it.getAttribute("alias").intern() + val command = commands[alias] ?: error("Missing aliased command.") + commands[name] = command.copy(name = name) + name to alias + } // Extract the other entities. @@ -89,20 +90,23 @@ data class Bitmask( val bitflags: MutableList, ) : Entity -private fun extractBitmask(e: Element) = Bitmask( - name = e.getAttribute("name").intern(), - api = null, - bitflags = e.getElements("enum") - .filter { !it.hasAttribute("alias") } - .map(::extractBitflag) - .toMutableList(), -) +private fun extractBitmask(e: Element) = + Bitmask( + name = e.getAttribute("name").intern(), + api = null, + bitflags = + e.getElements("enum") + .filter { !it.hasAttribute("alias") } + .map(::extractBitflag) + .toMutableList(), + ) -private fun extractBitmaskType(e: Element) = Bitmask( - name = e.getElementText("name")!!.intern(), - api = e.getAttributeText("api"), - bitflags = ArrayList(), -) +private fun extractBitmaskType(e: Element) = + Bitmask( + name = e.getElementText("name")!!.intern(), + api = e.getAttributeText("api"), + bitflags = ArrayList(), + ) /** A bitflag. */ data class Bitflag( @@ -111,14 +115,16 @@ data class Bitflag( val value: BigInteger, ) : Entity -private fun extractBitflag(e: Element) = Bitflag( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - value = e.getAttributeText("bitpos") - ?.toNumber() - ?.let { BigInteger.ONE.shiftLeft(it.toInt()) } - ?: e.getAttribute("value").toNumber().toBigInteger(), -) +private fun extractBitflag(e: Element) = + Bitflag( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + value = + e.getAttributeText("bitpos") + ?.toNumber() + ?.let { BigInteger.ONE.shiftLeft(it.toInt()) } + ?: e.getAttribute("value").toNumber().toBigInteger(), + ) // =============================================== // Command @@ -141,14 +147,16 @@ private fun extractCommand(e: Element): Command { api = e.getAttributeText("api"), params = e.queryElements("param", ::extractParam), result = extractType(proto.getElement("type")!!), - successcodes = e.getAttributeText("successcodes") - ?.split(",") - ?.map { it.intern() } - ?: emptyList(), - errorcodes = e.getAttributeText("errorcodes") - ?.split(",") - ?.map { it.intern() } - ?: emptyList(), + successcodes = + e.getAttributeText("successcodes") + ?.split(",") + ?.map { it.intern() } + ?: emptyList(), + errorcodes = + e.getAttributeText("errorcodes") + ?.split(",") + ?.map { it.intern() } + ?: emptyList(), ) } @@ -190,20 +198,22 @@ private fun extractConstant(e: Element): Constant { val name = e.getAttribute("name") val value = e.getAttribute("value") - val type = when { - name == "VK_TRUE" || name == "VK_FALSE" -> "uint32_t" - name != "WHOLE_SIZE" && (name.startsWith("VK_MAX") || name.endsWith("SIZE")) -> "size_t" - value.contains("ULL") -> "uint64_t" - value.contains("U") -> "uint32_t" - value.contains(Regex("[fF]$")) -> "float" - else -> "int32_t" - } + val type = + when { + name == "VK_TRUE" || name == "VK_FALSE" -> "uint32_t" + name != "WHOLE_SIZE" && (name.startsWith("VK_MAX") || name.endsWith("SIZE")) -> "size_t" + value.contains("ULL") -> "uint64_t" + value.contains("U") -> "uint32_t" + value.contains(Regex("[fF]$")) -> "float" + else -> "int32_t" + } - val expr = if (value.startsWith("(~")) { - value.replace('~', '!').replace(Regex("(\\d+)U(LL)?"), "$1") - } else { - value - } + val expr = + if (value.startsWith("(~")) { + value.replace('~', '!').replace(Regex("(\\d+)U(LL)?"), "$1") + } else { + value + } return Constant( name = name.intern(), @@ -224,14 +234,16 @@ data class Enum( val variants: MutableList, ) : Entity -private fun extractEnum(e: Element) = Enum( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - variants = e.getElements("enum") - .filter { !it.hasAttribute("alias") } - .map(::extractVariant) - .toMutableList(), -) +private fun extractEnum(e: Element) = + Enum( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + variants = + e.getElements("enum") + .filter { !it.hasAttribute("alias") } + .map(::extractVariant) + .toMutableList(), + ) /** An enum variant. */ data class Variant( @@ -240,11 +252,12 @@ data class Variant( val value: Long, ) : Entity -private fun extractVariant(e: Element) = Variant( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - value = e.getAttribute("value").toNumber(), -) +private fun extractVariant(e: Element) = + Variant( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + value = e.getAttribute("value").toNumber(), + ) // ================================================ // Function @@ -258,18 +271,20 @@ data class Function( val result: Type?, ) : Entity -private fun extractFunction(e: Element) = Function( - name = e.getElementText("name")!!.intern(), - api = e.getAttributeText("api"), - params = e.getElements("type", ::extractType), - result = when (val type = e.textContent.substring(8, e.textContent.indexOf("(VKAPI_PTR")).trim()) { - "void" -> null - "void*" -> PointerType(IdentifierType("void".intern()), false) - "VkBool32" -> IdentifierType("VkBool32".intern()) - "PFN_vkVoidFunction" -> IdentifierType("PFN_vkVoidFunction".intern()) - else -> error("Unsupported function pointer result type ($type).") - }, -) +private fun extractFunction(e: Element) = + Function( + name = e.getElementText("name")!!.intern(), + api = e.getAttributeText("api"), + params = e.getElements("type", ::extractType), + result = + when (val type = e.textContent.substring(8, e.textContent.indexOf("(VKAPI_PTR")).trim()) { + "void" -> null + "void*" -> PointerType(IdentifierType("void".intern()), false) + "VkBool32" -> IdentifierType("VkBool32".intern()) + "PFN_vkVoidFunction" -> IdentifierType("PFN_vkVoidFunction".intern()) + else -> error("Unsupported function pointer result type ($type).") + }, + ) // =============================================== // Handle @@ -282,11 +297,12 @@ data class Handle( val dispatchable: Boolean, ) : Entity -private fun extractHandle(e: Element) = Handle( - name = e.getElementText("name")!!.intern(), - api = e.getAttributeText("api"), - dispatchable = !e.getElementText("type")!!.contains("NON_DISPATCHABLE"), -) +private fun extractHandle(e: Element) = + Handle( + name = e.getElementText("name")!!.intern(), + api = e.getAttributeText("api"), + dispatchable = !e.getElementText("type")!!.contains("NON_DISPATCHABLE"), + ) // =============================================== // Structure @@ -300,12 +316,13 @@ data class Structure( val structextends: List?, ) : Entity -private fun extractStructure(e: Element) = Structure( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - members = e.getElements("member", ::extractMember), - structextends = e.getAttributeText("structextends")?.split(",")?.map { it.intern() }, -) +private fun extractStructure(e: Element) = + Structure( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + members = e.getElements("member", ::extractMember), + structextends = e.getAttributeText("structextends")?.split(",")?.map { it.intern() }, + ) /** A struct or union member. */ data class Member( @@ -319,16 +336,17 @@ data class Member( val bits: Int?, ) : Entity -private fun extractMember(e: Element) = Member( - name = e.getElementText("name")!!.intern(), - api = e.getAttributeText("api"), - type = extractType(e.getElement("type")!!), - values = e.getAttributeText("values")?.intern(), - len = e.getAttributeText("len")?.split(",")?.map { it.intern() }, - altlen = e.getAttribute("altlen"), - optional = e.getAttributeText("optional") == "true", - bits = Regex(":(\\d+)$").find(e.textContent)?.let { it.groupValues[1].toInt() }, -) +private fun extractMember(e: Element) = + Member( + name = e.getElementText("name")!!.intern(), + api = e.getAttributeText("api"), + type = extractType(e.getElement("type")!!), + values = e.getAttributeText("values")?.intern(), + len = e.getAttributeText("len")?.split(",")?.map { it.intern() }, + altlen = e.getAttribute("altlen"), + optional = e.getAttributeText("optional") == "true", + bits = Regex(":(\\d+)$").find(e.textContent)?.let { it.groupValues[1].toInt() }, + ) // =============================================== // Typedef @@ -341,11 +359,12 @@ data class Typedef( val type: IdentifierType, ) : Entity -private fun extractAlias(e: Element) = Typedef( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - type = IdentifierType(e.getAttribute("alias").intern()), -) +private fun extractAlias(e: Element) = + Typedef( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + type = IdentifierType(e.getAttribute("alias").intern()), + ) private fun extractBasetype(e: Element): Typedef? { val name = e.getElementText("name") ?: return null @@ -369,12 +388,13 @@ data class Version( val require: Require, ) : Entity -private fun extractVersion(e: Element) = Version( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - number = e.getAttribute("number").toFloat(), - require = extractRequire(e.getElements("require")), -) +private fun extractVersion(e: Element) = + Version( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + number = e.getAttribute("number").toFloat(), + require = extractRequire(e.getElements("require")), + ) /** A Vulkan extension. */ data class Extension( @@ -395,23 +415,24 @@ data class Extension( val require: Require, ) : Entity -private fun extractExtension(e: Element) = Extension( - name = e.getAttribute("name").intern(), - api = e.getAttributeText("api"), - number = e.getAttribute("number").toNumber(), - type = e.getAttributeText("type"), - author = e.getAttribute("author"), - contact = e.getAttribute("contact"), - platform = e.getAttributeText("platform"), - requires = e.getAttributeText("requires"), - requiresCore = e.getAttributeText("requiresCore"), - deprecatedby = e.getAttributeText("deprecatedby"), - obsoletedby = e.getAttributeText("obsoletedby"), - promotedto = e.getAttributeText("promotedto"), - supported = e.getAttribute("supported"), - provisional = e.getAttributeText("provisional") == "true", - require = extractRequire(e.getElements("require")), -) +private fun extractExtension(e: Element) = + Extension( + name = e.getAttribute("name").intern(), + api = e.getAttributeText("api"), + number = e.getAttribute("number").toNumber(), + type = e.getAttributeText("type"), + author = e.getAttribute("author"), + contact = e.getAttribute("contact"), + platform = e.getAttributeText("platform"), + requires = e.getAttributeText("requires"), + requiresCore = e.getAttributeText("requiresCore"), + deprecatedby = e.getAttributeText("deprecatedby"), + obsoletedby = e.getAttributeText("obsoletedby"), + promotedto = e.getAttributeText("promotedto"), + supported = e.getAttribute("supported"), + provisional = e.getAttributeText("provisional") == "true", + require = extractRequire(e.getElements("require")), + ) /** The commands, types, and enum extensions provided by a version or extension. */ data class Require( @@ -492,32 +513,37 @@ interface Entity { } /** Gets and maps all of the entities in this element with the supplied tag. */ -fun Element.getEntities(@Language("XPath") expr: String, transform: (Element) -> T?) = - getElements(expr, transform).associateBy { it.name } +fun Element.getEntities( + @Language("XPath") expr: String, + transform: (Element) -> T?, +) = getElements(expr, transform).associateBy { it.name } /** Finds the entities in this node that match the supplied XPath expression. */ -fun Node.queryEntities(@Language("XPath") expr: String, transform: (Element) -> T?) = - queryElements(expr, transform).associateBy { it.name } +fun Node.queryEntities( + @Language("XPath") expr: String, + transform: (Element) -> T?, +) = queryElements(expr, transform).associateBy { it.name } // =============================================== // Type // =============================================== /** The mapping from C/C++ primitive types to Rust primitive types. */ -private val primitives = mapOf( - "void" to "c_void", - "char" to "c_char", - "int" to "c_int", - "size_t" to "usize", - "int32_t" to "i32", - "int64_t" to "i64", - "uint8_t" to "u8", - "uint16_t" to "u16", - "uint32_t" to "u32", - "uint64_t" to "u64", - "float" to "f32", - "double" to "f64", -) +private val primitives = + mapOf( + "void" to "c_void", + "char" to "c_char", + "int" to "c_int", + "size_t" to "usize", + "int32_t" to "i32", + "int64_t" to "i64", + "uint8_t" to "u8", + "uint16_t" to "u16", + "uint32_t" to "u32", + "uint64_t" to "u64", + "float" to "f32", + "double" to "f64", + ) /** A C/C++ type. */ interface Type { @@ -538,15 +564,17 @@ private fun extractType(e: Element): Type { // `float matrix[3][4]` // `uint8_t deviceUUID[VK_UUID_SIZE]` if (e.parentNode is Element) { - val contents = e.parentNode.childNodes - .mapNodes { it } - .filter { it.nodeType == Node.TEXT_NODE || (it is Element && it.tagName == "enum") } - .joinToString("") { it.textContent } - val lengths = Regex("\\[([^]]+)]") - .findAll(contents) - .map { it.groups[1]!!.value.intern() } - .toList() - .reversed() + val contents = + e.parentNode.childNodes + .mapNodes { it } + .filter { it.nodeType == Node.TEXT_NODE || (it is Element && it.tagName == "enum") } + .joinToString("") { it.textContent } + val lengths = + Regex("\\[([^]]+)]") + .findAll(contents) + .map { it.groups[1]!!.value.intern() } + .toList() + .reversed() if (lengths.isNotEmpty()) { var array = ArrayType(identifier, lengths[0]) lengths.subList(1, lengths.size).forEach { array = ArrayType(array, it) } @@ -572,107 +600,121 @@ private fun extractType(e: Element): Type { return identifier } -fun Type.getBaseIdentifier(): Identifier? = when (this) { - is ArrayType -> element.getBaseIdentifier() - is IdentifierType -> identifier - is PointerType -> null - else -> error("Unreachable.") -} +fun Type.getBaseIdentifier(): Identifier? = + when (this) { + is ArrayType -> element.getBaseIdentifier() + is IdentifierType -> identifier + is PointerType -> null + else -> error("Unreachable.") + } // Array ========================================= /** A C/C++ fixed-length array type. */ data class ArrayType(val element: Type, val length: Identifier) : Type { - override fun generate() = when (element.getIdentifier()?.original) { - "char" -> "StringArray<$length>" - "uint8_t" -> "ByteArray<$length>" - else -> "[${element.generate()}; $length]" - } + override fun generate() = + when (element.getIdentifier()?.original) { + "char" -> "StringArray<$length>" + "uint8_t" -> "ByteArray<$length>" + else -> "[${element.generate()}; $length]" + } override fun generateForCommand() = "*const ${element.generateForCommand()}" - override fun generateDefault() = when (element.getIdentifier()?.original) { - "char" -> "StringArray::default()" - "uint8_t" -> "ByteArray::default()" - else -> "[${element.generateDefault()}; $length]" - } + override fun generateDefault() = + when (element.getIdentifier()?.original) { + "char" -> "StringArray::default()" + "uint8_t" -> "ByteArray::default()" + else -> "[${element.generateDefault()}; $length]" + } } -fun Type.getElement() = if (this is ArrayType) { element } else { null } -fun Type.getLength() = if (this is ArrayType) { length } else { null } +fun Type.getElement() = if (this is ArrayType) element else null + +fun Type.getLength() = if (this is ArrayType) length else null + fun Type.isByteArray() = getElement()?.getIdentifier()?.original == "uint8_t" + fun Type.isStringArray() = getElement()?.getIdentifier()?.original == "char" // Identifier ==================================== /** A C/C++ identifier type. */ data class IdentifierType(val identifier: Identifier) : Type { - override fun generate() = if (identifier.value.startsWith("StdVideo")) { - // Types from the Vulkan video headers are prefixed with `StdVideo` - // and are in a separate `video` module from the registry types. - "video::${identifier.value}" - } else { - primitives.getOrDefault(identifier.value, identifier.value) - } + override fun generate() = + if (identifier.value.startsWith("StdVideo")) { + // Types from the Vulkan video headers are prefixed with `StdVideo` + // and are in a separate `video` module from the registry types. + "video::${identifier.value}" + } else { + primitives.getOrDefault(identifier.value, identifier.value) + } override fun generateDefault() = "${generate()}::default()" } -fun Type.getIdentifier() = if (this is IdentifierType) { identifier } else { null } +fun Type.getIdentifier() = if (this is IdentifierType) identifier else null // Pointer ======================================= /** A C/C++ pointer type. */ data class PointerType(val pointee: Type, val const: Boolean) : Type { - override fun generate() = "*${if (const) { "const" } else { "mut" }} ${pointee.generate()}" - override fun generateDefault() = if (const) { "ptr::null()" } else { "ptr::null_mut()" } + override fun generate() = "*${if (const) "const" else "mut"} ${pointee.generate()}" + + override fun generateDefault() = if (const) "ptr::null()" else "ptr::null_mut()" } -fun Type.getPointee() = if (this is PointerType) { pointee } else { null } +fun Type.getPointee() = if (this is PointerType) pointee else null + fun Type.isPointer() = this is PointerType || isPlatformPointer() + fun Type.isOpaquePointer() = getPointee()?.getIdentifier()?.let { opaque.contains(it.value) } ?: false + fun Type.isPlatformPointer() = platformPointers.contains(getIdentifier()?.value) + fun Type.isStringPointer() = getPointee()?.getIdentifier()?.value == "char" /** The types which are used in opaque pointers (i.e., `void` and `void` typedefs). */ -private val opaque = setOf( - "c_void", - "ANativeWindow", - "AHardwareBuffer", - "IDirectFB", - "IDirectFBSurface", - "CAMetalLayer", - "_screen_context", - "_screen_window", - "wl_display", - "wl_surface", - "SECURITY_ATTRIBUTES", - "xcb_connection_t", -) +private val opaque = + setOf( + "c_void", + "ANativeWindow", + "AHardwareBuffer", + "IDirectFB", + "IDirectFBSurface", + "CAMetalLayer", + "_screen_context", + "_screen_window", + "wl_display", + "wl_surface", + "SECURITY_ATTRIBUTES", + "xcb_connection_t", + ) /** The platform typedefs which are aliases of pointer types. */ -private val platformPointers = setOf( - // iOS / macOS - "IOSurfaceRef", - "MTLBuffer_id", - "MTLCommandQueue_id", - "MTLDevice_id", - "MTLSharedEvent_id", - "MTLTexture_id", - // Windows - "HANDLE", - "HINSTANCE", - "HMONITOR", - "HWND", - "LPCWSTR", - // X11 - "Display", - // NvSciBuf / NvSciSync - "NvSciBufAttrList", - "NvSciBufObj", - "NvSciSyncAttrList", - "NvSciSyncObj", -) +private val platformPointers = + setOf( + // iOS / macOS + "IOSurfaceRef", + "MTLBuffer_id", + "MTLCommandQueue_id", + "MTLDevice_id", + "MTLSharedEvent_id", + "MTLTexture_id", + // Windows + "HANDLE", + "HINSTANCE", + "HMONITOR", + "HWND", + "LPCWSTR", + // X11 + "Display", + // NvSciBuf / NvSciSync + "NvSciBufAttrList", + "NvSciBufObj", + "NvSciSyncAttrList", + "NvSciSyncObj", + ) // =============================================== // Identifier @@ -704,10 +746,18 @@ class Identifier constructor(val original: String) : Comparable { private var _renamed = false val value get() = _value val renamed get() = _renamed - fun rename(value: String) { _value = value; _renamed = true } + + fun rename(value: String) { + _value = value + _renamed = true + } + override fun toString() = value + override fun equals(other: Any?) = other is Identifier && original == other.original + override fun hashCode() = original.hashCode() + override fun compareTo(other: Identifier) = original.compareTo(other.original) } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/registry/Filter.kt b/generator/src/main/kotlin/com/kylemayes/generator/registry/Filter.kt index eb8f5477..8713e2c1 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/registry/Filter.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/registry/Filter.kt @@ -3,6 +3,7 @@ package com.kylemayes.generator.registry /** Filters out entities that are not supported by `vulkanalia`. */ fun Registry.filterEntities(): Registry { val unsupportedEntities = getUnsupportedEntities() + fun Map.filterSupportedEntities() = filter { !unsupportedEntities.contains(it.key) && it.value.isVulkanApi() } @@ -31,8 +32,7 @@ fun Registry.filterEntities(): Registry { private val unsupportedExtensions = emptySet() /** Gets whether a Vulkan extension is supported by `vulkanalia`. */ -private fun Extension.isSupported() = - supported != "disabled" && !unsupportedExtensions.contains(name.original) +private fun Extension.isSupported() = supported != "disabled" && !unsupportedExtensions.contains(name.original) // =============================================== // Filter Entities @@ -53,9 +53,10 @@ private fun Registry.getUnsupportedEntities(): Set { val (vulkan, nonvulkan) = versions.values.partition { it.isVulkanApi() } - val vulkanEntities = vulkan - .flatMap { it.require.commands + it.require.types.map { n -> n.intern() } } - .toSet() + val vulkanEntities = + vulkan + .flatMap { it.require.commands + it.require.types.map { n -> n.intern() } } + .toSet() unsupportedEntities.addAll( nonvulkan @@ -70,14 +71,13 @@ private fun Registry.getUnsupportedEntities(): Set { // Filter Children // =============================================== -private fun Map.filterBitmasks() = - filterChildren({ it.bitflags }, { e, c -> e.copy(bitflags = c.toMutableList()) }) -private fun Map.filterCommands() = - filterChildren({ it.params }, { e, c -> e.copy(params = c) }) -private fun Map.filterEnums() = - filterChildren({ it.variants }, { e, c -> e.copy(variants = c.toMutableList()) }) -private fun Map.filterStructures() = - filterChildren({ it.members }, { e, c -> e.copy(members = c) }) +private fun Map.filterBitmasks() = filterChildren({ it.bitflags }, { e, c -> e.copy(bitflags = c.toMutableList()) }) + +private fun Map.filterCommands() = filterChildren({ it.params }, { e, c -> e.copy(params = c) }) + +private fun Map.filterEnums() = filterChildren({ it.variants }, { e, c -> e.copy(variants = c.toMutableList()) }) + +private fun Map.filterStructures() = filterChildren({ it.members }, { e, c -> e.copy(members = c) }) private fun Map.filterChildren( get: (T) -> List, diff --git a/generator/src/main/kotlin/com/kylemayes/generator/registry/Index.kt b/generator/src/main/kotlin/com/kylemayes/generator/registry/Index.kt index 28c35ca9..7c429c78 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/registry/Index.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/registry/Index.kt @@ -116,10 +116,15 @@ fun Registry.indexEntities(): String { private typealias Index = MutableList> -private fun Index.addEntity(name: String, path: String) = - add(name to "https://docs.rs/vulkanalia/%VERSION%/vulkanalia$path") +private fun Index.addEntity( + name: String, + path: String, +) = add(name to "https://docs.rs/vulkanalia/%VERSION%/vulkanalia$path") -private fun Index.addEntities(type: String, entities: Map) { +private fun Index.addEntities( + type: String, + entities: Map, +) { for (name in entities.keys) { addEntity("vk::$name", "/vk/$type.$name.html") } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/registry/Parse.kt b/generator/src/main/kotlin/com/kylemayes/generator/registry/Parse.kt index 337882fb..6abd7cf4 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/registry/Parse.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/registry/Parse.kt @@ -12,10 +12,11 @@ private val log = KotlinLogging.logger { /* */ } /** Parses a Vulkan API registry from an XML string. */ fun parseRegistry(xml: String): Registry { - val document = log.time("Parse XML") { - val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() - builder.parse(InputSource(StringReader(xml))).documentElement - } + val document = + log.time("Parse XML") { + val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + builder.parse(InputSource(StringReader(xml))).documentElement + } var registry = log.time("Extract Entities") { extractEntities(document) } registry = log.time("Filter Entities") { registry.filterEntities() } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/registry/Rename.kt b/generator/src/main/kotlin/com/kylemayes/generator/registry/Rename.kt index 6a4f59db..84a7ee56 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/registry/Rename.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/registry/Rename.kt @@ -73,41 +73,51 @@ fun Registry.renameEntities() { } /** Renames a command (e.g., `vkCreateInstance` to `create_instance`). */ -private fun renameCommand(name: String) = name - .removePrefix("vk") - .toSnakeCase() +private fun renameCommand(name: String) = + name + .removePrefix("vk") + .toSnakeCase() /** Renames a constant (e.g., `VK_UUID_SIZE` to `UUID_SIZE`). */ -private fun renameConstant(name: String) = name - .removePrefix("VK_") - .uppercase() +private fun renameConstant(name: String) = + name + .removePrefix("VK_") + .uppercase() /** Renames a member or parameter (e.g, `deviceLUIDValid` to `device_luid_valid`). */ -private fun renameMemberOrParameter(name: String) = name - .toSnakeCase() - .replace(Regex("^(p+|pfn)_"), "") - .replace(Regex("^(type)$"), "$1_") +private fun renameMemberOrParameter(name: String) = + name + .toSnakeCase() + .replace(Regex("^(p+|pfn)_"), "") + .replace(Regex("^(type)$"), "$1_") /** Renames a type (e.g., `VkCullModeFlags` to `CullModeFlags`). */ -private fun renameType(name: String) = name - .removePrefix("Vk") +private fun renameType(name: String) = + name + .removePrefix("Vk") /** Renames an enum variant or bitmask bitflag (e.g., `VK_CULL_MODE_FRONT_BIT` to `FRONT`). */ -private fun renameVariantOrBitflag(name: String, parent: String, bitflag: Boolean = false): String { +private fun renameVariantOrBitflag( + name: String, + parent: String, + bitflag: Boolean = false, +): String { // Find the extension author suffix in the parent name, if any. // E.g., `EXT` in `DebugReportObjectTypeEXT`. - val extension = parent - .reversed() - .takeWhile { it.isUpperCase() } - .reversed() + val extension = + parent + .reversed() + .takeWhile { it.isUpperCase() } + .reversed() // Determine the prefix to strip from the value name (parent name). // E.g., `DEBUG_REPORT_OBJECT_TYPE_` for `DebugReportObjectTypeEXT` (variant). // E.g., `DEBUG_REPORT_` for `DebugReportFlagsEXT` (bitflag). - var prefix = parent - .substring(0, parent.length - extension.length) - .toSnakeCase() - .uppercase() + var prefix = + parent + .substring(0, parent.length - extension.length) + .toSnakeCase() + .uppercase() if (bitflag) prefix = prefix.replace(Regex("FLAGS(\\d*)"), "$1") if (!prefix.endsWith('_')) prefix = "${prefix}_" @@ -115,16 +125,17 @@ private fun renameVariantOrBitflag(name: String, parent: String, bitflag: Boolea // E.g., `_EXT` for `DebugReportObjectTypeEXT` val suffix = "_$extension".trimEnd('_') - val renamed = name - .removePrefix("VK_") - .removePrefix(prefix) - .removeSuffix(suffix) - // Some value names start with digits after the prefixes have been - // stripped which would make them invalid identifiers. - .replace(Regex("^([0-9])"), "_$1") - // Some value names include lowercase characters that need to be - // capitalized (e.g., `VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT`). - .uppercase() + val renamed = + name + .removePrefix("VK_") + .removePrefix(prefix) + .removeSuffix(suffix) + // Some value names start with digits after the prefixes have been + // stripped which would make them invalid identifiers. + .replace(Regex("^([0-9])"), "_$1") + // Some value names include lowercase characters that need to be + // capitalized (e.g., `VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT`). + .uppercase() // Remove `BIT` component from bitflag name even when followed by extension author. return if (bitflag) { diff --git a/generator/src/main/kotlin/com/kylemayes/generator/support/Case.kt b/generator/src/main/kotlin/com/kylemayes/generator/support/Case.kt index 26b4f32e..16a6eb1e 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/support/Case.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/support/Case.kt @@ -9,7 +9,10 @@ fun String.toPascalCase(): String { var upper = true for (c in this) { when { - upper -> { pascal += c.uppercase(); upper = false } + upper -> { + pascal += c.uppercase() + upper = false + } c == '_' -> upper = true else -> pascal += c.lowercase() } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/support/Changelog.kt b/generator/src/main/kotlin/com/kylemayes/generator/support/Changelog.kt index ab7e2f2e..5eef5b03 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/support/Changelog.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/support/Changelog.kt @@ -50,7 +50,7 @@ fun parseMarkdown(markdown: String): Changelog { assert(versionPieces.size == 2) val version = versionPieces[0].removePrefix("[").removeSuffix("]") - val release = if (versionPieces[1] == "UNRELEASED") { null } else { versionPieces[1] } + val release = if (versionPieces[1] == "UNRELEASED") null else versionPieces[1] // Parse Sections @@ -63,9 +63,10 @@ fun parseMarkdown(markdown: String): Changelog { // Parse Section val name = sectionHeading.text.toString().trim() - val changes = sectionList.children - .map { it.childChars.toString().trim() } - .toMutableList() + val changes = + sectionList.children + .map { it.childChars.toString().trim() } + .toMutableList() sections.add(Section(name, changes)) } diff --git a/generator/src/main/kotlin/com/kylemayes/generator/support/Command.kt b/generator/src/main/kotlin/com/kylemayes/generator/support/Command.kt index 0d051f93..f7117a3d 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/support/Command.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/support/Command.kt @@ -19,7 +19,10 @@ private val killTimeout = 5.seconds fun bindgen(vararg args: String): String = execute("bindgen", arrayOf(*args)) /** Executes the `git` command and prints the output. */ -fun git(vararg args: String, directory: Path? = null) { +fun git( + vararg args: String, + directory: Path? = null, +) { println("> git ${args.joinToString(" ")}") println(execute("git", arrayOf(*args), directory = directory)) } @@ -28,7 +31,12 @@ fun git(vararg args: String, directory: Path? = null) { fun rustfmt(rust: String): String = execute("rustfmt", emptyArray(), input = rust) /** Executes a command (with a time limit) and returns the output. */ -private fun execute(command: String, args: Array, input: String? = null, directory: Path? = null): String { +private fun execute( + command: String, + args: Array, + input: String? = null, + directory: Path? = null, +): String { var builder = ProcessBuilder(command, *args) if (directory != null) builder = builder.directory(directory.toFile()) val process = builder.start() @@ -38,7 +46,10 @@ private fun execute(command: String, args: Array, input: String? = null, val stdout = AtomicReference(null) val stderr = AtomicReference(null) - fun operation(name: String, operation: () -> Unit) = Thread { + fun operation( + name: String, + operation: () -> Unit, + ) = Thread { try { Thread.currentThread().name = "$command-$name" log.slow("`$command`: $name", waitTimeout / 2) { operation() } @@ -49,27 +60,28 @@ private fun execute(command: String, args: Array, input: String? = null, } } - val threads = listOf( - operation("write stdin") { - if (input != null) process.outputStream.write(input.toByteArray()) - process.outputStream.flush() - process.outputStream.close() - }, - operation("read stdout") { - stdout.set(String(process.inputStream.readAllBytes())) - process.inputStream.close() - }, - operation("read stderr") { - stderr.set(String(process.errorStream.readAllBytes())) - process.errorStream.close() - }, - operation("wait") { - process.waitFor() - if (process.exitValue() != 0) { - error("Non-zero exit code (${process.exitValue()}).") - } - }, - ) + val threads = + listOf( + operation("write stdin") { + if (input != null) process.outputStream.write(input.toByteArray()) + process.outputStream.flush() + process.outputStream.close() + }, + operation("read stdout") { + stdout.set(String(process.inputStream.readAllBytes())) + process.inputStream.close() + }, + operation("read stderr") { + stderr.set(String(process.errorStream.readAllBytes())) + process.errorStream.close() + }, + operation("wait") { + process.waitFor() + if (process.exitValue() != 0) { + error("Non-zero exit code (${process.exitValue()}).") + } + }, + ) threads.forEach { it.start() } val countdown = latch.await(waitTimeout.inWholeMilliseconds, TimeUnit.MILLISECONDS) diff --git a/generator/src/main/kotlin/com/kylemayes/generator/support/Logger.kt b/generator/src/main/kotlin/com/kylemayes/generator/support/Logger.kt index 71610a62..94acc7e6 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/support/Logger.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/support/Logger.kt @@ -7,7 +7,10 @@ import java.util.concurrent.atomic.AtomicBoolean import kotlin.time.Duration /** Times a block of code. */ -fun KLogger.time(name: String, block: () -> T): T { +fun KLogger.time( + name: String, + block: () -> T, +): T { info { "[BEGIN] $name" } val start = System.nanoTime() val result = block() @@ -17,19 +20,26 @@ fun KLogger.time(name: String, block: () -> T): T { } /** Times a block of code if it is unexpectedly slow. */ -fun KLogger.slow(name: String, limit: Duration, block: () -> T): T { +fun KLogger.slow( + name: String, + limit: Duration, + block: () -> T, +): T { val done = AtomicBoolean(false) val slow = AtomicBoolean(false) - val thread = Thread { - try { - Thread.sleep(limit.inWholeMilliseconds) - if (!done.get()) { - slow.set(true) - warn { "[UPDATE(SLOW)] $name is taking a while (>${limit}ms)..." } + val thread = + Thread { + try { + Thread.sleep(limit.inWholeMilliseconds) + if (!done.get()) { + slow.set(true) + warn { "[UPDATE(SLOW)] $name is taking a while (>${limit}ms)..." } + } + } catch (e: InterruptedException) { + println(e) } - } catch (_: InterruptedException) {} - } + } thread.start() diff --git a/generator/src/main/kotlin/com/kylemayes/generator/support/PeekableIterator.kt b/generator/src/main/kotlin/com/kylemayes/generator/support/PeekableIterator.kt index e7cb8c48..4d7a3c0e 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/support/PeekableIterator.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/support/PeekableIterator.kt @@ -5,8 +5,11 @@ package com.kylemayes.generator.support /** A peekable iterator over a list of items. */ class PeekableIterator(private val items: List, private var index: Int = 0) { fun isEmpty() = index >= items.size + fun peek(offset: Int = 0) = items.getOrNull(index + offset) + fun advance() = items[index++] + fun takeWhile(f: (T) -> Boolean): List { val items = mutableListOf() while (peek()?.let(f) == true) items.add(advance()) diff --git a/generator/src/main/kotlin/com/kylemayes/generator/support/Xml.kt b/generator/src/main/kotlin/com/kylemayes/generator/support/Xml.kt index 99abba56..85a88998 100644 --- a/generator/src/main/kotlin/com/kylemayes/generator/support/Xml.kt +++ b/generator/src/main/kotlin/com/kylemayes/generator/support/Xml.kt @@ -14,35 +14,37 @@ import javax.xml.xpath.XPathFactory // =============================================== /** Gets the text content of an attribute in this element with the supplied name. */ -fun Element.getAttributeText(name: String): String? = - if (hasAttribute(name)) { getAttribute(name) } else { null } +fun Element.getAttributeText(name: String): String? = if (hasAttribute(name)) getAttribute(name) else null /** Gets the text content of the first child element in this element with the supplied tag. */ -fun Element.getElementText(tag: String): String? = - getElement(tag)?.textContent +fun Element.getElementText(tag: String): String? = getElement(tag)?.textContent /** Gets the first child element in this element with the supplied tag. */ -fun Element.getElement(tag: String): Element? = - getElements(tag).firstOrNull() +fun Element.getElement(tag: String): Element? = getElements(tag).firstOrNull() /** Gets and maps all of the child elements in this element with the supplied tag. */ -fun Element.getElements(tag: String, transform: (Element) -> T?): List = - getElements(tag).mapNotNull(transform) +fun Element.getElements( + tag: String, + transform: (Element) -> T?, +): List = getElements(tag).mapNotNull(transform) /** Gets all of the child elements in this element with the supplied tag. */ -fun Element.getElements(tag: String): List = - getElementsByTagName(tag).mapNodes { it as Element } +fun Element.getElements(tag: String): List = getElementsByTagName(tag).mapNodes { it as Element } // =============================================== // Node // =============================================== /** Finds and maps the elements in this node that match the supplied XPath expression. */ -fun Node.queryElements(@Language("XPath") expr: String, transform: (Element) -> T?): List = - queryElements(expr).mapNotNull(transform) +fun Node.queryElements( + @Language("XPath") expr: String, + transform: (Element) -> T?, +): List = queryElements(expr).mapNotNull(transform) /** Finds the elements in this node that match the supplied XPath expression. */ -fun Node.queryElements(@Language("XPath") expr: String): List { +fun Node.queryElements( + @Language("XPath") expr: String, +): List { val xpath = XPathFactory.newInstance().newXPath() val nodes = xpath.evaluate(expr, this, XPathConstants.NODESET) as NodeList return nodes.mapNodes { it as Element } @@ -53,5 +55,4 @@ fun Node.queryElements(@Language("XPath") expr: String): List { // =============================================== /** Maps the non-null nodes in this node list. */ -fun NodeList.mapNodes(transform: (Node) -> T): List = - (0..length).mapNotNull(::item).map(transform) +fun NodeList.mapNodes(transform: (Node) -> T): List = (0..length).mapNotNull(::item).map(transform) diff --git a/generator/src/test/kotlin/com/kylemayes/generator/support/ChangelogTest.kt b/generator/src/test/kotlin/com/kylemayes/generator/support/ChangelogTest.kt index 359fd0c1..d63066fc 100644 --- a/generator/src/test/kotlin/com/kylemayes/generator/support/ChangelogTest.kt +++ b/generator/src/test/kotlin/com/kylemayes/generator/support/ChangelogTest.kt @@ -12,8 +12,7 @@ import java.nio.file.Files import java.nio.file.Path class ChangelogTest { - private fun load(path: String) = - this::class.java.classLoader.getResource("changelog/$path")!!.readText() + private fun load(path: String) = this::class.java.classLoader.getResource("changelog/$path")!!.readText() @Test fun `Round Trip`() {