Implement "legacy" REPL wrappers on top of the "new" scripting infrastructure
This commit is contained in:
+6
@@ -96,5 +96,11 @@ open class AggregatedReplStageState<T1, T2>(val state1: IReplStageState<T1>, val
|
|||||||
override fun getNextLineNo() = state1.getNextLineNo()
|
override fun getNextLineNo() = state1.getNextLineNo()
|
||||||
|
|
||||||
override val currentGeneration: Int get() = state1.currentGeneration
|
override val currentGeneration: Int get() = state1.currentGeneration
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
state2.dispose()
|
||||||
|
state1.dispose()
|
||||||
|
super.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ sealed class ReplCompileResult : Serializable {
|
|||||||
val classes: List<CompiledClassData>,
|
val classes: List<CompiledClassData>,
|
||||||
val hasResult: Boolean,
|
val hasResult: Boolean,
|
||||||
val classpathAddendum: List<File>,
|
val classpathAddendum: List<File>,
|
||||||
val type: String?
|
val type: String?,
|
||||||
|
val data: Any? // TODO: temporary; migration to new scripting infrastructure
|
||||||
) : ReplCompileResult() {
|
) : ReplCompileResult() {
|
||||||
companion object { private val serialVersionUID: Long = 2L }
|
companion object { private val serialVersionUID: Long = 2L }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ interface IReplStageState<T> {
|
|||||||
else throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
|
else throw IllegalArgumentException("$this is not an expected instance of IReplStageState")
|
||||||
|
|
||||||
fun dispose() {
|
fun dispose() {
|
||||||
history.reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,4 +249,10 @@ interface CompiledScript<out ScriptBase : Any> {
|
|||||||
*/
|
*/
|
||||||
val otherScripts: List<CompiledScript<*>>
|
val otherScripts: List<CompiledScript<*>>
|
||||||
get() = emptyList()
|
get() = emptyList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and the type of the script's result field, if any
|
||||||
|
*/
|
||||||
|
val resultField: Pair<String, KotlinType>?
|
||||||
|
get() = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ val ScriptEvaluationConfigurationKeys.providedProperties by PropertiesCollection
|
|||||||
*/
|
*/
|
||||||
val ScriptEvaluationConfigurationKeys.constructorArgs by PropertiesCollection.key<List<Any?>>()
|
val ScriptEvaluationConfigurationKeys.constructorArgs by PropertiesCollection.key<List<Any?>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the script is a snippet in a REPL, this property expected to contain previous REPL snippets in historical order
|
||||||
|
* For the first snippet in a REPL an empty list should be passed explicitly
|
||||||
|
* An array of the previous snippets will be passed to the current snippet constructor
|
||||||
|
*/
|
||||||
|
val ScriptEvaluationConfigurationKeys.previousSnippets by PropertiesCollection.key<List<Any>>()
|
||||||
|
|
||||||
@Deprecated("use scriptsInstancesSharing flag instead", level = DeprecationLevel.ERROR)
|
@Deprecated("use scriptsInstancesSharing flag instead", level = DeprecationLevel.ERROR)
|
||||||
val ScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key<MutableMap<KClass<*>, EvaluationResult>>()
|
val ScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key<MutableMap<KClass<*>, EvaluationResult>>()
|
||||||
|
|
||||||
@@ -101,6 +108,11 @@ sealed class ResultValue {
|
|||||||
override fun toString(): String = "$name: $type = $value"
|
override fun toString(): String = "$name: $type = $value"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UnitValue(val scriptInstance: Any) : ResultValue() {
|
||||||
|
override fun toString(): String = "Unit"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: obsolete it, use differently named value in the saving evaluators
|
||||||
object Unit : ResultValue()
|
object Unit : ResultValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ open class PropertiesCollection(private val properties: Map<Key<*>, Any?> = empt
|
|||||||
data[this] = v
|
data[this] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> PropertiesCollection.Key<T>.put(v: T) {
|
||||||
|
data[this] = v
|
||||||
|
}
|
||||||
|
|
||||||
// generic for lists
|
// generic for lists
|
||||||
|
|
||||||
operator fun <T> PropertiesCollection.Key<in List<T>>.invoke(vararg vals: T) {
|
operator fun <T> PropertiesCollection.Key<in List<T>>.invoke(vararg vals: T) {
|
||||||
|
|||||||
+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.environment.setIdeaIoUseFallback
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
|
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.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
||||||
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
||||||
@@ -32,7 +35,7 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
|
|||||||
return ReplCompilationState(context)
|
return ReplCompilationState(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkSyntax(
|
override fun checkSyntax(
|
||||||
script: SourceCode,
|
script: SourceCode,
|
||||||
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||||
project: Project
|
project: Project
|
||||||
@@ -44,13 +47,18 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
|
|||||||
override val messageCollector = messageCollector
|
override val messageCollector = messageCollector
|
||||||
}
|
}
|
||||||
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(ktFile, errorHolder)
|
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,
|
compilationState: JvmReplCompilerState.Compilation,
|
||||||
snippet: SourceCode,
|
snippet: SourceCode,
|
||||||
snippetId: ReplSnippetId,
|
snippetId: ReplSnippetId,
|
||||||
|
// TODO: replace history with some interface based on CompiledScript
|
||||||
history: IReplStageHistory<ScriptDescriptor>
|
history: IReplStageHistory<ScriptDescriptor>
|
||||||
): ResultWithDiagnostics<CompiledScript<*>> =
|
): ResultWithDiagnostics<CompiledScript<*>> =
|
||||||
withMessageCollector(snippet) { messageCollector ->
|
withMessageCollector(snippet) { messageCollector ->
|
||||||
@@ -108,7 +116,6 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
|
|||||||
CompilationErrorHandler.THROW_EXCEPTION
|
CompilationErrorHandler.THROW_EXCEPTION
|
||||||
)
|
)
|
||||||
|
|
||||||
val generatedClassname = makeScriptBaseName(codeLine)
|
|
||||||
history.push(LineId(codeLine), scriptDescriptor)
|
history.push(LineId(codeLine), scriptDescriptor)
|
||||||
|
|
||||||
val compiledScript =
|
val compiledScript =
|
||||||
@@ -133,4 +140,3 @@ internal class ReplCompilationState(val context: SharedScriptCompilationContext)
|
|||||||
internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine =
|
internal fun makeReplCodeLine(id: ReplSnippetId, code: SourceCode): ReplCodeLine =
|
||||||
ReplCodeLine(id.no, id.generation, code.text)
|
ReplCodeLine(id.no, id.generation, code.text)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -62,10 +62,7 @@ internal fun createSharedCompilationContext(
|
|||||||
scriptCompilationConfiguration, hostConfiguration, scriptCompilationState, messageCollector, ignoredOptionsReportingState
|
scriptCompilationConfiguration, hostConfiguration, scriptCompilationState, messageCollector, ignoredOptionsReportingState
|
||||||
)
|
)
|
||||||
val environment =
|
val environment =
|
||||||
if (System.getProperty("idea.is.unit.test") == "true") KotlinCoreEnvironment.createForTests(
|
KotlinCoreEnvironment.createForProduction(
|
||||||
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
|
|
||||||
)
|
|
||||||
else KotlinCoreEnvironment.createForProduction(
|
|
||||||
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
|
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.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
import org.jetbrains.kotlin.psi.KtScript
|
||||||
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
|
import org.jetbrains.kotlin.scripting.dependencies.ScriptsCompilationDependencies
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -71,7 +72,7 @@ internal inline fun <T> withMessageCollectorAndDisposable(
|
|||||||
failure(messageCollector, ex.asDiagnostics(path = locationId))
|
failure(messageCollector, ex.asDiagnostics(path = locationId))
|
||||||
} finally {
|
} finally {
|
||||||
if (disposeOnSuccess || failed) {
|
if (disposeOnSuccess || failed) {
|
||||||
disposable.dispose()
|
Disposer.dispose(disposable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,6 +135,7 @@ internal fun makeCompiledScript(
|
|||||||
containingKtFile.virtualFile?.path,
|
containingKtFile.virtualFile?.path,
|
||||||
getScriptConfiguration(sourceFile),
|
getScriptConfiguration(sourceFile),
|
||||||
it.fqName.asString(),
|
it.fqName.asString(),
|
||||||
|
null,
|
||||||
makeOtherScripts(it),
|
makeOtherScripts(it),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
@@ -145,10 +147,18 @@ internal fun makeCompiledScript(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val module = makeCompiledModule(generationState)
|
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(
|
return KJvmCompiledScript(
|
||||||
script.locationId,
|
script.locationId,
|
||||||
getScriptConfiguration(ktScript.containingKtFile),
|
getScriptConfiguration(ktScript.containingKtFile),
|
||||||
ktScript.fqName.asString(),
|
ktScript.fqName.asString(),
|
||||||
|
resultField,
|
||||||
makeOtherScripts(ktScript),
|
makeOtherScripts(ktScript),
|
||||||
module
|
module
|
||||||
)
|
)
|
||||||
|
|||||||
+17
-104
@@ -5,112 +5,25 @@
|
|||||||
|
|
||||||
package kotlin.script.experimental.jvmhost.repl
|
package kotlin.script.experimental.jvmhost.repl
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Disposer
|
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
|
||||||
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.descriptors.ScriptDescriptor
|
||||||
import org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer
|
import kotlin.script.experimental.api.*
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
interface KJvmReplCompilerProxy {
|
interface KJvmReplCompilerProxy {
|
||||||
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
|
fun createReplCompilationState(scriptCompilationConfiguration: ScriptCompilationConfiguration): JvmReplCompilerState.Compilation
|
||||||
}
|
|
||||||
|
fun checkSyntax(
|
||||||
class JvmReplCompilerStageHistory(private val state: JvmReplCompilerState) :
|
script: SourceCode,
|
||||||
BasicReplStageHistory<ScriptDescriptor>(state.lock) {
|
scriptCompilationConfiguration: ScriptCompilationConfiguration,
|
||||||
|
project: Project
|
||||||
override fun reset(): Iterable<ILineId> {
|
): ResultWithDiagnostics<Boolean>
|
||||||
val removedCompiledLines = super.reset()
|
|
||||||
val removedAnalyzedLines = state.compilation.analyzerEngine.reset()
|
|
||||||
|
fun compileReplSnippet(
|
||||||
checkConsistent(removedCompiledLines, removedAnalyzedLines)
|
compilationState: JvmReplCompilerState.Compilation,
|
||||||
return removedCompiledLines
|
snippet: SourceCode,
|
||||||
}
|
snippetId: ReplSnippetId,
|
||||||
|
history: IReplStageHistory<ScriptDescriptor>
|
||||||
override fun resetTo(id: ILineId): Iterable<ILineId> {
|
): ResultWithDiagnostics<CompiledScript<*>>
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+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
|
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 outStream = ByteArrayOutputStream()
|
||||||
val errStream = ByteArrayOutputStream()
|
val errStream = ByteArrayOutputStream()
|
||||||
val prevOut = System.out
|
val prevOut = System.out
|
||||||
|
|||||||
+17
-6
@@ -43,7 +43,15 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
|
|||||||
?.resultOrNull()
|
?.resultOrNull()
|
||||||
?: configuration
|
?: configuration
|
||||||
|
|
||||||
scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults).let {
|
val instance =
|
||||||
|
scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults)
|
||||||
|
|
||||||
|
val resultValue = compiledScript.resultField?.let { (resultFieldName, resultType) ->
|
||||||
|
val resultField = scriptClass.java.getDeclaredField(resultFieldName).apply { isAccessible = true }
|
||||||
|
ResultValue.Value(resultFieldName, resultField.get(instance), resultType.typeName, instance)
|
||||||
|
} ?: ResultValue.Value("", instance, "", instance)
|
||||||
|
|
||||||
|
EvaluationResult(resultValue, refinedEvalConfiguration).let {
|
||||||
sharedScripts?.put(scriptClass, it)
|
sharedScripts?.put(scriptClass, it)
|
||||||
ResultWithDiagnostics.Success(it)
|
ResultWithDiagnostics.Success(it)
|
||||||
}
|
}
|
||||||
@@ -61,9 +69,15 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
|
|||||||
private fun KClass<*>.evalWithConfigAndOtherScriptsResults(
|
private fun KClass<*>.evalWithConfigAndOtherScriptsResults(
|
||||||
refinedEvalConfiguration: ScriptEvaluationConfiguration,
|
refinedEvalConfiguration: ScriptEvaluationConfiguration,
|
||||||
importedScriptsEvalResults: List<EvaluationResult>
|
importedScriptsEvalResults: List<EvaluationResult>
|
||||||
): EvaluationResult {
|
): Any {
|
||||||
val args = ArrayList<Any?>()
|
val args = ArrayList<Any?>()
|
||||||
|
|
||||||
|
refinedEvalConfiguration[ScriptEvaluationConfiguration.previousSnippets]?.let {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
args.add(it.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
refinedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let {
|
refinedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let {
|
||||||
args.addAll(it)
|
args.addAll(it)
|
||||||
}
|
}
|
||||||
@@ -81,10 +95,7 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
|
|||||||
val ctor = java.constructors.single()
|
val ctor = java.constructors.single()
|
||||||
val instance = ctor.newInstance(*args.toArray())
|
val instance = ctor.newInstance(*args.toArray())
|
||||||
|
|
||||||
return EvaluationResult(
|
return instance
|
||||||
ResultValue.Value("", instance, "", instance),
|
|
||||||
refinedEvalConfiguration
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-3
@@ -16,6 +16,7 @@ internal class KJvmCompiledScriptData(
|
|||||||
var sourceLocationId: String?,
|
var sourceLocationId: String?,
|
||||||
var compilationConfiguration: ScriptCompilationConfiguration,
|
var compilationConfiguration: ScriptCompilationConfiguration,
|
||||||
var scriptClassFQName: String,
|
var scriptClassFQName: String,
|
||||||
|
var resultField: Pair<String, KotlinType>?,
|
||||||
var otherScripts: List<CompiledScript<*>> = emptyList()
|
var otherScripts: List<CompiledScript<*>> = emptyList()
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ internal class KJvmCompiledScriptData(
|
|||||||
outputStream.writeObject(sourceLocationId)
|
outputStream.writeObject(sourceLocationId)
|
||||||
outputStream.writeObject(otherScripts)
|
outputStream.writeObject(otherScripts)
|
||||||
outputStream.writeObject(scriptClassFQName)
|
outputStream.writeObject(scriptClassFQName)
|
||||||
|
outputStream.writeObject(resultField)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -32,11 +34,12 @@ internal class KJvmCompiledScriptData(
|
|||||||
sourceLocationId = inputStream.readObject() as String?
|
sourceLocationId = inputStream.readObject() as String?
|
||||||
otherScripts = inputStream.readObject() as List<CompiledScript<*>>
|
otherScripts = inputStream.readObject() as List<CompiledScript<*>>
|
||||||
scriptClassFQName = inputStream.readObject() as String
|
scriptClassFQName = inputStream.readObject() as String
|
||||||
|
resultField = inputStream.readObject() as Pair<String, KotlinType>?
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private val serialVersionUID = 3L
|
private val serialVersionUID = 4L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,9 +52,13 @@ class KJvmCompiledScript<out ScriptBase : Any> internal constructor(
|
|||||||
sourceLocationId: String?,
|
sourceLocationId: String?,
|
||||||
compilationConfiguration: ScriptCompilationConfiguration,
|
compilationConfiguration: ScriptCompilationConfiguration,
|
||||||
scriptClassFQName: String,
|
scriptClassFQName: String,
|
||||||
|
resultField: Pair<String, KotlinType>?,
|
||||||
otherScripts: List<CompiledScript<*>> = emptyList(),
|
otherScripts: List<CompiledScript<*>> = emptyList(),
|
||||||
compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
|
compiledModule: KJvmCompiledModule? // module should be null for imported (other) scripts, so only one reference to the module is kept
|
||||||
): this(KJvmCompiledScriptData(sourceLocationId, compilationConfiguration, scriptClassFQName, otherScripts), compiledModule)
|
) : this(
|
||||||
|
KJvmCompiledScriptData(sourceLocationId, compilationConfiguration, scriptClassFQName, resultField, otherScripts),
|
||||||
|
compiledModule
|
||||||
|
)
|
||||||
|
|
||||||
override val sourceLocationId: String?
|
override val sourceLocationId: String?
|
||||||
get() = data.sourceLocationId
|
get() = data.sourceLocationId
|
||||||
@@ -65,6 +72,9 @@ class KJvmCompiledScript<out ScriptBase : Any> internal constructor(
|
|||||||
val scriptClassFQName: String
|
val scriptClassFQName: String
|
||||||
get() = data.scriptClassFQName
|
get() = data.scriptClassFQName
|
||||||
|
|
||||||
|
override val resultField: Pair<String, KotlinType>?
|
||||||
|
get() = data.resultField
|
||||||
|
|
||||||
override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>> = try {
|
override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics<KClass<*>> = try {
|
||||||
// ensuring proper defaults are used
|
// ensuring proper defaults are used
|
||||||
val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration()
|
val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration()
|
||||||
@@ -168,7 +178,8 @@ fun KJvmCompiledScript<*>.toBytes(): ByteArray {
|
|||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
oos?.close()
|
oos?.close()
|
||||||
} catch (e: IOException) {}
|
} catch (e: IOException) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -115,7 +115,8 @@ open class GenericReplCompiler(
|
|||||||
classpathAddendum ?: emptyList(),
|
classpathAddendum ?: emptyList(),
|
||||||
generationState.replSpecific.resultType?.let {
|
generationState.replSpecific.resultType?.let {
|
||||||
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it)
|
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it)
|
||||||
}
|
},
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user