
org.jetbrains.kotlin.cli.jvm.repl.ReplInterpreter.kt Maven / Gradle / Ivy
/*
* 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.jvm.repl
import com.google.common.base.Throwables
import com.intellij.openapi.Disposable
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful
import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors
import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder
import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplIdeDiagnosticMessageHolder
import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplSystemInWrapper
import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptParameter
import org.jetbrains.kotlin.script.StandardScriptDefinition
import java.io.PrintWriter
import java.net.URLClassLoader
class ReplInterpreter(
disposable: Disposable,
configuration: CompilerConfiguration,
private val ideMode: Boolean,
private val replReader: ReplSystemInWrapper?
) {
private var lineNumber = 0
private val earlierLines = arrayListOf()
private val previousIncompleteLines = arrayListOf()
private val classLoader: ReplClassLoader = run {
val classpath = configuration.jvmClasspathRoots.map { it.toURI().toURL() }
ReplClassLoader(URLClassLoader(classpath.toTypedArray(), null))
}
private val environment = run {
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, REPL_LINE_AS_SCRIPT_DEFINITION)
KotlinCoreEnvironment.createForProduction(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private val analyzerEngine = CliReplAnalyzerEngine(environment)
enum class LineResultType {
SUCCESS,
COMPILE_ERROR,
RUNTIME_ERROR,
INCOMPLETE
}
class LineResult private constructor(
private val resultingValue: Any?,
private val unit: Boolean,
val errorText: String?,
val type: LineResultType
) {
val value: Any?
get() {
checkSuccessful()
return resultingValue
}
val isUnit: Boolean
get() {
checkSuccessful()
return unit
}
private fun checkSuccessful() {
if (type != LineResultType.SUCCESS) {
error("it is error")
}
}
companion object {
private fun error(errorText: String, errorType: LineResultType): LineResult {
val resultingErrorText = when {
errorText.isEmpty() -> ""
!errorText.endsWith("\n") -> errorText + "\n"
else -> errorText
}
return LineResult(null, false, resultingErrorText, errorType)
}
fun successful(value: Any?, unit: Boolean): LineResult {
return LineResult(value, unit, null, LineResultType.SUCCESS)
}
fun compileError(errorText: String): LineResult {
return error(errorText, LineResultType.COMPILE_ERROR)
}
fun runtimeError(errorText: String): LineResult {
return error(errorText, LineResultType.RUNTIME_ERROR)
}
fun incomplete(): LineResult {
return LineResult(null, false, null, LineResultType.INCOMPLETE)
}
}
}
private fun createDiagnosticHolder(): DiagnosticMessageHolder =
if (ideMode)
ReplIdeDiagnosticMessageHolder()
else
ReplTerminalDiagnosticMessageHolder()
fun eval(line: String): LineResult {
++lineNumber
val fullText = (previousIncompleteLines + line).joinToString(separator = "\n")
val virtualFile =
LightVirtualFile("line$lineNumber${KotlinParserDefinition.STD_SCRIPT_EXT}", KotlinLanguage.INSTANCE, fullText).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Script file not analyzed at line $lineNumber: $fullText")
val errorHolder = createDiagnosticHolder()
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
if (syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof) {
return if (ideMode) {
LineResult.compileError(errorHolder.renderedDiagnostics)
}
else {
previousIncompleteLines.add(line)
LineResult.incomplete()
}
}
previousIncompleteLines.clear()
if (syntaxErrorReport.isHasErrors) {
return LineResult.compileError(errorHolder.renderedDiagnostics)
}
val analysisResult = analyzerEngine.analyzeReplLine(psiFile, lineNumber)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder, false)
val scriptDescriptor = when (analysisResult) {
is WithErrors -> return LineResult.compileError(errorHolder.renderedDiagnostics)
is Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult.javaClass}")
}
val state = GenerationState(psiFile.project, ClassBuilderFactories.BINARIES, analyzerEngine.module, analyzerEngine.trace.bindingContext, listOf(psiFile))
compileScript(psiFile.script!!, earlierLines.map(EarlierLine::getScriptDescriptor), state, CompilationErrorHandler.THROW_EXCEPTION)
for (outputFile in state.factory.asList()) {
if (outputFile.relativePath.endsWith(".class")) {
classLoader.addClass(JvmClassName.byInternalName(outputFile.relativePath.replaceFirst("\\.class$".toRegex(), "")),
outputFile.asByteArray())
}
}
try {
val scriptClass = classLoader.loadClass("Line$lineNumber")
val constructorParams = earlierLines.map(EarlierLine::getScriptClass).toTypedArray()
val constructorArgs = earlierLines.map(EarlierLine::getScriptInstance).toTypedArray()
val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams)
val scriptInstance = try {
replReader?.isReplScriptExecuting = true
scriptInstanceConstructor.newInstance(*constructorArgs)
}
catch (e: Throwable) {
return LineResult.runtimeError(renderStackTrace(e.cause!!))
}
finally {
replReader?.isReplScriptExecuting = false
}
val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true }
val rv = rvField.get(scriptInstance)
earlierLines.add(EarlierLine(line, scriptDescriptor, scriptClass, scriptInstance))
return LineResult.successful(rv, !state.replSpecific.hasResult)
}
catch (e: Throwable) {
val writer = PrintWriter(System.err)
classLoader.dumpClasses(writer)
writer.flush()
throw e
}
}
fun dumpClasses(out: PrintWriter) {
classLoader.dumpClasses(out)
}
companion object {
private val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
private val REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition {
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = emptyList()
override fun isScript(file: PsiFile): Boolean = StandardScriptDefinition.isScript(file)
override fun getScriptName(script: KtScript): Name = StandardScriptDefinition.getScriptName(script)
}
private fun renderStackTrace(cause: Throwable): String {
val newTrace = arrayListOf()
var skip = true
for ((i, element) in cause.stackTrace.withIndex().reversed()) {
// All our code happens in the script constructor, and no reflection/native code happens in constructors.
// So we ignore everything in the stack trace until the first constructor
if (element.methodName == "") {
skip = false
}
if (!skip) {
newTrace.add(element)
}
}
// throw away last element which contains Line1.kts(Unknown source)
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)
}
fun compileScript(
script: KtScript,
earlierScripts: List,
state: GenerationState,
errorHandler: CompilationErrorHandler
) {
state.replSpecific.scriptResultFieldName = SCRIPT_RESULT_FIELD_NAME
state.replSpecific.earlierScriptsForReplInterpreter = earlierScripts.toList()
state.beforeCompile()
KotlinCodegenFacade.generatePackage(
state,
script.getContainingKtFile().packageFqName,
setOf(script.getContainingKtFile()),
errorHandler
)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy