interpreter.scala

来自「JAVA 语言的函数式编程扩展」· SCALA 代码 · 共 960 行 · 第 1/3 页

SCALA
960
字号
/* NSC -- new Scala compiler * Copyright 2005-2008 LAMP/EPFL * @author  Martin Odersky */// $Id: Interpreter.scala 14530 2008-04-07 10:27:53Z washburn $package scala.tools.nscimport java.io.{File, PrintWriter, StringWriter, Writer}import java.lang.{Class, ClassLoader}import java.net.{URL, URLClassLoader}import scala.collection.immutable.ListSetimport scala.collection.mutableimport scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer}//import ast.parser.SyntaxAnalyzerimport io.PlainFileimport reporters.{ConsoleReporter, Reporter}import symtab.Flagsimport util.{SourceFile,BatchSourceFile,ClassPath,NameTransformer}import nsc.{InterpreterResults=>IR}/** <p> *    An interpreter for Scala code. *  </p> *  <p> *    The main public entry points are <code>compile()</code> and *    <code>interpret()</code>. The <code>compile()</code> method loads a *    complete Scala file.  The <code>interpret()</code> method executes one *    line of Scala code at the request of the user. *  </p> *  <p> *    The overall approach is based on compiling the requested code and then *    using a Java classloader and Java reflection to run the code *    and access its results. *  </p> *  <p>   *    In more detail, a single compiler instance is used *    to accumulate all successfully compiled or interpreted Scala code.  To *    "interpret" a line of code, the compiler generates a fresh object that *    includes the line of code and which has public member(s) to export *    all variables defined by that code.  To extract the result of an *    interpreted line to show the user, a second "result object" is created *    which imports the variables exported by the above object and then *    exports a single member named "result".  To accomodate user expressions *    that read from variables or methods defined in previous statements, "import" *    statements are used. *  </p> *  <p> *    This interpreter shares the strengths and weaknesses of using the *    full compiler-to-Java.  The main strength is that interpreted code *    behaves exactly as does compiled code, including running at full speed. *    The main weakness is that redefining classes and methods is not handled *    properly, because rebinding at the Java level is technically difficult. *  </p> * * @author Moez A. Abdel-Gawad * @author Lex Spoon */class Interpreter(val settings: Settings, out: PrintWriter) {  import symtab.Names  /* If the interpreter is running on pre-jvm-1.5 JVM,      it is necessary to force the target setting to jvm-1.4 */  private val major = System.getProperty("java.class.version").split("\\.")(0)  if (major.toInt < 49) {    this.settings.target.value = "jvm-1.4"  }  /** the compiler to compile expressions with */  val compiler: scala.tools.nsc.Global = newCompiler(settings, reporter)  import compiler.Traverser  import compiler.{Tree, TermTree,                   ValOrDefDef, ValDef, DefDef, Assign,                   ClassDef, ModuleDef, Ident, Select, TypeDef,                   Import, MemberDef, DocDef}  import compiler.CompilationUnit  import compiler.{Symbol,Name,Type}  import compiler.nme  import compiler.newTermName  import compiler.nme.{INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX}  import Interpreter.string2code  /** construct an interpreter that reports to Console */  def this(settings: Settings) =    this(settings,         new NewLinePrintWriter(new ConsoleWriter, true))  /** whether to print out result lines */  private var printResults: Boolean = true  /** Be quiet.  Do not print out the results of each    * submitted command unless an exception is thrown.  */  def beQuiet = { printResults = false }    /** Temporarily be quiet */  def beQuietDuring[T](operation: => T): T = {    val wasPrinting = printResults    try {      printResults = false      operation    } finally {      printResults = wasPrinting    }  }  /** interpreter settings */  val isettings = new InterpreterSettings  /** directory to save .class files to */  val classfilePath = File.createTempFile("scalaint", "")  classfilePath.delete  // the file is created as a file; make it a directory  classfilePath.mkdirs  /* set up the compiler's output directory */  settings.outdir.value = classfilePath.getPath  object reporter extends ConsoleReporter(settings, null, out) {    //override def printMessage(msg: String) { out.println(clean(msg)) }    override def printMessage(msg: String) { out.print(clean(msg) + "\n"); out.flush() }  }  /** Instantiate a compiler.  Subclasses can override this to   *  change the compiler class used by this interpreter. */  protected def newCompiler(settings: Settings, reporter: Reporter) =    new scala.tools.nsc.Global(settings, reporter)  /** the compiler's classpath, as URL's */  val compilerClasspath: List[URL] =    ClassPath.expandPath(compiler.settings.classpath.value).      map(s => new File(s).toURL)  /** class loader used to load compiled code */  /* A single class loader is used for all commands interpreted by this Interpreter.     It would also be possible to create a new class loader for each command     to interpret.  The advantages of the current approach are:       - Expressions are only evaluated one time.  This is especially         significant for I/O, e.g. "val x = Console.readLine"     The main disadvantage is:       - Objects, classes, and methods cannot be rebound.  Instead, definitions         shadow the old ones, and old code objects refer to the old         definitions.  */  private val classLoader =    if (parentClassLoader eq null)      new URLClassLoader((classfilePath.toURL :: compilerClasspath).toArray)    else      new URLClassLoader((classfilePath.toURL :: compilerClasspath).toArray,                          parentClassLoader)  /** Set the current Java "context" class loader to this    * interpreter's class loader */  def setContextClassLoader() {    Thread.currentThread.setContextClassLoader(classLoader)  }  /** XXX Let's get rid of this.  I believe the Eclipse plugin is    * the only user of it, so this should be doable.  */  protected def parentClassLoader: ClassLoader = null  /** the previous requests this interpreter has processed */  private val prevRequests = new ArrayBuffer[Request]()  /** next line number to use */  private var nextLineNo = 0  /** allocate a fresh line name */  private def newLineName = {    val num = nextLineNo    nextLineNo += 1    compiler.nme.INTERPRETER_LINE_PREFIX + num  }    /** next result variable number to use */  private var nextVarNameNo = 0  /** allocate a fresh variable name */  private def newVarName() = {    val num = nextVarNameNo    nextVarNameNo += 1    INTERPRETER_VAR_PREFIX + num  }  /** next internal variable number to use */  private var nextInternalVarNo = 0    /** allocate a fresh internal variable name */  private def newInternalVarName() = {    val num = nextVarNameNo    nextVarNameNo += 1    INTERPRETER_SYNTHVAR_PREFIX + num  }    /** Check if a name looks like it was generated by newVarName */  private def isGeneratedVarName(name: String): Boolean =    name.startsWith(INTERPRETER_VAR_PREFIX) && {      val suffix = name.drop(INTERPRETER_VAR_PREFIX.length)      suffix.forall(_.isDigit)    }  /** generate a string using a routine that wants to write on a stream */  private def stringFrom(writer: PrintWriter => Unit): String = {    val stringWriter = new StringWriter()    val stream = new NewLinePrintWriter(stringWriter)    writer(stream)    stream.close    stringWriter.toString  }  /** Truncate a string if it is longer than settings.maxPrintString */  private def truncPrintString(str: String): String = {    val maxpr = isettings.maxPrintString    if (maxpr <= 0)       return str    if (str.length <= maxpr)      return str        val trailer = "..."    if (maxpr >= trailer.length+1)      return str.substring(0, maxpr-3) + trailer     str.substring(0, maxpr)  }  /** Clean up a string for output */  private def clean(str: String) =    truncPrintString(Interpreter.stripWrapperGunk(str))  /** Indent some code by the width of the scala> prompt.   *  This way, compiler error messages read beettr.   */  def indentCode(code: String) = {    val spaces = "       "    stringFrom(str =>      for (line <- code.lines) {        str.print(spaces)        str.print(line + "\n")        str.flush()      })  }  implicit def name2string(name: Name) = name.toString  /** Compute imports that allow definitions from previous   *  requests to be visible in a new request.  Returns   *  three pieces of related code:   *   *  1. An initial code fragment that should go before   *  the code of the new request.   *   *  2. A code fragment that should go after the code   *  of the new request.   *   *  3. An access path which can be traverested to access   *  any bindings inside code wrapped by #1 and #2 .   *   * The argument is a set of Names that need to be imported.   *   * Limitations: This method is not as precise as it could be.   * (1) It does not process wildcard imports to see what exactly   * they import.   * (2) If it imports any names from a request, it imports all   * of them, which is not really necessary.   * (3) It imports multiple same-named implicits, but only the   * last one imported is actually usable.   */  private def importsCode(wanted: Set[Name]): (String, String, String) = {    /** Narrow down the list of requests from which imports      *  should be taken.  Removes requests which cannot contribute     *  useful imports for the specified set of wanted names.     */    def reqsToUse: List[(Request,MemberHandler)] = {      /** Loop through a list of MemberHandlers and select       *  which ones to keep.  'wanted' is the set of       *  names that need to be imported, and        *  'shadowed' is the list of names useless to import       *  because a later request will re-import it anyway.       */      def select(reqs: List[(Request,MemberHandler)], wanted: Set[Name]):       List[(Request,MemberHandler)] = {        reqs match {          case Nil => Nil          case (req,handler)::rest =>            val keepit =              (handler.definesImplicit ||               handler.importsWildcard ||               handler.importedNames.exists(wanted.contains(_)) ||               handler.boundNames.exists(wanted.contains(_)))            val newWanted =              if (keepit) {                (wanted                   ++ handler.usedNames                   -- handler.boundNames                    -- handler.importedNames)              } else {                wanted              }            val restToKeep = select(rest, newWanted)            if(keepit)              (req,handler) :: restToKeep            else              restToKeep        }      }

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?