diff --git a/hy/compiler.py b/hy/compiler.py index 8830aa3..f14c79f 100644 --- a/hy/compiler.py +++ b/hy/compiler.py @@ -36,7 +36,7 @@ from hy.models.float import HyFloat from hy.models.list import HyList from hy.models.dict import HyDict -from hy.macros import require, process +from hy.macros import require, macroexpand from hy._compat import str_type import hy.importer @@ -419,7 +419,7 @@ class HyASTCompiler(object): def compile(self, tree): try: - tree = process(tree, self.module_name) + tree = macroexpand(tree, self.module_name) _type = type(tree) ret = self.compile_atom(_type, tree) if ret: diff --git a/hy/macros.py b/hy/macros.py index b08f1a4..20d0cac 100644 --- a/hy/macros.py +++ b/hy/macros.py @@ -43,6 +43,17 @@ _hy_macros = defaultdict(dict) def macro(name): + """Decorator to define a macro called `name`. + + This stores the macro `name` in the namespace for the module where it is + defined. + + If the module where it is defined is in `hy.core`, then the macro is stored + in the default `None` namespace. + + This function is called from the `defmacro` special form in the compiler. + + """ def _(fn): module_name = fn.__module__ if module_name.startswith("hy.core"): @@ -52,20 +63,20 @@ def macro(name): return _ -def require(source_module_name, target_module_name): - macros = _hy_macros[source_module_name] - refs = _hy_macros[target_module_name] +def require(source_module, target_module): + """Load the macros from `source_module` in the namespace of + `target_module`. + + This function is called from the `require` special form in the compiler. + + """ + macros = _hy_macros[source_module] + refs = _hy_macros[target_module] for name, macro in macros.items(): refs[name] = macro -def _wrap_value(x): - wrapper = _wrappers.get(type(x)) - if wrapper is None: - return x - else: - return wrapper(x) - +# type -> wrapping function mapping for _wrap_value _wrappers = { int: HyInteger, bool: lambda x: HySymbol("True") if x else HySymbol("False"), @@ -77,27 +88,60 @@ _wrappers = { } -def process(tree, module_name): - load_macros(module_name) - old = None - while old != tree: - old = tree - tree = macroexpand(tree, module_name) - return tree +def _wrap_value(x): + """Wrap `x` into the corresponding Hy type. + + This allows a macro to return an unquoted expression transparently. + + """ + wrapper = _wrappers.get(type(x)) + if wrapper is None: + return x + else: + return wrapper(x) def load_macros(module_name): + """Load the hy builtin macros for module `module_name`. + + Modules from `hy.core` can only use the macros from CORE_MACROS. + Other modules get the macros from CORE_MACROS and EXTRA_MACROS. + + """ + + def _import(module, module_name=module_name): + "__import__ a module, avoiding recursions" + if module != module_name: + __import__(module) + for module in CORE_MACROS: - __import__(module) + _import(module) if module_name.startswith("hy.core"): return for module in EXTRA_MACROS: - __import__(module) + _import(module) def macroexpand(tree, module_name): + """Expand the toplevel macros for the `tree`. + + Load the macros from the given `module_name`, then expand the (top-level) + macros in `tree` until it stops changing. + + """ + load_macros(module_name) + old = None + while old != tree: + old = tree + tree = macroexpand_1(tree, module_name) + return tree + + +def macroexpand_1(tree, module_name): + """Expand the toplevel macro from `tree` once, in the context of + `module_name`.""" if isinstance(tree, HyExpression): if tree == []: return tree diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py index 46e33a1..373b967 100644 --- a/tests/macros/test_macro_processor.py +++ b/tests/macros/test_macro_processor.py @@ -1,5 +1,5 @@ -from hy.macros import macro, process +from hy.macros import macro, macroexpand from hy.lex import tokenize from hy.models.string import HyString @@ -16,14 +16,14 @@ def tmac(*tree): def test_preprocessor_simple(): """ Test basic macro expansion """ - obj = process(tokenize('(test "one" "two")')[0], __name__) + obj = macroexpand(tokenize('(test "one" "two")')[0], __name__) assert obj == HyList(["one", "two"]) assert type(obj) == HyList def test_preprocessor_expression(): """ Test that macro expansion doesn't recurse""" - obj = process(tokenize('(test (test "one" "two"))')[0], __name__) + obj = macroexpand(tokenize('(test (test "one" "two"))')[0], __name__) assert type(obj) == HyList assert type(obj[0]) == HyExpression @@ -34,4 +34,4 @@ def test_preprocessor_expression(): obj = HyList([HyString("one"), HyString("two")]) obj = tokenize('(shill ["one" "two"])')[0][1] - assert obj == process(obj, '') + assert obj == macroexpand(obj, '')