Move REPL implementations to the scripting compiler impl module

This commit is contained in:
Ilya Chernikov
2019-02-26 12:12:24 +01:00
parent 199b32cad7
commit 4f135a5fe5
48 changed files with 456 additions and 486 deletions
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2017 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.scripting.repl
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtDestructuringDeclarationEntry
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
open class DelegatePackageMemberDeclarationProvider(var delegate: PackageMemberDeclarationProvider) : PackageMemberDeclarationProvider {
// Can't use Kotlin delegate feature because of inability to change delegate object in runtime (KT-5870)
override fun getAllDeclaredSubPackages(nameFilter: (Name) -> Boolean) = delegate.getAllDeclaredSubPackages(nameFilter)
override fun getPackageFiles() = delegate.getPackageFiles()
override fun containsFile(file: KtFile) = delegate.containsFile(file)
override fun getDeclarations(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
) = delegate.getDeclarations(kindFilter, nameFilter)
override fun getFunctionDeclarations(name: Name) = delegate.getFunctionDeclarations(name)
override fun getPropertyDeclarations(name: Name) = delegate.getPropertyDeclarations(name)
override fun getDestructuringDeclarationsEntries(name: Name): Collection<KtDestructuringDeclarationEntry> =
delegate.getDestructuringDeclarationsEntries(name)
override fun getClassOrObjectDeclarations(name: Name) = delegate.getClassOrObjectDeclarations(name)
override fun getScriptDeclarations(name: Name): Collection<KtScriptInfo> = delegate.getScriptDeclarations(name)
override fun getTypeAliasDeclarations(name: Name) = delegate.getTypeAliasDeclarations(name)
override fun getDeclarationNames() = delegate.getDeclarationNames()
}
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl
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.psi.KtFile
import org.jetbrains.kotlin.scripting.repl.messages.DiagnosticMessageHolder
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.script.experimental.dependencies.ScriptDependencies
class ReplCompilerStageHistory(private val state: org.jetbrains.kotlin.scripting.repl.GenericReplCompilerState) : BasicReplStageHistory<ScriptDescriptor>(state.lock) {
override fun reset(): Iterable<ILineId> {
val removedCompiledLines = super.reset()
val removedAnalyzedLines = state.analyzerEngine.reset()
checkConsistent(removedCompiledLines, removedAnalyzedLines)
return removedCompiledLines
}
override fun resetTo(id: ILineId): Iterable<ILineId> {
val removedCompiledLines = super.resetTo(id)
val removedAnalyzedLines = state.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")
}
}
}
}
abstract class GenericReplCheckerState : IReplStageState<ScriptDescriptor> {
// "line" - is the unit of evaluation here, could in fact consists of several character lines
class LineState(
val codeLine: ReplCodeLine,
val psiFile: KtFile,
val errorHolder: DiagnosticMessageHolder
)
var lastLineState: org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState.LineState? = null // for transferring state to the compiler in most typical case
}
class GenericReplCompilerState(environment: KotlinCoreEnvironment, override val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()) :
IReplStageState<ScriptDescriptor>, org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState() {
override val history = org.jetbrains.kotlin.scripting.repl.ReplCompilerStageHistory(this)
override val currentGeneration: Int get() = (history as BasicReplStageHistory<*>).currentGeneration.get()
val analyzerEngine = org.jetbrains.kotlin.scripting.repl.ReplCodeAnalyzer(environment)
var lastDependencies: ScriptDependencies? = null
}
@@ -0,0 +1,90 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
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.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.repl.messages.ConsoleDiagnosticMessageHolder
import kotlin.concurrent.write
const val KOTLIN_REPL_JVM_TARGET_PROPERTY = "kotlin.repl.jvm.target"
open class GenericReplChecker(
disposable: Disposable,
private val scriptDefinition: KotlinScriptDefinition,
private val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCheckAction {
internal val environment = run {
compilerConfiguration.apply {
add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
if (get(JVMConfigurationKeys.JVM_TARGET) == null) {
put(JVMConfigurationKeys.JVM_TARGET,
System.getProperty(KOTLIN_REPL_JVM_TARGET_PROPERTY)?.let { JvmTarget.fromString(it) }
?: System.getProperty("java.specification.version")?.let { JvmTarget.fromString(it) }
?: JvmTarget.DEFAULT)
}
}
KotlinCoreEnvironment.createForProduction(disposable, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
private val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
private fun createDiagnosticHolder() = ConsoleDiagnosticMessageHolder()
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult {
state.lock.write {
val checkerState = state.asState(org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState::class.java)
val scriptFileName = makeScriptBaseName(codeLine)
val virtualFile =
LightVirtualFile(
"$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}",
KotlinLanguage.INSTANCE,
StringUtil.convertLineSeparators(codeLine.code)
).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: error("Script file not analyzed at line ${codeLine.no}: ${codeLine.code}")
val errorHolder = createDiagnosticHolder()
val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder)
if (!syntaxErrorReport.isHasErrors) {
checkerState.lastLineState =
org.jetbrains.kotlin.scripting.repl.GenericReplCheckerState.LineState(codeLine, psiFile, errorHolder)
}
return when {
syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof -> ReplCheckResult.Incomplete()
syntaxErrorReport.isHasErrors -> ReplCheckResult.Error(errorHolder.renderMessage())
else -> ReplCheckResult.Ok()
}
}
}
}
@@ -0,0 +1,126 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
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.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.write
// WARNING: not thread safe, assuming external synchronization
open class GenericReplCompiler(
disposable: Disposable,
scriptDefinition: KotlinScriptDefinition,
private val compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : ReplCompiler {
constructor(
scriptDefinition: KotlinScriptDefinition,
compilerConfiguration: CompilerConfiguration,
messageCollector: MessageCollector
) : this(Disposer.newDisposable(), scriptDefinition, compilerConfiguration, messageCollector)
private val checker =
GenericReplChecker(disposable, scriptDefinition, compilerConfiguration, messageCollector)
override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> =
org.jetbrains.kotlin.scripting.repl.GenericReplCompilerState(checker.environment, lock)
override fun check(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCheckResult = checker.check(state, codeLine)
override fun compile(state: IReplStageState<*>, codeLine: ReplCodeLine): ReplCompileResult {
state.lock.write {
val compilerState = state.asState(org.jetbrains.kotlin.scripting.repl.GenericReplCompilerState::class.java)
val (psiFile, errorHolder) = run {
if (compilerState.lastLineState == null || compilerState.lastLineState!!.codeLine != codeLine) {
val res = checker.check(state, codeLine)
when (res) {
is ReplCheckResult.Incomplete -> return@compile ReplCompileResult.Incomplete()
is ReplCheckResult.Error -> return@compile ReplCompileResult.Error(res.message, res.location)
is ReplCheckResult.Ok -> {
} // continue
}
}
Pair(compilerState.lastLineState!!.psiFile, compilerState.lastLineState!!.errorHolder)
}
val newDependencies = ScriptDependenciesProvider.getInstance(checker.environment.project)?.getScriptDependencies(psiFile)
var classpathAddendum: List<File>? = null
if (compilerState.lastDependencies != newDependencies) {
compilerState.lastDependencies = newDependencies
classpathAddendum = newDependencies?.let { checker.environment.updateClasspath(it.classpath.map(::JvmClasspathRoot)) }
}
val analysisResult = compilerState.analyzerEngine.analyzeReplLine(psiFile, codeLine)
AnalyzerWithCompilerReport.reportDiagnostics(analysisResult.diagnostics, errorHolder)
val scriptDescriptor = when (analysisResult) {
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return ReplCompileResult.Error(errorHolder.renderMessage())
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
else -> error("Unexpected result ${analysisResult::class.java}")
}
val type = (scriptDescriptor as ScriptDescriptor).resultValue?.returnType
val generationState = GenerationState.Builder(
psiFile.project,
ClassBuilderFactories.BINARIES,
compilerState.analyzerEngine.module,
compilerState.analyzerEngine.trace.bindingContext,
listOf(psiFile),
compilerConfiguration
).build()
generationState.replSpecific.resultType = type
generationState.replSpecific.scriptResultFieldName = scriptResultFieldName(codeLine.no)
generationState.replSpecific.earlierScriptsForReplInterpreter = compilerState.history.map { it.item }
generationState.beforeCompile()
KotlinCodegenFacade.generatePackage(
generationState,
psiFile.script!!.containingKtFile.packageFqName,
setOf(psiFile.script!!.containingKtFile),
CompilationErrorHandler.THROW_EXCEPTION
)
val generatedClassname = makeScriptBaseName(codeLine)
compilerState.history.push(LineId(codeLine), scriptDescriptor)
val classes = generationState.factory.asList().map { CompiledClassData(it.relativePath, it.asByteArray()) }
return ReplCompileResult.CompiledClasses(
LineId(codeLine),
compilerState.history.map { it.id },
generatedClassname,
classes,
generationState.replSpecific.hasResult,
classpathAddendum ?: emptyList(),
generationState.replSpecific.resultType?.let {
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(it)
}
)
}
}
companion object {
private const val SCRIPT_RESULT_FIELD_NAME = "\$\$result"
}
}
@@ -0,0 +1,233 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("UNUSED_PARAMETER")
package org.jetbrains.kotlin.scripting.repl
import org.jetbrains.kotlin.cli.common.repl.CompiledReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ILineId
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
import org.jetbrains.kotlin.cli.common.repl.ReplHistory
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingTraceContext
import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer
import org.jetbrains.kotlin.resolve.TopDownAnalysisContext
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.resolve.lazy.data.KtClassLikeInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.*
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.replaceImportingScopes
import org.jetbrains.kotlin.script.ScriptPriorities
class ReplCodeAnalyzer(environment: KotlinCoreEnvironment) {
private val topDownAnalysisContext: TopDownAnalysisContext
private val topDownAnalyzer: LazyTopDownAnalyzer
private val resolveSession: ResolveSession
private val scriptDeclarationFactory: ScriptMutableDeclarationProviderFactory
private val replState = ResettableAnalyzerState()
val module: ModuleDescriptorImpl
val trace: BindingTraceContext = NoScopeRecordCliBindingTrace()
init {
// Module source scope is empty because all binary classes are in the dependency module, and all source classes are guaranteed
// to be found via ResolveSession. The latter is true as long as light classes are not needed in REPL (which is currently true
// because no symbol declared in the REPL session can be used from Java)
val container = TopDownAnalyzerFacadeForJVM.createContainer(
environment.project,
emptyList(),
trace,
environment.configuration,
environment::createPackagePartProvider,
{ _, _ -> ScriptMutableDeclarationProviderFactory() }
)
this.module = container.get()
this.scriptDeclarationFactory = container.get()
this.resolveSession = container.get()
this.topDownAnalysisContext = TopDownAnalysisContext(
TopDownAnalysisMode.TopLevelDeclarations, DataFlowInfoFactory.EMPTY, resolveSession.declarationScopeProvider
)
this.topDownAnalyzer = container.get()
}
interface ReplLineAnalysisResult {
val scriptDescriptor: ClassDescriptorWithResolutionScopes?
val diagnostics: Diagnostics
data class Successful(override val scriptDescriptor: ClassDescriptorWithResolutionScopes, override val diagnostics: Diagnostics) :
ReplLineAnalysisResult
data class WithErrors(override val diagnostics: Diagnostics) :
ReplLineAnalysisResult {
override val scriptDescriptor: ClassDescriptorWithResolutionScopes? get() = null
}
}
fun resetToLine(lineId: ILineId): List<ReplCodeLine> = replState.resetToLine(lineId)
fun reset(): List<ReplCodeLine> = replState.reset()
fun analyzeReplLine(psiFile: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
topDownAnalysisContext.scripts.clear()
trace.clearDiagnostics()
psiFile.script!!.putUserData(ScriptPriorities.PRIORITY_KEY, codeLine.no)
return doAnalyze(psiFile, codeLine)
}
private fun doAnalyze(linePsi: KtFile, codeLine: ReplCodeLine): ReplLineAnalysisResult {
scriptDeclarationFactory.setDelegateFactory(FileBasedDeclarationProviderFactory(resolveSession.storageManager, listOf(linePsi)))
replState.submitLine(linePsi, codeLine)
val context = topDownAnalyzer.analyzeDeclarations(topDownAnalysisContext.topDownAnalysisMode, listOf(linePsi))
val diagnostics = trace.bindingContext.diagnostics
val hasErrors = diagnostics.any { it.severity == Severity.ERROR }
return if (hasErrors) {
replState.lineFailure(linePsi, codeLine)
ReplLineAnalysisResult.WithErrors(diagnostics)
} else {
val scriptDescriptor = context.scripts[linePsi.script]!!
replState.lineSuccess(linePsi, codeLine, scriptDescriptor)
ReplLineAnalysisResult.Successful(scriptDescriptor, diagnostics)
}
}
private class ScriptMutableDeclarationProviderFactory : DeclarationProviderFactory {
private lateinit var delegateFactory: DeclarationProviderFactory
private lateinit var rootPackageProvider: AdaptablePackageMemberDeclarationProvider
fun setDelegateFactory(delegateFactory: DeclarationProviderFactory) {
this.delegateFactory = delegateFactory
val provider = delegateFactory.getPackageMemberDeclarationProvider(FqName.ROOT)!!
try {
rootPackageProvider.addDelegateProvider(provider)
} catch (e: UninitializedPropertyAccessException) {
rootPackageProvider =
AdaptablePackageMemberDeclarationProvider(
provider
)
}
}
override fun getClassMemberDeclarationProvider(classLikeInfo: KtClassLikeInfo): ClassMemberDeclarationProvider {
return delegateFactory.getClassMemberDeclarationProvider(classLikeInfo)
}
override fun getPackageMemberDeclarationProvider(packageFqName: FqName): PackageMemberDeclarationProvider? {
if (packageFqName.isRoot) {
return rootPackageProvider
}
return delegateFactory.getPackageMemberDeclarationProvider(packageFqName)
}
override fun diagnoseMissingPackageFragment(fqName: FqName, file: KtFile?) {
delegateFactory.diagnoseMissingPackageFragment(fqName, file)
}
class AdaptablePackageMemberDeclarationProvider(
private var delegateProvider: PackageMemberDeclarationProvider
) : org.jetbrains.kotlin.scripting.repl.DelegatePackageMemberDeclarationProvider(delegateProvider) {
fun addDelegateProvider(provider: PackageMemberDeclarationProvider) {
delegateProvider = CombinedPackageMemberDeclarationProvider(listOf(provider, delegateProvider))
delegate = delegateProvider
}
}
}
// TODO: merge with org.jetbrains.kotlin.resolve.repl.ReplState when switching to new REPL infrastructure everywhere
// TODO: review its place in the extracted state infrastructure (now the analyzer itself is a part of the state)
class ResettableAnalyzerState {
private val successfulLines = ReplHistory<LineInfo.SuccessfulLine>()
private val submittedLines = hashMapOf<KtFile, LineInfo>()
fun resetToLine(lineId: ILineId): List<ReplCodeLine> {
val removed = successfulLines.resetToLine(lineId.no)
removed.forEach { submittedLines.remove(it.second.linePsi) }
return removed.map { it.first }
}
fun reset(): List<ReplCodeLine> {
submittedLines.clear()
return successfulLines.reset().map { it.first }
}
fun submitLine(ktFile: KtFile, codeLine: ReplCodeLine) {
val line = LineInfo.SubmittedLine(
ktFile,
successfulLines.lastValue()
)
submittedLines[ktFile] = line
ktFile.fileScopesCustomizer = object : FileScopesCustomizer {
override fun createFileScopes(fileScopeFactory: FileScopeFactory): FileScopes {
return lineInfo(ktFile)?.let { computeFileScopes(it, fileScopeFactory) } ?: fileScopeFactory.createScopesForFile(ktFile)
}
}
}
fun lineSuccess(ktFile: KtFile, codeLine: ReplCodeLine, scriptDescriptor: ClassDescriptorWithResolutionScopes) {
val successfulLine = LineInfo.SuccessfulLine(
ktFile,
successfulLines.lastValue(),
scriptDescriptor
)
submittedLines[ktFile] = successfulLine
successfulLines.add(CompiledReplCodeLine(ktFile.name, codeLine), successfulLine)
}
fun lineFailure(ktFile: KtFile, codeLine: ReplCodeLine) {
submittedLines[ktFile] = LineInfo.FailedLine(
ktFile,
successfulLines.lastValue()
)
}
private fun lineInfo(ktFile: KtFile) = submittedLines[ktFile]
// use sealed?
private sealed class LineInfo {
abstract val linePsi: KtFile
abstract val parentLine: SuccessfulLine?
class SubmittedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
class SuccessfulLine(
override val linePsi: KtFile,
override val parentLine: SuccessfulLine?,
val lineDescriptor: ClassDescriptorWithResolutionScopes
) : LineInfo()
class FailedLine(override val linePsi: KtFile, override val parentLine: SuccessfulLine?) : LineInfo()
}
private fun computeFileScopes(lineInfo: LineInfo, fileScopeFactory: FileScopeFactory): FileScopes? {
// create scope that wraps previous line lexical scope and adds imports from this line
val lexicalScopeAfterLastLine = lineInfo.parentLine?.lineDescriptor?.scopeForInitializerResolution ?: return null
val lastLineImports = lexicalScopeAfterLastLine.parentsWithSelf.first { it is ImportingScope } as ImportingScope
val scopesForThisLine = fileScopeFactory.createScopesForFile(lineInfo.linePsi, lastLineImports)
val combinedLexicalScopes = lexicalScopeAfterLastLine.replaceImportingScopes(scopesForThisLine.importingScope)
return FileScopes(combinedLexicalScopes, scopesForThisLine.importingScope, scopesForThisLine.importForceResolver)
}
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
import java.io.PrintWriter
import java.io.StringWriter
interface ReplExceptionReporter {
fun report(e: Throwable)
companion object DoNothing : ReplExceptionReporter {
override fun report(e: Throwable) {}
}
}
class IdeReplExceptionReporter(private val replWriter: ReplWriter) : ReplExceptionReporter {
override fun report(e: Throwable) {
val stringWriter = StringWriter()
val printWriter = PrintWriter(stringWriter)
e.printStackTrace(printWriter)
val writerString = stringWriter.toString()
val internalErrorText = if (writerString.isEmpty()) "Unknown error" else writerString
replWriter.sendInternalErrorReport(internalErrorText)
}
}
@@ -0,0 +1,167 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult
import org.jetbrains.kotlin.cli.common.repl.replUnescapeLineBreaks
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.scripting.repl.configuration.ConsoleReplConfiguration
import org.jetbrains.kotlin.scripting.repl.configuration.IdeReplConfiguration
import org.jetbrains.kotlin.scripting.repl.configuration.ReplConfiguration
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.Future
class ReplFromTerminal(
disposable: Disposable,
compilerConfiguration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val replInitializer: Future<ReplInterpreter> = Executors.newSingleThreadExecutor().submit(Callable {
ReplInterpreter(disposable, compilerConfiguration, replConfiguration)
})
private val replInterpreter: ReplInterpreter
get() = replInitializer.get()
private val writer get() = replConfiguration.writer
private val messageCollector = compilerConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
private fun doRun() {
try {
with(writer) {
printlnWelcomeMessage(
"Welcome to Kotlin version ${KotlinCompilerVersion.VERSION} " +
"(JRE ${System.getProperty("java.runtime.version")})"
)
printlnWelcomeMessage("Type :help for help, :quit for quit")
}
// Display compiler messages related to configuration and CLI arguments, quit if there are errors
val hasErrors = messageCollector.hasErrors()
(messageCollector as? GroupingMessageCollector)?.flush()
if (hasErrors) return
var next = WhatNextAfterOneLine.READ_LINE
while (true) {
next = one(next)
if (next == WhatNextAfterOneLine.QUIT) {
break
}
}
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
} finally {
try {
replConfiguration.commandReader.flushHistory()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
}
}
}
enum class WhatNextAfterOneLine {
READ_LINE,
INCOMPLETE,
QUIT
}
private fun one(next: WhatNextAfterOneLine): WhatNextAfterOneLine {
var line = replConfiguration.commandReader.readLine(next) ?: return WhatNextAfterOneLine.QUIT
line = line.replUnescapeLineBreaks()
if (line.startsWith(":") && (line.length == 1 || line[1] != ':')) {
val notQuit = oneCommand(line.substring(1))
return if (notQuit) WhatNextAfterOneLine.READ_LINE else WhatNextAfterOneLine.QUIT
}
val lineResult = eval(line)
return if (lineResult is ReplEvalResult.Incomplete) {
WhatNextAfterOneLine.INCOMPLETE
} else {
WhatNextAfterOneLine.READ_LINE
}
}
private fun eval(line: String): ReplEvalResult {
val evalResult = replInterpreter.eval(line)
when (evalResult) {
is ReplEvalResult.ValueResult, is ReplEvalResult.UnitResult -> {
writer.notifyCommandSuccess()
if (evalResult is ReplEvalResult.ValueResult) {
writer.outputCommandResult(evalResult.toString())
}
}
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Error.CompileTime -> writer.outputRuntimeError(evalResult.message)
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
}
return evalResult
}
@Throws(Exception::class)
private fun oneCommand(command: String): Boolean {
val split = splitCommand(command)
if (split.isNotEmpty() && command == "help") {
writer.printlnHelpMessage(
"Available commands:\n" +
":help show this help\n" +
":quit exit the interpreter\n" +
":dump bytecode dump classes to terminal\n" +
":load <file> load script from specified file"
)
return true
} else if (split.size >= 2 && split[0] == "dump" && split[1] == "bytecode") {
replInterpreter.dumpClasses(PrintWriter(System.out))
return true
} else if (split.isNotEmpty() && split[0] == "quit") {
return false
} else if (split.size >= 2 && split[0] == "load") {
val fileName = split[1]
try {
val scriptText = FileUtil.loadFile(File(fileName))
eval(scriptText)
} catch (e: IOException) {
writer.outputCompileError("Can not load script: ${e.message}")
}
return true
} else {
writer.printlnHelpMessage("Unknown command\n" + "Type :help for help")
return true
}
}
companion object {
private fun splitCommand(command: String): List<String> {
return Arrays.asList(*command.split(" ".toRegex()).dropLastWhile(String::isEmpty).toTypedArray())
}
fun run(disposable: Disposable, configuration: CompilerConfiguration) {
val replIdeMode = System.getProperty("kotlin.repl.ideMode") == "true"
val replConfiguration = if (replIdeMode) IdeReplConfiguration() else ConsoleReplConfiguration()
return try {
ReplFromTerminal(disposable, configuration, replConfiguration).doRun()
} catch (e: Exception) {
replConfiguration.exceptionReporter.report(e)
throw e
}
}
}
}
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.repl.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.scripting.repl.configuration.ReplConfiguration
import java.io.PrintWriter
import java.net.URLClassLoader
import java.util.concurrent.atomic.AtomicInteger
class ReplInterpreter(
disposable: Disposable,
private val configuration: CompilerConfiguration,
private val replConfiguration: ReplConfiguration
) {
private val lineNumber = AtomicInteger()
private val previousIncompleteLines = arrayListOf<String>()
private val classpathRoots = configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS).mapNotNull { root ->
when (root) {
is JvmModulePathRoot -> root.file // TODO: only add required modules
is JvmClasspathRoot -> root.file
else -> null
}
}
private val classLoader = ReplClassLoader(URLClassLoader(classpathRoots.map { it.toURI().toURL() }.toTypedArray(), null))
private val messageCollector = object : MessageCollector {
private var hasErrors = false
private val messageRenderer = MessageRenderer.WITHOUT_PATHS
override fun clear() {
hasErrors = false
}
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
val msg = messageRenderer.render(severity, message, location).trimEnd()
with(replConfiguration.writer) {
when (severity) {
CompilerMessageSeverity.EXCEPTION -> sendInternalErrorReport(msg)
CompilerMessageSeverity.ERROR -> outputCompileError(msg)
CompilerMessageSeverity.STRONG_WARNING -> {
} // TODO consider reporting this and two below
CompilerMessageSeverity.WARNING -> {
}
CompilerMessageSeverity.INFO -> {
}
else -> {
}
}
}
}
override fun hasErrors(): Boolean = hasErrors
}
// TODO: add script definition with project-based resolving for IDEA repl
private val scriptCompiler: ReplCompiler by lazy {
GenericReplCompiler(
disposable,
REPL_LINE_AS_SCRIPT_DEFINITION,
configuration,
messageCollector
)
}
private val scriptEvaluator: ReplFullEvaluator by lazy {
GenericReplCompilingEvaluator(scriptCompiler, classpathRoots, classLoader, null, ReplRepeatingMode.REPEAT_ANY_PREVIOUS)
}
private val evalState by lazy { scriptEvaluator.createState() }
fun eval(line: String): ReplEvalResult {
val fullText = (previousIncompleteLines + line).joinToString(separator = "\n")
try {
val evalRes = scriptEvaluator.compileAndEval(
evalState,
ReplCodeLine(lineNumber.getAndIncrement(), 0, fullText),
null,
object : InvokeWrapper {
override fun <T> invoke(body: () -> T): T = replConfiguration.executionInterceptor.execute(body)
}
)
when {
evalRes !is ReplEvalResult.Incomplete -> previousIncompleteLines.clear()
replConfiguration.allowIncompleteLines -> previousIncompleteLines.add(line)
else -> return ReplEvalResult.Error.CompileTime("incomplete code")
}
return evalRes
} 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 REPL_LINE_AS_SCRIPT_DEFINITION = object : KotlinScriptDefinition(Any::class) {
override val name = "Kotlin REPL"
}
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.configuration
import org.jetbrains.kotlin.scripting.repl.ReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.messages.ConsoleDiagnosticMessageHolder
import org.jetbrains.kotlin.scripting.repl.reader.ConsoleReplCommandReader
import org.jetbrains.kotlin.scripting.repl.writer.ConsoleReplWriter
class ConsoleReplConfiguration : ReplConfiguration {
override val writer = ConsoleReplWriter()
override val exceptionReporter
get() = ReplExceptionReporter
override val commandReader = ConsoleReplCommandReader()
override val allowIncompleteLines: Boolean
get() = true
override val executionInterceptor
get() = SnippetExecutionInterceptor
override fun createDiagnosticHolder() = ConsoleDiagnosticMessageHolder()
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.configuration
import org.jetbrains.kotlin.scripting.repl.IdeReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.ReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.messages.IdeDiagnosticMessageHolder
import org.jetbrains.kotlin.scripting.repl.reader.IdeReplCommandReader
import org.jetbrains.kotlin.scripting.repl.reader.ReplCommandReader
import org.jetbrains.kotlin.scripting.repl.reader.ReplSystemInWrapper
import org.jetbrains.kotlin.scripting.repl.writer.IdeSystemOutWrapperReplWriter
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
class IdeReplConfiguration : ReplConfiguration {
override val allowIncompleteLines: Boolean
get() = false
override val executionInterceptor: SnippetExecutionInterceptor = object :
SnippetExecutionInterceptor {
override fun <T> execute(block: () -> T): T {
try {
sinWrapper.isReplScriptExecuting = true
return block()
} finally {
sinWrapper.isReplScriptExecuting = false
}
}
}
override fun createDiagnosticHolder() = IdeDiagnosticMessageHolder()
override val writer: ReplWriter
override val exceptionReporter: ReplExceptionReporter
override val commandReader: ReplCommandReader
val sinWrapper: ReplSystemInWrapper
init {
// wrapper for `out` is required to escape every input in [ideMode];
// if [ideMode == false] then just redirects all input to [System.out]
// if user calls [System.setOut(...)] then undefined behaviour
val soutWrapper = IdeSystemOutWrapperReplWriter(System.out)
System.setOut(soutWrapper)
// wrapper for `in` is required to give user possibility of calling
// [readLine] from ide-console repl
sinWrapper = ReplSystemInWrapper(System.`in`, soutWrapper)
System.setIn(sinWrapper)
writer = soutWrapper
exceptionReporter = IdeReplExceptionReporter(writer)
commandReader = IdeReplCommandReader()
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.configuration
import org.jetbrains.kotlin.scripting.repl.ReplExceptionReporter
import org.jetbrains.kotlin.scripting.repl.messages.DiagnosticMessageHolder
import org.jetbrains.kotlin.scripting.repl.reader.ReplCommandReader
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
interface ReplConfiguration {
val writer: ReplWriter
val exceptionReporter: ReplExceptionReporter
val commandReader: ReplCommandReader
val allowIncompleteLines: Boolean
val executionInterceptor: SnippetExecutionInterceptor
fun createDiagnosticHolder(): DiagnosticMessageHolder
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.configuration
interface SnippetExecutionInterceptor {
fun <T> execute(block: () -> T): T
companion object Plain : SnippetExecutionInterceptor {
override fun <T> execute(block: () -> T) = block()
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.messages
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import java.io.ByteArrayOutputStream
import java.io.PrintStream
class ConsoleDiagnosticMessageHolder : MessageCollectorBasedReporter, DiagnosticMessageHolder {
private val outputStream = ByteArrayOutputStream()
override val messageCollector: GroupingMessageCollector = GroupingMessageCollector(
PrintingMessageCollector(PrintStream(outputStream), MessageRenderer.WITHOUT_PATHS, false),
false
)
override fun renderMessage(): String {
messageCollector.flush()
return outputStream.toString("UTF-8")
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2015 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.scripting.repl.messages
import org.jetbrains.kotlin.cli.common.messages.DiagnosticMessageReporter
interface DiagnosticMessageHolder : DiagnosticMessageReporter {
fun renderMessage(): String
}
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.messages
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.w3c.dom.ls.DOMImplementationLS
import javax.xml.parsers.DocumentBuilderFactory
class IdeDiagnosticMessageHolder : DiagnosticMessageHolder {
private val diagnostics = arrayListOf<Pair<Diagnostic, String>>()
override fun report(diagnostic: Diagnostic, file: PsiFile, render: String) {
diagnostics.add(Pair(diagnostic, render))
}
override fun renderMessage(): String {
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val errorReport = docBuilder.newDocument()
val rootElement = errorReport.createElement("report")
errorReport.appendChild(rootElement)
for ((diagnostic, message) in diagnostics) {
val errorRange = DiagnosticUtils.firstRange(diagnostic.textRanges)
val reportEntry = errorReport.createElement("reportEntry")
reportEntry.setAttribute("severity", diagnostic.severity.toString())
reportEntry.setAttribute("rangeStart", errorRange.startOffset.toString())
reportEntry.setAttribute("rangeEnd", errorRange.endOffset.toString())
reportEntry.appendChild(errorReport.createTextNode(StringUtil.escapeXml(message)))
rootElement.appendChild(reportEntry)
}
val domImplementation = errorReport.implementation as DOMImplementationLS
val lsSerializer = domImplementation.createLSSerializer()
return lsSerializer.writeToString(errorReport)
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.reader
import org.jetbrains.kotlin.scripting.repl.ReplFromTerminal
import org.jline.reader.EndOfFileException
import org.jline.reader.LineReader
import org.jline.reader.LineReaderBuilder
import org.jline.reader.UserInterruptException
import org.jline.terminal.TerminalBuilder
import java.io.File
import java.util.logging.Level
import java.util.logging.Logger
class ConsoleReplCommandReader : ReplCommandReader {
private val lineReader = LineReaderBuilder.builder()
.appName("kotlin")
.terminal(TerminalBuilder.terminal())
.variable(LineReader.HISTORY_FILE, File(File(System.getProperty("user.home")), ".kotlinc_history").absolutePath)
.build()
.apply {
setOpt(LineReader.Option.DISABLE_EVENT_EXPANSION)
}
override fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine): String? {
val prompt = if (next == ReplFromTerminal.WhatNextAfterOneLine.INCOMPLETE) "... " else ">>> "
return try {
lineReader.readLine(prompt)
} catch (e: UserInterruptException) {
println("<interrupted>")
System.out.flush()
""
} catch (e: EndOfFileException) {
null
}
}
override fun flushHistory() = lineReader.history.save()
private companion object {
init {
Logger.getLogger("org.jline").level = Level.OFF
}
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.reader
import org.jetbrains.kotlin.scripting.repl.ReplFromTerminal
class IdeReplCommandReader : ReplCommandReader {
override fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine) = readLine()
override fun flushHistory() = Unit
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.reader
import org.jetbrains.kotlin.scripting.repl.ReplFromTerminal
interface ReplCommandReader {
fun readLine(next: ReplFromTerminal.WhatNextAfterOneLine): String?
fun flushHistory()
}
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.reader
import org.jetbrains.kotlin.cli.common.repl.replUnescapeLineBreaks
import org.jetbrains.kotlin.scripting.repl.writer.ReplWriter
import org.w3c.dom.Element
import org.xml.sax.InputSource
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import javax.xml.parsers.DocumentBuilderFactory
class ReplSystemInWrapper(
private val stdin: InputStream,
private val replWriter: ReplWriter
) : InputStream() {
private var isXmlIncomplete = true
private var isLastByteProcessed = false
private var isReadLineStartSent = false
private var byteBuilder = ByteArrayOutputStream()
private var curBytePos = 0
private var inputByteArray = byteArrayOf()
private val isAtBufferEnd: Boolean
get() = curBytePos == inputByteArray.size
@Volatile
var isReplScriptExecuting = false
override fun read(): Int {
if (isLastByteProcessed) {
if (isReplScriptExecuting) {
isReadLineStartSent = false
replWriter.notifyReadLineEnd()
}
isLastByteProcessed = false
return -1
}
while (isXmlIncomplete) {
if (!isReadLineStartSent && isReplScriptExecuting) {
replWriter.notifyReadLineStart()
isReadLineStartSent = true
}
byteBuilder.write(stdin.read())
if (byteBuilder.toString().endsWith('\n')) {
isXmlIncomplete = false
isLastByteProcessed = false
inputByteArray = parseInput().toByteArray()
}
}
val nextByte = inputByteArray[curBytePos++].toInt()
resetBufferIfNeeded()
return nextByte
}
private fun parseInput(): String {
val xmlInput = byteBuilder.toString()
val unescapedXml = parseXml(xmlInput)
return if (isReplScriptExecuting)
unescapedXml.replUnescapeLineBreaks()
else
unescapedXml
}
private fun resetBufferIfNeeded() {
if (isAtBufferEnd) {
isXmlIncomplete = true
byteBuilder = ByteArrayOutputStream()
curBytePos = 0
isLastByteProcessed = true
}
}
}
private fun parseXml(inputMessage: String): String {
fun strToSource(s: String) = InputSource(ByteArrayInputStream(s.toByteArray()))
val docFactory = DocumentBuilderFactory.newInstance()
val docBuilder = docFactory.newDocumentBuilder()
val input = docBuilder.parse(strToSource(inputMessage))
val root = input.firstChild as Element
return root.textContent
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.writer
class ConsoleReplWriter : ReplWriter {
override fun printlnWelcomeMessage(x: String) = println(x)
override fun printlnHelpMessage(x: String) = println(x)
override fun outputCompileError(x: String) = println(x)
override fun outputCommandResult(x: String) = println(x)
override fun outputRuntimeError(x: String) = println(x)
override fun notifyReadLineStart() {}
override fun notifyReadLineEnd() {}
override fun notifyIncomplete() {}
override fun notifyCommandSuccess() {}
override fun sendInternalErrorReport(x: String) {}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.writer
import org.jetbrains.kotlin.cli.common.repl.replAddLineBreak
import org.jetbrains.kotlin.cli.common.repl.replOutputAsXml
import org.jetbrains.kotlin.utils.repl.ReplEscapeType
import org.jetbrains.kotlin.utils.repl.ReplEscapeType.*
import java.io.PrintStream
class IdeSystemOutWrapperReplWriter(standardOut: PrintStream) : PrintStream(standardOut, true),
ReplWriter {
override fun print(x: Boolean) = printWithEscaping(x.toString())
override fun print(x: Char) = printWithEscaping(x.toString())
override fun print(x: Int) = printWithEscaping(x.toString())
override fun print(x: Long) = printWithEscaping(x.toString())
override fun print(x: Float) = printWithEscaping(x.toString())
override fun print(x: Double) = printWithEscaping(x.toString())
override fun print(x: String) = printWithEscaping(x)
override fun print(x: Any?) = printWithEscaping(x.toString())
private fun printlnWithEscaping(text: String, escapeType: ReplEscapeType = USER_OUTPUT) {
printWithEscaping("$text\n", escapeType)
}
private fun printWithEscaping(text: String, escapeType: ReplEscapeType = USER_OUTPUT) {
super.print(text.replOutputAsXml(escapeType).replAddLineBreak())
}
override fun printlnWelcomeMessage(x: String) = printlnWithEscaping(x, INITIAL_PROMPT)
override fun printlnHelpMessage(x: String) = printlnWithEscaping(x, HELP_PROMPT)
override fun outputCommandResult(x: String) = printlnWithEscaping(x, REPL_RESULT)
override fun notifyReadLineStart() = printlnWithEscaping("", READLINE_START)
override fun notifyReadLineEnd() = printlnWithEscaping("", READLINE_END)
override fun notifyCommandSuccess() = printlnWithEscaping("", SUCCESS)
override fun notifyIncomplete() = printlnWithEscaping("", REPL_INCOMPLETE)
override fun outputCompileError(x: String) = printlnWithEscaping(x, COMPILE_ERROR)
override fun outputRuntimeError(x: String) = printlnWithEscaping(x, RUNTIME_ERROR)
override fun sendInternalErrorReport(x: String) = printlnWithEscaping(x, INTERNAL_ERROR)
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.repl.writer
interface ReplWriter {
fun printlnWelcomeMessage(x: String)
fun printlnHelpMessage(x: String)
fun outputCommandResult(x: String)
fun notifyReadLineStart()
fun notifyReadLineEnd()
fun notifyIncomplete()
fun notifyCommandSuccess()
fun outputCompileError(x: String)
fun outputRuntimeError(x: String)
fun sendInternalErrorReport(x: String)
}