simplify

viewer.py

102:896b313225a4
2006-10-22 paulb Added a special AST node annotation for convenient comparison operator access. Expanded node coverage in the viewer, adding operator target display.
     1 #!/usr/bin/env python     2      3 """     4 View annotated sources.     5      6 Copyright (C) 2006 Paul Boddie <paul@boddie.org.uk>     7      8 This software is free software; you can redistribute it and/or     9 modify it under the terms of the GNU General Public License as    10 published by the Free Software Foundation; either version 2 of    11 the License, or (at your option) any later version.    12     13 This software is distributed in the hope that it will be useful,    14 but WITHOUT ANY WARRANTY; without even the implied warranty of    15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    16 GNU General Public License for more details.    17     18 You should have received a copy of the GNU General Public    19 License along with this library; see the file LICENCE.txt    20 If not, write to the Free Software Foundation, Inc.,    21 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA    22 """    23     24 from compiler.visitor import ASTVisitor    25 from simplified import *    26 import sys    27 import textwrap    28     29 # Exceptions.    30     31 class ViewerError(SimplifiedError):    32     33     "An error in viewing."    34     35     pass    36     37 # Classes.    38     39 class Viewer(ASTVisitor):    40     41     """    42     A viewing visitor for AST nodes.    43     """    44     45     def __init__(self, stream):    46         ASTVisitor.__init__(self)    47         self.cached_files = {}    48         self.printed_lines = {}    49         self.visitor = self    50         self.stream = stream    51     52     def process(self, module):    53         self.dispatch(module)    54     55     def dispatch(self, node):    56         self.dispatch_only(node)    57         ASTVisitor.dispatch(self, node)    58     59     def dispatch_only(self, node, every_time=0):    60         self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None), every_time)    61     62     def print_line(self, filename, lineno, every_time):    63         last_printed = self.printed_lines.get(filename, 0)    64         if lineno > last_printed or every_time:    65             self.stream.write(self.get_line(filename, lineno))    66             self.printed_lines[filename] = lineno    67     68     def get_line(self, filename, lineno):    69         if filename is None or lineno is None:    70             return ""    71     72         if self.cached_files.has_key(filename):    73             lines = self.cached_files[filename]    74         else:    75             f = open(filename)    76             try:    77                 self.cached_files[filename] = lines = f.readlines()    78             finally:    79                 f.close()    80     81         try:    82             return lines[lineno - 1]    83         except IndexError:    84             return ""    85     86     def report(self, exc):    87         self.stream.write("Exception was:\n\n" + str(exc.exc) + "\n\n")    88         self.stream.write("Nodes:\n\n")    89         for node in exc.nodes:    90             self.stream.write(repr(node) + "\n")    91             self.dispatch_only(node.original, every_time=1)    92         self.stream.write("\nOriginal node was:\n\n" + repr(exc.nodes[0].original) + "\n")    93         self.stream.write("\nSimplified node was:\n\n")    94         exc.nodes[0].pprint(stream=self.stream)    95     96 # HTML-related output production.    97     98 html_header = """<?xml version="1.0" encoding="iso-8859-15"?>    99 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">   100 <html xmlns="http://www.w3.org/1999/xhtml">   101 <head>   102   <title>Module</title>   103   <style type="text/css">   104     body {   105       padding-top: 4em; padding-bottom: 4em;   106       font-size: 14pt; font-family: monospace;   107       background-color: black; color: white;   108     }   109    110     .class { margin-bottom: 1em; }   111     .function { margin-bottom: 1em; }   112     .body { padding-left: 2em; }   113     .keyword { color: yellow; }   114     .comment { color: blue; }   115     .str { color: #FF00FF; }   116     .doc { color: #FF00FF; margin-bottom: 1em; }   117     .ref { color: cyan; }   118     .ref a { color: cyan; text-decoration: none; }   119    120     .popup {   121       display: none; z-index: 2;   122       position: absolute; top: 1em; left: 0.5em;   123       padding: 0.2em; background-color: #000000;   124     }   125    126     .invocations {   127       padding: 0.5em; background-color: #770000;   128       clear: all;   129     }   130    131     .types {   132       padding: 0.5em; background-color: #0000FF;   133       float: right;   134     }   135    136     .scopes {   137       padding: 0.5em; background-color: #007700;   138       float: left;   139     }   140    141     .op,   142     .name,   143     .attr   144     {   145       position: relative;   146     }   147    148     .op:hover > .popup,   149     .name:hover > .popup,   150     .attr:hover > .popup   151     {   152        display: block;   153     }   154    155   </style>   156 </head>   157 <body>   158 """   159    160 html_footer = """</body>   161 </html>   162 """   163    164 # Browser classes.   165    166 class Browser(ASTVisitor):   167    168     """   169     A browsing visitor for AST nodes.   170    171     Covered: AssAttr, AssList, AssName, AssTuple, Assign, AugAssign, Break,   172              CallFunc, Class, Compare, Const, Continue, Dict, Discard, For,   173              Function, Getattr, If, Keyword, Lambda, List, Module, Name, Pass, Raise, Return, Slice,   174              Stmt, Subscript, Tuple, While.   175    176     Missing: And, Add, Assert, Backquote, Bitand, Bitor, Bitxor, Decorators, Div,   177              Ellipsis, Exec, FloorDiv, From, Global, Import, Invert, LeftShift, ListComp, ListCompFor,   178              ListCompIf, Mod, Mul, Not, Or, Power, Print, Printnl, RightShift, Sliceobj,   179              Sub, TryExcept, TryFinally, UnaryAdd, UnarySub, Yield.   180     """   181    182     def __init__(self, stream):   183         ASTVisitor.__init__(self)   184         self.visitor = self   185         self.stream = stream   186    187     def process(self, module):   188         self.stream.write(html_header)   189         self.dispatch(module)   190         self.stream.write(html_footer)   191    192     def dispatch(self, node):   193         try:   194             ASTVisitor.dispatch(self, node)   195         except ViewerError, exc:   196             exc.add(node)   197             raise   198         except Exception, exc:   199             raise ViewerError(exc, node)   200    201     def visitModule(self, node):   202         self.default(node)   203    204     # Statements.   205    206     def visitAssign(self, node):   207         self.stream.write("<div class='assign'>\n")   208         for lvalue in node.nodes:   209             self.dispatch(lvalue)   210             self.stream.write("=\n")   211         self.dispatch(node.expr)   212         self.stream.write("</div>\n")   213    214     def visitAugAssign(self, node):   215         self.stream.write("<div class='augassign'>\n")   216         self.dispatch(node.node)   217         self.stream.write("%s\n" % node.op)   218         self.dispatch(node.expr)   219         self.stream.write("</div>\n")   220    221     def visitBreak(self, node):   222         self.stream.write("<div class='break'>\n")   223         self._keyword("break")   224         self.stream.write("</div>\n")   225    226     def visitClass(self, node):   227         definition = node._node   228         structure = definition.expr.ref   229         self.stream.write("<div class='class' id='%s'>\n" % self._url(structure.full_name()))   230         self.stream.write("<div>\n")   231         self._keyword("class")   232         self._name_start(structure.name)   233         self._popup_start()   234         self._scopes(definition)   235         self._popup_end()   236         self._name_end()   237         bases = structure.bases   238         if bases:   239             self.stream.write("(")   240             first = 1   241             for base in bases:   242                 if not first:   243                     self.stream.write(",\n")   244                 self._name_start(base.name)   245                 self._popup_start()   246                 self._types(base)   247                 self._scopes(base)   248                 self._popup_end()   249                 self._name_end()   250                 first = 0   251             self.stream.write(")")   252         self.stream.write(":\n")   253         self._comment(self._text(structure.full_name()))   254         self.stream.write("</div>\n")   255    256         self.stream.write("<div class='body'>\n")   257         self._doc(node)   258         self.dispatch(node.code)   259         self.stream.write("</div>\n")   260         self.stream.write("</div>\n")   261    262     def visitContinue(self, node):   263         self.stream.write("<div class='continue'>\n")   264         self._keyword("continue")   265         self.stream.write("</div>\n")   266    267     def visitFor(self, node):   268         self.stream.write("<div class='if'>\n")   269         self.stream.write("<div>\n")   270         self._keyword("for")   271         self.dispatch(node.assign)   272         self._keyword("in")   273         self.dispatch(node.list)   274         self.stream.write(":\n")   275         self.stream.write("</div>\n")   276         self.stream.write("<div class='body'>\n")   277         self.dispatch(node.body)   278         self.stream.write("</div>\n")   279         if node.else_ is not None:   280             self.stream.write("<div>\n")   281             self._keyword("else")   282             self.stream.write(":\n")   283             self.stream.write("</div>\n")   284             self.stream.write("<div class='body'>\n")   285             self.dispatch(node.else_)   286             self.stream.write("</div>\n")   287         self.stream.write("</div>\n")   288    289     def visitFunction(self, node):   290         definition = node._node   291         subprogram = definition.expr.ref   292         self.stream.write("<div class='function' id='%s'>\n" % self._url(subprogram.full_name()))   293         self.stream.write("<div>\n")   294         self._keyword("def")   295         self._name_start(subprogram.name)   296         self._popup_start()   297         self._scopes(definition)   298         self._popup_end()   299         self._name_end()   300         self.stream.write("(")   301         self._parameters(subprogram)   302         self.stream.write(")")   303         self.stream.write(":\n")   304         self._comment(self._text(subprogram.full_name()))   305         self.stream.write("</div>\n")   306    307         self.stream.write("<div class='body'>\n")   308         self._doc(node)   309         self.dispatch(node.code)   310         self.stream.write("</div>\n")   311         self.stream.write("</div>\n")   312    313     def visitIf(self, node):   314         self.stream.write("<div class='if'>\n")   315         first = 1   316         for compare, stmt in node.tests:   317             self.stream.write("<div>\n")   318             if first:   319                 self._keyword("if")   320             else:   321                 self._keyword("elif")   322             self.dispatch(compare)   323             self.stream.write(":\n")   324             self.stream.write("</div>\n")   325             self.stream.write("<div class='body'>\n")   326             self.dispatch(stmt)   327             self.stream.write("</div>\n")   328             first = 0   329         if node.else_ is not None:   330             self.stream.write("<div>\n")   331             self._keyword("else")   332             self.stream.write(":\n")   333             self.stream.write("</div>\n")   334             self.stream.write("<div class='body'>\n")   335             self.dispatch(node.else_)   336             self.stream.write("</div>\n")   337         self.stream.write("</div>\n")   338    339     def visitPass(self, node):   340         self.stream.write("<div class='pass'>\n")   341         self._keyword("pass")   342         self.stream.write("</div>\n")   343    344     def visitRaise(self, node):   345         self.stream.write("<div class='raise'>\n")   346         self._keyword("raise")   347         self.dispatch(node.expr1)   348         if node.expr2 is not None:   349             self.stream.write(",\n")   350             self.dispatch(node.expr2)   351         if node.expr3 is not None:   352             self.stream.write(",\n")   353             self.dispatch(node.expr3)   354         self.stream.write("</div>\n")   355    356     def visitReturn(self, node):   357         self.stream.write("<div class='return'>\n")   358         self._keyword("return")   359         self.dispatch(node.value)   360         self.stream.write("</div>\n")   361    362     def visitStmt(self, node):   363         self.stream.write("<div class='stmt'>\n")   364         self.default(node)   365         self.stream.write("</div>\n")   366    367     def visitWhile(self, node):   368         self.stream.write("<div class='while'>\n")   369         self.stream.write("<div>\n")   370         self._keyword("while")   371         self.dispatch(node.test)   372         self.stream.write(":\n")   373         self.stream.write("</div>\n")   374         self.stream.write("<div class='body'>\n")   375         self.dispatch(node.body)   376         self.stream.write("</div>\n")   377         if node.else_ is not None:   378             self.stream.write("<div>\n")   379             self._keyword("else")   380             self.stream.write(":\n")   381             self.stream.write("</div>\n")   382             self.stream.write("<div class='body'>\n")   383             self.dispatch(node.else_)   384             self.stream.write("</div>\n")   385         self.stream.write("</div>\n")   386    387     # Expressions.   388    389     def visitAssAttr(self, node):   390         self.stream.write("<span class='assattr'>\n")   391         self.dispatch(node.expr)   392         self.stream.write("<span class='attr'>\n")   393         self.stream.write(".%s\n" % self._text(node.attrname))   394         if hasattr(node, "_node"):   395             self._popup_start()   396             self._types(node._node)   397             self._scopes(node._node)   398             self._popup_end()   399         else:   400             raise ValueError, node   401         self.stream.write("</span>\n")   402         self.stream.write("</span>\n")   403    404     def visitAssList(self, node):   405         self.stream.write("<span class='list'>\n")   406         self.stream.write("[")   407         self._sequence(node)   408         self.stream.write("]\n")   409         self.stream.write("</span>\n")   410    411     def visitAssName(self, node):   412         if hasattr(node, "_node"):   413             self._name_start(node._node.name)   414             self._popup_start()   415             self._types(node._node.expr)   416             self._scopes(node._node)   417             self._popup_end()   418             self._name_end()   419         else:   420             raise ValueError, node   421             self._name(node.name)   422    423     def visitAssTuple(self, node):   424         self.stream.write("<span class='tuple'>\n")   425         self.stream.write("(")   426         self._sequence(node)   427         self.stream.write(")\n")   428         self.stream.write("</span>\n")   429    430     def visitCallFunc(self, node):   431         self.stream.write("<span class='callfunc'>\n")   432         self.dispatch(node.node)   433         self.stream.write("(")   434         first = 1   435         for arg in node.args:   436             if not first:   437                 self.stream.write(",\n")   438             self.dispatch(arg)   439             first = 0   440         if node.star_args is not None:   441             if not first:   442                 self.stream.write(", *\n")   443             self.dispatch(node.star_args)   444             first = 0   445         if node.dstar_args is not None:   446             if not first:   447                 self.stream.write(", **\n")   448             self.dispatch(node.dstar_args)   449             first = 0   450         self.stream.write(")\n")   451         self.stream.write("</span>\n")   452    453     def visitCompare(self, node):   454         self.stream.write("<span class='compare'>\n")   455         self.dispatch(node.expr)   456         for (op_name, expr), _op in map(None, node.ops, node._ops):   457             self.stream.write("<span class='op'>\n")   458             self.stream.write(op_name)   459             self._popup_start()   460             self._op(op_name, _op)   461             self._popup_end()   462             self.stream.write("</span>\n")   463             self.dispatch(expr)   464         self.stream.write("</span>\n")   465    466     def visitConst(self, node):   467         self.stream.write(repr(node.value))   468    469     def visitGetattr(self, node):   470         self.stream.write("<span class='getattr'>\n")   471         self.dispatch(node.expr)   472         self.stream.write("<span class='attr'>\n")   473         self.stream.write(".%s\n" % self._text(node.attrname))   474         if hasattr(node, "_node"):   475             self._popup_start()   476             self._types(node._node)   477             self._scopes(node._node)   478             self._popup_end()   479         else:   480             raise ValueError, node   481         self.stream.write("</span>\n")   482         self.stream.write("</span>\n")   483    484     def visitKeyword(self, node):   485         self.stream.write("<span class='keyword'>\n")   486         self.stream.write(node.name)   487         self.stream.write("=")   488         self.dispatch(node.expr)   489         self.stream.write("</span>\n")   490    491     def visitLambda(self, node):   492         definition = node._node   493         subprogram = definition.expr.ref   494         self.stream.write("<span class='lambda'>\n")   495         self._keyword("lambda")   496         self._parameters(subprogram)   497         self.dispatch(node.code)   498         self.stream.write("</span>\n")   499    500     visitList = visitAssList   501    502     def visitName(self, node):   503         if hasattr(node, "_node"):   504             self._name_start(node._node.name)   505             self._popup_start()   506             self._types(node._node)   507             self._scopes(node._node)   508             self._popup_end()   509             self._name_end()   510         else:   511             raise ValueError, node   512             self._name(node.name)   513    514     def visitSlice(self, node):   515         self.stream.write("<span class='slice'>\n")   516         self.dispatch(node.expr)   517         self.stream.write("[")   518         if node.lower:   519             self.dispatch(node.lower)   520         self.stream.write(":")   521         if node.upper:   522             self.dispatch(node.upper)   523         # NOTE: Step?   524         self.stream.write("]")   525         self.stream.write("</span>\n")   526    527     def visitSubscript(self, node):   528         self.stream.write("<span class='subscript'>\n")   529         self.dispatch(node.expr)   530         self.stream.write("[")   531         first = 1   532         for sub in node.subs:   533             if not first:   534                 self.stream.write(", ")   535             self.dispatch(sub)   536             first = 0   537         self.stream.write("]")   538         self.stream.write("</span>\n")   539    540     visitTuple = visitAssTuple   541    542     # Output preparation methods.   543    544     def _text(self, text):   545         return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")   546    547     def _attr(self, attr):   548         return self._text(attr).replace("'", "&apos;").replace('"', "&quot;")   549    550     def _url(self, url):   551         return self._attr(url).replace("#", "%23").replace("-", "%2d")   552    553     def _comment(self, comment):   554         self.stream.write("<span class='comment'># %s</span>\n" % comment)   555    556     def _keyword(self, kw):   557         self.stream.write("<span class='keyword'>%s</span> " % kw)   558    559     def _doc(self, node):   560         if node.doc is not None:   561             self.stream.write("<pre class='doc'>\n")   562             self.stream.write('"""')   563             output = textwrap.dedent(node.doc.replace('"""', '\\"\\"\\"'))   564             self.stream.write(self._text(output))   565             self.stream.write('"""')   566             self.stream.write("</pre>\n")   567    568     def _sequence(self, node):   569         first = 1   570         for n in node.nodes:   571             if not first:   572                 self.stream.write(",\n")   573             self.dispatch(n)   574             first = 0   575    576     def _parameters(self, subprogram):   577         first = 1   578         for param, default in subprogram.params:   579             if not first:   580                 self.stream.write(",\n")   581             self._parameter(subprogram, param, default)   582             first = 0   583         if subprogram.star is not None:   584             if not first:   585                 self.stream.write(", *\n")   586             param, default = subprogram.star   587             self._parameter(subprogram, param, default)   588             first = 0   589         if subprogram.dstar is not None:   590             if not first:   591                 self.stream.write(", **\n")   592             param, default = subprogram.dstar   593             self._parameter(subprogram, param, default)   594             first = 0   595    596     def _parameter(self, subprogram, param, default):   597         self._name_start(param)   598         if hasattr(subprogram, "paramtypes"):   599             self._popup_start()   600             self._types_list(subprogram.paramtypes[param])   601             self._popup_end()   602         self._name_end()   603         if default is not None and default.original is not None:   604             self.stream.write("=\n")   605             self.dispatch(default.original)   606    607     def _name(self, name):   608         self.stream.write("<span class='name'>%s</span>\n" % name)   609    610     def _name_start(self, name):   611         self.stream.write("<span class='name'>%s\n" % name)   612    613     def _name_end(self):   614         self.stream.write("</span>\n")   615    616     def _popup_start(self):   617         self.stream.write("<span class='popup'>\n")   618    619     def _popup_end(self):   620         self.stream.write("</span>\n")   621    622     def _op(self, op_name, op):   623         if op is not None:   624             self._invocations(op)   625    626     def _invocations(self, node):   627         if hasattr(node, "invocations"):   628             self._invocations_list(node.invocations)   629    630     def _invocations_list(self, invocations):   631         self.stream.write("<div class='invocations'>\n")   632         for invocation in invocations:   633             fn = invocation.full_name()   634             self.stream.write("<div class='invocation'>")   635             self.stream.write(self._text(fn))    636             self.stream.write("</div>\n")   637         self.stream.write("</div>\n")   638    639     def _types(self, node):   640         if hasattr(node, "types"):   641             self._types_list(node.types)   642         elif hasattr(node, "writes"):   643             self._types_list(flatten(node.writes.values()))   644         elif hasattr(node, "accesses"):   645             self._types_list(flatten(node.accesses.values()))   646         else:   647             self.stream.write("<div class='types'>\n")   648             self.stream.write("unvisited\n")   649             self.stream.write("</div>\n")   650    651     def _types_list(self, types):   652         self.stream.write("<div class='types'>\n")   653         for type in types:   654             fn = type.type.full_name()   655             self.stream.write("<div class='type'>")   656             self.stream.write(self._text(fn))    657             self.stream.write("</div>\n")   658         self.stream.write("</div>\n")   659    660     def _scopes(self, node):   661         if not isinstance(node, LoadName):   662             if hasattr(node, "writes") or hasattr(node, "accesses"):   663                 self.stream.write("<div class='scopes'>\n")   664                 for ref in getattr(node, "writes", getattr(node, "accesses", {})).keys():   665                     fn = ref.full_name()   666                     self.stream.write("<div class='scope'>")   667                     self.stream.write(self._text(fn))    668                     self.stream.write("</div>\n")   669                 self.stream.write("</div>\n")   670    671 # Utility functions.   672    673 def flatten(lists):   674     result = []   675     for l in lists:   676         for attr in l:   677             if attr not in result:   678                 result.append(attr)   679     return result   680    681 # Convenience functions.   682    683 def view(module, stream=None):   684     viewer = Viewer(stream or sys.stdout)   685     viewer.process(module.original)   686    687 def browse(module, stream=None):   688     browser = Browser(stream or sys.stdout)   689     browser.process(module.original)   690    691 def makedoc(module, filename):   692     stream = open(filename, "wb")   693     try:   694         browser = Browser(stream)   695         browser.process(module.original)   696     finally:   697         stream.close()   698    699 # vim: tabstop=4 expandtab shiftwidth=4