From de778d986550ef93580eba0fff95de78aeb7ece5 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Tue, 28 Jul 2015 21:48:01 +0300 Subject: [PATCH] Prototyping preprocessor: stripping declarations and replacing name. --- .idea/modules.xml | 1 + .../conditional-preprocessor.iml | 15 +++ .../Conditional.kt | 61 ++++++++++ .../Evaluator.kt | 40 ++++++ .../Preprocessor.kt | 114 ++++++++++++++++++ .../SplitArguments.kt | 36 ++++++ 6 files changed, 267 insertions(+) create mode 100644 compiler/conditional-preprocessor/conditional-preprocessor.iml create mode 100644 compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Conditional.kt create mode 100644 compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Evaluator.kt create mode 100644 compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Preprocessor.kt create mode 100644 compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/SplitArguments.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 57d3de81d84..eb77c89fb96 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -19,6 +19,7 @@ + diff --git a/compiler/conditional-preprocessor/conditional-preprocessor.iml b/compiler/conditional-preprocessor/conditional-preprocessor.iml new file mode 100644 index 00000000000..07111c35a27 --- /dev/null +++ b/compiler/conditional-preprocessor/conditional-preprocessor.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Conditional.kt b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Conditional.kt new file mode 100644 index 00000000000..ba0bb38e624 --- /dev/null +++ b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Conditional.kt @@ -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 = listOf(JvmVersion, JsVersion, TargetName).toMap { it.name } + } +} + +fun JetAnnotated.parseConditionalAnnotations(): List = + annotationEntries.map { + val parser = Conditional.ANNOTATIONS.get(it.typeReferenceName) + parser?.parse?.invoke(it.valueArguments.splitToPositionalAndNamed()) + }.filterNotNull() + + diff --git a/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Evaluator.kt b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Evaluator.kt new file mode 100644 index 00000000000..766612e7853 --- /dev/null +++ b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Evaluator.kt @@ -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, Boolean> + +abstract class PlatformEvaluator : Evaluator { + final override fun invoke(conditions: List): Boolean = evaluate(conditions.filterIsInstance()) + + open fun evaluate(conditions: List): 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" +} \ No newline at end of file diff --git a/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Preprocessor.kt b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Preprocessor.kt new file mode 100644 index 00000000000..f7d6809913f --- /dev/null +++ b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/Preprocessor.kt @@ -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) { + 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) + .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 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().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) + + + diff --git a/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/SplitArguments.kt b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/SplitArguments.kt new file mode 100644 index 00000000000..031df9f3339 --- /dev/null +++ b/compiler/conditional-preprocessor/src/org.jetbrains.kotlin.preprocessor/SplitArguments.kt @@ -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, val named: List) +{ + fun get(position: Int, name: String): ValueArgument? = + positional.getOrNull(position) ?: named.find { it.getArgumentName()!!.asName.asString() == name } +} + +fun List.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().joinToString("") { it.text } \ No newline at end of file