From 56b0eb1d8ac0c430d2100495f9e70d6159abd281 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 4 Mar 2009 18:07:36 +0000 Subject: Major interpreter cleanup. readability of generated code in preparation for accomplishing more advanced things on that front, but along the way I was also able to refactor out significant amounts of duplication and remove some dead code. This also fixes bug #1546. --- src/compiler/scala/tools/nsc/Interpreter.scala | 841 +++++++++------------ src/compiler/scala/tools/nsc/ast/Trees.scala | 2 +- src/compiler/scala/tools/nsc/util/SourceFile.scala | 7 +- 3 files changed, 364 insertions(+), 486 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index a922087c45..af9f3dda27 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -6,21 +6,23 @@ package scala.tools.nsc -import java.io.{File, PrintWriter, StringWriter, Writer} -import java.lang.{Class, ClassLoader} -import java.net.{MalformedURLException, URL, URLClassLoader} +import java.io.{ File, PrintWriter, StringWriter, Writer } +import java.lang.{ Class, ClassLoader } +import java.net.{ MalformedURLException, URL, URLClassLoader } +import java.lang.reflect +import reflect.InvocationTargetException import scala.collection.immutable.ListSet import scala.collection.mutable -import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} +import scala.collection.mutable.{ ListBuffer, HashSet, ArrayBuffer } -//import ast.parser.SyntaxAnalyzer -import io.{PlainFile, VirtualDirectory} -import reporters.{ConsoleReporter, Reporter} -import symtab.Flags -import util.{SourceFile,BatchSourceFile,ClassPath,NameTransformer} -import nsc.{InterpreterResults=>IR} -import scala.tools.nsc.interpreter._ +import io.{ PlainFile, VirtualDirectory } +import reporters.{ ConsoleReporter, Reporter } +import symtab.{ Flags, Names } +import util.{ SourceFile, BatchSourceFile, ClassPath, NameTransformer } +import nsc.{ InterpreterResults => IR } +import nsc.interpreter._ +import Interpreter._ /**

