From d49b2c26f25324eadf833f39e33c3f8ea1ce9426 Mon Sep 17 00:00:00 2001 From: "S.Lott" Date: Tue, 20 May 2025 18:20:42 -0400 Subject: [PATCH 1/2] Change handling of "macro" Any CELEvalError from the list (on the left side of the macro) will now be exposed by the macro instead of throwing a `TypeError` This fixes issue #41 and issue #75 --- src/celpy/celparser.py | 7 +- src/celpy/evaluation.py | 123 ++++++++++++++++++----------------- tests/test_evaluation.py | 9 +++ tests/test_parser.py | 1 + type_check/lineprecision.txt | 6 +- 5 files changed, 77 insertions(+), 69 deletions(-) diff --git a/src/celpy/celparser.py b/src/celpy/celparser.py index d5c80e0..29583ac 100644 --- a/src/celpy/celparser.py +++ b/src/celpy/celparser.py @@ -283,11 +283,8 @@ def member_dot_arg(self, tree: lark.Tree) -> None: else: exprlist = "" right = cast(lark.Token, tree.children[1]).value - if self.stack: - left = self.stack.pop() - self.stack.append(f"{left}.{right}({exprlist})") - else: - self.stack.append(f".{right}({exprlist})") + left = self.stack.pop() + self.stack.append(f"{left}.{right}({exprlist})") def member_index(self, tree: lark.Tree) -> None: right = self.stack.pop() diff --git a/src/celpy/evaluation.py b/src/celpy/evaluation.py index 84b91bb..edba729 100644 --- a/src/celpy/evaluation.py +++ b/src/celpy/evaluation.py @@ -1928,69 +1928,70 @@ def member_dot_arg(self, tree: lark.Tree) -> Result: member_tree, method_name_token = cast(Tuple[lark.Tree, lark.Token], tree.children[:2]) - if method_name_token.value == "map": + if method_name_token.value in {"map", "filter", "all", "exists", "exists_one", "reduce", "min"}: member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - sub_expr = self.build_macro_eval(tree) - mapping = cast(Iterable[celpy.celtypes.Value], map(sub_expr, member_list)) - result = celpy.celtypes.ListType(mapping) - return result - - elif method_name_token.value == "filter": - member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - sub_expr = self.build_macro_eval(tree) - result = celpy.celtypes.ListType(filter(sub_expr, member_list)) - return result - - elif method_name_token.value == "all": - member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - and_oper = cast( - CELBoolFunction, - eval_error("no such overload", TypeError)( - celpy.celtypes.logical_and) - ) - sub_expr = self.build_ss_macro_eval(tree) - reduction = reduce(and_oper, map(sub_expr, member_list), celpy.celtypes.BoolType(True)) - return reduction - - elif method_name_token.value == "exists": - member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - or_oper = cast( - CELBoolFunction, - eval_error("no such overload", TypeError)( - celpy.celtypes.logical_or) - ) - sub_expr = self.build_ss_macro_eval(tree) - reduction = reduce(or_oper, map(sub_expr, member_list), celpy.celtypes.BoolType(False)) - return reduction + if isinstance(member_list, CELEvalError): + return member_list + + if method_name_token.value == "map": + sub_expr = self.build_macro_eval(tree) + mapping = cast(Iterable[celpy.celtypes.Value], map(sub_expr, member_list)) + result = celpy.celtypes.ListType(mapping) + return result + + elif method_name_token.value == "filter": + sub_expr = self.build_macro_eval(tree) + result = celpy.celtypes.ListType(filter(sub_expr, member_list)) + return result + + elif method_name_token.value == "all": + sub_expr = self.build_ss_macro_eval(tree) + and_oper = cast( + CELBoolFunction, + eval_error("no such overload", TypeError)( + celpy.celtypes.logical_and) + ) + reduction = reduce(and_oper, map(sub_expr, member_list), celpy.celtypes.BoolType(True)) + return reduction + + elif method_name_token.value == "exists": + sub_expr = self.build_ss_macro_eval(tree) + or_oper = cast( + CELBoolFunction, + eval_error("no such overload", TypeError)( + celpy.celtypes.logical_or) + ) + reduction = reduce(or_oper, map(sub_expr, member_list), celpy.celtypes.BoolType(False)) + return reduction + + elif method_name_token.value == "exists_one": + # Is there exactly 1? + sub_expr = self.build_macro_eval(tree) + count = sum(1 for value in member_list if bool(sub_expr(value))) + return celpy.celtypes.BoolType(count == 1) + + elif method_name_token.value == "reduce": + # Apply a function to reduce the list to a single value. + # The `tree` is a `member_dot_arg` construct with (member, method_name, args) + # The args have two variables and two expressions. + reduce_expr, init_expr_tree = self.build_reduce_macro_eval(tree) + initial_value = self.visit(init_expr_tree) + reduction = reduce(reduce_expr, member_list, initial_value) + return reduction + + elif method_name_token.value == "min": + # Special case of "reduce()" + # with .min() -> .reduce(r, i, int_max, r < i ? r : i) + try: + # Note. The Result type includes None, which will raise an exception. + reduction = min(member_list) # type: ignore [type-var] + except ValueError as ex: + err = "Attempt to reduce an empty sequence or a sequence with a None value" + reduction = CELEvalError(err, ex.__class__, ex.args, tree=tree) + return reduction - elif method_name_token.value == "exists_one": - # Is there exactly 1? - member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - sub_expr = self.build_macro_eval(tree) - count = sum(1 for value in member_list if bool(sub_expr(value))) - return celpy.celtypes.BoolType(count == 1) - - elif method_name_token.value == "reduce": - # Apply a function to reduce the list to a single value. - # The `tree` is a `member_dot_arg` construct with (member, method_name, args) - # The args have two variables and two expressions. - member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - reduce_expr, init_expr_tree = self.build_reduce_macro_eval(tree) - initial_value = self.visit(init_expr_tree) - reduction = reduce(reduce_expr, member_list, initial_value) - return reduction - - elif method_name_token.value == "min": - # Special case of "reduce()" - # with .min() -> .reduce(r, i, int_max, r < i ? r : i) - member_list = cast(celpy.celtypes.ListType, self.visit(member_tree)) - try: - # Note. The Result type includes None, which will raise an exception. - reduction = min(member_list) # type: ignore [type-var] - except ValueError as ex: - err = "Attempt to reduce an empty sequence or a sequence with a None value" - reduction = CELEvalError(err, ex.__class__, ex.args, tree=tree) - return reduction + else: + raise RuntimeError("Internal Design Error") # pragma: no cover else: # Not a macro: a method evaluation. diff --git a/tests/test_evaluation.py b/tests/test_evaluation.py index 80bd8e7..c5d8bab 100644 --- a/tests/test_evaluation.py +++ b/tests/test_evaluation.py @@ -1631,6 +1631,15 @@ def test_member_dot_arg_all(monkeypatch): ) +def test_member_dot_arg_all_issue_41(monkeypatch): + """The filter macro CelEvalError().all(x, x) == CelEvalError()""" + tree = macro_member_tree("all") + the_error = CELEvalError() + eval_0 = Evaluator(tree, activation=Mock(resolve_variable=Mock(return_value=the_error))) + assert eval_0.member_dot_arg(tree.children[0]) is the_error + + + def test_member_dot_arg_exists(monkeypatch): """The filter macro [true, false].exists(x, x) == [true]""" visit = Mock( diff --git a/tests/test_parser.py b/tests/test_parser.py index 63e321c..3accfde 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -448,3 +448,4 @@ def test_dump_issue_35(): def test_tree_dump(parser): ast = parser.parse("-(3*4+5-1/2%3==1)?name[index]:f(1,2)||false&&true") assert tree_dump(ast) == '- (3 * 4 + 5 - 1 / 2 % 3 == 1) ? name[index] : f(1, 2) || false && true' + diff --git a/type_check/lineprecision.txt b/type_check/lineprecision.txt index 6eafec4..5e06b3b 100644 --- a/type_check/lineprecision.txt +++ b/type_check/lineprecision.txt @@ -4,8 +4,8 @@ celpy 293 76 0 4 213 0 celpy.__main__ 465 172 7 42 244 0 celpy.adapter 137 35 3 9 85 5 celpy.c7nlib 1584 340 15 154 1075 0 -celpy.celparser 402 208 2 23 169 0 +celpy.celparser 401 207 2 22 170 0 celpy.celtypes 1503 438 14 221 791 39 -celpy.evaluation 2471 839 33 176 1408 15 +celpy.evaluation 2472 835 33 177 1411 16 xlate 0 0 0 0 0 0 -xlate.c7n_to_cel 1730 387 102 145 1091 5 +xlate.c7n_to_cel 1730 377 102 145 1101 5 From 27ee409a20c1709d79c7e0bd8fdf52e5d873472e Mon Sep 17 00:00:00 2001 From: "S.Lott" Date: Tue, 20 May 2025 18:21:14 -0400 Subject: [PATCH 2/2] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3e223f2..120531f 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ type_check/lineprecision.txt # PyCharm Stuff .idea/* +demo/issue_41.py