Implement "legacy" REPL wrappers on top of the "new" scripting infrastructure

This commit is contained in:
Ilya Chernikov
2019-04-30 15:43:33 +02:00
parent 010ae5326d
commit 46915df56f
18 changed files with 631 additions and 127 deletions
@@ -10,7 +10,10 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
import org.jetbrains.kotlin.cli.common.repl.LineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.scriptResultFieldName
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
@@ -32,7 +35,7 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
return ReplCompilationState(context)
}
fun checkSyntax(
override fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
@@ -44,13 +47,18 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
override val messageCollector = messageCollector
}
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder)
if (syntaxErrorReport.isHasErrors) failure(messageCollector) else true.asSuccess()
when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> false.asSuccess(messageCollector.diagnostics)
syntaxErrorReport.isHasErrors -> failure(messageCollector)
else -> true.asSuccess()
}
}
fun compileReplSnippet(
override fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
// TODO: replace history with some interface based on CompiledScript
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript<*>> =
withMessageCollector(snippet) { messageCollector ->
@@ -108,7 +116,6 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
CompilationErrorHandler.THROW_EXCEPTION
)
val generatedClassname = makeScriptBaseName(codeLine)
history.push(LineId(codeLine), scriptDescriptor)
val compiledScript =
@@ -133,4 +140,3 @@ internal class ReplCompilationState(val context: SharedScriptCompilationContext)
internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine =
ReplCodeLine(id.no, id.generation, code.text)
@@ -62,10 +62,7 @@ internal fun createSharedCompilationContext(
scriptCompilationConfiguration, hostConfiguration, scriptCompilationState, messageCollector, ignoredOptionsReportingState
)
val environment =
if (System.getProperty("idea.is.unit.test") == "true") KotlinCoreEnvironment.createForTests(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
else KotlinCoreEnvironment.createForProduction(
KotlinCoreEnvironment.createForProduction(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
@@ -71,7 +72,7 @@ internal inline fun <T> withMessageCollectorAndDisposable(
failure(messageCollector, ex.asDiagnostics(path = locationId))
} finally {
if (disposeOnSuccess || failed) {
disposable.dispose()
Disposer.dispose(disposable)
}
}
}
@@ -134,6 +135,7 @@ internal fun makeCompiledScript(
containingKtFile.virtualFile?.path,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
null,
makeOtherScripts(it),
null
)
@@ -145,10 +147,18 @@ internal fun makeCompiledScript(
}
val module = makeCompiledModule(generationState)
val resultField = with(generationState.replSpecific) {
// TODO: pass it in the configuration instead
if (!hasResult || resultType == null || scriptResultFieldName == null) null
else scriptResultFieldName!! to KotlinType(DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(resultType!!))
}
return KJvmCompiledScript(
script.locationId,
getScriptConfiguration(ktScript.containingKtFile),
ktScript.fqName.asString(),
resultField,
makeOtherScripts(ktScript),
module
)
@@ -5,112 +5,25 @@
package kotlin.script.experimental.jvmhost.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.impl.KJvmCompilerImpl
import kotlin.script.experimental.jvmhost.impl.withDefaults
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmCompilerImpl(hostConfiguration.withDefaults())
) : ReplCompiler {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
import kotlin.script.experimental.api.*
interface KJvmReplCompilerProxy {
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
fun checkSyntax(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
project: Project
): ResultWithDiagnostics<Boolean>
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.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")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
override val history = JvmReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
override fun dispose() {
lock.write {
_compilation?.disposable?.dispose()
_compilation = null
super.dispose()
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
}
}
fun compileReplSnippet(
compilationState: JvmReplCompilerState.Compilation,
snippet: SourceCode,
snippetId: ReplSnippetId,
history: IReplStageHistory<ScriptDescriptor>
): ResultWithDiagnostics<CompiledScript<*>>
}
@@ -0,0 +1,156 @@
/*
* 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 kotlin.script.experimental.jvmhost.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
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.scripting.repl.ReplCodeAnalyzer
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvmhost.impl.KJvmReplCompilerImpl
import kotlin.script.experimental.jvmhost.impl.withDefaults
/**
* REPL Compilation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
*/
class JvmReplCompiler(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
val replCompilerProxy: KJvmReplCompilerProxy = KJvmReplCompilerImpl(hostConfiguration.withDefaults())
) : ReplCompiler {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = JvmReplCompilerState(replCompilerProxy, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val res =
replCompilerProxy.checkSyntax(
codeLine.toSourceCode(scriptCompilationConfiguration),
compilation.baseScriptCompilationConfiguration,
compilation.environment.project
)
when {
// TODO: implement diagnostics rendering
res is ResultWithDiagnostics.Success && res.value -> ReplCheckResult.Ok()
res is ResultWithDiagnostics.Success && !res.value -> ReplCheckResult.Incomplete()
else -> ReplCheckResult.Error(res.reports.joinToString("\n") { it.message })
}
}
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult = state.lock.write {
val replCompilerState = state.asState(JvmReplCompilerState::class.java)
val compilation = replCompilerState.getCompilationState(scriptCompilationConfiguration)
val snippet = codeLine.toSourceCode(scriptCompilationConfiguration)
val snippetId = ReplSnippetIdImpl(codeLine.no, codeLine.generation, snippet)
when (val res = replCompilerProxy.compileReplSnippet(compilation, snippet, snippetId, replCompilerState.history)) {
is ResultWithDiagnostics.Success ->
ReplCompileResult.CompiledClasses(
LineId(codeLine),
replCompilerState.history.map { it.id },
snippet.name!!,
emptyList(),
res.value.resultField != null,
emptyList(),
res.value.resultField?.second?.typeName,
res.value
)
else -> ReplCompileResult.Error(res.reports.joinToString("\n") { it.message })
}
}
}
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.compilation.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")
}
}
}
}
class JvmReplCompilerState(
val replCompilerProxy: KJvmReplCompilerProxy,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<ScriptDescriptor> {
override val history = JvmReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
override fun dispose() {
lock.write {
_compilation?.disposable?.let {
Disposer.dispose(it)
}
_compilation = null
super.dispose()
}
}
fun getCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): Compilation = lock.write {
if (_compilation == null) {
initializeCompilation(scriptCompilationConfiguration)
}
_compilation!!
}
internal val compilation: Compilation
get() = _compilation ?: throw IllegalStateException("Compilation state is either not initializad or already destroyed")
private var _compilation: Compilation? = null
val isCompilationInitialized get() = _compilation != null
private fun initializeCompilation(scriptCompilationConfiguration: ScriptCompilationConfiguration) {
if (_compilation != null) throw IllegalStateException("Compilation state is already initialized")
_compilation = replCompilerProxy.createReplCompilationState(scriptCompilationConfiguration)
}
interface Compilation {
val disposable: Disposable
val baseScriptCompilationConfiguration: ScriptCompilationConfiguration
val environment: KotlinCoreEnvironment
val analyzerEngine: ReplCodeAnalyzer
}
}
internal class SourceCodeFromReplCodeLine(
val codeLine: ReplCodeLine,
compilationConfiguration: ScriptCompilationConfiguration
) : SourceCode {
override val text: String get() = codeLine.code
override val name: String = "${makeScriptBaseName(codeLine)}.${compilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
override val locationId: String? = null
}
internal fun ReplCodeLine.toSourceCode(compilationConfiguration: ScriptCompilationConfiguration): SourceCode =
SourceCodeFromReplCodeLine(this, compilationConfiguration)
@@ -0,0 +1,84 @@
/*
* 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 kotlin.script.experimental.jvmhost.repl
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.repl.*
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
import kotlin.script.experimental.jvm.jvm
/**
* REPL Evaluation wrapper for "legacy" REPL APIs defined in the org.jetbrains.kotlin.cli.common.repl package
*/
class JvmReplEvaluator(
val baseScriptEvaluationConfiguration: ScriptEvaluationConfiguration,
val scriptEvaluator: ScriptEvaluator = BasicJvmScriptEvaluator()
) : ReplEvaluator {
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
JvmReplEvaluatorState(baseScriptEvaluationConfiguration, lock)
override fun eval(
state: IReplStageState<*>,
compileResult: ReplCompileResult.CompiledClasses,
scriptArgs: ScriptArgsWithTypes?,
invokeWrapper: InvokeWrapper?
): ReplEvalResult = state.lock.write {
val evalState = state.asState(JvmReplEvaluatorState::class.java)
val compiledScript = (compileResult.data as? KJvmCompiledScript<*>)
?: return ReplEvalResult.Error.CompileTime("Unable to access compiled script: ${compileResult.data}")
val lastSnippetInstance = evalState.history.peek()?.item
val currentConfiguration = ScriptEvaluationConfiguration(baseScriptEvaluationConfiguration) {
if (evalState.history.isNotEmpty()) {
previousSnippets.put(evalState.history.map { it.item })
}
if (lastSnippetInstance != null) {
jvm {
baseClassLoader(lastSnippetInstance::class.java.classLoader)
}
}
}
val res = runBlocking { scriptEvaluator(compiledScript, currentConfiguration) }
when (res) {
is ResultWithDiagnostics.Success -> when (val retVal = res.value.returnValue) {
is ResultValue.Value -> {
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
// TODO: the latter check is temporary while the result is used to return the instance too
if (retVal.type.isNotBlank())
ReplEvalResult.ValueResult(retVal.name, retVal.value, retVal.type)
else
ReplEvalResult.UnitResult()
}
is ResultValue.UnitValue -> {
evalState.history.push(compileResult.lineId, retVal.scriptInstance)
ReplEvalResult.UnitResult()
}
else -> throw IllegalStateException("Expecting value with script instance, got $retVal")
}
else -> ReplEvalResult.Error.Runtime(res.reports.joinToString("\n") { it.message })
}
}
}
open class JvmReplEvaluatorState(
scriptEvaluationConfiguration: ScriptEvaluationConfiguration,
override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : IReplStageState<Any> {
override val history: IReplStageHistory<Any> = BasicReplStageHistory(lock)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val topClassLoader: ClassLoader = scriptEvaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]!!
}