summaryrefslogtreecommitdiff
path: root/src/repl
diff options
context:
space:
mode:
authorLukas Rytz <lukas.rytz@gmail.com>2016-06-01 13:04:32 +0200
committerLukas Rytz <lukas.rytz@gmail.com>2016-06-01 13:04:32 +0200
commit6c4414ee36186ee0add924b1655c827ee83d180b (patch)
tree649f37934b3a342006f7eda6b2b1082c5f64526f /src/repl
parent8384ebda45724b5a4e47dd2a508ecbbc3bca56f4 (diff)
parent90215ce2b9cb99b5dea7c0ef474eea50755c0a40 (diff)
downloadscala-6c4414ee36186ee0add924b1655c827ee83d180b.tar.gz
scala-6c4414ee36186ee0add924b1655c827ee83d180b.tar.bz2
scala-6c4414ee36186ee0add924b1655c827ee83d180b.zip
Merge commit '90215ce' into merge-2.11-to-2.12-june-1
Diffstat (limited to 'src/repl')
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ILoop.scala192
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala27
-rw-r--r--src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala95
3 files changed, 241 insertions, 73 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
index 56d62f3efc..8f19b4860a 100644
--- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
@@ -1,5 +1,5 @@
/* NSC -- new Scala compiler
- * Copyright 2005-2015 LAMP/EPFL
+ * Copyright 2005-2016 LAMP/EPFL
* @author Alexander Spoon
*/
package scala
@@ -15,7 +15,7 @@ import scala.tools.asm.ClassReader
import scala.util.Properties.jdkHome
import scala.tools.nsc.util.{ ClassPath, stringFromStream }
import scala.reflect.classTag
-import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader }
+import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader, NoPosition }
import ScalaClassLoader._
import scala.reflect.io.File
import scala.tools.util._
@@ -174,10 +174,19 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
echo("\n" + msg)
in.redrawLine()
}
- protected def echo(msg: String) = {
+ protected var mum = false
+ protected def echo(msg: String) = if (!mum) {
out println msg
out.flush()
}
+ // turn off intp reporter and our echo
+ def mumly[A](op: => A): A =
+ if (isReplDebug) op
+ else intp beQuietDuring {
+ val saved = mum
+ mum = true
+ try op finally mum = saved
+ }
/** Search the history */
def searchHistory(_cmdline: String) {
@@ -406,12 +415,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
* command() for each line of input, and stops when
* command() returns false.
*/
- @tailrec final def loop(): LineResult = {
+ final def loop(): LineResult = loop(readOneLine())
+
+ @tailrec final def loop(line: String): LineResult = {
import LineResults._
- readOneLine() match {
- case null => EOF
- case line => if (try processLine(line) catch crashRecovery) loop() else ERR
- }
+ if (line == null) EOF
+ else if (try processLine(line) catch crashRecovery) loop(readOneLine())
+ else ERR
}
/** interpret all lines from a specified file */
@@ -563,9 +573,9 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
}
- def withFile[A](filename: String)(action: File => A): Option[A] = {
+ def withFile[A](filename: String)(action: File => A): Option[A] = intp.withLabel(filename) {
val res = Some(File(filename)) filter (_.exists) map action
- if (res.isEmpty) echo("That file does not exist") // courtesy side-effect
+ if (res.isEmpty) intp.reporter.warning(NoPosition, s"File `$filename' does not exist.") // courtesy side-effect
res
}
@@ -702,6 +712,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
*/
def pasteCommand(arg: String): Result = {
var shouldReplay: Option[String] = None
+ var label = "<pastie>"
def result = Result(keepRunning = true, shouldReplay)
val (raw, file, margin) =
if (arg.isEmpty) (false, None, None)
@@ -722,6 +733,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
val code = (file, margin) match {
case (Some(name), None) =>
+ label = name
withFile(name) { f =>
shouldReplay = Some(s":paste $arg")
val s = f.slurp.trim
@@ -744,17 +756,17 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
text
}
def interpretCode() = {
- val res = intp interpret code
+ val res = intp.withLabel(label)(intp interpret code)
// if input is incomplete, let the compiler try to say why
if (res == IR.Incomplete) {
echo("The pasted code is incomplete!\n")
// Remembrance of Things Pasted in an object
- val errless = intp compileSources new BatchSourceFile("<pastie>", s"object pastel {\n$code\n}")
+ val errless = intp compileSources new BatchSourceFile(label, s"object pastel {\n$code\n}")
if (errless) echo("...but compilation found no error? Good luck with that.")
}
}
def compileCode() = {
- val errless = intp compileSources new BatchSourceFile("<pastie>", code)
+ val errless = intp compileSources new BatchSourceFile(label, code)
if (!errless) echo("There were compilation errors!")
}
if (code.nonEmpty) {
@@ -826,19 +838,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
}
- // runs :load `file` on any files passed via -i
- def loadFiles(settings: Settings) = settings match {
- case settings: GenericRunnerSettings =>
- for (filename <- settings.loadfiles.value) {
- val cmd = ":load " + filename
- command(cmd)
- addReplay(cmd)
- echo("")
- }
- case _ =>
- }
-
- /** Tries to create a JLineReader, falling back to SimpleReader,
+ /** Tries to create a jline.InteractiveReader, falling back to SimpleReader,
* unless settings or properties are such that it should start with SimpleReader.
* The constructor of the InteractiveReader must take a Completion strategy,
* supplied as a `() => Completion`; the Completion object provides a concrete Completer.
@@ -877,49 +877,115 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
}
- private def loopPostInit() {
- // Bind intp somewhere out of the regular namespace where
- // we can get at it in generated code.
- intp.quietBind(NamedParam[IMain]("$intp", intp)(tagOfIMain, classTag[IMain]))
- // Auto-run code via some setting.
- ( replProps.replAutorunCode.option
- flatMap (f => io.File(f).safeSlurp())
- foreach (intp quietRun _)
- )
- // classloader and power mode setup
- intp.setContextClassLoader()
- if (isReplPower) {
- replProps.power setValue true
- unleashAndSetPhase()
- asyncMessage(power.banner)
- }
- // SI-7418 Now, and only now, can we enable TAB completion.
- in.postInit()
- }
-
- // start an interpreter with the given settings
+ /** Start an interpreter with the given settings.
+ * @return true if successful
+ */
def process(settings: Settings): Boolean = savingContextLoader {
- this.settings = settings
- createInterpreter()
+ def newReader = in0.fold(chooseReader(settings))(r => SimpleReader(r, out, interactive = true))
- // sets in to some kind of reader depending on environmental cues
- in = in0.fold(chooseReader(settings))(r => SimpleReader(r, out, interactive = true))
- globalFuture = Future {
- intp.initializeSynchronous()
- loopPostInit()
- !intp.reporter.hasErrors
+ /** Reader to use before interpreter is online. */
+ def preLoop = {
+ val sr = SplashReader(newReader) { r =>
+ in = r
+ in.postInit()
+ }
+ in = sr
+ SplashLoop(sr, prompt)
}
- loadFiles(settings)
- printWelcome()
- try loop() match {
- case LineResults.EOF => out print Properties.shellInterruptedString
- case _ =>
+ /* Actions to cram in parallel while collecting first user input at prompt.
+ * Run with output muted both from ILoop and from the intp reporter.
+ */
+ def loopPostInit(): Unit = mumly {
+ // Bind intp somewhere out of the regular namespace where
+ // we can get at it in generated code.
+ intp.quietBind(NamedParam[IMain]("$intp", intp)(tagOfIMain, classTag[IMain]))
+
+ // Auto-run code via some setting.
+ ( replProps.replAutorunCode.option
+ flatMap (f => File(f).safeSlurp())
+ foreach (intp quietRun _)
+ )
+ // power mode setup
+ if (isReplPower) {
+ replProps.power setValue true
+ unleashAndSetPhase()
+ asyncMessage(power.banner)
+ }
+ loadInitFiles()
+ // SI-7418 Now, and only now, can we enable TAB completion.
+ in.postInit()
}
- catch AbstractOrMissingHandler()
- finally closeInterpreter()
+ def loadInitFiles(): Unit = settings match {
+ case settings: GenericRunnerSettings =>
+ for (f <- settings.loadfiles.value) {
+ loadCommand(f)
+ addReplay(s":load $f")
+ }
+ for (f <- settings.pastefiles.value) {
+ pasteCommand(f)
+ addReplay(s":paste $f")
+ }
+ case _ =>
+ }
+ // wait until after startup to enable noisy settings
+ def withSuppressedSettings[A](body: => A): A = {
+ val ss = this.settings
+ import ss._
+ val noisy = List(Xprint, Ytyperdebug)
+ val noisesome = noisy.exists(!_.isDefault)
+ val current = (Xprint.value, Ytyperdebug.value)
+ if (isReplDebug || !noisesome) body
+ else {
+ this.settings.Xprint.value = List.empty
+ this.settings.Ytyperdebug.value = false
+ try body
+ finally {
+ Xprint.value = current._1
+ Ytyperdebug.value = current._2
+ intp.global.printTypings = current._2
+ }
+ }
+ }
+ def startup(): String = withSuppressedSettings {
+ // starting
+ printWelcome()
+
+ // let them start typing
+ val splash = preLoop
+ splash.start()
- true
+ // while we go fire up the REPL
+ try {
+ createInterpreter()
+ intp.initializeSynchronous()
+ globalFuture = Future successful true
+ if (intp.reporter.hasErrors) {
+ echo("Interpreter encountered errors during initialization!")
+ null
+ } else {
+ loopPostInit()
+ val line = splash.line // what they typed in while they were waiting
+ if (line == null) { // they ^D
+ try out print Properties.shellInterruptedString
+ finally closeInterpreter()
+ }
+ line
+ }
+ } finally splash.stop()
+ }
+ this.settings = settings
+ startup() match {
+ case null => false
+ case line =>
+ try loop(line) match {
+ case LineResults.EOF => out print Properties.shellInterruptedString
+ case _ =>
+ }
+ catch AbstractOrMissingHandler()
+ finally closeInterpreter()
+ true
+ }
}
@deprecated("Use `process` instead", "2.9.0")
diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala
index 763a8ccd1b..2f20a1cd0a 100644
--- a/src/repl/scala/tools/nsc/interpreter/IMain.scala
+++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala
@@ -1,5 +1,5 @@
/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
+ * Copyright 2005-2016 LAMP/EPFL
* @author Martin Odersky
*/
@@ -69,13 +69,14 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
lazy val isClassBased: Boolean = settings.Yreplclassbased.value
- private[nsc] var printResults = true // whether to print result lines
- private[nsc] var totalSilence = false // whether to print anything
- private var _initializeComplete = false // compiler is initialized
- private var _isInitialized: Future[Boolean] = null // set up initialization future
- private var bindExceptions = true // whether to bind the lastException variable
- private var _executionWrapper = "" // code to be wrapped around all lines
- var partialInput: String = "" // code accumulated in multi-line REPL input
+ private[nsc] var printResults = true // whether to print result lines
+ private[nsc] var totalSilence = false // whether to print anything
+ private var _initializeComplete = false // compiler is initialized
+ private var _isInitialized: Future[Boolean] = null // set up initialization future
+ private var bindExceptions = true // whether to bind the lastException variable
+ private var _executionWrapper = "" // code to be wrapped around all lines
+ var partialInput: String = "" // code accumulated in multi-line REPL input
+ private var label = "<console>" // compilation unit name for reporting
/** We're going to go to some trouble to initialize the compiler asynchronously.
* It's critical that nothing call into it until it's been initialized or we will
@@ -103,6 +104,12 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
try body
finally if (!saved) settings.nowarn.value = false
}
+ // Apply a temporary label for compilation (for example, script name)
+ def withLabel[A](temp: String)(body: => A): A = {
+ val saved = label
+ label = temp
+ try body finally label = saved
+ }
// the expanded prompt but without color escapes and without leading newline, for purposes of indenting
lazy val formatting = Formatting.forPrompt(replProps.promptText)
@@ -749,7 +756,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
case Right(result) => Right(result)
}
- def compile(source: String): Boolean = compileAndSaveRun("<console>", source)
+ def compile(source: String): Boolean = compileAndSaveRun(label, source)
/** The innermost object inside the wrapper, found by
* following accessPath into the outer one.
@@ -1098,7 +1105,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
var isIncomplete = false
def parse = {
reporter.reset()
- val trees = newUnitParser(line).parseStats()
+ val trees = newUnitParser(line, label).parseStats()
if (reporter.hasErrors) Error(trees)
else if (isIncomplete) Incomplete(trees)
else Success(trees)
diff --git a/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala
index 71753a3e39..1f81d9965c 100644
--- a/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala
+++ b/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala
@@ -50,3 +50,98 @@ object InteractiveReader {
def createDefault(): InteractiveReader = apply() // used by sbt
}
+/** Collect one line of user input from the supplied reader.
+ * Runs on a new thread while the REPL is initializing on the main thread.
+ *
+ * The user can enter text or a `:paste` command.
+ */
+class SplashLoop(reader: InteractiveReader, prompt: String) extends Runnable {
+ import java.util.concurrent.SynchronousQueue
+ import scala.compat.Platform.EOL
+
+ private val result = new SynchronousQueue[Option[String]]
+ @volatile private var running: Boolean = _
+ private var thread: Thread = _
+
+ /** Read one line of input which can be retrieved with `line`. */
+ def run(): Unit = {
+ var line = ""
+ try
+ do {
+ line = reader.readLine(prompt)
+ if (line != null) {
+ line = process(line.trim)
+ }
+ } while (line != null && line.isEmpty && running)
+ finally {
+ result.put(Option(line))
+ }
+ }
+
+ /** Check for `:paste` command. */
+ private def process(line: String): String = {
+ def isPrefix(s: String, p: String, n: Int) = (
+ //s != null && p.inits.takeWhile(_.length >= n).exists(s == _)
+ s != null && s.length >= n && s.length <= p.length && s == p.take(s.length)
+ )
+ if (isPrefix(line, ":paste", 3)) {
+ // while collecting lines, check running flag
+ var help = f"// Entering paste mode (ctrl-D to finish)%n%n"
+ def readWhile(cond: String => Boolean) = {
+ Iterator continually reader.readLine(help) takeWhile { x =>
+ help = ""
+ x != null && cond(x)
+ }
+ }
+ val text = (readWhile(_ => running) mkString EOL).trim
+ val next =
+ if (text.isEmpty) "// Nothing pasted, nothing gained."
+ else "// Exiting paste mode, now interpreting."
+ Console println f"%n${next}%n"
+ text
+ } else {
+ line
+ }
+ }
+
+ def start(): Unit = result.synchronized {
+ require(thread == null, "Already started")
+ thread = new Thread(this)
+ running = true
+ thread.start()
+ }
+
+ def stop(): Unit = result.synchronized {
+ running = false
+ if (thread != null) thread.interrupt()
+ thread = null
+ }
+
+ /** Block for the result line, or null on ctl-D. */
+ def line: String = result.take getOrElse null
+}
+object SplashLoop {
+ def apply(reader: SplashReader, prompt: String): SplashLoop = new SplashLoop(reader, prompt)
+}
+
+/** Reader during splash. Handles splash-completion with a stub, otherwise delegates. */
+class SplashReader(reader: InteractiveReader, postIniter: InteractiveReader => Unit) extends InteractiveReader {
+ /** Invoke the postInit action with the underlying reader. */
+ override def postInit(): Unit = postIniter(reader)
+
+ override val interactive: Boolean = reader.interactive
+
+ override def reset(): Unit = reader.reset()
+ override def history: History = reader.history
+ override val completion: Completion = NoCompletion
+ override def redrawLine(): Unit = reader.redrawLine()
+
+ override protected[interpreter] def readOneLine(prompt: String): String = ??? // unused
+ override protected[interpreter] def readOneKey(prompt: String): Int = ??? // unused
+
+ override def readLine(prompt: String): String = reader.readLine(prompt)
+}
+object SplashReader {
+ def apply(reader: InteractiveReader)(postIniter: InteractiveReader => Unit) =
+ new SplashReader(reader, postIniter)
+}