diff --git a/build.gradle.kts b/build.gradle.kts index f3ac69b..4ab6911 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { - kotlin("jvm") version "1.7.10" - kotlin("plugin.serialization") version "1.7.10" + kotlin("jvm") version "1.9.0" + kotlin("plugin.serialization") version "1.9.0" id("com.github.johnrengelman.shadow") version "7.1.2" java distribution @@ -23,12 +23,12 @@ repositories { dependencies { implementation(kotlin("stdlib")) - implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.10") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0") + 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") - testImplementation(platform("org.junit:junit-bom:5.9.0")) - testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation(platform("org.junit:junit-bom:5.9.2")) + testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") } tasks.test { diff --git a/config.json b/config.json index 2fe881c..5b993f6 100644 --- a/config.json +++ b/config.json @@ -5,8 +5,8 @@ "^$", "^struct .*$", "^class .*$", - ".*(@|::|\\s|<).*", - "(std|gsl|mce|glm|entt|rapidjson|type_info|leveldb)" + ".*(@|\\s|<).*", + "^(?:std|gsl|mce|glm|entt|rapidjson|type_info|leveldb|asio)" ] }, "inclusion": { diff --git a/gradle.properties b/gradle.properties index 29e08e8..7fc6f1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -kotlin.code.style=official \ No newline at end of file +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..84a0b92 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/kotlin/META-INF/MANIFEST.MF b/src/main/kotlin/META-INF/MANIFEST.MF deleted file mode 100644 index 751d381..0000000 --- a/src/main/kotlin/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Main-Class: com.liteldev.headeroutput.HeaderOutput - diff --git a/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt b/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt new file mode 100644 index 0000000..dee20cd --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/HeaderGenerator.kt @@ -0,0 +1,132 @@ +package com.liteldev.headeroutput + +import com.liteldev.headeroutput.config.GeneratorConfig +import com.liteldev.headeroutput.entity.BaseType +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() + + fun generate() { + File(GeneratorConfig.generatePath).mkdirs() + createPredefineFile() + TypeManager.nestingMap.forEach { (_, baseType) -> + generate(baseType) + } + } + + private fun generate(type: BaseType) { + when { + type.isNamespace() -> generateNamespace(type) + else -> { + val sb = StringBuilder() + if (type.outerType != null) { + sb.appendLine("namespace ${type.outerType!!.name} {") + sb.appendLine() + sb.appendLine(type.generateTypeDefine()) + sb.appendLine("};") + } else { + sb.appendLine(type.generateTypeDefine()) + } + val file = File(GeneratorConfig.generatePath, type.getPath()) + 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() + ) + } + } + } + + private fun generateNamespace(type: BaseType) { + assert(type.isNamespace()) { "${type.name} is not namespace" } + + val file = File(GeneratorConfig.generatePath, type.getPath()) + 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() + ) + + if (type.innerTypes.isNotEmpty()) { + File(GeneratorConfig.generatePath, type.getPath().removeSuffix(".$HEADER_SUFFIX")).also { it.mkdirs() } + type.innerTypes.forEach { innerType -> + generate(innerType) + } + } + } + + private fun createPredefineFile() { + val file = File(GeneratorConfig.generatePath, PREDEFINE_FILE_NAME) + file.writeText( + """ +#pragma once + +#define MCAPI __declspec(dllimport) + +#include // STL general algorithms +#include // STL array container +#include // STL bitset container +#include // Character handling functions +#include // C Error codes +#include // C localization library +#include // Common mathematics functions +#include // C++11 Time library +#include // Complex number type +#include // C Standard Input/Output library +#include // General purpose utilities: program control, dynamic memory allocation, random numbers, sort and search +#include // C string handling +#include // C Time library +#include // Wide character type +#include // Wide character classification +#include // STL double ended queue container +#include // Exception handling classes +#include // STL forward list container +#include // File stream classes +#include // STL Function objects +#include // Input/Output manipulators +#include // Base input/output stream classes +#include // Input/Output forward declarations +#include // Standard Input/Output stream objects +#include // Basic input stream classes +#include // Numeric limits +#include // STL linear list container +#include // STL map container +#include // STL unique_ptr, shared_ptr, weak_ptr +#include // STL optional type +#include // Basic output stream classes +#include // STL queue and priority_queue container +#include // STL set and multiset container +#include // String stream classes +#include // STL stack container +#include // Standard exception objects +#include // Stream buffer classes +#include // String class +#include // STL string_view type +#include // STL unordered map container +#include // STL unordered set container +#include // STL utility components +#include // STL dynamic array container + +""".trimIndent() + ) + } +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt b/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt index 090d94d..147bfc6 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/HeaderOutput.kt @@ -1,207 +1,166 @@ package com.liteldev.headeroutput import com.liteldev.headeroutput.config.GeneratorConfig -import com.liteldev.headeroutput.config.MemberTypeData -import com.liteldev.headeroutput.config.TypeData -import com.liteldev.headeroutput.entity.* -import com.liteldev.headeroutput.generate.ClassGenerator -import com.liteldev.headeroutput.generate.NamespaceGenerator -import com.liteldev.headeroutput.generate.StructGenerator +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.entity.ClassType +import com.liteldev.headeroutput.entity.NamespaceType +import com.liteldev.headeroutput.entity.StructType import kotlinx.cli.ArgParser import kotlinx.cli.ArgType import kotlinx.cli.default import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.* import java.io.File - @OptIn(ExperimentalSerializationApi::class) private val json = Json { explicitNulls = false } object HeaderOutput { - - private lateinit var JSON_PATH: String - lateinit var OLD_PATH: String - lateinit var GENERATE_PATH: String - private lateinit var CONFIG_PATH: String - private lateinit var originData: JsonObject - private lateinit var funcListOfTypes: Map - lateinit var generatorConfig: GeneratorConfig - lateinit var realClassNameList: List - lateinit var realStructNameList: List - - val classMap = mutableMapOf() - val structMap = mutableMapOf() - val namespaceMap = mutableMapOf() - val notExistBaseType = mutableSetOf() + private lateinit var classNameList: MutableSet + private lateinit var structNameList: MutableSet + private lateinit var typeDataMap: MutableMap @JvmStatic fun main(args: Array) { if (!readCommandLineArgs(args)) return - loadConfig() + GeneratorConfig.loadConfig() loadOriginData() - loadIdentifier() - loadFuncListOfTypes() - - funcListOfTypes.forEach { (typeName, type) -> - when { - isNameSpace(typeName, type) -> { - namespaceMap[typeName] = NamespaceType(typeName, type).also { - runCatching { - it.readOldExtra() - it.readComments("namespace") - }.onFailure { - println("Warning: $typeName not found in old") - } - } - } - - realStructNameList.contains(typeName) -> { - structMap[typeName] = StructType(typeName, type).also { - runCatching { - it.readOldExtra() - it.readComments("struct") - }.onFailure { - println("Warning: $typeName not found in old") - } - } - } - - else/*realClassNameList.contains(typeName)*/ -> { - classMap[typeName] = ClassType(typeName, type).also { - runCatching { - it.readOldExtra() - it.readComments("class") - }.onFailure { - println("Warning: $typeName not found in old") - } - } - } - } - } - - //link every class - val rootClasses = mutableMapOf() - classMap.values.forEach { classType -> - classType.initIncludeList() - classType.constructLinkedClassMap(rootClasses) - } - - structMap.values.forEach { structType -> - structType.initIncludeList() - } - - namespaceMap.values.forEach { namespaceType -> - namespaceType.initIncludeList() - } - - println("Warning: these class has no information in originData but used by other classes\n$notExistBaseType") - //println(namespaceMap.keys) + loadIdentifiedTypes() + constructTypes() - File(GENERATE_PATH).mkdirs() + TypeManager.initParents() + TypeManager.initReferences() + TypeManager.initNestingMap() + TypeManager.initInclusionList() - ClassGenerator.generate() - StructGenerator.generate() - NamespaceGenerator.generate() - - File(OLD_PATH).listFiles()?.filter { it.isFile }?.forEach { - val origin = it.readText() - if (!origin.contains("#define AUTO_GENERATED")) { - val dest = File(GENERATE_PATH, it.name) - if (dest.isFile) - println("Warning: ${dest.name} is already exist") - it.copyTo(dest, true) - } - } - - val oldFileNames = (File(OLD_PATH).listFiles()?.map { it.name } ?: arrayListOf()).toSet() - val newFileNames = (File(GENERATE_PATH).listFiles()?.map { it.name } ?: arrayListOf()).toSet() - - println("Deleted:\t" + oldFileNames.subtract(newFileNames)) - println("Modified:\t" + oldFileNames.intersect(newFileNames)) - println("Addition:\t" + newFileNames.subtract(oldFileNames)) + HeaderGenerator.generate() } 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 oldPath by parser.option(ArgType.String, "old", "o", "The old header path").default("./old") - val generatePath by parser.option(ArgType.String, "generate", "g", "The generate header files path") + val generatePath by parser.option(ArgType.String, "output-dir", "o", "The header output path") .default("./header") - val jsonPath by parser.option(ArgType.String, "json", "j", "The original data json file path") + val jsonPath by parser.option(ArgType.String, "input", "i", "The original data json file path") .default("./header.json") parser.parse(args) - CONFIG_PATH = configPath - OLD_PATH = oldPath - GENERATE_PATH = generatePath - JSON_PATH = jsonPath - if (!File(CONFIG_PATH).isFile) { + GeneratorConfig.configPath = configPath + GeneratorConfig.generatePath = generatePath + GeneratorConfig.jsonPath = jsonPath + if (!File(GeneratorConfig.configPath).isFile) { println("Invalid config file path") return false } - if (!File(OLD_PATH).isDirectory) { - println("Invalid old header files path") - return false - } - if (!File(GENERATE_PATH).isDirectory) { - try { - File(GENERATE_PATH).mkdirs() - } catch (e: Exception) { + if (!File(GeneratorConfig.generatePath).isDirectory) { + if (!File(GeneratorConfig.generatePath).mkdirs()) { println("Fail to create generate header files path") return false } } - if (!File(JSON_PATH).isFile) { + if (!File(GeneratorConfig.jsonPath).isFile) { println("Invalid original data json file path") return false } return true } - private fun loadConfig() { - println("Loading config...") - val configText = File(this.CONFIG_PATH).readText() - generatorConfig = json.decodeFromString(configText) - } private fun loadOriginData() { println("Loading origin data...") - val configText = File(JSON_PATH).readText() + val configText = File(GeneratorConfig.jsonPath).readText() originData = Json.parseToJsonElement(configText).jsonObject + typeDataMap = originData["classes"]?.jsonObject?.mapValues { entry -> + json.decodeFromJsonElement(entry.value) + }?.toMutableMap() ?: mutableMapOf() + typeDataMap.values.forEach { type -> + var counter = 0 + type.virtual?.forEach { + // 对于没有名字的虚函数,将其标记为未知函数,并且将其名字设置为 __unk_vfn_0, __unk_vfn_1, ... + if (it.name == "" && !it.isUnknownFunction()) + it.symbolType = SymbolNodeType.Unknown + if (it.isUnknownFunction()) { + it.storageClass = StorageClassType.Virtual + it.addFlag(MemberTypeData.FLAG_PTR_CALL) + it.name = "void __unk_vfn_${counter}" + } + counter++ + + } + } } - private fun loadFuncListOfTypes() { - println("Loading func list of types...") - funcListOfTypes = originData["classes"]?.jsonObject?.filter { (k, _) -> - generatorConfig.exclusion.generation.regex.find { k.matches(Regex(it)) } == null - }?.mapValues { entry -> - json.decodeFromJsonElement(entry.value).also { type -> - var counter = 0 - type.virtual?.forEach { memberType -> - run { - if (memberType.name == "" && !memberType.isUnknownFunction()) - memberType.symbolType = SymbolNodeType.Unknown - if (memberType.isUnknownFunction()) { - memberType.storageClass = StorageClassType.Virtual - memberType.addFlag(MemberTypeData.PTR_CALL) - memberType.name = "void __unk_vfn_${counter}" + private fun constructTypes() { + val notIdentifiedTypes = mutableSetOf() + println("Loading types...") + typeDataMap + .filterNot { (k, _) -> GeneratorConfig.isExcludedFromGeneration(k) } + .forEach { (typeName, type) -> + TypeManager.addType( + typeName, + when { + isStruct(typeName) -> StructType(typeName, type) + isClass(typeName) -> ClassType(typeName, type) + isNameSpace(typeName, type) -> NamespaceType(typeName, type) + else -> { + notIdentifiedTypes.add(typeName) + ClassType(typeName, type) } - counter++ } - } + ) } - } ?: mapOf() + println("Warning: can not determine these types' type. Treat them as class type\n$notIdentifiedTypes") } - private fun loadIdentifier() { + private fun loadIdentifiedTypes() { println("Loading identifier...") val identifier = originData["identifier"]?.jsonObject - realClassNameList = - (identifier?.get("class")?.jsonArray).orEmpty().map { it.jsonPrimitive.content } - realStructNameList = - (identifier?.get("struct")?.jsonArray).orEmpty().map { it.jsonPrimitive.content } + classNameList = + (identifier?.get("class")?.jsonArray).orEmpty().map { it.jsonPrimitive.content }.toMutableSet() + structNameList = + (identifier?.get("struct")?.jsonArray).orEmpty().map { it.jsonPrimitive.content }.toMutableSet() + + // check if any type is not identified but derived from other types + val referencedTypes = typeDataMap.values + .flatMap { it.parentTypes.orEmpty() + it.collectReferencedTypes().keys } + .filter { it in originData["classes"]?.jsonObject?.keys.orEmpty() } + .toMutableSet() + .also { + it.removeAll(classNameList) + it.removeAll(structNameList) + } + + if (referencedTypes.isNotEmpty()) { + println( + "Warning: these types are referenced from other types but not identified. Treat them as class type\n$referencedTypes" + ) + classNameList.addAll(referencedTypes) + } } -} \ No newline at end of file + + private fun isNameSpace(typeName: String, typeData: TypeData): Boolean { + if (isStruct(typeName) || isClass(typeName)) + return false + if (listOf( + typeData.privateTypes, + typeData.privateStaticTypes, + typeData.protectedTypes, + typeData.protectedStaticTypes, + typeData.publicStaticTypes, + typeData.virtual, + typeData.vtblEntry + ).any { it != null } + ) return false + return typeData.publicTypes?.none { it.isPtrCall() } == true + } + + private fun isStruct(typeName: String) = structNameList.contains(typeName) + + + private fun isClass(typeName: String) = classNameList.contains(typeName) + +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt b/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt new file mode 100644 index 0000000..32eac5b --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/TypeManager.kt @@ -0,0 +1,116 @@ +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.entity.BaseType.TypeKind +import com.liteldev.headeroutput.entity.ClassType +import com.liteldev.headeroutput.entity.EnumType +import com.liteldev.headeroutput.entity.StructType + +object TypeManager { + private val typeMap = hashMapOf() + + val nestingMap = hashMapOf() + val template = hashSetOf() + + fun addType(fullName: String, type: BaseType) { + typeMap[fullName] = type + } + + fun getType(fullName: String): BaseType? { + return typeMap[fullName] + } + + fun hasType(fullName: String): Boolean { + return typeMap.containsKey(fullName) + } + + fun getAllTypes(): List { + return typeMap.values.toList() + } + + fun initParents() { + typeMap.values.filter { it.isClass() }.forEach { type -> + type as ClassType + type.typeData.parentTypes?.getOrNull(0)?.run { typeMap[this] }?.let { type.parents.add(it) } + /* + fixme: Fix in header generator: recursive parent + type.typeData.parentTypes?.forEach { parent -> + getType(parent)?.let { type.parents.add(it) } + } + */ + } + } + + fun initReferences() { + // copy to avoid ConcurrentModificationException due to enum is being added in `collectSelfReferencedType` + typeMap.values.toMutableSet().forEach { type -> + type.collectSelfReferencedType() + } + } + + fun initInclusionList() { + typeMap.forEach { (_, type) -> + type.initIncludeList() + } + } + + fun initNestingMap() { + typeMap.filter { !it.key.contains("::") } + .forEach { (key, value) -> + nestingMap[key] = value + value.constructInnerTypeList() + } + // 收集所有形成嵌套关系的类,检查哪些类没有被收集到 + val allNestingType = nestingMap.values.flatMap { it.innerTypes }.toMutableSet() + 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 }}") + // generate a dummy class for each not nesting class + notNestingType.forEach { + val parentName = it.name.substringBeforeLast("::") + if (!hasType(parentName)) { + createDummyClass(parentName) + } + val parentType = getType(parentName) ?: return@forEach + parentType.innerTypes.add(it) + it.outerType = parentType + } + } + + /** + * @param name: the type's name to be created, must be an inner type + */ + fun createDummyClass(name: String, type: TypeKind = TypeKind.CLASS): BaseType? { + assert(!hasType(name)) { "type $name already exists" } + + if (GeneratorConfig.isExcludedFromGeneration(name)) { + return null + } + + val 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) + else -> throw IllegalArgumentException("type $type is not supported") + } + + if (name.contains("::")) { + val parentName = name.substringBeforeLast("::") + if (!hasType(parentName)) { + createDummyClass(parentName) + } + + val parentType = getType(parentName) ?: return null + parentType.innerTypes.add(dummyClass) + dummyClass.outerType = parentType + } else { + nestingMap[name] = dummyClass + } + + addType(name, dummyClass) + return dummyClass + } +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt index 9eee731..12620bd 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfig.kt @@ -1,19 +1,32 @@ package com.liteldev.headeroutput.config -import kotlinx.serialization.Serializable +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import java.io.File -@Serializable -data class GeneratorConfig( - var exclusion: Exclusion -) { - @Serializable - data class Exclusion( - var generation: Generation, var inclusion: Inclusion - ) { - @Serializable - data class Generation(var regex: MutableList) +object GeneratorConfig { + @OptIn(ExperimentalSerializationApi::class) + private val json = Json { explicitNulls = false } - @Serializable - data class Inclusion(var regex: MutableList) + lateinit var jsonPath: String + lateinit var generatePath: String + lateinit var configPath: String + lateinit var generationExcludeRegexList: MutableList + lateinit var inclusionExcludeRegexList: MutableList + + private lateinit var generatorConfigData: GeneratorConfigData + + + fun loadConfig() { + println("Loading config...") + val configText = File(configPath).readText() + generatorConfigData = json.decodeFromString(configText) + generationExcludeRegexList = generatorConfigData.exclusion.generation.regex + inclusionExcludeRegexList = generatorConfigData.exclusion.inclusion.regex + } + + fun isExcludedFromGeneration(name: String): Boolean { + return generationExcludeRegexList.any { name.matches(it.toRegex()) } } } diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfigData.kt b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfigData.kt new file mode 100644 index 0000000..4bef3c3 --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/config/GeneratorConfigData.kt @@ -0,0 +1,19 @@ +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/TypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/config/TypeData.kt deleted file mode 100644 index 0f03bd2..0000000 --- a/src/main/kotlin/com/liteldev/headeroutput/config/TypeData.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.liteldev.headeroutput.config - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class TypeData( - @SerialName("child_types") val childTypes: List?, - @SerialName("parent_types") val parentTypes: List?, - @SerialName("private") val privateTypes: List?, - @SerialName("private.static") val privateStaticTypes: List?, - @SerialName("protected") val protectedTypes: List?, - @SerialName("protected.static") val protectedStaticTypes: List?, - @SerialName("public") val publicTypes: List?, - @SerialName("public.static") val publicStaticTypes: List?, - val virtual: List?, - @SerialName("virtual.unordered") val virtualUnordered: MutableList?, - @SerialName("vtbl_entry") val vtblEntry: List? -) diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/EnumType.kt b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/EnumType.kt new file mode 100644 index 0000000..4ca3f68 --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/EnumType.kt @@ -0,0 +1,110 @@ +package com.liteldev.headeroutput.config.origindata + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.reflect.KClass + +@Suppress("unused") +@Serializable(with = SymbolNodeType.Companion::class) +enum class SymbolNodeType(override val value: Int) : IntEnumBase { + Normal(0), + Constructor(1), + Destructor(2), + Operator(3), + StaticVar(4), + Unknown(5); + + companion object : IntEnumSerializer(SymbolNodeType::class.simpleName!!, SymbolNodeType::class) +} + +@Suppress("unused") +@Serializable(with = AccessType.Companion::class) +enum class AccessType(override val value: Int) : IntEnumBase { + Public(0), + Protected(1), + Private(2), + None(3); + + companion object : IntEnumSerializer(AccessType::class.simpleName!!, AccessType::class) +} + +/** StorageClassType 表示一个Symbol的类属性 + * 以下为其可能的取值 + * + * static: 静态成员,全局实例,无实例绑定 + * + * virtual: 虚拟对象,由虚表访问 + * + * none: 标准成员,全局实例,绑定到实例的访问 + * */ +@Suppress("unused") +@Serializable(with = StorageClassType.Companion::class) +enum class StorageClassType(override val value: Int) : IntEnumBase { + Static(0), + Virtual(1), + None(2); + + companion object : + IntEnumSerializer(StorageClassType::class.simpleName!!, StorageClassType::class) +} + +@Suppress("unused") +@Serializable(with = VarSymbolType.Companion::class) +enum class VarSymbolType(override val value: Int) : IntEnumBase { + Unknown(0), + Md5Symbol(1), + PrimitiveType(2), + FunctionSignature(3), + Identifier(4), + NamedIdentifier(5), + VcallThunkIdentifier(6), + LocalStaticGuardIdentifier(7), + IntrinsicFunctionIdentifier(8), + ConversionOperatorIdentifier(9), + DynamicStructorIdentifier(10), + StructorIdentifier(11), + LiteralOperatorIdentifier(12), + ThunkSignature(13), + PointerType(14), + TagType(15), + ArrayType(16), + Custom(17), + IntrinsicType(18), + NodeArray(19), + QualifiedName(20), + TemplateParameterReference(21), + EncodedStringLiteral(22), + IntegerLiteral(23), + RttiBaseClassDescriptor(24), + LocalStaticGuardVariable(25), + FunctionSymbol(26), + VariableSymbol(27), + SpecialTableSymbol(28); + + companion object : IntEnumSerializer(VarSymbolType::class.simpleName!!, VarSymbolType::class) +} + +interface IntEnumBase { + val value: Int +} + +open class IntEnumSerializer(serialName: String, private val clazz: KClass) : + KSerializer where E : Enum, E : IntEnumBase { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.INT) + + override fun serialize(encoder: Encoder, value: E) { + encoder.encodeInt(value.value) + } + + override fun deserialize(decoder: Decoder): E { + val decodeInt = decoder.decodeInt() + return clazz.java.enumConstants.firstOrNull { it.value == decodeInt } + ?: throw IllegalArgumentException("No enum constant found for value $decodeInt") + } +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/MemberTypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/MemberTypeData.kt similarity index 87% rename from src/main/kotlin/com/liteldev/headeroutput/config/MemberTypeData.kt rename to src/main/kotlin/com/liteldev/headeroutput/config/origindata/MemberTypeData.kt index d571830..ce8e331 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/MemberTypeData.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/MemberTypeData.kt @@ -1,9 +1,6 @@ -package com.liteldev.headeroutput.config +package com.liteldev.headeroutput.config.origindata import com.liteldev.headeroutput.appendSpace -import com.liteldev.headeroutput.entity.AccessType -import com.liteldev.headeroutput.entity.StorageClassType -import com.liteldev.headeroutput.entity.SymbolNodeType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -26,7 +23,6 @@ data class MemberTypeData( fun genFuncString( namespace: Boolean = false, useFakeSymbol: Boolean = false, - comment: String = "", vIndex: Int = -1 ): String { val symbol = @@ -42,7 +38,6 @@ data class MemberTypeData( if (symbol.isNotEmpty()) { ret.appendSpace(START_BLANK_SPACE + 1).append("* @symbol ${symbol.replace("@", "\\@")}\n") } - if (comment.isNotEmpty()) ret.appendSpace(START_BLANK_SPACE + 1).append("*\n").append(comment) ret.appendSpace(START_BLANK_SPACE + 1).append("*/\n") ret.appendSpace(START_BLANK_SPACE) @@ -90,8 +85,6 @@ data class MemberTypeData( ) } - fun isConst() = flags and CONST == CONST - fun isConstructor() = symbolType == SymbolNodeType.Constructor fun isDestructor() = symbolType == SymbolNodeType.Destructor @@ -102,9 +95,10 @@ data class MemberTypeData( fun isStaticGlobalVariable() = symbolType == SymbolNodeType.StaticVar - fun isPtrCall() = flags and PTR_CALL == PTR_CALL + fun isConst() = hasFlag(FLAG_CONST) + fun isPtrCall() = hasFlag(FLAG_PTR_CALL) - fun isPureCall() = flags and PURE_CALL == PURE_CALL + fun isPureCall() = hasFlag(FLAG_PURE_CALL) fun addFlag(flag: Int) { if (flags and flag != flag) flags += flag @@ -114,18 +108,18 @@ data class MemberTypeData( if (flags and flag == flag) flags -= flag } + fun hasFlag(flag: Int) = flags and flag == flag + fun isVirtual() = storageClass == StorageClassType.Virtual companion object { //[0] const //[1] __ptr64 spec //[2] isPureCall - const val CONST = 1 shl 0 - const val PTR_CALL = 1 shl 1 - const val PURE_CALL = 1 shl 2 + const val FLAG_CONST = 1 shl 0 + const val FLAG_PTR_CALL = 1 shl 1 + const val FLAG_PURE_CALL = 1 shl 2 const val START_BLANK_SPACE = 4 } - - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/origindata/TypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/TypeData.kt new file mode 100644 index 0000000..429aa0a --- /dev/null +++ b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/TypeData.kt @@ -0,0 +1,61 @@ +package com.liteldev.headeroutput.config.origindata + +import com.liteldev.headeroutput.TypeManager +import com.liteldev.headeroutput.entity.BaseType +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.util.* + +@Serializable +data class TypeData( + @SerialName("child_types") val childTypes: List?, + @SerialName("parent_types") val parentTypes: List?, + @SerialName("private") val privateTypes: List?, + @SerialName("private.static") val privateStaticTypes: List?, + @SerialName("protected") val protectedTypes: List?, + @SerialName("protected.static") val protectedStaticTypes: List?, + @SerialName("public") val publicTypes: List?, + @SerialName("public.static") val publicStaticTypes: List?, + val virtual: List?, + @SerialName("virtual.unordered") val virtualUnordered: MutableList?, + @SerialName("vtbl_entry") val vtblEntry: List? +) { + + fun collectAllFunction() = listOfNotNull( + privateTypes, + privateStaticTypes, + protectedTypes, + protectedStaticTypes, + publicTypes, + publicStaticTypes, + virtual, + virtualUnordered, + ).flatten() + + fun collectInstanceFunction() = listOfNotNull( + privateTypes, + protectedTypes, + publicTypes, + virtual, + virtualUnordered, + ).flatten() + + 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 + + + fun collectReferencedTypes(): Map { + return collectAllFunction().flatMap { memberType -> + (memberType.params?.mapNotNull { it.Name } ?: emptyList()) + listOfNotNull(memberType.valType.Name) + }.flatMap(::matchTypes).toMap() + } + + companion object { + val typeMatchRegex = Regex("(struct|class|enum)\\s+([a-zA-Z0-9_]+(?:::[a-zA-Z0-9_]+)*)") + + fun empty(): TypeData { + return TypeData(null, null, null, null, null, null, null, null, null, null, null) + } + } +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/config/VariableTypeData.kt b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/VariableTypeData.kt similarity index 78% rename from src/main/kotlin/com/liteldev/headeroutput/config/VariableTypeData.kt rename to src/main/kotlin/com/liteldev/headeroutput/config/origindata/VariableTypeData.kt index ccf4b16..c61d506 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/config/VariableTypeData.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/config/origindata/VariableTypeData.kt @@ -1,6 +1,5 @@ -package com.liteldev.headeroutput.config +package com.liteldev.headeroutput.config.origindata -import com.liteldev.headeroutput.entity.VarSymbolType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -16,9 +15,7 @@ data class VariableTypeData( other as VariableTypeData if (Name != other.Name) return false - if (Type != other.Type) return false - - return true + return Type == other.Type } override fun hashCode(): Int { @@ -26,4 +23,4 @@ data class VariableTypeData( result = 31 * result + Type.value return result } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt index 23e733d..338fbef 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/BaseType.kt @@ -1,130 +1,66 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.HeaderOutput -import com.liteldev.headeroutput.config.MemberTypeData -import com.liteldev.headeroutput.config.TypeData -import com.liteldev.headeroutput.parent -import com.liteldev.headeroutput.relativePath -import com.liteldev.headeroutput.substring -import java.io.File +import com.liteldev.headeroutput.HeaderGenerator.HEADER_SUFFIX +import com.liteldev.headeroutput.TypeManager +import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.getTopLevelFileType +import java.util.* abstract class BaseType( var name: String, + type: TypeKind, var typeData: TypeData, - val includeList: MutableSet = mutableSetOf(), - var beforeExtra: String = "", - var afterExtra: String = "", - var comment: String = "", - private var memberComments: MutableMap = mutableMapOf(), ) { + var type: TypeKind = type + protected set - abstract fun getPath(): String + var outerType: BaseType? = null - fun readOldExtra() { - val origin = File(HeaderOutput.OLD_PATH, getPath()).readText().replace("\r\n", "\n") - beforeExtra = origin.substring( - "#define BEFORE_EXTRA\n", - "\n#undef BEFORE_EXTRA" - ) - afterExtra = origin.substring( - "#define AFTER_EXTRA\n", - "\n#undef AFTER_EXTRA" - ) - } + val innerTypes: MutableSet = mutableSetOf() + val referenceTypes: MutableSet = mutableSetOf() + val includeList: MutableSet = mutableSetOf() - fun readComments(flag: String) { - val regex = Regex("/\\*\\*\n([\\S\\s]+)\\*/\n$flag", RegexOption.MULTILINE) - var origin = File(HeaderOutput.OLD_PATH, getPath()).readText().replace("\r\n", "\n") - origin = origin.substring("#undef BEFORE_EXTRA\n", "\n#define AFTER_EXTRA") + - origin.substringAfter("#undef AFTER_EXTRA\n") - comment = regex.find(origin)?.groupValues?.get(0)?.substring("", "\n$flag") ?: "" - val classBody = origin.substring("$flag $name ", "\n};") - var inComment = false - val comment = StringBuilder() - var symbol: String? = null - classBody.lines().forEach { - when { - it.contains("/*") -> inComment = true - it.contains("*/") -> { - inComment = false - symbol?.let { s -> - memberComments[s] = comment.toString() - symbol = null - } - comment.clear() - } + val simpleName = name.substringAfterLast("::") + val fullEscapeName = name.replace("::", "_") + val fullUpperEscapeName = fullEscapeName.uppercase(Locale.getDefault()) - inComment -> { - when { - it.contains("@symbol") -> symbol = it.substringAfter("@symbol ", "").trim().replace("\\@", "@") - it.contains("@vftbl") -> {} - it.contains("@hash") -> {} - else -> comment.append(it).append("\n") - } - } - } - } - } + abstract fun generateTypeDefine(): String + + open fun initIncludeList() {} - fun getCommentOf(member: MemberTypeData): String { - val symbol = member.symbol - return this.memberComments[symbol] ?: "" + fun getPath(): String { + return "./${getTopLevelFileType().name.replace("::", "/")}.$HEADER_SUFFIX" } - private fun readIncludeClassFromMembers(list: List): Set { - val retList = mutableSetOf() - list.forEach { memberType -> - memberType.params?.forEach { param -> - param.Name?.let { it -> - Regex("(\\w+)::").findAll(it).forEach { - retList.add(it.groupValues[1]) - } - } + fun constructInnerTypeList(outerType: BaseType? = null) { + this.outerType = outerType + innerTypes.addAll( + TypeManager.getAllTypes().filter { + it.name.startsWith(this.name + "::") && !it.name.substring(this.name.length + 2).contains("::") + }.onEach { + it.constructInnerTypeList(this) } - Regex("(\\w+)::").findAll(memberType.valType.Name ?: "").forEach { - retList.add(it.groupValues[1]) - } - } - return retList.filter { inclusion -> - HeaderOutput.generatorConfig.exclusion.inclusion.regex.find { - inclusion.matches( - Regex(it) - ) - } == null - } - .toSet() + ) } - private fun readList(list: List) = readIncludeClassFromMembers(list).filter { - val ret = - HeaderOutput.classMap.contains(it) || HeaderOutput.structMap.contains(it) || HeaderOutput.namespaceMap.contains( - it - ) - if (!ret) { - HeaderOutput.notExistBaseType.add(it) - } - ret - }.map { HeaderOutput.classMap[it] ?: HeaderOutput.structMap[it] ?: HeaderOutput.namespaceMap[it]!! } - .let(includeList::addAll) + protected fun collectAllReferencedType(): Set = + referenceTypes + innerTypes.flatMap { it.collectAllReferencedType() } - fun getGlobalHeaderPath() = "llapi/Global.h" + fun generateInnerTypeDefine(): String { + val generatedTypes = innerTypes.joinToString(separator = "\n") { it.generateTypeDefine() } + return if (generatedTypes.isNotBlank()) "\n$generatedTypes" else "" + } - fun getRelativeInclusions(): String { - val include = StringBuilder() - includeList.forEach { - include.appendLine("#include \"${(getPath().parent()).relativePath(it.getPath())}\"") + fun collectSelfReferencedType() { + typeData.collectReferencedTypes().forEach { (name, kind) -> + if (!TypeManager.hasType(name)) { + TypeManager.createDummyClass(name, kind) + } + TypeManager.getType(name)?.let(referenceTypes::add) } - return include.toString() } - open fun initIncludeList() { - typeData.virtual?.let(::readList) - typeData.publicTypes?.let(::readList) - typeData.publicStaticTypes?.let(::readList) - typeData.protectedTypes?.let(::readList) - typeData.protectedStaticTypes?.let(::readList) - typeData.privateTypes?.let(::readList) - typeData.privateStaticTypes?.let(::readList) - includeList.removeIf { it === this } + enum class TypeKind { + CLASS, STRUCT, ENUM, UNION, NAMESPACE } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt index cec6731..bbf98ef 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/ClassType.kt @@ -1,16 +1,15 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.HeaderOutput -import com.liteldev.headeroutput.config.MemberTypeData -import com.liteldev.headeroutput.config.TypeData +import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.relativePathTo open class ClassType( - name: String, typeData: TypeData, - var parent: ClassType? = null, - private val children: MutableMap = mutableMapOf(), -) : BaseType(name, typeData) { + name: String, typeData: TypeData, private val isTemplateClass: Boolean = false, +) : BaseType(name, TypeKind.CLASS, typeData) { - // TODO: Fix in header generator + val parents = arrayListOf() + + // fixme: Fix in header generator init { typeData.virtual?.forEach { virtual -> typeData.virtualUnordered?.removeIf { unordered -> @@ -19,77 +18,85 @@ open class ClassType( } } - override fun getPath(): String { - return "./$name.hpp" - /*if (parent == null) { - "./$name" - } else { - parent!!.getPath() + "/" + name - }*/ - } - - override fun hashCode(): Int { - return name.hashCode() + 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() } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ClassType - - if (name != other.name) return false - if (typeData != other.typeData) return false - - return true + 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) + } + .map { this.getPath().relativePathTo(it.getPath()) }.let(includeList::addAll) + if (parents.isNotEmpty()) { + includeList.addAll(parents.map { this.getPath().relativePathTo(it.getPath()) }) + } + includeList.remove(this.getPath().relativePathTo(this.getPath())) + includeList.remove("") } - fun constructLinkedClassMap(rootClasses: MutableMap) { - typeData.parentTypes?.also { parentNames -> - parent = HeaderOutput.classMap[parentNames[0]]?.also { - it.children[name] = this - } - } ?: run { rootClasses[name] = this } - parent?.let(includeList::add) + fun genParents(): String { + if (parents.isEmpty()) { + return "" + } + val sb = StringBuilder(": ") + parents.joinToString(", ") { "public ${it.name}" }.let(sb::append) + sb.append(" ") + return sb.toString() } - open fun genAntiReconstruction(): String { - val public = arrayListOf() - typeData.virtual?.let(public::addAll) - typeData.publicTypes?.let(public::addAll) - typeData.protectedTypes?.let(public::addAll) - typeData.privateTypes?.let(public::addAll) - public.filter { it.isConstructor() || (it.isOperator() && it.name == "operator=") } - .let(public::addAll) - val genOperator = public.find { - it.isOperator() && it.params?.run { - size == 1 && this[0].Name == "class $name const &" - } == true && it.valType.Name == "class $name &" - } == null - val genEmptyParamConstructor = public.find { it.name == name && it.params?.isEmpty() ?: true } == null - val genMoveConstructor = public.find { - it.name == name && it.params?.run { - size == 1 && this[0].Name == "class $name const &" + fun genAntiReconstruction(): String { + val classType = if (this.type == TypeKind.STRUCT) "struct" else "class" + val public = typeData.collectInstanceFunction() + .filter { it.isConstructor() || (it.isOperator() && it.name == "operator=") } + val genOperator = public.none { + it.isOperator() && it.params?.let { params -> + params.size == 1 && params[0].Name == "$classType $name const &" + } == true && it.valType.Name == "$classType $name &" + } + val genEmptyParamConstructor = public.none { it.name == simpleName && it.params?.isEmpty() ?: true } + val genMoveConstructor = public.none { + it.name == simpleName && it.params?.let { params -> + params.size == 1 && params[0].Name == "$classType $name const &" } == true - } == null - val sb = StringBuilder() - if (genOperator || genEmptyParamConstructor || genMoveConstructor) { - sb.appendLine() - sb.appendLine("#ifndef DISABLE_CONSTRUCTOR_PREVENTION_${name.uppercase()}") - sb.appendLine("public:") - if (genOperator) { - sb.appendLine(" class $name& operator=(class $name const &) = delete;") - } - if (genMoveConstructor) { - sb.appendLine(" $name(class $name const &) = delete;") - } - if (genEmptyParamConstructor) { - sb.appendLine(" $name() = delete;") - } - sb.appendLine("#endif") } - sb.appendLine() - return sb.toString() + return if (!genOperator && !genEmptyParamConstructor && !genMoveConstructor) { + "\n" + } else + StringBuilder( + """ + +#ifndef DISABLE_CONSTRUCTOR_PREVENTION_$fullUpperEscapeName +public: + + """.trimIndent() + ).apply { + if (genOperator) { + appendLine(" $simpleName& operator=($simpleName const &) = delete;") + } + if (genMoveConstructor) { + appendLine(" $simpleName($simpleName const &) = delete;") + } + if (genEmptyParamConstructor) { + appendLine(" $simpleName() = delete;") + } + appendLine("#endif") + appendLine() + }.toString() } fun genPublic(): String { @@ -98,16 +105,15 @@ open class ClassType( var counter = 0 typeData.virtual?.forEach { if (it.namespace.isEmpty() || it.namespace == name) - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it), vIndex = counter)) + sb.appendLine(it.genFuncString(vIndex = counter)) counter++ } if (typeData.virtualUnordered?.isNotEmpty() == true) { - sb.appendLine("#ifdef ENABLE_VIRTUAL_FAKESYMBOL_${name.uppercase()}") + sb.appendLine("#ifdef ENABLE_VIRTUAL_FAKESYMBOL_${fullUpperEscapeName}") typeData.virtualUnordered?.sortedBy { it.name }?.forEach { sb.appendLine( it.genFuncString( - comment = getCommentOf(it), useFakeSymbol = true ) ) @@ -116,10 +122,10 @@ open class ClassType( } typeData.publicTypes?.sortedBy { it.name }?.forEach { - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString()) } typeData.publicStaticTypes?.sortedBy { it.name }?.forEach { - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString()) } if (sb.equals("public:\n")) return "" @@ -141,11 +147,11 @@ open class ClassType( sb.appendLine("protected:") typeData.protectedTypes?.sortedBy { it.name }?.forEach { if ((genFunc && !it.isStaticGlobalVariable()) || (!genFunc && it.isStaticGlobalVariable())) - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString()) } typeData.protectedStaticTypes?.sortedBy { it.name }?.forEach { if ((genFunc && !it.isStaticGlobalVariable()) || (!genFunc && it.isStaticGlobalVariable())) - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString()) } if (sb.equals("protected:\n") || sb.equals("//protected:\n")) return "" @@ -155,9 +161,7 @@ open class ClassType( } fun genPrivate(genFunc: Boolean = true): String { - if ((typeData.privateTypes == null || typeData.privateTypes?.isEmpty() == true) - && (typeData.privateStaticTypes == null || typeData.privateStaticTypes?.isEmpty() == true) - ) { + if ((typeData.privateTypes?.isEmpty() != false) && (typeData.privateStaticTypes?.isEmpty() != false)) { return "" } val sb = StringBuilder() @@ -167,11 +171,11 @@ open class ClassType( sb.appendLine("private:") typeData.privateTypes?.sortedBy { it.name }?.forEach { if ((genFunc && !it.isStaticGlobalVariable()) || (!genFunc && it.isStaticGlobalVariable())) - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString()) } typeData.privateStaticTypes?.sortedBy { it.name }?.forEach { if ((genFunc && !it.isStaticGlobalVariable()) || (!genFunc && it.isStaticGlobalVariable())) - sb.appendLine(it.genFuncString(comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString()) } if (sb.equals("private:\n") || sb.equals("//private:\n")) return "" diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt index 3192003..bf6bb4a 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/EnumType.kt @@ -1,110 +1,10 @@ package com.liteldev.headeroutput.entity -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlin.reflect.KClass +import com.liteldev.headeroutput.config.origindata.TypeData -@Suppress("unused") -@Serializable(with = SymbolNodeType.Companion::class) -enum class SymbolNodeType(override val value: Int) : IntEnumBase { - Normal(0), - Constructor(1), - Destructor(2), - Operator(3), - StaticVar(4), - Unknown(5); - - companion object : IntEnumSerializer(SymbolNodeType::class.simpleName!!, SymbolNodeType::class) -} - -@Suppress("unused") -@Serializable(with = AccessType.Companion::class) -enum class AccessType(override val value: Int) : IntEnumBase { - Public(0), - Protected(1), - Private(2), - None(3); - - companion object : IntEnumSerializer(AccessType::class.simpleName!!, AccessType::class) -} - -/** StorageClassType 表示一个Symbol的类属性 - * 以下为其可能的取值 - * - * static: 静态成员,全局实例,无实例绑定 - * - * virtual: 虚拟对象,由虚表访问 - * - * none: 标准成员,全局实例,绑定到实例的访问 - * */ -@Suppress("unused") -@Serializable(with = StorageClassType.Companion::class) -enum class StorageClassType(override val value: Int) : IntEnumBase { - Static(0), - Virtual(1), - None(2); - - companion object : - IntEnumSerializer(StorageClassType::class.simpleName!!, StorageClassType::class) -} - -@Suppress("unused") -@Serializable(with = VarSymbolType.Companion::class) -enum class VarSymbolType(override val value: Int) : IntEnumBase { - Unknown(0), - Md5Symbol(1), - PrimitiveType(2), - FunctionSignature(3), - Identifier(4), - NamedIdentifier(5), - VcallThunkIdentifier(6), - LocalStaticGuardIdentifier(7), - IntrinsicFunctionIdentifier(8), - ConversionOperatorIdentifier(9), - DynamicStructorIdentifier(10), - StructorIdentifier(11), - LiteralOperatorIdentifier(12), - ThunkSignature(13), - PointerType(14), - TagType(15), - ArrayType(16), - Custom(17), - IntrinsicType(18), - NodeArray(19), - QualifiedName(20), - TemplateParameterReference(21), - EncodedStringLiteral(22), - IntegerLiteral(23), - RttiBaseClassDescriptor(24), - LocalStaticGuardVariable(25), - FunctionSymbol(26), - VariableSymbol(27), - SpecialTableSymbol(28); - - companion object : IntEnumSerializer(VarSymbolType::class.simpleName!!, VarSymbolType::class) -} - -interface IntEnumBase { - val value: Int -} - -open class IntEnumSerializer(serialName: String, private val clazz: KClass) : - KSerializer where E : Enum, E : IntEnumBase { - - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.INT) - - override fun serialize(encoder: Encoder, value: E) { - encoder.encodeInt(value.value) +class EnumType(name: String) : BaseType(name, TypeKind.ENUM, TypeData.empty()) { + override fun generateTypeDefine(): String { + return "enum class $simpleName {};\n" } - override fun deserialize(decoder: Decoder): E { - val decodeInt = decoder.decodeInt() - return clazz.java.enumConstants.firstOrNull { it.value == decodeInt } - ?: throw IllegalArgumentException("No enum constant found for value $decodeInt") - } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt b/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt index 51613ac..00f2c37 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/NamespaceType.kt @@ -1,19 +1,36 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.config.TypeData +import com.liteldev.headeroutput.config.origindata.TypeData +import com.liteldev.headeroutput.relativePathTo class NamespaceType( name: String, typeData: TypeData -) : BaseType(name, typeData) { - override fun getPath(): String { - return "./$name.hpp" - } +) : BaseType(name, TypeKind.NAMESPACE, typeData) { fun genPublic(): String { val sb = StringBuilder() typeData.publicTypes?.sortedBy { it.name }?.forEach { - sb.appendLine(it.genFuncString(namespace = true, comment = this.getCommentOf(it))) + sb.appendLine(it.genFuncString(namespace = true)) } + sb.appendLine() return sb.toString() } -} \ No newline at end of file + + override fun generateTypeDefine(): String { + val sb = StringBuilder() + sb.append("namespace $name {\n") + sb.append(genPublic()) + sb.appendLine("};") + return sb.toString() + } + + override fun initIncludeList() { + collectAllReferencedType() + // not include types can forward declare + .filter { it.name.contains("::") } + .map { this.getPath().relativePathTo(it.getPath()) } + .let(includeList::addAll) + includeList.remove(this.getPath().relativePathTo(this.getPath())) + 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 0913948..3260ce0 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/entity/StructType.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/entity/StructType.kt @@ -1,48 +1,26 @@ package com.liteldev.headeroutput.entity -import com.liteldev.headeroutput.config.MemberTypeData -import com.liteldev.headeroutput.config.TypeData +import com.liteldev.headeroutput.config.origindata.TypeData class StructType( - name: String, typeData: TypeData, -) : ClassType(name, typeData) { - override fun getPath(): String { - return "./$name.hpp" + name: String, typeData: TypeData, isTemplate: Boolean = false +) : ClassType(name, typeData, isTemplate) { + + init { + type = TypeKind.STRUCT } - override fun genAntiReconstruction(): String { - val public = arrayListOf() - typeData.publicTypes?.filter { it.isConstructor() || (it.isOperator() && it.name == "operator=") } - ?.let(public::addAll) - val genOperator = public.find { - it.isOperator() && it.params?.run { - size == 1 && this[0].Name == "struct $name const &" - } == true && it.valType.Name == "struct $name &" - } == null - val genEmptyParamConstructor = public.find { it.name == name && it.params?.isEmpty() ?: true } == null - val genMoveConstructor = public.find { - it.name == name && it.params?.run { - size == 1 && this[0].Name == "struct $name const &" - } == true - } == null - val sb = StringBuilder() - if (genOperator || genEmptyParamConstructor || genMoveConstructor) { - sb.appendLine() - sb.appendLine("#ifndef DISABLE_CONSTRUCTOR_PREVENTION_${name.uppercase()}") + override fun generateTypeDefine(): String { + val sb = StringBuilder("struct $simpleName {\n") + if (innerTypes.isNotEmpty()) { sb.appendLine("public:") - if (genOperator) { - sb.appendLine(" struct $name& operator=(struct $name const &) = delete;") - } - if (genMoveConstructor) { - sb.appendLine(" $name(struct $name const &) = delete;") - } - if (genEmptyParamConstructor) { - sb.appendLine(" $name() = delete;") - } - sb.appendLine("#endif") + sb.append(generateInnerTypeDefine().replace("\n", "\n ")) } - sb.appendLine() + sb.append(genAntiReconstruction()) + sb.append(genPublic()) + sb.append(genProtected()) + sb.append(genPrivate()) + sb.appendLine("};") return sb.toString() } - -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/liteldev/headeroutput/generate/ClassGenerator.kt b/src/main/kotlin/com/liteldev/headeroutput/generate/ClassGenerator.kt deleted file mode 100644 index 191380b..0000000 --- a/src/main/kotlin/com/liteldev/headeroutput/generate/ClassGenerator.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.liteldev.headeroutput.generate - -import com.liteldev.headeroutput.HeaderOutput -import java.io.File - -object ClassGenerator { - - fun generate() { - HeaderOutput.classMap.forEach { (name, classType) -> - val hpp = File(HeaderOutput.GENERATE_PATH, classType.getPath()) - hpp.writeText( - """ -/** - * @file $name.hpp - * - */ -#pragma once -#define AUTO_GENERATED -#include "${classType.getGlobalHeaderPath()}" -${classType.getRelativeInclusions()} -#define BEFORE_EXTRA -${classType.beforeExtra} -#undef BEFORE_EXTRA - -${classType.comment} -class $name ${run { if (classType.parent != null) ": public ${classType.parent!!.name} " else "" }}{ - -#define AFTER_EXTRA -${classType.afterExtra} -#undef AFTER_EXTRA -""".trimIndent() - ) - hpp.appendText(classType.genAntiReconstruction()) - hpp.appendText(classType.genPublic()) - hpp.appendText(classType.genProtected()) - hpp.appendText(classType.genPrivate()) - hpp.appendText(classType.genProtected(genFunc = false)) - hpp.appendText(classType.genPrivate(genFunc = false)) - hpp.appendText("};\n") - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/liteldev/headeroutput/generate/NamespaceGenerator.kt b/src/main/kotlin/com/liteldev/headeroutput/generate/NamespaceGenerator.kt deleted file mode 100644 index 9123fa6..0000000 --- a/src/main/kotlin/com/liteldev/headeroutput/generate/NamespaceGenerator.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.liteldev.headeroutput.generate - -import com.liteldev.headeroutput.HeaderOutput -import java.io.File - -object NamespaceGenerator { - - fun generate() { - HeaderOutput.namespaceMap.forEach { (name, namespaceType) -> - val file = File(HeaderOutput.GENERATE_PATH, namespaceType.getPath()) - file.writeText( - """ -/** - * @file $name.hpp - * - */ -#pragma once -#define AUTO_GENERATED -#include "${namespaceType.getGlobalHeaderPath()}" -${namespaceType.getRelativeInclusions()} -#define BEFORE_EXTRA -${namespaceType.beforeExtra} -#undef BEFORE_EXTRA - -${namespaceType.comment} -namespace $name { - -#define AFTER_EXTRA -${namespaceType.afterExtra} -#undef AFTER_EXTRA -${namespaceType.genPublic()} -}; -""".trimIndent() - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/liteldev/headeroutput/generate/StructGenerator.kt b/src/main/kotlin/com/liteldev/headeroutput/generate/StructGenerator.kt deleted file mode 100644 index 5ca28d2..0000000 --- a/src/main/kotlin/com/liteldev/headeroutput/generate/StructGenerator.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.liteldev.headeroutput.generate - -import com.liteldev.headeroutput.HeaderOutput -import java.io.File - -object StructGenerator { - - fun generate() { - HeaderOutput.structMap.forEach { (name, structType) -> - val hpp = File(HeaderOutput.GENERATE_PATH, structType.getPath()) - hpp.writeText( - """ -/** - * @file $name.hpp - * - */ -#pragma once -#define AUTO_GENERATED -#include "${structType.getGlobalHeaderPath()}" -${structType.getRelativeInclusions()} -#define BEFORE_EXTRA -${structType.beforeExtra} -#undef BEFORE_EXTRA - -${structType.comment} -struct $name { - -#define AFTER_EXTRA -${structType.afterExtra} -#undef AFTER_EXTRA -""".trimIndent() - ) - hpp.appendText(structType.genAntiReconstruction()) - hpp.appendText(structType.genPublic()) - hpp.appendText(structType.genProtected()) - hpp.appendText(structType.genPrivate()) - hpp.appendText("};") - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/liteldev/headeroutput/utils.kt b/src/main/kotlin/com/liteldev/headeroutput/utils.kt index 9c91320..3de52f6 100644 --- a/src/main/kotlin/com/liteldev/headeroutput/utils.kt +++ b/src/main/kotlin/com/liteldev/headeroutput/utils.kt @@ -1,36 +1,36 @@ package com.liteldev.headeroutput -import com.liteldev.headeroutput.config.TypeData +import com.liteldev.headeroutput.entity.BaseType import java.nio.file.Paths -fun String.relativePath(path: String) = Paths.get(this).relativize(Paths.get(path)).toString().replace("\\", "/") +fun String.relativePathTo(path: String): String { + return Paths.get(this.substringBeforeLast("/")).relativize(Paths.get(path)).toString().replace("\\", "/") +} -fun String.parent() = "$this/.." +fun StringBuilder.appendSpace(count: Int): StringBuilder = append(" ".repeat(count)) -fun String.substring(startStr: String, endStr: String): String { - return substringAfter(startStr, "").substringBefore(endStr, "") -} +fun BaseType.isClass(): Boolean = this.type == BaseType.TypeKind.CLASS -fun StringBuilder.appendSpace(count: Int): StringBuilder { - for (i in 0 until count) { - this.append(" ") - } - return this -} +fun BaseType.isStruct() = this.type == BaseType.TypeKind.STRUCT + +fun BaseType.isNamespace() = this.type == BaseType.TypeKind.NAMESPACE -fun isNameSpace(typeName: String, typeData: TypeData): Boolean { - if (typeData.privateTypes != null - || typeData.privateStaticTypes != null - || typeData.protectedTypes != null - || typeData.protectedStaticTypes != null - || typeData.publicStaticTypes != null - || typeData.virtual != null - || typeData.vtblEntry != null - ) { - return false +fun BaseType.isEnum() = this.type == BaseType.TypeKind.ENUM + +fun BaseType.getTopLevelFileType(): BaseType { + outerType ?: return this + if (isNamespace()) { + return this + } + var outer = outerType!! + if (outer.isNamespace()) { + return this } - val containsInIdentifierList = - HeaderOutput.realStructNameList.contains(typeName) || HeaderOutput.realClassNameList.contains(typeName) - return (typeData.publicTypes?.find { it.isPtrCall() } == null - && !containsInIdentifierList) -} \ No newline at end of file + while (outer.outerType != null) { + if (outer.outerType!!.isNamespace()) { + return outer + } + outer = outer.outerType!! + } + return outer +}