diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2e87b80..21b851f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -23,10 +23,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Build with Gradle diff --git a/.gitignore b/.gitignore index 11ecced..d25faff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,31 @@ -/.gradle/ -/.idea/ -/build/ +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# IDEA +.idea + +# HeaderOutput files /header/ /originalData.json -/old -/old_ \ No newline at end of file +/config.toml +/config.json +/declareMap.json diff --git a/build.gradle.kts b/build.gradle.kts index 4ab6911..07e6d91 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.9.0" @@ -19,6 +20,7 @@ application { repositories { mavenCentral() + maven { url = uri("https://jitpack.io") } } dependencies { @@ -26,6 +28,11 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.5") + implementation("org.apache.logging.log4j:log4j-api:2.20.0") + implementation("org.apache.logging.log4j:log4j-core:2.20.0") + implementation("org.apache.logging.log4j:log4j-slf4j-impl:2.20.0") + implementation("io.github.oshai:kotlin-logging-jvm:5.0.0") + implementation("cc.ekblad:4koma:1.2.0") testImplementation(platform("org.junit:junit-bom:5.9.2")) testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") @@ -39,4 +46,13 @@ tasks.getByName("shadowJar") { minimize() } +tasks.withType { + kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() +} + +tasks.withType { + sourceCompatibility = JavaVersion.VERSION_17.toString() + targetCompatibility = JavaVersion.VERSION_17.toString() +} + tasks.build { dependsOn(tasks.named("shadowJar")) } diff --git a/config.json b/config.json deleted file mode 100644 index 5b993f6..0000000 --- a/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "exclusion": { - "generation": { - "regex": [ - "^$", - "^struct .*$", - "^class .*$", - ".*(@|\\s|<).*", - "^(?:std|gsl|mce|glm|entt|rapidjson|type_info|leveldb|asio)" - ] - }, - "inclusion": { - "regex": [ - "^(std)$" - ] - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt b/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt index dee20cd..3392a29 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt @@ -2,18 +2,15 @@ package com.liteldev.headeroutput import com.liteldev.headeroutput.config.GeneratorConfig import com.liteldev.headeroutput.entity.BaseType +import io.github.oshai.kotlinlogging.KotlinLogging import java.io.File object HeaderGenerator { const val HEADER_SUFFIX = "h" private const val PREDEFINE_FILE_NAME = "_HeaderOutputPredefine.$HEADER_SUFFIX" - - private val HEADER_TEMPLATE = """ -#pragma once - - -""".trimIndent() + private const val HEADER_TEMPLATE = "#pragma once\n\n" + private val logger = KotlinLogging.logger { } fun generate() { File(GeneratorConfig.generatePath).mkdirs() @@ -36,16 +33,15 @@ object HeaderGenerator { } else { sb.appendLine(type.generateTypeDefine()) } - val file = File(GeneratorConfig.generatePath, type.getPath()) + val file = File(GeneratorConfig.generatePath, type.path) + file.parentFile.mkdirs() file.writeText( - HEADER_TEMPLATE + """ -#include "${type.getPath().relativePathTo(PREDEFINE_FILE_NAME)}" - -// auto generated inclusion list -${type.includeList.sorted().joinToString("\n") { "#include \"$it\"" }} - - - """.trimIndent() + sb.toString() + buildString { + append(HEADER_TEMPLATE) + append("#include \"${type.path.relativePathTo(PREDEFINE_FILE_NAME)}\"\n\n") + append(generateIncludes(type)) + append(sb.toString()) + } ) } } @@ -54,26 +50,36 @@ ${type.includeList.sorted().joinToString("\n") { "#include \"$it\"" }} private fun generateNamespace(type: BaseType) { assert(type.isNamespace()) { "${type.name} is not namespace" } - val file = File(GeneratorConfig.generatePath, type.getPath()) + val file = File(GeneratorConfig.generatePath, type.path) + file.parentFile.mkdirs() file.writeText( - HEADER_TEMPLATE + """ -#include "${type.getPath().relativePathTo(PREDEFINE_FILE_NAME)}" - -// auto generated inclusion list -${type.includeList.sorted().joinToString("\n") { "#include \"$it\"" }} - - - """.trimIndent() + type.generateTypeDefine() + buildString { + append(HEADER_TEMPLATE) + append("#include \"${type.path.relativePathTo(PREDEFINE_FILE_NAME)}\"\n\n") + append(generateIncludes(type)) + append(type.generateTypeDefine()) + } ) if (type.innerTypes.isNotEmpty()) { - File(GeneratorConfig.generatePath, type.getPath().removeSuffix(".$HEADER_SUFFIX")).also { it.mkdirs() } type.innerTypes.forEach { innerType -> generate(innerType) } } } + private fun generateIncludes(type: BaseType): String { + if (type.includeList.isEmpty()) { + return "" + } + return buildString { + append("// auto generated inclusion list\n") + append(type.includeList.sorted() + .joinToString("\n") { "#include \"$it\"" }) + append("\n\n") + } + } + private fun createPredefineFile() { val file = File(GeneratorConfig.generatePath, PREDEFINE_FILE_NAME) file.writeText( diff --git a/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt b/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt index 147bfc6..2092550 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt @@ -1,13 +1,15 @@ package com.liteldev.headeroutput import com.liteldev.headeroutput.config.GeneratorConfig -import com.liteldev.headeroutput.config.origindata.MemberTypeData -import com.liteldev.headeroutput.config.origindata.StorageClassType -import com.liteldev.headeroutput.config.origindata.SymbolNodeType -import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.data.MemberTypeData +import com.liteldev.headeroutput.data.StorageClassType +import com.liteldev.headeroutput.data.SymbolNodeType +import com.liteldev.headeroutput.data.TypeData +import com.liteldev.headeroutput.entity.BaseType import com.liteldev.headeroutput.entity.ClassType import com.liteldev.headeroutput.entity.NamespaceType import com.liteldev.headeroutput.entity.StructType +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.cli.ArgParser import kotlinx.cli.ArgType import kotlinx.cli.default @@ -19,6 +21,8 @@ import java.io.File private val json = Json { explicitNulls = false } object HeaderOutput { + private val logger = KotlinLogging.logger { } + private lateinit var originData: JsonObject private lateinit var classNameList: MutableSet private lateinit var structNameList: MutableSet @@ -39,31 +43,36 @@ object HeaderOutput { TypeManager.initInclusionList() HeaderGenerator.generate() + + logger.warn { "These types are not sorted by any rules or declare map: ${BaseType.notSortedTypes.sorted()}" } } private fun readCommandLineArgs(args: Array): Boolean { val parser = ArgParser("HeaderOutput") - val configPath by parser.option(ArgType.String, "config", "c", "The config file path").default("./config.json") + val configPath by parser.option(ArgType.String, "config", "c", "The config file path").default("./config.toml") + val declareMapFile by parser.option(ArgType.String, "declare-map", "d", "The declare map file path") + .default("./declareMap.json") val generatePath by parser.option(ArgType.String, "output-dir", "o", "The header output path") .default("./header") val jsonPath by parser.option(ArgType.String, "input", "i", "The original data json file path") - .default("./header.json") + .default("./originalData.json") parser.parse(args) GeneratorConfig.configPath = configPath GeneratorConfig.generatePath = generatePath GeneratorConfig.jsonPath = jsonPath + GeneratorConfig.declareMapPath = declareMapFile if (!File(GeneratorConfig.configPath).isFile) { - println("Invalid config file path") + logger.error { "Invalid config file path" } return false } if (!File(GeneratorConfig.generatePath).isDirectory) { if (!File(GeneratorConfig.generatePath).mkdirs()) { - println("Fail to create generate header files path") + logger.error { "Fail to create generate header files path" } return false } } if (!File(GeneratorConfig.jsonPath).isFile) { - println("Invalid original data json file path") + logger.error { "Invalid original data json file path" } return false } return true @@ -71,7 +80,7 @@ object HeaderOutput { private fun loadOriginData() { - println("Loading origin data...") + logger.info { "Loading origin data..." } val configText = File(GeneratorConfig.jsonPath).readText() originData = Json.parseToJsonElement(configText).jsonObject typeDataMap = originData["classes"]?.jsonObject?.mapValues { entry -> @@ -96,7 +105,7 @@ object HeaderOutput { private fun constructTypes() { val notIdentifiedTypes = mutableSetOf() - println("Loading types...") + logger.info { "Loading types..." } typeDataMap .filterNot { (k, _) -> GeneratorConfig.isExcludedFromGeneration(k) } .forEach { (typeName, type) -> @@ -113,11 +122,11 @@ object HeaderOutput { } ) } - println("Warning: can not determine these types' type. Treat them as class type\n$notIdentifiedTypes") + logger.warn { "Can not determine these types' type. Treat them as class type\n$notIdentifiedTypes" } } private fun loadIdentifiedTypes() { - println("Loading identifier...") + logger.info { "Loading identifier..." } val identifier = originData["identifier"]?.jsonObject classNameList = (identifier?.get("class")?.jsonArray).orEmpty().map { it.jsonPrimitive.content }.toMutableSet() @@ -135,9 +144,7 @@ object HeaderOutput { } if (referencedTypes.isNotEmpty()) { - println( - "Warning: these types are referenced from other types but not identified. Treat them as class type\n$referencedTypes" - ) + logger.warn { "These types are referenced from other types but not identified. Treat them as class type\n$referencedTypes" } classNameList.addAll(referencedTypes) } } diff --git a/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt b/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt index 32eac5b..4af5a26 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt @@ -1,18 +1,17 @@ package com.liteldev.headeroutput import com.liteldev.headeroutput.config.GeneratorConfig -import com.liteldev.headeroutput.config.origindata.TypeData -import com.liteldev.headeroutput.entity.BaseType +import com.liteldev.headeroutput.data.TypeData +import com.liteldev.headeroutput.entity.* import com.liteldev.headeroutput.entity.BaseType.TypeKind -import com.liteldev.headeroutput.entity.ClassType -import com.liteldev.headeroutput.entity.EnumType -import com.liteldev.headeroutput.entity.StructType +import io.github.oshai.kotlinlogging.KotlinLogging object TypeManager { + private val logger = KotlinLogging.logger { } private val typeMap = hashMapOf() val nestingMap = hashMapOf() - val template = hashSetOf() + val template = hashMapOf() fun addType(fullName: String, type: BaseType) { typeMap[fullName] = type @@ -67,7 +66,7 @@ object TypeManager { allNestingType.addAll(nestingMap.values) val allType = typeMap.values.toSet() val notNestingType = allType - allNestingType - println("Warning: these class has no nesting relationship\n${notNestingType.map { it.name }}") + logger.warn { "These class has no nesting relationship\n${notNestingType.map { it.name }}" } // generate a dummy class for each not nesting class notNestingType.forEach { val parentName = it.name.substringBeforeLast("::") @@ -90,13 +89,17 @@ object TypeManager { return null } - val dummyClass = when (type) { + var dummyClass = when (type) { TypeKind.CLASS -> ClassType(name, TypeData.empty(), template.contains(name)) TypeKind.STRUCT -> StructType(name, TypeData.empty(), template.contains(name)) TypeKind.ENUM -> EnumType(name) + TypeKind.NAMESPACE -> NamespaceType(name, TypeData.empty()) else -> throw IllegalArgumentException("type $type is not supported") } + if (type == TypeKind.CLASS && typeMap.any { (n, t) -> n.startsWith(dummyClass.name + "::") && t.isNamespace() }) + dummyClass = NamespaceType(name, TypeData.empty()) + if (name.contains("::")) { val parentName = name.substringBeforeLast("::") if (!hasType(parentName)) { diff --git a/src/main/kotlin/com/liteldev/headeroutput/ast/template/Node.kt b/src/main/kotlin/com/liteldev/headeroutput/ast/template/Node.kt new file mode 100644 index 0000000..74a0cac --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/ast/template/Node.kt @@ -0,0 +1,42 @@ +package com.liteldev.headeroutput.ast.template + +import com.liteldev.headeroutput.data.TypeData + +sealed class Node + +data class IntegerNode(val value: Int) : Node() + +data class FloatNode(val value: Float) : Node() + +data class BooleanNode(val value: Boolean) : Node() + +data class TypeNode(val name: String, val children: List = emptyList()) : Node() { + fun genTemplateDeclares(): String? { + if (children.isEmpty()) { + return null + } + var argCounter = 0 + return "template<${ + children.joinToString(", ") { + when (it) { + is TypeNode -> "typename T${argCounter++}" + is IntegerNode -> "int T${argCounter++}" + is FloatNode -> "float T${argCounter++}" + is BooleanNode -> "bool T${argCounter++}" + } + } + }>" + } + + fun pureName(): String? { + return TypeData.typeMatchRegex.find(name)?.groupValues?.get(2) + } + + fun flatten(): Map { + val map = mutableMapOf() + map[pureName()] = genTemplateDeclares() + children.filterIsInstance().forEach { map.putAll(it.flatten()) } + return map.mapNotNull { (k, v) -> if (k == null || v == null) return@mapNotNull null else k to v }.toMap() + } + +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/ast/template/Parser.kt b/src/main/kotlin/com/liteldev/headeroutput/ast/template/Parser.kt new file mode 100644 index 0000000..94c6551 --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/ast/template/Parser.kt @@ -0,0 +1,69 @@ +package com.liteldev.headeroutput.ast.template + +fun parse(node: String): Node { + val trimNode = node.trim() + if (trimNode == "true" || trimNode == "false") { + return BooleanNode(trimNode.toBoolean()) + } + if (!trimNode.all { it.isDigit() || it == '.' }) + return TypeNode(trimNode) + return runCatching { + if (trimNode.contains(".")) { + FloatNode(trimNode.toFloat()) + } else { + IntegerNode(trimNode.toInt()) + } + }.onFailure { TypeNode(trimNode) }.getOrThrow() +} + +fun parseType(type: String): TypeNode? { + val stack = mutableListOf() + var temp = "" + println(type) + fun addChildren(node: Node) { + if (stack.isNotEmpty()) { + val parent = stack.removeLast() + stack.add(parent.copy(children = parent.children + node)) + } + } + + for (char in type) { + when (char) { + '<' -> { + stack.add(TypeNode(temp.trim())) + temp = "" + } + + '>' -> { + if (temp.isBlank()) { + if (stack.size > 1) { + val current = stack.removeLast() + val parent = stack.removeLast() + stack.add(parent.copy(children = parent.children + current)) + } + continue + } + addChildren(parse(temp)) + temp = "" + + if (stack.size > 1) { + val current = stack.removeLast() + val parent = stack.removeLast() + stack.add(parent.copy(children = parent.children + current)) + } + } + + ',' -> { + if (temp.isBlank()) { + continue + } + + addChildren(parse(temp)) + temp = "" + } + + else -> temp += char + } + } + return if (stack.isNotEmpty()) stack.removeLast() else null +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/Exclusion.kt b/src/main/kotlin/com/liteldev/headeroutput/config/Exclusion.kt new file mode 100644 index 0000000..d303b60 --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/config/Exclusion.kt @@ -0,0 +1,19 @@ +package com.liteldev.headeroutput.config + +import kotlinx.serialization.Serializable + +@Serializable +data class Exclusion( + val generation: Generation, + val inclusion: Inclusion +) { + @Serializable + data class Generation( + val regex: List + ) + + @Serializable + data class Inclusion( + val regex: List + ) +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt index 12620bd..ed49609 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt @@ -1,27 +1,43 @@ package com.liteldev.headeroutput.config +import cc.ekblad.toml.decode +import cc.ekblad.toml.tomlMapper +import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import java.io.File +import kotlin.system.exitProcess object GeneratorConfig { + private val logger = KotlinLogging.logger { } + @OptIn(ExperimentalSerializationApi::class) private val json = Json { explicitNulls = false } lateinit var jsonPath: String lateinit var generatePath: String lateinit var configPath: String - lateinit var generationExcludeRegexList: MutableList - lateinit var inclusionExcludeRegexList: MutableList + lateinit var declareMapPath: String + lateinit var generationExcludeRegexList: List + lateinit var inclusionExcludeRegexList: List - private lateinit var generatorConfigData: GeneratorConfigData + private lateinit var generatorConfigData: OutputConfig fun loadConfig() { - println("Loading config...") + logger.info { "Loading config..." } val configText = File(configPath).readText() - generatorConfigData = json.decodeFromString(configText) + generatorConfigData = try { + tomlMapper { }.decode(configText) + } catch (e: Exception) { + try { + json.decodeFromString(configText) + } catch (e: Exception) { + logger.error { "Invalid config file" } + exitProcess(1) + } + } generationExcludeRegexList = generatorConfigData.exclusion.generation.regex inclusionExcludeRegexList = generatorConfigData.exclusion.inclusion.regex } @@ -29,4 +45,7 @@ object GeneratorConfig { fun isExcludedFromGeneration(name: String): Boolean { return generationExcludeRegexList.any { name.matches(it.toRegex()) } } + + fun getSortRules() = generatorConfigData.sort + } diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfigData.kt b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfigData.kt deleted file mode 100644 index 4bef3c3..0000000 --- a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfigData.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.liteldev.headeroutput.config - -import kotlinx.serialization.Serializable - -@Serializable -data class GeneratorConfigData( - var exclusion: Exclusion -) { - @Serializable - data class Exclusion( - var generation: Generation, var inclusion: Inclusion - ) { - @Serializable - data class Generation(var regex: MutableList) - - @Serializable - data class Inclusion(var regex: MutableList) - } -} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/OutputConfig.kt b/src/main/kotlin/com/liteldev/headeroutput/config/OutputConfig.kt new file mode 100644 index 0000000..54af4f9 --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/config/OutputConfig.kt @@ -0,0 +1,9 @@ +package com.liteldev.headeroutput.config + +import kotlinx.serialization.Serializable + +@Serializable +data class OutputConfig( + val exclusion: Exclusion, + val sort: Sort +) diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/Sort.kt b/src/main/kotlin/com/liteldev/headeroutput/config/Sort.kt new file mode 100644 index 0000000..617350c --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/config/Sort.kt @@ -0,0 +1,22 @@ +package com.liteldev.headeroutput.config + +import kotlinx.serialization.Serializable + +@Serializable +data class Sort( + val parent: List, + val regex: List +) { + @Serializable + data class Parent( + val parent: String, + val dst: String + ) + + @Serializable + data class Regex( + val regex: String, + val dst: String, + val override: Boolean = false + ) +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/EnumType.kt b/src/main/kotlin/com/liteldev/headeroutput/data/EnumType.kt similarity index 98% rename from src/main/kotlin/com/liteldev/headeroutput/config/origindata/EnumType.kt rename to src/main/kotlin/com/liteldev/headeroutput/data/EnumType.kt index 4ca3f68..a7379c9 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/EnumType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/data/EnumType.kt @@ -1,4 +1,4 @@ -package com.liteldev.headeroutput.config.origindata +package com.liteldev.headeroutput.data import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/MemberTypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/data/MemberTypeData.kt similarity index 98% rename from src/main/kotlin/com/liteldev/headeroutput/config/origindata/MemberTypeData.kt rename to src/main/kotlin/com/liteldev/headeroutput/data/MemberTypeData.kt index ce8e331..f6f6303 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/MemberTypeData.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/data/MemberTypeData.kt @@ -1,4 +1,4 @@ -package com.liteldev.headeroutput.config.origindata +package com.liteldev.headeroutput.data import com.liteldev.headeroutput.appendSpace import kotlinx.serialization.SerialName diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/TypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/data/TypeData.kt similarity index 88% rename from src/main/kotlin/com/liteldev/headeroutput/config/origindata/TypeData.kt rename to src/main/kotlin/com/liteldev/headeroutput/data/TypeData.kt index 429aa0a..d844beb 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/TypeData.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/data/TypeData.kt @@ -1,6 +1,7 @@ -package com.liteldev.headeroutput.config.origindata +package com.liteldev.headeroutput.data import com.liteldev.headeroutput.TypeManager +import com.liteldev.headeroutput.ast.template.parseType import com.liteldev.headeroutput.entity.BaseType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -42,7 +43,9 @@ data class TypeData( private fun matchTypes(name: String) = typeMatchRegex.findAll(name) .map { it.groupValues[2] to BaseType.TypeKind.valueOf(it.groupValues[1].uppercase(Locale.getDefault())) } - .onEach { (typeName, _) -> if (name.contains("$typeName<")) TypeManager.template.add(typeName) } // detects template class + .onEach { (typeName, _) -> + if (name.contains("$typeName<")) parseType(name)?.flatten()?.let(TypeManager.template::putAll) + } // detects template class fun collectReferencedTypes(): Map { diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/VariableTypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/data/VariableTypeData.kt similarity index 92% rename from src/main/kotlin/com/liteldev/headeroutput/config/origindata/VariableTypeData.kt rename to src/main/kotlin/com/liteldev/headeroutput/data/VariableTypeData.kt index c61d506..cc5c8ce 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/VariableTypeData.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/data/VariableTypeData.kt @@ -1,4 +1,4 @@ -package com.liteldev.headeroutput.config.origindata +package com.liteldev.headeroutput.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt index 338fbef..5573cf7 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt @@ -2,8 +2,14 @@ package com.liteldev.headeroutput.entity import com.liteldev.headeroutput.HeaderGenerator.HEADER_SUFFIX import com.liteldev.headeroutput.TypeManager -import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.config.GeneratorConfig +import com.liteldev.headeroutput.data.TypeData import com.liteldev.headeroutput.getTopLevelFileType +import com.liteldev.headeroutput.toSnakeCase +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import java.io.File import java.util.* abstract class BaseType( @@ -24,14 +30,49 @@ abstract class BaseType( val fullEscapeName = name.replace("::", "_") val fullUpperEscapeName = fullEscapeName.uppercase(Locale.getDefault()) - abstract fun generateTypeDefine(): String + // should be initialized after nested types are constructed and dummy types are created + val path: String by lazy { + getTopLevelFileType().run { + val regexRules = GeneratorConfig.getSortRules().regex + regexRules.filter { it.override }.find { this.name.matches(it.regex.toRegex()) }?.let { + return@run "./${it.dst}/${this.simpleName}.$HEADER_SUFFIX" + } + if (declareMap.containsKey(this.name)) { + return@run "./${declareMap[this.name]!!.toSnakeCase()}/${this.simpleName}.$HEADER_SUFFIX" + } + regexRules.filter { !it.override }.find { this.name.matches(it.regex.toRegex()) }?.let { + return@run "./${it.dst}/${this.simpleName}.$HEADER_SUFFIX" + } + if (this is ClassType) { + val parentRules = GeneratorConfig.getSortRules().parent + parentRules.find { this.typeData.parentTypes?.contains(it.parent) == true || this.name == it.parent } + ?.let { + return@run "./${it.dst}/${this.simpleName}.$HEADER_SUFFIX" + } + if (this.parents.isNotEmpty()) { + return@run this.parents[0].path + } + } + if (this is EnumType) { + return@run "./enums/${this.name.replace("::", "/")}.$HEADER_SUFFIX" + } - open fun initIncludeList() {} + notSortedTypes.add(this.name) + return@run "./${name.replace("::", "__")}.$HEADER_SUFFIX" - fun getPath(): String { - return "./${getTopLevelFileType().name.replace("::", "/")}.$HEADER_SUFFIX" +// if (this.name.contains("::")) { +// return@run "./${ +// this.name.replace("::", "/").substringBeforeLast("/", "").toSnakeCase() +// }/$simpleName.$HEADER_SUFFIX" +// } +// return@run "./$name.$HEADER_SUFFIX" + } } + abstract fun generateTypeDefine(): String + + open fun initIncludeList() {} + fun constructInnerTypeList(outerType: BaseType? = null) { this.outerType = outerType innerTypes.addAll( @@ -63,4 +104,16 @@ abstract class BaseType( enum class TypeKind { CLASS, STRUCT, ENUM, UNION, NAMESPACE } + + companion object { + private val logger = KotlinLogging.logger { } + val declareMap by lazy { + runCatching { + File(GeneratorConfig.declareMapPath).readText().let { Json.decodeFromString>(it) } + }.onFailure { + logger.error { "Failed to load declare map, types will be sorted all by rules" } + }.getOrNull() ?: emptyMap() + } + val notSortedTypes = hashSetOf() + } } diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt index bbf98ef..ffdbc8e 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt @@ -1,6 +1,8 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.TypeManager +import com.liteldev.headeroutput.data.TypeData +import com.liteldev.headeroutput.getTopLevelFileType import com.liteldev.headeroutput.relativePathTo open class ClassType( @@ -19,33 +21,33 @@ open class ClassType( } override fun generateTypeDefine(): String { - val sb = StringBuilder( - "class $simpleName ${genParents()}{\n" - ) - if (innerTypes.isNotEmpty()) { - sb.appendLine("public:") - sb.append(generateInnerTypeDefine().replace("\n", "\n ")) - } - sb.append(genAntiReconstruction()) - sb.append(genPublic()) - sb.append(genProtected()) - sb.append(genPrivate()) - sb.append(genProtected(genFunc = false)) - sb.append(genPrivate(genFunc = false)) - sb.appendLine("};") - return sb.toString() + return buildString { + TypeManager.template[name]?.let(this::appendLine) + appendLine("class $simpleName ${genParents()}{") + if (innerTypes.isNotEmpty()) { + appendLine("public:") + append(generateInnerTypeDefine().replace("\n", "\n ")) + } + append(genAntiReconstruction()) + append(genPublic()) + append(genProtected()) + append(genPrivate()) + append(genProtected(genFunc = false)) + append(genPrivate(genFunc = false)) + appendLine("};") + } } override fun initIncludeList() { // not include self, inner type, and types can forward declare collectAllReferencedType().filter { - !it.name.startsWith(this.name + "::") && (it.name.contains("::") || (it as? ClassType)?.isTemplateClass == true) + it.getTopLevelFileType() != this.getTopLevelFileType() && (it.name.contains("::") || (it as? ClassType)?.isTemplateClass == true) } - .map { this.getPath().relativePathTo(it.getPath()) }.let(includeList::addAll) + .map { this.path.relativePathTo(it.path) }.let(includeList::addAll) if (parents.isNotEmpty()) { - includeList.addAll(parents.map { this.getPath().relativePathTo(it.getPath()) }) + includeList.addAll(parents.map { this.path.relativePathTo(it.path) }) } - includeList.remove(this.getPath().relativePathTo(this.getPath())) + includeList.remove(this.path.relativePathTo(this.path)) includeList.remove("") } diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt index bf6bb4a..4f19e63 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt @@ -1,6 +1,6 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.data.TypeData class EnumType(name: String) : BaseType(name, TypeKind.ENUM, TypeData.empty()) { override fun generateTypeDefine(): String { diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt index 00f2c37..ec21f50 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt @@ -1,6 +1,6 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.data.TypeData import com.liteldev.headeroutput.relativePathTo class NamespaceType( @@ -28,9 +28,9 @@ class NamespaceType( collectAllReferencedType() // not include types can forward declare .filter { it.name.contains("::") } - .map { this.getPath().relativePathTo(it.getPath()) } + .map { this.path.relativePathTo(it.path) } .let(includeList::addAll) - includeList.remove(this.getPath().relativePathTo(this.getPath())) + includeList.remove(this.path.relativePathTo(this.path)) includeList.remove("") } } diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/StructType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/StructType.kt index 3260ce0..de718a7 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/StructType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/StructType.kt @@ -1,6 +1,7 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.TypeManager +import com.liteldev.headeroutput.data.TypeData class StructType( name: String, typeData: TypeData, isTemplate: Boolean = false @@ -11,16 +12,18 @@ class StructType( } override fun generateTypeDefine(): String { - val sb = StringBuilder("struct $simpleName {\n") - if (innerTypes.isNotEmpty()) { - sb.appendLine("public:") - sb.append(generateInnerTypeDefine().replace("\n", "\n ")) + return buildString { + TypeManager.template[name]?.let(this::appendLine) + appendLine("struct $simpleName {") + if (innerTypes.isNotEmpty()) { + appendLine("public:") + append(generateInnerTypeDefine().replace("\n", "\n ")) + } + append(genAntiReconstruction()) + append(genPublic()) + append(genProtected()) + append(genPrivate()) + appendLine("};") } - sb.append(genAntiReconstruction()) - sb.append(genPublic()) - sb.append(genProtected()) - sb.append(genPrivate()) - sb.appendLine("};") - return sb.toString() } } diff --git a/src/main/kotlin/com/liteldev/headeroutput/utils.kt b/src/main/kotlin/com/liteldev/headeroutput/utils.kt index 3de52f6..3a34ff9 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/utils.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/utils.kt @@ -18,6 +18,8 @@ fun BaseType.isNamespace() = this.type == BaseType.TypeKind.NAMESPACE fun BaseType.isEnum() = this.type == BaseType.TypeKind.ENUM fun BaseType.getTopLevelFileType(): BaseType { + assert(TypeManager.nestingMap.isNotEmpty()) { "TypeManager.nestingMap is empty" } + outerType ?: return this if (isNamespace()) { return this @@ -34,3 +36,18 @@ fun BaseType.getTopLevelFileType(): BaseType { } return outer } + +fun String.toSnakeCase(): String { + val sb = StringBuilder() + forEachIndexed { index, c -> + if (c.isUpperCase()) { + if (index != 0) { + sb.append("_") + } + sb.append(c.lowercaseChar()) + } else { + sb.append(c) + } + } + return sb.toString() +} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..4e471d0 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/kotlin/TestTemplateAst.kt b/src/test/kotlin/TestTemplateAst.kt new file mode 100644 index 0000000..d0dce49 --- /dev/null +++ b/src/test/kotlin/TestTemplateAst.kt @@ -0,0 +1,9 @@ +import com.liteldev.headeroutput.ast.template.parseType + +fun main() { + val type = + "class std::a, 1, bool, 1.2, false>" + val ast = parseType(type) + println(ast) + println(ast?.genTemplateDeclares()) +}