From edad717cc1934d80dc0b6a9af528eed8ef4b30b6 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 31 May 2011 05:08:25 +0000 Subject: Working on the finer points of the Total Repl E... Working on the finer points of the Total Repl Experience. Some of what is in this patch: -- Instantaneous repl startup. Closes #4561. -- SIGINT handler installed last. If you've been annoyed at the repl being difficult to interrupt during its initialization, this one is especially for you. -- Started abstracting out some bits which would go into an API and reconfiguring the repl in those terms. For a good time, call S-C-A-L-A -Dscala.repl.power. No review. --- .../scala/tools/nsc/interpreter/CodeHandlers.scala | 71 +++++++++ .../scala/tools/nsc/interpreter/ExprTyper.scala | 125 +++++++++++++++ .../scala/tools/nsc/interpreter/ILoop.scala | 110 +++++-------- .../scala/tools/nsc/interpreter/ILoopInit.scala | 124 +++++++++++++++ .../scala/tools/nsc/interpreter/IMain.scala | 170 +++------------------ .../scala/tools/nsc/interpreter/JLineReader.scala | 42 ++--- .../scala/tools/nsc/interpreter/ReplReporter.scala | 22 +++ .../scala/tools/nsc/interpreter/package.scala | 2 + 8 files changed, 431 insertions(+), 235 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala new file mode 100644 index 0000000000..42a47896a2 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/CodeHandlers.scala @@ -0,0 +1,71 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import CodeHandlers.NoSuccess +import scala.util.control.ControlThrowable + +/** + * The start of a simpler interface for utilizing the compiler with piecemeal + * code strings. The "T" here could potentially be a Tree, a Type, a Symbol, + * a Boolean, or something even more exotic. + */ +trait CodeHandlers[T] { + self => + + // Expressions are composed of operators and operands. + def expr(code: String): T + + // A declaration introduces names and assigns them types. + // It can form part of a class definition (§5.1) or of a refinement in a compound type (§3.2.7). + // (Ed: aka abstract members.) + // + // ‘val’ ValDcl | ‘var’ VarDcl | ‘def’ FunDcl | ‘type’ {nl} TypeDcl + def decl(code: String): T + + // A definition introduces names that denote terms or types. + // It can form part of an object or class definition or it can be local to a block. + // (Ed: aka concrete members.) + // + // ‘val’ PatDef | ‘var’ VarDef | ‘def’ FunDef | ‘type’ {nl} TypeDef | + // [‘case’] ‘class’ ClassDef | [‘case’] ‘object’ ObjectDef | ‘trait’ TraitDef + def defn(code: String): T + + // An import clause has the form import p.I where p is a stable identifier (§3.1) and I is an import expression. + def impt(code: String): T + + // Statements occur as parts of blocks and templates. + // A statement can be an import, a definition or an expression, or it can be empty. + // Statements used in the template of a class definition can also be declarations. + def stmt(code: String): T + def stmts(code: String): Seq[T] + + object opt extends CodeHandlers[Option[T]] { + val handler: PartialFunction[Throwable, Option[T]] = { + case _: NoSuccess => None + } + val handlerSeq: PartialFunction[Throwable, Seq[Option[T]]] = { + case _: NoSuccess => Nil + } + + def expr(code: String) = try Some(self.expr(code)) catch handler + def decl(code: String) = try Some(self.decl(code)) catch handler + def defn(code: String) = try Some(self.defn(code)) catch handler + def impt(code: String) = try Some(self.impt(code)) catch handler + def stmt(code: String) = try Some(self.stmt(code)) catch handler + def stmts(code: String) = try (self.stmts(code) map (x => Some(x))) catch handlerSeq + } +} + +object CodeHandlers { + def incomplete() = throw CodeIncomplete + def fail(msg: String) = throw new CodeException(msg) + + trait NoSuccess extends ControlThrowable + class CodeException(msg: String) extends RuntimeException(msg) with NoSuccess { } + object CodeIncomplete extends CodeException("CodeIncomplete") +} diff --git a/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala b/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala new file mode 100644 index 0000000000..e47eefa85c --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ExprTyper.scala @@ -0,0 +1,125 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import util.BatchSourceFile +import ast.parser.Tokens.EOF + +trait ExprTyper { + val repl: IMain + import repl._ + import global.{ reporter => _, _ } + import syntaxAnalyzer.UnitParser + import naming.freshInternalVarName + + object codeParser extends { val global: repl.global.type = repl.global } with CodeHandlers[Tree] { + def applyRule[T](code: String, rule: UnitParser => T): T = { + reporter.reset() + val unit = new CompilationUnit(new BatchSourceFile("", code)) + val scanner = new UnitParser(unit) + val result = rule(scanner) + if (!reporter.hasErrors) + scanner.accept(EOF) + + result + } + + def decl(code: String) = CodeHandlers.fail("todo") + def defn(code: String) = CodeHandlers.fail("todo") + def expr(code: String) = applyRule(code, _.expr()) + def impt(code: String) = applyRule(code, _.importExpr()) + def impts(code: String) = applyRule(code, _.importClause()) + def stmts(code: String) = applyRule(code, _.templateStatSeq(false)._2) + def stmt(code: String) = stmts(code) match { + case List(t) => t + case xs => CodeHandlers.fail("Not a single statement: " + xs.mkString(", ")) + } + } + + /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ + def parse(line: String): Option[List[Tree]] = { + var isIncomplete = false + reporter.withIncompleteHandler((_, _) => isIncomplete = true) { + val trees = codeParser.stmts(line) + if (reporter.hasErrors) Some(Nil) + else if (isIncomplete) None + else Some(trees) + } + } + + // TODO: integrate these into a CodeHandler[Type]. + + // XXX literals. + // 1) Identifiers defined in the repl. + // 2) A path loadable via getModule. + // 3) Try interpreting it as an expression. + private var typeOfExpressionDepth = 0 + def typeOfExpression(expr: String, silent: Boolean = true): Option[Type] = { + repltrace("typeOfExpression(" + expr + ")") + if (typeOfExpressionDepth > 2) { + repldbg("Terminating typeOfExpression recursion for expression: " + expr) + return None + } + + def asQualifiedImport = { + val name = expr.takeWhile(_ != '.') + importedTermNamed(name) flatMap { sym => + typeOfExpression(sym.fullName + expr.drop(name.length), true) + } + } + def asModule = safeModule(expr) map (_.tpe) + def asExpr = { + val lhs = freshInternalVarName() + val line = "lazy val " + lhs + " =\n" + expr + + interpret(line, true) match { + case IR.Success => typeOfExpression(lhs, true) + case _ => None + } + } + def evaluate() = { + typeOfExpressionDepth += 1 + try typeOfTerm(expr) orElse asModule orElse asExpr orElse asQualifiedImport + finally typeOfExpressionDepth -= 1 + } + + // Don't presently have a good way to suppress undesirable success output + // while letting errors through, so it is first trying it silently: if there + // is an error, and errors are desired, then it re-evaluates non-silently + // to induce the error message. + beSilentDuring(evaluate()) orElse beSilentDuring(typeOfDeclaration(expr)) orElse { + if (!silent) + evaluate() + + None + } + } + // Since people will be giving us ":t def foo = 5" even though that is not an + // expression, we have a means of typing declarations too. + private def typeOfDeclaration(code: String): Option[Type] = { + repltrace("typeOfDeclaration(" + code + ")") + val obname = freshInternalVarName() + + interpret("object " + obname + " {\n" + code + "\n}\n", true) match { + case IR.Success => + val sym = symbolOfTerm(obname) + if (sym == NoSymbol) None else { + // TODO: bitmap$n is not marked synthetic. + val decls = sym.tpe.decls.toList filterNot (x => x.isConstructor || x.isPrivate || (x.name.toString contains "$")) + repltrace("decls: " + decls) + decls.lastOption map (decl => typeCleanser(sym, decl.name)) + } + case _ => + None + } + } + // def compileAndTypeExpr(expr: String): Option[Typer] = { + // class TyperRun extends Run { + // override def stopPhase(name: String) = name == "superaccessors" + // } + // } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index dc2a653c83..51f8da6063 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -8,12 +8,11 @@ package interpreter import Predef.{ println => _, _ } import java.io.{ BufferedReader, FileReader, PrintWriter } +import java.util.concurrent.locks.ReentrantLock import scala.sys.process.Process import session._ -import scala.tools.nsc.interpreter.{ Results => IR } -import scala.tools.util.{ SignalManager, Signallable, Javap } +import scala.tools.util.{ Signallable, Javap } import scala.annotation.tailrec -import scala.util.control.Exception.{ ignoring } import scala.collection.mutable.ListBuffer import scala.concurrent.ops import util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream } @@ -35,6 +34,7 @@ import io.{ File, Sources } class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) extends AnyRef with LoopCommands + with ILoopInit { def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out) def this() = this(None, new PrintWriter(Console.out, true)) @@ -79,34 +79,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) /** Record a command for replay should the user request a :replay */ def addReplay(cmd: String) = replayCommandStack ::= cmd - /** Try to install sigint handler: ignore failure. Signal handler - * will interrupt current line execution if any is in progress. - * - * Attempting to protect the repl from accidental exit, we only honor - * a single ctrl-C if the current buffer is empty: otherwise we look - * for a second one within a short time. - */ - private def installSigIntHandler() { - def onExit() { - Console.println("") // avoiding "shell prompt in middle of line" syndrome - sys.exit(1) - } - ignoring(classOf[Exception]) { - SignalManager("INT") = { - if (intp == null) - onExit() - else if (intp.lineManager.running) - intp.lineManager.cancel() - else if (in.currentLine != "") { - // non-empty buffer, so make them hit ctrl-C a second time - SignalManager("INT") = onExit() - io.timer(5)(installSigIntHandler()) // and restore original handler if they don't - } - else onExit() - } - } - } - /** Close the interpreter and set the var to null. */ def closeInterpreter() { if (intp ne null) { @@ -143,8 +115,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) settings.classpath append addedClasspath intp = new ILoopInterpreter - intp.setContextClassLoader() - installSigIntHandler() } /** print a friendly help message */ @@ -184,19 +154,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } } - /** Print a welcome message */ - def printWelcome() { - import Properties._ - val welcomeMsg = - """|Welcome to Scala %s (%s, Java %s). - |Type in expressions to have them evaluated. - |Type :help for more information.""" . - stripMargin.format(versionString, javaVmName, javaVersion) - val addendum = if (isReplDebug) "\n" + new java.util.Date else "" - - echo(welcomeMsg + addendum) - } - /** Show the history */ lazy val historyCommand = new LoopCommand("history", "show the history (optional num is commands to show)") { override def usage = "[num]" @@ -217,11 +174,24 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } } - private def echo(msg: String) = { + // When you know you are most likely breaking into the middle + // of a line being typed. This softens the blow. + protected def echoAndRefresh(msg: String) = { + if (in.currentLine == "") { + in.eraseLine() + echo(msg) + echoNoNL(prompt) + } + else { + echo("\n" + msg) + in.redrawLine() + } + } + protected def echo(msg: String) = { out println msg out.flush() } - private def echoNoNL(msg: String) = { + protected def echoNoNL(msg: String) = { out print msg out.flush() } @@ -568,16 +538,19 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) in readLine prompt } // return false if repl should exit - def processLine(line: String): Boolean = + def processLine(line: String): Boolean = { + awaitInitialized() + runThunks() if (line eq null) false // assume null means EOF else command(line) match { case Result(false, _) => false case Result(_, Some(finalLine)) => addReplay(finalLine) ; true case _ => true } + } while (true) { - try if (!processLine(readOneLine)) return + try if (!processLine(readOneLine())) return catch crashRecovery } } @@ -655,7 +628,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) def enablePowerMode() = { replProps.power setValue true power.unleash() - echo(power.banner) + echoAndRefresh(power.banner) } def verbosity() = { @@ -806,7 +779,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) def chooseReader(settings: Settings): InteractiveReader = { if (settings.Xnojline.value || Properties.isEmacsShell) SimpleReader() - else try JLineReader( + else try new JLineReader( if (settings.noCompletion.value) NoCompletion else new JLineCompletion(intp) ) @@ -816,7 +789,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) SimpleReader() } } - def process(settings: Settings): Boolean = { this.settings = settings createInterpreter() @@ -824,7 +796,12 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) // sets in to some kind of reader depending on environmental cues in = in0 match { case Some(reader) => SimpleReader(reader, out, true) - case None => chooseReader(settings) + case None => + // some post-initialization + chooseReader(settings) match { + case x: JLineReader => addThunk(x.consoleReader.postInit) ; x + case x => x + } } loadFiles(settings) @@ -832,21 +809,18 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) if (intp.reporter.hasErrors) return false + // This is about the illusion of snappiness. We call initialize() + // which spins off a separate thread, then print the prompt and try + // our best to look ready. The interlocking lazy vals tend to + // inter-deadlock, so we break the cycle with a single asynchronous + // message to an actor. + intp initialize initializedCallback() printWelcome() - try { - // this is about the illusion of snappiness. We call initialize() - // which spins off a separate thread, then print the prompt and try - // our best to look ready. Ideally the user will spend a - // couple seconds saying "wow, it starts so fast!" and by the time - // they type a command the compiler is ready to roll. - intp.initialize() - if (isReplPower) { - echo("Starting in power mode, one moment...\n") - enablePowerMode() - } - loop() - } + + try loop() + catch AbstractOrMissingHandler() finally closeInterpreter() + true } @@ -952,7 +926,7 @@ object ILoop { repl.settings = new Settings(echo) repl.settings.embeddedDefaults[T] repl.createInterpreter() - repl.in = JLineReader(repl) + repl.in = new JLineReader(new JLineCompletion(repl)) // rebind exit so people don't accidentally call sys.exit by way of predef repl.quietRun("""def exit = println("Type :quit to resume program execution.")""") diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala new file mode 100644 index 0000000000..dbd4d77a44 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala @@ -0,0 +1,124 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import scala.tools.util.SignalManager +import scala.util.control.Exception.ignoring + +/** + * Machinery for the asynchronous initialization of the repl. + */ +trait ILoopInit { + self: ILoop => + + /** Print a welcome message */ + def printWelcome() { + import Properties._ + val welcomeMsg = + """|Welcome to Scala %s (%s, Java %s). + |Type in expressions to have them evaluated. + |Type :help for more information.""" . + stripMargin.format(versionString, javaVmName, javaVersion) + echo(welcomeMsg) + if (isReplDebug || isReplPower) + echo("[info] started at " + new java.util.Date) + } + + private def asyncMessage(msg: String) { + if (isReplDebug || isReplPower) + echoAndRefresh(msg) + } + + /** Try to install sigint handler: ignore failure. Signal handler + * will interrupt current line execution if any is in progress. + * + * Attempting to protect the repl from accidental exit, we only honor + * a single ctrl-C if the current buffer is empty: otherwise we look + * for a second one within a short time. + */ + protected def installSigIntHandler() { + def onExit() { + Console.println("") // avoiding "shell prompt in middle of line" syndrome + sys.exit(1) + } + ignoring(classOf[Exception]) { + SignalManager("INT") = { + if (intp == null) + onExit() + else if (intp.lineManager.running) + intp.lineManager.cancel() + else if (in.currentLine != "") { + // non-empty buffer, so make them hit ctrl-C a second time + SignalManager("INT") = onExit() + io.timer(5)(installSigIntHandler()) // and restore original handler if they don't + } + else onExit() + } + } + } + + private val initLock = new java.util.concurrent.locks.ReentrantLock() + private def withLock[T](body: => T): T = { + initLock.lock() + try body + finally initLock.unlock() + } + // a condition used to ensure serial access to the compiler. + @volatile private var initIsComplete = false + private val initCompilerCondition = initLock.newCondition() // signal the compiler is initialized + private val initLoopCondition = initLock.newCondition() // signal the whole repl is initialized + private val initStart = System.nanoTime + private def elapsed() = "%.3f".format((System.nanoTime - initStart).toDouble / 1000000000L) + + // Receives a single message once the interpreter is initialized. + private val initializedReactor = io.spawn { + withLock(initCompilerCondition.await()) + asyncMessage("[info] compiler init time: " + elapsed() + " s.") + postInitialization() + } + + // the method to be called when the interpreter is initialized. + // Very important this method does nothing synchronous (i.e. do + // not try to use the interpreter) because until it returns, the + // repl's lazy val `global` is still locked. + protected def initializedCallback() = withLock(initCompilerCondition.signal()) + + // called from main repl loop + protected def awaitInitialized() { + if (!initIsComplete) + withLock { while (!initIsComplete) initLoopCondition.await() } + } + // called once after init condition is signalled + protected def postInitialization() { + addThunk(intp.setContextClassLoader()) + // do this last to avoid annoying uninterruptible startups + addThunk(installSigIntHandler()) + if (isReplPower) + addThunk(enablePowerMode()) + + runThunks() + initIsComplete = true + asyncMessage("[info] total init time: " + elapsed() + " s.") + withLock(initLoopCondition.signal()) + } + // code to be executed only after the interpreter is initialized + // and the lazy val `global` can be accessed without risk of deadlock. + private var pendingThunks: List[() => Unit] = Nil + protected def addThunk(body: => Unit) = synchronized { + pendingThunks :+= (() => body) + } + protected def runThunks(): Unit = synchronized { + if (pendingThunks.nonEmpty) + repldbg("Clearing " + pendingThunks.size + " thunks.") + + while (pendingThunks.nonEmpty) { + val thunk = pendingThunks.head + pendingThunks = pendingThunks.tail + thunk() + } + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 2594df90d1..803ef0225a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -12,10 +12,9 @@ import java.lang.reflect import java.net.URL import util._ import io.VirtualDirectory -import reporters.{ ConsoleReporter, Reporter } +import reporters._ import symtab.Flags import scala.reflect.internal.Names -import scala.tools.nsc.interpreter.{ Results => IR } import scala.tools.util.PathResolver import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } import ScalaClassLoader.URLClassLoader @@ -91,7 +90,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo } /** reporter */ - lazy val reporter: ConsoleReporter = new IMain.ReplReporter(this) + lazy val reporter: ReplReporter = new ReplReporter(this) import reporter.{ printMessage, withoutTruncating } // not sure if we have some motivation to print directly to console @@ -110,55 +109,35 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo private var _initializeComplete = false def isInitializeComplete = _initializeComplete - private def _initialize(): Boolean = { - val source = """ - |class $repl_$init { - | scala.collection.immutable.List(1) map (_ + 1) - |} - |""".stripMargin - - val result = try { - new _compiler.Run() compileSources List(new BatchSourceFile("", source)) - if (isReplDebug || settings.debug.value) { - // Can't use printMessage here, it deadlocks - Console.println("Repl compiler initialized.") - } - // addImports(defaultImports: _*) + private def _initSources = List(new BatchSourceFile("", "class $repl_$init { }")) + private def _initialize() = { + try { + new _compiler.Run() compileSources _initSources + _initializeComplete = true true } - catch { - case x: AbstractMethodError => - printMessage(""" - |Failed to initialize compiler: abstract method error. - |This is most often remedied by a full clean and recompile. - |""".stripMargin - ) - x.printStackTrace() - false - case x: MissingRequirementError => printMessage(""" - |Failed to initialize compiler: %s not found. - |** Note that as of 2.8 scala does not assume use of the java classpath. - |** For the old behavior pass -usejavacp to scala, or if using a Settings - |** object programatically, settings.usejavacp.value = true.""".stripMargin.format(x.req) - ) - false - } - - try result - finally _initializeComplete = result + catch AbstractOrMissingHandler() } // set up initialization future private var _isInitialized: () => Boolean = null - def initialize() = synchronized { + // argument is a thunk to execute after init is done + def initialize(postInitSignal: => Unit): Unit = synchronized { if (_isInitialized == null) - _isInitialized = scala.concurrent.ops future _initialize() + _isInitialized = scala.concurrent.ops future { + val result = _initialize() + postInitSignal + result + } } /** the public, go through the future compiler */ lazy val global: Global = { - initialize() - + // If init hasn't been called yet you're on your own. + if (_isInitialized == null) { + repldbg("Warning: compiler accessed before init set up. Assuming no postInit code.") + initialize(()) + } // blocks until it is ; false means catastrophic failure if (_isInitialized()) _compiler else null @@ -382,26 +361,6 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo } } - /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ - def parse(line: String): Option[List[Tree]] = { - var justNeedsMore = false - reporter.withIncompleteHandler((pos,msg) => {justNeedsMore = true}) { - // simple parse: just parse it, nothing else - def simpleParse(code: String): List[Tree] = { - reporter.reset() - val unit = new CompilationUnit(new BatchSourceFile("", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - - scanner.templateStatSeq(false)._2 - } - val trees = simpleParse(line) - - if (reporter.hasErrors) Some(Nil) // the result did not parse, so stop - else if (justNeedsMore) None - else Some(trees) - } - } - private[nsc] def replwarn(msg: => String): Unit = if (!settings.nowarnings.value) printMessage(msg) @@ -683,12 +642,6 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo lineAfterTyper(sym.info member newTermName(name)) } } - - // def compileAndTypeExpr(expr: String): Option[Typer] = { - // class TyperRun extends Run { - // override def stopPhase(name: String) = name == "superaccessors" - // } - // } private var lastRun: Run = _ private def evalMethod(name: String) = { val methods = evalClass.getMethods filter (_.getName == name) @@ -974,76 +927,11 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo } } - // Since people will be giving us ":t def foo = 5" even though that is not an - // expression, we have a means of typing declarations too. - private def typeOfDeclaration(code: String): Option[Type] = { - repldbg("typeOfDeclaration(" + code + ")") - val obname = freshInternalVarName() - - interpret("object " + obname + " {\n" + code + "\n}\n", true) match { - case IR.Success => - val sym = symbolOfTerm(obname) - if (sym == NoSymbol) None else { - // TODO: bitmap$n is not marked synthetic. - val decls = sym.tpe.decls.toList filterNot (x => x.isConstructor || x.isPrivate || (x.name.toString contains "$")) - repldbg("decls: " + decls) - decls.lastOption map (decl => typeCleanser(sym, decl.name)) - } - case _ => - None - } - } - - // XXX literals. - // 1) Identifiers defined in the repl. - // 2) A path loadable via getModule. - // 3) Try interpreting it as an expression. - private var typeOfExpressionDepth = 0 + private object exprTyper extends { val repl: IMain.this.type = imain } with ExprTyper { } + def parse(line: String): Option[List[Tree]] = exprTyper.parse(line) def typeOfExpression(expr: String, silent: Boolean = true): Option[Type] = { - repldbg("typeOfExpression(" + expr + ")") - if (typeOfExpressionDepth > 2) { - repldbg("Terminating typeOfExpression recursion for expression: " + expr) - return None - } - - def asQualifiedImport = { - val name = expr.takeWhile(_ != '.') - importedTermNamed(name) flatMap { sym => - typeOfExpression(sym.fullName + expr.drop(name.length), true) - } - } - def asModule = safeModule(expr) map (_.tpe) - def asExpr = { - val lhs = freshInternalVarName() - val line = "lazy val " + lhs + " =\n" + expr - - interpret(line, true) match { - case IR.Success => typeOfExpression(lhs, true) - case _ => None - } - } - def evaluate() = { - typeOfExpressionDepth += 1 - try typeOfTerm(expr) orElse asModule orElse asExpr orElse asQualifiedImport - finally typeOfExpressionDepth -= 1 - } - - // Don't presently have a good way to suppress undesirable success output - // while letting errors through, so it is first trying it silently: if there - // is an error, and errors are desired, then it re-evaluates non-silently - // to induce the error message. - beSilentDuring(evaluate()) orElse beSilentDuring(typeOfDeclaration(expr)) orElse { - if (!silent) - evaluate() - - None - } + exprTyper.typeOfExpression(expr, silent) } - // def compileAndTypeExpr(expr: String): Option[Typer] = { - // class TyperRun extends Run { - // override def stopPhase(name: String) = name == "superaccessors" - // } - // } protected def onlyTerms(xs: List[Name]) = xs collect { case x: TermName => x } protected def onlyTypes(xs: List[Name]) = xs collect { case x: TypeName => x } @@ -1165,16 +1053,4 @@ object IMain { } } } - - class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, null, new ReplStrippingWriter(intp)) { - override def printMessage(msg: String) { - // Avoiding deadlock when the compiler starts logging before - // the lazy val is done. - if (intp.isInitializeComplete) { - if (intp.totalSilence) () - else super.printMessage(msg) - } - else Console.println(msg) - } - } } diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index d222a80196..ed61240542 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala @@ -13,9 +13,14 @@ import scala.collection.JavaConverters._ import Completion._ import io.Streamable.slurp -/** Reads from the console using JLine */ -class JLineReader(val completion: Completion) extends InteractiveReader { +/** + * Reads from the console using JLine. + */ +class JLineReader(_completion: => Completion) extends InteractiveReader { val interactive = true + val consoleReader = new JLineConsoleReader() + + lazy val completion = _completion lazy val history: JLineHistory = JLineHistory() lazy val keyBindings = try KeyBinding parse slurp(term.getDefaultBindings) @@ -44,34 +49,31 @@ class JLineReader(val completion: Completion) extends InteractiveReader { } def eraseLine() = consoleReader.resetPromptLine("", "", 0) def redrawLineAndFlush(): Unit = { flush() ; drawLine() ; flush() } + // override def readLine(prompt: String): String - this setBellEnabled false - if (history ne NoHistory) - this setHistory history + // A hook for running code after the repl is done initializing. + lazy val postInit: Unit = { + this setBellEnabled false + if (history ne NoHistory) + this setHistory history - if (completion ne NoCompletion) { - val argCompletor: ArgumentCompleter = - new ArgumentCompleter(new JLineDelimiter, scalaToJline(completion.completer())) - argCompletor setStrict false + if (completion ne NoCompletion) { + val argCompletor: ArgumentCompleter = + new ArgumentCompleter(new JLineDelimiter, scalaToJline(completion.completer())) + argCompletor setStrict false - this addCompleter argCompletor - this setAutoprintThreshold 400 // max completion candidates without warning + this addCompleter argCompletor + this setAutoprintThreshold 400 // max completion candidates without warning + } } } - val consoleReader: JLineConsoleReader = new JLineConsoleReader() - def currentLine: String = consoleReader.getCursorBuffer.buffer.toString def redrawLine() = consoleReader.redrawLineAndFlush() def eraseLine() = { - while (consoleReader.delete()) { } - // consoleReader.eraseLine() + // while (consoleReader.delete()) { } + consoleReader.eraseLine() } def readOneLine(prompt: String) = consoleReader readLine prompt def readOneKey(prompt: String) = consoleReader readOneKey prompt } - -object JLineReader { - def apply(intp: IMain): JLineReader = apply(new JLineCompletion(intp)) - def apply(comp: Completion): JLineReader = new JLineReader(comp) -} diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala b/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala new file mode 100644 index 0000000000..ab0911d374 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ReplReporter.scala @@ -0,0 +1,22 @@ +/* NSC -- new Scala compiler + * Copyright 2002-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import reporters._ +import IMain._ + +class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, null, new ReplStrippingWriter(intp)) { + override def printMessage(msg: String) { + // Avoiding deadlock if the compiler starts logging before + // the lazy val is complete. + if (intp.isInitializeComplete) { + if (intp.totalSilence) () + else super.printMessage(msg) + } + else Console.println(msg) + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala index 1865e90e92..4309ceaa4a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/package.scala +++ b/src/compiler/scala/tools/nsc/interpreter/package.scala @@ -30,6 +30,8 @@ package object interpreter extends ReplConfig with ReplStrings { type InputStream = java.io.InputStream type OutputStream = java.io.OutputStream + val IR = Results + private[nsc] implicit def enrichClass[T](clazz: Class[T]) = new RichClass[T](clazz) private[interpreter] implicit def javaCharSeqCollectionToScala(xs: JCollection[_ <: CharSequence]): List[String] = { import collection.JavaConverters._ -- cgit v1.2.3