From 85ad0f5ed9055bcc27dedde379809f8e22f9a4b8 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Tue, 19 Nov 2024 14:42:04 +0100 Subject: [PATCH 1/9] Fix pattern matching: pop optionals. --- mathics/core/pattern.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index d16847440..3a77b1ea9 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -757,6 +757,8 @@ def match_expression_with_one_identity( del parms["attributes"] assert new_pattern is not None new_pattern.match(expression=expression, pattern_context=parms) + for optional in optionals: + vars_dict.pop(optional) def basic_match_expression( From c1999ce8b160ac8e20fa0ec9878dbb20992dba98 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Tue, 19 Nov 2024 21:47:03 +0100 Subject: [PATCH 2/9] Fix optional patterns without default values for the given positions. --- mathics/core/pattern.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index 3a77b1ea9..6147f686d 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -724,8 +724,11 @@ def match_expression_with_one_identity( result = defaultvalue_expr.evaluate(evaluation) assert result is not None if result.sameQ(defaultvalue_expr): - return - optionals[key] = result + # The optional pattern has no default value + # for the given position + new_pattern = pat_elem + else: + optionals[key] = result else: return elif new_pattern is not None: From e1dee54db77c38e3e8fdb7c42297985c4636d815 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Tue, 19 Nov 2024 22:27:02 +0100 Subject: [PATCH 3/9] Fix optional patterns without default values: Newpattern only if not set. --- mathics/core/pattern.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index 6147f686d..acf34f646 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -724,9 +724,10 @@ def match_expression_with_one_identity( result = defaultvalue_expr.evaluate(evaluation) assert result is not None if result.sameQ(defaultvalue_expr): - # The optional pattern has no default value - # for the given position - new_pattern = pat_elem + if new_pattern is None: + # The optional pattern has no default value + # for the given position + new_pattern = pat_elem else: optionals[key] = result else: From 0a89945523224615a7d7d6c27a402fdf7fdf0ade Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Tue, 19 Nov 2024 23:39:10 +0100 Subject: [PATCH 4/9] Do not allow optional patterns for more than one argument. --- mathics/core/pattern.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index acf34f646..2fa2ce80c 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -701,7 +701,12 @@ def match_expression_with_one_identity( isinstance(pat_elem, PatternObject) and pat_elem.get_head() == SymbolOptional ): - if len(pat_elem.elements) == 2: + if optionals: + # A default pattern already exists + # Do not use the second one + if new_pattern is None: + new_pattern = pat_elem + elif len(pat_elem.elements) == 2: pat, value = pat_elem.elements if isinstance(pat, Pattern): key = pat.elements[0].atom.name # type: ignore[attr-defined] From eff7ebc8873bebc1b308863512b034d9de40cd09 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Fri, 22 Nov 2024 12:42:47 +0100 Subject: [PATCH 5/9] Add tests for the fixes. --- test/core/test_patterns.py | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test/core/test_patterns.py diff --git a/test/core/test_patterns.py b/test/core/test_patterns.py new file mode 100644 index 000000000..12e8453c1 --- /dev/null +++ b/test/core/test_patterns.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for mathics pattern matching +""" + +import sys +import time +from test.helper import check_evaluation, evaluate + +import pytest + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + # Two default arguments (linear) + ("MatchQ[1, a_.+b_.*x_]",None,"True",None), + ("MatchQ[x, a_.+b_.*x_]",None,"True",None), + ("MatchQ[2*x, a_.+b_.*x_]",None,"True",None), + ("MatchQ[1+x, a_.+b_.*x_]",None,"True",None), + ("MatchQ[1+2*x, a_.+b_.*x_]",None,"True",None), + # Default argument (power) + ("MatchQ[1, x_^m_.]",None,"True",None), + ("MatchQ[x, x_^m_.]",None,"True",None), + ("MatchQ[x^1, x_^m_.]",None,"True",None), + ("MatchQ[x^2, x_^m_.]",None,"True",None), + # Two default arguments (power) + ("MatchQ[1, x_.^m_.]",None,"True",None), + ("MatchQ[x, x_.^m_.]",None,"True",None), + ("MatchQ[x^1, x_.^m_.]",None,"True",None), + ("MatchQ[x^2, x_.^m_.]",None,"True",None), + # Two default arguments (no non-head) + ("MatchQ[1, a_.+b_.]",None,"True",None), + ("MatchQ[x, a_.+b_.]",None,"True",None), + ("MatchQ[1+x, a_.+b_.]",None,"True",None), + ("MatchQ[1+2*x, a_.+b_.]",None,"True",None), + ("MatchQ[1, a_.+b_.]",None,"True",None), + ("MatchQ[x, a_.*b_.]",None,"True",None), + ("MatchQ[2*x, a_.*b_.]",None,"True",None), + ], +) +def test_patterns(str_expr, msgs, str_expected, fail_msg): + """patterns""" + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From bac8e035d23d8b2ab0d97612864fd78cd1e70814 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Fri, 22 Nov 2024 12:45:53 +0100 Subject: [PATCH 6/9] Run isort and black. --- test/core/test_patterns.py | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/test/core/test_patterns.py b/test/core/test_patterns.py index 12e8453c1..48fafddc7 100644 --- a/test/core/test_patterns.py +++ b/test/core/test_patterns.py @@ -9,33 +9,34 @@ import pytest + @pytest.mark.parametrize( ("str_expr", "msgs", "str_expected", "fail_msg"), [ # Two default arguments (linear) - ("MatchQ[1, a_.+b_.*x_]",None,"True",None), - ("MatchQ[x, a_.+b_.*x_]",None,"True",None), - ("MatchQ[2*x, a_.+b_.*x_]",None,"True",None), - ("MatchQ[1+x, a_.+b_.*x_]",None,"True",None), - ("MatchQ[1+2*x, a_.+b_.*x_]",None,"True",None), + ("MatchQ[1, a_.+b_.*x_]", None, "True", None), + ("MatchQ[x, a_.+b_.*x_]", None, "True", None), + ("MatchQ[2*x, a_.+b_.*x_]", None, "True", None), + ("MatchQ[1+x, a_.+b_.*x_]", None, "True", None), + ("MatchQ[1+2*x, a_.+b_.*x_]", None, "True", None), # Default argument (power) - ("MatchQ[1, x_^m_.]",None,"True",None), - ("MatchQ[x, x_^m_.]",None,"True",None), - ("MatchQ[x^1, x_^m_.]",None,"True",None), - ("MatchQ[x^2, x_^m_.]",None,"True",None), + ("MatchQ[1, x_^m_.]", None, "True", None), + ("MatchQ[x, x_^m_.]", None, "True", None), + ("MatchQ[x^1, x_^m_.]", None, "True", None), + ("MatchQ[x^2, x_^m_.]", None, "True", None), # Two default arguments (power) - ("MatchQ[1, x_.^m_.]",None,"True",None), - ("MatchQ[x, x_.^m_.]",None,"True",None), - ("MatchQ[x^1, x_.^m_.]",None,"True",None), - ("MatchQ[x^2, x_.^m_.]",None,"True",None), + ("MatchQ[1, x_.^m_.]", None, "True", None), + ("MatchQ[x, x_.^m_.]", None, "True", None), + ("MatchQ[x^1, x_.^m_.]", None, "True", None), + ("MatchQ[x^2, x_.^m_.]", None, "True", None), # Two default arguments (no non-head) - ("MatchQ[1, a_.+b_.]",None,"True",None), - ("MatchQ[x, a_.+b_.]",None,"True",None), - ("MatchQ[1+x, a_.+b_.]",None,"True",None), - ("MatchQ[1+2*x, a_.+b_.]",None,"True",None), - ("MatchQ[1, a_.+b_.]",None,"True",None), - ("MatchQ[x, a_.*b_.]",None,"True",None), - ("MatchQ[2*x, a_.*b_.]",None,"True",None), + ("MatchQ[1, a_.+b_.]", None, "True", None), + ("MatchQ[x, a_.+b_.]", None, "True", None), + ("MatchQ[1+x, a_.+b_.]", None, "True", None), + ("MatchQ[1+2*x, a_.+b_.]", None, "True", None), + ("MatchQ[1, a_.+b_.]", None, "True", None), + ("MatchQ[x, a_.*b_.]", None, "True", None), + ("MatchQ[2*x, a_.*b_.]", None, "True", None), ], ) def test_patterns(str_expr, msgs, str_expected, fail_msg): From 0f9786a67372b432c861298bf3f699e830d108e0 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Fri, 22 Nov 2024 14:10:45 +0100 Subject: [PATCH 7/9] Add extended tests for function patterns. --- test/core/test_patterns.py | 48 +++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/test/core/test_patterns.py b/test/core/test_patterns.py index 48fafddc7..4c0bac0c8 100644 --- a/test/core/test_patterns.py +++ b/test/core/test_patterns.py @@ -34,7 +34,7 @@ ("MatchQ[x, a_.+b_.]", None, "True", None), ("MatchQ[1+x, a_.+b_.]", None, "True", None), ("MatchQ[1+2*x, a_.+b_.]", None, "True", None), - ("MatchQ[1, a_.+b_.]", None, "True", None), + ("MatchQ[1, a_.*b_.]", None, "True", None), ("MatchQ[x, a_.*b_.]", None, "True", None), ("MatchQ[2*x, a_.*b_.]", None, "True", None), ], @@ -50,3 +50,49 @@ def test_patterns(str_expr, msgs, str_expected, fail_msg): failure_message=fail_msg, expected_messages=msgs, ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + # Rule + ("rule=A[a_.+B[b_.*x_]]->{a,b,x};", None, "Null", None), + # Two default arguments (linear) + ("A[B[1]] /. rule", None, "{0, 1, 1}", None), + ("A[B[x]] /. rule", None, "{0, 1, x}", None), + ("A[B[2*x]] /. rule", None, "{0, x, 2}", None), + ("A[1+B[x]] /. rule", None, "{1, 1, x}", None), + ("A[1+B[2*x]] /. rule", None, "{1, x, 2}", None), + # Default argument (power) + ("rule=A[x_^n_.]->{x,n};", None, "Null", None), + ("A[1] /. rule", None, "{1, 1}", None), + ("A[x] /. rule", None, "{x, 1}", None), + ("A[x^1] /. rule", None, "{x, 1}", None), + ("A[x^2] /. rule", None, "{x, 2}", None), + # Two default arguments (power) + ("rule=A[x_.^n_.]->{x,n};", None, "Null", None), + ("A[] /. rule", None, "A[]", None), + ("A[1] /. rule", None, "{1, 1}", None), + ("A[x] /. rule", None, "{x, 1}", None), + ("A[x^1] /. rule", None, "{x, 1}", None), + ("A[x^2] /. rule", None, "{x, 2}", None), + # Two default arguments (no non-head) + ("rule=A[a_. + B[b_.*x_.]]->{a,b,x};", None, "Null", None), + ("A[B[]] /. rule", None, "A[B[]]", None), + ("A[B[1]] /. rule", None, "{0, 1, 1}", None), + ("A[B[x]] /. rule", None, "{0, 1, x}", None), + ("A[1 + B[x]] /. rule", None, "{1, 1, x}", None), + ("A[1 + B[2*x]] /. rule", None, "{1, 2, x}", None), + ], +) +def test_pattern_substitutions(str_expr, msgs, str_expected, fail_msg): + """pattern_substitutions""" + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) From ae7ea506a68bcaa6a7339c3c4a7f2d92442e05e3 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Fri, 22 Nov 2024 14:15:05 +0100 Subject: [PATCH 8/9] Minor housekeeping. --- test/core/test_patterns.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/core/test_patterns.py b/test/core/test_patterns.py index 4c0bac0c8..ef6eff4ef 100644 --- a/test/core/test_patterns.py +++ b/test/core/test_patterns.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Unit tests for mathics pattern matching +Unit tests for mathics.core.pattern """ import sys @@ -55,9 +55,8 @@ def test_patterns(str_expr, msgs, str_expected, fail_msg): @pytest.mark.parametrize( ("str_expr", "msgs", "str_expected", "fail_msg"), [ - # Rule - ("rule=A[a_.+B[b_.*x_]]->{a,b,x};", None, "Null", None), # Two default arguments (linear) + ("rule=A[a_.+B[b_.*x_]]->{a,b,x};", None, "Null", None), ("A[B[1]] /. rule", None, "{0, 1, 1}", None), ("A[B[x]] /. rule", None, "{0, 1, x}", None), ("A[B[2*x]] /. rule", None, "{0, x, 2}", None), From 8a7a56478528af0bfc9a2e532eba0e73b92d11c8 Mon Sep 17 00:00:00 2001 From: Aravindh Krishnamoorthy Date: Sat, 23 Nov 2024 18:15:10 +0100 Subject: [PATCH 9/9] Move Mathics tests to toplevel; rewrite mathics.eval.patterns tests in Python with parse --- test/builtin/test_testing_expressions.py | 42 ++++++++++ test/core/test_patterns.py | 97 ------------------------ test/core/test_rules.py | 45 +++++++++++ test/eval/test_patterns.py | 57 ++++++++++++++ 4 files changed, 144 insertions(+), 97 deletions(-) delete mode 100644 test/core/test_patterns.py create mode 100644 test/eval/test_patterns.py diff --git a/test/builtin/test_testing_expressions.py b/test/builtin/test_testing_expressions.py index fa4c1cfa4..8efe40db5 100644 --- a/test/builtin/test_testing_expressions.py +++ b/test/builtin/test_testing_expressions.py @@ -135,3 +135,45 @@ def test_private_doctests_numerical_properties(str_expr, msgs, str_expected, fai failure_message=fail_msg, expected_messages=msgs, ) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + # Two default arguments (linear) + ("MatchQ[1, a_.+b_.*x_]", None, "True", None), + ("MatchQ[x, a_.+b_.*x_]", None, "True", None), + ("MatchQ[2*x, a_.+b_.*x_]", None, "True", None), + ("MatchQ[1+x, a_.+b_.*x_]", None, "True", None), + ("MatchQ[1+2*x, a_.+b_.*x_]", None, "True", None), + # Default argument (power) + ("MatchQ[1, x_^m_.]", None, "True", None), + ("MatchQ[x, x_^m_.]", None, "True", None), + ("MatchQ[x^1, x_^m_.]", None, "True", None), + ("MatchQ[x^2, x_^m_.]", None, "True", None), + # Two default arguments (power) + ("MatchQ[1, x_.^m_.]", None, "True", None), + ("MatchQ[x, x_.^m_.]", None, "True", None), + ("MatchQ[x^1, x_.^m_.]", None, "True", None), + ("MatchQ[x^2, x_.^m_.]", None, "True", None), + # Two default arguments (no non-head) + ("MatchQ[1, a_.+b_.]", None, "True", None), + ("MatchQ[x, a_.+b_.]", None, "True", None), + ("MatchQ[1+x, a_.+b_.]", None, "True", None), + ("MatchQ[1+2*x, a_.+b_.]", None, "True", None), + ("MatchQ[1, a_.*b_.]", None, "True", None), + ("MatchQ[x, a_.*b_.]", None, "True", None), + ("MatchQ[2*x, a_.*b_.]", None, "True", None), + ], +) +def test_matchq(str_expr, msgs, str_expected, fail_msg): + """text_expressions.matchq""" + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/core/test_patterns.py b/test/core/test_patterns.py deleted file mode 100644 index ef6eff4ef..000000000 --- a/test/core/test_patterns.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Unit tests for mathics.core.pattern -""" - -import sys -import time -from test.helper import check_evaluation, evaluate - -import pytest - - -@pytest.mark.parametrize( - ("str_expr", "msgs", "str_expected", "fail_msg"), - [ - # Two default arguments (linear) - ("MatchQ[1, a_.+b_.*x_]", None, "True", None), - ("MatchQ[x, a_.+b_.*x_]", None, "True", None), - ("MatchQ[2*x, a_.+b_.*x_]", None, "True", None), - ("MatchQ[1+x, a_.+b_.*x_]", None, "True", None), - ("MatchQ[1+2*x, a_.+b_.*x_]", None, "True", None), - # Default argument (power) - ("MatchQ[1, x_^m_.]", None, "True", None), - ("MatchQ[x, x_^m_.]", None, "True", None), - ("MatchQ[x^1, x_^m_.]", None, "True", None), - ("MatchQ[x^2, x_^m_.]", None, "True", None), - # Two default arguments (power) - ("MatchQ[1, x_.^m_.]", None, "True", None), - ("MatchQ[x, x_.^m_.]", None, "True", None), - ("MatchQ[x^1, x_.^m_.]", None, "True", None), - ("MatchQ[x^2, x_.^m_.]", None, "True", None), - # Two default arguments (no non-head) - ("MatchQ[1, a_.+b_.]", None, "True", None), - ("MatchQ[x, a_.+b_.]", None, "True", None), - ("MatchQ[1+x, a_.+b_.]", None, "True", None), - ("MatchQ[1+2*x, a_.+b_.]", None, "True", None), - ("MatchQ[1, a_.*b_.]", None, "True", None), - ("MatchQ[x, a_.*b_.]", None, "True", None), - ("MatchQ[2*x, a_.*b_.]", None, "True", None), - ], -) -def test_patterns(str_expr, msgs, str_expected, fail_msg): - """patterns""" - check_evaluation( - str_expr, - str_expected, - to_string_expr=True, - to_string_expected=True, - hold_expected=True, - failure_message=fail_msg, - expected_messages=msgs, - ) - - -@pytest.mark.parametrize( - ("str_expr", "msgs", "str_expected", "fail_msg"), - [ - # Two default arguments (linear) - ("rule=A[a_.+B[b_.*x_]]->{a,b,x};", None, "Null", None), - ("A[B[1]] /. rule", None, "{0, 1, 1}", None), - ("A[B[x]] /. rule", None, "{0, 1, x}", None), - ("A[B[2*x]] /. rule", None, "{0, x, 2}", None), - ("A[1+B[x]] /. rule", None, "{1, 1, x}", None), - ("A[1+B[2*x]] /. rule", None, "{1, x, 2}", None), - # Default argument (power) - ("rule=A[x_^n_.]->{x,n};", None, "Null", None), - ("A[1] /. rule", None, "{1, 1}", None), - ("A[x] /. rule", None, "{x, 1}", None), - ("A[x^1] /. rule", None, "{x, 1}", None), - ("A[x^2] /. rule", None, "{x, 2}", None), - # Two default arguments (power) - ("rule=A[x_.^n_.]->{x,n};", None, "Null", None), - ("A[] /. rule", None, "A[]", None), - ("A[1] /. rule", None, "{1, 1}", None), - ("A[x] /. rule", None, "{x, 1}", None), - ("A[x^1] /. rule", None, "{x, 1}", None), - ("A[x^2] /. rule", None, "{x, 2}", None), - # Two default arguments (no non-head) - ("rule=A[a_. + B[b_.*x_.]]->{a,b,x};", None, "Null", None), - ("A[B[]] /. rule", None, "A[B[]]", None), - ("A[B[1]] /. rule", None, "{0, 1, 1}", None), - ("A[B[x]] /. rule", None, "{0, 1, x}", None), - ("A[1 + B[x]] /. rule", None, "{1, 1, x}", None), - ("A[1 + B[2*x]] /. rule", None, "{1, 2, x}", None), - ], -) -def test_pattern_substitutions(str_expr, msgs, str_expected, fail_msg): - """pattern_substitutions""" - check_evaluation( - str_expr, - str_expected, - to_string_expr=True, - to_string_expected=True, - hold_expected=True, - failure_message=fail_msg, - expected_messages=msgs, - ) diff --git a/test/core/test_rules.py b/test/core/test_rules.py index 280b5849d..6a8541561 100644 --- a/test/core/test_rules.py +++ b/test/core/test_rules.py @@ -184,3 +184,48 @@ def test_flat_on_rules(str_expr, str_expected, msg): @pytest.mark.xfail def test_default_optional_on_rules(str_expr, str_expected, msg): check_evaluation(str_expr, str_expected, failure_message=msg) + + +@pytest.mark.parametrize( + ("str_expr", "msgs", "str_expected", "fail_msg"), + [ + # Two default arguments (linear) + ("rule=A[a_.+B[b_.*x_]]->{a,b,x};", None, "Null", None), + ("A[B[1]] /. rule", None, "{0, 1, 1}", None), + ("A[B[x]] /. rule", None, "{0, 1, x}", None), + ("A[B[2*x]] /. rule", None, "{0, x, 2}", None), + ("A[1+B[x]] /. rule", None, "{1, 1, x}", None), + ("A[1+B[2*x]] /. rule", None, "{1, x, 2}", None), + # Default argument (power) + ("rule=A[x_^n_.]->{x,n};", None, "Null", None), + ("A[1] /. rule", None, "{1, 1}", None), + ("A[x] /. rule", None, "{x, 1}", None), + ("A[x^1] /. rule", None, "{x, 1}", None), + ("A[x^2] /. rule", None, "{x, 2}", None), + # Two default arguments (power) + ("rule=A[x_.^n_.]->{x,n};", None, "Null", None), + ("A[] /. rule", None, "A[]", None), + ("A[1] /. rule", None, "{1, 1}", None), + ("A[x] /. rule", None, "{x, 1}", None), + ("A[x^1] /. rule", None, "{x, 1}", None), + ("A[x^2] /. rule", None, "{x, 2}", None), + # Two default arguments (no non-head) + ("rule=A[a_. + B[b_.*x_.]]->{a,b,x};", None, "Null", None), + ("A[B[]] /. rule", None, "A[B[]]", None), + ("A[B[1]] /. rule", None, "{0, 1, 1}", None), + ("A[B[x]] /. rule", None, "{0, 1, x}", None), + ("A[1 + B[x]] /. rule", None, "{1, 1, x}", None), + ("A[1 + B[2*x]] /. rule", None, "{1, 2, x}", None), + ], +) +def test_pattern_rules(str_expr, msgs, str_expected, fail_msg): + """pattern_rules""" + check_evaluation( + str_expr, + str_expected, + to_string_expr=True, + to_string_expected=True, + hold_expected=True, + failure_message=fail_msg, + expected_messages=msgs, + ) diff --git a/test/eval/test_patterns.py b/test/eval/test_patterns.py new file mode 100644 index 000000000..bb2ae9efb --- /dev/null +++ b/test/eval/test_patterns.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" +Unit tests for mathics.eval.patterns +""" + +from test.helper import session + +import pytest + +from mathics.core.definitions import Definitions +from mathics.core.parser import MathicsSingleLineFeeder, parse +from mathics.core.pattern import ExpressionPattern +from mathics.eval.patterns import Matcher + +# Preload the Mathics definitions +defintions = Definitions(True) + + +def check_pattern(str_expr, str_pattern): + expr = parse(defintions, MathicsSingleLineFeeder(str_expr)) + pattern = ExpressionPattern(parse(defintions, MathicsSingleLineFeeder(str_pattern))) + ret = Matcher(pattern, session.evaluation).match(expr, session.evaluation) + assert ret == True + + +@pytest.mark.parametrize( + ("str_expr", "str_pattern"), + [ + # Two default arguments (linear) + ("1", "a_.+b_.*x_"), + ("x", "a_.+b_.*x_"), + ("2*x", "a_.+b_.*x_"), + ("1+x", "a_.+b_.*x_"), + ("1+2*x", "a_.+b_.*x_"), + # Default argument (power) + ("1", "x_^m_."), + ("x", "x_^m_."), + ("x^1", "x_^m_."), + ("x^2", "x_^m_."), + # Two default arguments (power) + ("1", "x_.^m_."), + ("x", "x_.^m_."), + ("x^1", "x_.^m_."), + ("x^2", "x_.^m_."), + # Two default arguments (no non-head) + ("1", "a_.+b_."), + ("x", "a_.+b_."), + ("1+x", "a_.+b_."), + ("1+2*x", "a_.+b_."), + ("1", "a_.*b_."), + ("x", "a_.*b_."), + ("2*x", "a_.*b_."), + ], +) +def test_eval_patterns(str_expr, str_pattern): + """eval_patterns""" + check_pattern(str_expr, str_pattern)