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 + -
显示快捷键?