@@ -10,7 +10,7 @@ import java.util.jar.JarFile
1010import scala .tools .nsc .Settings
1111
1212import scala .tools .nsc .{ Global , Settings }
13- import scala .tools .nsc .reporters .StoreReporter
13+ import scala .tools .nsc .reporters ._
1414import scala .tools .nsc .io .{ VirtualDirectory , AbstractFile }
1515import scala .reflect .internal .util .{ NoPosition , BatchSourceFile , AbstractFileClassLoader }
1616
@@ -23,6 +23,9 @@ import scala.util.Try
2323import scala .util .control .NonFatal
2424import scala .concurrent .duration ._
2525import scala .language .reflectiveCalls
26+ import com .twitter .util .Eval
27+
28+ import scala .reflect .internal .util .{ BatchSourceFile , Position }
2629
2730sealed trait Severity
2831final case object Info extends Severity
@@ -34,6 +37,7 @@ case class CompilationInfo(message: String, pos: Option[RangePosition])
3437case class RuntimeError (val error : Throwable , position : Option [Int ])
3538
3639sealed trait EvalResult [+ T ]
40+
3741object EvalResult {
3842 type CI = Map [Severity , List [CompilationInfo ]]
3943
@@ -45,214 +49,59 @@ object EvalResult {
4549}
4650
4751class Evaluator (timeout : Duration = 20 .seconds) {
48- val security = false
4952
50- private def classPathOfClass (className : String ) = {
51- val resource = className.split('.' ).mkString(" /" , " /" , " .class" )
52- val path = getClass.getResource(resource).getPath
53- if (path.indexOf(" file:" ) >= 0 ) {
54- val indexOfFile = path.indexOf(" file:" ) + 5
55- val indexOfSeparator = path.lastIndexOf('!' )
56- List (path.substring(indexOfFile, indexOfSeparator))
57- } else {
58- require(path.endsWith(resource))
59- List (path.substring(0 , path.length - resource.length + 1 ))
53+ def convert (errors : (Position , String , String )): (Severity , List [CompilationInfo ]) = {
54+ val (pos, msg, severity) = errors
55+ val sev = severity match {
56+ case " ERROR" ⇒ Error
57+ case " WARNING" ⇒ Warning
58+ case _ ⇒ Info
6059 }
60+ (sev, CompilationInfo (msg, Some (RangePosition (pos.start, pos.point, pos.end))) :: Nil )
6161 }
6262
63- private val compilerPath = try {
64- classPathOfClass(" scala.tools.nsc.Interpreter" )
65- } catch {
66- case e : Throwable ⇒
67- throw new RuntimeException (" Unable to load Scala interpreter from classpath (scala-compiler jar is missing?)" , e)
68- }
69-
70- private val libPath = try {
71- classPathOfClass(" scala.AnyVal" )
72- } catch {
73- case e : Throwable ⇒
74- throw new RuntimeException (" Unable to load scala base object from classpath (scala-library jar is missing?)" , e)
75- }
76-
77- private val impliedClassPath : List [String ] = {
78- def getClassPath (cl : ClassLoader , acc : List [List [String ]] = List .empty): List [List [String ]] = {
79- val cp = cl match {
80- case urlClassLoader : URLClassLoader ⇒ urlClassLoader.getURLs.filter(_.getProtocol == " file" ).
81- map(u ⇒ new File (u.toURI).getPath).toList
82- case _ ⇒ Nil
83- }
84- cl.getParent match {
85- case null ⇒ (cp :: acc).reverse
86- case parent ⇒ getClassPath(parent, cp :: acc)
87- }
63+ def apply [T ](pre : String , code : String ): EvalResult [T ] = {
64+ val allCode = s """
65+ | $pre
66+ | $code
67+ """ .stripMargin
68+ val eval = new Eval {
69+ @ volatile var errors : Map [Severity , List [CompilationInfo ]] = Map .empty
70+
71+ override lazy val compilerMessageHandler : Option [Reporter ] = Some (new AbstractReporter {
72+ override val settings : Settings = compilerSettings
73+ override def displayPrompt (): Unit = ()
74+ override def display (pos : Position , msg : String , severity : this .type # Severity ): Unit = {
75+ errors += convert((pos, msg, severity.toString))
76+ }
77+ override def reset () = {
78+ super .reset()
79+ errors = Map .empty
80+ }
81+ })
8882 }
8983
90- val classPath = getClassPath(this .getClass.getClassLoader)
91- val currentClassPath = classPath.head
84+ val result = for {
85+ _ ← Try (eval.check(allCode))
86+ result ← Try (eval.apply[T ](allCode, resetState = true ))
87+ } yield result
9288
93- // if there's just one thing in the classpath, and it's a jar, assume an executable jar.
94- currentClassPath ::: (if (currentClassPath.size == 1 && currentClassPath(0 ).endsWith(" .jar" )) {
95- val jarFile = currentClassPath(0 )
96- val relativeRoot = new File (jarFile).getParentFile()
97- val nestedClassPath = new JarFile (jarFile).getManifest.getMainAttributes.getValue(" Class-Path" )
98- if (nestedClassPath eq null ) {
99- Nil
100- } else {
101- nestedClassPath.split(" " ).map { f ⇒ new File (relativeRoot, f).getAbsolutePath }.toList
102- }
103- } else {
104- Nil
105- }) ::: classPath.tail.flatten
106- }
107-
108- def apply [T ](pre : String , code : String ): EvalResult [T ] = synchronized {
109- try {
110- runTimeout[T ](pre, code)
111- } catch {
112- case NonFatal (e) ⇒ EvalResult .GeneralError (e)
113- }
114- }
89+ val errors : Map [Severity , List [CompilationInfo ]] = eval.errors.toMap
11590
116- private def runTimeout [T ](pre : String , code : String ) =
117- withTimeout { eval[T ](pre, code) }(timeout).getOrElse(EvalResult .Timeout )
91+ println(allCode)
11892
119- private def eval [T ](pre : String , code : String ): EvalResult [T ] = {
120- val className = " Eval" + Math .abs(scala.util.Random .nextLong).toString
121- val wrapedCode =
122- s """ | $pre
123- |class $className extends (() ⇒ Any) {
124- | def apply() = $code
125- |} """ .stripMargin
93+ println(result)
12694
127- secured { compile(wrapedCode) }
95+ println(errors)
12896
129- val complilationInfos = check()
130- if (! complilationInfos.contains(Error )) {
131- try {
132- val (result, consoleOutput) = run[T ](className)
133- EvalResult .Success (complilationInfos, result, consoleOutput)
134- } catch { case NonFatal (e) ⇒ EvalResult .EvalRuntimeError (complilationInfos, handleException(e)) }
135- } else {
136- EvalResult .CompilationError (complilationInfos)
137- }
138- }
139-
140- private def run [T ](className : String ) = {
141- val cl = Class .forName(className, false , classLoader)
142- val cons = cl.getConstructor()
143- secured {
144- val baos = new java.io.ByteArrayOutputStream ()
145- val ps = new java.io.PrintStream (baos)
146- val result = Console .withOut(ps)(cons.newInstance().asInstanceOf [() ⇒ Any ].apply().asInstanceOf [T ])
147- (result, baos.toString(" UTF-8" ))
148- }
149- }
150-
151- private def withTimeout [T ](f : ⇒ T )(timeout : Duration ): Option [T ] = {
152- val task = new FutureTask (new Callable [T ]() { def call = f })
153- val thread = new Thread (task)
154- try {
155- thread.start()
156- Some (task.get(timeout.toMillis, TimeUnit .MILLISECONDS ))
157- } catch {
158- case e : TimeoutException ⇒ None
159- } finally {
160- if (thread.isAlive) thread.stop()
161- }
162- }
163-
164- private def handleException (e : Throwable ): Option [RuntimeError ] = {
165- def search (e : Throwable ) = {
166- e.getStackTrace.find(_.getFileName == " (inline)" ).map(v ⇒
167- (e, Some (v.getLineNumber)))
168- }
169- def loop (e : Throwable ): Option [(Throwable , Option [Int ])] = {
170- val s = search(e)
171- if (s.isEmpty)
172- if (e.getCause != null ) loop(e.getCause)
173- else Some ((e, None ))
174- else s
175- }
176- loop(e).map {
177- case (err, line) ⇒
178- RuntimeError (err, line)
179- }
180- }
181-
182- private def check (): Map [Severity , List [CompilationInfo ]] = {
183- val infos =
184- reporter.infos.map { info ⇒
185- val pos = info.pos match {
186- case NoPosition ⇒ None
187- case _ ⇒ Some (RangePosition (info.pos.start, info.pos.point, info.pos.end))
188- }
189- (
190- info.severity,
191- info.msg,
192- pos
193- )
194- }.to[List ]
195- .groupBy(_._1)
196- .mapValues { _.map { case (_, msg, pos) ⇒ (msg, pos) } }
197-
198- def convert (infos : Map [reporter.Severity , List [(String , Option [RangePosition ])]]): Map [Severity , List [CompilationInfo ]] = {
199- infos.map {
200- case (k, vs) ⇒
201- val sev = k match {
202- case reporter.ERROR ⇒ Error
203- case reporter.WARNING ⇒ Warning
204- case reporter.INFO ⇒ Info
205- }
206- val info = vs map {
207- case (msg, pos) ⇒
208- CompilationInfo (msg, pos)
209- }
210- (sev, info)
97+ result match {
98+ case scala.util.Success (r) ⇒ EvalResult .Success [T ](errors, r, " " )
99+ case scala.util.Failure (t) ⇒ t match {
100+ case e : Eval .CompilerException ⇒ EvalResult .CompilationError (errors)
101+ case e ⇒ EvalResult .EvalRuntimeError (errors, None )
211102 }
212103 }
213- convert(infos)
214- }
215-
216- private def reset (): Unit = {
217- target.clear()
218- reporter.reset()
219- classLoader = new AbstractFileClassLoader (target, artifactLoader)
220- }
221-
222- private def compile (code : String ): Unit = {
223- reset()
224- val run = new compiler.Run
225- val sourceFiles = List (new BatchSourceFile (" (inline)" , code))
226- run.compileSources(sourceFiles)
104+
227105 }
228106
229- private val reporter = new StoreReporter ()
230-
231- val settings = new Settings ()
232- settings.processArguments(List (
233- " -deprecation" ,
234- " -encoding" , " UTF-8" ,
235- " -unchecked"
236- ), true )
237- val classpath = (compilerPath ::: libPath ::: impliedClassPath).mkString(File .pathSeparator)
238- settings.bootclasspath.value = classpath
239- settings.classpath.value = classpath
240-
241- private val artifactLoader = {
242- val loaderFiles =
243- settings.classpath.value.split(File .pathSeparator).map(a ⇒ {
244- val node = new java.io.File (a)
245- val endSlashed =
246- if (node.isDirectory) node.toString + File .separator
247- else node.toString
248-
249- new File (endSlashed).toURI().toURL
250- })
251- new URLClassLoader (loaderFiles, this .getClass.getClassLoader)
252- }
253- private val target = new VirtualDirectory (" (memory)" , None )
254- settings.outputDirs.setSingleOutput(target)
255- private var classLoader : AbstractFileClassLoader = _
256- private val compiler = new Global (settings, reporter)
257- private val secured = new Secured (security)
258107}
0 commit comments