nemtus-catparser 3.2.0__tar.gz
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.
- nemtus_catparser-3.2.0/LICENSE +21 -0
- nemtus_catparser-3.2.0/PKG-INFO +88 -0
- nemtus_catparser-3.2.0/README.md +65 -0
- nemtus_catparser-3.2.0/catparser/AstPostProcessor.py +97 -0
- nemtus_catparser-3.2.0/catparser/AstValidator.py +243 -0
- nemtus_catparser-3.2.0/catparser/CatsLarkParser.py +187 -0
- nemtus_catparser-3.2.0/catparser/DisplayType.py +17 -0
- nemtus_catparser-3.2.0/catparser/__init__.py +0 -0
- nemtus_catparser-3.2.0/catparser/__main__.py +138 -0
- nemtus_catparser-3.2.0/catparser/ast.py +710 -0
- nemtus_catparser-3.2.0/catparser/generators/__init__.py +0 -0
- nemtus_catparser-3.2.0/catparser/generators/util.py +178 -0
- nemtus_catparser-3.2.0/catparser/grammar/catbuffer.lark +104 -0
- nemtus_catparser-3.2.0/pyproject.toml +20 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nemtus-catparser
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: Symbol Catbuffer Parser
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: symbol,catbuffer,catparser,parser,catbuffer-parser
|
|
8
|
+
Author: Symbol Contributors
|
|
9
|
+
Author-email: contributors@symbol.dev
|
|
10
|
+
Maintainer: Symbol Contributors
|
|
11
|
+
Maintainer-email: contributors@symbol.dev
|
|
12
|
+
Requires-Python: >=3.10,<4.0
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Project-URL: Repository, https://github.com/nemtus/symbol/tree/dev/catbuffer/parser
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
<!-- nemtus-mirror-notice -->
|
|
24
|
+
> **Note (nemtus mirror):** This package is a content mirror of the catbuffer
|
|
25
|
+
> parser from [`symbol/symbol`](https://github.com/symbol/symbol)
|
|
26
|
+
> (`catbuffer/parser`, upstream PyPI name `catparser`). nemtus republishes it on
|
|
27
|
+
> PyPI as [`nemtus-catparser`](https://pypi.org/project/nemtus-catparser/); the
|
|
28
|
+
> only change from upstream is the published distribution name. The import module
|
|
29
|
+
> name is unchanged — you still `import catparser`. For the canonical project, see
|
|
30
|
+
> [symbol/symbol](https://github.com/symbol/symbol).
|
|
31
|
+
<!-- /nemtus-mirror-notice -->
|
|
32
|
+
|
|
33
|
+
# catbuffer-parser
|
|
34
|
+
|
|
35
|
+
[](https://travis-ci.com/symbol/catbuffer-parser)
|
|
36
|
+
|
|
37
|
+
This project is the reference parser implementation for the CATS (Compact Affinitized Transfer Schema) DSL that is used by the Symbol blockchain. The parser converts a CATS file or files into an AST that can be used by a generator to produce serialization and deserialization code for defined objects.
|
|
38
|
+
|
|
39
|
+
- A reference to the DSL format can be found [here](docs/cats_dsl.md).
|
|
40
|
+
- For a sampling of real world schemas, those used by the Symbol blockchain can be found [here](https://github.com/symbol/catbuffer-schemas).
|
|
41
|
+
|
|
42
|
+
## Requirements
|
|
43
|
+
|
|
44
|
+
* Python >= 3.7
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/symbol/catbuffer-parser
|
|
50
|
+
pip3 install -r requirements.txt
|
|
51
|
+
pip3 install -r lint_requirements.txt # optional
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
usage: python -m catparser [-h] -s SCHEMA -i INCLUDE [-o OUTPUT]
|
|
58
|
+
|
|
59
|
+
CATS code generator
|
|
60
|
+
|
|
61
|
+
optional arguments:
|
|
62
|
+
-h, --help show this help message and exit
|
|
63
|
+
-s SCHEMA, --schema SCHEMA
|
|
64
|
+
input CATS file
|
|
65
|
+
-i INCLUDE, --include INCLUDE
|
|
66
|
+
schema root directory
|
|
67
|
+
-o OUTPUT, --output OUTPUT
|
|
68
|
+
yaml output file
|
|
69
|
+
-g GENERATOR, --generator GENERATOR
|
|
70
|
+
generator class to use to produce output files (defaults to YAML output)
|
|
71
|
+
-q, --quiet do not print type descriptors to console
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Examples
|
|
75
|
+
|
|
76
|
+
``catparser`` can be used on its own to parse input files, check their validity and optionally output a YAML file containing the parsed type descriptors:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# parse but don't output anything
|
|
80
|
+
python3 -m catparser --schema ../schemas/symbol/transfer/transfer.cats --include ../schemas/symbol --quiet
|
|
81
|
+
|
|
82
|
+
# parse and output the AST
|
|
83
|
+
python3 -m catparser --schema ../schemas/symbol/transfer/transfer.cats --include ../schemas/symbol
|
|
84
|
+
|
|
85
|
+
# parse and generate code using the specified generator (`generator.Generator`)
|
|
86
|
+
python3 -m catparser --schema ../schemas/symbol/transfer/transfer.cats --include ../schemas/symbol --generator generator.Generator
|
|
87
|
+
```
|
|
88
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!-- nemtus-mirror-notice -->
|
|
2
|
+
> **Note (nemtus mirror):** This package is a content mirror of the catbuffer
|
|
3
|
+
> parser from [`symbol/symbol`](https://github.com/symbol/symbol)
|
|
4
|
+
> (`catbuffer/parser`, upstream PyPI name `catparser`). nemtus republishes it on
|
|
5
|
+
> PyPI as [`nemtus-catparser`](https://pypi.org/project/nemtus-catparser/); the
|
|
6
|
+
> only change from upstream is the published distribution name. The import module
|
|
7
|
+
> name is unchanged — you still `import catparser`. For the canonical project, see
|
|
8
|
+
> [symbol/symbol](https://github.com/symbol/symbol).
|
|
9
|
+
<!-- /nemtus-mirror-notice -->
|
|
10
|
+
|
|
11
|
+
# catbuffer-parser
|
|
12
|
+
|
|
13
|
+
[](https://travis-ci.com/symbol/catbuffer-parser)
|
|
14
|
+
|
|
15
|
+
This project is the reference parser implementation for the CATS (Compact Affinitized Transfer Schema) DSL that is used by the Symbol blockchain. The parser converts a CATS file or files into an AST that can be used by a generator to produce serialization and deserialization code for defined objects.
|
|
16
|
+
|
|
17
|
+
- A reference to the DSL format can be found [here](docs/cats_dsl.md).
|
|
18
|
+
- For a sampling of real world schemas, those used by the Symbol blockchain can be found [here](https://github.com/symbol/catbuffer-schemas).
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
* Python >= 3.7
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/symbol/catbuffer-parser
|
|
28
|
+
pip3 install -r requirements.txt
|
|
29
|
+
pip3 install -r lint_requirements.txt # optional
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
usage: python -m catparser [-h] -s SCHEMA -i INCLUDE [-o OUTPUT]
|
|
36
|
+
|
|
37
|
+
CATS code generator
|
|
38
|
+
|
|
39
|
+
optional arguments:
|
|
40
|
+
-h, --help show this help message and exit
|
|
41
|
+
-s SCHEMA, --schema SCHEMA
|
|
42
|
+
input CATS file
|
|
43
|
+
-i INCLUDE, --include INCLUDE
|
|
44
|
+
schema root directory
|
|
45
|
+
-o OUTPUT, --output OUTPUT
|
|
46
|
+
yaml output file
|
|
47
|
+
-g GENERATOR, --generator GENERATOR
|
|
48
|
+
generator class to use to produce output files (defaults to YAML output)
|
|
49
|
+
-q, --quiet do not print type descriptors to console
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Examples
|
|
53
|
+
|
|
54
|
+
``catparser`` can be used on its own to parse input files, check their validity and optionally output a YAML file containing the parsed type descriptors:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# parse but don't output anything
|
|
58
|
+
python3 -m catparser --schema ../schemas/symbol/transfer/transfer.cats --include ../schemas/symbol --quiet
|
|
59
|
+
|
|
60
|
+
# parse and output the AST
|
|
61
|
+
python3 -m catparser --schema ../schemas/symbol/transfer/transfer.cats --include ../schemas/symbol
|
|
62
|
+
|
|
63
|
+
# parse and generate code using the specified generator (`generator.Generator`)
|
|
64
|
+
python3 -m catparser --schema ../schemas/symbol/transfer/transfer.cats --include ../schemas/symbol --generator generator.Generator
|
|
65
|
+
```
|
|
@@ -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
|