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

ammonite.repl.interp.Compiler.scala Maven / Gradle / Ivy

There is a newer version: 1.6.7-2-c28002d
Show newest version
package ammonite.repl
package interp


import acyclic.file
import ammonite.repl.ImportData
import ammonite.repl.Util.ClassFiles
import scala.collection.mutable
import scala.reflect.internal.util.Position
import scala.reflect.io
import scala.reflect.io._
import scala.tools.nsc
import scala.tools.nsc.{Global, Settings}
import scala.tools.nsc.backend.JavaPlatform
import scala.tools.nsc.interactive.Response
import scala.tools.nsc.plugins.Plugin

import scala.tools.nsc.reporters.AbstractReporter
import scala.tools.nsc.util.ClassPath.JavaContext
import scala.tools.nsc.util._


/**
 * Encapsulates (almost) all the ickiness of Scalac so it doesn't leak into
 * the rest of the codebase. Makes use of a good amount of mutable state
 * for things like the log-output-forwarder or compiler-plugin-output because
 * These things are hard-coded into Scalac and can't be passed in from run to
 * run.
 *
 * Turns source-strings into the bytes of classfiles, possibly more than one
 * classfile per source-string (e.g. inner classes, or lambdas). Also lets
 * you query source strings using an in-built presentation compiler
 */
trait Compiler{
  def compiler: nsc.Global
  def compile(src: Array[Byte],
              printer: Printer): Compiler.Output

