Scripting: switch legacy CLI REPL to the new infrastructure
This commit is contained in:
+1
-3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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}")
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
+131
-36
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user