Skip to content

Commit 62c3ace

Browse files
committed
Merge pull request scala#1573 from paulp/repl-checkin
Revamp of some repl mechanisms.
2 parents 4e325da + c7a2e39 commit 62c3ace

File tree

16 files changed

+550
-607
lines changed

16 files changed

+550
-607
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,8 +1525,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
15251525
val shown = if (settings.verbose.value)
15261526
stackTraceString(ex)
15271527
else
1528-
ex.getClass.getName
1529-
// ex.printStackTrace(Console.out) // DEBUG for fsc, note that error stacktraces do not print in fsc
1528+
stackTraceHeadString(ex) // note that error stacktraces do not print in fsc
1529+
15301530
globalError(supplementErrorMessage("uncaught exception during compilation: " + shown))
15311531
throw ex
15321532
}

src/compiler/scala/tools/nsc/interpreter/ILoop.scala

Lines changed: 77 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package interpreter
99
import Predef.{ println => _, _ }
1010
import java.io.{ BufferedReader, FileReader }
1111
import session._
12-
import scala.util.Properties.{ jdkHome, javaVersion }
12+
import scala.util.Properties.{ jdkHome, javaVersion, versionString, javaVmName }
1313
import scala.tools.util.{ Javap }
1414
import util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream }
1515
import io.{ File, Directory }
@@ -20,6 +20,8 @@ import scala.tools.util._
2020
import scala.language.{implicitConversions, existentials}
2121
import scala.reflect.classTag
2222
import scala.tools.reflect.StdRuntimeTags._
23+
import scala.concurrent.{ ExecutionContext, Await, Future, future }
24+
import ExecutionContext.Implicits._
2325

