From b36d1be5f858fe09f673c7568fa97d3028ec5c6e Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Fri, 13 May 2022 20:44:44 +0200 Subject: [PATCH] Scripting: switch legacy CLI REPL to the new infrastructure --- .../repl/KotlinJsr223JvmScriptEngineBase.kt | 2 +- .../kotlin/cli/common/repl/ReplApi.kt | 2 +- compiler/testData/repl/evaluationErrors.repl | 2 + .../repl/exceptionInValueToString.repl | 1 + .../repl/exceptionInVariableInitializer.repl | 6 +- compiler/testData/repl/syntaxErrors.repl | 3 - .../repl/AbstractReplInterpreterTest.kt | 12 +- .../script/experimental/api/errorHandling.kt | 4 + .../experimental/jvmhost/test/ReplTest.kt | 6 +- .../jvmhost/test/ScriptingHostTest.kt | 2 +- .../experimental/jvm/BasicJvmReplEvaluator.kt | 9 +- .../jvm/util/runtimeExceptionReporting.kt | 9 + .../plugin/JvmCliReplShellExtension.kt | 4 +- .../plugin/repl/GenericReplCompiler.kt | 6 +- .../compiler/plugin/repl/ReplFromTerminal.kt | 12 +- .../compiler/plugin/repl/ReplInterpreter.kt | 167 ++++++++++++++---- 16 files changed, 185 insertions(+), 62 deletions(-) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt index 93ccec6f34e..c6ecc8a80dc 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/KotlinJsr223JvmScriptEngineBase.kt @@ -98,7 +98,7 @@ abstract class KotlinJsr223JvmScriptEngineBase(protected val myFactory: ScriptEn is ReplEvalResult.Error -> when { result is ReplEvalResult.Error.Runtime && result.cause != null -> - throw ScriptException(result.cause) + throw ScriptException((result.cause as? java.lang.Exception) ?: RuntimeException(result.cause)) result is ReplEvalResult.Error.CompileTime && result.location != null -> throw ScriptException(result.message, result.location.path, result.location.line, result.location.column) else -> throw ScriptException(result.message) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt index c5713ace2d2..5748d958571 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/repl/ReplApi.kt @@ -148,7 +148,7 @@ sealed class ReplEvalResult : Serializable { } sealed class Error(val message: String) : ReplEvalResult() { - class Runtime(message: String, val cause: Exception? = null) : Error(message) { + class Runtime(message: String, val cause: Throwable? = null) : Error(message) { companion object { private val serialVersionUID: Long = 1L } } diff --git a/compiler/testData/repl/evaluationErrors.repl b/compiler/testData/repl/evaluationErrors.repl index 643e06a9a2b..539c81cc762 100644 --- a/compiler/testData/repl/evaluationErrors.repl +++ b/compiler/testData/repl/evaluationErrors.repl @@ -1,5 +1,6 @@ >>> throw Exception("hi there") java.lang.Exception: hi there + at Line_0.(Line_0.kts:1) >>> fun foo() = 2 >>> foo() 2 @@ -7,3 +8,4 @@ java.lang.Exception: hi there >>> bar() java.lang.AssertionError at Line_3.bar(Line_3.kts:1) + at Line_4.(Line_4.kts:1) diff --git a/compiler/testData/repl/exceptionInValueToString.repl b/compiler/testData/repl/exceptionInValueToString.repl index 6b58a56a844..38da1ef3c91 100644 --- a/compiler/testData/repl/exceptionInValueToString.repl +++ b/compiler/testData/repl/exceptionInValueToString.repl @@ -3,3 +3,4 @@ java.lang.IllegalStateException: message at Line_0$B.foo(Line_0.kts:1) at Line_0$B.toString(Line_0.kts:1) + at Line_1.(Line_1.kts:1) diff --git a/compiler/testData/repl/exceptionInVariableInitializer.repl b/compiler/testData/repl/exceptionInVariableInitializer.repl index 1315e78ee9c..9a4842a79a5 100644 --- a/compiler/testData/repl/exceptionInVariableInitializer.repl +++ b/compiler/testData/repl/exceptionInVariableInitializer.repl @@ -2,7 +2,7 @@ java.lang.RuntimeException at Line_0$x$1.invoke(Line_0.kts:1) at Line_0$x$1.invoke(Line_0.kts:1) + at Line_0.(Line_0.kts:1) >>> x -error: unresolved reference: x -x -^ +java.lang.NullPointerException + at Line_1.(Line_1.kts:1) diff --git a/compiler/testData/repl/syntaxErrors.repl b/compiler/testData/repl/syntaxErrors.repl index 507d83d928a..cba37d25db0 100644 --- a/compiler/testData/repl/syntaxErrors.repl +++ b/compiler/testData/repl/syntaxErrors.repl @@ -2,9 +2,6 @@ error: expecting an element )( ^ -error: expecting an expression -)( - ^ >>> fun foo() = 98 >>> foo() 98 diff --git a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt index a30fb5109ce..003c01f0385 100644 --- a/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/repl/AbstractReplInterpreterTest.kt @@ -17,7 +17,10 @@ package org.jetbrains.kotlin.repl import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.script.loadScriptingPlugin import org.jetbrains.kotlin.scripting.compiler.plugin.repl.ReplInterpreter import org.jetbrains.kotlin.scripting.compiler.plugin.repl.configuration.ConsoleReplConfiguration @@ -86,8 +89,14 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() { protected fun doTest(path: String) { val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK) loadScriptingPlugin(configuration) + val projectEnvironment = + KotlinCoreEnvironment.ProjectEnvironment( + testRootDisposable, + KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForTests(testRootDisposable, configuration), + configuration + ) val repl = ReplInterpreter( - testRootDisposable, configuration, + projectEnvironment, configuration, ConsoleReplConfiguration() ) @@ -100,6 +109,7 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() { val actual = when (lineResult) { is ReplEvalResult.ValueResult -> lineResult.value.toString() + is ReplEvalResult.Error.CompileTime -> MessageRenderer.WITHOUT_PATHS.render(CompilerMessageSeverity.ERROR, lineResult.message, lineResult.location) is ReplEvalResult.Error -> lineResult.message is ReplEvalResult.Incomplete -> INCOMPLETE_LINE_MESSAGE is ReplEvalResult.UnitResult -> "" diff --git a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt index 651997c1b21..6579299d946 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/api/errorHandling.kt @@ -98,6 +98,10 @@ data class ScriptDiagnostic( } } +fun ScriptDiagnostic.isError() = + (severity == ScriptDiagnostic.Severity.ERROR || severity == ScriptDiagnostic.Severity.FATAL) && + (code == ScriptDiagnostic.unspecifiedException || code == ScriptDiagnostic.unspecifiedError) + /** * The result wrapper with diagnostics container */ diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt index adfb9445cc2..5a827359036 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ReplTest.kt @@ -456,8 +456,10 @@ class ReplTest : TestCase() { is ResultValue.Unit -> Assert.assertNull("#$index: Expected $expectedVal, got Unit", expectedVal) is ResultValue.Error -> Assert.assertTrue( "#$index: Expected $expectedVal, got Error: ${actualVal.error}", - expectedVal is Throwable && expectedVal.message == actualVal.error.message - && expectedVal.cause?.message == actualVal.error.cause?.message + ((expectedVal as? Throwable) ?: (expectedVal as? ResultValue.Error)?.error).let { + it != null && it.message == actualVal.error.message + && it.cause?.message == actualVal.error.cause?.message + } ) is ResultValue.NotEvaluated -> Assert.assertEquals( "#$index: Expected $expectedVal, got NotEvaluated", diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index 1a877eb0450..ba7cc26a11a 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -298,7 +298,7 @@ class ScriptingHostTest : TestCase() { "println(\"$greeting\")".toScriptSource(), {}, { - scriptExecutionWrapper { + scriptExecutionWrapper { val outStream = ByteArrayOutputStream() val prevOut = System.out System.setOut(PrintStream(outStream)) diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt index a44c310f7aa..fb3e9cb6c29 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmReplEvaluator.kt @@ -57,8 +57,13 @@ class BasicJvmReplEvaluator(val scriptEvaluator: ScriptEvaluator = BasicJvmScrip } KJvmEvaluatedSnippet(snippetVal, currentConfiguration, retVal) } - else -> - KJvmEvaluatedSnippet(snippetVal, currentConfiguration, ResultValue.NotEvaluated) + else -> { + val firstError = evalRes.reports.find { it.isError() } + KJvmEvaluatedSnippet( + snippetVal, currentConfiguration, + firstError?.exception?.let { ResultValue.Error(it) } ?: ResultValue.NotEvaluated + ) + } } val newNode = lastEvaluatedSnippet.add(newEvalRes) diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/runtimeExceptionReporting.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/runtimeExceptionReporting.kt index 505e25f7826..0a60fe1adc3 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/runtimeExceptionReporting.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/runtimeExceptionReporting.kt @@ -5,6 +5,7 @@ package kotlin.script.experimental.jvm.util +import java.io.ByteArrayOutputStream import java.io.PrintStream import kotlin.script.experimental.api.ResultValue @@ -42,3 +43,11 @@ fun ResultValue.Error.renderError(stream: PrintStream) { } } } + +fun ResultValue.Error.renderError(): String = + ByteArrayOutputStream().use { os -> + val ps = PrintStream(os) + renderError(ps) + ps.flush() + os.toString() + } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliReplShellExtension.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliReplShellExtension.kt index e614cf81c7e..fb574a08408 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliReplShellExtension.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliReplShellExtension.kt @@ -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 } } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt index 48791f97037..29abb967a6e 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/GenericReplCompiler.kt @@ -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}") } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplFromTerminal.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplFromTerminal.kt index cf5f9faaf15..ce6cebc8053 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplFromTerminal.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplFromTerminal.kt @@ -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 = 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 diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt index 862f750463f..7f54cf02e9b 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/repl/ReplInterpreter.kt @@ -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 { replConfiguration.executionInterceptor.execute(it) } + constructorArgs(emptyArray()) + } + + replState = JvmReplCompilerState( + { + ReplCompilationState( + context, + analyzerInit = { context1, resolutionFilter -> + ReplCodeAnalyzerBase(context1.environment, implicitsResolutionFilter = resolutionFilter) + }, + implicitsResolutionFilter = ReplImplicitsExtensionsResolutionFilter() + ) + } + ) + } + + private val compiler = KJvmReplCompilerBase(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() 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 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" - } - } }