Implement "legacy" REPL wrappers on top of the "new" scripting infrastructure
This commit is contained in:
+12
-6
@@ -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)
|
||||
|
||||
|
||||
|
||||
+1
-4
@@ -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
|
||||
)
|
||||
|
||||
|
||||
+11
-1
@@ -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
|
||||
)
|
||||
|
||||
+15
-102
@@ -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<*>>
|
||||
}
|
||||
+156
@@ -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)
|
||||
+84
@@ -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]!!
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.test
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.cli.common.repl.*
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.resetApplicationToNull
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.script.experimental.jvmhost.repl.JvmReplCompiler
|
||||
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
|
||||
|
||||
// Adapted form GenericReplTest
|
||||
|
||||
class LegacyReplTest : KtUsefulTestCase() {
|
||||
fun testReplBasics() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
val res1 = repl.replCompiler.check(repl.state, ReplCodeLine(0, 0, "val x ="))
|
||||
TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete)
|
||||
|
||||
assertEvalResult(repl, "val l1 = listOf(1 + 2)\nl1.first()", 3)
|
||||
|
||||
assertEvalUnit(repl, "val x = 5")
|
||||
|
||||
assertEvalResult(repl, "x + 2", 7)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplErrors() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
repl.compileAndEval(repl.nextCodeLine("val x = 10"))
|
||||
|
||||
val res = repl.compileAndEval(repl.nextCodeLine("java.util.fish"))
|
||||
TestCase.assertTrue("Expected compile error", res.first is ReplCompileResult.Error)
|
||||
|
||||
val result = repl.compileAndEval(repl.nextCodeLine("x"))
|
||||
assertEquals(res.second.toString(), 10, (result.second as? ReplEvalResult.ValueResult)?.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplCodeFormat() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
val codeLine0 = ReplCodeLine(0, 0, "val l1 = 1\r\nl1\r\n")
|
||||
val res0 = repl.replCompiler.check(repl.state, codeLine0)
|
||||
val res0c = res0 as? ReplCheckResult.Ok
|
||||
TestCase.assertNotNull("Unexpected compile result: $res0", res0c)
|
||||
}
|
||||
}
|
||||
|
||||
fun testRepPackage() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
assertEvalResult(repl, "package mypackage\n\nval x = 1\nx+2", 3)
|
||||
|
||||
assertEvalResult(repl, "x+4", 5)
|
||||
}
|
||||
}
|
||||
|
||||
fun test256Evals() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
repl.compileAndEval(ReplCodeLine(0, 0, "val x0 = 0"))
|
||||
|
||||
val evals = 256
|
||||
for (i in 1..evals) {
|
||||
repl.compileAndEval(ReplCodeLine(i, 0, "val x$i = x${i-1} + 1"))
|
||||
}
|
||||
|
||||
val res = repl.compileAndEval(ReplCodeLine(evals + 1, 0, "x$evals"))
|
||||
assertEquals(res.second.toString(), evals, (res.second as? ReplEvalResult.ValueResult)?.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplSlowdownKt22740() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
repl.compileAndEval(ReplCodeLine(0, 0, "class Test<T>(val x: T) { fun <R> map(f: (T) -> R): R = f(x) }".trimIndent()))
|
||||
|
||||
// We expect that analysis time is not exponential
|
||||
for (i in 1..60) {
|
||||
repl.compileAndEval(ReplCodeLine(i, 0, "fun <T> Test<T>.map(f: (T) -> Double): List<Double> = listOf(f(this.x))"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplResultFieldWithFunction() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
assertEvalResultIs<Function0<Int>>(repl, "{ 1 + 2 }")
|
||||
assertEvalResultIs<Function0<Int>>(repl, "res0")
|
||||
assertEvalResult(repl, "res0()", 3)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplResultField() {
|
||||
LegacyTestRepl().use { repl ->
|
||||
assertEvalResult(repl, "5 * 4", 20)
|
||||
assertEvalResult(repl, "res0 + 3", 23)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertEvalUnit(repl: LegacyTestRepl, line: String) {
|
||||
val compiledClasses = checkCompile(repl, line)
|
||||
|
||||
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
|
||||
val unitResult = evalResult as? ReplEvalResult.UnitResult
|
||||
TestCase.assertNotNull("Unexpected eval result: $evalResult", unitResult)
|
||||
}
|
||||
|
||||
private fun<R> assertEvalResult(repl: LegacyTestRepl, line: String, expectedResult: R) {
|
||||
val compiledClasses = checkCompile(repl, line)
|
||||
|
||||
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
|
||||
val valueResult = evalResult as? ReplEvalResult.ValueResult
|
||||
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
|
||||
TestCase.assertEquals(expectedResult, valueResult!!.value)
|
||||
}
|
||||
|
||||
private inline fun<reified R> assertEvalResultIs(repl: LegacyTestRepl, line: String) {
|
||||
val compiledClasses = checkCompile(repl, line)
|
||||
|
||||
val evalResult = repl.compiledEvaluator.eval(repl.state, compiledClasses!!)
|
||||
val valueResult = evalResult as? ReplEvalResult.ValueResult
|
||||
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
|
||||
TestCase.assertTrue(valueResult!!.value is R)
|
||||
}
|
||||
|
||||
private fun checkCompile(repl: LegacyTestRepl, line: String): ReplCompileResult.CompiledClasses? {
|
||||
val codeLine = repl.nextCodeLine(line)
|
||||
val compileResult = repl.replCompiler.compile(repl.state, codeLine)
|
||||
val compiledClasses = compileResult as? ReplCompileResult.CompiledClasses
|
||||
TestCase.assertNotNull("Unexpected compile result: $compileResult", compiledClasses)
|
||||
return compiledClasses
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class LegacyTestRepl : Closeable {
|
||||
val application = ApplicationManager.getApplication()
|
||||
|
||||
val currentLineCounter = AtomicInteger()
|
||||
|
||||
fun nextCodeLine(code: String): ReplCodeLine = ReplCodeLine(currentLineCounter.getAndIncrement(), 0, code)
|
||||
|
||||
val replCompiler: JvmReplCompiler by lazy {
|
||||
JvmReplCompiler(simpleScriptompilationConfiguration)
|
||||
}
|
||||
|
||||
val compiledEvaluator: ReplEvaluator by lazy {
|
||||
JvmReplEvaluator(simpleScriptEvaluationConfiguration)
|
||||
}
|
||||
|
||||
val state by lazy {
|
||||
val stateLock = ReentrantReadWriteLock()
|
||||
AggregatedReplStageState(replCompiler.createState(stateLock), compiledEvaluator.createState(stateLock), stateLock)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
state.dispose()
|
||||
resetApplicationToNull(application)
|
||||
}
|
||||
}
|
||||
|
||||
private fun LegacyTestRepl.compileAndEval(codeLine: ReplCodeLine): Pair<ReplCompileResult, ReplEvalResult?> {
|
||||
|
||||
val compRes = replCompiler.compile(state, codeLine)
|
||||
|
||||
val evalRes = (compRes as? ReplCompileResult.CompiledClasses)?.let {
|
||||
|
||||
compiledEvaluator.eval(state, it)
|
||||
}
|
||||
return compRes to evalRes
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.test
|
||||
|
||||
import junit.framework.TestCase
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.cli.common.repl.BasicReplStageHistory
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.*
|
||||
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvmhost.impl.KJvmReplCompilerImpl
|
||||
|
||||
class ReplTest : TestCase() {
|
||||
|
||||
companion object {
|
||||
const val TEST_DATA_DIR = "libraries/scripting/jvm-host/testData"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompileAndEval() {
|
||||
val out = captureOut {
|
||||
chechEvaluateInReplNoErrors(
|
||||
simpleScriptompilationConfiguration,
|
||||
simpleScriptEvaluationConfiguration,
|
||||
sequenceOf(
|
||||
"val x = 3",
|
||||
"x + 4",
|
||||
"println(\"x = \$x\")"
|
||||
),
|
||||
sequenceOf(null, 7, null)
|
||||
)
|
||||
}
|
||||
Assert.assertEquals("x = 3", out)
|
||||
}
|
||||
|
||||
fun evaluateInRepl(
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
evaluationConfiguration: ScriptEvaluationConfiguration,
|
||||
snippets: Sequence<String>
|
||||
): Sequence<ResultWithDiagnostics<EvaluationResult>> {
|
||||
val replCompilerProxy = KJvmReplCompilerImpl(defaultJvmScriptingHostConfiguration)
|
||||
val compilationState = replCompilerProxy.createReplCompilationState(compilationConfiguration)
|
||||
val compilationHistory = BasicReplStageHistory<ScriptDescriptor>()
|
||||
val replEvaluator = BasicJvmScriptEvaluator()
|
||||
var currentEvalConfig = evaluationConfiguration
|
||||
return snippets.mapIndexed { snippetNo, snippetText ->
|
||||
val snippetSource = snippetText.toScriptSource("Line_$snippetNo.simplescript.kts")
|
||||
val snippetId = ReplSnippetIdImpl(snippetNo, 0, snippetSource)
|
||||
replCompilerProxy.compileReplSnippet(compilationState, snippetSource, snippetId, compilationHistory)
|
||||
.onSuccess {
|
||||
runBlocking {
|
||||
replEvaluator(it, currentEvalConfig)
|
||||
}
|
||||
}
|
||||
.onSuccess {
|
||||
val snippetInstance = when (val retVal = it.returnValue) {
|
||||
is ResultValue.Value -> retVal.scriptInstance
|
||||
is ResultValue.UnitValue -> retVal.scriptInstance
|
||||
else -> throw IllegalStateException("Expecting value with script instance, got $it")
|
||||
}
|
||||
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
|
||||
previousSnippets.append(snippetInstance)
|
||||
jvm {
|
||||
baseClassLoader(snippetInstance::class.java.classLoader)
|
||||
}
|
||||
}
|
||||
it.asSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun chechEvaluateInReplNoErrors(
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
evaluationConfiguration: ScriptEvaluationConfiguration,
|
||||
snippets: Sequence<String>,
|
||||
expected: Sequence<Any?>
|
||||
) {
|
||||
val expectedIter = expected.iterator()
|
||||
evaluateInRepl(compilationConfiguration, evaluationConfiguration, snippets).forEachIndexed { index, res ->
|
||||
when (res) {
|
||||
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
|
||||
is ResultWithDiagnostics.Success -> {
|
||||
val expectedVal = expectedIter.next()
|
||||
val resVal = res.value.returnValue
|
||||
if (resVal is ResultValue.Value && resVal.type.isNotBlank()) // TODO: the latter check is temporary while the result is used to return the instance too
|
||||
Assert.assertEquals("#$index: Expected $expectedVal, got $resVal", expectedVal, resVal.value)
|
||||
else
|
||||
Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@KotlinScript(fileExtension = "simplescript.kts")
|
||||
abstract class SimpleScript
|
||||
|
||||
val simpleScriptompilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScript> {
|
||||
jvm {
|
||||
dependenciesFromCurrentContext(wholeClasspath = true)
|
||||
}
|
||||
}
|
||||
|
||||
val simpleScriptEvaluationConfiguration = ScriptEvaluationConfiguration()
|
||||
+2
-2
@@ -526,9 +526,9 @@ private class FileBasedScriptCache(val baseDir: File) : ScriptingCacheWithCounte
|
||||
get() = _retrievedScripts
|
||||
}
|
||||
|
||||
private fun captureOut(body: () -> Unit): String = captureOutAndErr(body).first
|
||||
internal fun captureOut(body: () -> Unit): String = captureOutAndErr(body).first
|
||||
|
||||
private fun captureOutAndErr(body: () -> Unit): Pair<String, String> {
|
||||
internal fun captureOutAndErr(body: () -> Unit): Pair<String, String> {
|
||||
val outStream = ByteArrayOutputStream()
|
||||
val errStream = ByteArrayOutputStream()
|
||||
val prevOut = System.out
|
||||
|
||||
Reference in New Issue
Block a user