# Path: formulaic/parser/types/ast_node.py
# class ASTNode:
#     """
#     Represents a node in an Abstract Syntax Tree (AST).
# 
#     An `ASTNode` instance is composed of an `Operator` instance and a set of
#     arguments to be passed into that operator. The arguments may include nested
#     `ASTNode`s or other arguments. Once evaluated, a set of `Term` instances
#     is returned.
# 
#     Attributes:
#         operator: The `Operator` instance associated with this node.
#         args: The arguments associated with this node.
#     """
# 
#     def __init__(self, operator: Operator, args: Iterable[Any]):
#         self.operator = operator
#         self.args = args
# 
#     def to_terms(self) -> Iterable[Term]:
#         """
#         Evaluate this AST node and return the resulting set of `Term` instances.
#         """
#         return self.operator.to_terms(*self.args)
# 
#     def __repr__(self):
#         return f"<ASTNode {self.operator}: {self.args}>"
# 
#     def flatten(self, str_args: bool = False) -> List[Any]:
#         """
#         Flatten this `ASTNode` instance into a list of form: [<operator>, *<args>].
# 
#         This is primarily useful during debugging and unit testing, since it
#         provides a human readable summary of the entire AST.
# 
#         Args:
#             str_args: Whether to cast every element of the flattened object to
#                 a string.
#         """
#         return [
#             str(self.operator) if str_args else self.operator,
#             *[
#                 arg.flatten(str_args=str_args)
#                 if isinstance(arg, ASTNode)
#                 else (str(arg) if str_args else arg)
#                 for arg in self.args
#             ],
#         ]
# 
# Path: formulaic/parser/types/operator.py
# class Operator:
#     """
#     Specification for how an operator in a formula string should behave.
# 
#     Attributes:
#         symbol: The operator for which the configuration applies.
#         arity: The number of arguments that this operator consumes.
#         precedence: How tightly this operator binds its arguments (the higher
#             the number, the more tightly it binds). Operators with higher
#             precedence will be evaluated first.
#         associativity: One of 'left', 'right', or 'none'; indicating how
#             operators of the same precedence should be evaluated in the absence
#             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,
#     ):
#         self.symbol = symbol
#         self.arity = arity
#         self.precedence = precedence
#         self.associativity = associativity
#         self.fixity = fixity
#         self._to_terms = to_terms
# 
#     @property
#     def associativity(self):
#         return self._associativity
# 
#     @associativity.setter
#     def associativity(self, associativity):
#         self._associativity = Operator.Associativity(associativity or "none")
# 
#     @property
#     def fixity(self):
#         return self._fixity
# 
#     @fixity.setter
#     def fixity(self, fixity):
#         self._fixity = Operator.Fixity(fixity)
# 
#     def to_terms(self, *args):
#         if self._to_terms is None:
#             raise RuntimeError(f"`to_terms` is not implemented for '{self.symbol}'.")
#         return self._to_terms(*args)
# 
#     def __repr__(self):
#         return self.symbol


import pytest
from formulaic.parser.types import ASTNode, Operator



class TestASTNode:
    @pytest.fixture
    def ast_node(self):
        return ASTNode(
            Operator("+", arity=2, precedence=100, to_terms=lambda *args: args),
            ("a", "b", "c"),
        )

    def test_to_terms(self, ast_node):

# Path: formulaic/parser/types/ast_node.py
# class ASTNode:
#     """
#     Represents a node in an Abstract Syntax Tree (AST).
# 
#     An `ASTNode` instance is composed of an `Operator` instance and a set of
#     arguments to be passed into that operator. The arguments may include nested
#     `ASTNode`s or other arguments. Once evaluated, a set of `Term` instances
#     is returned.
# 
#     Attributes:
#         operator: The `Operator` instance associated with this node.
#         args: The arguments associated with this node.
#     """
# 
#     def __init__(self, operator: Operator, args: Iterable[Any]):
#         self.operator = operator
#         self.args = args
# 
#     def to_terms(self) -> Iterable[Term]:
#         """
#         Evaluate this AST node and return the resulting set of `Term` instances.
#         """
#         return self.operator.to_terms(*self.args)
# 
#     def __repr__(self):
#         return f"<ASTNode {self.operator}: {self.args}>"
# 
#     def flatten(self, str_args: bool = False) -> List[Any]:
#         """
#         Flatten this `ASTNode` instance into a list of form: [<operator>, *<args>].
# 
#         This is primarily useful during debugging and unit testing, since it
#         provides a human readable summary of the entire AST.
# 
#         Args:
#             str_args: Whether to cast every element of the flattened object to
#                 a string.
#         """
#         return [
#             str(self.operator) if str_args else self.operator,
#             *[
#                 arg.flatten(str_args=str_args)
#                 if isinstance(arg, ASTNode)
#                 else (str(arg) if str_args else arg)
#                 for arg in self.args
#             ],
#         ]
# 
# Path: formulaic/parser/types/operator.py
# class Operator:
#     """
#     Specification for how an operator in a formula string should behave.
# 
#     Attributes:
#         symbol: The operator for which the configuration applies.
#         arity: The number of arguments that this operator consumes.
#         precedence: How tightly this operator binds its arguments (the higher
#             the number, the more tightly it binds). Operators with higher
#             precedence will be evaluated first.
#         associativity: One of 'left', 'right', or 'none'; indicating how
#             operators of the same precedence should be evaluated in the absence
#             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,
#     ):
#         self.symbol = symbol
#         self.arity = arity
#         self.precedence = precedence
#         self.associativity = associativity
#         self.fixity = fixity
#         self._to_terms = to_terms
# 
#     @property
#     def associativity(self):
#         return self._associativity
# 
#     @associativity.setter
#     def associativity(self, associativity):
#         self._associativity = Operator.Associativity(associativity or "none")
# 
#     @property
#     def fixity(self):
#         return self._fixity
# 
#     @fixity.setter
#     def fixity(self, fixity):
#         self._fixity = Operator.Fixity(fixity)
# 
#     def to_terms(self, *args):
#         if self._to_terms is None:
#             raise RuntimeError(f"`to_terms` is not implemented for '{self.symbol}'.")
#         return self._to_terms(*args)
# 
#     def __repr__(self):
#         return self.symbol


# Path: tests/parser/types/test_ast_node.py
import pytest
from formulaic.parser.types import ASTNode, Operator




class TestASTNode:
    @pytest.fixture
    def ast_node(self):
        return ASTNode(
            Operator("+", arity=2, precedence=100, to_terms=lambda *args: args),
            ("a", "b", "c"),
        )

    def test_to_terms(self, ast_node):

        assert ast_node.to_terms() == ("a", "b", "c")