Skip to content

Commit 989c28e

Browse files
committed
DM XMLs: Add parsing for data types [WIP]
Note that anything with a within-data-type conformance will have a conformance of optional currently. This is OK since we're not actually using these for anything expect understanding P markings currently. TODO: Still not parsing multi-bit bitfields correctly. TODO: Needs unit tests TODO: Probably not passing CI on the test support tests because of the new errors with the bitfields.
1 parent 1e91c2a commit 989c28e

File tree

2 files changed

+93
-5
lines changed

2 files changed

+93
-5
lines changed

src/python_testing/matter_testing_infrastructure/chip/testing/conformance.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ def is_disallowed(conformance: Callable):
113113
return conformance(0, [], []).decision == ConformanceDecision.DISALLOWED
114114

115115

116+
def is_provisional(conformance: Callable):
117+
return conformance(0, [], []).decision == ConformanceDecision.PROVISIONAL
118+
119+
116120
@dataclass
117121
class Conformance(Callable):
118122
def __call__(self, feature_map: uint, attribute_list: list[uint], all_command_list: list[uint]) -> ConformanceDecisionWithChoice:
@@ -182,7 +186,7 @@ def __str__(self):
182186

183187
class literal(Conformance):
184188
def __init__(self, value: str):
185-
self.value = int(value)
189+
self.value = int(value, 0)
186190

187191
def __call__(self):
188192
# This should never be called

src/python_testing/matter_testing_infrastructure/chip/testing/spec_parsing.py

+88-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import zipfile
2424
from copy import deepcopy
2525
from dataclasses import dataclass
26-
from enum import Enum, auto
26+
from enum import Enum, StrEnum, auto
2727
from importlib.abc import Traversable
2828
from typing import Callable, Optional, Union
2929

@@ -58,6 +58,29 @@ class SpecParsingException(Exception):
5858
ConformanceCallable = Callable[[uint, list[uint], list[uint]], ConformanceDecision]
5959

6060

61+
class DataTypeEnum(StrEnum):
62+
kStruct = 'struct'
63+
kEnum = 'enum'
64+
kBitmap = 'bitmap'
65+
66+
67+
@dataclass
68+
class XmlDataTypeComponent:
69+
value: uint
70+
name: str
71+
# TODO: other fields are available as well - type, constraints etc.
72+
conformance: ConformanceCallable
73+
74+
75+
@dataclass
76+
class XmlDataType:
77+
data_type: DataTypeEnum
78+
name: str
79+
components: dict[uint, XmlDataTypeComponent]
80+
# if this is None, this is a global struct
81+
cluster_ids: Optional[list[uint]]
82+
83+
6184
@dataclass
6285
class XmlFeature:
6386
code: str
@@ -120,6 +143,9 @@ class XmlCluster:
120143
generated_commands: dict[uint, XmlCommand]
121144
unknown_commands: list[XmlCommand]
122145
events: dict[uint, XmlEvent]
146+
structs: dict[str, XmlDataType]
147+
enums: dict[str, XmlDataType]
148+
bitmaps: dict[str, XmlDataType]
123149
pics: str
124150
is_provisional: bool
125151

@@ -379,6 +405,56 @@ def str_to_access_type(privilege_str: str) -> Clusters.AccessControl.Enums.Acces
379405
invoke_access = Clusters.AccessControl.Enums.AccessControlEntryPrivilegeEnum.kUnknownEnumValue
380406
return (read_access, write_access, invoke_access)
381407

