diff --git a/launch/launch/substitutions/python_expression.py b/launch/launch/substitutions/python_expression.py index 3ee4fe957..0eeb214a3 100644 --- a/launch/launch/substitutions/python_expression.py +++ b/launch/launch/substitutions/python_expression.py @@ -15,6 +15,7 @@ """Module for the PythonExpression substitution.""" import collections.abc +import itertools import math from typing import Iterable from typing import List @@ -25,6 +26,8 @@ from ..some_substitutions_type import SomeSubstitutionsType from ..substitution import Substitution from ..utilities import ensure_argument_type +from ..utilities import normalize_to_list_of_substitutions +from ..utilities import perform_substitutions @expose_substitution('eval') @@ -47,15 +50,17 @@ def __init__(self, expression: SomeSubstitutionsType) -> None: 'expression', 'PythonExpression') - from ..utilities import normalize_to_list_of_substitutions self.__expression = normalize_to_list_of_substitutions(expression) @classmethod def parse(cls, data: Iterable[SomeSubstitutionsType]): """Parse `PythonExpression` substitution.""" - if len(data) != 1: - raise TypeError('eval substitution expects 1 argument') - return cls, {'expression': data[0]} + if len(data) == 0: + raise TypeError('eval substitution expects 1 or more argument') + elif len(data) == 1: + return cls, {'expression': data[0]} + expression = normalize_to_list_of_substitutions(itertools.chain.from_iterable(data)) + return cls, {'expression': expression} @property def expression(self) -> List[Substitution]: @@ -68,5 +73,12 @@ def describe(self) -> Text: def perform(self, context: LaunchContext) -> Text: """Perform the substitution by evaluating the expression.""" - from ..utilities import perform_substitutions - return str(eval(perform_substitutions(context, self.expression), {}, math.__dict__)) + expressions = [perform_substitutions(context, [exp]) for exp in self.expression] + + if len(expressions) == 3 and (expressions[1] in ['==', '!=']): + left = expressions[0] + right = expressions[2] + expression = f"'{left}' {expressions[1]} '{right}'" + return str(eval(expression, {}, math.__dict__)) + + return str(eval(''.join(expressions), {}, math.__dict__)) diff --git a/launch/test/launch/frontend/test_substitutions.py b/launch/test/launch/frontend/test_substitutions.py index 9fabbc6a9..74220571f 100644 --- a/launch/test/launch/frontend/test_substitutions.py +++ b/launch/test/launch/frontend/test_substitutions.py @@ -21,6 +21,7 @@ from launch import SomeSubstitutionsType from launch import Substitution from launch.actions import ExecuteProcess +from launch.actions import SetLaunchConfiguration from launch.frontend.expose import expose_substitution from launch.frontend.parse_substitution import parse_if_substitutions from launch.frontend.parse_substitution import parse_substitution @@ -205,6 +206,12 @@ def test_eval_subst(): assert 'asdbsd' == expr.perform(LaunchContext()) +def test_eval_subst_empty(): + from lark.visitors import VisitError + with pytest.raises(VisitError): + parse_substitution(r'$(eval)') + + def test_eval_subst_of_math_expr(): subst = parse_substitution(r'$(eval "ceil(1.3)")') assert len(subst) == 1 @@ -213,6 +220,35 @@ def test_eval_subst_of_math_expr(): assert '2' == expr.perform(LaunchContext()) +def test_eval_equal(): + context = LaunchContext() + SetLaunchConfiguration('value', 'abc').execute(context) + + subst = parse_substitution(r"$(eval $(var value) == 'abc')") + assert len(subst) == 1 + expr = subst[0] + assert isinstance(expr, PythonExpression) + assert 'True' == expr.perform(context) + + subst = parse_substitution(r"$(eval $(var value) == 'def')") + assert len(subst) == 1 + expr = subst[0] + assert isinstance(expr, PythonExpression) + assert 'False' == expr.perform(context) + + subst = parse_substitution(r"$(eval $(var value) != 'abc')") + assert len(subst) == 1 + expr = subst[0] + assert isinstance(expr, PythonExpression) + assert 'False' == expr.perform(context) + + subst = parse_substitution(r"$(eval $(var value) != 'def')") + assert len(subst) == 1 + expr = subst[0] + assert isinstance(expr, PythonExpression) + assert 'True' == expr.perform(context) + + def expand_cmd_subs(cmd_subs: List[SomeSubstitutionsType]): return [perform_substitutions_without_context(x) for x in cmd_subs]