Files
kotlin-fork/libraries/tools/idl2k/src/main/kotlin/render.kt
T

276 lines
11 KiB
Kotlin

package org.jetbrains.idl2k
import java.math.BigInteger
private fun <O : Appendable> O.indent(commented: Boolean = false, level: Int) {
if (commented) {
append("//")
}
for (i in 1..level) {
append(" ")
}
}
private fun Appendable.renderAttributeDeclaration(arg: GenerateAttribute, modality: MemberModality, omitDefaults: Boolean = false) {
if (arg.vararg) {
append("vararg ")
}
else {
when (modality) {
MemberModality.OVERRIDE -> append("override ")
MemberModality.ABSTRACT -> append("abstract ")
MemberModality.OPEN -> append("open ")
MemberModality.FINAL -> {}
}
}
append(when(arg.kind) {
AttributeKind.VAL -> "val "
AttributeKind.VAR -> "var "
AttributeKind.ARGUMENT -> ""
})
append(arg.name.replaceKeywords())
append(": ")
append(arg.type.render())
if (arg.initializer != null && !omitDefaults) {
append(" = ")
append(arg.initializer.replaceWrongConstants(arg.type))
}
}
private fun Appendable.renderAttributeDeclarationAsProperty(arg: GenerateAttribute, modality: MemberModality, commented: Boolean, level: Int, omitDefaults: Boolean = false) {
indent(commented, level)
renderAttributeDeclaration(arg, modality, omitDefaults)
appendln()
if (arg.getterNoImpl) {
indent(commented, level + 1)
appendln("get() = noImpl")
}
if (arg.setterNoImpl) {
indent(commented, level + 1)
appendln("set(value) = noImpl")
}
}
private val keywords = setOf("interface")
private fun String.parse() = if (this.startsWith("0x")) BigInteger(this.substring(2), 16) else BigInteger(this)
private fun String.replaceWrongConstants(type: Type) = when {
this == "noImpl" || type is SimpleType && type.type == "Int" && parse() > BigInteger.valueOf(Int.MAX_VALUE.toLong()) -> "noImpl"
type is SimpleType && type.type == "Double" && this.matches("[0-9]+".toRegex()) -> "${this}.0"
else -> this
}
private fun String.replaceKeywords() = if (this in keywords) this + "_" else this
private fun Appendable.renderArgumentsDeclaration(args: List<GenerateAttribute>, omitDefaults: Boolean = false) =
args.joinTo(this, ", ", "(", ")") {
StringBuilder().apply { renderAttributeDeclaration(it, if (it.override) MemberModality.OVERRIDE else MemberModality.FINAL, omitDefaults) }
}
private fun renderCall(call: GenerateFunctionCall) = "${call.name.replaceKeywords()}(${call.arguments.joinToString(separator = ", ", transform = String::replaceKeywords)})"
private fun Appendable.renderFunctionDeclaration(f: GenerateFunction, override: Boolean, commented: Boolean, level: Int = 1) {
indent(commented, level)
if (f.nativeGetterOrSetter == NativeGetterOrSetter.GETTER
&& !f.returnType.nullable
&& f.returnType != DynamicType) {
appendln("@Suppress(\"NATIVE_GETTER_RETURN_TYPE_SHOULD_BE_NULLABLE\")")
indent(commented, level)
}
when (f.nativeGetterOrSetter) {
NativeGetterOrSetter.GETTER -> { appendln("@nativeGetter"); indent(commented, level); append("operator ") }
NativeGetterOrSetter.SETTER -> { appendln("@nativeSetter"); indent(commented, level); append("operator ") }
NativeGetterOrSetter.NONE -> {}
}
if (override) {
append("override ")
}
if (f.name in keywords) {
append("@native(\"${f.name}\") ")
}
append("fun ${f.name.replaceKeywords()}")
renderArgumentsDeclaration(f.arguments, override)
appendln(": ${f.returnType.render()} = noImpl")
}
private fun List<GenerateAttribute>.hasNoVars() = none { it.isVar }
private fun GenerateAttribute.isCommented(parent: String) = "$parent.$name" in commentOutDeclarations || "$parent.$name: ${type.render()}" in commentOutDeclarations
private fun GenerateFunction.isCommented(parent: String) =
"$parent.$name" in commentOutDeclarations || "$parent.$name(${arguments.size})" in commentOutDeclarations
private fun GenerateAttribute.isRequiredFunctionArgument(owner: String, functionName: String) = "$owner.$functionName.$name" in requiredArguments
private fun GenerateFunction.fixRequiredArguments(parent: String) = copy(arguments = arguments.map { arg -> arg.copy(initializer = if (arg.isRequiredFunctionArgument(parent, name)) null else arg.initializer) })
fun Appendable.render(allTypes: Map<String, GenerateTraitOrClass>, typeNamesToUnions: Map<String, List<String>>, iface: GenerateTraitOrClass, markerAnnotation: Boolean = false) {
append("@native public ")
if (markerAnnotation) {
append("@marker ")
}
when (iface.kind) {
GenerateDefinitionKind.CLASS -> append("open class ")
GenerateDefinitionKind.ABSTRACT_CLASS -> append("abstract class ")
GenerateDefinitionKind.TRAIT -> append("interface ")
}
val allSuperTypes = iface.allSuperTypes(allTypes)
val allSuperTypesNames = allSuperTypes.map { it.name }.toSet()
append(iface.name)
val primary = iface.primaryConstructor
if (primary != null && (primary.constructor.arguments.isNotEmpty() || iface.secondaryConstructors.isNotEmpty())) {
renderArgumentsDeclaration(primary.constructor.fixRequiredArguments(iface.name).arguments.dynamicIfUnknownType(allTypes.keys), false)
}
val superCallName = primary?.initTypeCall?.name
val superTypesWithCalls =
(primary?.initTypeCall?.let { listOf(renderCall(it)) } ?: emptyList()) +
iface.superTypes.filter { it != superCallName && it in allSuperTypesNames } +
(typeNamesToUnions[iface.name] ?: emptyList())
if (superTypesWithCalls.isNotEmpty()) {
superTypesWithCalls.joinTo(this, ", ", " : ")
}
appendln (" {")
iface.secondaryConstructors.forEach { secondary ->
indent(false, 1)
append("constructor")
renderArgumentsDeclaration(secondary.constructor.fixRequiredArguments(iface.name).arguments.dynamicIfUnknownType(allTypes.keys), false)
if (secondary.initTypeCall != null) {
append(" : ")
append(renderCall(secondary.initTypeCall))
}
appendln()
}
val superAttributes = allSuperTypes.flatMap { it.memberAttributes }.distinct()
val superAttributesByName = superAttributes.groupBy { it.name }
val superFunctions = allSuperTypes.flatMap { it.memberFunctions }.distinct()
val superSignatures = superAttributes.map { it.signature } merge superFunctions.map { it.signature }
iface.memberAttributes
.filter { it !in superAttributes && !it.static && (it.isVar || (it.isVal && superAttributesByName[it.name]?.hasNoVars() ?: true)) }
.map { it.dynamicIfUnknownType(allTypes.keys) }
.groupBy { it.signature }.reduceValues().values.forEach { arg ->
val modality = when {
arg.signature in superSignatures -> MemberModality.OVERRIDE
iface.kind == GenerateDefinitionKind.CLASS && arg.isVal -> MemberModality.OPEN
iface.kind == GenerateDefinitionKind.ABSTRACT_CLASS -> when {
arg.initializer != null || arg.getterSetterNoImpl -> MemberModality.OPEN
else -> MemberModality.ABSTRACT
}
else -> MemberModality.FINAL
}
renderAttributeDeclarationAsProperty(arg,
modality = modality,
commented = arg.isCommented(iface.name),
omitDefaults = iface.kind == GenerateDefinitionKind.TRAIT,
level = 1
)
}
iface.memberFunctions.filter { it !in superFunctions && !it.static }.map { it.dynamicIfUnknownType(allTypes.keys) }.groupBy { it.signature }.reduceValues(::betterFunction).values.forEach {
renderFunctionDeclaration(it.fixRequiredArguments(iface.name), it.signature in superSignatures, commented = it.isCommented(iface.name))
}
val staticAttributes = iface.memberAttributes.filter { it.static }
val staticFunctions = iface.memberFunctions.filter { it.static }
if (iface.constants.isNotEmpty() || staticAttributes.isNotEmpty() || staticFunctions.isNotEmpty()) {
appendln()
indent(false, 1)
appendln("companion object {")
iface.constants.forEach {
renderAttributeDeclarationAsProperty(it, MemberModality.FINAL, level = 2, commented = it.isCommented(iface.name))
}
staticAttributes.forEach {
renderAttributeDeclarationAsProperty(it, MemberModality.FINAL, level = 2, commented = it.isCommented(iface.name))
}
staticFunctions.forEach {
renderFunctionDeclaration(it.fixRequiredArguments(iface.name), override = false, level = 2, commented = it.isCommented(iface.name))
}
indent(false, 1)
appendln("}")
}
appendln("}")
appendln()
if (iface.generateBuilderFunction) {
renderBuilderFunction(iface, allSuperTypes, allTypes.keys)
}
}
fun Appendable.renderBuilderFunction(dictionary: GenerateTraitOrClass, allSuperTypes: List<GenerateTraitOrClass>, allTypes: Set<String>) {
val fields = (dictionary.memberAttributes + allSuperTypes.flatMap { it.memberAttributes }).distinctBy { it.signature }.map { it.copy(kind = AttributeKind.ARGUMENT) }.dynamicIfUnknownType(allTypes)
appendln("@Suppress(\"NOTHING_TO_INLINE\")")
append("public inline fun ${dictionary.name}")
renderArgumentsDeclaration(fields)
appendln(": ${dictionary.name} {")
indent(level = 1)
appendln("val o = js(\"({})\")")
appendln()
for (field in fields) {
indent(level = 1)
appendln("o[\"${field.name}\"] = ${field.name.replaceKeywords()}")
}
appendln()
indent(level = 1)
appendln("return o")
appendln("}")
appendln()
}
fun betterFunction(f1: GenerateFunction, f2: GenerateFunction): GenerateFunction =
f1.copy(
arguments = f1.arguments
.zip(f2.arguments)
.map { it.first.copy(type = it.map { it.type }.betterType(), name = it.map { it.name }.betterName()) },
nativeGetterOrSetter = listOf(f1.nativeGetterOrSetter, f2.nativeGetterOrSetter)
.firstOrNull { it != NativeGetterOrSetter.NONE } ?: NativeGetterOrSetter.NONE
)
private fun <F, T> Pair<F, F>.map(block: (F) -> T) = block(first) to block(second)
private fun Pair<Type, Type>.betterType() = if (first is DynamicType || first is AnyType) first else second
private fun Pair<String, String>.betterName() = if (((0..9).map { it.toString() } + listOf("arg")).none { first.toLowerCase().contains(it) }) first else second
fun <K, V> List<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = groupBy { it.first }.mapValues { it.value.map { it.second } }
fun Appendable.render(namespace: String, ifaces: List<GenerateTraitOrClass>, unions : GenerateUnionTypes) {
val declaredTypes = ifaces.associateBy { it.name }
val allTypes = declaredTypes + unions.anonymousUnionsMap + unions.typedefsMarkersMap
declaredTypes.values.filter { it.namespace == namespace }.forEach {
render(allTypes, unions.typeNamesToUnionsMap, it)
}
unions.anonymousUnionsMap.values.filter { it.namespace == "" || it.namespace == namespace }.forEach {
render(allTypes, emptyMap(), it, markerAnnotation = true)
}
unions.typedefsMarkersMap.values.filter { it.namespace == "" || it.namespace == namespace }.forEach {
render(allTypes, emptyMap(), it, markerAnnotation = true)
}
}
enum class MemberModality {
OPEN,
ABSTRACT,
OVERRIDE,
FINAL
}