Files
RecordFlux/tests/unit/expr_test.py
Tobias Reiher 209664fb5b Rewrite binary expressions in Rust
Ref. eng/recordflux/RecordFlux#1774
2024-12-17 15:38:47 +01:00

3051 lines
92 KiB
Python

from __future__ import annotations
import math
import textwrap
from collections.abc import Callable, Mapping
import pytest
from rflx import ty
from rflx.expr import (
FALSE,
TRUE,
UNDEFINED,
Add,
Aggregate,
And,
AndThen,
Attribute,
Call,
CaseExpr,
Comprehension,
Conversion,
DeltaMessageAggregate,
Div,
Equal,
Expr,
First,
ForAllIn,
ForAllOf,
ForSomeIn,
Greater,
GreaterEqual,
HasData,
Head,
IfExpr,
In,
Indexed,
Last,
Length,
Less,
LessEqual,
Literal,
MessageAggregate,
Mod,
Mul,
Neg,
Not,
NotEqual,
NotIn,
Number,
Opaque,
Or,
OrElse,
Pow,
Precedence,
Present,
QualifiedExpr,
Rem,
Selected,
Size,
String,
Sub,
Val,
Valid,
ValidChecksum,
ValueRange,
Variable,
substitution,
)
from rflx.identifier import ID, StrID
from rflx.model import Integer
from rflx.rapidflux import Location, RecordFluxError
from tests.data import models
from tests.utils import assert_equal, check_regex
EXPR = Equal(Variable("UNDEFINED_1"), Variable("UNDEFINED_2"))
TINY_INT = Integer("P::Tiny", Number(1), Number(3), Number(8), location=Location((1, 2)))
INT = Integer("P::Int", Number(1), Number(100), Number(8), location=Location((3, 2)))
INT_TY = ty.Integer("I", ty.Bounds(10, 100))
ENUM_TY = ty.Enumeration("E", [ID("E1"), ID("E2")])
MSG_TY = ty.Message("M")
SEQ_TY = ty.Sequence("S", ty.Message("M"))
def assert_type(expr: Expr, type_: ty.Type) -> None:
expr.check_type(type_).propagate()
assert expr.type_ == type_
def assert_type_error(expr: Expr, regex: str) -> None:
check_regex(regex)
with pytest.raises(RecordFluxError, match=regex):
expr.check_type(ty.Any()).propagate()
def assert_type_instance(expr: Expr, type_: type[ty.Type] | tuple[type[ty.Type], ...]) -> None:
expr.check_type_instance(type_).propagate()
def test_true_type() -> None:
assert_type(
TRUE,
ty.BOOLEAN,
)
def test_true_simplified() -> None:
assert TRUE.simplified() == TRUE
def test_true_variables() -> None:
assert not TRUE.variables()
def test_false_type() -> None:
assert_type(
FALSE,
ty.BOOLEAN,
)
def test_false_simplified() -> None:
assert FALSE.simplified() == FALSE
def test_false_variables() -> None:
assert not FALSE.variables()
def test_not_type() -> None:
assert_type(
Not(Variable("X", type_=ty.BOOLEAN)),
ty.BOOLEAN,
)
def test_not_type_error() -> None:
assert_type_error(
Not(Variable(ID("X", location=Location((10, 20))), type_=INT_TY)),
r'^<stdin>:10:20: error: expected enumeration type "__BUILTINS__::Boolean"\n'
r'<stdin>:10:20: error: found integer type "I" \(10 \.\. 100\)$',
)
def test_not_neg() -> None:
assert -Not(Variable("X")) == Variable("X")
assert -Variable("X") != Variable("X")
y = Variable("Y")
assert y == y # noqa: PLR0124
assert y != -y
def test_not_findall() -> None:
assert Not(Variable("X")).findall(lambda x: isinstance(x, Variable)) == [Variable("X")]
def test_not_substituted() -> None:
assert_equal(
Not(Variable("X")).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
Not(Variable("P_X")),
)
assert_equal(
Not(Variable("X")).substituted(lambda x: Variable("Y") if x == Not(Variable("X")) else x),
Variable("Y"),
)
def test_not_simplified() -> None:
assert_equal(Not(TRUE).simplified(), FALSE)
assert_equal(Not(FALSE).simplified(), TRUE)
assert_equal(Or(FALSE, Not(And(TRUE, FALSE)), EXPR).simplified(), TRUE)
assert_equal(And(TRUE, Not(Or(FALSE, TRUE)), EXPR).simplified(), FALSE)
assert_equal(
Not(
And(FALSE, Not(And(TRUE, FALSE))),
).simplified(),
TRUE,
)
assert_equal(
Not(Less(Variable("X"), Variable("Y"))).simplified(),
GreaterEqual(Variable("X"), Variable("Y")),
)
assert_equal(
Not(LessEqual(Variable("X"), Variable("Y"))).simplified(),
Greater(Variable("X"), Variable("Y")),
)
assert_equal(
Not(Equal(Variable("X"), Variable("Y"))).simplified(),
NotEqual(Variable("X"), Variable("Y")),
)
assert_equal(
Not(GreaterEqual(Variable("X"), Variable("Y"))).simplified(),
Less(Variable("X"), Variable("Y")),
)
assert_equal(
Not(Greater(Variable("X"), Variable("Y"))).simplified(),
LessEqual(Variable("X"), Variable("Y")),
)
assert_equal(
Not(NotEqual(Variable("X"), Variable("Y"))).simplified(),
Equal(Variable("X"), Variable("Y")),
)
def test_bin_expr_findall() -> None:
assert Less(Variable("X"), Number(1)).findall(lambda x: isinstance(x, Number)) == [Number(1)]
def test_bin_expr_substituted() -> None:
assert_equal(
Less(Variable("X"), Number(1)).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
Less(Variable("P_X"), Number(1)),
)
assert_equal(
Sub(Variable("X"), Number(1)).substituted(
lambda x: Variable("Y") if x == Sub(Variable("X"), Number(1)) else x,
),
Variable("Y"),
)
assert_equal(
NotEqual(Variable("X"), Number(1)).substituted(
lambda x: (
Variable(f"P_{x}")
if isinstance(x, Variable)
else (Equal(x.left, x.right) if isinstance(x, NotEqual) else x)
),
),
Equal(Variable("P_X"), Number(1)),
)
def test_bin_expr_substituted_location() -> None:
expr = Less(Variable("X"), Number(1), location=Location((1, 2))).substituted(lambda x: x)
assert expr.location
def test_ass_expr_str() -> None:
assert (
str(
Add(
Number(1),
IfExpr([(Variable("A"), Variable("B"))], Variable("C")),
IfExpr([(Variable("X"), Variable("Y"))], Variable("Z")),
),
)
== "1 + (if A then B else C) + (if X then Y else Z)"
)
def test_ass_expr_findall() -> None:
assert_equal(
And(Equal(Variable("X"), Number(1)), Less(Variable("Y"), Number(2))).findall(
lambda x: isinstance(x, Number),
),
[Number(1), Number(2)],
)
def test_ass_expr_simplified() -> None:
assert_equal(
Add(
Number(8),
IfExpr(
[
(
And(
Variable("A"),
Or(Variable("B"), Variable("C")),
Equal(Variable("D"), TRUE),
),
Variable("X"),
),
],
Variable("Y"),
),
Number(16),
IfExpr(
[
(
And(
Variable("A"),
Or(Variable("B"), Variable("C")),
Equal(Variable("D"), FALSE),
),
Variable("X"),
),
],
Variable("Y"),
),
Number(24),
).simplified(),
Add(
IfExpr(
[
(
Or(
And(
Variable("A"),
Or(Variable("B"), Variable("C")),
Variable("D"),
),
And(
Variable("A"),
Or(Variable("B"), Variable("C")),
Not(Variable("D")),
),
),
Variable("X"),
),
],
Variable("Y"),
),
Number(48),
),
)
def test_ass_expr_substituted() -> None:
assert_equal(
And(Equal(Variable("X"), Number(1)), Variable("Y")).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
And(Equal(Variable("P_X"), Number(1)), Variable("P_Y")),
)
assert_equal(
Mul(Variable("X"), Number(1)).substituted(
lambda x: Variable("Y") if x == Mul(Variable("X"), Number(1)) else x,
),
Variable("Y"),
)
assert_equal(
And(Equal(Variable("X"), Number(1)), Variable("Y")).substituted(
lambda x: (
Variable(f"P_{x}")
if isinstance(x, Variable)
else (Or(*x.terms) if isinstance(x, And) else x)
),
),
Or(Equal(Variable("P_X"), Number(1)), Variable("P_Y")),
)
def test_ass_expr_substituted_location() -> None:
expr = And(
Equal(Variable("X"), Number(1)),
Variable("Y"),
location=Location((1, 2)),
).substituted(lambda x: x)
assert expr.location
def test_ass_expr_if_expr_simplified_location() -> None:
simplified_if = Add(
IfExpr(
[(Or(Variable("X"), Variable("Y"), location=Location((1, 1))), Variable("A"))],
Number(42),
),
IfExpr(
[(Or(Variable("X"), Variable("A"), location=Location((1, 2))), Variable("A"))],
Number(42),
),
).simplified()
assert isinstance(simplified_if, IfExpr)
assert simplified_if.condition_expressions[0][0].location == Location((1, 1), end=(1, 2))
def test_bool_expr_str() -> None:
assert_equal(
str(And(Variable("A"), Or(Variable("B"), Variable("C")), Variable("D"))),
textwrap.dedent(
"""\
A
and (B
or C)
and D""",
),
)
assert_equal(
str(AndThen(Variable("A"), OrElse(Variable("B"), Variable("C")), Variable("D"))),
textwrap.dedent(
"""\
A
and then (B
or else C)
and then D""",
),
)
@pytest.mark.parametrize("operation", [And, Or])
def test_bool_expr_type(operation: Callable[[Expr, Expr], Expr]) -> None:
assert_type(
operation(Variable("X", type_=ty.BOOLEAN), Variable("Y", type_=ty.BOOLEAN)),
ty.BOOLEAN,
)
@pytest.mark.parametrize("operation", [And, Or])
def test_bool_expr_type_error(operation: Callable[[Expr, Expr], Expr]) -> None:
assert_type_error(
operation(
Variable(
ID("X", location=Location((10, 20))),
type_=ty.Integer("A", ty.Bounds(0, 100)),
),
Number(1, location=Location((10, 30))),
),
r'^<stdin>:10:20: error: expected enumeration type "__BUILTINS__::Boolean"\n'
r'<stdin>:10:20: error: found integer type "A" \(0 .. 100\)\n'
r'<stdin>:10:30: error: expected enumeration type "__BUILTINS__::Boolean"\n'
r"<stdin>:10:30: error: found type universal integer \(1 .. 1\)$",
)
def test_and_neg() -> None:
with pytest.raises(NotImplementedError):
-And(Variable("X"), TRUE)
def test_and_variables() -> None:
assert_equal(And(Variable("X"), Variable("Y")).variables(), [Variable("X"), Variable("Y")])
def test_and_contains() -> None:
assert Variable("X") in And(TRUE, Less(Variable("X"), Number(42)))
assert Variable("Y") not in And(TRUE, Less(Variable("X"), Number(42)))
def test_and_simplified() -> None:
assert And().simplified() == TRUE
assert And(TRUE, TRUE).simplified() == TRUE
assert And(TRUE, FALSE, TRUE).simplified() == FALSE
assert And(TRUE, EXPR).simplified() == EXPR
assert And(EXPR, TRUE).simplified() == EXPR
assert And(EXPR, FALSE).simplified() == FALSE
assert And(FALSE, EXPR).simplified() == FALSE
assert (
And(
Equal(Variable("X"), Number(0)),
NotEqual(Variable("X"), Number(0)),
Variable("Y"),
).simplified()
== FALSE
)
assert And(
Equal(Variable("X"), Number(0)),
Equal(Variable("X"), Number(0)),
).simplified() == Equal(Variable("X"), Number(0))
def test_and_str() -> None:
assert str(And(Variable("X"), Variable("Y"))) == "X\nand Y"
assert str(And()) == "True"
def test_or_neg() -> None:
with pytest.raises(NotImplementedError):
-Or(Variable("X"), TRUE)
def test_or_variables() -> None:
assert Or(Variable("X"), Variable("Y")).variables() == [Variable("X"), Variable("Y")]
def test_or_contains() -> None:
assert Variable("X") in Or(Less(Variable("X"), Number(42)), TRUE)
assert Variable("Y") not in Or(Less(Variable("X"), Number(42)), TRUE)
def test_or_simplified() -> None:
assert Or().simplified() == FALSE
assert Or(TRUE, TRUE).simplified() == TRUE
assert Or(TRUE, EXPR).simplified() == TRUE
assert Or(EXPR, TRUE).simplified() == TRUE
assert (
Or(
Equal(Variable("X"), Number(0)),
NotEqual(Variable("X"), Number(0)),
Variable("Y"),
).simplified()
== TRUE
)
assert Or(
Equal(Variable("X"), Number(0)),
Equal(Variable("X"), Number(0)),
).simplified() == Equal(Variable("X"), Number(0))
def test_or_str() -> None:
assert str(Or(Variable("X"), Variable("Y"))) == "X\nor Y"
assert str(Or()) == "False"
def test_undefined_simplified() -> None:
assert UNDEFINED.simplified() == UNDEFINED
def test_undefined_str() -> None:
assert str(UNDEFINED) == "__UNDEFINED__"
def test_number_type() -> None:
assert_type(
Number(1),
ty.UniversalInteger(ty.Bounds(1, 1)),
)
def test_number_neg() -> None:
assert -Number(42) == Number(-42)
def test_number_simplified() -> None:
assert Number(42).simplified() == Number(42)
def test_number_add() -> None:
assert Number(5) + Number(3) == Number(8)
def test_number_add_location() -> None:
result = Number(5, location=Location((1, 1))) + Number(3, location=Location((1, 2)))
assert isinstance(result, Number)
assert result.location == Location((1, 1), end=(1, 2))
def test_number_sub() -> None:
assert Number(5) - Number(3) == Number(2)
def test_number_sub_location() -> None:
result = Number(5, location=Location((1, 1))) - Number(3, location=Location((1, 2)))
assert isinstance(result, Number)
assert result.location == Location((1, 1), end=(1, 2))
def test_number_mul() -> None:
assert Number(4) * Number(2) == Number(8)
def test_number_mul_location() -> None:
result = Number(5, location=Location((1, 1))) * Number(3, location=Location((1, 2)))
assert isinstance(result, Number)
assert result.location == Location((1, 1), end=(1, 2))
def test_number_div() -> None:
assert Number(4) // Number(2) == Number(2)
def test_number_div_location() -> None:
result = Number(5, location=Location((1, 1))) // Number(3, location=Location((1, 2)))
assert isinstance(result, Div)
assert result.location == Location((1, 1), end=(1, 2))
def test_number_pow() -> None:
assert Number(2) ** Number(4) == Number(16)
def test_number_pow_location() -> None:
result = Number(5, location=Location((1, 1))) ** Number(3, location=Location((1, 2)))
assert isinstance(result, Number)
assert result.location == Location((1, 1), end=(1, 2))
def test_number_mod_location() -> None:
result = Number(3, location=Location((1, 1))) % Number(2, location=Location((1, 2)))
assert isinstance(result, Number)
assert result.location == Location((1, 1), end=(1, 2))
def test_number_eq() -> None:
assert Number(1) == Number(1)
assert Number(1, 10) == Number(1, 16)
assert Number(42, 16) == Number(42, 10)
def test_number_ne() -> None:
assert Number(1) != Number(2)
assert Number(1, 16) != Number(2, 16)
def test_number_lt() -> None:
assert Number(1) < Number(2)
assert not Number(2) < Number(2)
assert not Number(3) < Number(2)
assert not Variable("X") < Number(2)
assert not Number(2) < Variable("X")
def test_number_le() -> None:
assert Number(1) <= Number(2)
assert Number(2) <= Number(2)
assert not Number(3) <= Number(2)
assert not Variable("X") <= Number(2)
assert not Number(2) <= Variable("X")
def test_number_gt() -> None:
assert not Number(1) > Number(2)
assert not Number(2) > Number(2)
assert Number(3) > Number(2)
assert Variable("X") > Number(2)
assert not Number(2) > Variable("X")
def test_number_ge() -> None:
assert not Number(1) >= Number(2)
assert Number(2) >= Number(2)
assert Number(3) >= Number(2)
assert Variable("X") >= Number(2)
assert not Number(2) >= Variable("X")
def test_number_hashable() -> None:
assert {Number(1), Number(2)}
@pytest.mark.parametrize("operation", [Add, Mul, Sub, Div, Pow])
def test_math_expr_type(operation: Callable[[Expr, Expr], Expr]) -> None:
assert_type(
operation(Variable("X", type_=INT_TY), Variable("Y", type_=INT_TY)),
ty.BASE_INTEGER,
)
@pytest.mark.parametrize("operation", [Add, Mul, Sub, Div, Pow])
def test_math_expr_type_error(operation: Callable[[Expr, Expr], Expr]) -> None:
assert_type_error(
operation(
Variable(ID("X", location=Location((10, 20))), type_=ty.BOOLEAN),
Variable(ID("True", location=Location((10, 30))), type_=ty.BOOLEAN),
),
r"^<stdin>:10:20: error: expected integer type\n"
r'<stdin>:10:20: error: found enumeration type "__BUILTINS__::Boolean"\n'
r"<stdin>:10:30: error: expected integer type\n"
r'<stdin>:10:30: error: found enumeration type "__BUILTINS__::Boolean"$',
)
def test_neg_str() -> None:
assert str(Neg(Variable("X"))) == "-X"
assert str(Neg(Neg(Variable("X")))) == "-(-X)"
def test_neg_type() -> None:
assert_type(
Neg(Variable("X", type_=INT_TY)),
INT_TY,
)
def test_neg_type_error() -> None:
assert_type_error(
Neg(Variable(ID("X", location=Location((10, 20))), type_=ty.BOOLEAN)),
r"^<stdin>:10:20: error: expected integer type\n"
r'<stdin>:10:20: error: found enumeration type "__BUILTINS__::Boolean"$',
)
def test_neg_neg() -> None:
assert -Neg(Variable("X")) == Variable("X")
assert -Variable("X") != Variable("X")
y = Variable("Y")
assert y == y # noqa: PLR0124
assert y != -y
def test_neg_findall() -> None:
assert Neg(Variable("X")).findall(lambda x: isinstance(x, Variable)) == [Variable("X")]
def test_neg_substituted() -> None:
assert_equal(
Neg(Variable("X")).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
Neg(Variable("P_X")),
)
assert_equal(
Neg(Variable("X")).substituted(lambda x: Variable("Y") if x == Neg(Variable("X")) else x),
Variable("Y"),
)
@pytest.mark.parametrize(
("expr", "expected"),
[
# Argument is Neg
(Neg(Neg(Variable("X"))), Variable("X")),
# Argument simplifies to Neg
(Neg(Neg(Neg(Variable("X")))), Neg(Variable("X"))),
(Neg(Neg(Neg(Neg(Variable("X"))))), Variable("X")),
# Argument is Number
(Neg(Number(42)), Number(-42)),
(Neg(Number(-42)), Number(42)),
# Argument simplifies to Number
(Neg(Neg(Number(42))), Number(42)),
(Neg(Neg(Neg(Number(42)))), Number(-42)),
(Neg(Mul(Number(3), Neg(Number(2)))), Number(6)),
(Neg(Mul(Number(3), Neg(Number(-2)))), Number(-6)),
(Sub(Number(0), Neg(Add(Number(8), Number(7)))), Number(15)),
# Argument simplifies to some other expression
(
Neg(Mul(Variable("X"), Add(Number(2), Number(3)))),
Mul(Variable("X"), Number(-5)),
),
# Argument cannot be simplified
(Neg(Variable("X")), Neg(Variable("X"))),
],
)
def test_neg_simplified(expr: Expr, expected: Expr) -> None:
assert expr.simplified() == expected
def test_add_str() -> None:
assert str(Add(Variable("X"), Number(1))) == "X + 1"
assert str(-Add(Variable("X"), Number(1))) == "-X - 1"
assert str(Add(Number(1), Call("Test", ty.BASE_INTEGER, []))) == "1 + Test"
assert str(Add(Number(1), -Call("Test", ty.BASE_INTEGER, []))) == "1 - Test"
assert str(Add()) == "0"
def test_add_neg() -> None:
assert -Add(Variable("X"), Number(1)) == Add(Neg(Variable("X")), Number(-1))
@pytest.mark.parametrize(
("args"),
[
[],
[0],
[42],
# 0 in argument
[0, 0],
[0, 5],
[1, 0],
[1, 0, 0],
# Argument sign variations
[1, 5],
[-1, 5],
[1, -5],
[-1, -5],
[1, 5, 42],
[-1, 5, -42],
[1, -5, 42],
[-1, -5, -42],
],
)
def test_add_neg_eval(args: list[int]) -> None:
assert (-Add(*[Number(a) for a in args])).simplified() == Number(-sum(args))
def test_add_variables() -> None:
assert_equal(Add(Variable("X"), Variable("Y")).variables(), [Variable("X"), Variable("Y")])
def test_add_simplified() -> None:
assert Add(Variable("X"), Number(1)).simplified() == Add(Variable("X"), Number(1))
assert Add(Variable("X"), Number(0)).simplified() == Variable("X")
assert Add(Number(2), Number(3), Number(5)).simplified() == Number(10)
assert Add(Variable("X"), Variable("Y"), Neg(Variable("X"))).simplified() == Variable(
"Y",
)
assert Add(Variable("X"), Variable("Y"), Variable("X"), -Variable("X")).simplified() == Add(
Variable("X"),
Variable("Y"),
)
def test_add_lt() -> None:
assert Add(Variable("X"), Number(1)) < Add(Variable("X"), Number(2))
assert not Add(Variable("X"), Number(2)) < Add(Variable("X"), Number(2))
assert not Add(Variable("X"), Number(3)) < Add(Variable("X"), Number(2))
assert Add(Variable("X"), Number(1)) < Add(Variable("Y"), Number(2))
assert Add(Variable("X"), Number(2)) < Add(Variable("Y"), Number(1))
assert Add(Variable("X"), Number(2)) < Add(Variable("Y"), Variable("Z"), Number(1))
def test_add_le() -> None:
assert Add(Variable("X"), Number(1)) <= Add(Variable("X"), Number(2))
assert Add(Variable("X"), Number(2)) <= Add(Variable("X"), Number(2))
assert not Add(Variable("X"), Number(3)) <= Add(Variable("X"), Number(2))
assert Add(Variable("X"), Number(1)) <= Add(Variable("Y"), Number(2))
assert Add(Variable("X"), Number(2)) <= Add(Variable("Y"), Number(1))
assert Add(Variable("X"), Number(2)) <= Add(Variable("Y"), Variable("Z"), Number(1))
def test_add_gt() -> None:
assert not Add(Variable("X"), Number(1)) > Add(Variable("X"), Number(2))
assert not Add(Variable("X"), Number(2)) > Add(Variable("X"), Number(2))
assert Add(Variable("X"), Number(3)) > Add(Variable("X"), Number(2))
assert not Add(Variable("X"), Number(1)) > Add(Variable("Y"), Number(2))
assert not Add(Variable("X"), Number(2)) > Add(Variable("Y"), Number(1))
assert not Add(Variable("X"), Number(2)) > Add(Variable("Y"), Variable("Z"), Number(1))
def test_add_ge() -> None:
assert not Add(Variable("X"), Number(1)) >= Add(Variable("X"), Number(2))
assert Add(Variable("X"), Number(2)) >= Add(Variable("X"), Number(2))
assert Add(Variable("X"), Number(3)) >= Add(Variable("X"), Number(2))
assert not Add(Variable("X"), Number(1)) >= Add(Variable("Y"), Number(2))
assert not Add(Variable("X"), Number(2)) >= Add(Variable("Y"), Number(1))
assert not Add(Variable("X"), Number(2)) >= Add(Variable("Y"), Variable("Z"), Number(1))
def test_add_simplified_location() -> None:
simplified = Add(
Number(1),
Number(1),
Number(1),
Number(1),
location=Location((1, 1)),
).simplified()
assert simplified == Number(4)
assert simplified.location == Location((1, 1))
def test_mul_neg() -> None:
assert -Mul(Variable("X"), Number(2)) == Mul(Variable("X"), Number(-2))
@pytest.mark.parametrize(
("args"),
[
[],
[0],
[42],
# 0 in argument
[0, 0],
[0, 5],
[1, 0],
[1, 0, 0],
# Argument sign variations
[1, 5],
[-1, 5],
[1, -5],
[-1, -5],
[1, 5, 42],
[-1, 5, -42],
[1, -5, 42],
[-1, -5, -42],
],
)
def test_mul_neg_eval(args: list[int]) -> None:
assert (-Mul(*[Number(a) for a in args])).simplified() == Number(-math.prod(args))
def test_mul_variables() -> None:
assert_equal(Mul(Variable("X"), Variable("Y")).variables(), [Variable("X"), Variable("Y")])
def test_mul_simplified() -> None:
assert Mul(Variable("X"), Number(2)).simplified() == Mul(Variable("X"), Number(2))
assert Mul(Variable("X"), Number(1)).simplified() == Variable("X")
assert Mul(Number(2), Number(3), Number(5)).simplified() == Number(30)
def test_sub_str() -> None:
assert str(Sub(Variable("X"), Number(1))) == "X - 1"
assert str(Sub(Neg(Variable("X")), Number(-1))) == "-X - (-1)"
def test_sub_eq() -> None:
assert Sub(Variable("X"), Number(1), location=Location((1, 2))) == Sub(Variable("X"), Number(1))
def test_sub_ne() -> None:
assert Sub(Variable("X"), Number(1)) != Sub(Variable("Y"), Number(1))
assert Sub(Variable("X"), Number(1)) != Sub(Variable("X"), Number(2))
def test_sub_neg() -> None:
assert -Sub(Number(1), Variable("X")) == Sub(Variable("X"), Number(1))
@pytest.mark.parametrize(
("left", "right"),
[
# 0 in argument
(0, 0),
(0, 5),
(1, 0),
# Argument sign variations
(1, 5),
(-1, 5),
(1, -5),
(-1, -5),
],
)
def test_sub_neg_eval(left: int, right: int) -> None:
assert (-Sub(Number(left), Number(right))).simplified() == Number(-(left - right))
def test_sub_type() -> None:
assert_type(
Sub(Variable("X", type_=INT_TY), Number(1)),
ty.BASE_INTEGER,
)
assert_type_instance(
Sub(Variable("X", type_=INT_TY), Number(1)),
ty.Integer,
)
def test_sub_type_error() -> None:
assert_type_error(
Sub(Variable(ID("X", location=Location((1, 2))), type_=ty.BOOLEAN), Number(1)),
r"^"
r"<stdin>:1:2: error: expected integer type\n"
r'<stdin>:1:2: error: found enumeration type "__BUILTINS__::Boolean"'
r"$",
)
def test_sub_variables() -> None:
assert Sub(Variable("X"), Variable("Y")).variables() == [Variable("X"), Variable("Y")]
def test_sub_findall() -> None:
assert Sub(Variable("X"), Variable("Y")).findall(lambda x: isinstance(x, Variable)) == [
Variable("X"),
Variable("Y"),
]
def test_sub_substituted() -> None:
assert Sub(Variable("X"), Variable("Y")).substituted(
lambda x: Number(42) if x == Variable("X") else x,
) == Sub(Number(42), Variable("Y"))
def test_sub_simplified() -> None:
assert Sub(Number(1), Variable("X")).simplified() == Add(Number(1), -Variable("X"))
assert Sub(Variable("X"), Number(1)).simplified() == Add(Variable("X"), Number(-1))
assert Sub(Number(6), Number(2)).simplified() == Number(4)
assert Sub(Variable("X"), Variable("Y")).simplified() == Add(Variable("X"), -Variable("Y"))
assert (
Equal(Variable("Q"), Sub(Add(Variable("Y"), Variable("Q")), Variable("Y")))
.simplified()
.simplified()
== TRUE
)
def test_sub_simplified_location() -> None:
simplified = Sub(
Number(5, location=Location((1, 1))),
Number(2, location=Location((1, 2))),
).simplified()
assert isinstance(simplified, Number)
assert simplified.location == Location((1, 1), end=(1, 2))
def test_sub_simplified_to_add() -> None:
simplified = Sub(
Variable(ID("X", location=Location((1, 1)))),
Number(2, location=Location((1, 2))),
).simplified()
assert isinstance(simplified, Add)
assert simplified.location == Location((1, 1), end=(1, 2))
def test_div_str() -> None:
assert str(Div(Variable("X"), Number(1))) == "X / 1"
assert str(Div(Neg(Variable("X")), Number(-1))) == "(-X) / (-1)"
def test_div_eq() -> None:
assert Div(Variable("X"), Number(1), location=Location((1, 2))) == Div(Variable("X"), Number(1))
def test_div_ne() -> None:
assert Div(Variable("X"), Number(1)) != Div(Variable("Y"), Number(1))
assert Div(Variable("X"), Number(1)) != Div(Variable("X"), Number(2))
def test_div_neg() -> None:
assert -Div(Variable("X"), Number(5)) == Div(-(Variable("X")), Number(5))
@pytest.mark.parametrize(
("left", "right"),
[
# 0 in argument
(0, 5),
# Argument sign variations
(10, 5),
(-10, 5),
(10, -5),
(-10, -5),
],
)
def test_div_neg_eval(left: int, right: int) -> None:
assert (-Div(Number(left), Number(right))).simplified() == Number(-(left // right))
def test_div_type() -> None:
assert_type(
Div(Variable("X", type_=INT_TY), Number(1)),
ty.BASE_INTEGER,
)
assert_type_instance(
Div(Variable("X", type_=INT_TY), Number(1)),
ty.Integer,
)
def test_div_type_error() -> None:
assert_type_error(
Div(Variable(ID("X", location=Location((1, 2))), type_=ty.BOOLEAN), Number(1)),
r"^"
r"<stdin>:1:2: error: expected integer type\n"
r'<stdin>:1:2: error: found enumeration type "__BUILTINS__::Boolean"'
r"$",
)
def test_div_variables() -> None:
assert Div(Variable("X"), Variable("Y")).variables() == [Variable("X"), Variable("Y")]
def test_div_findall() -> None:
assert Div(Variable("X"), Variable("Y")).findall(lambda x: isinstance(x, Variable)) == [
Variable("X"),
Variable("Y"),
]
def test_div_substituted() -> None:
assert Div(Variable("X"), Variable("Y")).substituted(
lambda x: Number(42) if x == Variable("X") else x,
) == Div(Number(42), Variable("Y"))
def test_div_simplified() -> None:
assert Div(Variable("X"), Number(1)).simplified() == Div(Variable("X"), Number(1))
assert Div(Number(6), Number(2)).simplified() == Number(3)
assert Div(Number(9), Number(2)).simplified() == Div(Number(9), Number(2))
def test_pow_str() -> None:
assert str(Pow(Variable("X"), Number(1))) == "X ** 1"
assert str(Pow(Neg(Variable("X")), Number(-1))) == "(-X) ** (-1)"
def test_pow_eq() -> None:
assert Pow(Variable("X"), Number(1), location=Location((1, 2))) == Pow(Variable("X"), Number(1))
def test_pow_ne() -> None:
assert Pow(Variable("X"), Number(1)) != Pow(Variable("Y"), Number(1))
assert Pow(Variable("X"), Number(1)) != Pow(Variable("X"), Number(2))
def test_pow_neg() -> None:
assert -Pow(Variable("X"), Number(5)) == -Pow(Variable("X"), Number(5))
@pytest.mark.parametrize(
("left", "right"),
[
# 0 in argument
(0, 0),
(-10, 0),
(0, 4),
# Argument sign variations
# Constraints:
# * The second argument cannot be negative.
# * The second argument must be tested with an even and odd value.
(10, 4),
(-10, 4),
(10, 5),
(-10, 5),
],
)
def test_pow_neg_eval(left: int, right: int) -> None:
assert (-Pow(Number(left), Number(right))).simplified() == Number(-(left**right))
def test_pow_type() -> None:
assert_type(
Pow(Variable("X", type_=INT_TY), Number(1)),
ty.BASE_INTEGER,
)
assert_type_instance(
Pow(Variable("X", type_=INT_TY), Number(1)),
ty.Integer,
)
def test_pow_type_error() -> None:
assert_type_error(
Pow(Variable(ID("X", location=Location((1, 2))), type_=ty.BOOLEAN), Number(1)),
r"^"
r"<stdin>:1:2: error: expected integer type\n"
r'<stdin>:1:2: error: found enumeration type "__BUILTINS__::Boolean"'
r"$",
)
def test_pow_variables() -> None:
assert Pow(Variable("X"), Variable("Y")).variables() == [Variable("X"), Variable("Y")]
def test_pow_findall() -> None:
assert Pow(Variable("X"), Variable("Y")).findall(lambda x: isinstance(x, Variable)) == [
Variable("X"),
Variable("Y"),
]
def test_pow_substituted() -> None:
assert Pow(Variable("X"), Variable("Y")).substituted(
lambda x: Number(42) if x == Variable("X") else x,
) == Pow(Number(42), Variable("Y"))
def test_pow_simplified() -> None:
assert Pow(Variable("X"), Number(1)).simplified() == Pow(Variable("X"), Number(1))
assert Pow(Variable("X"), Add(Number(1), Number(1))).simplified() == Pow(
Variable("X"),
Number(2),
)
assert Pow(Number(6), Number(2)).simplified() == Number(36)
def test_rem_neg() -> None:
assert -Rem(Variable("X"), Number(5)) == -Rem(Variable("X"), Number(5))
@pytest.mark.parametrize(
("left", "right", "expected"),
[
# 0 in argument
(0, 3, -Rem(Number(0), Number(3))),
# Argument sign variations
(7, 3, -Rem(Number(7), Number(3))),
(-7, 3, -Rem(Number(-7), Number(3))),
(7, -3, -Rem(Number(7), Number(-3))),
(-7, -3, -Rem(Number(-7), Number(-3))),
],
)
def test_rem_neg_eval(left: int, right: int, expected: Expr) -> None:
assert (-Rem(Number(left), Number(right))).simplified() == expected
def test_mod_str() -> None:
assert str(Mod(Variable("X"), Number(1))) == "X mod 1"
assert str(Mod(Neg(Variable("X")), Number(-1))) == "(-X) mod (-1)"
def test_mod_eq() -> None:
assert Mod(Variable("X"), Number(1), location=Location((1, 2))) == Mod(Variable("X"), Number(1))
def test_mod_ne() -> None:
assert Mod(Variable("X"), Number(1)) != Mod(Variable("Y"), Number(1))
assert Mod(Variable("X"), Number(1)) != Mod(Variable("X"), Number(2))
def test_mod_neg() -> None:
assert -Mod(Variable("X"), Number(5)) == -Mod(Variable("X"), Number(5))
@pytest.mark.parametrize(
("left", "right"),
[
# 0 in argument
(0, 3),
# Argument sign variations
(7, 3),
(-7, 3),
(7, -3),
(-7, -3),
],
)
def test_mod_neg_eval(left: int, right: int) -> None:
assert (-Mod(Number(left), Number(right))).simplified() == Number(-(left % right))
def test_mod_variables() -> None:
assert Mod(Variable("X"), Variable("Y")).variables() == [Variable("X"), Variable("Y")]
def test_mod_findall() -> None:
assert Mod(Variable("X"), Variable("Y")).findall(lambda x: isinstance(x, Variable)) == [
Variable("X"),
Variable("Y"),
]
def test_mod_substituted() -> None:
assert Mod(Variable("X"), Variable("Y")).substituted(
lambda x: Number(42) if x == Variable("X") else x,
) == Mod(Number(42), Variable("Y"))
def test_mod_simplified() -> None:
assert Mod(Variable("X"), Number(1)).simplified() == Mod(Variable("X"), Number(1))
assert Mod(Variable("X"), Add(Number(1), Number(1))).simplified() == Mod(
Variable("X"),
Number(2),
)
assert Mod(Number(6), Number(2)).simplified() == Number(0)
def test_term_simplified() -> None:
assert_equal(
Add(
Mul(Number(1), Number(6)),
Sub(Variable("X"), Number(10)),
Add(Number(1), Number(3)),
).simplified(),
Variable("X"),
)
def test_variable_invalid_name() -> None:
with pytest.raises(BaseException, match=r"^invalid identifier$"):
Variable("Foo (Bar)")
def test_variable_type() -> None:
assert_type(
Variable("X", type_=ty.BOOLEAN),
ty.BOOLEAN,
)
assert_type(
Variable("X", type_=INT_TY),
INT_TY,
)
def test_variable_type_error() -> None:
assert_type_error(
Variable(ID("X", location=Location((10, 20)))),
r'^<stdin>:10:20: error: undefined variable "X"$',
)
def test_variable_neg() -> None:
assert -Variable("X") == Neg(Variable("X"))
def test_variable_variables() -> None:
assert Variable("X").variables() == [Variable("X")]
assert (-Variable("X")).variables() == [Variable("X")]
def test_variable_substituted() -> None:
assert_equal(
Variable("X").substituted(lambda x: Number(42) if x == Variable("X") else x),
Number(42),
)
def test_mutable_variable_substituted() -> None:
x = Variable("X")
assert_equal(x.substituted(substitution({Variable("X"): Number(42)})), Number(42))
def test_variable_simplified() -> None:
assert Variable("X").simplified() == Variable("X")
def test_attribute() -> None:
assert isinstance(Size("X"), Attribute)
assert isinstance(Length("X"), Attribute)
assert isinstance(First("X"), Attribute)
assert isinstance(Last("X"), Attribute)
assert First("X") == First(Variable("X"))
assert First("X") == First(ID("X"))
assert First("X") == First(Variable(ID("X")))
@pytest.mark.parametrize(
("attribute", "expr", "expected"),
[
(Size, Variable("X", type_=INT_TY), ty.UNIVERSAL_INTEGER),
(Length, Variable("X", type_=INT_TY), ty.UNIVERSAL_INTEGER),
(First, Variable("X", type_=INT_TY), ty.UNIVERSAL_INTEGER),
(Last, Variable("X", type_=INT_TY), ty.UNIVERSAL_INTEGER),
(ValidChecksum, Variable("X", type_=INT_TY), ty.BOOLEAN),
(Valid, Variable("X", type_=ty.Message("A")), ty.BOOLEAN),
(
Present,
Selected(
Variable("X", type_=ty.Message("M", {("F",)}, {ID("F"): INT_TY})),
"F",
),
ty.BOOLEAN,
),
(Head, Variable("X", type_=ty.Sequence("A", INT_TY)), INT_TY),
(Opaque, Variable("X", type_=ty.Message("A")), ty.OPAQUE),
(
Head,
Comprehension(
"X",
Variable("Y", type_=ty.Sequence("A", INT_TY)),
Variable("X", type_=INT_TY),
TRUE,
location=Location((10, 30)),
),
INT_TY,
),
(
Head,
Variable(
ID("Z", location=Location((10, 20))),
type_=ty.Sequence("Universal::Options", ty.Message("Universal::Option")),
),
ty.Message("Universal::Option"),
),
],
)
def test_attribute_type(attribute: Callable[[Expr], Expr], expr: Expr, expected: ty.Type) -> None:
assert_type(
attribute(expr),
expected,
)
@pytest.mark.parametrize(
("expr", "match"),
[
(
Present(Variable(ID("X", location=Location((10, 30))))),
r"^<stdin>:10:30: error: invalid prefix for attribute Present$",
),
(
Head(
Opaque(
Variable(
ID("X", location=Location((10, 30))),
type_=ty.Sequence("A", INT_TY),
),
),
),
r"^<stdin>:10:30: error: prefix of attribute Head must be a name or comprehension$",
),
(
Opaque(
Call(
"X",
ty.UNDEFINED,
[Variable(ID("Y", location=Location((10, 30))))],
location=Location((10, 20)),
),
),
r'^<stdin>:10:30: error: undefined variable "Y"\n'
r'<stdin>:10:20: error: undefined function "X"$',
),
],
)
def test_attribute_type_error(expr: Expr, match: str) -> None:
assert_type_error(
expr,
match,
)
def test_attribute_neg() -> None:
assert -First("X") == Neg(First("X"))
def test_attributes_findall() -> None:
assert First("X").findall(lambda x: isinstance(x, Variable)) == [Variable("X")]
def test_attribute_substituted() -> None:
assert_equal(First("X").substituted(lambda x: Number(42) if x == First("X") else x), Number(42))
assert_equal(
-First("X").substituted(lambda x: Number(42) if x == First("X") else x),
Number(-42),
)
assert_equal(
First("X").substituted(lambda x: Call("Y", ty.BASE_INTEGER) if x == Variable("X") else x),
First(Call("Y", ty.BASE_INTEGER)),
)
assert_equal(
-First("X").substituted(lambda x: Call("Y", ty.BASE_INTEGER) if x == Variable("X") else x),
-First(Call("Y", ty.BASE_INTEGER)),
)
assert_equal(
-First("X").substituted(
lambda x: (
Variable(f"P_{x}")
if isinstance(x, Variable)
else (Last(x.prefix) if isinstance(x, First) else x)
),
),
-Last(Variable("P_X")),
)
assert_equal(
First("X").substituted(lambda x: First("Y") if x == Variable("X") else x),
First("X"),
)
def test_attribute_substituted_location() -> None:
expr = First(Variable(ID("X", location=Location((1, 2))))).substituted(lambda x: x)
assert expr.location
def test_attribute_simplified() -> None:
assert First("X").simplified() == First("X")
def test_attribute_str() -> None:
assert str(First("X")) == "X'First"
assert str(HasData("X")) == "X'Has_Data"
def test_attribute_variables() -> None:
assert First("X").variables() == [Variable("X")]
assert First(Call("X", ty.BASE_INTEGER, [Variable("Y")])).variables() == [
Variable("X"),
Variable("Y"),
]
def test_val_substituted() -> None:
assert_equal( # pragma: no branch
Val("X", Variable("Y")).substituted(
lambda x: Number(42) if x == Val("X", Variable("Y")) else x,
),
Val("X", Variable("Y")),
)
assert_equal( # pragma: no branch
-Val("X", Variable("Y")).substituted(
lambda x: Number(42) if x == Val("X", Variable("Y")) else x,
),
-Val("X", Variable("Y")),
)
def test_val_simplified() -> None:
assert Val("X", Add(Number(1), Number(1))).simplified() == Val("X", Add(Number(1), Number(1)))
def test_val_str() -> None:
assert str(Val("X", Number(1))) == "X'Val (1)"
def test_aggregate_type() -> None:
assert_type(
Aggregate(Number(0), Number(1)),
ty.Aggregate(ty.UniversalInteger(ty.Bounds(0, 1))),
)
def test_aggregate_substituted() -> None:
assert_equal(
Aggregate(First("X")).substituted(
lambda x: Number(42) if x == Aggregate(First("X")) else x,
),
Number(42),
)
assert_equal(
Aggregate(First("X")).substituted(lambda x: Number(42) if x == First("X") else x),
Aggregate(Number(42)),
)
assert_equal(
Aggregate(Variable("X")).substituted(
lambda x: (
Variable(f"P_{x}")
if isinstance(x, Variable)
else (Aggregate(*([*x.elements, Variable("Y")])) if isinstance(x, Aggregate) else x)
),
),
Aggregate(Variable("P_X"), Variable("P_Y")),
)
def test_aggregate_substituted_location() -> None:
expr = Aggregate(Number(0), location=Location((1, 2))).substituted(lambda x: x)
assert expr.location
def test_aggregate_simplified() -> None:
assert Aggregate(Add(Number(1), Number(1))).simplified() == Aggregate(Number(2))
def test_aggregate_str() -> None:
assert str(Aggregate(Number(1))) == "[1]"
assert str(Aggregate(Number(1), Number(2))) == "[1, 2]"
def test_aggregate_precedence() -> None:
assert Aggregate(Number(1), Number(2)).precedence == Precedence.LITERAL
@pytest.mark.parametrize("relation", [Less, LessEqual, Equal, GreaterEqual, Greater, NotEqual])
def test_relation_integer_type(relation: Callable[[Expr, Expr], Expr]) -> None:
assert_type(
relation(Variable("X", type_=INT_TY), Variable("Y", type_=INT_TY)),
ty.BOOLEAN,
)
@pytest.mark.parametrize("relation", [Less, LessEqual, Equal, GreaterEqual, Greater, NotEqual])
def test_relation_integer_type_error(relation: Callable[[Expr, Expr], Expr]) -> None:
integer_type = (
r'integer type "I" \(10 \.\. 100\)' if relation in [Equal, NotEqual] else r"integer type"
)
assert_type_error(
relation(
Variable("X", type_=INT_TY),
Variable(ID("True", location=Location((10, 30))), type_=ty.BOOLEAN),
),
rf"^<stdin>:10:30: error: expected {integer_type}\n"
r'<stdin>:10:30: error: found enumeration type "__BUILTINS__::Boolean"$',
)
@pytest.mark.parametrize("relation", [In, NotIn])
def test_relation_composite_type(relation: Callable[[Expr, Expr], Expr]) -> None:
assert_type(
relation(
Variable("X", type_=INT_TY),
Variable("Y", type_=ty.Sequence("A", INT_TY)),
),
ty.BOOLEAN,
)
@pytest.mark.parametrize("relation", [In, NotIn])
def test_relation_composite_type_error(relation: Callable[[Expr, Expr], Expr]) -> None:
assert_type_error(
relation(
Variable(ID("X", location=Location((10, 20))), type_=INT_TY),
Variable(ID("True", location=Location((10, 30))), type_=ty.BOOLEAN),
),
r"^<stdin>:10:30: error: expected aggregate"
r' with element integer type "I" \(10 \.\. 100\)\n'
r'<stdin>:10:30: error: found enumeration type "__BUILTINS__::Boolean"$',
)
assert_type_error(
relation(
Variable(ID("X", location=Location((10, 20))), type_=INT_TY),
Variable(ID("Y", location=Location((10, 30))), type_=ty.Sequence("A", ty.BOOLEAN)),
),
r"^<stdin>:10:30: error: expected aggregate"
r' with element integer type "I" \(10 \.\. 100\)\n'
r'<stdin>:10:30: error: found sequence type "A"'
r' with element enumeration type "__BUILTINS__::Boolean"$',
)
def test_relation_substituted() -> None:
assert_equal(
Equal(Variable("X"), Variable("Y")).substituted(
lambda x: Number(1) if x == Variable("X") else x,
),
Equal(Number(1), Variable("Y")),
)
assert_equal(
Equal(Variable("X"), Variable("Y")).substituted(
lambda x: Number(1) if x == Variable("Y") else x,
),
Equal(Variable("X"), Number(1)),
)
assert_equal(
Equal(Variable("X"), Variable("Y")).substituted(
lambda x: Number(1) if x == Equal(Variable("X"), Variable("Y")) else x,
),
Number(1),
)
def test_relation_substituted_location() -> None:
expr = Equal(Variable("X"), Variable("Y"), location=Location((1, 2))).substituted(lambda x: x)
assert expr.location
def test_relation_simplified() -> None:
assert_equal(
Equal(Variable("X"), Add(Number(1), Number(1))).simplified(),
Equal(Variable("X"), Number(2)),
)
assert_equal(
Equal(Add(Number(1), Number(1)), Variable("X")).simplified(),
Equal(Number(2), Variable("X")),
)
assert_equal(
Equal(Add(Number(1), Number(1)), Add(Number(1), Number(1))).simplified(),
TRUE,
)
assert_equal(Equal(String("Foo Bar"), String("Foo Bar")).simplified(), TRUE)
assert_equal(
Equal(String("Foo"), Aggregate(Number(70), Number(111), Number(111))).simplified(),
TRUE,
)
assert_equal(
Equal(
Aggregate(Number(0), Number(1), Number(2)),
Aggregate(Number(0), Number(1), Number(2)),
).simplified(),
TRUE,
)
assert_equal(
Equal(
Aggregate(Number(1), Number(2), Number(3)),
Aggregate(Number(4), Number(5), Number(6)),
).simplified(),
FALSE,
)
l = Literal("L")
k = Literal("K")
assert Equal(l, l).simplified() == TRUE
assert Equal(l, k).simplified() == FALSE
assert NotEqual(l, l).simplified() == FALSE
assert NotEqual(l, k).simplified() == TRUE
assert Equal(TRUE, TRUE).simplified() == TRUE
assert Equal(TRUE, FALSE).simplified() == FALSE
assert NotEqual(TRUE, TRUE).simplified() == FALSE
assert NotEqual(TRUE, FALSE).simplified() == TRUE
assert Equal(Variable("X"), Variable("X")).simplified() == TRUE
assert LessEqual(Variable("X"), Variable("X")).simplified() == TRUE
assert GreaterEqual(Variable("X"), Variable("X")).simplified() == TRUE
assert NotEqual(Variable("X"), Variable("X")).simplified() == FALSE
assert Equal(Variable("X"), Variable("Y")).simplified() == Equal(Variable("X"), Variable("Y"))
assert NotEqual(Variable("X"), Variable("Y")).simplified() == NotEqual(
Variable("X"),
Variable("Y"),
)
def test_relation_contains() -> None:
assert Variable("X") in Less(Variable("X"), Number(42))
def test_relation_variables() -> None:
assert Less(Variable("X"), Variable("Y")).variables() == [Variable("X"), Variable("Y")]
def test_less_neg() -> None:
assert -Less(Variable("X"), Number(1)) == GreaterEqual(Variable("X"), Number(1))
def test_less_simplified() -> None:
assert Less(Number(0), Number(1)).simplified() == TRUE
assert Less(Number(1), Number(1)).simplified() == FALSE
assert Less(Number(2), Number(1)).simplified() == FALSE
assert Less(Div(Number(1), Number(8)), Div(Number(1), Number(8))).simplified() == FALSE
assert Less(Div(Number(1), Number(8)), Div(Number(2), Number(8))).simplified() == TRUE
assert Less(Div(Number(1), Number(6)), Div(Number(1), Number(8))).simplified() == FALSE
def test_less_equal_neg() -> None:
assert -LessEqual(Variable("X"), Number(1)) == Greater(Variable("X"), Number(1))
def test_less_equal_simplified() -> None:
assert LessEqual(Number(0), Number(1)).simplified() == TRUE
assert LessEqual(Number(1), Number(1)).simplified() == TRUE
assert LessEqual(Number(2), Number(1)).simplified() == FALSE
assert LessEqual(Div(Number(1), Number(8)), Div(Number(1), Number(8))).simplified() == TRUE
assert LessEqual(Div(Number(1), Number(8)), Div(Number(2), Number(8))).simplified() == TRUE
assert LessEqual(Div(Number(1), Number(6)), Div(Number(1), Number(8))).simplified() == FALSE
def test_equal_neg() -> None:
assert -Equal(Variable("X"), Number(1)) == NotEqual(Variable("X"), Number(1))
def test_equal_simplified() -> None:
assert Equal(Number(0), Number(1)).simplified() == FALSE
assert Equal(Number(1), Number(1)).simplified() == TRUE
assert Equal(Number(2), Number(1)).simplified() == FALSE
assert Equal(Div(Number(1), Number(8)), Div(Number(1), Number(8))).simplified() == TRUE
assert Equal(Div(Number(1), Number(8)), Div(Number(2), Number(8))).simplified() == FALSE
assert Equal(Div(Number(1), Number(6)), Div(Number(1), Number(8))).simplified() == FALSE
assert Equal(Variable("X"), TRUE).simplified() == Variable("X")
assert Equal(Variable("X"), FALSE).simplified() == Not(Variable("X"))
assert NotEqual(Variable("X"), FALSE).simplified() == Variable("X")
assert NotEqual(Variable("X"), TRUE).simplified() == Not(Variable("X"))
assert NotEqual(TRUE, Variable("X")).simplified() == Not(Variable("X"))
def test_greater_equal_neg() -> None:
assert -GreaterEqual(Variable("X"), Number(1)) == Less(Variable("X"), Number(1))
def test_greater_equal_simplified() -> None:
assert GreaterEqual(Number(0), Number(1)).simplified() == FALSE
assert GreaterEqual(Number(1), Number(1)).simplified() == TRUE
assert GreaterEqual(Number(2), Number(1)).simplified() == TRUE
assert GreaterEqual(Div(Number(1), Number(8)), Div(Number(1), Number(8))).simplified() == TRUE
assert GreaterEqual(Div(Number(1), Number(8)), Div(Number(2), Number(8))).simplified() == FALSE
assert GreaterEqual(Div(Number(1), Number(6)), Div(Number(1), Number(8))).simplified() == TRUE
def test_greater_neg() -> None:
assert -Greater(Variable("X"), Number(1)) == LessEqual(Variable("X"), Number(1))
def test_greater_simplified() -> None:
assert Greater(Number(0), Number(1)).simplified() == FALSE
assert Greater(Number(1), Number(1)).simplified() == FALSE
assert Greater(Number(2), Number(1)).simplified() == TRUE
assert Greater(Div(Number(1), Number(8)), Div(Number(1), Number(8))).simplified() == FALSE
assert Greater(Div(Number(1), Number(8)), Div(Number(2), Number(8))).simplified() == FALSE
assert Greater(Div(Number(1), Number(6)), Div(Number(1), Number(8))).simplified() == TRUE
def test_not_equal_neg() -> None:
assert -NotEqual(Variable("X"), Number(1)) == Equal(Variable("X"), Number(1))
def test_not_equal_simplified() -> None:
assert NotEqual(Number(0), Number(1)).simplified() == TRUE
assert NotEqual(Number(1), Number(1)).simplified() == FALSE
assert NotEqual(Number(2), Number(1)).simplified() == TRUE
assert NotEqual(Div(Number(1), Number(8)), Div(Number(1), Number(8))).simplified() == FALSE
assert NotEqual(Div(Number(1), Number(8)), Div(Number(2), Number(8))).simplified() == TRUE
assert NotEqual(Div(Number(1), Number(6)), Div(Number(1), Number(8))).simplified() == TRUE
def test_in_neg() -> None:
assert -In(Number(1), Variable("X")) == NotIn(Number(1), Variable("X"))
def test_in_simplified() -> None:
assert_equal(
In(Add(Number(21), Number(21)), Variable("X")).simplified(),
In(Number(42), Variable("X")),
)
def test_in_str() -> None:
assert str(In(Variable("X"), Variable("Y"))) == "X in Y"
def test_not_in_neg() -> None:
assert -NotIn(Number(1), Variable("X")) == In(Number(1), Variable("X"))
def test_not_in_simplified() -> None:
assert_equal(
NotIn(Add(Number(21), Number(21)), Variable("X")).simplified(),
NotIn(Number(42), Variable("X")),
)
def test_not_in_str() -> None:
assert str(NotIn(Variable("X"), Variable("Y"))) == "X not in Y"
def test_if_expr_str() -> None:
assert str(IfExpr([(Variable("X"), Variable("Y"))], Variable("Z"))) == "(if X then Y else Z)"
assert str(IfExpr([(Variable("X"), Variable("Y"))])) == "(if X then Y)"
assert str(
IfExpr([(Variable("X" * 30), Variable("Y" * 30))], Variable("Z" * 30)),
) == textwrap.dedent(
"""\
(if
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
then
YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
else
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ)""",
)
assert str(IfExpr([(Variable("X" * 50), Variable("Y" * 50))])) == textwrap.dedent(
"""\
(if
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
then
YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY)""",
)
def test_if_expr_substituted() -> None:
assert IfExpr([(Variable("X"), Variable("Y"))], Variable("Z")).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
) == IfExpr([(Variable("P_X"), Variable("P_Y"))], Variable("P_Z"))
assert IfExpr([(Variable("X"), Variable("Y"))], Variable("Z")).substituted(
lambda x: Variable("X") if isinstance(x, IfExpr) else x,
) == Variable("X")
def test_if_expr_simplified() -> None:
assert IfExpr([(TRUE, Variable("X"))], Variable("Y")).simplified() == Variable("X")
assert IfExpr([(FALSE, Variable("X"))], Variable("Y")).simplified() == Variable("Y")
assert IfExpr([(Variable("X"), Variable("Y"))], Variable("Y")).simplified() == Variable("Y")
assert IfExpr(
[(Variable("X"), Variable("Y")), (Variable("Y"), Variable("X"))],
Variable("Z"),
).simplified() == IfExpr(
[(Variable("X"), Variable("Y")), (Variable("Y"), Variable("X"))],
Variable("Z"),
)
def test_value_range_type() -> None:
assert_type(
ValueRange(Number(1), Number(42)),
ty.Any(),
)
def test_value_range_type_error() -> None:
assert_type_error(
ValueRange(
Variable(ID("X", location=Location((10, 30))), type_=ty.BOOLEAN),
Variable(ID("Y", location=Location((10, 40))), type_=ty.Sequence("A", INT_TY)),
location=Location((10, 20)),
),
r"^"
r"<stdin>:10:30: error: expected integer type\n"
r'<stdin>:10:30: error: found enumeration type "__BUILTINS__::Boolean"\n'
r"<stdin>:10:40: error: expected integer type\n"
r'<stdin>:10:40: error: found sequence type "A"'
r' with element integer type "I" \(10 \.\. 100\)'
r"$",
)
def test_value_range_simplified() -> None:
assert_equal(
ValueRange(Number(1), Add(Number(21), Number(21))).simplified(),
ValueRange(Number(1), Number(42)),
)
def test_value_range_substituted() -> None:
expr = ValueRange(lower=First("Test"), upper=Sub(Last("Test"), Number(1)))
def func(expr: Expr) -> Expr:
if expr == First("Test"):
return Number(1)
if expr == Last("Test"):
return Number(11)
return expr
assert expr.substituted(func) == ValueRange(lower=Number(1), upper=Sub(Number(11), Number(1)))
assert_equal(
ValueRange(lower=Variable("X"), upper=Variable("Y")).substituted(
lambda x: Variable("Z") if isinstance(x, ValueRange) else x,
),
Variable("Z"),
)
@pytest.mark.parametrize("expr", [ForAllIn, ForSomeIn])
def test_quantified_expression_type(expr: Callable[[str, Expr, Expr], Expr]) -> None:
assert_type(
expr(
"X",
Variable("Y", type_=ty.Sequence("A", INT_TY)),
Variable("Z", type_=ty.BOOLEAN),
),
ty.BOOLEAN,
)
@pytest.mark.parametrize("expr", [ForAllIn, ForSomeIn])
@pytest.mark.parametrize(
("iterable", "predicate", "match"),
[
(
Variable(ID("Y", location=Location((10, 30))), type_=ty.BOOLEAN),
Variable(ID("Z", location=Location((10, 40))), type_=ty.Sequence("A", INT_TY)),
r"^<stdin>:10:30: error: expected composite type\n"
r'<stdin>:10:30: error: found enumeration type "__BUILTINS__::Boolean"\n'
r'<stdin>:10:40: error: expected enumeration type "__BUILTINS__::Boolean"\n'
r'<stdin>:10:40: error: found sequence type "A"'
r' with element integer type "I" \(10 \.\. 100\)$',
),
(
Variable(ID("Y", location=Location((10, 30))), type_=ty.BOOLEAN),
Equal(Variable("X"), Number(1)),
r"^<stdin>:10:30: error: expected composite type\n"
r'<stdin>:10:30: error: found enumeration type "__BUILTINS__::Boolean"$',
),
(
Variable("Y", type_=ty.Sequence("A", ty.BOOLEAN)),
Equal(Variable("X"), Number(1, location=Location((10, 30)))),
r'^<stdin>:10:30: error: expected enumeration type "__BUILTINS__::Boolean"\n'
r"<stdin>:10:30: error: found type universal integer \(1 .. 1\)$",
),
],
)
def test_quantified_expression_type_error(
expr: Callable[[str, Expr, Expr, Location], Expr],
iterable: Expr,
predicate: Expr,
match: str,
) -> None:
assert_type_error(
expr(
"X",
iterable,
predicate,
Location((10, 20)),
),
match,
)
def test_quantified_expression_substituted() -> None:
assert_equal(
ForAllOf("X", Variable("Y"), Add(Last("Z"), Add(Number(21), Number(21)))).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
ForAllOf("X", Variable("P_Y"), Add(Last("P_Z"), Add(Number(21), Number(21)))),
)
def test_quantified_expression_substituted_location() -> None:
expr = ForAllOf("X", Variable("Y"), TRUE, location=Location((1, 2))).substituted(lambda x: x)
assert expr.location
def test_quantified_expression_simplified() -> None:
assert_equal(
ForAllOf("X", Variable("List"), Add(Last("Y"), Add(Number(21), Number(21)))).simplified(),
ForAllOf("X", Variable("List"), Add(Last("Y"), Number(42))),
)
def test_quantified_expression_variables() -> None:
assert_equal(
ForAllOf(
"A",
Variable("List"),
Add(Variable("X"), Add(Variable("Y"), Variable("Z"))),
).variables(),
[Variable("List"), Variable("X"), Variable("Y"), Variable("Z")],
)
def test_quantified_expression_str() -> None:
assert str(ForAllOf("X", Variable("Y"), Variable("Z"))) == "(for all X of Y =>\n Z)"
assert str(ForAllIn("X", Variable("Y"), Variable("Z"))) == "(for all X in Y =>\n Z)"
assert str(ForSomeIn("X", Variable("Y"), Variable("Z"))) == "(for some X in Y =>\n Z)"
def test_for_all_in_variables() -> None:
result = ForAllIn(
"Q",
Variable("List"),
Equal(Selected(Variable("Q"), "Fld"), Variable("X")),
).variables()
expected = [Variable("List"), Variable("X")]
assert result == expected
def test_for_some_in_variables() -> None:
result = ForSomeIn(
"Q",
Variable("List"),
Equal(Selected(Variable("Q"), "Fld"), Variable("X")),
).variables()
expected = [Variable("List"), Variable("X")]
assert result == expected
def test_expr_contains() -> None:
assert Variable("X") in Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(42))),
)
assert Variable("Z") not in Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(42))),
)
assert Less(Variable("X"), Number(42)) in Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(42))),
)
assert Less(Variable("Z"), Number(42)) not in Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(1))),
)
def test_expr_variables() -> None:
assert_equal(
Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(42))),
).variables(),
[Variable("Y"), Variable("X")],
)
assert_equal(
Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(42))),
).variables(),
[Variable("Y"), Variable("X")],
)
assert_equal(
Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(42))),
).variables(),
[Variable("Y"), Variable("X")],
)
assert_equal(
Or(
Greater(Variable("Y"), Number(42)),
And(TRUE, Less(Variable("X"), Number(1))),
).variables(),
[Variable("Y"), Variable("X")],
)
def test_expr_variables_duplicates() -> None:
assert_equal(
And(Variable("X"), Variable("Y"), Variable("X")).variables(),
[Variable("X"), Variable("Y")],
)
assert_equal(
Or(Variable("X"), Variable("Y"), Variable("X")).variables(),
[Variable("X"), Variable("Y")],
)
assert_equal(
Add(Variable("X"), Variable("Y"), Variable("X")).variables(),
[Variable("X"), Variable("Y")],
)
assert_equal(
Mul(Variable("X"), Variable("Y"), Variable("X")).variables(),
[Variable("X"), Variable("Y")],
)
assert_equal(Sub(Variable("X"), Variable("X")).variables(), [Variable("X")])
assert_equal(Div(Variable("X"), Variable("X")).variables(), [Variable("X")])
assert_equal(
Or(
Greater(Variable("X"), Number(42)),
And(TRUE, Less(Variable("X"), Number(1))),
).variables(),
[Variable("X")],
)
def test_length_variables() -> None:
assert Length("Z").variables() == [Variable("Z")]
def test_last_variables() -> None:
assert Last("Z").variables() == [Variable("Z")]
def test_first_variables() -> None:
assert First("Z").variables() == [Variable("Z")]
def test_size_variables() -> None:
assert Size("Z").variables() == [Variable("Z")]
def test_valid_variables() -> None:
assert Valid(Variable("X")).variables() == [Variable("X")]
def test_present_variables() -> None:
assert Present(Variable("X")).variables() == [Variable("X")]
def test_head_variables() -> None:
assert Head(Variable("X")).variables() == [Variable("X")]
def test_opaque_variables() -> None:
assert Opaque(Variable("X")).variables() == [Variable("X")]
def test_not_variables() -> None:
assert Not(Variable("X")).variables() == [Variable("X")]
def test_number_str() -> None:
assert str(Number(15)) == "15"
def test_number_str_long() -> None:
assert str(Number(539535)) == "539535"
def test_number_str_neg_long() -> None:
assert str(Number(-539535)) == "(-539535)"
def test_number_str_hex() -> None:
assert str(Number(4096, 16)) == "16#1000#"
def test_number_str_neg_hex() -> None:
assert str(Number(-4096, 16)) == "(-16#1000#)"
def test_number_str_dec() -> None:
assert str(Number(4096, 10)) == "10#4096#"
def test_number_str_oct() -> None:
assert str(Number(45432, 8)) == "8#130570#"
def test_number_str_neg_oct() -> None:
assert str(Number(-45432, 8)) == "(-8#130570#)"
def test_number_str_bin() -> None:
assert str(Number(454, 2)) == "2#111000110#"
def test_number_simplified_location() -> None:
assert Number(42, location=Location((1, 1))).simplified().location == Location((1, 1))
def test_string_variables() -> None:
assert not String("X").variables()
def test_string_simplified() -> None:
assert String("Test").simplified() == String("Test")
def test_string_substituted() -> None:
assert String("Test").substituted(
lambda x: String("TestSub") if x == String("Test") else x,
) == String("TestSub")
def test_string_elements() -> None:
assert String("Test").elements == [Number(84), Number(101), Number(115), Number(116)]
def test_string_str() -> None:
assert str(String("X Y")) == '"X Y"'
assert str(Equal(String("S"), Variable("X"))) == '"S" = X'
def test_selected_type() -> None:
assert_type(
Selected(Variable("X", type_=ty.Message("M", {("F",)}, {ID("F"): INT_TY})), "F"),
INT_TY,
)
@pytest.mark.parametrize(
("expr", "match"),
[
(
Selected(Variable(ID("X", location=Location((10, 20))), type_=ty.BOOLEAN), "Y"),
r"^<stdin>:10:20: error: expected message type\n"
r'<stdin>:10:20: error: found enumeration type "__BUILTINS__::Boolean"$',
),
(
Selected(
Variable(
"X",
type_=ty.Message("M", {("F",)}, {ID("F"): INT_TY}),
),
"Y",
location=Location((10, 20)),
),
r'^<stdin>:10:20: error: invalid field "Y" for message type "M"$',
),
(
Selected(
Variable(
"X",
type_=ty.Message(
"M",
{("F1",), ("F2",)},
{ID("F1"): INT_TY, ID("F2"): INT_TY},
),
),
"F",
location=Location((10, 20)),
),
r'^<stdin>:10:20: error: invalid field "F" for message type "M"\n'
r"<stdin>:10:20: help: similar field names: F1, F2$",
),
],
)
def test_selected_type_error(expr: Expr, match: str) -> None:
assert_type_error(
expr,
match,
)
def test_selected_substituted() -> None:
assert_equal(
Selected(Variable("X"), "Y").substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
Selected(Variable("P_X"), "Y"),
)
assert_equal(
Selected(Variable("X"), "Y").substituted(
lambda x: Variable("Z") if isinstance(x, Selected) else x,
),
Variable("Z"),
)
def test_selected_substituted_location() -> None:
expr = Selected(Variable(ID("X", location=Location((1, 2)))), "Y").substituted(lambda x: x)
assert expr.location
def test_selected_variables() -> None:
result = Selected(Variable("X"), "Y").variables()
expected = [Variable("X")]
assert result == expected
def test_in_variables() -> None:
result = In(Variable("A"), Variable("B")).variables()
expected = [Variable("A"), Variable("B")]
assert result == expected
def test_call_type() -> None:
assert_type(
Call(
"X",
ty.BOOLEAN,
[Variable("Y", type_=INT_TY)],
argument_types=[INT_TY],
),
ty.BOOLEAN,
)
def test_call_type_error() -> None:
assert_type_error(
Call(
"X",
ty.UNDEFINED,
[Variable(ID("Y", location=Location((10, 30))))],
location=Location((10, 20)),
),
r'^<stdin>:10:30: error: undefined variable "Y"\n'
r'<stdin>:10:20: error: undefined function "X"$',
)
assert_type_error(
Call(
"X",
ty.BOOLEAN,
[
Variable(ID("Y", location=Location((10, 30))), type_=INT_TY),
Variable(ID("Z", location=Location((10, 40))), type_=ty.BOOLEAN),
],
argument_types=[
ty.BOOLEAN,
INT_TY,
],
),
r'^<stdin>:10:30: error: expected enumeration type "__BUILTINS__::Boolean"\n'
r'<stdin>:10:30: error: found integer type "I" \(10 \.\. 100\)\n'
r'<stdin>:10:40: error: expected integer type "I" \(10 \.\. 100\)\n'
r'<stdin>:10:40: error: found enumeration type "__BUILTINS__::Boolean"$',
)
def test_call_variables() -> None:
result = Call("Sub", ty.BASE_INTEGER, [Variable("A"), Variable("B")]).variables()
expected = [Variable("Sub"), Variable("A"), Variable("B")]
assert result == expected
def test_call_findall() -> None:
assert Call("X", ty.BASE_INTEGER, [Variable("Y"), Variable("Z")]).findall(
lambda x: isinstance(x, Variable),
) == [
Variable("Y"),
Variable("Z"),
]
def test_call_str() -> None:
assert str(Call("Test", ty.BASE_INTEGER, [])) == "Test"
def test_call_neg() -> None:
assert -Call("Test", ty.BASE_INTEGER, []) == Neg(Call("Test", ty.BASE_INTEGER, []))
def test_conversion_type() -> None:
assert_type(
Conversion(
"X",
Selected(Variable("Y", type_=ty.Message("Y", {("Z",)}, {ID("Z"): ty.OPAQUE})), "Z"),
type_=ty.Message("X"),
argument_types=[ty.Message("Y")],
),
ty.Message("X"),
)
def test_conversion_type_error() -> None:
assert_type_error(
Conversion(
"X",
Selected(Variable(ID("Y", location=Location((10, 30)))), "Z"),
location=Location((10, 20)),
),
r'^<stdin>:10:30: error: undefined variable "Y"\n'
r'<stdin>:10:20: error: invalid conversion to "X"\n'
r'<stdin>:10:20: error: undefined type "X"$',
)
def test_conversion_simplified() -> None:
assert Conversion("X", Add(Number(1), Number(2))).simplified() == Conversion("X", Number(3))
def test_conversion_substituted() -> None:
assert_equal(
Conversion("X", Variable("Y")).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
Conversion("X", Variable("P_Y")),
)
assert_equal(
Conversion("X", Variable("Y")).substituted(
lambda x: Variable("Z") if isinstance(x, Conversion) else x,
),
Variable("Z"),
)
def test_conversion_substituted_location() -> None:
expr = Conversion("X", Variable("Y"), location=Location((1, 2))).substituted(lambda x: x)
assert expr.location
def test_conversion_variables() -> None:
result = Conversion("Sub", Variable("X")).variables()
expected = [Variable("X")]
assert result == expected
def test_qualified_expr_simplified() -> None:
assert QualifiedExpr("X", Add(Number(21), Number(21))).simplified() == QualifiedExpr(
"X",
Number(42),
)
def test_comprehension_type() -> None:
assert_type(
Comprehension(
"X",
Variable("Y", type_=ty.Sequence("A", INT_TY)),
Add(Variable("X"), Variable("Z", type_=INT_TY)),
TRUE,
),
ty.Aggregate(ty.BASE_INTEGER),
)
assert_type(
Comprehension(
"X",
Selected(
Variable(
"Y",
type_=ty.Message(
"M",
{("F",)},
{ID("F"): ty.Sequence("A", INT_TY)},
),
),
"F",
),
Variable("X"),
Equal(Variable("X"), Number(1)),
),
ty.Aggregate(INT_TY),
)
def test_comprehension_type_error() -> None:
assert_type_error(
Comprehension(
"X",
Variable(ID("Y", location=Location((10, 20)))),
Variable(ID("X", location=Location((10, 30)))),
TRUE,
),
r'^<stdin>:10:20: error: undefined variable "Y"$',
)
def test_comprehension_simplified() -> None:
assert Comprehension(
"X",
Variable("Y"),
Add(Number(1), Number(2)),
TRUE,
).simplified() == Comprehension("X", Variable("Y"), Number(3), TRUE)
def test_comprehension_substituted() -> None:
assert_equal(
Comprehension("X", Variable("Y"), Variable("Z"), TRUE).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
Comprehension("X", Variable("P_Y"), Variable("P_Z"), TRUE),
)
assert_equal(
Comprehension("X", Variable("Y"), Variable("Z"), TRUE).substituted(
lambda x: Variable("Z") if isinstance(x, Comprehension) else x,
),
Variable("Z"),
)
def test_comprehension_substituted_location() -> None:
expr = Comprehension(
"X",
Variable("Y"),
Variable("Z"),
TRUE,
location=Location((1, 2)),
).substituted(lambda x: x)
assert expr.location
def test_comprehension_variables() -> None:
result = Comprehension(
"I",
Variable("List"),
Selected(Variable("I"), "Data"),
Less(Selected(Variable("I"), "X"), Variable("Z")),
).variables()
expected = [Variable("List"), Variable("Z")]
assert result == expected
def test_comprehension_str() -> None:
assert (
str(Comprehension("X", Variable("Y"), Variable("Z"), TRUE)) == "[for X in Y if True => Z]"
)
assert (
str(In(Variable("A"), Comprehension("X", Variable("Y"), Variable("Z"), TRUE)))
== "A in [for X in Y if True => Z]"
)
@pytest.mark.parametrize(
("field_values", "type_"),
[
(
{"X": Variable("A", type_=INT_TY), "Y": Variable("B", type_=ty.BOOLEAN)},
ty.Message(
"M",
{
("X",),
("X", "Y"),
("X", "Y", "Z"),
},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
),
(
{"X": Variable("A", type_=ty.Message("I"))},
ty.Message(
"M",
{
("X",),
},
{},
{
ID("X"): ty.OPAQUE,
},
[
ty.Refinement(ID("X"), ty.Message("I"), "P"),
ty.Refinement(ID("X"), ty.Message("J"), "P"),
],
),
),
],
)
def test_message_aggregate_type(field_values: Mapping[StrID, Expr], type_: ty.Type) -> None:
assert_type(
MessageAggregate(
"M",
field_values,
type_=type_,
),
type_,
)
@pytest.mark.parametrize(
("field_values", "type_", "match"),
[
(
{
"X": Variable(ID("A", location=Location((10, 30)))),
"Y": Variable(ID("B", location=Location((10, 40)))),
},
ty.Message(
"M",
{
("X", "Y"),
},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
r'^<stdin>:10:30: error: undefined variable "A"\n'
r'<stdin>:10:40: error: undefined variable "B"$',
),
(
{
"X": Variable("A", type_=INT_TY),
"Y": Variable("B", type_=ty.BOOLEAN),
ID("Z", location=Location((10, 50))): Variable("Z", type_=INT_TY),
},
ty.Message(
"M",
{
("X", "Y"),
},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
r'^<stdin>:10:50: error: invalid field "Z" for message type "M"$',
),
(
{
ID("Y", location=Location((10, 30))): Variable("B", type_=ty.BOOLEAN),
"X": Variable("A", type_=INT_TY),
},
ty.Message(
"M",
{
("X", "Y"),
},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
r'^<stdin>:10:30: error: invalid position for field "Y" of message type "M"$',
),
(
{
"X": Variable("A", type_=INT_TY),
},
ty.Message(
"M",
{
("X", "Y"),
("X", "Y", "Z"),
},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
ID("Z"): INT_TY,
},
),
r'^<stdin>:10:20: error: missing fields for message type "M"\n'
r"<stdin>:10:20: note: possible next fields: Y$",
),
(
{
"X": Variable(ID("A", location=Location((10, 40)))),
"Y": Variable(ID("B", location=Location((10, 30)))),
},
ty.Message(
"M",
{
("X", "Y", "Z"),
},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
ID("Z"): INT_TY,
},
),
r'^<stdin>:10:40: error: undefined variable "A"\n'
r'<stdin>:10:30: error: undefined variable "B"\n'
r'<stdin>:10:20: error: missing fields for message type "M"\n'
r"<stdin>:10:20: note: possible next fields: Z$",
),
(
{
"X": Variable(ID("A", location=Location((10, 40)))),
"Y": Literal(ID("B", location=Location((10, 30)))),
},
ty.Undefined(),
r'^<stdin>:10:40: error: undefined variable "A"\n'
r'<stdin>:10:30: error: undefined literal "B"\n'
r'<stdin>:10:20: error: undefined message "X"$',
),
],
)
def test_message_aggregate_type_error(
field_values: Mapping[StrID, Expr],
type_: ty.Type,
match: str,
) -> None:
assert_type_error(
MessageAggregate("X", field_values, type_=type_, location=Location((10, 20))),
match,
)
def test_message_aggregate_simplified() -> None:
assert MessageAggregate(
"X",
{"Y": Add(Number(1), Number(2)), "Z": Variable("B")},
).simplified() == MessageAggregate("X", {"Y": Number(3), "Z": Variable("B")})
def test_message_aggregate_substituted() -> None:
assert_equal(
MessageAggregate("X", {"Y": Variable("A"), "Z": Variable("B")}).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
MessageAggregate("X", {"Y": Variable("P_A"), "Z": Variable("P_B")}),
)
assert_equal(
MessageAggregate("X", {"Y": Variable("A"), "Z": Variable("B")}).substituted(
lambda x: Variable("Z") if isinstance(x, MessageAggregate) else x,
),
Variable("Z"),
)
def test_message_aggregate_substituted_location() -> None:
expr = MessageAggregate(
"X",
{"Y": Variable("A"), "Z": Variable("B")},
location=Location((1, 2)),
).substituted(lambda x: x)
assert expr.location
def test_message_aggregate_variables() -> None:
result = MessageAggregate(
"Aggr",
{"X": Variable("A"), "Y": Variable("B"), "Baz": Variable("C")},
).variables()
expected = [Variable("A"), Variable("B"), Variable("C")]
assert result == expected
@pytest.mark.parametrize(
("field_values", "type_"),
[
(
{"Y": Variable("A", type_=INT_TY), "Z": Variable("B", type_=ty.BOOLEAN)},
ty.Message(
"M",
{
("X",),
("X", "Y"),
("X", "Y", "Z"),
},
{},
{
ID("Y"): INT_TY,
ID("Z"): ty.BOOLEAN,
},
),
),
(
{"Y": Variable("A", type_=ty.Message("I"))},
ty.Message(
"M",
{
("X", "Y", "Z"),
},
{},
{
ID("Y"): ty.OPAQUE,
},
[
ty.Refinement(ID("Y"), ty.Message("I"), "P"),
ty.Refinement(ID("Y"), ty.Message("J"), "P"),
],
),
),
],
)
def test_delta_message_aggregate_type(field_values: Mapping[StrID, Expr], type_: ty.Type) -> None:
assert_type(
DeltaMessageAggregate(
"M",
field_values,
type_=type_,
),
type_,
)
@pytest.mark.parametrize(
("field_values", "type_", "match"),
[
(
{
"X": Variable(ID("A", location=Location((10, 30)))),
"Y": Variable(ID("B", location=Location((10, 40)))),
},
ty.Message(
"M",
{
("X", "Y"),
},
{},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
r'^<stdin>:10:30: error: undefined variable "A"\n'
r'<stdin>:10:40: error: undefined variable "B"$',
),
(
{
"X": Variable("A", type_=INT_TY),
"Y": Variable("B", type_=ty.BOOLEAN),
ID("Z", location=Location((10, 50))): Variable("Z", type_=INT_TY),
},
ty.Message(
"M",
{
("X", "Y"),
},
{},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
r'^<stdin>:10:50: error: invalid field "Z" for message type "M"$',
),
(
{
"Y": Variable("B", type_=ty.BOOLEAN),
ID("X", location=Location((10, 30))): Variable("A", type_=INT_TY),
},
ty.Message(
"M",
{
("X", "Y"),
},
{},
{
ID("X"): INT_TY,
ID("Y"): ty.BOOLEAN,
},
),
r'^<stdin>:10:30: error: invalid position for field "X" of message type "M"$',
),
(
{
"X": Variable(ID("A", location=Location((10, 40)))),
"Y": Variable(ID("B", location=Location((10, 30)))),
},
ty.Undefined(),
r'^<stdin>:10:40: error: undefined variable "A"\n'
r'<stdin>:10:30: error: undefined variable "B"\n'
r'<stdin>:10:20: error: undefined message "T"$',
),
],
)
def test_delta_message_aggregate_type_error(
field_values: Mapping[StrID, Expr],
type_: ty.Type,
match: str,
) -> None:
assert_type_error(
DeltaMessageAggregate("T", field_values, type_=type_, location=Location((10, 20))),
match,
)
def test_delta_message_aggregate_substituted() -> None:
assert_equal(
DeltaMessageAggregate("X", {"Y": Variable("A"), "Z": Variable("B")}).substituted(
lambda x: Variable(f"P_{x}") if isinstance(x, Variable) else x,
),
DeltaMessageAggregate("X", {"Y": Variable("P_A"), "Z": Variable("P_B")}),
)
assert_equal(
DeltaMessageAggregate("X", {"Y": Variable("A"), "Z": Variable("B")}).substituted(
lambda x: Variable("Z") if isinstance(x, DeltaMessageAggregate) else x,
),
Variable("Z"),
)
def test_delta_message_aggregate_substituted_location() -> None:
expr = DeltaMessageAggregate(
"X",
{"Y": Variable("A"), "Z": Variable("B")},
location=Location((1, 2)),
).substituted(lambda x: x)
assert expr.location
def test_delta_message_aggregate_simplified() -> None:
assert_equal(
DeltaMessageAggregate(
"X",
{"Y": Variable("A"), "Z": Add(Number(1), Number(2))},
).simplified(),
DeltaMessageAggregate("X", {"Y": Variable("A"), "Z": Number(3)}),
)
def test_delta_message_aggregate_variables() -> None:
result = DeltaMessageAggregate(
"Aggr",
{"X": Variable("A"), "Y": Variable("B"), "Baz": Variable("C")},
).variables()
expected = [Variable("A"), Variable("B"), Variable("C")]
assert result == expected
def test_indexed_neg() -> None:
assert -Indexed(Variable("X"), Variable("Y")) == Neg(
Indexed(
Variable("X"),
Variable("Y"),
),
)
def test_case_variables() -> None:
assert_equal(
CaseExpr(
Variable("C"),
[([ID("V1"), ID("V2")], Number(1)), ([ID("V3")], Variable("E"))],
).variables(),
[Variable("C"), Variable("E")],
)
def test_case_substituted() -> None:
c = CaseExpr(
Variable("C"),
[([ID("V1"), ID("V2")], Variable("E1")), ([ID("V3")], Variable("E2"))],
)
assert_equal(
c.substituted(lambda x: Number(42) if x == Variable("E1") else x),
CaseExpr(Variable("C"), [([ID("V1"), ID("V2")], Number(42)), ([ID("V3")], Variable("E2"))]),
)
assert_equal(
c.substituted(
lambda x: Number(42) if isinstance(x, Variable) and x.name.startswith("E") else x,
),
CaseExpr(Variable("C"), [([ID("V1"), ID("V2")], Number(42)), ([ID("V3")], Number(42))]),
)
assert_equal(
c.substituted(lambda x: Variable("C_Subst") if x == Variable("C") else x),
CaseExpr(
Variable("C_Subst"),
[([ID("V1"), ID("V2")], Variable("E1")), ([ID("V3")], Variable("E2"))],
),
)
assert_equal(
c.substituted(lambda x: Variable("C_Subst") if isinstance(x, CaseExpr) else x),
Variable("C_Subst"),
)
def test_case_substituted_location() -> None:
c = CaseExpr(
Variable("C"),
[([ID("V1"), ID("V2")], Variable("E1")), ([ID("V3")], Variable("E2"))],
location=Location((1, 2)),
).substituted(lambda x: x)
assert c.location
def test_case_findall() -> None:
assert_equal(
CaseExpr(
Variable("C1"),
[([ID("V1"), ID("V2")], Variable("E1")), ([ID("V3")], Variable("E2"))],
).findall(lambda x: isinstance(x, Variable) and x.name.endswith("1")),
[Variable("C1"), Variable("E1")],
)
def test_case_type() -> None:
c1 = Variable("C", type_=models.enumeration().type_)
assert_type(CaseExpr(c1, [([ID("Zero"), ID("One")], TRUE), ([ID("Two")], FALSE)]), ty.BOOLEAN)
assert_type(
CaseExpr(c1, [([ID("Zero"), ID("One")], Number(1)), ([ID("Two")], Number(2))]),
ty.UniversalInteger(ty.Bounds(1, 2)),
)
c2 = Variable("C", type_=TINY_INT.type_)
assert_type(CaseExpr(c2, [([Number(1), Number(2)], TRUE), ([Number(3)], FALSE)]), ty.BOOLEAN)
assert_type_error(
CaseExpr(
c1,
[
([ID("V1", location=Location((1, 1))), ID("V2", location=Location((1, 2)))], TRUE),
([ID("V3", location=Location((1, 3)))], Number(1, location=Location((1, 4)))),
],
),
r'^__BUILTINS__:1:1: error: dependent expression "True" has incompatible enumeration type '
r'"__BUILTINS__::Boolean"\n'
r'<stdin>:1:4: note: conflicting with "1" which has type universal integer \(1 .. 1\)$',
)
assert_type_error(
CaseExpr(
Opaque(
Variable(
ID("X", location=Location((1, 1))),
type_=ty.Message(ID("A", location=Location((1, 2)))),
),
),
[([ID("V", location=Location((1, 4)))], Number(1, location=Location((1, 5))))],
),
r'^<stdin>:1:1: error: invalid discrete choice with sequence type "__INTERNAL__::Opaque" '
r'with element integer type "__INTERNAL__::Byte" \(0 .. 255\)\n'
r"<stdin>:1:1: note: expected enumeration or integer type$",
)
def test_case_simplified() -> None:
assert_equal(
CaseExpr(
Variable("C"),
[([ID("V1"), ID("V2")], And(TRUE, FALSE)), ([ID("V3")], FALSE)],
).simplified(),
CaseExpr(Variable("C"), [([ID("V1"), ID("V2")], FALSE), ([ID("V3")], FALSE)]),
)
def test_case_invalid() -> None:
assert_type_error(
CaseExpr(
Variable("C", type_=models.enumeration().type_),
[([ID("Zero")], TRUE), ([ID("One")], FALSE)],
location=Location((1, 2)),
),
"^<stdin>:1:2: error: not all enumeration literals covered by case expression\n"
'<stdin>:10:2: note: missing literal "Two"$',
)
assert_type_error(
CaseExpr(
Variable("C", type_=models.enumeration().type_),
[([ID("Zero"), ID("One")], TRUE), ([ID("Two"), ID("Invalid")], FALSE)],
location=Location((1, 2)),
),
"^<stdin>:1:2: error: invalid literals used in case expression\n"
'<stdin>:10:2: note: literal "Invalid" not part of "P::Enumeration"$',
)
assert_type_error(
CaseExpr(
Variable("C", type_=models.enumeration().type_),
[
([ID("Zero"), ID("One", location=Location((2, 2)))], TRUE),
([ID("Two"), ID("One", location=Location((3, 2)))], FALSE),
],
location=Location((1, 2)),
),
"^<stdin>:1:2: error: duplicate literals used in case expression\n"
'<stdin>:3:2: note: duplicate literal "One"$',
)
assert_type_error(
CaseExpr(
Variable("C", type_=TINY_INT.type_),
[([Number(1)], TRUE), ([Number(2)], FALSE)],
location=Location((2, 2)),
),
'^<stdin>:2:2: error: case expression does not cover full range of "P::Tiny"\n'
"<stdin>:1:2: note: missing value 3$",
)
assert_type_error(
CaseExpr(
Variable("C", type_=TINY_INT.type_),
[([Number(1)], TRUE)],
location=Location((2, 2)),
),
'^<stdin>:2:2: error: case expression does not cover full range of "P::Tiny"\n'
"<stdin>:1:2: note: missing range 2 .. 3$",
)
assert_type_error(
CaseExpr(
Variable("C", type_=INT.type_),
[([Number(1), Number(2)], TRUE), ([Number(51)], FALSE), ([Number(53)], TRUE)],
location=Location((5, 2)),
),
'^<stdin>:5:2: error: case expression does not cover full range of "P::Int"\n'
"<stdin>:3:2: note: missing range 3 .. 50\n"
"<stdin>:3:2: note: missing value 52\n"
"<stdin>:3:2: note: missing range 54 .. 100$",
)
assert_type_error(
CaseExpr(
Variable("C", type_=TINY_INT.type_),
[([Number(1), Number(2)], TRUE), ([Number(3), Number(4)], FALSE)],
location=Location((2, 2)),
),
"^<stdin>:2:2: error: invalid literals used in case expression\n"
'<stdin>:1:2: note: value 4 not part of "P::Tiny"$',
)
assert_type_error(
CaseExpr(
Variable("C", type_=TINY_INT.type_),
[
([Number(1), Number(2, location=Location((1, 8)))], TRUE),
([Number(3), Number(2, location=Location((1, 14)))], FALSE),
],
location=Location((1, 2)),
),
"^<stdin>:1:2: error: duplicate literals used in case expression\n"
'<stdin>:1:14: note: duplicate literal "2"$',
)
def test_invalid_division_by_zero() -> None:
with pytest.raises(
RecordFluxError,
match=(r"^<stdin>:1:2: error: division by zero$"),
):
Div(Number(255), Number(0), location=Location((1, 2))).simplified()
def test_invalid_modulo_by_zero() -> None:
with pytest.raises(
RecordFluxError,
match=(r"^<stdin>:1:2: error: modulo by zero$"),
):
Mod(Number(255), Number(0), location=Location((1, 2))).simplified()