You've already forked RecordFlux
mirror of
https://github.com/AdaCore/RecordFlux.git
synced 2026-02-12 13:01:56 -08:00
3051 lines
92 KiB
Python
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()
|