408+
def _parse_components(self, struct: ElementTree.Element, component_type: DataTypeEnum) -> dict[uint, XmlDataTypeComponent]:
409+
@dataclass
410+
class ComponentTag:
411+
tag: str
412+
id_attrib: str
413+
component_tags = {DataTypeEnum.kStruct: ComponentTag('field', 'id'), DataTypeEnum.kEnum: ComponentTag(
414+
'item', 'value'), DataTypeEnum.kBitmap: ComponentTag('bitfield', 'bit')}
415+
components = {}
416+
struct_name = struct.attrib['name']
417+
location = ClusterPathLocation(0, self._cluster_id)
418+
for xml_field in list(struct):
419+
if xml_field.tag != component_tags[component_type].tag:
420+
continue
421+
try:
422+
name = xml_field.attrib['name']
423+
id = xml_field.attrib[component_tags[component_type].id_attrib]
424+
except KeyError:
425+
p = ProblemNotice("Spec XML Parsing", location=location,
426+
severity=ProblemSeverity.WARNING, problem=f"Struct field in {struct_name} with no id or name")
427+
self._problems.append(p)
428+
continue
429+
xml_conformance, problems = get_conformance(xml_field, self._cluster_id)
430+
# There are a LOT of struct fields with either arithmetic or desc conformances. We'll just call these as optional if we can't parse
431+
# These are currently unused, so this is fine for now.
432+
conformance = None
433+
if not problems:
434+
conformance = self.parse_conformance(xml_conformance)
435+
if not conformance:
436+
conformance = optional()
437+
components[id] = XmlDataTypeComponent(id, name, conformance)
438+
return components
439+
440+
def _parse_data_type(self, data_type: DataTypeEnum) -> dict[str, XmlDataType]:
441+
''' Returns XmlStructs, key is the name.'''
442+
data_types = {}
443+
container_tags = self._cluster.iter('dataTypes')
444+
for container in container_tags:
445+
xmls = container.iter(str(data_type))
446+
for element in xmls:
447+
try:
448+
name = element.attrib['name']
449+
except KeyError:
450+
location = ClusterPathLocation(0, self._cluster_id)
451+
self._problems.append(ProblemNotice("Spec XML Parsing", location=location,
452+
severity=ProblemSeverity.WARNING, problem=f"Struct {element} with no id or name"))
453+
continue
454+
data_types[name] = XmlDataType(data_type=data_type, name=name, components=self._parse_components(
455+
element, data_type), cluster_ids=[self._cluster_id])
456+
return data_types
457+
382458
def parse_features(self) -> dict[uint, XmlFeature]:
383459
features = {}
384460
for element, conformance_xml, _ in self.feature_elements:
@@ -483,7 +559,9 @@ def create_cluster(self) -> XmlCluster:
483559
accepted_commands=self.parse_commands(CommandType.ACCEPTED),
484560
generated_commands=self.parse_commands(CommandType.GENERATED),
485561
unknown_commands=self.parse_unknown_commands(),
486-
events=self.parse_events(), pics=self._pics, is_provisional=self._is_provisional)
562+
events=self.parse_events(),
563+
structs=self._parse_data_type(DataTypeEnum.kStruct), enums=self._parse_data_type(DataTypeEnum.kEnum),
564+
bitmaps=self._parse_data_type(DataTypeEnum.kBitmap), pics=self._pics, is_provisional=self._is_provisional)
487565

488566
def get_problems(self) -> list[ProblemNotice]:
489567
return self._problems
@@ -768,6 +846,12 @@ def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAt
768846
generated_commands.update(c.generated_commands)
769847
events = deepcopy(base.events)
770848
events.update(c.events)
849+
structs = deepcopy(base.structs)
850+
structs.update(c.structs)
851+
enums = deepcopy(base.enums)
852+
enums.update(c.enums)
853+
bitmaps = deepcopy(base.bitmaps)
854+
enums.update(c.bitmaps)
771855
unknown_commands = deepcopy(base.unknown_commands)
772856
for cmd in c.unknown_commands:
773857
if cmd.id in accepted_commands.keys() and cmd.name == accepted_commands[uint(cmd.id)].name:
@@ -781,8 +865,8 @@ def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAt
781865
new = XmlCluster(revision=c.revision, derived=c.derived, name=c.name,
782866
feature_map=feature_map, attribute_map=attribute_map, command_map=command_map,
783867
features=features, attributes=attributes, accepted_commands=accepted_commands,
784-
generated_commands=generated_commands, unknown_commands=unknown_commands, events=events, pics=c.pics,
785-
is_provisional=provisional)
868+
generated_commands=generated_commands, unknown_commands=unknown_commands, events=events, structs=structs,
869+
enums=enums, bitmaps=bitmaps, pics=c.pics, is_provisional=provisional)
786870
xml_clusters[id] = new
787871

788872

0 commit comments

Comments
 (0)