Skip to content

Commit

Permalink
Add multiply operator
Browse files Browse the repository at this point in the history
  • Loading branch information
Viicos committed Oct 1, 2024
1 parent 2c7ed68 commit 07ba477
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/jsonlogic/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Map,
Minus,
Modulo,
Multiply,
NotEqual,
Plus,
Var,
Expand All @@ -29,6 +30,7 @@
"Map",
"Minus",
"Modulo",
"Multiply",
"NotEqual",
"Plus",
"Var",
Expand All @@ -49,5 +51,6 @@
operator_registry.register("+", Plus)
operator_registry.register("-", Minus)
operator_registry.register("/", Division)
operator_registry.register("*", Multiply)
operator_registry.register("%", Modulo)
operator_registry.register("map", Map)
35 changes: 35 additions & 0 deletions src/jsonlogic/operators/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,41 @@ class Modulo(BinaryOperator):
operator_symbol = "%"


@dataclass
class Multiply(Operator):
arguments: list[OperatorArgument]

@classmethod
def from_expression(cls, operator: str, arguments: list[OperatorArgument]) -> Self:
if not len(arguments) >= 2:
raise JSONLogicSyntaxError(f"{operator!r} expects at least two arguments, got {len(arguments)}")
return cls(operator=operator, arguments=arguments)

def typecheck(self, context: TypecheckContext) -> JSONSchemaType:
types = (get_type(obj, context) for obj in self.arguments)
result_type = next(types)

for i, typ in enumerate(types, start=1):
try:
result_type = result_type.binary_op(typ, "*")
except UnsupportedOperation:
if len(self.arguments) == 2:
msg = f'Operator "*" not supported for types {result_type.name} and {typ.name}'
else:
msg = f'Operator "*" not supported for types {result_type.name} (argument {i}) and {typ.name} (argument {i + 1})' # noqa: E501
context.add_diagnostic(
msg,
"operator",
self,
)
return AnyType()

return result_type

def evaluate(self, context: EvaluationContext) -> Any:
return functools.reduce(lambda a, b: get_value(a, context) * get_value(b, context), self.arguments)


@dataclass
class Plus(Operator):
arguments: list[OperatorArgument]
Expand Down
20 changes: 20 additions & 0 deletions tests/operators/test_evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ def test_binary_op() -> None:
assert rv is True


def test_multiply() -> None:
op_two_operands = as_op({"*": [2, 2]})
rv = evaluate(
op_two_operands,
data={},
data_schema=None,
)

assert rv == 4

op_three_operands = as_op({"*": [2, 2, 2]})
rv = evaluate(
op_three_operands,
data={},
data_schema=None,
)

assert rv == 8


def test_plus() -> None:
op_two_operands = as_op({"+": [1, 2]})
rv = evaluate(
Expand Down
9 changes: 9 additions & 0 deletions tests/operators/test_from_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
If,
Map,
Minus,
Multiply,
Plus,
Var,
)
Expand Down Expand Up @@ -63,6 +64,14 @@ def test_binary_op() -> None:
GreaterThan.from_expression(">", [1, 2, 3])


def test_multiply() -> None:
multipy = Multiply.from_expression("*", [1, 2, 3, 4])
assert multipy.arguments == [1, 2, 3, 4]

with pytest.raises(JSONLogicSyntaxError, match="'*' expects at least two arguments, got 1"):
Plus.from_expression("*", [1])


def test_plus() -> None:
plus = Plus.from_expression("+", [1, 2, 3, 4])
assert plus.arguments == [1, 2, 3, 4]
Expand Down
24 changes: 24 additions & 0 deletions tests/operators/test_typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,30 @@ def test_binary_op() -> None:
assert diag.message == 'Operator ">=" not supported for types integer and date'


def test_multiply() -> None:
op_two_operands = as_op({"*": ["a", "b"]})
rt, diagnostics = typecheck(op_two_operands, {})
diag = diagnostics[0]

assert rt == AnyType()
assert diag.category == "operator"
assert diag.message == 'Operator "*" not supported for types string and string'

op_three_operands = as_op({"*": ["a", 1, 2]})
rt, diagnostics = typecheck(op_three_operands, {})
diag = diagnostics[0]

assert rt == AnyType()
assert diag.category == "operator"
assert diag.message == 'Operator "*" not supported for types string (argument 1) and integer (argument 2)'

op_ok = as_op({"*": [1, 2, 3, 4]})
rt, diagnostics = typecheck(op_ok, {})

assert rt == IntegerType()
assert diagnostics == []


def test_plus() -> None:
op_two_operands = as_op({"+": ["a", "b"]})
rt, diagnostics = typecheck(op_two_operands, {})
Expand Down

0 comments on commit 07ba477

Please sign in to comment.