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.
@@ -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())
@@ -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()