diff --git a/mathics/builtin/exp_structure/general.py b/mathics/builtin/exp_structure/general.py
index 05afab9b1..70404a036 100644
--- a/mathics/builtin/exp_structure/general.py
+++ b/mathics/builtin/exp_structure/general.py
@@ -8,7 +8,7 @@
from mathics.core.exceptions import InvalidLevelspecError
from mathics.core.expression import Evaluation, Expression
from mathics.core.list import ListExpression
-from mathics.core.rules import Pattern
+from mathics.core.rules import BasePattern
from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue
from mathics.core.systemsymbols import SymbolMap
from mathics.eval.parts import python_levelspec, walk_levels
@@ -114,7 +114,7 @@ class FreeQ(Builtin):
def eval(self, expr, form, evaluation: Evaluation):
"FreeQ[expr_, form_]"
- form = Pattern.create(form)
+ form = BasePattern.create(form)
if expr.is_free(form, evaluation):
return SymbolTrue
else:
diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py
index f84ac6232..ff135e53a 100644
--- a/mathics/builtin/list/constructing.py
+++ b/mathics/builtin/list/constructing.py
@@ -13,7 +13,7 @@
from mathics.builtin.box.layout import RowBox
from mathics.core.atoms import Integer, is_integer_rational_or_real
from mathics.core.attributes import A_HOLD_FIRST, A_LISTABLE, A_LOCKED, A_PROTECTED
-from mathics.core.builtin import Builtin, IterationFunction, Pattern
+from mathics.core.builtin import BasePattern, Builtin, IterationFunction
from mathics.core.convert.expression import to_expression
from mathics.core.convert.sympy import from_sympy
from mathics.core.element import ElementsProperties
@@ -431,7 +431,7 @@ def eval(self, expr, patterns, f, evaluation: Evaluation):
"Reap[expr_, {patterns___}, f_]"
patterns = patterns.get_sequence()
- sown = [(Pattern.create(pattern), []) for pattern in patterns]
+ sown = [(BasePattern.create(pattern), []) for pattern in patterns]
def listener(e, tag):
result = False
diff --git a/mathics/builtin/numbers/algebra.py b/mathics/builtin/numbers/algebra.py
index 1a6df8541..6fb082bcf 100644
--- a/mathics/builtin/numbers/algebra.py
+++ b/mathics/builtin/numbers/algebra.py
@@ -33,7 +33,7 @@
MATHICS3_NEG_INFINITY,
)
from mathics.core.list import ListExpression
-from mathics.core.rules import Pattern
+from mathics.core.rules import BasePattern
from mathics.core.symbols import (
Atom,
Symbol,
@@ -549,11 +549,11 @@ def coeff_power_internal(
else:
return [([], expr)]
if len(var_exprs) == 1:
- target_pat = Pattern.create(var_exprs[0])
+ target_pat = BasePattern.create(var_exprs[0])
var_pats = [target_pat]
else:
- target_pat = Pattern.create(Expression(SymbolAlternatives, *var_exprs))
- var_pats = [Pattern.create(var) for var in var_exprs]
+ target_pat = BasePattern.create(Expression(SymbolAlternatives, *var_exprs))
+ var_pats = [BasePattern.create(var) for var in var_exprs]
# ###### Auxiliary functions #########
def key_powers(lst: list) -> Union[int, float]:
@@ -1172,7 +1172,7 @@ def eval_patt(self, expr, target, evaluation: Evaluation, options: dict):
return
if target:
- kwargs["pattern"] = Pattern.create(target)
+ kwargs["pattern"] = BasePattern.create(target)
kwargs["evaluation"] = evaluation
return expand(expr, True, False, **kwargs)
@@ -1235,7 +1235,7 @@ def eval_patt(self, expr, target, evaluation: Evaluation, options: dict):
return
if target:
- kwargs["pattern"] = Pattern.create(target)
+ kwargs["pattern"] = BasePattern.create(target)
kwargs["evaluation"] = evaluation
return expand(expr, numer=True, denom=True, deep=True, **kwargs)
diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py
index 7fb5c340c..7e1df9af2 100644
--- a/mathics/builtin/numbers/calculus.py
+++ b/mathics/builtin/numbers/calculus.py
@@ -46,7 +46,7 @@
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.number import MACHINE_EPSILON, dps
-from mathics.core.rules import Pattern
+from mathics.core.rules import BasePattern
from mathics.core.symbols import (
BaseElement,
Symbol,
@@ -228,7 +228,7 @@ def eval(self, f, x, evaluation: Evaluation):
if f == x:
return Integer1
- x_pattern = Pattern.create(x)
+ x_pattern = BasePattern.create(x)
if f.is_free(x_pattern, evaluation):
return Integer0
@@ -1917,7 +1917,7 @@ def eval_times(
nummax.get_int_value(),
den.get_int_value(),
)
- x_pattern = Pattern.create(x)
+ x_pattern = BasePattern.create(x)
incompat_series = []
max_exponent = Integer(int(series[2] / series[3] + 1))
if coeff.get_head() is SymbolSequence:
@@ -2263,7 +2263,7 @@ def eval(self, eqs, vars, evaluation: Evaluation):
vars = []
vars_sympy = []
for var, var_sympy in zip(all_vars, all_vars_sympy):
- pattern = Pattern.create(var)
+ pattern = BasePattern.create(var)
if not eqs.is_free(pattern, evaluation):
vars.append(var)
vars_sympy.append(var_sympy)
diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py
index 7117bf7a8..d57d140e5 100644
--- a/mathics/builtin/patterns.py
+++ b/mathics/builtin/patterns.py
@@ -37,9 +37,6 @@
The attributes 'Flat', 'Orderless', and 'OneIdentity' affect pattern matching.
"""
-# This tells documentation how to sort this module
-sort_order = "mathics.builtin.rules-and-patterns"
-
from typing import Callable, List, Optional as OptionalType, Tuple, Union
from mathics.core.atoms import Integer, Number, Rational, Real, String
@@ -63,12 +60,15 @@
from mathics.core.exceptions import InvalidLevelspecError
from mathics.core.expression import Expression, SymbolVerbatim
from mathics.core.list import ListExpression
-from mathics.core.pattern import Pattern, StopGenerator
+from mathics.core.pattern import BasePattern, StopGenerator
from mathics.core.rules import Rule
from mathics.core.symbols import Atom, Symbol, SymbolList, SymbolTrue
from mathics.core.systemsymbols import SymbolBlank, SymbolDefault, SymbolDispatch
from mathics.eval.parts import python_levelspec
+# This tells documentation how to sort this module
+sort_order = "mathics.builtin.rules-and-patterns"
+
class Rule_(BinaryOperator):
"""
@@ -159,7 +159,7 @@ def create_rules(
if any_lists:
all_lists = True
for item in rules:
- if not item.get_head() is SymbolList:
+ if item.get_head() is not SymbolList:
all_lists = False
break
@@ -568,7 +568,7 @@ def init(
"System`NonNegative": self.match_nonnegative,
}
- self.pattern = Pattern.create(expr.elements[0], evaluation=evaluation)
+ self.pattern = BasePattern.create(expr.elements[0], evaluation=evaluation)
self.test = expr.elements[1]
testname = self.test.get_name()
self.test_name = testname
@@ -765,7 +765,8 @@ def init(
) -> None:
super(Alternatives, self).init(expr, evaluation=evaluation)
self.alternatives = [
- Pattern.create(element, evaluation=evaluation) for element in expr.elements
+ BasePattern.create(element, evaluation=evaluation)
+ for element in expr.elements
]
def match(self, yield_func, expression, vars, evaluation, **kwargs):
@@ -826,11 +827,11 @@ def init(
self, expr: Expression, evaluation: OptionalType[Evaluation] = None
) -> None:
super(Except, self).init(expr, evaluation=evaluation)
- self.c = Pattern.create(expr.elements[0])
+ self.c = BasePattern.create(expr.elements[0])
if len(expr.elements) == 2:
- self.p = Pattern.create(expr.elements[1], evaluation=evaluation)
+ self.p = BasePattern.create(expr.elements[1], evaluation=evaluation)
else:
- self.p = Pattern.create(Expression(SymbolBlank), evaluation=evaluation)
+ self.p = BasePattern.create(Expression(SymbolBlank), evaluation=evaluation)
def match(self, yield_func, expression, vars, evaluation, **kwargs):
def except_yield_func(vars, rest):
@@ -910,7 +911,7 @@ def init(
self, expr: Expression, evaluation: OptionalType[Evaluation] = None
) -> None:
super(HoldPattern, self).init(expr, evaluation=evaluation)
- self.pattern = Pattern.create(expr.elements[0], evaluation=evaluation)
+ self.pattern = BasePattern.create(expr.elements[0], evaluation=evaluation)
def match(self, yield_func, expression, vars, evaluation, **kwargs):
# for new_vars, rest in self.pattern.match(
@@ -919,7 +920,7 @@ def match(self, yield_func, expression, vars, evaluation, **kwargs):
self.pattern.match(yield_func, expression, vars, evaluation)
-class Pattern_(PatternObject):
+class Pattern(PatternObject):
"""
:WMA link:https://reference.wolfram.com/language/ref/Pattern.html
@@ -995,9 +996,9 @@ def init(
varname = expr.elements[0].get_name()
if varname is None or varname == "":
self.error("patvar", expr)
- super(Pattern_, self).init(expr, evaluation=evaluation)
+ super(Pattern, self).init(expr, evaluation=evaluation)
self.varname = varname
- self.pattern = Pattern.create(expr.elements[1], evaluation=evaluation)
+ self.pattern = BasePattern.create(expr.elements[1], evaluation=evaluation)
def __repr__(self):
return "" % repr(self.pattern)
@@ -1005,10 +1006,10 @@ def __repr__(self):
def get_match_count(self, vars={}):
return self.pattern.get_match_count(vars)
- def match(self, yield_func, expression, vars, evaluation, **kwargs):
- existing = vars.get(self.varname, None)
+ def match(self, yield_func, expression, vars_dict, evaluation, **kwargs):
+ existing = vars_dict.get(self.varname, None)
if existing is None:
- new_vars = vars.copy()
+ new_vars = vars_dict.copy()
new_vars[self.varname] = expression
# for vars_2, rest in self.pattern.match(
# expression, new_vars, evaluation):
@@ -1021,22 +1022,24 @@ def match(self, yield_func, expression, vars, evaluation, **kwargs):
self.pattern.match(yield_func, expression, new_vars, evaluation)
else:
if existing.sameQ(expression):
- yield_func(vars, None)
+ yield_func(vars_dict, None)
def get_match_candidates(
- self, elements: tuple, expression, attributes, evaluation, vars={}
+ self, elements: tuple, expression, attributes, evaluation, vars_dict=None
):
- existing = vars.get(self.varname, None)
+ if vars_dict is None:
+ vars_dict = {}
+ existing = vars_dict.get(self.varname, None)
if existing is None:
return self.pattern.get_match_candidates(
- elements, expression, attributes, evaluation, vars
+ elements, expression, attributes, evaluation, vars_dict
)
else:
# Treat existing variable as verbatim
verbatim_expr = Expression(SymbolVerbatim, existing)
verbatim = Verbatim(verbatim_expr)
return verbatim.get_match_candidates(
- elements, expression, attributes, evaluation, vars
+ elements, expression, attributes, evaluation, vars_dict
)
@@ -1097,7 +1100,7 @@ def init(
self, expr: Expression, evaluation: OptionalType[Evaluation] = None
) -> None:
super(Optional, self).init(expr, evaluation=evaluation)
- self.pattern = Pattern.create(expr.elements[0], evaluation=evaluation)
+ self.pattern = BasePattern.create(expr.elements[0], evaluation=evaluation)
if len(expr.elements) == 2:
self.default = expr.elements[1]
else:
@@ -1398,7 +1401,7 @@ def init(
min: int = 1,
evaluation: OptionalType[Evaluation] = None,
):
- self.pattern = Pattern.create(expr.elements[0], evaluation=evaluation)
+ self.pattern = BasePattern.create(expr.elements[0], evaluation=evaluation)
self.max = None
self.min = min
if len(expr.elements) == 2:
@@ -1550,9 +1553,9 @@ def init(
# if (expr.elements[0].get_head_name() == "System`Condition" and
# len(expr.elements[0].elements) == 2):
# self.test = Expression(SymbolAnd, self.test, expr.elements[0].elements[1])
- # self.pattern = Pattern.create(expr.elements[0].elements[0])
+ # self.pattern = BasePattern.create(expr.elements[0].elements[0])
# else:
- self.pattern = Pattern.create(expr.elements[0], evaluation=evaluation)
+ self.pattern = BasePattern.create(expr.elements[0], evaluation=evaluation)
def match(
self,
diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py
index 2a7c0cf46..61bc1887d 100644
--- a/mathics/core/builtin.py
+++ b/mathics/core/builtin.py
@@ -54,7 +54,7 @@
from mathics.core.list import ListExpression
from mathics.core.number import PrecisionValueError, dps, get_precision, min_prec
from mathics.core.parser.util import PyMathicsDefinitions, SystemDefinitions
-from mathics.core.pattern import Pattern
+from mathics.core.pattern import BasePattern
from mathics.core.rules import FunctionApplyRule, Rule
from mathics.core.symbols import (
BaseElement,
@@ -1131,7 +1131,7 @@ def __init__(self, name, count, expected):
super().__init__(name, "argr", count, expected)
-class PatternObject(BuiltinElement, Pattern):
+class PatternObject(BuiltinElement, BasePattern):
needs_verbatim = True
arg_counts: List[int] = []
@@ -1142,9 +1142,10 @@ def init(self, expr, evaluation: Optional[Evaluation] = None):
if len(expr.elements) not in self.arg_counts:
self.error_args(len(expr.elements), *self.arg_counts)
self.expr = expr
- self.head = Pattern.create(expr.head, evaluation=evaluation)
+ self.head = BasePattern.create(expr.head, evaluation=evaluation)
self.elements = [
- Pattern.create(element, evaluation=evaluation) for element in expr.elements
+ BasePattern.create(element, evaluation=evaluation)
+ for element in expr.elements
]
def error(self, tag, *args):
diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py
index 8818da1d0..898d2120e 100644
--- a/mathics/core/pattern.py
+++ b/mathics/core/pattern.py
@@ -1,9 +1,19 @@
# cython: language_level=3
# cython: profile=False
# -*- coding: utf-8 -*-
+"""Core to Mathics3 is are patterns which match symbolic expressions. A pattern are built up in a custon pattern notation.
+The parts of a pattern are called "Pattern Objects".
+While there is a built-in function which allows users to match parts of expressions, patterns are also used in applying of transformation
+rules and deciding functions that get applied.
+
+See also: mathics.core.rules and https://reference.wolfram.com/language/tutorial/PatternsAndTransformationRules.html
+"""
+
+
+from abc import ABC
from itertools import chain
-from typing import Callable, List, Optional, Tuple
+from typing import Callable, List, Optional, Tuple, Union
from mathics.core.atoms import Integer
from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_ORDERLESS
@@ -27,7 +37,6 @@
)
from mathics.core.util import permutations, subranges, subsets
-# FIXME: create definitions in systemsymbols for missing items below.
SYSTEM_SYMBOLS_PATTERNS = symbol_set(
SymbolAlternatives,
SymbolBlank,
@@ -58,16 +67,22 @@ def __init__(self, value=None):
class StopGenerator_ExpressionPattern_match(StopGenerator):
- pass
+ """
+ Exception raised when an ExpressionPattern matches
+ an expression.
+ """
class StopGenerator_Pattern(StopGenerator):
- pass
+ """
+ Exception raised when BasePattern matches
+ an expression.
+ """
-class Pattern:
+class BasePattern(ABC):
"""
- This is the base class for Mathics Pattern objects.
+ This is the base class for Mathics3 Pattern objects.
A Pattern is a way to represent classes of expressions.
For example, ``F[x_Symbol]`` is a pattern which matches an expression whose
@@ -75,11 +90,13 @@ class Pattern:
When the pattern matches, the symbol is bound to the parameter ``x``.
"""
- # TODO: In WMA, when a Pattern is created, the attributes
+ expr: BaseElement
+
+ # TODO: In WMA, when a BasePattern is created, the attributes
# from the head are read from the evaluation context and
# stored as a part of a rule.
#
- # As Patterns are nested structures, the factory not only needs
+ # As BasePatterns are nested structures, the factory not only needs
# the attributes of the head, but also the full evaluation context
# which is needed to create patterns for its elements.
#
@@ -100,7 +117,7 @@ class Pattern:
# Also, when the initial Definitions object for the evaluation
# context is created, many rules must be created without an
# evaluation context available. For that case, we still
- # must be able to create Patten objects without the evaluation context.
+ # must be able to create Pattern objects without the evaluation context.
#
# In any case, just by caching the attributes in the first use of
# the pattern there is a win ~5% in performance.
@@ -109,11 +126,11 @@ class Pattern:
# to specialize the match method.
#
#
- # Corner case: `Alternaties`
- # ==========================
+ # Corner case: `Alternatives`
+ # ===========================
#
# Notice also that the case of `Alternatives` is a corner case,
- # where attributes are readed at the moment of the rule application:
+ # where attributes are read at the moment of the rule application:
#
# For example, in WMA, let's consider this example
# ```
@@ -149,9 +166,10 @@ class Pattern:
# ``
#
#
-
@staticmethod
- def create(expr: BaseElement, evaluation: Optional[Evaluation] = None) -> "Pattern":
+ def create(
+ expr: BaseElement, evaluation: Optional[Evaluation] = None
+ ) -> "BasePattern":
"""
If ``expr`` is listed in ``pattern_object`` return the pattern found there.
Otherwise, if ``expr`` is an ``Atom``, create and return ``AtomPattern`` for ``expr``.
@@ -163,18 +181,62 @@ def create(expr: BaseElement, evaluation: Optional[Evaluation] = None) -> "Patte
return pattern_object(expr, evaluation=evaluation)
if isinstance(expr, Atom):
return AtomPattern(expr, evaluation)
- else:
- return ExpressionPattern(expr, evaluation)
+ return ExpressionPattern(expr, evaluation)
+
+ def get_attributes(self, definitions):
+ """The attributes of the expression"""
+ return self.expr.get_attributes(definitions)
+
+ def get_elements(self):
+ """The elements of the expression."""
+ return self.expr.get_elements()
+
+ def get_head(self):
+ """The head of the expression"""
+ return self.expr.get_head()
+
+ def get_head_name(self):
+ """
+ Return the name of the symbol in head.
+ If head is not a symbol, return "".
+ """
+ return self.expr.get_head_name()
+
+ def get_lookup_name(self):
+ """
+ Return symbol name of leftmost head.
+ """
+ return self.expr.get_lookup_name()
+
+ def get_name(self):
+ """Return the name of the expression."""
+ return self.expr.get_name()
+
+ def get_sequence(self):
+ """The sequence of elements in the expression"""
+ return self.expr.get_sequence()
+
+ def get_sort_key(self, pattern_sort: bool = False) -> tuple:
+ """The sort key of the expression"""
+ return self.expr.get_sort_key(pattern_sort=pattern_sort)
+
+ def get_option_values(self):
+ """Option values of the expression"""
+ return self.expr.get_option_values()
+
+ def has_form(self, *args):
+ """Compare the expression against a form"""
+ return self.expr.has_form(*args)
def match(
self,
yield_func: Callable,
expression: BaseElement,
- vars: dict,
+ vars_dict: dict,
evaluation: Evaluation,
- head: Symbol = None,
- element_index: int = None,
- element_count: int = None,
+ head: Optional[Symbol] = None,
+ element_index: Optional[int] = None,
+ element_count: Optional[int] = None,
fully: bool = True,
):
"""
@@ -197,15 +259,15 @@ def does_match(
self,
expression: BaseElement,
evaluation: Evaluation,
- vars: Optional[dict] = None,
+ vars_dict: Optional[dict] = None,
fully: bool = True,
) -> bool:
- """
- returns True if `expression` matches self.
+ """returns True if `expression` matches self or we have
+ reached the end of the matches, and False if it does not.
"""
- if vars is None:
- vars = {}
+ if vars_dict is None:
+ vars_dict = {}
# for sub_vars, rest in self.match( # nopep8
# expression, vars, evaluation, fully=fully):
# return True
@@ -214,53 +276,22 @@ def yield_match(sub_vars, rest):
raise StopGenerator_Pattern(True)
try:
- self.match(yield_match, expression, vars, evaluation, fully=fully)
+ self.match(yield_match, expression, vars_dict, evaluation, fully=fully)
except StopGenerator_Pattern as exc:
return exc.value
return False
- def get_name(self):
- return self.expr.get_name()
-
- def get_head_name(self):
- return self.expr.get_head_name()
-
- def sameQ(self, other: BaseElement) -> bool:
- """Mathics SameQ"""
- return self.expr.sameQ(other.expr)
-
- def get_head(self):
- return self.expr.get_head()
-
- def get_elements(self):
- return self.expr.get_elements()
-
- def get_sort_key(self, pattern_sort: bool = False) -> tuple:
- return self.expr.get_sort_key(pattern_sort=pattern_sort)
-
- def get_lookup_name(self):
- return self.expr.get_lookup_name()
-
- def get_attributes(self, definitions):
- return self.expr.get_attributes(definitions)
-
- def get_sequence(self):
- return self.expr.get_sequence()
-
- def get_option_values(self):
- return self.expr.get_option_values()
-
- def has_form(self, *args):
- return self.expr.has_form(*args)
-
def get_match_candidates(
self,
elements: Tuple[BaseElement],
expression: BaseElement,
attributes: int,
evaluation: Evaluation,
- vars: dict = {},
+ vars_dict: Optional[dict] = None,
):
+ """
+ Get the candidates that matches with the pattern.
+ """
return tuple()
def get_match_candidates_count(
@@ -269,59 +300,76 @@ def get_match_candidates_count(
expression: BaseElement,
attributes: int,
evaluation: Evaluation,
- vars: dict = {},
+ vars_dict: Optional[dict] = None,
):
+ """Return the number of candidates that match with the pattern."""
return len(
self.get_match_candidates(
- elements, expression, attributes, evaluation, vars
+ elements, expression, attributes, evaluation, vars_dict
)
)
+ def sameQ(self, other: BaseElement) -> bool:
+ """Mathics SameQ"""
+ return self.expr.sameQ(other.expr)
+
+
+class AtomPattern(BasePattern):
+ """
+ A pattern that matches with an atom.
+ """
-class AtomPattern(Pattern):
def __init__(self, expr: Atom, evaluation: Optional[Evaluation] = None) -> None:
- self.atom = expr
self.expr = expr
+ self.atom = expr
if isinstance(expr, Symbol):
self.match = self.match_symbol
self.get_match_candidates = self.get_match_symbol_candidates
def __repr__(self):
- return "" % self.atom
+ return f""
def match_symbol(
self,
- yield_func,
- expression,
- vars,
- evaluation,
- head=None,
- element_index=None,
- element_count=None,
- fully=True,
+ yield_func: Callable,
+ expression: BaseElement,
+ vars_dict: dict,
+ evaluation: Evaluation,
+ head: Optional[Symbol] = None,
+ element_index: Optional[int] = None,
+ element_count: Optional[int] = None,
+ fully: bool = True,
):
+ """Match against a symbol"""
if expression is self.atom:
- yield_func(vars, None)
+ yield_func(vars_dict, None)
def get_match_symbol_candidates(
- self, elements, expression, attributes, evaluation, vars={}
+ self,
+ elements: tuple,
+ expression: BaseElement,
+ attributes: int,
+ evaluation: Evaluation,
+ vars_dict: Optional[dict] = None,
):
- return [element for element in elements if (element is self.atom)]
+ """Find the candidates that matches with the pattern"""
+ return [element for element in elements if element is self.atom]
def match(
self,
yield_func: Callable,
expression: BaseElement,
- vars: dict,
+ vars_dict: dict,
evaluation: Evaluation,
head: Optional[Symbol] = None,
element_index: Optional[int] = None,
element_count: Optional[int] = None,
fully: bool = True,
):
+ """Try to match the patterh with the expression."""
if isinstance(expression, Atom) and expression.sameQ(self.atom):
# yield vars, None
- yield_func(vars, None)
+ yield_func(vars_dict, None)
def get_match_candidates(
self,
@@ -329,7 +377,7 @@ def get_match_candidates(
expression: BaseElement,
attributes: int,
evaluation: Evaluation,
- vars: dict = {},
+ vars_dict: Optional[dict] = None,
):
return [
element
@@ -337,7 +385,8 @@ def get_match_candidates(
if (isinstance(element, Atom) and element.sameQ(self.atom))
]
- def get_match_count(self, vars: dict = {}):
+ def get_match_count(self, vars_dict: Optional[dict] = None):
+ """The number of matches"""
return (1, 1)
@@ -345,198 +394,83 @@ def get_match_count(self, vars: dict = {}):
# pass
-class ExpressionPattern(Pattern):
+class ExpressionPattern(BasePattern):
+ """
+ Pattern that matches with an Expression.
+ """
+
# get_pre_choices = pattern_nocython.get_pre_choices
# match = pattern_nocython.match
+ attributes: Optional[int] = None
+
+ def __init__(self, expr: Expression, evaluation: Optional[Evaluation] = None):
+ self.expr = expr
+ head = expr.head
+ attributes = (
+ None if evaluation is None else head.get_attributes(evaluation.definition)
+ )
+ self.__set_pattern_attributes__(attributes)
+ self.head = BasePattern.create(head)
+ self.elements = [BasePattern.create(element) for element in expr.elements]
+
+ def __set_pattern_attributes__(self, attributes):
+ if attributes is None or self.attributes is not None:
+ return
+
+ self.attributes = attributes
+ if A_ORDERLESS & attributes:
+ self.sort()
+ self.get_pre_choices = get_pre_choices_orderless
+ else:
+ self.get_pre_choices = get_pre_choices_with_order
+
def match(
self,
yield_func: Callable,
expression: BaseElement,
- vars: dict,
+ vars_dict: dict,
evaluation: Evaluation,
head: Optional[Symbol] = None,
element_index: Optional[int] = None,
element_count: Optional[int] = None,
fully: bool = True,
):
+ """Try to match the pattern against an Expression"""
evaluation.check_stopped()
if self.attributes is None:
- self.attributes = self.head.get_attributes(evaluation.definitions)
+ self.__set_pattern_attributes__(
+ self.head.get_attributes(evaluation.definitions)
+ )
attributes = self.attributes
if not A_FLAT & attributes:
fully = True
- if not isinstance(expression, Atom):
- # don't do this here, as self.get_pre_choices changes the
- # ordering of the elements!
- # if self.elements:
- # next_element = self.elements[0]
- # next_elements = self.elements[1:]
-
- def yield_choice(pre_vars):
- next_element = self.elements[0]
- next_elements = self.elements[1:]
-
- # "leading_blanks" below handles expressions with leading Blanks H[x_, y_, ...]
- # much more efficiently by not calling get_match_candidates_count() on elements
- # that have already been matched with one of the leading Blanks. this approach
- # is only valid for Expressions that are not Orderless (as with Orderless, the
- # concept of leading items does not exist).
- #
- # simple performance test case:
- #
- # f[x_, {a__, b_}] = 0;
- # f[x_, y_] := y + Total[x];
- # First[Timing[f[Range[5000], 1]]]"
- #
- # without "leading_blanks", Range[5000] will be tested against {a__, b_} in a
- # call to get_match_candidates_count(), which is slow.
-
- unmatched_elements = expression.elements
- leading_blanks = not A_ORDERLESS & attributes
-
- for element in self.elements:
- match_count = element.get_match_count()
-
- if leading_blanks:
- if tuple(match_count) == (
- 1,
- 1,
- ): # Blank? (i.e. length exactly 1?)
- if not unmatched_elements:
- raise StopGenerator_ExpressionPattern_match()
- if not element.does_match(
- unmatched_elements[0], evaluation, pre_vars
- ):
- raise StopGenerator_ExpressionPattern_match()
- unmatched_elements = unmatched_elements[1:]
- else:
- leading_blanks = False
-
- if not leading_blanks:
- candidates = element.get_match_candidates_count(
- unmatched_elements,
- expression,
- attributes,
- evaluation,
- pre_vars,
- )
- if candidates < match_count[0]:
- raise StopGenerator_ExpressionPattern_match()
-
- # for new_vars, rest in self.match_element( # nopep8
- # self.elements[0], self.elements[1:], ([], expression.elements),
- # pre_vars, expression, attributes, evaluation, first=True,
- # fully=fully, element_count=len(self.elements)):
- # def yield_element(new_vars, rest):
- # yield_func(new_vars, rest)
- self.match_element(
- yield_func,
- next_element,
- tuple(next_elements),
- ([], expression.elements),
- pre_vars,
- expression,
- attributes,
- evaluation,
- first=True,
- fully=fully,
- element_count=len(self.elements),
- )
- # for head_vars, _ in self.head.match(expression.get_head(), vars,
- # evaluation):
- def yield_head(head_vars, _):
- if self.elements:
- # pre_choices = self.get_pre_choices(
- # expression, attributes, head_vars)
- # for pre_vars in pre_choices:
-
- self.get_pre_choices(
- yield_choice, expression, attributes, head_vars
- )
- else:
- if not expression.elements:
- yield_func(head_vars, None)
- else:
- return
+ parms = {
+ "attributes": attributes,
+ "evaluation": evaluation,
+ "expression": expression,
+ "fully": fully,
+ "self": self,
+ "vars_dict": vars_dict,
+ "yield_func": yield_func,
+ }
+ if not isinstance(expression, Atom):
try:
- self.head.match(yield_head, expression.get_head(), vars, evaluation)
+ basic_match_expression(
+ parms,
+ )
except StopGenerator_ExpressionPattern_match:
return
if A_ONE_IDENTITY & attributes:
- # This is all about the pattern. We do this
- # each time because at some point we should need
- # to check the default values each time...
-
- # This tries to reduce the pattern to a non empty
- # set of default values, and a single pattern.
- default_indx = 0
- optionals = {}
- new_pattern = None
- pattern_head = self.head.expr
- for pat_elem in self.elements:
- default_indx += 1
- if isinstance(pat_elem, AtomPattern):
- if new_pattern is not None:
- return
- new_pattern = pat_elem
- # TODO: check into account the second argument,
- # and if there is a default value...
- elif pat_elem.get_head_name() == "System`Optional":
- if len(pat_elem.elements) == 2:
- pat, value = pat_elem.elements
- if pat.get_head_name() == "System`Pattern":
- key = pat.elements[0].atom.name
- else:
- # if the first element of the Optional
- # is not a `Pattern`, then we need to
- # store an empty element.
- key = ""
- optionals[key] = value
- elif len(pat_elem.elements) == 1:
- pat = pat_elem.elements[0]
- if pat.get_head_name() == "System`Pattern":
- key = pat.elements[0].atom.name
- else:
- key = ""
- # Now, determine the default value
- defaultvalue_expr = Expression(
- SymbolDefault, pattern_head, Integer(default_indx)
- )
- value = defaultvalue_expr.evaluate(evaluation)
- if value.sameQ(defaultvalue_expr):
- return
- optionals[key] = value
- else:
- return
- else:
- if new_pattern is not None:
- return
- new_pattern = pat_elem
-
- # If there is not optional values in the pattern, then
- # it can not match any expression as a OneIdentity pattern:
- if len(optionals) == 0:
- return
-
- # Remove the empty key and load the default values in vars
- if "" in optionals:
- del optionals[""]
- vars.update(optionals)
- # Try to match the non-optional element with the expression
- new_pattern.match(
- yield_func,
- expression,
- vars,
- evaluation,
- head=head,
+ match_expression_with_one_identity(
+ parms,
element_index=element_index,
element_count=element_count,
- fully=fully,
+ head=head,
)
def get_pre_choices(
@@ -544,127 +478,30 @@ def get_pre_choices(
yield_choice: Callable,
expression: BaseElement,
attributes: int,
- vars: dict,
+ vars_dict: dict,
):
"""
If not Orderless, call yield_choice with vars as the parameter.
"""
if A_ORDERLESS & attributes:
- self.sort()
- patterns = self.filter_elements("Pattern")
- # a dict with entries having patterns with the same name
- # which are not in vars.
- groups = {}
- prev_pattern = prev_name = None
- for pattern in patterns:
- name = pattern.elements[0].get_name()
- existing = vars.get(name, None)
- if existing is None:
- # There's no need for pre-choices if the variable is
- # already set.
- if name == prev_name:
- if name in groups:
- groups[name].append(pattern)
- else:
- groups[name] = [prev_pattern, pattern]
- prev_pattern = pattern
- prev_name = name
- # prev_element = None
-
- # count duplicate elements
- expr_groups = {}
- for element in expression.elements:
- expr_groups[element] = expr_groups.get(element, 0) + 1
-
- def per_name(yield_name: Callable, groups: Tuple, vars: dict):
- """
- Yields possible variable settings (dictionaries) for the
- remaining pattern groups
- """
-
- if groups:
- name, patterns = groups[0]
-
- match_count = [0, None]
- for pattern in patterns:
- sub_match_count = pattern.get_match_count()
- if sub_match_count[0] > match_count[0]:
- match_count[0] = sub_match_count[0]
- if match_count[1] is None or (
- sub_match_count[1] is not None
- and sub_match_count[1] < match_count[1]
- ):
- match_count[1] = sub_match_count[1]
- # possibilities = [{}]
- # sum = 0
-
- def per_expr(yield_expr, expr_groups, sum=0):
- """
- Yields possible values (sequence lists) for the current
- variable (name) taking into account the
- (expression, count)'s in expr_groups
- """
-
- if expr_groups:
- expr, count = expr_groups.popitem()
- max_per_pattern = count // len(patterns)
- for per_pattern in range(max_per_pattern, -1, -1):
- for next in per_expr( # nopep8
- expr_groups, sum + per_pattern
- ):
- yield_expr([expr] * per_pattern + next)
- else:
- if sum >= match_count[0]:
- yield_expr([])
- # Until we learn that the below is incorrect, we'll return basically no match.
- yield None
-
- # for sequence in per_expr(expr_groups.items()):
- def yield_expr(sequence):
- # FIXME: this call is wrong and needs a
- # wrapper_function as the 1st parameter.
- wrappings = self.get_wrappings(
- sequence, match_count[1], expression, attributes
- )
- for wrapping in wrappings:
- # for next in per_name(groups[1:], vars):
- def yield_next(next):
- setting = next.copy()
- setting[name] = wrapping
- yield_name(setting)
-
- per_name(yield_next, groups[1:], vars)
-
- per_expr(yield_expr, expr_groups)
- else: # no groups left
- yield_name(vars)
-
- # for setting in per_name(groups.items(), vars):
- # def yield_name(setting):
- # yield_func(setting)
- per_name(yield_choice, tuple(groups.items()), vars)
+ get_pre_choices_orderless(
+ self, yield_choice, expression, attributes, vars_dict
+ )
else:
- yield_choice(vars)
-
- def __init__(self, expr: Expression, evaluation: Optional[Evaluation] = None):
- head = expr.head
- self.attributes = (
- None if evaluation is None else head.get_attributes(evaluation.definition)
- )
- self.head = Pattern.create(head)
- self.elements = [Pattern.create(element) for element in expr.elements]
- self.expr = expr
+ yield_choice(vars_dict)
def filter_elements(self, head_name: str):
+ """Filter the elements with a given head_name"""
head_name = ensure_context(head_name)
return [
element for element in self.elements if element.get_head_name() == head_name
]
def __repr__(self):
- return "" % self.expr
+ return f""
- def get_match_count(self, vars: dict = {}):
+ def get_match_count(self, vars_dict: Optional[dict] = None):
+ """the number of matches"""
return (1, 1)
def get_wrappings(
@@ -676,6 +513,7 @@ def get_wrappings(
attributes: int,
include_flattened: bool = True,
):
+ """Get the possible wrappings"""
if len(items) == 1:
yield_func(items[0])
else:
@@ -698,7 +536,7 @@ def match_element(
element: BaseElement,
rest_elements: Tuple,
rest_expression: Tuple[List, List],
- vars: dict,
+ vars_dict: dict,
expression: BaseElement,
attributes: int,
evaluation: Evaluation,
@@ -708,24 +546,25 @@ def match_element(
fully: bool = True,
depth: int = 1,
):
+ """Try to match an element."""
if rest_expression is None:
rest_expression = ([], [])
evaluation.check_stopped()
- match_count = element.get_match_count(vars)
+ match_count = element.get_match_count(vars_dict)
element_candidates = element.get_match_candidates(
tuple(rest_expression[1]), # element.candidates,
expression,
attributes,
evaluation,
- vars,
+ vars_dict,
)
if len(element_candidates) < match_count[0]:
return
- candidates = rest_expression[1]
+ candidates = tuple(rest_expression[1])
# "Artificially" only use more elements than specified for some kind
# of pattern.
@@ -749,42 +588,20 @@ def match_element(
less_first = len(rest_elements) > 0
if A_ORDERLESS & attributes:
- # we only want element_candidates to be a set if we're orderless.
- # otherwise, constructing a set() is very slow for large lists.
- # performance test case:
- # x = Range[100000]; Timing[Combinatorica`BinarySearch[x, 100]]
- element_candidates = set(element_candidates) # for fast lookup
-
- sets = None
- if element.get_head_name() == "System`Pattern":
- varname = element.elements[0].get_name()
- existing = vars.get(varname, None)
- if existing is not None:
- head = existing.get_head()
- if head.get_name() == "System`Sequence" or (
- A_FLAT & attributes and head == expression.get_head()
- ):
- needed = existing.elements
- else:
- needed = [existing]
- available = list(candidates)
- for needed_element in needed:
- if (
- needed_element in available
- and needed_element in element_candidates # nopep8
- ):
- available.remove(needed_element)
- else:
- return
- sets = [(needed, ([], available))]
-
- if sets is None:
- sets = subsets(
- candidates,
- included=element_candidates,
- less_first=less_first,
- *set_lengths,
- )
+ parms = {
+ "expression": expression,
+ "element": element,
+ "vars_dict": vars_dict,
+ "attributes": attributes,
+ }
+
+ sets = expression_pattern_match_element_orderless(
+ parms,
+ candidates,
+ element_candidates,
+ less_first,
+ set_lengths,
+ )
else:
# a generator that yields partitions of
# candidates as [before | block | after ]
@@ -796,77 +613,31 @@ def match_element(
less_first=less_first,
*set_lengths,
)
+
+ parms = {
+ "attributes": attributes,
+ "depth": depth + 1,
+ "element": element,
+ "element_count": element_count,
+ "element_index": element_index,
+ "evaluation": evaluation,
+ "expression": expression,
+ "fully": fully,
+ "match_count": match_count,
+ "next_index": element_index + 1,
+ "pattern": self,
+ "rest_elements": rest_elements,
+ "rest_expression": rest_expression,
+ "try_flattened": try_flattened,
+ "yield_func": yield_func,
+ "vars": vars_dict,
+ }
if rest_elements:
- next_element = rest_elements[0]
- next_rest_elements = rest_elements[1:]
- next_depth = depth + 1
- next_index = element_index + 1
+ parms["next_element"] = rest_elements[0]
+ parms["next_rest_elements"] = rest_elements[1:]
for items, items_rest in sets:
- # Include wrappings like Plus[a, b] only if not all items taken
- # - in that case we would match the same expression over and over.
-
- include_flattened = try_flattened and 0 < len(items) < len(
- expression.elements
- )
-
- # Don't try flattened when the expression would remain the same!
-
- def element_yield(next_vars, next_rest):
- # if next_rest is None:
- # next_rest = ([], [])
- # yield_func(next_vars, (rest_expression[0] + items_rest[0],
- # next_rest[1]))
- if next_rest is None:
- yield_func(
- next_vars, (list(chain(rest_expression[0], items_rest[0])), [])
- )
- else:
- yield_func(
- next_vars,
- (list(chain(rest_expression[0], items_rest[0])), next_rest[1]),
- )
-
- def match_yield(new_vars, _):
- if rest_elements:
- self.match_element(
- element_yield,
- next_element,
- next_rest_elements,
- items_rest,
- new_vars,
- expression,
- attributes,
- evaluation,
- fully=fully,
- depth=next_depth,
- element_index=next_index,
- element_count=element_count,
- )
- else:
- if not fully or (not items_rest[0] and not items_rest[1]):
- yield_func(new_vars, items_rest)
-
- def yield_wrapping(item):
- element.match(
- match_yield,
- item,
- vars,
- evaluation,
- fully=True,
- head=expression.head,
- element_index=element_index,
- element_count=element_count,
- )
-
- self.get_wrappings(
- yield_wrapping,
- tuple(items),
- match_count[1],
- expression,
- attributes,
- include_flattened=include_flattened,
- )
+ expression_pattern_match_element_process_items(items, items_rest, parms)
def get_match_candidates(
self,
@@ -874,7 +645,7 @@ def get_match_candidates(
expression: BaseElement,
attributes: int,
evaluation: Evaluation,
- vars: dict = {},
+ vars_dict: Optional[dict] = None,
):
"""
Finds possible elements that could match the pattern, ignoring future
@@ -886,7 +657,7 @@ def get_match_candidates(
return [
element
for element in elements
- if self.does_match(element, evaluation, vars)
+ if self.does_match(element, evaluation, vars_dict)
]
def get_match_candidates_count(
@@ -895,7 +666,7 @@ def get_match_candidates_count(
expression: BaseElement,
attributes: int,
evaluation: Evaluation,
- vars: dict = {},
+ vars_dict: Optional[dict] = None,
):
"""
Finds possible elements that could match the pattern, ignoring future
@@ -906,9 +677,481 @@ def get_match_candidates_count(
count = 0
for element in elements:
- if self.does_match(element, evaluation, vars):
+ if self.does_match(element, evaluation, vars_dict):
count += 1
return count
def sort(self):
+ """Sort the elements according to their sort key"""
self.elements.sort(key=lambda e: e.get_sort_key(pattern_sort=True))
+
+
+def match_expression_with_one_identity(
+ parms: dict,
+ element_index: Optional[int],
+ element_count: Optional[int],
+ head: Symbol,
+):
+ """
+ Process expressions with the attribute OneIdentity.
+ """
+ # This is all about the pattern. We do this
+ # each time because at some point we should need
+ # to check the default values each time...
+
+ # This tries to reduce the pattern to a non empty
+ # set of default values, and a single pattern.
+
+ self: ExpressionPattern = parms["self"]
+ vars_dict: dict = parms["vars_dict"]
+ evaluation: Evaluation = parms["evaluation"]
+
+ default_indx: int = 0
+ optionals: dict = {}
+ new_pattern: Optional[Pattern] = None
+ pattern_head: Expression = self.head.expr
+ for pat_elem in self.elements:
+ default_indx += 1
+ if isinstance(pat_elem, AtomPattern):
+ if new_pattern is not None:
+ return
+ new_pattern = pat_elem
+ # TODO: check into account the second argument,
+ # and if there is a default value...
+ elif pat_elem.get_head_name() == "System`Optional":
+ if len(pat_elem.elements) == 2:
+ pat, value = pat_elem.elements
+ if pat.get_head_name() == "System`Pattern":
+ key = pat.elements[0].atom.name
+ else:
+ # if the first element of the Optional
+ # is not a `Pattern`, then we need to
+ # store an empty element.
+ key = ""
+ optionals[key] = value
+ elif len(pat_elem.elements) == 1:
+ pat = pat_elem.elements[0]
+ if pat.get_head_name() == "System`Pattern":
+ key = pat.elements[0].atom.name
+ else:
+ key = ""
+ # Now, determine the default value
+ defaultvalue_expr = Expression(
+ SymbolDefault, pattern_head, Integer(default_indx)
+ )
+ value = defaultvalue_expr.evaluate(evaluation)
+ if value.sameQ(defaultvalue_expr):
+ return
+ optionals[key] = value
+ else:
+ return
+ else:
+ if new_pattern is not None:
+ return
+ new_pattern = pat_elem
+
+ # If there is not optional values in the pattern, then
+ # it can not match any expression as a OneIdentity pattern:
+ if len(optionals) == 0:
+ return
+
+ # Remove the empty key and load the default values in vars
+ if "" in optionals:
+ del optionals[""]
+ vars_dict.update(optionals)
+ # Try to match the non-optional element with the expression
+ new_pattern.match(
+ parms["yield_func"],
+ parms["expression"],
+ vars_dict,
+ evaluation,
+ head=head,
+ element_index=element_index,
+ element_count=element_count,
+ fully=parms["fully"],
+ )
+
+
+def basic_match_expression(parms):
+ """
+ Try to match a pattern with an expression
+ """
+ # don't do this here, as self.get_pre_choices changes the
+ # ordering of the elements!
+ # if self.elements:
+ # next_element = self.elements[0]
+ # next_elements = self.elements[1:]
+
+ self: ExpressionPattern = parms["self"]
+ yield_func: Callable = parms["yield_func"]
+ expression: Expression = parms["expression"]
+ vars_dict: dict = parms["vars_dict"]
+ evaluation: Evaluation = parms["evaluation"]
+ attributes: int = parms["attributes"]
+ fully: bool = parms["fully"]
+
+ def yield_choice(pre_vars):
+ next_element = self.elements[0]
+ next_elements = self.elements[1:]
+
+ # "leading_blanks" below handles expressions with leading Blanks H[x_, y_, ...]
+ # much more efficiently by not calling get_match_candidates_count() on elements
+ # that have already been matched with one of the leading Blanks. this approach
+ # is only valid for Expressions that are not Orderless (as with Orderless, the
+ # concept of leading items does not exist).
+ #
+ # simple performance test case:
+ #
+ # f[x_, {a__, b_}] = 0;
+ # f[x_, y_] := y + Total[x];
+ # First[Timing[f[Range[5000], 1]]]"
+ #
+ # without "leading_blanks", Range[5000] will be tested against {a__, b_} in a
+ # call to get_match_candidates_count(), which is slow.
+
+ unmatched_elements = expression.elements
+ leading_blanks = not A_ORDERLESS & attributes
+
+ for element in self.elements:
+ match_count = element.get_match_count()
+
+ if leading_blanks:
+ if tuple(match_count) == (
+ 1,
+ 1,
+ ): # Blank? (i.e. length exactly 1?)
+ if not unmatched_elements:
+ raise StopGenerator_ExpressionPattern_match()
+ if not element.does_match(
+ unmatched_elements[0], evaluation, pre_vars
+ ):
+ raise StopGenerator_ExpressionPattern_match()
+ unmatched_elements = unmatched_elements[1:]
+ else:
+ leading_blanks = False
+
+ if not leading_blanks:
+ candidates = element.get_match_candidates_count(
+ unmatched_elements,
+ expression,
+ attributes,
+ evaluation,
+ pre_vars,
+ )
+ if candidates < match_count[0]:
+ raise StopGenerator_ExpressionPattern_match()
+
+ # for new_vars, rest in self.match_element( # nopep8
+ # self.elements[0], self.elements[1:], ([], expression.elements),
+ # pre_vars, expression, attributes, evaluation, first=True,
+ # fully=fully, element_count=len(self.elements)):
+ # def yield_element(new_vars, rest):
+ # yield_func(new_vars, rest)
+ self.match_element(
+ yield_func,
+ next_element,
+ tuple(next_elements),
+ ([], expression.elements),
+ pre_vars,
+ expression,
+ attributes,
+ evaluation,
+ first=True,
+ fully=fully,
+ element_count=len(self.elements),
+ )
+
+ # for head_vars, _ in self.head.match(expression.get_head(), vars,
+ # evaluation):
+ def yield_head(head_vars, _):
+ if self.elements:
+ # pre_choices = self.get_pre_choices(
+ # expression, attributes, head_vars)
+ # for pre_vars in pre_choices:
+
+ self.get_pre_choices(self, yield_choice, expression, attributes, head_vars)
+ else:
+ if not expression.elements:
+ yield_func(head_vars, None)
+ else:
+ return
+
+ self.head.match(yield_head, expression.get_head(), vars_dict, evaluation)
+
+
+def expression_pattern_match_element_orderless(
+ parms: dict,
+ candidates: tuple,
+ element_candidates: Union[tuple, set],
+ less_first: bool,
+ set_lengths: tuple,
+):
+ """
+ match element for orderless expressions
+ """
+ # we only want element_candidates to be a set if we're orderless.
+ # otherwise, constructing a set() is very slow for large lists.
+ # performance test case:
+ # x = Range[100000]; Timing[Combinatorica`BinarySearch[x, 100]]
+
+ element: BaseElement = parms["element"]
+ element_candidates = set(element_candidates) # for fast lookup
+
+ sets = None
+ if element.get_head_name() == "System`Pattern":
+ varname = element.elements[0].get_name()
+ existing = parms["vars_dict"].get(varname, None)
+ if existing is not None:
+ head = existing.get_head()
+ if head.get_name() == "System`Sequence" or (
+ A_FLAT & parms["attributes"] and head == parms["expression"].get_head()
+ ):
+ needed = existing.elements
+ else:
+ needed = (existing,)
+ available = list(candidates)
+ for needed_element in needed:
+ if (
+ needed_element in available
+ and needed_element in element_candidates # nopep8
+ ):
+ available.remove(needed_element)
+ else:
+ return set()
+ sets = [
+ (
+ needed,
+ (
+ [],
+ available,
+ ),
+ )
+ ]
+
+ if sets is None:
+ sets = subsets(
+ candidates,
+ included=element_candidates,
+ less_first=less_first,
+ *set_lengths,
+ )
+ return sets
+
+
+# TODO: adding the annotations for items
+# and items_rest as ``tuples`` produce failures in cython.
+# We should investigate what is the right type to pass here.
+def expression_pattern_match_element_process_items(
+ items: Union[tuple, list],
+ items_rest: Union[tuple, list],
+ parms: dict,
+):
+ # Include wrappings like Plus[a, b] only if not all items taken
+ # - in that case we would match the same expression over and over.
+
+ attributes: int = parms["attributes"]
+ element_count: int = parms["element_count"]
+ expression: Expression = parms["expression"]
+ evaluation: Evaluation = parms["evaluation"]
+ fully: bool = parms["fully"]
+ pattern: ExpressionPattern = parms["pattern"]
+ rest_expression = parms["rest_expression"]
+ yield_func: Callable = parms["yield_func"]
+
+ include_flattened: bool = parms["try_flattened"] and 0 < len(items) < len(
+ expression.elements
+ )
+
+ # Don't try flattened when the expression would remain the same!
+
+ def element_yield(next_vars_parm, next_rest_parm):
+ # if next_rest is None:
+ # next_rest = ([], [])
+ # yield_func(next_vars, (rest_expression[0] + items_rest[0],
+ # next_rest[1]))
+ if next_rest_parm is None:
+ yield_func(
+ next_vars_parm,
+ (list(chain(rest_expression[0], items_rest[0])), []),
+ )
+ else:
+ yield_func(
+ next_vars_parm,
+ (
+ list(chain(rest_expression[0], items_rest[0])),
+ next_rest_parm[1],
+ ),
+ )
+
+ def match_yield(new_vars, _):
+ if parms["rest_elements"]:
+ pattern.match_element(
+ element_yield,
+ parms["next_element"],
+ parms["next_rest_elements"],
+ items_rest,
+ new_vars,
+ expression,
+ attributes,
+ evaluation,
+ fully=fully,
+ depth=parms["depth"],
+ element_index=parms["next_index"],
+ element_count=element_count,
+ )
+ else:
+ if not fully or (not items_rest[0] and not items_rest[1]):
+ yield_func(new_vars, items_rest)
+
+ def yield_wrapping(item):
+ parms["element"].match(
+ match_yield,
+ item,
+ parms["vars"],
+ evaluation,
+ fully=True,
+ head=expression.head,
+ element_index=parms["element_index"],
+ element_count=element_count,
+ )
+
+ pattern.get_wrappings(
+ yield_wrapping,
+ tuple(items),
+ parms["match_count"][1],
+ expression,
+ attributes,
+ include_flattened=include_flattened,
+ )
+
+
+# TODO: these two functions should collect all their arguments
+# in a dict
+def get_pre_choices_with_order(
+ pat: ExpressionPattern,
+ yield_choice: Callable,
+ expression: Expression,
+ attributes: int,
+ vars_dict: dict,
+):
+ """
+ Yield pre choices for expressions without
+ the attribute Orderless.
+
+ In this case, all we have to do is to call
+ the parameter `yield_choice` with the collected
+ var_dict.
+ """
+ yield_choice(vars_dict)
+
+
+def get_pre_choices_orderless(
+ pat: ExpressionPattern,
+ yield_choice: Callable,
+ expression: Expression,
+ attributes: int,
+ vars_dict: dict,
+):
+ """
+ Yield pre choices for expressions with
+ the attribute Orderless.
+
+ This case is more involved, since the pattern can include subpatterns.
+
+ """
+ patterns = pat.filter_elements("Pattern")
+ # a dict with entries having patterns with the same name
+ # which are not in vars_dict.
+ groups = {}
+ prev_pattern = prev_name = None
+ for pattern in patterns:
+ name = pattern.elements[0].get_name()
+ existing = vars_dict.get(name, None)
+ if existing is None:
+ # There's no need for pre-choices if the variable is
+ # already set.
+ if name == prev_name:
+ if name in groups:
+ groups[name].append(pattern)
+ else:
+ groups[name] = [prev_pattern, pattern]
+ prev_pattern = pattern
+ prev_name = name
+ # prev_element = None
+
+ # count duplicate elements
+ expr_groups = {}
+ for element in expression.elements:
+ expr_groups[element] = expr_groups.get(element, 0) + 1
+
+ def per_name(yield_name: Callable, groups: Tuple, vars_dict: dict):
+ """
+ Yields possible variable settings (dictionaries) for the
+ remaining pattern groups
+ """
+ # TODO: check why this condition is never reached in tests.
+ if groups:
+ # name, patterns = groups[0]
+
+ # match_count = [0, None]
+ # for pattern in patterns:
+ # sub_match_count = pattern.get_match_count()
+ # if sub_match_count[0] > match_count[0]:
+ # match_count[0] = sub_match_count[0]
+ # if match_count[1] is None or (
+ # sub_match_count[1] is not None
+ # and sub_match_count[1] < match_count[1]
+ # ):
+ # match_count[1] = sub_match_count[1]
+ # # possibilities = [{}]
+ # # sum = 0
+
+ # def per_expr(yield_expr, expr_groups, sum_int=0):
+ # """
+ # Yields possible values (sequence lists) for the current
+ # variable (name) taking into account the
+ # (expression, count)'s in expr_groups
+ # """
+
+ # if expr_groups:
+ # expr, count = expr_groups.popitem()
+ # max_per_pattern = count // len(patterns)
+ # for per_pattern in range(max_per_pattern, -1, -1):
+ # for next_expr in per_expr( # nopep8
+ # expr_groups, sum_int + per_pattern
+ # ):
+ # yield_expr([expr] * per_pattern + next_expr)
+ # else:
+ # if sum_int >= match_count[0]:
+ # yield_expr([])
+ # # Until we learn that the below is incorrect,
+ # # we'll return basically no match.
+ # yield None
+
+ # # for sequence in per_expr(expr_groups.items()):
+ # def yield_expr(sequence):
+ # # FIXME: this call is wrong and needs a
+ # # wrapper_function as the 1st parameter.
+ # wrappings = pat.get_wrappings(
+ # sequence, match_count[1], expression, attributes
+ # )
+ # for wrapping in wrappings:
+ # 1/0
+ # # for next in per_name(groups[1:], vars_dict):
+
+ # def yield_next(next_expr):
+ # setting = next_expr.copy()
+ # setting[name] = wrapping
+ # yield_name(setting)
+
+ # per_name(yield_next, groups[1:], vars_dict)
+
+ # per_expr(yield_expr, expr_groups)
+ pass
+ else: # no groups left
+ yield_name(vars_dict)
+
+ # for setting in per_name(groups.items(), vars):
+ # def yield_name(setting):
+ # yield_func(setting)
+ per_name(yield_choice, tuple(groups.items()), vars_dict)
diff --git a/mathics/core/rules.py b/mathics/core/rules.py
index 6af8f5ff5..4c35fb5c7 100644
--- a/mathics/core/rules.py
+++ b/mathics/core/rules.py
@@ -57,7 +57,7 @@ def eval_f(self, x, evaluation) -> Optional[Expression]:
from mathics.core.element import BaseElement, KeyComparable
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
-from mathics.core.pattern import Pattern, StopGenerator
+from mathics.core.pattern import BasePattern, StopGenerator
from mathics.core.symbols import strip_context
@@ -98,7 +98,7 @@ def __init__(
system: bool = False,
evaluation: Optional[Evaluation] = None,
) -> None:
- self.pattern = Pattern.create(pattern, evaluation=evaluation)
+ self.pattern = BasePattern.create(pattern, evaluation=evaluation)
self.system = system
def apply(
diff --git a/mathics/eval/numbers/calculus/series.py b/mathics/eval/numbers/calculus/series.py
index a998a0c3d..c61b4d0a9 100644
--- a/mathics/eval/numbers/calculus/series.py
+++ b/mathics/eval/numbers/calculus/series.py
@@ -6,7 +6,7 @@
from mathics.core.convert.expression import to_mathics_list
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
-from mathics.core.rules import Pattern
+from mathics.core.rules import BasePattern
from mathics.core.symbols import Atom, Symbol, SymbolPlus, SymbolPower, SymbolTimes
from mathics.core.systemsymbols import (
SymbolComplexInfinity,
@@ -372,7 +372,7 @@ def build_series(f, x, x0, n, evaluation):
vars = {
x_name: x0,
}
- x_pattern = Pattern.create(x)
+ x_pattern = BasePattern.create(x)
if f.is_free(x_pattern, evaluation):
print(x, " not in ", f)
diff --git a/mathics/eval/patterns.py b/mathics/eval/patterns.py
index 8e975b634..649ced81f 100644
--- a/mathics/eval/patterns.py
+++ b/mathics/eval/patterns.py
@@ -1,5 +1,5 @@
from mathics.core.evaluation import Evaluation
-from mathics.core.pattern import Pattern, StopGenerator
+from mathics.core.pattern import BasePattern, StopGenerator
class _StopGeneratorMatchQ(StopGenerator):
@@ -8,10 +8,10 @@ class _StopGeneratorMatchQ(StopGenerator):
class Matcher:
def __init__(self, form):
- if isinstance(form, Pattern):
+ if isinstance(form, BasePattern):
self.form = form
else:
- self.form = Pattern.create(form)
+ self.form = BasePattern.create(form)
def match(self, expr, evaluation: Evaluation):
def yield_func(vars, rest):
diff --git a/mathics/eval/testing_expressions.py b/mathics/eval/testing_expressions.py
index c37bb0f6e..0285de12d 100644
--- a/mathics/eval/testing_expressions.py
+++ b/mathics/eval/testing_expressions.py
@@ -5,7 +5,7 @@
from mathics.core.atoms import Complex, Integer, Integer0, Integer1, IntegerM1
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
-from mathics.core.rules import Pattern
+from mathics.core.rules import BasePattern
from mathics.core.symbols import SymbolFalse, SymbolTimes, SymbolTrue
from mathics.core.systemsymbols import SymbolDirectedInfinity, SymbolSparseArray
@@ -112,7 +112,7 @@ def is_number(sympy_value) -> bool:
def check_ArrayQ(expr, pattern, test, evaluation: Evaluation):
"Check if expr is an Array which test yields true for each of its elements."
- pattern = Pattern.create(pattern)
+ pattern = BasePattern.create(pattern)
dims = [len(expr.get_elements())] # to ensure an atom is not an array
@@ -152,7 +152,7 @@ def check_SparseArrayQ(expr, pattern, test, evaluation: Evaluation):
if not expr.head.sameQ(SymbolSparseArray):
return SymbolFalse
- pattern = Pattern.create(pattern)
+ pattern = BasePattern.create(pattern)
dims, default_value, rules = expr.elements[1:]
if not pattern.does_match(Integer(len(dims.elements)), evaluation):
return SymbolFalse