Generalizes the allowed inputs to the interpreter to be any combination of phrases that can be placed inside a template. Instead of having one "request" object try to understand the entire combination of phrases, the code now uses multiple "member handlers" for each request, one member handler for each phrase.

git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@14054 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
spoon 2008-02-18 21:22:08 +00:00
parent 81f3b03c5b
commit 10aba6a2cd
4 changed files with 274 additions and 279 deletions

View File

@ -10,6 +10,7 @@ import java.io.{File, PrintWriter, StringWriter, Writer}
import java.lang.{Class, ClassLoader}
import java.net.{URL, URLClassLoader}
import scala.collection.immutable.ListSet
import scala.collection.mutable
import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer}
@ -78,6 +79,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
import compiler.CompilationUnit
import compiler.{Symbol,Name,Type}
import compiler.nme
import compiler.newTermName
import compiler.nme.{INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX}
import Interpreter.string2code
/** construct an interpreter that reports to Console */
@ -181,9 +184,27 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
private def newVarName() = {
val num = nextVarNameNo
nextVarNameNo += 1
compiler.nme.INTERPRETER_VAR_PREFIX + num
INTERPRETER_VAR_PREFIX + num
}
/** next internal variable number to use */
private var nextInternalVarNo = 0
/** allocate a fresh internal variable name */
private def newInternalVarName() = {
val num = nextVarNameNo
nextVarNameNo += 1
INTERPRETER_SYNTHVAR_PREFIX + num
}
/** Check if a name looks like it was generated by newVarName */
private def isGeneratedVarName(name: String): Boolean =
name.startsWith(INTERPRETER_VAR_PREFIX) && {
val suffix = name.drop(INTERPRETER_VAR_PREFIX.length)
suffix.forall(_.isDigit)
}
/** generate a string using a routine that wants to write on a stream */
private def stringFrom(writer: PrintWriter => Unit): String = {
@ -259,34 +280,31 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
* should be taken. Removes requests which cannot contribute
* useful imports for the specified set of wanted names.
*/
def reqsToUse: List[Request] = {
/** Loop through the requests in reverse and select
def reqsToUse: List[(Request,MemberHandler)] = {
/** Loop through a list of MemberHandlers and select
* which ones to keep. 'wanted' is the set of
* names that need to be imported, and
* 'shadowed' is the list of names useless to import
* because a later request will re-import it anyway.
*/
def select(reqs: List[Request], wanted: Set[Name]): List[Request] = {
def select(reqs: List[(Request,MemberHandler)], wanted: Set[Name]):
List[(Request,MemberHandler)] = {
reqs match {
case Nil => Nil
case req::rest =>
val keepit = req.definesImplicit || (req match {
case req:ImportReq =>
req.importsWildcard ||
req.importedNames.exists(wanted.contains)
case _ =>
req.boundNames.exists(wanted.contains)
})
case (req,handler)::rest =>
val keepit =
(handler.definesImplicit ||
handler.importsWildcard ||
handler.importedNames.exists(wanted.contains(_)) ||
handler.boundNames.exists(wanted.contains(_)))
val newWanted =
if (keepit) {
req match {
case req:ImportReq =>
wanted -- req.importedNames ++ req.usedNames
case _ => wanted -- req.boundNames
}
(wanted
++ handler.usedNames
-- handler.boundNames
-- handler.importedNames)
} else {
wanted
}
@ -294,13 +312,18 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
val restToKeep = select(rest, newWanted)
if(keepit)
req :: restToKeep
(req,handler) :: restToKeep
else
restToKeep
}
}
select(prevRequests.toList.reverse, wanted).reverse
val rhpairs = for {
req <- prevRequests.toList.reverse
handler <- req.handlers
} yield (req, handler)
select(rhpairs, wanted).reverse
}
val code = new StringBuffer
@ -321,40 +344,37 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
// loop through previous requests, adding imports
// for each one
for (req <- reqsToUse) {
req match {
case req:ImportReq =>
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(req.importsWildcard ||
currentImps.exists(req.importedNames.contains))
if(handler.importsWildcard ||
currentImps.exists(handler.importedNames.contains))
if(!currentImps.isEmpty)
addWrapper()
code.append(req.line + ";\n")
if (handler.member.isInstanceOf[Import])
code.append(handler.member.toString + ";\n")
// give wildcard imports a import wrapper all to their own
if(req.importsWildcard)
if(handler.importsWildcard)
addWrapper()
else
currentImps ++= req.importedNames
currentImps ++= handler.importedNames
case req =>
// 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 <- req.boundNames) {
for (imv <- handler.boundNames) {
if (currentImps.contains(imv))
addWrapper()
code.append("import ")
code.append(req.objectName + req.accessPath + ".`" + imv + "`;\n")
currentImps += imv
}
}
}
addWrapper() // Add one extra wrapper, to prevent warnings
@ -411,26 +431,20 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
* 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(t:TypeDef) if compiler.treeInfo.isAliasTypeDef(t) =>
new TypeAliasReq(line, lineName)
case List(_:Import) => new ImportReq(line, lineName)
case _ =>
reporter.error(null, "That kind of statement combination is not supported by the interpreter.")
null
new Request(line, lineName)
private def chooseHandler(member: Tree): Option[MemberHandler] =
member match {
case member: DefDef =>
Some(new DefHandler(member))
case member: ValDef =>
Some(new ValHandler(member))
case member@Assign(Ident(_), _) => Some(new AssignHandler(member))
case member: ModuleDef => Some(new ModuleHandler(member))
case member: ClassDef => Some(new ClassHandler(member))
case member: TypeDef => Some(new TypeAliasHandler(member))
case member: Import => Some(new ImportHandler(member))
case _ => None
}
/** <p>
@ -458,6 +472,19 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
case Some(trees) => trees
}
trees match {
case List(_:Assign) => ()
case List(_:TermTree) | List(_:Ident) | List(_:Select) =>
// Treat a single bare expression specially.
// This is necessary due to it being hard to modify
// code at a textual level, and it being hard to
// submit an AST to the compiler.
return interpret("val "+newVarName()+" = \n"+line)
case _ => ()
}
val lineName = newLineName
// figure out what kind of request
@ -472,10 +499,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
if (printResults || !succeeded) {
// print the result
out.print(clean(interpreterResultString))
// print out types of functions; they are not printed in the
// request printout
out.print(clean(req.defTypesSummary))
}
// book-keeping
@ -556,8 +579,159 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
}
/** Class to handle one member among all the members included
* in a single interpreter request.
*/
private sealed abstract class MemberHandler(val member: Tree) {
val usedNames: List[Name] = {
val ivt = new ImportVarsTraverser(boundNames)
ivt.traverseTrees(List(member))
ivt.importVars.toList
}
val boundNames: List[Name] = Nil
def valAndVarNames: List[Name] = Nil
def defNames: List[Name] = Nil
val importsWildcard = false
val importedNames: Seq[Name] = Nil
val definesImplicit =
member match {
case tree:MemberDef =>
tree.mods.hasFlag(symtab.Flags.IMPLICIT)
case _ => false
}
def extraCodeToEvaluate(req: Request, code: PrintWriter) { }
def resultExtractionCode(req: Request, code: PrintWriter) { }
}
private class ValHandler(member: ValDef) extends MemberHandler(member) {
override val boundNames = List(member.name)
override def valAndVarNames = boundNames
override def resultExtractionCode(req: Request, code: PrintWriter) {
val vname = member.name
if (member.mods.isPublic &&
!(isGeneratedVarName(vname) &&
req.typeOf(compiler.encode(vname)) == "Unit"))
{
code.print(" + \"" + vname + ": " +
string2code(req.typeOf(vname)) +
" = \" + " +
" (if(" +
req.fullPath(vname) +
".asInstanceOf[AnyRef] != null) " +
" ((if(" +
req.fullPath(vname) +
".toString.contains('\\n')) " +
" \"\\n\" else \"\") + " +
req.fullPath(vname) + ".toString + \"\\n\") else \"null\\n\") ")
}
}
}
private class DefHandler(defDef: DefDef) extends MemberHandler(defDef) {
override val boundNames = List(defDef.name)
override def defNames = boundNames
override def resultExtractionCode(req: Request, code: PrintWriter) {
if (defDef.mods.isPublic)
code.print("+\""+string2code(defDef.name)+": "+
string2code(req.typeOf(defDef.name))+"\\n\"")
}
}
private class AssignHandler(member: Assign) extends MemberHandler(member) {
val lhs = member. lhs.asInstanceOf[Ident] // an unfortunate limitation
val helperName = newTermName(newInternalVarName())
override val valAndVarNames = List(helperName)
override def extraCodeToEvaluate(req: Request, code: PrintWriter) {
code.println("val "+helperName+" = "+member.lhs+";")
}
/** Print out lhs instead of the generated varName */
override def resultExtractionCode(req: Request, code: PrintWriter) {
code.print(" + \"" + lhs + ": " +
string2code(req.typeOf(compiler.encode(helperName))) +
" = \" + " +
string2code(req.fullPath(helperName))
+ " + \"\\n\"")
}
}
private class ModuleHandler(module: ModuleDef) extends MemberHandler(module) {
override val boundNames = List(module.name)
override def resultExtractionCode(req: Request, code: PrintWriter) {
code.println(" + \"defined module " +
string2code(module.name)
+ "\\n\"")
}
}
private class ClassHandler(classdef: ClassDef)
extends MemberHandler(classdef)
{
override val boundNames =
List(classdef.name) :::
(if (classdef.mods.hasFlag(Flags.CASE))
List(classdef.name.toTermName)
else
Nil)
// TODO: MemberDef.keyword does not include "trait";
// otherwise it could be used here
def keyword: String =
if (classdef.mods.isTrait) "trait" else "class"
override def resultExtractionCode(req: Request, code: PrintWriter) {
code.print(
" + \"defined " +
keyword +
" " +
string2code(classdef.name) +
"\\n\"")
}
}
private class TypeAliasHandler(typeDef: TypeDef)
extends MemberHandler(typeDef)
{
override val boundNames =
if (typeDef.mods.isPublic && compiler.treeInfo.isAliasTypeDef(typeDef))
List(typeDef.name)
else
Nil
override def resultExtractionCode(req: Request, code: PrintWriter) {
code.println(" + \"defined type alias " +
string2code(typeDef.name) + "\\n\"")
}
}
private class ImportHandler(imp: Import) extends MemberHandler(imp) {
override def resultExtractionCode(req: Request, code: PrintWriter) {
code.println("+ \"" + imp.toString + "\\n\"")
}
/** Whether this import includes a wildcard import */
override val importsWildcard =
imp.selectors.map(_._1).contains(nme.USCOREkw)
/** The individual names imported by this statement */
override val importedNames: Seq[Name] =
for {
val (_,sel) <- imp.selectors
sel != null
sel != nme.USCOREkw
val name <- List(sel.toTypeName, sel.toTermName)
}
yield name
}
/** One line of code submitted by the user for interpretation */
private abstract class Request(val line: String, val lineName: String) {
private class Request(val line: String, val lineName: String) {
val trees = parse(line) match {
case Some(ts) => ts
case None => Nil
@ -569,80 +743,13 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
/** 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 */
val needsVarName: Boolean = false
/** A cache for the chosen variable name, if one has been calculated */
var varNameCache: Option[String] = None
/** A computed variable name, if one is needed */
def varName = varNameCache match {
case None =>
varNameCache = Some(newVarName)
varNameCache.get
case Some(name) =>
name
}
/** list of methods defined */
val defNames =
for (DefDef(mods, name, _, _, _, _) <- trees if mods.isPublic)
yield name
/** list of val's and var's defined */
val valAndVarNames = {
val baseNames =
for (ValDef(mods, name, _, _) <- trees if mods.isPublic)
yield name
if (needsVarName)
compiler.encode(varName) :: baseNames // add a var name
else
baseNames
}
/** list of modules defined */
val moduleNames = {
val explicit =
for (ModuleDef(mods, name, _) <- trees if 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 (ClassDef(mods, name, _, _) <- trees if mods.isPublic)
yield name
/** list of type aliases defined */
val typeNames =
for (t @ TypeDef(mods, name, _, _) <- trees
if mods.isPublic && compiler.treeInfo.isAliasTypeDef(t))
yield name
val handlers: List[MemberHandler] = trees.flatMap(chooseHandler(_))
/** all (public) names defined by these statements */
val boundNames =
defNames ::: valAndVarNames ::: moduleNames ::: classNames ::: typeNames
val boundNames = (ListSet() ++ handlers.flatMap(_.boundNames)).toList
/** list of names used by this expression */
val usedNames: List[Name] = {
val ivt = new ImportVarsTraverser(boundNames)
ivt.traverseTrees(trees)
ivt.importVars.toList
}
/** Whether this request defines an implicit. */
def definesImplicit = trees.exists {
case tree:MemberDef =>
tree.mods.hasFlag(symtab.Flags.IMPLICIT)
case _ => false
}
val usedNames: List[Name] = handlers.flatMap(_.usedNames)
def myImportsCode = importsCode(Set.empty ++ usedNames)
@ -671,11 +778,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
code.print(importsPreamble)
// the variable to compute, if any
if (needsVarName)
code.println(" val " + varName + " = ")
code.println(indentCode(toCompute))
handlers.foreach(_.extraCodeToEvaluate(this,code))
code.println(importsTrailer)
@ -694,28 +799,13 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
code.println("object " + resultObjectName)
code.println("{ val result: String = {")
code.println(objectName + accessPath + ";") // evaluate the object, to make sure its constructor is run
code.print("\"\"") // print an initial empty string, so later code can
code.print("(\"\"") // print an initial empty string, so later code can
// uniformly be: + morestuff
resultExtractionCode(code)
code.println("}")
handlers.foreach(_.resultExtractionCode(this, code))
code.println("\n)}")
code.println(";}")
})
def resultExtractionCode(code: PrintWriter) {
for (vname <- valAndVarNames) {
code.print(" + \"" + vname + ": " +
string2code(typeOf(vname)) +
" = \" + " +
" (if(" +
fullPath(vname) +
".asInstanceOf[AnyRef] != null) " +
" ((if(" +
fullPath(vname) +
".toString.contains('\\n')) " +
" \"\\n\" else \"\") + " +
fullPath(vname) + ".toString + \"\\n\") else \"null\\n\") ")
}
}
/** Compile the object file. Returns whether the compilation succeeded.
* If all goes well, the "types" map is computed. */
@ -750,11 +840,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
* @return ...
*/
def findTypes(objRun: compiler.Run): Map[Name, String] = {
def valAndVarNames = handlers.flatMap(_.valAndVarNames)
def defNames = handlers.flatMap(_.defNames)
def getTypes(names: List[Name], nameMap: Name=>Name): Map[Name, String] = {
/** the outermost wrapper object */
val outerResObjSym: Symbol =
compiler.definitions.getMember(compiler.definitions.EmptyPackage,
compiler.newTermName(objectName))
newTermName(objectName))
/** the innermost object inside the wrapper, found by
* following accessPath into the outer one. */
@ -762,7 +855,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
(accessPath.split("\\.")).foldLeft(outerResObjSym)((sym,name) =>
if(name == "") sym else
compiler.atPhase(objRun.typerPhase.next) {
sym.info.member(compiler.newTermName(name)) })
sym.info.member(newTermName(name)) })
names.foldLeft(Map.empty[Name,String])((map, name) => {
val rawType =
@ -802,139 +895,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
(stringFrom(str => orig.printStackTrace(str)), false)
}
}
/** return a summary of the defined methods */
def defTypesSummary: String =
stringFrom(summ => {
for (methname <- defNames)
summ.println("" + methname + ": " +
string2code(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 val needsVarName = true
/** Perform the assignment, and then return the new value */
override def toCompute = "{\n" + line + "\n;\n" + lhs + "\n}"
/** Print out lhs instead of the generated varName */
override def resultExtractionCode(code: PrintWriter) {
code.print(" + \"" + lhs + ": " +
string2code(typeOf(compiler.encode(varName))) +
" = \" + " +
string2code(fullPath(varName))
+ " + \"\\n\"")
// override def resultExtractionCode(code: PrintWriter) {
// {wrapperObj; lhs}
// }
}
}
/** A single expression */
private class ExprReq(line: String, lineName: String)
extends Request(line, lineName) {
override val needsVarName = true
/** Skip the printout if the expression has type Unit */
override def resultExtractionCode(code: PrintWriter) {
if (typeOf(compiler.encode(varName)) != "Unit")
super.resultExtractionCode(code)
}
}
/** 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) {
super.resultExtractionCode(code)
code.println(" + \"defined module " +
string2code(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
}
def classdef = trees.head.asInstanceOf[ClassDef]
// TODO: MemberDef.keyword does not include "trait";
// otherwise it could be used here
def keyword: String =
if (classdef.mods.isTrait) "trait" else "class"
override def resultExtractionCode(code: PrintWriter) {
super.resultExtractionCode(code)
code.print(
" + \"defined " +
keyword +
" " +
string2code(newClassName) +
"\\n\"")
}
}
/** a type alias */
private class TypeAliasReq(line: String, lineName: String)
extends Request(line, lineName) {
def newTypeName = trees match {
case List(TypeDef(_, name, _, _)) => name
}
override def resultExtractionCode(code: PrintWriter) {
super.resultExtractionCode(code)
code.println(" + \"defined type alias " + newTypeName + "\\n\"")
}
}
/** an import */
private class ImportReq(line: String, lineName: String)
extends Request(line, lineName) {
override val boundNames = Nil
override def resultExtractionCode(code: PrintWriter) {
code.println("+ \"" + trees.head.toString + "\\n\"")
}
/** Whether this import includes a wildcard import */
def importsWildcard =
trees.exists {
case Import(_, selectors) =>
selectors.map(_._1).contains(nme.USCOREkw)
case _ => false
}
/** The individual names imported by this statement */
def importedNames: Seq[Name] =
for {
val Import(_, selectors) <- trees
val (_,sel) <- selectors
sel != null
sel != nme.USCOREkw
val name <- List(sel.toTypeName, sel.toTermName)
}
yield name
}
}
class NewLinePrintWriter(out: Writer, autoFlush: Boolean)
extends PrintWriter(out, autoFlush) {
def this(out: Writer) = this(out, false)
override def println() { print("\n"); flush() }
}
}
/** Utility methods for the Interpreter. */
object Interpreter {

View File

@ -78,6 +78,7 @@ trait StdNames {
val INTERPRETER_LINE_PREFIX = "line"
val INTERPRETER_VAR_PREFIX = "res"
val INTERPRETER_IMPORT_WRAPPER = "$iw"
val INTERPRETER_SYNTHVAR_PREFIX = "synthvar$"
def LOCAL(clazz: Symbol) = newTermName(LOCALDUMMY_PREFIX_STRING + clazz.name+">")
def TUPLE_FIELD(index: Int) = newTermName(TUPLE_FIELD_PREFIX_STRING + index)

View File

@ -27,6 +27,11 @@ scala> <console>:5: error: type mismatch;
scala> defined trait PointlessTrait
scala> x: Int = 2
y: Int = 3
scala> hello
scala>
scala>
scala> defined class Foo
@ -151,7 +156,7 @@ scala> <console>:1: error: identifier expected but integer literal found.
scala>
scala>
scala>
scala> | | | | res2: scala.xml.Elem =
scala> | | | | res3: scala.xml.Elem =
<a>
<b d="dd" c="c"></b></a>
@ -161,7 +166,7 @@ scala> | | | |
scala>
scala>
scala>
scala> | | | res3: java.lang.String =
scala> | | | res4: java.lang.String =
hello
there
@ -177,3 +182,16 @@ scala> x: Int = 1
scala>
scala>
scala> defined class Exp
defined class Fact
defined class Term
scala> | | <console>:15: warning: match is not exhaustive!
missing combination Term
def f(e: Exp) = e match { // non-exhaustive warning here
^
f: (Exp)Int
scala>
scala>

View File

@ -19,6 +19,8 @@ type anotherint = Int
val four: anotherint = 4
val bogus: anotherint = "hello"
trait PointlessTrait
val (x,y) = (2,3)
println("hello")
// implicit conversions
case class Foo(n: int)
@ -111,6 +113,12 @@ there
def `match` = 1
val x = `match`
// multiple classes defined on one line
sealed class Exp; class Fact extends Exp; class Term extends Exp
def f(e: Exp) = e match {{ // non-exhaustive warning here
case _:Fact => 3
}}
</code>.text
/** A writer that skips the first line of text. The first