Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions py_trees_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,41 @@ def _sub_args(self, args, var):

return None

def _evaluate_condition(self, condition: str, args: dict) -> bool:
"""
Evaluate a conditional expression with the given arguments.

Args:
----
condition (str): The condition to evaluate.
args (dict): The arguments to use in evaluation.

Returns:
-------
bool: The result of the condition evaluation.
"""
self.logger.debug(f"Evaluating condition: {condition} with args: {args}")

# Replace argument placeholders with their values
for arg_name, arg_value in args.items():
placeholder = "${" + arg_name + "}"
if placeholder in condition:
# For string arg values, ensure they're properly quoted in the condition
if isinstance(arg_value, str) and not arg_value.startswith('"') and not arg_value.startswith("'"):
arg_value = f"'{arg_value}'"
condition = condition.replace(placeholder, str(arg_value))

self.logger.debug(f"Condition after arg substitution: {condition}")

try:
# Safely evaluate the condition
result = eval(condition, {"__builtins__": {}}, {})
self.logger.debug(f"Condition result: {result}")
return bool(result)
except Exception as ex:
self.logger.error(f"Error evaluating condition '{condition}': {ex}")
raise ValueError(f"Error evaluating condition '{condition}': {ex}")

def _build_tree(
self,
xml_node: Element,
Expand Down Expand Up @@ -484,6 +519,41 @@ def _build_tree(
f"Unexpected tag in subtree ({subtree_name}): {child_xml.tag.lower()}"
)
return self._build_tree(self._get_xml(include), {**args, **new_args})

# Handle conditional inclusion based on arguments
if xml_node.tag.lower() == "conditional":
condition = xml_node.attrib.get("if")
if not condition:
self.logger.error("Conditional tag missing 'if' attribute")
raise ValueError("Conditional tag missing 'if' attribute")

# Process condition
self.logger.debug(f"Processing conditional: {condition}")
include_children = self._evaluate_condition(condition, args)

if not include_children:
self.logger.debug("Condition evaluated to False, skipping children")
# Return a dummy composite node with no children
return py_trees.composites.Sequence(name="conditional_skipped")

# Condition is true, include children
self.logger.debug("Condition evaluated to True, including children")
if len(list(xml_node)) == 0:
self.logger.warn("Conditional has no children")
return py_trees.composites.Sequence(name="empty_conditional")
elif len(list(xml_node)) == 1:
# If there's only one child, return it directly
child_xml = list(xml_node)[0]
self._process_args(child_xml, args)
return self._build_tree(child_xml, args)
else:
# If there are multiple children, wrap them in a sequence
children = list()
for child_xml in xml_node:
self._process_args(child_xml, args)
child = self._build_tree(child_xml, args)
children.append(child)
return py_trees.composites.Sequence(name="conditional_sequence", children=children)

# we only need to find children if the node is a composite
children = list()
Expand Down
31 changes: 31 additions & 0 deletions test/data/test_conditional.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<py_trees.composites.Selector name="Conditional Test" memory="$(False)">
<!-- Basic condition: equal comparison -->
<conditional if="${mode} == 'advanced'">
<py_trees.behaviours.Success name="Advanced Mode Feature" />
</conditional>

<!-- Basic condition: not equal comparison -->
<conditional if="${mode} != 'basic'">
<py_trees.behaviours.Running name="Non-Basic Mode Feature" />
</conditional>

<!-- Numeric comparison -->
<conditional if="${level} > 5">
<py_trees.behaviours.Periodic name="High Level Feature" n="3" />
</conditional>

<!-- Boolean condition -->
<conditional if="${enable_debug} == True">
<py_trees.behaviours.Success name="Debug Feature" />
</conditional>

<!-- Multiple children in a conditional -->
<conditional if="${include_all} == True">
<py_trees.behaviours.Success name="Feature 1" />
<py_trees.behaviours.Success name="Feature 2" />
<py_trees.behaviours.Success name="Feature 3" />
</conditional>

<!-- Always included node -->
<py_trees.behaviours.Success name="Always Included" />
</py_trees.composites.Selector>
6 changes: 6 additions & 0 deletions test/data/test_conditional_main.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<subtree name="test_conditional" include="$(ament_index_python.get_package_share_directory('py_trees_parser') + '/test/data/test_conditional.xml')" >
<arg name="mode" value="advanced" />
<arg name="level" value="10" />
<arg name="enable_debug" value="True" />
<arg name="include_all" value="True" />
</subtree>
1 change: 1 addition & 0 deletions test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def _setup(tree_file):
"test/data/test_function_parse.xml",
"test/data/test_subtree_main.xml",
"test/data/test_arg_substitution_main.xml",
"test/data/test_conditional_main.xml",
],
)
def test_tree_parser(setup_parser, tree_file):
Expand Down
Loading