123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- """The main source of all things MacroPy"""
- import sys
- import imp
- import ast
- import itertools
- from ast import *
- from util import *
- from walkers import *
- # Monkey Patching pickle to pickle module objects properly
- import pickle
- pickle.Pickler.dispatch[type(pickle)] = pickle.Pickler.save_global
- class WrappedFunction(object):
- """Wraps a function which is meant to be handled (and removed) by macro
- expansion, and never called directly with square brackets."""
- def __init__(self, func, msg):
- self.func = func
- self.msg = msg
- import functools
- functools.update_wrapper(self, func)
- def __call__(self, *args, **kwargs):
- return self.func(*args, **kwargs)
- def __getitem__(self, i):
- raise TypeError(self.msg.replace("%s", self.func.__name__))
- def macro_function(func):
- """Wraps a function, to provide nicer error-messages in the common
- case where the macro is imported but macro-expansion isn't triggered"""
- return WrappedFunction(
- func,
- "Macro `%s` illegally invoked at runtime; did you import it "
- "properly using `from ... import macros, %s`?"
- )
- def macro_stub(func):
- """Wraps a function that is a stub meant to be used by macros but never
- called directly."""
- return WrappedFunction(
- func,
- "Stub `%s` illegally invoked at runtime; is it used "
- "properly within a macro?"
- )
- class Macros(object):
- """A registry of macros belonging to a module; used via
- ```python
- macros = Macros()
- @macros.expr
- def my_macro(tree):
- ...
- ```
- Where the decorators are used to register functions as macros belonging
- to that module.
- """
- class Registry(object):
- def __init__(self, wrap = lambda x: x):
- self.registry = {}
- self.wrap = wrap
- def __call__(self, f, name=None):
- if name is not None:
- self.registry[name] = self.wrap(f)
- if hasattr(f, "func_name"):
- self.registry[f.func_name] = self.wrap(f)
- if hasattr(f, "__name__"):
- self.registry[f.__name__] = self.wrap(f)
- return self.wrap(f)
- def __init__(self):
- # Different kinds of macros
- self.expr = Macros.Registry(macro_function)
- self.block = Macros.Registry(macro_function)
- self.decorator = Macros.Registry(macro_function)
- self.expose_unhygienic = Macros.Registry()
- # For other modules to hook into MacroPy's workflow while
- # keeping this module itself unaware of their presence.
- injected_vars = [] # functions to inject values throughout each files macros
- filters = [] # functions to call on every macro-expanded snippet
- post_processing = [] # functions to call on every macro-expanded file
- def expand_entire_ast(tree, src, bindings):
- def expand_macros(tree):
- """Go through an AST, hunting for macro invocations and expanding any that
- are found"""
- def expand_if_in_registry(macro_tree, body_tree, args, registry, **kwargs):
- """check if `tree` is a macro in `registry`, and if so use it to expand `args`"""
- if isinstance(macro_tree, Name) and macro_tree.id in registry:
- (the_macro, the_module) = registry[macro_tree.id]
- try:
- new_tree = the_macro(
- tree=body_tree,
- args=args,
- src=src,
- expand_macros=expand_macros,
- **dict(kwargs.items() + file_vars.items())
- )
- except Exception as e:
- new_tree = e
- for filter in reversed(filters):
- new_tree = filter(
- tree=new_tree,
- args=args,
- src=src,
- expand_macros=expand_macros,
- lineno=macro_tree.lineno,
- col_offset=macro_tree.col_offset,
- **dict(kwargs.items() + file_vars.items())
- )
- return new_tree
- elif isinstance(macro_tree, Call):
- args.extend(macro_tree.args)
- return expand_if_in_registry(macro_tree.func, body_tree, args, registry)
- def preserve_line_numbers(func):
- """Decorates a tree-transformer function to stick the original line
- numbers onto the transformed tree"""
- def run(tree):
- pos = (tree.lineno, tree.col_offset) if hasattr(tree, "lineno") and hasattr(tree, "col_offset") else None
- new_tree = func(tree)
- if pos:
- t = new_tree
- while type(t) is list:
- t = t[0]
- (t.lineno, t.col_offset) = pos
- return new_tree
- return run
- @preserve_line_numbers
- def macro_expand(tree):
- """Tail Recursively expands all macros in a single AST node"""
- if isinstance(tree, With):
- assert isinstance(tree.body, list), real_repr(tree.body)
- new_tree = expand_if_in_registry(tree.context_expr, tree.body, [], block_registry, target=tree.optional_vars)
- if new_tree:
- if isinstance(new_tree, expr):
- new_tree = [Expr(new_tree)]
- if isinstance(new_tree, Exception): raise new_tree
- assert isinstance(new_tree, list), type(new_tree)
- return macro_expand(new_tree)
- if isinstance(tree, Subscript) and type(tree.slice) is Index:
- new_tree = expand_if_in_registry(tree.value, tree.slice.value, [], expr_registry)
- if new_tree:
- assert isinstance(new_tree, expr), type(new_tree)
- return macro_expand(new_tree)
- if isinstance(tree, ClassDef) or isinstance(tree, FunctionDef):
- seen_decs = []
- additions = []
- while tree.decorator_list != []:
- dec = tree.decorator_list[0]
- tree.decorator_list = tree.decorator_list[1:]
- new_tree = expand_if_in_registry(dec, tree, [], decorator_registry)
- if new_tree is None:
- seen_decs.append(dec)
- else:
- tree = new_tree
- tree = macro_expand(tree)
- if type(tree) is list:
- additions = tree[1:]
- tree = tree[0]
- elif isinstance(tree, expr):
- tree = [Expr(tree)]
- break
- if type(tree) is ClassDef or type(tree) is FunctionDef:
- tree.decorator_list = seen_decs
- if len(additions) == 0:
- return tree
- else:
- return [tree] + additions
- return tree
- @Walker
- def macro_searcher(tree, **kw):
- x = macro_expand(tree)
- return x
- tree = macro_searcher.recurse(tree)
- return tree
- file_vars = {}
- for v in injected_vars:
- file_vars[v.func_name] = v(tree=tree, src=src, expand_macros=expand_macros, **file_vars)
- allnames = [
- (m, name, asname)
- for m, names in bindings
- for name, asname in names
- ]
- def extract_macros(pick_registry):
- return {
- asname: (registry[name], ma)
- for ma, name, asname in allnames
- for registry in [pick_registry(ma.macros).registry]
- if name in registry.keys()
- }
- block_registry = extract_macros(lambda x: x.block)
- expr_registry = extract_macros(lambda x: x.expr)
- decorator_registry = extract_macros(lambda x: x.decorator)
- tree = expand_macros(tree)
- for post in post_processing:
- tree = post(
- tree=tree,
- src=src,
- expand_macros=expand_macros,
- **file_vars
- )
- return tree
- def detect_macros(tree):
- """Look for macros imports within an AST, transforming them and extracting
- the list of macro modules."""
- bindings = []
- for stmt in tree.body:
- if isinstance(stmt, ImportFrom) \
- and stmt.names[0].name == 'macros' \
- and stmt.names[0].asname is None:
- __import__(stmt.module)
- mod = sys.modules[stmt.module]
- bindings.append((
- stmt.module,
- [(t.name, t.asname or t.name) for t in stmt.names[1:]]
- ))
- stmt.names = [
- name for name in stmt.names
- if name.name not in mod.macros.block.registry
- if name.name not in mod.macros.expr.registry
- if name.name not in mod.macros.decorator.registry
- ]
- stmt.names.extend([
- alias(x, x) for x in
- mod.macros.expose_unhygienic.registry.keys()
- ])
- return bindings
- def check_annotated(tree):
- """Shorthand for checking if an AST is of the form something[...]"""
- if isinstance(tree, Subscript) and \
- type(tree.slice) is Index and \
- type(tree.value) is Name:
- return tree.value.id, tree.slice.value
- # import other modules in order to register their hooks
|