[3] | 1 | # ast.py |
---|
| 2 | # Copyright (C) 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
---|
| 3 | # |
---|
| 4 | # This module is part of Mako and is released under |
---|
| 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php |
---|
| 6 | |
---|
| 7 | """utilities for analyzing expressions and blocks of Python code, as well as generating Python from AST nodes""" |
---|
| 8 | |
---|
| 9 | from mako import exceptions, pyparser, util |
---|
| 10 | import re |
---|
| 11 | |
---|
| 12 | class PythonCode(object): |
---|
| 13 | """represents information about a string containing Python code""" |
---|
| 14 | def __init__(self, code, **exception_kwargs): |
---|
| 15 | self.code = code |
---|
| 16 | |
---|
| 17 | # represents all identifiers which are assigned to at some point in the code |
---|
| 18 | self.declared_identifiers = util.Set() |
---|
| 19 | |
---|
| 20 | # represents all identifiers which are referenced before their assignment, if any |
---|
| 21 | self.undeclared_identifiers = util.Set() |
---|
| 22 | |
---|
| 23 | # note that an identifier can be in both the undeclared and declared lists. |
---|
| 24 | |
---|
| 25 | # using AST to parse instead of using code.co_varnames, code.co_names has several advantages: |
---|
| 26 | # - we can locate an identifier as "undeclared" even if its declared later in the same block of code |
---|
| 27 | # - AST is less likely to break with version changes (for example, the behavior of co_names changed a little bit |
---|
| 28 | # in python version 2.5) |
---|
| 29 | if isinstance(code, basestring): |
---|
| 30 | expr = pyparser.parse(code.lstrip(), "exec", **exception_kwargs) |
---|
| 31 | else: |
---|
| 32 | expr = code |
---|
| 33 | |
---|
| 34 | f = pyparser.FindIdentifiers(self, **exception_kwargs) |
---|
| 35 | f.visit(expr) |
---|
| 36 | |
---|
| 37 | class ArgumentList(object): |
---|
| 38 | """parses a fragment of code as a comma-separated list of expressions""" |
---|
| 39 | def __init__(self, code, **exception_kwargs): |
---|
| 40 | self.codeargs = [] |
---|
| 41 | self.args = [] |
---|
| 42 | self.declared_identifiers = util.Set() |
---|
| 43 | self.undeclared_identifiers = util.Set() |
---|
| 44 | if isinstance(code, basestring): |
---|
| 45 | if re.match(r"\S", code) and not re.match(r",\s*$", code): |
---|
| 46 | # if theres text and no trailing comma, insure its parsed |
---|
| 47 | # as a tuple by adding a trailing comma |
---|
| 48 | code += "," |
---|
| 49 | expr = pyparser.parse(code, "exec", **exception_kwargs) |
---|
| 50 | else: |
---|
| 51 | expr = code |
---|
| 52 | |
---|
| 53 | f = pyparser.FindTuple(self, PythonCode, **exception_kwargs) |
---|
| 54 | f.visit(expr) |
---|
| 55 | |
---|
| 56 | class PythonFragment(PythonCode): |
---|
| 57 | """extends PythonCode to provide identifier lookups in partial control statements |
---|
| 58 | |
---|
| 59 | e.g. |
---|
| 60 | for x in 5: |
---|
| 61 | elif y==9: |
---|
| 62 | except (MyException, e): |
---|
| 63 | etc. |
---|
| 64 | """ |
---|
| 65 | def __init__(self, code, **exception_kwargs): |
---|
| 66 | m = re.match(r'^(\w+)(?:\s+(.*?))?:\s*(#|$)', code.strip(), re.S) |
---|
| 67 | if not m: |
---|
| 68 | raise exceptions.CompileException("Fragment '%s' is not a partial control statement" % code, **exception_kwargs) |
---|
| 69 | if m.group(3): |
---|
| 70 | code = code[:m.start(3)] |
---|
| 71 | (keyword, expr) = m.group(1,2) |
---|
| 72 | if keyword in ['for','if', 'while']: |
---|
| 73 | code = code + "pass" |
---|
| 74 | elif keyword == 'try': |
---|
| 75 | code = code + "pass\nexcept:pass" |
---|
| 76 | elif keyword == 'elif' or keyword == 'else': |
---|
| 77 | code = "if False:pass\n" + code + "pass" |
---|
| 78 | elif keyword == 'except': |
---|
| 79 | code = "try:pass\n" + code + "pass" |
---|
| 80 | else: |
---|
| 81 | raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, **exception_kwargs) |
---|
| 82 | super(PythonFragment, self).__init__(code, **exception_kwargs) |
---|
| 83 | |
---|
| 84 | |
---|
| 85 | class FunctionDecl(object): |
---|
| 86 | """function declaration""" |
---|
| 87 | def __init__(self, code, allow_kwargs=True, **exception_kwargs): |
---|
| 88 | self.code = code |
---|
| 89 | expr = pyparser.parse(code, "exec", **exception_kwargs) |
---|
| 90 | |
---|
| 91 | f = pyparser.ParseFunc(self, **exception_kwargs) |
---|
| 92 | f.visit(expr) |
---|
| 93 | if not hasattr(self, 'funcname'): |
---|
| 94 | raise exceptions.CompileException("Code '%s' is not a function declaration" % code, **exception_kwargs) |
---|
| 95 | if not allow_kwargs and self.kwargs: |
---|
| 96 | raise exceptions.CompileException("'**%s' keyword argument not allowed here" % self.argnames[-1], **exception_kwargs) |
---|
| 97 | |
---|
| 98 | def get_argument_expressions(self, include_defaults=True): |
---|
| 99 | """return the argument declarations of this FunctionDecl as a printable list.""" |
---|
| 100 | namedecls = [] |
---|
| 101 | defaults = [d for d in self.defaults] |
---|
| 102 | kwargs = self.kwargs |
---|
| 103 | varargs = self.varargs |
---|
| 104 | argnames = [f for f in self.argnames] |
---|
| 105 | argnames.reverse() |
---|
| 106 | for arg in argnames: |
---|
| 107 | default = None |
---|
| 108 | if kwargs: |
---|
| 109 | arg = "**" + arg |
---|
| 110 | kwargs = False |
---|
| 111 | elif varargs: |
---|
| 112 | arg = "*" + arg |
---|
| 113 | varargs = False |
---|
| 114 | else: |
---|
| 115 | default = len(defaults) and defaults.pop() or None |
---|
| 116 | if include_defaults and default: |
---|
| 117 | namedecls.insert(0, "%s=%s" % (arg, pyparser.ExpressionGenerator(default).value())) |
---|
| 118 | else: |
---|
| 119 | namedecls.insert(0, arg) |
---|
| 120 | return namedecls |
---|
| 121 | |
---|
| 122 | class FunctionArgs(FunctionDecl): |
---|
| 123 | """the argument portion of a function declaration""" |
---|
| 124 | def __init__(self, code, **kwargs): |
---|
| 125 | super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, **kwargs) |
---|