feat: introduce binding generator

A squash of hundreds of commits with a binding API generator.

The API generator works with a hybrid model:
1. We define methods and fields in the code.
2. Mark them with API annotations.
3. For specific use cases, we use the old approach with JNI generation comments.

There is also an API to generate an AST tree, which is saved to JSON.
This JSON can be used for full API generation by declaring only field/method names.
Currently, it is used to generate constants.

By eyeballing, 95% of the API has been converted to the new approach.
This is sufficient for the current moment. Only edge cases with specific API calls are left.

fix: ambiguous call for ImGui::GetColorU32

build: disable checkstyle for imgui-binding module

Since the module is heavily generated, checkstyle for it doesn't make sense.

build: make buildSrc to use JDK 11

build: make generateApi optional when compiling project

Initially generated module was not included to the project VCS.
Since it's now in the separate src folder, we don't need to call generator every time.
Only when a new generation result is required.
This commit is contained in:
SpaiR committed 2024-08-08 14:46:23 +03:00
1 parent e64df5a433
commit 8a8cd22ed6
323 files changed
+113518 -15318

No files matched your search

@@ -36,7 +36,7 @@ class GenerateLibs extends DefaultTask {
private final boolean isLocal = System.properties.containsKey('local')
private final boolean withFreeType = Boolean.valueOf(System.properties.getProperty('freetype', 'false'))
private final String sourceDir = project.file('src/main/java')
private final String sourceDir = project.file('src/generated/java')
private final String classpath = project.file('build/classes/java/main')
private final String rootDir = (isLocal ? project.buildDir.path : '/tmp/imgui')
private final String jniDir = "$rootDir/jni"
@@ -0,0 +1,155 @@
package tool.generator.api
import spoon.reflect.code.CtJavaDoc
import spoon.reflect.declaration.CtElement
import spoon.reflect.declaration.CtField
import spoon.reflect.declaration.CtMethod
import spoon.reflect.declaration.CtType
class BindingSourceProcessor(
private val type: CtType<*>
) {
companion object {
private const val BLANK_SPACE = " "
private val CLEANUP_ANNOTATIONS_REGEX = Regex(
"@" + CLEANUP_ANNOTATIONS_LIST.joinToString(separator = "|", prefix = "(", postfix = ")") + ".*"
)
private val CLEANUP_IMPORTS_REGEX = Regex(
"import.+" + CLEANUP_ANNOTATIONS_LIST.joinToString(separator = "|", prefix = "(", postfix = ")")
)
fun isProcessable(type: CtType<*>): Boolean {
return type.hasAnnotation(A_NAME_BINDING_SOURCE)
}
}
private val originalContent: String = type.position.file.readText()
fun process() {
val contentToReplace = mutableMapOf<ReplacePos, String>()
contentToReplace += collectBindingFields()
contentToReplace += collectBindingMethods()
contentToReplace += collectBindingAstEnums()
val contentBuilder = StringBuilder(originalContent)
contentToReplace.keys.sortedDescending().forEach { replacePos ->
contentBuilder.replace(replacePos.startIdx, replacePos.endIdx, contentToReplace[replacePos])
}
cleanupBindingAnnotations(contentBuilder)
type.position.file.writeText(contentBuilder.toString())
}
private fun collectBindingFields(): Map<ReplacePos, String> {
val result = mutableMapOf<ReplacePos, String>()
for (field in type.fields) {
if (field.hasAnnotation(A_NAME_BINDING_FIELD)) {
result += processBindingField(field)
}
}
return result
}
private fun collectBindingMethods(): Map<ReplacePos, String> {
val result = mutableMapOf<ReplacePos, String>()
for (method in type.methods) {
if (method.hasAnnotation(A_NAME_BINDING_METHOD)) {
result += processBindingMethod(method)
}
}
return result
}
private fun collectBindingAstEnums(): Map<ReplacePos, String> {
val result = mutableMapOf<ReplacePos, String>()
for (field in type.fields) {
if (field.hasAnnotation(A_NAME_BINDING_AST_ENUM)) {
result += processBindingAstEnum(field)
}
}
return result
}
private fun cleanupBindingAnnotations(contentBuilder: StringBuilder) {
val linesToRemove = mutableListOf<String>()
contentBuilder.lineSequence().forEach { line ->
if (line.contains(CLEANUP_IMPORTS_REGEX)) {
linesToRemove += line
}
if (line.contains(CLEANUP_ANNOTATIONS_REGEX)) {
linesToRemove += line
}
}
linesToRemove.reversed().forEach { line ->
val idx = contentBuilder.indexOf(line)
contentBuilder.delete(idx, idx + line.length + 1)
}
}
private fun processBindingMethod(method: CtMethod<*>): Map<ReplacePos, String> {
val contentToReplace = mutableMapOf<ReplacePos, String>()
// Remove the original javadoc comment from the generated content.
method.comments.find { it is CtJavaDoc }?.let {
// sourceEnd has an additional offset of 6.
// 1 - '/'
// 2 - '\n'
// 3-6 - ' '
contentToReplace[ReplacePos(it.position.sourceStart, it.position.sourceEnd + 6)] = ""
}
val content = jvmMethodContent(method) + jniMethodContent(method)
contentToReplace[method.findReplacePos()] = content.joinWithIndention()
return contentToReplace
}
private fun processBindingField(field: CtField<*>): Map<ReplacePos, String> {
val contentToReplace = mutableMapOf<ReplacePos, String>()
// Remove the original javadoc comment from the generated content.
field.comments.find { it is CtJavaDoc }?.let {
// sourceEnd has an additional offset of 6.
// 1 - '/'
// 2 - '\n'
// 3-6 - ' '
contentToReplace[ReplacePos(it.position.sourceStart, it.position.sourceEnd + 6)] = ""
}
val content = jvmFieldContent(field) + jniFieldContent(field)
contentToReplace[field.findReplacePos()] = content.joinWithIndention()
return contentToReplace
}
private fun processBindingAstEnum(field: CtField<*>): Map<ReplacePos, String> {
val content = astEnumContent(field.getAnnotation(A_NAME_BINDING_AST_ENUM)!!)
val contentStr = content.joinWithIndention()
return mapOf(field.findReplacePos() to contentStr)
}
private fun List<String>.joinWithIndention(): String {
return joinToString("\n\n") { str ->
str.lines().joinToString("\n") {
it.takeIf(String::isNotBlank)?.prependIndent(BLANK_SPACE) ?: it
}
}
}
private fun CtElement.findReplacePos(): ReplacePos {
var startIdx = annotations[0].position.sourceStart
while (originalContent[startIdx] != '\n') {
startIdx--
}
startIdx++
val endIdx = position.sourceEnd + 1
return ReplacePos(startIdx, endIdx)
}
private data class ReplacePos(val startIdx: Int, val endIdx: Int) : Comparable<ReplacePos> {
override fun compareTo(other: ReplacePos): Int {
return startIdx.compareTo(other.startIdx)
}
}
}
@@ -0,0 +1,23 @@
package tool.generator.api
import org.apache.commons.io.FileUtils
import spoon.reflect.declaration.CtType
class ExcludedSourceProcessor(
private val type: CtType<*>
) {
companion object {
fun isProcessable(type: CtType<*>): Boolean {
return type.hasAnnotation(A_NAME_EXCLUDED_SOURCE)
}
}
fun process() {
val sourceFile = type.position.file
val parentDir = sourceFile.parentFile
sourceFile.delete()
if (FileUtils.isEmptyDirectory(parentDir)) {
parentDir.delete()
}
}
}
@@ -0,0 +1,60 @@
package tool.generator.api
import spoon.reflect.declaration.CtAnnotation
import spoon.reflect.declaration.ModifierKind
import tool.generator.ast.AstEnumConstantDecl
import tool.generator.ast.AstParser
fun astEnumContent(markerAnnotation: CtAnnotation<*>): List<String> {
val result = mutableListOf<String>()
val file = markerAnnotation.getValueAsString(A_VALUE_FILE)!!
val qualType = markerAnnotation.getValueAsString(A_VALUE_QUAL_TYPE)!!
val sanitizeName = markerAnnotation.getValueAsString(A_VALUE_SANITIZE_NAME)?.takeIf(String::isNotEmpty) ?: qualType
val astRoot = AstParser.readFromResource(file)
val enums = astRoot.decls.filterDecls0 {
it is AstEnumConstantDecl && it.qualType == qualType
}.map { it as AstEnumConstantDecl }
val factory = markerAnnotation.factory
enums.forEach { e ->
val f = factory.createField<Nothing>()
f.setModifiers<Nothing>(setOf(ModifierKind.PUBLIC, ModifierKind.STATIC, ModifierKind.FINAL))
f.setType<Nothing>(factory.createTypeParam("int"))
f.setSimpleName<Nothing>(e.name.replace(sanitizeName, ""))
buildString {
if (e.docComment != null) {
appendLine(e.docComment)
if (e.value != null) {
appendLine()
append("<p>")
}
}
if (e.value != null) {
append("Definition: {@code ${e.value}}")
}
}.let {
if (it.isNotEmpty()) {
f.setDocComment<Nothing>(sanitizeDocComment(it))
}
}
f.setAssignment<Nothing>(factory.createCodeSnippetExpression((e.evaluatedValue ?: e.order).toString()))
result += f.prettyprint()
}
return result
}
private fun sanitizeDocComment(doc: String): String {
return doc
.replace("*/", "* /")
.replace(" > ", "{@code >}")
.replace("< <", "{@code <<}")
.replace(" < ", "{@code <}")
.replace("->", "{@code ->}")
.replace("&", "{@code &}")
}
@@ -0,0 +1,688 @@
package tool.generator.api
import spoon.reflect.code.CtStatement
import spoon.reflect.declaration.CtField
import spoon.reflect.declaration.CtMethod
import spoon.reflect.declaration.CtParameter
import spoon.reflect.declaration.ModifierKind
import spoon.reflect.factory.Factory
import spoon.reflect.reference.CtTypeReference
private fun convertParams2jni(f: Factory, params: List<CtParameter<*>>, defaults: IntArray): List<CtParameter<*>> {
val result = mutableListOf<CtParameter<*>>()
for ((index, p) in params.withIndex()) {
if (defaults.isNotEmpty() && !defaults.contains(index)) {
continue
}
if (p.isType("Void")) {
continue
}
if (p.isType("ImVec2") || p.isType("ImVec4")) { // vec param always accepted as primitive values
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}X")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}Y")
}
if (p.isType("ImVec4")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}Z")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}W")
}
}
} else if (p.isType("ImRect")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}MinX")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}MinY")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}MaxX")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("float"))
setSimpleName<Nothing>("${p.simpleName}MaxY")
}
} else if (p.isType("ImPlotPoint")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}X")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}Y")
}
} else if (p.isType("ImPlotRange")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}Min")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}Max")
}
} else if (p.isType("ImPlotLimits")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}MinX")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}MinY")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}MaxX")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("double"))
setSimpleName<Nothing>("${p.simpleName}MaxY")
}
} else if (p.isType("TextEditorCoordinates")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("${p.simpleName}Line")
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("${p.simpleName}Column")
}
} else if (p.isType("String[]")) {
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("String[]"))
setSimpleName<Nothing>(p.simpleName)
}
result += f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("${p.simpleName}$PARAM_ARR_LEN_POSTFIX")
}
} else {
result += f.createParameter<Nothing>().apply {
val type = if (p.type.isPtrClass()) {
f.createTypeParam("long")
} else if (p.type.isClass) {
when (p.type.simpleName) {
"ImBoolean" -> f.createTypeParam("boolean[]")
"ImInt" -> f.createTypeParam("int[]")
"ImFloat" -> f.createTypeParam("float[]")
"ImLong" -> f.createTypeParam("long[]")
"ImDouble" -> f.createTypeParam("double[]")
else -> p.type
}
} else {
p.type
}
setType<Nothing>(type)
setSimpleName<Nothing>(p.simpleName)
}
}
}
return result
}
private fun joinInBodyParams(params: List<CtParameter<*>>, defaults: IntArray): String {
fun param2str(p: CtParameter<*>): String {
return if (p.type.isPtrClass()) {
"reinterpret_cast<${p.type.simpleName}*>(${p.simpleName})"
} else if (p.isPrimitivePtrType()) {
"&${p.simpleName}[0]"
} else if (p.type.isClass) {
when (p.type.simpleName) {
"ImBoolean", "ImInt", "ImFloat", "ImLong", "ImDouble" -> {
"(${p.simpleName} != NULL ? &${p.simpleName}[0] : NULL)"
}
"ImRect" -> {
"ImRect(${p.simpleName}MinX, ${p.simpleName}MinY, ${p.simpleName}MaxX, ${p.simpleName}MaxY)"
}
"ImPlotPoint" -> {
"ImPlotPoint(${p.simpleName}X, ${p.simpleName}Y)"
}
"ImPlotRange" -> {
"ImPlotRange(${p.simpleName}Min, ${p.simpleName}Max)"
}
"ImPlotLimits" -> {
"ImPlotLimits(${p.simpleName}MinX, ${p.simpleName}MinY, ${p.simpleName}MaxX, ${p.simpleName}MaxY)"
}
"TextEditorCoordinates" -> {
"TextEditor::Coordinates(${p.simpleName}Line, ${p.simpleName}Column)"
}
else -> p.simpleName
}
} else {
p.simpleName
}
}
val visibleParams = mutableListOf<String>()
for ((index, p) in params.withIndex()) {
if (defaults.isNotEmpty() && !defaults.contains(index)) {
visibleParams += p.getAnnotation(A_NAME_OPT_ARG)!!.getValueAsString(A_VALUE_CALL_VALUE)
continue
}
if (p.isType("Void")) {
visibleParams += p.getAnnotation(A_NAME_ARG_VALUE)?.getValueAsString(A_VALUE_CALL_VALUE) ?: p.simpleName
continue
}
var value = param2str(p)
p.getAnnotation(A_NAME_ARG_VALUE)?.let { a ->
a.getValueAsString(A_VALUE_CALL_VALUE).takeIf(String::isNotEmpty)?.let { v ->
value = v
}
a.getValueAsString(A_VALUE_CALL_PREFIX).takeIf(String::isNotEmpty)?.let { v ->
value = v + value
}
a.getValueAsString(A_VALUE_CALL_SUFFIX).takeIf(String::isNotEmpty)?.let { v ->
value += v
}
a.getValueAsString(A_VALUE_STATIC_CAST).takeIf(String::isNotEmpty)?.let { v ->
value = "static_cast<$v>($value)"
}
a.getValueAsString(A_VALUE_REINTERPRET_CAST).takeIf(String::isNotEmpty)?.let { v ->
value = "reinterpret_cast<$v>($value)"
}
}
visibleParams += value
}
return visibleParams.joinToString()
}
private fun createMethod(mOrig: CtMethod<*>, params: List<CtParameter<*>>, defaults: IntArray): CtMethod<*> {
val f = mOrig.factory
val newM = f.createMethod<Nothing>()
newM.setParent<Nothing>(mOrig.parent)
newM.addModifier<Nothing>(ModifierKind.PRIVATE)
newM.addModifier<Nothing>(ModifierKind.NATIVE)
if (mOrig.isStatic) {
newM.addModifier<Nothing>(ModifierKind.STATIC)
}
if (DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)) {
newM.setType<Nothing>(f.createTypeParam("void"))
} else if (mOrig.type.isPtrClass()) { // classes returned as a pointer
newM.setType<Nothing>(f.createTypeParam("long"))
} else {
newM.setType<Nothing>(mOrig.type)
}
newM.setSimpleName<Nothing>(mOrig.getJniName())
convertParams2jni(f, params, defaults).forEach {
newM.addParameter<Nothing>(it)
}
if (DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)) {
newM.addParameterAt<Nothing>(0, f.createParameter<Nothing>().apply {
setType<Nothing>(mOrig.type)
setSimpleName<Nothing>("dst")
})
}
newM.setBody<Nothing>(f.createCodeSnippet(
buildString {
val jniCpyReturn = DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)
val isStrReturn = mOrig.isType("String")
if (jniCpyReturn) {
append("Jni::${mOrig.type.simpleName}Cpy(env, ")
} else {
if (!mOrig.isType("void")) {
append("return ")
if (isStrReturn) {
append("env->NewStringUTF(")
} else if (!mOrig.type.isPrimitive) {
append("($PTR_JNI_CAST)")
}
mOrig.getAnnotation(A_NAME_RETURN_VALUE)?.let { a ->
append(a.getValueAsString(A_VALUE_CALL_PREFIX))
}
}
}
val callName = mOrig.getAnnotation(A_NAME_BINDING_METHOD)?.let { a ->
a.getValueAsString(A_VALUE_CALL_NAME)?.takeIf(String::isNotEmpty)
} ?: mOrig.simpleName
val callPtr = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
a.getValueAsString(A_VALUE_CALL_PTR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
mOrig.declaringType.simpleName
} else {
PTR_JNI_THIS
}
}
val callOperator = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
a.getValueAsString(A_VALUE_CALL_OPERATOR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
"::"
} else {
"->"
}
}
append("$callPtr$callOperator${callName}(")
append(joinInBodyParams(params, defaults))
append(')')
mOrig.getAnnotation(A_NAME_RETURN_VALUE)?.let { a ->
append(a.getValueAsString(A_VALUE_CALL_SUFFIX))
}
if (jniCpyReturn) {
append(", dst)")
} else if (isStrReturn) {
append(')')
}
}
))
return newM
}
private fun createMethodVecValueReturn(
vecVal: String,
mOrig: CtMethod<*>,
nameOrig: String,
params: List<CtParameter<*>>,
defaults: IntArray
): CtMethod<*> {
val callPtr = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
a.getValueAsString(A_VALUE_CALL_PTR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
mOrig.declaringType.simpleName
} else {
PTR_JNI_THIS
}
}
val callOperator = mOrig.parent.getAnnotation(A_NAME_BINDING_SOURCE)!!.let { a ->
a.getValueAsString(A_VALUE_CALL_OPERATOR)?.takeIf(String::isNotEmpty) ?: if (mOrig.isStatic) {
"::"
} else {
"->"
}
}
val mNew = mOrig.clone()
mNew.setSimpleName<Nothing>(mOrig.simpleName + vecVal.capitalize())
mNew.removeParameter(mNew.parameters[0]) // dst param which was added during orig method creation
mNew.type.setSimpleName<Nothing>("float")
mNew.setBody<Nothing>(mOrig.factory.createCodeSnippet(
buildString {
append("return ")
append("$callPtr$callOperator$nameOrig(")
append(joinInBodyParams(params, defaults))
append(").$vecVal")
}
))
return mNew
}
private fun convertManualJni(mOrig: CtMethod<*>, method: CtMethod<*>): CtMethod<*>? {
val mNew = method.clone()
val getLines = mutableListOf<String>()
val releaseLines = mutableListOf<String>()
mNew.parameters.forEach { p ->
var isConverted = false
if (p.isPrimitivePtrType()) {
val typeCast = PRIMITIVE_PTR_TYPECAST_MAP[p.type.simpleName]
getLines += "auto ${p.simpleName} = obj_${p.simpleName} == NULL ? NULL : (${typeCast})env->GetPrimitiveArrayCritical(obj_${p.simpleName}, JNI_FALSE)"
releaseLines += "if (${p.simpleName} != NULL) env->ReleasePrimitiveArrayCritical(obj_${p.simpleName}, ${p.simpleName}, JNI_FALSE)"
isConverted = true
}
if (p.isType("String")) {
getLines += "auto ${p.simpleName} = obj_${p.simpleName} == NULL ? NULL : (char*)env->GetStringUTFChars(obj_${p.simpleName}, JNI_FALSE)"
releaseLines += "if (${p.simpleName} != NULL) env->ReleaseStringUTFChars(obj_${p.simpleName}, ${p.simpleName})"
isConverted = true
}
if (p.isType("String[]")) {
val countParam = "${p.simpleName}$PARAM_ARR_LEN_POSTFIX"
getLines += """
const char* ${p.simpleName}[$countParam];
for (int i = 0; i < ${p.simpleName}$PARAM_ARR_LEN_POSTFIX; i++) {
const jstring str = (jstring)env->GetObjectArrayElement(obj_${p.simpleName}, i);
auto rawStr = (char*)env->GetStringUTFChars(str, JNI_FALSE);
${p.simpleName}[i] = rawStr;
}
""".trimIndent()
releaseLines += """
for (int i = 0; i < $countParam; i++) {
const jstring str = (jstring)env->GetObjectArrayElement(obj_${p.simpleName}, i);
env->ReleaseStringUTFChars(str, ${p.simpleName}[i]);
}
""".trimIndent()
isConverted = true
}
if (p.isType("ImVec2[]")) {
val lengthVar = "${p.simpleName}Length"
getLines += """
int $lengthVar = env->GetArrayLength(obj_${p.simpleName});
ImVec2 ${p.simpleName}[$lengthVar];
for (int i = 0; i < $lengthVar; i++) {
jobject src = env->GetObjectArrayElement(obj_${p.simpleName}, i);
ImVec2 dst;
Jni::ImVec2Cpy(env, src, &dst);
${p.simpleName}[i] = dst;
}
""".trimIndent()
isConverted = true
}
if (p.isType("ImVec4[]")) {
val lengthVar = "${p.simpleName}Length"
getLines += """
int $lengthVar = env->GetArrayLength(obj_${p.simpleName});
ImVec4 ${p.simpleName}[$lengthVar];
for (int i = 0; i < $lengthVar; i++) {
jobject src = env->GetObjectArrayElement(obj_${p.simpleName}, i);
ImVec4 dst;
Jni::ImVec4Cpy(env, src, &dst);
${p.simpleName}[i] = dst;
}
""".trimIndent()
isConverted = true
}
fun isAddPrefixType(type: CtTypeReference<*>): Boolean {
return setOf(
"String[]",
"ImVec2[]",
"ImVec4[]",
).contains(type.simpleName)
}
if (isConverted && (!mNew.isType("void") || isAddPrefixType(p.type))) {
p.setSimpleName<Nothing>("obj_" + p.simpleName)
}
}
// Sine we work with an already transformed method, which doesn't contain original information,
// we need to process the original method instead. For example, ImVec args are transformed into floats.
mOrig.parameters.forEach { p ->
// Transformed method may not have the current param. For example, if it's optional.
// So for ImVec arguments we check, if there is a parameter with the same name and "X" attached.
// It's a sort of marker in our case.
if (method.parameters.find { it.simpleName == p.simpleName + "X" } != null) {
if (p.isType("ImVec2")) {
getLines += "ImVec2 ${p.simpleName} = ImVec2(${p.simpleName}X, ${p.simpleName}Y)"
}
if (p.isType("ImVec4")) {
getLines += "ImVec4 ${p.simpleName} = ImVec4(${p.simpleName}X, ${p.simpleName}Y, ${p.simpleName}Z, ${p.simpleName}W)"
}
}
}
if (getLines.isNotEmpty() || releaseLines.isNotEmpty()) {
val bOrigCode = mNew.body.getLastStatement<CtStatement>().prettyprint()
mNew.body.statements.clear()
getLines.forEach {
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet(it))
}
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet(bOrigCode.replace("return ", "auto _result = ")))
releaseLines.forEach {
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet(it))
}
if (!mNew.isType("void")) {
mNew.body.addStatement<Nothing>(mNew.factory.createCodeSnippet("return _result"))
}
return mNew
}
return null
}
private fun CtMethod<*>.prettyprint(isManual: Boolean): String {
return buildString {
val str = prettyprint().let {
if (isManual) {
it.replaceFirst(" {", "; /*MANUAL")
} else {
it.replaceFirst(" {", "; /*")
}
}
append(str)
lastIndexOf("}").let { idx ->
replace(idx, idx + 1, "*/")
}
}
}
private fun transformMethodToContent(
mOrig: CtMethod<*>,
params: List<CtParameter<*>> = emptyList(),
defaults: IntArray = intArrayOf()
): List<String> {
fun CtMethod<*>.printJni(): String {
return convertManualJni(mOrig, this)?.prettyprint(true) ?: prettyprint(false)
}
val methods = mutableListOf<String>()
val mNew = createMethod(mOrig, params, defaults)
methods += mNew.printJni()
if (mOrig.isType("ImVec2") || mOrig.isType("ImVec4")) {
methods += createMethodVecValueReturn("x", mNew, mOrig.simpleName, params, defaults).printJni()
methods += createMethodVecValueReturn("y", mNew, mOrig.simpleName, params, defaults).printJni()
if (mOrig.isType("ImVec4")) {
methods += createMethodVecValueReturn("z", mNew, mOrig.simpleName, params, defaults).printJni()
methods += createMethodVecValueReturn("w", mNew, mOrig.simpleName, params, defaults).printJni()
}
}
return methods
}
fun jniMethodContent(method: CtMethod<*>): List<String> {
val methods = mutableListOf<String>()
for (paramsSize in (findFirstOptParam(method)..method.parameters.size)) {
val params = method.parameters.subList(0, paramsSize)
methods += transformMethodToContent(method, params)
}
for (defaults in findDefaultsCombinations(method)) {
methods += transformMethodToContent(method, method.parameters, defaults)
}
return methods
}
private fun createFieldGetContent(field: CtField<*>): List<String> {
val f = field.factory
val getAccessor = f.createMethod<Nothing>()
getAccessor.setParent<Nothing>(field.parent)
getAccessor.setSimpleName<Nothing>("get${field.simpleName}")
getAccessor.setType<Nothing>(field.type)
getAccessor.addModifier<Nothing>(ModifierKind.PRIVATE)
getAccessor.setAnnotations<Nothing>(field.annotations)
val result = mutableListOf<String>()
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)
val arraySize = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_SIZE)
val getArray = getAccessor.clone()
getArray.addModifier<Nothing>(ModifierKind.NATIVE)
getArray.setSimpleName<Nothing>("nGet${field.simpleName}")
getArray.setType<Nothing>(f.createTypeParam("$arrayType[]"))
when (arrayType) {
"boolean", "short", "int", "float", "double", "long" -> {
getArray.setBody<Nothing>(
f.createCodeSnippet(
"""
j$arrayType jBuf[$arraySize];
for (int i = 0; i < $arraySize; i++)
jBuf[i] = $PTR_JNI_THIS->${field.getCallName()}[i];
j${arrayType}Array result = env->New${arrayType.capitalize()}Array($arraySize);
env->Set${arrayType.capitalize()}ArrayRegion(result, 0, $arraySize, jBuf);
return result
""".trimIndent()
)
)
}
"ImVec2", "ImVec4" -> {
getArray.setBody<Nothing>(
f.createCodeSnippet(
"""
return Jni::New${arrayType}Array(env, $PTR_JNI_THIS->${field.getCallName()}, $arraySize)
""".trimIndent()
)
)
}
}
result += getArray.prettyprint(false)
when (arrayType) {
"boolean", "short", "int", "float", "double", "long" -> {
val getArrayIdx = getArray.clone()
getArrayIdx.addParameter<Nothing>(f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("idx")
})
getArrayIdx.setType<Nothing>(f.createTypeParam(arrayType))
getArrayIdx.setBody<Nothing>(f.createCodeSnippet("return $PTR_JNI_THIS->${field.getCallName()}[idx]"))
result += getArrayIdx.prettyprint(false)
}
}
} else {
getAccessor.addAnnotation<Nothing>(f.createAnnotation(f.createTypeReference<Nothing?>().apply {
setSimpleName<Nothing>("imgui.binding.annotation.BindingMethod")
}).apply {
addValue<Nothing>(A_VALUE_CALL_NAME, field.getCallName())
})
result += transformMethodToContent(getAccessor).map {
it.replace("$PTR_JNI_THIS->${getAccessor.simpleName}()", "$PTR_JNI_THIS->${field.getCallName()}")
}
}
return result
}
private fun createFieldSetContent(field: CtField<*>): List<String> {
val f = field.factory
val setAccessor = f.createMethod<Nothing>()
setAccessor.setType<Nothing>(f.createTypeParam("void"))
setAccessor.setParent<Nothing>(field.parent)
setAccessor.setSimpleName<Nothing>("set${field.simpleName}")
setAccessor.addModifier<Nothing>(ModifierKind.PRIVATE)
val valueParam = f.createParameter<Nothing>().apply {
setType<Nothing>(field.type)
setSimpleName<Nothing>("value")
}
// Add this param to the method placeholder, since some transformations can check it.
setAccessor.addParameter<Nothing>(valueParam)
val result = mutableListOf<String>()
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)
val arraySize = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_SIZE)
val setArray = setAccessor.clone()
setArray.addModifier<Nothing>(ModifierKind.NATIVE)
setArray.setSimpleName<Nothing>("nSet${field.simpleName}")
when (arrayType) {
"boolean", "short", "int", "float", "double", "long" -> {
setArray.setBody<Nothing>(
f.createCodeSnippet(
"""
for (int i = 0; i < $arraySize; i++)
$PTR_JNI_THIS->${field.getCallName()}[i] = value[i]
""".trimIndent()
)
)
}
"ImVec2", "ImVec4" -> {
setArray.setBody<Nothing>(
f.createCodeSnippet("""
Jni::${arrayType}ArrayCpy(env, value, $PTR_JNI_THIS->${field.getCallName()}, $arraySize)
""".trimIndent()
)
)
}
}
result += setArray.prettyprint(false)
when (arrayType) {
"boolean", "short", "int", "float", "double", "long" -> {
val setArrayIdx = setArray.clone()
setArrayIdx.parameters.clear()
setArrayIdx.addParameter<Nothing>(f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("idx")
})
setArrayIdx.addParameter<Nothing>(f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam(arrayType))
setSimpleName<Nothing>("value")
})
setArrayIdx.setType<Nothing>(f.createTypeParam(arrayType))
setArrayIdx.setBody<Nothing>(f.createCodeSnippet("$PTR_JNI_THIS->${field.getCallName()}[idx] = value"))
result += setArrayIdx.prettyprint(false)
}
}
} else {
result += transformMethodToContent(setAccessor, listOf(valueParam)).map {
it.replace(
"$PTR_JNI_THIS->${setAccessor.simpleName}\\((.+)\\)".toRegex(),
"$PTR_JNI_THIS->${field.getCallName()} = $1"
)
}
}
return result
}
fun jniFieldContent(field: CtField<*>): List<String> {
val fields = mutableListOf<String>()
val a = field.getAnnotation(A_NAME_BINDING_FIELD)
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_GETTER) == true) {
fields += createFieldGetContent(field)
}
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_SETTER) == true) {
fields += createFieldSetContent(field)
}
return fields
}
@@ -0,0 +1,713 @@
package tool.generator.api
import spoon.reflect.code.CtJavaDoc
import spoon.reflect.code.CtJavaDocTag
import spoon.reflect.declaration.CtField
import spoon.reflect.declaration.CtMethod
import spoon.reflect.declaration.CtParameter
import spoon.reflect.declaration.ModifierKind
import kotlin.math.absoluteValue
private fun createStaticStructFieldName(method: CtMethod<*>): String {
return "_${method.simpleName.toUpperCase()}_${method.parameters.hashCode().absoluteValue}"
}
private fun createStaticStructField(typeName: String, fieldName: String): String {
return "private static final $typeName $fieldName = new $typeName(0);"
}
private fun joinInBodyParams(params: List<CtParameter<*>>, defaults: IntArray): String {
fun param2str(p: CtParameter<*>): String {
return if (p.type.isPtrClass()) {
"${p.simpleName}.$PTR_JVM_FIELD"
} else if (p.type.isClass) {
when (p.type.simpleName) {
"ImBoolean", "ImShort", "ImInt", "ImFloat", "ImLong", "ImDouble" -> {
"${p.simpleName} != null ? ${p.simpleName}.getData() : null"
}
"ImVec2" -> "${p.simpleName}.x, ${p.simpleName}.y"
"ImVec4" -> "${p.simpleName}.x, ${p.simpleName}.y, ${p.simpleName}.z, ${p.simpleName}.w"
"ImRect" -> "${p.simpleName}.min.x, ${p.simpleName}.min.y, ${p.simpleName}.max.x, ${p.simpleName}.max.y"
"ImPlotPoint" -> "${p.simpleName}.x, ${p.simpleName}.y"
"ImPlotRange" -> "${p.simpleName}.min, ${p.simpleName}.max"
"ImPlotLimits" -> "${p.simpleName}.x.min, ${p.simpleName}.y.min, ${p.simpleName}.x.max, ${p.simpleName}.y.max"
"TextEditorCoordinates" -> "${p.simpleName}.mLine, ${p.simpleName}.mColumn"
"String[]" -> "${p.simpleName}, ${p.simpleName}.length"
"boolean[]", "byte[]", "short[]", "int[]", "float[]", "long[]", "double[]" -> p.simpleName
else -> p.simpleName
}
} else {
p.simpleName
}
}
val visibleParams = mutableListOf<String>()
for ((index, p) in params.withIndex()) {
if (defaults.isNotEmpty() && !defaults.contains(index)) {
continue
}
if (p.isType("Void")) {
continue
}
visibleParams += param2str(p)
}
return visibleParams.joinToString()
}
private fun createBodyStaticStructReturn(
method: CtMethod<*>,
params: List<CtParameter<*>>,
defaults: IntArray
): String {
val fieldName = createStaticStructFieldName(method)
return buildString {
append("${fieldName}.$PTR_JVM_FIELD = ${method.getJniName()}(")
append(joinInBodyParams(params, defaults))
appendLine(");")
append("return $fieldName")
}
}
private fun createBodyStructReturn(
method: CtMethod<*>,
params: List<CtParameter<*>>,
defaults: IntArray
): String {
return buildString {
append("return new ${method.type.simpleName}(${method.getJniName()}(")
append(joinInBodyParams(params, defaults))
append("))")
}
}
private fun createBodyDstReturn(
method: CtMethod<*>,
params: List<CtParameter<*>>,
defaults: IntArray
): String {
val type = method.type.simpleName
return buildString {
appendLine("final $type dst = new ${type}();")
append("${method.getJniName()}(dst")
joinInBodyParams(params, defaults).let {
if (it.isNotEmpty()) {
append(", $it")
}
}
appendLine(");")
append("return dst")
}
}
private fun createMethod(origM: CtMethod<*>, params: List<CtParameter<*>>, defaults: IntArray): CtMethod<*> {
val f = origM.factory
val newM = f.createMethod<Nothing>()
if (origM.isPublic) {
newM.addModifier<Nothing>(ModifierKind.PUBLIC)
}
if (origM.isStatic) {
newM.addModifier<Nothing>(ModifierKind.STATIC)
}
if (origM.docComment.isNotBlank()) {
newM.setDocComment<Nothing>(origM.docComment)
}
newM.setAnnotations<Nothing>(origM.annotations)
newM.setType<Nothing>(origM.type)
newM.setSimpleName<Nothing>(origM.getName())
for ((index, p) in params.withIndex()) {
if (defaults.isNotEmpty() && !defaults.contains(index)) {
continue
}
if (p.isType("Void")) {
continue
}
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
addModifier<Nothing>(ModifierKind.FINAL)
setType<Nothing>(p.type)
setSimpleName<Nothing>(p.simpleName)
})
}
sanitizeDocComment(newM)
sanitizeAnnotations(newM)
newM.setBody<Nothing>(f.createCodeSnippet(
buildString {
if (origM.isStaticStructReturnValue()) {
append(createBodyStaticStructReturn(origM, params, defaults))
} else if (DST_RETURN_TYPE_SET.contains(origM.type.simpleName)) {
append(createBodyDstReturn(origM, params, defaults))
} else if (origM.type.isClass && !origM.type.isPrimitive && !origM.isType("void") && !origM.isType("String") && !origM.isPrimitivePtrType()) {
append(createBodyStructReturn(origM, params, defaults))
} else {
if (!origM.isType("void")) {
append("return ")
}
append("${origM.getJniName()}(")
append(joinInBodyParams(params, defaults))
append(")")
}
}
))
return newM
}
private fun methodVecUnwrappedContent(method: CtMethod<*>): String {
val newMethod = method.clone()
val newParams = mutableListOf<CtParameter<*>>()
val vecParamNames = mutableSetOf<String>()
for (p in newMethod.parameters) {
if (p.isType("ImVec2") || p.isType("ImVec4")) {
vecParamNames += p.simpleName
val paramX = p.factory.createParameter<Nothing>()
paramX.addModifier<Nothing>(ModifierKind.FINAL)
paramX.setType<Nothing>(p.factory.createTypeParam("float"))
paramX.setSimpleName<Nothing>("${p.simpleName}X")
val paramY = paramX.clone()
paramY.setSimpleName<Nothing>("${p.simpleName}Y")
newParams += paramX
newParams += paramY
if (p.isType("ImVec4")) {
val paramZ = paramX.clone()
paramZ.setSimpleName<Nothing>("${p.simpleName}Z")
val paramW = paramX.clone()
paramW.setSimpleName<Nothing>("${p.simpleName}W")
newParams += paramZ
newParams += paramW
}
getJDoc(newMethod)?.let { jDoc ->
val idx = jDoc.tags.indexOfFirst { it.param == p.simpleName }
if (idx != -1) {
jDoc.removeTag<Nothing>(idx)
}
}
} else {
newParams += p
}
}
newMethod.setParameters<Nothing>(newParams)
val mContentOrig = method.prettyprint()
val mContent = newMethod.prettyprint().let {
var str = it
for (paramName in vecParamNames) {
str = str.replace("${paramName}.x, ${paramName}.y", "${paramName}X, ${paramName}Y")
str = str.replace("${paramName}.z, ${paramName}.w", "${paramName}Z, ${paramName}W")
}
str
}
if (mContent != mContentOrig) {
return mContent
}
return ""
}
private fun methodRectUnwrappedContent(method: CtMethod<*>): String {
val newMethod = method.clone()
val newParams = mutableListOf<CtParameter<*>>()
val paramNames = mutableSetOf<String>()
for (p in newMethod.parameters) {
if (p.isType("ImRect")) {
paramNames += p.simpleName
val paramMinX = p.factory.createParameter<Nothing>()
paramMinX.addModifier<Nothing>(ModifierKind.FINAL)
paramMinX.setType<Nothing>(p.factory.createTypeParam("float"))
paramMinX.setSimpleName<Nothing>("${p.simpleName}MinX")
val paramMinY = paramMinX.clone()
paramMinY.setSimpleName<Nothing>("${p.simpleName}MinY")
newParams += paramMinX
newParams += paramMinY
val paramMaxX = paramMinX.clone()
paramMaxX.setSimpleName<Nothing>("${p.simpleName}MaxX")
val paramMaxY = paramMinX.clone()
paramMaxY.setSimpleName<Nothing>("${p.simpleName}MaxY")
newParams += paramMaxX
newParams += paramMaxY
} else {
newParams += p
}
}
newMethod.setParameters<Nothing>(newParams)
val mContentOrig = method.prettyprint()
val mContent = newMethod.prettyprint().let {
var str = it
for (paramName in paramNames) {
str = str.replace(
"${paramName}.min.x, ${paramName}.min.y, ${paramName}.max.x, ${paramName}.max.y",
"${paramName}MinX, ${paramName}MinY, ${paramName}MaxX, ${paramName}MaxY"
)
}
str
}
if (mContent != mContentOrig) {
return mContent
}
return ""
}
private fun methodPlotPointUnwrappedContent(method: CtMethod<*>): String {
val newMethod = method.clone()
val newParams = mutableListOf<CtParameter<*>>()
val paramNames = mutableSetOf<String>()
for (p in newMethod.parameters) {
if (p.isType("ImPlotPoint")) {
paramNames += p.simpleName
val paramX = p.factory.createParameter<Nothing>()
paramX.addModifier<Nothing>(ModifierKind.FINAL)
paramX.setType<Nothing>(p.factory.createTypeParam("double"))
paramX.setSimpleName<Nothing>("${p.simpleName}X")
val paramY = paramX.clone()
paramY.setSimpleName<Nothing>("${p.simpleName}Y")
newParams += paramX
newParams += paramY
} else {
newParams += p
}
}
newMethod.setParameters<Nothing>(newParams)
val mContentOrig = method.prettyprint()
val mContent = newMethod.prettyprint().let {
var str = it
for (paramName in paramNames) {
str = str.replace("${paramName}.x, ${paramName}.y", "${paramName}X, ${paramName}Y")
}
str
}
if (mContent != mContentOrig) {
return mContent
}
return ""
}
private fun methodPlotRangeUnwrappedContent(method: CtMethod<*>): String {
val newMethod = method.clone()
val newParams = mutableListOf<CtParameter<*>>()
val paramNames = mutableSetOf<String>()
for (p in newMethod.parameters) {
if (p.isType("ImPlotRange")) {
paramNames += p.simpleName
val paramX = p.factory.createParameter<Nothing>()
paramX.addModifier<Nothing>(ModifierKind.FINAL)
paramX.setType<Nothing>(p.factory.createTypeParam("double"))
paramX.setSimpleName<Nothing>("${p.simpleName}Min")
val paramY = paramX.clone()
paramY.setSimpleName<Nothing>("${p.simpleName}Max")
newParams += paramX
newParams += paramY
} else {
newParams += p
}
}
newMethod.setParameters<Nothing>(newParams)
val mContentOrig = method.prettyprint()
val mContent = newMethod.prettyprint().let {
var str = it
for (paramName in paramNames) {
str = str.replace("${paramName}.min, ${paramName}.max", "${paramName}Min, ${paramName}Max")
}
str
}
if (mContent != mContentOrig) {
return mContent
}
return ""
}
private fun methodPlotLimitsUnwrappedContent(method: CtMethod<*>): String {
val newMethod = method.clone()
val newParams = mutableListOf<CtParameter<*>>()
val paramNames = mutableSetOf<String>()
for (p in newMethod.parameters) {
if (p.isType("ImPlotLimits")) {
paramNames += p.simpleName
val paramMinX = p.factory.createParameter<Nothing>()
paramMinX.addModifier<Nothing>(ModifierKind.FINAL)
paramMinX.setType<Nothing>(p.factory.createTypeParam("double"))
paramMinX.setSimpleName<Nothing>("${p.simpleName}MinX")
val paramMinY = paramMinX.clone()
paramMinY.setSimpleName<Nothing>("${p.simpleName}MinY")
newParams += paramMinX
newParams += paramMinY
val paramMaxX = paramMinX.clone()
paramMaxX.setSimpleName<Nothing>("${p.simpleName}MaxX")
val paramMaxY = paramMinX.clone()
paramMaxY.setSimpleName<Nothing>("${p.simpleName}MaxY")
newParams += paramMaxX
newParams += paramMaxY
} else {
newParams += p
}
}
newMethod.setParameters<Nothing>(newParams)
val mContentOrig = method.prettyprint()
val mContent = newMethod.prettyprint().let {
var str = it
for (paramName in paramNames) {
str = str.replace(
"${paramName}.x.min, ${paramName}.y.min, ${paramName}.x.max, ${paramName}.y.max",
"${paramName}MinX, ${paramName}MinY, ${paramName}MaxX, ${paramName}MaxY"
)
}
str
}
if (mContent != mContentOrig) {
return mContent
}
return ""
}
private fun methodCoordinatesUnwrappedContent(method: CtMethod<*>): String {
val newMethod = method.clone()
val newParams = mutableListOf<CtParameter<*>>()
val paramNames = mutableSetOf<String>()
for (p in newMethod.parameters) {
if (p.isType("TextEditorCoordinates")) {
paramNames += p.simpleName
val paramX = p.factory.createParameter<Nothing>()
paramX.addModifier<Nothing>(ModifierKind.FINAL)
paramX.setType<Nothing>(p.factory.createTypeParam("int"))
paramX.setSimpleName<Nothing>("${p.simpleName}Line")
val paramY = paramX.clone()
paramY.setSimpleName<Nothing>("${p.simpleName}Column")
newParams += paramX
newParams += paramY
} else {
newParams += p
}
}
newMethod.setParameters<Nothing>(newParams)
val mContentOrig = method.prettyprint()
val mContent = newMethod.prettyprint().let {
var str = it
for (paramName in paramNames) {
str = str.replace("${paramName}.mLine, ${paramName}.mColumn", "${paramName}Line, ${paramName}Column")
}
str
}
if (mContent != mContentOrig) {
return mContent
}
return ""
}
private fun createMethodDstReturn(
mOrig: CtMethod<*>,
params: List<CtParameter<*>>,
defaults: IntArray
): CtMethod<*> {
val mNew = mOrig.clone()
mNew.setType<Nothing>(mNew.factory.createTypeParam("void"))
mNew.addParameterAt<Nothing>(0, mOrig.factory.createParameter<Nothing>().apply {
addModifier<Nothing>(ModifierKind.FINAL)
setType<Nothing>(factory.createTypeParam(mOrig.type.simpleName))
setSimpleName<Nothing>("dst")
})
mNew.setBody<Nothing>(mOrig.factory.createCodeSnippet(
buildString {
append("${mOrig.getJniName()}(dst")
joinInBodyParams(params, defaults).let {
if (it.isNotEmpty()) {
append(", $it")
}
}
append(")")
}
))
return mNew
}
private fun createMethodVecValueReturn(
vecVal: String,
mOrig: CtMethod<*>,
params: List<CtParameter<*>>,
defaults: IntArray
): CtMethod<*> {
val mNew = mOrig.clone()
mNew.setSimpleName<Nothing>(mOrig.getName() + vecVal.capitalize())
mNew.type.setSimpleName<Nothing>("float")
mNew.setBody<Nothing>(mOrig.factory.createCodeSnippetStatement().apply {
setValue<Nothing>(buildString {
append("return ")
append("${mNew.getJniName()}(")
append(joinInBodyParams(params, defaults))
append(")")
})
})
return mNew
}
private fun transformMethodToContent(
mOrig: CtMethod<*>,
params: List<CtParameter<*>> = emptyList(),
defaults: IntArray = intArrayOf()
): List<String> {
val methods = mutableListOf<String>()
val mNew = createMethod(mOrig, params, defaults)
methods += mNew.prettyprint()
if (params.find { it.isType("ImVec2") || it.isType("ImVec4") } != null) {
methodVecUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
}
if (params.find { it.isType("ImRect") } != null) {
methodRectUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
}
if (params.find { it.isType("ImPlotPoint") } != null) {
methodPlotPointUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
}
if (params.find { it.isType("ImPlotRange") } != null) {
methodPlotRangeUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
}
if (params.find { it.isType("ImPlotLimits") } != null) {
methodPlotLimitsUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
}
if (params.find { it.isType("TextEditorCoordinates") } != null) {
methodCoordinatesUnwrappedContent(mNew).takeIf(String::isNotEmpty)?.run(methods::add)
}
if (mOrig.isType("ImVec2") || mOrig.isType("ImVec4")) {
methods += createMethodVecValueReturn("x", mNew, params, defaults).prettyprint()
methods += createMethodVecValueReturn("y", mNew, params, defaults).prettyprint()
if (mOrig.isType("ImVec4")) {
methods += createMethodVecValueReturn("z", mNew, params, defaults).prettyprint()
methods += createMethodVecValueReturn("w", mNew, params, defaults).prettyprint()
}
}
if (DST_RETURN_TYPE_SET.contains(mOrig.type.simpleName)) {
methods += createMethodDstReturn(mNew, params, defaults).prettyprint()
}
return methods
}
fun jvmMethodContent(method: CtMethod<*>): List<String> {
val addContent = mutableListOf<String>()
if (method.isStaticStructReturnValue()) {
val staticStructFieldName = createStaticStructFieldName(method)
addContent += createStaticStructField(method.type.simpleName, staticStructFieldName)
}
val methods = mutableListOf<String>()
for (paramsSize in (findFirstOptParam(method)..method.parameters.size)) {
val params = method.parameters.subList(0, paramsSize)
methods += transformMethodToContent(method, params)
}
for (defaults in findDefaultsCombinations(method)) {
methods += transformMethodToContent(method, method.parameters, defaults)
}
return addContent + methods
}
private fun CtMethod<*>.isStaticStructReturnValue(): Boolean {
return !isType("void") && getAnnotation(A_NAME_RETURN_VALUE)?.getValueAsObject(A_VALUE_IS_STATIC) == true
}
private fun createFieldGetContent(field: CtField<*>): List<String> {
val f = field.factory
val getAccessor = f.createMethod<Nothing>()
getAccessor.setParent<Nothing>(field.parent)
getAccessor.setSimpleName<Nothing>("get${field.simpleName}")
getAccessor.setType<Nothing>(field.type)
getAccessor.addModifier<Nothing>(ModifierKind.PUBLIC)
getAccessor.setDocComment<Nothing>(field.docComment)
getAccessor.setAnnotations<Nothing>(field.annotations)
val result = jvmMethodContent(getAccessor).toMutableList()
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
when (val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)) {
"boolean", "short", "int", "float", "double", "long" -> {
val newM = getAccessor.clone()
newM.setType<Nothing>(f.createTypeParam(arrayType))
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("idx")
})
result += jvmMethodContent(newM)
}
}
}
return result
}
private fun createFieldSetContent(field: CtField<*>): List<String> {
val f = field.factory
val setAccessor = f.createMethod<Nothing>()
setAccessor.setType<Nothing>(f.createTypeParam("void"))
setAccessor.setParent<Nothing>(field.parent)
setAccessor.setSimpleName<Nothing>("set${field.simpleName}")
setAccessor.addModifier<Nothing>(ModifierKind.PUBLIC)
setAccessor.setDocComment<Nothing>(field.docComment)
setAccessor.setAnnotations<Nothing>(field.annotations)
val valueParam = f.createParameter<Nothing>().apply {
setType<Nothing>(field.type)
setSimpleName<Nothing>("value")
}
val result = transformMethodToContent(setAccessor, listOf(valueParam)).toMutableList()
if (field.hasAnnotation(A_NAME_TYPE_ARRAY)) {
when (val arrayType = field.getAnnotation(A_NAME_TYPE_ARRAY)!!.getValueAsString(A_VALUE_TYPE)) {
"boolean", "short", "int", "float", "double", "long" -> {
val newM = setAccessor.clone()
newM.parameters.clear()
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam("int"))
setSimpleName<Nothing>("idx")
})
newM.addParameter<Nothing>(f.createParameter<Nothing>().apply {
setType<Nothing>(f.createTypeParam(arrayType))
setSimpleName<Nothing>("value")
})
result += transformMethodToContent(newM, newM.parameters)
}
}
}
return result
}
private fun createFieldFlagUtils(field: CtField<*>): List<String> {
val mAdd = field.factory.createMethod<Nothing>()
mAdd.setSimpleName<Nothing>("add${field.simpleName}")
mAdd.setType<Nothing>(field.factory.createTypeParam("void"))
mAdd.addModifier<Nothing>(ModifierKind.PUBLIC)
mAdd.setDocComment<Nothing>(field.docComment)
mAdd.addParameter<Nothing>(field.factory.createParameter<Nothing>().apply {
addModifier<Nothing>(ModifierKind.FINAL)
setType<Nothing>(field.factory.createTypeParam("int"))
setSimpleName<Nothing>("flags")
})
mAdd.setBody<Nothing>(field.factory.createCodeSnippet("set${field.simpleName}(get${field.simpleName}() | flags)"))
val mRemove = mAdd.clone()
mRemove.setSimpleName<Nothing>("remove${field.simpleName}")
mRemove.setBody<Nothing>(field.factory.createCodeSnippet("set${field.simpleName}(get${field.simpleName}() & ~(flags))"))
val mHas = mAdd.clone()
mHas.setSimpleName<Nothing>("has${field.simpleName}")
mHas.setType<Nothing>(field.factory.createTypeParam("boolean"))
mHas.setBody<Nothing>(field.factory.createCodeSnippet("return (get${field.simpleName}() & flags) != 0"))
val utilMethods = mutableListOf<String>()
val bindingFieldAnnotation = field.getAnnotation(A_NAME_BINDING_FIELD)
if (bindingFieldAnnotation?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_SETTER) == true) {
utilMethods += mAdd.prettyprint()
utilMethods += mRemove.prettyprint()
}
if (bindingFieldAnnotation?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_GETTER) == true) {
utilMethods += mHas.prettyprint()
}
return utilMethods
}
fun jvmFieldContent(field: CtField<*>): List<String> {
val fields = mutableListOf<String>()
val a = field.getAnnotation(A_NAME_BINDING_FIELD)
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_GETTER) == true) {
fields += createFieldGetContent(field)
}
if (a?.containsValue(A_VALUE_ACCESSORS, A_TYPE_VALUE_ACCESSOR_SETTER) == true) {
fields += createFieldSetContent(field)
}
if (field.getAnnotation(A_NAME_BINDING_FIELD)?.getValueAsObject(A_VALUE_IS_FLAG) == true) {
fields += createFieldFlagUtils(field)
}
return fields
}
private fun getJDoc(method: CtMethod<*>): CtJavaDoc? {
return method.comments.filterIsInstance<CtJavaDoc>().firstOrNull()
}
private fun sanitizeDocComment(method: CtMethod<*>) {
val jDoc = getJDoc(method) ?: return
val paramNames = method.parameters.map { it.simpleName }
jDoc.setTags<Nothing>(jDoc.tags.filter { it.type != CtJavaDocTag.TagType.PARAM || paramNames.contains(it.param) })
}
private fun sanitizeAnnotations(method: CtMethod<*>) {
method.setAnnotations<Nothing>(method.annotations.filter { !CLEANUP_ANNOTATIONS_LIST.contains(it.name) })
}
@@ -0,0 +1,48 @@
package tool.generator.api.task
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import spoon.Launcher
import spoon.support.compiler.FileSystemFolder
import tool.generator.api.BindingSourceProcessor
import tool.generator.api.ExcludedSourceProcessor
open class GenerateApi : DefaultTask() {
@Internal
override fun getGroup() = "build"
@Internal
override fun getDescription() = "Generate API for native binaries."
private val genSrcDir = project.file("src/main/java")
private val genDstDir = project.file("src/generated/java")
@TaskAction
fun run() {
logger.info("Generating API...")
logger.info("Removing old generated files in $genDstDir...")
project.file(genDstDir).deleteRecursively()
logger.info("Copying raw sources...")
logger.info("| from: $genSrcDir")
logger.info("| into: $genDstDir")
project.copy {
from(genSrcDir)
into(genDstDir)
}
logger.info("Processing generated sources...")
val launcher = Launcher()
launcher.addInputResource(FileSystemFolder(genDstDir))
val model = launcher.buildModel()
model.allTypes.filter(BindingSourceProcessor::isProcessable).forEach {
BindingSourceProcessor(it).process()
}
model.allTypes.filter(ExcludedSourceProcessor::isProcessable).forEach {
ExcludedSourceProcessor(it).process()
}
}
}
@@ -0,0 +1,186 @@
package tool.generator.api
import spoon.reflect.code.CtCodeSnippetStatement
import spoon.reflect.code.CtExpression
import spoon.reflect.code.CtFieldRead
import spoon.reflect.code.CtNewArray
import spoon.reflect.declaration.*
import spoon.reflect.factory.Factory
import spoon.reflect.reference.CtTypeParameterReference
import tool.generator.ast.Decl
import tool.generator.ast.DeclContainer
const val A_NAME_BINDING_SOURCE = "BindingSource"
const val A_NAME_BINDING_METHOD = "BindingMethod"
const val A_NAME_BINDING_FIELD = "BindingField"
const val A_NAME_BINDING_AST_ENUM = "BindingAstEnum"
const val A_NAME_OPT_ARG = "OptArg"
const val A_NAME_EXCLUDED_SOURCE = "ExcludedSource"
const val A_NAME_RETURN_VALUE = "ReturnValue"
const val A_NAME_ARG_VALUE = "ArgValue"
const val A_NAME_TYPE_ARRAY = "TypeArray"
const val A_VALUE_CALL_PTR = "callPtr"
const val A_VALUE_CALL_OPERATOR = "callOperator"
const val A_VALUE_CALL_PREFIX = "callPrefix"
const val A_VALUE_CALL_SUFFIX = "callSuffix"
const val A_VALUE_STATIC_CAST = "staticCast"
const val A_VALUE_REINTERPRET_CAST = "reinterpretCast"
const val A_VALUE_IS_STATIC = "isStatic"
const val A_VALUE_CALL_VALUE = "callValue"
const val A_VALUE_NAME = "name"
const val A_VALUE_CALL_NAME = "callName"
const val A_VALUE_ACCESSORS = "accessors"
const val A_VALUE_IS_FLAG = "isFlag"
const val A_VALUE_TYPE = "type"
const val A_VALUE_SIZE = "size"
const val A_VALUE_FILE = "file"
const val A_VALUE_QUAL_TYPE = "qualType"
const val A_VALUE_SANITIZE_NAME = "sanitizeName"
const val A_TYPE_VALUE_ACCESSOR_SETTER = "SETTER"
const val A_TYPE_VALUE_ACCESSOR_GETTER = "GETTER"
const val PTR_JVM_FIELD = "ptr"
const val PTR_JNI_CAST = "intptr_t"
const val PTR_JNI_THIS = "THIS"
const val PARAM_ARR_LEN_POSTFIX = "Count"
val CLEANUP_ANNOTATIONS_LIST = listOf(
A_NAME_BINDING_SOURCE,
A_NAME_BINDING_METHOD,
A_NAME_BINDING_FIELD,
A_NAME_RETURN_VALUE,
A_NAME_OPT_ARG,
A_NAME_ARG_VALUE,
A_NAME_TYPE_ARRAY,
A_NAME_BINDING_AST_ENUM,
)
val PRIMITIVE_PTR_TYPECAST_MAP = mapOf(
"boolean[]" to "bool*",
"byte[]" to "char*",
"short[]" to "short*",
"int[]" to "int*",
"float[]" to "float*",
"long[]" to "long*",
"double[]" to "double*",
)
val DST_RETURN_TYPE_SET = setOf(
"ImVec2",
"ImVec4",
"ImRect",
"ImPlotPoint",
"ImPlotRange",
"ImPlotLimits",
"TextEditorCoordinates",
)
fun CtElement.hasAnnotation(annotationName: String): Boolean {
return getAnnotation(annotationName) != null
}
fun CtElement.getAnnotation(annotationName: String): CtAnnotation<*>? {
return annotations.find { a -> a?.name == annotationName }
}
fun CtAnnotation<*>.containsValue(annotationField: String, value: String): Boolean {
val v = getValue<CtExpression<Any>>(annotationField)
if (v is CtFieldRead) {
return v.prettyprint().contains(value)
}
if (v is CtNewArray) {
return v.elements.find { it.prettyprint().contains(value) } != null
}
return false
}
fun Factory.createTypeParam(name: String): CtTypeParameterReference = createTypeParameterReference().apply {
setSimpleName<Nothing>(name)
}
fun Factory.createCodeSnippet(code: String): CtCodeSnippetStatement = createCodeSnippetStatement().apply {
setValue<Nothing>(code)
}
fun CtTypedElement<*>.isType(type: String): Boolean = this.type.simpleName == type
fun CtTypedElement<*>.isPrimitivePtrType(): Boolean {
return PRIMITIVE_PTR_TYPECAST_MAP.containsKey(type.simpleName)
}
fun CtTypeInformation.isPtrClass(): Boolean {
return getDeclaredOrInheritedField(PTR_JVM_FIELD) != null
}
fun CtMethod<*>.getJniName(): String = "n${simpleName.capitalize()}"
fun CtMethod<*>.getName(): String {
return getAnnotation(A_NAME_BINDING_METHOD)?.getValueAsString(A_VALUE_NAME)?.takeIf(String::isNotEmpty)
?: simpleName.decapitalize()
}
fun CtField<*>.getCallName(): String {
return getAnnotation(A_NAME_BINDING_FIELD)?.getValueAsString(A_VALUE_CALL_NAME)?.takeIf(String::isNotEmpty)
?: simpleName
}
fun findDefaultsCombinations(method: CtMethod<*>): List<IntArray> {
fun findDefaults(params: List<CtParameter<*>>): Map<Int, String> {
val defaults = mutableMapOf<Int, String>()
for ((index, p) in params.withIndex()) {
p.getAnnotation(A_NAME_OPT_ARG)?.let { a ->
val value = a.getValueAsString(A_VALUE_CALL_VALUE)
if (value.isNotEmpty()) {
defaults[index] = value
}
}
}
return defaults
}
fun concatNextParam0(combinations: MutableList<IntArray>, idx0: Int, idxN: Int) {
val p1 = method.parameters[idx0]
val p2 = method.parameters[idxN]
if (p1.type.simpleName != p2.type.simpleName) {
combinations.add(((0 until idx0) + (idxN until method.parameters.size)).toIntArray())
if (p2.getAnnotation(A_NAME_OPT_ARG)?.getValueAsString(A_VALUE_CALL_VALUE)?.isNotEmpty() == true) {
concatNextParam0(combinations, idx0, idxN + 1)
}
}
}
val combinations = mutableListOf<IntArray>()
findDefaults(method.parameters).forEach { (idx, _) -> concatNextParam0(combinations, idx, idx + 1) }
return combinations
}
fun findFirstOptParam(method: CtMethod<*>): Int {
if (method.parameters.isEmpty()) {
return method.parameters.size
}
for ((index, p) in method.parameters.withIndex()) {
if (p.hasAnnotation(A_NAME_OPT_ARG)) {
return index
}
}
return method.parameters.size
}
fun Collection<Decl>.filterDecls0(filter: (Decl) -> Boolean): Collection<Decl> {
val result = mutableListOf<Decl>()
fun filter0(decls: Collection<Decl>) {
decls.forEach { decl ->
if (filter(decl)) {
result += decl
}
if (decl is DeclContainer) {
filter0(decl.decls)
}
}
}
filter0(this)
return result
}
@@ -0,0 +1,22 @@
package tool.generator.ast
import com.fasterxml.jackson.databind.ObjectMapper
import java.io.InputStream
object AstParser {
const val RESOURCE_PATH: String = "generator/api/ast"
private val astCache = mutableMapOf<String, AstRoot>()
fun readFromResource(astResourcePath: String): AstRoot {
return astCache.computeIfAbsent(astResourcePath) {
val astResourceContent = getAtResourceStream(astResourcePath).use { s -> s.reader().use { r -> r.readText() } }
val objectMapper = ObjectMapper()
objectMapper.readValue(astResourceContent, AstRoot::class.java)
}
}
private fun getAtResourceStream(astResourcePath: String): InputStream {
return AstRoot::class.java.classLoader.getResourceAsStream("$RESOURCE_PATH/$astResourcePath")!!
}
}
@@ -0,0 +1,127 @@
package tool.generator.ast
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes(
JsonSubTypes.Type(AstNamespaceDecl::class),
JsonSubTypes.Type(AstFullComment::class),
JsonSubTypes.Type(AstParagraphComment::class),
JsonSubTypes.Type(AstTextComment::class),
JsonSubTypes.Type(AstFunctionDecl::class),
JsonSubTypes.Type(AstParmVarDecl::class),
JsonSubTypes.Type(AstEnumDecl::class),
JsonSubTypes.Type(AstEnumConstantDecl::class),
JsonSubTypes.Type(AstRecordDecl::class),
JsonSubTypes.Type(AstFieldDecl::class),
)
interface Decl {
val offset: Int
}
interface DeclContainer {
val decls: List<Decl>
}
data class AstRoot(
val info: AstInfo = AstInfo(),
override val decls: List<Decl> = emptyList()
) : DeclContainer
data class AstInfo(
val source: String = "",
val hash: String = "",
val url: String = "",
val revision: String = "",
)
data class AstNamespaceDecl(
override val offset: Int = -1,
val name: String = "",
override val decls: List<Decl> = emptyList(),
) : Decl, DeclContainer
data class AstFullComment(
override val offset: Int = -1,
override val decls: List<Decl> = emptyList(),
) : Decl, DeclContainer
data class AstParagraphComment(
override val offset: Int = -1,
override val decls: List<Decl> = emptyList(),
) : Decl, DeclContainer
data class AstTextComment(
override val offset: Int = -1,
val text: String = "",
) : Decl
data class AstFunctionDecl(
override val offset: Int = -1,
val name: String = "",
val resultType: String = "",
override val decls: List<Decl> = emptyList(),
) : Decl, DeclContainer {
@JsonIgnore
fun getParams(): List<AstParmVarDecl> {
return decls.filterIsInstance<AstParmVarDecl>()
}
}
data class AstParmVarDecl(
override val offset: Int = -1,
val name: String = "",
val qualType: String = "",
val desugaredQualType: String = "",
val defaultValue: String? = null,
) : Decl {
companion object {
const val FORMAT_ATTR_NAME = "#FORMAT_ATTR_MARKER#"
fun asFormatAttr(offset: Int): AstParmVarDecl {
return AstParmVarDecl(
offset = offset,
name = FORMAT_ATTR_NAME,
qualType = FORMAT_ATTR_NAME,
desugaredQualType = FORMAT_ATTR_NAME,
defaultValue = null
)
}
}
@JsonIgnore
fun isFormatAttr(): Boolean {
return this.name == FORMAT_ATTR_NAME
}
}
data class AstEnumDecl(
override val offset: Int = -1,
val name: String = "",
override val decls: List<Decl> = emptyList(),
) : Decl, DeclContainer
data class AstEnumConstantDecl(
override val offset: Int = -1,
val name: String = "",
val docComment: String? = null,
val qualType: String = "",
val order: Int = -1,
val value: String? = null,
val evaluatedValue: String? = null,
) : Decl
data class AstRecordDecl(
override val offset: Int = -1,
val name: String = "",
override val decls: List<Decl> = emptyList(),
) : Decl, DeclContainer
data class AstFieldDecl(
override val offset: Int = -1,
val name: String = "",
val qualType: String = "",
val desugaredQualType: String = "",
) : Decl
@@ -0,0 +1,530 @@
package tool.generator.ast.task
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.node.ObjectNode
import com.lordcodes.turtle.shellRun
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import tool.generator.ast.*
import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
import java.util.*
open class GenerateAst : DefaultTask() {
@Internal
override fun getGroup() = "build"
@Internal
override fun getDescription() = "Generate AST tree for declared header files."
@InputFiles
lateinit var headerFiles: Map<File, Collection<String>>
private val dstDir: File = File("${project.rootDir}/buildSrc/src/main/resources/${AstParser.RESOURCE_PATH}")
private val objectMapper: ObjectMapper = ObjectMapper()
/**
* During the parsing of ast-dump we need to access an initial header file content.
* Required for cases like: get the content of the file (comment or default param value) by offset pos in file.
*/
private var currentParsingHeaderContent: String = ""
/**
* This is used only to log stuff properly.
*/
private var currentParsingIndent: Int = 0
init {
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true)
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
}
@TaskAction
fun run() {
logger.info("Generating AST...")
dstDir.mkdirs()
val scriptPath = "$dstDir/_gen_ast.sh"
File(scriptPath).setExecutable(true)
logger.info("Processing headers...")
headerFiles.forEach { (header, defines) ->
System.gc()
logger.info("| $header")
// Store the header content in the field, so we can access it when parsing.
currentParsingHeaderContent = header.readText()
// Header hash content to create a unique path for generated content.
val headerHash = md5Hash(currentParsingHeaderContent)
// Append additional defines if provided.
currentParsingHeaderContent = defines.joinToString("\n") + currentParsingHeaderContent
// We create a unique folder to store files generated for the header.
// This is necessary because when we store all headers in the same directory,
// their AST dump can overlap with each other.
val astBuildDir = File("${project.buildDir}/generated/ast/$headerHash")
astBuildDir.mkdirs()
// Move the header content to the temp file to make an ast-dump of it.
val headerName = header.nameWithoutExtension
val tmpHeaderFile = File("$astBuildDir/$headerName.h")
tmpHeaderFile.createNewFile()
tmpHeaderFile.writeText(currentParsingHeaderContent)
logger.info(" | Making an ast-dump...")
// Destination for ast-dump.
val astBumpJson = File("$astBuildDir/$headerName.json")
// Call clang++ with the script.
// During the process of making an ast-dump there could be errors/warnings.
// Thus making a call like that can help to ignore them.
callClangAstBump(scriptPath, tmpHeaderFile, astBumpJson)
logger.info(" | Processing an ast-dump result: $astBumpJson...")
// Read the ast-dump of the whole header content.
// It will contain technical information, like unneeded typdefs, std types etc.
val fullDeclsList = mutableListOf<Decl>()
objectMapper.readTree(astBumpJson).get("inner").forEach { topDecl ->
parseDeclNode0(topDecl)?.let(fullDeclsList::add)
}
//////////////
// After parsing the entire header file, we need to remove unnecessary information from it.
// So we pre-process the initial content and parse it again.
// This way, we can compare the initial result with the trimmed version
// to remove types and declarations that represent compiler information.
// Remove all includes and typedefs.
// They are not needed to process the AST, but they add extra information to the final dump.
// And since we want to have an ast-dump as minimal as possible we remove that information.
currentParsingHeaderContent = currentParsingHeaderContent
.replace("#include .*".toRegex(), "")
.replace("typedef .*".toRegex(), "")
// Re-write with pre-processed content.
tmpHeaderFile.writeText(currentParsingHeaderContent)
callClangAstBump(scriptPath, tmpHeaderFile, astBumpJson)
// As its said - we do the parsing once again, but now the final result won't contain std types.
val strippedDeclsList = mutableListOf<Decl>()
objectMapper.readTree(astBumpJson).get("inner").forEach { topDecl ->
parseDeclNode0(topDecl)?.let(strippedDeclsList::add)
}
logger.info(" | Removing an ast-decls diff...")
val resultDeclsList = removeAstDiff(fullDeclsList, strippedDeclsList)
logger.info(" | Sorting an ast-decls...")
sortDecls0(resultDeclsList)
///////////////
// In the end we write down the ast-decls result to the file.
// The file itself will be stored in VCS, so we can understand if there were any changes in native code.
val astResultJson = File("$dstDir/ast-$headerName.json")
logger.info(" | Writing processed AST: $astResultJson...")
objectMapper.writer().writeValue(
astResultJson, AstRoot(
info = AstInfo(
source = header.relativeTo(project.rootDir).path,
hash = headerHash,
url = shellRun(header.parentFile) {
command("git", listOf("config", "--get", "remote.origin.url"))
},
revision = shellRun(header.parentFile) {
command("git", listOf("rev-parse", "HEAD"))
},
),
decls = resultDeclsList
)
)
}
}
private fun parseDeclNode0(declNode: JsonNode): Decl? {
fun logParsingDecl(name: String) {
logger.info(" ${" ".repeat(currentParsingIndent)}| Parsing $name...")
}
fun logParsingInner(name: String) {
logger.info(" ${" ".repeat(currentParsingIndent)}| $name")
}
fun JsonNode.hasNoName(): Boolean {
return !has("name") || get("name").asText().isBlank()
}
fun JsonNode.hasNoInnerContent(): Boolean {
return !has("inner") || get("inner").isEmpty
}
fun JsonNode.getOffset(): Int {
val loc = (get("loc") ?: get("range").get("begin")).let { loc ->
if (loc.has("expansionLoc")) {
loc.get("expansionLoc")
} else {
loc
}
}
return loc.get("offset")?.asInt() ?: -1
}
fun JsonNode.getDefaultParamValue(): String {
fun getOffset(jsonNode: JsonNode): Pair<Int, Int> {
val loc = if (jsonNode.has("expansionLoc")) {
jsonNode.get("expansionLoc")
} else {
jsonNode
}
return loc.get("offset").asInt() to loc.get("tokLen").asInt()
}
return get("inner").get(0).get("range").let { range ->
val (beginOffset, _) = getOffset(range.get("begin"))
val (endOffset, endTokLoc) = getOffset(range.get("end"))
currentParsingHeaderContent.substring(beginOffset, endOffset + endTokLoc)
}
}
try {
return when (declNode.get("kind").textValue()) {
"NamespaceDecl" -> {
if (declNode.hasNoInnerContent()) {
return null
}
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val decls = mutableListOf<Decl>()
if (name.startsWith("_")) { // internal API namespaces
return null
}
logParsingDecl("namespace $name")
currentParsingIndent++
declNode.get("inner").forEach { innerDecl ->
parseDeclNode0(innerDecl)?.let(decls::add)
}
currentParsingIndent--
return AstNamespaceDecl(offset, name, decls)
}
"FullComment" -> {
if (declNode.hasNoInnerContent()) {
return null
}
val offset = declNode.getOffset()
val decls = mutableListOf<Decl>()
logParsingDecl("full comment")
currentParsingIndent++
declNode.get("inner").forEach { innerDecl ->
parseDeclNode0(innerDecl)?.let(decls::add)
}
currentParsingIndent--
return AstFullComment(offset, decls)
}
"ParagraphComment" -> {
if (declNode.hasNoInnerContent()) {
return null
}
val offset = declNode.getOffset()
val decls = mutableListOf<Decl>()
logParsingDecl("paragraph comment")
currentParsingIndent++
declNode.get("inner").forEach { innerDecl ->
parseDeclNode0(innerDecl)?.let(decls::add)
}
currentParsingIndent--
return AstParagraphComment(offset, decls)
}
"TextComment" -> {
val offset = declNode.getOffset()
val text = declNode.get("text").asText()
logParsingInner("text comment")
return AstTextComment(offset, text)
}
"FunctionDecl", "CXXMethodDecl" -> {
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val resultType = declNode.get("type").get("qualType").asText().substringBefore("(").trim()
val decls = mutableListOf<Decl>()
logParsingDecl("function $name")
currentParsingIndent++
declNode.get("inner")?.forEach { innerDecl ->
parseDeclNode0(innerDecl)?.let(decls::add)
}
currentParsingIndent--
return AstFunctionDecl(offset, name, resultType, decls)
}
"ParmVarDecl" -> {
if (declNode.hasNoName()) {
return null
}
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val qualType = declNode.get("type").get("qualType").asText()
val desugaredQualType = declNode.get("type").get("desugaredQualType")?.asText() ?: qualType
val defaultValue: String? = if (declNode.has("init")) {
declNode.getDefaultParamValue()
} else {
null
}
logParsingInner("param $name")
return AstParmVarDecl(offset, name, qualType, desugaredQualType, defaultValue)
}
"FormatAttr" -> {
logParsingInner("param ...")
return AstParmVarDecl.asFormatAttr(declNode.getOffset())
}
"EnumDecl" -> {
if (declNode.hasNoInnerContent() || declNode.hasNoName()) {
return null
}
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val decls = mutableListOf<Decl>()
logParsingDecl("enum $name")
currentParsingIndent++
declNode.get("inner").forEach { innerDecl ->
parseDeclNode0(innerDecl)?.let(decls::add)
}
currentParsingIndent
// Sort enums by their real pos in the header file.
sortDecls0(decls)
var order = 0
val sortedDecls = decls.map { decl ->
if (decl is AstEnumConstantDecl) {
if (order == 0 && decl.value != null) {
order = decl.value.toIntOrNull() ?: 0
}
AstEnumConstantDecl(
decl.offset,
decl.name,
decl.docComment,
decl.qualType,
order++,
decl.value,
decl.evaluatedValue
)
} else {
decl
}
}
return AstEnumDecl(offset, name, sortedDecls)
}
"EnumConstantDecl" -> {
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val qualType = declNode.get("type").get("qualType").asText()
// ->inner[0]->
val declValue: String? = declNode.get("inner")?.get(0)?.let {
// Check if the first node is not a comment.
if (it.get("kind").asText() != "FullComment") {
declNode.getDefaultParamValue()
} else {
null
}
}
val evaluatedValue: String? = findNode0(declNode, "kind", "ConstantExpr")?.get("value")?.asText()
val docComment: String? = declNode.findValuesAsText("text")?.joinToString(" ") { it.trim() }
logParsingInner("enum value $name")
return AstEnumConstantDecl(
offset,
name,
docComment,
qualType,
// We provide a proper order in EnumDecl, after sorting all enums by their offset.
-1,
declValue,
evaluatedValue
)
}
"CXXRecordDecl" -> {
if (declNode.hasNoInnerContent() || declNode.hasNoName()) {
return null
}
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val decls = mutableListOf<Decl>()
logParsingDecl("record $name")
currentParsingIndent++
declNode.get("inner").forEach { innerDecl ->
parseDeclNode0(innerDecl)?.let(decls::add)
}
currentParsingIndent--
return AstRecordDecl(offset, name, decls)
}
"FieldDecl" -> {
if (declNode.hasNoName()) {
return null
}
val offset = declNode.getOffset()
val name = declNode.get("name").asText()
val qualType = declNode.get("type").get("qualType").asText()
val desugaredQualType = declNode.get("type").get("desugaredQualType")?.asText() ?: qualType
logParsingInner("field $name")
return AstFieldDecl(offset, name, qualType, desugaredQualType)
}
else -> {
null
}
}
} catch (e: Exception) {
logger.error("Unable to parse: {}", declNode)
throw Error(e)
}
}
private fun md5Hash(str: String): String {
val md = MessageDigest.getInstance("MD5")
val bigInt = BigInteger(1, md.digest(str.toByteArray(Charsets.UTF_8)))
return String.format("%032x", bigInt)
}
private fun callClangAstBump(scriptPath: String, srcHeader: File, dstJson: File) {
dstJson.delete()
val pb = ProcessBuilder()
pb.command(scriptPath, srcHeader.absolutePath, dstJson.absolutePath)
pb.start().waitFor()
}
/**
* Function removes difference between ast-trees.
* Basically, it returns the list which contains only types with the same name.
*/
private fun removeAstDiff(fullDeclsList: List<Decl>, strippedDeclsList: List<Decl>): List<Decl> {
fun isSameNamespaceName(d1: Decl, d2: Decl): Boolean {
return d1 is AstNamespaceDecl && d2 is AstNamespaceDecl && d1.name == d2.name
}
fun isSameRecordName(d1: Decl, d2: Decl): Boolean {
return d1 is AstRecordDecl && d2 is AstRecordDecl && d1.name == d2.name
}
fun isSameEnumName(d1: Decl, d2: Decl): Boolean {
return d1 is AstEnumDecl && d2 is AstEnumDecl && d1.name == d2.name
}
fun isSameFunctionName(d1: Decl, d2: Decl): Boolean {
return d1 is AstFunctionDecl && d2 is AstFunctionDecl && d1.name == d2.name
}
return fullDeclsList
.filter { d1 ->
strippedDeclsList
.find { d2 ->
d1 == d2
|| isSameRecordName(d1, d2)
|| isSameNamespaceName(d1, d2)
|| isSameEnumName(d1, d2)
|| isSameFunctionName(d1, d2)
} != null
}
.filterNot { it is AstFunctionDecl && it.name.startsWith("operator") }
.toMutableList() // Ensure final list will be mutable for later sorting.
}
/**
* Sort decls in the provided list recursively using offset value.
*/
@Suppress("JavaCollectionsStaticMethodOnImmutableList")
private fun sortDecls0(decls: List<Decl>) {
Collections.sort(decls) { d1, d2 ->
d1.offset.compareTo(d2.offset)
}
decls.forEach {
if (it is DeclContainer) {
sortDecls0(it.decls)
}
}
}
private fun findNode0(root: JsonNode, field: String, value: String): JsonNode? {
if (root.isObject) {
val objectNode = root as ObjectNode
val fields = objectNode.fields()
while (fields.hasNext()) {
val entry = fields.next()
if (entry.key == field && entry.value.asText() == value) {
return root
} else {
val foundNode = findNode0(entry.value, field, value)
if (foundNode != null) {
return foundNode
}
}
}
} else if (root.isArray) {
for (node in root) {
val foundNode = findNode0(node, field, value)
if (foundNode != null) {
return foundNode
}
}
}
return null
}
}
@@ -0,0 +1,7 @@
#!/usr/bin/env sh
# Separate shell script required to ensure that clang++ command is runnable.
# Java ProcessBuilder doesn't see command properly.
# Also, running like that helps to ignore local clang++ warnings which can happen.
clang++ -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments -fmerge-all-constants "$1" >> "$2"
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,779 @@
{
"info" : {
"source" : "include/imguizmo/ImGuizmo.h",
"hash" : "e4cff3bc8014e2bccae881f1b0535298",
"url" : "https://github.com/CedricGuillemet/ImGuizmo.git",
"revision" : "f7bbbe39971d9d45816417a70e9b53a0f698c56e"
},
"decls" : [ {
"@type" : "AstNamespaceDecl",
"offset" : 4983,
"name" : "ImGuizmo",
"decls" : [ {
"@type" : "AstFunctionDecl",
"offset" : 5199,
"name" : "SetDrawlist",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 5001,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 5001,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 5001,
"text" : " call inside your own window and before Manipulate() in order to draw gizmo to that window."
}, {
"@type" : "AstTextComment",
"offset" : 5099,
"text" : " Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList())."
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 5223,
"name" : "drawlist",
"qualType" : "int *",
"desugaredQualType" : "int *",
"defaultValue" : "="
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 5323,
"name" : "BeginFrame",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 5252,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 5252,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 5252,
"text" : " call BeginFrame right after ImGui_XXXX_NewFrame();"
} ]
} ]
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 5687,
"name" : "SetImGuiContext",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 5345,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 5345,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 5345,
"text" : " this is necessary because when imguizmo is compiled into a dll, and imgui into another"
}, {
"@type" : "AstTextComment",
"offset" : 5439,
"text" : " globals are not shared between them."
}, {
"@type" : "AstTextComment",
"offset" : 5483,
"text" : " More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam"
}, {
"@type" : "AstTextComment",
"offset" : 5632,
"text" : " expose method to set imgui context"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 5717,
"name" : "ctx",
"qualType" : "int *",
"desugaredQualType" : "int *"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 5838,
"name" : "IsOver",
"resultType" : "bool",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 5731,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 5731,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 5731,
"text" : " return true if mouse cursor is over any gizmo control (axis, plan or screen component)"
} ]
} ]
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 5939,
"name" : "IsUsing",
"resultType" : "bool",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 5856,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 5856,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 5856,
"text" : " return true if mouse IsOver or if the gizmo is in moving state"
} ]
} ]
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 6121,
"name" : "Enable",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 5958,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 5958,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 5958,
"text" : " enable/disable the gizmo. Stay in the state until next call to Enable."
}, {
"@type" : "AstTextComment",
"offset" : 6036,
"text" : " gizmo is rendered with gray half transparent color when disabled"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 6133,
"name" : "enable",
"qualType" : "bool",
"desugaredQualType" : "bool"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 6946,
"name" : "DecomposeMatrixToComponents",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 6150,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 6150,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 6150,
"text" : " helper functions for manualy editing translation/rotation/scale with an input float"
}, {
"@type" : "AstTextComment",
"offset" : 6241,
"text" : " translation, rotation and scale float points to 3 floats each"
}, {
"@type" : "AstTextComment",
"offset" : 6310,
"text" : " Angles are in degrees (more suitable for human editing)"
}, {
"@type" : "AstTextComment",
"offset" : 6373,
"text" : " example:"
}, {
"@type" : "AstTextComment",
"offset" : 6389,
"text" : " float matrixTranslation[3], matrixRotation[3], matrixScale[3];"
}, {
"@type" : "AstTextComment",
"offset" : 6459,
"text" : " ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale);"
}, {
"@type" : "AstTextComment",
"offset" : 6570,
"text" : " ImGui::InputFloat3(\"Tr\", matrixTranslation, 3);"
}, {
"@type" : "AstTextComment",
"offset" : 6625,
"text" : " ImGui::InputFloat3(\"Rt\", matrixRotation, 3);"
}, {
"@type" : "AstTextComment",
"offset" : 6677,
"text" : " ImGui::InputFloat3(\"Sc\", matrixScale, 3);"
}, {
"@type" : "AstTextComment",
"offset" : 6726,
"text" : " ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16);"
} ]
}, {
"@type" : "AstParagraphComment",
"offset" : 6846,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 6846,
"text" : " These functions have some numerical stability issues for now. Use with caution."
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 6987,
"name" : "matrix",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7002,
"name" : "translation",
"qualType" : "float *",
"desugaredQualType" : "float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7022,
"name" : "rotation",
"qualType" : "float *",
"desugaredQualType" : "float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7039,
"name" : "scale",
"qualType" : "float *",
"desugaredQualType" : "float *"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 7066,
"name" : "RecomposeMatrixFromComponents",
"resultType" : "void",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 7109,
"name" : "translation",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7135,
"name" : "rotation",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7158,
"name" : "scale",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7172,
"name" : "matrix",
"qualType" : "float *",
"desugaredQualType" : "float *"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 7202,
"name" : "SetRect",
"resultType" : "void",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 7216,
"name" : "x",
"qualType" : "float",
"desugaredQualType" : "float"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7225,
"name" : "y",
"qualType" : "float",
"desugaredQualType" : "float"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7234,
"name" : "width",
"qualType" : "float",
"desugaredQualType" : "float"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7247,
"name" : "height",
"qualType" : "float",
"desugaredQualType" : "float"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 7299,
"name" : "SetOrthographic",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 7262,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 7262,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 7262,
"text" : " default is false"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 7320,
"name" : "isOrthographic",
"qualType" : "bool",
"desugaredQualType" : "bool"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 7449,
"name" : "DrawCubes",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 7345,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 7345,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 7345,
"text" : " Render a cube with face color corresponding to face normal. Usefull for debug/tests"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 7472,
"name" : "view",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7491,
"name" : "projection",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7516,
"name" : "matrices",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7530,
"name" : "matrixCount",
"qualType" : "int",
"desugaredQualType" : "int"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 7563,
"name" : "DrawGrid",
"resultType" : "void",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 7585,
"name" : "view",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7604,
"name" : "projection",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7629,
"name" : "matrix",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 7649,
"name" : "gridSize",
"qualType" : "const float",
"desugaredQualType" : "const float"
} ]
}, {
"@type" : "AstEnumDecl",
"offset" : 7946,
"name" : "OPERATION",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 7668,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 7668,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 7668,
"text" : " call it when you want a gizmo"
}, {
"@type" : "AstTextComment",
"offset" : 7705,
"text" : " Needs view and projection matrices. "
}, {
"@type" : "AstTextComment",
"offset" : 7749,
"text" : " matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional"
}, {
"@type" : "AstTextComment",
"offset" : 7898,
"text" : " translation is applied in world space"
} ]
} ]
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 7969,
"name" : "TRANSLATE_X",
"qualType" : "ImGuizmo::OPERATION",
"order" : 0,
"value" : "(1u << 0)",
"evaluatedValue" : "1"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8006,
"name" : "TRANSLATE_Y",
"qualType" : "ImGuizmo::OPERATION",
"order" : 1,
"value" : "(1u << 1)",
"evaluatedValue" : "2"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8043,
"name" : "TRANSLATE_Z",
"qualType" : "ImGuizmo::OPERATION",
"order" : 2,
"value" : "(1u << 2)",
"evaluatedValue" : "4"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8080,
"name" : "ROTATE_X",
"qualType" : "ImGuizmo::OPERATION",
"order" : 3,
"value" : "(1u << 3)",
"evaluatedValue" : "8"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8117,
"name" : "ROTATE_Y",
"qualType" : "ImGuizmo::OPERATION",
"order" : 4,
"value" : "(1u << 4)",
"evaluatedValue" : "16"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8154,
"name" : "ROTATE_Z",
"qualType" : "ImGuizmo::OPERATION",
"order" : 5,
"value" : "(1u << 5)",
"evaluatedValue" : "32"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8191,
"name" : "ROTATE_SCREEN",
"qualType" : "ImGuizmo::OPERATION",
"order" : 6,
"value" : "(1u << 6)",
"evaluatedValue" : "64"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8228,
"name" : "SCALE_X",
"qualType" : "ImGuizmo::OPERATION",
"order" : 7,
"value" : "(1u << 7)",
"evaluatedValue" : "128"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8265,
"name" : "SCALE_Y",
"qualType" : "ImGuizmo::OPERATION",
"order" : 8,
"value" : "(1u << 8)",
"evaluatedValue" : "256"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8302,
"name" : "SCALE_Z",
"qualType" : "ImGuizmo::OPERATION",
"order" : 9,
"value" : "(1u << 9)",
"evaluatedValue" : "512"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8339,
"name" : "BOUNDS",
"qualType" : "ImGuizmo::OPERATION",
"order" : 10,
"value" : "(1u << 10)",
"evaluatedValue" : "1024"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8377,
"name" : "TRANSLATE",
"qualType" : "ImGuizmo::OPERATION",
"order" : 11,
"value" : "TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z",
"evaluatedValue" : "7"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8437,
"name" : "ROTATE",
"qualType" : "ImGuizmo::OPERATION",
"order" : 12,
"value" : "ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN",
"evaluatedValue" : "120"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8501,
"name" : "SCALE",
"qualType" : "ImGuizmo::OPERATION",
"order" : 13,
"value" : "SCALE_X | SCALE_Y | SCALE_Z",
"evaluatedValue" : "896"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 8567,
"name" : "operator|",
"resultType" : "OPERATION",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 8587,
"name" : "lhs",
"qualType" : "OPERATION",
"desugaredQualType" : "ImGuizmo::OPERATION"
}, {
"@type" : "AstParmVarDecl",
"offset" : 8602,
"name" : "rhs",
"qualType" : "OPERATION",
"desugaredQualType" : "ImGuizmo::OPERATION"
} ]
}, {
"@type" : "AstEnumDecl",
"offset" : 8714,
"name" : "MODE",
"decls" : [ {
"@type" : "AstEnumConstantDecl",
"offset" : 8732,
"name" : "LOCAL",
"qualType" : "ImGuizmo::MODE",
"order" : 0
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 8746,
"name" : "WORLD",
"qualType" : "ImGuizmo::MODE",
"order" : 1
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 8780,
"name" : "Manipulate",
"resultType" : "bool",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 8804,
"name" : "view",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 8823,
"name" : "projection",
"qualType" : "const float *",
"desugaredQualType" : "const float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 8845,
"name" : "operation",
"qualType" : "OPERATION",
"desugaredQualType" : "ImGuizmo::OPERATION"
}, {
"@type" : "AstParmVarDecl",
"offset" : 8861,
"name" : "mode",
"qualType" : "MODE",
"desugaredQualType" : "ImGuizmo::MODE"
}, {
"@type" : "AstParmVarDecl",
"offset" : 8874,
"name" : "matrix",
"qualType" : "float *",
"desugaredQualType" : "float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 8889,
"name" : "deltaMatrix",
"qualType" : "float *",
"desugaredQualType" : "float *",
"defaultValue" : "="
}, {
"@type" : "AstParmVarDecl",
"offset" : 8922,
"name" : "snap",
"qualType" : "const float *",
"desugaredQualType" : "const float *",
"defaultValue" : "="
}, {
"@type" : "AstParmVarDecl",
"offset" : 8948,
"name" : "localBounds",
"qualType" : "const float *",
"desugaredQualType" : "const float *",
"defaultValue" : "="
}, {
"@type" : "AstParmVarDecl",
"offset" : 8981,
"name" : "boundsSnap",
"qualType" : "const float *",
"desugaredQualType" : "const float *",
"defaultValue" : "="
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 9338,
"name" : "ViewManipulate",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 9014,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 9014,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 9014,
"text" : " Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en"
}, {
"@type" : "AstTextComment",
"offset" : 9127,
"text" : " It seems to be a defensive patent in the US. I don't think it will bring troubles using it as"
}, {
"@type" : "AstTextComment",
"offset" : 9228,
"text" : " other software are using the same mechanics. But just in case, you are now warned!"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 9360,
"name" : "view",
"qualType" : "float *",
"desugaredQualType" : "float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 9372,
"name" : "length",
"qualType" : "float",
"desugaredQualType" : "float"
}, {
"@type" : "AstParmVarDecl",
"offset" : 9387,
"name" : "position",
"qualType" : "int",
"desugaredQualType" : "int"
}, {
"@type" : "AstParmVarDecl",
"offset" : 9404,
"name" : "size",
"qualType" : "int",
"desugaredQualType" : "int"
}, {
"@type" : "AstParmVarDecl",
"offset" : 9416,
"name" : "backgroundColor",
"qualType" : "int",
"desugaredQualType" : "int"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 9455,
"name" : "SetID",
"resultType" : "void",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 9465,
"name" : "id",
"qualType" : "int",
"desugaredQualType" : "int"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 9554,
"name" : "IsOver",
"resultType" : "bool",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 9478,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 9478,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 9478,
"text" : " return true if the cursor is over the operation's gizmo"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 9571,
"name" : "op",
"qualType" : "OPERATION",
"desugaredQualType" : "ImGuizmo::OPERATION"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 9595,
"name" : "SetGizmoSizeClipSpace",
"resultType" : "void",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 9623,
"name" : "value",
"qualType" : "float",
"desugaredQualType" : "float"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 9821,
"name" : "AllowAxisFlip",
"resultType" : "void",
"decls" : [ {
"@type" : "AstFullComment",
"offset" : 9639,
"decls" : [ {
"@type" : "AstParagraphComment",
"offset" : 9639,
"decls" : [ {
"@type" : "AstTextComment",
"offset" : 9639,
"text" : " Allow axis to flip"
}, {
"@type" : "AstTextComment",
"offset" : 9665,
"text" : " When true (default), the guizmo axis flip for better visibility"
}, {
"@type" : "AstTextComment",
"offset" : 9736,
"text" : " When false, they always stay along the positive world/local axis"
} ]
} ]
}, {
"@type" : "AstParmVarDecl",
"offset" : 9840,
"name" : "value",
"qualType" : "bool",
"desugaredQualType" : "bool"
} ]
} ]
} ]
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,279 @@
{
"info" : {
"source" : "include/imgui-knobs/imgui-knobs.h",
"hash" : "1f637f21a158aff7116bdb283dc83d46",
"url" : "https://github.com/altschuler/imgui-knobs",
"revision" : "4f207526f9ef036a0aff7edfaad92cfbe12d987a"
},
"decls" : [ {
"@type" : "AstEnumDecl",
"offset" : 87,
"name" : "ImGuiKnobFlags_",
"decls" : [ {
"@type" : "AstEnumConstantDecl",
"offset" : 109,
"name" : "ImGuiKnobFlags_NoTitle",
"qualType" : "ImGuiKnobFlags_",
"order" : 0,
"value" : "1 << 0",
"evaluatedValue" : "1"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 146,
"name" : "ImGuiKnobFlags_NoInput",
"qualType" : "ImGuiKnobFlags_",
"order" : 1,
"value" : "1 << 1",
"evaluatedValue" : "2"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 183,
"name" : "ImGuiKnobFlags_ValueTooltip",
"qualType" : "ImGuiKnobFlags_",
"order" : 2,
"value" : "1 << 2",
"evaluatedValue" : "4"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 225,
"name" : "ImGuiKnobFlags_DragHorizontal",
"qualType" : "ImGuiKnobFlags_",
"order" : 3,
"value" : "1 << 3",
"evaluatedValue" : "8"
} ]
}, {
"@type" : "AstEnumDecl",
"offset" : 305,
"name" : "ImGuiKnobVariant_",
"decls" : [ {
"@type" : "AstEnumConstantDecl",
"offset" : 329,
"name" : "ImGuiKnobVariant_Tick",
"qualType" : "ImGuiKnobVariant_",
"order" : 0,
"value" : "1 << 0",
"evaluatedValue" : "1"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 365,
"name" : "ImGuiKnobVariant_Dot",
"qualType" : "ImGuiKnobVariant_",
"order" : 1,
"value" : "1 << 1",
"evaluatedValue" : "2"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 400,
"name" : "ImGuiKnobVariant_Wiper",
"qualType" : "ImGuiKnobVariant_",
"order" : 2,
"value" : "1 << 2",
"evaluatedValue" : "4"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 437,
"name" : "ImGuiKnobVariant_WiperOnly",
"qualType" : "ImGuiKnobVariant_",
"order" : 3,
"value" : "1 << 3",
"evaluatedValue" : "8"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 478,
"name" : "ImGuiKnobVariant_WiperDot",
"qualType" : "ImGuiKnobVariant_",
"order" : 4,
"value" : "1 << 4",
"evaluatedValue" : "16"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 518,
"name" : "ImGuiKnobVariant_Stepped",
"qualType" : "ImGuiKnobVariant_",
"order" : 5,
"value" : "1 << 5",
"evaluatedValue" : "32"
}, {
"@type" : "AstEnumConstantDecl",
"offset" : 557,
"name" : "ImGuiKnobVariant_Space",
"qualType" : "ImGuiKnobVariant_",
"order" : 6,
"value" : "1 << 6",
"evaluatedValue" : "64"
} ]
}, {
"@type" : "AstNamespaceDecl",
"offset" : 604,
"name" : "ImGuiKnobs",
"decls" : [ {
"@type" : "AstRecordDecl",
"offset" : 629,
"name" : "color_set",
"decls" : [ {
"@type" : "AstFieldDecl",
"offset" : 657,
"name" : "base",
"qualType" : "int",
"desugaredQualType" : "int"
}, {
"@type" : "AstFieldDecl",
"offset" : 679,
"name" : "hovered",
"qualType" : "int",
"desugaredQualType" : "int"
}, {
"@type" : "AstFieldDecl",
"offset" : 704,
"name" : "active",
"qualType" : "int",
"desugaredQualType" : "int"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 974,
"name" : "Knob",
"resultType" : "bool",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 991,
"name" : "label",
"qualType" : "const char *",
"desugaredQualType" : "const char *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1005,
"name" : "p_value",
"qualType" : "float *",
"desugaredQualType" : "float *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1020,
"name" : "v_min",
"qualType" : "float",
"desugaredQualType" : "float"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1033,
"name" : "v_max",
"qualType" : "float",
"desugaredQualType" : "float"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1046,
"name" : "speed",
"qualType" : "float",
"desugaredQualType" : "float",
"defaultValue" : "0"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1069,
"name" : "format",
"qualType" : "const char *",
"desugaredQualType" : "const char *",
"defaultValue" : "NULL"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1101,
"name" : "variant",
"qualType" : "ImGuiKnobVariant",
"desugaredQualType" : "int",
"defaultValue" : "ImGuiKnobVariant_Tick"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1140,
"name" : "size",
"qualType" : "float",
"desugaredQualType" : "float",
"defaultValue" : "0"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1165,
"name" : "flags",
"qualType" : "ImGuiKnobFlags",
"desugaredQualType" : "int",
"defaultValue" : "0"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1180,
"name" : "steps",
"qualType" : "int",
"desugaredQualType" : "int",
"defaultValue" : "10"
} ]
}, {
"@type" : "AstFunctionDecl",
"offset" : 1202,
"name" : "KnobInt",
"resultType" : "bool",
"decls" : [ {
"@type" : "AstParmVarDecl",
"offset" : 1222,
"name" : "label",
"qualType" : "const char *",
"desugaredQualType" : "const char *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1234,
"name" : "p_value",
"qualType" : "int *",
"desugaredQualType" : "int *"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1247,
"name" : "v_min",
"qualType" : "int",
"desugaredQualType" : "int"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1258,
"name" : "v_max",
"qualType" : "int",
"desugaredQualType" : "int"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1271,
"name" : "speed",
"qualType" : "float",
"desugaredQualType" : "float",
"defaultValue" : "0"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1294,
"name" : "format",
"qualType" : "const char *",
"desugaredQualType" : "const char *",
"defaultValue" : "NULL"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1326,
"name" : "variant",
"qualType" : "ImGuiKnobVariant",
"desugaredQualType" : "int",
"defaultValue" : "ImGuiKnobVariant_Tick"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1365,
"name" : "size",
"qualType" : "float",
"desugaredQualType" : "float",
"defaultValue" : "0"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1390,
"name" : "flags",
"qualType" : "ImGuiKnobFlags",
"desugaredQualType" : "int",
"defaultValue" : "0"
}, {
"@type" : "AstParmVarDecl",
"offset" : 1405,
"name" : "steps",
"qualType" : "int",
"desugaredQualType" : "int",
"defaultValue" : "10"
} ]
} ]
} ]
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff