diff options
Diffstat (limited to 'src')
8 files changed, 280 insertions, 113 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala index 955e558461..39c27cfbb1 100644 --- a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala @@ -17,20 +17,28 @@ class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent) with ScalaClassLoader { - def getBytesForClass(name: String): Array[Byte] = { - def onull[T](x: T): T = if (x == null) throw new ClassNotFoundException(name) else x + private def findBytes(name: String, onError: => Array[Byte]): Array[Byte] = { var file: AbstractFile = root - val pathParts = name.split("[./]").toList + val pathParts = name.split("[./]").toList - for (dirPart <- pathParts.init) - file = onull(file.lookupName(dirPart, true)) + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, true) + if (file == null) + return onError + } - file = onull(file.lookupName(pathParts.last+".class", false)) - file.toByteArray + file.lookupName(pathParts.last+".class", false) match { + case null => onError + case file => file.toByteArray + } } + override def findBytesForClassName(name: String): Array[Byte] = + findBytes(name, super.findBytesForClassName(name)) + override def findClass(name: String): JClass = { - val bytes = getBytesForClass(name) + val bytes = findBytes(name, throw new ClassNotFoundException(name)) defineClass(name, bytes, 0, bytes.length) } } + diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 4c6e14ed6f..a075fe5bb0 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -8,11 +8,10 @@ package interpreter import Predef.{ println => _, _ } import java.io.{ BufferedReader, FileReader, PrintWriter } -import java.io.IOException import scala.sys.process.Process import scala.tools.nsc.interpreter.{ Results => IR } -import scala.tools.util.SignalManager +import scala.tools.util.{ SignalManager, Javap } import scala.annotation.tailrec import scala.util.control.Exception.{ ignoring } import scala.collection.mutable.ListBuffer @@ -197,12 +196,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) NoArgs("help", "print this help message", printHelp), VarArgs("history", "show the history (optional arg: lines to show)", printHistory), LineArg("h?", "search the history", searchHistory), + LineArg("javap", "disassemble a file or class name", javapCommand), LineArg("keybindings", "show how ctrl-[A-Z] and other keys are bound", keybindingsCommand), OneArg("load", "load and interpret a Scala file", load), NoArgs("power", "enable power user mode", powerCmd), NoArgs("quit", "exit the interpreter", () => Result(false, None)), NoArgs("replay", "reset execution and replay all previous commands", replay), - LineArg("sh", "fork a shell and run a command", runShellCmd), + LineArg("sh", "fork a shell and run a command", shCommand), NoArgs("silent", "disable/enable automatic printing of results", verbosity) ) } @@ -216,6 +216,18 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) LineArg("wrap", "code to wrap around all executions", wrapCommand) ) } + private def javapCommand(line: String): Result = { + if (line == "") + return ":javap <filename or classname>" + val javap = new Javap(intp.classLoader) { + override def defaultPrintWriter = new IMain.ReplStrippingWriter(intp) + } + val path = intp.pathToFlatName(line) + val result = javap guess path + + if (result.isError) "Failed: " + result.value + else result.show() + } private def keybindingsCommand(line: String): Result = { if (in.keyBindings.isEmpty) "Key bindings unavailable." else { @@ -336,11 +348,16 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } /** fork a shell and run a command */ - def runShellCmd(cmd: String) { - intp.beQuietDuring { intp.interpret("import _root_.scala.sys.process._") } - val xs = Process(cmd).lines - if (xs.nonEmpty) - intp.bind("stdout", "scala.Stream[String]", xs) + def shCommand(cmd: String): Result = { + if (cmd == "") + return "Usage: sh <command line>" + + intp quietRun "import _root_.scala.sys.process._" + val pb = Process(cmd) + intp.bind("builder", pb) + val stdout = Process(cmd).lines + intp.bind("stdout", stdout) + () } def withFile(filename: String)(action: File => Unit) { diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 492b79cb84..3c54c18fe1 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -25,45 +25,37 @@ import scala.util.control.Exception.{ ultimately } import scala.reflect.NameTransformer import IMain._ -/** <p> - * An interpreter for Scala code. - * </p> - * <p> - * The main public entry points are <code>compile()</code>, - * <code>interpret()</code>, and <code>bind()</code>. - * The <code>compile()</code> method loads a - * complete Scala file. The <code>interpret()</code> method executes one - * line of Scala code at the request of the user. The <code>bind()</code> - * method binds an object to a variable that can then be used by later - * interpreted code. - * </p> - * <p> - * The overall approach is based on compiling the requested code and then - * using a Java classloader and Java reflection to run the code - * and access its results. - * </p> - * <p> - * In more detail, a single compiler instance is used - * to accumulate all successfully compiled or interpreted Scala code. To - * "interpret" a line of code, the compiler generates a fresh object that - * includes the line of code and which has public member(s) to export - * 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 - * that read from variables or methods defined in previous statements, "import" - * statements are used. - * </p> - * <p> - * This interpreter shares the strengths and weaknesses of using the - * full compiler-to-Java. The main strength is that interpreted code - * behaves exactly as does compiled code, including running at full speed. - * The main weakness is that redefining classes and methods is not handled - * properly, because rebinding at the Java level is technically difficult. - * </p> +/** An interpreter for Scala code. * - * @author Moez A. Abdel-Gawad - * @author Lex Spoon + * The main public entry points are compile(), interpret(), and bind(). + * The compile() method loads a complete Scala file. The interpret() method + * executes one line of Scala code at the request of the user. The bind() + * method binds an object to a variable that can then be used by later + * interpreted code. + * + * The overall approach is based on compiling the requested code and then + * using a Java classloader and Java reflection to run the code + * and access its results. + * + * In more detail, a single compiler instance is used + * to accumulate all successfully compiled or interpreted Scala code. To + * "interpret" a line of code, the compiler generates a fresh object that + * includes the line of code and which has public member(s) to export + * 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 + * that read from variables or methods defined in previous statements, "import" + * statements are used. + * + * This interpreter shares the strengths and weaknesses of using the + * full compiler-to-Java. The main strength is that interpreted code + * behaves exactly as does compiled code, including running at full speed. + * The main weakness is that redefining classes and methods is not handled + * properly, because rebinding at the Java level is technically difficult. + * + * @author Moez A. Abdel-Gawad + * @author Lex Spoon */ class IMain(val settings: Settings, protected val out: PrintWriter) { intp => @@ -275,6 +267,17 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { private def allReqAndHandlers = prevRequests.toList flatMap (req => req.handlers map (req -> _)) private def importHandlers = allHandlers collect { case x: ImportHandler => x } + /** Given a simple repl-defined name, returns the real name of + * the class representing it, e.g. for "Bippy" it may return + * + * $line19.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$Bippy + */ + def pathToFlatName(id: String): String = { + requestForIdent(id) match { + case Some(req) => req fullFlatName id + case _ => id + } + } def allDefinedNames = definedNameMap.keys.toList sortBy (_.toString) def pathToType(id: String): String = pathToName(newTypeName(id)) def pathToTerm(id: String): String = pathToName(newTermName(id)) @@ -484,8 +487,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { compileSources(new BatchSourceFile("<script>", code)) - /** Build a request from the user. <code>trees</code> is <code>line</code> - * after being parsed. + /** Build a request from the user. `trees` is `line` after being parsed. */ private def buildRequest(line: String, trees: List[Tree]): Request = new Request(line, trees) @@ -515,16 +517,16 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { Right(buildRequest(line, trees)) } - /** <p> + /** * 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. - * </p> - * <p> + * + * * The return value is whether the line was interpreter successfully, * e.g. that there were no parse errors. - * </p> + * * * @param line ... * @return ... @@ -585,8 +587,10 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { |} """.stripMargin.format(bindRep.evalName, boundType, boundType) ) - bindRep.call("set", value) - interpret("val %s = %s.value".format(name, bindRep.evalPath)) + 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 + } } def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) @@ -637,12 +641,19 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { def evalPath = pathTo(evalName) def printPath = pathTo(printName) - def call(name: String, args: Any*) = + def call(name: String, args: Any*): AnyRef = evalMethod(name).invoke(evalClass, args.map(_.asInstanceOf[AnyRef]): _*) + def callOpt(name: String, args: Any*): Option[AnyRef] = + try Some(call(name, args: _*)) + catch { case ex: Exception => + quietBind("lastException", ex) + None + } + lazy val readRoot = definitions.getModule(readPath) // the outermost wrapper lazy val evalClass = loadByName(evalPath) - lazy val evalValue = try Some(call(valueMethod)) catch { case ex: Exception => None } + lazy val evalValue = callOpt(valueMethod) def compile(source: String): Boolean = compileAndSaveRun("<console>", source) def afterTyper[T](op: => T): T = { @@ -703,6 +714,12 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { def fullPath(vname: String) = ( lineRep.readPath + accessPath + ".`%s`".format(vname) ) + /** Same as fullpath, but after it has been flattened, so: + * $line5.$iw.$iw.$iw.Bippy // fullPath + * $line5.$iw$$iw$$iw$Bippy // fullFlatName + */ + def fullFlatName(name: String) = + lineRep.readPath + accessPath.replace('.', '$') + "$" + name /** Code to access a variable with the specified name */ def fullPath(vname: Name): String = fullPath(vname.toString) @@ -849,7 +866,9 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { } try { - val execution = lineManager.set(originalLine)(lineRep call "$export") + val execution = lineManager.set(originalLine) { + lineRep call "$export" + } execution.await() execution.state match { @@ -1054,43 +1073,61 @@ object IMain { code println postamble } } - class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, null, intp.out) { - import intp._ - - /** Truncate a string if it is longer than isettings.maxPrintString */ - private def truncPrintString(str: String): String = { - val maxpr = isettings.maxPrintString - val trailer = "..." - if (!truncationOK || maxpr <= 0 || str.length <= maxpr) str - else (str take maxpr-3) + trailer + trait StrippingWriter { + def isStripping: Boolean + def stripImpl(str: String): String + def strip(str: String): String = if (isStripping) stripImpl(str) else str + } + trait TruncatingWriter { + def maxStringLength: Int + def isTruncating: Boolean + def truncate(str: String): String = { + if (isTruncating && str.length > maxStringLength) + (str take maxStringLength - 3) + "..." + else str + } + } + abstract class StrippingTruncatingWriter(out: PrintWriter) + extends PrintWriter(out) + with StrippingWriter + with TruncatingWriter { + self => + + def clean(str: String): String = truncate(strip(str)) + override def write(str: String) = super.write(clean(str)) + } + class ReplStrippingWriter(intp: IMain) extends StrippingTruncatingWriter(intp.out) { + import intp._ + def maxStringLength = isettings.maxPrintString + def isStripping = isettings.unwrapStrings + def isTruncating = reporter.truncationOK + + def stripImpl(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 + } } - /** Clean up a string for output */ - private def clean(str: String) = truncPrintString( - if (isettings.unwrapStrings) stripWrapperGunk(str) - else str - ) + // 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 removeLineWrapper(s: String) = s.replaceAll("""\$line\d+[./]\$(read|eval|print)[$.]""", "") + private def removeIWPackages(s: String) = s.replaceAll("""\$iw[$.]""", "") + } + class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, null, new ReplStrippingWriter(intp)) { override def printMessage(msg: String) { - if (totalSilence) - return - - out println clean(msg) - out.flush() + if (intp.totalSilence) () + else super.printMessage(msg) } } - - import scala.collection.generic.CanBuildFrom - def partialFlatMap[A, B, CC[X] <: Traversable[X]] - (coll: CC[A]) - (pf: PartialFunction[A, CC[B]]) - (implicit bf: CanBuildFrom[CC[A], B, CC[B]]) = - { - val b = bf(coll) - for (x <- coll collect pf) - b ++= x - - b.result - } } diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala index 2847dc53d0..80040be8d0 100644 --- a/src/compiler/scala/tools/nsc/interpreter/package.scala +++ b/src/compiler/scala/tools/nsc/interpreter/package.scala @@ -45,17 +45,6 @@ package object interpreter { private[nsc] def isQuoted(s: String) = (s.length >= 2) && (s.head == s.last) && ("\"'" contains s.head) - // 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 removeLineWrapper(s: String) = s.replaceAll("""\$line\d+\.\$(read|eval|print)[$.]""", "") - private def removeIWPackages(s: String) = s.replaceAll("""\$iw[$.]""", "") - - /** Heuristically strip interpreter wrapper prefixes - * from an interpreter output string. - */ - def stripWrapperGunk(str: String) = removeIWPackages(removeLineWrapper(str)) - /** Class objects */ def classForName(name: String): Option[JClass] = try Some(Class forName name) diff --git a/src/compiler/scala/tools/nsc/io/NullPrintStream.scala b/src/compiler/scala/tools/nsc/io/NullPrintStream.scala index 5bbdf97f5e..52c7ddc74b 100644 --- a/src/compiler/scala/tools/nsc/io/NullPrintStream.scala +++ b/src/compiler/scala/tools/nsc/io/NullPrintStream.scala @@ -22,4 +22,16 @@ object NullPrintStream extends NullPrintStream { body } } + + def sinkingSystemOutAndErr[T](body: => T): T = { + val savedOut = System.out + val savedErr = System.err + System setOut NullPrintStream + System setErr NullPrintStream + try body + finally { + System setOut savedOut + System setErr savedErr + } + } } diff --git a/src/compiler/scala/tools/nsc/io/Streamable.scala b/src/compiler/scala/tools/nsc/io/Streamable.scala index c472c82e20..6edca13b42 100644 --- a/src/compiler/scala/tools/nsc/io/Streamable.scala +++ b/src/compiler/scala/tools/nsc/io/Streamable.scala @@ -111,11 +111,11 @@ object Streamable { try f(stream) finally stream.close() - def bytes(is: InputStream): Array[Byte] = - new Bytes { val inputStream = is } toByteArray + def bytes(is: => InputStream): Array[Byte] = + new Bytes { def inputStream() = is } toByteArray - def slurp(is: InputStream)(implicit codec: Codec): String = - new Chars { val inputStream = is } slurp codec + def slurp(is: => InputStream)(implicit codec: Codec): String = + new Chars { def inputStream() = is } slurp codec def slurp(url: URL)(implicit codec: Codec): String = slurp(url.openStream()) diff --git a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala index 3691870179..ee71d04827 100644 --- a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala @@ -65,7 +65,7 @@ trait ScalaClassLoader extends JavaClassLoader { val url = this.getResource(name) if (url == null) Array() - else new io.Streamable.Bytes { def inputStream() = url.openStream } . toByteArray() + else io.Streamable.bytes(url.openStream) } /** Run the main method of a class to be loaded by this classloader */ diff --git a/src/compiler/scala/tools/util/Javap.scala b/src/compiler/scala/tools/util/Javap.scala new file mode 100644 index 0000000000..b31683b1f7 --- /dev/null +++ b/src/compiler/scala/tools/util/Javap.scala @@ -0,0 +1,104 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package util + +import java.io.{ InputStream, PrintWriter, ByteArrayInputStream, FileNotFoundException } +import java.lang.reflect.{ GenericSignatureFormatError, Method, Constructor } +import java.lang.{ ClassLoader => JavaClassLoader } +import scala.tools.nsc.util.ScalaClassLoader +import scala.tools.nsc.io.{ File, NullPrintStream } + +trait JavapResult { + type ResultType + def isError: Boolean + def value: ResultType + def show(): Unit + // todo + // def header(): String + // def fields(): List[String] + // def methods(): List[String] + // def signatures(): List[String] +} +class JavapError(msg: String) extends JavapResult { + type ResultType = String + def isError = true + def value = msg + def show() = println(msg) +} +class JavapSuccess(val value: AnyRef) extends JavapResult { + type ResultType = AnyRef + def isError = false + def show() = value.asInstanceOf[{ def print(): Unit }].print() +} + +class Javap(val loader: ScalaClassLoader) { + def this() = this(ScalaClassLoader.getSystemLoader()) + def defaultPrintWriter = new PrintWriter(System.out, true) + + private val envFieldsToSet = List[(String, Any)]( + // "showLineAndLocal" -> true, + "showDisassembled" -> true, + "showVerbose" -> true, + "showInternalSigs" -> true + ) + val Env = "sun.tools.javap.JavapEnvironment" + val Printer = "sun.tools.javap.JavapPrinter" + + val EnvClass = loader.tryToInitializeClass[AnyRef](Env).orNull + val PrinterClass = loader.tryToInitializeClass[AnyRef](Printer).orNull + val EnvCtr = EnvClass.getConstructor(List[Class[_]](): _*) + val PrinterCtr = PrinterClass.getConstructor(classOf[InputStream], classOf[PrintWriter], EnvClass) + + /** Assume the string is a path and try to find the classfile + * it represents. + */ + def tryFile(path: String): Option[Array[Byte]] = { + val file = File( + if (path.endsWith(".class")) path + else path.replace('.', '/') + ".class" + ) + if (!file.exists) None + else try Some(file.toByteArray) catch { case x: Exception => None } + } + /** Assume the string is a fully qualified class name and try to + * find the class object it represents. + */ + def tryClass(path: String): Array[Byte] = { + val extName = ( + if (path endsWith ".class") (path dropRight 6).replace('/', '.') + else path + ) + loader.findBytesForClassName(extName) + } + + def newEnv(): AnyRef = { + val env = EnvClass.newInstance() + envFieldsToSet foreach { case (name, value) => + val x = EnvClass getDeclaredField name + x setAccessible true + x.set(env, value.asInstanceOf[AnyRef]) + } + env + } + def newPrinter( + in: InputStream, + pw: PrintWriter = defaultPrintWriter, + env: AnyRef = newEnv() + ): AnyRef = { + PrinterCtr.newInstance(in, pw, env) + } + + def guess(path: String): JavapResult = { + val bytes = tryFile(path) getOrElse tryClass(path) + if (bytes.length > 0) new JavapSuccess(newPrinter(new ByteArrayInputStream(bytes))) + else new JavapError("Could not find class bytes for '%s'".format(path)) + } +} + +object Javap extends Javap(ScalaClassLoader.getSystemLoader()) { + def apply(path: String): AnyRef = guess(path) +} |