From 5ebbba7a711105d8b6b19ce6497b3dcef0039c6f Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 29 Mar 2011 02:59:31 +0000 Subject: Polishing the programmatic interface to the rep... Polishing the programmatic interface to the repl and other bits of machinery which we'll have to live with for a while. The repl classloader now works more like you'd expect a classloader to, as seen here: % scala -Dscala.repl.power scala> class Bippus extends Traversable[Int] { def foreach[U](f: Int => U) = () } defined class Bippus scala> intp.classLoader.getResourceAsStream("Bippus").bytes() res0: Array[Byte] = Array(-54, -2, -70, ... scala> res0.size res1: Int = 23954 scala> case class Bippy(x: Int) defined class Bippy // classBytes is shorter way to say the same thing scala> intp.classLoader.classBytes("Bippy").size res2: Int = 2356 scala> intp.classLoader.classBytes("Bippy$").size res3: Int = 1741 Closes #4399, no review. --- .../nsc/interpreter/AbstractFileClassLoader.scala | 41 +++++++---- .../scala/tools/nsc/interpreter/ILoop.scala | 4 +- .../scala/tools/nsc/interpreter/IMain.scala | 37 +++++++--- .../scala/tools/nsc/interpreter/Phased.scala | 28 +++++--- .../scala/tools/nsc/interpreter/Power.scala | 80 ++++++++++++++++------ .../scala/tools/nsc/interpreter/package.scala | 2 + .../scala/tools/nsc/util/ScalaClassLoader.scala | 21 +++--- src/compiler/scala/tools/util/Javap.scala | 2 +- src/scalap/scala/tools/scalap/Decode.scala | 4 +- 9 files changed, 151 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala index 39c27cfbb1..85dcff2086 100644 --- a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala @@ -5,8 +5,9 @@ package scala.tools.nsc package interpreter -import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.io.{ File, AbstractFile } import util.ScalaClassLoader +import java.net.URL /** * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. @@ -17,28 +18,42 @@ class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) extends ClassLoader(parent) with ScalaClassLoader { - private def findBytes(name: String, onError: => Array[Byte]): Array[Byte] = { + protected def classNameToPath(name: String): String = + if (name endsWith ".class") name + else name.replace('.', '/') + ".class" + + protected def findAbstractFile(name: String): AbstractFile = { var file: AbstractFile = root - val pathParts = name.split("[./]").toList + val pathParts = classNameToPath(name) split '/' for (dirPart <- pathParts.init) { file = file.lookupName(dirPart, true) if (file == null) - return onError + return null } - file.lookupName(pathParts.last+".class", false) match { - case null => onError - case file => file.toByteArray + file.lookupName(pathParts.last, false) match { + case null => null + case file => file } } - override def findBytesForClassName(name: String): Array[Byte] = - findBytes(name, super.findBytesForClassName(name)) - + override def getResourceAsStream(name: String) = findAbstractFile(name) match { + case null => super.getResourceAsStream(name) + case file => file.input + } + override def classBytes(name: String): Array[Byte] = findAbstractFile(name) match { + case null => super.classBytes(name) + case file => file.toByteArray + } override def findClass(name: String): JClass = { - val bytes = findBytes(name, throw new ClassNotFoundException(name)) - defineClass(name, bytes, 0, bytes.length) + val bytes = classBytes(name) + if (bytes.isEmpty) throw new ClassNotFoundException(name) + else defineClass(name, bytes, 0, bytes.length) } + // Don't know how to construct an URL for something which exists only in memory + // override def getResource(name: String): URL = findAbstractFile(name) match { + // case null => super.getResource(name) + // case file => new URL(...) + // } } - diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index d018a5420d..8972bb6420 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -311,8 +311,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) // 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 pathToFlatName path - def moduleName = (intp pathToFlatName path.stripSuffix("$")) + "$" + def className = intp flatName path + def moduleName = (intp flatName path.stripSuffix("$")) + "$" val bytes = super.tryClass(className) if (bytes.nonEmpty) bytes diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 1a50b87569..5681196ef1 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -11,7 +11,7 @@ import java.io.{ PrintWriter } import java.lang.reflect import java.net.URL import util.{ Set => _, _ } -import io.VirtualDirectory +import io.{ AbstractFile, VirtualDirectory } import reporters.{ ConsoleReporter, Reporter } import symtab.{ Flags, Names } import scala.tools.nsc.interpreter.{ Results => IR } @@ -78,7 +78,16 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { import formatting._ /** directory to save .class files to */ - val virtualDirectory = new VirtualDirectory("(memory)", None) + val virtualDirectory = new VirtualDirectory("(memory)", None) { + private def pp(root: io.AbstractFile, indentLevel: Int) { + val spaces = " " * indentLevel + out.println(spaces + root.name) + if (root.isDirectory) + root.toList sortBy (_.name) foreach (x => pp(x, indentLevel + 1)) + } + // print the contents hierarchically + def show() = pp(this, 0) + } /** reporter */ lazy val reporter: ConsoleReporter = new IMain.ReplReporter(this) @@ -265,7 +274,19 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { if (parentClassLoader == null) ScalaClassLoader fromURLs compilerClasspath else new URLClassLoader(compilerClasspath, parentClassLoader) - new AbstractFileClassLoader(virtualDirectory, parent) + new AbstractFileClassLoader(virtualDirectory, parent) { + /** Overridden here to try translating a simple name to the generated + * class name if the original attempt fails. This method is used by + * getResourceAsStream as well as findClass. + */ + override protected def findAbstractFile(name: String): AbstractFile = { + super.findAbstractFile(name) match { + // deadlocks on startup if we try to translate names too early + case null if isInitializeComplete => generatedName(name) map (x => super.findAbstractFile(x)) orNull + case file => file + } + } + } } private def loadByName(s: String): Class[_] = (classLoader tryToInitializeClass s) getOrElse sys.error("Failed to load expected class: '" + s + "'") @@ -283,12 +304,12 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { * * $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 generatedName(simpleName: String): Option[String] = { + if (simpleName endsWith "$") optFlatName(simpleName.init) map (_ + "$") + else optFlatName(simpleName) } + def flatName(id: String) = optFlatName(id) getOrElse id + def optFlatName(id: String) = requestForIdent(id) map (_ fullFlatName id) def allDefinedNames = definedNameMap.keys.toList sortBy (_.toString) def pathToType(id: String): String = pathToName(newTypeName(id)) diff --git a/src/compiler/scala/tools/nsc/interpreter/Phased.scala b/src/compiler/scala/tools/nsc/interpreter/Phased.scala index 1406d2af39..b3d33325e4 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Phased.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Phased.scala @@ -6,6 +6,9 @@ package scala.tools.nsc package interpreter +import scala.collection.{ mutable, immutable } +import immutable.SortedMap + /** Mix this into an object and use it as a phasing * swiss army knife. */ @@ -63,11 +66,14 @@ trait Phased { try parseInternal(str) catch { case _: Exception => NoPhaseName } - def apply[T](body: => T): T = atPhase(get)(body) + def apply[T](body: => T): SortedMap[PhaseName, T] = + SortedMap[PhaseName, T](atMap(PhaseName.all)(body): _*) + + def atCurrent[T](body: => T): T = atPhase(get)(body) def multi[T](body: => T): Seq[T] = multi map (ph => at(ph)(body)) - def all[T](body: => T): Seq[T] = ats(PhaseName.all)(body) - def allshow[T](body: => T): Seq[T] = { - val pairs = atz(PhaseName.all)(body) + def all[T](body: => T): Seq[T] = atMulti(PhaseName.all)(body) + def show[T](body: => T): Seq[T] = { + val pairs = atMap(PhaseName.all)(body) pairs foreach { case (ph, op) => Console.println("%15s -> %s".format(ph, op.toString take 240)) } pairs map (_._2) } @@ -75,25 +81,27 @@ trait Phased { def at[T](ph: PhaseName)(body: => T): T = { val saved = get set(ph) - try apply(body) + try atCurrent(body) finally set(saved) } - def ats[T](phs: Seq[PhaseName])(body: => T): Seq[T] = { + def atMulti[T](phs: Seq[PhaseName])(body: => T): Seq[T] = { val saved = multi setMulti(phs) try multi(body) finally setMulti(saved) } - def atshow[T](phs: Seq[PhaseName])(body: => T): Unit = - atz[T](phs)(body) foreach { + def showAt[T](phs: Seq[PhaseName])(body: => T): Unit = + atMap[T](phs)(body) foreach { case (ph, op) => Console.println("%15s -> %s".format(ph, op.toString take 240)) } - def atz[T](phs: Seq[PhaseName])(body: => T): Seq[(PhaseName, T)] = - phs zip ats(phs)(body) + def atMap[T](phs: Seq[PhaseName])(body: => T): Seq[(PhaseName, T)] = + phs zip atMulti(phs)(body) object PhaseName { + implicit lazy val phaseNameOrdering: Ordering[PhaseName] = Ordering[Int] on (_.id) + lazy val all = List( Parser, Namer, Packageobjects, Typer, Superaccessors, Pickler, Refchecks, Selectiveanf, Liftcode, Selectivecps, Uncurry, Tailcalls, Specialize, diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index 01b2d2328f..d608cccd56 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -11,6 +11,9 @@ import scala.collection.{ mutable, immutable } import scala.util.matching.Regex import scala.tools.nsc.util.{ BatchSourceFile } import session.{ History } +import scala.io.Codec +import java.net.{ URL, MalformedURLException } +import io.{ Path } trait SharesGlobal[G <: Global] { val global: G @@ -130,13 +133,14 @@ abstract class Power[G <: Global]( def init = """ |import scala.tools.nsc._ |import interpreter.Power + |import scala.collection.JavaConverters._ |final val global = repl.power.global |final val power = repl.power.asInstanceOf[Power[global.type]] |final val intp = repl.intp |import global._ |import definitions._ - |import power.phased |import power.Implicits.{ global => _, _ } + |import power.Utilities._ """.stripMargin /** Starts up power mode and runs whatever is in init. @@ -212,10 +216,10 @@ abstract class Power[G <: Global]( trait LowPriorityPrettifier { implicit object AnyPrettifier extends Prettifier[Any] { def prettify(x: Any): List[String] = x match { - case x: Name => List(x.decode) - case Tuple2(k, v) => List(prettify(k) ++ Seq("->") ++ prettify(v) mkString " ") - case xs: Traversable[_] => (xs.toList flatMap prettify).sorted.distinct - case x => List("" + x) + 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.distinct + case x => List(Utilities.stringOf(x)) } } } @@ -240,7 +244,7 @@ abstract class Power[G <: Global]( def grep(x: T, p: String => Boolean): Unit = prettify(x) filter p foreach (x => println(spaces + x)) } - class MultiPrintingConvenience[T: Prettifier](coll: Traversable[T]) { + class MultiPrintingConvenience[T: Prettifier](coll: TraversableOnce[T]) { val pretty = implicitly[Prettifier[T]] import pretty._ @@ -278,36 +282,72 @@ abstract class Power[G <: Global]( def >(r: Regex): Unit = >(_ matches r.pattern.toString) def >(p: String => Boolean): Unit = pretty.grep(value, p) } + class RichInputStream(in: InputStream)(implicit codec: Codec) { + def bytes(): Array[Byte] = io.Streamable.bytes(in) + def slurp(): String = io.Streamable.slurp(in) + } + protected trait Implicits1 { // fallback implicit def replPrinting[T](x: T)(implicit pretty: Prettifier[T] = Prettifier.default[T]) = new PrintingConvenience[T](x) } - object Implicits extends Implicits1 with SharesGlobal[G] { - val global = Power.this.global + trait Implicits2 extends Implicits1 with SharesGlobal[G] { import global._ - implicit lazy val powerNameOrdering: Ordering[Name] = Ordering[String] on (_.toString) - implicit lazy val powerSymbolOrdering: Ordering[Symbol] = Ordering[Name] on (_.name) - implicit lazy val powerTypeOrdering: Ordering[Type] = Ordering[Symbol] on (_.typeSymbol) - + class RichSymbol(sym: Symbol) { + // convenient type application + def apply(targs: Type*): Type = typeRef(NoPrefix, sym, targs.toList) + } object symbolSubtypeOrdering extends Ordering[Symbol] { def compare(s1: Symbol, s2: Symbol) = if (s1 eq s2) 0 else if (s1 isLess s2) -1 else 1 } - implicit def replCollPrinting[T: Prettifier](xs: Traversable[T]): MultiPrintingConvenience[T] = new MultiPrintingConvenience[T](xs) + implicit lazy val powerNameOrdering: Ordering[Name] = Ordering[String] on (_.toString) + 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 replInternalInfo[T: Manifest](x: T): InternalInfo[T] = new InternalInfo[T](Some(x)) implicit def replPrettifier[T] : Prettifier[T] = Prettifier.default[T] - implicit def vararsTypeApplication(sym: Symbol) = new { - def apply(targs: Type*) = typeRef(NoPrefix, sym, targs.toList) - } + 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 = Power.this.global + } + trait ReplUtilities { def ?[T: Manifest] = InternalInfo[T] + def url(s: String) = { + try new URL(s) + catch { case _: MalformedURLException => + if (Path(s).exists) Path(s).toURL + else new URL("http://" + s) + } + } + def sanitize(s: String): String = sanitize(s.getBytes()) + def sanitize(s: Array[Byte]): String = (s map { + case x if x.toChar.isControl => '?' + case x => x.toChar + }).mkString + + def strings(s: Seq[Byte]): List[String] = { + if (s.length == 0) Nil + else s dropWhile (_.toChar.isControl) span (x => !x.toChar.isControl) match { + case (next, rest) => next.map(_.toChar).mkString :: strings(rest) + } + } + def stringOf(x: Any): String = scala.runtime.ScalaRunTime.stringOf(x) } - - object phased extends Phased with SharesGlobal[G] { - val global: G = Power.this.global + object Utilities extends ReplUtilities { + object phased extends Phased with SharesGlobal[G] { + val global: G = Power.this.global + } } + lazy val phased = Utilities.phased + def context(code: String) = analyzer.rootContext(unit(code)) def source(code: String) = new BatchSourceFile("", code) def unit(code: String) = new CompilationUnit(source(code)) @@ -320,7 +360,7 @@ abstract class Power[G <: Global]( |Names: %s |Identifiers: %s """.stripMargin.format( - phased.get, + Utilities.phased.get, intp.allDefinedNames mkString " ", intp.unqualifiedIds mkString " " ) diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala index 13fb860128..b5ba2d9b8e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/package.scala +++ b/src/compiler/scala/tools/nsc/interpreter/package.scala @@ -26,6 +26,8 @@ package object interpreter { type JClass = java.lang.Class[_] type JList[T] = java.util.List[T] type JCollection[T] = java.util.Collection[T] + type InputStream = java.io.InputStream + type OutputStream = java.io.OutputStream private[nsc] val DebugProperty = "scala.repl.debug" private[nsc] val TraceProperty = "scala.repl.trace" diff --git a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala index be69c39547..d6d19eef92 100644 --- a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala @@ -37,12 +37,8 @@ trait ScalaClassLoader extends JavaClassLoader { Class.forName(path, initialize, this).asInstanceOf[Class[T]] /** Create an instance of a class with this classloader */ - def create(path: String): AnyRef = { - tryToInitializeClass(path) match { - case Some(clazz) => clazz.newInstance() - case None => null - } - } + def create(path: String): AnyRef = + tryToInitializeClass[AnyRef](path) map (_.newInstance()) orNull override def findClass(name: String) = { val result = super.findClass(name) @@ -60,14 +56,15 @@ trait ScalaClassLoader extends JavaClassLoader { manifest[T].erasure.getConstructors.toList map (_.asInstanceOf[Constructor[T]]) /** The actual bytes for a class file, or an empty array if it can't be found. */ - def findBytesForClassName(s: String): Array[Byte] = { - val name = s.replaceAll("""\.""", "/") + ".class" - val url = this.getResource(name) - - if (url == null) Array() - else io.Streamable.bytes(url.openStream) + def classBytes(className: String): Array[Byte] = classAsStream(className) match { + case null => Array() + case stream => io.Streamable.bytes(stream) } + /** An InputStream representing the given class name, or null if not found. */ + def classAsStream(className: String) = + getResourceAsStream(className.replaceAll("""\.""", "/") + ".class") + /** Run the main method of a class to be loaded by this classloader */ def run(objectName: String, arguments: Seq[String]) { val clsToRun = tryToInitializeClass(objectName) getOrElse ( diff --git a/src/compiler/scala/tools/util/Javap.scala b/src/compiler/scala/tools/util/Javap.scala index bda8ffbd1b..581cc9dbef 100644 --- a/src/compiler/scala/tools/util/Javap.scala +++ b/src/compiler/scala/tools/util/Javap.scala @@ -77,7 +77,7 @@ class Javap( if (path endsWith ".class") (path dropRight 6).replace('/', '.') else path ) - loader.findBytesForClassName(extName) + loader.classBytes(extName) } } diff --git a/src/scalap/scala/tools/scalap/Decode.scala b/src/scalap/scala/tools/scalap/Decode.scala index c8bb58c81e..816041720f 100644 --- a/src/scalap/scala/tools/scalap/Decode.scala +++ b/src/scalap/scala/tools/scalap/Decode.scala @@ -33,7 +33,7 @@ object Decode { */ def scalaSigBytes(name: String): Option[Array[Byte]] = scalaSigBytes(name, getSystemLoader()) def scalaSigBytes(name: String, classLoader: ScalaClassLoader): Option[Array[Byte]] = { - val bytes = classLoader.findBytesForClassName(name) + val bytes = classLoader.classBytes(name) val reader = new ByteArrayReader(bytes) val cf = new Classfile(reader) cf.scalaSigAttribute map (_.data) @@ -43,7 +43,7 @@ object Decode { */ def scalaSigAnnotationBytes(name: String): Option[Array[Byte]] = scalaSigAnnotationBytes(name, getSystemLoader()) def scalaSigAnnotationBytes(name: String, classLoader: ScalaClassLoader): Option[Array[Byte]] = { - val bytes = classLoader.findBytesForClassName(name) + val bytes = classLoader.classBytes(name) val byteCode = ByteCode(bytes) val classFile = ClassFileParser.parse(byteCode) import classFile._ -- cgit v1.2.3