nemtus-catparser 3.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- catparser/AstPostProcessor.py +97 -0
- catparser/AstValidator.py +243 -0
- catparser/CatsLarkParser.py +187 -0
- catparser/DisplayType.py +17 -0
- catparser/__init__.py +0 -0
- catparser/__main__.py +138 -0
- catparser/ast.py +710 -0
- catparser/generators/__init__.py +0 -0
- catparser/generators/util.py +178 -0
- catparser/grammar/catbuffer.lark +104 -0
- nemtus_catparser-3.2.0.dist-info/METADATA +88 -0
- nemtus_catparser-3.2.0.dist-info/RECORD +14 -0
- nemtus_catparser-3.2.0.dist-info/WHEEL +4 -0
- nemtus_catparser-3.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from .ast import AstException, Struct, StructInlinePlaceholder
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AstPostProcessor:
|
|
5
|
+
"""Post processes AST type descriptors."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, type_descriptors):
|
|
8
|
+
self.raw_type_descriptors = type_descriptors
|
|
9
|
+
self.type_descriptor_map = {model.name: model for model in self.raw_type_descriptors}
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def type_descriptors(self):
|
|
13
|
+
# filter out inline structs
|
|
14
|
+
return [model for model in self.raw_type_descriptors if not hasattr(model, 'disposition') or 'inline' != model.disposition]
|
|
15
|
+
|
|
16
|
+
def apply_attributes(self):
|
|
17
|
+
"""Sets properties from attributes within all structures."""
|
|
18
|
+
for model in self._structs():
|
|
19
|
+
for field in model.fields:
|
|
20
|
+
if not hasattr(field, 'attributes') or not field.attributes:
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
for attribute in field.attributes:
|
|
24
|
+
if not hasattr(field.field_type, attribute.name):
|
|
25
|
+
raise AstException(f'field {field.name} ({field.field_type}) does not have property {attribute.name}')
|
|
26
|
+
|
|
27
|
+
setattr(field.field_type, attribute.name, attribute.values)
|
|
28
|
+
|
|
29
|
+
def _structs(self):
|
|
30
|
+
return [model for _, model in self.type_descriptor_map.items() if isinstance(model, Struct)]
|
|
31
|
+
|
|
32
|
+
def expand_named_inlines(self):
|
|
33
|
+
"""Expands named inline fields within all structures."""
|
|
34
|
+
|
|
35
|
+
for model in self._structs_with_named_inlines():
|
|
36
|
+
original_fields = model.fields[:]
|
|
37
|
+
model.fields = []
|
|
38
|
+
|
|
39
|
+
for field in original_fields:
|
|
40
|
+
if not self._is_named_inline(field):
|
|
41
|
+
model.fields.append(field)
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if field.field_type not in self.type_descriptor_map:
|
|
45
|
+
raise AstException(f'struct {model.name} contains named inline of unknown type {field.field_type}')
|
|
46
|
+
|
|
47
|
+
referenced_type_model = self.type_descriptor_map[field.field_type]
|
|
48
|
+
model.fields.extend(referenced_type_model.apply_inline_template(field))
|
|
49
|
+
|
|
50
|
+
def _structs_with_named_inlines(self):
|
|
51
|
+
return [
|
|
52
|
+
model for _, model in self.type_descriptor_map.items()
|
|
53
|
+
if isinstance(model, Struct) and any(self._is_named_inline(field) for field in model.fields)
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _is_named_inline(field):
|
|
58
|
+
return hasattr(field, 'disposition') and 'inline' == field.disposition and hasattr(field, 'name')
|
|
59
|
+
|
|
60
|
+
def expand_unnamed_inlines(self):
|
|
61
|
+
"""Expands unnamed inline fields within all structures."""
|
|
62
|
+
|
|
63
|
+
for model in self._structs_with_unnamed_inlines():
|
|
64
|
+
while self._has_unnamed_inline_field(model):
|
|
65
|
+
original_fields = model.fields[:]
|
|
66
|
+
model.fields = []
|
|
67
|
+
|
|
68
|
+
for field in original_fields:
|
|
69
|
+
if not isinstance(field, StructInlinePlaceholder):
|
|
70
|
+
model.fields.append(field)
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if field.inlined_typename not in self.type_descriptor_map:
|
|
74
|
+
raise AstException(f'struct {model.name} contains unnamed inline of unknown type {field.inlined_typename}')
|
|
75
|
+
|
|
76
|
+
referenced_type_model = self.type_descriptor_map[field.inlined_typename]
|
|
77
|
+
if 'abstract' == referenced_type_model.disposition:
|
|
78
|
+
model.factory_type = referenced_type_model.name
|
|
79
|
+
elif referenced_type_model.factory_type:
|
|
80
|
+
model.factory_type = referenced_type_model.factory_type
|
|
81
|
+
|
|
82
|
+
model.fields.extend(referenced_type_model.fields)
|
|
83
|
+
if referenced_type_model.attributes:
|
|
84
|
+
if not model.attributes:
|
|
85
|
+
model.attributes = []
|
|
86
|
+
|
|
87
|
+
model.attributes.extend(referenced_type_model.attributes)
|
|
88
|
+
|
|
89
|
+
def _structs_with_unnamed_inlines(self):
|
|
90
|
+
return [
|
|
91
|
+
model for _, model in self.type_descriptor_map.items()
|
|
92
|
+
if self._has_unnamed_inline_field(model)
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _has_unnamed_inline_field(model):
|
|
97
|
+
return isinstance(model, Struct) and any(isinstance(field, StructInlinePlaceholder) for field in model.fields)
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from .ast import Array, Conditional, Enum, FixedSizeInteger, Struct, StructInlinePlaceholder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ErrorDescriptor:
|
|
7
|
+
"""Describes an AST validation error."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message, typename, field_names=None):
|
|
10
|
+
self.message = message
|
|
11
|
+
self.typename = typename
|
|
12
|
+
self.field_names = [field_names] if isinstance(field_names, str) else field_names
|
|
13
|
+
|
|
14
|
+
def __eq__(self, rhs):
|
|
15
|
+
return isinstance(rhs, ErrorDescriptor) and str(self) == str(rhs)
|
|
16
|
+
|
|
17
|
+
def __repr__(self):
|
|
18
|
+
if self.field_names:
|
|
19
|
+
joined_field_names = ', '.join(self.field_names)
|
|
20
|
+
return f'[{self.typename}::{{ {joined_field_names} }}] {self.message}'
|
|
21
|
+
|
|
22
|
+
return f'[{self.typename}] {self.message}'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AstValidator:
|
|
26
|
+
"""Validates AST type descriptors."""
|
|
27
|
+
|
|
28
|
+
class Mode(Enum):
|
|
29
|
+
"""Validation mode."""
|
|
30
|
+
|
|
31
|
+
PRE_EXPANSION = 1
|
|
32
|
+
POST_EXPANSION = 2
|
|
33
|
+
|
|
34
|
+
def __init__(self, type_descriptors):
|
|
35
|
+
self.raw_type_descriptors = type_descriptors
|
|
36
|
+
self.type_descriptor_map = {model.name: model for model in self.raw_type_descriptors}
|
|
37
|
+
|
|
38
|
+
self.mode = self.Mode.PRE_EXPANSION
|
|
39
|
+
self.errors = []
|
|
40
|
+
|
|
41
|
+
def set_validation_mode(self, mode):
|
|
42
|
+
"""Sets the validation mode."""
|
|
43
|
+
self.mode = mode
|
|
44
|
+
|
|
45
|
+
def validate(self):
|
|
46
|
+
"""Validates all types for correctness."""
|
|
47
|
+
|
|
48
|
+
for _, model in self.type_descriptor_map.items():
|
|
49
|
+
if isinstance(model, Enum):
|
|
50
|
+
self._validate_enum(model)
|
|
51
|
+
|
|
52
|
+
if isinstance(model, Struct):
|
|
53
|
+
self._validate_struct(model)
|
|
54
|
+
|
|
55
|
+
def _validate_enum(self, model):
|
|
56
|
+
duplicate_names = self._find_duplicate_names(model.values)
|
|
57
|
+
if duplicate_names:
|
|
58
|
+
self.errors.append(ErrorDescriptor('duplicate enum values', model.name, duplicate_names))
|
|
59
|
+
|
|
60
|
+
def _validate_struct(self, model):
|
|
61
|
+
duplicate_names = self._find_duplicate_names(model.fields)
|
|
62
|
+
if duplicate_names:
|
|
63
|
+
self.errors.append(ErrorDescriptor('duplicate struct fields', model.name, duplicate_names))
|
|
64
|
+
|
|
65
|
+
field_map = {field.name: field for field in model.fields if hasattr(field, 'name')}
|
|
66
|
+
for field in model.fields:
|
|
67
|
+
def create_error_descriptor(message, error_field=field):
|
|
68
|
+
return ErrorDescriptor(message, model.name, error_field.name)
|
|
69
|
+
|
|
70
|
+
if isinstance(field, StructInlinePlaceholder):
|
|
71
|
+
self._validate_unnamed_inline(field, lambda message: ErrorDescriptor(message, model.name))
|
|
72
|
+
else:
|
|
73
|
+
self._validate_struct_field(field, field_map, create_error_descriptor)
|
|
74
|
+
|
|
75
|
+
if self.Mode.PRE_EXPANSION != self.mode:
|
|
76
|
+
self._check_struct_attributes(model, field_map)
|
|
77
|
+
|
|
78
|
+
def _validate_unnamed_inline(self, field, create_error_descriptor):
|
|
79
|
+
if not self._is_known_type(field.inlined_typename):
|
|
80
|
+
self.errors.append(create_error_descriptor(f'reference to unknown inlined type "{field.inlined_typename}"'))
|
|
81
|
+
else:
|
|
82
|
+
# all dispositions are allowed as unnamed inline
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def _validate_struct_field(self, field, field_map, create_error_descriptor):
|
|
86
|
+
if not self._is_known_type(field.field_type):
|
|
87
|
+
self.errors.append(create_error_descriptor(f'reference to unknown type "{field.field_type}"'))
|
|
88
|
+
else:
|
|
89
|
+
if 'inline' == field.disposition and 'inline' != self.type_descriptor_map[field.field_type].disposition:
|
|
90
|
+
self.errors.append(create_error_descriptor(f'named inline field referencing non inline struct "{field.field_type}"'))
|
|
91
|
+
|
|
92
|
+
if isinstance(field.field_type, FixedSizeInteger):
|
|
93
|
+
self._validate_integer(field.field_type, field_map, create_error_descriptor)
|
|
94
|
+
|
|
95
|
+
if isinstance(field.field_type, Array):
|
|
96
|
+
self._validate_array(field.field_type, field_map, create_error_descriptor)
|
|
97
|
+
|
|
98
|
+
if field.value is not None:
|
|
99
|
+
if 'sizeof' == field.disposition:
|
|
100
|
+
self._validate_sizeof(field, field_map, create_error_descriptor)
|
|
101
|
+
elif isinstance(field.value, Conditional):
|
|
102
|
+
self._validate_conditional(field, field_map, create_error_descriptor)
|
|
103
|
+
else:
|
|
104
|
+
self._validate_in_range(field.field_type, field.value, create_error_descriptor)
|
|
105
|
+
|
|
106
|
+
if field.attributes:
|
|
107
|
+
for attribute in field.attributes:
|
|
108
|
+
if not hasattr(field.field_type, attribute.name):
|
|
109
|
+
self.errors.append(create_error_descriptor(f'inapplicable attribute "{attribute.name}"'))
|
|
110
|
+
|
|
111
|
+
def _validate_integer(self, field_type, field_map, create_error_descriptor):
|
|
112
|
+
sizeref = field_type.sizeref
|
|
113
|
+
if not sizeref:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
if sizeref.property_name not in field_map:
|
|
117
|
+
self.errors.append(create_error_descriptor(f'reference to unknown sizeref property "{sizeref.property_name}"'))
|
|
118
|
+
|
|
119
|
+
def _validate_array(self, field_type, field_map, create_error_descriptor):
|
|
120
|
+
element_type = field_type.element_type
|
|
121
|
+
is_sort_key_valid = True
|
|
122
|
+
sort_key = field_type.sort_key
|
|
123
|
+
if not self._is_known_type(element_type):
|
|
124
|
+
self.errors.append(create_error_descriptor(f'reference to unknown element type "{element_type}"'))
|
|
125
|
+
is_sort_key_valid = not sort_key
|
|
126
|
+
else:
|
|
127
|
+
is_sort_key_valid = not sort_key or any(
|
|
128
|
+
sort_key == element_field.name for element_field in self.type_descriptor_map[element_type].fields
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if not is_sort_key_valid:
|
|
132
|
+
self.errors.append(create_error_descriptor(f'reference to unknown sort_key property "{sort_key}"'))
|
|
133
|
+
|
|
134
|
+
size = field_type.size
|
|
135
|
+
if isinstance(size, str) and size not in field_map:
|
|
136
|
+
self.errors.append(create_error_descriptor(f'reference to unknown size property "{size}"'))
|
|
137
|
+
|
|
138
|
+
def _validate_sizeof(self, field, field_map, create_error_descriptor):
|
|
139
|
+
if field.value not in field_map:
|
|
140
|
+
self.errors.append(create_error_descriptor(f'reference to unknown sizeof property "{field.value}"'))
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
reference_typename = field_map[field.value].field_type
|
|
144
|
+
reference_type = None if not isinstance(reference_typename, str) else self.type_descriptor_map[reference_typename]
|
|
145
|
+
if not isinstance(reference_type, Struct):
|
|
146
|
+
self.errors.append(create_error_descriptor(f'sizeof property references fixed size type "{reference_typename}"'))
|
|
147
|
+
elif not reference_type.is_size_implicit:
|
|
148
|
+
message = f'sizeof property references type "{reference_typename}" without is_size_implicit attribute'
|
|
149
|
+
self.errors.append(create_error_descriptor(message))
|
|
150
|
+
|
|
151
|
+
def _validate_conditional(self, field, field_map, create_error_descriptor):
|
|
152
|
+
linked_field_name = field.value.linked_field_name
|
|
153
|
+
|
|
154
|
+
if linked_field_name not in field_map:
|
|
155
|
+
self.errors.append(create_error_descriptor(f'reference to unknown condition field "{linked_field_name}"'))
|
|
156
|
+
else:
|
|
157
|
+
self._validate_in_range(field_map[linked_field_name].field_type, field.value.value, create_error_descriptor)
|
|
158
|
+
|
|
159
|
+
def _validate_in_range(self, value_type, value, create_error_descriptor):
|
|
160
|
+
if isinstance(value_type, str):
|
|
161
|
+
if self._is_known_type(value_type):
|
|
162
|
+
value_type = self.type_descriptor_map[value_type]
|
|
163
|
+
|
|
164
|
+
if isinstance(value_type, Enum):
|
|
165
|
+
if not any(value == enum_value.name for enum_value in value_type.values):
|
|
166
|
+
self.errors.append(create_error_descriptor(f'field value "{value}" is not a valid enum value'))
|
|
167
|
+
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
if not isinstance(value, int):
|
|
171
|
+
self.errors.append(create_error_descriptor(f'field value "{value}" is not a valid numeric value'))
|
|
172
|
+
|
|
173
|
+
def _check_struct_attributes(self, model, field_map):
|
|
174
|
+
if self._check_known_field(model, field_map, 'size'):
|
|
175
|
+
if not isinstance(field_map[model.size].field_type, FixedSizeInteger):
|
|
176
|
+
self.errors.append(ErrorDescriptor(f'reference to "size" property "{model.size}" has unexpected type', model.name))
|
|
177
|
+
|
|
178
|
+
self._check_known_field(model, field_map, 'discriminator', True)
|
|
179
|
+
|
|
180
|
+
self._check_comparer(model, field_map)
|
|
181
|
+
self._check_initializers(model, field_map)
|
|
182
|
+
|
|
183
|
+
def _check_comparer(self, model, field_map):
|
|
184
|
+
if not model.comparer:
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
for (property_name, transform) in model.comparer:
|
|
188
|
+
if property_name not in field_map:
|
|
189
|
+
self.errors.append(ErrorDescriptor(f'reference to unknown "comparer" property "{property_name}"', model.name))
|
|
190
|
+
if transform not in (None, 'ripemd_keccak_256'):
|
|
191
|
+
self.errors.append(ErrorDescriptor(f'reference to unknown "comparer" transform "{transform}"', model.name))
|
|
192
|
+
|
|
193
|
+
def _check_initializers(self, model, field_map):
|
|
194
|
+
if not model.initializers:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
for initializer in model.initializers:
|
|
198
|
+
is_valid = True
|
|
199
|
+
is_concrete = model.disposition not in ('abstract', 'inline')
|
|
200
|
+
for property_name, raise_error in [(initializer.target_property_name, True), (initializer.value, is_concrete)]:
|
|
201
|
+
if property_name not in field_map:
|
|
202
|
+
is_valid = False
|
|
203
|
+
if raise_error:
|
|
204
|
+
self.errors.append(ErrorDescriptor(
|
|
205
|
+
f'reference to unknown "intializes" property "{property_name}"',
|
|
206
|
+
model.name))
|
|
207
|
+
|
|
208
|
+
if not is_valid:
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
# str compare is good enough as it lets us differentiate among subtypes of things like FixedSizeInteger
|
|
212
|
+
if str(field_map[initializer.target_property_name].field_type) != str(field_map[initializer.value].field_type):
|
|
213
|
+
self.errors.append(ErrorDescriptor(
|
|
214
|
+
f'property "{initializer.target_property_name}" has initializer "{initializer.value}" of different type',
|
|
215
|
+
model.name))
|
|
216
|
+
|
|
217
|
+
def _check_known_field(self, model, field_map, property_name, multi_value=False):
|
|
218
|
+
values = getattr(model, property_name)
|
|
219
|
+
if not values:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
if not multi_value:
|
|
223
|
+
values = [values]
|
|
224
|
+
|
|
225
|
+
has_error = False
|
|
226
|
+
for value in values:
|
|
227
|
+
if value not in field_map:
|
|
228
|
+
self.errors.append(ErrorDescriptor(f'reference to unknown "{property_name}" property "{value}"', model.name))
|
|
229
|
+
has_error = True
|
|
230
|
+
|
|
231
|
+
return not has_error
|
|
232
|
+
|
|
233
|
+
def _is_known_type(self, typename):
|
|
234
|
+
return not isinstance(typename, str) or typename in self.type_descriptor_map
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _find_duplicate_names(items):
|
|
238
|
+
unique_names = set()
|
|
239
|
+
duplicate_names = set()
|
|
240
|
+
for item in filter(lambda item: hasattr(item, 'name'), items):
|
|
241
|
+
(unique_names if item.name not in unique_names else duplicate_names).add(item.name)
|
|
242
|
+
|
|
243
|
+
return duplicate_names
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
|
|
2
|
+
from lark import Lark, Transformer
|
|
3
|
+
from lark.indenter import Indenter
|
|
4
|
+
|
|
5
|
+
from .ast import (
|
|
6
|
+
Alias,
|
|
7
|
+
Array,
|
|
8
|
+
Attribute,
|
|
9
|
+
Comment,
|
|
10
|
+
Conditional,
|
|
11
|
+
Enum,
|
|
12
|
+
EnumValue,
|
|
13
|
+
FixedSizeBuffer,
|
|
14
|
+
FixedSizeInteger,
|
|
15
|
+
Struct,
|
|
16
|
+
StructField,
|
|
17
|
+
StructInlinePlaceholder
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_cats_lark_parser():
|
|
22
|
+
"""Creates a CATS grammar-based Lark parser that can be used for parsing CATS files."""
|
|
23
|
+
|
|
24
|
+
class CatbufferIndenter(Indenter):
|
|
25
|
+
NL_type = '_NL'
|
|
26
|
+
OPEN_PAREN_types = []
|
|
27
|
+
CLOSE_PAREN_types = []
|
|
28
|
+
INDENT_type = '_INDENT'
|
|
29
|
+
DEDENT_type = '_DEDENT'
|
|
30
|
+
tab_len = 4 # pylint: disable=invalid-name
|
|
31
|
+
|
|
32
|
+
class CatbufferTransformer(Transformer):
|
|
33
|
+
# pylint: disable=too-many-public-methods
|
|
34
|
+
|
|
35
|
+
# region PODs
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def ESCAPED_STRING(string): # pylint: disable=invalid-name
|
|
39
|
+
return string[1:-1] # trim quotes
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def DEC_NUMBER(string): # pylint: disable=invalid-name
|
|
43
|
+
return int(string, 10)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def HEX_NUMBER(string): # pylint: disable=invalid-name
|
|
47
|
+
return int(string, 16)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def FIXED_SIZE_INTEGER(string): # pylint: disable=invalid-name
|
|
51
|
+
return FixedSizeInteger(string)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def fixed_size_buffer(tokens): # pylint: disable=invalid-name
|
|
55
|
+
return FixedSizeBuffer(tokens[0])
|
|
56
|
+
|
|
57
|
+
# endregion
|
|
58
|
+
|
|
59
|
+
# region comment
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def comment(tokens):
|
|
63
|
+
return Comment(tokens[0])
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def statement(tokens):
|
|
67
|
+
tokens[1].comment = tokens[0]
|
|
68
|
+
return tokens[1]
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _remove_comments(tokens):
|
|
72
|
+
return [token for token in tokens if not isinstance(token, Comment)]
|
|
73
|
+
|
|
74
|
+
# endregion
|
|
75
|
+
|
|
76
|
+
# region attribute
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def enum_attribute(tokens):
|
|
80
|
+
return Attribute(tokens)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def enum_attributes(tokens):
|
|
84
|
+
return tokens # forward array upstream
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def field_attribute(tokens):
|
|
88
|
+
return Attribute(tokens)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def field_attributes(tokens):
|
|
92
|
+
return tokens # forward array upstream
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def struct_attribute(tokens):
|
|
96
|
+
return Attribute(tokens)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def struct_attributes(tokens):
|
|
100
|
+
return tokens # forward array upstream
|
|
101
|
+
|
|
102
|
+
# endregion
|
|
103
|
+
|
|
104
|
+
# region alias
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def alias(tokens):
|
|
108
|
+
return Alias(tokens)
|
|
109
|
+
|
|
110
|
+
# endregion
|
|
111
|
+
|
|
112
|
+
# region enum
|
|
113
|
+
|
|
114
|
+
@staticmethod
|
|
115
|
+
def enum(tokens):
|
|
116
|
+
enum_model = Enum(CatbufferTransformer._remove_comments(tokens[1:]))
|
|
117
|
+
enum_model.attributes = tokens[0]
|
|
118
|
+
return enum_model
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def enum_child(tokens):
|
|
122
|
+
return CatbufferTransformer.statement(tokens)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def enum_value(tokens):
|
|
126
|
+
return EnumValue(tokens)
|
|
127
|
+
|
|
128
|
+
# endregion
|
|
129
|
+
|
|
130
|
+
# region struct
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def struct(tokens):
|
|
134
|
+
struct_model = Struct(CatbufferTransformer._remove_comments(tokens[1:]))
|
|
135
|
+
struct_model.attributes = tokens[0]
|
|
136
|
+
return struct_model
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def struct_child(tokens):
|
|
140
|
+
return CatbufferTransformer.statement(tokens)
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def struct_inline(tokens):
|
|
144
|
+
return StructInlinePlaceholder(tokens)
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def struct_field(tokens):
|
|
148
|
+
field_model = StructField(tokens[1:])
|
|
149
|
+
field_model.attributes = tokens[0]
|
|
150
|
+
return field_model
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def struct_field_const(tokens):
|
|
154
|
+
return StructField(tokens, 'const')
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def struct_field_reserved(tokens):
|
|
158
|
+
return StructField(tokens, 'reserved')
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def struct_field_sizeof(tokens):
|
|
162
|
+
return StructField(tokens, 'sizeof')
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def struct_field_inline(tokens):
|
|
166
|
+
return StructField(tokens, 'inline')
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def conditional_expression(tokens):
|
|
170
|
+
return Conditional(tokens)
|
|
171
|
+
|
|
172
|
+
# endregion
|
|
173
|
+
|
|
174
|
+
# region array
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def array_expression(tokens):
|
|
178
|
+
return Array(tokens)
|
|
179
|
+
|
|
180
|
+
# endregion
|
|
181
|
+
|
|
182
|
+
return Lark.open(
|
|
183
|
+
'grammar/catbuffer.lark',
|
|
184
|
+
rel_to=__file__,
|
|
185
|
+
parser='lalr',
|
|
186
|
+
postlex=CatbufferIndenter(),
|
|
187
|
+
transformer=CatbufferTransformer())
|
catparser/DisplayType.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DisplayType(Enum):
|
|
5
|
+
"""Enumeration of AST object display types."""
|
|
6
|
+
|
|
7
|
+
UNSET = 0
|
|
8
|
+
INTEGER = 1
|
|
9
|
+
BYTE_ARRAY = 2
|
|
10
|
+
TYPED_ARRAY = 3
|
|
11
|
+
ENUM = 4
|
|
12
|
+
STRUCT = 5
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def is_array(self):
|
|
16
|
+
"""Returns true if this display type is an array type."""
|
|
17
|
+
return self in (DisplayType.BYTE_ARRAY, DisplayType.TYPED_ARRAY)
|
catparser/__init__.py
ADDED
|
File without changes
|
catparser/__main__.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import importlib
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from lark import Tree
|
|
8
|
+
|
|
9
|
+
from .ast import AstException, Statement
|
|
10
|
+
from .AstPostProcessor import AstPostProcessor
|
|
11
|
+
from .AstValidator import AstValidator
|
|
12
|
+
from .CatsLarkParser import create_cats_lark_parser
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def print_error(message):
|
|
16
|
+
print(f'\033[31m{message}\033[39m')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LarkMultiFileParser:
|
|
20
|
+
"""Multifile CATS parser implementation using Lark."""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.parser = create_cats_lark_parser()
|
|
24
|
+
self.dirname = None
|
|
25
|
+
|
|
26
|
+
self.type_descriptors = []
|
|
27
|
+
self.processed_filepaths = []
|
|
28
|
+
|
|
29
|
+
def set_include_path(self, include_path):
|
|
30
|
+
self.dirname = Path(include_path)
|
|
31
|
+
|
|
32
|
+
def parse(self, filepath):
|
|
33
|
+
if filepath in self.processed_filepaths:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
print(f'processing \033[33m{filepath}\033[39m...')
|
|
37
|
+
self.processed_filepaths.append(filepath)
|
|
38
|
+
|
|
39
|
+
with open(filepath, 'rt', encoding='utf8') as infile:
|
|
40
|
+
contents = infile.read()
|
|
41
|
+
|
|
42
|
+
parse_result = self.parser.parse(contents)
|
|
43
|
+
if isinstance(parse_result, Statement):
|
|
44
|
+
return [parse_result]
|
|
45
|
+
|
|
46
|
+
descriptors = []
|
|
47
|
+
unprocessed_trees = [child for child in parse_result.children if isinstance(child, Tree)]
|
|
48
|
+
for unprocessed_tree in unprocessed_trees:
|
|
49
|
+
if 'import' == unprocessed_tree.data:
|
|
50
|
+
descriptors += self.parse(self.dirname / unprocessed_tree.children[0])
|
|
51
|
+
else:
|
|
52
|
+
raise AstException(f'found unexpected unprocessed tree "{unprocessed_tree.data}"')
|
|
53
|
+
|
|
54
|
+
# filter out trees and unattached comments
|
|
55
|
+
return descriptors + [descriptor for descriptor in parse_result.children if isinstance(descriptor, Statement)]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _validate(raw_type_descriptors, stage, mode):
|
|
59
|
+
validator = AstValidator(raw_type_descriptors)
|
|
60
|
+
validator.set_validation_mode(mode)
|
|
61
|
+
validator.validate()
|
|
62
|
+
if validator.errors:
|
|
63
|
+
print_error(f'[ERRORS DETECTED AT STAGE {stage}]')
|
|
64
|
+
for error in validator.errors:
|
|
65
|
+
print(f' + {error}')
|
|
66
|
+
|
|
67
|
+
sys.exit(2)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _load_generator_class(generator_full_name):
|
|
71
|
+
generator_class_name = Path(generator_full_name).suffix[1:]
|
|
72
|
+
|
|
73
|
+
print()
|
|
74
|
+
print(f'loading generator {generator_class_name} from {generator_full_name}')
|
|
75
|
+
|
|
76
|
+
generator_module = importlib.import_module(generator_full_name)
|
|
77
|
+
generator_class = getattr(generator_module, generator_class_name)
|
|
78
|
+
|
|
79
|
+
print(f'loaded generator: {generator_class}')
|
|
80
|
+
print()
|
|
81
|
+
|
|
82
|
+
return generator_class
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class NoAliasDumper(yaml.SafeDumper):
|
|
86
|
+
def ignore_aliases(self, data):
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main():
|
|
91
|
+
parser = argparse.ArgumentParser(
|
|
92
|
+
prog=None if globals().get('__spec__') is None else f'python -m {__spec__.name.partition(".")[0]}',
|
|
93
|
+
description='CATS code generator'
|
|
94
|
+
)
|
|
95
|
+
parser.add_argument('-s', '--schema', help='input CATS file', required=True)
|
|
96
|
+
parser.add_argument('-i', '--include', help='schema root directory', required=True)
|
|
97
|
+
parser.add_argument('-o', '--output', help='yaml output file')
|
|
98
|
+
parser.add_argument('-g', '--generator', help='generator class to use to produce output files (defaults to YAML output)')
|
|
99
|
+
parser.add_argument('-q', '--quiet', help='do not print type descriptors to console', action='store_true')
|
|
100
|
+
args = parser.parse_args()
|
|
101
|
+
|
|
102
|
+
file_parser = LarkMultiFileParser()
|
|
103
|
+
file_parser.set_include_path(args.include)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
raw_type_descriptors = file_parser.parse(args.schema)
|
|
107
|
+
except (AstException, OSError) as ex:
|
|
108
|
+
print_error(str(ex))
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
processor = AstPostProcessor(raw_type_descriptors)
|
|
112
|
+
|
|
113
|
+
_validate(raw_type_descriptors, 'PRE EXPANSION', AstValidator.Mode.PRE_EXPANSION)
|
|
114
|
+
|
|
115
|
+
processor.apply_attributes()
|
|
116
|
+
processor.expand_named_inlines()
|
|
117
|
+
processor.expand_unnamed_inlines()
|
|
118
|
+
|
|
119
|
+
_validate(raw_type_descriptors, 'POST EXPANSION', AstValidator.Mode.POST_EXPANSION)
|
|
120
|
+
|
|
121
|
+
type_descriptors = [model.to_legacy_descriptor() for model in processor.type_descriptors]
|
|
122
|
+
|
|
123
|
+
if not args.quiet:
|
|
124
|
+
# dump parsed type descriptors to console
|
|
125
|
+
yaml.dump(type_descriptors, sys.stdout, Dumper=NoAliasDumper)
|
|
126
|
+
|
|
127
|
+
if args.output:
|
|
128
|
+
if args.generator:
|
|
129
|
+
generator_class = _load_generator_class(args.generator)
|
|
130
|
+
generator_class.generate(processor.type_descriptors, args.output)
|
|
131
|
+
else:
|
|
132
|
+
# save YAML to file
|
|
133
|
+
with open(args.output, 'wt', encoding='utf8') as out:
|
|
134
|
+
yaml.dump(type_descriptors, out, Dumper=NoAliasDumper)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if '__main__' == __name__:
|
|
138
|
+
main()
|