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:
spoon 2006-02-16 23:08:08 +00:00
parent 8c812563f0
commit 8f65d9787b
2 changed files with 549 additions and 264 deletions

View File

@ -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
}

View File

@ -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)
}
}