From b7279aac92214d24be6ab2f41c2bcb6f8ad5599f Mon Sep 17 00:00:00 2001 From: Alina Lenk Date: Tue, 16 Apr 2024 00:00:07 +0200 Subject: [PATCH] generate_packets.py: allow compatible fields with same name and different caps See RM #458 Signed-off-by: Alina Lenk --- common/generate_packets.py | 74 ++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/common/generate_packets.py b/common/generate_packets.py index 6d98081241..6135b8f6cb 100755 --- a/common/generate_packets.py +++ b/common/generate_packets.py @@ -714,6 +714,28 @@ class FieldType(RawFieldType): dataio stream.""" raise NotImplementedError + def _compat_keys(self, location: Location): + """Internal helper function. Yield keys to compare for + type compatibility. See is_type_compatible()""" + yield self.get_code_declaration(location) + yield self.get_code_handle_param(location) + yield self.get_code_handle_arg(location) + yield self.get_code_fill(location) + + def is_type_compatible(self, other: "FieldType") -> bool: + """Determine whether two field types can be used interchangeably as + part of the packet struct, i.e. differ in dataio transmission only""" + if other is self: + return True + loc = Location("compat_test_field_name") + return all( + a == b + for a, b in zip_longest( + self._compat_keys(loc), + other._compat_keys(loc), + ) + ) + class BasicType(FieldType): """Type information for a field without any specialized treatment""" @@ -1447,6 +1469,22 @@ class Field: """Set of all capabilities affecting this field""" return self.flags.add_caps | self.flags.remove_caps + def is_compatible(self, other: "Field") -> bool: + """Whether two field objects are variants of the same field, i.e. + type-compatible in the packet struct and mutually exclusive based + on their required capabilities. + + Note that this function does not test field name.""" + return bool( + ( + (self.flags.add_caps & other.flags.remove_caps) + or + (self.flags.remove_caps & other.flags.add_caps) + ) + and + self.type_info.is_type_compatible(other.type_info) + ) + def present_with_caps(self, caps: typing.Container[str]) -> bool: """Determine whether this field should be part of a variant with the given capabilities""" @@ -1678,7 +1716,7 @@ class Variant: use this variant""" self.fields = [ field - for field in packet.fields + for field in packet.all_fields if field.present_with_caps(self.poscaps) ] """All fields that are transmitted when using this variant""" @@ -2541,16 +2579,32 @@ class Packet: self.dirs = Directions(dirs) """Which directions this packet can be sent in""" - self.fields = [ + raw_fields = [ field for line in lines for field in Field.parse(self.cfg, line, resolve_type) ] - """List of all fields of this packet""" - self.key_fields = [field for field in self.fields if field.is_key] - """List of only the key fields of this packet""" - self.other_fields = [field for field in self.fields if not field.is_key] - """List of only the non-key fields of this packet""" + # put key fields before all others + key_fields = [field for field in raw_fields if field.is_key] + other_fields = [field for field in raw_fields if not field.is_key] + self.all_fields = key_fields + other_fields + """List of all fields of this packet, including name duplicates for + different capability variants that are compatible. + + Only relevant for creating Variants; self.fields should be used when + not dealing with capabilities or Variants.""" + + self.fields = [Field(_, _, _, _) for _ in []] + """List of all fields of this packet, with only one field of each name""" + # check for duplicate field names + for next_field in self.all_fields: + duplicates = [field for field in self.fields if field.name == next_field.name] + if not duplicates: + self.fields.append(next_field) + continue + if not all(field.is_compatible(next_field) for field in duplicates): + raise ValueError("incompatible fields with duplicate name: %s(%d).%s" + % (packet_type, packet_number, next_field.name)) # valid, since self.fields is already set if self.no_packet: @@ -2561,7 +2615,7 @@ class Packet: raise ValueError("requested dsend for %s without fields isn't useful" % self.name) # create cap variants - all_caps = self.all_caps # valid, since self.fields is already set + all_caps = self.all_caps # valid, since self.all_fields is already set self.variants = [ Variant(caps, all_caps.difference(caps), self, i + 100) for i, caps in enumerate(powerset(sorted(all_caps))) @@ -2636,7 +2690,7 @@ class Packet: @property def all_caps(self) -> "set[str]": """Set of all capabilities affecting this packet""" - return {cap for field in self.fields for cap in field.all_caps} + return {cap for field in self.all_fields for cap in field.all_caps} def get_struct(self) -> str: @@ -2651,7 +2705,7 @@ struct {self.name} {{ body = "".join( prefix(" ", field.get_declar()) - for field in chain(self.key_fields, self.other_fields) + for field in self.fields ) or """\ char __dummy; /* to avoid malloc(0); */ """ -- 2.34.1