summaryrefslogtreecommitdiff
path: root/src/compiler/scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-11-13 03:18:12 +0000
committerPaul Phillips <paulp@improving.org>2010-11-13 03:18:12 +0000
commit6bf1e7a2684c19f21ebc6367a39bc3e19b32e321 (patch)
tree3d3284bf656edc70a66c736e472077cc67bf6008 /src/compiler/scala
parenta061def4dd3d16b226f17a5246470255623177c2 (diff)
downloadscala-6bf1e7a2684c19f21ebc6367a39bc3e19b32e321.tar.gz
scala-6bf1e7a2684c19f21ebc6367a39bc3e19b32e321.tar.bz2
scala-6bf1e7a2684c19f21ebc6367a39bc3e19b32e321.zip
Another exciting development in the world of -Y...
Another exciting development in the world of -Y options which I and three other people will use. Today's is -Yrich-exceptions. Use it like so: SOURCEPATH=/path/to/src scalac -Yrich-exceptions a.scala In the repl, -Yrich-exceptions will cause lastException to be bound to an Exceptional instead of old rusty Throwable. That spins up new powers: scala> Nil.head [Nil.head] (List.scala:389) (access lastException for the full trace) scala> lastException.show /* The repl internal portion of the stack trace is elided. */ [Nil.head] 386: override def isEmpty = true 387: override def head: Nothing = 388: throw new NoSuchElementException("head of empty list") *389: override def tail: List[Nothing] = 390: throw new UnsupportedOperationException("tail of empty list") 391: // Removal of equals method here might lead to an infinite recursion similar to IntMap.equals. 392: override def equals(that: Any) = that match { [line0.<init>] (<console>:6) [line0.<clinit>] (<console>:-1) Also try "lastException.showTable" but this is getting a little long for more excerpt. No review.
Diffstat (limited to 'src/compiler/scala')
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala8
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala36
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterSettings.scala4
-rw-r--r--src/compiler/scala/tools/nsc/Main.scala78
-rw-r--r--src/compiler/scala/tools/nsc/io/Fileish.scala34
-rw-r--r--src/compiler/scala/tools/nsc/io/NullPrintStream.scala6
-rw-r--r--src/compiler/scala/tools/nsc/io/Path.scala16
-rw-r--r--src/compiler/scala/tools/nsc/io/SourceJar.scala23
-rw-r--r--src/compiler/scala/tools/nsc/io/Sources.scala73
-rw-r--r--src/compiler/scala/tools/nsc/io/Streamable.scala5
-rw-r--r--src/compiler/scala/tools/nsc/io/package.scala18
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala1
-rw-r--r--src/compiler/scala/tools/nsc/util/Exceptional.scala123
-rw-r--r--src/compiler/scala/tools/nsc/util/JavaStackFrame.scala64
-rw-r--r--src/compiler/scala/tools/nsc/util/ScalaPrefs.scala25
-rw-r--r--src/compiler/scala/tools/nsc/util/TableDef.scala95
-rw-r--r--src/compiler/scala/tools/nsc/util/package.scala10
-rw-r--r--src/compiler/scala/tools/util/StringOps.scala7
18 files changed, 571 insertions, 55 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 0f05a7fcce..d05eb26443 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -12,7 +12,7 @@ import compat.Platform.currentTime
import scala.collection.{ mutable, immutable }
import io.{ SourceReader, AbstractFile, Path }
import reporters.{ Reporter, ConsoleReporter }
-import util.{ ClassPath, SourceFile, Statistics, BatchSourceFile, ScriptSourceFile, ShowPickled, returning }
+import util.{ Exceptional, ClassPath, SourceFile, Statistics, BatchSourceFile, ScriptSourceFile, ShowPickled, returning }
import reflect.generic.{ PickleBuffer, PickleFormat }
import symtab.{ Flags, SymbolTable, SymbolLoaders }
@@ -147,6 +147,11 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
def logError(msg: String, t: Throwable): Unit = ()
def log(msg: => AnyRef): Unit = if (opt.logPhase) inform("[log " + phase + "] " + msg)
+ def logThrowable(t: Throwable): Unit = error(throwableAsString(t))
+ def throwableAsString(t: Throwable): String =
+ if (opt.richExes) Exceptional(t).force().context()
+ else util.stringFromWriter(t printStackTrace _)
+
// ------------ File interface -----------------------------------------
private val reader: SourceReader = {
@@ -227,6 +232,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
def logClasspath = settings.Ylogcp.value
def printLate = settings.printLate.value
def printStats = settings.Ystatistics.value
+ def richExes = settings.YrichExes.value
def showTrees = settings.Xshowtrees.value
def target = settings.target.value
def typerDebug = settings.Ytyperdebug.value
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index 0200773e05..3ef1a995b4 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -18,14 +18,14 @@ import scala.PartialFunction.{ cond, condOpt }
import scala.tools.util.PathResolver
import scala.reflect.Manifest
import scala.collection.mutable.{ ListBuffer, HashSet, HashMap, ArrayBuffer }
-import scala.tools.nsc.util.ScalaClassLoader
+import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional }
import ScalaClassLoader.URLClassLoader
import scala.util.control.Exception.{ Catcher, catching, catchingPromiscuously, ultimately, unwrapping }
import io.{ PlainFile, VirtualDirectory }
import reporters.{ ConsoleReporter, Reporter }
import symtab.{ Flags, Names }
-import util.{ SourceFile, BatchSourceFile, ScriptSourceFile, ClassPath, Chars, stringFromWriter }
+import util.{ ScalaPrefs, JavaStackFrame, SourceFile, BatchSourceFile, ScriptSourceFile, ClassPath, Chars, stringFromWriter }
import scala.reflect.NameTransformer
import scala.tools.nsc.{ InterpreterResults => IR }
import interpreter._
@@ -150,7 +150,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef,
ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef,
ImportSelector, EmptyTree, NoType }
- import compiler.{ nme, newTermName, newTypeName }
+ import compiler.{ opt, nme, newTermName, newTypeName }
import nme.{
INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX,
INTERPRETER_IMPORT_WRAPPER, INTERPRETER_WRAPPER_SUFFIX, USCOREkw
@@ -628,6 +628,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
interpret("val %s = %s.value".format(name, binderName))
}
+ def quietBind(name: String, clazz: Class[_], value: Any): IR.Result =
+ quietBind(name, clazz.getName, value) // XXX need to port toTypeString
def quietBind(name: String, boundType: String, value: Any): IR.Result =
beQuietDuring { bind(name, boundType, value) }
@@ -964,20 +966,36 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
getTypes(valueNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity)
}
+ 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 resultObjectName)
+ override def contextPrelude = super.contextPrelude + "/* The repl internal portion of the stack trace is elided. */\n"
+ }
+
+ quietBind("lastException", classOf[Exceptional], ex)
+ ex.contextHead + "\n(access lastException for the full trace)"
+ }
+ private def bindUnexceptionally(t: Throwable) = {
+ quietBind("lastException", classOf[Throwable], t)
+ stringFromWriter(t printStackTrace _)
+ }
/** load and run the code using reflection */
def loadAndRun: (String, Boolean) = {
- val resultValMethod: reflect.Method = loadedResultObject getMethod "scala_repl_result"
- // XXX if wrapperExceptions isn't type-annotated we crash scalac
- val wrapperExceptions: List[Class[_ <: Throwable]] =
- List(classOf[InvocationTargetException], classOf[ExceptionInInitializerError])
+ val resultValMethod = loadedResultObject getMethod "scala_repl_result"
+ val wrapperExceptions = List(classOf[InvocationTargetException], classOf[ExceptionInInitializerError])
/** We turn off the binding to accomodate ticket #2817 */
def onErr: Catcher[(String, Boolean)] = {
case t: Throwable if bindLastException =>
withoutBindingLastException {
- quietBind("lastException", "java.lang.Throwable", t)
- (stringFromWriter(t.printStackTrace(_)), false)
+ val message =
+ if (opt.richExes) bindExceptionally(t)
+ else bindUnexceptionally(t)
+
+ (message, false)
}
}
diff --git a/src/compiler/scala/tools/nsc/InterpreterSettings.scala b/src/compiler/scala/tools/nsc/InterpreterSettings.scala
index b53a6f6955..c04efe8e98 100644
--- a/src/compiler/scala/tools/nsc/InterpreterSettings.scala
+++ b/src/compiler/scala/tools/nsc/InterpreterSettings.scala
@@ -14,6 +14,10 @@ class InterpreterSettings(repl: Interpreter) {
/** A list of paths where :load should look */
var loadPath = List(".")
+ /** Set this to true to see repl machinery under -Yrich-exceptions.
+ */
+ var showInternalStackTraces = false
+
/** The maximum length of toString to use when printing the result
* of an evaluation. 0 means no maximum. If a printout requires
* more than this number of characters, then the printout is
diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala
index 1e6699079d..37fb5952e3 100644
--- a/src/compiler/scala/tools/nsc/Main.scala
+++ b/src/compiler/scala/tools/nsc/Main.scala
@@ -18,7 +18,6 @@ import Properties.{ versionString, copyrightString, residentPromptString, msilLi
* language Scala.
*/
object Main extends AnyRef with EvalLoop {
-
val versionMsg = "Scala compiler " +
versionString + " -- " +
copyrightString
@@ -28,13 +27,9 @@ object Main extends AnyRef with EvalLoop {
var reporter: ConsoleReporter = _
def error(msg: String) {
- reporter.error(/*new Position */FakePos("scalac"),
- msg + "\n scalac -help gives more information")
+ reporter.error(FakePos("scalac"), msg + "\n scalac -help gives more information")
}
- /* needed ?? */
- //def errors() = reporter.errors
-
def resident(compiler: Global) {
loop { line =>
val args = line.split(' ').toList
@@ -45,33 +40,36 @@ object Main extends AnyRef with EvalLoop {
}
def process(args: Array[String]) {
- val settings = new Settings(error)
- reporter = new ConsoleReporter(settings)
- val command = new CompilerCommand(args.toList, settings)
- if (command.settings.version.value)
+ val ss = new Settings(error)
+ reporter = new ConsoleReporter(ss)
+ val command = new CompilerCommand(args.toList, ss)
+ val settings = command.settings
+
+ if (settings.version.value)
reporter.info(null, versionMsg, true)
- else if (command.settings.Yidedebug.value) {
- command.settings.Xprintpos.value = true
- command.settings.Yrangepos.value = true
- val compiler = new interactive.Global(command.settings, reporter)
+ else if (settings.Yidedebug.value) {
+ settings.Xprintpos.value = true
+ settings.Yrangepos.value = true
+ val compiler = new interactive.Global(settings, reporter)
import compiler.{ reporter => _, _ }
- val sfs = command.files.map(getSourceFile(_))
+ val sfs = command.files map getSourceFile
val reloaded = new interactive.Response[Unit]
askReload(sfs, reloaded)
+
reloaded.get.right.toOption match {
case Some(ex) => reporter.cancelled = true // Causes exit code to be non-0
case None => reporter.reset // Causes other compiler errors to be ignored
}
askShutdown
- } else if (command.settings.Ybuilderdebug.value != "none") {
+ }
+ else if (settings.Ybuilderdebug.value != "none") {
def fileSet(files : List[String]) = Set.empty ++ (files map AbstractFile.getFile)
- val buildManager = if (command.settings.Ybuilderdebug.value == "simple")
- new SimpleBuildManager(settings)
- else
- new RefinedBuildManager(settings)
-
+ val buildManager = settings.Ybuilderdebug.value match {
+ case "simple" => new SimpleBuildManager(settings)
+ case _ => new RefinedBuildManager(settings)
+ }
buildManager.addSourceFiles(fileSet(command.files))
// enter resident mode
@@ -80,38 +78,42 @@ object Main extends AnyRef with EvalLoop {
val command = new CompilerCommand(args.toList, settings)
buildManager.update(fileSet(command.files), Set.empty)
}
- } else {
- if (command.settings.target.value == "msil")
- msilLibPath foreach (x => command.settings.assemrefs.value += (pathSeparator + x))
+ }
+ else {
+ if (settings.target.value == "msil")
+ msilLibPath foreach (x => settings.assemrefs.value += (pathSeparator + x))
- try {
- val compiler = if (command.settings.Yrangepos.value) new interactive.Global(command.settings, reporter)
- else new Global(command.settings, reporter)
+ val compiler =
+ if (settings.Yrangepos.value) new interactive.Global(settings, reporter)
+ else new Global(settings, reporter)
- if (reporter.hasErrors) {
- reporter.flush()
- return
- }
+ try {
+ if (reporter.hasErrors)
+ return reporter.flush()
if (command.shouldStopWithInfo) {
reporter.info(null, command.getInfoMessage(compiler), true)
- } else {
- if (command.settings.resident.value)
+ }
+ else {
+ if (settings.resident.value)
resident(compiler)
else if (command.files.isEmpty) {
reporter.info(null, command.usageMsg, true)
reporter.info(null, compiler.pluginOptionsHelp, true)
- } else {
+ }
+ else {
val run = new compiler.Run()
run compile command.files
reporter.printSummary()
}
}
- } catch {
- case ex @ FatalError(msg) =>
- if (true || command.settings.debug.value) // !!!
- ex.printStackTrace();
+ }
+ catch {
+ case FatalError(msg) =>
reporter.error(null, "fatal error: " + msg)
+ case ex if compiler.opt.richExes =>
+ compiler.logThrowable(ex)
+ throw ex
}
}
}
diff --git a/src/compiler/scala/tools/nsc/io/Fileish.scala b/src/compiler/scala/tools/nsc/io/Fileish.scala
new file mode 100644
index 0000000000..445b4fd11b
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/io/Fileish.scala
@@ -0,0 +1,34 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package io
+
+import java.io.{ InputStream }
+import java.util.jar.JarEntry
+
+/** A common interface for File-based things and Stream-based things.
+ * (In particular, io.File and JarEntry.)
+ */
+class Fileish(val path: Path, val input: () => InputStream) extends Streamable.Chars {
+ def inputStream() = input()
+
+ def parent = path.parent
+ def name = path.name
+ def isSourceFile = path.hasExtension("java", "scala")
+
+ private lazy val pkgLines = lines() collect { case x if x startsWith "package " => x stripPrefix "package" trim }
+ lazy val pkgFromPath = parent.path.replaceAll("""[/\\]""", ".")
+ lazy val pkgFromSource = pkgLines map (_ stripSuffix ";") mkString "."
+
+ override def toString = path.path
+}
+
+object Fileish {
+ def apply(f: File): Fileish = new Fileish(f, () => f.inputStream())
+
+ def apply(f: JarEntry, in: () => InputStream): Fileish = new Fileish(Path(f.getName), in)
+ def apply(path: String, in: () => InputStream): Fileish = new Fileish(Path(path), in)
+}
diff --git a/src/compiler/scala/tools/nsc/io/NullPrintStream.scala b/src/compiler/scala/tools/nsc/io/NullPrintStream.scala
index 9340796a83..fe2a0bf877 100644
--- a/src/compiler/scala/tools/nsc/io/NullPrintStream.scala
+++ b/src/compiler/scala/tools/nsc/io/NullPrintStream.scala
@@ -16,4 +16,10 @@ object NullPrintStream extends NullPrintStream {
def setOut() = Console setOut this
def setErr() = Console setErr this
def setOutAndErr() = { setOut() ; setErr() }
+ def sinkingOutAndErr[T](body: => T): T =
+ Console.withOut(this) {
+ Console.withErr(this) {
+ body
+ }
+ }
}
diff --git a/src/compiler/scala/tools/nsc/io/Path.scala b/src/compiler/scala/tools/nsc/io/Path.scala
index 4373cc64a5..5d3fcee11e 100644
--- a/src/compiler/scala/tools/nsc/io/Path.scala
+++ b/src/compiler/scala/tools/nsc/io/Path.scala
@@ -1,5 +1,6 @@
/* NSC -- new Scala compiler
* Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
*/
package scala.tools.nsc
@@ -41,6 +42,21 @@ object Path {
implicit def string2path(s: String): Path = apply(s)
implicit def jfile2path(jfile: JFile): Path = apply(jfile)
+ def locateJarByClass(clazz: Class[_]): Option[File] = {
+ try Some(File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()))
+ catch { case _: Exception => None }
+ }
+ /** Walks upward from wherever the scala library jar is searching for
+ * the given jar name. This approach finds the scala library jar in the
+ * release layout and in trunk builds going up from pack.
+ */
+ def locateJarByName(name: String): Option[File] = {
+ def toSrc(d: Directory) = d.dirs.toList map (_ / name)
+ def walk(d: Directory) = d.parents flatMap toSrc find (_.isFile) map (_.toFile)
+
+ locateJarByClass(classOf[ScalaObject]) flatMap (x => walk(x.parent))
+ }
+
// java 7 style, we don't use it yet
// object AccessMode extends Enumeration("AccessMode") {
// val EXECUTE, READ, WRITE = Value
diff --git a/src/compiler/scala/tools/nsc/io/SourceJar.scala b/src/compiler/scala/tools/nsc/io/SourceJar.scala
new file mode 100644
index 0000000000..6be77963cb
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/io/SourceJar.scala
@@ -0,0 +1,23 @@
+package scala.tools.nsc
+package io
+
+import java.io.IOException
+import Properties.{ javaHome }
+import Path.isJarOrZip
+import java.util.concurrent.ExecutionException
+import java.util.jar._
+import java.util.zip.ZipException
+import scala.util.matching.Regex
+import collection.JavaConverters._
+
+class SourceJar(jarfile: File) {
+ private def fail(entry: JarEntry) = throw new IOException("No such entry: " + entry)
+
+ val jarFile = new JarFile(jarfile.jfile)
+ def iterator: Iterator[Fileish] = jarFile.entries.asScala map (x => Fileish(x, () => getStream(x)))
+ def toList: List[Fileish] = iterator.toList
+ def getStream(entry: JarEntry) = Option(jarFile getInputStream entry) getOrElse fail(entry)
+ def filesNamed(name: String) = iterator filter (_.name == name)
+
+ override def toString = jarfile.toString
+}
diff --git a/src/compiler/scala/tools/nsc/io/Sources.scala b/src/compiler/scala/tools/nsc/io/Sources.scala
new file mode 100644
index 0000000000..a3f0e09bb2
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/io/Sources.scala
@@ -0,0 +1,73 @@
+package scala.tools.nsc
+package io
+
+import util.ClassPath
+import java.util.concurrent.{ Future, ConcurrentHashMap, ExecutionException }
+import java.util.zip.ZipException
+import Path.{ isJarOrZip, locateJarByName }
+import collection.JavaConverters._
+import Properties.{ envOrElse, propOrElse }
+
+class Sources(val path: String) {
+ val expandedPath = ClassPath.join(ClassPath expandPath path: _*)
+ val cache = new ConcurrentHashMap[String, List[Fileish]]
+ def allNames = cache.keys.asScala.toList.sorted
+ def apply(name: String) = get(name)
+ def size = cache.asScala.values map (_.length) sum
+
+ private var debug = false
+ private def dbg(msg: => Any) = if (debug) Console println msg
+ private val partitioned = ClassPath toPaths expandedPath partition (_.isDirectory)
+
+ val dirs = partitioned._1 map (_.toDirectory)
+ val jars = partitioned._2 filter isJarOrZip map (_.toFile)
+ val (isDone, force) = {
+ val f1 = spawn(calculateDirs())
+ val f2 = spawn(calculateJars())
+ val fn1 = () => { f1.isDone() && f2.isDone() }
+ val fn2 = () => { f1.get() ; f2.get() ; () }
+
+ (fn1, fn2)
+ }
+
+ private def catchZip(body: => Unit): Unit = {
+ try body
+ catch { case x: ZipException => dbg("Caught: " + x) }
+ }
+
+ private def calculateDirs() =
+ dirs foreach { d => dbg(d) ; catchZip(addSources(d.deepFiles map (x => Fileish(x)))) }
+
+ private def calculateJars() =
+ jars foreach { j => dbg(j) ; catchZip(addSources(new SourceJar(j).iterator)) }
+
+ private def addSources(fs: TraversableOnce[Fileish]) =
+ fs foreach { f => if (f.isSourceFile) add(f.name, f) }
+
+ private def get(key: String): List[Fileish] =
+ if (cache containsKey key) cache.get(key) else Nil
+
+ private def add(key: String, value: Fileish) = {
+ if (cache containsKey key) cache.replace(key, value :: cache.get(key))
+ else cache.put(key, List(value))
+ }
+ override def toString = "Sources(%d dirs, %d jars, %d sources)".format(
+ dirs.size, jars.size, cache.asScala.values map (_.length) sum
+ )
+}
+
+trait LowPrioritySourcesImplicits {
+ self: Sources.type =>
+
+ implicit def fallbackSources: Sources = defaultSources
+}
+
+
+object Sources extends LowPrioritySourcesImplicits {
+ val scalaSourceJars = List("scala-library-src.jar", "scala-compiler-src.jar")
+ val sourcePathEnv = envOrElse("SOURCEPATH", "")
+ val scalaLibraryJarPath = (scalaSourceJars map locateJarByName).flatten map (_.path)
+ val defaultSources = apply(scalaLibraryJarPath :+ sourcePathEnv: _*)
+
+ def apply(paths: String*): Sources = new Sources(ClassPath.join(paths: _*))
+}
diff --git a/src/compiler/scala/tools/nsc/io/Streamable.scala b/src/compiler/scala/tools/nsc/io/Streamable.scala
index 16a867ade8..93f17ca7ba 100644
--- a/src/compiler/scala/tools/nsc/io/Streamable.scala
+++ b/src/compiler/scala/tools/nsc/io/Streamable.scala
@@ -1,5 +1,6 @@
/* NSC -- new Scala compiler
* Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
*/
package scala.tools.nsc
@@ -9,7 +10,6 @@ import java.net.{ URI, URL }
import java.io.{ BufferedInputStream, InputStream, PrintStream, File => JFile }
import java.io.{ BufferedReader, InputStreamReader }
import scala.io.{ Codec, BufferedSource, Source }
-
import collection.mutable.ArrayBuffer
import Path.fail
@@ -19,8 +19,7 @@ import Path.fail
* @since 2.8
*/
-object Streamable
-{
+object Streamable {
/** Traits which can be viewed as a sequence of bytes. Source types
* which know their length should override def length: Long for more
* efficient method implementations.
diff --git a/src/compiler/scala/tools/nsc/io/package.scala b/src/compiler/scala/tools/nsc/io/package.scala
new file mode 100644
index 0000000000..18d374a617
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/io/package.scala
@@ -0,0 +1,18 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+
+import java.util.concurrent.{ Future, Callable, Executors }
+
+package object io {
+ def runnable(body: => Unit): Runnable = new Runnable { override def run() = body }
+ def callable[T](body: => T): Callable[T] = new Callable[T] { override def call() = body }
+ def spawn[T](body: => T): Future[T] = Executors.newSingleThreadExecutor() submit callable[T](body)
+ def submit(runnable: Runnable) = Executors.newSingleThreadExecutor() submit runnable
+ def runnableFn(f: () => Unit): Runnable = runnable(f())
+ def callableFn[T](f: () => T): Callable[T] = callable(f())
+ def spawnFn[T](f: () => T): Future[T] = spawn(f())
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 98e2f4aacf..2c767d2e78 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -136,6 +136,7 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings {
List("no-cache", "mono-cache", "poly-cache", "invoke-dynamic"), "poly-cache") .
withHelpSyntax("-Ystruct-dispatch:<method>")
val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.")
+ val YrichExes = BooleanSetting ("-Yrich-exceptions", "More revealing exceptions. Set SOURCEPATH to java/scala source jars.")
val Yidedebug = BooleanSetting ("-Yide-debug", "Generate, validate and output trees using the interactive compiler.")
val Ybuilderdebug = ChoiceSetting ("-Ybuilder-debug", "Compile using the specified build manager", List("none", "refined", "simple"), "none") .
withHelpSyntax("-Ybuilder-debug:<method>")
diff --git a/src/compiler/scala/tools/nsc/util/Exceptional.scala b/src/compiler/scala/tools/nsc/util/Exceptional.scala
new file mode 100644
index 0000000000..c5fc92443d
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/util/Exceptional.scala
@@ -0,0 +1,123 @@
+package scala.tools.nsc
+package util
+
+import java.lang.reflect.InvocationTargetException
+import io.{ Sources, Fileish }
+import scala.tools.util.StringOps._
+
+/** A simple throwable wrapper so it looks more like a parade of
+ * glittering frame-shaped beauties than the other thing.
+ */
+class Exceptional(val ex: Throwable)(implicit prefs: ScalaPrefs) {
+ val formatter = prefs.exceptionFormatter(ex)
+ val unwrapped = Exceptional.unwrap(ex)
+ val table = formatter.newTable(unwrapped)
+ def rawTrace() = unwrapped.printStackTrace()
+ def isScanDone = prefs.codeSources.isDone()
+
+ /** Block until the scanning is complete. */
+ def force(): this.type = {
+ prefs.codeSources.force()
+ this
+ }
+
+ /** Stack frame contexts are only shown as long as this is true. */
+ def spanFn(frame: JavaStackFrame): Boolean = true
+
+ /** The result of this will be printed before a context trace. */
+ def contextPrelude: String =
+ if (isScanDone) ""
+ else "/* Still scanning source path: there may be more momentarily. */\n"
+
+ /** Frames with surrounding context. */
+ private def contextFrames = toList takeWhile spanFn
+ def contextHead(): String = contextElems.headOption getOrElse ""
+ def contextElems() = contextFrames map formatter.inContext
+ def context(): String = context(length)
+ def context(num: Int): String = contextPrelude + ojoinOr(contextFrames take num map formatter.inContext, "\n", "No stack trace.")
+
+ /** Exceptional doesn't extend Seq because it turns out to be super
+ * annoying in the repl: tab-completion sees all the Seq methods.
+ */
+ def length = toList.length
+ def toList = table.toList
+ def iterator = table.iterator
+ def apply(index: Int) = table(index)
+
+ def causes = Exceptional.causes(ex)
+ def summary = unwrapped.toString + "\n at " + apply(0).shortNameString
+
+ def show(): Unit = println(context())
+ def show(num: Int): Unit = println(context(num))
+ def showCauses() = println((ex :: causes).mkString("", "\n caused by -> ", ""))
+ def showTable() = println(table)
+ def showSummary() = println(summary)
+
+ override def toString = summary
+}
+
+
+object Exceptional {
+ /** The Throwable => Exceptional implicit plus the associated factory. */
+ implicit def throwableToExceptional(ex: Throwable)(implicit prefs: ScalaPrefs): Exceptional = apply(ex)(prefs)
+ def apply(ex: Throwable)(implicit prefs: ScalaPrefs) = new Exceptional(ex)(prefs)
+
+ /** Some handy functions. */
+ def stack() = JavaStackFrame frames ((new Throwable).getStackTrace dropWhile isLocal)
+ def showme() = apply(new Throwable).show()
+
+ /** A frame formatter with more refined aesthetics than the default.
+ * Come, let us be civilized.
+ */
+ object ScalaFormat extends TableDef[JavaStackFrame] {
+ >> ("file" -> (_.fileName)) >+ ":"
+ << ("line" -> (_.line))
+ >> ("class" -> (_.shortestName)) >+ "."
+ << ("method" -> (_.methodName))
+ }
+
+ trait Calibrated {
+ def newTable(ex: Throwable): TableDef[JavaStackFrame]#Table
+ def inContext(frame: JavaStackFrame): String
+ }
+ trait Formatter extends (Throwable => Calibrated) {
+ def apply(ex: Throwable): Calibrated
+ }
+ object Formatter {
+ def apply(implicit prefs: ScalaPrefs): Formatter = new Formatter {
+ def apply(ex: Throwable) = new Calibrated {
+ def newTable(ex: Throwable) = new ScalaFormat.Table(JavaStackFrame frames ex)
+ def inContext(frame: JavaStackFrame) = new FrameContext(frame, prefs.codeSources) toString
+ }
+ }
+ }
+
+ /** Java stack traces have the interesting property of using only the name
+ * of the file, no paths. This makes it a bit of a gamble to try to associate
+ * a stack frame with a specific file. Witness the heuristic.
+ */
+ def locateSources(sources: Sources, frame: JavaStackFrame): List[Fileish] = {
+ // if only one has a matching path, that's fairly sure to be the one
+ val matches = sources(frame.fileName) filter (_.pkgFromPath endsWith frame.pkgName)
+ if (matches.isEmpty || matches.tail.isEmpty)
+ return matches
+
+ // otherwise we'll drink them in and look for a package name
+ matches filter (_.pkgFromSource endsWith frame.pkgName)
+ }
+
+ /** Right now this punts if more than one match and it accepts the first at random.
+ */
+ def locateSource(sources: Sources, frame: JavaStackFrame): Option[Fileish] =
+ locateSources(sources, frame).headOption
+
+ def isLocal(ste: StackTraceElement) = ste.getClassName startsWith this.getClass.getName
+ def causes(x: Throwable): List[Throwable] = x.getCause match {
+ case null => Nil
+ case ex => x :: causes(ex)
+ }
+ def unwrap(x: Throwable): Throwable = x match {
+ case _: InvocationTargetException | _: ExceptionInInitializerError if x.getCause != null => unwrap(x.getCause)
+ case _ => x
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/util/JavaStackFrame.scala b/src/compiler/scala/tools/nsc/util/JavaStackFrame.scala
new file mode 100644
index 0000000000..a08c9ed3b8
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/util/JavaStackFrame.scala
@@ -0,0 +1,64 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package util
+
+import io.{ Fileish, Sources }
+import Exceptional._
+
+class FrameContext(frame: JavaStackFrame, codeSources: Sources) {
+ val sourceFile = locateSource(codeSources, frame)
+ import frame._
+
+ def windowWidth = 3
+ def windowSize = windowWidth * 2 + 1
+
+ lazy val context = sourceFile collect {
+ case f if line > 0 =>
+ val start = math.max(0, line - windowWidth)
+ f.lines().toList.slice(start, start + windowSize)
+ } getOrElse Nil
+
+ protected def fallbackContext = "%s (%s:%s)".format(tag, fileName, line)
+
+ private def linestr(index: Int) = {
+ val current = line - windowWidth + index
+ val marker = if (current == line) "*" else " "
+ marker + current
+ }
+ private def contextLines = context.zipWithIndex map {
+ case (l, i) => linestr(i) + ": " + l + "\n"
+ }
+ override def toString =
+ if (context.isEmpty) fallbackContext
+ else contextLines.mkString(tag + "\n", "", "")
+}
+
+class JavaStackFrame(val elem: StackTraceElement) {
+ def className: String = elem.getClassName()
+ def methodName: String = elem.getMethodName()
+ def fileName: String = elem.getFileName()
+ def line: Int = elem.getLineNumber()
+
+ private def segs = className takeWhile (ch => ch != '$' && ch != '(') split '.' toList ;
+ lazy val pkgName = segs.init mkString "."
+ lazy val shortName = segs.last
+ lazy val shortestName = if (fileName startsWith (shortName + ".")) "<--" else shortName
+
+ private def standardString(which: String) =
+ "%s.%s(%s:%s)".format(which, methodName, fileName, line)
+
+ def javaString = standardString(className)
+ def shortNameString = standardString(shortName)
+ def tag = "[%s.%s]".format(shortName, methodName)
+
+ override def toString = shortNameString
+}
+
+object JavaStackFrame {
+ def frames(xs: Array[StackTraceElement]): Array[JavaStackFrame] = xs map (x => new JavaStackFrame(x))
+ def frames(t: Throwable): Array[JavaStackFrame] = frames(Exceptional.unwrap(t).getStackTrace)
+}
diff --git a/src/compiler/scala/tools/nsc/util/ScalaPrefs.scala b/src/compiler/scala/tools/nsc/util/ScalaPrefs.scala
new file mode 100644
index 0000000000..816a00d5db
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/util/ScalaPrefs.scala
@@ -0,0 +1,25 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package util
+
+import io.Sources
+
+trait ScalaPrefs {
+ def codeSources: Sources
+ def exceptionFormatter: Exceptional.Formatter
+}
+
+trait LowPriorityScalaPrefs {
+ implicit object DefaultScalaPrefs extends ScalaPrefs {
+ def codeSources = Sources.defaultSources
+ def exceptionFormatter = Exceptional.Formatter(this)
+ }
+}
+
+object ScalaPrefs extends LowPriorityScalaPrefs {
+ def apply(implicit prefs: ScalaPrefs): ScalaPrefs = prefs
+}
diff --git a/src/compiler/scala/tools/nsc/util/TableDef.scala b/src/compiler/scala/tools/nsc/util/TableDef.scala
new file mode 100644
index 0000000000..b18eaaa400
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/util/TableDef.scala
@@ -0,0 +1,95 @@
+package scala.tools.nsc
+package util
+
+import TableDef._
+
+/** A class for representing tabular data in a way that preserves
+ * its inner beauty. See JavaStackFrame for an example usage.
+ * One creates an instance of TableDef by defining the columns of
+ * the table, then uses that to create an instance of Table by
+ * passing in a sequence of rows.
+ */
+class TableDef[T](_cols: Column[T]*) {
+ /** These operators are about all there is to it.
+ *
+ * ~ appends a column to the table
+ * >> creates a right-justified column and appends it
+ * << creates a left-justified column and appends it
+ * >+ specifies a string to separate the previous column from the next.
+ * if none is specified, a space is used.
+ */
+ def ~(next: Column[T]) = retThis(cols :+= next)
+ def >>(pair: (String, T => Any)) = this ~ Column(pair._1, pair._2, false)
+ def <<(pair: (String, T => Any)) = this ~ Column(pair._1, pair._2, true)
+ def >+(sep: String) = retThis(separators += ((cols.size - 1, sep)))
+
+ /** Below this point should all be considered private/internal.
+ */
+ private var cols: List[Column[T]] = _cols.toList
+ private var separators: Map[Int, String] = Map()
+
+ def defaultSep(index: Int) = if (index > (cols.size - 2)) "" else " "
+ def sepAfter(i: Int): String = separators.getOrElse(i, defaultSep(i))
+ def sepWidths = cols.indices map (i => sepAfter(i).length)
+
+ def columns = cols
+ def colNames = cols map (_.name)
+ def colFunctions = cols map (_.f)
+ def colApply(el: T) = colFunctions map (f => f(el))
+ def retThis(body: => Unit): this.type = { body ; this }
+
+ class Table(val rows: Seq[T]) extends Seq[T] {
+ def iterator = rows.iterator
+ def apply(index: Int) = rows(index)
+ def length = rows.length
+
+ def maxColWidth(col: Column[T]) = col.name +: (rows map col.f) map (_.toString.length) max
+ def specs = cols map (_ formatSpec rows)
+
+ val colWidths = cols map maxColWidth
+ val rowFormat = mkFormatString(sepAfter)
+ val headFormat = mkFormatString(i => " " * sepWidths(i))
+ val argLists = rows map colApply
+
+ val headers = List(
+ headFormat.format(colNames: _*),
+ (colWidths, sepWidths).zipped map ((w1, w2) => "-" * w1 + " " * w2) mkString
+ )
+
+ def mkFormatString(sepf: Int => String): String =
+ specs.zipWithIndex map { case (c, i) => c + sepf(i) } mkString
+
+ def pp(): Unit = allToSeq foreach println
+
+ def toFormattedSeq = argLists map (xs => rowFormat.format(xs: _*))
+ def allToSeq = headers ++ toFormattedSeq
+
+ override def toString = allToSeq mkString "\n"
+ }
+
+ def formatterFor(rows: Seq[T]): T => String = {
+ val formatStr = new Table(rows).rowFormat
+
+ x => formatStr.format(colApply(x) : _*)
+ }
+
+ def table(rows: Seq[T]) = new Table(rows)
+
+ override def toString = cols.mkString("TableDef(", ", ", ")")
+}
+
+object TableDef {
+ case class Column[-T](name: String, f: T => Any, left: Boolean) {
+ def maxWidth(elems: Seq[T]): Int = name +: (elems map f) map (_.toString.length) max
+ def formatSpec(elems: Seq[T]): String = {
+ val justify = if (left) "-" else ""
+ "%" + justify + maxWidth(elems) + "s"
+ }
+ override def toString = {
+ val justify = if (left) "<<" else ">>"
+ justify + "(" + name + ")"
+ }
+ }
+
+ def apply[T](cols: Column[T]*) = new TableDef[T](cols: _*)
+}
diff --git a/src/compiler/scala/tools/nsc/util/package.scala b/src/compiler/scala/tools/nsc/util/package.scala
index 427e385aca..7924d4debd 100644
--- a/src/compiler/scala/tools/nsc/util/package.scala
+++ b/src/compiler/scala/tools/nsc/util/package.scala
@@ -8,6 +8,8 @@ package scala.tools.nsc
import java.io.{ OutputStream, PrintStream, ByteArrayOutputStream, PrintWriter, StringWriter }
package object util {
+ def onull[T](value: T, orElse: => T): T = if (value == null) orElse else value
+
/** Apply a function and return the passed value */
def returning[T](x: T)(f: T => Unit): T = { f(x) ; x }
@@ -35,10 +37,10 @@ package object util {
* execution to complete.
*/
def waitingForThreads[T](body: => T) = {
- val ts1 = allThreads()
- val result = body
- val ts2 = allThreads()
- val newThreads = (ts2.toSet -- ts1) filterNot (_.isDaemon())
+ val ts1 = allThreads()
+ val result = body
+ val ts2 = allThreads()
+ val newThreads = ts2.toSet -- ts1 filterNot (_.isDaemon())
newThreads foreach (_.join())
result
diff --git a/src/compiler/scala/tools/util/StringOps.scala b/src/compiler/scala/tools/util/StringOps.scala
index 1a42c32fc8..a3e7c512eb 100644
--- a/src/compiler/scala/tools/util/StringOps.scala
+++ b/src/compiler/scala/tools/util/StringOps.scala
@@ -17,6 +17,13 @@ package util
* @version 1.0
*/
object StringOps {
+ def oempty(xs: String*) = xs filterNot (x => x == null || x == "")
+ def ojoin(xs: Seq[String], sep: String) = oempty(xs: _*) mkString sep
+ def ojoinOr(xs: Seq[String], sep: String, orElse: String) = {
+ val ys = oempty(xs: _*)
+ if (ys.isEmpty) orElse else ys mkString sep
+ }
+
def decompose(str: String, sep: Char): List[String] = {
def ws(start: Int): List[String] =
if (start == str.length) List()