diff options
author | Georg Brandl <georg@python.org> | 2008-06-10 16:37:50 (GMT) |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2008-06-10 16:37:50 (GMT) |
commit | 0c77a82c3d3342affd157f3693812c789a8d6ae1 (patch) | |
tree | 77eac854fc9af2188fbfb5c91b7147d6a6aa50ab /Lib/ast.py | |
parent | 0312494665ba9a6492956183c181e1b87e10820d (diff) | |
download | cpython-0c77a82c3d3342affd157f3693812c789a8d6ae1.zip cpython-0c77a82c3d3342affd157f3693812c789a8d6ae1.tar.gz cpython-0c77a82c3d3342affd157f3693812c789a8d6ae1.tar.bz2 |
Merged revisions 63829-63831,63858,63865,63879,63882,63948,63970-63972,63976,63989,64014-64015,64021-64022,64063-64065,64067 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk
........
r63829 | mark.summerfield | 2008-05-31 15:05:34 +0200 (Sat, 31 May 2008) | 4 lines
Added a note to [] that special forms & special chars lose their meaning
and backrefs can't be used inside []
........
r63830 | georg.brandl | 2008-05-31 16:40:09 +0200 (Sat, 31 May 2008) | 2 lines
#3010: clarification about stdin/use_rawinput.
........
r63831 | georg.brandl | 2008-05-31 16:45:55 +0200 (Sat, 31 May 2008) | 2 lines
#3005: add explaining sentence to easydialogs docs.
........
r63858 | georg.brandl | 2008-06-01 18:41:31 +0200 (Sun, 01 Jun 2008) | 2 lines
Add plain text make target.
........
r63865 | georg.brandl | 2008-06-01 21:24:36 +0200 (Sun, 01 Jun 2008) | 2 lines
Spaces vs. tabs.
........
r63879 | gregory.p.smith | 2008-06-02 00:57:47 +0200 (Mon, 02 Jun 2008) | 3 lines
Make the _H #define's match the header file names. Fix comments to
mention the correct type names.
........
r63882 | gregory.p.smith | 2008-06-02 01:48:47 +0200 (Mon, 02 Jun 2008) | 3 lines
Adds a Thread.getIdent() method to provide the _get_ident() value for
any given threading.Thread object. feature request issue 2871.
........
r63948 | alexandre.vassalotti | 2008-06-04 22:41:44 +0200 (Wed, 04 Jun 2008) | 2 lines
Fixed complex.__getnewargs__() to not emit another complex object.
........
r63970 | andrew.kuchling | 2008-06-06 01:33:54 +0200 (Fri, 06 Jun 2008) | 1 line
Document 'utc' parameter
........
r63971 | andrew.kuchling | 2008-06-06 01:35:31 +0200 (Fri, 06 Jun 2008) | 1 line
Add various items
........
r63972 | andrew.kuchling | 2008-06-06 01:35:48 +0200 (Fri, 06 Jun 2008) | 1 line
Grammar fix
........
r63976 | georg.brandl | 2008-06-06 09:34:50 +0200 (Fri, 06 Jun 2008) | 2 lines
Markup fix.
........
r63989 | thomas.heller | 2008-06-06 20:42:11 +0200 (Fri, 06 Jun 2008) | 2 lines
Add a reminder for the maintainer of whatsnew.
........
r64014 | georg.brandl | 2008-06-07 17:59:10 +0200 (Sat, 07 Jun 2008) | 3 lines
Factor out docstring dedenting from inspect.getdoc() into inspect.cleandoc()
to ease standalone use of the algorithm.
........
r64015 | georg.brandl | 2008-06-07 18:04:01 +0200 (Sat, 07 Jun 2008) | 2 lines
Revert unwanted changes.
........
r64021 | georg.brandl | 2008-06-07 20:16:12 +0200 (Sat, 07 Jun 2008) | 2 lines
X-ref to numbers module.
........
r64022 | georg.brandl | 2008-06-07 20:17:37 +0200 (Sat, 07 Jun 2008) | 3 lines
Document the "st" API, to avoid confusion with the "new" AST.
Add a note about using the new AST module.
........
r64063 | martin.v.loewis | 2008-06-10 07:03:35 +0200 (Tue, 10 Jun 2008) | 2 lines
Add Gregor Lingl.
........
r64064 | georg.brandl | 2008-06-10 09:45:28 +0200 (Tue, 10 Jun 2008) | 2 lines
Add the "ast" module, containing helpers to ease use of the "_ast" classes.
........
r64065 | raymond.hettinger | 2008-06-10 09:57:15 +0200 (Tue, 10 Jun 2008) | 1 line
Add Arnaud for his efforts on multi-arg set operations.
........
r64067 | georg.brandl | 2008-06-10 14:46:39 +0200 (Tue, 10 Jun 2008) | 2 lines
#2536: fix itertools.permutations and itertools.combinations docstrings.
........
Diffstat (limited to 'Lib/ast.py')
-rw-r--r-- | Lib/ast.py | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/Lib/ast.py b/Lib/ast.py new file mode 100644 index 0000000..2aac2dd --- /dev/null +++ b/Lib/ast.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +""" + ast + ~~~ + + The `ast` module helps Python applications to process trees of the Python + abstract syntax grammar. The abstract syntax itself might change with + each Python release; this module helps to find out programmatically what + the current grammar looks like and allows modifications of it. + + An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as + a flag to the `compile()` builtin function or by using the `parse()` + function from this module. The result will be a tree of objects whose + classes all inherit from `ast.AST`. + + A modified abstract syntax tree can be compiled into a Python code object + using the built-in `compile()` function. + + Additionally various helper functions are provided that make working with + the trees simpler. The main intention of the helper functions and this + module in general is to provide an easy to use interface for libraries + that work tightly with the python syntax (template engines for example). + + + :copyright: Copyright 2008 by Armin Ronacher. + :license: Python License. +""" +from _ast import * + + +def parse(expr, filename='<unknown>', mode='exec'): + """ + Parse an expression into an AST node. + Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST). + """ + return compile(expr, filename, mode, PyCF_ONLY_AST) + + +def literal_eval(node_or_string): + """ + Safely evaluate an expression node or a string containing a Python + expression. The string or node provided may only consist of the following + Python literal structures: strings, numbers, tuples, lists, dicts, booleans, + and None. + """ + _safe_names = {'None': None, 'True': True, 'False': False} + if isinstance(node_or_string, str): + node_or_string = parse(node_or_string, mode='eval') + if isinstance(node_or_string, Expression): + node_or_string = node_or_string.body + def _convert(node): + if isinstance(node, Str): + return node.s + elif isinstance(node, Num): + return node.n + elif isinstance(node, Tuple): + return tuple(map(_convert, node.elts)) + elif isinstance(node, List): + return list(map(_convert, node.elts)) + elif isinstance(node, Dict): + return dict((_convert(k), _convert(v)) for k, v + in zip(node.keys, node.values)) + elif isinstance(node, Name): + if node.id in _safe_names: + return _safe_names[node.id] + raise ValueError('malformed string') + return _convert(node_or_string) + + +def dump(node, annotate_fields=True, include_attributes=False): + """ + Return a formatted dump of the tree in *node*. This is mainly useful for + debugging purposes. The returned string will show the names and the values + for fields. This makes the code impossible to evaluate, so if evaluation is + wanted *annotate_fields* must be set to False. Attributes such as line + numbers and column offsets are dumped by default. If this is wanted, + *include_attributes* can be set to True. + """ + def _format(node): + if isinstance(node, AST): + fields = [(a, _format(b)) for a, b in iter_fields(node)] + rv = '%s(%s' % (node.__class__.__name__, ', '.join( + ('%s=%s' % field for field in fields) + if annotate_fields else + (b for a, b in fields) + )) + if include_attributes and node._attributes: + rv += fields and ', ' or ' ' + rv += ', '.join('%s=%s' % (a, _format(getattr(node, a))) + for a in node._attributes) + return rv + ')' + elif isinstance(node, list): + return '[%s]' % ', '.join(_format(x) for x in node) + return repr(node) + if not isinstance(node, AST): + raise TypeError('expected AST, got %r' % node.__class__.__name__) + return _format(node) + + +def copy_location(new_node, old_node): + """ + Copy source location (`lineno` and `col_offset` attributes) from + *old_node* to *new_node* if possible, and return *new_node*. + """ + for attr in 'lineno', 'col_offset': + if attr in old_node._attributes and attr in new_node._attributes \ + and hasattr(old_node, attr): + setattr(new_node, attr, getattr(old_node, attr)) + return new_node + + +def fix_missing_locations(node): + """ + When you compile a node tree with compile(), the compiler expects lineno and + col_offset attributes for every node that supports them. This is rather + tedious to fill in for generated nodes, so this helper adds these attributes + recursively where not already set, by setting them to the values of the + parent node. It works recursively starting at *node*. + """ + def _fix(node, lineno, col_offset): + if 'lineno' in node._attributes: + if not hasattr(node, 'lineno'): + node.lineno = lineno + else: + lineno = node.lineno + if 'col_offset' in node._attributes: + if not hasattr(node, 'col_offset'): + node.col_offset = col_offset + else: + col_offset = node.col_offset + for child in iter_child_nodes(node): + _fix(child, lineno, col_offset) + _fix(node, 1, 0) + return node + + +def increment_lineno(node, n=1): + """ + Increment the line number of each node in the tree starting at *node* by *n*. + This is useful to "move code" to a different location in a file. + """ + if 'lineno' in node._attributes: + node.lineno = getattr(node, 'lineno', 0) + n + for child in walk(node): + if 'lineno' in child._attributes: + child.lineno = getattr(child, 'lineno', 0) + n + return node + + +def iter_fields(node): + """ + Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields`` + that is present on *node*. + """ + for field in node._fields: + try: + yield field, getattr(node, field) + except AttributeError: + pass + + +def iter_child_nodes(node): + """ + Yield all direct child nodes of *node*, that is, all fields that are nodes + and all items of fields that are lists of nodes. + """ + for name, field in iter_fields(node): + if isinstance(field, AST): + yield field + elif isinstance(field, list): + for item in field: + if isinstance(item, AST): + yield item + + +def get_docstring(node, clean=True): + """ + Return the docstring for the given node or None if no docstring can + be found. If the node provided does not have docstrings a TypeError + will be raised. + """ + if not isinstance(node, (FunctionDef, ClassDef, Module)): + raise TypeError("%r can't have docstrings" % node.__class__.__name__) + if node.body and isinstance(node.body[0], Expr) and \ + isinstance(node.body[0].value, Str): + if clean: + import inspect + return inspect.cleandoc(node.body[0].value.s) + return node.body[0].value.s + + +def walk(node): + """ + Recursively yield all child nodes of *node*, in no specified order. This is + useful if you only want to modify nodes in place and don't care about the + context. + """ + from collections import deque + todo = deque([node]) + while todo: + node = todo.popleft() + todo.extend(iter_child_nodes(node)) + yield node + + +class NodeVisitor(object): + """ + A node visitor base class that walks the abstract syntax tree and calls a + visitor function for every node found. This function may return a value + which is forwarded by the `visit` method. + + This class is meant to be subclassed, with the subclass adding visitor + methods. + + Per default the visitor functions for the nodes are ``'visit_'`` + + class name of the node. So a `TryFinally` node visit function would + be `visit_TryFinally`. This behavior can be changed by overriding + the `visit` method. If no visitor function exists for a node + (return value `None`) the `generic_visit` visitor is used instead. + + Don't use the `NodeVisitor` if you want to apply changes to nodes during + traversing. For this a special visitor exists (`NodeTransformer`) that + allows modifications. + """ + + def visit(self, node): + """Visit a node.""" + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + return visitor(node) + + def generic_visit(self, node): + """Called if no explicit visitor function exists for a node.""" + for field, value in iter_fields(node): + if isinstance(value, list): + for item in value: + if isinstance(item, AST): + self.visit(item) + elif isinstance(value, AST): + self.visit(value) + + +class NodeTransformer(NodeVisitor): + """ + A :class:`NodeVisitor` subclass that walks the abstract syntax tree and + allows modification of nodes. + + The `NodeTransformer` will walk the AST and use the return value of the + visitor methods to replace or remove the old node. If the return value of + the visitor method is ``None``, the node will be removed from its location, + otherwise it is replaced with the return value. The return value may be the + original node in which case no replacement takes place. + + Here is an example transformer that rewrites all occurrences of name lookups + (``foo``) to ``data['foo']``:: + + class RewriteName(NodeTransformer): + + def visit_Name(self, node): + return copy_location(Subscript( + value=Name(id='data', ctx=Load()), + slice=Index(value=Str(s=node.id)), + ctx=node.ctx + ), node) + + Keep in mind that if the node you're operating on has child nodes you must + either transform the child nodes yourself or call the :meth:`generic_visit` + method for the node first. + + For nodes that were part of a collection of statements (that applies to all + statement nodes), the visitor may also return a list of nodes rather than + just a single node. + + Usually you use the transformer like this:: + + node = YourTransformer().visit(node) + """ + + def generic_visit(self, node): + for field, old_value in iter_fields(node): + old_value = getattr(node, field, None) + if isinstance(old_value, list): + new_values = [] + for value in old_value: + if isinstance(value, AST): + value = self.visit(value) + if value is None: + continue + elif not isinstance(value, AST): + new_values.extend(value) + continue + new_values.append(value) + old_value[:] = new_values + elif isinstance(old_value, AST): + new_node = self.visit(old_value) + if new_node is None: + delattr(node, field) + else: + setattr(node, field, new_node) + return node |