diff options
author | Lex Spoon <lex@lexspoon.org> | 2008-05-05 14:09:00 +0000 |
---|---|---|
committer | Lex Spoon <lex@lexspoon.org> | 2008-05-05 14:09:00 +0000 |
commit | 06efde1f28ec6ad3f0008e3aaf08f292f0ae452f (patch) | |
tree | d1c81a7504a4ada89ff193282d638d3bd79a5c02 /src | |
parent | 72615dc18e18dabd211a684a5b395daf3373dfed (diff) | |
download | scala-06efde1f28ec6ad3f0008e3aaf08f292f0ae452f.tar.gz scala-06efde1f28ec6ad3f0008e3aaf08f292f0ae452f.tar.bz2 scala-06efde1f28ec6ad3f0008e3aaf08f292f0ae452f.zip |
The interpreter no longer generates class files...
The interpreter no longer generates class files to disk. It instead uses
an in-memory virtual directory.
Diffstat (limited to 'src')
8 files changed, 200 insertions, 41 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index f0a3750aac..713b4c7b99 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -15,11 +15,12 @@ import scala.collection.mutable import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} //import ast.parser.SyntaxAnalyzer -import io.PlainFile +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._ /** <p> * An interpreter for Scala code. @@ -68,6 +69,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) { 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) @@ -109,14 +113,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** 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() } @@ -124,8 +120,11 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** 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) + protected def newCompiler(settings: Settings, reporter: Reporter) = { + val comp = new scala.tools.nsc.Global(settings, reporter) + comp.genJVM.outputDir = virtualDirectory + comp + } /** the compiler's classpath, as URL's */ @@ -133,7 +132,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { 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: @@ -147,12 +145,16 @@ class Interpreter(val settings: Settings, out: PrintWriter) { 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) + /** class loader used to load compiled code */ + private val classLoader = { + val parent = + if (parentClassLoader == null) + new URLClassLoader(compilerClasspath.toArray) + else + new URLClassLoader(compilerClasspath.toArray, + parentClassLoader) + new AbstractFileClassLoader(virtualDirectory, parent) + } /** Set the current Java "context" class loader to this * interpreter's class loader */ @@ -548,19 +550,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** <p> * This instance is no longer needed, so release any resources - * it is using. - * </p> - * <p> - * Specifically, this deletes the temporary directory used for holding - * class files for this instance. This cannot safely be done after - * each command is executed because of Java's demand loading. - * </p> - * <p> - * Also, this flushes the reporter's output. + * it is using. The reporter's output gets flushed. * </p> */ def close() { - Interpreter.deleteRecursively(classfilePath) reporter.flush() } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 0813bd88ce..280246a6bd 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -7,11 +7,12 @@ package scala.tools.nsc.backend.jvm -import java.io.File +import java.io.{DataOutputStream, File, OutputStream} import java.nio.ByteBuffer import scala.collection.immutable.{Set, ListSet} import scala.collection.mutable.{Map, HashMap, HashSet} +import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.symtab._ import scala.tools.nsc.util.{Position, NoPosition} @@ -29,6 +30,12 @@ abstract class GenJVM extends SubComponent { val phaseName = "jvm" + /** + * Directory where output will be written. By default it + * is the directory specified by Settings.outdir. + */ + var outputDir: AbstractFile = AbstractFile.getDirectory(settings.outdir.value) + /** Create a new phase */ override def newPhase(p: Phase) = new JvmPhase(p) @@ -124,9 +131,9 @@ abstract class GenJVM extends SubComponent { addScalaAttr(if (isTopLevelModule(sym)) sym.sourceModule else sym); addInnerClasses - val outfile = getFile(jclass, ".class") + val outfile = new DataOutputStream(getFile(jclass, ".class")) jclass.writeTo(outfile) - val file = scala.tools.nsc.io.AbstractFile.getFile(outfile) + outfile.close() informProgress("wrote " + outfile) } @@ -273,9 +280,9 @@ abstract class GenJVM extends SubComponent { jcode.emitRETURN() // write the bean information class file. - val outfile = getFile(beanInfoClass, ".class") + val outfile = new DataOutputStream(getFile(beanInfoClass, ".class")) beanInfoClass.writeTo(outfile) - val file = scala.tools.nsc.io.AbstractFile.getFile(outfile) + outfile.close() informProgress("wrote BeanInfo " + outfile) } @@ -1513,9 +1520,14 @@ abstract class GenJVM extends SubComponent { res } - def getFile(cls: JClass, suffix: String): String = { - val path = cls.getName().replace('.', File.separatorChar) - settings.outdir.value + File.separatorChar + path + suffix + def getFile(cls: JClass, suffix: String): OutputStream = { + var dir: AbstractFile = outputDir + val pathParts = cls.getName().split("[./]").toList + for (part <- pathParts.init) { + dir = dir.subdirectoryNamed(part) + } + val file = dir.fileNamed(pathParts.last + suffix) + file.output } /** Emit a Local variable table for debugging purposes. diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala new file mode 100644 index 0000000000..3aaead472a --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala @@ -0,0 +1,32 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2007 LAMP/EPFL + */ +// $Id$ +package scala.tools.nsc.interpreter +import scala.tools.nsc.io.AbstractFile + +/** + * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. + * + * @author Lex Spoon + */ +class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) +extends ClassLoader(parent) +{ + override def findClass(name: String): Class[_] = { + var file: AbstractFile = root + val pathParts = name.split("[./]").toList + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, true) + if (file == null) { + throw new ClassNotFoundException(name) + } + } + file = file.lookupName(pathParts.last+".class", false) + if (file == null) { + throw new ClassNotFoundException(name) + } + val bytes = file.toByteArray + defineClass(name, bytes, 0, bytes.length) + } +} diff --git a/src/compiler/scala/tools/nsc/io/AbstractFile.scala b/src/compiler/scala/tools/nsc/io/AbstractFile.scala index 36c17ef6c9..26782dfeb9 100644 --- a/src/compiler/scala/tools/nsc/io/AbstractFile.scala +++ b/src/compiler/scala/tools/nsc/io/AbstractFile.scala @@ -7,7 +7,7 @@ package scala.tools.nsc.io -import java.io.{File, IOException, InputStream} +import java.io.{File, FileOutputStream, IOException, InputStream, OutputStream} import java.net.URL import scala.collection.mutable.ArrayBuffer @@ -117,6 +117,9 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { /** returns an input stream so the file can be read */ def input: InputStream + /** Returns an output stream for writing the file */ + def output: OutputStream + /** size of this file if it is a concrete file. */ def size: Option[Int] = None @@ -187,6 +190,38 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { file } + /** + * Get the file in this directory with the given name, + * creating an empty file if it does not already existing. + */ + def fileNamed(name: String): AbstractFile = { + assert(isDirectory) + val existing = lookupName(name, false) + if (existing == null) { + val newFile = new File(file, name) + newFile.createNewFile() + new PlainFile(newFile) + } else { + existing + } + } + + /** + * Get the subdirectory with a given name, creating it if it + * does not already exist. + */ + def subdirectoryNamed(name: String): AbstractFile = { + assert (isDirectory) + val existing = lookupName(name, true) + if (existing == null) { + val dir = new File(file, name) + dir.mkdir() + new PlainFile(dir) + } else { + existing + } + } + /** Returns the path of this abstract file. */ override def toString() = path diff --git a/src/compiler/scala/tools/nsc/io/PlainFile.scala b/src/compiler/scala/tools/nsc/io/PlainFile.scala index a489dd1694..924822401e 100644 --- a/src/compiler/scala/tools/nsc/io/PlainFile.scala +++ b/src/compiler/scala/tools/nsc/io/PlainFile.scala @@ -7,7 +7,7 @@ package scala.tools.nsc.io -import java.io.{File, FileInputStream, IOException} +import java.io.{File, FileInputStream, FileOutputStream, IOException} object PlainFile { /** @@ -37,6 +37,7 @@ class PlainFile(val file: File) extends AbstractFile { override def container : AbstractFile = new PlainFile(file.getParentFile) override def input = new FileInputStream(file) + override def output = new FileOutputStream(file) override def size = Some(file.length.toInt) diff --git a/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala b/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala new file mode 100644 index 0000000000..c4ffa4892b --- /dev/null +++ b/src/compiler/scala/tools/nsc/io/VirtualDirectory.scala @@ -0,0 +1,67 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2007 LAMP/EPFL + */ +// $Id$ +package scala.tools.nsc.io +import scala.collection.{mutable=>mut} + +/** + * An in-memory directory. + * + * @author Lex Spoon + */ +class VirtualDirectory(val name: String, maybeContainer: Option[VirtualDirectory]) +extends AbstractFile { + def path: String = + maybeContainer match { + case None => name + case Some(parent) => parent.path+'/'+ name + } + def container = maybeContainer.get + def isDirectory = true + var lastModified: Long = System.currentTimeMillis + private def updateLastModified { + lastModified = System.currentTimeMillis + } + override def file = null + override def input = error("directories cannot be read") + override def output = error("directories cannot be written") + + private val files = mut.Map.empty[String, AbstractFile] + + // the toList is so that the directory may continue to be + // modified while its elements are iterated + def elements = files.values.toList.elements + + override def lookupName(name: String, directory: Boolean): AbstractFile = { + files.get(name) match { + case None => null + case Some(file) => + if (file.isDirectory == directory) + file + else + null + } + } + + override def fileNamed(name: String): AbstractFile = { + val existing = lookupName(name, false) + if (existing == null) { + val newFile = new VirtualFile(name, path+'/'+name) + files(name) = newFile + newFile + } else { + existing + } + } + + override def subdirectoryNamed(name: String): AbstractFile = { + val existing = lookupName(name, true) + if (existing == null) { + val dir = new VirtualDirectory(name, Some(this)) + dir + } else { + existing + } + } +} diff --git a/src/compiler/scala/tools/nsc/io/VirtualFile.scala b/src/compiler/scala/tools/nsc/io/VirtualFile.scala index ec1e4396c7..f8fcb0eba1 100644 --- a/src/compiler/scala/tools/nsc/io/VirtualFile.scala +++ b/src/compiler/scala/tools/nsc/io/VirtualFile.scala @@ -7,9 +7,10 @@ package scala.tools.nsc.io -import java.io.{File, InputStream} +import java.io.{ByteArrayInputStream, ByteArrayOutputStream, + File, InputStream, OutputStream} -/** This class implements an empty abstract regular file. +/** This class implements an in-memory file. * * @author Philippe Altherr * @version 1.0, 23/03/2004 @@ -36,6 +37,11 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { case _ => false } + + //######################################################################## + // Private data + private var content = new Array[Byte](0) + //######################################################################## // Public Methods @@ -44,7 +50,18 @@ class VirtualFile(val name: String, _path: String) extends AbstractFile { /** Returns null. */ final def file: File = null - def input : InputStream = throw new Error("not supported"); + override def size: Option[Int] = Some(content.size) + + def input : InputStream = new ByteArrayInputStream(content); + + override def output: OutputStream = { + new ByteArrayOutputStream() { + override def close() { + super.close() + content = toByteArray() + } + } + } def container : AbstractFile = throw new Error("not supported") diff --git a/src/compiler/scala/tools/nsc/io/ZipArchive.scala b/src/compiler/scala/tools/nsc/io/ZipArchive.scala index 3267163363..2c7eadc19e 100644 --- a/src/compiler/scala/tools/nsc/io/ZipArchive.scala +++ b/src/compiler/scala/tools/nsc/io/ZipArchive.scala @@ -238,6 +238,8 @@ final class URLZipArchive(url: URL) extends AbstractFile { def input: InputStream = url.openStream() + def output = throw new Error("unsupported") + override def elements: Iterator[AbstractFile] = { if (root eq null) load() root.elements |