Move REPL implementations to the scripting compiler impl module
This commit is contained in:
+54
@@ -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()
|
||||
}
|
||||
+65
@@ -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
|
||||
}
|
||||
+90
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -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"
|
||||
}
|
||||
}
|
||||
+233
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -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)
|
||||
}
|
||||
}
|
||||
+167
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -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()
|
||||
}
|
||||
+57
@@ -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()
|
||||
}
|
||||
}
|
||||
+21
@@ -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
|
||||
}
|
||||
+14
@@ -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()
|
||||
}
|
||||
}
|
||||
+27
@@ -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")
|
||||
}
|
||||
}
|
||||
+23
@@ -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
|
||||
}
|
||||
+46
@@ -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)
|
||||
}
|
||||
}
|
||||
+48
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
+13
@@ -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()
|
||||
}
|
||||
+95
@@ -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
|
||||
}
|
||||
+20
@@ -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) {}
|
||||
}
|
||||
+43
@@ -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)
|
||||
}
|
||||
+19
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user