From 0f05f1ab53c9b4430fb1bafea14b4a888adb42e5 Mon Sep 17 00:00:00 2001 From: Robert Haschke Date: Sun, 3 Oct 2021 01:28:58 +0200 Subject: [PATCH 1/3] Generic loop examples Fails, because we cannot re-pass the body block into next loop iterations. Error: Not enough blocks --- examples/loop.xacro | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/loop.xacro diff --git a/examples/loop.xacro b/examples/loop.xacro new file mode 100644 index 0000000..cffb3b1 --- /dev/null +++ b/examples/loop.xacro @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + item + + + From b15de6a0ff7706416005f9d13bcf38200af40d17 Mon Sep 17 00:00:00 2001 From: Robert Haschke Date: Thu, 9 Sep 2021 08:05:17 +0200 Subject: [PATCH 2/3] Allow default arguments for block arguments This allows recursive calls with identical block args, i.e. loop works. However, I had to drop early evaluation of body (which is conceptually wrong, I believe). This breaks test_pr2 and test_should_replace_before_macroexpand! --- examples/loop.xacro | 2 +- src/xacro/__init__.py | 41 +++++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/examples/loop.xacro b/examples/loop.xacro index cffb3b1..c60bb25 100644 --- a/examples/loop.xacro +++ b/examples/loop.xacro @@ -14,7 +14,7 @@ - item + item: ${item} diff --git a/src/xacro/__init__.py b/src/xacro/__init__.py index 757de01..cf40331 100644 --- a/src/xacro/__init__.py +++ b/src/xacro/__init__.py @@ -589,6 +589,8 @@ def parse_macro_arg(s): param, forward, default, rest = m.groups() if not default: default = None + elif param[0] == '*': + raise XacroException("Invalid default '{}' for block argument '{}'".format(default, param), macro=m) return param, (param if forward else None, default), rest else: # there is no default specified at all @@ -799,34 +801,33 @@ def handle_macro_call(node, macros, symbols): scoped_symbols._setitem(name, eval_text(value, symbols), unevaluated=False) node.setAttribute(name, "") # suppress second evaluation in eval_all() - # Evaluate block parameters in node - eval_all(node, macros, symbols) - - # Fetch block parameters, in order + # Fetch block parameters (in order) or/and defaults for remaining arguments block = first_child_element(node) for param in params[:]: - if param[0] == '*': - if not block: + # get potential default + name, default = m.defaultmap.get(param, (None, None)) + have_default = name is not None or default is not None + + if param[0] == '*': # block argument expected + if block: + params.remove(param) + scoped_symbols[param] = block + block = next_sibling_element(block) + elif have_default: + default = eval_default_arg(name, default, symbols, m) + assert(isinstance(default, xml.dom.minidom.Element)) + scoped_symbols._setitem(param, default, unevaluated=False) + params.remove(param) + else: raise XacroException("Not enough blocks", macro=m) + elif have_default: # a default was specified + default = eval_default_arg(name, default, symbols, m) + scoped_symbols._setitem(param, default, unevaluated=False) params.remove(param) - scoped_symbols[param] = block - block = next_sibling_element(block) if block is not None: raise XacroException("Unused block \"%s\"" % block.tagName, macro=m) - # Try to load defaults for any remaining non-block parameters - for param in params[:]: - # block parameters are not supported for defaults - if param[0] == '*': - continue - - # get default - name, default = m.defaultmap.get(param, (None, None)) - if name is not None or default is not None: - scoped_symbols._setitem(param, eval_default_arg(name, default, symbols, m), unevaluated=False) - params.remove(param) - if params: raise XacroException("Undefined parameters [%s]" % ",".join(params), macro=m) From 26c55ea85bd4ee80056bc161a7fdb5afb01b74c9 Mon Sep 17 00:00:00 2001 From: Robert Haschke Date: Thu, 9 Sep 2021 08:05:17 +0200 Subject: [PATCH 3/3] Try late evaluation of inserted blocks This is required for using properties within the block that are only defined in the header of the calling macro (before calling insert_block). However, the old code (e.g. test_should_replace_before_macroexpand) now fails with `maximum recursion depth exceeded while calling a Python object` due to infinite recursion. --- src/xacro/__init__.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/xacro/__init__.py b/src/xacro/__init__.py index cf40331..3905c50 100644 --- a/src/xacro/__init__.py +++ b/src/xacro/__init__.py @@ -660,8 +660,8 @@ def grab_property(elt, table): return if value is None: - name = '**' + name - value = elt # debug + name = '*' + name + value = first_child_element(elt) replace_node(elt, by=None) @@ -895,6 +895,14 @@ def remove_previous_comments(node): return +def wrap_node(node): + impl = xml.dom.minidom.getDOMImplementation() + doc = impl.createDocument(None, "wrapper", None) + root = doc.documentElement + root.appendChild(node.cloneNode(deep=True)) + return root + + def eval_all(node, macros, symbols): """Recursively evaluate node, expanding macros, replacing properties, and evaluating expressions""" # evaluate the attributes @@ -929,11 +937,9 @@ def eval_all(node, macros, symbols): else: raise XacroException("Undefined block \"%s\"" % name) - # cloning block allows to insert the same block multiple times - block = block.cloneNode(deep=True) - # recursively evaluate block - eval_all(block, macros, symbols) - replace_node(node, by=block, content_only=content_only) + wrapper = wrap_node(block) # wrap block into a dummy node + eval_all(wrapper, macros, symbols) # to allow evaluation of the block itself + replace_node(node, by=first_child_element(wrapper), content_only=content_only) elif node.tagName == 'xacro:include': process_include(node, macros, symbols, eval_all)