2426
/** The Scala interactive shell. It provides a read-eval-print loop
2527
* around the Interpreter class.
@@ -36,64 +38,32 @@ import scala.tools.reflect.StdRuntimeTags._
3638
class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
3739
extends AnyRef
3840
with LoopCommands
39-
with ILoopInit
4041
{
4142
def this(in0: BufferedReader, out: JPrintWriter) = this(Some(in0), out)
4243
def this() = this(None, new JPrintWriter(Console.out, true))
4344

44-
var in: InteractiveReader = _ // the input stream from which commands come
45-
var settings: Settings = _
46-
var intp: IMain = _
47-
4845
@deprecated("Use `intp` instead.", "2.9.0") def interpreter = intp
4946
@deprecated("Use `intp` instead.", "2.9.0") def interpreter_= (i: Interpreter): Unit = intp = i
5047

51-
/** Having inherited the difficult "var-ness" of the repl instance,
52-
* I'm trying to work around it by moving operations into a class from
53-
* which it will appear a stable prefix.
54-
*/
55-
private def onIntp[T](f: IMain => T): T = f(intp)
56-
57-
class IMainOps[T <: IMain](val intp: T) {
58-
import intp._
59-
import global._
60-
61-
def printAfterTyper(msg: => String) =
62-
intp.reporter printUntruncatedMessage exitingTyper(msg)
63-
64-
/** Strip NullaryMethodType artifacts. */
65-
private def replInfo(sym: Symbol) = {
66-
sym.info match {
67-
case NullaryMethodType(restpe) if sym.isAccessor => restpe
68-
case info => info
69-
}
70-
}
71-
def echoTypeStructure(sym: Symbol) =
72-
printAfterTyper("" + deconstruct.show(replInfo(sym)))
48+
var in: InteractiveReader = _ // the input stream from which commands come
49+
var settings: Settings = _
50+
var intp: IMain = _
7351

74-
def echoTypeSignature(sym: Symbol, verbose: Boolean) = {
75-
if (verbose) ILoop.this.echo("// Type signature")
76-
printAfterTyper("" + replInfo(sym))
52+
private var globalFuture: Future[Boolean] = _
7753

78-
if (verbose) {
79-
ILoop.this.echo("\n// Internal Type structure")
80-
echoTypeStructure(sym)
81-
}
82-
}
54+
/** Print a welcome message */
55+
def printWelcome() {
56+
echo(s"""
57+
|Welcome to Scala $versionString ($javaVmName, Java $javaVersion).
58+
|Type in expressions to have them evaluated.
59+
|Type :help for more information.""".trim.stripMargin
60+
)
61+
replinfo("[info] started at " + new java.util.Date)
8362
}
84-
implicit def stabilizeIMain(intp: IMain) = new IMainOps[intp.type](intp)
8563

86-
/** TODO -
87-
* -n normalize
88-
* -l label with case class parameter names
89-
* -c complete - leave nothing out
90-
*/
91-
private def typeCommandInternal(expr: String, verbose: Boolean): Result = {
92-
onIntp { intp =>
93-
val sym = intp.symbolOfLine(expr)
94-
if (sym.exists) intp.echoTypeSignature(sym, verbose)
95-
else ""
96-
}
64+
protected def asyncMessage(msg: String) {
65+
if (isReplInfo || isReplPower)
66+
echoAndRefresh(msg)
9767
}
9868

9969
override def echoCommandMessage(msg: String) {
@@ -251,7 +221,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
251221
historyCommand,
252222
cmd("h?", "<string>", "search the history", searchHistory),
253223
cmd("imports", "[name name ...]", "show import history, identifying sources of names", importsCommand),
254-
cmd("implicits", "[-v]", "show the implicits in scope", implicitsCommand),
224+
cmd("implicits", "[-v]", "show the implicits in scope", intp.implicitsCommand),
255225
cmd("javap", "<path|class>", "disassemble a file or class name", javapCommand),
256226
cmd("load", "<path>", "load and interpret a Scala file", loadCommand),
257227
nullary("paste", "enter paste mode: all input up to ctrl-D compiled together", pasteCommand),
@@ -294,63 +264,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
294264
}
295265
}
296266

297-
private def implicitsCommand(line: String): Result = onIntp { intp =>
298-
import intp._
299-
import global._
300-
301-
def p(x: Any) = intp.reporter.printMessage("" + x)
302-
303-
// If an argument is given, only show a source with that
304-
// in its name somewhere.
305-
val args = line split "\\s+"
306-
val filtered = intp.implicitSymbolsBySource filter {
307-
case (source, syms) =>
308-
(args contains "-v") || {
309-
if (line == "") (source.fullName.toString != "scala.Predef")
310-
else (args exists (source.name.toString contains _))
311-
}
312-
}
313-
314-
if (filtered.isEmpty)
315-
return "No implicits have been imported other than those in Predef."
316-
317-
filtered foreach {
318-
case (source, syms) =>
319-
p("/* " + syms.size + " implicit members imported from " + source.fullName + " */")
320-
321-
// This groups the members by where the symbol is defined
322-
val byOwner = syms groupBy (_.owner)
323-
val sortedOwners = byOwner.toList sortBy { case (owner, _) => exitingTyper(source.info.baseClasses indexOf owner) }
324-
325-
sortedOwners foreach {
326-
case (owner, members) =>
327-
// Within each owner, we cluster results based on the final result type
328-
// if there are more than a couple, and sort each cluster based on name.
329-
// This is really just trying to make the 100 or so implicits imported
330-
// by default into something readable.
331-
val memberGroups: List[List[Symbol]] = {
332-
val groups = members groupBy (_.tpe.finalResultType) toList
333-
val (big, small) = groups partition (_._2.size > 3)
334-
val xss = (
335-
(big sortBy (_._1.toString) map (_._2)) :+
336-
(small flatMap (_._2))
337-
)
338-
339-
xss map (xs => xs sortBy (_.name.toString))
340-
}
341-
342-
val ownerMessage = if (owner == source) " defined in " else " inherited from "
343-
p(" /* " + members.size + ownerMessage + owner.fullName + " */")
344-
345-
memberGroups foreach { group =>
346-
group foreach (s => p(" " + intp.symbolDefString(s)))
347-
p("")
348-
}
349-
}
350-
p("")
351-
}
352-
}
353-
354267
private def findToolsJar() = {
355268
val jdkPath = Directory(jdkHome)
356269
val jar = jdkPath / "lib" / "tools.jar" toFile;
@@ -376,32 +289,12 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
376289
}
377290
}
378291

