# HG changeset patch # User paulb@jeremy # Date 1159829403 -7200 # Node ID eb536453519ed7c31fb9de401044da94a0e5bfd0 # Parent 34b00cce5ea25043ae46e6f8e6a9abaa8f8c4b98 Introduced lots of convenience functions. Changed the simplified nodes to refer to their simplifier at the root level. Tidied up the visual representation of the simplified prettyprinted output. Changed the accesses annotation on StoreAttr to become the writes annotation. Made InvokeFunction and InvokeBlock subclasses of Invoke. Added a viewer module. diff -r 34b00cce5ea2 -r eb536453519e annotate.py --- a/annotate.py Sat Sep 30 18:54:06 2006 +0200 +++ b/annotate.py Tue Oct 03 00:50:03 2006 +0200 @@ -24,18 +24,22 @@ -------- -To annotate a module, first obtain an Annotator: +To use this module, the easiest approach is to use the annotate function: + +annotate(module, builtins) + +The more complicated approach involves obtaining an Annotator: annotator = Annotator() -Then, process the Simplifier object which holds the details of the module: +Then, processing an existing module with it: -annotator.process(simplifier) +annotator.process(module) If a module containing built-in classes and functions has already been annotated, such a module should be passed in as an additional argument: -annotator.process(simplifier, builtins_simplifier) +annotator.process(module, builtins) """ from simplified import * @@ -95,11 +99,11 @@ self.visitor = self - def process(self, visitor, builtins_visitor=None): + def process(self, module, builtins=None): """ - Process the resources of the given 'visitor', using the optional - 'builtins_visitor' to access built-in classes and functions. + Process the given 'module', using the optional 'builtins' to access + built-in classes and functions. """ self.subprograms = [] @@ -108,19 +112,19 @@ # Give constants their own namespace. - for value, constant in visitor.constants.items(): + for value, constant in module.simplifier.constants.items(): constant.namespace = Namespace() # Process the module, supplying builtins if possible. self.global_namespace = Namespace() - if builtins_visitor is not None: - self.builtins_namespace = builtins_visitor.result.namespace + if builtins is not None: + self.builtins_namespace = builtins.namespace else: self.builtins_namespace = self.global_namespace - return self.process_node(visitor.result) + return self.process_node(module) def process_node(self, node, locals=None): @@ -265,14 +269,14 @@ storeattr.expr = self.dispatch(storeattr.expr) expr = self.namespace.types storeattr.lvalue = self.dispatch(storeattr.lvalue) - accesses = {} + writes = {} for attr in self.namespace.types: if attr is None: print "Empty attribute storage attempt" continue attr.type.namespace.store(storeattr.name, expr) - accesses[attr.type] = attr.type.namespace.load(storeattr.name) - storeattr.accesses = accesses + writes[attr.type] = attr.type.namespace.load(storeattr.name) + storeattr.writes = writes return storeattr def visitConditional(self, conditional): @@ -410,7 +414,7 @@ # Provide the correct namespace for the invocation. - if getattr(invoke, "same_frame", 0): + if isinstance(invoke, InvokeBlock): namespace = Namespace() namespace.merge_namespace(self.namespace) else: @@ -427,7 +431,7 @@ self.namespace.set_types(self.last_returns) self.annotate(invoke) - if getattr(invoke, "same_frame", 0): + if isinstance(invoke, InvokeBlock): for locals in self.returned_locals: self.namespace.merge_namespace(locals) @@ -700,4 +704,24 @@ results.append((attribute, accessor)) return results +# Convenience functions. + +def annotate(module, builtins=None): + + """ + Annotate the given 'module', also employing the optional 'builtins' module, + if specified. + """ + + annotator = Annotator() + if builtins is not None: + annotator.process(module, builtins) + else: + annotator.process(module) + +def annotate_all(modules, builtins): + annotate(builtins) + for module in modules: + annotate(module, builtins) + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 34b00cce5ea2 -r eb536453519e fixnames.py --- a/fixnames.py Sat Sep 30 18:54:06 2006 +0200 +++ b/fixnames.py Tue Oct 03 00:50:03 2006 +0200 @@ -23,19 +23,22 @@ -------- -To use this module, first instantiate a Fixer object: +To use this module, the easiest approach is to use the fix function: + +fix(module) + +The more complicated approach involves instantiating a Fixer object: fixer = Fixer() -Then, apply the fixer to an existing Simplifier object: +Then, applying the fixer to an existing module: -simplifier = ... -fixer.process(simplifier) +fixer.process(module) -If an existing simplifier has been used to process a module containing built-in -classes and functions, apply the fixer as follows: +If a module containing built-in classes and functions exists, apply the fixer as +follows: -fixer.process(simplifier, builtins_simplifier) +fixer.process(module, builtins) """ from simplified import * @@ -87,11 +90,11 @@ self.visitor = self - def process(self, visitor, builtins_visitor=None): + def process(self, module, builtins=None): """ - Process the resources of the given 'visitor' optionally using a - 'builtins_visitor' to reference built-in objects. + Process the given 'module' optionally using some 'builtins' to reference + built-in objects. """ # The fixer maintains a list of transformed subprograms (added for each @@ -108,14 +111,10 @@ # defined at that level. self.global_namespace = None - self.module = visitor.result + self.module = module + self.builtins = builtins - if builtins_visitor is not None: - self.builtins_module = builtins_visitor.result - else: - self.builtins_module = None - - self.process_node(visitor.result) + self.process_node(self.module) # Then, process all functions and methods, providing a global namespace. # By setting a global namespace, we influence the resolution of names: @@ -125,7 +124,7 @@ self.global_namespace = self.namespace - for subprogram in visitor.subprograms: + for subprogram in self.module.simplifier.subprograms: # Internal subprograms are skipped here and processed specially via # Invoke nodes. @@ -135,8 +134,8 @@ # Ultimately, we redefine the list of subprograms on the visitor. - visitor.subprograms = self.subprograms - return visitor + self.module.simplifier.subprograms = self.subprograms + return self.module def process_node(self, node, namespace=None): @@ -263,7 +262,7 @@ # built-in. if scope == "global": - result = self.dispatch(LoadAttr(expr=LoadRef(ref=self.builtins_module), name=loadname.name, nstype="module")) + result = self.dispatch(LoadAttr(expr=LoadRef(ref=self.builtins), name=loadname.name, nstype="module")) # Otherwise, it is within the global namespace and must be a # global. @@ -275,7 +274,7 @@ # must be accessing a built-in. else: - result = self.dispatch(LoadAttr(expr=LoadRef(ref=self.builtins_module), name=loadname.name, nstype="module")) + result = self.dispatch(LoadAttr(expr=LoadRef(ref=self.builtins), name=loadname.name, nstype="module")) # For local accesses... @@ -327,20 +326,23 @@ else: return self.dispatch(StoreAttr(lvalue=LoadRef(ref=self.module), name=storename.name, expr=storename.expr, nstype="module")) - def visitInvoke(self, invoke): + def visitInvokeFunction(self, invoke): + + "Transform the 'invoke' node, performing processing on subprograms." + + return self.default(invoke) + + def visitInvokeBlock(self, invoke): "Transform the 'invoke' node, performing processing on subprograms." # The special case of internal subprogram invocation is addressed by # propagating namespace information to the subprogram and processing it. - if getattr(invoke, "same_frame", 0): - subprogram = self.process_node(invoke.expr.ref, self.namespace) - if subprogram is not None: - self.subprograms.append(subprogram) - return invoke - else: - return self.default(invoke) + subprogram = self.process_node(invoke.expr.ref, self.namespace) + if subprogram is not None: + self.subprograms.append(subprogram) + return invoke class ScopeMismatch(Exception): pass @@ -400,4 +402,19 @@ def __repr__(self): return repr(self.names) +# Convenience functions. + +def fix(module, builtins=None): + + """ + Fix the names in the given 'module', also employing the optional 'builtins' + module, if specified. + """ + + fixer = Fixer() + if builtins is not None: + fixer.process(module, builtins) + else: + fixer.process(module) + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 34b00cce5ea2 -r eb536453519e simplified.py --- a/simplified.py Sat Sep 30 18:54:06 2006 +0200 +++ b/simplified.py Tue Oct 03 00:50:03 2006 +0200 @@ -94,15 +94,15 @@ def __repr__(self): if hasattr(self, "name"): - return "%s '%s' (at %x)" % (self.__class__, self.name, id(self)) + return "%s '%s' (at %x)" % (self.__class__.__name__, self.name, id(self)) if hasattr(self, "index"): - return "%s (%s) (at %x)" % (self.__class__, self.index, id(self)) + return "%s (%s)" % (self.__class__.__name__, self.index) elif hasattr(self, "value"): - return "%s %s (at %x)" % (self.__class__, repr(self.value), id(self)) + return "%s %s" % (self.__class__.__name__, repr(self.value)) elif hasattr(self, "ref"): - return "%s '%x' (at %x)" % (self.__class__, id(self.ref), id(self)) + return "%s '%x' (at %x)" % (self.__class__.__name__, id(self.ref), id(self)) else: - return "%s (at %x)" % (self.__class__, id(self)) + return "%s" % (self.__class__.__name__,) def _pprint(self, indent, continuation, s): if continuation: @@ -135,7 +135,7 @@ self.test.pprint(indent + 2, "? ") for attr in "code", "body", "else_", "handler", "finally_", "choices": if hasattr(self, attr) and getattr(self, attr): - self._pprint(indent, "", "{ (%s)" % attr) + self._pprint(indent, "", "%s {" % attr) for node in getattr(self, attr): node.pprint(indent + 2) self._pprint(indent, "", "}") @@ -148,7 +148,7 @@ for node in self.nodes: node.pprint(indent + 2, "- ") if hasattr(self, "lvalue"): - self.lvalue.pprint(indent + 2, "= ") + self.lvalue.pprint(indent + 2, "->") if hasattr(self, "nstype"): self._pprint(indent + 2, "", self.nstype) if hasattr(self, "args"): @@ -164,13 +164,22 @@ if hasattr(self, "accesses"): self._pprint(indent, "", "--------") for ref, attributes in self.accesses.items(): - self._pprint(indent + 2, "| ", "%s: %s" % (ref, attributes)) + self._pprint(indent + 2, "| ", "when %s: %s" % (ref, ", ".join([("%s via %s" % attr_acc) for attr_acc in attributes]))) + self._pprint(indent, "", "--------") + if hasattr(self, "writes"): + self._pprint(indent, "", "--------") + for ref, attribute in self.writes.items(): + self._pprint(indent + 2, "| ", "when %s: %s" % (ref, attribute)) self._pprint(indent, "", "--------") class Module(Node): "A Python module." class Subprogram(Node): "A subprogram: functions, methods and loops." class Pass(Node): "A placeholder node corresponding to pass." -class Invoke(Node): "A function, method or loop invocation." + +class Invoke(Node): "An invocation." +class InvokeFunction(Invoke): "A function or method invocation." +class InvokeBlock(Invoke): "A block or loop invocation." + class Return(Node): "Return an evaluated expression." class Assign(Node): "A grouping node for assignment-related operations." class Keyword(Node): "A grouping node for keyword arguments." @@ -203,9 +212,9 @@ def __repr__(self): if hasattr(self, "name"): - return "%s '%s' (at %x)" % (self.__class__, self.name, id(self)) + return "%s '%s' (at %x)" % (self.__class__.__name__, self.name, id(self)) else: - return "%s (at %x)" % (self.__class__, id(self)) + return "%s (at %x)" % (self.__class__.__name__, id(self)) class Class(Structure): diff -r 34b00cce5ea2 -r eb536453519e simplify.py --- a/simplify.py Sat Sep 30 18:54:06 2006 +0200 +++ b/simplify.py Tue Oct 03 00:50:03 2006 +0200 @@ -24,13 +24,17 @@ -------- -To use this module, first instantiate a Simplifier object: +To use this module, the easiest approach is to use the simplify function: + +simplify(filename) + +The more complicated approach involves first instantiating a Simplifier object: simplifier = Simplifier() -Then, apply the simplifier to an AST tree: +Then, applying the simplifier to an AST tree: -module = compiler.parseFile(...) +module = compiler.parseFile(filename) simplifier.process(module) """ @@ -64,7 +68,6 @@ """ Visitor.__init__(self) - self.result = None # The resulting tree. self.subprograms = [] # Subprograms outside the tree. self.structures = [] # Structures/classes. self.constants = {} # Constants. @@ -76,7 +79,9 @@ self.visitor = self def process(self, node): - return self.dispatch(node) + result = self.dispatch(node) + result.simplifier = self + return result def dispatch_or_none(self, node, *args): if node is not None: @@ -104,7 +109,7 @@ resulting program nodes. """ - self.result = Module(module) + result = Module(module) module_code = self.dispatch(module.node) # NOTE: Constant initialisation necessary for annotation but perhaps @@ -117,10 +122,10 @@ # NOTE: Hack to ensure correct initialisation of constants. if self.builtins: - self.result.code = module_code + init_code + result.code = module_code + init_code else: - self.result.code = init_code + module_code - return self.result + result.code = init_code + module_code + return result def visitGetattr(self, getattr): result = LoadAttr(getattr, @@ -182,9 +187,7 @@ return result def visitContinue(self, continue_): - result = Invoke(continue_, - same_frame=1, produces_result=0, - star=None, dstar=None, args=[], + result = InvokeBlock(continue_, expr=LoadRef(ref=self.current_subprograms[-1]) ) return result @@ -397,7 +400,7 @@ # Make an invocation of the subprogram. - result = Invoke(compare, same_frame=1, star=None, dstar=None, args=[]) + result = InvokeBlock(compare, produces_result=1) result.expr = LoadRef(ref=subprogram) return result @@ -457,7 +460,7 @@ # Make an invocation of the subprogram. - result = Invoke(and_, same_frame=1, star=None, dstar=None, args=[]) + result = InvokeBlock(and_, produces_result=1) result.expr = LoadRef(ref=subprogram) return result @@ -517,7 +520,7 @@ # Make an invocation of the subprogram. - result = Invoke(or_, same_frame=1, star=None, dstar=None, args=[]) + result = InvokeBlock(or_, produces_result=1) result.expr = LoadRef(ref=subprogram) return result @@ -908,7 +911,7 @@ return LoadRef(lambda_, ref=subprogram) def visitCallFunc(self, callfunc): - result = Invoke(callfunc, same_frame=0, star=None, dstar=None) + result = InvokeFunction(callfunc, star=None, dstar=None) result.args = self.dispatches(callfunc.args) if callfunc.star_args is not None: result.star = self.dispatch(callfunc.star_args) @@ -944,7 +947,7 @@ # Inside the conditional, add a recursive invocation to the subprogram # if the test condition was satisfied. - continuation = Invoke(same_frame=1, star=None, dstar=None, args=[]) + continuation = InvokeBlock() continuation.expr = LoadRef(ref=subprogram) # Return within the main section of the loop. @@ -965,7 +968,7 @@ # Make an invocation of the subprogram. - result = Invoke(while_, same_frame=1, produces_result=0, star=None, dstar=None, args=[]) + result = InvokeBlock(while_) result.expr = LoadRef(ref=subprogram) return result @@ -1018,7 +1021,7 @@ # Inside the conditional, add a recursive invocation to the subprogram # if the test condition was satisfied. - continuation = Invoke(same_frame=1, produces_result=0, star=None, dstar=None, args=[]) + continuation = InvokeBlock() continuation.expr = LoadRef(ref=subprogram) try_except.body = [assign] + self.dispatch(for_.body) + [continuation] subprogram.code = [try_except, Return()] @@ -1034,7 +1037,7 @@ result = Assign(for_) result.code = [ StoreTemp(expr=Invoke(expr=LoadAttr(name="__iter__", expr=self.dispatch(for_.list)), args=[], star=None, dstar=None)), - Invoke(expr=LoadRef(ref=subprogram), same_frame=1, produces_result=0, star=None, dstar=None, args=[]), + InvokeBlock(expr=LoadRef(ref=subprogram)), ReleaseTemp() ] return result @@ -1049,4 +1052,20 @@ result.finally_ = self.dispatch(tryfinally.final) return result +# Convenience functions. + +def simplify(filename, builtins=0): + + """ + Simplify the module stored in the file with the given 'filename'. If the + optional 'builtins' parameter is set to a true value (the default being a + false value), then the module is considered as the builtins module. + """ + + simplifier = Simplifier(builtins) + module = compiler.parseFile(filename) + simplified = simplifier.process(module) + compiler.misc.set_filename(filename, module) + return simplified + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 34b00cce5ea2 -r eb536453519e test.py --- a/test.py Sat Sep 30 18:54:06 2006 +0200 +++ b/test.py Tue Oct 03 00:50:03 2006 +0200 @@ -1,17 +1,14 @@ -import simplify, compiler, sys, os +#!/usr/bin/env python + +import sys, os +import simplify import fixnames +import viewer import annotate -a = annotate.Annotator() -b = compiler.parseFile(os.path.join("lib", "builtins.py")) -m = compiler.parseFile(sys.argv[1]) -vb = builtins_simplifier = simplify.Simplifier(1) -builtins_simplifier.process(b) -v = module_simplifier = simplify.Simplifier() -module_simplifier.process(m) -builtins_fixer = fixnames.Fixer() -builtins_fixer.process(builtins_simplifier) -module_fixer = fixnames.Fixer() -module_fixer.process(module_simplifier, builtins_simplifier) -rb = builtins_simplifier.result -r = module_simplifier.result +builtins = simplify.simplify(os.path.join("lib", "builtins.py"), 1) +module = simplify.simplify(sys.argv[1]) +fixnames.fix(builtins) +fixnames.fix(module, builtins) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 34b00cce5ea2 -r eb536453519e viewer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/viewer.py Tue Oct 03 00:50:03 2006 +0200 @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +""" +View annotated sources. + +Copyright (C) 2006 Paul Boddie + +This software is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of +the License, or (at your option) any later version. + +This software is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this library; see the file LICENCE.txt +If not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from compiler.visitor import ASTVisitor +import sys + +class Viewer(ASTVisitor): + + """ + A viewing visitor for AST nodes. + """ + + def __init__(self, stream): + ASTVisitor.__init__(self) + self.cached_files = {} + self.printed_lines = {} + self.visitor = self + self.stream = stream + + def process(self, module): + self.dispatch(module) + + def dispatch(self, node): + self.print_line(getattr(node, "filename", None), getattr(node, "lineno", None)) + ASTVisitor.dispatch(self, node) + + def print_line(self, filename, lineno): + last_printed = self.printed_lines.get(filename, 0) + if lineno > last_printed: + self.stream.write(self.get_line(filename, lineno)) + self.printed_lines[filename] = lineno + + def get_line(self, filename, lineno): + if filename is None or lineno is None: + return "" + + if self.cached_files.has_key(filename): + lines = self.cached_files[filename] + else: + f = open(filename) + try: + self.cached_files[filename] = lines = f.readlines() + finally: + f.close() + + try: + return lines[lineno - 1] + except IndexError: + return "" + +# Convenience functions. + +def view(module, stream=None): + viewer = Viewer(stream or sys.stdout) + viewer.process(module.original) + +# vim: tabstop=4 expandtab shiftwidth=4