# Path: formulaic/parser/types/term.py
# class Term:
#     """
#     Represents a "term" of a formula.
# 
#     A "term" is a product of "factors" (represented by `Factor`) instances, and
#     a formula is made up of a sum of terms.
# 
#     Attributes:
#         factors: The set of factors to be multipled to form the term.
#     """
# 
#     def __init__(self, factors: Iterable["Factor"]):
#         self.factors = set(factors)
# 
#     def __mul__(self, other):
#         if isinstance(other, Term):
#             return Term([*self.factors, *other.factors])
#         return NotImplemented
# 
#     @property
#     def _tuple(self):
#         return tuple(factor.expr for factor in sorted(self.factors))
# 
#     def __hash__(self):
#         return hash(str(self))
# 
#     def __eq__(self, other):
#         if isinstance(other, Term):
#             return self._tuple == other._tuple
#         if isinstance(other, str):
#             return repr(self) == other
#         return NotImplemented
# 
#     def __lt__(self, other):
#         if isinstance(other, Term):
#             if len(self.factors) == len(other.factors):
#                 return sorted(self.factors) < sorted(other.factors)
#             if len(self.factors) < len(other.factors):
#                 return True
#             return False
#         return NotImplemented
# 
#     def __repr__(self):
#         return ":".join(self._tuple)


from enum import Enum
from numbers import Number
from typing import Callable, Iterable, Union
from .term import Term
            of explicit grouping parentheses. If left associative, groups are
            formed from the left [e.g. a % b % c -> ((a % b) % c)]; and
            similarly for right.
        fixity: One of 'prefix', 'infix', or 'postfix'; indicating how the
            operator is positioned relative to its arguments. If 'prefix', the
            operator comes before its arguments; if 'infix', the operator comes
            between its arguments (and there must be exactly two of them); and
            if 'postfix', the operator comes after its arguments.
        to_terms: A callable that maps the arguments pass to the operator to
            an iterable of `Term` instances.
    """

    class Associativity(Enum):
        LEFT = "left"
        RIGHT = "right"
        NONE = "none"

    class Fixity(Enum):
        PREFIX = "prefix"
        INFIX = "infix"
        POSTFIX = "postfix"

    def __init__(
        self,
        symbol: str,
        *,
        arity: int,
        precedence: Number,
        associativity: Union[str, Associativity] = "none",
        fixity: Union[str, Fixity] = "infix",

# Path: formulaic/parser/types/term.py
# class Term:
#     """
#     Represents a "term" of a formula.
# 
#     A "term" is a product of "factors" (represented by `Factor`) instances, and
#     a formula is made up of a sum of terms.
# 
#     Attributes:
#         factors: The set of factors to be multipled to form the term.
#     """
# 
#     def __init__(self, factors: Iterable["Factor"]):
#         self.factors = set(factors)
# 
#     def __mul__(self, other):
#         if isinstance(other, Term):
#             return Term([*self.factors, *other.factors])
#         return NotImplemented
# 
#     @property
#     def _tuple(self):
#         return tuple(factor.expr for factor in sorted(self.factors))
# 
#     def __hash__(self):
#         return hash(str(self))
# 
#     def __eq__(self, other):
#         if isinstance(other, Term):
#             return self._tuple == other._tuple
#         if isinstance(other, str):
#             return repr(self) == other
#         return NotImplemented
# 
#     def __lt__(self, other):
#         if isinstance(other, Term):
#             if len(self.factors) == len(other.factors):
#                 return sorted(self.factors) < sorted(other.factors)
#             if len(self.factors) < len(other.factors):
#                 return True
#             return False
#         return NotImplemented
# 
#     def __repr__(self):
#         return ":".join(self._tuple)


# Path: formulaic/parser/types/operator.py
from enum import Enum
from numbers import Number
from typing import Callable, Iterable, Union
from .term import Term

            of explicit grouping parentheses. If left associative, groups are
            formed from the left [e.g. a % b % c -> ((a % b) % c)]; and
            similarly for right.
        fixity: One of 'prefix', 'infix', or 'postfix'; indicating how the
            operator is positioned relative to its arguments. If 'prefix', the
            operator comes before its arguments; if 'infix', the operator comes
            between its arguments (and there must be exactly two of them); and
            if 'postfix', the operator comes after its arguments.
        to_terms: A callable that maps the arguments pass to the operator to
            an iterable of `Term` instances.
    """

    class Associativity(Enum):
        LEFT = "left"
        RIGHT = "right"
        NONE = "none"

    class Fixity(Enum):
        PREFIX = "prefix"
        INFIX = "infix"
        POSTFIX = "postfix"

    def __init__(
        self,
        symbol: str,
        *,
        arity: int,
        precedence: Number,
        associativity: Union[str, Associativity] = "none",
        fixity: Union[str, Fixity] = "infix",

        to_terms: Callable[..., Iterable[Term]] = None,