All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ammonite.runtime.Evaluator.scala Maven / Gradle / Ivy

There is a newer version: 0.8.3-3
Show newest version
package ammonite.runtime

import java.io.OutputStream
import java.lang.reflect.InvocationTargetException

import acyclic.file
import ammonite._
import util.Util.{ClassFiles, newLine}
import ammonite.util._

import scala.reflect.io.VirtualDirectory
import scala.util.Try

/**
 * Evaluates already-compiled Bytecode.
  *
  * Deals with all the munging of `Classloader`s, `Class[_]` objects,
  * and `Array[Byte]`s representing class files, and reflection necessary
  * to take the already-compile Scala bytecode and execute it in our process.
 */
trait Evaluator{
  def specialLocalClasses: Set[String]
  def loadClass(wrapperName: String, classFiles: ClassFiles): Res[Class[_]]
  def evalMain(cls: Class[_]): Any
  def getCurrentLine: String
  def update(newImports: Imports): Unit

  def processLine(classFiles: ClassFiles,
                  newImports: Imports,
                  printer: Printer,
                  fileName: String,
                  isExec: Boolean,
                  indexedWrapperName: Name): Res[Evaluated]

  def processScriptBlock(cls: Class[_],
                         newImports: Imports,
                         wrapperName: Name,
                         pkgName: Seq[Name],
                         tag: String): Res[Evaluated]

  def frames: List[Frame]

  def frames_=(newValue: List[Frame]): Unit

  def evalCachedClassFiles(cachedData: Seq[ClassFiles],
                           pkg: String,
                           wrapper: String,
                           dynamicClasspath: VirtualDirectory,
                           classFilesList: Seq[String]): Res[Seq[_]]

}

