diff --git a/CHANGES b/CHANGES index 3bc7f61..7b53e93 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,11 @@ plusminus Change Log +0.8.1 - + + - Fixed bug in evaluating boolean expressions with more than + two terms, reported by ccaapton - fixed Issue #23. + + 0.8.0 - - Added tox for automated testing on multiple Python versions. diff --git a/plusminus/plusminus.py b/plusminus/plusminus.py index 74c00b3..4c22bd0 100644 --- a/plusminus/plusminus.py +++ b/plusminus/plusminus.py @@ -60,7 +60,7 @@ DEFAULT_BASE_FUNCTION_MAP""".split() VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel serial") -__version_info__ = VersionInfo(0, 8, 0, "final", 0) +__version_info__ = VersionInfo(0, 8, 1, "final", 0) __version__ = ".".join(map(str, __version_info__[:3])) # increase recursion limit if not already modified @@ -541,10 +541,10 @@ def evaluate(self): class BinaryLogicalOperator(BinaryNode): opns_map = { - "and": operator.and_, - "or": operator.or_, - "∧": operator.and_, - "∨": operator.or_, + "and": all, + "or": any, + "∧": all, + "∨": any, } def evaluate(self): @@ -552,16 +552,9 @@ def evaluate(self): return self.left_associative_evaluate(self.opns_map) def left_associative_evaluate(self, oper_fn_map): - with _trimming_exception_traceback(): - last = bool(self.tokens[0].evaluate()) - ret = True - for oper, operand in zip(self.tokens[1::2], self.tokens[2::2]): - next_ = bool(operand.evaluate()) - ret = ret and oper_fn_map[oper](last, next_) - if not ret: - break - last = next_ - return ret + oper_fn = oper_fn_map[self.tokens[1]] + ret = oper_fn(bool(t.evaluate()) for t in self.tokens[::2]) + return ret class TernaryComp(TernaryNode): diff --git a/test/arith_tests.py b/test/arith_tests.py index f79f861..c32663b 100644 --- a/test/arith_tests.py +++ b/test/arith_tests.py @@ -83,6 +83,18 @@ def post_parse_evaluate(teststr, result): parser.initialize_variable("temp_c", "(ftemp - 32) * 5 / 9", as_formula=True) parser.initialize_variable("temp_f", "32 + ctemp * 9 / 5", as_formula=True) +parser.runTests( + """\ + False + False or True + True or False + True or True + False or False + False or False or True + """, + postParse=post_parse_evaluate, +) + parser.runTests( """\ sin(rad(30)) diff --git a/test/test_unit.py b/test/test_unit.py index a4f1f00..5bae941 100644 --- a/test/test_unit.py +++ b/test/test_unit.py @@ -89,6 +89,77 @@ def _test_evaluate(self, basic_arithmetic_parser, input_string, expected_value): def test_evaluate(self, basic_arithmetic_parser, input_string, expected_value): self._test_evaluate(basic_arithmetic_parser, input_string, expected_value) + @pytest.mark.parametrize( + "input_string, expected_value", + [ + ("False", False), + ("True", True), + + ("False or False", False), + ("False or True", True), + ("True or False", True), + ("True or True", True), + + ("False or False or False", False), + ("False or True or False", True), + ("True or False or False", True), + ("True or True or False", True), + + ("False or False or True", True), + ("False or True or True", True), + ("True or False or True", True), + ("True or True or True", True), + + ("False and False", False), + ("False and True", False), + ("True and False", False), + ("True and True", True), + + ("False and False and False", False), + ("False and True and False", False), + ("True and False and False", False), + ("True and True and False", False), + + ("False and False and True", False), + ("False and True and True", False), + ("True and False and True", False), + ("True and True and True", True), + + ("False or False and False", False), + ("False or True and False", False), + ("True or False and False", True), + ("True or True and False", True), + + ("False or False and True", False), + ("False or True and True", True), + ("True or False and True", True), + ("True or True and True", True), + + ("False and False or False", False), + ("False and True or False", False), + ("True and False or False", False), + ("True and True or False", True), + + ("False and False or True", True), + ("False and True or True", True), + ("True and False or True", True), + ("True and True or True", True), + + + ("(False or False) and False", False), + ("(False or True) and False", False), + ("(True or False) and False", False), + ("(True or True) and False", False), + + ("(False or False) and True", False), + ("(False or True) and True", True), + ("(True or False) and True", True), + ("(True or True) and True", True), + ], + ) + def test_evaluate_boolean_expressions(self, basic_arithmetic_parser, input_string, expected_value): + self._test_evaluate(basic_arithmetic_parser, input_string, expected_value) + @pytest.mark.parametrize( "input_string, expected_value", [ @@ -272,20 +343,17 @@ def test_set_parser_vars(self, basic_arithmetic_parser): def test_clearing_parser_vars(self, basic_arithmetic_parser): with pytest.raises(NameError): - a_value = basic_arithmetic_parser.evaluate("a") - pytest.fail("unexpected 'a' value {!r}".format(a_value)) + basic_arithmetic_parser.evaluate("a") print("a, b", basic_arithmetic_parser.parse("a, b = 1, 2")) print("c", basic_arithmetic_parser.parse("c = a + b")) print("clear a", basic_arithmetic_parser.parse("a =")) with pytest.raises(NameError): - a_value = basic_arithmetic_parser["a"] - pytest.fail("returned unexpected 'a' value {!r}".format(a_value)) + basic_arithmetic_parser["a"] with pytest.raises(NameError): - a_value = basic_arithmetic_parser.evaluate("c = a + b") - pytest.fail("unexpected 'a' value {!r}".format(a_value)) + basic_arithmetic_parser.evaluate("c = a + b") def test_maximum_formula_depth(self, basic_arithmetic_parser): basic_arithmetic_parser.maximum_formula_depth = 5