379-
protected def newJavap() = new JavapClass(addToolsJarToLoader(), new IMain.ReplStrippingWriter(intp)) {
380-
override def tryClass(path: String): Array[Byte] = {
381-
val hd :: rest = path split '.' toList;
382-
// If there are dots in the name, the first segment is the
383-
// key to finding it.
384-
if (rest.nonEmpty) {
385-
intp optFlatName hd match {
386-
case Some(flat) =>
387-
val clazz = flat :: rest mkString NAME_JOIN_STRING
388-
val bytes = super.tryClass(clazz)
389-
if (bytes.nonEmpty) bytes
390-
else super.tryClass(clazz + MODULE_SUFFIX_STRING)
391-
case _ => super.tryClass(path)
392-
}
393-
}
394-
else {
395-
// Look for Foo first, then Foo$, but if Foo$ is given explicitly,
396-
// we have to drop the $ to find object Foo, then tack it back onto
397-
// the end of the flattened name.
398-
def className = intp flatName path
399-
def moduleName = (intp flatName path.stripSuffix(MODULE_SUFFIX_STRING)) + MODULE_SUFFIX_STRING
400-
401-
val bytes = super.tryClass(className)
402-
if (bytes.nonEmpty) bytes
403-
else super.tryClass(moduleName)
404-
}
292+
protected def newJavap() = {
293+
val intp = ILoop.this.intp
294+
import intp._
295+
296+
new JavapClass(addToolsJarToLoader(), new IMain.ReplStrippingWriter(intp)) {
297+
override def tryClass(path: String) = super.tryClass(translatePath(path) getOrElse path)
405298
}
406299
}
407300
private lazy val javap = substituteAndLog[Javap]("javap", NoJavap)(newJavap())
@@ -410,8 +303,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
410303
private def typeCommand(line0: String): Result = {
411304
line0.trim match {
412305
case "" => ":type [-v] <expression>"
413-
case s if s startsWith "-v " => typeCommandInternal(s stripPrefix "-v " trim, true)
414-
case s => typeCommandInternal(s, false)
306+
case s if s startsWith "-v " => intp.typeCommandInternal(s stripPrefix "-v " trim, true)
307+
case s => intp.typeCommandInternal(s, false)
415308
}
416309
}
417310

@@ -436,7 +329,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
436329
}
437330
}
438331

439-
private def pathToPhaseWrapper = intp.pathToTerm("$r") + ".phased.atCurrent"
332+
private def pathToPhaseWrapper = intp.originalPath("$r") + ".phased.atCurrent"
333+
440334
private def phaseCommand(name: String): Result = {
441335
val phased: Phased = power.phased
442336
import phased.NoPhaseName
@@ -495,33 +389,30 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
495389
true
496390
}
497391

