• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from dataclasses import dataclass, field
16from typing import Optional, List, Dict, Tuple
17
18constructors_ = dict()
19
20
21def node(kind: str):
22
23    def decorator(cls):
24        cls = dataclass(cls)
25        constructors_[kind] = cls
26        return cls
27
28    return decorator
29
30
31@dataclass
32class SourceLocation:
33    offset: int
34    line: int
35    column: int
36
37
38@dataclass
39class SourceRange:
40    file: int
41    start: SourceLocation
42    end: SourceLocation
43
44
45@dataclass
46class Node:
47    kind: str
48    loc: SourceLocation
49
50
51@node('tag')
52class Tag(Node):
53    id: str
54    value: Optional[int] = field(default=None)
55    range: Optional[Tuple[int, int]] = field(default=None)
56    tags: Optional[List['Tag']] = field(default=None)
57
58
59@node('constraint')
60class Constraint(Node):
61    id: str
62    value: Optional[int]
63    tag_id: Optional[str]
64
65
66@dataclass
67class Field(Node):
68    parent: Node = field(init=False)
69
70
71@node('checksum_field')
72class ChecksumField(Field):
73    field_id: str
74
75
76@node('padding_field')
77class PaddingField(Field):
78    size: int
79
80
81@node('size_field')
82class SizeField(Field):
83    field_id: str
84    width: int
85
86
87@node('count_field')
88class CountField(Field):
89    field_id: str
90    width: int
91
92
93@node('body_field')
94class BodyField(Field):
95    id: str = field(init=False, default='_body_')
96
97
98@node('payload_field')
99class PayloadField(Field):
100    size_modifier: Optional[str]
101    id: str = field(init=False, default='_payload_')
102
103
104@node('fixed_field')
105class FixedField(Field):
106    width: Optional[int] = None
107    value: Optional[int] = None
108    enum_id: Optional[str] = None
109    tag_id: Optional[str] = None
110
111    @property
112    def type(self) -> Optional['Declaration']:
113        return self.parent.file.typedef_scope[self.enum_id] if self.enum_id else None
114
115
116@node('reserved_field')
117class ReservedField(Field):
118    width: int
119
120
121@node('array_field')
122class ArrayField(Field):
123    id: str
124    width: Optional[int]
125    type_id: Optional[str]
126    size_modifier: Optional[str]
127    size: Optional[int]
128    padded_size: Optional[int] = field(init=False, default=None)
129
130    @property
131    def type(self) -> Optional['Declaration']:
132        return self.parent.file.typedef_scope[self.type_id] if self.type_id else None
133
134
135@node('scalar_field')
136class ScalarField(Field):
137    id: str
138    width: int
139
140
141@node('typedef_field')
142class TypedefField(Field):
143    id: str
144    type_id: str
145
146    @property
147    def type(self) -> 'Declaration':
148        return self.parent.file.typedef_scope[self.type_id]
149
150
151@node('group_field')
152class GroupField(Field):
153    group_id: str
154    constraints: List[Constraint]
155
156
157@dataclass
158class Declaration(Node):
159    file: 'File' = field(init=False)
160
161    def __post_init__(self):
162        if hasattr(self, 'fields'):
163            for f in self.fields:
164                f.parent = self
165
166
167@node('endianness_declaration')
168class EndiannessDeclaration(Node):
169    value: str
170
171
172@node('checksum_declaration')
173class ChecksumDeclaration(Declaration):
174    id: str
175    function: str
176    width: int
177
178
179@node('custom_field_declaration')
180class CustomFieldDeclaration(Declaration):
181    id: str
182    function: str
183    width: Optional[int]
184
185
186@node('enum_declaration')
187class EnumDeclaration(Declaration):
188    id: str
189    tags: List[Tag]
190    width: int
191
192
193@node('packet_declaration')
194class PacketDeclaration(Declaration):
195    id: str
196    parent_id: Optional[str]
197    constraints: List[Constraint]
198    fields: List[Field]
199
200    @property
201    def parent(self) -> Optional['PacketDeclaration']:
202        return self.file.packet_scope[self.parent_id] if self.parent_id else None
203
204
205@node('struct_declaration')
206class StructDeclaration(Declaration):
207    id: str
208    parent_id: Optional[str]
209    constraints: List[Constraint]
210    fields: List[Field]
211
212    @property
213    def parent(self) -> Optional['StructDeclaration']:
214        return self.file.typedef_scope[self.parent_id] if self.parent_id else None
215
216
217@node('group_declaration')
218class GroupDeclaration(Declaration):
219    id: str
220    fields: List[Field]
221
222
223@dataclass
224class File:
225    endianness: EndiannessDeclaration
226    declarations: List[Declaration]
227    packet_scope: Dict[str, Declaration] = field(init=False)
228    typedef_scope: Dict[str, Declaration] = field(init=False)
229    group_scope: Dict[str, Declaration] = field(init=False)
230
231    def __post_init__(self):
232        self.packet_scope = dict()
233        self.typedef_scope = dict()
234        self.group_scope = dict()
235
236        # Construct the toplevel declaration scopes.
237        for d in self.declarations:
238            d.file = self
239            if isinstance(d, PacketDeclaration):
240                self.packet_scope[d.id] = d
241            elif isinstance(d, GroupDeclaration):
242                self.group_scope[d.id] = d
243            else:
244                self.typedef_scope[d.id] = d
245
246    @staticmethod
247    def from_json(obj: object) -> 'File':
248        """Import a File exported as JSON object by the PDL parser."""
249        endianness = convert_(obj['endianness'])
250        declarations = convert_(obj['declarations'])
251        return File(endianness, declarations)
252
253    @property
254    def byteorder(self) -> str:
255        return 'little' if self.endianness.value == 'little_endian' else 'big'
256
257    @property
258    def byteorder_short(self, short: bool = False) -> str:
259        return 'le' if self.endianness.value == 'little_endian' else 'be'
260
261
262def convert_(obj: object) -> object:
263    if obj is None:
264        return None
265    if isinstance(obj, (int, str)):
266        return obj
267    if isinstance(obj, list):
268        return [convert_(elt) for elt in obj]
269    if isinstance(obj, object):
270        if 'start' in obj.keys() and 'end' in obj.keys():
271            return (objs.start, obj.end)
272        kind = obj['kind']
273        loc = obj['loc']
274        loc = SourceRange(loc['file'], SourceLocation(**loc['start']), SourceLocation(**loc['end']))
275        constructor = constructors_.get(kind)
276        members = {'loc': loc, 'kind': kind}
277        for name, value in obj.items():
278            if name != 'kind' and name != 'loc':
279                members[name] = convert_(value)
280        return constructor(**members)
281    raise Exception('Unhandled json object type')
282