Skip to content

Commit

Permalink
Dev for 0.8.1; fix bug evaluating boolean expressions with more than …
Browse files Browse the repository at this point in the history
…2 terms
  • Loading branch information
ptmcg committed Aug 2, 2024
1 parent da4b000 commit e8e22cc
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 21 deletions.
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
23 changes: 8 additions & 15 deletions plusminus/plusminus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -541,27 +541,20 @@ 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):
with _trimming_exception_traceback():
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):
Expand Down
12 changes: 12 additions & 0 deletions test/arith_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
80 changes: 74 additions & 6 deletions test/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
[
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit e8e22cc

Please sign in to comment.