diff options
39 files changed, 1563 insertions, 615 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractOrMissingHandler.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractOrMissingHandler.scala new file mode 100644 index 0000000000..2f47685757 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractOrMissingHandler.scala @@ -0,0 +1,41 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +class AbstractOrMissingHandler[T](onError: String => Unit, value: T) extends PartialFunction[Throwable, T] { + def isDefinedAt(t: Throwable) = t match { + case _: AbstractMethodError => true + case _: NoSuchMethodError => true + case _: MissingRequirementError => true + case _: NoClassDefFoundError => true + case _ => false + } + def apply(t: Throwable) = t match { + case x @ (_: AbstractMethodError | _: NoSuchMethodError | _: NoClassDefFoundError) => + onError(""" + |Failed to initialize compiler: %s. + |This is most often remedied by a full clean and recompile. + |Otherwise, your classpath may continue bytecode compiled by + |different and incompatible versions of scala. + |""".stripMargin.format(x.getClass.getName split '.' last) + ) + x.printStackTrace() + value + case x: MissingRequirementError => + onError(""" + |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) + ) + value + } +} + +object AbstractOrMissingHandler { + def apply[T]() = new AbstractOrMissingHandler[T](Console println _, null.asInstanceOf[T]) +} 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("<console>", 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 74a23d119d..b249d37006 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -7,13 +7,12 @@ package scala.tools.nsc package interpreter import Predef.{ println => _, _ } -import java.io.{ BufferedReader, FileReader, PrintWriter } +import java.io.{ BufferedReader, FileReader } +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 } @@ -32,17 +31,19 @@ import io.{ File, Sources } * @author Lex Spoon * @version 1.2 */ -class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) +class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) 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)) + def this(in0: BufferedReader, out: JPrintWriter) = this(Some(in0), out) + def this() = this(None, new JPrintWriter(Console.out, true)) var in: InteractiveReader = _ // the input stream from which commands come var settings: Settings = _ var intp: IMain = _ + def isAsync = !settings.Yreplsync.value lazy val power = { val g = intp.global Power[g.type](this, g) @@ -79,34 +80,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 +116,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 +155,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 +175,17 @@ 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) = { + 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() } @@ -389,25 +353,42 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) protected def newJavap() = new Javap(intp.classLoader, new IMain.ReplStrippingWriter(intp)) { override def tryClass(path: String): Array[Byte] = { - // Look for Foo first, then Foo$, but if Foo$ is given explicitly, - // we have to drop the $ to find object Foo, then tack it back onto - // the end of the flattened name. - def className = intp flatName path - def moduleName = (intp flatName path.stripSuffix("$")) + "$" - - val bytes = super.tryClass(className) - if (bytes.nonEmpty) bytes - else super.tryClass(moduleName) + val hd :: rest = path split '.' toList; + // If there are dots in the name, the first segment is the + // key to finding it. + if (rest.nonEmpty) { + intp optFlatName hd match { + case Some(flat) => + val clazz = flat :: rest mkString "$" + val bytes = super.tryClass(clazz) + if (bytes.nonEmpty) bytes + else super.tryClass(clazz + "$") + case _ => super.tryClass(path) + } + } + else { + // Look for Foo first, then Foo$, but if Foo$ is given explicitly, + // we have to drop the $ to find object Foo, then tack it back onto + // the end of the flattened name. + def className = intp flatName path + def moduleName = (intp flatName path.stripSuffix("$")) + "$" + + val bytes = super.tryClass(className) + if (bytes.nonEmpty) bytes + else super.tryClass(moduleName) + } } } private lazy val javap = try newJavap() catch { case _: Exception => null } + // Still todo: modules. private def typeCommand(line: String): Result = { - intp.typeOfExpression(line) match { - case Some(tp) => tp.toString - case _ => "Failed to determine type." + if (line.trim == "") ":type <expression>" + else intp.typeOfExpression(line, false) match { + case Some(tp) => intp.afterTyper(tp.toString) + case _ => "" // the error message was already printed } } @@ -508,8 +489,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) ) val replayQuestionMessage = - """|The repl compiler has crashed spectacularly. Shall I replay your - |session? I can re-run all lines except the last one. + """|That entry seems to have slain the compiler. Shall I replay + |your session? I can re-run each line except the last one. |[y/n] """.trim.stripMargin @@ -522,17 +503,18 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) if (isReplDebug) "[searching " + sources.path + " for exception contexts...]" else "[searching for exception contexts...]" ) - echo(Exceptional(ex).force().context()) - } - else { - echo(util.stackTraceString(ex)) } + echo(intp.global.throwableAsString(ex)) + ex match { case _: NoSuchMethodError | _: NoClassDefFoundError => - echo("Unrecoverable error.") + echo("\nUnrecoverable error.") throw ex case _ => - def fn(): Boolean = in.readYesOrNo(replayQuestionMessage, { echo("\nYou must enter y or n.") ; fn() }) + def fn(): Boolean = + try in.readYesOrNo(replayQuestionMessage, { echo("\nYou must enter y or n.") ; fn() }) + catch { case _: RuntimeException => false } + if (fn()) replay() else echo("\nAbandoning crashed session.") } @@ -548,16 +530,21 @@ 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 = { + if (isAsync) { + 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 } } @@ -630,12 +617,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) def powerCmd(): Result = { if (isReplPower) "Already in power mode." - else enablePowerMode() + else enablePowerMode(false) } - def enablePowerMode() = { + def enablePowerMode(isDuringInit: Boolean) = { replProps.power setValue true power.unleash() - echo(power.banner) + if (isDuringInit) asyncMessage(power.banner) + else echo(power.banner) } def verbosity() = { @@ -682,11 +670,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } def transcript(start: String) = { - // Printing this message doesn't work very well because it's buried in the - // transcript they just pasted. Todo: a short timer goes off when - // lines stop coming which tells them to hit ctrl-D. - // - // echo("// Detected repl transcript paste: ctrl-D to finish.") + echo("\n// Detected repl transcript paste: ctrl-D to finish.\n") apply(Iterator(start) ++ readWhile(_.trim != PromptString.trim)) } } @@ -786,7 +770,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) ) @@ -796,7 +780,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) SimpleReader() } } - def process(settings: Settings): Boolean = { this.settings = settings createInterpreter() @@ -804,7 +787,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) @@ -812,21 +800,25 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) if (intp.reporter.hasErrors) return false - 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() + // 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. + if (isAsync) { + intp initialize initializedCallback() + createAsyncListener() // listens for signal to run postInitialization + } + else { + intp.initializeSynchronous() + postInitialization() } + printWelcome() + + try loop() + catch AbstractOrMissingHandler() finally closeInterpreter() + true } @@ -867,7 +859,7 @@ object ILoop { stringFromStream { ostream => Console.withOut(ostream) { - val output = new PrintWriter(new OutputStreamWriter(ostream), true) { + val output = new JPrintWriter(new OutputStreamWriter(ostream), true) { override def write(str: String) = { // completely skip continuation lines if (str forall (ch => ch.isWhitespace || ch == '|')) () @@ -903,7 +895,7 @@ object ILoop { stringFromStream { ostream => Console.withOut(ostream) { val input = new BufferedReader(new StringReader(code)) - val output = new PrintWriter(new OutputStreamWriter(ostream), true) + val output = new JPrintWriter(new OutputStreamWriter(ostream), true) val repl = new ILoop(input, output) if (sets.classpath.isDefault) @@ -932,7 +924,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..f2171ad732 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ILoopInit.scala @@ -0,0 +1,131 @@ +/* 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) + replinfo("[info] started at " + new java.util.Date) + } + + protected def asyncMessage(msg: String) { + if (isReplInfo || 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 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 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 def elapsed() = "%.3f".format((System.nanoTime - initStart).toDouble / 1000000000L) + + // 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()) + + // Spins off a thread which awaits a single message once the interpreter + // has been initialized. + protected def createAsyncListener() = { + io.spawn { + withLock(initCompilerCondition.await()) + asyncMessage("[info] compiler init time: " + elapsed() + " s.") + postInitialization() + } + } + + // called from main repl loop + protected def awaitInitialized() { + if (!initIsComplete) + withLock { while (!initIsComplete) initLoopCondition.await() } + } + protected def postInitThunks = List[Option[() => Unit]]( + Some(intp.setContextClassLoader _), + if (isReplPower) Some(() => enablePowerMode(true)) else None, + // do this last to avoid annoying uninterruptible startups + Some(installSigIntHandler _) + ).flatten + // called once after init condition is signalled + protected def postInitialization() { + postInitThunks foreach (f => addThunk(f())) + runThunks() + initIsComplete = true + + if (isAsync) { + 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 a364f218da..ebc3146cc7 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -7,14 +7,17 @@ package scala.tools.nsc package interpreter import Predef.{ println => _, _ } -import java.io.{ PrintWriter } -import java.lang.reflect -import java.net.URL import util.{ Set => _, _ } -import io.{ AbstractFile, VirtualDirectory } -import reporters.{ ConsoleReporter, Reporter } -import symtab.{ Flags, Names } -import scala.tools.nsc.interpreter.{ Results => IR } +import scala.collection.{ mutable, immutable } +import scala.sys.BooleanProp +import Exceptional.unwrap +import ScalaClassLoader.URLClassLoader +import symtab.Flags +import io.VirtualDirectory +import scala.tools.nsc.io.AbstractFile +import reporters._ +import symtab.Flags +import scala.reflect.generic.Names import scala.tools.util.PathResolver import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } import ScalaClassLoader.URLClassLoader @@ -44,7 +47,7 @@ import IMain._ * all variables defined by that code. To extract the result of an * interpreted line to show the user, a second "result object" is created * which imports the variables exported by the above object and then - * exports a single member named "$export". To accomodate user expressions + * exports members called "$eval" and "$print". To accomodate user expressions * that read from variables or methods defined in previous statements, "import" * statements are used. * @@ -57,25 +60,29 @@ import IMain._ * @author Moez A. Abdel-Gawad * @author Lex Spoon */ -class IMain(val settings: Settings, protected val out: PrintWriter) extends Imports { +class IMain(val settings: Settings, protected val out: JPrintWriter) extends Imports { imain => /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this() = this(new Settings()) - /** whether to print out result lines */ - private[nsc] var printResults: Boolean = true - - /** whether to print errors */ - private[nsc] var totalSilence: Boolean = false - - private val RESULT_OBJECT_PREFIX = "RequestResult$" - + lazy val repllog: Logger = new Logger { + val out: JPrintWriter = imain.out + val isInfo: Boolean = BooleanProp keyExists "scala.repl.info" + val isDebug: Boolean = BooleanProp keyExists "scala.repl.debug" + val isTrace: Boolean = BooleanProp keyExists "scala.repl.trace" + } lazy val formatting: Formatting = new Formatting { val prompt = Properties.shellPromptString } + lazy val reporter: ConsoleReporter = new ReplReporter(this) + import formatting._ + import reporter.{ printMessage, withoutTruncating } + + private[nsc] var printResults: Boolean = true // whether to print result lines + private[nsc] var totalSilence: Boolean = false // whether to print anything /** directory to save .class files to */ val virtualDirectory = new VirtualDirectory("(memory)", None) { @@ -89,15 +96,9 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def show() = pp(this, 0) } - /** reporter */ - lazy val reporter: ConsoleReporter = new IMain.ReplReporter(this) - import reporter.{ printMessage, withoutTruncating } - - // not sure if we have some motivation to print directly to console + // This exists mostly because using the reporter too early leads to deadlock. private def echo(msg: String) { Console println msg } - // protected def defaultImports: List[String] = List("_root_.scala.sys.exit") - /** 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 * run into unrecoverable issues, but the perceived repl startup time goes @@ -107,66 +108,64 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo */ private val _compiler: Global = newCompiler(settings, reporter) private var _initializeComplete = false - def isInitializeComplete = _initializeComplete - - private def _initialize(): Boolean = { - val source = """ - |class $repl_$init { - | List(1) map (_ + 1) - |} - |""".stripMargin - - val result = try { - new _compiler.Run() compileSources List(new BatchSourceFile("<init>", 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("<init>", "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 + } + } + def initializeSynchronous(): Unit = { + if (!isInitializeComplete) { + _initialize() + assert(global != null, global) + } } + def isInitializeComplete = _initializeComplete /** the public, go through the future compiler */ lazy val global: Global = { - initialize() - - // blocks until it is ; false means catastrophic failure - if (_isInitialized()) _compiler - else null + if (isInitializeComplete) _compiler + else { + // 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 + } } @deprecated("Use `global` for access to the compiler instance.", "2.9.0") lazy val compiler: global.type = global import global._ + import definitions.{ ScalaPackage, JavaLangPackage, PredefModule, RootClass } + private def privateTreeOps(t: Tree): List[Tree] = { + (new Traversable[Tree] { + def foreach[U](f: Tree => U): Unit = t foreach { x => f(x) ; () } + }).toList + } + + // TODO: If we try to make naming a lazy val, we run into big time + // scalac unhappiness with what look like cycles. It has not been easy to + // reduce, but name resolution clearly takes different paths. object naming extends { val global: imain.global.type = imain.global } with Naming { @@ -176,6 +175,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo if (definedNameMap contains name) freshUserVarName() else name } + def isInternalVarName(name: Name): Boolean = isInternalVarName("" + name) } import naming._ @@ -193,14 +193,11 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def afterTyper[T](op: => T): T = atPhase(currentRun.typerPhase.next)(op) /** Temporarily be quiet */ - def beQuietDuring[T](operation: => T): T = { - val wasPrinting = printResults - ultimately(printResults = wasPrinting) { - if (isReplDebug) echo(">> beQuietDuring") - else printResults = false - - operation - } + def beQuietDuring[T](body: => T): T = { + val saved = printResults + printResults = false + try body + finally printResults = saved } def beSilentDuring[T](operation: => T): T = { val saved = totalSilence @@ -211,8 +208,21 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def quietRun[T](code: String) = beQuietDuring(interpret(code)) + private def logAndDiscard[T](label: String, alt: => T): PartialFunction[Throwable, T] = { + case t => repldbg(label + ": " + t) ; alt + } + /** whether to bind the lastException variable */ - private var bindLastException = true + private var bindExceptions = true + /** takes AnyRef because it may be binding a Throwable or an Exceptional */ + private def withLastExceptionLock[T](body: => T): T = { + assert(bindExceptions, "withLastExceptionLock called incorrectly.") + bindExceptions = false + + try beQuietDuring(body) + catch logAndDiscard("bindLastException", null.asInstanceOf[T]) + finally bindExceptions = true + } /** A string representing code to be wrapped around all lines. */ private var _executionWrapper: String = "" @@ -220,31 +230,28 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def setExecutionWrapper(code: String) = _executionWrapper = code def clearExecutionWrapper() = _executionWrapper = "" - /** Temporarily stop binding lastException */ - def withoutBindingLastException[T](operation: => T): T = { - val wasBinding = bindLastException - ultimately(bindLastException = wasBinding) { - bindLastException = false - operation - } - } - - protected def createLineManager(): Line.Manager = new Line.Manager lazy val lineManager = createLineManager() /** interpreter settings */ lazy val isettings = new ISettings(this) - /** Instantiate a compiler. Subclasses can override this to - * change the compiler class used by this interpreter. */ + /** Create a line manager. Overridable. */ + protected def createLineManager(): Line.Manager = new Line.Manager + + /** Instantiate a compiler. Overridable. */ protected def newCompiler(settings: Settings, reporter: Reporter) = { settings.outputDirs setSingleOutput virtualDirectory settings.exposeEmptyPackage.value = true + new Global(settings, reporter) } + /** Parent classloader. Overridable. */ + protected def parentClassLoader: ClassLoader = + settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() ) + /** the compiler's classpath, as URL's */ - lazy val compilerClasspath: List[URL] = new PathResolver(settings) asURLs + lazy val compilerClasspath = global.classPath.asURLs /* A single class loader is used for all commands interpreted by this Interpreter. It would also be possible to create a new class loader for each command @@ -286,11 +293,6 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo } } } - private def loadByName(s: String): JClass = - (classLoader tryToInitializeClass s) getOrElse sys.error("Failed to load expected class: '" + s + "'") - - protected def parentClassLoader: ClassLoader = - settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() ) def getInterpreterClassLoader() = classLoader @@ -332,7 +334,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo /** Stubs for work in progress. */ def handleTypeRedefinition(name: TypeName, old: Request, req: Request) = { for (t1 <- old.simpleNameOfType(name) ; t2 <- req.simpleNameOfType(name)) { - DBG("Redefining type '%s'\n %s -> %s".format(name, t1, t2)) + repldbg("Redefining type '%s'\n %s -> %s".format(name, t1, t2)) } } @@ -342,7 +344,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo // assertion failed: fatal: <refinement> has owner value x, but a class owner is required // so DBG is by-name now to keep it in the family. (It also traps the assertion error, // but we don't want to unnecessarily risk hosing the compiler's internal state.) - DBG("Redefining term '%s'\n %s -> %s".format(name, t1, t2)) + repldbg("Redefining term '%s'\n %s -> %s".format(name, t1, t2)) } } def recordRequest(req: Request) { @@ -355,16 +357,15 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo // warning about serially defining companions. It'd be easy // enough to just redefine them together but that may not always // be what people want so I'm waiting until I can do it better. - if (!settings.nowarnings.value) { - for { - name <- req.definedNames filterNot (x => req.definedNames contains x.companionName) - oldReq <- definedNameMap get name.companionName - newSym <- req.definedSymbols get name - oldSym <- oldReq.definedSymbols get name.companionName - } { - printMessage("warning: previously defined %s is not a companion to %s.".format(oldSym, newSym)) - printMessage("Companions must be defined together; you may wish to use :paste mode for this.") - } + for { + name <- req.definedNames filterNot (x => req.definedNames contains x.companionName) + oldReq <- definedNameMap get name.companionName + newSym <- req.definedSymbols get name + oldSym <- oldReq.definedSymbols get name.companionName + } { + replwarn("warning: previously defined %s is not a companion to %s.".format( + stripString("" + oldSym), stripString("" + newSym))) + replwarn("Companions must be defined together; you may wish to use :paste mode for this.") } // Updating the defined name map @@ -377,32 +378,20 @@ 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("<console>", 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) def isParseable(line: String): Boolean = { beSilentDuring { - parse(line) match { + try parse(line) match { case Some(xs) => xs.nonEmpty // parses as-is case None => true // incomplete } + catch { case x: Exception => // crashed the compiler + replwarn("Exception in isParseable(\"" + line + "\"): " + x) + false + } } } @@ -425,45 +414,115 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo */ private def buildRequest(line: String, trees: List[Tree]): Request = new Request(line, trees) + // rewriting "5 // foo" to "val x = { 5 // foo }" creates broken code because + // the close brace is commented out. Strip single-line comments. + // ... but for error message output reasons this is not used, and rather than + // enclosing in braces it is constructed like "val x =\n5 // foo". + private def removeComments(line: String): String = { + showCodeIfDebugging(line) // as we're about to lose our // show + line.lines map (s => s indexOf "//" match { + case -1 => s + case idx => s take idx + }) mkString "\n" + } + private def safePos(t: Tree, alt: Int): Int = + try t.pos.startOrPoint + catch { case _: UnsupportedOperationException => alt } + + // Given an expression like 10 * 10 * 10 we receive the parent tree positioned + // at a '*'. So look at each subtree and find the earliest of all positions. + private def earliestPosition(tree: Tree): Int = { + var pos = Int.MaxValue + tree foreach { t => + pos = math.min(pos, safePos(t, Int.MaxValue)) + } + pos + } + private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = { - val trees = parse(indentCode(line)) match { + val content = indentCode(line) + val trees = parse(content) match { case None => return Left(IR.Incomplete) case Some(Nil) => return Left(IR.Error) // parse error or empty input case Some(trees) => trees } - - // use synthetic vars to avoid filling up the resXX slots - def varName = if (synthetic) freshInternalVarName() else freshUserVarName() - - // Treat a single bare expression specially. This is necessary due to it being hard to - // modify code at a textual level, and it being hard to submit an AST to the compiler. - if (trees.size == 1) trees.head match { - case _:Assign => // we don't want to include assignments - case _:TermTree | _:Ident | _:Select => // ... but do want these as valdefs. - requestFromLine("val %s =\n%s".format(varName, line), synthetic) match { + repltrace( + trees map { t => + privateTreeOps(t) map { t0 => t0.getClass + " at " + safePos(t0, -1) + "\n" } + } mkString + ) + // If the last tree is a bare expression, pinpoint where it begins using the + // AST node position and snap the line off there. Rewrite the code embodied + // by the last tree as a ValDef instead, so we can access the value. + trees.last match { + case _:Assign => // we don't want to include assignments + case _:TermTree | _:Ident | _:Select => // ... but do want other unnamed terms. + val varName = if (synthetic) freshInternalVarName() else freshUserVarName() + val rewrittenLine = ( + // In theory this would come out the same without the 1-specific test, but + // it's a cushion against any more sneaky parse-tree position vs. code mismatches: + // this way such issues will only arise on multiple-statement repl input lines, + // which most people don't use. + if (trees.size == 1) "val " + varName + " =\n" + content + else { + // The position of the last tree + val lastpos0 = earliestPosition(trees.last) + // Oh boy, the parser throws away parens so "(2+2)" is mispositioned. + // So until we can fix the parser we'll have to go trawling. + val adjustment = ((content take lastpos0).reverse takeWhile { ch => + ch.isWhitespace || ch == '(' || ch == ')' + }).length + val lastpos = lastpos0 - adjustment + + // the source code split at the laboriously determined position. + val (l1, l2) = content splitAt lastpos + val prefix = if (l1.trim == "") "" else l1 + ";\n" + // Note to self: val source needs to have this precise structure so that + // error messages print the user-submitted part without the "val res0 = " part. + val combined = prefix + "val " + varName + " =\n" + l2 + + repldbg(List( + " line" -> line, + " content" -> content, + " was" -> l2, + "combined" -> combined) map { + case (label, s) => label + ": '" + s + "'" + } mkString "\n" + ) + combined + } + ) + // Rewriting "foo ; bar ; 123" + // to "foo ; bar ; val resXX = 123" + requestFromLine(rewrittenLine, synthetic) match { case Right(req) => return Right(req withOriginalLine line) case x => return x } - case _ => + case _ => } - - // figure out what kind of request Right(buildRequest(line, trees)) } + def typeCleanser(sym: Symbol, memberName: Name): Type = { + // the types are all =>T; remove the => + val tp1 = afterTyper(sym.info.nonPrivateDecl(memberName).tpe match { + case NullaryMethodType(tp) => tp + case tp => tp + }) + // normalize non-public types so we don't see protected aliases like Self + afterTyper(tp1 match { + case TypeRef(_, sym, _) if !sym.isPublic => tp1.normalize + case tp => tp + }) + } + /** - * Interpret one line of input. All feedback, including parse errors - * and evaluation results, are printed via the supplied compiler's - * reporter. Values defined are available for future interpreted - * strings. - * + * Interpret one line of input. All feedback, including parse errors + * and evaluation results, are printed via the supplied compiler's + * reporter. Values defined are available for future interpreted strings. * - * The return value is whether the line was interpreter successfully, - * e.g. that there were no parse errors. - * - * - * @param line ... - * @return ... + * The return value is whether the line was interpreter successfully, + * e.g. that there were no parse errors. */ def interpret(line: String): IR.Result = interpret(line, false) def interpret(line: String, synthetic: Boolean): IR.Result = { @@ -481,6 +540,9 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo if (succeeded) { if (printResults) show() + else if (isReplDebug) // show quiet-mode activity + printMessage(result.trim.lines map ("[quiet] " + _) mkString "\n") + // Book-keeping. Have to record synthetic requests too, // as they may have been issued for information, e.g. :type recordRequest(req) @@ -523,7 +585,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo ) bindRep.callOpt("set", value) match { case Some(_) => interpret("val %s = %s.value".format(name, bindRep.evalPath)) - case _ => DBG("Set failed in bind(%s, %s, %s)".format(name, boundType, value)) ; IR.Error + case _ => repldbg("Set failed in bind(%s, %s, %s)".format(name, boundType, value)) ; IR.Error } } def rebind(p: NamedParam): IR.Result = { @@ -543,7 +605,8 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) def bind[T: Manifest](name: String, value: T): IR.Result = bind((name, value)) - def bindValue(x: Any): IR.Result = bind(freshUserVarName(), TypeStrings.fromValue(x), x) + def bindValue(x: Any): IR.Result = bindValue(freshUserVarName(), x) + def bindValue(name: String, x: Any): IR.Result = bind(name, TypeStrings.fromValue(x), x) /** Reset this interpreter, forgetting all user-specified requests. */ def reset() { @@ -571,11 +634,40 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo class ReadEvalPrint(lineId: Int) { def this() = this(freshLineId()) - val packageName = "$line" + lineId - val readName = "$read" - val evalName = "$eval" - val printName = "$print" - val valueMethod = "$result" // no-args method giving result + val packageName = sessionNames.line + lineId + val readName = sessionNames.read + val evalName = sessionNames.eval + val printName = sessionNames.print + + class LineExceptional(ex: Throwable) extends Exceptional(ex) { + private def showReplInternal = isettings.showInternalStackTraces + + override def spanFn(frame: JavaStackFrame) = + if (showReplInternal) super.spanFn(frame) + else !(frame.className startsWith evalPath) + + override def contextPrelude = super.contextPrelude + ( + if (showReplInternal) "" + else "/* The repl internal portion of the stack trace is elided. */\n" + ) + } + def bindError(t: Throwable) = { + if (!bindExceptions) // avoid looping if already binding + throw t + + val unwrapped = unwrap(t) + withLastExceptionLock { + if (opt.richExes) { + val ex = new LineExceptional(unwrapped) + bind[Exceptional]("lastException", ex) + ex.contextHead + "\n(access lastException for the full trace)" + } + else { + bind[Throwable]("lastException", unwrapped) + util.stackTraceString(unwrapped) + } + } + } // TODO: split it out into a package object and a regular // object and we can do that much less wrapping. @@ -593,13 +685,13 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def callOpt(name: String, args: Any*): Option[AnyRef] = try Some(call(name, args: _*)) - catch { case ex: Exception => - quietBind("lastException", ex) - None - } + catch { case ex: Exception => bindError(ex) ; None } - lazy val evalClass = loadByName(evalPath) - lazy val evalValue = callOpt(valueMethod) + private def load(s: String): Class[_] = + (classLoader tryToInitializeClass s) getOrElse sys.error("Failed to load expected class: '" + s + "'") + + lazy val evalClass = load(evalPath) + lazy val evalValue = callOpt(evalName) def compile(source: String): Boolean = compileAndSaveRun("<console>", source) def lineAfterTyper[T](op: => T): T = { @@ -617,17 +709,10 @@ 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) - assert(methods.size == 1, "Internal error - eval object method " + name + " is overloaded: " + methods) - methods.head + private def evalMethod(name: String) = evalClass.getMethods filter (_.getName == name) match { + case Array(method) => method + case xs => sys.error("Internal error: eval object " + evalClass + ", " + xs.mkString("\n", "\n", "")) } private def compileAndSaveRun(label: String, code: String) = { showCodeIfDebugging(code) @@ -660,6 +745,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo /** def and val names */ def termNames = handlers flatMap (_.definesTerm) def typeNames = handlers flatMap (_.definesType) + def definedOrImported = handlers flatMap (_.definedOrImported) /** Code to import bound names from previous lines - accessPath is code to * append to objectName to access anything bound by request. @@ -678,6 +764,9 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def fullFlatName(name: String) = lineRep.readPath + accessPath.replace('.', '$') + "$" + name + /** The unmangled symbol name, but supplemented with line info. */ + def disambiguated(name: Name): String = name + " (in " + lineRep + ")" + /** Code to access a variable with the specified name */ def fullPath(vname: Name): String = fullPath(vname.toString) @@ -688,7 +777,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo private object ObjectSourceCode extends CodeAssembler[MemberHandler] { val preamble = """ |object %s { - | %s%s + |%s%s """.stripMargin.format(lineRep.readName, importsPreamble, indentCode(toCompute)) val postamble = importsTrailer + "\n}" val generate = (m: MemberHandler) => m extraCodeToEvaluate Request.this @@ -704,9 +793,9 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo case Some(vname) if typeOf contains vname => """ |lazy val $result = { - | $export | %s - |}""".stripMargin.format(fullPath(vname)) + | %s + |}""".stripMargin.format(lineRep.printName, fullPath(vname)) case _ => "" } // first line evaluates object to make sure constructor is run @@ -714,11 +803,12 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo val preamble = """ |object %s { | %s - | val $export: String = %s { + | val %s: String = %s { | %s | ("" """.stripMargin.format( - lineRep.evalName, evalResult, executionWrapper, lineRep.readName + accessPath + lineRep.evalName, evalResult, lineRep.printName, + executionWrapper, lineRep.readName + accessPath ) val postamble = """ @@ -751,7 +841,9 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo typesOfDefinedTerms // compile the result-extraction object - lineRep compile ResultObjectSourceCode(handlers) + beSilentDuring { + lineRep compile ResultObjectSourceCode(handlers) + } } } @@ -762,25 +854,13 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo def lookupTypeOf(name: Name) = typeOf.getOrElse(name, typeOf(global.encode(name.toString))) def simpleNameOfType(name: TypeName) = (compilerTypeOf get name) map (_.typeSymbol.simpleName) - private def typeMap[T](f: Type => T): Map[Name, T] = { - def toType(name: Name): T = { - // the types are all =>T; remove the => - val tp1 = lineAfterTyper(resultSymbol.info.nonPrivateDecl(name).tpe match { - case NullaryMethodType(tp) => tp - case tp => tp - }) - // normalize non-public types so we don't see protected aliases like Self - lineAfterTyper(tp1 match { - case TypeRef(_, sym, _) if !sym.isPublic => f(tp1.normalize) - case tp => f(tp) - }) - } - termNames ++ typeNames map (x => x -> toType(x)) toMap - } + private def typeMap[T](f: Type => T): Map[Name, T] = + termNames ++ typeNames map (x => x -> f(typeCleanser(resultSymbol, x))) toMap + /** Types of variables defined by this request. */ lazy val compilerTypeOf = typeMap[Type](x => x) /** String representations of same. */ - lazy val typeOf = typeMap[String](_.toString) + lazy val typeOf = typeMap[String](tp => afterTyper(tp.toString)) // lazy val definedTypes: Map[Name, Type] = { // typeNames map (x => x -> afterTyper(resultSymbol.info.nonPrivateDecl(x).tpe)) toMap @@ -793,50 +873,17 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo lazy val typesOfDefinedTerms: Map[Name, Type] = termNames map (x => x -> applyToResultMember(x, _.tpe)) toMap - private def bindExceptionally(t: Throwable) = { - val ex: Exceptional = - if (isettings.showInternalStackTraces) Exceptional(t) - else new Exceptional(t) { - override def spanFn(frame: JavaStackFrame) = !(frame.className startsWith lineRep.evalPath) - override def contextPrelude = super.contextPrelude + "/* The repl internal portion of the stack trace is elided. */\n" - } - - quietBind("lastException", ex) - ex.contextHead + "\n(access lastException for the full trace)" - } - private def bindUnexceptionally(t: Throwable) = { - quietBind("lastException", t) - stackTraceString(t) - } - /** load and run the code using reflection */ def loadAndRun: (String, Boolean) = { import interpreter.Line._ - def handleException(t: Throwable) = { - /** We turn off the binding to accomodate ticket #2817 */ - withoutBindingLastException { - val message = - if (opt.richExes) bindExceptionally(unwrap(t)) - else bindUnexceptionally(unwrap(t)) - - (message, false) - } - } - try { - val execution = lineManager.set(originalLine)(lineRep call "$export") + val execution = lineManager.set(originalLine)(lineRep call sessionNames.print) execution.await() execution.state match { case Done => ("" + execution.get(), true) - case Threw => - val ex = execution.caught() - if (isReplDebug) - ex.printStackTrace() - - if (bindLastException) handleException(ex) - else throw ex + case Threw => (lineRep.bindError(execution.caught()), false) case Cancelled => ("Execution interrupted by signal.\n", false) case Running => ("Execution still running! Seems impossible.", false) } @@ -860,14 +907,17 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo case _ => naming.mostRecentVar }) - private def requestForName(name: Name): Option[Request] = { + def requestForName(name: Name): Option[Request] = { assert(definedNameMap != null, "definedNameMap is null") definedNameMap get name } - private def requestForIdent(line: String): Option[Request] = + def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) orElse requestForName(newTypeName(line)) + def requestHistoryForName(name: Name): List[Request] = + prevRequests.toList.reverse filter (_.definedNames contains name) + def safeClass(name: String): Option[Symbol] = { try Some(definitions.getClass(newTypeName(name))) catch { case _: MissingRequirementError => None } @@ -918,44 +968,11 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo } } - // 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): Option[Type] = { - DBG("typeOfExpression(" + expr + ")") - if (typeOfExpressionDepth > 2) { - DBG("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)) - } - } - def asModule = safeModule(expr) map (_.tpe) - def asExpr = beSilentDuring { - val lhs = freshInternalVarName() - val line = "lazy val " + lhs + " = { " + expr + " } " - - interpret(line, true) match { - case IR.Success => typeOfExpression(lhs) - case _ => None - } - } - - typeOfExpressionDepth += 1 - try typeOfTerm(expr) orElse asModule orElse asExpr orElse asQualifiedImport - finally typeOfExpressionDepth -= 1 + 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] = { + 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 } @@ -1001,20 +1018,16 @@ class IMain(val settings: Settings, protected val out: PrintWriter) extends Impo /** Secret bookcase entrance for repl debuggers: end the line * with "// show" and see what's going on. */ - if (code.lines exists (_.trim endsWith "// show")) { + if (repllog.isTrace || (code.lines exists (_.trim endsWith "// show"))) { echo(code) - parse(code) foreach (ts => ts foreach (t => withoutUnwrapping(DBG(asCompactString(t))))) + parse(code) foreach (ts => ts foreach (t => withoutUnwrapping(repldbg(asCompactString(t))))) } } // debugging def debugging[T](msg: String)(res: T) = { - DBG(msg + " " + res) + repldbg(msg + " " + res) res } - def DBG(s: => String) = if (isReplDebug) { - try repldbg(s) - catch { case x: AssertionError => repldbg("Assertion error printing debug string:\n " + x) } - } } /** Utility methods for the Interpreter. */ @@ -1053,8 +1066,8 @@ object IMain { else str } } - abstract class StrippingTruncatingWriter(out: PrintWriter) - extends PrintWriter(out) + abstract class StrippingTruncatingWriter(out: JPrintWriter) + extends JPrintWriter(out) with StrippingWriter with TruncatingWriter { self => @@ -1068,29 +1081,6 @@ object IMain { def isStripping = isettings.unwrapStrings def isTruncating = reporter.truncationOK - def stripImpl(str: String): String = { - val cleaned = stripString(str) - var ctrlChars = 0 - cleaned map { ch => - if (ch.isControl && !ch.isWhitespace) { - ctrlChars += 1 - if (ctrlChars > 5) return "[line elided for control chars: possibly a scala signature]" - else '?' - } - else ch - } - } - } - - 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) - } + def stripImpl(str: String): String = naming.unmangle(str) } } diff --git a/src/compiler/scala/tools/nsc/interpreter/Imports.scala b/src/compiler/scala/tools/nsc/interpreter/Imports.scala index b2706330e7..10b3d82d69 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Imports.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Imports.scala @@ -15,6 +15,9 @@ trait Imports { import definitions.{ ScalaPackage, JavaLangPackage, PredefModule } import memberHandlers._ + def isNoImports = settings.noimports.value + def isNoPredef = false // settings.nopredef.value + /** Synthetic import handlers for the language defined imports. */ private def makeWildcardImportHandler(sym: Symbol): ImportHandler = { val hd :: tl = sym.fullName.split('.').toList map newTermName @@ -88,7 +91,7 @@ trait Imports { * 2. A code fragment that should go after the code * of the new request. * - * 3. An access path which can be traverested to access + * 3. An access path which can be traversed to access * any bindings inside code wrapped by #1 and #2 . * * The argument is a set of Names that need to be imported. @@ -114,12 +117,11 @@ trait Imports { * 'wanted' is the set of names that need to be imported. */ def select(reqs: List[ReqAndHandler], wanted: Set[Name]): List[ReqAndHandler] = { - val isWanted = wanted contains _ // Single symbol imports might be implicits! See bug #1752. Rather than // try to finesse this, we will mimic all imports for now. def keepHandler(handler: MemberHandler) = handler match { case _: ImportHandler => true - case x => x.definesImplicit || (x.definedNames exists isWanted) + case x => x.definesImplicit || (x.definedNames exists wanted) } reqs match { @@ -157,7 +159,7 @@ trait Imports { // If the user entered an import, then just use it; add an import wrapping // level if the import might conflict with some other import case x: ImportHandler => - if (x.importsWildcard || (currentImps exists (x.importedNames contains _))) + if (x.importsWildcard || currentImps.exists(x.importedNames contains _)) addWrapper() code append (x.member + "\n") @@ -175,7 +177,7 @@ trait Imports { for (imv <- x.definedNames) { if (currentImps contains imv) addWrapper() - code append ("import %s\n" format (req fullPath imv)) + code append ("import " + (req fullPath imv) + "\n") currentImps += imv } } diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala index b7baea57fc..8b387823ff 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -18,7 +18,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput import global._ import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } type ExecResult = Any - import intp.{ DBG, debugging, afterTyper } + import intp.{ debugging, afterTyper } // verbosity goes up with consecutive tabs private var verbosity: Int = 0 @@ -325,7 +325,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput // This is jline's entry point for completion. override def complete(buf: String, cursor: Int): Candidates = { verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 - DBG("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) + repldbg("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) // we don't try lower priority completions unless higher ones return no results. def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Candidates] = { @@ -338,7 +338,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput val advance = commonPrefix(winners) lastCursor = p.position + advance.length lastBuf = (buf take p.position) + advance - DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format( + repldbg("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format( p, lastBuf, lastCursor, p.position)) p.position } @@ -369,7 +369,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput } catch { case ex: Exception => - DBG("Error: complete(%s, %s) provoked %s".format(buf, cursor, ex)) + repldbg("Error: complete(%s, %s) provoked %s".format(buf, cursor, ex)) Candidates(cursor, List(" ", "<completion error: " + ex.getMessage + ">")) } } diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index d222a80196..2e3dc506c6 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,30 @@ 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 currentLine = consoleReader.getCursorBuffer.buffer.toString def redrawLine() = consoleReader.redrawLineAndFlush() - def eraseLine() = { - while (consoleReader.delete()) { } - // consoleReader.eraseLine() - } + def eraseLine() = consoleReader.eraseLine() + // Alternate implementation, not sure if/when I need this. + // def eraseLine() = while (consoleReader.delete()) { } 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/Logger.scala b/src/compiler/scala/tools/nsc/interpreter/Logger.scala new file mode 100644 index 0000000000..d6cba5da6a --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/Logger.scala @@ -0,0 +1,18 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +trait Logger { + def isInfo: Boolean + def isDebug: Boolean + def isTrace: Boolean + def out: JPrintWriter + + def info(msg: => Any): Unit = if (isInfo) out println msg + def debug(msg: => Any): Unit = if (isDebug) out println msg + def trace(msg: => Any): Unit = if (isTrace) out println msg +} diff --git a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala index 196d8b882f..188f891054 100644 --- a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala +++ b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala @@ -27,7 +27,7 @@ object ProcessResult { } trait LoopCommands { - protected def out: java.io.PrintWriter + protected def out: JPrintWriter // a single interpreter command abstract class LoopCommand(val name: String, val help: String) extends (String => Result) { diff --git a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala index 330294f823..0d51c16e53 100644 --- a/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala +++ b/src/compiler/scala/tools/nsc/interpreter/MemberHandlers.scala @@ -9,7 +9,7 @@ package interpreter import scala.collection.{ mutable, immutable } import scala.PartialFunction.cond import scala.reflect.NameTransformer -import util.Chars +import scala.tools.nsc.util.Chars trait MemberHandlers { val intp: IMain @@ -52,15 +52,15 @@ trait MemberHandlers { } def chooseHandler(member: Tree): MemberHandler = member match { - case member: DefDef => new DefHandler(member) - case member: ValDef => new ValHandler(member) - case member@Assign(Ident(_), _) => new AssignHandler(member) - case member: ModuleDef => new ModuleHandler(member) - case member: ClassDef => new ClassHandler(member) - case member: TypeDef => new TypeAliasHandler(member) - case member: Import => new ImportHandler(member) - case DocDef(_, documented) => chooseHandler(documented) - case member => new GenericHandler(member) + case member: DefDef => new DefHandler(member) + case member: ValDef => new ValHandler(member) + case member: Assign => new AssignHandler(member) + case member: ModuleDef => new ModuleHandler(member) + case member: ClassDef => new ClassHandler(member) + case member: TypeDef => new TypeAliasHandler(member) + case member: Import => new ImportHandler(member) + case DocDef(_, documented) => chooseHandler(documented) + case member => new GenericHandler(member) } sealed abstract class MemberDefHandler(override val member: MemberDef) extends MemberHandler(member) { @@ -80,10 +80,7 @@ trait MemberHandlers { sealed abstract class MemberHandler(val member: Tree) { def definesImplicit = false def definesValue = false - def isLegalTopLevel = member match { - case _: ModuleDef | _: ClassDef | _: Import => true - case _ => false - } + def isLegalTopLevel = false def definesTerm = Option.empty[TermName] def definesType = Option.empty[TypeName] @@ -129,7 +126,7 @@ trait MemberHandlers { } class AssignHandler(member: Assign) extends MemberHandler(member) { - val lhs = member.lhs.asInstanceOf[Ident] // an unfortunate limitation + val Assign(lhs, rhs) = member val name = newTermName(freshInternalVarName()) override def definesTerm = Some(name) @@ -140,7 +137,7 @@ trait MemberHandlers { /** Print out lhs instead of the generated varName */ override def resultExtractionCode(req: Request) = { val lhsType = string2code(req lookupTypeOf name) - val res = string2code(req fullPath name) + val res = string2code(req fullPath name) """ + "%s: %s = " + %s + "\n" """.format(lhs, lhsType, res) + "\n" } @@ -149,6 +146,7 @@ trait MemberHandlers { class ModuleHandler(module: ModuleDef) extends MemberDefHandler(module) { override def definesTerm = Some(name) override def definesValue = true + override def isLegalTopLevel = true override def resultExtractionCode(req: Request) = codegenln("defined module ", name) } @@ -156,6 +154,7 @@ trait MemberHandlers { class ClassHandler(member: ClassDef) extends MemberDefHandler(member) { override def definesType = Some(name.toTypeName) override def definesTerm = Some(name.toTermName) filter (_ => mods.isCase) + override def isLegalTopLevel = true override def resultExtractionCode(req: Request) = codegenln("defined %s %s".format(keyword, name)) @@ -172,6 +171,19 @@ trait MemberHandlers { class ImportHandler(imp: Import) extends MemberHandler(imp) { val Import(expr, selectors) = imp def targetType = intp.typeOfExpression("" + expr) + override def isLegalTopLevel = true + + def createImportForName(name: Name): String = { + selectors foreach { + case sel @ ImportSelector(old, _, `name`, _) => return "import %s.{ %s }".format(expr, sel) + case _ => () + } + "import %s.%s".format(expr, name) + } + // TODO: Need to track these specially to honor Predef masking attempts, + // because they must be the leading imports in the code generated for each + // line. We can use the same machinery as Contexts now, anyway. + def isPredefImport = false // treeInfo.isPredefExpr(expr) // wildcard imports, e.g. import foo._ private def selectorWild = selectors filter (_.name == nme.USCOREkw) @@ -181,6 +193,9 @@ trait MemberHandlers { /** Whether this import includes a wildcard import */ val importsWildcard = selectorWild.nonEmpty + /** Whether anything imported is implicit .*/ + def importsImplicit = implicitSymbols.nonEmpty + def implicitSymbols = importedSymbols filter (_.isImplicit) def importedSymbols = individualSymbols ++ wildcardSymbols diff --git a/src/compiler/scala/tools/nsc/interpreter/Naming.scala b/src/compiler/scala/tools/nsc/interpreter/Naming.scala index 2b9c8a954a..89868abfb5 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Naming.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Naming.scala @@ -6,13 +6,56 @@ package scala.tools.nsc package interpreter +/** This is for name logic which is independent of the compiler (notice there's no Global.) + * That includes at least generating, metaquoting, mangling, and unmangling. + */ trait Naming { - val global: Global + def unmangle(str: String): String = { + val cleaned = removeIWPackages(removeLineWrapper(str)) + var ctrlChars = 0 + cleaned map { ch => + if (ch.isControl && !ch.isWhitespace) { + ctrlChars += 1 + if (ctrlChars > 5) return "[line elided for control chars: possibly a scala signature]" + else '?' + } + else ch + } + } + + // The two name forms this is catching are the two sides of this assignment: + // + // $line3.$read.$iw.$iw.Bippy = + // $line3.$read$$iw$$iw$Bippy@4a6a00ca + + private def noMeta(s: String) = "\\Q" + s + "\\E" + private lazy val lineRegex = { + val sn = sessionNames + val members = List(sn.read, sn.eval, sn.print) map noMeta mkString ("(?:", "|", ")") + debugging("lineRegex")(noMeta(sn.line) + """\d+[./]""" + members + """[$.]""") + } + + private def removeLineWrapper(s: String) = s.replaceAll(lineRegex, "") + private def removeIWPackages(s: String) = s.replaceAll("""\$iw[$.]""", "") + + trait SessionNames { + // All values are configurable by passing e.g. -Dscala.repl.naming.read=XXX + final def propOr(name: String): String = propOr(name, "$" + name) + final def propOr(name: String, default: String): String = + sys.props.getOrElse("scala.repl.naming." + name, default) + + // Prefixes used in repl machinery. Default to $line, $read, etc. + def line = propOr("line") + def read = propOr("read") + def eval = propOr("eval") + def print = propOr("print") - import global.{ Name, nme } - import nme.{ - INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX + // The prefix for unnamed results: by default res0, res1, etc. + def res = propOr("res", "res") // INTERPRETER_VAR_PREFIX + // Internal ones + def ires = propOr("ires") } + lazy val sessionNames: SessionNames = new SessionNames { } /** Generates names pre0, pre1, etc. via calls to apply method */ class NameCreator(pre: String) { @@ -29,24 +72,21 @@ trait Naming { (name startsWith pre) && ((name drop pre.length) forall (_.isDigit)) } - private lazy val line = new NameCreator(INTERPRETER_LINE_PREFIX) // line name, like line$0 - private lazy val userVar = new NameCreator(INTERPRETER_VAR_PREFIX) // var name, like res0 - private lazy val internalVar = new NameCreator(INTERPRETER_SYNTHVAR_PREFIX) // internal var name, like $synthvar0 + private lazy val userVar = new NameCreator(sessionNames.res) // var name, like res0 + private lazy val internalVar = new NameCreator(sessionNames.ires) // internal var name, like $ires0 - def isUserVarName(name: String) = userVar didGenerate name - def isInternalVarName(name: String): Boolean = internalVar didGenerate name - def isInternalVarName(name: Name): Boolean = internalVar didGenerate name.toString + def isLineName(name: String) = (name startsWith sessionNames.line) && (name stripPrefix sessionNames.line forall (_.isDigit)) + def isUserVarName(name: String) = userVar didGenerate name + def isInternalVarName(name: String) = internalVar didGenerate name val freshLineId = { var x = 0 () => { x += 1 ; x } } - def freshLineName() = line() def freshUserVarName() = userVar() def freshInternalVarName() = internalVar() def resetAllCreators() { - line.reset() userVar.reset() internalVar.reset() } diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index 5a0830841d..d907d5024f 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -56,7 +56,12 @@ abstract class Power[G <: Global]( val intp: IMain ) extends SharesGlobal[G] { import intp.{ beQuietDuring, interpret, parse } - import global.{ opt, definitions, stringToTermName, NoSymbol, NoType, analyzer, CompilationUnit } + import global.{ + opt, definitions, analyzer, + stringToTermName, typeRef, + CompilationUnit, + NoSymbol, NoPrefix, NoType + } abstract class SymSlurper { def isKeep(sym: Symbol): Boolean @@ -132,12 +137,14 @@ abstract class Power[G <: Global]( |** New defs! Type power.<tab> to reveal ** """.stripMargin.trim - def init = customInit getOrElse """ - |import scala.tools.nsc._ - |import scala.collection.JavaConverters._ - |import global.{ error => _, _ } - |import power.Implicits._ - """.stripMargin + private def initImports = List( + "scala.tools.nsc._", + "scala.collection.JavaConverters._", + "global.{ error => _, _ }", + "power.Implicits._", + "power.rutil._" + ) + def init = customInit getOrElse "import " + initImports.mkString(", ") /** Starts up power mode and runs whatever is in init. */ @@ -205,7 +212,7 @@ abstract class Power[G <: Global]( } def ? = this - def whoHas(name: String) = bts filter (_.decls.toList exists (_.name.toString == name)) + def whoHas(name: String) = bts filter (_.decls exists (_.name.toString == name)) def <:<[U: Manifest](other: U) = tpe <:< InternalInfo[U].tpe def lub[U: Manifest](other: U) = global.lub(List(tpe, InternalInfo[U].tpe)) def glb[U: Manifest](other: U) = global.glb(List(tpe, InternalInfo[U].tpe)) @@ -219,85 +226,97 @@ abstract class Power[G <: Global]( trait LowPriorityPrettifier { implicit object AnyPrettifier extends Prettifier[Any] { - def prettify(x: Any): List[String] = x match { + def show(x: Any): Unit = prettify(x) foreach println + def prettify(x: Any): TraversableOnce[String] = x match { case x: Name => List(x.decode) - case Tuple2(k, v) => List(prettify(k) ++ Seq("->") ++ prettify(v) mkString " ") - case xs: TraversableOnce[_] => (xs.toList flatMap prettify).sorted - case x => List(rutil.stringOf(x)) + case Tuple2(k, v) => List(prettify(k).toIterator ++ Iterator("->") ++ prettify(v) mkString " ") + case xs: Array[_] => xs.iterator flatMap prettify + case xs: TraversableOnce[_] => xs flatMap prettify + case x => List(Prettifier.stringOf(x)) } } } + object StringPrettifier extends Prettifier[String] { + def show(x: String) = println(x) + def prettify(x: String) = List(Prettifier stringOf x) + } object Prettifier extends LowPriorityPrettifier { - def prettify[T](value: T): List[String] = default[T] prettify value + def stringOf(x: Any): String = scala.runtime.ScalaRunTime.stringOf(x) + def prettify[T](value: T): TraversableOnce[String] = default[T] prettify value def default[T] = new Prettifier[T] { - def prettify(x: T): List[String] = AnyPrettifier prettify x + def prettify(x: T): TraversableOnce[String] = AnyPrettifier prettify x + def show(x: T): Unit = AnyPrettifier show x } } trait Prettifier[T] { - def prettify(x: T): List[String] - - private var indentLevel = 0 - private def spaces = " " * indentLevel - def indented[T](body: => T): T = { - indentLevel += 1 - try body - finally indentLevel -= 1 - } + def show(x: T): Unit + def prettify(x: T): TraversableOnce[String] - def show(x: T): Unit = grep(x, _ => true) - def grep(x: T, p: String => Boolean): Unit = - prettify(x) filter p foreach (x => println(spaces + x)) + def show(xs: TraversableOnce[T]): Unit = prettify(xs) foreach println + def prettify(xs: TraversableOnce[T]): TraversableOnce[String] = xs flatMap (x => prettify(x)) } - class MultiPrintingConvenience[T: Prettifier](coll: TraversableOnce[T]) { + + abstract class PrettifierClass[T: Prettifier]() { val pretty = implicitly[Prettifier[T]] import pretty._ - def freqBy[U](p: T => U) = { - val map = coll.toList groupBy p - map.toList sortBy (-_._2.size) - } - def freqByFormatted[U](p: T => U) = { - val buf = new mutable.ListBuffer[String] + def value: Seq[T] - freqBy(p) foreach { case (k, vs) => - buf += "%d: %s".format(vs.size, Prettifier.prettify(k)) - vs flatMap prettify foreach (buf += " " + _) - } - buf.toList - } + def pp(f: Seq[T] => Seq[T]): Unit = + pretty prettify f(value) foreach (StringPrettifier show _) + + def freq[U](p: T => U) = (value.toSeq groupBy p mapValues (_.size)).toList sortBy (-_._2) map (_.swap) + def ppfreq[U](p: T => U): Unit = freq(p) foreach { case (count, key) => println("%5d %s".format(count, key)) } + + def |[U](f: Seq[T] => Seq[U]): Seq[U] = f(value) + def ^^[U](f: T => U): Seq[U] = value map f + def ^?[U](pf: PartialFunction[T, U]): Seq[U] = value collect pf + + def >>!(implicit ord: Ordering[T]): Unit = pp(_.sorted.distinct) + def >>(implicit ord: Ordering[T]): Unit = pp(_.sorted) + def >!(): Unit = pp(_.distinct) + def >(): Unit = pp(identity) - /** It makes sense. - * - * # means how many - * ? means "I said, HOW MANY?" - * > means print - * - * Now don't you feel silly for what you were thinking. - */ - def #?>[U](p: T => U) = this freqByFormatted p foreach println - def #?[U](p: T => U) = this freqByFormatted p + def >#(): Unit = this ># (identity[T] _) + def >#[U](p: T => U): Unit = this ppfreq p + + def >?(p: T => Boolean): Unit = pp(_ filter p) + def >?(s: String): Unit = pp(_ filter (_.toString contains s)) + def >?(r: Regex): Unit = pp(_ filter (_.toString matches fixRegex(r))) + + private def fixRegex(r: scala.util.matching.Regex): String = { + val s = r.pattern.toString + val prefix = if (s startsWith "^") "" else """^.*?""" + val suffix = if (s endsWith "$") "" else """.*$""" + + prefix + s + suffix + } } - class PrintingConvenience[T: Prettifier](value: T) { - val pretty = implicitly[Prettifier[T]] + class MultiPrettifierClass[T: Prettifier](val value: Seq[T]) extends PrettifierClass[T]() { } + class SinglePrettifierClass[T: Prettifier](single: T) extends PrettifierClass[T]() { + val value = List(single) + } - def >() { >(_ => true) } - def >(s: String): Unit = >(_ contains s) - def >(r: Regex): Unit = >(_ matches r.pattern.toString) - def >(p: String => Boolean): Unit = pretty.grep(value, p) + class RichReplString(s: String) { + def u: URL = ( + if (s contains ":") new java.net.URL(s) + else if (new java.io.File(s) exists) new java.io.File(s).toURI.toURL + else new java.net.URL("http://" + s) + ) } class RichInputStream(in: InputStream)(implicit codec: Codec) { - def bytes(): Array[Byte] = io.Streamable.bytes(in) - def slurp(): String = io.Streamable.slurp(in) + def bytes(): Array[Byte] = io.Streamable.bytes(in) + def slurp(): String = io.Streamable.slurp(in) + def <<(): String = slurp() } protected trait Implicits1 { // fallback - implicit def replPrinting[T](x: T)(implicit pretty: Prettifier[T] = Prettifier.default[T]) = new PrintingConvenience[T](x) + implicit def replPrinting[T](x: T)(implicit pretty: Prettifier[T] = Prettifier.default[T]) = + new SinglePrettifierClass[T](x) } - trait Implicits2 extends Implicits1 with SharesGlobal[G] { - import global._ - + trait Implicits2 extends Implicits1 { class RichSymbol(sym: Symbol) { // convenient type application def apply(targs: Type*): Type = typeRef(NoPrefix, sym, targs.toList) @@ -312,18 +331,19 @@ abstract class Power[G <: Global]( implicit lazy val powerSymbolOrdering: Ordering[Symbol] = Ordering[Name] on (_.name) implicit lazy val powerTypeOrdering: Ordering[Type] = Ordering[Symbol] on (_.typeSymbol) - implicit def replCollPrinting[T: Prettifier](xs: TraversableOnce[T]): MultiPrintingConvenience[T] = new MultiPrintingConvenience[T](xs) + implicit def replEnhancedStrings(s: String): RichReplString = new RichReplString(s) + implicit def replMultiPrinting[T: Prettifier](xs: TraversableOnce[T]): MultiPrettifierClass[T] = + new MultiPrettifierClass[T](xs.toSeq) implicit def replInternalInfo[T: Manifest](x: T): InternalInfo[T] = new InternalInfo[T](Some(x)) implicit def replPrettifier[T] : Prettifier[T] = Prettifier.default[T] implicit def replTypeApplication(sym: Symbol): RichSymbol = new RichSymbol(sym) - implicit def replInputStream(in: InputStream)(implicit codec: Codec): RichInputStream = new RichInputStream(in) - implicit def replInputStreamURL(url: URL)(implicit codec: Codec) = replInputStream(url.openStream()) - } - object Implicits extends Implicits2 { - val global: G = Power.this.global + implicit def replInputStream(in: InputStream)(implicit codec: Codec) = new RichInputStream(in) + implicit def replInputStreamURL(url: URL)(implicit codec: Codec) = new RichInputStream(url.openStream()) } + object Implicits extends Implicits2 { } trait ReplUtilities { + def info[T: Manifest] = InternalInfo[T] def ?[T: Manifest] = InternalInfo[T] def url(s: String) = { try new URL(s) @@ -344,7 +364,6 @@ abstract class Power[G <: Global]( case (next, rest) => next.map(_.toChar).mkString :: strings(rest) } } - def stringOf(x: Any): String = scala.runtime.ScalaRunTime.stringOf(x) } lazy val rutil: ReplUtilities = new ReplUtilities { } diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplConfig.scala b/src/compiler/scala/tools/nsc/interpreter/ReplConfig.scala index a8287e3a9a..c4a77e9630 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplConfig.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplConfig.scala @@ -6,37 +6,30 @@ package scala.tools.nsc package interpreter -import sys.{ Prop, BooleanProp } - trait ReplConfig { - - class ReplProps { - private def bool(name: String) = BooleanProp.keyExists(name) - - val jlineDebug = bool("scala.tools.jline.internal.Log.debug") - val jlineTrace = bool("scala.tools.jline.internal.Log.trace") - - val debug = bool("scala.repl.debug") - val trace = bool("scala.repl.trace") - val power = bool("scala.repl.power") - - val replInitCode = Prop[JFile]("scala.repl.initcode") - val powerInitCode = Prop[JFile]("scala.repl.power.initcode") - val powerBanner = Prop[JFile]("scala.repl.power.banner") - } lazy val replProps = new ReplProps - /** Debug output */ - private[nsc] def repldbg(msg: String) = if (isReplDebug) Console println msg + class TapMaker[T](x: T) { + def tapInfo(msg: => String): T = tap(x => replinfo(parens(x))) + def tapDebug(msg: => String): T = tap(x => repldbg(parens(x))) + def tapTrace(msg: => String): T = tap(x => repltrace(parens(x))) + def tap[U](f: T => U): T = { + f(x) + x + } + } - /** Tracing */ - private[nsc] def tracing[T](msg: String)(x: T): T = { - if (isReplDebug) - println("(" + msg + ") " + x) + private def parens(x: Any) = "(" + x + ")" + private def echo(msg: => String) = + try Console println msg + catch { case x: AssertionError => Console.println("Assertion error printing debugging output: " + x) } - x - } + private[nsc] def repldbg(msg: => String) = if (isReplDebug) echo(msg) + private[nsc] def repltrace(msg: => String) = if (isReplTrace) echo(msg) + private[nsc] def replinfo(msg: => String) = if (isReplInfo) echo(msg) - def isReplDebug: Boolean = replProps.debug + def isReplTrace: Boolean = replProps.trace + def isReplDebug: Boolean = replProps.debug || isReplTrace + def isReplInfo: Boolean = replProps.info || isReplDebug def isReplPower: Boolean = replProps.power } diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala b/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala new file mode 100644 index 0000000000..5eb1e0ae18 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ReplProps.scala @@ -0,0 +1,25 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import scala.sys._ + +class ReplProps { + private def bool(name: String) = BooleanProp.keyExists(name) + + val jlineDebug = bool("scala.tools.jline.internal.Log.debug") + val jlineTrace = bool("scala.tools.jline.internal.Log.trace") + + val info = bool("scala.repl.info") + val debug = bool("scala.repl.debug") + val trace = bool("scala.repl.trace") + val power = bool("scala.repl.power") + + val replInitCode = Prop[JFile]("scala.repl.initcode") + val powerInitCode = Prop[JFile]("scala.repl.power.initcode") + val powerBanner = Prop[JFile]("scala.repl.power.banner") +} 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..e181f98018 --- /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("[init] " + msg) + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala b/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala index cef46c963a..9c83db3d85 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReplStrings.scala @@ -9,7 +9,7 @@ package interpreter import scala.collection.{ mutable, immutable } import scala.PartialFunction.cond import scala.reflect.NameTransformer -import util.Chars +import scala.tools.nsc.util.Chars trait ReplStrings { // Longest common prefix diff --git a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala index 5249f89dfb..992bef8056 100644 --- a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala @@ -6,13 +6,13 @@ package scala.tools.nsc package interpreter -import java.io.{ BufferedReader, PrintWriter } +import java.io.{ BufferedReader } import session.NoHistory /** Reads using standard JDK API */ class SimpleReader( in: BufferedReader, - out: PrintWriter, + out: JPrintWriter, val interactive: Boolean) extends InteractiveReader { @@ -37,8 +37,8 @@ extends InteractiveReader object SimpleReader { def defaultIn = Console.in - def defaultOut = new PrintWriter(Console.out) + def defaultOut = new JPrintWriter(Console.out) - def apply(in: BufferedReader = defaultIn, out: PrintWriter = defaultOut, interactive: Boolean = true): SimpleReader = + def apply(in: BufferedReader = defaultIn, out: JPrintWriter = defaultOut, interactive: Boolean = true): SimpleReader = new SimpleReader(in, out, interactive) }
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala index 1865e90e92..e78e92c8f8 100644 --- a/src/compiler/scala/tools/nsc/interpreter/package.scala +++ b/src/compiler/scala/tools/nsc/interpreter/package.scala @@ -27,12 +27,19 @@ package object interpreter extends ReplConfig with ReplStrings { type JClass = java.lang.Class[_] type JList[T] = java.util.List[T] type JCollection[T] = java.util.Collection[T] + type JPrintWriter = java.io.PrintWriter type InputStream = java.io.InputStream type OutputStream = java.io.OutputStream - private[nsc] implicit def enrichClass[T](clazz: Class[T]) = new RichClass[T](clazz) + val IR = Results + private[interpreter] implicit def javaCharSeqCollectionToScala(xs: JCollection[_ <: CharSequence]): List[String] = { import collection.JavaConverters._ xs.asScala.toList map ("" + _) } + + private[nsc] implicit def enrichClass[T](clazz: Class[T]) = new RichClass[T](clazz) + private[nsc] implicit def enrichAnyRefWithTap[T](x: T) = new TapMaker(x) + private[nsc] def tracing[T](msg: String)(x: T): T = x.tapTrace(msg) + private[nsc] def debugging[T](msg: String)(x: T) = x.tapDebug(msg) } diff --git a/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala b/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala index 5f11a0ea8f..e238bdf654 100644 --- a/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala +++ b/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala @@ -46,9 +46,17 @@ trait FileBackedHistory extends JLineHistory with JPersistentHistory { if (!historyFile.canRead) historyFile.createFile() - val lines: IndexedSeq[String] = + val lines: IndexedSeq[String] = { try historyFile.lines().toIndexedSeq - catch { case _: Exception => Vector() } + catch { + // It seems that control characters in the history file combined + // with the default codec can lead to nio spewing exceptions. Rather + // than abandon hope we'll try to read it as ISO-8859-1 + case _: Exception => + try historyFile.lines("ISO-8859-1").toIndexedSeq + catch { case _: Exception => Vector() } + } + } repldbg("Loading " + lines.size + " into history.") @@ -61,6 +69,7 @@ trait FileBackedHistory extends JLineHistory with JPersistentHistory { } moveToEnd() } + def flush(): Unit = () def purge(): Unit = historyFile.truncate() } diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index a92b5d7034..ce1236d788 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -143,6 +143,7 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { BooleanSetting ("-Ybuild-manager-debug", "Generate debug information for the Refined Build Manager compiler.") val Ytyperdebug = BooleanSetting ("-Ytyper-debug", "Trace all type assignments.") val Ypmatdebug = BooleanSetting ("-Ypmat-debug", "Trace all pattern matcher activity.") + val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yrepldebug = BooleanSetting ("-Yrepl-debug", "Trace all repl activity.") . withPostSetHook(_ => interpreter.replProps.debug setValue true) val Ycompletion = BooleanSetting ("-Ycompletion-debug", "Trace all tab completion activity.") diff --git a/src/partest/scala/tools/partest/ReplTest.scala b/src/partest/scala/tools/partest/ReplTest.scala index b31c43ba6f..02ab154d4b 100644 --- a/src/partest/scala/tools/partest/ReplTest.scala +++ b/src/partest/scala/tools/partest/ReplTest.scala @@ -14,48 +14,26 @@ import java.lang.reflect.{ Method => JMethod, Field => JField } */ abstract class ReplTest extends App { def code: String - def settings: Settings = new Settings // override for custom settings + // override to add additional settings with strings + def extraSettings: String = "" + // override to transform Settings object immediately before the finish + def transformSettings(s: Settings): Settings = s + + // final because we need to enforce the existence of a couple settings. + final def settings: Settings = { + val s = new Settings + s.Yreplsync.value = true + s.Xnojline.value = true + val settingString = sys.props("scala.partest.debug.repl-args") match { + case null => extraSettings + case s => extraSettings + " " + s + } + s processArgumentString settingString + transformSettings(s) + } def eval() = ILoop.runForTranscript(code, settings).lines drop 1 def show() = eval() foreach println - show() -} - -trait SigTest { - def mstr(m: JMethod) = " (m) %s%s".format( - m.toGenericString, - if (m.isBridge) " (bridge)" else "" - ) - def fstr(f: JField) = " (f) %s".format(f.toGenericString) - - def isObjectMethodName(name: String) = classOf[Object].getMethods exists (_.getName == name) - - def fields[T: ClassManifest](p: JField => Boolean) = { - val cl = classManifest[T].erasure - val fs = (cl.getFields ++ cl.getDeclaredFields).distinct sortBy (_.getName) - - fs filter p - } - def methods[T: ClassManifest](p: JMethod => Boolean) = { - val cl = classManifest[T].erasure - val ms = (cl.getMethods ++ cl.getDeclaredMethods).distinct sortBy (x => (x.getName, x.isBridge)) - - ms filter p - } - def allFields[T: ClassManifest]() = fields[T](_ => true) - def allMethods[T: ClassManifest]() = methods[T](m => !isObjectMethodName(m.getName)) - def fieldsNamed[T: ClassManifest](name: String) = fields[T](_.getName == name) - def methodsNamed[T: ClassManifest](name: String) = methods[T](_.getName == name) - - def allGenericStrings[T: ClassManifest]() = - (allMethods[T]() map mstr) ++ (allFields[T]() map fstr) - - def genericStrings[T: ClassManifest](name: String) = - (methodsNamed[T](name) map mstr) ++ (fieldsNamed[T](name) map fstr) - - def show[T: ClassManifest](name: String = "") = { - println(classManifest[T].erasure.getName) - if (name == "") allGenericStrings[T]() foreach println - else genericStrings[T](name) foreach println - } + try show() + catch { case t => println(t) ; sys.exit(1) } } diff --git a/src/partest/scala/tools/partest/SigTest.scala b/src/partest/scala/tools/partest/SigTest.scala new file mode 100644 index 0000000000..072ec006f9 --- /dev/null +++ b/src/partest/scala/tools/partest/SigTest.scala @@ -0,0 +1,51 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.partest + +import scala.tools.nsc.Settings +import scala.tools.nsc.interpreter.ILoop +import java.lang.reflect.{ Method => JMethod, Field => JField } + +/** Support code for testing signatures. + */ +trait SigTest { + def mstr(m: JMethod) = " (m) %s%s".format( + m.toGenericString, + if (m.isBridge) " (bridge)" else "" + ) + def fstr(f: JField) = " (f) %s".format(f.toGenericString) + + def isObjectMethodName(name: String) = classOf[Object].getMethods exists (_.getName == name) + + def fields[T: ClassManifest](p: JField => Boolean) = { + val cl = classManifest[T].erasure + val fs = (cl.getFields ++ cl.getDeclaredFields).distinct sortBy (_.getName) + + fs filter p + } + def methods[T: ClassManifest](p: JMethod => Boolean) = { + val cl = classManifest[T].erasure + val ms = (cl.getMethods ++ cl.getDeclaredMethods).distinct sortBy (x => (x.getName, x.isBridge)) + + ms filter p + } + def allFields[T: ClassManifest]() = fields[T](_ => true) + def allMethods[T: ClassManifest]() = methods[T](m => !isObjectMethodName(m.getName)) + def fieldsNamed[T: ClassManifest](name: String) = fields[T](_.getName == name) + def methodsNamed[T: ClassManifest](name: String) = methods[T](_.getName == name) + + def allGenericStrings[T: ClassManifest]() = + (allMethods[T]() map mstr) ++ (allFields[T]() map fstr) + + def genericStrings[T: ClassManifest](name: String) = + (methodsNamed[T](name) map mstr) ++ (fieldsNamed[T](name) map fstr) + + def show[T: ClassManifest](name: String = "") = { + println(classManifest[T].erasure.getName) + if (name == "") allGenericStrings[T]() foreach println + else genericStrings[T](name) foreach println + } +} diff --git a/test/files/jvm/interpreter.check b/test/files/jvm/interpreter.check index dd0f39551c..42a8ae8855 100644 --- a/test/files/jvm/interpreter.check +++ b/test/files/jvm/interpreter.check @@ -13,7 +13,7 @@ scala> def gcd(x: Int, y: Int): Int = { else if (y == 0) x else gcd(y%x, x) } -gcd: (x: Int,y: Int)Int +gcd: (x: Int, y: Int)Int scala> val five = gcd(15,35) five: Int = 5 @@ -370,5 +370,5 @@ plusOne: (x: Int)Int res0: Int = 6 res1: java.lang.String = after reset <console>:8: error: not found: value plusOne - plusOne(5) // should be undefined now - ^ + plusOne(5) // should be undefined now + ^ diff --git a/test/files/jvm/interpreter.scala b/test/files/jvm/interpreter.scala new file mode 100644 index 0000000000..752a129950 --- /dev/null +++ b/test/files/jvm/interpreter.scala @@ -0,0 +1,158 @@ +import scala.tools.nsc._ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = <code> +// basics +3+4 +def gcd(x: Int, y: Int): Int = {{ + if (x == 0) y + else if (y == 0) x + else gcd(y%x, x) +}} +val five = gcd(15,35) +var x = 1 +x = 2 +val three = x+1 +type anotherint = Int +val four: anotherint = 4 +val bogus: anotherint = "hello" +trait PointlessTrait +val (x,y) = (2,3) +println("hello") + +// ticket #1513 +val t1513 = Array(null) +// ambiguous toString problem from #547 +val atom = new scala.xml.Atom() +// overriding toString problem from #1404 +class S(override val toString : String) +val fish = new S("fish") +// Test that arrays pretty print nicely. +val arr = Array("What's", "up", "doc?") +// Test that arrays pretty print nicely, even when we give them type Any +val arrInt : Any = Array(1,2,3) +// Test that nested arrays are pretty-printed correctly +val arrArrInt : Any = Array(Array(1, 2), Array(3, 4)) + +// implicit conversions +case class Foo(n: Int) +case class Bar(n: Int) +implicit def foo2bar(foo: Foo) = Bar(foo.n) +val bar: Bar = Foo(3) + +// importing from a previous result +import bar._ +val m = n + +// stressing the imports mechanism +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 +val one = 1 + + +val x1 = 1 +val x2 = 1 +val x3 = 1 +val x4 = 1 +val x5 = 1 +val x6 = 1 +val x7 = 1 +val x8 = 1 +val x9 = 1 +val x10 = 1 +val x11 = 1 +val x12 = 1 +val x13 = 1 +val x14 = 1 +val x15 = 1 +val x16 = 1 +val x17 = 1 +val x18 = 1 +val x19 = 1 +val x20 = 1 + +val two = one + x5 + +// handling generic wildcard arrays (#2386) +// It's put here because type feedback is an important part of it. +val xs: Array[_] = Array(1, 2) +xs.size +xs.head +xs filter (_ == 2) +xs map (_ => "abc") +xs map (x => x) +xs map (x => (x, x)) + +// interior syntax errors should *not* go into multi-line input mode. +// both of the following should abort immediately: +def x => y => z +[1,2,3] + + +// multi-line XML +<a> +<b + c="c" + d="dd" +/></a> + + +/* + /* + multi-line comment + */ +*/ + + +// multi-line string +""" +hello +there +""" + +(1 + // give up early by typing two blank lines + + +// defining and using quoted names should work (ticket #323) +def `match` = 1 +val x = `match` + +// multiple classes defined on one line +sealed class Exp; class Fact extends Exp; class Term extends Exp +def f(e: Exp) = e match {{ // non-exhaustive warning here + case _:Fact => 3 +}} + +</code>.text + + def appendix() = { + val settings = new Settings + settings.classpath.value = sys.props("java.class.path") + val interp = new Interpreter(settings) + interp.interpret("def plusOne(x: Int) = x + 1") + interp.interpret("plusOne(5)") + interp.reset() + interp.interpret("\"after reset\"") + interp.interpret("plusOne(5) // should be undefined now") + } + + appendix() +} diff --git a/test/files/run/bug4710.check b/test/files/run/bug4710.check new file mode 100644 index 0000000000..aa2f08d452 --- /dev/null +++ b/test/files/run/bug4710.check @@ -0,0 +1,7 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> def method : String = { implicit def f(s: Symbol) = "" ; 'symbol } +method: String + +scala> diff --git a/test/files/run/bug4710.scala b/test/files/run/bug4710.scala new file mode 100644 index 0000000000..5e5b1e86b5 --- /dev/null +++ b/test/files/run/bug4710.scala @@ -0,0 +1,6 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """def method : String = { implicit def f(s: Symbol) = "" ; 'symbol }""" +} + diff --git a/test/files/run/constrained-types.scala b/test/files/run/constrained-types.scala index 5f7eb7adde..91bd856d00 100644 --- a/test/files/run/constrained-types.scala +++ b/test/files/run/constrained-types.scala @@ -79,15 +79,12 @@ val x : Int @Where(self > 0 && self < 100) = 3 """ - override def settings: Settings = { - val s = new Settings - + override def transformSettings(s: Settings): Settings = { s.Xexperimental.value = true s.selfInAnnots.value = true s.deprecation.value = true // when running that compiler, give it a scala-library to the classpath s.classpath.value = sys.props("java.class.path") - s } } diff --git a/test/files/run/repl-bare-expr.check b/test/files/run/repl-bare-expr.check new file mode 100644 index 0000000000..04daa48232 --- /dev/null +++ b/test/files/run/repl-bare-expr.check @@ -0,0 +1,36 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> 2 ; 3 +res0: Int = 3 + +scala> { 2 ; 3 } +res1: Int = 3 + +scala> 5 ; 10 ; case object Cow ; 20 ; class Moo { override def toString = "Moooooo" } ; 30 ; def bippy = { + 1 + + 2 + + 3 } ; bippy+88+11 +defined module Cow +defined class Moo +bippy: Int +res2: Int = 105 + +scala> + +scala> object Bovine { var x: List[_] = null } ; case class Ruminant(x: Int) ; bippy * bippy * bippy +defined module Bovine +defined class Ruminant +res3: Int = 216 + +scala> Bovine.x = List(Ruminant(5), Cow, new Moo) +Bovine.x: List[Any] = List(Ruminant(5), Cow, Moooooo) + +scala> Bovine.x +res4: List[Any] = List(Ruminant(5), Cow, Moooooo) + +scala> + +scala> diff --git a/test/files/run/repl-bare-expr.scala b/test/files/run/repl-bare-expr.scala new file mode 100644 index 0000000000..df9849fa6d --- /dev/null +++ b/test/files/run/repl-bare-expr.scala @@ -0,0 +1,16 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ +2 ; 3 +{ 2 ; 3 } +5 ; 10 ; case object Cow ; 20 ; class Moo { override def toString = "Moooooo" } ; 30 ; def bippy = { + 1 + + 2 + + 3 } ; bippy+88+11 + +object Bovine { var x: List[_] = null } ; case class Ruminant(x: Int) ; bippy * bippy * bippy +Bovine.x = List(Ruminant(5), Cow, new Moo) +Bovine.x + """ +}
\ No newline at end of file diff --git a/test/files/run/repl-colon-type.check b/test/files/run/repl-colon-type.check new file mode 100644 index 0000000000..66c2fcc77f --- /dev/null +++ b/test/files/run/repl-colon-type.check @@ -0,0 +1,57 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> :type List[1, 2, 3] +<console>:2: error: identifier expected but integer literal found. + List[1, 2, 3] + ^ + + +scala> :type List(1, 2, 3) +List[Int] + +scala> :type def foo[T](x: T) = List(x) +[T](x: T)List[T] + +scala> :type val bar = List(Set(1)) +List[scala.collection.immutable.Set[Int]] + +scala> :type lazy val bar = Set(Set(1)) +scala.collection.immutable.Set[scala.collection.immutable.Set[Int]] + +scala> :type def f[T >: Null, U <: String](x: T, y: U) = Set(x, y) +[T >: Null, U <: String](x: T, y: U)scala.collection.immutable.Set[Any] + +scala> :type def x = 1 ; def bar[T >: Null <: AnyRef](xyz: T) = 5 +[T >: Null <: AnyRef](xyz: T)Int + +scala> + +scala> :type 5 +Int + +scala> :type val f = 5 +Int + +scala> :type lazy val f = 5 +Int + +scala> :type protected lazy val f = 5 +Int + +scala> :type def f = 5 +Int + +scala> :type def f() = 5 +()Int + +scala> + +scala> :type def g[T](xs: Set[_ <: T]) = Some(xs.head) +[T](xs: Set[_ <: T])Some[T] + +scala> + +scala> diff --git a/test/files/run/repl-colon-type.scala b/test/files/run/repl-colon-type.scala new file mode 100644 index 0000000000..39ab580d2a --- /dev/null +++ b/test/files/run/repl-colon-type.scala @@ -0,0 +1,23 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ + |:type List[1, 2, 3] + |:type List(1, 2, 3) + |:type def foo[T](x: T) = List(x) + |:type val bar = List(Set(1)) + |:type lazy val bar = Set(Set(1)) + |:type def f[T >: Null, U <: String](x: T, y: U) = Set(x, y) + |:type def x = 1 ; def bar[T >: Null <: AnyRef](xyz: T) = 5 + | + |:type 5 + |:type val f = 5 + |:type lazy val f = 5 + |:type protected lazy val f = 5 + |:type def f = 5 + |:type def f() = 5 + | + |:type def g[T](xs: Set[_ <: T]) = Some(xs.head) + """.stripMargin +} + diff --git a/test/files/run/repl-parens.check b/test/files/run/repl-parens.check new file mode 100644 index 0000000000..f41c2e74bd --- /dev/null +++ b/test/files/run/repl-parens.check @@ -0,0 +1,58 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> (2) +res0: Int = 2 + +scala> (2 + 2) +res1: Int = 4 + +scala> ((2 + 2)) +res2: Int = 4 + +scala> ((2 + 2)) +res3: Int = 4 + +scala> ( (2 + 2)) +res4: Int = 4 + +scala> ( (2 + 2 ) ) +res5: Int = 4 + +scala> 5 ; ( (2 + 2 ) ) ; ((5)) +res6: Int = 5 + +scala> (((2 + 2)), ((2 + 2))) +res7: (Int, Int) = (4,4) + +scala> (((2 + 2)), ((2 + 2)), 2) +res8: (Int, Int, Int) = (4,4,2) + +scala> ((((2 + 2)), ((2 + 2)), 2).productIterator ++ Iterator(3) mkString) +res9: String = 4423 + +scala> + +scala> 55 ; ((2 + 2)) ; (1, 2, 3) +res10: (Int, Int, Int) = (1,2,3) + +scala> + +scala> () => 5 +res11: () => Int = <function0> + +scala> 55 ; () => 5 +res12: () => Int = <function0> + +scala> () => { class X ; new X } +res13: () => java.lang.Object with ScalaObject = <function0> + +scala> + +scala> def foo(x: Int)(y: Int)(z: Int) = x+y+z +foo: (x: Int)(y: Int)(z: Int)Int + +scala> foo(5)(10)(15)+foo(5)(10)(15) +res14: Int = 60 + +scala> diff --git a/test/files/run/repl-parens.scala b/test/files/run/repl-parens.scala new file mode 100644 index 0000000000..081db3606a --- /dev/null +++ b/test/files/run/repl-parens.scala @@ -0,0 +1,26 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ +(2) +(2 + 2) +((2 + 2)) + ((2 + 2)) + ( (2 + 2)) + ( (2 + 2 ) ) +5 ; ( (2 + 2 ) ) ; ((5)) +(((2 + 2)), ((2 + 2))) +(((2 + 2)), ((2 + 2)), 2) +((((2 + 2)), ((2 + 2)), 2).productIterator ++ Iterator(3) mkString) + +55 ; ((2 + 2)) ; (1, 2, 3) + +() => 5 +55 ; () => 5 +() => { class X ; new X } + +def foo(x: Int)(y: Int)(z: Int) = x+y+z +foo(5)(10)(15)+foo(5)(10)(15) + + """.trim +}
\ No newline at end of file diff --git a/test/files/run/repl-paste-2.check b/test/files/run/repl-paste-2.check index 435592567d..18bd6d2434 100644 --- a/test/files/run/repl-paste-2.check +++ b/test/files/run/repl-paste-2.check @@ -4,6 +4,9 @@ Type :help for more information. scala> scala> scala> 0123 + +// Detected repl transcript paste: ctrl-D to finish. + res4: Int = 0123 scala> 123 diff --git a/test/files/run/repl-power.check b/test/files/run/repl-power.check new file mode 100644 index 0000000000..9561c04eca --- /dev/null +++ b/test/files/run/repl-power.check @@ -0,0 +1,17 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> :power +** Power User mode enabled - BEEP BOOP WHIR ** +** scala.tools.nsc._ has been imported ** +** global._ and definitions._ also imported ** +** New vals! Try repl, intp, global, power ** +** New cmds! :help to discover them ** +** New defs! Type power.<tab> to reveal ** + +scala> // guarding against "error: reference to global is ambiguous" + +scala> global.emptyValDef // "it is imported twice in the same scope by ..." +res0: global.emptyValDef.type = private val _ = _ + +scala> diff --git a/test/files/run/repl-power.scala b/test/files/run/repl-power.scala new file mode 100644 index 0000000000..9f70ac4b68 --- /dev/null +++ b/test/files/run/repl-power.scala @@ -0,0 +1,10 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ +:power +// guarding against "error: reference to global is ambiguous" +global.emptyValDef // "it is imported twice in the same scope by ..." + """.trim +} + diff --git a/test/files/run/repl-transcript.check b/test/files/run/repl-transcript.check index 03162451b6..6d22353882 100644 --- a/test/files/run/repl-transcript.check +++ b/test/files/run/repl-transcript.check @@ -4,6 +4,9 @@ Type :help for more information. scala> scala> scala> class Bippity + +// Detected repl transcript paste: ctrl-D to finish. + defined class Bippity scala> def f = new Bippity |