From 6f1bcdfcae63d06eacc90cd7a25741ab159903f3 Mon Sep 17 00:00:00 2001 From: Robin Senn Date: Tue, 19 Apr 2022 15:49:13 +0200 Subject: [PATCH] Add support for relations with aggregates and variables in pyrflx Ref: #964 --- rflx/expression.py | 24 ++++++++++++++-- rflx/pyrflx/typevalue.py | 22 ++++++++------ tests/data/fixtures/pyrflx.py | 8 +++++- tests/data/specs/aggregate_in_relation.rflx | 32 +++++++++++++++++++++ tests/unit/expression_test.py | 26 +++++++++++++++++ tests/unit/pyrflx_test.py | 22 ++++++++++++++ 6 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 tests/data/specs/aggregate_in_relation.rflx diff --git a/rflx/expression.py b/rflx/expression.py index 1ba4b78c39..ffb77a630f 100644 --- a/rflx/expression.py +++ b/rflx/expression.py @@ -1604,6 +1604,15 @@ def simplified(self) -> Expr: def length(self) -> Expr: return Number(len(self.elements)) + def to_bytes(self) -> bytes: + if not all((isinstance(element, Number) for element in self.elements)): + return NotImplemented + return b"".join( + element.value.to_bytes((element.value.bit_length() + 7) // 8, "big") + for element in self.elements + if isinstance(element, Number) + ) + def ada_expr(self) -> ada.Expr: return ada.Aggregate(*[e.ada_expr() for e in self.elements]) @@ -1710,8 +1719,19 @@ def _simplified(self, relation_operator: Callable[[Expr, Expr], bool]) -> Expr: } if (relation_operator, left, right) in mapping: return mapping[(relation_operator, left, right)] - if isinstance(left, Number) and isinstance(right, Number): - return TRUE if relation_operator(left, right) else FALSE + if isinstance(left, (Number, Aggregate)) and isinstance(right, (Number, Aggregate)): + left_number = ( + Number(int.from_bytes(left.to_bytes(), "big")) + if isinstance(left, Aggregate) + else left + ) + right_number = ( + Number(int.from_bytes(right.to_bytes(), "big")) + if isinstance(right, Aggregate) + else right + ) + assert isinstance(left_number, Number) and isinstance(right_number, Number) + return TRUE if relation_operator(left_number, right_number) else FALSE return self.__class__(left, right) @property diff --git a/rflx/pyrflx/typevalue.py b/rflx/pyrflx/typevalue.py index 62da0e8ec8..dcaa83bae0 100644 --- a/rflx/pyrflx/typevalue.py +++ b/rflx/pyrflx/typevalue.py @@ -1153,14 +1153,6 @@ def _calculate_checksum(self, checksum: "MessageValue.Checksum") -> int: expr_tuple.evaluated_expression.lower.value, expr_tuple.evaluated_expression.upper.value, ) - elif isinstance(expr_tuple.evaluated_expression, Variable): - assert ( - expr_tuple.evaluated_expression.name in self.fields - and self._fields[expr_tuple.evaluated_expression.name].set - ) - val = self._fields[expr_tuple.evaluated_expression.name].typeval.value - assert isinstance(val, bytes) - arguments[str(expr_tuple.expression)] = val else: assert isinstance(expr_tuple.evaluated_expression, Number) arguments[str(expr_tuple.expression)] = expr_tuple.evaluated_expression.value @@ -1356,6 +1348,20 @@ def subst(expression: Expr) -> Expr: if expression in self._parameters: assert isinstance(expression, Name) return self._parameters[expression] + if isinstance(expression, Variable) and expression.name in self.fields: + if self._fields[expression.identifier.flat].set: + exp_value = self._fields[expression.identifier.flat].typeval.value + if isinstance(exp_value, bytes): + return Number(int.from_bytes(exp_value, "big")) + if ( + isinstance(exp_value, list) + and len(exp_value) > 0 + and isinstance(exp_value[0], IntegerValue) + ): + return Number( + int.from_bytes(b"".join([bytes(v.bitstring) for v in exp_value]), "big") + ) + return NotImplemented return expression return expr.substituted(func=subst).substituted(func=subst).simplified() diff --git a/tests/data/fixtures/pyrflx.py b/tests/data/fixtures/pyrflx.py index 8766e355ca..98fbc73e99 100644 --- a/tests/data/fixtures/pyrflx.py +++ b/tests/data/fixtures/pyrflx.py @@ -29,6 +29,7 @@ def fixture_pyrflx() -> pyrflx.PyRFLX: f"{SPEC_DIR}/parameterized.rflx", f"{SPEC_DIR}/endianness.rflx", f"{SPEC_DIR}/low_order.rflx", + f"{SPEC_DIR}/aggregate_in_relation.rflx", ], skip_model_verification=True, ) @@ -235,5 +236,10 @@ def fixture_endianness_package(pyrflx_: pyrflx.PyRFLX) -> pyrflx.Package: @pytest.fixture(name="low_order_package", scope="session") -def fixure_low_order_package(pyrflx_: pyrflx.PyRFLX) -> pyrflx.Package: +def fixture_low_order_package(pyrflx_: pyrflx.PyRFLX) -> pyrflx.Package: return pyrflx_.package("Low_Order") + + +@pytest.fixture(name="aggregate_in_relation_package", scope="session") +def fixture_aggregate_in_relation_package(pyrflx_: pyrflx.PyRFLX) -> pyrflx.Package: + return pyrflx_.package("Aggregate_In_Relation") diff --git a/tests/data/specs/aggregate_in_relation.rflx b/tests/data/specs/aggregate_in_relation.rflx new file mode 100644 index 0000000000..547c3eb4fc --- /dev/null +++ b/tests/data/specs/aggregate_in_relation.rflx @@ -0,0 +1,32 @@ +package Aggregate_In_Relation is + + type B is mod 2 ** 8; + type C is mod 2 ** 8; + type D is mod 2 ** 8; + + type E is mod 2 ** 8; + type S is sequence of E; + + type Aggregate_In_Relation_Msg is + message + Fld_A : Opaque + with Size => 16 + then Fld_B + if Fld_A = [16#AA#, 16#AA#] + then null + if [16#AA#, 16#AA#] /= Fld_A; + Fld_B : B + then Fld_C + if [16#01#, 16#02#] = [16#01#, 16#02#] + then null + if [16#01#, 16#02#] /= [16#01#, 16#02#]; + Fld_C : S + with Size => 16 + then Fld_D + if Fld_C = [16#CC#, 16#CC#] + then null + if Fld_C /= [16#CC#, 16#CC#]; + Fld_D : D; + end message; + +end Aggregate_In_Relation; diff --git a/tests/unit/expression_test.py b/tests/unit/expression_test.py index cf53213698..79cb54f214 100644 --- a/tests/unit/expression_test.py +++ b/tests/unit/expression_test.py @@ -1081,6 +1081,26 @@ def test_relation_simplified() -> None: 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, + ) + assert_equal(Equal(Number(0), Aggregate(Number(0), Number(1), Number(2))).simplified(), FALSE) + assert_equal(Equal(Aggregate(Number(0), Number(1), Number(2)), Number(0)).simplified(), FALSE) + assert_equal(NotEqual(Number(4), Aggregate(Number(0), Number(1), Number(2))).simplified(), TRUE) + assert_equal(NotEqual(Number(0), Aggregate(Number(0), Number(1), Number(2))).simplified(), TRUE) assert Equal(TRUE, TRUE).simplified() == TRUE assert Equal(TRUE, FALSE).simplified() == FALSE assert NotEqual(TRUE, TRUE).simplified() == FALSE @@ -1573,6 +1593,12 @@ 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)] diff --git a/tests/unit/pyrflx_test.py b/tests/unit/pyrflx_test.py index 428fb7db90..9161ac5692 100644 --- a/tests/unit/pyrflx_test.py +++ b/tests/unit/pyrflx_test.py @@ -78,6 +78,7 @@ def test_pyrflx_iterator(pyrflx_: PyRFLX) -> None: "Message_Type_Size_Condition", "Always_Valid_Aspect", "Low_Order", + "Aggregate_In_Relation", } @@ -1515,3 +1516,24 @@ def test_low_order(low_order_package: Package) -> None: assert m1.valid_message assert m1.bytestring == b"\x01\x00" + + +def test_aggregate_in_relation_invalid(aggregate_in_relation_package: Package) -> None: + msg = aggregate_in_relation_package.new_message("Aggregate_In_Relation_Msg") + with pytest.raises( + PyRFLXError, + match=( + "^" + "pyrflx: error: Bitstring representing the message is too short" + " - stopped while parsing field: Fld_B" + "$" + ), + ): + msg.parse(b"\xAA\xAA") + + +def test_aggregate_in_relation_valid(aggregate_in_relation_package: Package) -> None: + msg = aggregate_in_relation_package.new_message("Aggregate_In_Relation_Msg") + msg.parse(b"\xAA\xAA\xBB\xCC\xCC\xDD") + assert msg.bytestring == b"\xAA\xAA\xBB\xCC\xCC\xDD" + assert msg.valid_message