From 4d132aa2ebfcbfca74ec4553b6b3eb14b151ab72 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Sat, 6 Oct 2018 00:17:13 +0300 Subject: [PATCH 1/4] add support for named arguments to macros --- macropy/core/macros.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/macropy/core/macros.py b/macropy/core/macros.py index 259356b4..c8531341 100644 --- a/macropy/core/macros.py +++ b/macropy/core/macros.py @@ -73,7 +73,7 @@ def macro_stub(func): MacroData = collections.namedtuple('MacroData', ['macro', 'macro_tree', 'body_tree', 'call_args', - 'kwargs', 'name']) + 'kwargs', 'extrakws', 'name']) MacroData.__doc__ = """ Contains a macro's detailed informations needed to expand it. @@ -103,22 +103,27 @@ def get_macro_details(self, macro_tree): """Given an AST tree of a macro, returns detailed informations about it. :param macro_tree: an AST tree - :returns: A tuple containing tree elements: + :returns: A tuple containing four elements: - the name of the macro; - the tree containing the macro itself (it is *macro_tree* itself as of now); - - the arguments to the macro invocation. + - arguments to the macro invocation; + - if a Call, named arguments to the macro invocation, as OrderedDict. """ + kwargs = collections.OrderedDict() # keywords is a list; preserve order if isinstance(macro_tree, ast.Call): call_args = tuple(macro_tree.args) + for kw in macro_tree.keywords: + if kw.arg is not None: + kwargs[kw.arg] = kw.value macro_tree = macro_tree.func else: call_args = () if isinstance(macro_tree, ast.Name): - return macro_tree.id, macro_tree, call_args + return macro_tree.id, macro_tree, call_args, kwargs else: - return None, macro_tree, call_args + return None, macro_tree, call_args, kwargs @abstractmethod def detect_macro(self, in_tree): @@ -147,10 +152,10 @@ def detect_macro(self, in_tree): if (isinstance(in_tree, ast.Subscript) and type(in_tree.slice) is ast.Index): # noqa: E129 body_tree = in_tree.slice.value - name, macro_tree, call_args = self.get_macro_details(in_tree.value) + name, macro_tree, call_args, kwargs = self.get_macro_details(in_tree.value) if name is not None and name in self.registry: new_tree = yield MacroData(self.registry[name], macro_tree, - body_tree, call_args, {}, name) + body_tree, call_args, kwargs, {}, name) assert isinstance(new_tree, ast.expr), ('Wrong type %r' % type(new_tree)) new_tree = ast.Expr(new_tree) @@ -171,13 +176,12 @@ def detect_macro(self, in_tree): assert isinstance(in_tree.body, list), real_repr(in_tree.body) new_tree = None for wi in in_tree.items: - name, macro_tree, call_args = self.get_macro_details( + name, macro_tree, call_args, kwargs = self.get_macro_details( wi.context_expr) if name is not None and name in self.registry: new_tree = yield MacroData(self.registry[name], macro_tree, - in_tree.body, call_args, - {'target': wi.optional_vars}, - name) + in_tree.body, call_args, kwargs, + {'target': wi.optional_vars}, name) if new_tree: if isinstance(new_tree, ast.expr): @@ -212,7 +216,7 @@ def detect_macro(self, in_tree): additions = [] # process each decorator from the innermost outwards for dec in rev_decs: - name, macro_tree, call_args = self.get_macro_details(dec) + name, macro_tree, call_args, kwargs = self.get_macro_details(dec) # if the decorator is not a macro, add it to a list # for later re-insertion, either before executing an # outer macro or at the end of the loop if no macro is found @@ -226,7 +230,7 @@ def detect_macro(self, in_tree): tree.decorator_list) seen_decs = [] tree = yield MacroData(self.registry[name], macro_tree, tree, - call_args, {}, name) + call_args, kwargs, {}, name) if type(tree) is list: additions = tree[1:] tree = tree[0] @@ -400,7 +404,7 @@ def create_single_macro_expand_generator(self, mfunc, *args, **kwargs): def gen_macro_expand_single(tree): return self.macro_expand_single(MacroData( (mfunc, sys.modules[mfunc.__module__]), - tree, tree, args, kwargs, mfunc.__name__)) + tree, tree, args, kwargs, {}, mfunc.__name__)) return gen_macro_expand_single def expand_macro(self, mfunc, tree=None, *args, **kwargs): @@ -497,9 +501,10 @@ def macro_expand_single(self, macro_data): new_tree = mfunc( tree=new_tree, args=macro_data.call_args, + kwargs=macro_data.kwargs, src=self.src, expand_macros=self.expand_macros, - **dict(tuple(macro_data.kwargs.items()) + + **dict(tuple(macro_data.extrakws.items()) + tuple(self.file_vars.items())) ) # the result is a generator, treat it like a @@ -527,11 +532,12 @@ def macro_expand_single(self, macro_data): new_tree = function( tree=new_tree, args=macro_data.call_args, + kwargs=macro_data.kwargs, src=self.src, expand_macros=self.expand_macros, lineno=macro_data.macro_tree.lineno, col_offset=macro_data.macro_tree.col_offset, - **dict(tuple(macro_data.kwargs.items()) + + **dict(tuple(macro_data.extrakws.items()) + tuple(self.file_vars.items())) ) # yield it for one more walking From 15880bf12dd04c830a015bf467b767a0dc4d12a1 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Sat, 6 Oct 2018 01:16:50 +0300 Subject: [PATCH 2/4] support named arguments, sensibly --- macropy/core/macros.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/macropy/core/macros.py b/macropy/core/macros.py index c8531341..1b3c1f50 100644 --- a/macropy/core/macros.py +++ b/macropy/core/macros.py @@ -108,18 +108,16 @@ def get_macro_details(self, macro_tree): - the tree containing the macro itself (it is *macro_tree* itself as of now); - arguments to the macro invocation; - - if a Call, named arguments to the macro invocation, as OrderedDict. + - named arguments to the macro invocation. """ - kwargs = collections.OrderedDict() # keywords is a list; preserve order if isinstance(macro_tree, ast.Call): call_args = tuple(macro_tree.args) - for kw in macro_tree.keywords: - if kw.arg is not None: - kwargs[kw.arg] = kw.value + kwargs = tuple(macro_tree.keywords) macro_tree = macro_tree.func else: call_args = () + kwargs = () if isinstance(macro_tree, ast.Name): return macro_tree.id, macro_tree, call_args, kwargs else: From fd883ddb23100b3e9c71da751c8fefcdfdf84977 Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 15 May 2019 15:33:10 +0300 Subject: [PATCH 3/4] consistent naming: kwargs -> call_kwargs in the custom named args passing mechanism; now kwargs is a dict, {name_as_str: ast_node} --- macropy/core/macros.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/macropy/core/macros.py b/macropy/core/macros.py index 1b3c1f50..8968e842 100644 --- a/macropy/core/macros.py +++ b/macropy/core/macros.py @@ -73,7 +73,7 @@ def macro_stub(func): MacroData = collections.namedtuple('MacroData', ['macro', 'macro_tree', 'body_tree', 'call_args', - 'kwargs', 'extrakws', 'name']) + 'call_kwargs', 'extrakws', 'name']) MacroData.__doc__ = """ Contains a macro's detailed informations needed to expand it. @@ -113,15 +113,15 @@ def get_macro_details(self, macro_tree): """ if isinstance(macro_tree, ast.Call): call_args = tuple(macro_tree.args) - kwargs = tuple(macro_tree.keywords) + call_kwargs = {x.arg: x.value for x in macro_tree.keywords} macro_tree = macro_tree.func else: call_args = () - kwargs = () + call_kwargs = {} if isinstance(macro_tree, ast.Name): - return macro_tree.id, macro_tree, call_args, kwargs + return macro_tree.id, macro_tree, call_args, call_kwargs else: - return None, macro_tree, call_args, kwargs + return None, macro_tree, call_args, call_kwargs @abstractmethod def detect_macro(self, in_tree): @@ -150,10 +150,10 @@ def detect_macro(self, in_tree): if (isinstance(in_tree, ast.Subscript) and type(in_tree.slice) is ast.Index): # noqa: E129 body_tree = in_tree.slice.value - name, macro_tree, call_args, kwargs = self.get_macro_details(in_tree.value) + name, macro_tree, call_args, call_kwargs = self.get_macro_details(in_tree.value) if name is not None and name in self.registry: new_tree = yield MacroData(self.registry[name], macro_tree, - body_tree, call_args, kwargs, {}, name) + body_tree, call_args, call_kwargs, {}, name) assert isinstance(new_tree, ast.expr), ('Wrong type %r' % type(new_tree)) new_tree = ast.Expr(new_tree) @@ -174,11 +174,11 @@ def detect_macro(self, in_tree): assert isinstance(in_tree.body, list), real_repr(in_tree.body) new_tree = None for wi in in_tree.items: - name, macro_tree, call_args, kwargs = self.get_macro_details( + name, macro_tree, call_args, call_kwargs = self.get_macro_details( wi.context_expr) if name is not None and name in self.registry: new_tree = yield MacroData(self.registry[name], macro_tree, - in_tree.body, call_args, kwargs, + in_tree.body, call_args, call_kwargs, {'target': wi.optional_vars}, name) if new_tree: @@ -214,7 +214,7 @@ def detect_macro(self, in_tree): additions = [] # process each decorator from the innermost outwards for dec in rev_decs: - name, macro_tree, call_args, kwargs = self.get_macro_details(dec) + name, macro_tree, call_args, call_kwargs = self.get_macro_details(dec) # if the decorator is not a macro, add it to a list # for later re-insertion, either before executing an # outer macro or at the end of the loop if no macro is found @@ -228,7 +228,7 @@ def detect_macro(self, in_tree): tree.decorator_list) seen_decs = [] tree = yield MacroData(self.registry[name], macro_tree, tree, - call_args, kwargs, {}, name) + call_args, call_kwargs, {}, name) if type(tree) is list: additions = tree[1:] tree = tree[0] @@ -499,7 +499,7 @@ def macro_expand_single(self, macro_data): new_tree = mfunc( tree=new_tree, args=macro_data.call_args, - kwargs=macro_data.kwargs, + kwargs=macro_data.call_kwargs, src=self.src, expand_macros=self.expand_macros, **dict(tuple(macro_data.extrakws.items()) + @@ -530,7 +530,7 @@ def macro_expand_single(self, macro_data): new_tree = function( tree=new_tree, args=macro_data.call_args, - kwargs=macro_data.kwargs, + kwargs=macro_data.call_kwargs, src=self.src, expand_macros=self.expand_macros, lineno=macro_data.macro_tree.lineno, From 077cb355ff27fad7d212eab4b3dd823f07c834be Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Wed, 15 May 2019 15:34:18 +0300 Subject: [PATCH 4/4] add test for new named macro args mechanism --- macropy/core/test/macros/argument.py | 6 ++++-- macropy/core/test/macros/argument_macros.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/macropy/core/test/macros/argument.py b/macropy/core/test/macros/argument.py index 3ad6ed01..38e24332 100644 --- a/macropy/core/test/macros/argument.py +++ b/macropy/core/test/macros/argument.py @@ -1,9 +1,11 @@ -from macropy.core.test.macros.argument_macros import macros, expr_macro, block_macro, decorator_macro +from macropy.core.test.macros.argument_macros import macros, expr_macro, block_macro, decorator_macro, expr_macro_with_named_args import math def run(): x = expr_macro(1 + math.sqrt(5))[10 + 10 + 10] + x = expr_macro_with_named_args(1 + 2 + 3, a=(1 + math.sqrt(5)))[10 + 10 + 10] + with block_macro(1 + math.sqrt(5)) as y: x = x + 1 @@ -11,4 +13,4 @@ def run(): def f(): pass - return x \ No newline at end of file + return x diff --git a/macropy/core/test/macros/argument_macros.py b/macropy/core/test/macros/argument_macros.py index b5b0c2e3..e60b350a 100644 --- a/macropy/core/test/macros/argument_macros.py +++ b/macropy/core/test/macros/argument_macros.py @@ -3,6 +3,13 @@ macros = macropy.core.macros.Macros() +@macros.expr +def expr_macro_with_named_args(tree, args, kwargs, **kw): + assert "a" in kwargs, kwargs + assert macropy.core.unparse(kwargs["a"]) == "(1 + math.sqrt(5))", macropy.core.unparse(kwargs["a"]) + assert list(map(macropy.core.unparse, args)) == ["((1 + 2) + 3)"], macropy.core.unparse(args) + return tree + @macros.expr def expr_macro(tree, args, **kw): assert list(map(macropy.core.unparse, args)) == ["(1 + math.sqrt(5))"], macropy.core.unparse(args)