Skip to content

Commit 336364c

Browse files
Merge pull request #1318 from youknowone/ast-module
ast module
2 parents 23075e5 + 78dce22 commit 336364c

File tree

11 files changed

+477
-59
lines changed

11 files changed

+477
-59
lines changed

Lib/ast.py

+331
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
"""
2+
ast
3+
~~~
4+
5+
The `ast` module helps Python applications to process trees of the Python
6+
abstract syntax grammar. The abstract syntax itself might change with
7+
each Python release; this module helps to find out programmatically what
8+
the current grammar looks like and allows modifications of it.
9+
10+
An abstract syntax tree can be generated by passing `ast.PyCF_ONLY_AST` as
11+
a flag to the `compile()` builtin function or by using the `parse()`
12+
function from this module. The result will be a tree of objects whose
13+
classes all inherit from `ast.AST`.
14+
15+
A modified abstract syntax tree can be compiled into a Python code object
16+
using the built-in `compile()` function.
17+
18+
Additionally various helper functions are provided that make working with
19+
the trees simpler. The main intention of the helper functions and this
20+
module in general is to provide an easy to use interface for libraries
21+
that work tightly with the python syntax (template engines for example).
22+
23+
24+
:copyright: Copyright 2008 by Armin Ronacher.
25+
:license: Python License.
26+
"""
27+
from _ast import *
28+
29+
30+
def parse(source, filename='<unknown>', mode='exec'):
31+
"""
32+
Parse the source into an AST node.
33+
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
34+
"""
35+
return compile(source, filename, mode, PyCF_ONLY_AST)
36+
37+
38+
def literal_eval(node_or_string):
39+
"""
40+
Safely evaluate an expression node or a string containing a Python
41+
expression. The string or node provided may only consist of the following
42+
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
43+
sets, booleans, and None.
44+
"""
45+
if isinstance(node_or_string, str):
46+
node_or_string = parse(node_or_string, mode='eval')
47+
if isinstance(node_or_string, Expression):
48+
node_or_string = node_or_string.body
49+
def _convert_num(node):
50+
if isinstance(node, Constant):
51+
if isinstance(node.value, (int, float, complex)):
52+
return node.value
53+
elif isinstance(node, Num):
54+
return node.n
55+
raise ValueError('malformed node or string: ' + repr(node))
56+
def _convert_signed_num(node):
57+
if isinstance(node, UnaryOp) and isinstance(node.op, (UAdd, USub)):
58+
operand = _convert_num(node.operand)
59+
if isinstance(node.op, UAdd):
60+
return + operand
61+
else:
62+
return - operand
63+
return _convert_num(node)
64+
def _convert(node):
65+
if isinstance(node, Constant):
66+
return node.value
67+
elif isinstance(node, (Str, Bytes)):
68+
return node.s
69+
elif isinstance(node, Num):
70+
return node.n
71+
elif isinstance(node, Tuple):
72+
return tuple(map(_convert, node.elts))
73+
elif isinstance(node, List):
74+
return list(map(_convert, node.elts))
75+
elif isinstance(node, Set):
76+
return set(map(_convert, node.elts))
77+
elif isinstance(node, Dict):
78+
return dict(zip(map(_convert, node.keys),
79+
map(_convert, node.values)))
80+
elif isinstance(node, NameConstant):
81+
return node.value
82+
elif isinstance(node, BinOp) and isinstance(node.op, (Add, Sub)):
83+
left = _convert_signed_num(node.left)
84+
right = _convert_num(node.right)
85+
if isinstance(left, (int, float)) and isinstance(right, complex):
86+
if isinstance(node.op, Add):
87+
return left + right
88+
else:
89+
return left - right
90+
return _convert_signed_num(node)
91+
return _convert(node_or_string)
92+
93+
94+
def dump(node, annotate_fields=True, include_attributes=False):
95+
"""
96+
Return a formatted dump of the tree in *node*. This is mainly useful for
97+
debugging purposes. The returned string will show the names and the values
98+
for fields. This makes the code impossible to evaluate, so if evaluation is
99+
wanted *annotate_fields* must be set to False. Attributes such as line
100+
numbers and column offsets are not dumped by default. If this is wanted,
101+
*include_attributes* can be set to True.
102+
"""
103+
def _format(node):
104+
if isinstance(node, AST):
105+
fields = [(a, _format(b)) for a, b in iter_fields(node)]
106+
rv = '%s(%s' % (node.__class__.__name__, ', '.join(
107+
('%s=%s' % field for field in fields)
108+
if annotate_fields else
109+
(b for a, b in fields)
110+
))
111+
if include_attributes and node._attributes:
112+
rv += fields and ', ' or ' '
113+
rv += ', '.join('%s=%s' % (a, _format(getattr(node, a)))
114+
for a in node._attributes)
115+
return rv + ')'
116+
elif isinstance(node, list):
117+
return '[%s]' % ', '.join(_format(x) for x in node)
118+
return repr(node)
119+
if not isinstance(node, AST):
120+
raise TypeError('expected AST, got %r' % node.__class__.__name__)
121+
return _format(node)
122+
123+
124+
def copy_location(new_node, old_node):
125+
"""
126+
Copy source location (`lineno` and `col_offset` attributes) from
127+
*old_node* to *new_node* if possible, and return *new_node*.
128+
"""
129+
for attr in 'lineno', 'col_offset':
130+
if attr in old_node._attributes and attr in new_node._attributes \
131+
and hasattr(old_node, attr):
132+
setattr(new_node, attr, getattr(old_node, attr))
133+
return new_node
134+
135+
136+
def fix_missing_locations(node):
137+
"""
138+
When you compile a node tree with compile(), the compiler expects lineno and
139+
col_offset attributes for every node that supports them. This is rather
140+
tedious to fill in for generated nodes, so this helper adds these attributes
141+
recursively where not already set, by setting them to the values of the
142+
parent node. It works recursively starting at *node*.
143+
"""
144+
def _fix(node, lineno, col_offset):
145+
if 'lineno' in node._attributes:
146+
if not hasattr(node, 'lineno'):
147+
node.lineno = lineno
148+
else:
149+
lineno = node.lineno
150+
if 'col_offset' in node._attributes:
151+
if not hasattr(node, 'col_offset'):
152+
node.col_offset = col_offset
153+
else:
154+
col_offset = node.col_offset
155+
for child in iter_child_nodes(node):
156+
_fix(child, lineno, col_offset)
157+
_fix(node, 1, 0)
158+
return node
159+
160+
161+
def increment_lineno(node, n=1):
162+
"""
163+
Increment the line number of each node in the tree starting at *node* by *n*.
164+
This is useful to "move code" to a different location in a file.
165+
"""
166+
for child in walk(node):
167+
if 'lineno' in child._attributes:
168+
child.lineno = getattr(child, 'lineno', 0) + n
169+
return node
170+
171+
172+
def iter_fields(node):
173+
"""
174+
Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
175+
that is present on *node*.
176+
"""
177+
for field in node._fields:
178+
try:
179+
yield field, getattr(node, field)
180+
except AttributeError:
181+
pass
182+
183+
184+
def iter_child_nodes(node):
185+
"""
186+
Yield all direct child nodes of *node*, that is, all fields that are nodes
187+
and all items of fields that are lists of nodes.
188+
"""
189+
for name, field in iter_fields(node):
190+
if isinstance(field, AST):
191+
yield field
192+
elif isinstance(field, list):
193+
for item in field:
194+
if isinstance(item, AST):
195+
yield item
196+
197+
198+
def get_docstring(node, clean=True):
199+
"""
200+
Return the docstring for the given node or None if no docstring can
201+
be found. If the node provided does not have docstrings a TypeError
202+
will be raised.
203+
204+
If *clean* is `True`, all tabs are expanded to spaces and any whitespace
205+
that can be uniformly removed from the second line onwards is removed.
206+
"""
207+
if not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)):
208+
raise TypeError("%r can't have docstrings" % node.__class__.__name__)
209+
if not(node.body and isinstance(node.body[0], Expr)):
210+
return None
211+
node = node.body[0].value
212+
if isinstance(node, Str):
213+
text = node.s
214+
elif isinstance(node, Constant) and isinstance(node.value, str):
215+
text = node.value
216+
else:
217+
return None
218+
if clean:
219+
import inspect
220+
text = inspect.cleandoc(text)
221+
return text
222+
223+
224+
def walk(node):
225+
"""
226+
Recursively yield all descendant nodes in the tree starting at *node*
227+
(including *node* itself), in no specified order. This is useful if you
228+
only want to modify nodes in place and don't care about the context.
229+
"""
230+
from collections import deque
231+
todo = deque([node])
232+
while todo:
233+
node = todo.popleft()
234+
todo.extend(iter_child_nodes(node))
235+
yield node
236+
237+
238+
class NodeVisitor(object):
239+
"""
240+
A node visitor base class that walks the abstract syntax tree and calls a
241+
visitor function for every node found. This function may return a value
242+
which is forwarded by the `visit` method.
243+
244+
This class is meant to be subclassed, with the subclass adding visitor
245+
methods.
246+
247+
Per default the visitor functions for the nodes are ``'visit_'`` +
248+
class name of the node. So a `TryFinally` node visit function would
249+
be `visit_TryFinally`. This behavior can be changed by overriding
250+
the `visit` method. If no visitor function exists for a node
251+
(return value `None`) the `generic_visit` visitor is used instead.
252+
253+
Don't use the `NodeVisitor` if you want to apply changes to nodes during
254+
traversing. For this a special visitor exists (`NodeTransformer`) that
255+
allows modifications.
256+
"""
257+
258+
def visit(self, node):
259+
"""Visit a node."""
260+
method = 'visit_' + node.__class__.__name__
261+
visitor = getattr(self, method, self.generic_visit)
262+
return visitor(node)
263+
264+
def generic_visit(self, node):
265+
"""Called if no explicit visitor function exists for a node."""
266+
for field, value in iter_fields(node):
267+
if isinstance(value, list):
268+
for item in value:
269+
if isinstance(item, AST):
270+
self.visit(item)
271+
elif isinstance(value, AST):
272+
self.visit(value)
273+
274+
275+
class NodeTransformer(NodeVisitor):
276+
"""
277+
A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
278+
allows modification of nodes.
279+
280+
The `NodeTransformer` will walk the AST and use the return value of the
281+
visitor methods to replace or remove the old node. If the return value of
282+
the visitor method is ``None``, the node will be removed from its location,
283+
otherwise it is replaced with the return value. The return value may be the
284+
original node in which case no replacement takes place.
285+
286+
Here is an example transformer that rewrites all occurrences of name lookups
287+
(``foo``) to ``data['foo']``::
288+
289+
class RewriteName(NodeTransformer):
290+
291+
def visit_Name(self, node):
292+
return copy_location(Subscript(
293+
value=Name(id='data', ctx=Load()),
294+
slice=Index(value=Str(s=node.id)),
295+
ctx=node.ctx
296+
), node)
297+
298+
Keep in mind that if the node you're operating on has child nodes you must
299+
either transform the child nodes yourself or call the :meth:`generic_visit`
300+
method for the node first.
301+
302+
For nodes that were part of a collection of statements (that applies to all
303+
statement nodes), the visitor may also return a list of nodes rather than
304+
just a single node.
305+
306+
Usually you use the transformer like this::
307+
308+
node = YourTransformer().visit(node)
309+
"""
310+
311+
def generic_visit(self, node):
312+
for field, old_value in iter_fields(node):
313+
if isinstance(old_value, list):
314+
new_values = []
315+
for value in old_value:
316+
if isinstance(value, AST):
317+
value = self.visit(value)
318+
if value is None:
319+
continue
320+
elif not isinstance(value, AST):
321+
new_values.extend(value)
322+
continue
323+
new_values.append(value)
324+
old_value[:] = new_values
325+
elif isinstance(old_value, AST):
326+
new_node = self.visit(old_value)
327+
if new_node is None:
328+
delattr(node, field)
329+
else:
330+
setattr(node, field, new_node)
331+
return node

