1 """ @package antlr3.dottreegenerator 2 @brief ANTLR3 runtime package, tree module 3 4 This module contains all support classes for AST construction and tree parsers. 5 6 """ 7 8 # begin[licence] 9 # 10 # [The "BSD licence"] 11 # Copyright (c) 2005-2008 Terence Parr 12 # All rights reserved. 13 # 14 # Redistribution and use in source and binary forms, with or without 15 # modification, are permitted provided that the following conditions 16 # are met: 17 # 1. Redistributions of source code must retain the above copyright 18 # notice, this list of conditions and the following disclaimer. 19 # 2. Redistributions in binary form must reproduce the above copyright 20 # notice, this list of conditions and the following disclaimer in the 21 # documentation and/or other materials provided with the distribution. 22 # 3. The name of the author may not be used to endorse or promote products 23 # derived from this software without specific prior written permission. 24 # 25 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 26 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 27 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 29 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 30 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 # 36 # end[licence] 37 38 # lot's of docstrings are missing, don't complain for now... 39 # pylint: disable-msg=C0111 40 41 from antlr3.tree import CommonTreeAdaptor 42 import stringtemplate3 43 44 class DOTTreeGenerator(object): 45 """ 46 A utility class to generate DOT diagrams (graphviz) from 47 arbitrary trees. You can pass in your own templates and 48 can pass in any kind of tree or use Tree interface method. 49 """ 50 51 _treeST = stringtemplate3.StringTemplate( 52 template=( 53 "digraph {\n" + 54 " ordering=out;\n" + 55 " ranksep=.4;\n" + 56 " node [shape=plaintext, fixedsize=true, fontsize=11, fontname=\"Courier\",\n" + 57 " width=.25, height=.25];\n" + 58 " edge [arrowsize=.5]\n" + 59 " $nodes$\n" + 60 " $edges$\n" + 61 "}\n") 62 ) 63 64 _nodeST = stringtemplate3.StringTemplate( 65 template="$name$ [label=\"$text$\"];\n" 66 ) 67 68 _edgeST = stringtemplate3.StringTemplate( 69 template="$parent$ -> $child$ // \"$parentText$\" -> \"$childText$\"\n" 70 ) 71 72 def __init__(self): 73 ## Track node to number mapping so we can get proper node name back 74 self.nodeToNumberMap = {} 75 76 ## Track node number so we can get unique node names 77 self.nodeNumber = 0 78 79 80 def toDOT(self, tree, adaptor=None, treeST=_treeST, edgeST=_edgeST): 81 if adaptor is None: 82 adaptor = CommonTreeAdaptor() 83 84 treeST = treeST.getInstanceOf() 85 86 self.nodeNumber = 0 87 self.toDOTDefineNodes(tree, adaptor, treeST) 88 89 self.nodeNumber = 0 90 self.toDOTDefineEdges(tree, adaptor, treeST, edgeST) 91 return treeST 92 93 94 def toDOTDefineNodes(self, tree, adaptor, treeST, knownNodes=None): 95 if knownNodes is None: 96 knownNodes = set() 97 98 if tree is None: 99 return 100 101 n = adaptor.getChildCount(tree) 102 if n == 0: 103 # must have already dumped as child from previous 104 # invocation; do nothing 105 return 106 107 # define parent node 108 number = self.getNodeNumber(tree) 109 if number not in knownNodes: 110 parentNodeST = self.getNodeST(adaptor, tree) 111 treeST.setAttribute("nodes", parentNodeST) 112 knownNodes.add(number) 113 114 # for each child, do a "<unique-name> [label=text]" node def 115 for i in range(n): 116 child = adaptor.getChild(tree, i) 117 118 number = self.getNodeNumber(child) 119 if number not in knownNodes: 120 nodeST = self.getNodeST(adaptor, child) 121 treeST.setAttribute("nodes", nodeST) 122 knownNodes.add(number) 123 124 self.toDOTDefineNodes(child, adaptor, treeST, knownNodes) 125 126 127 def toDOTDefineEdges(self, tree, adaptor, treeST, edgeST): 128 if tree is None: 129 return 130 131 n = adaptor.getChildCount(tree) 132 if n == 0: 133 # must have already dumped as child from previous 134 # invocation; do nothing 135 return 136 137 parentName = "n%d" % self.getNodeNumber(tree) 138 139 # for each child, do a parent -> child edge using unique node names 140 parentText = adaptor.getText(tree) 141 for i in range(n): 142 child = adaptor.getChild(tree, i) 143 childText = adaptor.getText(child) 144 childName = "n%d" % self.getNodeNumber(child) 145 edgeST = edgeST.getInstanceOf() 146 edgeST.setAttribute("parent", parentName) 147 edgeST.setAttribute("child", childName) 148 edgeST.setAttribute("parentText", parentText) 149 edgeST.setAttribute("childText", childText) 150 treeST.setAttribute("edges", edgeST) 151 self.toDOTDefineEdges(child, adaptor, treeST, edgeST) 152 153 154 def getNodeST(self, adaptor, t): 155 text = adaptor.getText(t) 156 nodeST = self._nodeST.getInstanceOf() 157 uniqueName = "n%d" % self.getNodeNumber(t) 158 nodeST.setAttribute("name", uniqueName) 159 if text is not None: 160 text = text.replace('"', r'\\"') 161 nodeST.setAttribute("text", text) 162 return nodeST 163 164 165 def getNodeNumber(self, t): 166 try: 167 return self.nodeToNumberMap[t] 168 except KeyError: 169 self.nodeToNumberMap[t] = self.nodeNumber 170 self.nodeNumber += 1 171 return self.nodeNumber - 1 172 173 174 def toDOT(tree, adaptor=None, treeST=DOTTreeGenerator._treeST, edgeST=DOTTreeGenerator._edgeST): 175 """ 176 Generate DOT (graphviz) for a whole tree not just a node. 177 For example, 3+4*5 should generate: 178 179 digraph { 180 node [shape=plaintext, fixedsize=true, fontsize=11, fontname="Courier", 181 width=.4, height=.2]; 182 edge [arrowsize=.7] 183 "+"->3 184 "+"->"*" 185 "*"->4 186 "*"->5 187 } 188 189 Return the ST not a string in case people want to alter. 190 191 Takes a Tree interface object. 192 193 Example of invokation: 194 195 import antlr3 196 import antlr3.extras 197 198 input = antlr3.ANTLRInputStream(sys.stdin) 199 lex = TLexer(input) 200 tokens = antlr3.CommonTokenStream(lex) 201 parser = TParser(tokens) 202 tree = parser.e().tree 203 print tree.toStringTree() 204 st = antlr3.extras.toDOT(t) 205 print st 206 207 """ 208 209 gen = DOTTreeGenerator() 210 return gen.toDOT(tree, adaptor, treeST, edgeST) 211