aboutsummaryrefslogtreecommitdiff
path: root/libraries
diff options
context:
space:
mode:
authorSimeon H.K. Fitch <fitch@datamininglab.com>2015-02-05 05:57:45 +0000
committerChristopher Vogt <oss.nsp@cvogt.org>2016-11-07 02:08:39 -0500
commit44b6e6845c931aff4a69f98a0b44c8c69128369b (patch)
tree58b95a9814b5f989d6a5b91c18809cbafe840a3b /libraries
parent623cfdd2a0b26ef2d1e0a78025b950f2958b67b7 (diff)
downloadcbt-44b6e6845c931aff4a69f98a0b44c8c69128369b.tar.gz
cbt-44b6e6845c931aff4a69f98a0b44c8c69128369b.tar.bz2
cbt-44b6e6845c931aff4a69f98a0b44c8c69128369b.zip
Added ability to override `Reporter` used during compilation. See issue #89
Signed-off-by: Moses Nakamura <mnakamura@twitter.com> RB_ID=572344
Diffstat (limited to 'libraries')
-rw-r--r--libraries/eval/Eval.scala67
-rw-r--r--libraries/eval/test/EvalTest.scala54
2 files changed, 101 insertions, 20 deletions
diff --git a/libraries/eval/Eval.scala b/libraries/eval/Eval.scala
index e9c95fd..7e5fb71 100644
--- a/libraries/eval/Eval.scala
+++ b/libraries/eval/Eval.scala
@@ -27,7 +27,7 @@ import scala.collection.mutable
import scala.io.Source
import scala.tools.nsc.interpreter.AbstractFileClassLoader
import scala.tools.nsc.io.{AbstractFile, VirtualDirectory}
-import scala.tools.nsc.reporters.AbstractReporter
+import scala.tools.nsc.reporters.{Reporter, AbstractReporter}
import scala.tools.nsc.util.{BatchSourceFile, Position}
import scala.tools.nsc.{Global, Settings}
import scala.util.matching.Regex
@@ -108,8 +108,14 @@ class Eval(target: Option[File]) {
)
)
- private[this] val STYLE_INDENT = 2
- private[this] lazy val compiler = new StringCompiler(STYLE_INDENT, target)
+ // For derived classes to provide an alternate compiler message handler.
+ protected lazy val compilerMessageHandler: Option[Reporter] = None
+
+ // For derived classes do customize or override the default compiler settings.
+ protected lazy val compilerSettings: Settings = new EvalSettings(target)
+
+ // Primary encapsulation around native Scala compiler
+ private[this] lazy val compiler = new StringCompiler(codeWrapperLineOffset, target, compilerSettings, compilerMessageHandler)
/**
* run preprocessors on our string, returning a String that is the processed source
@@ -290,9 +296,10 @@ class Eval(target: Option[File]) {
}
/*
- * Wrap source code in a new class with an apply method.
+ * Wraps source code in a new class with an apply method.
+ * NB: If this method is changed, make sure `codeWrapperLineOffset` is correct.
*/
- private def wrapCodeInClass(className: String, code: String) = {
+ private[this] def wrapCodeInClass(className: String, code: String) = {
"class " + className + " extends (() => Any) {\n" +
" def apply() = {\n" +
code + "\n" +
@@ -301,6 +308,13 @@ class Eval(target: Option[File]) {
}
/*
+ * Defines the number of code lines that proceed evaluated code.
+ * Used to ensure compile error messages report line numbers aligned with user's code.
+ * NB: If `wrapCodeInClass(String,String)` is changed, make sure this remains correct.
+ */
+ private[this] val codeWrapperLineOffset = 2
+
+ /*
* For a given FQ classname, trick the resource finder into telling us the containing jar.
*/
private def classPathOfClass(className: String) = {
@@ -429,27 +443,34 @@ class Eval(target: Option[File]) {
}
}
+ lazy val compilerOutputDir = target match {
+ case Some(dir) => AbstractFile.getDirectory(dir)
+ case None => new VirtualDirectory("(memory)", None)
+ }
+
+ class EvalSettings(targetDir: Option[File]) extends Settings {
+ nowarnings.value = true // warnings are exceptions, so disable
+ outputDirs.setSingleOutput(compilerOutputDir)
+ private[this] val pathList = compilerPath ::: libPath
+ bootclasspath.value = pathList.mkString(File.pathSeparator)
+ classpath.value = (pathList ::: impliedClassPath).mkString(File.pathSeparator)
+ }
+
/**
* Dynamic scala compiler. Lots of (slow) state is created, so it may be advantageous to keep
* around one of these and reuse it.
*/
- private class StringCompiler(lineOffset: Int, targetDir: Option[File]) {
- val target = targetDir match {
- case Some(dir) => AbstractFile.getDirectory(dir)
- case None => new VirtualDirectory("(memory)", None)
- }
+ private class StringCompiler(
+ lineOffset: Int, targetDir: Option[File], settings: Settings, messageHandler: Option[Reporter]) {
val cache = new mutable.HashMap[String, Class[_]]()
+ val target = compilerOutputDir
- val settings = new Settings
- settings.nowarnings.value = true // warnings are exceptions, so disable
- settings.outputDirs.setSingleOutput(target)
-
- val pathList = compilerPath ::: libPath
- settings.bootclasspath.value = pathList.mkString(File.pathSeparator)
- settings.classpath.value = (pathList ::: impliedClassPath).mkString(File.pathSeparator)
+ trait MessageCollector {
+ val messages: Seq[List[String]]
+ }
- val reporter = new AbstractReporter {
+ val reporter = messageHandler getOrElse new AbstractReporter with MessageCollector {
val settings = StringCompiler.this.settings
val messages = new mutable.ListBuffer[List[String]]
@@ -509,7 +530,7 @@ class Eval(target: Option[File]) {
}
}
cache.clear()
- reporter.reset
+ reporter.reset()
classLoader = new AbstractFileClassLoader(target, this.getClass.getClassLoader)
}
@@ -557,7 +578,13 @@ class Eval(target: Option[File]) {
compiler.compileSources(sourceFiles)
if (reporter.hasErrors || reporter.WARNING.count > 0) {
- throw new CompilerException(reporter.messages.toList)
+ val msgs: List[List[String]] = reporter match {
+ case collector: MessageCollector =>
+ collector.messages.toList
+ case _ =>
+ List(List(reporter.toString))
+ }
+ throw new CompilerException(msgs)
}
}
diff --git a/libraries/eval/test/EvalTest.scala b/libraries/eval/test/EvalTest.scala
index 2f80894..e53dc47 100644
--- a/libraries/eval/test/EvalTest.scala
+++ b/libraries/eval/test/EvalTest.scala
@@ -5,10 +5,14 @@ import java.io.{File, FileWriter}
import scala.io.Source
import org.junit.runner.RunWith
+import org.scalatest.DiagrammedAssertions._
import org.scalatest.WordSpec
import org.scalatest.junit.JUnitRunner
import com.twitter.io.TempFile
+import scala.tools.nsc.reporters.{AbstractReporter, Reporter}
+import scala.tools.nsc.Settings
+import scala.tools.nsc.util.Position
@RunWith(classOf[JUnitRunner])
class EvalTest extends WordSpec {
@@ -166,5 +170,55 @@ class EvalTest extends WordSpec {
// with crazy things
assert(e.fileToClassName(new File("foo$! -@@@")) == "foo$24$21$20$2d$40$40$40")
}
+
+ "allow custom error reporting" when {
+ class Ctx {
+ val eval = new Eval {
+ @volatile var errors: Seq[(String, String)] = Nil
+
+ override lazy val compilerMessageHandler: Option[Reporter] = Some(new AbstractReporter {
+ override val settings: Settings = compilerSettings
+ override def displayPrompt(): Unit = ()
+ override def display(pos: Position, msg: String, severity: this.type#Severity): Unit = {
+ errors = errors :+ (msg, severity.toString)
+ }
+ override def reset() = {
+ super.reset()
+ errors = Nil
+ }
+ })
+ }
+ }
+
+ "not report errors on success" in {
+ val ctx = new Ctx
+ import ctx._
+
+ assert(eval[Int]("val a = 3; val b = 2; a + b", true) == 5)
+ assert(eval.errors.isEmpty)
+ }
+
+ "report errors on bad code" in {
+ val ctx = new Ctx
+ import ctx._
+
+ intercept[Throwable] {
+ eval[Int]("val a = 3; val b = q; a + b", true)
+ }
+ assert(eval.errors.nonEmpty)
+ }
+
+ "reset reporter between invocations" in {
+ val ctx = new Ctx
+ import ctx._
+
+ intercept[Throwable] {
+ eval[Int]("val a = 3; val b = q; a + b", true)
+ }
+ assert(eval.errors.nonEmpty)
+ assert(eval[Int]("val d = 3; val e = 2; d + e", true) == 5)
+ assert(eval.errors.isEmpty)
+ }
+ }
}
}