Scripting: switch legacy CLI REPL to the new infrastructure

This commit is contained in:
Ilya Chernikov
2022-05-13 20:44:44 +02:00
committed by teamcity
parent 6784cb63aa
commit b36d1be5f8
16 changed files with 185 additions and 62 deletions
@@ -10,7 +10,6 @@ import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.extensions.ShellExtension
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplFromTerminal
class JvmCliReplShellExtension : ShellExtension {
@@ -22,8 +21,7 @@ class JvmCliReplShellExtension : ShellExtension {
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ExitCode {
configuration.put(JVMConfigurationKeys.IR, false)
ReplFromTerminal.run(projectEnvironment.parentDisposable, configuration)
ReplFromTerminal.run(projectEnvironment, configuration)
return ExitCode.OK
}
}
@@ -81,13 +81,13 @@ open class GenericReplCompiler(
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder, renderDiagnosticName = false)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> {
return ReplCompileResult.Error(errorHolder.renderMessage())
}
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.Successful -> {
(analysisResult.scriptDescriptor as? ScriptDescriptor)
?: error("Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}")
}
is ReplCodeAnalyzerBase.ReplLineAnalysisResult.WithErrors -> {
return ReplCompileResult.Error(errorHolder.renderMessage())
}
else -> error("Unexpected result ${analysisResult::class.java}")
}
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
import com.intellij.openapi.Disposable
import com.intellij.core.JavaCoreProjectEnvironment
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
@@ -24,12 +24,12 @@ import java.util.concurrent.Executors
import java.util.concurrent.Future
class ReplFromTerminal(
disposable: Disposable,
projectEnvironment: JavaCoreProjectEnvironment,
compilerConfiguration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val replInitializer: Future<ReplInterpreter> = Executors.newSingleThreadExecutor().submit(Callable {
ReplInterpreter(disposable, compilerConfiguration, replConfiguration)
ReplInterpreter(projectEnvironment, compilerConfiguration, replConfiguration)
})
private val replInterpreter: ReplInterpreter
@@ -109,7 +109,7 @@ class ReplFromTerminal(
}
}
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Error.CompileTime -> writer.outputCompileError(evalResult.message)
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
is ReplEvalResult.HistoryMismatch -> {} // assuming handled elsewhere
}
@@ -153,11 +153,11 @@ class ReplFromTerminal(
return listOf(*command.split(" ".toRegex()).dropLastWhile(String::isEmpty).toTypedArray())
}
fun run(disposable: Disposable, configuration: CompilerConfiguration) {
fun run(projectEnvironment: JavaCoreProjectEnvironment, configuration: CompilerConfiguration) {
val replIdeMode = System.getProperty("kotlin.repl.ideMode") == "true"
val replConfiguration = if (replIdeMode) IdeReplConfiguration() else ConsoleReplConfiguration()
return try {
ReplFromTerminal(disposable, configuration, replConfiguration).doRun()
ReplFromTerminal(projectEnvironment, configuration, replConfiguration).doRun()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
@@ -5,29 +5,98 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.repl
import com.intellij.openapi.Disposable
import com.intellij.core.JavaCoreProjectEnvironment
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
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.common.messages.*
import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.messageCollector
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.compiler.plugin.impl.KJvmReplCompilerBase
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ReplCompilationState
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptDiagnosticsMessageCollector
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.createCompilationContextFromEnvironment
import org.jetbrains.kotlin.scripting.compiler.plugin.repl.configuration.ReplConfiguration
import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import java.io.PrintWriter
import java.net.URLClassLoader
import java.util.concurrent.atomic.AtomicInteger
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.impl.internalScriptingRunSuspend
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.util.renderError
class ReplInterpreter(
disposable: Disposable,
projectEnvironment: JavaCoreProjectEnvironment,
private val configuration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val hostConfiguration: ScriptingHostConfiguration
private val compilationConfiguration: ScriptCompilationConfiguration
private val evaluationConfiguration: ScriptEvaluationConfiguration
private val replState: JvmReplCompilerState<*>
init {
val scriptDefinition: ScriptDefinition? =
ScriptDefinitionProvider.getInstance(projectEnvironment.project)?.getDefaultDefinition()
hostConfiguration = scriptDefinition?.hostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
val environment = (projectEnvironment as? KotlinCoreEnvironment.ProjectEnvironment)?.let {
KotlinCoreEnvironment.createForProduction(it, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
?: KotlinCoreEnvironment.createForProduction(
projectEnvironment.parentDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
val context =
createCompilationContextFromEnvironment(
scriptDefinition?.compilationConfiguration.with {
hostConfiguration(this@ReplInterpreter.hostConfiguration)
},
environment,
ScriptDiagnosticsMessageCollector(environment.messageCollector)
)
compilationConfiguration = context.baseScriptCompilationConfiguration
evaluationConfiguration = scriptDefinition?.evaluationConfiguration.with {
hostConfiguration(this@ReplInterpreter.hostConfiguration)
scriptExecutionWrapper<Any> { replConfiguration.executionInterceptor.execute(it) }
constructorArgs(emptyArray<String>())
}
replState = JvmReplCompilerState(
{
ReplCompilationState(
context,
analyzerInit = { context1, resolutionFilter ->
ReplCodeAnalyzerBase(context1.environment, implicitsResolutionFilter = resolutionFilter)
},
implicitsResolutionFilter = ReplImplicitsExtensionsResolutionFilter()
)
}
)
}
private val compiler = KJvmReplCompilerBase<ReplCodeAnalyzerBase>(hostConfiguration, replState)
private val evaluator = BasicJvmReplEvaluator()
private val lineNumber = AtomicInteger()
private fun nextSnippet(code: String) =
code.toScriptSource(
"Line_${lineNumber.getAndIncrement()}.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
)
private val previousIncompleteLines = arrayListOf<String>()
private val classpathRoots = configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNull { root ->
@@ -75,33 +144,65 @@ class ReplInterpreter(
override fun hasErrors(): Boolean = hasErrors
}
// TODO: add script definition with project-based resolving for IDEA repl
private val scriptCompiler: ReplCompilerWithoutCheck 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)
val snippet = nextSnippet(fullText)
fun SourceCode.Location.toCompilerMessageLocation() =
CompilerMessageLocation.create(
snippet.name,
start.line, start.col,
snippet.text.lines().getOrNull(start.line - 1)
)
fun ResultWithDiagnostics<*>.reportToMessageCollector() {
for (it in reports) {
val diagnosticSeverity = when (it.severity) {
ScriptDiagnostic.Severity.ERROR -> CompilerMessageSeverity.ERROR
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
ScriptDiagnostic.Severity.WARNING -> CompilerMessageSeverity.WARNING
else -> continue
}
messageCollector.report(diagnosticSeverity, it.message, it.location?.toCompilerMessageLocation())
}
)
}
@Suppress("DEPRECATION_ERROR")
val evalRes: ReplEvalResult = internalScriptingRunSuspend {
when (val compileResult = compiler.compile(listOf(snippet), compilationConfiguration)) {
is ResultWithDiagnostics.Failure -> {
val incompleteReport = compileResult.reports.find { it.code == ScriptDiagnostic.incompleteCode }
val firstError = compileResult.reports.find { it.isError() }
if (incompleteReport != null)
ReplEvalResult.Incomplete(incompleteReport.message)
else {
compileResult.reportToMessageCollector()
ReplEvalResult.Error.CompileTime(firstError?.message ?: "", firstError?.location?.toCompilerMessageLocation())
}
}
is ResultWithDiagnostics.Success -> {
compileResult.reportToMessageCollector()
val evalResult = evaluator.eval(compileResult.value, evaluationConfiguration)
when (evalResult) {
is ResultWithDiagnostics.Success -> {
when (val evalValue = evalResult.value.get().result) {
is ResultValue.Unit -> ReplEvalResult.UnitResult()
is ResultValue.Value -> ReplEvalResult.ValueResult(evalValue.name, evalValue.value, evalValue.type)
is ResultValue.Error -> ReplEvalResult.Error.Runtime(evalValue.renderError())
else -> ReplEvalResult.Error.Runtime("Error: snippet is not evaluated")
}
}
else -> {
val firstError = evalResult.reports.find { it.isError() }
evalResult.reportToMessageCollector()
ReplEvalResult.Error.Runtime(firstError?.message ?: "", firstError?.exception)
}
}
}
}
}
when {
evalRes !is ReplEvalResult.Incomplete -> previousIncompleteLines.clear()
@@ -120,10 +221,4 @@ class ReplInterpreter(
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"
}
}
}