Refactor scripting compiler plugin - move cli dependent parts out of the -impl jar

The kotlin-scripting-compiler-impl jar is used in the idea plugin and
therefore should not depend on the cli parts of the compiler to avoid
dependency from the plugin to the kotlin-compiler.jar.
Therefore the cli-dependent parts were moved to the scripting plugin
jar, which is used only in cli compiler based environments.
Also implement required abstractions to allow this movement and
drop some redundant dependencies to the cli parts in other projects.
This commit is contained in:
Ilya Chernikov
2019-07-08 16:52:19 +02:00
parent 9ae0ff03fa
commit 9c004c3a52
55 changed files with 185 additions and 185 deletions
@@ -11,7 +11,6 @@ dependencies {
compileOnly(project(":compiler:frontend.java"))
compileOnly(project(":compiler:psi"))
compileOnly(project(":compiler:plugin-api"))
compileOnly(project(":compiler:cli"))
compile(project(":kotlin-scripting-common"))
compile(project(":kotlin-scripting-jvm"))
compile(kotlinStdlib())
@@ -1,36 +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.scripting.configuration
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.scripting.definitions.loadScriptTemplatesFromClasspath
import org.jetbrains.kotlin.scripting.definitions.reporter
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
const val KOTLIN_SCRIPTING_PLUGIN_ID = "kotlin.scripting"
fun configureScriptDefinitions(
scriptTemplates: List<String>,
configuration: CompilerConfiguration,
baseClassloader: ClassLoader,
messageCollector: MessageCollector,
hostConfiguration: ScriptingHostConfiguration
) {
// TODO: consider using escaping to allow kotlin escaped names in class names
val templatesFromClasspath = loadScriptTemplatesFromClasspath(
scriptTemplates, configuration.jvmClasspathRoots, emptyList(), baseClassloader, hostConfiguration, messageCollector.reporter
)
configuration.addAll(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, templatesFromClasspath.toList())
}
fun makeHostConfiguration(project: Project, configuration: CompilerConfiguration): ScriptingHostConfiguration =
ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
// TODO: add jdk path and other params if needed
}
@@ -5,19 +5,14 @@
package org.jetbrains.kotlin.scripting.definitions
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import java.io.File
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.fileExtension
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
class LazyScriptDefinitionFromDiscoveredClass internal constructor(
baseHostConfiguration: ScriptingHostConfiguration,
@@ -43,7 +38,7 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor(
private val configurations by lazy(LazyThreadSafetyMode.PUBLICATION) {
messageReporter(
CompilerMessageSeverity.LOGGING,
ScriptDiagnostic.Severity.DEBUG,
"Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}"
)
try {
@@ -61,11 +56,11 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor(
)
compileCfg to evalCfg
} catch (ex: ClassNotFoundException) {
messageReporter(CompilerMessageSeverity.ERROR, "Cannot find script definition class $className")
messageReporter(ScriptDiagnostic.Severity.ERROR, "Cannot find script definition class $className")
InvalidScriptDefinition
} catch (ex: Exception) {
messageReporter(
CompilerMessageSeverity.ERROR,
ScriptDiagnostic.Severity.ERROR,
"Error processing script definition class $className: ${ex.message}\nclasspath:\n${classpath.joinToString("\n", " ")}"
)
InvalidScriptDefinition
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.scripting.definitions
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
@@ -14,21 +12,14 @@ import java.io.IOException
import java.net.URLClassLoader
import java.util.jar.JarFile
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.templates.ScriptTemplateDefinition
const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/templates/"
const val SCRIPT_DEFINITION_MARKERS_EXTENSION_WITH_DOT = ".classname"
typealias MessageReporter = (CompilerMessageSeverity, String) -> Unit
val MessageCollector.reporter: MessageReporter
get() = { severity, message ->
this.report(severity, message)
}
typealias MessageReporter = (ScriptDiagnostic.Severity, String) -> Unit
class ScriptDefinitionsFromClasspathDiscoverySource(
private val classpath: List<File>,
@@ -107,7 +98,7 @@ private fun scriptTemplatesDiscoverySequence(
)
if (notFoundClasses.isNotEmpty()) {
messageReporter(
CompilerMessageSeverity.STRONG_WARNING,
ScriptDiagnostic.Severity.WARNING,
"Configure scripting: unable to find script definitions [${notFoundClasses.joinToString(", ")}]"
)
}
@@ -131,12 +122,12 @@ private fun scriptTemplatesDiscoverySequence(
}
else -> {
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
messageReporter(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
messageReporter(ScriptDiagnostic.Severity.DEBUG, "Configure scripting: Unknown classpath entry $dep")
}
}
} catch (e: IOException) {
messageReporter(
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
ScriptDiagnostic.Severity.WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
)
}
}
@@ -152,13 +143,13 @@ private fun scriptTemplatesDiscoverySequence(
remainingDefinitionCandidates = notFoundDefinitions
} catch (e: IOException) {
messageReporter(
CompilerMessageSeverity.STRONG_WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
ScriptDiagnostic.Severity.WARNING, "Configure scripting: unable to process classpath entry $dep: $e"
)
}
}
if (remainingDefinitionCandidates.isNotEmpty()) {
messageReporter(
CompilerMessageSeverity.STRONG_WARNING,
ScriptDiagnostic.Severity.WARNING,
"The following script definitions are not found in the classpath: [${remainingDefinitionCandidates.joinToString()}]"
)
}
@@ -207,7 +198,7 @@ fun loadScriptTemplatesFromClasspath(
}
else -> {
// assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here
messageReporter(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep")
messageReporter(ScriptDiagnostic.Severity.DEBUG, "Configure scripting: Unknown classpath entry $dep")
DefinitionsLoadPartitionResult(
listOf(),
remainingTemplates
@@ -222,7 +213,7 @@ fun loadScriptTemplatesFromClasspath(
}
} catch (e: IOException) {
messageReporter(
CompilerMessageSeverity.STRONG_WARNING,
ScriptDiagnostic.Severity.WARNING,
"Configure scripting: unable to process classpath entry $dep: $e"
)
}
@@ -230,7 +221,7 @@ fun loadScriptTemplatesFromClasspath(
if (remainingTemplates.isNotEmpty()) {
messageReporter(
CompilerMessageSeverity.STRONG_WARNING,
ScriptDiagnostic.Severity.WARNING,
"Configure scripting: unable to find script definition classes: ${remainingTemplates.joinToString(", ")}"
)
}
@@ -319,14 +310,14 @@ private fun loadScriptDefinition(
}
if (def != null) {
messageReporter(
CompilerMessageSeverity.LOGGING,
ScriptDiagnostic.Severity.DEBUG,
"Configure scripting: Added template $templateClassName from ${classpathWithLoader.classpath}"
)
return def
}
}
messageReporter(
CompilerMessageSeverity.STRONG_WARNING,
ScriptDiagnostic.Severity.WARNING,
"Configure scripting: $templateClassName is not marked with any known kotlin script annotation"
)
return null
@@ -347,7 +338,7 @@ private fun loadScriptDefinition(
ScriptDefinition.FromLegacyTemplate(hostConfiguration, cls.kotlin)
}
messageReporter(
CompilerMessageSeverity.INFO,
ScriptDiagnostic.Severity.INFO,
"Added script definition $template to configuration: name = ${def.name}"
)
return def
@@ -356,7 +347,7 @@ private fun loadScriptDefinition(
} catch (ex: Exception) {
// other exceptions - might be an error
messageReporter(
CompilerMessageSeverity.STRONG_WARNING,
ScriptDiagnostic.Severity.WARNING,
"Error on loading script definition $template: ${ex.message}"
)
}
@@ -1,86 +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.scripting.dependencies
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.jvm.compiler.createSourceFilesFromSourceRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import java.io.File
import kotlin.script.experimental.api.valueOrNull
data class ScriptsCompilationDependencies(
val classpath: List<File>,
val sources: List<KtFile>,
val sourceDependencies: List<SourceDependencies>
) {
data class SourceDependencies(
val scriptFile: KtFile,
val sourceDependencies: List<KtFile>
)
}
// recursively collect dependencies from initial and imported scripts
fun collectScriptsCompilationDependencies(
configuration: CompilerConfiguration,
project: Project,
initialSources: Iterable<KtFile>
): ScriptsCompilationDependencies {
val collectedClassPath = ArrayList<File>()
val collectedSources = ArrayList<KtFile>()
val collectedSourceDependencies = ArrayList<ScriptsCompilationDependencies.SourceDependencies>()
var remainingSources = initialSources
val knownSourcePaths = initialSources.mapNotNullTo(HashSet()) { it.virtualFile?.path }
val importsProvider = ScriptDependenciesProvider.getInstance(project)
if (importsProvider != null) {
while (true) {
val newRemainingSources = ArrayList<KtFile>()
for (source in remainingSources) {
val refinedConfiguration = importsProvider.getScriptConfigurationResult(source)?.valueOrNull()
if (refinedConfiguration != null) {
collectedClassPath.addAll(refinedConfiguration.dependenciesClassPath)
val sourceDependenciesRoots = refinedConfiguration.importedScripts.map {
KotlinSourceRoot(it.path, false)
}
val sourceDependencies =
createSourceFilesFromSourceRoots(
configuration, project, sourceDependenciesRoots,
// TODO: consider receiving and using precise location from the resolver in the future
source.virtualFile?.path?.let { CompilerMessageLocation.create(it) }
)
if (sourceDependencies.isNotEmpty()) {
collectedSourceDependencies.add(
ScriptsCompilationDependencies.SourceDependencies(
source,
sourceDependencies
)
)
val newSources = sourceDependencies.filterNot { knownSourcePaths.contains(it.virtualFile.path) }
for (newSource in newSources) {
collectedSources.add(newSource)
newRemainingSources.add(newSource)
knownSourcePaths.add(newSource.virtualFile.path)
}
}
}
}
if (newRemainingSources.isEmpty()) break
else {
remainingSources = newRemainingSources
}
}
}
return ScriptsCompilationDependencies(
collectedClassPath.distinctBy { it.absolutePath },
collectedSources,
collectedSourceDependencies
)
}
@@ -1,45 +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.scripting.extensions
import com.intellij.core.JavaCoreProjectEnvironment
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.extensions.ReplFactoryExtension
import org.jetbrains.kotlin.cli.common.repl.ReplCompiler
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.repl.GenericReplCompiler
import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate
import java.io.File
import java.net.URLClassLoader
class JvmStandardReplFactoryExtension : ReplFactoryExtension {
override fun makeReplCompiler(
templateClassName: String,
templateClasspath: List<File>,
baseClassLoader: ClassLoader?,
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ReplCompiler = GenericReplCompiler(
projectEnvironment.parentDisposable,
makeScriptDefinition(templateClasspath, templateClassName, baseClassLoader),
configuration,
configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
)
private fun makeScriptDefinition(
templateClasspath: List<File>, templateClassName: String, baseClassLoader: ClassLoader?
): KotlinScriptDefinition = try {
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader)
val cls = classloader.loadClass(templateClassName)
KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, emptyMap())
} catch (ex: ClassNotFoundException) {
throw IllegalStateException("Cannot find script definition template class $templateClassName", ex)
} catch (ex: Exception) {
throw IllegalStateException("Error loading script definition template $templateClassName", ex)
}
}
@@ -1,30 +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.scripting.extensions
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.extensions.CollectAdditionalSourcesExtension
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.dependencies.collectScriptsCompilationDependencies
class ScriptingCollectAdditionalSourcesExtension(val project: MockProject) : CollectAdditionalSourcesExtension {
override fun collectAdditionalSourcesAndUpdateConfiguration(
knownSources: Collection<KtFile>,
configuration: CompilerConfiguration,
project: Project
): Collection<KtFile> {
val (newSourcesClasspath, newSources, _) = collectScriptsCompilationDependencies(
configuration,
project,
knownSources
)
configuration.addJvmClasspathRoots(newSourcesClasspath)
return newSources
}
}
@@ -1,54 +0,0 @@
/*
* Copyright 2010-2017 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.scripting.repl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
open class DelegatePackageMemberDeclarationProvider(var delegate: PackageMemberDeclarationProvider) : PackageMemberDeclarationProvider {
// Can't use Kotlin delegate feature because of inability to change delegate object in runtime (KT-5870)
override fun getAllDeclaredSubPackages(nameFilter: (Name) -> Boolean) = delegate.getAllDeclaredSubPackages(nameFilter)
override fun getPackageFiles() = delegate.getPackageFiles()
override fun containsFile(file: KtFile) = delegate.containsFile(file)
override fun getDeclarations(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
) = delegate.getDeclarations(kindFilter, nameFilter)
override fun getFunctionDeclarations(name: Name) = delegate.getFunctionDeclarations(name)
override fun getPropertyDeclarations(name: Name) = delegate.getPropertyDeclarations(name)
override fun getDestructuringDeclarationsEntries(name: Name): Collection<KtDestructuringDeclarationEntry> =
delegate.getDestructuringDeclarationsEntries(name)
override fun getClassOrObjectDeclarations(name: Name) = delegate.getClassOrObjectDeclarations(name)
override fun getScriptDeclarations(name: Name): Collection<KtScriptInfo> = delegate.getScriptDeclarations(name)
override fun getTypeAliasDeclarations(name: Name) = delegate.getTypeAliasDeclarations(name)
override fun getDeclarationNames() = delegate.getDeclarationNames()
}
@@ -1,65 +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.scripting.repl
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.repl.messages.DiagnosticMessageHolder
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.script.experimental.dependencies.ScriptDependencies
class ReplCompilerStageHistory(private val state: org.jetbrains.kotlin.scripting.repl.GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.analyzerEngine.resetToLine(id)
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
private fun checkConsistent(removedCompiledLines: Iterable<ILineId>, removedAnalyzedLines: List<ReplCodeLine>) {
removedCompiledLines.zip(removedAnalyzedLines).forEach { (removedCompiledLine, removedAnalyzedLine) ->
if (removedCompiledLine != LineId(removedAnalyzedLine)) {
throw IllegalStateException("History mismatch when resetting lines: ${removedCompiledLine.no} != $removedAnalyzedLine")
}
}
}
}
abstract class GenericReplCheckerState : IReplStageState<ScriptDescriptor> {
// "line" - is the unit of evaluation here, could in fact consists of several character lines
class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder
)
var lastLineState: org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState.LineState? = null // for transferring state to the compiler in most typical case
}
class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) :
IReplStageState<ScriptDescriptor>, org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState() {
override val history = org.jetbrains.kotlin.scripting.repl.ReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val analyzerEngine = org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer(environment)
var lastDependencies: ScriptDependencies? = null
}
@@ -1,100 +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.scripting.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.repl.messages.ConsoleDiagnosticMessageHolder
import kotlin.concurrent.write
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target"
open class GenericReplChecker(
disposable: Disposable,
private val scriptDefinition: KotlinScriptDefinition,
private val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCheckAction {
internal val environment = run {
compilerConfiguration.apply {
val hostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
configurationDependencies(JvmDependency(jvmClasspathRoots))
}
add(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, ScriptDefinition.FromLegacy(hostConfiguration, scriptDefinition))
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
if (get(JVMConfigurationKeys.JVM_TARGET) == null) {
put(JVMConfigurationKeys.JVM_TARGET,
System.getProperty(KOTLIN_REPL_JVM_TARGET_PROPERTY)?.let { JvmTarget.fromString(it) }
?: System.getProperty("java.specification.version")?.let { JvmTarget.fromString(it) }
?: JvmTarget.DEFAULT)
}
}
KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private fun createDiagnosticHolder() = ConsoleDiagnosticMessageHolder()
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
state.lock.write {
val checkerState = state.asState(org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState::class.java)
val scriptFileName = makeScriptBaseName(codeLine)
val virtualFile =
LightVirtualFile(
"$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}",
KotlinLanguage.INSTANCE,
StringUtil.convertLineSeparators(codeLine.code)
).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
val errorHolder = createDiagnosticHolder()
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
if (!syntaxErrorReport.isHasErrors) {
checkerState.lastLineState =
org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState.LineState(codeLine, psiFile, errorHolder)
}
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete()
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderMessage())
else -> ReplCheckResult.Ok()
}
}
}
}
@@ -1,131 +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.scripting.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDependenciesProvider
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.valueOrNull
// WARNING: not thread safe, assuming external synchronization
open class GenericReplCompiler(
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
private val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCompiler {
constructor(
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : this(Disposer.newDisposable(), scriptDefinition, compilerConfiguration, messageCollector)
private val checker =
GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector)
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
org.jetbrains.kotlin.scripting.repl.GenericReplCompilerState(checker.environment, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = checker.check(state, codeLine)
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
state.lock.write {
val compilerState = state.asState(org.jetbrains.kotlin.scripting.repl.GenericReplCompilerState::class.java)
val (psiFile, errorHolder) = run {
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
val res = checker.check(state, codeLine)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete()
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
is ReplCheckResult.Ok -> {
} // continue
}
}
Pair(compilerState.lastLineState!!.psiFile, compilerState.lastLineState!!.errorHolder)
}
val newDependencies =
ScriptDependenciesProvider.getInstance(checker.environment.project)?.getScriptConfigurationResult(psiFile)?.valueOrNull()
?.legacyDependencies
var classpathAddendum: List<File>? = null
if (compilerState.lastDependencies != newDependencies) {
compilerState.lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> {
return ReplCompileResult.Error(errorHolder.renderMessage())
}
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> {
(analysisResult.scriptDescriptor as? ScriptDescriptor)
?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}")
}
else -> error("Unexpected result ${analysisResult::class.java}")
}
val generationState = GenerationState.Builder(
psiFile.project,
ClassBuilderFactories.BINARIES,
compilerState.analyzerEngine.module,
compilerState.analyzerEngine.trace.bindingContext,
listOf(psiFile),
compilerConfiguration
).build()
generationState.scriptSpecific.earlierScriptsForReplInterpreter = compilerState.history.map { it.item }
generationState.beforeCompile()
KotlinCodegenFacade.generatePackage(
generationState,
psiFile.script!!.containingKtFile.packageFqName,
setOf(psiFile.script!!.containingKtFile),
CompilationErrorHandler.THROW_EXCEPTION
)
val generatedClassname = makeScriptBaseName(codeLine)
compilerState.history.push(LineId(codeLine), scriptDescriptor)
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
return ReplCompileResult.CompiledClasses(
LineId(codeLine),
compilerState.history.map { it.id },
generatedClassname,
classes,
generationState.scriptSpecific.resultFieldName != null,
classpathAddendum ?: emptyList(),
generationState.scriptSpecific.resultType?.let {
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it)
},
null
)
}
}
companion object {
private const val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
}
@@ -1,242 +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.
*/
@file:Suppress("UNUSED_PARAMETER")
package org.jetbrains.kotlin.scripting.repl
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
import org.jetbrains.kotlin.resolve.TopDownAnalysisContext
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.resolve.lazy.data.KtClassLikeInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.*
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.scripting.definitions.ScriptPriorities
class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableAnalyzerState()
val module: ModuleDescriptorImpl
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
init {
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
// to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true
// because no symbol declared in the REPL session can be used from Java)
val container = TopDownAnalyzerFacadeForJVM.createContainer(
environment.project,
emptyList(),
trace,
environment.configuration,
environment::createPackagePartProvider,
{ _, _ -> ScriptMutableDeclarationProviderFactory() }
)
this.module = container.get()
this.scriptDeclarationFactory = container.get()
this.resolveSession = container.get()
this.topDownAnalysisContext = TopDownAnalysisContext(
TopDownAnalysisMode.TopLevelDeclarations, DataFlowInfoFactory.EMPTY, resolveSession.declarationScopeProvider
)
this.topDownAnalyzer = container.get()
}
interface ReplLineAnalysisResult {
val scriptDescriptor: ClassDescriptorWithResolutionScopes?
val diagnostics: Diagnostics
data class Successful(override val scriptDescriptor: ClassDescriptorWithResolutionScopes, override val diagnostics: Diagnostics) :
ReplLineAnalysisResult
data class WithErrors(override val diagnostics: Diagnostics) :
ReplLineAnalysisResult {
override val scriptDescriptor: ClassDescriptorWithResolutionScopes? get() = null
}
}
fun resetToLine(lineId: ILineId): List<ReplCodeLine> = replState.resetToLine(lineId)
fun reset(): List<ReplCodeLine> = replState.reset()
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, emptyList(), codeLine)
}
fun analyzeReplLineWithImportedScripts(psiFile: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, importedScripts, codeLine)
}
private fun doAnalyze(linePsi: KtFile, importedScripts: List<KtFile>, codeLine: ReplCodeLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
replState.submitLine(linePsi, codeLine)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi) + importedScripts)
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
return if (hasErrors) {
replState.lineFailure(linePsi, codeLine)
ReplLineAnalysisResult.WithErrors(diagnostics)
} else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
ReplLineAnalysisResult.Successful(scriptDescriptor, diagnostics)
}
}
private class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
private lateinit var delegateFactory: DeclarationProviderFactory
private lateinit var rootPackageProvider: AdaptablePackageMemberDeclarationProvider
fun setDelegateFactory(delegateFactory: DeclarationProviderFactory) {
this.delegateFactory = delegateFactory
val provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT)!!
try {
rootPackageProvider.addDelegateProvider(provider)
} catch (e: UninitializedPropertyAccessException) {
rootPackageProvider =
AdaptablePackageMemberDeclarationProvider(
provider
)
}
}
override fun getClassMemberDeclarationProvider(classLikeInfo: KtClassLikeInfo): ClassMemberDeclarationProvider {
return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo)
}
override fun getPackageMemberDeclarationProvider(packageFqName: FqName): PackageMemberDeclarationProvider? {
if (packageFqName.isRoot) {
return rootPackageProvider
}
return delegateFactory.getPackageMemberDeclarationProvider(packageFqName)
}
override fun diagnoseMissingPackageFragment(fqName: FqName, file: KtFile?) {
delegateFactory.diagnoseMissingPackageFragment(fqName, file)
}
class AdaptablePackageMemberDeclarationProvider(
private var delegateProvider: PackageMemberDeclarationProvider
) : org.jetbrains.kotlin.scripting.repl.DelegatePackageMemberDeclarationProvider(delegateProvider) {
fun addDelegateProvider(provider: PackageMemberDeclarationProvider) {
delegateProvider = CombinedPackageMemberDeclarationProvider(listOf(provider, delegateProvider))
delegate = delegateProvider
}
}
}
// TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastructure everywhere
// TODO: review its place in the extracted state infrastructure (now the analyzer itself is a part of the state)
class ResettableAnalyzerState {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineId.no)
removed.forEach { submittedLines.remove(it.second.linePsi) }
return removed.map { it.first }
}
fun reset(): List<ReplCodeLine> {
submittedLines.clear()
return successfulLines.reset().map { it.first }
}
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(
ktFile,
successfulLines.lastValue()
)
submittedLines[ktFile] = line
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
return lineInfo(ktFile)?.let { computeFileScopes(it, fileScopeFactory) } ?: fileScopeFactory.createScopesForFile(ktFile)
}
}
}
fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) {
val successfulLine = LineInfo.SuccessfulLine(
ktFile,
successfulLines.lastValue(),
scriptDescriptor
)
submittedLines[ktFile] = successfulLine
successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine)
}
fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) {
submittedLines[ktFile] = LineInfo.FailedLine(
ktFile,
successfulLines.lastValue()
)
}
private fun lineInfo(ktFile: KtFile) = submittedLines[ktFile]
// use sealed?
private sealed class LineInfo {
abstract val linePsi: KtFile
abstract val parentLine: SuccessfulLine?
class SubmittedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
class SuccessfulLine(
override val linePsi: KtFile,
override val parentLine: SuccessfulLine?,
val lineDescriptor: ClassDescriptorWithResolutionScopes
) : LineInfo()
class FailedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
}
private fun computeFileScopes(lineInfo: LineInfo, fileScopeFactory: FileScopeFactory): FileScopes? {
// create scope that wraps previous line lexical scope and adds imports from this line
val lexicalScopeAfterLastLine = lineInfo.parentLine?.lineDescriptor?.scopeForInitializerResolution ?: return null
val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.first { it is ImportingScope } as ImportingScope
val scopesForThisLine = fileScopeFactory.createScopesForFile(lineInfo.linePsi, lastLineImports)
val combinedLexicalScopes = lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope)
return FileScopes(combinedLexicalScopes, scopesForThisLine.importingScope, scopesForThisLine.importForceResolver)
}
}
}
@@ -1,31 +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.scripting.repl
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
import java.io.PrintWriter
import java.io.StringWriter
interface ReplExceptionReporter {
fun report(e: Throwable)
companion object DoNothing : ReplExceptionReporter {
override fun report(e: Throwable) {}
}
}
class IdeReplExceptionReporter(private val replWriter: ReplWriter) : ReplExceptionReporter {
override fun report(e: Throwable) {
val stringWriter = StringWriter()
val printWriter = PrintWriter(stringWriter)
e.printStackTrace(printWriter)
val writerString = stringWriter.toString()
val internalErrorText = if (writerString.isEmpty()) "Unknown error" else writerString
replWriter.sendInternalErrorReport(internalErrorText)
}
}
@@ -1,167 +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.scripting.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.common.repl.replUnescapeLineBreaks
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.scripting.repl.configuration.ConsoleReplConfiguration
import org.jetbrains.kotlin.scripting.repl.configuration.IdeReplConfiguration
import org.jetbrains.kotlin.scripting.repl.configuration.ReplConfiguration
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.Future
class ReplFromTerminal(
disposable: Disposable,
compilerConfiguration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val replInitializer: Future<ReplInterpreter> = Executors.newSingleThreadExecutor().submit(Callable {
ReplInterpreter(disposable, compilerConfiguration, replConfiguration)
})
private val replInterpreter: ReplInterpreter
get() = replInitializer.get()
private val writer get() = replConfiguration.writer
private val messageCollector = compilerConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
private fun doRun() {
try {
with(writer) {
printlnWelcomeMessage(
"Welcome to Kotlin version ${KotlinCompilerVersion.VERSION} " +
"(JRE ${System.getProperty("java.runtime.version")})"
)
printlnWelcomeMessage("Type :help for help, :quit for quit")
}
// Display compiler messages related to configuration and CLI arguments, quit if there are errors
val hasErrors = messageCollector.hasErrors()
(messageCollector as? GroupingMessageCollector)?.flush()
if (hasErrors) return
var next = WhatNextAfterOneLine.READ_LINE
while (true) {
next = one(next)
if (next == WhatNextAfterOneLine.QUIT) {
break
}
}
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
} finally {
try {
replConfiguration.commandReader.flushHistory()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
}
}
}
enum class WhatNextAfterOneLine {
READ_LINE,
INCOMPLETE,
QUIT
}
private fun one(next: WhatNextAfterOneLine): WhatNextAfterOneLine {
var line = replConfiguration.commandReader.readLine(next) ?: return WhatNextAfterOneLine.QUIT
line = line.replUnescapeLineBreaks()
if (line.startsWith(":") && (line.length == 1 || line[1] != ':')) {
val notQuit = oneCommand(line.substring(1))
return if (notQuit) WhatNextAfterOneLine.READ_LINE else WhatNextAfterOneLine.QUIT
}
val lineResult = eval(line)
return if (lineResult is ReplEvalResult.Incomplete) {
WhatNextAfterOneLine.INCOMPLETE
} else {
WhatNextAfterOneLine.READ_LINE
}
}
private fun eval(line: String): ReplEvalResult {
val evalResult = replInterpreter.eval(line)
when (evalResult) {
is ReplEvalResult.ValueResult, is ReplEvalResult.UnitResult -> {
writer.notifyCommandSuccess()
if (evalResult is ReplEvalResult.ValueResult) {
writer.outputCommandResult(evalResult.toString())
}
}
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
}
return evalResult
}
@Throws(Exception::class)
private fun oneCommand(command: String): Boolean {
val split = splitCommand(command)
if (split.isNotEmpty() && command == "help") {
writer.printlnHelpMessage(
"Available commands:\n" +
":help show this help\n" +
":quit exit the interpreter\n" +
":dump bytecode dump classes to terminal\n" +
":load <file> load script from specified file"
)
return true
} else if (split.size >= 2 && split[0] == "dump" && split[1] == "bytecode") {
replInterpreter.dumpClasses(PrintWriter(System.out))
return true
} else if (split.isNotEmpty() && split[0] == "quit") {
return false
} else if (split.size >= 2 && split[0] == "load") {
val fileName = split[1]
try {
val scriptText = FileUtil.loadFile(File(fileName))
eval(scriptText)
} catch (e: IOException) {
writer.outputCompileError("Can not load script: ${e.message}")
}
return true
} else {
writer.printlnHelpMessage("Unknown command\n" + "Type :help for help")
return true
}
}
companion object {
private fun splitCommand(command: String): List<String> {
return listOf(*command.split(" ".toRegex()).dropLastWhile(String::isEmpty).toTypedArray())
}
fun run(disposable: Disposable, configuration: CompilerConfiguration) {
val replIdeMode = System.getProperty("kotlin.repl.ideMode") == "true"
val replConfiguration = if (replIdeMode) IdeReplConfiguration() else ConsoleReplConfiguration()
return try {
ReplFromTerminal(disposable, configuration, replConfiguration).doRun()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
}
}
}
}
@@ -1,122 +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.scripting.repl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.repl.configuration.ReplConfiguration
import java.io.PrintWriter
import java.net.URLClassLoader
import java.util.concurrent.atomic.AtomicInteger
class ReplInterpreter(
disposable: Disposable,
private val configuration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val lineNumber = AtomicInteger()
private val previousIncompleteLines = arrayListOf<String>()
private val classpathRoots = configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNull { root ->
when (root) {
is JvmModulePathRoot -> root.file // TODO: only add required modules
is JvmClasspathRoot -> root.file
else -> null
}
}
private val classLoader = ReplClassLoader(URLClassLoader(classpathRoots.map { it.toURI().toURL() }.toTypedArray(), null))
private val messageCollector = object : MessageCollector {
private var hasErrors = false
private val messageRenderer = MessageRenderer.WITHOUT_PATHS
override fun clear() {
hasErrors = false
}
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
val msg = messageRenderer.render(severity, message, location).trimEnd()
with(replConfiguration.writer) {
when (severity) {
CompilerMessageSeverity.EXCEPTION -> sendInternalErrorReport(msg)
CompilerMessageSeverity.ERROR -> outputCompileError(msg)
CompilerMessageSeverity.STRONG_WARNING -> {
} // TODO consider reporting this and two below
CompilerMessageSeverity.WARNING -> {
}
CompilerMessageSeverity.INFO -> {
}
else -> {
}
}
}
}
override fun hasErrors(): Boolean = hasErrors
}
// TODO: add script definition with project-based resolving for IDEA repl
private val scriptCompiler: ReplCompiler by lazy {
GenericReplCompiler(
disposable,
REPL_LINE_AS_SCRIPT_DEFINITION,
configuration,
messageCollector
)
}
private val scriptEvaluator: ReplFullEvaluator by lazy {
GenericReplCompilingEvaluator(scriptCompiler, classpathRoots, classLoader, null, ReplRepeatingMode.REPEAT_ANY_PREVIOUS)
}
private val evalState by lazy { scriptEvaluator.createState() }
fun eval(line: String): ReplEvalResult {
val fullText = (previousIncompleteLines + line).joinToString(separator = "\n")
try {
val evalRes = scriptEvaluator.compileAndEval(
evalState,
ReplCodeLine(lineNumber.getAndIncrement(), 0, fullText),
null,
object : InvokeWrapper {
override fun <T> invoke(body: () -> T): T = replConfiguration.executionInterceptor.execute(body)
}
)
when {
evalRes !is ReplEvalResult.Incomplete -> previousIncompleteLines.clear()
replConfiguration.allowIncompleteLines -> previousIncompleteLines.add(line)
else -> return ReplEvalResult.Error.CompileTime("incomplete code")
}
return evalRes
} catch (e: Throwable) {
val writer = PrintWriter(System.err)
classLoader.dumpClasses(writer)
writer.flush()
throw e
}
}
fun dumpClasses(out: PrintWriter) {
classLoader.dumpClasses(out)
}
companion object {
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
override val name = "Kotlin REPL"
}
}
}
@@ -1,28 +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.scripting.repl.configuration
import org.jetbrains.kotlin.scripting.repl.ReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.messages.ConsoleDiagnosticMessageHolder
import org.jetbrains.kotlin.scripting.repl.reader.ConsoleReplCommandReader
import org.jetbrains.kotlin.scripting.repl.writer.ConsoleReplWriter
class ConsoleReplConfiguration : ReplConfiguration {
override val writer = ConsoleReplWriter()
override val exceptionReporter
get() = ReplExceptionReporter
override val commandReader = ConsoleReplCommandReader()
override val allowIncompleteLines: Boolean
get() = true
override val executionInterceptor
get() = SnippetExecutionInterceptor
override fun createDiagnosticHolder() = ConsoleDiagnosticMessageHolder()
}
@@ -1,57 +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.scripting.repl.configuration
import org.jetbrains.kotlin.scripting.repl.IdeReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.ReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.messages.IdeDiagnosticMessageHolder
import org.jetbrains.kotlin.scripting.repl.reader.IdeReplCommandReader
import org.jetbrains.kotlin.scripting.repl.reader.ReplCommandReader
import org.jetbrains.kotlin.scripting.repl.reader.ReplSystemInWrapper
import org.jetbrains.kotlin.scripting.repl.writer.IdeSystemOutWrapperReplWriter
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
class IdeReplConfiguration : ReplConfiguration {
override val allowIncompleteLines: Boolean
get() = false
override val executionInterceptor: SnippetExecutionInterceptor = object :
SnippetExecutionInterceptor {
override fun <T> execute(block: () -> T): T {
try {
sinWrapper.isReplScriptExecuting = true
return block()
} finally {
sinWrapper.isReplScriptExecuting = false
}
}
}
override fun createDiagnosticHolder() = IdeDiagnosticMessageHolder()
override val writer: ReplWriter
override val exceptionReporter: ReplExceptionReporter
override val commandReader: ReplCommandReader
val sinWrapper: ReplSystemInWrapper
init {
// wrapper for `out` is required to escape every input in [ideMode];
// if [ideMode == false] then just redirects all input to [System.out]
// if user calls [System.setOut(...)] then undefined behaviour
val soutWrapper = IdeSystemOutWrapperReplWriter(System.out)
System.setOut(soutWrapper)
// wrapper for `in` is required to give user possibility of calling
// [readLine] from ide-console repl
sinWrapper = ReplSystemInWrapper(System.`in`, soutWrapper)
System.setIn(sinWrapper)
writer = soutWrapper
exceptionReporter = IdeReplExceptionReporter(writer)
commandReader = IdeReplCommandReader()
}
}
@@ -1,21 +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.scripting.repl.configuration
import org.jetbrains.kotlin.scripting.repl.ReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.messages.DiagnosticMessageHolder
import org.jetbrains.kotlin.scripting.repl.reader.ReplCommandReader
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
interface ReplConfiguration {
val writer: ReplWriter
val exceptionReporter: ReplExceptionReporter
val commandReader: ReplCommandReader
val allowIncompleteLines: Boolean
val executionInterceptor: SnippetExecutionInterceptor
fun createDiagnosticHolder(): DiagnosticMessageHolder
}
@@ -1,14 +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.scripting.repl.configuration
interface SnippetExecutionInterceptor {
fun <T> execute(block: () -> T): T
companion object Plain : SnippetExecutionInterceptor {
override fun <T> execute(block: () -> T) = block()
}
}
@@ -1,27 +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.scripting.repl.messages
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import java.io.ByteArrayOutputStream
import java.io.PrintStream
class ConsoleDiagnosticMessageHolder : MessageCollectorBasedReporter, DiagnosticMessageHolder {
private val outputStream = ByteArrayOutputStream()
override val messageCollector: GroupingMessageCollector = GroupingMessageCollector(
PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.WITHOUT_PATHS, false),
false
)
override fun renderMessage(): String {
messageCollector.flush()
return outputStream.toString("UTF-8")
}
}
@@ -1,23 +0,0 @@
/*
* 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.scripting.repl.messages
import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter
interface DiagnosticMessageHolder : DiagnosticMessageReporter {
fun renderMessage(): String
}
@@ -1,46 +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.scripting.repl.messages
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.w3c.dom.ls.DOMImplementationLS
import javax.xml.parsers.DocumentBuilderFactory
class IdeDiagnosticMessageHolder : DiagnosticMessageHolder {
private val diagnostics = arrayListOf<Pair<Diagnostic, String>>()
override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) {
diagnostics.add(Pair(diagnostic, render))
}
override fun renderMessage(): String {
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val errorReport = docBuilder.newDocument()
val rootElement = errorReport.createElement("report")
errorReport.appendChild(rootElement)
for ((diagnostic, message) in diagnostics) {
val errorRange = DiagnosticUtils.firstRange(diagnostic.textRanges)
val reportEntry = errorReport.createElement("reportEntry")
reportEntry.setAttribute("severity", diagnostic.severity.toString())
reportEntry.setAttribute("rangeStart", errorRange.startOffset.toString())
reportEntry.setAttribute("rangeEnd", errorRange.endOffset.toString())
reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(message)))
rootElement.appendChild(reportEntry)
}
val domImplementation = errorReport.implementation as DOMImplementationLS
val lsSerializer = domImplementation.createLSSerializer()
return lsSerializer.writeToString(errorReport)
}
}
@@ -1,48 +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.scripting.repl.reader
import org.jetbrains.kotlin.scripting.repl.ReplFromTerminal
import org.jline.reader.EndOfFileException
import org.jline.reader.LineReader
import org.jline.reader.LineReaderBuilder
import org.jline.reader.UserInterruptException
import org.jline.terminal.TerminalBuilder
import java.io.File
import java.util.logging.Level
import java.util.logging.Logger
class ConsoleReplCommandReader : ReplCommandReader {
private val lineReader = LineReaderBuilder.builder()
.appName("kotlin")
.terminal(TerminalBuilder.terminal())
.variable(LineReader.HISTORY_FILE, File(File(System.getProperty("user.home")), ".kotlinc_history").absolutePath)
.build()
.apply {
setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION)
}
override fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine): String? {
val prompt = if (next == ReplFromTerminal.WhatNextAfterOneLine.INCOMPLETE) "... " else ">>> "
return try {
lineReader.readLine(prompt)
} catch (e: UserInterruptException) {
println("<interrupted>")
System.out.flush()
""
} catch (e: EndOfFileException) {
null
}
}
override fun flushHistory() = lineReader.history.save()
private companion object {
init {
Logger.getLogger("org.jline").level = Level.OFF
}
}
}
@@ -1,13 +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.scripting.repl.reader
import org.jetbrains.kotlin.scripting.repl.ReplFromTerminal
class IdeReplCommandReader : ReplCommandReader {
override fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine) = readLine()
override fun flushHistory() = Unit
}
@@ -1,13 +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.scripting.repl.reader
import org.jetbrains.kotlin.scripting.repl.ReplFromTerminal
interface ReplCommandReader {
fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine): String?
fun flushHistory()
}
@@ -1,95 +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.scripting.repl.reader
import org.jetbrains.kotlin.cli.common.repl.replUnescapeLineBreaks
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
import org.w3c.dom.Element
import org.xml.sax.InputSource
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import javax.xml.parsers.DocumentBuilderFactory
class ReplSystemInWrapper(
private val stdin: InputStream,
private val replWriter: ReplWriter
) : InputStream() {
private var isXmlIncomplete = true
private var isLastByteProcessed = false
private var isReadLineStartSent = false
private var byteBuilder = ByteArrayOutputStream()
private var curBytePos = 0
private var inputByteArray = byteArrayOf()
private val isAtBufferEnd: Boolean
get() = curBytePos == inputByteArray.size
@Volatile
var isReplScriptExecuting = false
override fun read(): Int {
if (isLastByteProcessed) {
if (isReplScriptExecuting) {
isReadLineStartSent = false
replWriter.notifyReadLineEnd()
}
isLastByteProcessed = false
return -1
}
while (isXmlIncomplete) {
if (!isReadLineStartSent && isReplScriptExecuting) {
replWriter.notifyReadLineStart()
isReadLineStartSent = true
}
byteBuilder.write(stdin.read())
if (byteBuilder.toString().endsWith('\n')) {
isXmlIncomplete = false
isLastByteProcessed = false
inputByteArray = parseInput().toByteArray()
}
}
val nextByte = inputByteArray[curBytePos++].toInt()
resetBufferIfNeeded()
return nextByte
}
private fun parseInput(): String {
val xmlInput = byteBuilder.toString()
val unescapedXml = parseXml(xmlInput)
return if (isReplScriptExecuting)
unescapedXml.replUnescapeLineBreaks()
else
unescapedXml
}
private fun resetBufferIfNeeded() {
if (isAtBufferEnd) {
isXmlIncomplete = true
byteBuilder = ByteArrayOutputStream()
curBytePos = 0
isLastByteProcessed = true
}
}
}
private fun parseXml(inputMessage: String): String {
fun strToSource(s: String) = InputSource(ByteArrayInputStream(s.toByteArray()))
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val input = docBuilder.parse(strToSource(inputMessage))
val root = input.firstChild as Element
return root.textContent
}
@@ -1,20 +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.scripting.repl.writer
class ConsoleReplWriter : ReplWriter {
override fun printlnWelcomeMessage(x: String) = println(x)
override fun printlnHelpMessage(x: String) = println(x)
override fun outputCompileError(x: String) = println(x)
override fun outputCommandResult(x: String) = println(x)
override fun outputRuntimeError(x: String) = println(x)
override fun notifyReadLineStart() {}
override fun notifyReadLineEnd() {}
override fun notifyIncomplete() {}
override fun notifyCommandSuccess() {}
override fun sendInternalErrorReport(x: String) {}
}
@@ -1,43 +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.scripting.repl.writer
import org.jetbrains.kotlin.cli.common.repl.replAddLineBreak
import org.jetbrains.kotlin.cli.common.repl.replOutputAsXml
import org.jetbrains.kotlin.utils.repl.ReplEscapeType
import org.jetbrains.kotlin.utils.repl.ReplEscapeType.*
import java.io.PrintStream
class IdeSystemOutWrapperReplWriter(standardOut: PrintStream) : PrintStream(standardOut, true),
ReplWriter {
override fun print(x: Boolean) = printWithEscaping(x.toString())
override fun print(x: Char) = printWithEscaping(x.toString())
override fun print(x: Int) = printWithEscaping(x.toString())
override fun print(x: Long) = printWithEscaping(x.toString())
override fun print(x: Float) = printWithEscaping(x.toString())
override fun print(x: Double) = printWithEscaping(x.toString())
override fun print(x: String) = printWithEscaping(x)
override fun print(x: Any?) = printWithEscaping(x.toString())
private fun printlnWithEscaping(text: String, escapeType: ReplEscapeType = USER_OUTPUT) {
printWithEscaping("$text\n", escapeType)
}
private fun printWithEscaping(text: String, escapeType: ReplEscapeType = USER_OUTPUT) {
super.print(text.replOutputAsXml(escapeType).replAddLineBreak())
}
override fun printlnWelcomeMessage(x: String) = printlnWithEscaping(x, INITIAL_PROMPT)
override fun printlnHelpMessage(x: String) = printlnWithEscaping(x, HELP_PROMPT)
override fun outputCommandResult(x: String) = printlnWithEscaping(x, REPL_RESULT)
override fun notifyReadLineStart() = printlnWithEscaping("", READLINE_START)
override fun notifyReadLineEnd() = printlnWithEscaping("", READLINE_END)
override fun notifyCommandSuccess() = printlnWithEscaping("", SUCCESS)
override fun notifyIncomplete() = printlnWithEscaping("", REPL_INCOMPLETE)
override fun outputCompileError(x: String) = printlnWithEscaping(x, COMPILE_ERROR)
override fun outputRuntimeError(x: String) = printlnWithEscaping(x, RUNTIME_ERROR)
override fun sendInternalErrorReport(x: String) = printlnWithEscaping(x, INTERNAL_ERROR)
}
@@ -1,19 +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.scripting.repl.writer
interface ReplWriter {
fun printlnWelcomeMessage(x: String)
fun printlnHelpMessage(x: String)
fun outputCommandResult(x: String)
fun notifyReadLineStart()
fun notifyReadLineEnd()
fun notifyIncomplete()
fun notifyCommandSuccess()
fun outputCompileError(x: String)
fun outputRuntimeError(x: String)
fun sendInternalErrorReport(x: String)
}