[FIR] Split printer in tree generator to several files
Also use smart printer instead of `PrintWriter`
This commit is contained in:
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.fir.tree.generator
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.*
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.GENERATED_MESSAGE
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.printElements
|
||||
import java.io.File
|
||||
|
||||
|
||||
|
||||
-814
@@ -1,814 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.*
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
val COPYRIGHT = """
|
||||
/*
|
||||
* Copyright 2010-${GregorianCalendar()[Calendar.YEAR]} JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
""".trimIndent()
|
||||
|
||||
const val BASE_PACKAGE = "org.jetbrains.kotlin.fir"
|
||||
//const val BASE_PATH = "compiler/fir/tree/src/"
|
||||
private const val VISITOR_PACKAGE = "org.jetbrains.kotlin.fir.visitors"
|
||||
private const val INDENT = " "
|
||||
val GENERATED_MESSAGE = """
|
||||
/*
|
||||
* This file was generated automatically
|
||||
* DO NOT MODIFY IT MANUALLY
|
||||
*/
|
||||
""".trimIndent()
|
||||
|
||||
fun printElements(builder: AbstractFirTreeBuilder, generationPath: File) {
|
||||
builder.elements.forEach { it.generateCode(generationPath) }
|
||||
builder.elements.flatMap { it.allImplementations }.forEach { it.generateCode(generationPath) }
|
||||
|
||||
printVisitor(builder.elements, generationPath)
|
||||
printVisitorVoid(builder.elements, generationPath)
|
||||
printTransformer(builder.elements, generationPath)
|
||||
}
|
||||
|
||||
fun PrintWriter.printCopyright() {
|
||||
println(COPYRIGHT)
|
||||
println()
|
||||
}
|
||||
|
||||
fun PrintWriter.printGeneratedMessage() {
|
||||
println(GENERATED_MESSAGE)
|
||||
println()
|
||||
}
|
||||
|
||||
fun printVisitor(elements: List<Element>, generationPath: File) {
|
||||
val dir = File(generationPath, VISITOR_PACKAGE.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
File(dir, "FirVisitor.kt").printWriter().use { printer ->
|
||||
with(printer) {
|
||||
printCopyright()
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
elements.forEach { println("import ${it.fullQualifiedName}") }
|
||||
println()
|
||||
printGeneratedMessage()
|
||||
|
||||
println("abstract class FirVisitor<out R, in D> {")
|
||||
|
||||
indent()
|
||||
println("abstract fun visitElement(element: FirElement, data: D): R")
|
||||
println()
|
||||
for (element in elements) {
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) continue
|
||||
with(element) {
|
||||
indent()
|
||||
val varName = safeDecapitalizedName
|
||||
println("open fun ${typeParameters}visit$name($varName: $typeWithArguments, data: D): R${multipleUpperBoundsList()} = visitElement($varName, data)")
|
||||
println()
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun printVisitorVoid(elements: List<Element>, generationPath: File) {
|
||||
val dir = File(generationPath, VISITOR_PACKAGE.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
File(dir, "FirVisitorVoid.kt").printWriter().use { printer ->
|
||||
with(printer) {
|
||||
printCopyright()
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
elements.forEach { println("import ${it.fullQualifiedName}") }
|
||||
println()
|
||||
printGeneratedMessage()
|
||||
|
||||
println("abstract class FirVisitorVoid : FirVisitor<Unit, Nothing?>() {")
|
||||
|
||||
indent()
|
||||
println("abstract fun visitElement(element: FirElement)")
|
||||
println()
|
||||
for (element in elements) {
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) continue
|
||||
with(element) {
|
||||
indent()
|
||||
val varName = safeDecapitalizedName
|
||||
println("open fun ${typeParameters}visit$name($varName: $typeWithArguments)${multipleUpperBoundsList()}{")
|
||||
indent(2)
|
||||
println("visitElement($varName)")
|
||||
indent()
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
for (element in elements) {
|
||||
with(element) {
|
||||
indent()
|
||||
val varName = safeDecapitalizedName
|
||||
println("final override fun ${typeParameters}visit$name($varName: $typeWithArguments, data: Nothing?)${multipleUpperBoundsList()}{")
|
||||
indent(2)
|
||||
println("visit$name($varName)")
|
||||
indent()
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Element.generateCode(generationPath: File) {
|
||||
val dir = generationPath.resolve(packageName.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
val file = File(dir, "$type.kt")
|
||||
file.printWriter().use { printer ->
|
||||
with(printer) {
|
||||
printCopyright()
|
||||
println("package $packageName")
|
||||
println()
|
||||
val imports = collectImports()
|
||||
imports.forEach { println("import $it") }
|
||||
if (imports.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
printGeneratedMessage()
|
||||
printElement(this@generateCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Implementation.generateCode(generationPath: File) {
|
||||
val dir = generationPath.resolve(packageName.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
val file = File(dir, "$type.kt")
|
||||
file.printWriter().use { printer ->
|
||||
with(printer) {
|
||||
printCopyright()
|
||||
println("package $packageName")
|
||||
println()
|
||||
val imports = collectImports()
|
||||
imports.forEach { println("import $it") }
|
||||
if (imports.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
printGeneratedMessage()
|
||||
printImplementation(this@generateCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val KindOwner.needPureAbstractElement: Boolean get() = (kind != Implementation.Kind.Interface) && !allParents.any { it.kind == Implementation.Kind.AbstractClass }
|
||||
|
||||
fun Implementation.collectImports(): List<String> {
|
||||
return element.collectImportsInternal(
|
||||
listOf(
|
||||
element.fullQualifiedName)
|
||||
+ usedTypes.mapNotNull { it.fullQualifiedName } + parents.mapNotNull { it.fullQualifiedName }
|
||||
+ listOfNotNull(pureAbstractElementType.fullQualifiedName?.takeIf { needPureAbstractElement }
|
||||
),
|
||||
isImpl = true
|
||||
)
|
||||
}
|
||||
|
||||
fun Element.collectImports(): List<String> {
|
||||
val baseTypes = parents.mapTo(mutableListOf()) { it.fullQualifiedName }
|
||||
baseTypes += parentsArguments.values.flatMap { it.values }.mapNotNull { it.fullQualifiedName }
|
||||
val isBaseFirElement = this == AbstractFirTreeBuilder.baseFirElement
|
||||
if (isBaseFirElement) {
|
||||
baseTypes += compositeTransformResultType.fullQualifiedName!!
|
||||
}
|
||||
if (needPureAbstractElement) {
|
||||
baseTypes += pureAbstractElementType.fullQualifiedName!!
|
||||
}
|
||||
return collectImportsInternal(
|
||||
baseTypes,
|
||||
isImpl = false
|
||||
)
|
||||
}
|
||||
|
||||
fun Element.collectImportsInternal(base: List<String>, isImpl: Boolean): List<String> {
|
||||
val fqns = base + allFields.mapNotNull { it.fullQualifiedName } +
|
||||
allFields.flatMap { it.arguments.mapNotNull { it.fullQualifiedName } } +
|
||||
typeArguments.flatMap { it.upperBounds.mapNotNull { it.fullQualifiedName } }
|
||||
val realPackageName = if (isImpl) "$packageName.impl." else "$packageName."
|
||||
return fqns.filter { fqn ->
|
||||
fqn.dropLastWhile { it != '.' } != realPackageName
|
||||
}.distinct().sorted() + "$VISITOR_PACKAGE.*"
|
||||
}
|
||||
|
||||
val Field.isVal: Boolean get() = this is FieldList || (this is FieldWithDefault && origin is FieldList) || !isMutable
|
||||
|
||||
fun PrintWriter.printFieldWithDefaultInImplementation(field: Field) {
|
||||
val defaultValue = field.defaultValue
|
||||
indent()
|
||||
print("override ")
|
||||
if (field.isLateinit) {
|
||||
print("lateinit ")
|
||||
}
|
||||
if (field.isVal) {
|
||||
print("val")
|
||||
} else {
|
||||
print("var")
|
||||
}
|
||||
print(" ${field.name}: ${field.mutableType}")
|
||||
if (field.isLateinit) {
|
||||
println()
|
||||
return
|
||||
} else {
|
||||
print(" ")
|
||||
}
|
||||
if (field.withGetter) {
|
||||
if (field.customSetter != null) {
|
||||
println()
|
||||
indent(2)
|
||||
}
|
||||
print("get() ")
|
||||
}
|
||||
requireNotNull(defaultValue) {
|
||||
"No default value for $field"
|
||||
}
|
||||
println("= $defaultValue")
|
||||
field.customSetter?.let {
|
||||
indent(2)
|
||||
println("set(value) {")
|
||||
indent(3)
|
||||
println(it)
|
||||
indent(2)
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintWriter.printImplementation(implementation: Implementation) {
|
||||
fun Field.transform() {
|
||||
when (this) {
|
||||
is FieldWithDefault -> origin.transform()
|
||||
|
||||
is FirField ->
|
||||
println("$name = ${name}${call()}transformSingle(transformer, data)")
|
||||
|
||||
is FieldList -> {
|
||||
println("${name}.transformInplace(transformer, data)")
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
with(implementation) {
|
||||
print("${kind!!.title} $type")
|
||||
print(element.typeParameters)
|
||||
val fieldsWithoutDefault = allFields.filter { it.defaultValue == null && !it.isLateinit }
|
||||
val fieldsWithDefault = allFields.filter { it.defaultValue != null || it.isLateinit }
|
||||
|
||||
val isInterface = kind == Implementation.Kind.Interface
|
||||
val isAbstract = kind == Implementation.Kind.AbstractClass
|
||||
|
||||
fun abstract() {
|
||||
if (isAbstract) {
|
||||
print("abstract ")
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInterface && !isAbstract && fieldsWithoutDefault.isNotEmpty()) {
|
||||
println("(")
|
||||
fieldsWithoutDefault.forEachIndexed { i, field ->
|
||||
val end = if (i == fieldsWithoutDefault.size - 1) "" else ","
|
||||
printField(field, isImplementation = true, override = true, end = end, withIndent = true)
|
||||
}
|
||||
print(")")
|
||||
}
|
||||
|
||||
print(" : ")
|
||||
if (!isInterface && !allParents.any { it.kind == Implementation.Kind.AbstractClass }) {
|
||||
print("${pureAbstractElementType.type}(), ")
|
||||
}
|
||||
// print(element.typeWithArguments)
|
||||
print(allParents.joinToString { "${it.typeWithArguments}${it.kind.braces()}" })
|
||||
println(" {")
|
||||
|
||||
if (isInterface || isAbstract) {
|
||||
allFields.forEach {
|
||||
indent()
|
||||
abstract()
|
||||
printField(it, isImplementation = true, override = true, end = "", withIndent = false)
|
||||
}
|
||||
} else {
|
||||
fieldsWithDefault.forEach {
|
||||
printFieldWithDefaultInImplementation(it)
|
||||
}
|
||||
if (fieldsWithDefault.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
element.allFields.filter { it.type.contains("Symbol") && it !is FieldList }
|
||||
.takeIf { it.isNotEmpty() && !isInterface && !isAbstract && !element.type.contains("Reference")}
|
||||
?.let { symbolFields ->
|
||||
indent(1)
|
||||
println("init {")
|
||||
for (symbolField in symbolFields) {
|
||||
indent(2)
|
||||
println("${symbolField.name}${symbolField.call()}bind(this)")
|
||||
}
|
||||
indent(1)
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
|
||||
fun Field.acceptString(): String = "${name}${call()}accept(visitor, data)"
|
||||
if (!isInterface && !isAbstract) {
|
||||
|
||||
indent(1)
|
||||
print("override fun <R, D> acceptChildren(visitor: FirVisitor<R, D>, data: D) {")
|
||||
|
||||
|
||||
if (element.allFirFields.isNotEmpty()) {
|
||||
println()
|
||||
for (field in allFields.filter { it.isFirType }) {
|
||||
if (field.withGetter || !field.needAcceptAndTransform) continue
|
||||
when (field.name) {
|
||||
"explicitReceiver" -> {
|
||||
val explicitReceiver = implementation["explicitReceiver"]!!
|
||||
val dispatchReceiver = implementation["dispatchReceiver"]!!
|
||||
val extensionReceiver = implementation["extensionReceiver"]!!
|
||||
println(
|
||||
"""
|
||||
| ${explicitReceiver.acceptString()}
|
||||
| if (dispatchReceiver !== explicitReceiver) {
|
||||
| ${dispatchReceiver.acceptString()}
|
||||
| }
|
||||
| if (extensionReceiver !== explicitReceiver && extensionReceiver !== dispatchReceiver) {
|
||||
| ${extensionReceiver.acceptString()}
|
||||
| }
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
|
||||
"dispatchReceiver", "extensionReceiver", "subjectVariable" -> {
|
||||
}
|
||||
"companionObject" -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (type in setOf("FirClassImpl", "FirSealedClassImpl") && field.name == "declarations") {
|
||||
indent(2)
|
||||
println("(declarations.firstOrNull { it is FirConstructorImpl } as? FirConstructorImpl)?.typeParameters?.forEach { it.accept(visitor, data) }")
|
||||
}
|
||||
if (type == "FirWhenExpressionImpl" && field.name == "subject") {
|
||||
println(
|
||||
"""
|
||||
| if (subjectVariable != null) {
|
||||
| subjectVariable.accept(visitor, data)
|
||||
| } else {
|
||||
| subject?.accept(visitor, data)
|
||||
| }
|
||||
""".trimMargin()
|
||||
)
|
||||
} else {
|
||||
indent(2)
|
||||
when (field.origin) {
|
||||
is FirField -> {
|
||||
println(field.acceptString())
|
||||
}
|
||||
|
||||
is FieldList -> {
|
||||
println("${field.name}.forEach { it.accept(visitor, data) }")
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
indent()
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
|
||||
indent()
|
||||
abstract()
|
||||
print("override fun <D> transformChildren(transformer: FirTransformer<D>, data: D): $typeWithArguments")
|
||||
if (!isInterface && !isAbstract) {
|
||||
println(" {")
|
||||
for (field in allFields) {
|
||||
when {
|
||||
!field.isMutable || !field.isFirType || field.withGetter || !field.needAcceptAndTransform -> {
|
||||
}
|
||||
field.name == "explicitReceiver" -> {
|
||||
val explicitReceiver = implementation["explicitReceiver"]!!
|
||||
val dispatchReceiver = implementation["dispatchReceiver"]!!
|
||||
val extensionReceiver = implementation["extensionReceiver"]!!
|
||||
if (explicitReceiver.isMutable) {
|
||||
indent(2)
|
||||
println("explicitReceiver = explicitReceiver${explicitReceiver.call()}transformSingle(transformer, data)")
|
||||
}
|
||||
if (dispatchReceiver.isMutable) {
|
||||
println(
|
||||
"""
|
||||
| if (dispatchReceiver !== explicitReceiver) {
|
||||
| dispatchReceiver = dispatchReceiver.transformSingle(transformer, data)
|
||||
| }
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
if (extensionReceiver.isMutable) {
|
||||
println(
|
||||
"""
|
||||
| if (extensionReceiver !== explicitReceiver && extensionReceiver !== dispatchReceiver) {
|
||||
| extensionReceiver = extensionReceiver.transformSingle(transformer, data)
|
||||
| }
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
}
|
||||
field.name in setOf("dispatchReceiver", "extensionReceiver") -> {
|
||||
}
|
||||
type in setOf("FirClassImpl", "FirSealedClassImpl") && field.name == "declarations" -> {
|
||||
indent(2)
|
||||
println("(declarations.firstOrNull { it is FirConstructorImpl } as? FirConstructorImpl)?.typeParameters?.transformInplace(transformer, data)")
|
||||
indent(2)
|
||||
println("declarations.transformInplace(transformer, data)")
|
||||
}
|
||||
field.name == "companionObject" -> {
|
||||
indent(2)
|
||||
println("companionObject = declarations.asSequence().filterIsInstance<FirRegularClass>().firstOrNull { it.status.isCompanion }")
|
||||
}
|
||||
field.needsSeparateTransform -> {
|
||||
indent(2)
|
||||
println("transform${field.name.capitalize()}(transformer, data)")
|
||||
}
|
||||
!element.needTransformOtherChildren -> {
|
||||
indent(2)
|
||||
field.transform()
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (element.needTransformOtherChildren) {
|
||||
indent(2)
|
||||
println("transformOtherChildren(transformer, data)")
|
||||
}
|
||||
indent(2)
|
||||
println("return this")
|
||||
indent()
|
||||
println("}")
|
||||
} else {
|
||||
println()
|
||||
}
|
||||
|
||||
for (field in allFields) {
|
||||
if (!field.needsSeparateTransform) continue
|
||||
println()
|
||||
indent()
|
||||
abstract()
|
||||
print("override ${field.transformFunctionDeclaration(typeWithArguments)}")
|
||||
if (isInterface || isAbstract) {
|
||||
println()
|
||||
continue
|
||||
}
|
||||
println(" {")
|
||||
if (field.isMutable && field.isFirType) {
|
||||
// TODO: replace with smth normal
|
||||
if (type == "FirWhenExpressionImpl" && field.name == "subject") {
|
||||
println("""
|
||||
| if (subjectVariable != null) {
|
||||
| subjectVariable = subjectVariable?.transformSingle(transformer, data)
|
||||
| } else {
|
||||
| subject = subject?.transformSingle(transformer, data)
|
||||
| }
|
||||
""".trimMargin())
|
||||
} else {
|
||||
indent(2)
|
||||
field.transform()
|
||||
}
|
||||
}
|
||||
indent(2)
|
||||
println("return this")
|
||||
indent()
|
||||
println("}")
|
||||
}
|
||||
|
||||
if (element.needTransformOtherChildren) {
|
||||
println()
|
||||
indent()
|
||||
abstract()
|
||||
print("override fun <D> transformOtherChildren(transformer: FirTransformer<D>, data: D): $typeWithArguments")
|
||||
if (isInterface || isAbstract) {
|
||||
println()
|
||||
} else {
|
||||
println(" {")
|
||||
for (field in allFields) {
|
||||
if (!field.isMutable || !field.isFirType || field.name == "subjectVariable") continue
|
||||
if (!field.needsSeparateTransform) {
|
||||
indent(2)
|
||||
field.transform()
|
||||
}
|
||||
}
|
||||
indent(2)
|
||||
println("return this")
|
||||
indent()
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
for (field in allFields.filter { it.withReplace }) {
|
||||
println()
|
||||
indent()
|
||||
abstract()
|
||||
print("override ${field.replaceFunctionDeclaration()}")
|
||||
if (isInterface || isAbstract) {
|
||||
println()
|
||||
continue
|
||||
}
|
||||
print(" {")
|
||||
if (!field.isMutable) {
|
||||
println("}")
|
||||
continue
|
||||
}
|
||||
println()
|
||||
indent(2)
|
||||
val newValue = "new${field.name.capitalize()}"
|
||||
when {
|
||||
field.withGetter -> {
|
||||
}
|
||||
|
||||
field.origin is FieldList -> {
|
||||
println("${field.name}.clear()")
|
||||
indent(2)
|
||||
println("${field.name}.addAll($newValue)")
|
||||
}
|
||||
|
||||
else -> {
|
||||
println("${field.name} = $newValue")
|
||||
}
|
||||
}
|
||||
indent()
|
||||
println("}")
|
||||
}
|
||||
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
fun Field.transformFunctionDeclaration(returnType: String): String {
|
||||
return transformFunctionDeclaration(name.capitalize(), returnType)
|
||||
}
|
||||
|
||||
fun transformFunctionDeclaration(transformName: String, returnType: String): String {
|
||||
return "fun <D> transform$transformName(transformer: FirTransformer<D>, data: D): $returnType"
|
||||
}
|
||||
|
||||
fun Field.replaceFunctionDeclaration(): String {
|
||||
val capName = name.capitalize()
|
||||
return "fun replace$capName(new$capName: $typeWithArgumentsWithoutNullablity)"
|
||||
}
|
||||
|
||||
fun printTransformer(elements: List<Element>, generationPath: File) {
|
||||
val dir = File(generationPath, VISITOR_PACKAGE.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
File(dir, "FirTransformer.kt").printWriter().use { printer ->
|
||||
with(printer) {
|
||||
printCopyright()
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
elements.forEach { println("import ${it.fullQualifiedName}") }
|
||||
println("import ${compositeTransformResultType.fullQualifiedName}")
|
||||
println()
|
||||
printGeneratedMessage()
|
||||
|
||||
println("abstract class FirTransformer<in D> : FirVisitor<CompositeTransformResult<FirElement>, D>() {")
|
||||
println()
|
||||
indent()
|
||||
println("abstract fun <E : FirElement> transformElement(element: E, data: D): CompositeTransformResult<E>")
|
||||
println()
|
||||
for (element in elements) {
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) continue
|
||||
indent()
|
||||
val varName = element.safeDecapitalizedName
|
||||
print("open fun ")
|
||||
element.typeParameters.takeIf { it.isNotBlank() }?.let { print(it) }
|
||||
println("transform${element.name}($varName: ${element.typeWithArguments}, data: D): CompositeTransformResult<${element.transformerType.typeWithArguments}>${element.multipleUpperBoundsList()}{")
|
||||
indent(2)
|
||||
println("return transformElement($varName, data)")
|
||||
indent()
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
|
||||
for (element in elements) {
|
||||
indent()
|
||||
val varName = element.safeDecapitalizedName
|
||||
print("final override fun ")
|
||||
element.typeParameters.takeIf { it.isNotBlank() }?.let { print(it) }
|
||||
|
||||
println("visit${element.name}($varName: ${element.typeWithArguments}, data: D): CompositeTransformResult<${element.transformerType.typeWithArguments}>${element.multipleUpperBoundsList()}{")
|
||||
indent(2)
|
||||
println("return transform${element.name}($varName, data)")
|
||||
indent()
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun PrintWriter.printField(field: Field, isImplementation: Boolean, override: Boolean, end: String, withIndent: Boolean) {
|
||||
if (withIndent) {
|
||||
indent()
|
||||
}
|
||||
if (override) {
|
||||
print("override ")
|
||||
}
|
||||
if (!isImplementation || field.isVal) {
|
||||
print("val")
|
||||
} else {
|
||||
print("var")
|
||||
}
|
||||
val type = if (isImplementation) field.mutableType else field.typeWithArguments
|
||||
println(" ${field.name}: $type$end")
|
||||
}
|
||||
|
||||
val Field.mutableType: String get() = when (this) {
|
||||
is FieldList -> if (isMutable) "Mutable$typeWithArguments" else typeWithArguments
|
||||
is FieldWithDefault -> if (isMutable) origin.mutableType else typeWithArguments
|
||||
else -> typeWithArguments
|
||||
}
|
||||
|
||||
fun Field.call(): String = if (nullable) "?." else "."
|
||||
|
||||
fun Element.multipleUpperBoundsList(): String {
|
||||
return typeArguments.filterIsInstance<TypeArgumentWithMultipleUpperBounds>().takeIf { it.isNotEmpty() }?.let { arguments ->
|
||||
val upperBoundsList = arguments.joinToString(", ", postfix = " ") { argument ->
|
||||
argument.upperBounds.joinToString(", ") { upperBound -> "${argument.name} : ${upperBound.typeWithArguments}" }
|
||||
}
|
||||
" where $upperBoundsList"
|
||||
} ?: " "
|
||||
}
|
||||
|
||||
fun Implementation.Kind?.braces(): String = when (this) {
|
||||
Implementation.Kind.Interface -> ""
|
||||
Implementation.Kind.OpenClass, Implementation.Kind.AbstractClass -> "()"
|
||||
else -> throw IllegalStateException(this.toString())
|
||||
}
|
||||
|
||||
fun PrintWriter.printElement(element: Element) {
|
||||
with(element) {
|
||||
val isInterface = kind == Implementation.Kind.Interface
|
||||
|
||||
fun abstract() {
|
||||
indent()
|
||||
if (!isInterface) {
|
||||
print("abstract ")
|
||||
}
|
||||
}
|
||||
|
||||
fun override() {
|
||||
if (this != AbstractFirTreeBuilder.baseFirElement) {
|
||||
print("override ")
|
||||
}
|
||||
}
|
||||
|
||||
print("${kind!!.title} $type")
|
||||
if (typeArguments.isNotEmpty()) {
|
||||
print(typeArguments.joinToString(", ", "<", ">") { it.toString() })
|
||||
}
|
||||
val needPureAbstractElement = !isInterface && !allParents.any { it.kind == Implementation.Kind.AbstractClass }
|
||||
|
||||
if (parents.isNotEmpty() || needPureAbstractElement) {
|
||||
print(" : ")
|
||||
if (needPureAbstractElement) {
|
||||
print("${pureAbstractElementType.type}()")
|
||||
if (parents.isNotEmpty()) {
|
||||
print(", ")
|
||||
}
|
||||
}
|
||||
print(parents.joinToString(", ") {
|
||||
var result = it.type
|
||||
parentsArguments[it]?.let { arguments ->
|
||||
result += arguments.values.joinToString(", ", "<", ">") { it.typeWithArguments }
|
||||
}
|
||||
result + it.kind.braces()
|
||||
})
|
||||
}
|
||||
print(multipleUpperBoundsList())
|
||||
println("{")
|
||||
allFields.forEach {
|
||||
abstract()
|
||||
printField(it, isImplementation = false, override = it.fromParent, end = "", withIndent = false)
|
||||
}
|
||||
if (allFields.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
|
||||
indent()
|
||||
override()
|
||||
println("fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visit$name(this, data)")
|
||||
|
||||
fields.filter { it.withReplace }.forEach {
|
||||
println()
|
||||
abstract()
|
||||
if (it.fromParent) print("override ")
|
||||
println(it.replaceFunctionDeclaration())
|
||||
}
|
||||
|
||||
for (field in allFields) {
|
||||
if (!field.needsSeparateTransform) continue
|
||||
println()
|
||||
abstract()
|
||||
if (field.fromParent) {
|
||||
print("override ")
|
||||
}
|
||||
println(field.transformFunctionDeclaration(typeWithArguments))
|
||||
}
|
||||
if (needTransformOtherChildren) {
|
||||
println()
|
||||
abstract()
|
||||
if (element.parents.any { it.needTransformOtherChildren }) {
|
||||
print("override ")
|
||||
}
|
||||
println(transformFunctionDeclaration("OtherChildren", typeWithArguments))
|
||||
}
|
||||
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) {
|
||||
require(isInterface)
|
||||
println()
|
||||
indent()
|
||||
println("fun accept(visitor: FirVisitorVoid) = accept(visitor, null)")
|
||||
println()
|
||||
indent()
|
||||
println("fun <R, D> acceptChildren(visitor: FirVisitor<R, D>, data: D)")
|
||||
println()
|
||||
indent()
|
||||
println("fun acceptChildren(visitor: FirVisitorVoid) = acceptChildren(visitor, null)")
|
||||
println()
|
||||
indent()
|
||||
println("@Suppress(\"UNCHECKED_CAST\")")
|
||||
indent()
|
||||
println("fun <E : FirElement, D> transform(visitor: FirTransformer<D>, data: D): CompositeTransformResult<E> =")
|
||||
indent(2)
|
||||
println("accept(visitor, data) as CompositeTransformResult<E>")
|
||||
println()
|
||||
indent()
|
||||
println("fun <D> transformChildren(transformer: FirTransformer<D>, data: D): FirElement")
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------- Helpers ---------------------------------------
|
||||
|
||||
fun PrintWriter.indent(n: Int = 1) {
|
||||
print(INDENT.repeat(n))
|
||||
}
|
||||
|
||||
val Element.safeDecapitalizedName: String get() = if (name == "Class") "klass" else name.decapitalize()
|
||||
|
||||
val Importable.typeWithArguments: String
|
||||
get() = when (this) {
|
||||
is AbstractElement -> type + generics
|
||||
is Implementation -> type + element.generics
|
||||
is FirField -> element.typeWithArguments + if (nullable) "?" else ""
|
||||
is Field -> type + generics + if (nullable) "?" else ""
|
||||
is Type -> type + generics
|
||||
is ImplementationWithArg -> type + generics
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val Importable.typeWithArgumentsWithoutNullablity: String get() = typeWithArguments.dropLastWhile { it == '?' }
|
||||
|
||||
val ImplementationWithArg.generics: String
|
||||
get() = argument?.let { "<${it.type}>" } ?: ""
|
||||
|
||||
val AbstractElement.generics: String
|
||||
get() = typeArguments.takeIf { it.isNotEmpty() }
|
||||
?.let { it.joinToString(", ", "<", ">") { it.name } }
|
||||
?: ""
|
||||
|
||||
val Field.generics: String
|
||||
get() = arguments.takeIf { it.isNotEmpty() }
|
||||
?.let { it.joinToString(", ", "<", ">") { it.typeWithArguments } }
|
||||
?: ""
|
||||
|
||||
val Element.typeParameters: String
|
||||
get() = typeArguments.takeIf { it.isNotEmpty() }
|
||||
?.joinToString(", ", "<", "> ")
|
||||
?: ""
|
||||
|
||||
val Type.generics: String
|
||||
get() = arguments.takeIf { it.isNotEmpty() }?.joinToString(",", "<", ">") ?: ""
|
||||
+1
-1
@@ -5,9 +5,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.context
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.BASE_PACKAGE
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Element
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Type
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.BASE_PACKAGE
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
abstract class AbstractFirTreeBuilder {
|
||||
|
||||
+1
-1
@@ -5,9 +5,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.context
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.call
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.*
|
||||
import org.jetbrains.kotlin.fir.tree.generator.noReceiverExpressionType
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.call
|
||||
|
||||
abstract class AbstractFirTreeImplementationConfigurator {
|
||||
private val elementsWithImpl = mutableSetOf<Element>()
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.model
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.BASE_PACKAGE
|
||||
import org.jetbrains.kotlin.fir.tree.generator.typeWithArguments
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.BASE_PACKAGE
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.typeWithArguments
|
||||
|
||||
interface KindOwner : Importable {
|
||||
var kind: Implementation.Kind?
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.fir.tree.generator.model
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.type
|
||||
import org.jetbrains.kotlin.fir.tree.generator.typeWithArguments
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.typeWithArguments
|
||||
|
||||
// ----------- Simple field -----------
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.model
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.typeWithArguments
|
||||
import org.jetbrains.kotlin.fir.tree.generator.printer.typeWithArguments
|
||||
|
||||
sealed class Field : Importable {
|
||||
abstract val name: String
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import java.io.File
|
||||
import java.lang.Appendable
|
||||
|
||||
class SmartPrinter(appendable: Appendable) {
|
||||
private val printer = org.jetbrains.kotlin.utils.Printer(appendable)
|
||||
|
||||
private var notFirstPrint: Boolean = false
|
||||
|
||||
fun print(vararg objects: Any) {
|
||||
if (notFirstPrint) {
|
||||
printer.printWithNoIndent(*objects)
|
||||
} else {
|
||||
printer.print(*objects)
|
||||
}
|
||||
notFirstPrint = true
|
||||
}
|
||||
|
||||
fun println(vararg objects: Any) {
|
||||
if (notFirstPrint) {
|
||||
printer.printlnWithNoIndent(*objects)
|
||||
} else {
|
||||
printer.println(*objects)
|
||||
}
|
||||
notFirstPrint = false
|
||||
}
|
||||
|
||||
fun pushIndent() {
|
||||
printer.pushIndent()
|
||||
}
|
||||
|
||||
fun popIndent() {
|
||||
printer.popIndent()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun SmartPrinter.withIndent(block: () -> Unit) {
|
||||
pushIndent()
|
||||
block()
|
||||
popIndent()
|
||||
}
|
||||
|
||||
inline fun File.useSmartPrinter(block: SmartPrinter.() -> Unit) {
|
||||
writer().use { SmartPrinter(it).block() }
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Element
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Implementation
|
||||
import org.jetbrains.kotlin.fir.tree.generator.pureAbstractElementType
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun Element.generateCode(generationPath: File) {
|
||||
val dir = generationPath.resolve(packageName.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
val file = File(dir, "$type.kt")
|
||||
file.useSmartPrinter {
|
||||
printCopyright()
|
||||
println("package $packageName")
|
||||
println()
|
||||
val imports = collectImports()
|
||||
imports.forEach { println("import $it") }
|
||||
if (imports.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
printGeneratedMessage()
|
||||
printElement(this@generateCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun SmartPrinter.printElement(element: Element) {
|
||||
with(element) {
|
||||
val isInterface = kind == Implementation.Kind.Interface
|
||||
|
||||
fun abstract() {
|
||||
if (!isInterface) {
|
||||
print("abstract ")
|
||||
}
|
||||
}
|
||||
|
||||
fun override() {
|
||||
if (this != AbstractFirTreeBuilder.baseFirElement) {
|
||||
print("override ")
|
||||
}
|
||||
}
|
||||
|
||||
print("${kind!!.title} $type")
|
||||
if (typeArguments.isNotEmpty()) {
|
||||
print(typeArguments.joinToString(", ", "<", ">") { it.toString() })
|
||||
}
|
||||
val needPureAbstractElement = !isInterface && !allParents.any { it.kind == Implementation.Kind.AbstractClass }
|
||||
|
||||
if (parents.isNotEmpty() || needPureAbstractElement) {
|
||||
print(" : ")
|
||||
if (needPureAbstractElement) {
|
||||
print("${pureAbstractElementType.type}()")
|
||||
if (parents.isNotEmpty()) {
|
||||
print(", ")
|
||||
}
|
||||
}
|
||||
print(
|
||||
parents.joinToString(", ") {
|
||||
var result = it.type
|
||||
parentsArguments[it]?.let { arguments ->
|
||||
result += arguments.values.joinToString(", ", "<", ">") { it.typeWithArguments }
|
||||
}
|
||||
result + it.kind.braces()
|
||||
},
|
||||
)
|
||||
}
|
||||
print(multipleUpperBoundsList())
|
||||
println("{")
|
||||
withIndent {
|
||||
allFields.forEach {
|
||||
abstract()
|
||||
printField(it, isImplementation = false, override = it.fromParent, end = "")
|
||||
}
|
||||
if (allFields.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
|
||||
override()
|
||||
println("fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visit$name(this, data)")
|
||||
|
||||
fields.filter { it.withReplace }.forEach {
|
||||
println()
|
||||
abstract()
|
||||
if (it.fromParent) print("override ")
|
||||
println(it.replaceFunctionDeclaration())
|
||||
}
|
||||
|
||||
for (field in allFields) {
|
||||
if (!field.needsSeparateTransform) continue
|
||||
println()
|
||||
abstract()
|
||||
if (field.fromParent) {
|
||||
print("override ")
|
||||
}
|
||||
println(field.transformFunctionDeclaration(typeWithArguments))
|
||||
}
|
||||
if (needTransformOtherChildren) {
|
||||
println()
|
||||
abstract()
|
||||
if (element.parents.any { it.needTransformOtherChildren }) {
|
||||
print("override ")
|
||||
}
|
||||
println(transformFunctionDeclaration("OtherChildren", typeWithArguments))
|
||||
}
|
||||
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) {
|
||||
require(isInterface)
|
||||
println()
|
||||
println("fun accept(visitor: FirVisitorVoid) = accept(visitor, null)")
|
||||
println()
|
||||
println("fun <R, D> acceptChildren(visitor: FirVisitor<R, D>, data: D)")
|
||||
println()
|
||||
println("fun acceptChildren(visitor: FirVisitorVoid) = acceptChildren(visitor, null)")
|
||||
println()
|
||||
println("@Suppress(\"UNCHECKED_CAST\")")
|
||||
println("fun <E : FirElement, D> transform(visitor: FirTransformer<D>, data: D): CompositeTransformResult<E> =")
|
||||
withIndent {
|
||||
println("accept(visitor, data) as CompositeTransformResult<E>")
|
||||
}
|
||||
println()
|
||||
println("fun <D> transformChildren(transformer: FirTransformer<D>, data: D): FirElement")
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Field
|
||||
|
||||
|
||||
fun SmartPrinter.printField(field: Field, isImplementation: Boolean, override: Boolean, end: String) {
|
||||
if (override) {
|
||||
print("override ")
|
||||
}
|
||||
if (!isImplementation || field.isVal) {
|
||||
print("val")
|
||||
} else {
|
||||
print("var")
|
||||
}
|
||||
val type = if (isImplementation) field.mutableType else field.typeWithArguments
|
||||
println(" ${field.name}: $type$end")
|
||||
}
|
||||
|
||||
fun SmartPrinter.printFieldWithDefaultInImplementation(field: Field) {
|
||||
val defaultValue = field.defaultValue
|
||||
print("override ")
|
||||
if (field.isLateinit) {
|
||||
print("lateinit ")
|
||||
}
|
||||
if (field.isVal) {
|
||||
print("val")
|
||||
} else {
|
||||
print("var")
|
||||
}
|
||||
print(" ${field.name}: ${field.mutableType}")
|
||||
if (field.isLateinit) {
|
||||
println()
|
||||
return
|
||||
} else {
|
||||
print(" ")
|
||||
}
|
||||
if (field.withGetter) {
|
||||
if (field.customSetter != null) {
|
||||
println()
|
||||
pushIndent()
|
||||
}
|
||||
print("get() ")
|
||||
}
|
||||
requireNotNull(defaultValue) {
|
||||
"No default value for $field"
|
||||
}
|
||||
println("= $defaultValue")
|
||||
field.customSetter?.let {
|
||||
println("set(value) {")
|
||||
pushIndent()
|
||||
println(it)
|
||||
popIndent()
|
||||
println("}")
|
||||
popIndent()
|
||||
}
|
||||
}
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.*
|
||||
import org.jetbrains.kotlin.fir.tree.generator.pureAbstractElementType
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun Implementation.generateCode(generationPath: File) {
|
||||
val dir = generationPath.resolve(packageName.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
val file = File(dir, "$type.kt")
|
||||
file.useSmartPrinter {
|
||||
printCopyright()
|
||||
println("package $packageName")
|
||||
println()
|
||||
val imports = collectImports()
|
||||
imports.forEach { println("import $it") }
|
||||
if (imports.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
printGeneratedMessage()
|
||||
printImplementation(this@generateCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun SmartPrinter.printImplementation(implementation: Implementation) {
|
||||
fun Field.transform() {
|
||||
when (this) {
|
||||
is FieldWithDefault -> origin.transform()
|
||||
|
||||
is FirField ->
|
||||
println("$name = ${name}${call()}transformSingle(transformer, data)")
|
||||
|
||||
is FieldList -> {
|
||||
println("${name}.transformInplace(transformer, data)")
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
with(implementation) {
|
||||
print("${kind!!.title} $type")
|
||||
print(element.typeParameters)
|
||||
val fieldsWithoutDefault = allFields.filter { it.defaultValue == null && !it.isLateinit }
|
||||
val fieldsWithDefault = allFields.filter { it.defaultValue != null || it.isLateinit }
|
||||
|
||||
val isInterface = kind == Implementation.Kind.Interface
|
||||
val isAbstract = kind == Implementation.Kind.AbstractClass
|
||||
|
||||
fun abstract() {
|
||||
if (isAbstract) {
|
||||
print("abstract ")
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInterface && !isAbstract && fieldsWithoutDefault.isNotEmpty()) {
|
||||
println("(")
|
||||
withIndent {
|
||||
fieldsWithoutDefault.forEachIndexed { i, field ->
|
||||
val end = if (i == fieldsWithoutDefault.size - 1) "" else ","
|
||||
printField(field, isImplementation = true, override = true, end = end)
|
||||
}
|
||||
}
|
||||
print(")")
|
||||
}
|
||||
|
||||
print(" : ")
|
||||
if (!isInterface && !allParents.any { it.kind == Implementation.Kind.AbstractClass }) {
|
||||
print("${pureAbstractElementType.type}(), ")
|
||||
}
|
||||
print(allParents.joinToString { "${it.typeWithArguments}${it.kind.braces()}" })
|
||||
println(" {")
|
||||
withIndent {
|
||||
if (isInterface || isAbstract) {
|
||||
allFields.forEach {
|
||||
abstract()
|
||||
printField(it, isImplementation = true, override = true, end = "")
|
||||
}
|
||||
} else {
|
||||
fieldsWithDefault.forEach {
|
||||
printFieldWithDefaultInImplementation(it)
|
||||
}
|
||||
if (fieldsWithDefault.isNotEmpty()) {
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
element.allFields.filter { it.type.contains("Symbol") && it !is FieldList }
|
||||
.takeIf { it.isNotEmpty() && !isInterface && !isAbstract && !element.type.contains("Reference") }
|
||||
?.let { symbolFields ->
|
||||
println("init {")
|
||||
for (symbolField in symbolFields) {
|
||||
withIndent {
|
||||
println("${symbolField.name}${symbolField.call()}bind(this)")
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
|
||||
fun Field.acceptString(): String = "${name}${call()}accept(visitor, data)"
|
||||
if (!isInterface && !isAbstract) {
|
||||
print("override fun <R, D> acceptChildren(visitor: FirVisitor<R, D>, data: D) {")
|
||||
|
||||
if (element.allFirFields.isNotEmpty()) {
|
||||
println()
|
||||
withIndent {
|
||||
for (field in allFields.filter { it.isFirType }) {
|
||||
if (field.withGetter || !field.needAcceptAndTransform) continue
|
||||
when (field.name) {
|
||||
"explicitReceiver" -> {
|
||||
val explicitReceiver = implementation["explicitReceiver"]!!
|
||||
val dispatchReceiver = implementation["dispatchReceiver"]!!
|
||||
val extensionReceiver = implementation["extensionReceiver"]!!
|
||||
println(
|
||||
"""
|
||||
|${explicitReceiver.acceptString()}
|
||||
| if (dispatchReceiver !== explicitReceiver) {
|
||||
| ${dispatchReceiver.acceptString()}
|
||||
| }
|
||||
| if (extensionReceiver !== explicitReceiver && extensionReceiver !== dispatchReceiver) {
|
||||
| ${extensionReceiver.acceptString()}
|
||||
| }
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
"dispatchReceiver", "extensionReceiver", "subjectVariable", "companionObject" -> {
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (type in setOf("FirClassImpl", "FirSealedClassImpl") && field.name == "declarations") {
|
||||
println("(declarations.firstOrNull { it is FirConstructorImpl } as? FirConstructorImpl)?.typeParameters?.forEach { it.accept(visitor, data) }")
|
||||
}
|
||||
if (type == "FirWhenExpressionImpl" && field.name == "subject") {
|
||||
println(
|
||||
"""
|
||||
|if (subjectVariable != null) {
|
||||
| subjectVariable.accept(visitor, data)
|
||||
| } else {
|
||||
| subject?.accept(visitor, data)
|
||||
| }
|
||||
""".trimMargin(),
|
||||
)
|
||||
} else {
|
||||
when (field.origin) {
|
||||
is FirField -> {
|
||||
println(field.acceptString())
|
||||
}
|
||||
|
||||
is FieldList -> {
|
||||
println("${field.name}.forEach { it.accept(visitor, data) }")
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
|
||||
abstract()
|
||||
print("override fun <D> transformChildren(transformer: FirTransformer<D>, data: D): $typeWithArguments")
|
||||
if (!isInterface && !isAbstract) {
|
||||
println(" {")
|
||||
withIndent {
|
||||
for (field in allFields) {
|
||||
when {
|
||||
!field.isMutable || !field.isFirType || field.withGetter || !field.needAcceptAndTransform -> {}
|
||||
|
||||
field.name == "explicitReceiver" -> {
|
||||
val explicitReceiver = implementation["explicitReceiver"]!!
|
||||
val dispatchReceiver = implementation["dispatchReceiver"]!!
|
||||
val extensionReceiver = implementation["extensionReceiver"]!!
|
||||
if (explicitReceiver.isMutable) {
|
||||
println("explicitReceiver = explicitReceiver${explicitReceiver.call()}transformSingle(transformer, data)")
|
||||
}
|
||||
if (dispatchReceiver.isMutable) {
|
||||
println(
|
||||
"""
|
||||
|if (dispatchReceiver !== explicitReceiver) {
|
||||
| dispatchReceiver = dispatchReceiver.transformSingle(transformer, data)
|
||||
| }
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
if (extensionReceiver.isMutable) {
|
||||
println(
|
||||
"""
|
||||
|if (extensionReceiver !== explicitReceiver && extensionReceiver !== dispatchReceiver) {
|
||||
| extensionReceiver = extensionReceiver.transformSingle(transformer, data)
|
||||
| }
|
||||
""".trimMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
field.name in setOf("dispatchReceiver", "extensionReceiver") -> {}
|
||||
|
||||
type in setOf("FirClassImpl", "FirSealedClassImpl") && field.name == "declarations" -> {
|
||||
println("(declarations.firstOrNull { it is FirConstructorImpl } as? FirConstructorImpl)?.typeParameters?.transformInplace(transformer, data)")
|
||||
println("declarations.transformInplace(transformer, data)")
|
||||
}
|
||||
|
||||
field.name == "companionObject" -> {
|
||||
println("companionObject = declarations.asSequence().filterIsInstance<FirRegularClass>().firstOrNull { it.status.isCompanion }")
|
||||
}
|
||||
|
||||
field.needsSeparateTransform -> {
|
||||
println("transform${field.name.capitalize()}(transformer, data)")
|
||||
}
|
||||
|
||||
!element.needTransformOtherChildren -> {
|
||||
field.transform()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
if (element.needTransformOtherChildren) {
|
||||
println("transformOtherChildren(transformer, data)")
|
||||
}
|
||||
println("return this")
|
||||
}
|
||||
println("}")
|
||||
} else {
|
||||
println()
|
||||
}
|
||||
|
||||
for (field in allFields) {
|
||||
if (!field.needsSeparateTransform) continue
|
||||
println()
|
||||
abstract()
|
||||
print("override ${field.transformFunctionDeclaration(typeWithArguments)}")
|
||||
if (isInterface || isAbstract) {
|
||||
println()
|
||||
continue
|
||||
}
|
||||
println(" {")
|
||||
withIndent {
|
||||
if (field.isMutable && field.isFirType) {
|
||||
// TODO: replace with smth normal
|
||||
if (type == "FirWhenExpressionImpl" && field.name == "subject") {
|
||||
println(
|
||||
"""
|
||||
|if (subjectVariable != null) {
|
||||
| subjectVariable = subjectVariable?.transformSingle(transformer, data)
|
||||
| } else {
|
||||
| subject = subject?.transformSingle(transformer, data)
|
||||
| }
|
||||
""".trimMargin(),
|
||||
)
|
||||
} else {
|
||||
field.transform()
|
||||
}
|
||||
}
|
||||
println("return this")
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
|
||||
if (element.needTransformOtherChildren) {
|
||||
println()
|
||||
abstract()
|
||||
print("override fun <D> transformOtherChildren(transformer: FirTransformer<D>, data: D): $typeWithArguments")
|
||||
if (isInterface || isAbstract) {
|
||||
println()
|
||||
} else {
|
||||
println(" {")
|
||||
withIndent {
|
||||
for (field in allFields) {
|
||||
if (!field.isMutable || !field.isFirType || field.name == "subjectVariable") continue
|
||||
if (!field.needsSeparateTransform) {
|
||||
field.transform()
|
||||
}
|
||||
}
|
||||
println("return this")
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
for (field in allFields.filter { it.withReplace }) {
|
||||
println()
|
||||
abstract()
|
||||
print("override ${field.replaceFunctionDeclaration()}")
|
||||
if (isInterface || isAbstract) {
|
||||
println()
|
||||
continue
|
||||
}
|
||||
print(" {")
|
||||
if (!field.isMutable) {
|
||||
println("}")
|
||||
continue
|
||||
}
|
||||
println()
|
||||
withIndent {
|
||||
val newValue = "new${field.name.capitalize()}"
|
||||
when {
|
||||
field.withGetter -> {}
|
||||
|
||||
field.origin is FieldList -> {
|
||||
println("${field.name}.clear()")
|
||||
println("${field.name}.addAll($newValue)")
|
||||
}
|
||||
|
||||
else -> {
|
||||
println("${field.name} = $newValue")
|
||||
}
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
private val COPYRIGHT = """
|
||||
/*
|
||||
* Copyright 2010-${GregorianCalendar()[Calendar.YEAR]} JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
""".trimIndent()
|
||||
|
||||
const val VISITOR_PACKAGE = "org.jetbrains.kotlin.fir.visitors"
|
||||
const val BASE_PACKAGE = "org.jetbrains.kotlin.fir"
|
||||
val GENERATED_MESSAGE = """
|
||||
/*
|
||||
* This file was generated automatically
|
||||
* DO NOT MODIFY IT MANUALLY
|
||||
*/
|
||||
""".trimIndent()
|
||||
|
||||
fun printElements(builder: AbstractFirTreeBuilder, generationPath: File) {
|
||||
builder.elements.forEach { it.generateCode(generationPath) }
|
||||
builder.elements.flatMap { it.allImplementations }.forEach { it.generateCode(generationPath) }
|
||||
|
||||
printVisitor(builder.elements, generationPath)
|
||||
printVisitorVoid(builder.elements, generationPath)
|
||||
printTransformer(builder.elements, generationPath)
|
||||
}
|
||||
|
||||
fun SmartPrinter.printCopyright() {
|
||||
println(COPYRIGHT)
|
||||
println()
|
||||
}
|
||||
|
||||
fun SmartPrinter.printGeneratedMessage() {
|
||||
println(GENERATED_MESSAGE)
|
||||
println()
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.compositeTransformResultType
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Element
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun printTransformer(elements: List<Element>, generationPath: File) {
|
||||
val dir = File(generationPath, VISITOR_PACKAGE.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
File(dir, "FirTransformer.kt").useSmartPrinter {
|
||||
printCopyright()
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
elements.forEach { println("import ${it.fullQualifiedName}") }
|
||||
println("import ${compositeTransformResultType.fullQualifiedName}")
|
||||
println()
|
||||
printGeneratedMessage()
|
||||
|
||||
println("abstract class FirTransformer<in D> : FirVisitor<CompositeTransformResult<FirElement>, D>() {")
|
||||
println()
|
||||
withIndent {
|
||||
println("abstract fun <E : FirElement> transformElement(element: E, data: D): CompositeTransformResult<E>")
|
||||
println()
|
||||
for (element in elements) {
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) continue
|
||||
val varName = element.safeDecapitalizedName
|
||||
print("open fun ")
|
||||
element.typeParameters.takeIf { it.isNotBlank() }?.let { print(it) }
|
||||
println(
|
||||
"transform${element.name}($varName: ${element.typeWithArguments}, data: D): CompositeTransformResult<${element.transformerType
|
||||
.typeWithArguments}>${element.multipleUpperBoundsList()}{",
|
||||
)
|
||||
withIndent {
|
||||
println("return transformElement($varName, data)")
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
|
||||
for (element in elements) {
|
||||
val varName = element.safeDecapitalizedName
|
||||
print("final override fun ")
|
||||
element.typeParameters.takeIf { it.isNotBlank() }?.let { print(it) }
|
||||
|
||||
println(
|
||||
"visit${element.name}($varName: ${element.typeWithArguments}, data: D): CompositeTransformResult<${element.transformerType
|
||||
.typeWithArguments}>${element.multipleUpperBoundsList()}{",
|
||||
)
|
||||
withIndent {
|
||||
println("return transform${element.name}($varName, data)")
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.compositeTransformResultType
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.*
|
||||
import org.jetbrains.kotlin.fir.tree.generator.pureAbstractElementType
|
||||
|
||||
|
||||
fun Implementation.collectImports(): List<String> {
|
||||
return element.collectImportsInternal(
|
||||
listOf(
|
||||
element.fullQualifiedName,
|
||||
)
|
||||
+ usedTypes.mapNotNull { it.fullQualifiedName } + parents.mapNotNull { it.fullQualifiedName }
|
||||
+ listOfNotNull(
|
||||
pureAbstractElementType.fullQualifiedName?.takeIf { needPureAbstractElement },
|
||||
),
|
||||
isImpl = true,
|
||||
)
|
||||
}
|
||||
|
||||
fun Element.collectImports(): List<String> {
|
||||
val baseTypes = parents.mapTo(mutableListOf()) { it.fullQualifiedName }
|
||||
baseTypes += parentsArguments.values.flatMap { it.values }.mapNotNull { it.fullQualifiedName }
|
||||
val isBaseFirElement = this == AbstractFirTreeBuilder.baseFirElement
|
||||
if (isBaseFirElement) {
|
||||
baseTypes += compositeTransformResultType.fullQualifiedName!!
|
||||
}
|
||||
if (needPureAbstractElement) {
|
||||
baseTypes += pureAbstractElementType.fullQualifiedName!!
|
||||
}
|
||||
return collectImportsInternal(
|
||||
baseTypes,
|
||||
isImpl = false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Element.collectImportsInternal(base: List<String>, isImpl: Boolean): List<String> {
|
||||
val fqns = base + allFields.mapNotNull { it.fullQualifiedName } +
|
||||
allFields.flatMap { it.arguments.mapNotNull { it.fullQualifiedName } } +
|
||||
typeArguments.flatMap { it.upperBounds.mapNotNull { it.fullQualifiedName } }
|
||||
val realPackageName = if (isImpl) "$packageName.impl." else "$packageName."
|
||||
return fqns.filter { fqn ->
|
||||
fqn.dropLastWhile { it != '.' } != realPackageName
|
||||
}.distinct().sorted() + "$VISITOR_PACKAGE.*"
|
||||
}
|
||||
|
||||
val KindOwner.needPureAbstractElement: Boolean
|
||||
get() = (kind != Implementation.Kind.Interface) && !allParents.any { it.kind == Implementation.Kind.AbstractClass }
|
||||
|
||||
|
||||
val Field.isVal: Boolean get() = this is FieldList || (this is FieldWithDefault && origin is FieldList) || !isMutable
|
||||
|
||||
|
||||
fun Field.transformFunctionDeclaration(returnType: String): String {
|
||||
return transformFunctionDeclaration(name.capitalize(), returnType)
|
||||
}
|
||||
|
||||
fun transformFunctionDeclaration(transformName: String, returnType: String): String {
|
||||
return "fun <D> transform$transformName(transformer: FirTransformer<D>, data: D): $returnType"
|
||||
}
|
||||
|
||||
fun Field.replaceFunctionDeclaration(): String {
|
||||
val capName = name.capitalize()
|
||||
return "fun replace$capName(new$capName: $typeWithArgumentsWithoutNullablity)"
|
||||
}
|
||||
|
||||
val Field.mutableType: String
|
||||
get() = when (this) {
|
||||
is FieldList -> if (isMutable) "Mutable$typeWithArguments" else typeWithArguments
|
||||
is FieldWithDefault -> if (isMutable) origin.mutableType else typeWithArguments
|
||||
else -> typeWithArguments
|
||||
}
|
||||
|
||||
fun Field.call(): String = if (nullable) "?." else "."
|
||||
|
||||
fun Element.multipleUpperBoundsList(): String {
|
||||
return typeArguments.filterIsInstance<TypeArgumentWithMultipleUpperBounds>().takeIf { it.isNotEmpty() }?.let { arguments ->
|
||||
val upperBoundsList = arguments.joinToString(", ", postfix = " ") { argument ->
|
||||
argument.upperBounds.joinToString(", ") { upperBound -> "${argument.name} : ${upperBound.typeWithArguments}" }
|
||||
}
|
||||
" where $upperBoundsList"
|
||||
} ?: " "
|
||||
}
|
||||
|
||||
fun Implementation.Kind?.braces(): String = when (this) {
|
||||
Implementation.Kind.Interface -> ""
|
||||
Implementation.Kind.OpenClass, Implementation.Kind.AbstractClass -> "()"
|
||||
else -> throw IllegalStateException(this.toString())
|
||||
}
|
||||
|
||||
val Element.safeDecapitalizedName: String get() = if (name == "Class") "klass" else name.decapitalize()
|
||||
|
||||
val Importable.typeWithArguments: String
|
||||
get() = when (this) {
|
||||
is AbstractElement -> type + generics
|
||||
is Implementation -> type + element.generics
|
||||
is FirField -> element.typeWithArguments + if (nullable) "?" else ""
|
||||
is Field -> type + generics + if (nullable) "?" else ""
|
||||
is Type -> type + generics
|
||||
is ImplementationWithArg -> type + generics
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val Importable.typeWithArgumentsWithoutNullablity: String get() = typeWithArguments.dropLastWhile { it == '?' }
|
||||
|
||||
val ImplementationWithArg.generics: String
|
||||
get() = argument?.let { "<${it.type}>" } ?: ""
|
||||
|
||||
val AbstractElement.generics: String
|
||||
get() = typeArguments.takeIf { it.isNotEmpty() }
|
||||
?.let { it.joinToString(", ", "<", ">") { it.name } }
|
||||
?: ""
|
||||
|
||||
val Field.generics: String
|
||||
get() = arguments.takeIf { it.isNotEmpty() }
|
||||
?.let { it.joinToString(", ", "<", ">") { it.typeWithArguments } }
|
||||
?: ""
|
||||
|
||||
val Element.typeParameters: String
|
||||
get() = typeArguments.takeIf { it.isNotEmpty() }
|
||||
?.joinToString(", ", "<", "> ")
|
||||
?: ""
|
||||
|
||||
val Type.generics: String
|
||||
get() = arguments.takeIf { it.isNotEmpty() }?.joinToString(",", "<", ">") ?: ""
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.tree.generator.printer
|
||||
|
||||
import org.jetbrains.kotlin.fir.tree.generator.context.AbstractFirTreeBuilder
|
||||
import org.jetbrains.kotlin.fir.tree.generator.model.Element
|
||||
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
|
||||
fun printVisitor(elements: List<Element>, generationPath: File) {
|
||||
val dir = File(generationPath, VISITOR_PACKAGE.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
File(dir, "FirVisitor.kt").useSmartPrinter {
|
||||
printCopyright()
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
elements.forEach { println("import ${it.fullQualifiedName}") }
|
||||
println()
|
||||
printGeneratedMessage()
|
||||
|
||||
println("abstract class FirVisitor<out R, in D> {")
|
||||
|
||||
pushIndent()
|
||||
println("abstract fun visitElement(element: FirElement, data: D): R\n")
|
||||
for (element in elements) {
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) continue
|
||||
with(element) {
|
||||
val varName = safeDecapitalizedName
|
||||
println("open fun ${typeParameters}visit$name($varName: $typeWithArguments, data: D): R${multipleUpperBoundsList()} = visitElement($varName, data)")
|
||||
println()
|
||||
}
|
||||
}
|
||||
popIndent()
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun printVisitorVoid(elements: List<Element>, generationPath: File) {
|
||||
val dir = File(generationPath, VISITOR_PACKAGE.replace(".", "/"))
|
||||
dir.mkdirs()
|
||||
File(dir, "FirVisitorVoid.kt").useSmartPrinter {
|
||||
printCopyright()
|
||||
println("package $VISITOR_PACKAGE")
|
||||
println()
|
||||
elements.forEach { println("import ${it.fullQualifiedName}") }
|
||||
println()
|
||||
printGeneratedMessage()
|
||||
|
||||
println("abstract class FirVisitorVoid : FirVisitor<Unit, Nothing?>() {")
|
||||
|
||||
withIndent {
|
||||
println("abstract fun visitElement(element: FirElement)")
|
||||
println()
|
||||
for (element in elements) {
|
||||
if (element == AbstractFirTreeBuilder.baseFirElement) continue
|
||||
with(element) {
|
||||
val varName = safeDecapitalizedName
|
||||
println("open fun ${typeParameters}visit$name($varName: $typeWithArguments)${multipleUpperBoundsList()}{")
|
||||
withIndent {
|
||||
println("visitElement($varName)")
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
||||
for (element in elements) {
|
||||
with(element) {
|
||||
val varName = safeDecapitalizedName
|
||||
println("final override fun ${typeParameters}visit$name($varName: $typeWithArguments, data: Nothing?)${multipleUpperBoundsList()}{")
|
||||
withIndent {
|
||||
println("visit$name($varName)")
|
||||
}
|
||||
println("}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user