compiler/src/compile.rs

+1-30
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! https://github.com/micropython/micropython/blob/master/py/compile.c
77
88
use crate::error::{CompileError, CompileErrorType};
9+
pub use crate::mode::Mode;
910
use crate::output_stream::{CodeObjectStream, OutputStream};
1011
use crate::peephole::PeepholeOptimizer;
1112
use crate::symboltable::{
@@ -105,36 +106,6 @@ pub fn compile_program_single(
105106
})
106107
}
107108

108-
#[derive(Clone, Copy)]
109-
pub enum Mode {
110-
Exec,
111-
Eval,
112-
Single,
113-
}
114-
115-
impl std::str::FromStr for Mode {
116-
type Err = ModeParseError;
117-
fn from_str(s: &str) -> Result<Self, ModeParseError> {
118-
match s {
119-
"exec" => Ok(Mode::Exec),
120-
"eval" => Ok(Mode::Eval),
121-
"single" => Ok(Mode::Single),
122-
_ => Err(ModeParseError { _priv: () }),
123-
}
124-
}
125-
}
126-
127-
#[derive(Debug)]
128-
pub struct ModeParseError {
129-
_priv: (),
130-
}
131-
132-
impl std::fmt::Display for ModeParseError {
133-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
134-
write!(f, r#"mode should be "exec", "eval", or "single""#)
135-
}
136-
}
137-
138109
impl<O> Default for Compiler<O>
139110
where
140111
O: OutputStream,

compiler/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern crate log;
88

99
pub mod compile;
1010
pub mod error;
11+
pub mod mode;
1112
pub(crate) mod output_stream;
1213
pub mod peephole;
1314
pub mod symboltable;

0 commit comments

Comments
 (0)