* An interpreter for Scala code. @@ -63,37 +65,29 @@ import scala.tools.nsc.interpreter._ * @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 */ + /* If running on pre-1.5 JVM, force target setting to 1.4 */ private val major = System.getProperty("java.class.version").split("\\.")(0) - if (major.toInt < 49) { - this.settings.target.value = "jvm-1.4" - } + if (major.toInt < 49) this.settings.target.value = "jvm-1.4" /** directory to save .class files to */ val virtualDirectory = new VirtualDirectory("(memory)", None) /** 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 + val compiler: nsc.Global = newCompiler(settings, reporter) + + import compiler.{ Traverser, CompilationUnit, Symbol, Name, Type } + import compiler.{ + Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef, + ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef } + import compiler.{ nme, newTermName } + import nme.{ + INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX, + INTERPRETER_IMPORT_WRAPPER, INTERPRETER_WRAPPER_SUFFIX, USCOREkw + } /** construct an interpreter that reports to Console */ def this(settings: Settings) = - this(settings, - new NewLinePrintWriter(new ConsoleWriter, true)) + this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) /** whether to print out result lines */ private var printResults: Boolean = true @@ -117,28 +111,26 @@ class Interpreter(val settings: Settings, out: PrintWriter) { lazy val isettings = new InterpreterSettings 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) = { - val comp = new scala.tools.nsc.Global(settings, reporter) + val comp = new nsc.Global(settings, reporter) comp.genJVM.outputDir = virtualDirectory comp } - /** the compiler's classpath, as URL's */ val compilerClasspath: List[URL] = { val classpathPart = - (ClassPath.expandPath(compiler.settings.classpath.value). - map(s => new File(s).toURL)) + ClassPath.expandPath(compiler.settings.classpath.value).map(s => new File(s).toURL) def parseURL(s: String): Option[URL] = - try { Some(new URL(s)) } - catch { case _:MalformedURLException => None } - val codebasePart = (compiler.settings.Xcodebase.value.split(" ")).toList.flatMap(parseURL) + try { Some(new URL(s)) } + catch { case _: MalformedURLException => None } + + val codebasePart = (compiler.settings.Xcodebase.value.split(" ")).toList flatMap parseURL classpathPart ::: codebasePart } @@ -157,64 +149,53 @@ class Interpreter(val settings: Settings, out: PrintWriter) { */ private var classLoader = makeClassLoader() private def makeClassLoader(): ClassLoader = { + val cp = compilerClasspath.toArray val parent = - if (parentClassLoader == null) - new URLClassLoader(compilerClasspath.toArray) - else - new URLClassLoader(compilerClasspath.toArray, - parentClassLoader) + if (parentClassLoader == null) new URLClassLoader(cp) + else new URLClassLoader(cp, parentClassLoader) + new AbstractFileClassLoader(virtualDirectory, parent) } - /** Set the current Java "context" class loader to this - * interpreter's class loader */ - def setContextClassLoader() { - Thread.currentThread.setContextClassLoader(classLoader) - } + private def loadByName(s: String): Class[_] = Class.forName(s, true, classLoader) + // XXX how does this differ from getMethod("set") ? + private def methodByName(c: Class[_], name: String): Option[reflect.Method] = + c.getDeclaredMethods.toList.find(_.getName == name) + + // Set the current Java "context" class loader to this interpreter's class loader + def setContextClassLoader() = Thread.currentThread.setContextClassLoader(classLoader) protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader() /** 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 + /** counter creator */ + def mkNameCreator(s: String) = new Function0[String] with Function1[String,String] { + private var x = -1 + def apply(): String = { x += 1 ; s + x.toString } + // second apply method temp for newInternalVarName's bug compatibility + def apply(pre: String) = { x += 1 ; pre + x.toString } + def reset(): Unit = x = -1 } - /** 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 - } + /** allocate a fresh line name */ + private val newLineName = mkNameCreator(INTERPRETER_LINE_PREFIX) - /** next internal variable number to use */ - private var nextInternalVarNo = 0 + /** allocate a fresh var name */ + private val newVarName = mkNameCreator(INTERPRETER_VAR_PREFIX) /** allocate a fresh internal variable name */ - private def newInternalVarName() = { - val num = nextVarNameNo - nextVarNameNo += 1 - INTERPRETER_SYNTHVAR_PREFIX + num - } - + /** XXX temporarily shares newVarName's creator to be bug-compatible with + * test case interpreter.scala */ + private def newInternalVarName = () => newVarName(INTERPRETER_SYNTHVAR_PREFIX) + // private val newInternalVarName = mkNameCreator(INTERPRETER_SYNTHVAR_PREFIX) /** 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) - } - + private def isGeneratedVarName(name: String): Boolean = { + val pre = INTERPRETER_VAR_PREFIX + (name startsWith pre) && (name drop pre.length).forall(_.isDigit) + } /** generate a string using a routine that wants to write on a stream */ private def stringFrom(writer: PrintWriter => Unit): String = { @@ -228,37 +209,26 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** 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) + if (maxpr <= 0 || str.length <= maxpr) str + else str.substring(0, maxpr-3) + trailer } /** Clean up a string for output */ - private def clean(str: String) = - truncPrintString(Interpreter.stripWrapperGunk(str)) + private def clean(str: String) = truncPrintString(stripWrapperGunk(str)) /** Indent some code by the width of the scala> prompt. * This way, compiler error messages read better. */ - def indentCode(code: String) = { - val spaces = " " - + private final val spaces = List.make(7, " ").mkString + def indentCode(code: String) = stringFrom(str => for (line <- code.lines) { str.print(spaces) str.print(line + "\n") str.flush() }) - } implicit def name2string(name: Name) = name.toString @@ -285,149 +255,117 @@ class Interpreter(val settings: Settings, out: PrintWriter) { * (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) = { + private case class ComputedImports(prepend: String, append: String, access: String) + private def importsCode(wanted: Set[Name]): ComputedImports = { /** 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. + case class ReqAndHandler(req: Request, handler: MemberHandler) + def reqsToUse: List[ReqAndHandler] = { + /** Loop through a list of MemberHandlers and select which ones to keep. + * 'wanted' is the set of names that need to be imported. */ - def select(reqs: List[(Request,MemberHandler)], wanted: Set[Name]): - List[(Request,MemberHandler)] = { + def select(reqs: List[ReqAndHandler], wanted: Set[Name]): List[ReqAndHandler] = { + val isWanted = wanted contains _ + def keepHandler(handler: MemberHandler): Boolean = { + import handler._ + definesImplicit || importsWildcard || (importedNames ++ boundNames).exists(isWanted) + } + 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 + case Nil => Nil + case rh :: rest if !keepHandler(rh.handler) => select(rest, wanted) + case rh :: rest => + import rh.handler._ + val newWanted = wanted ++ usedNames -- boundNames -- importedNames + rh :: select(rest, newWanted) } } val rhpairs = for { req <- prevRequests.toList.reverse handler <- req.handlers - } yield (req, handler) + } yield ReqAndHandler(req, handler) select(rhpairs, wanted).reverse } - val code = new StringBuffer - val trailingBraces = new StringBuffer - val accessPath = new StringBuffer - val impname = compiler.nme.INTERPRETER_IMPORT_WRAPPER + val code, trailingBraces, accessPath = new StringBuffer val currentImps = mutable.Set.empty[Name] // add code for a new object to hold some imports def addWrapper() { - code.append("object " + impname + "{\n") - trailingBraces.append("}\n") - accessPath.append("." + impname) + val impname = INTERPRETER_IMPORT_WRAPPER + code append "object %s {\n".format(impname) + trailingBraces append "}\n" + accessPath append ("." + impname) + currentImps.clear } addWrapper() - // loop through previous requests, adding imports - // for each one - for ((req,handler) <- reqsToUse) { - // If the user entered an import, then just use it - - // add an import wrapping level if the import might - // conflict with some other import - if(handler.importsWildcard || - currentImps.exists(handler.importedNames.contains)) - if(!currentImps.isEmpty) - addWrapper() - - if (handler.member.isInstanceOf[Import]) - code.append(handler.member.toString + ";\n") - - // give wildcard imports a import wrapper all to their own - if(handler.importsWildcard) - addWrapper() - else - currentImps ++= handler.importedNames - - // For other requests, import each bound variable. - // import them explicitly instead of with _, so that - // ambiguity errors will not be generated. Also, quote - // the name of the variable, so that we don't need to - // handle quoting keywords separately. - for (imv <- handler.boundNames) { - if (currentImps.contains(imv)) - addWrapper() - code.append("import ") - code.append(req.objectName + req.accessPath + ".`" + imv + "`;\n") - currentImps += imv - } + // loop through previous requests, adding imports for each one + for (ReqAndHandler(req, handler) <- reqsToUse) { + import handler._ + // If the user entered an import, then just use it; add an import wrapping + // level if the import might conflict with some other import + if (importsWildcard || currentImps.exists(importedNames.contains)) + addWrapper() + + if (member.isInstanceOf[Import]) + code append (member.toString + "\n") + + // give wildcard imports a import wrapper all to their own + if (importsWildcard) addWrapper() + else currentImps ++= importedNames + + // For other requests, import each bound variable. + // import them explicitly instead of with _, so that + // ambiguity errors will not be generated. Also, quote + // the name of the variable, so that we don't need to + // handle quoting keywords separately. + for (imv <- boundNames) { + if (currentImps contains imv) addWrapper() + + code append ("import " + req.fullPath(imv)) + currentImps += imv + } } - addWrapper() // Add one extra wrapper, to prevent warnings - // in the frequent case of redefining - // the value bound in the last interpreter - // request. - - (code.toString, trailingBraces.toString, accessPath.toString) + // add one extra wrapper, to prevent warnings in the common case of + // redefining the value bound in the last interpreter request. + addWrapper() + ComputedImports(code.toString, trailingBraces.toString, accessPath.toString) } - /** Parse a line into a sequence of trees. Returns None if the input - * is incomplete. */ + /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ private def parse(line: String): Option[List[Tree]] = { var justNeedsMore = false reporter.withIncompleteHandler((pos,msg) => {justNeedsMore = true}) { // simple parse: just parse it, nothing else def simpleParse(code: String): List[Tree] = { reporter.reset - val unit = - new CompilationUnit( - new BatchSourceFile("", code.toCharArray())) - val scanner = new compiler.syntaxAnalyzer.UnitParser(unit); - val xxx = scanner.templateStatSeq(false); - (xxx._2) - } - val (trees) = simpleParse(line) - if (reporter.hasErrors) { - Some(Nil) // the result did not parse, so stop - } else if (justNeedsMore) { - None - } else { - Some(trees) + val unit = new CompilationUnit(new BatchSourceFile("", code)) + val scanner = new compiler.syntaxAnalyzer.UnitParser(unit) + + scanner.templateStatSeq(false)._2 } + val trees = simpleParse(line) + + if (reporter.hasErrors) Some(Nil) // the result did not parse, so stop + else if (justNeedsMore) None + else Some(trees) } } /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false othrewise. */ - def compileSources(sources: List[SourceFile]): Boolean = { - val cr = new compiler.Run + def compileSources(sources: SourceFile*): Boolean = { reporter.reset - cr.compileSources(sources) + new compiler.Run() compileSources sources.toList !reporter.hasErrors } @@ -435,7 +373,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { * compilation errors, or false otherwise. */ def compileString(code: String): Boolean = - compileSources(List(new BatchSourceFile("