blob: c630e21ad943726e8013addc1a35d6076bee432a [file]
"""utilities for analyzing expressions and blocks of Python code, as well as generating Python from AST nodes"""
from compiler import ast, parse, visitor
from mako import util, exceptions
from StringIO import StringIO
import re
class PythonCode(object):
"""represents information about a string containing Python code"""
def __init__(self, code, lineno, pos):
self.code = code
self.declared_identifiers = util.Set()
self.undeclared_identifiers = util.Set()
expr = parse(code, "exec")
class FindIdentifiers(object):
def visitAssName(s, node, *args):
if node.name not in self.undeclared_identifiers:
self.declared_identifiers.add(node.name)
def visitTryExcept(s, node, *args):
for (decl, s2, s3) in node.handlers:
if decl is not None:
(exception, ident) = [n.name for n in decl.nodes]
self.declared_identifiers.add(ident)
def visitName(s, node, *args):
if node.name not in __builtins__ and node.name not in self.declared_identifiers:
self.undeclared_identifiers.add(node.name)
def visitImport(s, node, *args):
for (mod, alias) in node.names:
if alias is not None:
self.declared_identifiers.add(alias)
else:
self.declared_identifiers.add(mod)
def visitFrom(s, node, *args):
for (mod, alias) in node.names:
if alias is not None:
self.declared_identifiers.add(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.", lineno, pos)
self.declared_identifiers.add(mod)
f = FindIdentifiers()
visitor.walk(expr, f) #, walker=walker())
class PythonFragment(PythonCode):
"""extends PythonCode to provide identifier lookups in partial control statements
e.g.
for x in 5:
elif y==9:
except (MyException, e):
etc.
"""
def __init__(self, code, lineno, pos):
m = re.match(r'^(\w+)(?:\s+(.*?))?:$', code)
if not m:
raise exceptions.CompileException("Fragment '%s' is not a partial control statement" % code, lineno, pos)
(keyword, expr) = m.group(1,2)
if keyword in ['for','if', 'while']:
code = code + "pass"
elif keyword == 'try':
code = code + "pass\nexcept:pass"
elif keyword == 'elif' or keyword == 'else':
code = "if False:pass\n" + code + "pass"
elif keyword == 'except':
code = "try:pass\n" + code + "pass"
else:
raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, lineno, pos)
super(PythonFragment, self).__init__(code, lineno, pos)
class walker(visitor.ASTVisitor):
def dispatch(self, node, *args):
print "Node:", str(node)
#print "dir:", dir(node)
return visitor.ASTVisitor.dispatch(self, node, *args)
class FunctionDecl(object):
"""function declaration"""
def __init__(self, code, lineno, pos):
self.code = code
expr = parse(code, "exec")
class ParseFunc(object):
def visitFunction(s, node, *args):
self.funcname = node.name
self.argnames = node.argnames
self.defaults = node.defaults
self.varargs = node.varargs
self.kwargs = node.kwargs
f = ParseFunc()
visitor.walk(expr, f)
if not hasattr(self, 'funcname'):
raise exceptions.CompileException("Code '%s' is not a function declaration" % code, lineno, pos)
def get_argument_expressions(self, include_defaults=True):
"""return the argument declarations of this FunctionDecl as a printable list"""
namedecls = []
defaults = [d for d in self.defaults]
kwargs = self.kwargs
varargs = self.varargs
argnames = [f for f in self.argnames]
argnames.reverse()
for arg in argnames:
default = None
if kwargs:
arg = "**" + arg
kwargs = False
elif varargs:
arg = "*" + arg
varargs = False
else:
default = len(defaults) and defaults.pop() or None
if include_defaults and default:
namedecls.insert(0, "%s=%s" % (arg, ExpressionGenerator(default).value()))
else:
namedecls.insert(0, arg)
return namedecls
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 visitConst(self, node, *args):
self.buf.write(repr(node.value))
def visitName(self, node, *args):
self.buf.write(node.name)
def visitMul(self, node, *args):
self.operator("*", 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 visitDiv(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 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 visitCallFunc(self, node, *args):
self.visit(node.node)
self.buf.write("(")
self.visit(node.args[0])
for a in node.args[1:]:
self.buf.write(", ")
self.visit(a)
self.buf.write(")")