  def search(name: scala.reflect.runtime.universe.Type): Option[String]
  /**
   * Either the statements that were parsed or the error message
   */
  def parse(line: String): Either[String, Seq[Global#Tree]]

  /**
   * Writes files to dynamicClasspath. Needed for loading cached classes.
   */ 
  def addToClasspath(classFiles: ClassFiles): Unit
}
object Compiler{
  /**
   * If the Option is None, it means compilation failed
   * Otherwise it's a Traversable of (filename, bytes) tuples
   */
  type Output = Option[(Traversable[(String, Array[Byte])], Seq[ImportData])]

  /**
   * Converts a bunch of bytes into Scalac's weird VirtualFile class
   */
  def makeFile(src: Array[Byte], name: String = "Main.scala") = {
    val singleFile = new io.VirtualFile(name)
    val output = singleFile.output
    output.write(src)
    output.close()
    singleFile
  }

  /**
   * Converts Scalac's weird Future type
   * into a standard scala.concurrent.Future
   */
  def awaitResponse[T](func: Response[T] => Unit): T = {
    val r = new Response[T]
    func(r)
    r.get.fold(
      x => x,
      e => throw e
    )
  }

  /**
   * Code to initialize random bits and pieces that are needed
   * for the Scala compiler to function, common between the
   * normal and presentation compiler
   */
  def initGlobalBits(classpath: Seq[java.io.File],
                     dynamicClasspath: VirtualDirectory,
                     errorLogger: => String => Unit,
                     warningLogger: => String => Unit,
                     infoLogger: => String => Unit) = {
    val vd = new io.VirtualDirectory("(memory)", None)
    lazy val settings = new Settings
    val settingsX = settings
    val jCtx = new JavaContext()
    val (dirDeps, jarDeps) = classpath.partition(_.isDirectory)

    val jarCP =
      jarDeps.filter(x => x.getName.endsWith(".jar") || Classpath.canBeOpenedAsJar(x))
             .map(x => new DirectoryClassPath(new FileZipArchive(x), jCtx))
             .toVector

    val dirCP =
      dirDeps.map(x => new DirectoryClassPath(new PlainDirectory(new Directory(x)), jCtx))

    val dynamicCP = Seq(new DirectoryClassPath(dynamicClasspath, jCtx))
    val jcp = new JavaClassPath(jarCP ++ dirCP ++ dynamicCP, jCtx)

    if (Classpath.traceClasspathIssues) {
      settings.Ylogcp.value = true
      println("jardeps")
      jarDeps.foreach(p => println(s"${p.getName.takeRight(4)} $p"))
      println("finished")
    }

    settings.outputDirs.setSingleOutput(vd)
    settings.nowarnings.value = true
    val reporter = new AbstractReporter {
      def displayPrompt(): Unit = ???

      def display(pos: Position, msg: String, severity: Severity) = {
        severity match{
          case ERROR =>
            Classpath.traceClasspathProblem(s"ERROR: $msg")
            errorLogger(Position.formatMessage(pos, msg, false))
          case WARNING =>
            warningLogger(Position.formatMessage(pos, msg, false))
          case INFO =>
            infoLogger(Position.formatMessage(pos, msg, false))
        }
      }

      val settings = settingsX
    }
    (settings, reporter, vd, jcp)
  }

  def apply(classpath: Seq[java.io.File],
            dynamicClasspath: VirtualDirectory,
            evalClassloader: => ClassLoader,
            pluginClassloader: => ClassLoader,
            shutdownPressy: () => Unit): Compiler = new Compiler{

    val PluginXML = "scalac-plugin.xml"

    lazy val plugins0 = {
      import scala.collection.JavaConverters._
      val loader = pluginClassloader

      val urls = loader
        .getResources(PluginXML)
        .asScala
        .toVector

      val plugins = for {
        url <- urls
        elem = scala.xml.XML.load(url.openStream())
        name = (elem \\ "plugin" \ "name").text
        className = (elem \\ "plugin" \ "classname").text
        if name.nonEmpty && className.nonEmpty
        classOpt =
          try Some(loader.loadClass(className))
          catch { case _: ClassNotFoundException => None }
      } yield (name, className, classOpt)

      val notFound = plugins.collect{case (name, className, None) => (name, className) }
      if (notFound.nonEmpty) {
        for ((name, className) <- notFound.sortBy(_._1))
          Console.err.println(s"Implementation $className of plugin $name not found.")
      }

      plugins.collect{case (name, _, Some(cls)) => name -> cls }
    }

    var errorLogger: String => Unit = s => ()
    var warningLogger: String => Unit = s => ()
    var infoLogger: String => Unit = s => ()

    var lastImports = Seq.empty[ImportData]

    val (vd, reporter, compiler) = {
      val (settings, reporter, vd, jcp) = initGlobalBits(
        classpath, dynamicClasspath, errorLogger, warningLogger, infoLogger
      )
      val scalac = new nsc.Global(settings, reporter) { g =>
        override lazy val plugins = List(new AmmonitePlugin(g, lastImports = _)) ++ {
          for {
            (name, cls) <- plugins0
            plugin = Plugin.instantiate(cls, g)
            initOk =
              try CompilerCompatibility.pluginInit(plugin, Nil, g.globalError)
              catch { case ex: Exception =>
                Console.err.println(s"Warning: disabling plugin $name, initialization failed: $ex")
                false
              }
            if initOk
          } yield plugin
        }

        // Actually jcp, avoiding a path-dependent type issue in 2.10 here
        override def classPath = platform.classPath
        override lazy val platform: ThisPlatform = new JavaPlatform{
          val global: g.type = g
          override def classPath = jcp
        }
        override lazy val analyzer = CompilerCompatibility.analyzer(g, evalClassloader)
      }
      // Initialize scalac to the parser phase immediately, so we can start
      // using Compiler#parse even if we haven't compiled any compilation
      // units yet due to caching
      val run = new scalac.Run()
      scalac.phase = run.parserPhase
      run.cancel()
      (vd, reporter, scalac)
    }

    def search(target: scala.reflect.runtime.universe.Type) = {
      def resolve(path: String*): compiler.Symbol = {
        var curr = path.toList
        var start: compiler.Symbol = compiler.RootClass
        while(curr != Nil){
          val head :: rest = curr
          start = start.typeSignature.member(compiler.newTermName(head))
          curr = rest
        }
        start
      }
      var thingsInScope = Map[compiler.Symbol, List[compiler.Name]](
        resolve() -> List(),
        resolve("java", "lang") -> List(),
        resolve("scala") -> List(),
        resolve("scala", "Predef") -> List()
      )
      var level = 5
      var found: Option[String] = None
      while(level > 0){
        thingsInScope = for {
          (sym, path) <- thingsInScope
          // No clue why this one blows up
          m <- util.Try(sym.typeSignature.members).toOption.toSeq.flatten
        } yield (m, m.name :: path)
        thingsInScope.find(target.typeSymbol.fullName == _._1.fullName).foreach{ path =>
          level = 0
          found = Some(path._2.mkString("."))
        }
      }
      found
    }
    /**
     * Compiles a blob of bytes and spits of a list of classfiles
     */
    def compile(src: Array[Byte],
                printer: Printer): Output = {
      compiler.reporter.reset()
      this.errorLogger = printer.error
      this.warningLogger = printer.warning
      this.infoLogger = printer.info
      val singleFile = makeFile( src)

      val run = new compiler.Run()
      vd.clear()
      run.compileFiles(List(singleFile))
      if (reporter.hasErrors) None
      else Some{
        shutdownPressy()

        val files = for{
          x <- vd.iterator.to[collection.immutable.Traversable]
          if x.name.endsWith(".class")
        } yield {
          val output = dynamicClasspath.fileNamed(x.name).output
          output.write(x.toByteArray)
          output.close()
          (x.name.stripSuffix(".class"), x.toByteArray)
        }
        val imports = lastImports.toList
        (files, imports)
      }
    }

    def addToClasspath(classFiles: Traversable[(String, Array[Byte])]): Unit = {
      for((name, bytes) <- classFiles){
        val output = dynamicClasspath.fileNamed(s"$name.class").output
        output.write(bytes)
        output.close()
      }
    }

    def parse(line: String): Either[String, Seq[Global#Tree]]= {
      val errors = mutable.Buffer.empty[String]
      val warnings = mutable.Buffer.empty[String]
      val infos = mutable.Buffer.empty[String]
      errorLogger = errors.append(_)
      warningLogger = warnings.append(_)
      infoLogger = infos.append(_)
      reporter.reset()
      val parser = compiler.newUnitParser(line)
      val trees = CompilerCompatibility.trees(compiler)(parser)
      if (reporter.hasErrors) Left(errors.mkString("\n"))
      else Right(trees)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy