Many, many cleanups and small improvements.
git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@5770 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
parent
8c812563f0
commit
8f65d9787b
|
@ -3,232 +3,459 @@
|
|||
* @author Martin Odersky
|
||||
*/
|
||||
// $Id$
|
||||
package scala.tools.nsc;
|
||||
package scala.tools.nsc
|
||||
|
||||
import scala.tools.nsc.reporters.Reporter;
|
||||
import reporters.Reporter
|
||||
import nsc.util.SourceFile
|
||||
import scala.tools.util.PlainFile
|
||||
import java.io.{File, Writer, PrintWriter, StringWriter}
|
||||
import nsc.ast.parser.SyntaxAnalyzer
|
||||
import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer}
|
||||
import scala.collection.immutable.{Map, ListMap}
|
||||
import symtab.Flags
|
||||
|
||||
abstract class Interpreter {
|
||||
import scala.collection.mutable.ListBuffer;
|
||||
import symtab.Names;
|
||||
/** An interpreter for Scala code.
|
||||
|
||||
// list of names defined, for each line number
|
||||
val prevDefines : ListBuffer[Pair[Int,ListBuffer[Names#Name]]] = new ListBuffer();
|
||||
The main public entry points are compile() and interpret(). The compile()
|
||||
method loads a complete Scala file. The interpret() method executes one
|
||||
line of Scala code at the request of the user.
|
||||
|
||||
The overall approach is based on compiling the requested code and then
|
||||
using a Java classloader and using Java reflection to run the code
|
||||
and access its results.
|
||||
|
||||
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 exports a single member named "result". To accomodate user expressions
|
||||
that read from variables or methods defined in previous statements, "import"
|
||||
statements are used.
|
||||
|
||||
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.
|
||||
*/
|
||||
class Interpreter(val compiler: Global, output: (String => Unit)) {
|
||||
import symtab.Names
|
||||
import compiler.Traverser
|
||||
import compiler.{Tree, TermTree,
|
||||
ValOrDefDef, ValDef, DefDef, Assign,
|
||||
ClassDef, ModuleDef, Ident, Select, AliasTypeDef}
|
||||
import compiler.CompilationUnit
|
||||
import compiler.Symbol
|
||||
import compiler.Name
|
||||
|
||||
val compiler: Global;
|
||||
|
||||
import scala.tools.nsc.ast.parser.SyntaxAnalyzer;
|
||||
object syntaxAnalyzer extends SyntaxAnalyzer {
|
||||
val global: compiler.type = compiler
|
||||
/** construct an interpreter that prints to the compiler's reporter */
|
||||
def this(compiler: Global) = {
|
||||
this(compiler, str => compiler.reporter.info(null, str, true))
|
||||
}
|
||||
|
||||
def interpret(line: String, reporter: Reporter): unit = {
|
||||
import scala.tools.nsc.util.SourceFile;
|
||||
private def reporter = compiler.reporter
|
||||
|
||||
/** directory to save .class files to */
|
||||
private val classfilePath = File.createTempFile("scalaint", "")
|
||||
classfilePath.delete // the file is created as a file; make it a directory
|
||||
classfilePath.mkdirs
|
||||
|
||||
// convert input to a compilation unit, using SourceFile;
|
||||
// and parse it, using syntaxAnalyzer, to get input ASTs
|
||||
val inASTs = syntaxAnalyzer.interpreterParse(
|
||||
new compiler.CompilationUnit(
|
||||
//if SourceFile is not modified, then fix a bug
|
||||
//here by adding an EOF character to the end of
|
||||
//the 'line'
|
||||
new SourceFile("<console>",line.toCharArray())));
|
||||
|
||||
|
||||
/* set up the compiler's output directory */
|
||||
compiler.settings.outdir.value = classfilePath.getPath
|
||||
|
||||
//todo: if (errors in parsing) after reporting them, exit method
|
||||
/** 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 to refer to the old
|
||||
definitions.
|
||||
*/
|
||||
private val classLoader = new java.net.URLClassLoader(Predef.Array(classfilePath.toURL))
|
||||
|
||||
val dvt = new DefinedVarsTraverser;
|
||||
dvt.traverseTrees(inASTs);
|
||||
val definedVars = dvt.definedVars;
|
||||
|
||||
/** the previous requests this interpreter has processed */
|
||||
private val prevRequests = new ArrayBuffer[Request]()
|
||||
|
||||
val ivt = new ImportVarsTraverser(definedVars);
|
||||
ivt.traverseTrees(inASTs);
|
||||
val importVars = ivt.importVars;
|
||||
|
||||
val lineno = prevDefines.length;
|
||||
//todo: it is probably nice to include date & time, as well as a process id, in the filename
|
||||
val filename = getTempPath().getPath()+java.io.File.separator+"InterpreterTempLine"+lineno+".scala";
|
||||
writeTempScalaFile(filename, line, lineno, definedVars, importVars);
|
||||
|
||||
// first phase: compile auto-generated file
|
||||
compiler.settings.outdir.value = getTempPath().getPath();
|
||||
val cr = new compiler.Run;
|
||||
cr compile List(filename);
|
||||
|
||||
//todo: if no errors in compilation then
|
||||
// second phase: execute JVM, and print outcome
|
||||
// else consider definition as if has not happened and exit method
|
||||
//todo: use Scala's reflection API, which I designed, instead, for the following code
|
||||
val cl = new java.net.URLClassLoader(Predef.Array(getTempPath().toURL()));
|
||||
val interpreterResultObject: Class = Class.forName("InterpreterLine"+lineno+"Result",true,cl);
|
||||
val resultValMethod: java.lang.reflect.Method = interpreterResultObject.getMethod("result",null);
|
||||
var interpreterResultString: String = resultValMethod.invoke(interpreterResultObject,null).toString();
|
||||
|
||||
//var interpreterResultJavaTypeString: String = resultValMethod.getReturnType().getName();
|
||||
//Console.println(compiler.definitions.EmptyPackage.info.members);
|
||||
val interpreterResultSym: compiler.Symbol =
|
||||
compiler.definitions.getMember(compiler.definitions.EmptyPackage,
|
||||
compiler.newTermName("InterpreterLine"+lineno+"Result"));
|
||||
|
||||
def findSymbolWithName(ls: List[compiler.Symbol], name: compiler.Name): compiler.Symbol =
|
||||
ls.find(s=>s.name == name) match {
|
||||
case None => throw new IllegalStateException("Cannot find field '"+name+"' in InterpreterResult");
|
||||
case Some(s) => s;
|
||||
}
|
||||
|
||||
//var lastname: String = compiler.atPhase(cr.typerPhase.next){interpreterResultSym.info.decls.toList.last.name.toString()};
|
||||
//reporter.info(null,lastname,true);
|
||||
//todo: similar to what I should be doing for Scala's reflection??
|
||||
var interpreterResultScalaTypeString: String =
|
||||
compiler.atPhase(cr.typerPhase.next){
|
||||
findSymbolWithName(interpreterResultSym.info.decls.toList,
|
||||
compiler.nme.getterToLocal(compiler.newTermName("result")))
|
||||
.tpe.toString()
|
||||
};
|
||||
reporter.info(null,interpreterResultString+": "+interpreterResultScalaTypeString/*+" ("+interpreterResultJavaTypeString+")"*/,true);
|
||||
|
||||
/*
|
||||
val scalaInterpFile: File = ScalaInterpFile(filename);
|
||||
scalaInterpFile.deleteOnExit();
|
||||
if(scalaInterpFile.exists())
|
||||
scalaInterpFile.delete();
|
||||
|
||||
getvalue of line#.last_var_defined_in_line (from defined_vars)
|
||||
(works for 'it' as it was added as last val to definedvars)
|
||||
and send it to reporter
|
||||
*/
|
||||
|
||||
// book-keeping
|
||||
//todo: if no errors in evaluation then
|
||||
prevDefines += Pair(lineno,definedVars);
|
||||
// else consider definition as if has not happened.
|
||||
|
||||
// report some debug info
|
||||
//reporter.info(null,"inASTs="+inASTs,true);
|
||||
//reporter.info(null,"definedVars="+definedVars,true);
|
||||
//reporter.info(null,"importVars="+importVars,true);
|
||||
//reporter.info(null,"prevDefines="+prevDefines,true);
|
||||
/** look up the request that bound a specified term or type */
|
||||
private def reqBinding(vname: Name): Option[Request] = {
|
||||
prevRequests.toList.reverse.find(lin => lin.boundNames.contains(vname))
|
||||
}
|
||||
|
||||
import java.io.File;
|
||||
def getTempPath(): File = {
|
||||
val tempdir = {
|
||||
val tempdir1 = System.getProperty("java.io.tmpdir");
|
||||
if (tempdir1 == null){
|
||||
val tempdir2 = System.getProperty("TEMP");
|
||||
if (tempdir2 == null){
|
||||
val tempdir3 = System.getProperty("TMP");
|
||||
if (tempdir3 == null)
|
||||
throw new IllegalStateException("No temporary folder defined")
|
||||
else tempdir3 }
|
||||
else tempdir2 }
|
||||
else tempdir1
|
||||
};
|
||||
val path = new File(tempdir);
|
||||
if (!path.exists() || !path.isDirectory())
|
||||
throw new IllegalStateException("Invalid temporary directory")
|
||||
else if (!path.canWrite())
|
||||
throw new IllegalStateException("Temporary directory not writable")
|
||||
else path
|
||||
};
|
||||
|
||||
def writeTempScalaFile(filename: String, line: String, lineno: Int, definedVars: ListBuffer[Names#Name], importVars: ListBuffer[Pair[Names#Name,Int]]) = {
|
||||
import java.io.{File, PrintWriter, FileOutputStream};
|
||||
val scalaFile = new File(filename);
|
||||
scalaFile.deleteOnExit();
|
||||
if(scalaFile.exists()) // to prevent old lingering files from having results from them reported!
|
||||
scalaFile.delete();
|
||||
|
||||
val module = new PrintWriter(new FileOutputStream(scalaFile));
|
||||
//todo:"import "+LoadedModules?.getName
|
||||
//module.println("\n");
|
||||
|
||||
for(val Pair(ivname,ivlineno) <- importVars.toList) yield
|
||||
module.println("import line"+ivlineno+"."+ivname+";\n");
|
||||
|
||||
module.println("object line"+lineno+" {");
|
||||
var fullLine = line;
|
||||
if(definedVars.length == 0) { // input is just an expression
|
||||
fullLine = " var it = " + line;
|
||||
definedVars += compiler.encode("it"); }
|
||||
else fullLine = " " + line;
|
||||
module.println(fullLine);
|
||||
module.println("}");
|
||||
module.println();
|
||||
module.println("object InterpreterLine"+lineno+"Result ");
|
||||
module.println("{ val result = (line"+lineno+"."+definedVars.toList.reverse.head+"); }");
|
||||
// reflection is used later (see above) to get the result value above
|
||||
|
||||
module.flush();
|
||||
module.close();
|
||||
|
||||
/** 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 PrintWriter(stringWriter)
|
||||
writer(stream)
|
||||
stream.close
|
||||
stringWriter.toString
|
||||
}
|
||||
|
||||
/** parse a line into a sequence of trees */
|
||||
private def parse(line: String): List[Tree] = {
|
||||
reporter.reset
|
||||
|
||||
val unit =
|
||||
new CompilationUnit(
|
||||
new SourceFile("<console>",line.toCharArray()))
|
||||
|
||||
import compiler.Traverser;
|
||||
import compiler.Tree;
|
||||
class DefinedVarsTraverser extends Traverser {
|
||||
val definedVars = new ListBuffer[Names#Name];
|
||||
override def traverse(ast: Tree): unit =
|
||||
if (!ast.isDef) ()
|
||||
else {
|
||||
import compiler._;
|
||||
ast match {
|
||||
// only the outer level needed, so do not recurse to go deeper
|
||||
// todo: combine similar cases in one case
|
||||
case ClassDef(_,name,_,_,_) => definedVars += name
|
||||
case ModuleDef(_, name,_) => definedVars += name
|
||||
case ValDef(_, name,_,_) => definedVars += name
|
||||
case DefDef(_,name,_,_,_,_) => definedVars += name
|
||||
//todo:case Bind(name,_) => ((name != nme.WILDCARD) && (definedVars.elements forall (name !=))) definedVars += name
|
||||
val trees = new compiler.syntaxAnalyzer.Parser(unit).templateStatSeq
|
||||
|
||||
if(reporter.errors > 0)
|
||||
return Nil // the result did not parse, so stop
|
||||
|
||||
//case Ident(name) => if (name...is defined) definedVars += name;
|
||||
|
||||
//todo:
|
||||
//case PackageDef(name, _) => throw new InterpIllegalDefException(name.toString()+": package definitions not allowed")
|
||||
//case AbsTypeDef(_,name,_,_) => throw new InterpIllegalDefException(name.toString()+": absract type definitions not allowed")
|
||||
//case AliasTypeDef(_,name,_,_) => throw new InterpIllegalDefException(name.toString()+": alias type definitions not allowed")
|
||||
//case LabelDef(name,_,_) => throw new InterpIllegalDefException(name.toString()+": label definitions not allowed")
|
||||
case _ => throw new InterpIllegalDefException("Unsupported interpreter definition. Contact Scala developers for adding interpreter support for it.")// ()
|
||||
}
|
||||
}
|
||||
trees
|
||||
}
|
||||
case class InterpIllegalDefException(msg: String) extends RuntimeException(msg);
|
||||
|
||||
// class ListTraverser extends Traverser {
|
||||
// def traverse(trees: List[Tree]): Unit =
|
||||
// trees foreach traverse;
|
||||
// }
|
||||
//
|
||||
// class ListDefinedVarsTraverser extends DefinedVarsTraverser with ListTraverser;
|
||||
|
||||
class ImportVarsTraverser(definedVars: ListBuffer[Names#Name]) extends Traverser {
|
||||
val importVars = new ListBuffer[Pair[Names#Name,Int]];
|
||||
var curAST = 0;
|
||||
import compiler.Ident;
|
||||
override def traverse(ast: Tree): unit = ast match {
|
||||
case Ident(name) => {
|
||||
var lastPrevDefsIdx = -1;
|
||||
//reporter.info(null,"name="+name,true);
|
||||
for(val Pair(lineno,defs) <- prevDefines.toList) yield {
|
||||
//reporter.info(null,"line#="+lineno+", defs="+defs,true);
|
||||
if (defs.indexOf(name) != -1) lastPrevDefsIdx = lineno
|
||||
}
|
||||
val foundInPrevDefines = (lastPrevDefsIdx != -1);
|
||||
//reporter.info(null,"lastPrevDefsIdx="+lastPrevDefsIdx,true);
|
||||
if(foundInPrevDefines) {
|
||||
val firstCurDefIdx = definedVars.indexOf(name);
|
||||
val foundInDefinedVars = (firstCurDefIdx != -1);
|
||||
if((!foundInDefinedVars ||
|
||||
(foundInDefinedVars && (firstCurDefIdx > curAST)))
|
||||
&& (importVars.indexOf(Pair(name,lastPrevDefsIdx)) == -1))
|
||||
// to prevent duplicate imports (todo: use find instead of indexOf?)
|
||||
importVars += Pair(name,lastPrevDefsIdx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Compile one source file */
|
||||
def compile(filename: String): Unit = {
|
||||
val jfile = new File(filename)
|
||||
if(!jfile.exists) {
|
||||
reporter.error(null, "no such file: " + filename)
|
||||
return ()
|
||||
}
|
||||
val cr = new compiler.Run
|
||||
cr.compileSources(List(new SourceFile(PlainFile.fromFile(jfile))))
|
||||
}
|
||||
|
||||
/** build a request from the user. "tree" is "line" after being parsed */
|
||||
private def buildRequest(trees: List[Tree], line: String, lineName: String): Request = {
|
||||
trees match {
|
||||
/* This case for assignments is more specialized than desirable: it only
|
||||
handles assignments to an identifier. It would be better to support
|
||||
arbitrary paths being assigned, but that is technically difficult
|
||||
because of the way objectSourceCode and resultObjectSourceCode are
|
||||
implemented in class Request. */
|
||||
case List(Assign(Ident(lhs), _)) => new AssignReq(lhs, line, lineName)
|
||||
|
||||
|
||||
case _ if trees.forall(t => t.isInstanceOf[ValOrDefDef]) => new DefReq(line, lineName)
|
||||
case List(_:TermTree) | List(_:Ident) | List(_:Select) => new ExprReq(line, lineName)
|
||||
case List(_:ModuleDef) => new ModuleReq(line, lineName)
|
||||
case List(_:ClassDef) => new ClassReq(line, lineName)
|
||||
case List(_:AliasTypeDef) => new TypeAliasReq(line, lineName)
|
||||
case _ => {
|
||||
// using case x, instead of case _, we can have: reporter.info(null,"x="+x,true);
|
||||
super.traverse(ast)
|
||||
reporter.error(null, "That kind of statement combination is not supported by the interpreter.")
|
||||
null
|
||||
}
|
||||
}
|
||||
override def traverseTrees(asts: List[Tree]): unit =
|
||||
asts foreach { curAST = curAST+1; traverse; }
|
||||
|
||||
}
|
||||
|
||||
/** interpret one line of input. All feedback, including parse errors
|
||||
and evaluation results, are printed via the supplied compiler's
|
||||
reporter. Values defined are available for future interpreted
|
||||
strings. */
|
||||
def interpret(line: String): Unit = {
|
||||
// parse
|
||||
val trees = parse(line)
|
||||
if(trees.isEmpty) return () // parse error or empty input
|
||||
|
||||
// figure out what kind of request
|
||||
val lineno = prevRequests.length
|
||||
val lineName = "line" + lineno
|
||||
|
||||
val req = buildRequest(trees, line, lineName)
|
||||
if(req == null) return () // a disallowed statement type
|
||||
|
||||
|
||||
if(!req.compile)
|
||||
return () // an error happened during compilation, e.g. a type error
|
||||
|
||||
val interpreterResultString = req.loadAndRun
|
||||
|
||||
// print the result
|
||||
output(interpreterResultString)
|
||||
|
||||
// print out types of functions; they are not printed in the
|
||||
// request printout
|
||||
output(req.defTypesSummary)
|
||||
|
||||
// book-keeping
|
||||
prevRequests += req
|
||||
}
|
||||
|
||||
|
||||
/** Delete a directory tree recursively. Use with care! */
|
||||
private def deleteRecursively(path: File): Unit = {
|
||||
path match {
|
||||
case _ if(!path.exists) => ()
|
||||
case _ if(path.isDirectory) =>
|
||||
for(val p <- path.listFiles)
|
||||
deleteRecursively(p)
|
||||
path.delete
|
||||
case _ => path.delete
|
||||
}
|
||||
}
|
||||
|
||||
/** This instance is no longer needed, so release any resources
|
||||
it is using.
|
||||
|
||||
Specifically, this deletes the temporary directory used for holding
|
||||
class files for this instance. This cannot safely be done as commands
|
||||
are executed becaus of Java's demand loading.
|
||||
*/
|
||||
def close: Unit = {
|
||||
deleteRecursively(classfilePath)
|
||||
}
|
||||
|
||||
|
||||
/** A traverser that finds all mentioned identifiers, i.e. things that need to be imported.
|
||||
It might return extra names. */
|
||||
private class ImportVarsTraverser(definedVars: List[Name]) extends Traverser {
|
||||
val importVars = new HashSet[Name]()
|
||||
|
||||
override def traverse(ast: Tree): unit = ast match {
|
||||
case Ident(name) => importVars += name
|
||||
case _ => super.traverse(ast)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** One line of code submitted by the user for interpretation */
|
||||
private abstract class Request(line: String, val lineName: String) {
|
||||
val trees = parse(line)
|
||||
|
||||
/** name to use for the object that will compute "line" */
|
||||
def objectName = lineName + "$object" // make it unlikely to clash with user variables
|
||||
|
||||
/** name of the object that retrieves the result from the above object */
|
||||
def resultObjectName = "RequestResult$" + objectName
|
||||
|
||||
/** whether the trees need a variable name, as opposed to standing
|
||||
alone */
|
||||
def needsVarName: Boolean = false
|
||||
|
||||
/** list of methods defined */
|
||||
val defNames =
|
||||
for {
|
||||
val DefDef(mods, name, _, _, _, _) <- trees
|
||||
mods.isPublic
|
||||
} yield name
|
||||
|
||||
/** list of val's and var's defined */
|
||||
val valAndVarNames = {
|
||||
val baseNames =
|
||||
for {
|
||||
val ValDef(mods, name, _, _) <- trees
|
||||
mods.isPublic
|
||||
} yield name
|
||||
|
||||
if(needsVarName)
|
||||
compiler.encode(lineName) :: baseNames // add a var name
|
||||
else
|
||||
baseNames
|
||||
}
|
||||
//XXXshorten all these for loops
|
||||
/** list of modules defined */
|
||||
val moduleNames = {
|
||||
val explicit =
|
||||
for(val ModuleDef(mods, name, _) <- trees; mods.isPublic)
|
||||
yield name
|
||||
val caseClasses =
|
||||
for {val ClassDef(mods, name, _, _, _) <- trees
|
||||
mods.isPublic
|
||||
mods.hasFlag(Flags.CASE)}
|
||||
yield name.toTermName
|
||||
explicit ::: caseClasses
|
||||
}
|
||||
|
||||
/** list of classes defined */
|
||||
val classNames =
|
||||
for(val ClassDef(mods, name, _, _, _) <- trees; mods.isPublic)
|
||||
yield name
|
||||
|
||||
/** list of type aliases defined */
|
||||
val typeNames =
|
||||
for(val AliasTypeDef(mods, name, _, _) <- trees; mods.isPublic)
|
||||
yield name
|
||||
|
||||
|
||||
/** all (public) names defined by these statements */
|
||||
val boundNames = defNames ::: valAndVarNames ::: moduleNames ::: classNames ::: typeNames
|
||||
|
||||
|
||||
/** list of names used by this expression */
|
||||
val usedNames: List[Name] = {
|
||||
val ivt = new ImportVarsTraverser(boundNames)
|
||||
ivt.traverseTrees(trees)
|
||||
ivt.importVars.toList
|
||||
}
|
||||
|
||||
/** names to print out to the user after evaluation */
|
||||
def namesToPrintForUser = valAndVarNames
|
||||
|
||||
/** generate the source code for the object that computes this request */
|
||||
def objectSourceCode: String =
|
||||
stringFrom(code => {
|
||||
// write an import for each imported variable
|
||||
for{val imv <- usedNames
|
||||
val lastDefiner <- reqBinding(imv).toList } {
|
||||
code.println("import " + lastDefiner.objectName + "." + imv)
|
||||
}
|
||||
|
||||
// object header
|
||||
code.println("object "+objectName+" {")
|
||||
|
||||
// the line of code to compute
|
||||
if(needsVarName)
|
||||
code.println(" val " + lineName + " = " + line)
|
||||
else
|
||||
code.println(" " + line)
|
||||
|
||||
//end
|
||||
code.println(";}")
|
||||
})
|
||||
|
||||
/** Types of variables defined by this request. They are computed
|
||||
after compilation of the main object */
|
||||
var typeOf: Map[Name, String] = _
|
||||
|
||||
|
||||
/** generate source code for the object that retrieves the result
|
||||
from objectSourceCode */
|
||||
def resultObjectSourceCode: String =
|
||||
stringFrom(code => {
|
||||
code.println("object " + resultObjectName)
|
||||
code.println("{ val result:String = {")
|
||||
code.println(objectName + ";") // evaluate the object, to make sure its constructor is run
|
||||
code.print("\"\"") // print an initial empty string, so later code can
|
||||
// uniformly be: + morestuff
|
||||
resultExtractionCode(code)
|
||||
code.println("}")
|
||||
code.println(";}")
|
||||
})
|
||||
|
||||
def resultExtractionCode(code: PrintWriter): Unit = {
|
||||
for(val vname <- namesToPrintForUser) {
|
||||
code.println(" + \"" + vname + ": " + typeOf(vname) +
|
||||
" = \" + " + objectName + "." + vname + " + \"\\n\"")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Compile the object file. Returns whether the compilation succeeded.
|
||||
If all goes well, types is computed and set */
|
||||
def compile: Boolean = {
|
||||
reporter.reset // without this, error counting is not correct,
|
||||
// and the interpreter sometimes overlooks compile failures!
|
||||
|
||||
// compile the main object
|
||||
val objRun = new compiler.Run()
|
||||
objRun.compileSources(List(new SourceFile("<console>", objectSourceCode.toCharArray)))
|
||||
if(reporter.errors > 0) return false
|
||||
|
||||
// extract and remember types
|
||||
typeOf = findTypes(objRun)
|
||||
|
||||
// compile the result-extraction object
|
||||
new compiler.Run().compileSources(List(new SourceFile("<console>", resultObjectSourceCode.toCharArray)))
|
||||
if(reporter.errors > 0) return false
|
||||
|
||||
// success
|
||||
true
|
||||
}
|
||||
|
||||
/** dig the types of all bound variables out of the compiler run */
|
||||
def findTypes(objRun: compiler.Run): Map[Name, String] = {
|
||||
def getTypes(names: List[Name], nameMap: Name=>Name): Map[Name, String] = {
|
||||
names.foldLeft[Map[Name,String]](new ListMap[Name, String]())((map, name) => {
|
||||
val resObjSym: Symbol =
|
||||
compiler.definitions.getMember(compiler.definitions.EmptyPackage,
|
||||
compiler.newTermName(objectName))
|
||||
|
||||
val typeString =
|
||||
compiler.atPhase(objRun.typerPhase.next) {
|
||||
resObjSym.info.decls.toList.find(s=>s.name == nameMap(name)).get.tpe.toString()
|
||||
}
|
||||
|
||||
map + name -> typeString
|
||||
})
|
||||
}
|
||||
|
||||
val names1 = getTypes(valAndVarNames, n=>compiler.nme.getterToLocal(n))
|
||||
val names2 = getTypes(defNames, id)
|
||||
names1.incl(names2)
|
||||
}
|
||||
|
||||
/** load and run the code using reflection */
|
||||
def loadAndRun: String = {
|
||||
val interpreterResultObject: Class = Class.forName(resultObjectName,true,classLoader)
|
||||
val resultValMethod: java.lang.reflect.Method = interpreterResultObject.getMethod("result",null)
|
||||
resultValMethod.invoke(interpreterResultObject,null).toString()
|
||||
}
|
||||
|
||||
/** return a summary of the defined methods */
|
||||
def defTypesSummary: String =
|
||||
stringFrom(summ => {
|
||||
for(val methname <- defNames) {
|
||||
summ.println("" + methname + ": " + typeOf(methname))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** A sequence of definition's. val's, var's, def's. */
|
||||
private class DefReq(line: String, lineName: String) extends Request(line, lineName) {
|
||||
}
|
||||
|
||||
/** Assignment of a single variable: lhs = exp */
|
||||
private class AssignReq(val lhs: Name, line: String, lineName: String) extends Request(line, lineName) {
|
||||
override def resultExtractionCode(code: PrintWriter): Unit = {
|
||||
super.resultExtractionCode(code)
|
||||
val bindReq = reqBinding(lhs).get
|
||||
code.println(" + \"" + lhs + " = \" + " + bindReq.objectName + "." + lhs)
|
||||
}
|
||||
override def namesToPrintForUser = Nil
|
||||
}
|
||||
|
||||
/** A single expression */
|
||||
private class ExprReq(line: String, lineName: String) extends Request(line, lineName) {
|
||||
override val needsVarName = true
|
||||
}
|
||||
|
||||
/** A module definition */
|
||||
private class ModuleReq(line: String, lineName: String) extends Request(line, lineName) {
|
||||
def moduleName = trees match {
|
||||
case List(ModuleDef(_, name, _)) => name
|
||||
}
|
||||
override def resultExtractionCode(code: PrintWriter): Unit = {
|
||||
super.resultExtractionCode(code)
|
||||
code.println(" + \"defined module " + moduleName + "\\n\"")
|
||||
}
|
||||
}
|
||||
|
||||
/** A class definition */
|
||||
private class ClassReq(line: String, lineName: String) extends Request(line, lineName) {
|
||||
def newClassName = trees match {
|
||||
case List(ClassDef(_, name, _, _, _)) => name
|
||||
}
|
||||
|
||||
override def resultExtractionCode(code: PrintWriter): Unit = {
|
||||
super.resultExtractionCode(code)
|
||||
code.println(" + \"defined class " + newClassName + "\\n\"")
|
||||
}
|
||||
}
|
||||
|
||||
/** a type alias */
|
||||
private class TypeAliasReq(line: String, lineName: String) extends Request(line, lineName) {
|
||||
def newTypeName = trees match {
|
||||
case List(AliasTypeDef(_, name, _, _)) => name
|
||||
}
|
||||
|
||||
override def resultExtractionCode(code: PrintWriter): Unit = {
|
||||
super.resultExtractionCode(code)
|
||||
code.println(" + \"defined type alias " + newTypeName + "\\n\"")
|
||||
}
|
||||
}
|
||||
//todo: unit-test cases
|
||||
}
|
||||
|
|
|
@ -3,72 +3,130 @@
|
|||
* @author emir
|
||||
*/
|
||||
// $Id$
|
||||
package scala.tools.nsc;
|
||||
package scala.tools.nsc
|
||||
|
||||
import java.io._;
|
||||
import scala.tools.nsc.util.{Position};
|
||||
import scala.tools.nsc.reporters.{Reporter, ConsoleReporter};
|
||||
import java.io._
|
||||
import scala.tools.nsc.util.{Position}
|
||||
import scala.tools.nsc.reporters.{Reporter, ConsoleReporter}
|
||||
|
||||
/** The main class for the new scala interpreter.
|
||||
*/
|
||||
object MainInterpreter extends Object with EvalLoop {
|
||||
// lots of stuff duplicated from Main
|
||||
val PRODUCT: String =
|
||||
System.getProperty("scala.product", "scalaint");
|
||||
val VERSION: String =
|
||||
System.getProperty("scala.version", "unknown version");
|
||||
val versionMsg = PRODUCT + " " + VERSION + " -- (c) 2002-05 LAMP/EPFL";
|
||||
val prompt = "\nnsc> ";
|
||||
object MainInterpreter {
|
||||
val reporter = new ConsoleReporter()
|
||||
|
||||
var interpreter: Interpreter = _
|
||||
|
||||
private var reporter: ConsoleReporter = _;
|
||||
|
||||
def error(msg: String): unit =
|
||||
reporter.error(new Position(PRODUCT),
|
||||
msg + "\n " + PRODUCT + " -help gives more information");
|
||||
|
||||
def errors() = reporter.errors;
|
||||
|
||||
def interpret(gCompiler: Global): unit = {
|
||||
val interpreter = new Interpreter {
|
||||
val compiler: gCompiler.type = gCompiler
|
||||
};
|
||||
loop(line => try {
|
||||
interpreter.interpret(line.trim(), reporter)
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
reporter.info(null,e.getMessage(),true);
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
)
|
||||
/** print a friendly help message */
|
||||
def printHelp = {
|
||||
Console.println("This is an interpreter for Scala.")
|
||||
Console.println("Type in expressions to have them evaluated.")
|
||||
Console.println("Type :quit to exit the interpreter.")
|
||||
Console.println("Type :compile followed by a filename to compile a complete Scala file.")
|
||||
Console.println("Type :load followed by a filename to load a sequence of interpreter commands.")
|
||||
Console.println("Type :help to repeat this message later.")
|
||||
}
|
||||
|
||||
/** A simple, generic read-eval-print loop with a pluggable eval-print function.
|
||||
Loop reading lines of input and invoking the eval-print function.
|
||||
Stop looping when eval-print returns false. */
|
||||
def repl(evpr: String => Boolean): Unit = {
|
||||
val in = new BufferedReader(new InputStreamReader(System.in))
|
||||
|
||||
while(true) {
|
||||
Console.print("\nscala> ")
|
||||
var line = in.readLine()
|
||||
if(line == null)
|
||||
return () // assumes null means EOF
|
||||
|
||||
val keepGoing = evpr(line)
|
||||
|
||||
if(!keepGoing)
|
||||
return () // the evpr function said to stop
|
||||
}
|
||||
}
|
||||
|
||||
def process(args: Array[String]): unit = {
|
||||
reporter = new ConsoleReporter();
|
||||
val command = new CompilerCommand(List.fromArray(args), error, false);
|
||||
reporter.prompt = (command.settings.prompt.value);
|
||||
if (command.settings.version.value)
|
||||
reporter.info(null, versionMsg, true)
|
||||
else if (command.settings.help.value) // 2do replace with InterpCommand
|
||||
reporter.info(null, command.usageMsg, true)
|
||||
|
||||
else {
|
||||
try {
|
||||
val compiler = new Global(command.settings, reporter);
|
||||
interpret(compiler);
|
||||
} catch {
|
||||
case ex @ FatalError(msg) =>
|
||||
if (command.settings.debug.value)
|
||||
ex.printStackTrace();
|
||||
reporter.error(null, "fatal error: " + msg);
|
||||
/** interpret one line of code submitted by the user */
|
||||
def interpretOne(line: String): Unit = {
|
||||
try {
|
||||
interpreter.interpret(line)
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
reporter.info(null,"Exception occurred: " + e.getMessage(),true)
|
||||
//e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** interpret all lines from a specified file */
|
||||
def interpretAllFrom(filename: String): Unit = {
|
||||
val fileIn = try {
|
||||
new FileReader(filename)
|
||||
} catch {
|
||||
case _:IOException =>
|
||||
Console.println("Error opening file: " + filename)
|
||||
null
|
||||
}
|
||||
if(fileIn == null) return ()
|
||||
val in = new BufferedReader(fileIn)
|
||||
while(true) {
|
||||
val line = in.readLine
|
||||
if(line == null) {
|
||||
fileIn.close
|
||||
return ()
|
||||
}
|
||||
reporter.printSummary()
|
||||
command(line)
|
||||
}
|
||||
}
|
||||
|
||||
/** run one command submitted by the user */
|
||||
def command(line: String): Boolean = {
|
||||
def withFile(command: String)(action: String=>Unit): Unit = {
|
||||
val spaceIdx = command.indexOf(' ')
|
||||
if(spaceIdx <= 0) {
|
||||
Console.println("That command requires a filename to be specified.")
|
||||
return ()
|
||||
}
|
||||
val filename = command.substring(spaceIdx).trim
|
||||
action(filename)
|
||||
}
|
||||
|
||||
if(line.startsWith(":"))
|
||||
line match {
|
||||
case ":help" => printHelp
|
||||
case ":quit" => return false
|
||||
case _ if line.startsWith(":compile") => withFile(line)(f => interpreter.compile(f))
|
||||
case _ if line.startsWith(":load") => withFile(line)(f => interpretAllFrom(f))
|
||||
case _ => Console.println("Unknown command. Type :help for help.")
|
||||
}
|
||||
else
|
||||
interpretOne(line)
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** the main interpreter loop */
|
||||
def interpretLoop(compiler: Global): unit = {
|
||||
interpreter = new Interpreter(compiler, str=>Console.print(str))
|
||||
repl(command)
|
||||
interpreter.close
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** process the command-line arguments and do as they request */
|
||||
def process(args: Array[String]): unit = {
|
||||
val command = new CompilerCommand(List.fromArray(args), error, false)
|
||||
reporter.prompt = command.settings.prompt.value
|
||||
if (command.settings.help.value) {
|
||||
reporter.info(null, command.usageMsg, true)
|
||||
} else {
|
||||
printHelp
|
||||
interpretLoop(new Global(command.settings, reporter))
|
||||
}
|
||||
}
|
||||
|
||||
def main(args: Array[String]): unit = {
|
||||
process(args);
|
||||
System.exit(if (reporter.errors > 0) 1 else 0);
|
||||
process(args)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue