| # mako/pyparser.py |
| # Copyright (C) 2006-2012 the Mako authors and contributors <see AUTHORS file> |
| # |
| # This module is part of Mako and is released under |
| # the MIT License: http://www.opensource.org/licenses/mit-license.php |
| |
| """Handles parsing of Python code. |
| |
| Parsing to AST is done via _ast on Python > 2.5, otherwise the compiler |
| module is used. |
| """ |
| |
| from mako import exceptions, util, compat |
| from mako.compat import StringIO |
| import operator |
| |
| if compat.py3k: |
| # words that cannot be assigned to (notably |
| # smaller than the total keys in __builtins__) |
| reserved = set(['True', 'False', 'None', 'print']) |
| |
| # the "id" attribute on a function node |
| arg_id = operator.attrgetter('arg') |
| else: |
| # words that cannot be assigned to (notably |
| # smaller than the total keys in __builtins__) |
| reserved = set(['True', 'False', 'None']) |
| |
| # the "id" attribute on a function node |
| arg_id = operator.attrgetter('id') |
| |
| |
| try: |
| import _ast |
| util.restore__ast(_ast) |
| from mako import _ast_util |
| except ImportError: |
| _ast = None |
| from compiler import parse as compiler_parse |
| from compiler import visitor |
| |
| |
| def parse(code, mode='exec', **exception_kwargs): |
| """Parse an expression into AST""" |
| |
| |
| try: |
| if _ast: |
| return _ast_util.parse(code, '<unknown>', mode) |
| else: |
| if isinstance(code, compat.text_type): |
| code = code.encode('ascii', 'backslashreplace') |
| return compiler_parse(code, mode) |
| except Exception: |
| raise exceptions.SyntaxException( |
| "(%s) %s (%r)" % ( |
| compat.exception_as().__class__.__name__, |
| compat.exception_as(), |
| code[0:50] |
| ), **exception_kwargs) |
| |
| |
| if _ast: |
| class FindIdentifiers(_ast_util.NodeVisitor): |
| |
| def __init__(self, listener, **exception_kwargs): |
| self.in_function = False |
| self.in_assign_targets = False |
| self.local_ident_stack = set() |
| self.listener = listener |
| self.exception_kwargs = exception_kwargs |
| |
| def _add_declared(self, name): |
| if not self.in_function: |
| self.listener.declared_identifiers.add(name) |
| else: |
| self.local_ident_stack.add(name) |
| |
| def visit_ClassDef(self, node): |
| self._add_declared(node.name) |
| |
| def visit_Assign(self, node): |
| |
| # flip around the visiting of Assign so the expression gets |
| # evaluated first, in the case of a clause like "x=x+5" (x |
| # is undeclared) |
| |
| self.visit(node.value) |
| in_a = self.in_assign_targets |
| self.in_assign_targets = True |
| for n in node.targets: |
| self.visit(n) |
| self.in_assign_targets = in_a |
| |
| if compat.py3k: |
| |
| # ExceptHandler is in Python 2, but this block only works in |
| # Python 3 (and is required there) |
| |
| def visit_ExceptHandler(self, node): |
| if node.name is not None: |
| self._add_declared(node.name) |
| if node.type is not None: |
| self.listener.undeclared_identifiers.add(node.type.id) |
| for statement in node.body: |
| self.visit(statement) |
| |
| def visit_Lambda(self, node, *args): |
| self._visit_function(node, True) |
| |
| def visit_FunctionDef(self, node): |
| self._add_declared(node.name) |
| self._visit_function(node, False) |
| |
| def _expand_tuples(self, args): |
| for arg in args: |
| if isinstance(arg, _ast.Tuple): |
| for n in arg.elts: |
| yield n |
| else: |
| yield arg |
| |
| def _visit_function(self, node, islambda): |
| |
| # push function state onto stack. dont log any more |
| # identifiers as "declared" until outside of the function, |
| # but keep logging identifiers as "undeclared". track |
| # argument names in each function header so they arent |
| # counted as "undeclared" |
| |
| inf = self.in_function |
| self.in_function = True |
| |
| local_ident_stack = self.local_ident_stack |
| self.local_ident_stack = local_ident_stack.union([ |
| arg_id(arg) for arg in self._expand_tuples(node.args.args) |
| ]) |
| if islambda: |
| self.visit(node.body) |
| else: |
| for n in node.body: |
| self.visit(n) |
| self.in_function = inf |
| self.local_ident_stack = local_ident_stack |
| |
| def visit_For(self, node): |
| |
| # flip around visit |
| |
| self.visit(node.iter) |
| self.visit(node.target) |
| for statement in node.body: |
| self.visit(statement) |
| for statement in node.orelse: |
| self.visit(statement) |
| |
| def visit_Name(self, node): |
| if isinstance(node.ctx, _ast.Store): |
| # this is eqiuvalent to visit_AssName in |
| # compiler |
| self._add_declared(node.id) |
| elif node.id not in reserved and node.id \ |
| not in self.listener.declared_identifiers and node.id \ |
| not in self.local_ident_stack: |
| self.listener.undeclared_identifiers.add(node.id) |
| |
| def visit_Import(self, node): |
| for name in node.names: |
| if name.asname is not None: |
| self._add_declared(name.asname) |
| else: |
| self._add_declared(name.name.split('.')[0]) |
| |
| def visit_ImportFrom(self, node): |
| for name in node.names: |
| if name.asname is not None: |
| self._add_declared(name.asname) |
| else: |
| if name.name == '*': |
| raise exceptions.CompileException( |
| "'import *' is not supported, since all identifier " |
| "names must be explicitly declared. Please use the " |
| "form 'from <modulename> import <name1>, <name2>, " |
| "...' instead.", **self.exception_kwargs) |
| self._add_declared(name.name) |
| |
| |
| class FindTuple(_ast_util.NodeVisitor): |
| |
| def __init__(self, listener, code_factory, **exception_kwargs): |
| self.listener = listener |
| self.exception_kwargs = exception_kwargs |
| self.code_factory = code_factory |
| |
| def visit_Tuple(self, node): |
| for n in node.elts: |
| p = self.code_factory(n, **self.exception_kwargs) |
| self.listener.codeargs.append(p) |
| self.listener.args.append(ExpressionGenerator(n).value()) |
| self.listener.declared_identifiers = \ |
| self.listener.declared_identifiers.union( |
| p.declared_identifiers) |
| self.listener.undeclared_identifiers = \ |
| self.listener.undeclared_identifiers.union( |
| p.undeclared_identifiers) |
| |
| |
| class ParseFunc(_ast_util.NodeVisitor): |
| |
| def __init__(self, listener, **exception_kwargs): |
| self.listener = listener |
| self.exception_kwargs = exception_kwargs |
| |
| def visit_FunctionDef(self, node): |
| self.listener.funcname = node.name |
| argnames = [arg_id(arg) for arg in node.args.args] |
| if node.args.vararg: |
| argnames.append(node.args.vararg) |
| if node.args.kwarg: |
| argnames.append(node.args.kwarg) |
| self.listener.argnames = argnames |
| self.listener.defaults = node.args.defaults # ast |
| self.listener.varargs = node.args.vararg |
| self.listener.kwargs = node.args.kwarg |
| |
| |
| class ExpressionGenerator(object): |
| |
| def __init__(self, astnode): |
| self.generator = _ast_util.SourceGenerator(' ' * 4) |
| self.generator.visit(astnode) |
| |
| def value(self): |
| return ''.join(self.generator.result) |
| else: |
| class FindIdentifiers(object): |
| |
| def __init__(self, listener, **exception_kwargs): |
| self.in_function = False |
| self.local_ident_stack = set() |
| self.listener = listener |
| self.exception_kwargs = exception_kwargs |
| |
| def _add_declared(self, name): |
| if not self.in_function: |
| self.listener.declared_identifiers.add(name) |
| else: |
| self.local_ident_stack.add(name) |
| |
| def visitClass(self, node, *args): |
| self._add_declared(node.name) |
| |
| def visitAssName(self, node, *args): |
| self._add_declared(node.name) |
| |
| def visitAssign(self, node, *args): |
| |
| # flip around the visiting of Assign so the expression gets |
| # evaluated first, in the case of a clause like "x=x+5" (x |
| # is undeclared) |
| self.visit(node.expr, *args) |
| for n in node.nodes: |
| self.visit(n, *args) |
| |
| def visitLambda(self, node, *args): |
| self._visit_function(node, args) |
| |
| def visitFunction(self, node, *args): |
| self._add_declared(node.name) |
| self._visit_function(node, args) |
| |
| def _expand_tuples(self, args): |
| for arg in args: |
| if isinstance(arg, tuple): |
| for n in arg: |
| yield n |
| else: |
| yield arg |
| |
| def _visit_function(self, node, args): |
| |
| # push function state onto stack. dont log any more |
| # identifiers as "declared" until outside of the function, |
| # but keep logging identifiers as "undeclared". track |
| # argument names in each function header so they arent |
| # counted as "undeclared" |
| |
| inf = self.in_function |
| self.in_function = True |
| |
| local_ident_stack = self.local_ident_stack |
| self.local_ident_stack = local_ident_stack.union([ |
| arg for arg in self._expand_tuples(node.argnames) |
| ]) |
| |
| for n in node.getChildNodes(): |
| self.visit(n, *args) |
| self.in_function = inf |
| self.local_ident_stack = local_ident_stack |
| |
| def visitFor(self, node, *args): |
| |
| # flip around visit |
| |
| self.visit(node.list, *args) |
| self.visit(node.assign, *args) |
| self.visit(node.body, *args) |
| |
| def visitName(self, node, *args): |
| if node.name not in reserved and node.name \ |
| not in self.listener.declared_identifiers and node.name \ |
| not in self.local_ident_stack: |
| self.listener.undeclared_identifiers.add(node.name) |
| |
| def visitImport(self, node, *args): |
| for mod, alias in node.names: |
| if alias is not None: |
| self._add_declared(alias) |
| else: |
| self._add_declared(mod.split('.')[0]) |
| |
| def visitFrom(self, node, *args): |
| for mod, alias in node.names: |
| if alias is not None: |
| self._add_declared(alias) |
| else: |
| if mod == '*': |
| raise exceptions.CompileException( |
| "'import *' is not supported, since all identifier " |
| "names must be explicitly declared. Please use the " |
| "form 'from <modulename> import <name1>, <name2>, " |
| "...' instead.", **self.exception_kwargs) |
| self._add_declared(mod) |
| |
| def visit(self, expr): |
| visitor.walk(expr, self) # , walker=walker()) |
| |
| |
| class FindTuple(object): |
| |
| def __init__(self, listener, code_factory, **exception_kwargs): |
| self.listener = listener |
| self.exception_kwargs = exception_kwargs |
| self.code_factory = code_factory |
| |
| def visitTuple(self, node, *args): |
| for n in node.nodes: |
| p = self.code_factory(n, **self.exception_kwargs) |
| self.listener.codeargs.append(p) |
| self.listener.args.append(ExpressionGenerator(n).value()) |
| self.listener.declared_identifiers = \ |
| self.listener.declared_identifiers.union( |
| p.declared_identifiers) |
| self.listener.undeclared_identifiers = \ |
| self.listener.undeclared_identifiers.union( |
| p.undeclared_identifiers) |
| |
| def visit(self, expr): |
| visitor.walk(expr, self) # , walker=walker()) |
| |
| |
| class ParseFunc(object): |
| |
| def __init__(self, listener, **exception_kwargs): |
| self.listener = listener |
| self.exception_kwargs = exception_kwargs |
| |
| def visitFunction(self, node, *args): |
| self.listener.funcname = node.name |
| self.listener.argnames = node.argnames |
| self.listener.defaults = node.defaults |
| self.listener.varargs = node.varargs |
| self.listener.kwargs = node.kwargs |
| |
| def visit(self, expr): |
| visitor.walk(expr, self) |
| |
| |
| class ExpressionGenerator(object): |
| |
| """given an AST node, generates an equivalent literal Python |
| expression.""" |
| |
| def __init__(self, astnode): |
| self.buf = StringIO() |
| visitor.walk(astnode, self) # , walker=walker()) |
| |
| def value(self): |
| return self.buf.getvalue() |
| |
| def operator(self, op, node, *args): |
| self.buf.write('(') |
| self.visit(node.left, *args) |
| self.buf.write(' %s ' % op) |
| self.visit(node.right, *args) |
| self.buf.write(')') |
| |
| def booleanop(self, op, node, *args): |
| self.visit(node.nodes[0]) |
| for n in node.nodes[1:]: |
| self.buf.write(' ' + op + ' ') |
| self.visit(n, *args) |
| |
| def visitConst(self, node, *args): |
| self.buf.write(repr(node.value)) |
| |
| def visitAssName(self, node, *args): |
| |
| # TODO: figure out OP_ASSIGN, other OP_s |
| |
| self.buf.write(node.name) |
| |
| def visitName(self, node, *args): |
| self.buf.write(node.name) |
| |
| def visitMul(self, node, *args): |
| self.operator('*', node, *args) |
| |
| def visitAnd(self, node, *args): |
| self.booleanop('and', node, *args) |
| |
| def visitOr(self, node, *args): |
| self.booleanop('or', node, *args) |
| |
| def visitBitand(self, node, *args): |
| self.booleanop('&', node, *args) |
| |
| def visitBitor(self, node, *args): |
| self.booleanop('|', node, *args) |
| |
| def visitBitxor(self, node, *args): |
| self.booleanop('^', node, *args) |
| |
| def visitAdd(self, node, *args): |
| self.operator('+', node, *args) |
| |
| def visitGetattr(self, node, *args): |
| self.visit(node.expr, *args) |
| self.buf.write('.%s' % node.attrname) |
| |
| def visitSub(self, node, *args): |
| self.operator('-', node, *args) |
| |
| def visitNot(self, node, *args): |
| self.buf.write('not ') |
| self.visit(node.expr) |
| |
| def visitDiv(self, node, *args): |
| self.operator('/', node, *args) |
| |
| def visitFloorDiv(self, node, *args): |
| self.operator('//', node, *args) |
| |
| def visitSubscript(self, node, *args): |
| self.visit(node.expr) |
| self.buf.write('[') |
| [self.visit(x) for x in node.subs] |
| self.buf.write(']') |
| |
| def visitUnarySub(self, node, *args): |
| self.buf.write('-') |
| self.visit(node.expr) |
| |
| def visitUnaryAdd(self, node, *args): |
| self.buf.write('-') |
| self.visit(node.expr) |
| |
| def visitSlice(self, node, *args): |
| self.visit(node.expr) |
| self.buf.write('[') |
| if node.lower is not None: |
| self.visit(node.lower) |
| self.buf.write(':') |
| if node.upper is not None: |
| self.visit(node.upper) |
| self.buf.write(']') |
| |
| def visitDict(self, node): |
| self.buf.write('{') |
| c = node.getChildren() |
| for i in range(0, len(c), 2): |
| self.visit(c[i]) |
| self.buf.write(': ') |
| self.visit(c[i + 1]) |
| if i < len(c) - 2: |
| self.buf.write(', ') |
| self.buf.write('}') |
| |
| def visitTuple(self, node): |
| self.buf.write('(') |
| c = node.getChildren() |
| for i in range(0, len(c)): |
| self.visit(c[i]) |
| if i < len(c) - 1: |
| self.buf.write(', ') |
| self.buf.write(')') |
| |
| def visitList(self, node): |
| self.buf.write('[') |
| c = node.getChildren() |
| for i in range(0, len(c)): |
| self.visit(c[i]) |
| if i < len(c) - 1: |
| self.buf.write(', ') |
| self.buf.write(']') |
| |
| def visitListComp(self, node): |
| self.buf.write('[') |
| self.visit(node.expr) |
| self.buf.write(' ') |
| for n in node.quals: |
| self.visit(n) |
| self.buf.write(']') |
| |
| def visitListCompFor(self, node): |
| self.buf.write(' for ') |
| self.visit(node.assign) |
| self.buf.write(' in ') |
| self.visit(node.list) |
| for n in node.ifs: |
| self.visit(n) |
| |
| def visitListCompIf(self, node): |
| self.buf.write(' if ') |
| self.visit(node.test) |
| |
| def visitCompare(self, node): |
| self.visit(node.expr) |
| for tup in node.ops: |
| self.buf.write(tup[0]) |
| self.visit(tup[1]) |
| |
| def visitCallFunc(self, node, *args): |
| self.visit(node.node) |
| self.buf.write('(') |
| if len(node.args): |
| self.visit(node.args[0]) |
| for a in node.args[1:]: |
| self.buf.write(', ') |
| self.visit(a) |
| self.buf.write(')') |
| |
| |
| class walker(visitor.ASTVisitor): |
| |
| def dispatch(self, node, *args): |
| print('Node:', str(node)) |
| |
| # print "dir:", dir(node) |
| |
| return visitor.ASTVisitor.dispatch(self, node, *args) |