Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dynamic_expressions/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class BinaryExpressionNode(Node):
right: Node


@dataclass(slots=True, frozen=True, kw_only=True)
@dataclass(slots=True, frozen=True)
class LiteralNode(Node):
value: Any

Expand Down
4 changes: 4 additions & 0 deletions dynamic_expressions/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ class ExecutionContext:
"/",
"//",
"^",
"&",
"|",
"getitem",
"getattr",
]
13 changes: 11 additions & 2 deletions dynamic_expressions/visitors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import abc
import operator
from collections.abc import Callable, Mapping
from functools import reduce
from typing import Any, ClassVar, Protocol

from dynamic_expressions.nodes import (
Expand Down Expand Up @@ -61,9 +62,13 @@ async def visit(
return True


def _visit_getattr(value: Any, properties: Any) -> object: # noqa: ANN401
return reduce(getattr, properties.split("."), value)


class BinaryExpressionVisitor(Visitor[BinaryExpressionNode, EmptyContext]):
operator_mapping: ClassVar[
Mapping[BinaryExpressionOperator, Callable[[Any, Any], bool]]
Mapping[BinaryExpressionOperator, Callable[[Any, Any], object]]
] = {
"=": operator.eq,
">": operator.gt,
Expand All @@ -78,6 +83,10 @@ class BinaryExpressionVisitor(Visitor[BinaryExpressionNode, EmptyContext]):
"/": operator.truediv,
"//": operator.floordiv,
"^": operator.pow,
"&": operator.and_,
"|": operator.or_,
"getitem": operator.getitem,
"getattr": _visit_getattr,
}

async def visit(
Expand All @@ -86,7 +95,7 @@ async def visit(
node: BinaryExpressionNode,
dispatch: Dispatch[EmptyContext],
context: EmptyContext,
) -> bool:
) -> object:
left = await dispatch(node.left, context)
right = await dispatch(node.right, context)

Expand Down
37 changes: 37 additions & 0 deletions tests/nodes/test_binary_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,40 @@ async def test_eq(
) -> None:
node = BinaryExpressionNode(operator="=", left=left, right=right)
assert await dispatcher.visit(node, None) == (left.value == right.value)


@pytest.mark.parametrize(
("left", "right", "expected"),
[
(0, 63, 0),
(5, 4, 4),
(8, 4, 0),
],
)
async def test_binary_and(
dispatcher: VisitorDispatcher[EmptyContext], left: int, right: int, expected: int
) -> None:
node = BinaryExpressionNode(
operator="&", left=LiteralNode(left), right=LiteralNode(right)
)
assert await dispatcher.visit(node, None) == expected == (left & right)


@pytest.mark.parametrize(
("left", "right", "expected"),
[
(pytest, "mark", pytest.mark),
(pytest, "mark.parametrize", pytest.mark.parametrize),
(pytest, "mark.parametrize.markname", pytest.mark.parametrize.markname),
],
)
async def test_getattr(
dispatcher: VisitorDispatcher[EmptyContext],
left: object,
right: str,
expected: object,
) -> None:
node = BinaryExpressionNode(
operator="getattr", left=LiteralNode(left), right=LiteralNode(right)
)
assert await dispatcher.visit(node, None) == expected
Loading
Loading