def field_value(field: model.Field) -> expr.Expr: if public: return expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")]) return expr.Selected( expr.Indexed( expr.Variable(ID("Ctx") * "Cursors" if not embedded else "Cursors"), expr.Variable(field.affixed_name), ), "Value", )
def valid_predecessors_invariant() -> Expr: return AndThen( *[ If( [ ( Call( "Structural_Valid", [ Indexed( Variable("Cursors"), Variable(f.affixed_name), ) ], ), Or( *[ expr.AndThen( expr.Call( "Structural_Valid" if l.source in composite_fields else "Valid", [ expr.Indexed( expr.Variable("Cursors"), expr.Variable(l.source.affixed_name), ) ], ), expr.Equal( expr.Selected( expr.Indexed( expr.Variable("Cursors"), expr.Variable(f.affixed_name), ), "Predecessor", ), expr.Variable(l.source.affixed_name), ), l.condition.substituted( substitution(message, embedded=True, prefix=prefix) ), ) .simplified() .ada_expr() for l in message.incoming(f) ] ), ) ] ) for f in message.fields if f not in message.direct_successors(model.INITIAL) ] )
def parameter_value(parameter: model.Field, parameter_type: model.Type) -> expr.Expr: if isinstance(parameter_type, model.Enumeration): if embedded: return expr.Call("To_Base_Integer", [expr.Variable(parameter.name)]) return expr.Call("To_Base_Integer", [expr.Variable("Ctx" * parameter.identifier)]) if isinstance(parameter_type, model.Scalar): if embedded: return expr.Variable(parameter.name) return expr.Variable("Ctx" * parameter.identifier) assert False, f'unexpected type "{type(parameter_type).__name__}"'
def field_size(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Size", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Add( expr.Sub( expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last"), expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "First"), ), expr.Number(1), )
def has_aggregate_dependent_condition(message: model.Message, field: model.Field = None) -> bool: links = message.outgoing(field) if field else message.structure fields = [field] if field else message.fields return any( r for l in links for r in l.condition.findall(lambda x: isinstance(x, (expr.Equal, expr.NotEqual))) if isinstance(r, (expr.Equal, expr.NotEqual)) and r.findall(lambda x: isinstance(x, expr.Aggregate)) and any( r.left == expr.Variable(f.identifier) or r.right == expr.Variable(f.identifier) for f in fields ) )
def constraints(self, name: str, proof: bool = False, same_package: bool = True) -> ty.Sequence[expr.Expr]: if proof: return [ expr.GreaterEqual(expr.Variable(name), self.first, location=self.location), expr.LessEqual(expr.Variable(name), self.last, location=self.location), expr.Equal(expr.Size(name), self.size, location=self.location), ] raise NotImplementedError
def constraints(self, name: str, proof: bool = False, same_package: bool = True) -> ty.Sequence[expr.Expr]: if proof: prefixed_literals = { self.package * l: v for l, v in self.literals.items() } if self.package == const.BUILTINS_PACKAGE: literals = self.literals elif same_package: literals = {**self.literals, **prefixed_literals} else: literals = prefixed_literals result: ty.List[expr.Expr] = [ expr.Or( *[ expr.Equal(expr.Variable(name), expr.Literal(l), self.location) for l in literals ], location=self.location, ) ] result.extend([ expr.Equal(expr.Literal(l), v, self.location) for l, v in literals.items() ]) result.append(expr.Equal(expr.Size(name), self.size, self.location)) return result raise NotImplementedError
def valid_message_condition(self, message: Message, structural: bool = False) -> Expr: return (expr.Or(*[ expr.AndThen( expr.Call( "Structural_Valid" if structural and isinstance( message.field_types[l.source], Composite) else "Valid", [ expr.Variable("Ctx"), expr.Variable(l.source.affixed_name, immutable=True), ], ), l.condition, ) for l in message.incoming(FINAL) if l.target == FINAL ]).substituted(common.substitution( message, self.prefix)).simplified().ada_expr())
def test_named_aggregate_rflx_expr() -> None: assert ada.NamedAggregate( ("X", ada.Number(1)), (ada.ValueRange(ada.Number(2), ada.Number(3)), ada.Variable("Y")), ).rflx_expr() == expr.NamedAggregate( ("X", expr.Number(1)), (expr.ValueRange(expr.Number(2), expr.Number(3)), expr.Variable("Y")), )
def condition(pair: FieldPair) -> expr.Expr: if isinstance(pair.source_type, Integer): first = pair.source_type.first.value last = pair.source_type.last.value if last - first > 0: return expr.Equal( expr.Variable(pair.source.name), expr.Number(draw(st.integers(min_value=first, max_value=last))), ) elif isinstance(pair.source_type, Enumeration) and len(pair.source_type.literals) > 1: return expr.Equal( expr.Variable(pair.source.name), expr.Variable( list(pair.source_type.literals.keys())[ draw(st.integers(min_value=0, max_value=len(pair.source_type.literals) - 1)) ] ), ) return expr.TRUE
def test_substitution_relation_aggregate( relation: Callable[[expr.Expr, expr.Expr], expr.Relation], left: expr.Expr, right: expr.Expr ) -> None: equal_call = expr.Call( "Equal", [ expr.Variable("Ctx"), expr.Variable("F_Value"), expr.Aggregate( expr.Val(expr.Variable("Types.Byte"), expr.Number(1)), expr.Val(expr.Variable("Types.Byte"), expr.Number(2)), ), ], ) assert_equal( relation(left, right).substituted(common.substitution(TLV_MESSAGE)), equal_call if relation == expr.Equal else expr.Not(equal_call), )
def size(pair: FieldPair) -> expr.Expr: max_size = 2**29 - 1 if isinstance(pair.target_type, (Opaque, Sequence)): if isinstance(pair.source_type, Integer): if pair.source_type.last.value <= max_size: return expr.Mul(expr.Variable(pair.source.name), expr.Number(8)) return expr.Number( draw(st.integers(min_value=1, max_value=max_size).map(lambda x: x * 8)) ) return expr.UNDEFINED
def field_value(field: model.Field, field_type: model.Type) -> expr.Expr: if isinstance(field_type, model.Enumeration): if public: return expr.Call( "To_Base_Integer", [expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")])] ) return expr.Selected( expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Value", ) if isinstance(field_type, model.Scalar): if public: return expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")]) return expr.Selected( expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Value", ) if isinstance(field_type, model.Composite): return expr.Variable(field.name) assert False, f'unexpected type "{type(field_type).__name__}"'
skip_model_verification=True).package( "Parameterized").new_message("Message")) validation_result = validator._validate_message( # pylint: disable = protected-access Path(TEST_DIR / "parameterized/message/valid/parameterized_message.raw"), valid_original_message=True, message_value=message, ) assert validation_result.validation_success @pytest.mark.parametrize( "expression,expected", [ ( expr.Or(expr.Equal(expr.Variable("A"), expr.TRUE), expr.Equal(expr.Variable("B"), expr.TRUE)), [ expr.Equal(expr.Variable("A"), expr.TRUE), expr.Equal(expr.Variable("B"), expr.TRUE), ], ), ( expr.And(expr.Equal(expr.Variable("A"), expr.TRUE), expr.Equal(expr.Variable("B"), expr.TRUE)), [ expr.And( expr.Equal(expr.Variable("A"), expr.TRUE), expr.Equal(expr.Variable("B"), expr.TRUE), ), ],
def message_structure_invariant( message: model.Message, prefix: str, link: model.Link = None, embedded: bool = False ) -> Expr: def prefixed(name: str) -> expr.Expr: return expr.Selected(expr.Variable("Ctx"), name) if not embedded else expr.Variable(name) if not link: return message_structure_invariant( message, prefix, message.outgoing(model.INITIAL)[0], embedded ) source = link.source target = link.target if target == model.FINAL: return TRUE field_type = message.types[target] condition = link.condition.substituted(substitution(message, prefix, embedded)).simplified() size = ( field_type.size if isinstance(field_type, model.Scalar) else link.size.substituted( substitution(message, prefix, embedded, target_type=const.TYPES_BIT_LENGTH) ).simplified() ) first = ( prefixed("First") if source == model.INITIAL else link.first.substituted( substitution(message, prefix, embedded, target_type=const.TYPES_BIT_INDEX) ) .substituted( mapping={ expr.UNDEFINED: expr.Add( expr.Selected( expr.Indexed(prefixed("Cursors"), expr.Variable(source.affixed_name)), "Last", ), expr.Number(1), ) } ) .simplified() ) invariant = [ message_structure_invariant(message, prefix, l, embedded) for l in message.outgoing(target) ] return If( [ ( AndThen( Call( "Structural_Valid", [Indexed(prefixed("Cursors").ada_expr(), Variable(target.affixed_name))], ), *([condition.ada_expr()] if condition != expr.TRUE else []), ), AndThen( Equal( Add( Sub( Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "Last", ), Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "First", ), ), Number(1), ), size.ada_expr(), ), Equal( Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "Predecessor", ), Variable(source.affixed_name), ), Equal( Selected( Indexed( prefixed("Cursors").ada_expr(), Variable(target.affixed_name), ), "First", ), first.ada_expr(), ), *[i for i in invariant if i != TRUE], ), ) ] )
def test_conversion_rflx_expr() -> None: assert ada.Conversion("X", ada.Variable("Y")).rflx_expr() == expr.Conversion( "X", expr.Variable("Y"))
def test_not_rflx_expr() -> None: assert ada.Not(ada.Variable("X")).rflx_expr() == expr.Not( expr.Variable("X"))
def test_math_expr_ada_expr( expression: Callable[[ada.Expr, ada.Expr], ada.Expr]) -> None: result = expression(ada.Variable("X"), ada.Variable("Y")).rflx_expr() expected = getattr(expr, expression.__name__)(expr.Variable("X"), expr.Variable("Y")) assert result == expected
def test_qualified_expr_rflx_expr() -> None: assert ada.QualifiedExpr( "X", ada.Variable("Y")).rflx_expr() == expr.QualifiedExpr( "X", expr.Variable("Y"))
def substitution_facts( message: model.Message, prefix: str, embedded: bool = False, public: bool = False, target_type: Optional[ID] = const.TYPES_BASE_INT, ) -> Mapping[expr.Name, expr.Expr]: def prefixed(name: str) -> expr.Expr: return expr.Variable(ID("Ctx") * name) if not embedded else expr.Variable(name) first = prefixed("First") last = expr.Call("Written_Last", [expr.Variable("Ctx")]) if public else prefixed("Written_Last") cursors = prefixed("Cursors") def field_first(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_First", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "First") def field_last(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Last", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last") def field_size(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Size", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Add( expr.Sub( expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last"), expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "First"), ), expr.Number(1), ) def parameter_value(parameter: model.Field, parameter_type: model.Type) -> expr.Expr: if isinstance(parameter_type, model.Enumeration): if embedded: return expr.Call("To_Base_Integer", [expr.Variable(parameter.name)]) return expr.Call("To_Base_Integer", [expr.Variable("Ctx" * parameter.identifier)]) if isinstance(parameter_type, model.Scalar): if embedded: return expr.Variable(parameter.name) return expr.Variable("Ctx" * parameter.identifier) assert False, f'unexpected type "{type(parameter_type).__name__}"' def field_value(field: model.Field, field_type: model.Type) -> expr.Expr: if isinstance(field_type, model.Enumeration): if public: return expr.Call( "To_Base_Integer", [expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")])] ) return expr.Selected( expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Value", ) if isinstance(field_type, model.Scalar): if public: return expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")]) return expr.Selected( expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Value", ) if isinstance(field_type, model.Composite): return expr.Variable(field.name) assert False, f'unexpected type "{type(field_type).__name__}"' def type_conversion(expression: expr.Expr) -> expr.Expr: return expr.Call(target_type, [expression]) if target_type else expression return { **{expr.First("Message"): type_conversion(first)}, **{expr.Last("Message"): type_conversion(last)}, **{expr.Size("Message"): type_conversion(expr.Add(last, -first, expr.Number(1)))}, **{expr.First(f.name): type_conversion(field_first(f)) for f in message.fields}, **{expr.Last(f.name): type_conversion(field_last(f)) for f in message.fields}, **{expr.Size(f.name): type_conversion(field_size(f)) for f in message.fields}, **{ expr.Variable(p.identifier): type_conversion(parameter_value(p, t)) for p, t in message.parameter_types.items() }, **{ expr.Variable(f.name): type_conversion(field_value(f, t)) for f, t in message.field_types.items() }, **{ expr.Literal(l): type_conversion(expr.Call("To_Base_Integer", [expr.Variable(l)])) for t in message.types.values() if isinstance(t, model.Enumeration) and t != model.BOOLEAN for l in t.literals.keys() }, **{ expr.Literal(t.package * l): type_conversion( expr.Call("To_Base_Integer", [expr.Variable(prefix * t.package * l)]) ) for t in message.types.values() if isinstance(t, model.Enumeration) and t != model.BOOLEAN for l in t.literals.keys() }, # https://github.com/Componolit/RecordFlux/issues/276 **{expr.ValidChecksum(f): expr.TRUE for f in message.checksums}, }
def test_derivation_spec() -> None: generator = generate(DERIVATION_MODEL) assert_specification(generator) def test_derivation_body() -> None: generator = generate(DERIVATION_MODEL) assert_body(generator) @pytest.mark.parametrize( "left,right", [ (expr.Variable("Value"), expr.Aggregate(expr.Number(1), expr.Number(2))), (expr.Aggregate(expr.Number(1), expr.Number(2)), expr.Variable("Value")), ], ) @pytest.mark.parametrize("relation", [expr.Equal, expr.NotEqual]) def test_substitution_relation_aggregate( relation: Callable[[expr.Expr, expr.Expr], expr.Relation], left: expr.Expr, right: expr.Expr ) -> None: equal_call = expr.Call( "Equal", [ expr.Variable("Ctx"), expr.Variable("F_Value"), expr.Aggregate( expr.Val(expr.Variable("Types.Byte"), expr.Number(1)), expr.Val(expr.Variable("Types.Byte"), expr.Number(2)),
def func(expression: expr.Expr) -> expr.Expr: # pylint: disable = too-many-branches def byte_aggregate(aggregate: expr.Aggregate) -> expr.Aggregate: return expr.Aggregate(*[expr.Val(const.TYPES_BYTE, e) for e in aggregate.elements]) if isinstance(expression, expr.Name) and expression in facts: return facts[expression] if isinstance(expression, expr.String): return expr.Aggregate(*expression.elements) if isinstance(expression, (expr.Equal, expr.NotEqual)): field = None aggregate = None if isinstance(expression.left, expr.Variable) and isinstance( expression.right, expr.Aggregate ): field = model.Field(expression.left.name) aggregate = byte_aggregate(expression.right) elif isinstance(expression.left, expr.Aggregate) and isinstance( expression.right, expr.Variable ): field = model.Field(expression.right.name) aggregate = byte_aggregate(expression.left) if field and field in message.fields and len(field.identifier.parts) == 1 and aggregate: if embedded: return expression.__class__( expr.Indexed( expr.Variable(ID("Buffer") * "all"), expr.ValueRange( expr.Call( const.TYPES_TO_INDEX, [ expr.Selected( expr.Indexed( expr.Variable("Cursors"), expr.Variable(field.affixed_name), ), "First", ) ], ), expr.Call( const.TYPES_TO_INDEX, [ expr.Selected( expr.Indexed( expr.Variable("Cursors"), expr.Variable(field.affixed_name), ), "Last", ) ], ), ), ), aggregate, ) equal_call = expr.Call( "Equal", [expr.Variable("Ctx"), expr.Variable(field.affixed_name), aggregate], ) return equal_call if isinstance(expression, expr.Equal) else expr.Not(equal_call) boolean_literal = None other = None if ( isinstance(expression.left, expr.Literal) and expression.left.identifier in model.BOOLEAN.literals ): boolean_literal = expression.left other = expression.right if ( isinstance(expression.right, expr.Literal) and expression.right.identifier in model.BOOLEAN.literals ): boolean_literal = expression.right other = expression.left if boolean_literal and other: return expression.__class__( other, type_conversion(expr.Call("To_Base_Integer", [boolean_literal])) ) def field_value(field: model.Field) -> expr.Expr: if public: return expr.Call(f"Get_{field.name}", [expr.Variable("Ctx")]) return expr.Selected( expr.Indexed( expr.Variable(ID("Ctx") * "Cursors" if not embedded else "Cursors"), expr.Variable(field.affixed_name), ), "Value", ) if isinstance(expression, expr.Relation): if ( isinstance(expression.left, expr.Variable) and model.Field(expression.left.name) in message.fields and isinstance(expression.right, expr.Number) ): return expression.__class__( field_value(model.Field(expression.left.name)), expression.right ) if ( isinstance(expression.right, expr.Variable) and model.Field(expression.right.name) in message.fields and isinstance(expression.left, expr.Number) ): return expression.__class__( expression.left, field_value(model.Field(expression.right.name)) ) return expression
def prefixed(name: str) -> expr.Expr: return expr.Variable(ID("Ctx") * name) if not embedded else expr.Variable(name)
def test_quantified_expression_rflx_expr( expression: Callable[[str, ada.Expr, ada.Expr], ada.Expr]) -> None: result = expression("X", ada.Variable("Y"), ada.Variable("Z")).rflx_expr() expected = getattr(expr, expression.__name__)("X", expr.Variable("Y"), expr.Variable("Z")) assert result == expected
("16#6664#", expr.Number(26212)), ("16#66_64#", expr.Number(26212)), ("-1000", expr.Number(-1000)), ("-1_000", expr.Number(-1000)), ("-16#6664#", expr.Number(-26212)), ("-16#66_64#", expr.Number(-26212)), ], ) def test_expression_numeric_literal(string: str, expected: expr.Expr) -> None: actual = parse_math_expression(string, extended=False) assert actual == expected assert actual.location @pytest.mark.parametrize( "string,expected", [("X", expr.Variable("X")), ("X::Y", expr.Variable("X::Y"))] ) def test_variable(string: str, expected: decl.Declaration) -> None: actual = parse_expression(string, lang.GrammarRule.variable_rule) assert actual == expected assert actual.location @pytest.mark.parametrize( "string,expected", [ ("X'First", expr.First(expr.Variable("X"))), ("X'Last", expr.Last(expr.Variable("X"))), ("X'Size", expr.Size(expr.Variable("X"))), ("X'Head", expr.Head(expr.Variable("X"))), ("X'Opaque", expr.Opaque(expr.Variable("X"))),
def field_last(field: model.Field) -> expr.Expr: if public: return expr.Call( "Field_Last", [expr.Variable("Ctx"), expr.Variable(field.affixed_name)] ) return expr.Selected(expr.Indexed(cursors, expr.Variable(field.affixed_name)), "Last")
def variables(draw: Draw, elements: st.SearchStrategy[str]) -> expr.Variable: return expr.Variable(draw(elements))
def prefixed(name: str) -> expr.Expr: return expr.Selected(expr.Variable("Ctx"), name) if not embedded else expr.Variable(name)
def test_consistency_specification_parsing_generation(tmp_path: Path) -> None: tag = Enumeration( "Test::Tag", [("Msg_Data", expr.Number(1)), ("Msg_Error", expr.Number(3))], expr.Number(8), always_valid=False, ) length = ModularInteger("Test::Length", expr.Pow(expr.Number(2), expr.Number(16))) message = Message( "Test::Message", [ Link(INITIAL, Field("Tag")), Link( Field("Tag"), Field("Length"), expr.Equal(expr.Variable("Tag"), expr.Variable("Msg_Data")), ), Link(Field("Tag"), FINAL, expr.Equal(expr.Variable("Tag"), expr.Variable("Msg_Error"))), Link( Field("Length"), Field("Value"), size=expr.Mul(expr.Variable("Length"), expr.Number(8)), ), Link(Field("Value"), FINAL), ], { Field("Tag"): tag, Field("Length"): length, Field("Value"): OPAQUE }, skip_proof=True, ) session = Session( "Test::Session", "A", "C", [ State( "A", declarations=[], actions=[stmt.Read("X", expr.Variable("M"))], transitions=[ Transition("B"), ], ), State( "B", declarations=[ decl.VariableDeclaration("Z", BOOLEAN.identifier, expr.Variable("Y")), ], actions=[], transitions=[ Transition( "C", condition=expr.And( expr.Equal(expr.Variable("Z"), expr.TRUE), expr.Equal(expr.Call("G", [expr.Variable("F")]), expr.TRUE), ), description="rfc1149.txt+45:4-47:8", ), Transition("A"), ], description="rfc1149.txt+51:4-52:9", ), State("C"), ], [ decl.VariableDeclaration("M", "Test::Message"), decl.VariableDeclaration("Y", BOOLEAN.identifier, expr.FALSE), ], [ decl.ChannelDeclaration("X", readable=True, writable=True), decl.TypeDeclaration(Private("Test::T")), decl.FunctionDeclaration("F", [], "Test::T"), decl.FunctionDeclaration("G", [decl.Argument("P", "Test::T")], BOOLEAN.identifier), ], [BOOLEAN, OPAQUE, tag, length, message], ) model = Model(types=[BOOLEAN, OPAQUE, tag, length, message], sessions=[session]) model.write_specification_files(tmp_path) p = parser.Parser() p.parse(tmp_path / "test.rflx") parsed_model = p.create_model() assert parsed_model.types == model.types assert parsed_model.sessions == model.sessions assert parsed_model == model
def test_value_range_rflx_expr() -> None: assert ada.ValueRange(ada.Variable("X"), ada.Variable("Y")).rflx_expr() == expr.ValueRange( expr.Variable("X"), expr.Variable("Y"))