diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplConfiguration.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplConfiguration.kt new file mode 100644 index 00000000000..c6406754ce5 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplConfiguration.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.cli.jvm.repl + +import org.jetbrains.kotlin.cli.jvm.repl.messages.* +import org.jetbrains.kotlin.cli.jvm.repl.reader.ConsoleReplCommandReader +import org.jetbrains.kotlin.cli.jvm.repl.reader.IdeReplCommandReader +import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandReader +import java.io.PrintWriter +import java.io.StringWriter + +interface ReplConfiguration { + val writer: ReplWriter + val errorLogger: ReplErrorLogger + val commandReader: ReplCommandReader + val allowIncompleteLines: Boolean + + fun createDiagnosticHolder(): DiagnosticMessageHolder + fun onUserCodeExecuting(isExecuting: Boolean) +} + +class ReplForIdeConfiguration : ReplConfiguration { + override val allowIncompleteLines: Boolean + get() = false + + override fun onUserCodeExecuting(isExecuting: Boolean) { + sinWrapper.isReplScriptExecuting = isExecuting + } + + override fun createDiagnosticHolder() = ReplIdeDiagnosticMessageHolder() + + override val writer: ReplWriter + override val errorLogger: ReplErrorLogger + 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 = ReplSystemOutWrapperForIde(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 + errorLogger = IdeReplErrorLogger(writer) + commandReader = IdeReplCommandReader() + } +} + +class ConsoleReplConfiguration : ReplConfiguration { + override val allowIncompleteLines: Boolean + get() = true + + override fun onUserCodeExecuting(isExecuting: Boolean) { + // do nothing + } + + override fun createDiagnosticHolder() = ReplTerminalDiagnosticMessageHolder() + + override val writer: ReplWriter + override val errorLogger: ReplErrorLogger + override val commandReader: ReplCommandReader + + init { + writer = ReplConsoleWriter() + errorLogger = object : ReplErrorLogger { + override fun logException(e: Throwable): Nothing { + throw e + } + } + + commandReader = ConsoleReplCommandReader() + } +} + +interface ReplErrorLogger { + fun logException(e: Throwable): Nothing +} + +class IdeReplErrorLogger(private val replWriter: ReplWriter) : ReplErrorLogger { + override fun logException(e: Throwable): Nothing { + val errorStringWriter = StringWriter() + val errorPrintWriter = PrintWriter(errorStringWriter) + e.printStackTrace(errorPrintWriter) + + val writerString = errorStringWriter.toString() + val internalErrorText = if (writerString.isEmpty()) "Unknown error" else writerString + + replWriter.sendInternalErrorReport(internalErrorText) + throw e + } +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt index e1026f9ac41..a6f380eee9f 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplFromTerminal.kt @@ -19,115 +19,33 @@ package org.jetbrains.kotlin.cli.jvm.repl import com.intellij.openapi.Disposable import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.cli.common.KotlinVersion -import org.jetbrains.kotlin.cli.jvm.repl.messages.* -import org.jetbrains.kotlin.cli.jvm.repl.reader.ConsoleReplCommandReader -import org.jetbrains.kotlin.cli.jvm.repl.reader.IdeReplCommandReader -import org.jetbrains.kotlin.cli.jvm.repl.reader.ReplCommandReader +import org.jetbrains.kotlin.cli.jvm.repl.messages.unescapeLineBreaks import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.utils.* - import java.io.File import java.io.PrintWriter -import java.util.Arrays +import java.util.* +import java.util.concurrent.Callable +import java.util.concurrent.Executors +import java.util.concurrent.Future class ReplFromTerminal( disposable: Disposable, - compilerConfiguration: CompilerConfiguration -) { + compilerConfiguration: CompilerConfiguration, + replConfiguration: ReplConfiguration +) : ReplConfiguration by replConfiguration { - private var replInterpreter: ReplInterpreter? = null - private var replInitializationFailed: Throwable? = null - private val waitRepl = Object() + private val replInitializer: Future = Executors.newSingleThreadExecutor().submit(Callable { + ReplInterpreter(disposable, compilerConfiguration, replConfiguration) + }) - private val ideMode: Boolean - private var replReader: ReplSystemInWrapper? = null - private val replWriter: ReplWriter - private val replErrorLogger: ReplErrorLogger - - private var commandReader: ReplCommandReader? = null - - init { - val replIdeMode = System.getProperty("kotlin.repl.ideMode") - ideMode = replIdeMode != null && replIdeMode == "true" - - // 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 - if (ideMode) { - val soutWrapper = ReplSystemOutWrapperForIde(System.out) - replWriter = soutWrapper - System.setOut(soutWrapper) - } - else { - replWriter = ReplConsoleWriter() - } - - // wrapper for `in` is required to give user possibility of calling - // [readLine] from ide-console repl - if (ideMode) { - replReader = ReplSystemInWrapper(System.`in`, replWriter) - System.setIn(replReader!!) - } - - replErrorLogger = ReplErrorLogger(ideMode, replWriter) - - object : Thread("initialize-repl") { - override fun run() { - try { - replInterpreter = ReplInterpreter(disposable, compilerConfiguration, ideMode, replReader) - } - catch (e: Throwable) { - replInitializationFailed = e - } - - synchronized (waitRepl) { - waitRepl.notifyAll() - } - } - }.start() - - try { - commandReader = createCommandReader() - } - catch (e: Exception) { - replErrorLogger.logException(e) - } - - } - - private fun createCommandReader(): ReplCommandReader { - return if (ideMode) - IdeReplCommandReader() - else - ConsoleReplCommandReader() - } - - private fun getReplInterpreter(): ReplInterpreter { - if (replInterpreter != null) { - return replInterpreter!! - } - synchronized (waitRepl) { - while (replInterpreter == null && replInitializationFailed == null) { - try { - waitRepl.wait() - } - catch (e: Throwable) { - throw rethrow(e) - } - - } - if (replInterpreter != null) { - return replInterpreter!! - } - throw rethrow(replInitializationFailed!!) - } - } + private val replInterpreter: ReplInterpreter + get() = replInitializer.get() private fun doRun() { try { - replWriter.printlnWelcomeMessage("Welcome to Kotlin version ${KotlinVersion.VERSION} " + - "(JRE ${System.getProperty("java.runtime.version")})") - replWriter.printlnWelcomeMessage("Type :help for help, :quit for quit") + writer.printlnWelcomeMessage("Welcome to Kotlin version ${KotlinVersion.VERSION} " + + "(JRE ${System.getProperty("java.runtime.version")})") + writer.printlnWelcomeMessage("Type :help for help, :quit for quit") var next = WhatNextAfterOneLine.READ_LINE while (true) { next = one(next) @@ -137,14 +55,14 @@ class ReplFromTerminal( } } catch (e: Exception) { - replErrorLogger.logException(e) + errorLogger.logException(e) } finally { try { - commandReader!!.flushHistory() + commandReader.flushHistory() } catch (e: Exception) { - replErrorLogger.logException(e) + errorLogger.logException(e) } } @@ -157,46 +75,40 @@ class ReplFromTerminal( } private fun one(next: WhatNextAfterOneLine): WhatNextAfterOneLine { - try { - var line = commandReader!!.readLine(next) ?: return WhatNextAfterOneLine.QUIT + var line = commandReader.readLine(next) ?: return WhatNextAfterOneLine.QUIT - line = unescapeLineBreaks(line) + line = unescapeLineBreaks(line) - if (line.startsWith(":") && (line.length == 1 || line.get(1) != ':')) { - val notQuit = oneCommand(line.substring(1)) - return if (notQuit) WhatNextAfterOneLine.READ_LINE else WhatNextAfterOneLine.QUIT - } - - val lineResultType = eval(line) - if (lineResultType === ReplInterpreter.LineResultType.INCOMPLETE) { - return WhatNextAfterOneLine.INCOMPLETE - } - else { - return WhatNextAfterOneLine.READ_LINE - } - } - catch (e: Exception) { - throw rethrow(e) + if (line.startsWith(":") && (line.length == 1 || line.get(1) != ':')) { + val notQuit = oneCommand(line.substring(1)) + return if (notQuit) WhatNextAfterOneLine.READ_LINE else WhatNextAfterOneLine.QUIT } + val lineResultType = eval(line) + if (lineResultType === ReplInterpreter.LineResultType.INCOMPLETE) { + return WhatNextAfterOneLine.INCOMPLETE + } + else { + return WhatNextAfterOneLine.READ_LINE + } } private fun eval(line: String): ReplInterpreter.LineResultType { - val lineResult = getReplInterpreter().eval(line) + val lineResult = replInterpreter.eval(line) if (lineResult.type === ReplInterpreter.LineResultType.SUCCESS) { - replWriter.notifyCommandSuccess() + writer.notifyCommandSuccess() if (!lineResult.isUnit) { - replWriter.outputCommandResult(lineResult.value) + writer.outputCommandResult(lineResult.value) } } else if (lineResult.type === ReplInterpreter.LineResultType.INCOMPLETE) { - replWriter.notifyIncomplete() + writer.notifyIncomplete() } else if (lineResult.type === ReplInterpreter.LineResultType.COMPILE_ERROR) { - replWriter.outputCompileError(lineResult.errorText!!) + writer.outputCompileError(lineResult.errorText!!) } else if (lineResult.type === ReplInterpreter.LineResultType.RUNTIME_ERROR) { - replWriter.outputRuntimeError(lineResult.errorText!!) + writer.outputRuntimeError(lineResult.errorText!!) } else { throw IllegalStateException("unknown line result type: " + lineResult) @@ -208,15 +120,15 @@ class ReplFromTerminal( private fun oneCommand(command: String): Boolean { val split = splitCommand(command) if (split.size >= 1 && command == "help") { - replWriter.printlnHelpMessage("Available commands:\n" + - ":help show this help\n" + - ":quit exit the interpreter\n" + - ":dump bytecode dump classes to terminal\n" + - ":load load script from specified file") + writer.printlnHelpMessage("Available commands:\n" + + ":help show this help\n" + + ":quit exit the interpreter\n" + + ":dump bytecode dump classes to terminal\n" + + ":load load script from specified file") return true } else if (split.size >= 2 && split[0] == "dump" && split[1] == "bytecode") { - getReplInterpreter().dumpClasses(PrintWriter(System.out)) + replInterpreter.dumpClasses(PrintWriter(System.out)) return true } else if (split.size >= 1 && split[0] == "quit") { @@ -229,20 +141,26 @@ class ReplFromTerminal( return true } else { - replWriter.printlnHelpMessage("Unknown command\n" + "Type :help for help") + writer.printlnHelpMessage("Unknown command\n" + "Type :help for help") return true } } companion object { - private fun splitCommand(command: String): List { return Arrays.asList(*command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) } fun run(disposable: Disposable, configuration: CompilerConfiguration) { - ReplFromTerminal(disposable, configuration).doRun() + val replIdeMode = System.getProperty("kotlin.repl.ideMode") == "true" + val replConfiguration = if (replIdeMode) ReplForIdeConfiguration() else ConsoleReplConfiguration() + return try { + ReplFromTerminal(disposable, configuration, replConfiguration).doRun() + } + catch (e: Exception) { + replConfiguration.errorLogger.logException(e) + } } } -} +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt index 15e4f820ef0..cc5610ac85d 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/ReplInterpreter.kt @@ -29,10 +29,6 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.Successful import org.jetbrains.kotlin.cli.jvm.repl.CliReplAnalyzerEngine.ReplLineAnalysisResult.WithErrors -import org.jetbrains.kotlin.cli.jvm.repl.messages.DiagnosticMessageHolder -import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplIdeDiagnosticMessageHolder -import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplSystemInWrapper -import org.jetbrains.kotlin.cli.jvm.repl.messages.ReplTerminalDiagnosticMessageHolder import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.CompilationErrorHandler import org.jetbrains.kotlin.codegen.KotlinCodegenFacade @@ -55,9 +51,8 @@ import java.net.URLClassLoader class ReplInterpreter( disposable: Disposable, private val configuration: CompilerConfiguration, - private val ideMode: Boolean, - private val replReader: ReplSystemInWrapper? -) { + private val replConfiguration: ReplConfiguration +): ReplConfiguration by replConfiguration { private var lineNumber = 0 private val earlierLines = arrayListOf() @@ -135,12 +130,6 @@ class ReplInterpreter( } } - private fun createDiagnosticHolder(): DiagnosticMessageHolder = - if (ideMode) - ReplIdeDiagnosticMessageHolder() - else - ReplTerminalDiagnosticMessageHolder() - fun eval(line: String): LineResult { ++lineNumber @@ -158,13 +147,13 @@ class ReplInterpreter( val syntaxErrorReport = AnalyzerWithCompilerReport.reportSyntaxErrors(psiFile, errorHolder) if (syntaxErrorReport.isHasErrors && syntaxErrorReport.isAllErrorsAtEof) { - return if (ideMode) { - LineResult.compileError(errorHolder.renderedDiagnostics) - } - else { + return if (allowIncompleteLines) { previousIncompleteLines.add(line) LineResult.incomplete() } + else { + LineResult.compileError(errorHolder.renderedDiagnostics) + } } previousIncompleteLines.clear() @@ -203,14 +192,14 @@ class ReplInterpreter( val scriptInstanceConstructor = scriptClass.getConstructor(*constructorParams) val scriptInstance = try { - replReader?.isReplScriptExecuting = true + onUserCodeExecuting(true) scriptInstanceConstructor.newInstance(*constructorArgs) } catch (e: Throwable) { return LineResult.runtimeError(renderStackTrace(e.cause!!)) } finally { - replReader?.isReplScriptExecuting = false + onUserCodeExecuting(false) } val rvField = scriptClass.getDeclaredField(SCRIPT_RESULT_FIELD_NAME).apply { isAccessible = true } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplErrorLogger.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplErrorLogger.kt deleted file mode 100644 index 8f3f568ddf9..00000000000 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/repl/messages/ReplErrorLogger.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.cli.jvm.repl.messages - -import org.jetbrains.kotlin.utils.rethrow -import java.io.PrintWriter -import java.io.StringWriter - -class ReplErrorLogger(private val ideMode: Boolean, private val replWriter: ReplWriter) { - fun logException(e: Throwable?) { - if (ideMode) { - val errorStringWriter = StringWriter() - val errorPrintWriter = PrintWriter(errorStringWriter) - e?.printStackTrace(errorPrintWriter) - - val writerString = errorStringWriter.toString() - val internalErrorText = if (writerString.isEmpty()) "Unknown error" else writerString - - replWriter.sendInternalErrorReport(internalErrorText) - } - - throw rethrow(e ?: Throwable("null exception", e)) - } -} \ No newline at end of file