392+
// return false if repl should exit
393+
def processLine(line: String): Boolean = {
394+
import scala.concurrent.duration._
395+
Await.ready(globalFuture, 60.seconds)
396+
397+
(line ne null) && (command(line) match {
398+
case Result(false, _) => false
399+
case Result(_, Some(line)) => addReplay(line) ; true
400+
case _ => true
401+
})
402+
}
403+
404+
private def readOneLine() = {
405+
out.flush()
406+
in readLine prompt
407+
}
408+
498409
/** The main read-eval-print loop for the repl. It calls
499410
* command() for each line of input, and stops when
500411
* command() returns false.
501412
*/
502-
def loop() {
503-
def readOneLine() = {
504-
out.flush()
505-
in readLine prompt
506-
}
507-
// return false if repl should exit
508-
def processLine(line: String): Boolean = {
509-
if (isAsync) {
510-
if (!awaitInitialized()) return false
511-
runThunks()
512-
}
513-
if (line eq null) false // assume null means EOF
514-
else command(line) match {
515-
case Result(false, _) => false
516-
case Result(_, Some(finalLine)) => addReplay(finalLine) ; true
517-
case _ => true
518-
}
519-
}
520-
def innerLoop() {
521-
if ( try processLine(readOneLine()) catch crashRecovery )
522-
innerLoop()
523-
}
524-
innerLoop()
413+
@tailrec final def loop() {
414+
if ( try processLine(readOneLine()) catch crashRecovery )
415+
loop()
525416
}
526417

527418
/** interpret all lines from a specified file */
@@ -767,45 +658,40 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
767658
SimpleReader()
768659
}
769660
}
770-
def process(settings: Settings): Boolean = savingContextLoader {
771-
this.settings = settings
772-
createInterpreter()
773661

774-
// sets in to some kind of reader depending on environmental cues
775-
in = in0 match {
776-
case Some(reader) => SimpleReader(reader, out, true)
777-
case None =>
778-
// some post-initialization
779-
chooseReader(settings) match {
780-
case x: JLineReader => addThunk(x.consoleReader.postInit) ; x
781-
case x => x
782-
}
662+
private def loopPostInit() {
663+
in match {
664+
case x: JLineReader => x.consoleReader.postInit
665+
case _ =>
783666
}
784667
// Bind intp somewhere out of the regular namespace where
785668
// we can get at it in generated code.
786-
addThunk(intp.quietBind(NamedParam[IMain]("$intp", intp)(tagOfIMain, classTag[IMain])))
787-
addThunk({
788-
val autorun = replProps.replAutorunCode.option flatMap (f => io.File(f).safeSlurp())
789-
if (autorun.isDefined) intp.quietRun(autorun.get)
790-
})
791-
792-
loadFiles(settings)
793-
// it is broken on startup; go ahead and exit
794-
if (intp.reporter.hasErrors)
795-
return false
796-
797-
// This is about the illusion of snappiness. We call initialize()
798-
// which spins off a separate thread, then print the prompt and try
799-
// our best to look ready. The interlocking lazy vals tend to
800-
// inter-deadlock, so we break the cycle with a single asynchronous
801-
// message to an actor.
802-
if (isAsync) {
803-
intp initialize initializedCallback()
804-
createAsyncListener() // listens for signal to run postInitialization
669+
intp.quietBind(NamedParam[IMain]("$intp", intp)(tagOfIMain, classTag[IMain]))
670+
// Auto-run code via some setting.
671+
( replProps.replAutorunCode.option
672+
flatMap (f => io.File(f).safeSlurp())
673+
foreach (intp quietRun _)
674+
)
675+
// classloader and power mode setup
676+
intp.setContextClassLoader
677+
if (isReplPower) {
678+
replProps.power setValue true
679+
unleashAndSetPhase()
680+
asyncMessage(power.banner)
805681
}
806-
else {
682+
}
683+
def process(settings: Settings): Boolean = savingContextLoader {
684+
this.settings = settings
685+
createInterpreter()
686+
var thunks: List[() => Unit] = Nil
687+
688+
// sets in to some kind of reader depending on environmental cues
689+
in = in0.fold(chooseReader(settings))(r => SimpleReader(r, out, true))
690+
globalFuture = future {
807691
intp.initializeSynchronous()
808-
postInitialization()
692+
loopPostInit()
693+
loadFiles(settings)
694+
!intp.reporter.hasErrors
809695
}
810696
printWelcome()
811697

0 commit comments

Comments
 (0)