package scala.tools.nsc
package ast
import java.awt.{List => awtList, _}
import java.awt.event._
import java.io.StringWriter
import javax.swing._
import javax.swing.event.TreeModelListener
import javax.swing.tree._
import scala.concurrent.Lock
import scala.text._
import symtab.Flags._
import symtab.SymbolTable
abstract class TreeBrowsers {
val global: Global
import global._
import nme.EMPTY
val borderSize = 10
def create(): SwingBrowser = new SwingBrowser();
case class ProgramTree(units: List[UnitTree]) extends Tree {
override def toString(): String = "Program"
}
case class UnitTree(unit: CompilationUnit) extends Tree {
override def toString(): String = unit.toString()
}
class SwingBrowser {
def browse(t: Tree): Tree = {
val tm = new ASTTreeModel(t)
val frame = new BrowserFrame()
frame.setTreeModel(tm)
val lock = new Lock()
frame.createFrame(lock)
lock.acquire
t
}
def browse(pName: String, units: Iterator[CompilationUnit]): Unit =
browse(pName, units.toList)
def browse(pName: String, units: List[CompilationUnit]): Unit = {
var unitList: List[UnitTree] = Nil
for (i <- units)
unitList = UnitTree(i) :: unitList
val tm = new ASTTreeModel(ProgramTree(unitList))
val frame = new BrowserFrame(pName)
frame.setTreeModel(tm)
val lock = new Lock()
frame.createFrame(lock)
lock.acquire
}
}
class ASTTreeModel(val program: Tree) extends TreeModel {
var listeners: List[TreeModelListener] = Nil
def addTreeModelListener(l: TreeModelListener): Unit =
listeners = l :: listeners
def getChild(parent: AnyRef, index: Int): AnyRef =
packChildren(parent)(index)
def getChildCount(parent: AnyRef): Int =
packChildren(parent).length
def getIndexOfChild(parent: AnyRef, child: AnyRef): Int =
packChildren(parent) indexOf child
def getRoot(): AnyRef = program
def isLeaf(node: AnyRef): Boolean = packChildren(node).isEmpty
def removeTreeModelListener(l: TreeModelListener): Unit =
listeners = listeners filterNot (_ == l)
def valueForPathChanged(path: TreePath, newValue: AnyRef) = ()
def packChildren(t: AnyRef): List[AnyRef] = TreeInfo.children(t.asInstanceOf[Tree])
}
class BrowserFrame(phaseName: String = "unknown") {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel")
}
catch {
case _ => UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName())
}
val frame = new JFrame("Scala AST after " + phaseName + " phase")
frame.setJMenuBar(new ASTMenuBar())
val topLeftPane = new JPanel(new BorderLayout())
val topRightPane = new JPanel(new BorderLayout())
val bottomPane = new JPanel(new BorderLayout())
var splitPane: JSplitPane = _
var treeModel: ASTTreeModel = _
var jTree: JTree = _
val textArea: JTextArea = new JTextArea(30, 120)
textArea.setBorder(BorderFactory.createEmptyBorder(borderSize, borderSize, borderSize, borderSize))
val infoPanel = new TextInfoPanel()
private def setExpansionState(root: JTree, expand: Boolean): Unit = {
def _setExpansionState(root: JTree, path: TreePath): Unit = {
val last = path.getLastPathComponent
for (i <- 0 until root.getModel.getChildCount(last)) {
val child = root.getModel.getChild(last, i)
val childPath = path pathByAddingChild child
_setExpansionState(root, childPath)
}
if (expand) {jTree expandPath path}
else {jTree collapsePath path}
}
_setExpansionState(root, new TreePath(root.getModel.getRoot))
}
def expandAll(subtree: JTree) = setExpansionState(subtree, true)
def collapseAll(subtree: JTree) = setExpansionState(subtree, false)
def createFrame(lock: Lock): Unit = {
lock.acquire
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
frame.addWindowListener(new WindowAdapter() {
override def windowClosed(e: WindowEvent): Unit = lock.release
});
jTree = new JTree(treeModel) {
override def convertValueToText(value: Any, sel: Boolean,
exp: Boolean, leaf: Boolean,
row: Int, hasFocus: Boolean) = {
val (cls, name) = TreeInfo.treeName(value.asInstanceOf[Tree])
if (name != EMPTY)
cls + "[" + name.toString() + "]"
else
cls
}
}
jTree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
def valueChanged(e: javax.swing.event.TreeSelectionEvent): Unit = {
textArea.setText(e.getPath().getLastPathComponent().toString())
infoPanel.update(e.getPath().getLastPathComponent())
}
})
val topSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, topLeftPane, topRightPane)
topSplitPane.setResizeWeight(0.5)
jTree.setBorder(
BorderFactory.createEmptyBorder(borderSize, borderSize, borderSize, borderSize))
topLeftPane.add(new JScrollPane(jTree), BorderLayout.CENTER)
topRightPane.add(new JScrollPane(infoPanel), BorderLayout.CENTER)
bottomPane.add(new JScrollPane(textArea), BorderLayout.CENTER)
textArea.setFont(new Font("monospaced", Font.PLAIN, 14))
textArea.setEditable(false)
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, topSplitPane, bottomPane)
frame.getContentPane().add(splitPane)
frame.pack()
frame.setVisible(true)
}
class ASTMenuBar extends JMenuBar {
val menuKey = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
val shiftKey = InputEvent.SHIFT_MASK
val jmFile = new JMenu("File")
def closeWindow() = frame.getToolkit().getSystemEventQueue().postEvent(
new WindowEvent(frame, WindowEvent.WINDOW_CLOSING))
val jmiCancel = new JMenuItem (
new AbstractAction("Cancel Compilation") {
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, menuKey + shiftKey, false))
override def actionPerformed(e: ActionEvent) {
closeWindow()
global.currentRun.cancel
}
}
)
jmFile add jmiCancel
val jmiExit = new JMenuItem (
new AbstractAction("Exit") {
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, menuKey, false))
override def actionPerformed(e: ActionEvent) = closeWindow()
}
)
jmFile add jmiExit
add(jmFile)
val jmView = new JMenu("View")
val jmiExpand = new JMenuItem(
new AbstractAction("Expand All Nodes") {
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_E, menuKey, false))
override def actionPerformed(e: ActionEvent) {
expandAll(jTree)
}
}
)
jmView add jmiExpand
val jmiCollapse = new JMenuItem(
new AbstractAction("Collapse All Nodes") {
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_L, menuKey, false))
override def actionPerformed(e: ActionEvent) {
collapseAll(jTree)
}
}
)
jmView add jmiCollapse
add(jmView)
}
def setTreeModel(tm: ASTTreeModel): Unit = treeModel = tm
}
class TextInfoPanel extends JTextArea(20, 50) {
setBorder(BorderFactory.createEmptyBorder(borderSize, borderSize, borderSize, borderSize))
setEditable(false)
setFont(new Font("monospaced", Font.PLAIN, 12))
def update(v: AnyRef): Unit = {
val t: Tree = v.asInstanceOf[Tree]
val str = new StringBuilder()
var buf = new StringWriter()
t match {
case ProgramTree(_) => ()
case UnitTree(_) => ()
case _ =>
str.append("tree.id: ").append(t.id)
str.append("\ntree.pos: ").append(t.pos)
str.append("\nSymbol: ").append(TreeInfo.symbolText(t))
str.append("\nSymbol owner: ").append(
if ((t.symbol ne null) && t.symbol != NoSymbol)
t.symbol.owner.toString
else
"NoSymbol has no owner")
if ((t.symbol ne null) && t.symbol.isType) {
str.append("\ntermSymbol: " + t.symbol.tpe.termSymbol
+ "\ntypeSymbol: " + t.symbol.tpe.typeSymbol)
if (t.symbol.isTypeSkolem)
str.append("\nSkolem of: " + t.symbol.deSkolemize)
}
str.append("\nSymbol tpe: ")
if (t.symbol ne null) {
str.append(t.symbol.tpe).append("\n")
buf = new StringWriter()
TypePrinter.toDocument(t.symbol.tpe).format(getWidth() / getColumnWidth(), buf)
str.append(buf.toString())
}
str.append("\n\nSymbol info: \n")
TreeInfo.symbolTypeDoc(t).format(getWidth() / getColumnWidth(), buf)
str.append(buf.toString())
str.append("\n\nSymbol Attributes: \n").append(TreeInfo.symbolAttributes(t))
str.append("\ntree.tpe: ")
if (t.tpe ne null) {
str.append(t.tpe.toString()).append("\n")
buf = new StringWriter()
TypePrinter.toDocument(t.tpe).format(getWidth() / getColumnWidth(), buf)
str.append(buf.toString())
}
}
setText(str.toString())
}
}
object TreeInfo {
def treeName(t: Tree): (String, Name) = t match {
case ProgramTree(units) =>
("Program", EMPTY)
case UnitTree(unit) =>
("CompilationUnit", unit.toString())
case DocDef(comment, definition) =>
("DocDef", EMPTY)
case ClassDef(mods, name, tparams, impl) =>
("ClassDef", name)
case PackageDef(packaged, impl) =>
("PackageDef", EMPTY)
case ModuleDef(mods, name, impl) =>
("ModuleDef", name)
case ValDef(mods, name, tpe, rhs) =>
("ValDef", name)
case DefDef(mods, name, tparams, vparams, tpe, rhs) =>
("DefDef", name)
case TypeDef(mods, name, tparams, rhs) =>
("TypeDef", name)
case Import(expr, selectors) =>
("Import", EMPTY)
case CaseDef(pat, guard, body) =>
("CaseDef", EMPTY)
case Template(parents, self, body) =>
("Template", EMPTY)
case LabelDef(name, params, rhs) =>
("LabelDef", name)
case Block(stats, expr) =>
("Block", EMPTY)
case Alternative(trees) =>
("Alternative", EMPTY)
case Bind(name, rhs) =>
("Bind", name)
case UnApply(fun, args) =>
("UnApply", EMPTY)
case Match(selector, cases) =>
("Visitor", EMPTY)
case Function(vparams, body) =>
("Function", EMPTY)
case Assign(lhs, rhs) =>
("Assign", EMPTY)
case If(cond, thenp, elsep) =>
("If", EMPTY)
case Return(expr) =>
("Return", EMPTY)
case Throw(expr) =>
("Throw", EMPTY)
case New(init) =>
("New", EMPTY)
case Typed(expr, tpe) =>
("Typed", EMPTY)
case TypeApply(fun, args) =>
("TypeApply", EMPTY)
case Apply(fun, args) =>
("Apply", EMPTY)
case ApplyDynamic(qual, args) =>
("Apply", EMPTY)
case Super(qualif, mix) =>
("Super", "mix: " + mix.toString())
case This(qualifier) =>
("This", qualifier)
case Select(qualifier, selector) =>
("Select", selector)
case Ident(name) =>
("Ident", name)
case Literal(value) =>
("Literal", EMPTY)
case TypeTree() =>
("TypeTree", EMPTY)
case Annotated(annot, arg) =>
("Annotated", EMPTY)
case SingletonTypeTree(ref) =>
("SingletonType", EMPTY)
case SelectFromTypeTree(qualifier, selector) =>
("SelectFromType", selector)
case CompoundTypeTree(template) =>
("CompoundType", EMPTY)
case AppliedTypeTree(tpe, args) =>
("AppliedType", EMPTY)
case TypeBoundsTree(lo, hi) =>
("TypeBoundsTree", EMPTY)
case ExistentialTypeTree(tpt, whereClauses) =>
("ExistentialTypeTree", EMPTY)
case Try(block, catcher, finalizer) =>
("Try", EMPTY)
case EmptyTree =>
("Empty", EMPTY)
case ArrayValue(elemtpt, trees) =>
("ArrayValue", EMPTY)
case Star(t) =>
("Star", EMPTY)
}
def children(t: Tree): List[Tree] = t match {
case ProgramTree(units) =>
units
case UnitTree(unit) =>
List(unit.body)
case DocDef(comment, definition) =>
List(definition)
case ClassDef(mods, name, tparams, impl) => {
var children: List[Tree] = List()
children = tparams ::: children
mods.annotations ::: impl :: children
}
case PackageDef(pid, stats) =>
stats
case ModuleDef(mods, name, impl) =>
mods.annotations ::: List(impl)
case ValDef(mods, name, tpe, rhs) =>
mods.annotations ::: List(tpe, rhs)
case DefDef(mods, name, tparams, vparams, tpe, rhs) =>
mods.annotations ::: tpe :: rhs :: vparams.flatten ::: tparams
case TypeDef(mods, name, tparams, rhs) =>
mods.annotations ::: rhs :: tparams
case Import(expr, selectors) =>
List(expr)
case CaseDef(pat, guard, body) =>
List(pat, guard, body)
case Template(parents, self, body) =>
parents ::: List(self) ::: body
case LabelDef(name, params, rhs) =>
params ::: List(rhs)
case Block(stats, expr) =>
stats ::: List(expr)
case Alternative(trees) =>
trees
case Bind(name, rhs) =>
List(rhs)
case UnApply(fun, args) =>
fun :: args
case Match(selector, cases) =>
selector :: cases
case Function(vparams, body) =>
vparams ::: List(body)
case Assign(lhs, rhs) =>
List(lhs, rhs)
case If(cond, thenp, elsep) =>
List(cond, thenp, elsep)
case Return(expr) =>
List(expr)
case Throw(expr) =>
List(expr)
case New(init) =>
List(init)
case Typed(expr, tpe) =>
List(expr, tpe)
case TypeApply(fun, args) =>
List(fun) ::: args
case Apply(fun, args) =>
List(fun) ::: args
case ApplyDynamic(qual, args) =>
List(qual) ::: args
case Super(qualif, mix) =>
List(qualif)
case This(qualif) =>
Nil
case Select(qualif, selector) =>
List(qualif)
case Ident(name) =>
Nil
case Literal(value) =>
Nil
case TypeTree() =>
Nil
case Annotated(annot, arg) =>
annot :: List(arg)
case SingletonTypeTree(ref) =>
List(ref)
case SelectFromTypeTree(qualif, selector) =>
List(qualif)
case CompoundTypeTree(templ) =>
List(templ)
case AppliedTypeTree(tpe, args) =>
tpe :: args
case TypeBoundsTree(lo, hi) =>
List(lo, hi)
case ExistentialTypeTree(tpt, whereClauses) =>
tpt :: whereClauses
case Try(block, catches, finalizer) =>
block :: catches ::: List(finalizer)
case ArrayValue(elemtpt, elems) =>
elemtpt :: elems
case EmptyTree =>
Nil
case Star(t) =>
List(t)
}
def symbolText(t: Tree): String = {
val prefix =
if (t.hasSymbol) "[has] "
else if (t.isDef) "[defines] "
else ""
prefix + t.symbol
}
def symbolTypeDoc(t: Tree): Document = {
val s = t.symbol
if (s ne null)
TypePrinter.toDocument(s.info)
else
DocNil
}
def symbolAttributes(t: Tree): String = {
val s = t.symbol
var att = ""
if ((s ne null) && (s != NoSymbol)) {
var str = flagsToString(s.flags)
if (s.isStaticMember) str = str + " isStatic ";
(str + " annotations: " + s.annotations.mkString("", " ", "")
+ (if (s.isTypeSkolem) "\ndeSkolemized annotations: " + s.deSkolemize.annotations.mkString("", " ", "") else ""))
}
else ""
}
}
object TypePrinter {
implicit def view(n: String): Document = DocText(n)
def toDocument(sym: Symbol): Document =
toDocument(sym.info)
def symsToDocument(syms: List[Symbol]): Document = syms match {
case Nil => DocNil
case s :: Nil => Document.group(toDocument(s))
case _ =>
Document.group(
syms.tail.foldLeft (toDocument(syms.head) :: ", ") (
(d: Document, s2: Symbol) => toDocument(s2) :: ", " :/: d) )
}
def toDocument(ts: List[Type]): Document = ts match {
case Nil => DocNil
case t :: Nil => Document.group(toDocument(t))
case _ =>
Document.group(
ts.tail.foldLeft (toDocument(ts.head) :: ", ") (
(d: Document, t2: Type) => toDocument(t2) :: ", " :/: d) )
}
def toDocument(t: Type): Document = t match {
case ErrorType => "ErrorType()"
case WildcardType => "WildcardType()"
case NoType => "NoType()"
case NoPrefix => "NoPrefix()"
case ThisType(s) => "ThisType(" + s.name + ")"
case SingleType(pre, sym) =>
Document.group(
Document.nest(4, "SingleType(" :/:
toDocument(pre) :: ", " :/: sym.name.toString() :: ")")
)
case ConstantType(value) =>
"ConstantType(" + value + ")"
case TypeRef(pre, sym, args) =>
Document.group(
Document.nest(4, "TypeRef(" :/:
toDocument(pre) :: ", " :/:
sym.name.toString() + sym.idString :: ", " :/:
"[ " :: toDocument(args) ::"]" :: ")")
)
case TypeBounds(lo, hi) =>
Document.group(
Document.nest(4, "TypeBounds(" :/:
toDocument(lo) :: ", " :/:
toDocument(hi) :: ")")
)
case RefinedType(parents, defs) =>
Document.group(
Document.nest(4, "RefinedType(" :/:
toDocument(parents) :: ")")
)
case ClassInfoType(parents, defs, clazz) =>
Document.group(
Document.nest(4,"ClassInfoType(" :/:
toDocument(parents) :: ", " :/:
clazz.name.toString() + clazz.idString :: ")")
)
case MethodType(params, result) =>
Document.group(
Document.nest(4, "MethodType(" :/:
Document.group("(" :/:
symsToDocument(params) :/:
"), ") :/:
toDocument(result) :: ")")
)
case NullaryMethodType(result) =>
Document.group(
Document.nest(4,"NullaryMethodType(" :/:
toDocument(result) :: ")")
)
case PolyType(tparams, result) =>
Document.group(
Document.nest(4,"PolyType(" :/:
Document.group("(" :/:
symsToDocument(tparams) :/:
"), ") :/:
toDocument(result) :: ")")
)
case AnnotatedType(annots, tp, _) =>
Document.group(
Document.nest(4, "AnnotatedType(" :/:
annots.mkString("[", ",", "]") :/:
"," :/: toDocument(tp) :: ")")
)
case ExistentialType(tparams, result) =>
Document.group(
Document.nest(4, "ExistentialType(" :/:
Document.group("(" :/: symsToDocument(tparams) :/: "), ") :/:
toDocument(result) :: ")"))
case global.analyzer.ImportType(expr) =>
"ImportType(" + expr.toString + ")"
case SuperType(thistpe, supertpe) =>
Document.group(
Document.nest(4, "SuperType(" :/:
toDocument(thistpe) :/: ", " :/:
toDocument(supertpe) ::")"))
case _ =>
sys.error("Unknown case: " + t.toString() +", "+ t.getClass)
}
}
}