Prototyping preprocessor: stripping declarations and replacing name.

This commit is contained in:
Ilya Gorbunov
2015-07-28 21:48:01 +03:00
parent 3d0e91065a
commit de778d9865
6 changed files with 267 additions and 0 deletions
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="module" module-name="cli" />
<orderEntry type="library" name="intellij-core" level="project" />
<orderEntry type="module" module-name="frontend" />
</component>
</module>
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preprocessor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
interface Conditional {
interface PlatformVersion : Conditional
abstract class Parser(val name: String, val parse: (arguments: SplitArguments) -> Conditional)
data class JvmVersion(val minimum: Int, val maximum: Int): PlatformVersion {
val versionRange: IntRange = minimum..maximum
companion object : Parser("JvmVersion", parse = { arguments ->
val minimum = arguments[0, "minimum"]?.parseIntegerValue()
val maximum = arguments[1, "maximum"]?.parseIntegerValue()
JvmVersion(minimum ?: 6, maximum ?: 100)
})
}
data class JsVersion(): PlatformVersion {
companion object : Parser("JsVersion", parse = { JsVersion() })
}
data class TargetName(val name: String): Conditional {
companion object : Parser("RenameOnTargetPlatform", parse = { arguments ->
val name = arguments[0, "name"]!!.parseStringValue()
TargetName(name)
})
}
companion object {
val ANNOTATIONS: Map<String, Parser> = listOf<Parser>(JvmVersion, JsVersion, TargetName).toMap { it.name }
}
}
fun JetAnnotated.parseConditionalAnnotations(): List<Conditional> =
annotationEntries.map {
val parser = Conditional.ANNOTATIONS.get(it.typeReferenceName)
parser?.parse?.invoke(it.valueArguments.splitToPositionalAndNamed())
}.filterNotNull()
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preprocessor
interface Evaluator : Function1<List<Conditional>, Boolean>
abstract class PlatformEvaluator : Evaluator {
final override fun invoke(conditions: List<Conditional>): Boolean = evaluate(conditions.filterIsInstance())
open fun evaluate(conditions: List<Conditional.PlatformVersion>): Boolean
= conditions.isEmpty() || conditions.any { match(it) }
abstract fun match(platformCondition: Conditional.PlatformVersion): Boolean
}
data class JvmPlatformEvaluator(val version: Int): PlatformEvaluator() {
override fun match(platformCondition: Conditional.PlatformVersion)
= platformCondition is Conditional.JvmVersion && version in platformCondition.versionRange
override fun toString() = "platform: JVM$version"
}
data class JsPlatformEvaluator(val ecmaScriptVersion: Int = 5): PlatformEvaluator() {
override fun match(platformCondition: Conditional.PlatformVersion)
= platformCondition is Conditional.JsVersion
override fun toString() = "platform: JS"
}
@@ -0,0 +1,114 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preprocessor
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.psi.*
import java.io.File
fun main(args: Array<String>) {
require(args.size() == 1, "Please specify path to sources")
val sourcePath = File(args.first())
val configuration = CompilerConfiguration()
val environment = KotlinCoreEnvironment.createForProduction(Disposable { }, configuration, emptyList())
val project = environment.project
val jetPsiFactory = JetPsiFactory(project)
val fileType = JetFileType.INSTANCE
val evaluator = JvmPlatformEvaluator(version = 7)
//val evaluator = JsPlatformEvaluator()
println("Using condition evaluator: $evaluator")
(FileTreeWalk(sourcePath) as Sequence<File>)
.filter { it.isFile && it.extension == fileType.defaultExtension }
.forEach { sourceFile ->
val sourceText = sourceFile.readText().convertLineSeparators()
val psiFile = jetPsiFactory.createFile(sourceFile.name, sourceText)
println("$psiFile")
val visitor = EvaluatorVisitor(evaluator)
psiFile.accept(visitor)
var prevIndex = 0
val resultText = StringBuilder()
for ((range, selector) in visitor.elementModifications) {
resultText.append(sourceText, prevIndex, range.startOffset)
val rangeText = range.substring(sourceText)
val newValue = selector(rangeText)
if (newValue.isEmpty()) {
resultText.append("/* Not available on ${visitor.evaluator} */")
repeat(StringUtil.getLineBreakCount(rangeText)) {
resultText.append("\n")
}
}
else {
resultText.append(newValue)
}
prevIndex = range.endOffset
}
resultText.append(sourceText, prevIndex, sourceText.length())
println(resultText.toString())
//processDeclaration("/", psiFile, evaluator)
}
}
class EvaluatorVisitor(val evaluator: Evaluator) : JetTreeVisitorVoid() {
val elementModifications: MutableList<Pair<TextRange, (String) -> String>> = arrayListOf()
override fun visitDeclaration(declaration: JetDeclaration) {
super.visitDeclaration(declaration)
val annotations = declaration.parseConditionalAnnotations()
val name = (declaration as? JetNamedDeclaration)?.nameAsSafeName ?: declaration.name
val conditionalResult = evaluator(annotations)
println("declaration: ${declaration.javaClass.simpleName} $name, annotations: ${annotations.joinToString { it.toString() }}, evaluation result: $conditionalResult")
if (!conditionalResult)
elementModifications.add(declaration.textRange to {it -> ""})
else {
val targetName = annotations.filterIsInstance<Conditional.TargetName>().singleOrNull()
if (targetName != null) {
val placeholderName = (declaration as JetNamedDeclaration).nameAsName!!.asString()
val realName = targetName.name
elementModifications.add(declaration.textRange to { it -> it.replace(placeholderName, realName) })
}
}
}
}
val JetAnnotationEntry.typeReferenceName: String? get() =
(typeReference?.typeElement as? JetUserType)?.referencedName
fun String.convertLineSeparators(): String = StringUtil.convertLineSeparators(this)
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preprocessor
import org.jetbrains.kotlin.psi.JetStringTemplateEntry
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
data class SplitArguments(val positional: List<ValueArgument>, val named: List<ValueArgument>)
{
fun get(position: Int, name: String): ValueArgument? =
positional.getOrNull(position) ?: named.find { it.getArgumentName()!!.asName.asString() == name }
}
fun List<ValueArgument>.splitToPositionalAndNamed(): SplitArguments {
val (positional, named) = partition { !it.isNamed() }
return SplitArguments(positional, named)
}
fun ValueArgument.parseIntegerValue(): Int = getArgumentExpression()!!.text.toInt()
fun ValueArgument.parseStringValue(): String = getArgumentExpression()!!.getChildrenOfType<JetStringTemplateEntry>().joinToString("") { it.text }