class EvaluatorImpl(currentClassloader: ClassLoader,
                    startingLine: Int) extends Evaluator{ eval =>


    /**
     * The current line number of the REPL, used to make sure every snippet
     * evaluated can have a distinct name that doesn't collide.
     */
    var currentLine = startingLine

    /**
     * Weird indirection only necessary because of
     * https://issues.scala-lang.org/browse/SI-7085
     */
    def getCurrentLine = currentLine.toString.replace("-", "_")

    /**
     * Performs the conversion of our pre-compiled `Array[Byte]`s into
     * actual classes with methods we can execute.
     */

    def initialFrame = {
      val hash = SpecialClassLoader.initialClasspathSignature(currentClassloader)
      def special = new SpecialClassLoader(eval.specialLocalClasses, currentClassloader, hash)
      new Frame(
        special,
        special,
        Imports(),
        Seq()
      )
    }
    var frames = List(initialFrame)


    def specialLocalClasses = Set(
      "ammonite.repl.ReplBridge",
      "ammonite.repl.ReplBridge$",
      "ammonite.runtime.InterpBridge",
      "ammonite.runtime.InterpBridge$"
    )

    def loadClass(fullName: String, classFiles: ClassFiles): Res[Class[_]] = {
      Res[Class[_]](Try {
        for ((name, bytes) <- classFiles.sortBy(_._1)) {
          frames.head.classloader.addClassFile(name, bytes)
        }
        val names = classFiles.map(_._1)
        val res = Class.forName(fullName, true, frames.head.classloader)
        res
      }, { e: Throwable =>

        val desc = Ex.unapplySeq(e).get.map(t => s"$t\n" + t.getStackTrace.map("  " + _).mkString("\n")).mkString("\n")

        s"Failed to load compiled class $fullName\n" + desc
      })
    }


    def evalMain(cls: Class[_]) = {
      cls.getDeclaredMethod("$main").invoke(null)
    }


    type InvEx = InvocationTargetException
    type InitEx = ExceptionInInitializerError

    val userCodeExceptionHandler: PartialFunction[Throwable, Res.Failing] = {
      // Exit
      case Ex(_: InvEx, _: InitEx, ReplExit(value))  => Res.Exit(value)

      // Interrupted during pretty-printing
      case Ex(e: ThreadDeath)                 =>  Evaluator.interrupted(e)

      // Interrupted during evaluation
      case Ex(_: InvEx, e: ThreadDeath)       =>  Evaluator.interrupted(e)

      case Ex(_: InvEx, _: InitEx, userEx@_*) => Res.Exception(userEx(0), "")
      case Ex(_: InvEx, userEx@_*)            => Res.Exception(userEx(0), "")
      case Ex(userEx@_*)                      => Res.Exception(userEx(0), "")
    }

    def process(printer: Printer, isExec: Boolean, value: AnyRef): Any =
      value.asInstanceOf[Iterator[String]].foreach(printer.out)

    def processLine(classFiles: Util.ClassFiles,
                    newImports: Imports,
                    printer: Printer,
                    fileName: String,
                    isExec: Boolean,
                    indexedWrapperName: Name) = {
      for {
        cls <- loadClass("$sess." + indexedWrapperName.backticked, classFiles)
        _ = currentLine += 1
        _ <- Catching{userCodeExceptionHandler}
      } yield {
        // Exhaust the printer iterator now, before exiting the `Catching`
        // block, so any exceptions thrown get properly caught and handled
        val processedValue = process(printer, isExec, Evaluator.evaluatorRunPrinter(evalMain(cls)))

        // "" Empty string as cache tag of repl code
        evaluationResult(Seq(Name("$sess"), indexedWrapperName), newImports, "", processedValue)
      }
    }


    def processScriptBlock(cls: Class[_],
                           newImports: Imports,
                           wrapperName: Name,
                           pkgName: Seq[Name],
                           tag: String) = {
      for {
        _ <- Catching{userCodeExceptionHandler}
      } yield {
        evalMain(cls)
        val res = evaluationResult(pkgName :+ wrapperName, newImports, tag, null)
        res
      }
    }

    def evalCachedClassFiles(cachedData: Seq[ClassFiles],
                             pkg: String,
                             wrapper: String,
                             dynamicClasspath: VirtualDirectory,
                             classFilesList: Seq[String]): Res[Seq[_]] = {
      Res.map(cachedData.zipWithIndex) {
        case (clsFiles, index) =>
          Evaluator.addToClasspath(clsFiles, dynamicClasspath)
          val cls = eval.loadClass(classFilesList(index), clsFiles)
          try cls.map(eval.evalMain(_))
          catch userCodeExceptionHandler
      }
    }

    def update(newImports: Imports) = {
      frames.head.addImports(newImports)
    }

    def evaluationResult(wrapperName: Seq[Name],
                         imports: Imports,
                         tag: String,
                         result: Any) = {
      Evaluated(
        wrapperName,
        Imports(
          for(id <- imports.value) yield {
            val filledPrefix =
              if (id.prefix.isEmpty) {
                // For some reason, for things not-in-packages you can't access
                // them off of `_root_`
                wrapperName ++ Seq(Name("wrapper"), Name("wrapper"))
              } else {
                id.prefix
              }
            val rootedPrefix: Seq[Name] =
              if (filledPrefix.headOption.exists(_.backticked == "_root_")) filledPrefix
              else Seq(Name("_root_")) ++ filledPrefix

            id.copy(prefix = rootedPrefix)
          }
        ),
        tag,
        result
      )
    }
  }

object Evaluator{

  def interrupted(e: Throwable) = {
    Thread.interrupted()
    Res.Failure(Some(e), newLine + "Interrupted!")
  }

  def apply(currentClassloader: ClassLoader,
            startingLine: Int): Evaluator = new EvaluatorImpl(currentClassloader, startingLine)
  /**
   * Dummy function used to mark this method call in the stack trace,
   * so we can easily cut out the irrelevant part of the trace when
   * showing it to the user.
   */
  def evaluatorRunPrinter[T](f: => T): T = f


  def writeDeep(d: VirtualDirectory,
                path: List[String],
                suffix: String): OutputStream = path match {
    case head :: Nil => d.fileNamed(path.head + suffix).output
    case head :: rest =>
      writeDeep(
        d.subdirectoryNamed(head).asInstanceOf[VirtualDirectory],
        rest, suffix
      )
  }

  /**
    * Writes files to dynamicClasspath. Needed for loading cached classes.
    */
  def addToClasspath(classFiles: Traversable[(String, Array[Byte])],
                     dynamicClasspath: VirtualDirectory): Unit = {
    val names = classFiles.map(_._1)
    for((name, bytes) <- classFiles){
      val output = writeDeep(dynamicClasspath, name.split('.').toList, ".class")
      output.write(bytes)
      output.close()
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy