Prepare repl interfaces and generic implementation for separation into possibly remote parts
This commit is contained in:
@@ -11,5 +11,6 @@
|
|||||||
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
|
<orderEntry type="library" scope="PROVIDED" name="intellij-core" level="project" />
|
||||||
<orderEntry type="library" exported="" name="cli-parser" level="project" />
|
<orderEntry type="library" exported="" name="cli-parser" level="project" />
|
||||||
<orderEntry type="library" name="jps" level="project" />
|
<orderEntry type="library" name="jps" level="project" />
|
||||||
|
<orderEntry type="module" module-name="descriptor.loader.java" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.common.repl
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||||
|
import kotlin.concurrent.read
|
||||||
|
import kotlin.concurrent.write
|
||||||
|
|
||||||
|
open class GenericReplCompiledEvaluator(baseClasspath: Iterable<File>) : ReplCompiledEvaluator {
|
||||||
|
|
||||||
|
private val classpath = baseClasspath.toMutableList()
|
||||||
|
|
||||||
|
private var classLoader: org.jetbrains.kotlin.cli.common.repl.ReplClassLoader =
|
||||||
|
org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader))
|
||||||
|
private val classLoaderLock = ReentrantReadWriteLock()
|
||||||
|
|
||||||
|
private class ClassWithInstance(val klass: Class<*>, val instance: Any)
|
||||||
|
|
||||||
|
private val compiledLoadedClassesHistory = arrayListOf<Pair<ReplCodeLine, ClassWithInstance>>()
|
||||||
|
|
||||||
|
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult {
|
||||||
|
|
||||||
|
checkAndUpdateReplHistoryCollection(compiledLoadedClassesHistory, history)?.let {
|
||||||
|
return@eval ReplEvalResult.HistoryMismatch(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
classLoaderLock.read {
|
||||||
|
compiledClasses.filter { it.path.endsWith(".class") }
|
||||||
|
.forEach {
|
||||||
|
classLoader.addClass(JvmClassName.byInternalName(it.path.replaceFirst("\\.class$".toRegex(), "")), it.bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scriptClass = classLoaderLock.read { classLoader.loadClass("Line${codeLine.no}") }
|
||||||
|
|
||||||
|
val constructorParams = compiledLoadedClassesHistory.map { it.second.klass }.toTypedArray()
|
||||||
|
val constructorArgs = compiledLoadedClassesHistory.map { it.second.instance }.toTypedArray()
|
||||||
|
|
||||||
|
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
||||||
|
val scriptInstance =
|
||||||
|
try {
|
||||||
|
evalWithIO { scriptInstanceConstructor.newInstance(*constructorArgs) }
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
// ignore everything in the stack trace until this constructor call
|
||||||
|
return ReplEvalResult.Error.Runtime(renderReplStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"))
|
||||||
|
}
|
||||||
|
|
||||||
|
compiledLoadedClassesHistory.add(codeLine to ClassWithInstance(scriptClass, scriptInstance))
|
||||||
|
|
||||||
|
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
|
||||||
|
val rv: Any? = rvField.get(scriptInstance)
|
||||||
|
|
||||||
|
return if (hasResult) ReplEvalResult.ValueResult(rv) else ReplEvalResult.UnitResult
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dependenciesAdded(newClasspath: List<File>) {
|
||||||
|
classLoaderLock.write {
|
||||||
|
classpath.addAll(newClasspath)
|
||||||
|
classLoader = org.jetbrains.kotlin.cli.common.repl.ReplClassLoader(URLClassLoader(newClasspath.map { it.toURI().toURL() }.toTypedArray(), classLoader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.common.repl
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class ReplCodeLine(val no: Int, val code: String)
|
||||||
|
|
||||||
|
data class CompiledClassData(val path: String, val bytes: ByteArray) : Serializable {
|
||||||
|
override fun equals(other: Any?): Boolean = (other as? CompiledClassData)?.let { path == it.path && Arrays.equals(bytes, it.bytes) } ?: false
|
||||||
|
override fun hashCode(): Int = path.hashCode() + Arrays.hashCode(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ReplCheckResult : Serializable {
|
||||||
|
object Ok : ReplCheckResult()
|
||||||
|
object Incomplete : ReplCheckResult()
|
||||||
|
class Error(val message: String) : ReplCheckResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ReplCompileResult : Serializable {
|
||||||
|
class CompiledClasses(val classes: List<CompiledClassData>, val hasResult: Boolean) : ReplCompileResult()
|
||||||
|
object Incomplete : ReplCompileResult()
|
||||||
|
class HistoryMismatch(val lineNo: Int): ReplCompileResult()
|
||||||
|
class Error(val message: String) : ReplCompileResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ReplEvalResult : Serializable {
|
||||||
|
class ValueResult(val value: Any?) : ReplEvalResult()
|
||||||
|
object UnitResult : ReplEvalResult()
|
||||||
|
object Incomplete : ReplEvalResult()
|
||||||
|
class HistoryMismatch(val lineNo: Int): ReplEvalResult()
|
||||||
|
sealed class Error(val message: String) : ReplEvalResult() {
|
||||||
|
class Runtime(message: String) : Error(message)
|
||||||
|
class CompileTime(message: String) : Error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplChecker {
|
||||||
|
fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplCompiler : ReplChecker {
|
||||||
|
fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult
|
||||||
|
|
||||||
|
// a callback
|
||||||
|
fun dependenciesAdded(classpath: List<File>)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReplCompiledEvaluator {
|
||||||
|
|
||||||
|
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>, compiledClasses: List<CompiledClassData>, hasResult: Boolean): ReplEvalResult
|
||||||
|
|
||||||
|
// override to capture output
|
||||||
|
fun<T> evalWithIO(body: () -> T): T = body()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ReplEvaluator : ReplChecker {
|
||||||
|
|
||||||
|
fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult
|
||||||
|
|
||||||
|
fun dependenciesAdded(classpath: List<File>)
|
||||||
|
|
||||||
|
// override to capture output
|
||||||
|
fun<T> evalWithIO(body: () -> T): T = body()
|
||||||
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.cli.jvm.repl;
|
package org.jetbrains.kotlin.cli.common.repl;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.common.repl
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables
|
||||||
|
|
||||||
|
fun <T> checkAndUpdateReplHistoryCollection(col: MutableList<Pair<ReplCodeLine, T>>, baseHistory: Iterable<ReplCodeLine>): Int? {
|
||||||
|
val baseHistoryIt = baseHistory.iterator()
|
||||||
|
var idx = 0
|
||||||
|
while (baseHistoryIt.hasNext()) {
|
||||||
|
val curLine = baseHistoryIt.next()
|
||||||
|
if (col[idx].first != curLine) return curLine.no
|
||||||
|
idx += 1
|
||||||
|
}
|
||||||
|
col.dropLast(col.size - idx)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renderReplStackTrace(cause: Throwable, startFromMethodName: String): String {
|
||||||
|
val newTrace = arrayListOf<StackTraceElement>()
|
||||||
|
var skip = true
|
||||||
|
for ((i, element) in cause.stackTrace.withIndex().reversed()) {
|
||||||
|
if ("${element.className}.${element.methodName}" == startFromMethodName) {
|
||||||
|
skip = false
|
||||||
|
}
|
||||||
|
if (!skip) {
|
||||||
|
newTrace.add(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val resultingTrace = newTrace.reversed().dropLast(1)
|
||||||
|
|
||||||
|
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
|
||||||
|
(cause as java.lang.Throwable).setStackTrace(resultingTrace.toTypedArray())
|
||||||
|
|
||||||
|
return Throwables.getStackTraceAsString(cause)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.cli.jvm.repl
|
package org.jetbrains.kotlin.cli.jvm.repl
|
||||||
|
|
||||||
import com.google.common.base.Throwables
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.vfs.CharsetToolkit
|
import com.intellij.openapi.vfs.CharsetToolkit
|
||||||
@@ -26,6 +25,7 @@ import com.intellij.testFramework.LightVirtualFile
|
|||||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.*
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
||||||
@@ -41,100 +41,97 @@ import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
|||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
|
import org.jetbrains.kotlin.script.KotlinScriptExternalDependencies
|
||||||
import org.jetbrains.kotlin.script.ScriptParameter
|
import org.jetbrains.kotlin.script.ScriptParameter
|
||||||
import java.net.URLClassLoader
|
import java.io.File
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
|
||||||
import kotlin.concurrent.read
|
|
||||||
import kotlin.concurrent.write
|
|
||||||
|
|
||||||
private val logger = Logger.getInstance(GenericRepl::class.java)
|
private val logger = Logger.getInstance(GenericRepl::class.java)
|
||||||
|
|
||||||
open class GenericRepl(
|
open class GenericReplChecker(
|
||||||
disposable: Disposable,
|
disposable: Disposable,
|
||||||
val scriptDefinition: KotlinScriptDefinition,
|
val scriptDefinition: KotlinScriptDefinition,
|
||||||
val compilerConfiguration: CompilerConfiguration,
|
val compilerConfiguration: CompilerConfiguration,
|
||||||
messageCollector: MessageCollector
|
messageCollector: MessageCollector
|
||||||
) {
|
) : ReplChecker {
|
||||||
private val environment = run {
|
protected val environment = run {
|
||||||
compilerConfiguration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, DelegatingScriptDefWithNoParams(scriptDefinition))
|
compilerConfiguration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, DelegatingScriptDefWithNoParams(scriptDefinition))
|
||||||
compilerConfiguration.put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
|
compilerConfiguration.put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
|
||||||
KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
|
protected val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
|
||||||
private val analyzerEngine = CliReplAnalyzerEngine(environment)
|
|
||||||
|
|
||||||
// "line" - is the unit of evaluation here, could in fact consists of several character lines
|
// "line" - is the unit of evaluation here, could in fact consists of several character lines
|
||||||
private class LineState(
|
protected class LineState(
|
||||||
val code: String,
|
val codeLine: ReplCodeLine,
|
||||||
val psiFile: KtFile,
|
val psiFile: KtFile,
|
||||||
val errorHolder: DiagnosticMessageHolder)
|
val errorHolder: DiagnosticMessageHolder)
|
||||||
|
|
||||||
sealed class EvalResult {
|
protected class DelegatingScriptDefWithNoParams(parent: KotlinScriptDefinition) : KotlinScriptDefinition by parent {
|
||||||
class ValueResult(val value: Any?): EvalResult()
|
|
||||||
|
|
||||||
object UnitResult: EvalResult()
|
|
||||||
object Ready: EvalResult()
|
|
||||||
object Incomplete : EvalResult()
|
|
||||||
|
|
||||||
sealed class Error(val errorText: String): EvalResult() {
|
|
||||||
class Runtime(errorText: String): Error(errorText)
|
|
||||||
class CompileTime(errorText: String): Error(errorText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DelegatingScriptDefWithNoParams(parent: KotlinScriptDefinition) : KotlinScriptDefinition by parent {
|
|
||||||
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> = emptyList()
|
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var lineState: LineState? = null
|
protected var lineState: LineState? = null
|
||||||
|
|
||||||
private var lastDependencies: KotlinScriptExternalDependencies? = null
|
val classpath: MutableList<File> = compilerConfiguration.jvmClasspathRoots.toMutableList()
|
||||||
|
|
||||||
val classpath = compilerConfiguration.jvmClasspathRoots.toMutableList()
|
|
||||||
|
|
||||||
private var classLoader: ReplClassLoader =
|
|
||||||
ReplClassLoader(URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader))
|
|
||||||
private val classLoaderLock = ReentrantReadWriteLock()
|
|
||||||
|
|
||||||
private val earlierLines = arrayListOf<EarlierLine>()
|
|
||||||
|
|
||||||
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder()
|
||||||
|
|
||||||
fun checkComplete(executionNumber: Long, code: String): EvalResult {
|
override fun check(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCheckResult {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
val virtualFile =
|
val virtualFile =
|
||||||
LightVirtualFile("line$executionNumber${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, code).apply {
|
LightVirtualFile("line${codeLine.no}${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, codeLine.code).apply {
|
||||||
charset = CharsetToolkit.UTF8_CHARSET
|
charset = CharsetToolkit.UTF8_CHARSET
|
||||||
}
|
}
|
||||||
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
|
||||||
?: error("Script file not analyzed at line $executionNumber: $code")
|
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
|
||||||
|
|
||||||
val errorHolder = createDiagnosticHolder()
|
val errorHolder = createDiagnosticHolder()
|
||||||
|
|
||||||
val syntaxErrorReport = AnalyzerWithCompilerReport.Companion.reportSyntaxErrors(psiFile, errorHolder)
|
val syntaxErrorReport = AnalyzerWithCompilerReport.Companion.reportSyntaxErrors(psiFile, errorHolder)
|
||||||
|
|
||||||
if (!syntaxErrorReport.isHasErrors) {
|
if (!syntaxErrorReport.isHasErrors) {
|
||||||
lineState = LineState(code, psiFile, errorHolder)
|
lineState = LineState(codeLine, psiFile, errorHolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> EvalResult.Incomplete
|
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete
|
||||||
syntaxErrorReport.isHasErrors -> EvalResult.Error.CompileTime(errorHolder.renderedDiagnostics)
|
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderedDiagnostics)
|
||||||
else -> EvalResult.Ready
|
else -> ReplCheckResult.Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun eval(executionNumber: Long, code: String): EvalResult {
|
|
||||||
|
abstract class GenericReplCompiler(
|
||||||
|
disposable: Disposable,
|
||||||
|
scriptDefinition: KotlinScriptDefinition,
|
||||||
|
compilerConfiguration: CompilerConfiguration,
|
||||||
|
messageCollector: MessageCollector
|
||||||
|
) : ReplCompiler, GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
|
||||||
|
private val analyzerEngine = CliReplAnalyzerEngine(environment)
|
||||||
|
|
||||||
|
private var lastDependencies: KotlinScriptExternalDependencies? = null
|
||||||
|
|
||||||
|
private val descriptorsHistory = arrayListOf<Pair<ReplCodeLine, ScriptDescriptor>>()
|
||||||
|
|
||||||
|
override fun compile(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplCompileResult {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
|
||||||
|
checkAndUpdateReplHistoryCollection(descriptorsHistory, history)?.let {
|
||||||
|
return@compile ReplCompileResult.HistoryMismatch(it)
|
||||||
|
}
|
||||||
|
|
||||||
val (psiFile, errorHolder) = run {
|
val (psiFile, errorHolder) = run {
|
||||||
if (lineState == null || lineState!!.code != code) {
|
if (lineState == null || lineState!!.codeLine != codeLine) {
|
||||||
val res = checkComplete(executionNumber, code)
|
val res = check(codeLine, history)
|
||||||
if (res != EvalResult.Ready) return@eval res
|
when (res) {
|
||||||
|
ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete
|
||||||
|
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message)
|
||||||
|
ReplCheckResult.Ok -> {} // continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Pair(lineState!!.psiFile, lineState!!.errorHolder)
|
Pair(lineState!!.psiFile, lineState!!.errorHolder)
|
||||||
}
|
}
|
||||||
@@ -145,20 +142,17 @@ open class GenericRepl(
|
|||||||
val newCp = environment.updateClasspath(it.classpath.map(::JvmClasspathRoot))
|
val newCp = environment.updateClasspath(it.classpath.map(::JvmClasspathRoot))
|
||||||
if (newCp != null && newCp.isNotEmpty()) {
|
if (newCp != null && newCp.isNotEmpty()) {
|
||||||
logger.debug("new dependencies: $newCp")
|
logger.debug("new dependencies: $newCp")
|
||||||
classLoaderLock.write {
|
dependenciesAdded(newCp)
|
||||||
classpath.addAll(newCp)
|
|
||||||
classLoader = ReplClassLoader(URLClassLoader(newCp.map { it.toURI().toURL() }.toTypedArray(), classLoader))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lastDependencies != newDependencies) {
|
if (lastDependencies != newDependencies) {
|
||||||
lastDependencies = newDependencies
|
lastDependencies = newDependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, executionNumber.toInt())
|
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, codeLine.no)
|
||||||
AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder, false)
|
AnalyzerWithCompilerReport.Companion.reportDiagnostics(analysisResult.diagnostics, errorHolder, false)
|
||||||
val scriptDescriptor = when (analysisResult) {
|
val scriptDescriptor = when (analysisResult) {
|
||||||
is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return EvalResult.Error.CompileTime(errorHolder.renderedDiagnostics)
|
is CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderedDiagnostics)
|
||||||
is CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
|
is CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
|
||||||
else -> error("Unexpected result ${analysisResult.javaClass}")
|
else -> error("Unexpected result ${analysisResult.javaClass}")
|
||||||
}
|
}
|
||||||
@@ -172,7 +166,7 @@ open class GenericRepl(
|
|||||||
compilerConfiguration
|
compilerConfiguration
|
||||||
)
|
)
|
||||||
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
|
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
|
||||||
state.replSpecific.earlierScriptsForReplInterpreter = earlierLines.map(EarlierLine::getScriptDescriptor)
|
state.replSpecific.earlierScriptsForReplInterpreter = descriptorsHistory.map { it.second }
|
||||||
state.beforeCompile()
|
state.beforeCompile()
|
||||||
KotlinCodegenFacade.generatePackage(
|
KotlinCodegenFacade.generatePackage(
|
||||||
state,
|
state,
|
||||||
@@ -180,68 +174,44 @@ open class GenericRepl(
|
|||||||
setOf(psiFile.script!!.getContainingKtFile()),
|
setOf(psiFile.script!!.getContainingKtFile()),
|
||||||
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
|
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
|
||||||
|
|
||||||
for (outputFile in state.factory.asList()) {
|
descriptorsHistory.add(codeLine to scriptDescriptor)
|
||||||
if (outputFile.relativePath.endsWith(".class")) {
|
|
||||||
classLoaderLock.read {
|
|
||||||
classLoader.addClass(JvmClassName.byInternalName(outputFile.relativePath.replaceFirst("\\.class$".toRegex(), "")),
|
|
||||||
outputFile.asByteArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
return ReplCompileResult.CompiledClasses(state.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }, state.replSpecific.hasResult)
|
||||||
val scriptClass = classLoaderLock.read { classLoader.loadClass("Line$executionNumber") }
|
|
||||||
|
|
||||||
val constructorParams = earlierLines.map(EarlierLine::getScriptClass).toTypedArray()
|
|
||||||
val constructorArgs = earlierLines.map(EarlierLine::getScriptInstance).toTypedArray()
|
|
||||||
|
|
||||||
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
|
|
||||||
val scriptInstance =
|
|
||||||
try {
|
|
||||||
evalWithIO { scriptInstanceConstructor.newInstance(*constructorArgs) }
|
|
||||||
}
|
|
||||||
catch (e: Throwable) {
|
|
||||||
// ignore everything in the stack trace until this constructor call
|
|
||||||
return EvalResult.Error.Runtime(renderStackTrace(e.cause!!, startFromMethodName = "${scriptClass.name}.<init>"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
|
|
||||||
val rv: Any? = rvField.get(scriptInstance)
|
|
||||||
|
|
||||||
earlierLines.add(EarlierLine(code, scriptDescriptor, scriptClass, scriptInstance))
|
|
||||||
|
|
||||||
return if (state.replSpecific.hasResult) EvalResult.ValueResult(rv) else EvalResult.UnitResult
|
|
||||||
}
|
|
||||||
catch (e: Throwable) {
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override to capture output
|
|
||||||
open fun<T> evalWithIO(body: () -> T): T = body()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderStackTrace(cause: Throwable, startFromMethodName: String): String {
|
|
||||||
val newTrace = arrayListOf<StackTraceElement>()
|
open class GenericRepl(
|
||||||
var skip = true
|
disposable: Disposable,
|
||||||
for ((i, element) in cause.stackTrace.withIndex().reversed()) {
|
scriptDefinition: KotlinScriptDefinition,
|
||||||
if ("${element.className}.${element.methodName}" == startFromMethodName) {
|
compilerConfiguration: CompilerConfiguration,
|
||||||
skip = false
|
messageCollector: MessageCollector
|
||||||
}
|
) : ReplEvaluator, GenericReplCompiler(disposable, scriptDefinition, compilerConfiguration, messageCollector) {
|
||||||
if (!skip) {
|
|
||||||
newTrace.add(element)
|
private val compiledEvaluator = GenericReplCompiledEvaluator(classpath)
|
||||||
|
|
||||||
|
override fun eval(codeLine: ReplCodeLine, history: Iterable<ReplCodeLine>): ReplEvalResult {
|
||||||
|
synchronized(this) {
|
||||||
|
|
||||||
|
val (compiledClasses, hasResult) = compile(codeLine, history).let {
|
||||||
|
when (it) {
|
||||||
|
ReplCompileResult.Incomplete -> return@eval ReplEvalResult.Incomplete
|
||||||
|
is ReplCompileResult.HistoryMismatch -> return@eval ReplEvalResult.HistoryMismatch(it.lineNo)
|
||||||
|
is ReplCompileResult.Error -> return@eval ReplEvalResult.Error.CompileTime(it.message)
|
||||||
|
is ReplCompileResult.CompiledClasses -> it.classes to it.hasResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val resultingTrace = newTrace.reversed().dropLast(1)
|
return compiledEvaluator.eval(codeLine, history, compiledClasses, hasResult)
|
||||||
|
|
||||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UsePropertyAccessSyntax")
|
|
||||||
(cause as java.lang.Throwable).setStackTrace(resultingTrace.toTypedArray())
|
|
||||||
|
|
||||||
return Throwables.getStackTraceAsString(cause)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun dependenciesAdded(classpath: List<File>) {
|
||||||
|
compiledEvaluator.dependenciesAdded(classpath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
|||||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||||
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
import org.jetbrains.kotlin.script.KotlinScriptDefinition
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
@@ -72,20 +74,24 @@ class KotlinJsr232ScriptEngine(
|
|||||||
|
|
||||||
private val repl = object : GenericRepl(disposable, scriptDefinition, compilerConfiguration, messageCollector) {}
|
private val repl = object : GenericRepl(disposable, scriptDefinition, compilerConfiguration, messageCollector) {}
|
||||||
|
|
||||||
private var lineCount = 0L
|
private var lineCount = 0
|
||||||
|
|
||||||
|
private val history = arrayListOf<ReplCodeLine>()
|
||||||
|
|
||||||
override fun eval(script: String, context: ScriptContext?): Any? {
|
override fun eval(script: String, context: ScriptContext?): Any? {
|
||||||
lineCount += 1
|
lineCount += 1
|
||||||
// TODO bind to context
|
// TODO bind to context
|
||||||
val evalResult = repl.eval(lineCount, script)
|
val codeLine = ReplCodeLine(lineCount, script)
|
||||||
|
val evalResult = repl.eval(codeLine, history)
|
||||||
messageCollector.resetAndThrowOnErrors()
|
messageCollector.resetAndThrowOnErrors()
|
||||||
val ret = when (evalResult) {
|
val ret = when (evalResult) {
|
||||||
is GenericRepl.EvalResult.ValueResult -> evalResult.value
|
is ReplEvalResult.ValueResult -> evalResult.value
|
||||||
is GenericRepl.EvalResult.UnitResult -> null
|
is ReplEvalResult.UnitResult -> null
|
||||||
is GenericRepl.EvalResult.Error -> throw ScriptException(evalResult.errorText)
|
is ReplEvalResult.Error -> throw ScriptException(evalResult.message)
|
||||||
is GenericRepl.EvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
is ReplEvalResult.Incomplete -> throw ScriptException("error: incomplete code")
|
||||||
else -> throw ScriptException("Unexpected result from eval call: $evalResult")
|
is ReplEvalResult.HistoryMismatch -> throw ScriptException("Repl history mismatch at line: ${evalResult.lineNo}")
|
||||||
}
|
}
|
||||||
|
history.add(codeLine)
|
||||||
// TODO update context
|
// TODO update context
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiFileFactory
|
|||||||
import com.intellij.psi.impl.PsiFileFactoryImpl
|
import com.intellij.psi.impl.PsiFileFactoryImpl
|
||||||
import com.intellij.testFramework.LightVirtualFile
|
import com.intellij.testFramework.LightVirtualFile
|
||||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||||
|
import org.jetbrains.kotlin.cli.common.repl.ReplClassLoader
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||||
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
|
||||||
|
|||||||
Reference in New Issue
Block a user