yuzihan@SIM1
Deploy SRHarness Docker Space
d06fa57
Raw History Blame Contribute Delete
5.01 kB
"""Immutable expression-tree traversal and transformation helpers."""
from __future__ import annotations
from dataclasses import replace as dataclass_replace
from .expression import (
Aggregate,
Binary,
Expression,
Function,
Gather,
GroupedParameter,
Indexed,
Reduction,
RelationLift,
Unary,
)
def children(node: Expression) -> tuple[Expression, ...]:
"""Return the direct child expressions of a node.
Args:
node: Expression or syntax-tree node.
Returns:
The node's direct children in structural order.
"""
if isinstance(node, Unary):
return (node.operand,)
if isinstance(node, Binary):
return (node.left, node.right)
if isinstance(node, Function):
return node.arguments
if isinstance(node, Indexed):
return (node.base,)
if isinstance(node, Reduction):
return (node.operand,) if node.relation is None else (node.relation, node.operand)
if isinstance(node, Gather):
return (node.relation, node.operand)
if isinstance(node, Aggregate):
return (node.relation, node.operand)
if isinstance(node, RelationLift):
return (node.operand,) if node.relation is None else (node.relation, node.operand)
if isinstance(node, GroupedParameter):
return (node.by,)
return ()
def with_children(node: Expression, values: tuple[Expression, ...]) -> Expression:
"""Return a copy of a node with new direct children.
Args:
node: Expression or syntax-tree node.
values: Replacement children in structural order.
Returns:
The rebuilt expression node.
"""
if len(values) != len(children(node)):
raise ValueError(f"Expected {len(children(node))} children, got {len(values)}.")
if isinstance(node, Unary):
return dataclass_replace(node, operand=values[0])
if isinstance(node, Binary):
return dataclass_replace(node, left=values[0], right=values[1])
if isinstance(node, Function):
return dataclass_replace(node, arguments=values)
if isinstance(node, Indexed):
return dataclass_replace(node, base=values[0])
if isinstance(node, Reduction):
if node.relation is None:
return dataclass_replace(node, operand=values[0])
return dataclass_replace(node, relation=values[0], operand=values[1])
if isinstance(node, Gather):
return dataclass_replace(node, relation=values[0], operand=values[1])
if isinstance(node, Aggregate):
return dataclass_replace(node, relation=values[0], operand=values[1])
if isinstance(node, RelationLift):
if node.relation is None:
return dataclass_replace(node, operand=values[0])
return dataclass_replace(node, relation=values[0], operand=values[1])
if isinstance(node, GroupedParameter):
return dataclass_replace(node, by=values[0])
return node
def iter_preorder(node: Expression):
"""Yield an expression tree in preorder.
Args:
node: Expression or syntax-tree node.
Yields:
The node followed recursively by its children.
"""
yield node
for child in children(node):
yield from iter_preorder(child)
def iter_postorder(node: Expression):
"""Yield an expression tree in postorder.
Args:
node: Expression or syntax-tree node.
Yields:
Descendants followed by their parent node.
"""
for child in children(node):
yield from iter_postorder(child)
yield node
def transform(node: Expression, function) -> Expression:
"""Apply a bottom-up transformation to an expression tree.
Args:
node: Expression or syntax-tree node.
function: Transformation callback.
Returns:
The transformed expression tree.
"""
transformed = with_children(node, tuple(transform(child, function) for child in children(node)))
return function(transformed)
def replace(node: Expression, old: Expression, new: Expression) -> Expression:
"""Replace a node by object identity throughout a tree.
Args:
node: Expression or syntax-tree node.
old: Existing expression node to replace.
new: Replacement expression node.
Returns:
The rebuilt expression tree.
"""
if node is old:
return new
return with_children(node, tuple(replace(child, old, new) for child in children(node)))
def replace_at_path(node: Expression, path: tuple[int, ...], new: Expression) -> Expression:
"""Replace the node at a tuple of child indices.
Args:
node: Expression or syntax-tree node.
path: Child indices from the root to the target node.
new: Replacement expression node.
Returns:
The rebuilt expression tree.
"""
if not path:
return new
values = list(children(node))
index = path[0]
values[index] = replace_at_path(values[index], path[1:], new)
return with_children(node, tuple(values))