Init Commit: Moved bproto to seperate repo
This commit is contained in:
14
.coveragerc
Normal file
14
.coveragerc
Normal file
@@ -0,0 +1,14 @@
|
||||
[report]
|
||||
exclude_also =
|
||||
def __repr__
|
||||
precision = 2
|
||||
sort = Name
|
||||
|
||||
[xml]
|
||||
output = coverage/cov_python.xml
|
||||
|
||||
[run]
|
||||
branch = True
|
||||
include = test/backend_output/python/*,
|
||||
src/*,
|
||||
omit = src/gen/*
|
||||
15
.flake8
Normal file
15
.flake8
Normal file
@@ -0,0 +1,15 @@
|
||||
[flake8]
|
||||
exclude =
|
||||
src/gen/**/*.py,
|
||||
__pycache__,
|
||||
.git
|
||||
filename =
|
||||
src/**/*.py,
|
||||
test/**/*.py
|
||||
|
||||
|
||||
|
||||
ignore =
|
||||
W293,
|
||||
E501,
|
||||
E402
|
||||
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
.antlr
|
||||
.venv
|
||||
.pytest_cache
|
||||
.coverage
|
||||
.$*
|
||||
|
||||
**/__pycache__
|
||||
test_out/*
|
||||
**/.cmake
|
||||
**/build
|
||||
|
||||
test/backend_output/*
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "test/output/c/src/Unity"]
|
||||
path = test/output/c/src/Unity
|
||||
url = https://github.com/ThrowTheSwitch/Unity
|
||||
77
.vscode/launch.json
vendored
Normal file
77
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: bproto compiler Python",
|
||||
"type": "debugpy",
|
||||
"program": "src/main.py",
|
||||
"console": "integratedTerminal",
|
||||
"args": [
|
||||
"-i", "test/test_protocol.bproto",
|
||||
"-o", "test/python/bproto/",
|
||||
"-t", "python"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Pytest: Python Backend Tests",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "test/runner.py",
|
||||
"console": "integratedTerminal",
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Tests C Debug (gdb)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test_runner",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/test/backend_output/c",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Set Disassembly Flavor to Intel",
|
||||
"text": "-gdb-set disassembly-flavor intel",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "CMake: Build C Tests"
|
||||
},
|
||||
{
|
||||
"name": "Tests C Python Integration Debug (gdb)",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test_integration_python",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/test/backend_output/c",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "Set Disassembly Flavor to Intel",
|
||||
"text": "-gdb-set disassembly-flavor intel",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "CMake: Build C Tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
33
.vscode/settings.json
vendored
Normal file
33
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"cmath": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"__nullptr": "cpp",
|
||||
"target": "cpp",
|
||||
"bproto_encdec.h": "c",
|
||||
"functional": "c",
|
||||
"__locale": "c",
|
||||
"bproto_package_ids.h": "c",
|
||||
"bproto_package_structs.h": "c",
|
||||
"random": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"suit.h": "c",
|
||||
"stdint.h": "c",
|
||||
"hcp_message.h": "c"
|
||||
},
|
||||
"cmake.sourceDirectory": "/home/alexander/Projects/tumRobotic25/protocolGenerator/test/output/c",
|
||||
"coverage-gutters.coverageFileNames": [
|
||||
"coverage/cov_c.xml",
|
||||
"coverage/cov_python.xml",
|
||||
],
|
||||
"coverage-gutters.showGutterCoverage": false,
|
||||
"coverage-gutters.showLineCoverage": true,
|
||||
"flake8.enabled": true,
|
||||
"flake8.args": [
|
||||
"--config=.flake8",
|
||||
"--count"
|
||||
],
|
||||
"flake8.ignorePatterns": [
|
||||
"src/gen/*.py"
|
||||
]
|
||||
}
|
||||
155
.vscode/tasks.json
vendored
Normal file
155
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{"label": "Antlr: Generate Parser",
|
||||
"type": "shell",
|
||||
"command": "antlr4",
|
||||
"args": [
|
||||
"-Dlanguage=Python3",
|
||||
"-visitor",
|
||||
"-o",
|
||||
"src/gen",
|
||||
"bprotoV1.g4"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{"label": "Bproto compiler C",
|
||||
"type": "shell",
|
||||
"command": "${command:python.interpreterPath}",
|
||||
"args": [
|
||||
"src/main.py",
|
||||
"-i", "test/test_protocol.bproto",
|
||||
"-o", "test/backend_output",
|
||||
"-t", "c"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{"label": "CMake: Configure C Test",
|
||||
"type": "cmake",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/test/output/c"
|
||||
},
|
||||
"command": "configure",
|
||||
"args": [
|
||||
"-DCMAKE_BUILD_TYPE=Debug"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{"label": "CMake: Build C Tests",
|
||||
"type": "cmake",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/test/output/c"
|
||||
},
|
||||
"command": "build",
|
||||
"args": [
|
||||
"all"
|
||||
],
|
||||
"problemMatcher": ["$gcc"],
|
||||
"dependsOn": ["Bproto compiler C"],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
{"label": "Run C Tests",
|
||||
"type": "shell",
|
||||
"command": "build/test_runner",
|
||||
"problemMatcher": ["$gcc"],
|
||||
"dependsOn": ["CMake: Build C Tests"],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
{"label": "gcovr: Generate C Coverage",
|
||||
"type": "shell",
|
||||
"command": "gcovr",
|
||||
"args": [
|
||||
"-p", "--txt",
|
||||
"--xml", "coverage/cov_c.xml",
|
||||
"--root", ".",
|
||||
"--object-directory", "build/CMakeFiles/test_runner.dir/src/"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Run C Tests"],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
|
||||
{"label": "Run Compiler Python",
|
||||
"type": "shell",
|
||||
"command": "${command:python.interpreterPath}",
|
||||
"args": [
|
||||
"src/main.py",
|
||||
"-i", "test/test_protocol.bproto",
|
||||
"-o", "test/backend_output",
|
||||
"-t", "python"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{"label": "pytest coverage",
|
||||
"type": "shell",
|
||||
"command": "${command:python.interpreterPath}",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"TEST_COVERAGE": "1" // This cause integration tests to be ignored, as they push up the coverage
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
"-m",
|
||||
"pytest",
|
||||
"--cov",
|
||||
"--cov-report=term",
|
||||
"--cov-report=xml",
|
||||
],
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Run Compiler Python"]
|
||||
},
|
||||
{"label": "pytest test",
|
||||
"type": "shell",
|
||||
"command": "${command:python.interpreterPath}",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}",
|
||||
},
|
||||
"args": [
|
||||
"-m",
|
||||
"pytest",
|
||||
"--cov",
|
||||
"--cov-report=term",
|
||||
"--cov-report=xml",
|
||||
],
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "dedicated",
|
||||
"showReuseMessage": true,
|
||||
"clear": true
|
||||
},
|
||||
"dependsOn": [ "Run Compiler Python" ]
|
||||
},
|
||||
]
|
||||
}
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM python:alpine3.21
|
||||
|
||||
WORKDIR /compiler
|
||||
ADD src /compiler/src
|
||||
ADD template /compiler/template
|
||||
ADD requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
RUN addgroup --g 1000 groupcontainer
|
||||
RUN adduser -u 1000 -G groupcontainer -h /home/containeruser -D containeruser
|
||||
|
||||
USER containeruser
|
||||
|
||||
WORKDIR /compiler/data
|
||||
WORKDIR /compiler
|
||||
|
||||
ENTRYPOINT [ "python3" ]
|
||||
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright 2025 AlexanderHD27
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
122
README.md
Normal file
122
README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# bproto
|
||||
_binarized protocol_
|
||||
|
||||
A protocol description language for generating lightweight, binary formats optimized for embedded systems and constrained environments.
|
||||
|
||||
Bproto is a transpiler from the bproto format to a target language. Unlike protobuf and JSON, bproto focuses on creating highly efficient binary formats suitable for constrained environments.
|
||||
|
||||
bproto is designed to be statically typed and sized. No memory allocation is needed for serialization and deserialization, making it suitable for embedded systems and real-time applications.
|
||||
|
||||
**bproto supports:**
|
||||
- Numerical, Floating-point, and Boolean datatypes
|
||||
- Fixed-size arrays and strings
|
||||
- Enums and Bitfields
|
||||
|
||||
**bproto does not support:**
|
||||
- Variable-length arrays or strings
|
||||
- Nested messages
|
||||
- Optional fields
|
||||
|
||||
**Current supported Targets: C, Python**
|
||||
|
||||
Planned support: Markdown Documentation, TypeScript/Deno
|
||||
|
||||
## Getting Start
|
||||
|
||||
For Beginners have a look at the [Getting Started Guide](/docs/getting_started.md)
|
||||
|
||||
Currently the easiest way to run the bproto-compiler is via `run_docker_compiler.py`.
|
||||
For that you need docker ([Install Docker](https://docs.docker.com/engine/install/)) and python ([Install Python](https://www.python.org/downloads/)).
|
||||
|
||||
Running the compiler:
|
||||
```bash
|
||||
./run_docker_compiler.py --input protocol_description_file.bproto --output output_folder/ --target txt --target python --target c
|
||||
```
|
||||
|
||||
This will compile the `protocol_description_file.bproto` to `txt`, `python` and `c`. The output is placed in `output_folder/`
|
||||
|
||||
This use the `alexanderhd27/bproto-compiler:latest` docker image
|
||||
|
||||
### Running it nativ
|
||||
|
||||
You need python3.12.
|
||||
1. Install the python requirements with `pip install -r requirements.txt`
|
||||
2. Running the compiler with
|
||||
```
|
||||
--input protocol_description_file.bproto --output output_folder/ --target txt --target python --target c
|
||||
```
|
||||
|
||||
IMPORTANTED: You need to run the compiler from `protocolGenerator` directory, such that the compiler has access to template folder
|
||||
|
||||
## Core concept
|
||||
|
||||
How does bproto work? There are three main components:
|
||||
|
||||
<img src="docs/img/bprotoDocumentationGraphics.drawio.png" alt="Diagram showing the flow from the protocol definition via the bproto-compiler to the protocol implementation (C and Python)" width="600px">
|
||||
|
||||
<br>
|
||||
|
||||
- **Protocol Description** – A structured definition of the binary format, specifying message types, field layouts, and encoding rules.
|
||||
- **bproto-Compiler** – Translates the protocol description into optimized source code, generating efficient serializers and deserializers.
|
||||
- **Protocol Implementation (aka the target)** – The generated code and supporting files that implement the described protocol, ready for integration into (embedded) applications.
|
||||
|
||||
<br>
|
||||
|
||||
## Protocol Description
|
||||
|
||||
A protocol is described in a .bproto file.
|
||||
|
||||
Example Protocol for an imaginary aircraft:
|
||||
```bproto
|
||||
protocol myCustomAirplaneProtocol version 2
|
||||
|
||||
enum engine_status {
|
||||
OFF = 0,
|
||||
IDLE = 1,
|
||||
RUNNING = 2,
|
||||
FULL_THRUST = 3
|
||||
}
|
||||
|
||||
bits engine_enables {
|
||||
engine_left, engine_right
|
||||
}
|
||||
|
||||
// Message or Package sent from engine to cockpit engine display
|
||||
message [1] EngineSensorData {
|
||||
[0] engine_thrust_left : float32,
|
||||
[1] engine_thrust_right : float32,
|
||||
[2] fuel_tanks_fill : uint32[8], // Fill levels of 8 fuel tanks
|
||||
[3] engine_fault_left : bool,
|
||||
[4] engine_fault_right : bool
|
||||
}
|
||||
|
||||
// Message or Package sent from cockpit to the engine controller
|
||||
message [2] EngineCtrlCommand {
|
||||
[0] engine_status : enum engine_status,
|
||||
[1] engine_enables : bits engine_enables,
|
||||
[2] flaps : int8,
|
||||
[3] rudder : float64,
|
||||
[4] trim : int16
|
||||
}
|
||||
```
|
||||
(this is just an example. bproto should NOT be used in an airplane or any system that can cause harm)
|
||||
|
||||
More about bproto's syntax can be found in the [syntax documentation](/docs/syntax.md).
|
||||
|
||||
### Core Description Elements:
|
||||
|
||||
- **Protocol Tag Line** – First Line of every protocol description. Defines the protocol name and version. It establishes the protocol's identity and ensures compatibility across protocol implementations.
|
||||
- **Message** – A data structure designed to be sent over the wire. It contains fields with specific types and orders, representing the data to be transmitted.
|
||||
- **Enum** – A set of named integer values that represent discrete states or options. Enums enhance code clarity by using descriptive names instead of raw numeric values.
|
||||
- **Bitfields** – A compact representation of multiple boolean flags within a single byte or group of bytes, optimizing space for binary communication.
|
||||
|
||||
Bitfields and Enums can also be declared inline in messages.
|
||||
|
||||
Syntax documentation details can be found here!
|
||||
|
||||
## Tooling/Versions for testing
|
||||
gcc: 11.4.0
|
||||
gcov: 11.4.0
|
||||
gcovr: 5.0
|
||||
|
||||
|
||||
24
TODOs.md
Normal file
24
TODOs.md
Normal file
@@ -0,0 +1,24 @@
|
||||
## TODOs:
|
||||
- [X] Create pytest unit test
|
||||
- [X] Create combine unit test (python -> c -> python)
|
||||
- [X] Package ID + Sizes
|
||||
- [X] Generic Deocde (decode with Id)
|
||||
- [X] Converage Reports
|
||||
|
||||
- [X] Clean Up Code / Add consitanzie for Code
|
||||
- [ ] Add _Proper_ docs (code, dev tooling and product)
|
||||
- [ ] Dockerize for simplification
|
||||
|
||||
- [ ] Markdown Backend
|
||||
- [X] Hash + CRC checksum
|
||||
- [X] Proper Docs
|
||||
- [X] Vscode Plugin Syntax Highlighting
|
||||
|
||||
- [X] Add __eq__ for bitfields
|
||||
|
||||
- [X] Bitfield and
|
||||
- [X] Handle different endiandiss on differnt architectures (currently only little)
|
||||
- [ ] Current: using memcpy to handle diffrent endiandiss, is there a bette way?
|
||||
|
||||
- [X] Introduce flake8
|
||||
- [X] Big Reafactor
|
||||
39
bprotoV1.g4
Normal file
39
bprotoV1.g4
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
grammar bprotoV1;
|
||||
|
||||
protocol_defintion : protocol_header (message_def | enum_def | bit_field_def)+;
|
||||
|
||||
protocol_header : 'protocol' IDENTIFIER 'version' INT ;
|
||||
|
||||
// Protocol definitions
|
||||
message_def : 'message' message_id IDENTIFIER '{' (field ',')* (field ','?)? '}' ;
|
||||
message_id : '[' INT ']';
|
||||
enum_def : 'enum' IDENTIFIER enum_body;
|
||||
bit_field_def : 'bits' IDENTIFIER bitfield_body;
|
||||
|
||||
field : field_pos IDENTIFIER ':' dtype;
|
||||
field_pos : '[' INT ']';
|
||||
|
||||
dtype: type_standard | type_enum | type_bitfield;
|
||||
|
||||
type_standard : IDENTIFIER array_extension?;
|
||||
type_enum : 'enum' (enum_body | IDENTIFIER);
|
||||
type_bitfield : 'bits' (bitfield_body | IDENTIFIER);
|
||||
|
||||
array_extension: '[' INT ']';
|
||||
|
||||
// enum def
|
||||
enum_body : '{' (enum_field ',')* (enum_field ','?)? '}';
|
||||
enum_field: IDENTIFIER ('=' INT)? ;
|
||||
|
||||
// bit field def
|
||||
bitfield_body : '{' (bitfield_field ',')* (bitfield_field ','?)? '}';
|
||||
bitfield_field: IDENTIFIER (':' INT)?;
|
||||
|
||||
// Lexer rules
|
||||
INT : [0-9]+;
|
||||
IDENTIFIER : [a-zA-Z0-9_]+;
|
||||
|
||||
WS : [ \t\r\n]+ -> skip;
|
||||
COMMENT : '//' ~[\r\n]* -> skip;
|
||||
COMMENT_MULTILINE : '/*' .*? '*/' -> skip;
|
||||
3
coverage/cov_c.xml
Normal file
3
coverage/cov_c.xml
Normal file
File diff suppressed because one or more lines are too long
1554
coverage/cov_python.xml
Normal file
1554
coverage/cov_python.xml
Normal file
File diff suppressed because it is too large
Load Diff
57
docs/bprotoDocumentationGraphics.drawio
Normal file
57
docs/bprotoDocumentationGraphics.drawio
Normal file
@@ -0,0 +1,57 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.0.3 Chrome/130.0.6723.137 Electron/33.2.1 Safari/537.36" version="26.0.3" pages="2">
|
||||
<diagram name="Core Concept" id="uytGOHupnUP-L8cOnyTT">
|
||||
<mxGraphModel dx="512" dy="301" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;targetPerimeterSpacing=6;sourcePerimeterSpacing=6;strokeWidth=2;" edge="1" parent="1" source="-I73Jp0letHz3FIaNn9U-2" target="-I73Jp0letHz3FIaNn9U-7">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-2" value="<b>.bproto</b>" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="125" width="60" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-3" value="Protocol Description" style="text;html=1;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="110" y="210" width="80" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-7" value="" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="120" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-6" value="" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#232F3D;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.gear;" vertex="1" parent="1">
|
||||
<mxGeometry x="250" y="129" width="59" height="59" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-8" value="bproto-Compiler" style="text;html=1;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="210" width="80" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-14" value="<b>.py</b>" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="170" width="40" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-16" value="<b>.c/.h</b>" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="100" width="40" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-17" value="Protocol Implementation" style="text;html=1;align=center;verticalAlign=top;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="355" y="230" width="90" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;targetPerimeterSpacing=6;sourcePerimeterSpacing=6;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="-I73Jp0letHz3FIaNn9U-7" target="-I73Jp0letHz3FIaNn9U-16">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="196" y="170" as="sourcePoint" />
|
||||
<mxPoint x="244" y="170" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="-I73Jp0letHz3FIaNn9U-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;targetPerimeterSpacing=6;sourcePerimeterSpacing=6;strokeWidth=2;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="-I73Jp0letHz3FIaNn9U-7" target="-I73Jp0letHz3FIaNn9U-14">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="330" y="170" as="sourcePoint" />
|
||||
<mxPoint x="390" y="125" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram id="zBquDBVsXlGvDgQ8uFh9" name="Compadibilty Desition Tree">
|
||||
<mxGraphModel dx="717" dy="422" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
75
docs/compatibility.md
Normal file
75
docs/compatibility.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Compatibility and Versioning
|
||||
|
||||
This document describes how bproto handles compatibility and versioning of protocols and messages.
|
||||
|
||||
When receiving a message, the receiver must know if the data is compatible with the hard-coded protocol implementation.
|
||||
There are multiple mechanisms to ensure this.
|
||||
|
||||
The worst-case scenario is that the receiver gets a message that is not compatible with the protocol implementation -> meaning something has changed in the protocol and the receiver is not aware of it.
|
||||
|
||||
## Compatibility
|
||||
|
||||
**A protocol/message is compatible if**
|
||||
- ✅ protocol and message definitions are the same
|
||||
- ✅ the order of declaration of fields or messages has changed (but not the ID)
|
||||
|
||||
**A protocol/message is incompatible if**
|
||||
- ❌ If the protocol version has changed
|
||||
- ❌ If a message has been removed or added
|
||||
- ❌ If the protocol, message, bitfield, enum, or field name has changed
|
||||
- ❌ If Message or Field ID has changed
|
||||
- ❌ the number, type, order, or array size of fields in a message has changed
|
||||
- ❌ If the order of declaration of enums or bitfields has changed
|
||||
|
||||
=> In general, the receiver must have the same protocol implementation build as the sender.
|
||||
|
||||
## Naming Conventions
|
||||
Use descriptive names that clearly indicate the purpose of the protocol. Avoid generic names. Also include a hint about the fact that this is for transmitting data like "protocol" or "datastream".
|
||||
|
||||
## Versioning Strategies
|
||||
The version number is used to differentiate between different iterations of the protocol. Different versions of the same protocol are incompatible. By changing the protocol version, the developer can indicate a major change in structure, design, or purpose of the protocol/messages.Evalia
|
||||
|
||||
**Example:**
|
||||
```bproto
|
||||
protocol robotControlProtocol version 1
|
||||
```
|
||||
|
||||
More about bproto's syntax can be found in the [syntax documentation](/docs/syntax.md#protocol-tag-line).
|
||||
|
||||
## Compatibility Validation Process in Detail
|
||||
|
||||
This is the message validation process that should prevent incompatible messages from being processed, causing undefined behavior.
|
||||
|
||||
**Step #1 - Message Size Check:**
|
||||
The first byte of the message is the message ID.
|
||||
The receiver can with the message ID derive the expected message size. (If the message ID is unknown, the size is set to 0)
|
||||
The receiver can then check if the received message is of the expected size.
|
||||
If the size is not as expected, the message is discarded.
|
||||
-> This prevents messages with changed field/message sizes from being processed.
|
||||
|
||||
**Step #2 - CRC-16 Check:**
|
||||
The CRC-16 is calculated over the message data and compared to the last 2 bytes of the message.
|
||||
If the CRC-16 is not correct, the message is discarded.
|
||||
|
||||
The CRC-16 is initialized by first prepending a SHA-256 hash of a text form of the protocol definition to the message data.
|
||||
This text form is a human-readable representation of the protocol definition but also represents the internal structure of the protocol used by the bproto-compiler to generate other protocol implementations.
|
||||
If something changes in this internal structure, a potential incompatibility is introduced.
|
||||
|
||||
More details about how the CRC-16 is calculated can be found in the [serialization documentation](/docs/serialization.md#crc-16-calculation).
|
||||
|
||||
This text form can be generated by the bproto-compiler when choosing the backend "txt".
|
||||
This will also display the CRC-16 initialization vector and SHA-256 hash of the protocol definition.
|
||||
|
||||
There are two possible reasons for a failed CRC check:
|
||||
1. The message has been corrupted during transmission.
|
||||
2. The message data is fine but the protocol definition has changed, and therefore the SHA-256 hash is different.
|
||||
|
||||
=> The CRC-16 protects against data corruption and protocol changes.
|
||||
|
||||
**Step #3 - Message ID Check:**
|
||||
The receiver checks if the message ID is known.
|
||||
If the message ID is unknown, the message is discarded.
|
||||
|
||||
After Step #2 the receiver should already have discarded the message if it is incompatible with the protocol implementation. Nevertheless, this step is an additional line of defense.
|
||||
|
||||
|
||||
BIN
docs/img/bprotoDocumentationGraphics.drawio.png
Normal file
BIN
docs/img/bprotoDocumentationGraphics.drawio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
66
docs/serialization.md
Normal file
66
docs/serialization.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Message Encoding
|
||||
|
||||
This section describes how messages are encoded/serialized and decoded/deserialized.
|
||||
bproto is binary data with a well-defined structure.
|
||||
|
||||
## Message Structure
|
||||
|
||||
Every bproto message looks like this:
|
||||
|
||||
```
|
||||
[ 1 ] [ N ] [ 2 ]
|
||||
[Message ID] [Field Data] [CRC-16]
|
||||
```
|
||||
|
||||
- **Message ID:** The first byte of the message is the message ID. This is used to identify the message type for decoding. Every message is declared with a unique ID in the protocol definition. See [Message](/docs/syntax.md#message)
|
||||
- **Field Data:** The field data is the serialized data of the message fields. See [Message Field Data Encoding](#message-field-data-encoding)
|
||||
- **CRC-16:** The last 2 bytes of the message are the CRC-16 checksum of the message data. This is used to verify the integrity of the message. See [CRC-16 Check](/docs/compatibility.md#step-2---crc-16-check) and [CRC-16 Calculation](/docs/serialization.md#crc-16-calculation)
|
||||
|
||||
### Message Field Data Encoding
|
||||
|
||||
The order, length, and type of the fields are defined in the protocol definition.
|
||||
This means that bproto message structure is fixed and can be decoded without any additional delimiters or markers.
|
||||
|
||||
The fields are just concatenated together in the order of their field ID.
|
||||
Field IDs are declared in the protocol definition and are declaration order independent (see [Syntax of Fields](/docs/syntax.md#fields)).
|
||||
|
||||
All numeric data types are serialized in big-endian byte order.
|
||||
Data types are serialized as follows:
|
||||
- For floating point numbers, the IEEE 754 standard is used.
|
||||
- Signed integers are serialized as two's complement.
|
||||
- Unsigned integers are serialized as is.
|
||||
- Char and string are serialized as ASCII characters.
|
||||
- Bool is serialized as 0 for false and 1 for true.
|
||||
- Enums are serialized as 8-bit unsigned integers.
|
||||
- Bitfields are serialized as a single (or multiple) byte with each bit representing a boolean value. The order of the bits is from the least significant bit to the most significant bit and from the first byte to the last byte.
|
||||
|
||||
Datatype sizes are fixed and are documented in the datatypes-table in the [Datatypes](/docs/syntax.md#datatypes) section.
|
||||
compatiblity
|
||||
For arrays, the elements are serialized in order, with the first element first, the second element second, and so on.
|
||||
|
||||
### Example Message Encoding
|
||||
|
||||
An example of a message and serialization with 3 fields:
|
||||
```
|
||||
message [1] ExampleMessage {
|
||||
[0] a : uint8,
|
||||
[1] b : uint16,
|
||||
[2] c : bool
|
||||
}
|
||||
```
|
||||
with the values a=42, b=12345, c=true would be serialized as: `012a390501`
|
||||
```
|
||||
[Message ID] [Field a] [Field b] [Field c] [CRC16]
|
||||
[ 01 ] [ 2a ] [ 3905 ] [ 01 ] [31 06]
|
||||
```
|
||||
|
||||
### CRC-16 Calculation
|
||||
|
||||
The CRC-16 has the purpose of verifying the integrity of the message and preventing incompatible messages/protocol versions from being processed.
|
||||
|
||||
When performing the CRC-16 calculation, the CRC-16 is initialized by first prepending a SHA-256 hash of a text form of the protocol definition to the message data.
|
||||
This text hash is a fingerprint of the protocol definition and is used to detect changes in the protocol definition. See [Compatibility Validation Process in Detail](/docs/compatibility.md#compatibility-validation-process-in-detail)
|
||||
|
||||
Then CRC-16 is calculated over the message data and compared to the last 2 bytes of the message.
|
||||
If the CRC-16 is not correct, the message should be discarded.
|
||||
|
||||
221
docs/syntax.md
Normal file
221
docs/syntax.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# Syntax Documentation
|
||||
|
||||
bproto uses a custom syntax `bproto` (file extension `.bproto`).
|
||||
Each protocol is described in a single bproto file.
|
||||
|
||||
For Beginners, a simple example of a protocol definition and usage can be found [here](/docs/quickstart.md).
|
||||
|
||||
The protocol definitions consist of the following basic components: **messages**, **enums**, **bitfields**, and the **protocol tag line**.
|
||||
|
||||
## Table of Contents
|
||||
- [Syntax Documentation](#syntax-documentation)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Names and IDs](#names-and-ids)
|
||||
- [Protocol Tag Line](#protocol-tag-line)
|
||||
- [Message](#message)
|
||||
- [Fields](#fields)
|
||||
- [Datatypes](#datatypes)
|
||||
- [Bitfields](#bitfields)
|
||||
- [Enumerations](#enumerations)
|
||||
- [Comments](#comments)
|
||||
|
||||
## Names and IDs
|
||||
Names of enums, bitfields, messages, and fields support any letter (lower and uppercase), numbers, and underscores.
|
||||
|
||||
Regular expression: `[a-zA-Z0-9_]+`
|
||||
|
||||
Numbers/IDs are decimal encoded numbers. Trailing zeros are ignored.
|
||||
|
||||
Regular expression: `[0-9]+`
|
||||
|
||||
## Protocol Tag Line
|
||||
This is a short description of the protocol consisting of a name and a version number.
|
||||
This must be the first line of any bproto definition file.
|
||||
|
||||
**Syntax:**
|
||||
```bproto
|
||||
protocol <name> version <version>
|
||||
```
|
||||
**Example:**
|
||||
```bproto
|
||||
protocol myCustomAirplaneProtocol version 2
|
||||
```
|
||||
|
||||
The name should identify the protocol in the implementation context (like `robotControlProtocol` or `weatherStationTelemetryDataStream`).
|
||||
The version number is used to differentiate between different iterations of the protocol. Different versions of the same protocol are incompatible.
|
||||
|
||||
For detailed information on naming conventions and versioning strategies, please refer to the [compatibility documentation](/docs/compatibility.md#naming-conventions).
|
||||
|
||||
## Message
|
||||
|
||||
A message is a collection of fields that can be serialized and deserialized to be sent over the wire or channel.
|
||||
A message should only be a transport container and should not, if possible, be stored permanently.
|
||||
|
||||
**Syntax:**
|
||||
```bproto
|
||||
message [<Message ID>] <Message Name> {
|
||||
// List of fields
|
||||
[<field ID #0>] <field name #0> : <field type #0>,
|
||||
[<field ID #1>] <field name #1> : <field type #1>,
|
||||
....
|
||||
[<field ID #N>] <field name #N> : <field type #N>,
|
||||
}
|
||||
```
|
||||
**Example:**
|
||||
```bproto
|
||||
message [1] EngineSensorData {
|
||||
[0] engine_thrust_left : float32,
|
||||
[1] engine_thrust_right : float32,
|
||||
[2] fuel_tanks_fill : uint32[8], // Fill levels of 8 fuel tanks
|
||||
[3] engine_fault_left : bool,
|
||||
[4] engine_fault_right : bool
|
||||
}
|
||||
```
|
||||
|
||||
- **Message ID:** Unique identifier within a protocol definition used to differentiate different messages from each other. The Message ID should be between 0 and 255.
|
||||
- **Message Name:** Unique name given to this message. This will be used to identify a field in the protocol implementation.
|
||||
|
||||
### Fields
|
||||
A message can have as many fields as necessary. These are comparable to members in a class or fields in a C-struct.
|
||||
A field has an `ID`, `Name`, and a `datatype`.
|
||||
|
||||
**Syntax:**
|
||||
`[<ID>] <Name> : <datatype>`
|
||||
**Example:**
|
||||
``[2] fanSpeed : float32``
|
||||
|
||||
- **Name:** This is a unique name given to the field. This will be used to identify a field in the protocol implementation.
|
||||
- **ID:** A unique identifier used to order the fields in the encoding/decoding. More about serialization can be found [here](/docs/serialization.md#message-field-data-encoding).
|
||||
- **datatype:** What type of data to store in this field.
|
||||
|
||||
### Datatypes
|
||||
Datatypes in bproto are independent of language and are translated into the target language-specific type.
|
||||
Every Field in a message must have a datatype.
|
||||
|
||||
Here is a list of datatypes available:
|
||||
|
||||
| bproto Datatype | Encode Size (bytes) | Python | C | Value Range | Description |
|
||||
| --------------- | ------------------- | ------- | ---------- | ------------------------------------------------------- | ---------------------------- |
|
||||
| `uint8` | 1 | `int` | `uint8_t` | 0 to 255 | Unsigned 8-bit integer |
|
||||
| `uint16` | 2 | `int` | `uint16_t` | 0 to 65,535 | Unsigned 16-bit integer |
|
||||
| `uint32` | 4 | `int` | `uint32_t` | 0 to 4,294,967,295 | Unsigned 32-bit integer |
|
||||
| `int64` | 8 | `int` | `uint64_t` | 0 to 18,446,744,073,709,551,615 | Unsigned 64-bit integer |
|
||||
| `int8` | 1 | `int` | `int8_t` | -128 to 127 | Signed 8-bit integer |
|
||||
| `int16` | 2 | `int` | `int16_t` | -32,768 to 32,767 | Signed 16-bit integer |
|
||||
| `int32` | 4 | `int` | `int32_t` | -2,147,483,648 to 2,147,483,647 | Signed 32-bit integer |
|
||||
| `int64` | 8 | `int` | `int64_t` | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | Signed 64-bit integer |
|
||||
| `float32` | 4 | `float` | `float` | 1.2E-38 to 3.4E+38 | 32-bit floating point number |
|
||||
| `float64` | 8 | `float` | `double` | 2.3E-308 to 1.7E+308 | 64-bit floating point number |
|
||||
| `string[<N>]` | N | `str` | `char[]` | N/A | Fixed-size string |
|
||||
| `char` | 1 | `str` | `char` | Single character | Single character |
|
||||
| `bool` | 1 | `bool` | `bool` | true or false | Boolean value |
|
||||
|
||||
Every datatype in this list can be used in a fixed-size array by using this format:
|
||||
|
||||
**Syntax:**
|
||||
`<datatype>[<array_size>]`
|
||||
**Example:**
|
||||
`float[]`
|
||||
|
||||
A special case is `string`.
|
||||
Strings _must_ be declared as an array because their size must be known from the start.
|
||||
|
||||
There are also `bits` and `enum` corresponding to **bitfields** and **enums**.
|
||||
They are a bit special and are not able to be used as an array.
|
||||
Bitfields and enums can be declared inline.
|
||||
More details can be found in the sections about [enums](#enumerations) and [bitfields](#bitfields).
|
||||
|
||||
How different datatypes are encoded can be found [here](/docs/serialization.md#message-field-data-encoding).
|
||||
|
||||
## Bitfields
|
||||
A compact representation of multiple boolean flags within a single byte or group of bytes, optimizing space for binary communication.
|
||||
Bitfields are treated as datatypes.
|
||||
|
||||
**Syntax:**
|
||||
```bproto
|
||||
bits <bitfield name> {
|
||||
<1st bit>, <2nd bit>, ...
|
||||
}
|
||||
```
|
||||
**Example:**
|
||||
```bproto
|
||||
bits engine_enables {
|
||||
engine_left, engine_right
|
||||
}
|
||||
```
|
||||
|
||||
Each bit can be either 0 or 1 (true or false), represented as a bool in the target language.
|
||||
More details on how Bitfields are encoded in the corresponding language can be found [here](/docs/datatype_language_details.md#bitfields).
|
||||
|
||||
Bitfields, like Enumerations, can be declared inline in a [message](#message).
|
||||
|
||||
**Syntax:**
|
||||
```bproto
|
||||
[<Field ID>] <Field Name> : bits {
|
||||
<1st bit>, <2nd bit>, ...
|
||||
}
|
||||
```
|
||||
**Example:**
|
||||
```bproto
|
||||
[1] lightsEnable: bits {
|
||||
Front, Spot, Key, BackLight
|
||||
}
|
||||
```
|
||||
|
||||
Bitfield naming when using them inline is found [here](/docs/datatype_language_details.md#inline).
|
||||
|
||||
## Enumerations
|
||||
A set of named integer values that represent discrete states or options.
|
||||
Enums enhance code clarity by using descriptive names instead of raw numeric values.
|
||||
Enumerations or Enums in bproto are comparable to C or Python enums.
|
||||
More details on how Enums are encoded in the corresponding language can be found [here](/docs/datatype_language_details.md#enums).
|
||||
Enums are treated as datatypes in bproto.
|
||||
|
||||
Each Enum item represents a positive (or zero) numeric value.
|
||||
These can be implicitly generated by bproto, incrementing by 1.
|
||||
The value of the Enum value therefore depends on the declaration order.
|
||||
This may lead to bugs and issues when updating one client/receiver's protocol but not the counterparts.
|
||||
For more info about this problem, see [here](/docs/serialization.md#message-field-data-encoding).
|
||||
|
||||
Enum values can also be set explicitly, which eliminates this problem.
|
||||
|
||||
**Syntax:**
|
||||
```bproto
|
||||
enum <enum name> {
|
||||
<Name #0> (= <Value #0>),
|
||||
<Name #1> (= <Value #1>),
|
||||
...
|
||||
<Name #N> (= <Value #N>),
|
||||
}
|
||||
```
|
||||
**Example:**
|
||||
```bproto
|
||||
dangerLvl: enum {
|
||||
WARNING,
|
||||
MELTDOWN,
|
||||
ERROR = 10,
|
||||
}
|
||||
```
|
||||
|
||||
**Syntax:**
|
||||
```bproto
|
||||
[<Field ID>] <Field Name> : enum {
|
||||
<Name #0> (= <Value #0>),
|
||||
<Name #1> (= <Value #1>),
|
||||
...
|
||||
<Name #N> (= <Value #N>),
|
||||
}
|
||||
```
|
||||
**Example:**
|
||||
```bproto
|
||||
[6] dangerLvl: enum {
|
||||
WARNING,
|
||||
MELTDOWN = 5,
|
||||
ERROR,
|
||||
}
|
||||
```
|
||||
|
||||
Enumeration naming when using them inline is found [here](/docs/datatype_language_details.md#inline).
|
||||
|
||||
## Comments
|
||||
bproto supports C-style comments (with `// ...` and `/* ... */`).
|
||||
4
pytest.ini
Normal file
4
pytest.ini
Normal file
@@ -0,0 +1,4 @@
|
||||
[pytest]
|
||||
testpaths =
|
||||
test/output
|
||||
test/compiler
|
||||
11
requirements.txt
Normal file
11
requirements.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
antlr4-python3-runtime==4.13.2
|
||||
coverage==7.6.12
|
||||
exceptiongroup==1.2.2
|
||||
iniconfig==2.0.0
|
||||
Jinja2==3.1.5
|
||||
MarkupSafe==3.0.2
|
||||
packaging==24.2
|
||||
pluggy==1.5.0
|
||||
pytest==8.3.4
|
||||
pytest-cov==6.0.0
|
||||
tomli==2.2.1
|
||||
50
run_docker_compiler.py
Executable file
50
run_docker_compiler.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import argparse
|
||||
|
||||
cli_args_parser = argparse.ArgumentParser(description="Bproto Compiler")
|
||||
|
||||
cli_args_parser.add_argument(
|
||||
"-i", "--input",
|
||||
help="Input file to compile",
|
||||
required=True
|
||||
)
|
||||
cli_args_parser.add_argument(
|
||||
"-o", "--output",
|
||||
help="Output file to write compiled code to",
|
||||
required=True
|
||||
)
|
||||
cli_args_parser.add_argument(
|
||||
"-t", "--target",
|
||||
help="Target language to compile to",
|
||||
required=True,
|
||||
choices=["python", "c", "txt"],
|
||||
action="append"
|
||||
)
|
||||
|
||||
args = cli_args_parser.parse_args()
|
||||
|
||||
targets = " ".join([
|
||||
f"--target \"{i}\""
|
||||
for i in args.target
|
||||
])
|
||||
|
||||
input_basename = os.path.basename(args.input)
|
||||
input_path = os.path.abspath(args.input)
|
||||
output_path = os.path.abspath(args.output)
|
||||
|
||||
if not os.path.isfile(input_path):
|
||||
print(f"{input_path} is not a file")
|
||||
exit(-1)
|
||||
|
||||
if not os.path.isdir(output_path):
|
||||
print(f"{output_path} is not folder")
|
||||
exit(-1)
|
||||
|
||||
c = "docker pull alexanderhd27/bproto-compiler:latest"
|
||||
print(f"RUN: {c}")
|
||||
os.system(c)
|
||||
|
||||
c = f"docker run -v {output_path}:/compiler/data -v {input_path}:/compiler/{input_basename} --rm alexanderhd27/bproto-compiler:latest /compiler/src/main.py --output /compiler/data --input /compiler/{input_basename} {targets}"
|
||||
print(f"RUN: {c}")
|
||||
os.system(c)
|
||||
3
scripts/docker_and_push.bash
Executable file
3
scripts/docker_and_push.bash
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/bash
|
||||
docker build . --tag alexanderhd27/bproto-compiler:latest
|
||||
docker push alexanderhd27/bproto-compiler:latest
|
||||
15
src/backend/__init__.py
Normal file
15
src/backend/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from jinja2 import Environment
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
class BackendRenderer(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, output_folder: str):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def render(self, protocol_definition: ProtocolDefinitions, jinja_env: Environment) -> BackendFileSystemOutput:
|
||||
raise NotImplementedError()
|
||||
237
src/backend/fsOutput.py
Normal file
237
src/backend/fsOutput.py
Normal file
@@ -0,0 +1,237 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Literal
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import jinja2
|
||||
|
||||
FILE_TREE_STRING_POSITION_t = Literal["last", "first", "mid"]
|
||||
|
||||
|
||||
def map_link_symbol(position: FILE_TREE_STRING_POSITION_t) -> str:
|
||||
"""For displaying the file tree in the console, this function maps the position of the file in the tree to the correct link symbol.
|
||||
|
||||
Args:
|
||||
position (FILE_TREE_STRING_POSITION_t): The position of the file in the tree.
|
||||
|
||||
Returns:
|
||||
str: The correct link symbol for the position.
|
||||
"""
|
||||
match position:
|
||||
case "last":
|
||||
return "└"
|
||||
case "first":
|
||||
return "├"
|
||||
case "mid":
|
||||
return "├"
|
||||
case _: # This should not happend
|
||||
return "├"
|
||||
|
||||
|
||||
class BackendFileSystemOutput(ABC):
|
||||
"""Represents a node in the filesystem output tree.
|
||||
|
||||
This class is abstract and should be inherited by classes that represent files or folders in the filesystem output tree.
|
||||
"""
|
||||
INDENT_FILE_TREE_STRING = " " * 4
|
||||
|
||||
@abstractmethod
|
||||
def saveToDisk(self, parent_path: str):
|
||||
"""Saves the file or folder to the disk.
|
||||
Calles their potential subfiles and subfolders to save themselves.
|
||||
|
||||
Args:
|
||||
parent_path (str): Where to save the file or folder.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def assemble_file_tree_string(self, indent: str = "", position: FILE_TREE_STRING_POSITION_t = "last") -> str:
|
||||
"""Assembles a string representation of the file tree.
|
||||
This is ment to display to the use what the output will look like.
|
||||
|
||||
Args:
|
||||
layer (int, optional): Indentation layer. Defaults to "". Should be left untouched for the intial call.
|
||||
position (FILE_TREE_STRING_POSITION_t, optional): For multiple children nodes, what position in the parent node. Defaults to "first". Should be left untouched for the intial call.
|
||||
|
||||
Returns:
|
||||
str: The string representation of the file tree (multiline).
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def toString(self) -> dict[str, str]:
|
||||
"""Renders out FilesystemOutput as a dictionary of strings.
|
||||
Each key is a filename and the value is the content of the file.
|
||||
Folders are include in the filename list with a trailing slash, but not as a separate key.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: _description_
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BackendFSOutFile(BackendFileSystemOutput):
|
||||
"""Represents a file with string conntent in the filesystem output tree.
|
||||
|
||||
Inherited from BackendFileSystemOutput.
|
||||
"""
|
||||
def __init__(self, filename: str, conntent: str):
|
||||
"""Initializes the file with conntent and a filename.
|
||||
|
||||
Args:
|
||||
conntent (str): File conntent as string.
|
||||
filename (str): Filename.
|
||||
"""
|
||||
self.conntent = conntent
|
||||
self.filename = filename
|
||||
|
||||
def saveToDisk(self, parent_path: str):
|
||||
p = os.path.normpath(os.path.join(parent_path, self.filename))
|
||||
with open(p, "w") as f:
|
||||
f.write(self.conntent)
|
||||
|
||||
def toString(self) -> dict[str, str]:
|
||||
return {self.filename: self.conntent}
|
||||
|
||||
def assemble_file_tree_string(self, indent: str = "", position: FILE_TREE_STRING_POSITION_t = "last") -> str:
|
||||
return indent + f"{map_link_symbol(position)}── [{self.filename}]\n"
|
||||
|
||||
|
||||
class BackendFSOutFolder(BackendFileSystemOutput):
|
||||
"""Represents a folder in the filesystem output tree.
|
||||
This Node has multiple subnodes, can be files or folders.
|
||||
|
||||
Inherited from BackendFileSystemOutput.
|
||||
"""
|
||||
def __init__(self, name: str, sub_content: list[BackendFileSystemOutput] = []):
|
||||
"""Initializes the folder with a name and subnodes.
|
||||
|
||||
Args:
|
||||
name (str): Name of the folder.
|
||||
sub_content (list[BackendFileSystemOutput]): Subnodes of the folder.
|
||||
"""
|
||||
self.name = name
|
||||
self.sub_content: list[BackendFileSystemOutput] = sub_content
|
||||
|
||||
def saveToDisk(self, parent_path: str):
|
||||
p = os.path.normpath(os.path.join(parent_path, self.name))
|
||||
# This cause an **incident**
|
||||
# if os.path.exists(p):
|
||||
# shutil.rmtree(p)
|
||||
|
||||
os.makedirs(p, exist_ok=True)
|
||||
|
||||
for sub in self.sub_content:
|
||||
sub.saveToDisk(p)
|
||||
|
||||
def toString(self) -> dict[str, str]:
|
||||
res = {}
|
||||
|
||||
for sub in self.sub_content:
|
||||
res.update({
|
||||
os.path.normpath(os.path.join(self.name, k)): v
|
||||
for k, v in sub.toString().items()
|
||||
})
|
||||
return res
|
||||
|
||||
def assemble_file_tree_string(self, indent: str = "", position: FILE_TREE_STRING_POSITION_t = "last") -> str:
|
||||
if position == "last":
|
||||
indent_string = indent + BackendFileSystemOutput.INDENT_FILE_TREE_STRING
|
||||
else:
|
||||
indent_string = indent + ("│" + BackendFileSystemOutput.INDENT_FILE_TREE_STRING[1:])
|
||||
|
||||
s = indent + f"{map_link_symbol(position)}── {self.name}\n"
|
||||
for i, sub in enumerate(self.sub_content):
|
||||
if i == len(self.sub_content) - 1:
|
||||
s += sub.assemble_file_tree_string(indent_string, "last")
|
||||
else:
|
||||
s += sub.assemble_file_tree_string(indent_string, "mid")
|
||||
return s
|
||||
|
||||
|
||||
class BackendFSOutStaticConent(BackendFileSystemOutput):
|
||||
"""Represents a static file or folder in the filesystem output tree.
|
||||
|
||||
The content is copied from the source to the output.
|
||||
|
||||
Inherited from BackendFileSystemOutput.
|
||||
"""
|
||||
def __init__(self, src_path: str, dest_path: str | None = None):
|
||||
"""Initializes the static content with a source path and a destination path.
|
||||
|
||||
If dest_path is None, the top level folder (basename) of the source path is used.
|
||||
|
||||
Args:
|
||||
src_path (str): _description_
|
||||
dest_path (str | None, optional): _description_. Defaults to None.
|
||||
"""
|
||||
self.src_path = src_path
|
||||
self.dest_path = dest_path or os.path.basename(src_path)
|
||||
|
||||
def saveToDisk(self, parent_path: str):
|
||||
dest = os.path.normpath(os.path.join(parent_path, self.dest_path))
|
||||
|
||||
# if os.path.exists(dest):
|
||||
# shutil.rmtree(dest)
|
||||
|
||||
shutil.copy(self.src_path, dest)
|
||||
|
||||
def toString(self) -> dict[str, str]:
|
||||
res = {}
|
||||
if os.path.isfile(self.src_path):
|
||||
with open(self.src_path, "r") as f:
|
||||
res.update({
|
||||
os.path.normpath(os.path.join(self.dest_path, os.path.basename(self.src_path))): f.read()
|
||||
})
|
||||
else:
|
||||
for root, _, files in os.walk(self.src_path):
|
||||
for file in files:
|
||||
with open(os.path.normpath(os.path.join(root, file)), "r") as f:
|
||||
res.update({
|
||||
os.path.normpath(os.path.join(self.dest_path, file)): f.read()
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
def assemble_file_tree_string(self, indent: str = "", position: FILE_TREE_STRING_POSITION_t = "last") -> str:
|
||||
return indent + f"{map_link_symbol(position)}── Static [{self.dest_path}]\n"
|
||||
|
||||
|
||||
class BackendFSOutputJinjaFile(BackendFSOutFile):
|
||||
"""Represents a file that is rendered with jinja2.
|
||||
|
||||
Inherited from BackendFSOutFile.
|
||||
"""
|
||||
def __init__(self, jinja_env: jinja2.Environment, template_name: str, output_name: str | None, context: dict):
|
||||
"""Initializes the file with a jinja2 environment, a template name, an output name and a context.
|
||||
|
||||
Args:
|
||||
jinja_env (jinja2.Environment): Jinja2 environment to render the template.
|
||||
template_name (str): Jinja2 template name.
|
||||
output_name (str): Filename of the output; If left None, template_name is used and the .jinja2 extension is removed.
|
||||
context (dict): Context for the template, needed for rendering.
|
||||
"""
|
||||
self.jinja_env = jinja_env
|
||||
self.template_name = template_name
|
||||
self.output_name = output_name
|
||||
self.context = context
|
||||
|
||||
if self.output_name is None:
|
||||
self.output_name = re.sub(r"(?i)\b\.jinja\d?\b", "", self.template_name)
|
||||
|
||||
def saveToDisk(self, parent_path: str):
|
||||
template = self.jinja_env.get_template(self.template_name)
|
||||
output = template.render(self.context)
|
||||
|
||||
p = os.path.normpath(os.path.join(parent_path, self.output_name))
|
||||
with open(p, "w") as f:
|
||||
f.write(output)
|
||||
|
||||
def toString(self) -> dict[str, str]:
|
||||
template = self.jinja_env.get_template(self.template_name)
|
||||
output = template.render(self.context)
|
||||
return {self.output_name: output}
|
||||
|
||||
def assemble_file_tree_string(self, indent: str = "", position: FILE_TREE_STRING_POSITION_t = "last") -> str:
|
||||
return indent + f"{map_link_symbol(position)}── Template [{self.output_name}]\n"
|
||||
270
src/backend/rendering/cBackend.py
Normal file
270
src/backend/rendering/cBackend.py
Normal file
@@ -0,0 +1,270 @@
|
||||
from backend import BackendRenderer
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
from protocol_components.bitfields import Bitfield
|
||||
from protocol_components.field import Field, FieldBitfield, FieldEnum
|
||||
from protocol_components.crc import CRC_SIZE
|
||||
|
||||
from backend.fsOutput import BackendFileSystemOutput, BackendFSOutputJinjaFile, BackendFSOutFolder, BackendFSOutStaticConent
|
||||
from nameHandling.style.cNameStyle import NameStyleC
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
from jinja2 import Environment
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
C_DATA_TYPE_MAP = {
|
||||
BprotoFieldBaseType.BOOL: ("bool", False),
|
||||
|
||||
BprotoFieldBaseType.UINT8: ("uint8_t", 0),
|
||||
BprotoFieldBaseType.UINT16: ("uint16_t", 0),
|
||||
BprotoFieldBaseType.UINT32: ("uint32_t", 0),
|
||||
BprotoFieldBaseType.UINT64: ("uint64_t", 0),
|
||||
|
||||
BprotoFieldBaseType.INT8: ("int8_t", 0),
|
||||
BprotoFieldBaseType.INT16: ("int16_t", 0),
|
||||
BprotoFieldBaseType.INT32: ("int32_t", 0),
|
||||
BprotoFieldBaseType.INT64: ("int64_t", 0),
|
||||
|
||||
BprotoFieldBaseType.FLOAT32: ("float", 0.0),
|
||||
BprotoFieldBaseType.FLOAT64: ("double", 0.0),
|
||||
|
||||
BprotoFieldBaseType.CHAR: ("char", "''"),
|
||||
BprotoFieldBaseType.STRING: ("char", "\"\""),
|
||||
|
||||
BprotoFieldBaseType.BITFIELD: ("", ""),
|
||||
BprotoFieldBaseType.ENUM: ("", ""),
|
||||
}
|
||||
|
||||
FOR_LOOP_THRESHOLD = 8
|
||||
|
||||
|
||||
def map_data_type(field: Field) -> str:
|
||||
if isinstance(field, FieldBitfield):
|
||||
return {
|
||||
"name": NameStyleC.toStr(field.bitfield.name, "bitfield_name"),
|
||||
"aux": None,
|
||||
"array": ""
|
||||
}
|
||||
|
||||
elif isinstance(field, FieldEnum):
|
||||
return {
|
||||
"name": NameStyleC.toStr(field.enum.name, "enum_name"),
|
||||
"aux": C_DATA_TYPE_MAP[field.enum.map_auxiliary_datatypes()][0],
|
||||
"array": ""
|
||||
}
|
||||
|
||||
elif field.type in BprotoFieldBaseType:
|
||||
if field.array_size > 1 or field.type == BprotoFieldBaseType.STRING:
|
||||
return {
|
||||
"name": C_DATA_TYPE_MAP[field.type][0],
|
||||
"aux": None,
|
||||
"array": f"[{field.array_size}]"
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"name": C_DATA_TYPE_MAP[field.type][0],
|
||||
"aux": None,
|
||||
"array": ""
|
||||
}
|
||||
|
||||
else:
|
||||
raise ValueError(f"Type {field.type} is not implement in map_Data_type()")
|
||||
|
||||
|
||||
def convert_bitfield_to_c_style_bit_list(bitfield: Bitfield) -> list[tuple[str, int]]:
|
||||
last_pos = 0
|
||||
|
||||
res = []
|
||||
|
||||
for bit_name, pos in bitfield.bits.items():
|
||||
if pos - (last_pos + 1) > 1:
|
||||
# We skiped bits
|
||||
res.append({
|
||||
"name": "",
|
||||
"length": pos - (last_pos + 1) - 1,
|
||||
"spacer": True,
|
||||
})
|
||||
last_pos = pos
|
||||
|
||||
res.append({
|
||||
"name": NameStyleC.toStr(bit_name, "bitfield_member"),
|
||||
"length": 1,
|
||||
"spacer": False,
|
||||
"bit_pos": pos % 8,
|
||||
"byte_pos": pos // 8
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def generate_max_message_struct_expr(p: ProtocolDefinitions) -> str:
|
||||
res = "{}"
|
||||
|
||||
for i in p.messages.values():
|
||||
res = res.format("MAX({}, sizeof(" + NameStyleC.toStr(i.name, "struct_name") + "))")
|
||||
return res.format(0)
|
||||
|
||||
|
||||
def convert_field_to_toBytes_overwrite(field: Field):
|
||||
if isinstance(field, FieldBitfield):
|
||||
return f"toBytes_{NameStyleC.toStr(field.bitfield.name, 'bitfield_name')}(data, &msg->{NameStyleC.toStr(field.name, 'struct_member')})"
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def convert_field_to_fromBytes_overwrite(field: Field):
|
||||
if isinstance(field, FieldBitfield):
|
||||
return f"fromBytes_{NameStyleC.toStr(field.bitfield.name, 'bitfield_name')}(data, &msg->{NameStyleC.toStr(field.name, 'struct_member')})"
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class CBackendRenderer(BackendRenderer):
|
||||
|
||||
def __init__(self, output_folder):
|
||||
self.output_folder = output_folder
|
||||
|
||||
self.protocol: ProtocolDefinitions = None
|
||||
self.jinja_env: Environment = None
|
||||
self.jinja_context: dict = None
|
||||
|
||||
def render(self,
|
||||
protocol_definition: ProtocolDefinitions,
|
||||
jinja_env: Environment) -> BackendFileSystemOutput:
|
||||
|
||||
if protocol_definition.protocol_hash_inital is None:
|
||||
raise ValueError("Protocol hash inital for crc16 should no be None")
|
||||
|
||||
self.protocol = NameStyleC.preprocess(deepcopy(protocol_definition))
|
||||
|
||||
self.jinja_env = jinja_env
|
||||
self.jinja_context = {
|
||||
"protocol": {
|
||||
"name": NameStyleC.toStr(self.protocol.name, "enum_item"),
|
||||
"version": self.protocol.version,
|
||||
"crc_size": CRC_SIZE,
|
||||
"crc_initial": hex(self.protocol.protocol_hash_inital),
|
||||
"max_struct_size_expr": generate_max_message_struct_expr(self.protocol),
|
||||
"max_data_size": max([i.get_size_bytes() for i in self.protocol.messages.values()]) + 1 + CRC_SIZE
|
||||
},
|
||||
"import_name": NameStyleC.toStr(self.protocol.name, 'enum_item'),
|
||||
"enums": [
|
||||
{
|
||||
"name": NameStyleC.toStr(name, "enum_name"),
|
||||
"consts": [
|
||||
(NameStyleC.toStr(k, "enum_item"), v)
|
||||
for k, v in e.values.items()
|
||||
]
|
||||
}
|
||||
for name, e in self.protocol.enums.items()
|
||||
],
|
||||
"bitfields": [
|
||||
{
|
||||
"name": NameStyleC.toStr(b.name, "bitfield_name"),
|
||||
"size": b.get_size_bytes(),
|
||||
"size_name": NameStyleC.toStr(b.name, "enum_item").upper() + "_SIZE",
|
||||
"bits": convert_bitfield_to_c_style_bit_list(b),
|
||||
}
|
||||
for name, b in self.protocol.bitfields.items()
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"name": NameStyleC.toStr(name, "struct_name"),
|
||||
"id": m.get_identifier(),
|
||||
"id_name": NameStyleC.toStr(name, "enum_item") + "_ID",
|
||||
"size": m.get_size_bytes() + 1 + CRC_SIZE,
|
||||
"size_name": NameStyleC.toStr(name, "enum_item") + "_SIZE",
|
||||
"fields": [
|
||||
{
|
||||
"name": NameStyleC.toStr(k, "struct_member"),
|
||||
"size": v.get_size_bytes(),
|
||||
"base_size": v.get_base_size_bytes(),
|
||||
"array_size": v.array_size,
|
||||
"decompose_mode": (
|
||||
"for"
|
||||
if v.array_size > FOR_LOOP_THRESHOLD or v.type == BprotoFieldBaseType.STRING
|
||||
else (
|
||||
"inline_for" if v.array_size > 1 else "normal"
|
||||
)
|
||||
),
|
||||
"type": map_data_type(v),
|
||||
"pre_init_value": 0 if isinstance(v, FieldEnum) else None,
|
||||
"toBytes_overwride": convert_field_to_toBytes_overwrite(v),
|
||||
"fromBytes_overwride": convert_field_to_fromBytes_overwrite(v)
|
||||
}
|
||||
for k, v in m.fields.items()
|
||||
]
|
||||
}
|
||||
for name, m, in self.protocol.messages.items()
|
||||
]
|
||||
}
|
||||
|
||||
return BackendFSOutFolder(self.output_folder, [
|
||||
BackendFSOutputJinjaFile( # CMake
|
||||
self.jinja_env,
|
||||
"c/template/bproto.cmake.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}.cmake",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutFolder("include", [
|
||||
BackendFSOutputJinjaFile( # Enum
|
||||
self.jinja_env,
|
||||
"c/template/include/enums.h.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_enum.h",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Message
|
||||
self.jinja_env,
|
||||
"c/template/include/message/message.h.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_message.h",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Bitfields
|
||||
self.jinja_env,
|
||||
"c/template/include/bitfield/bitfield.h.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_bitfield.h",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Bitfields
|
||||
self.jinja_env,
|
||||
"c/template/include/crc.h.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_crc.h",
|
||||
self.jinja_context
|
||||
),
|
||||
]),
|
||||
BackendFSOutFolder("src", [
|
||||
BackendFSOutputJinjaFile( # Bitfields
|
||||
self.jinja_env,
|
||||
"c/template/src/bitfield.c.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_bitfield.c",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Message fromBytes
|
||||
self.jinja_env,
|
||||
"c/template/src/message/fromBytes.c.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_message_fromBytes.c",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Message toBytes
|
||||
self.jinja_env,
|
||||
"c/template/src/message/toBytes.c.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_message_toBytes.c",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # CRC
|
||||
self.jinja_env,
|
||||
"c/template/src/crc.c.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_crc.c",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Map Message Size/ID
|
||||
self.jinja_env,
|
||||
"c/template/src/message/mapping.c.jinja2",
|
||||
f"{NameStyleC.toStr(self.protocol.name, 'enum_item')}_mapping.c",
|
||||
self.jinja_context
|
||||
),
|
||||
]),
|
||||
])
|
||||
244
src/backend/rendering/pythonBackend.py
Normal file
244
src/backend/rendering/pythonBackend.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from backend import BackendRenderer
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
from protocol_components.field import Field, FieldBitfield, FieldEnum
|
||||
from protocol_components.crc import CRC_SIZE
|
||||
|
||||
from backend.fsOutput import BackendFileSystemOutput, BackendFSOutputJinjaFile, BackendFSOutFolder, BackendFSOutStaticConent
|
||||
from nameHandling.style.pythonNameStyle import NameStylePython
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
from jinja2 import Environment
|
||||
from copy import deepcopy
|
||||
|
||||
ENDIANDNESS = "<"
|
||||
|
||||
PYTHON_DATA_TYPE_MAP = {
|
||||
BprotoFieldBaseType.BOOL: ("bool", False),
|
||||
|
||||
BprotoFieldBaseType.UINT8: ("int", 0),
|
||||
BprotoFieldBaseType.UINT16: ("int", 0),
|
||||
BprotoFieldBaseType.UINT32: ("int", 0),
|
||||
BprotoFieldBaseType.UINT64: ("int", 0),
|
||||
|
||||
BprotoFieldBaseType.INT8: ("int", 0),
|
||||
BprotoFieldBaseType.INT16: ("int", 0),
|
||||
BprotoFieldBaseType.INT32: ("int", 0),
|
||||
BprotoFieldBaseType.INT64: ("int", 0),
|
||||
|
||||
BprotoFieldBaseType.FLOAT32: ("float", 0.0),
|
||||
BprotoFieldBaseType.FLOAT64: ("float", 0.0),
|
||||
|
||||
BprotoFieldBaseType.CHAR: ("str", "''"),
|
||||
BprotoFieldBaseType.STRING: ("str", "''"),
|
||||
|
||||
BprotoFieldBaseType.BITFIELD: ("", ""),
|
||||
BprotoFieldBaseType.ENUM: ("", ""),
|
||||
}
|
||||
|
||||
PYTHON_DATA_TYPE_STRUCT_FORMAT = {
|
||||
BprotoFieldBaseType.BOOL: "B",
|
||||
|
||||
BprotoFieldBaseType.UINT8: "B",
|
||||
BprotoFieldBaseType.UINT16: "H",
|
||||
BprotoFieldBaseType.UINT32: "I",
|
||||
BprotoFieldBaseType.UINT64: "Q",
|
||||
|
||||
BprotoFieldBaseType.INT8: "b",
|
||||
BprotoFieldBaseType.INT16: "h",
|
||||
BprotoFieldBaseType.INT32: "i",
|
||||
BprotoFieldBaseType.INT64: "q",
|
||||
|
||||
BprotoFieldBaseType.FLOAT32: "f",
|
||||
BprotoFieldBaseType.FLOAT64: "d",
|
||||
}
|
||||
|
||||
|
||||
def map_to_bytes_conversion(field: Field, endiend: str) -> str:
|
||||
name = NameStylePython.toStr(field.name, "class_member")
|
||||
|
||||
if isinstance(field, FieldEnum):
|
||||
return f"struct.pack('{endiend}B', self.{name}.value)"
|
||||
|
||||
elif isinstance(field, FieldBitfield):
|
||||
return f"self.{name}.to_bytes()"
|
||||
|
||||
elif field.type in PYTHON_DATA_TYPE_STRUCT_FORMAT:
|
||||
if field.array_size > 1:
|
||||
argument = ", ".join([
|
||||
f"self.{name}[{i}]"
|
||||
for i in range(field.array_size)
|
||||
])
|
||||
return f"struct.pack('{endiend}{PYTHON_DATA_TYPE_STRUCT_FORMAT[field.type] * field.array_size}', {argument})"
|
||||
else:
|
||||
return f"struct.pack('{endiend}{PYTHON_DATA_TYPE_STRUCT_FORMAT[field.type]}', self.{name})"
|
||||
|
||||
elif field.type == BprotoFieldBaseType.STRING or field.type == BprotoFieldBaseType.CHAR:
|
||||
return f"self.{name}.encode('ascii')[:{field.array_size}].ljust({field.array_size}, b'\\x00')"
|
||||
|
||||
else:
|
||||
raise TypeError(f"Cannot convert field {field} to python to bytes conversion string")
|
||||
|
||||
|
||||
def map_from_bytes_conversion(field: Field, endiend: str) -> str:
|
||||
if isinstance(field, FieldEnum):
|
||||
return f"{NameStylePython.toStr(field.enum.name, "enum_name")}(struct.unpack('{endiend}B', data[:{field.get_size_bytes()}])[0])"
|
||||
|
||||
elif isinstance(field, FieldBitfield):
|
||||
return f"{NameStylePython.toStr(field.bitfield.name, "class_name")}().from_bytes(data[:{field.get_size_bytes()}])"
|
||||
|
||||
elif field.type == BprotoFieldBaseType.STRING or field.type == BprotoFieldBaseType.CHAR:
|
||||
return f"data[:{field.get_size_bytes()}].decode('ascii').strip('\\x00')"
|
||||
|
||||
elif field.type == BprotoFieldBaseType.BOOL:
|
||||
if field.array_size > 1:
|
||||
return f"[bool(i) for i in struct.unpack('{endiend}{PYTHON_DATA_TYPE_STRUCT_FORMAT[field.type] * field.array_size}', data[:{field.get_size_bytes()}])]"
|
||||
else:
|
||||
return f"bool(struct.unpack('>{PYTHON_DATA_TYPE_STRUCT_FORMAT[field.type]}', data[:{field.get_size_bytes()}])[0])"
|
||||
|
||||
elif field.type in PYTHON_DATA_TYPE_STRUCT_FORMAT:
|
||||
if field.array_size > 1:
|
||||
return f"list(struct.unpack('{endiend}{PYTHON_DATA_TYPE_STRUCT_FORMAT[field.type] * field.array_size}', data[:{field.get_size_bytes()}]))"
|
||||
else:
|
||||
return f"struct.unpack('{endiend}{PYTHON_DATA_TYPE_STRUCT_FORMAT[field.type]}', data[:{field.get_size_bytes()}])[0]"
|
||||
|
||||
else:
|
||||
raise TypeError(f"Cannot convert field {field} to python to bytes conversion string")
|
||||
|
||||
|
||||
def map_data_type_anotation(field: Field) -> str:
|
||||
if isinstance(field, FieldEnum):
|
||||
return f"{NameStylePython.toStr(field.enum.name, "enum_name")}"
|
||||
|
||||
elif isinstance(field, FieldBitfield):
|
||||
return f"{NameStylePython.toStr(field.bitfield.name, "class_name")}"
|
||||
|
||||
elif field.type in PYTHON_DATA_TYPE_STRUCT_FORMAT:
|
||||
if field.array_size > 1:
|
||||
return f"Annotated[List[{PYTHON_DATA_TYPE_MAP[field.type][0]}], {field.array_size}]"
|
||||
else:
|
||||
return f"{PYTHON_DATA_TYPE_MAP[field.type][0]}"
|
||||
|
||||
elif field.type == BprotoFieldBaseType.STRING or field.type == BprotoFieldBaseType.CHAR:
|
||||
return "str"
|
||||
|
||||
else:
|
||||
raise TypeError(f"Cannot convert field {field} to python to bytes conversion string")
|
||||
|
||||
|
||||
class PythonBackendRenderer(BackendRenderer):
|
||||
|
||||
def __init__(self, output_folder):
|
||||
self.output_folder = output_folder
|
||||
|
||||
self.protocol: ProtocolDefinitions = None
|
||||
self.jinja_env: Environment = None
|
||||
self.jinja_context: dict = None
|
||||
|
||||
def render(self,
|
||||
protocol_definition: ProtocolDefinitions,
|
||||
jinja_env: Environment) -> BackendFileSystemOutput:
|
||||
|
||||
if protocol_definition.protocol_hash_inital is None:
|
||||
raise ValueError("Protocol hash inital for crc16 should no be None")
|
||||
|
||||
self.protocol = NameStylePython.preprocess(deepcopy(protocol_definition))
|
||||
|
||||
self.jinja_env = jinja_env
|
||||
self.jinja_context = {
|
||||
"protocol": {
|
||||
"name": NameStylePython.toStr(self.protocol.name, "enum_item"),
|
||||
"version": self.protocol.version,
|
||||
"crc_size": CRC_SIZE,
|
||||
"crc_initial": hex(self.protocol.protocol_hash_inital),
|
||||
"endian_format": ENDIANDNESS,
|
||||
"endian_str": "little" if ENDIANDNESS == "<" else "big"
|
||||
},
|
||||
"import_name": NameStylePython.toStr(self.protocol.name, 'enum_item'),
|
||||
"enums": [
|
||||
{
|
||||
"name": NameStylePython.toStr(name, "enum_name"),
|
||||
"consts": [
|
||||
(NameStylePython.toStr(k, "enum_item"), v)
|
||||
for k, v in e.values.items()
|
||||
]
|
||||
}
|
||||
for name, e in self.protocol.enums.items()
|
||||
],
|
||||
"bitfields": [
|
||||
{
|
||||
"name": NameStylePython.toStr(b.name, "class_name"),
|
||||
"size": b.get_size_bytes(),
|
||||
"bits": [
|
||||
{
|
||||
"name": NameStylePython.toStr(k, "class_member"),
|
||||
"pos": v
|
||||
}
|
||||
for k, v in b.bits.items()
|
||||
]
|
||||
}
|
||||
for name, b in self.protocol.bitfields.items()
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"name": NameStylePython.toStr(name, "class_name"),
|
||||
"id": m.get_identifier(),
|
||||
"id_name": NameStylePython.toStr(ComponentName(["MSG", "ID"]) + name, "enum_item"),
|
||||
"size": m.get_size_bytes() + 1 + CRC_SIZE,
|
||||
"fields": [
|
||||
{
|
||||
"name": NameStylePython.toStr(k, "class_member"),
|
||||
"default_value": NameStylePython.toStr(v.bitfield.name, "class_name") + "()"
|
||||
if v.type == BprotoFieldBaseType.BITFIELD
|
||||
else (
|
||||
NameStylePython.toStr(v.enum.name, "enum_name") + "." + NameStylePython.toStr(list(v.enum.values.keys())[0], "enum_item")
|
||||
if v.type == BprotoFieldBaseType.ENUM
|
||||
else PYTHON_DATA_TYPE_MAP[v.type][1]
|
||||
),
|
||||
"size": v.get_size_bytes(),
|
||||
"array_size": v.array_size,
|
||||
"type": map_data_type_anotation(v),
|
||||
"to_bytes_conversion": map_to_bytes_conversion(v, ENDIANDNESS),
|
||||
"from_bytes_conversion": map_from_bytes_conversion(v, ENDIANDNESS)
|
||||
}
|
||||
for k, v in m.fields.items()
|
||||
]
|
||||
}
|
||||
for name, m, in self.protocol.messages.items()
|
||||
]
|
||||
}
|
||||
|
||||
return BackendFSOutFolder(self.output_folder, [
|
||||
BackendFSOutputJinjaFile( # Enum
|
||||
self.jinja_env,
|
||||
"python/template/enum.py.jinja2",
|
||||
f"{NameStylePython.toStr(self.protocol.name, 'enum_item')}_protocol_enum.py",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Bitfields
|
||||
self.jinja_env,
|
||||
"python/template/bproto_protocol_bitfield.py.jinja2",
|
||||
f"{NameStylePython.toStr(self.protocol.name, 'enum_item')}_protocol_bitfield.py",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Message IDs
|
||||
self.jinja_env,
|
||||
"python/template/bproto_protocol_packets_ids.py.jinja2",
|
||||
f"{NameStylePython.toStr(self.protocol.name, 'enum_item')}_protocol_message_ids.py",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutputJinjaFile( # Message
|
||||
self.jinja_env,
|
||||
"python/template/bproto_protocol_packets.py.jinja2",
|
||||
f"{NameStylePython.toStr(self.protocol.name, 'enum_item')}_protocol_packets.py",
|
||||
self.jinja_context
|
||||
),
|
||||
BackendFSOutStaticConent(
|
||||
"template/python/static/bproto_base.py",
|
||||
"bproto_base.py"
|
||||
),
|
||||
BackendFSOutStaticConent(
|
||||
"template/python/static/bproto_error.py",
|
||||
"bproto_error.py"
|
||||
)
|
||||
])
|
||||
71
src/backend/rendering/txtBackend.py
Normal file
71
src/backend/rendering/txtBackend.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from backend import BackendRenderer
|
||||
from jinja2 import Environment
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
from backend.fsOutput import BackendFileSystemOutput, BackendFSOutputJinjaFile, BackendFSOutFolder
|
||||
from nameHandling.base import NameStyleBproto
|
||||
|
||||
|
||||
class TxtBackendRenderer(BackendRenderer):
|
||||
|
||||
def __init__(self, output_folder):
|
||||
self.output_folder = output_folder
|
||||
|
||||
def render(self,
|
||||
protocol_definition: ProtocolDefinitions,
|
||||
jinja_env: Environment) -> BackendFileSystemOutput:
|
||||
|
||||
return BackendFSOutFolder(self.output_folder, [
|
||||
BackendFSOutputJinjaFile(
|
||||
jinja_env,
|
||||
"txt/protocolSummary.txt.jinja2",
|
||||
"protocolSummary.txt", context={
|
||||
"name": NameStyleBproto.toStr(protocol_definition.name),
|
||||
"version": protocol_definition.version,
|
||||
"enums": [
|
||||
{
|
||||
"name": NameStyleBproto.toStr(name),
|
||||
"consts": [
|
||||
(NameStyleBproto.toStr(k, "enum"), v)
|
||||
for k, v in e.values.items()
|
||||
]
|
||||
}
|
||||
for name, e in protocol_definition.enums.items()
|
||||
],
|
||||
"bitfields": [
|
||||
{
|
||||
"name": NameStyleBproto.toStr(name),
|
||||
"fields": [
|
||||
(NameStyleBproto.toStr(k, "member"), v)
|
||||
for k, v in b.bits.items()
|
||||
]
|
||||
}
|
||||
for name, b in protocol_definition.bitfields.items()
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"name": NameStyleBproto.toStr(name),
|
||||
"id": m.get_identifier(),
|
||||
"size": m.get_size_bytes(),
|
||||
"fields": [
|
||||
{
|
||||
"name": NameStyleBproto.toStr(k, "member"),
|
||||
"type": v.type.value,
|
||||
"size": v.get_size_bytes(),
|
||||
"array_size": v.array_size,
|
||||
"ref": NameStyleBproto.toStr(v.bitfield.name)
|
||||
if v.type == BprotoFieldBaseType.BITFIELD
|
||||
else (
|
||||
NameStyleBproto.toStr(v.enum.name)
|
||||
if v.type == BprotoFieldBaseType.ENUM
|
||||
else None
|
||||
)
|
||||
}
|
||||
for k, v in m.fields.items()
|
||||
]
|
||||
}
|
||||
for name, m, in protocol_definition.messages.items()
|
||||
]
|
||||
}
|
||||
)]
|
||||
)
|
||||
54
src/compiler.py
Normal file
54
src/compiler.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from backend import BackendRenderer
|
||||
from backend.rendering.txtBackend import TxtBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput, BackendFSOutFolder
|
||||
|
||||
from protocol_components.crc import crc16_calc_inital
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
|
||||
from parser.parser import parse_ast_string, bproto_ErrorListener
|
||||
from parser.ast_visitor import BprotoASTVisitor
|
||||
|
||||
from jinja2 import FileSystemLoader, Environment
|
||||
import sys
|
||||
|
||||
|
||||
class BprotoCompiler:
|
||||
|
||||
def __init__(self, backends: list[BackendRenderer, str], template_directory: str):
|
||||
self.backends = backends
|
||||
self.template_dir = template_directory
|
||||
|
||||
def compile(self, source_text: str, main_output_folder: str) -> BackendFSOutFolder:
|
||||
# Stage 1 - Parse & Create AST
|
||||
ast = parse_ast_string(source_text)
|
||||
|
||||
if type(ast) is bproto_ErrorListener:
|
||||
print(
|
||||
ast.get_syntax_error_message(source_text),
|
||||
file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Stage 2 - Interpret AST **Magic** and build ProtocolDefinition Object
|
||||
# Also this does the forward and backward resolving
|
||||
vinterp = BprotoASTVisitor()
|
||||
protocol_definition: ProtocolDefinitions = vinterp.visit(ast)
|
||||
|
||||
# Stage 3.1 - Render Txt for Hash
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader(self.template_dir)
|
||||
)
|
||||
|
||||
txt_backend = TxtBackendRenderer(".")
|
||||
txt_res = txt_backend.render(protocol_definition, jinja_env).toString().get("protocolSummary.txt")
|
||||
protocol_definition.protocol_hash_inital = crc16_calc_inital(txt_res.encode("ascii"))
|
||||
|
||||
# Stage 3.2 - Backend Rendering
|
||||
backend_outputs: list[BackendFileSystemOutput] = []
|
||||
|
||||
for backend in self.backends:
|
||||
backend_outputs.append(
|
||||
backend.render(protocol_definition, jinja_env)
|
||||
)
|
||||
|
||||
return BackendFSOutFolder(main_output_folder, backend_outputs)
|
||||
113
src/errors.py
Normal file
113
src/errors.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from protocol_components import AbstractProtocolComponent
|
||||
|
||||
|
||||
class BprotoCompilerError(Exception):
|
||||
"""A General error that is raised during the compilation stage.
|
||||
|
||||
Inherited from Exception.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BprotoFrontendError(BprotoCompilerError):
|
||||
"""Error raised during the frontend compilation stage.
|
||||
|
||||
Inherited from BprotoCompilerError.
|
||||
"""
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BprotoAlreadyDefinedError(BprotoFrontendError):
|
||||
"""Error that indicates that a protocol component in the same category/scope with the same name is already defined.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, entity: AbstractProtocolComponent):
|
||||
self.entity = entity
|
||||
super().__init__(f"{self.entity.get_type_name()} {self.entity.get_name()} already defined!")
|
||||
|
||||
|
||||
class BprotoDuplicateNameError(BprotoFrontendError):
|
||||
"""Error that an item with an a entity with the same name is already defined in the same scope.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Its very similar to BprotoAlreadyDefinedError, but this error is used for items that are not protocol components like fields or enum values
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, item: str, parent_entity: AbstractProtocolComponent):
|
||||
self.item = item
|
||||
self.parent = parent_entity
|
||||
super().__init__(f"{item} already defined in {parent_entity.get_type_name()} {parent_entity.get_name()}!")
|
||||
|
||||
|
||||
class BprotoDuplicateEnumValueError(BprotoFrontendError):
|
||||
"""Error raise when building an enumeration and a value is used more than once.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Incomparision to BprotoDuplicateNameError, this error is used for enum values, not the name.
|
||||
Its specific to the enum component.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
|
||||
def __init__(self, value: int, enum: AbstractProtocolComponent):
|
||||
self.value = value
|
||||
self.enum_name = enum
|
||||
super().__init__(f"Value {value} already used in enum {enum.get_name()}!")
|
||||
|
||||
|
||||
class BprotoDuplicateBitfieldPositionError(BprotoFrontendError):
|
||||
"""Error raise when building a bitfield and a position is used more than once.
|
||||
This error should be use during the frontend compilation
|
||||
|
||||
Incomparision to BprotoDuplicateNameError, this error is used for bitfield positions, not the name.
|
||||
Its specific to the bitfield component.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, position: int, bitfield: AbstractProtocolComponent):
|
||||
self.position = position
|
||||
self.bitfield_name = bitfield
|
||||
super().__init__(f"Position {position} already used in bitfield {bitfield.get_name()}!")
|
||||
|
||||
|
||||
class BprotoEnumBitsizeTooLargeError(BprotoFrontendError):
|
||||
"""Error raise when building an enumeration and the size of the enum exceeds the size of 8 bytes.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
This is cause by the fact that bproto enums are just unsigned integer and the largest unsigned integer is 8 bytes long.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, enum: AbstractProtocolComponent):
|
||||
self.enum = enum
|
||||
super().__init__(f"Enum {enum.get_name()} exceed size of 8 bytes (= uint64) with {enum.get_size_bytes()} bytes!")
|
||||
|
||||
|
||||
class BprotoMessageIDAlreadyUsed(BprotoFrontendError):
|
||||
"""Error raise when building a message and the message id is already used.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, message_id: int, message: AbstractProtocolComponent):
|
||||
self.message_id = message_id
|
||||
self.message = message
|
||||
super().__init__(f"Message id {message_id} already used in {message.get_type_name()} {message.get_name()}!")
|
||||
|
||||
|
||||
class BprotoUnresolvedReferenceError(BprotoFrontendError):
|
||||
"""Error raise when a reference to a protocol component could not be resolved.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, reference: str, parent: AbstractProtocolComponent, grand_parent: AbstractProtocolComponent):
|
||||
self.reference = reference
|
||||
self.parent = parent
|
||||
super().__init__(f"Reference {reference} in {parent.get_type_name()} {parent.get_name()} (in {grand_parent.get_type_name()} {grand_parent.get_name()}) could not be resolved!")
|
||||
62
src/gen/bprotoV1.interp
Normal file
62
src/gen/bprotoV1.interp
Normal file
@@ -0,0 +1,62 @@
|
||||
token literal names:
|
||||
null
|
||||
'protocol'
|
||||
'version'
|
||||
'message'
|
||||
'{'
|
||||
','
|
||||
'}'
|
||||
'['
|
||||
']'
|
||||
'enum'
|
||||
'bits'
|
||||
':'
|
||||
'='
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
INT
|
||||
IDENTIFIER
|
||||
WS
|
||||
COMMENT
|
||||
COMMENT_MULTILINE
|
||||
|
||||
rule names:
|
||||
protocol_defintion
|
||||
protocol_header
|
||||
message_def
|
||||
message_id
|
||||
enum_def
|
||||
bit_field_def
|
||||
field
|
||||
field_pos
|
||||
dtype
|
||||
type_standard
|
||||
type_enum
|
||||
type_bitfield
|
||||
array_extension
|
||||
enum_body
|
||||
enum_field
|
||||
bitfield_body
|
||||
bitfield_field
|
||||
|
||||
|
||||
atn:
|
||||
[4, 1, 17, 156, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 1, 0, 1, 0, 1, 0, 1, 0, 4, 0, 39, 8, 0, 11, 0, 12, 0, 40, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 55, 8, 2, 10, 2, 12, 2, 58, 9, 2, 1, 2, 1, 2, 3, 2, 62, 8, 2, 3, 2, 64, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 3, 8, 92, 8, 8, 1, 9, 1, 9, 3, 9, 96, 8, 9, 1, 10, 1, 10, 1, 10, 3, 10, 101, 8, 10, 1, 11, 1, 11, 1, 11, 3, 11, 106, 8, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 116, 8, 13, 10, 13, 12, 13, 119, 9, 13, 1, 13, 1, 13, 3, 13, 123, 8, 13, 3, 13, 125, 8, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 3, 14, 132, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 138, 8, 15, 10, 15, 12, 15, 141, 9, 15, 1, 15, 1, 15, 3, 15, 145, 8, 15, 3, 15, 147, 8, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 3, 16, 154, 8, 16, 1, 16, 0, 0, 17, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 0, 0, 157, 0, 34, 1, 0, 0, 0, 2, 42, 1, 0, 0, 0, 4, 47, 1, 0, 0, 0, 6, 67, 1, 0, 0, 0, 8, 71, 1, 0, 0, 0, 10, 75, 1, 0, 0, 0, 12, 79, 1, 0, 0, 0, 14, 84, 1, 0, 0, 0, 16, 91, 1, 0, 0, 0, 18, 93, 1, 0, 0, 0, 20, 97, 1, 0, 0, 0, 22, 102, 1, 0, 0, 0, 24, 107, 1, 0, 0, 0, 26, 111, 1, 0, 0, 0, 28, 128, 1, 0, 0, 0, 30, 133, 1, 0, 0, 0, 32, 150, 1, 0, 0, 0, 34, 38, 3, 2, 1, 0, 35, 39, 3, 4, 2, 0, 36, 39, 3, 8, 4, 0, 37, 39, 3, 10, 5, 0, 38, 35, 1, 0, 0, 0, 38, 36, 1, 0, 0, 0, 38, 37, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 40, 41, 1, 0, 0, 0, 41, 1, 1, 0, 0, 0, 42, 43, 5, 1, 0, 0, 43, 44, 5, 14, 0, 0, 44, 45, 5, 2, 0, 0, 45, 46, 5, 13, 0, 0, 46, 3, 1, 0, 0, 0, 47, 48, 5, 3, 0, 0, 48, 49, 3, 6, 3, 0, 49, 50, 5, 14, 0, 0, 50, 56, 5, 4, 0, 0, 51, 52, 3, 12, 6, 0, 52, 53, 5, 5, 0, 0, 53, 55, 1, 0, 0, 0, 54, 51, 1, 0, 0, 0, 55, 58, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 63, 1, 0, 0, 0, 58, 56, 1, 0, 0, 0, 59, 61, 3, 12, 6, 0, 60, 62, 5, 5, 0, 0, 61, 60, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 64, 1, 0, 0, 0, 63, 59, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 65, 1, 0, 0, 0, 65, 66, 5, 6, 0, 0, 66, 5, 1, 0, 0, 0, 67, 68, 5, 7, 0, 0, 68, 69, 5, 13, 0, 0, 69, 70, 5, 8, 0, 0, 70, 7, 1, 0, 0, 0, 71, 72, 5, 9, 0, 0, 72, 73, 5, 14, 0, 0, 73, 74, 3, 26, 13, 0, 74, 9, 1, 0, 0, 0, 75, 76, 5, 10, 0, 0, 76, 77, 5, 14, 0, 0, 77, 78, 3, 30, 15, 0, 78, 11, 1, 0, 0, 0, 79, 80, 3, 14, 7, 0, 80, 81, 5, 14, 0, 0, 81, 82, 5, 11, 0, 0, 82, 83, 3, 16, 8, 0, 83, 13, 1, 0, 0, 0, 84, 85, 5, 7, 0, 0, 85, 86, 5, 13, 0, 0, 86, 87, 5, 8, 0, 0, 87, 15, 1, 0, 0, 0, 88, 92, 3, 18, 9, 0, 89, 92, 3, 20, 10, 0, 90, 92, 3, 22, 11, 0, 91, 88, 1, 0, 0, 0, 91, 89, 1, 0, 0, 0, 91, 90, 1, 0, 0, 0, 92, 17, 1, 0, 0, 0, 93, 95, 5, 14, 0, 0, 94, 96, 3, 24, 12, 0, 95, 94, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 19, 1, 0, 0, 0, 97, 100, 5, 9, 0, 0, 98, 101, 3, 26, 13, 0, 99, 101, 5, 14, 0, 0, 100, 98, 1, 0, 0, 0, 100, 99, 1, 0, 0, 0, 101, 21, 1, 0, 0, 0, 102, 105, 5, 10, 0, 0, 103, 106, 3, 30, 15, 0, 104, 106, 5, 14, 0, 0, 105, 103, 1, 0, 0, 0, 105, 104, 1, 0, 0, 0, 106, 23, 1, 0, 0, 0, 107, 108, 5, 7, 0, 0, 108, 109, 5, 13, 0, 0, 109, 110, 5, 8, 0, 0, 110, 25, 1, 0, 0, 0, 111, 117, 5, 4, 0, 0, 112, 113, 3, 28, 14, 0, 113, 114, 5, 5, 0, 0, 114, 116, 1, 0, 0, 0, 115, 112, 1, 0, 0, 0, 116, 119, 1, 0, 0, 0, 117, 115, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 124, 1, 0, 0, 0, 119, 117, 1, 0, 0, 0, 120, 122, 3, 28, 14, 0, 121, 123, 5, 5, 0, 0, 122, 121, 1, 0, 0, 0, 122, 123, 1, 0, 0, 0, 123, 125, 1, 0, 0, 0, 124, 120, 1, 0, 0, 0, 124, 125, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 127, 5, 6, 0, 0, 127, 27, 1, 0, 0, 0, 128, 131, 5, 14, 0, 0, 129, 130, 5, 12, 0, 0, 130, 132, 5, 13, 0, 0, 131, 129, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 29, 1, 0, 0, 0, 133, 139, 5, 4, 0, 0, 134, 135, 3, 32, 16, 0, 135, 136, 5, 5, 0, 0, 136, 138, 1, 0, 0, 0, 137, 134, 1, 0, 0, 0, 138, 141, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 146, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 142, 144, 3, 32, 16, 0, 143, 145, 5, 5, 0, 0, 144, 143, 1, 0, 0, 0, 144, 145, 1, 0, 0, 0, 145, 147, 1, 0, 0, 0, 146, 142, 1, 0, 0, 0, 146, 147, 1, 0, 0, 0, 147, 148, 1, 0, 0, 0, 148, 149, 5, 6, 0, 0, 149, 31, 1, 0, 0, 0, 150, 153, 5, 14, 0, 0, 151, 152, 5, 11, 0, 0, 152, 154, 5, 13, 0, 0, 153, 151, 1, 0, 0, 0, 153, 154, 1, 0, 0, 0, 154, 33, 1, 0, 0, 0, 17, 38, 40, 56, 61, 63, 91, 95, 100, 105, 117, 122, 124, 131, 139, 144, 146, 153]
|
||||
29
src/gen/bprotoV1.tokens
Normal file
29
src/gen/bprotoV1.tokens
Normal file
@@ -0,0 +1,29 @@
|
||||
T__0=1
|
||||
T__1=2
|
||||
T__2=3
|
||||
T__3=4
|
||||
T__4=5
|
||||
T__5=6
|
||||
T__6=7
|
||||
T__7=8
|
||||
T__8=9
|
||||
T__9=10
|
||||
T__10=11
|
||||
T__11=12
|
||||
INT=13
|
||||
IDENTIFIER=14
|
||||
WS=15
|
||||
COMMENT=16
|
||||
COMMENT_MULTILINE=17
|
||||
'protocol'=1
|
||||
'version'=2
|
||||
'message'=3
|
||||
'{'=4
|
||||
','=5
|
||||
'}'=6
|
||||
'['=7
|
||||
']'=8
|
||||
'enum'=9
|
||||
'bits'=10
|
||||
':'=11
|
||||
'='=12
|
||||
68
src/gen/bprotoV1Lexer.interp
Normal file
68
src/gen/bprotoV1Lexer.interp
Normal file
@@ -0,0 +1,68 @@
|
||||
token literal names:
|
||||
null
|
||||
'protocol'
|
||||
'version'
|
||||
'message'
|
||||
'{'
|
||||
','
|
||||
'}'
|
||||
'['
|
||||
']'
|
||||
'enum'
|
||||
'bits'
|
||||
':'
|
||||
'='
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
INT
|
||||
IDENTIFIER
|
||||
WS
|
||||
COMMENT
|
||||
COMMENT_MULTILINE
|
||||
|
||||
rule names:
|
||||
T__0
|
||||
T__1
|
||||
T__2
|
||||
T__3
|
||||
T__4
|
||||
T__5
|
||||
T__6
|
||||
T__7
|
||||
T__8
|
||||
T__9
|
||||
T__10
|
||||
T__11
|
||||
INT
|
||||
IDENTIFIER
|
||||
WS
|
||||
COMMENT
|
||||
COMMENT_MULTILINE
|
||||
|
||||
channel names:
|
||||
DEFAULT_TOKEN_CHANNEL
|
||||
HIDDEN
|
||||
|
||||
mode names:
|
||||
DEFAULT_MODE
|
||||
|
||||
atn:
|
||||
[4, 0, 17, 126, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 4, 12, 86, 8, 12, 11, 12, 12, 12, 87, 1, 13, 4, 13, 91, 8, 13, 11, 13, 12, 13, 92, 1, 14, 4, 14, 96, 8, 14, 11, 14, 12, 14, 97, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 106, 8, 15, 10, 15, 12, 15, 109, 9, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 117, 8, 16, 10, 16, 12, 16, 120, 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 118, 0, 17, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 1, 0, 4, 1, 0, 48, 57, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 10, 10, 13, 13, 130, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 1, 35, 1, 0, 0, 0, 3, 44, 1, 0, 0, 0, 5, 52, 1, 0, 0, 0, 7, 60, 1, 0, 0, 0, 9, 62, 1, 0, 0, 0, 11, 64, 1, 0, 0, 0, 13, 66, 1, 0, 0, 0, 15, 68, 1, 0, 0, 0, 17, 70, 1, 0, 0, 0, 19, 75, 1, 0, 0, 0, 21, 80, 1, 0, 0, 0, 23, 82, 1, 0, 0, 0, 25, 85, 1, 0, 0, 0, 27, 90, 1, 0, 0, 0, 29, 95, 1, 0, 0, 0, 31, 101, 1, 0, 0, 0, 33, 112, 1, 0, 0, 0, 35, 36, 5, 112, 0, 0, 36, 37, 5, 114, 0, 0, 37, 38, 5, 111, 0, 0, 38, 39, 5, 116, 0, 0, 39, 40, 5, 111, 0, 0, 40, 41, 5, 99, 0, 0, 41, 42, 5, 111, 0, 0, 42, 43, 5, 108, 0, 0, 43, 2, 1, 0, 0, 0, 44, 45, 5, 118, 0, 0, 45, 46, 5, 101, 0, 0, 46, 47, 5, 114, 0, 0, 47, 48, 5, 115, 0, 0, 48, 49, 5, 105, 0, 0, 49, 50, 5, 111, 0, 0, 50, 51, 5, 110, 0, 0, 51, 4, 1, 0, 0, 0, 52, 53, 5, 109, 0, 0, 53, 54, 5, 101, 0, 0, 54, 55, 5, 115, 0, 0, 55, 56, 5, 115, 0, 0, 56, 57, 5, 97, 0, 0, 57, 58, 5, 103, 0, 0, 58, 59, 5, 101, 0, 0, 59, 6, 1, 0, 0, 0, 60, 61, 5, 123, 0, 0, 61, 8, 1, 0, 0, 0, 62, 63, 5, 44, 0, 0, 63, 10, 1, 0, 0, 0, 64, 65, 5, 125, 0, 0, 65, 12, 1, 0, 0, 0, 66, 67, 5, 91, 0, 0, 67, 14, 1, 0, 0, 0, 68, 69, 5, 93, 0, 0, 69, 16, 1, 0, 0, 0, 70, 71, 5, 101, 0, 0, 71, 72, 5, 110, 0, 0, 72, 73, 5, 117, 0, 0, 73, 74, 5, 109, 0, 0, 74, 18, 1, 0, 0, 0, 75, 76, 5, 98, 0, 0, 76, 77, 5, 105, 0, 0, 77, 78, 5, 116, 0, 0, 78, 79, 5, 115, 0, 0, 79, 20, 1, 0, 0, 0, 80, 81, 5, 58, 0, 0, 81, 22, 1, 0, 0, 0, 82, 83, 5, 61, 0, 0, 83, 24, 1, 0, 0, 0, 84, 86, 7, 0, 0, 0, 85, 84, 1, 0, 0, 0, 86, 87, 1, 0, 0, 0, 87, 85, 1, 0, 0, 0, 87, 88, 1, 0, 0, 0, 88, 26, 1, 0, 0, 0, 89, 91, 7, 1, 0, 0, 90, 89, 1, 0, 0, 0, 91, 92, 1, 0, 0, 0, 92, 90, 1, 0, 0, 0, 92, 93, 1, 0, 0, 0, 93, 28, 1, 0, 0, 0, 94, 96, 7, 2, 0, 0, 95, 94, 1, 0, 0, 0, 96, 97, 1, 0, 0, 0, 97, 95, 1, 0, 0, 0, 97, 98, 1, 0, 0, 0, 98, 99, 1, 0, 0, 0, 99, 100, 6, 14, 0, 0, 100, 30, 1, 0, 0, 0, 101, 102, 5, 47, 0, 0, 102, 103, 5, 47, 0, 0, 103, 107, 1, 0, 0, 0, 104, 106, 8, 3, 0, 0, 105, 104, 1, 0, 0, 0, 106, 109, 1, 0, 0, 0, 107, 105, 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 110, 1, 0, 0, 0, 109, 107, 1, 0, 0, 0, 110, 111, 6, 15, 0, 0, 111, 32, 1, 0, 0, 0, 112, 113, 5, 47, 0, 0, 113, 114, 5, 42, 0, 0, 114, 118, 1, 0, 0, 0, 115, 117, 9, 0, 0, 0, 116, 115, 1, 0, 0, 0, 117, 120, 1, 0, 0, 0, 118, 119, 1, 0, 0, 0, 118, 116, 1, 0, 0, 0, 119, 121, 1, 0, 0, 0, 120, 118, 1, 0, 0, 0, 121, 122, 5, 42, 0, 0, 122, 123, 5, 47, 0, 0, 123, 124, 1, 0, 0, 0, 124, 125, 6, 16, 0, 0, 125, 34, 1, 0, 0, 0, 6, 0, 87, 92, 97, 107, 118, 1, 6, 0, 0]
|
||||
106
src/gen/bprotoV1Lexer.py
Normal file
106
src/gen/bprotoV1Lexer.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# Generated from bprotoV1.g4 by ANTLR 4.13.2
|
||||
from antlr4 import *
|
||||
from io import StringIO
|
||||
import sys
|
||||
if sys.version_info[1] > 5:
|
||||
from typing import TextIO
|
||||
else:
|
||||
from typing.io import TextIO
|
||||
|
||||
|
||||
def serializedATN():
|
||||
return [
|
||||
4,0,17,126,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
|
||||
2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
|
||||
13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,1,0,1,0,1,0,1,0,1,0,1,0,1,
|
||||
0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,
|
||||
2,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,6,1,6,1,7,1,7,1,8,1,8,1,8,1,
|
||||
8,1,8,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,12,4,12,86,8,12,
|
||||
11,12,12,12,87,1,13,4,13,91,8,13,11,13,12,13,92,1,14,4,14,96,8,14,
|
||||
11,14,12,14,97,1,14,1,14,1,15,1,15,1,15,1,15,5,15,106,8,15,10,15,
|
||||
12,15,109,9,15,1,15,1,15,1,16,1,16,1,16,1,16,5,16,117,8,16,10,16,
|
||||
12,16,120,9,16,1,16,1,16,1,16,1,16,1,16,1,118,0,17,1,1,3,2,5,3,7,
|
||||
4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,
|
||||
16,33,17,1,0,4,1,0,48,57,4,0,48,57,65,90,95,95,97,122,3,0,9,10,13,
|
||||
13,32,32,2,0,10,10,13,13,130,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,
|
||||
0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,
|
||||
17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,
|
||||
27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,1,35,1,0,0,0,3,
|
||||
44,1,0,0,0,5,52,1,0,0,0,7,60,1,0,0,0,9,62,1,0,0,0,11,64,1,0,0,0,
|
||||
13,66,1,0,0,0,15,68,1,0,0,0,17,70,1,0,0,0,19,75,1,0,0,0,21,80,1,
|
||||
0,0,0,23,82,1,0,0,0,25,85,1,0,0,0,27,90,1,0,0,0,29,95,1,0,0,0,31,
|
||||
101,1,0,0,0,33,112,1,0,0,0,35,36,5,112,0,0,36,37,5,114,0,0,37,38,
|
||||
5,111,0,0,38,39,5,116,0,0,39,40,5,111,0,0,40,41,5,99,0,0,41,42,5,
|
||||
111,0,0,42,43,5,108,0,0,43,2,1,0,0,0,44,45,5,118,0,0,45,46,5,101,
|
||||
0,0,46,47,5,114,0,0,47,48,5,115,0,0,48,49,5,105,0,0,49,50,5,111,
|
||||
0,0,50,51,5,110,0,0,51,4,1,0,0,0,52,53,5,109,0,0,53,54,5,101,0,0,
|
||||
54,55,5,115,0,0,55,56,5,115,0,0,56,57,5,97,0,0,57,58,5,103,0,0,58,
|
||||
59,5,101,0,0,59,6,1,0,0,0,60,61,5,123,0,0,61,8,1,0,0,0,62,63,5,44,
|
||||
0,0,63,10,1,0,0,0,64,65,5,125,0,0,65,12,1,0,0,0,66,67,5,91,0,0,67,
|
||||
14,1,0,0,0,68,69,5,93,0,0,69,16,1,0,0,0,70,71,5,101,0,0,71,72,5,
|
||||
110,0,0,72,73,5,117,0,0,73,74,5,109,0,0,74,18,1,0,0,0,75,76,5,98,
|
||||
0,0,76,77,5,105,0,0,77,78,5,116,0,0,78,79,5,115,0,0,79,20,1,0,0,
|
||||
0,80,81,5,58,0,0,81,22,1,0,0,0,82,83,5,61,0,0,83,24,1,0,0,0,84,86,
|
||||
7,0,0,0,85,84,1,0,0,0,86,87,1,0,0,0,87,85,1,0,0,0,87,88,1,0,0,0,
|
||||
88,26,1,0,0,0,89,91,7,1,0,0,90,89,1,0,0,0,91,92,1,0,0,0,92,90,1,
|
||||
0,0,0,92,93,1,0,0,0,93,28,1,0,0,0,94,96,7,2,0,0,95,94,1,0,0,0,96,
|
||||
97,1,0,0,0,97,95,1,0,0,0,97,98,1,0,0,0,98,99,1,0,0,0,99,100,6,14,
|
||||
0,0,100,30,1,0,0,0,101,102,5,47,0,0,102,103,5,47,0,0,103,107,1,0,
|
||||
0,0,104,106,8,3,0,0,105,104,1,0,0,0,106,109,1,0,0,0,107,105,1,0,
|
||||
0,0,107,108,1,0,0,0,108,110,1,0,0,0,109,107,1,0,0,0,110,111,6,15,
|
||||
0,0,111,32,1,0,0,0,112,113,5,47,0,0,113,114,5,42,0,0,114,118,1,0,
|
||||
0,0,115,117,9,0,0,0,116,115,1,0,0,0,117,120,1,0,0,0,118,119,1,0,
|
||||
0,0,118,116,1,0,0,0,119,121,1,0,0,0,120,118,1,0,0,0,121,122,5,42,
|
||||
0,0,122,123,5,47,0,0,123,124,1,0,0,0,124,125,6,16,0,0,125,34,1,0,
|
||||
0,0,6,0,87,92,97,107,118,1,6,0,0
|
||||
]
|
||||
|
||||
class bprotoV1Lexer(Lexer):
|
||||
|
||||
atn = ATNDeserializer().deserialize(serializedATN())
|
||||
|
||||
decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
|
||||
|
||||
T__0 = 1
|
||||
T__1 = 2
|
||||
T__2 = 3
|
||||
T__3 = 4
|
||||
T__4 = 5
|
||||
T__5 = 6
|
||||
T__6 = 7
|
||||
T__7 = 8
|
||||
T__8 = 9
|
||||
T__9 = 10
|
||||
T__10 = 11
|
||||
T__11 = 12
|
||||
INT = 13
|
||||
IDENTIFIER = 14
|
||||
WS = 15
|
||||
COMMENT = 16
|
||||
COMMENT_MULTILINE = 17
|
||||
|
||||
channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ]
|
||||
|
||||
modeNames = [ "DEFAULT_MODE" ]
|
||||
|
||||
literalNames = [ "<INVALID>",
|
||||
"'protocol'", "'version'", "'message'", "'{'", "','", "'}'",
|
||||
"'['", "']'", "'enum'", "'bits'", "':'", "'='" ]
|
||||
|
||||
symbolicNames = [ "<INVALID>",
|
||||
"INT", "IDENTIFIER", "WS", "COMMENT", "COMMENT_MULTILINE" ]
|
||||
|
||||
ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6",
|
||||
"T__7", "T__8", "T__9", "T__10", "T__11", "INT", "IDENTIFIER",
|
||||
"WS", "COMMENT", "COMMENT_MULTILINE" ]
|
||||
|
||||
grammarFileName = "bprotoV1.g4"
|
||||
|
||||
def __init__(self, input=None, output:TextIO = sys.stdout):
|
||||
super().__init__(input, output)
|
||||
self.checkVersion("4.13.2")
|
||||
self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
|
||||
self._actions = None
|
||||
self._predicates = None
|
||||
|
||||
|
||||
29
src/gen/bprotoV1Lexer.tokens
Normal file
29
src/gen/bprotoV1Lexer.tokens
Normal file
@@ -0,0 +1,29 @@
|
||||
T__0=1
|
||||
T__1=2
|
||||
T__2=3
|
||||
T__3=4
|
||||
T__4=5
|
||||
T__5=6
|
||||
T__6=7
|
||||
T__7=8
|
||||
T__8=9
|
||||
T__9=10
|
||||
T__10=11
|
||||
T__11=12
|
||||
INT=13
|
||||
IDENTIFIER=14
|
||||
WS=15
|
||||
COMMENT=16
|
||||
COMMENT_MULTILINE=17
|
||||
'protocol'=1
|
||||
'version'=2
|
||||
'message'=3
|
||||
'{'=4
|
||||
','=5
|
||||
'}'=6
|
||||
'['=7
|
||||
']'=8
|
||||
'enum'=9
|
||||
'bits'=10
|
||||
':'=11
|
||||
'='=12
|
||||
165
src/gen/bprotoV1Listener.py
Normal file
165
src/gen/bprotoV1Listener.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# Generated from bprotoV1.g4 by ANTLR 4.13.2
|
||||
from antlr4 import *
|
||||
if "." in __name__:
|
||||
from .bprotoV1Parser import bprotoV1Parser
|
||||
else:
|
||||
from bprotoV1Parser import bprotoV1Parser
|
||||
|
||||
# This class defines a complete listener for a parse tree produced by bprotoV1Parser.
|
||||
class bprotoV1Listener(ParseTreeListener):
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#protocol_defintion.
|
||||
def enterProtocol_defintion(self, ctx:bprotoV1Parser.Protocol_defintionContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#protocol_defintion.
|
||||
def exitProtocol_defintion(self, ctx:bprotoV1Parser.Protocol_defintionContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#protocol_header.
|
||||
def enterProtocol_header(self, ctx:bprotoV1Parser.Protocol_headerContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#protocol_header.
|
||||
def exitProtocol_header(self, ctx:bprotoV1Parser.Protocol_headerContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#message_def.
|
||||
def enterMessage_def(self, ctx:bprotoV1Parser.Message_defContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#message_def.
|
||||
def exitMessage_def(self, ctx:bprotoV1Parser.Message_defContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#message_id.
|
||||
def enterMessage_id(self, ctx:bprotoV1Parser.Message_idContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#message_id.
|
||||
def exitMessage_id(self, ctx:bprotoV1Parser.Message_idContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#enum_def.
|
||||
def enterEnum_def(self, ctx:bprotoV1Parser.Enum_defContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#enum_def.
|
||||
def exitEnum_def(self, ctx:bprotoV1Parser.Enum_defContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#bit_field_def.
|
||||
def enterBit_field_def(self, ctx:bprotoV1Parser.Bit_field_defContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#bit_field_def.
|
||||
def exitBit_field_def(self, ctx:bprotoV1Parser.Bit_field_defContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#field.
|
||||
def enterField(self, ctx:bprotoV1Parser.FieldContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#field.
|
||||
def exitField(self, ctx:bprotoV1Parser.FieldContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#field_pos.
|
||||
def enterField_pos(self, ctx:bprotoV1Parser.Field_posContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#field_pos.
|
||||
def exitField_pos(self, ctx:bprotoV1Parser.Field_posContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#dtype.
|
||||
def enterDtype(self, ctx:bprotoV1Parser.DtypeContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#dtype.
|
||||
def exitDtype(self, ctx:bprotoV1Parser.DtypeContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#type_standard.
|
||||
def enterType_standard(self, ctx:bprotoV1Parser.Type_standardContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#type_standard.
|
||||
def exitType_standard(self, ctx:bprotoV1Parser.Type_standardContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#type_enum.
|
||||
def enterType_enum(self, ctx:bprotoV1Parser.Type_enumContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#type_enum.
|
||||
def exitType_enum(self, ctx:bprotoV1Parser.Type_enumContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#type_bitfield.
|
||||
def enterType_bitfield(self, ctx:bprotoV1Parser.Type_bitfieldContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#type_bitfield.
|
||||
def exitType_bitfield(self, ctx:bprotoV1Parser.Type_bitfieldContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#array_extension.
|
||||
def enterArray_extension(self, ctx:bprotoV1Parser.Array_extensionContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#array_extension.
|
||||
def exitArray_extension(self, ctx:bprotoV1Parser.Array_extensionContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#enum_body.
|
||||
def enterEnum_body(self, ctx:bprotoV1Parser.Enum_bodyContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#enum_body.
|
||||
def exitEnum_body(self, ctx:bprotoV1Parser.Enum_bodyContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#enum_field.
|
||||
def enterEnum_field(self, ctx:bprotoV1Parser.Enum_fieldContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#enum_field.
|
||||
def exitEnum_field(self, ctx:bprotoV1Parser.Enum_fieldContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#bitfield_body.
|
||||
def enterBitfield_body(self, ctx:bprotoV1Parser.Bitfield_bodyContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#bitfield_body.
|
||||
def exitBitfield_body(self, ctx:bprotoV1Parser.Bitfield_bodyContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by bprotoV1Parser#bitfield_field.
|
||||
def enterBitfield_field(self, ctx:bprotoV1Parser.Bitfield_fieldContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by bprotoV1Parser#bitfield_field.
|
||||
def exitBitfield_field(self, ctx:bprotoV1Parser.Bitfield_fieldContext):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
del bprotoV1Parser
|
||||
1266
src/gen/bprotoV1Parser.py
Normal file
1266
src/gen/bprotoV1Parser.py
Normal file
File diff suppressed because it is too large
Load Diff
98
src/gen/bprotoV1Visitor.py
Normal file
98
src/gen/bprotoV1Visitor.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# Generated from bprotoV1.g4 by ANTLR 4.13.2
|
||||
from antlr4 import *
|
||||
if "." in __name__:
|
||||
from .bprotoV1Parser import bprotoV1Parser
|
||||
else:
|
||||
from bprotoV1Parser import bprotoV1Parser
|
||||
|
||||
# This class defines a complete generic visitor for a parse tree produced by bprotoV1Parser.
|
||||
|
||||
class bprotoV1Visitor(ParseTreeVisitor):
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#protocol_defintion.
|
||||
def visitProtocol_defintion(self, ctx:bprotoV1Parser.Protocol_defintionContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#protocol_header.
|
||||
def visitProtocol_header(self, ctx:bprotoV1Parser.Protocol_headerContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#message_def.
|
||||
def visitMessage_def(self, ctx:bprotoV1Parser.Message_defContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#message_id.
|
||||
def visitMessage_id(self, ctx:bprotoV1Parser.Message_idContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#enum_def.
|
||||
def visitEnum_def(self, ctx:bprotoV1Parser.Enum_defContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#bit_field_def.
|
||||
def visitBit_field_def(self, ctx:bprotoV1Parser.Bit_field_defContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#field.
|
||||
def visitField(self, ctx:bprotoV1Parser.FieldContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#field_pos.
|
||||
def visitField_pos(self, ctx:bprotoV1Parser.Field_posContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#dtype.
|
||||
def visitDtype(self, ctx:bprotoV1Parser.DtypeContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#type_standard.
|
||||
def visitType_standard(self, ctx:bprotoV1Parser.Type_standardContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#type_enum.
|
||||
def visitType_enum(self, ctx:bprotoV1Parser.Type_enumContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#type_bitfield.
|
||||
def visitType_bitfield(self, ctx:bprotoV1Parser.Type_bitfieldContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#array_extension.
|
||||
def visitArray_extension(self, ctx:bprotoV1Parser.Array_extensionContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#enum_body.
|
||||
def visitEnum_body(self, ctx:bprotoV1Parser.Enum_bodyContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#enum_field.
|
||||
def visitEnum_field(self, ctx:bprotoV1Parser.Enum_fieldContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#bitfield_body.
|
||||
def visitBitfield_body(self, ctx:bprotoV1Parser.Bitfield_bodyContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
# Visit a parse tree produced by bprotoV1Parser#bitfield_field.
|
||||
def visitBitfield_field(self, ctx:bprotoV1Parser.Bitfield_fieldContext):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
|
||||
|
||||
del bprotoV1Parser
|
||||
62
src/main.py
Normal file
62
src/main.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import argparse
|
||||
|
||||
from backend.rendering.pythonBackend import PythonBackendRenderer
|
||||
from backend.rendering.cBackend import CBackendRenderer
|
||||
from backend.rendering.txtBackend import TxtBackendRenderer
|
||||
from compiler import BprotoCompiler
|
||||
|
||||
INPUT_FILE = "examples/test_protocol.bproto"
|
||||
|
||||
|
||||
def main():
|
||||
cli_args_parser = argparse.ArgumentParser(description="Bproto Compiler")
|
||||
|
||||
cli_args_parser.add_argument(
|
||||
"-i", "--input",
|
||||
help="Input file to compile",
|
||||
required=True
|
||||
)
|
||||
cli_args_parser.add_argument(
|
||||
"-o", "--output",
|
||||
help="Output file to write compiled code to",
|
||||
required=True
|
||||
)
|
||||
cli_args_parser.add_argument(
|
||||
"-t", "--target",
|
||||
help="Target language to compile to",
|
||||
required=True,
|
||||
choices=["python", "c", "txt"],
|
||||
action="append"
|
||||
)
|
||||
|
||||
args = cli_args_parser.parse_args()
|
||||
|
||||
with open(args.input, "r") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Backend configuration
|
||||
backends = []
|
||||
|
||||
if "python" in args.target:
|
||||
print("Compiling to Python")
|
||||
backends.append(PythonBackendRenderer("python"))
|
||||
|
||||
if "c" in args.target:
|
||||
print("Compiling to C")
|
||||
backends.append(CBackendRenderer("c"))
|
||||
|
||||
if "txt" in args.target:
|
||||
print("Compiling to Txt")
|
||||
backends.append(TxtBackendRenderer("txt"))
|
||||
|
||||
compiler = BprotoCompiler(backends, "template")
|
||||
output = compiler.compile(source_text, ".")
|
||||
|
||||
output.saveToDisk(args.output)
|
||||
string_outputs = output.toString()
|
||||
print("Output Files:", list(string_outputs.keys()))
|
||||
print(string_outputs.get("txt/protocolSummary.txt"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
169
src/nameHandling/base.py
Normal file
169
src/nameHandling/base.py
Normal file
@@ -0,0 +1,169 @@
|
||||
from typing import Literal
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class ComponentName:
|
||||
"""Class to represent a component name. A portable container for component names.
|
||||
Should be used instead of strings to represent component like bitfields, enums, messages, etc.
|
||||
|
||||
Conversions to diffrent target styles are implemented separately via ComponentNameStyle classes.
|
||||
"""
|
||||
|
||||
def __init__(self, name_parts: list = [], source_style: object | None = None, source_string: str = ""):
|
||||
"""Initialize a ComponentName object.
|
||||
|
||||
Args:
|
||||
name_parts (list, optional): list of lowercase name components as string . Defaults to [].
|
||||
source_style (object | None, optional): ComponentNameStyle use to create this ComponentName. This is used for compare other strings agains. Defaults to None.
|
||||
source_string (str, optional): String representation of the name. Defaults to "". This is used for clearification in error messages.
|
||||
"""
|
||||
self.name_parts: list[str] = name_parts
|
||||
self.source_style = source_style
|
||||
self.source_string = source_string
|
||||
|
||||
def __repr__(self):
|
||||
return f"ComponentName({'-'.join(map(lambda s: s[0].upper() + s[1:].lower(), self.name_parts))})"
|
||||
|
||||
def __eq__(self, value: object | str) -> bool:
|
||||
if isinstance(value, ComponentName):
|
||||
return self.name_parts == value.name_parts
|
||||
elif isinstance(value, str) and self.source_style is not None:
|
||||
return self.source_style.fromStr(value) == self
|
||||
else:
|
||||
return False
|
||||
|
||||
def __add__(self, other: str):
|
||||
if isinstance(other, type(self)):
|
||||
return ComponentName(
|
||||
self.name_parts + other.name_parts,
|
||||
self.source_style
|
||||
)
|
||||
|
||||
elif isinstance(other, str):
|
||||
return ComponentName(
|
||||
self.name_parts + self.source_style.fromStr(other).name_parts,
|
||||
self.source_style
|
||||
)
|
||||
|
||||
else:
|
||||
raise TypeError(f"Cannot combinde {type(other)} with ComponentName")
|
||||
|
||||
def __hash__(self):
|
||||
return hash(tuple(self.name_parts))
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
res = ComponentName(
|
||||
[],
|
||||
self.source_style,
|
||||
self.source_string
|
||||
)
|
||||
|
||||
res.name_parts = deepcopy(self.name_parts, memo)
|
||||
res.source_style = deepcopy(self.source_style, memo)
|
||||
res.source_string = deepcopy(self.source_string, memo)
|
||||
|
||||
memo[id(self)] = res
|
||||
memo[id(res)] = res
|
||||
return res
|
||||
|
||||
|
||||
class NameStyle(ABC):
|
||||
"""Abstract class to represent a style of component names.
|
||||
This converts ComponentNames to strings and vice versa.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def toStr(component_name: ComponentName, context: str = "") -> str:
|
||||
"""Given a ComponentName object, convert it to a string in the target style.
|
||||
|
||||
Args:
|
||||
component_name (NameStyle): The ComponentName object to convert.
|
||||
context (str): Additional context to use when converting the name.
|
||||
|
||||
Returns:
|
||||
str: String representation of the ComponentName object in a target style.
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def fromStr(name: str, context: str = "") -> ComponentName:
|
||||
"""Given a string in the target style, convert it to a ComponentName object.
|
||||
|
||||
Args:
|
||||
name (str): The string to convert.
|
||||
context (str): Additional context to use when converting the name.
|
||||
|
||||
Returns:
|
||||
ComponentName: ComponentName object representing a name
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NameStyleBproto(NameStyle):
|
||||
"""Converter class for bproto style component names.
|
||||
|
||||
Inherited from NameStyle.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def toStr(component_name: ComponentName, context: str = Literal["", "enum", "member"]) -> str:
|
||||
|
||||
if context == "enum":
|
||||
return '_'.join(map(lambda s: s.upper(), component_name.name_parts))
|
||||
elif context == "member":
|
||||
res = ''.join(map(lambda s: s[0].upper() + s[1:].lower(), component_name.name_parts))
|
||||
return res[0].lower() + res[1:]
|
||||
else:
|
||||
return ''.join(map(lambda s: s[0].upper() + s[1:].lower(), component_name.name_parts))
|
||||
|
||||
@staticmethod
|
||||
def fromStr(bproto_style_name: str | ComponentName, context: Literal["", "enum", "protocol_name"] = "") -> ComponentName:
|
||||
if isinstance(bproto_style_name, ComponentName):
|
||||
bproto_style_name.source_style = NameStyleBproto
|
||||
return bproto_style_name
|
||||
|
||||
first_split = re.split(r"-|_", bproto_style_name)
|
||||
second_split: list[str] = []
|
||||
|
||||
if context == "enum" or context == "protocol_name":
|
||||
second_split.extend(list(map(lambda s: s.lower(), first_split)))
|
||||
else:
|
||||
for part in first_split:
|
||||
second_split.extend(
|
||||
list(map(lambda s: s.lower(),
|
||||
re.split(r'(?=[A-Z])', part)
|
||||
))
|
||||
)
|
||||
|
||||
while "" in second_split:
|
||||
second_split.remove("")
|
||||
|
||||
return ComponentName(second_split, NameStyleBproto, bproto_style_name)
|
||||
|
||||
|
||||
def to_cap_case(component_name: ComponentName) -> str:
|
||||
return "".join([i[0].upper() + i[1:].lower() for i in component_name.name_parts])
|
||||
|
||||
|
||||
def to_camel_case(component_name: ComponentName) -> str:
|
||||
res = "".join([i[0].upper() + i[1:].lower() for i in component_name.name_parts])
|
||||
return res[0].lower() + res[1:]
|
||||
|
||||
|
||||
def to_snake_case(component_name: ComponentName) -> str:
|
||||
return "_".join([i.lower() for i in component_name.name_parts])
|
||||
|
||||
|
||||
def to_screaming_case(component_name: ComponentName) -> str:
|
||||
return "_".join([i.upper() for i in component_name.name_parts])
|
||||
53
src/nameHandling/brand_applier.py
Normal file
53
src/nameHandling/brand_applier.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from protocol_components.message import Message
|
||||
|
||||
|
||||
class ComponentNameModifier(ABC):
|
||||
"""A generic statefull 'maschine' that modifies (apply) a change to a ComponentName.
|
||||
|
||||
This is an abstract class
|
||||
"""
|
||||
@abstractmethod
|
||||
def apply(self, name: ComponentName) -> ComponentName:
|
||||
"""Apply the change to the ComponentName.
|
||||
|
||||
This changes the component name in place and returns it.
|
||||
|
||||
Args:
|
||||
name (ComponentName): The ComponentName to modify.
|
||||
|
||||
Returns:
|
||||
ComponentName: Modified ComponentName
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BrandApplier(ComponentNameModifier):
|
||||
"""Applies a protocol brand name to a ComponentName.
|
||||
Just prepends the brand name to the ComponentName.
|
||||
|
||||
Inherited from ComponentNameModifier.
|
||||
"""
|
||||
def __init__(self, brand_name: str | ComponentName):
|
||||
"""Initialize the BrandApplier.
|
||||
|
||||
Args:
|
||||
brand_name (str|ComponentName): Brand name to apply.
|
||||
"""
|
||||
if isinstance(brand_name, ComponentName):
|
||||
self.brand_name: ComponentName = brand_name
|
||||
elif isinstance(brand_name, str):
|
||||
self.brand_name: ComponentName = NameStyleBproto.fromStr(brand_name)
|
||||
else:
|
||||
raise TypeError("Invalid Type for Brand Name")
|
||||
|
||||
def apply(self, obj: ComponentName | Message) -> ComponentName:
|
||||
|
||||
if isinstance(obj, ComponentName):
|
||||
return self.brand_name + obj
|
||||
elif isinstance(obj, Message):
|
||||
obj.name = self.apply(obj.name)
|
||||
return obj
|
||||
else:
|
||||
raise TypeError(f"Cannot apply naming to type {type(obj)}")
|
||||
95
src/nameHandling/resolver.py
Normal file
95
src/nameHandling/resolver.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from protocol_components.field import Field, FieldEnum, FieldBitfield, FieldEnumRef, FieldBitfieldRef, FactoryField
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.message import Message
|
||||
from nameHandling.brand_applier import BrandApplier
|
||||
import protocol_components.protocol
|
||||
|
||||
from errors import BprotoUnresolvedReferenceError
|
||||
|
||||
|
||||
class ProtocolReferenceResolver:
|
||||
"""Resolves refences in fields in messages in protocol definitiown
|
||||
This is a one-time use class, once the resolve_refrence_protocol is called, the object should not be used anymore
|
||||
|
||||
Usally used after the ProtocolDefinitions object is created by the FactoryProtocolDefition
|
||||
"""
|
||||
|
||||
def __init__(self, protocol: protocol_components.protocol.ProtocolDefinitions):
|
||||
"""Initializes the ProtocolReferenceResolver class
|
||||
|
||||
Args:
|
||||
protocol (ProtocolDefinitions): Protocol to resolve refrences in, must not be fully assembled by the ProtocolFactory
|
||||
"""
|
||||
self.protocol = protocol
|
||||
|
||||
def resolve_refrence_protocol(self) -> protocol_components.protocol.ProtocolDefinitions:
|
||||
"""Resolves refrences in the protocol definitiown
|
||||
This modifies the protocol definitiown in place and returns it
|
||||
|
||||
Returns:
|
||||
ProtocolDefinitions: Protocol definitiown with refrences resolved
|
||||
"""
|
||||
for name, message in self.protocol.messages.items():
|
||||
self.protocol.messages[name] = self._resolve_refrence_fields_in_message(message)
|
||||
return self.protocol
|
||||
|
||||
def _resolve_refrence_fields_in_message(self, message: Message) -> Message:
|
||||
for name, field in message.fields.items():
|
||||
message.fields[name] = self._resolve_refrence_fields(field, message)
|
||||
return message
|
||||
|
||||
def _resolve_refrence_fields(self, field: Field, parent_message: Message) -> Field:
|
||||
if isinstance(field, FieldBitfieldRef):
|
||||
return self._resolve_refrence_bitfield(field, parent_message)
|
||||
elif isinstance(field, FieldEnumRef):
|
||||
return self._resolve_refrence_enum(field, parent_message)
|
||||
else:
|
||||
return field
|
||||
|
||||
def _resolve_refrence_bitfield(self, field: FieldBitfieldRef, parent_message: Message) -> FieldBitfield:
|
||||
if field.ref not in self.protocol.bitfields:
|
||||
raise BprotoUnresolvedReferenceError(field.ref, field, parent_message)
|
||||
|
||||
factory = FactoryField()
|
||||
return factory.assemble(
|
||||
field.name,
|
||||
field.pos,
|
||||
BprotoFieldBaseType.BITFIELD,
|
||||
field.array_size,
|
||||
self.protocol.bitfields[field.ref]
|
||||
)
|
||||
|
||||
def _resolve_refrence_enum(self, field: FieldEnumRef, parent_message: Message) -> FieldEnum:
|
||||
if field.ref not in self.protocol.enums:
|
||||
raise BprotoUnresolvedReferenceError(field.ref, field, parent_message)
|
||||
|
||||
factory = FactoryField()
|
||||
return factory.assemble(
|
||||
field.name,
|
||||
field.pos,
|
||||
BprotoFieldBaseType.ENUM,
|
||||
field.array_size,
|
||||
self.protocol.enums[field.ref]
|
||||
)
|
||||
|
||||
def back_resolve_inlines(self) -> protocol_components.protocol.ProtocolDefinitions:
|
||||
inline_brand_applyer = BrandApplier("inline")
|
||||
for msg_name, msg in self.protocol.messages.items():
|
||||
for field_name, field in msg.fields.items():
|
||||
if isinstance(field, FieldBitfield) and field.bitfield.inline:
|
||||
field.bitfield.name = inline_brand_applyer.apply(
|
||||
msg.name + field.name
|
||||
)
|
||||
self.protocol.bitfields.update({
|
||||
field.bitfield.name: field.bitfield
|
||||
})
|
||||
|
||||
elif isinstance(field, FieldEnum) and field.enum.inline:
|
||||
field.enum.name = inline_brand_applyer.apply(
|
||||
msg.name + field.name
|
||||
)
|
||||
self.protocol.enums.update({
|
||||
field.enum.name: field.enum
|
||||
})
|
||||
|
||||
return self.protocol
|
||||
75
src/nameHandling/style/cNameStyle.py
Normal file
75
src/nameHandling/style/cNameStyle.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from nameHandling.base import ComponentName, NameStyle, to_cap_case, to_camel_case, to_screaming_case
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
|
||||
import re
|
||||
from typing import Literal
|
||||
|
||||
|
||||
class NameStyleC(NameStyle):
|
||||
|
||||
@staticmethod
|
||||
def toStr(bproto_style_name: str | ComponentName, context:
|
||||
Literal[
|
||||
"", "enum_name", "enum_item",
|
||||
"struct_name", "struct_member",
|
||||
"bitfield_name", "bitfield_member",
|
||||
] = "") -> str:
|
||||
|
||||
match context:
|
||||
case "struct_name":
|
||||
res = to_cap_case(bproto_style_name)
|
||||
case "struct_member":
|
||||
res = to_camel_case(bproto_style_name)
|
||||
case "enum_name":
|
||||
res = to_cap_case(bproto_style_name)
|
||||
case "enum_item":
|
||||
res = to_screaming_case(bproto_style_name)
|
||||
case "bitfield_name":
|
||||
res = to_cap_case(bproto_style_name)
|
||||
case "bitfield_member":
|
||||
res = to_camel_case(bproto_style_name)
|
||||
case _:
|
||||
res = to_camel_case(bproto_style_name)
|
||||
|
||||
res = re.sub(r"_+", "_", res)
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def fromStr(bproto_style_name: str | ComponentName, context: str = "") -> ComponentName:
|
||||
raise NotImplementedError("This mode is not avaialbe for NameStyleC")
|
||||
|
||||
@staticmethod
|
||||
def preprocess(protocol: ProtocolDefinitions) -> ProtocolDefinitions:
|
||||
# Enum Mod
|
||||
protocol.enums = {
|
||||
protocol.name + ComponentName(["_enum_"]) + v.name: v
|
||||
for _, v in protocol.enums.items()
|
||||
}
|
||||
|
||||
for enum_name, e in protocol.enums.items():
|
||||
e.name = enum_name
|
||||
e.values = {
|
||||
enum_name + "value" + k: v
|
||||
for k, v in e.values.items()
|
||||
}
|
||||
|
||||
# Bitfield mod
|
||||
protocol.bitfields = {
|
||||
protocol.name + ComponentName(["_bitfield_"]) + v.name: v
|
||||
for _, v in protocol.bitfields.items()
|
||||
}
|
||||
|
||||
for name, b in protocol.bitfields.items():
|
||||
b.name = name
|
||||
|
||||
# Message mod
|
||||
protocol.messages = {
|
||||
protocol.name + ComponentName(["_message_"]) + v.name: v
|
||||
for _, v in protocol.messages.items()
|
||||
}
|
||||
|
||||
for name, m in protocol.messages.items():
|
||||
m.name = name
|
||||
|
||||
return protocol
|
||||
57
src/nameHandling/style/pythonNameStyle.py
Normal file
57
src/nameHandling/style/pythonNameStyle.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from typing import Literal
|
||||
from nameHandling.base import ComponentName, NameStyle, to_cap_case, to_snake_case, to_screaming_case
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
|
||||
|
||||
class NameStylePython(NameStyle):
|
||||
|
||||
@staticmethod
|
||||
def toStr(bproto_style_name: str | ComponentName, context:
|
||||
Literal[
|
||||
"", "enum_name", "enum_item",
|
||||
"class_name", "class_member",
|
||||
] = "") -> str:
|
||||
|
||||
match context:
|
||||
case "class_name":
|
||||
return to_cap_case(bproto_style_name)
|
||||
case "class_member":
|
||||
return to_snake_case(bproto_style_name)
|
||||
case "enum_name":
|
||||
return to_cap_case(bproto_style_name)
|
||||
case "enum_item":
|
||||
return to_screaming_case(bproto_style_name)
|
||||
case _:
|
||||
return to_snake_case(bproto_style_name)
|
||||
|
||||
@staticmethod
|
||||
def fromStr(bproto_style_name: str | ComponentName, context: str = "") -> ComponentName:
|
||||
raise NotImplementedError("This mode is not avaialbe for NameStylePython")
|
||||
|
||||
@staticmethod
|
||||
def preprocess(protocol: ProtocolDefinitions) -> ProtocolDefinitions:
|
||||
protocol.enums = {
|
||||
protocol.name + "Enum" + v.name: v
|
||||
for _, v in protocol.enums.items()
|
||||
}
|
||||
|
||||
for name, e in protocol.enums.items():
|
||||
e.name = name
|
||||
|
||||
protocol.bitfields = {
|
||||
protocol.name + "Bitfield" + v.name: v
|
||||
for _, v in protocol.bitfields.items()
|
||||
}
|
||||
|
||||
for name, b in protocol.bitfields.items():
|
||||
b.name = name
|
||||
|
||||
protocol.messages = {
|
||||
protocol.name + "Message" + v.name: v
|
||||
for _, v in protocol.messages.items()
|
||||
}
|
||||
|
||||
for name, m in protocol.messages.items():
|
||||
m.name = name
|
||||
|
||||
return protocol
|
||||
157
src/parser/ast_visitor.py
Normal file
157
src/parser/ast_visitor.py
Normal file
@@ -0,0 +1,157 @@
|
||||
from protocol_components import AbstractProtocolComponent
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.field import Field, FactoryField
|
||||
from protocol_components.enumeration import FactoryEnumeration
|
||||
from protocol_components.bitfields import FactoryBitfield
|
||||
from protocol_components.message import Message, FactoryMessage
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
|
||||
from gen.bprotoV1Visitor import bprotoV1Visitor as BprotoVisitor
|
||||
from antlr4 import TerminalNode
|
||||
|
||||
|
||||
class BprotoASTVisitor(BprotoVisitor):
|
||||
# Defintion parsing
|
||||
def visitProtocol_defintion(self, ctx) -> ProtocolDefinitions:
|
||||
factory = FactoryProtocolDefition()
|
||||
|
||||
name, version = self.visit(ctx.protocol_header())
|
||||
|
||||
for message in ctx.message_def():
|
||||
message_object = self.visit(message)
|
||||
factory.add_message(message_object)
|
||||
|
||||
for enum in ctx.enum_def():
|
||||
enum_object = self.visit(enum)
|
||||
factory.add_enum(enum_object)
|
||||
|
||||
for bitfield in ctx.bit_field_def():
|
||||
bitfield_object = self.visit(bitfield)
|
||||
factory.add_bitfield(bitfield_object)
|
||||
|
||||
return factory.assemble(name, version)
|
||||
|
||||
def visitProtocol_header(self, ctx):
|
||||
return ctx.IDENTIFIER().getText(), int(ctx.INT().getText())
|
||||
|
||||
def visitBit_field_def(self, ctx):
|
||||
factory: FactoryBitfield = self.visit(ctx.bitfield_body())
|
||||
name = ctx.IDENTIFIER().getText()
|
||||
return factory.assemble(name)
|
||||
|
||||
def visitEnum_def(self, ctx):
|
||||
factory: FactoryEnumeration = self.visit(ctx.enum_body())
|
||||
name = ctx.IDENTIFIER().getText()
|
||||
return factory.assemble(name)
|
||||
|
||||
# Message definition parsing
|
||||
def visitMessage_def(self, ctx) -> Message:
|
||||
identifier = ctx.IDENTIFIER().getText()
|
||||
index_number = self.visit(ctx.message_id())
|
||||
|
||||
factory = FactoryMessage()
|
||||
|
||||
fields = ctx.field()
|
||||
|
||||
for field in fields:
|
||||
factory.add_field(self.visit(field))
|
||||
|
||||
return factory.assemble(identifier, index_number)
|
||||
|
||||
def visitMessage_id(self, ctx) -> int:
|
||||
return int(ctx.INT().getText())
|
||||
|
||||
def visitField(self, ctx) -> Field:
|
||||
identfier = ctx.IDENTIFIER().getText()
|
||||
pos = self.visit(ctx.field_pos())
|
||||
field_type, array_size, refrence = self.visit(ctx.dtype())
|
||||
|
||||
factory = FactoryField()
|
||||
|
||||
return factory.assemble(
|
||||
identfier,
|
||||
pos,
|
||||
field_type,
|
||||
array_size,
|
||||
refrence
|
||||
)
|
||||
|
||||
def visitField_pos(self, ctx) -> int:
|
||||
return int(ctx.INT().getText())
|
||||
|
||||
# Field Type parsing
|
||||
def visitDtype(self, ctx) -> tuple[BprotoFieldBaseType, int, None | str | AbstractProtocolComponent]:
|
||||
if ctx.type_enum():
|
||||
return self.visit(ctx.type_enum())
|
||||
elif ctx.type_bitfield():
|
||||
return self.visit(ctx.type_bitfield())
|
||||
elif ctx.type_standard():
|
||||
return self.visit(ctx.type_standard())
|
||||
|
||||
def visitType_standard(self, ctx) -> tuple[BprotoFieldBaseType, int, None]:
|
||||
return (
|
||||
ctx.IDENTIFIER().getText(),
|
||||
self.visit(ctx.array_extension())
|
||||
if ctx.array_extension() else 1,
|
||||
None,
|
||||
)
|
||||
|
||||
def visitArray_extension(self, ctx):
|
||||
return int(ctx.INT().getText())
|
||||
|
||||
def visitType_enum(self, ctx) -> tuple[BprotoFieldBaseType, int, AbstractProtocolComponent | str]:
|
||||
if isinstance(ctx.getChild(1), TerminalNode):
|
||||
ref_name = ctx.IDENTIFIER().getText()
|
||||
return BprotoFieldBaseType.ENUM, 1, ref_name
|
||||
else:
|
||||
enum_factory: FactoryEnumeration = self.visit(ctx.enum_body())
|
||||
return BprotoFieldBaseType.ENUM, 1, enum_factory.assemble("", inline=True)
|
||||
|
||||
def visitType_bitfield(self, ctx):
|
||||
if isinstance(ctx.getChild(1), TerminalNode):
|
||||
ref_name = ctx.IDENTIFIER().getText()
|
||||
return BprotoFieldBaseType.BITFIELD, 1, ref_name
|
||||
else:
|
||||
factory: FactoryBitfield = self.visit(ctx.bitfield_body())
|
||||
return BprotoFieldBaseType.BITFIELD, 1, factory.assemble("", inline=True)
|
||||
|
||||
# Enum parsing
|
||||
def visitEnum_body(self, ctx) -> FactoryEnumeration:
|
||||
enumeration_factory = FactoryEnumeration()
|
||||
|
||||
for field in ctx.enum_field():
|
||||
name, value = self.visit(field)
|
||||
enumeration_factory.add_value(name, value)
|
||||
|
||||
return enumeration_factory
|
||||
|
||||
def visitEnum_field(self, ctx):
|
||||
name = ctx.IDENTIFIER().getText()
|
||||
|
||||
if ctx.getChildCount() > 1:
|
||||
value = int(ctx.INT().getText())
|
||||
else:
|
||||
value = None
|
||||
|
||||
return name, value
|
||||
|
||||
# Bit Field parsing
|
||||
def visitBitfield_body(self, ctx):
|
||||
factory = FactoryBitfield()
|
||||
|
||||
for field in ctx.bitfield_field():
|
||||
name, pos = self.visit(field)
|
||||
factory.add_bit(name, pos)
|
||||
|
||||
return factory
|
||||
|
||||
def visitBitfield_field(self, ctx):
|
||||
name = ctx.IDENTIFIER().getText()
|
||||
|
||||
if ctx.getChildCount() > 1:
|
||||
position = int(ctx.INT().getText())
|
||||
else:
|
||||
position = None
|
||||
|
||||
return name, position
|
||||
120
src/parser/parser.py
Normal file
120
src/parser/parser.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from antlr4 import InputStream, CommonTokenStream, ParserRuleContext, DFA
|
||||
from antlr4.error.ErrorListener import ErrorListener
|
||||
|
||||
from gen.bprotoV1Lexer import bprotoV1Lexer as BprotoLexer
|
||||
from gen.bprotoV1Parser import bprotoV1Parser as BprotoParser
|
||||
|
||||
|
||||
class bproto_ErrorListener(ErrorListener):
|
||||
"""
|
||||
Error listener for the ANTLR parser.
|
||||
This handles/collects syntax errors and formats them for the user
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.syntax_error_list: list[tuple[int, int, str, Exception]] = []
|
||||
self.previous_line = 5
|
||||
|
||||
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
|
||||
self.syntax_error_list.append((line, column, msg, e))
|
||||
|
||||
def reportAmbiguity(self, recognizer, dfa: DFA, startIndex,
|
||||
stopIndex, exact, ambigAlts, configs):
|
||||
pass
|
||||
|
||||
def reportAttemptingFullContext(self, recognizer, dfa, startIndex,
|
||||
stopIndex, conflictingAlts, configs):
|
||||
pass
|
||||
|
||||
def reportContextSensitivity(self, recognizer, dfa, startIndex,
|
||||
stopIndex, prediction, configs):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def make_code_preview(
|
||||
source_text: list[str], line: int, column: int) -> str:
|
||||
|
||||
code_window = ""
|
||||
for i in range(max(0, line - 5), line):
|
||||
code_window += f"{i + 1:3d} | {source_text[i]}\n"
|
||||
code_window += " | " + " " * (column) + "^\n"
|
||||
return code_window
|
||||
|
||||
def get_syntax_error_message(self, source_text: str) -> str:
|
||||
"""generates a humman readable error message from the syntax errors
|
||||
|
||||
Args:
|
||||
source_text (str): The source text that was parsed
|
||||
|
||||
Returns:
|
||||
str: The human readable error message (multi-line)
|
||||
"""
|
||||
source_text: list[str] = source_text.split("\n")
|
||||
result_error_message = ""
|
||||
|
||||
for line, column, msg, e in self.syntax_error_list:
|
||||
result_error_message += bproto_ErrorListener.make_code_preview(
|
||||
source_text, line, column
|
||||
)
|
||||
result_error_message += f"line {line} column {column}: {msg}\n\n"
|
||||
|
||||
result_error_message += f"Found {len(self.syntax_error_list)} " \
|
||||
+ "syntax errors"
|
||||
|
||||
return result_error_message
|
||||
|
||||
|
||||
def create_ast_parser(protocol_defintion_text: str, error_message_listner: bproto_ErrorListener) -> BprotoParser:
|
||||
"""Creates an ANTLR parser for the given protocol definition text
|
||||
This function just creates and configures the parser, it does not parse the text
|
||||
|
||||
Args:
|
||||
protocol_defintion_text (str): The protocol definition text from the source files
|
||||
error_message_listner (bproto_ErrorListener): The error listener to collect syntax errors
|
||||
|
||||
Returns:
|
||||
BprotoParser: The configured ANTLR parser, ready to walk the tree
|
||||
"""
|
||||
input_stream = InputStream(protocol_defintion_text)
|
||||
|
||||
antlr_lexer = BprotoLexer(input_stream)
|
||||
antlr_lexer.removeErrorListeners()
|
||||
antlr_lexer.addErrorListener(error_message_listner)
|
||||
|
||||
antlr_token_stream = CommonTokenStream(antlr_lexer)
|
||||
|
||||
antlr_parser = BprotoParser(antlr_token_stream)
|
||||
antlr_parser.removeErrorListeners()
|
||||
antlr_parser.addErrorListener(error_message_listner)
|
||||
|
||||
return antlr_parser
|
||||
|
||||
|
||||
def parse_ast_string(
|
||||
protocol_defintion_text: str
|
||||
) -> ParserRuleContext | bproto_ErrorListener:
|
||||
"""
|
||||
Parse the protocol definition text into an AST.
|
||||
If there are syntax errors, return an error listener with the syntax errors
|
||||
|
||||
Args:
|
||||
protocol_defintion_text (str): The protocol definition text from the
|
||||
source files
|
||||
Returns:
|
||||
ParserRuleContext | bproto_ErrorListener: The parsed AST if successful,
|
||||
otherwise an error listener with syntax errors for reporting
|
||||
"""
|
||||
error_message_listner = bproto_ErrorListener()
|
||||
|
||||
antlr_parser = create_ast_parser(
|
||||
protocol_defintion_text,
|
||||
error_message_listner
|
||||
)
|
||||
|
||||
ast = antlr_parser.protocol_defintion()
|
||||
|
||||
if antlr_parser.getNumberOfSyntaxErrors() > 0:
|
||||
return error_message_listner
|
||||
|
||||
return ast
|
||||
91
src/protocol_components/__init__.py
Normal file
91
src/protocol_components/__init__.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import OrderedDict
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
|
||||
class AbstractProtocolComponent(ABC):
|
||||
@abstractmethod
|
||||
def get_identifier(self) -> int | ComponentName:
|
||||
"""Get the identifier of the protocol component.
|
||||
|
||||
This identifier should be unique and is ment for sorting and indexing.
|
||||
|
||||
Returns:
|
||||
int | str: Identifier of the protocol component
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_name(self) -> ComponentName:
|
||||
"""Get the display name of the protocol component.
|
||||
|
||||
This is ment to display error messages, render into code, etc.
|
||||
Should be human readable and unique.
|
||||
|
||||
Returns:
|
||||
str: Display name of the protocol component
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_type_name(self) -> str:
|
||||
"""Get the type name of the protocol component.
|
||||
|
||||
This is ment to display error messages.
|
||||
Should be human readable and describe the type of the component.
|
||||
|
||||
Returns:
|
||||
str: Type name of the protocol component
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_size_bytes(self) -> int:
|
||||
"""Get the size of the protocol component in bytes.
|
||||
Should be the same as ceil(get_bitsize() / 8).
|
||||
|
||||
Returns:
|
||||
int: Size of the protocol component in bytes
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_size_bits(self) -> int:
|
||||
"""Get the size of the protocol component in bits.
|
||||
Should be the same as get_byte_size() * 8.
|
||||
|
||||
Returns:
|
||||
int: Size of the protocol component in bits
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def sort_dict_by_value(d: dict[str, int]) -> dict[str, int]:
|
||||
"""Sort a dictionary by its values.
|
||||
Values should be something sortable.
|
||||
|
||||
Args:
|
||||
d (dict[str, int]): dictionary to be sorted
|
||||
|
||||
Returns:
|
||||
dict[str,int]: Sorted dictionary
|
||||
"""
|
||||
return OrderedDict(sorted(d.items(), key=lambda x: x[1]))
|
||||
|
||||
|
||||
def find_smallset_next_value(s: set[int], inital=0) -> int:
|
||||
"""Find the next smallest value that is not in the set.
|
||||
Used to fill in missing values in an enumeration or bitfield.
|
||||
|
||||
Args:
|
||||
s (set[int]): Set of values
|
||||
inital (int, optional): Start value to search from. Defaults to 0. For optimization purposes.
|
||||
|
||||
Returns:
|
||||
int: Next smallest value not in the set
|
||||
"""
|
||||
|
||||
i = inital
|
||||
while i in s:
|
||||
i += 1
|
||||
return i
|
||||
167
src/protocol_components/bitfields.py
Normal file
167
src/protocol_components/bitfields.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from . import AbstractProtocolComponent, find_smallset_next_value, sort_dict_by_value
|
||||
from errors import BprotoDuplicateNameError, BprotoDuplicateBitfieldPositionError
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
|
||||
import math
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class Bitfield(AbstractProtocolComponent):
|
||||
"""Represents a bitfield in a protocol.
|
||||
This should not be instantiated directly, but rather through the FactoryBitfield class.
|
||||
Inherit from AbstractProtocolComponent.
|
||||
"""
|
||||
|
||||
def __init__(self, name: ComponentName, inline: bool = False):
|
||||
"""Initialize a Bitfield object; should not be used directly.
|
||||
This methode should only be called by the FactoryBitfield class.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the bitfield, should be unique
|
||||
length (int): Length of the bitfield in bits.
|
||||
inline: If True mark this bitfield as a inline definition
|
||||
"""
|
||||
self.name: ComponentName = name
|
||||
self.length: int = 0
|
||||
self.bits: dict[ComponentName, int] = {}
|
||||
self.inline: bool = inline
|
||||
|
||||
# Inherited from AbstractProtocolComponent
|
||||
def get_identifier(self):
|
||||
return self.name
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_type_name(self):
|
||||
return "bitfield"
|
||||
|
||||
def get_size_bits(self):
|
||||
return self.length
|
||||
|
||||
def get_size_bytes(self):
|
||||
return max(math.ceil(self.get_size_bits() / 8), 1)
|
||||
|
||||
# Methodes specific to this class
|
||||
def apply_naming(self, naming: str):
|
||||
"""Apply a naming scheme to the bitfield.
|
||||
TODO: Is this method needed? REFACTOR!!!
|
||||
"""
|
||||
self.name = f"{naming}_{self.name}"
|
||||
|
||||
def __repr__(self):
|
||||
field_names = ", ".join([f"{name}:{pos}" for name, pos in self.bits.items()])
|
||||
return f"Bitfield({self.name}, {field_names})"
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
new_bitfield = Bitfield(
|
||||
deepcopy(self.name, memo),
|
||||
deepcopy(self.inline, memo)
|
||||
)
|
||||
new_bitfield.length = deepcopy(self.length, memo)
|
||||
new_bitfield.bits = {
|
||||
deepcopy(k, memo): deepcopy(v, memo)
|
||||
for k, v in self.bits.items()
|
||||
}
|
||||
|
||||
memo[id(self)] = new_bitfield
|
||||
memo[id(new_bitfield)] = new_bitfield
|
||||
return new_bitfield
|
||||
|
||||
|
||||
class FactoryBitfield:
|
||||
"""Factory class to create Bitfield objects.
|
||||
Used to assemble a Bitfield object from its parts.
|
||||
|
||||
This class should be used to create Bitfield objects.
|
||||
Its a one-time use class, so create a new instance for each Bitfield object.
|
||||
|
||||
This Factory is ment to be used during traversal of the abstract syntax tree.
|
||||
|
||||
Simular to the FactoryEnumeration class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.bits_list: list[tuple[str, int | None]] = []
|
||||
|
||||
def add_bit(self, name: str, position: int | None):
|
||||
"""Add a bit to the bitfield.
|
||||
Used to build the Bitfield object.
|
||||
|
||||
Duplication checks are not done here, but in the assemble
|
||||
|
||||
Args:
|
||||
name (str): Name of the bit
|
||||
position (int | None): Position of the bit in the bitfield. If None, the position will be filled in later.
|
||||
"""
|
||||
self.bits_list.append((NameStyleBproto.fromStr(name), position))
|
||||
|
||||
@staticmethod
|
||||
def calculate_bitsize(bits: dict[int, str]):
|
||||
"""Given a dictionary of bit names and their positions, calculate the bitsize of the bitfield.
|
||||
Its just a wrapper around the max methode to be honest.
|
||||
|
||||
Args:
|
||||
bits (dict[int, str]): Dictionary of bit names and their positions
|
||||
|
||||
Returns:
|
||||
int: Number of bits needed to represent the bit
|
||||
"""
|
||||
return max(bits.values(), default=0) + 1
|
||||
|
||||
def assemble(self, name: str, inline: bool = False):
|
||||
"""Finalize the Bitfield object and return it.
|
||||
This method should be called after adding all bits to the bitfield.
|
||||
This will:
|
||||
1. Check for duplicate names and positions
|
||||
2. Fill in missing positions
|
||||
3. Calculate the bitsize of the bitfield
|
||||
4. Sort the dictionary by value
|
||||
|
||||
(Very simular to the FactoryEnumeration.assemble method)
|
||||
|
||||
Args:
|
||||
name (str): Name of the bitfield, should be unique
|
||||
inline (bool): If true marks the resulting bitfield as inline
|
||||
|
||||
Raises:
|
||||
BprotoDuplicateBitfieldPositionError: Thrown when a bitfield position is duplicated
|
||||
BprotoDuplicateNameError: Thrown when a bitfield name is duplicated
|
||||
|
||||
Returns:
|
||||
_type_: The finalized Bitfield object
|
||||
"""
|
||||
resulting_bitfield = Bitfield(NameStyleBproto.fromStr(name), inline=inline)
|
||||
bits: dict[ComponentName, int | None] = {}
|
||||
|
||||
bit_names: set[ComponentName] = set()
|
||||
bit_positions = set()
|
||||
|
||||
# Check for duplicates in name or value
|
||||
for name, pos in self.bits_list:
|
||||
if pos is not None and pos in bit_positions:
|
||||
raise BprotoDuplicateBitfieldPositionError(pos, resulting_bitfield)
|
||||
if name in bit_names:
|
||||
raise BprotoDuplicateNameError(name, resulting_bitfield)
|
||||
|
||||
if pos is not None:
|
||||
bit_positions.add(pos)
|
||||
|
||||
bits[name] = pos
|
||||
bit_names.add(name)
|
||||
|
||||
# Fill in missing values
|
||||
for name, pos in self.bits_list:
|
||||
if pos is None:
|
||||
pos = find_smallset_next_value(bit_positions)
|
||||
bit_positions.add(pos)
|
||||
bits[name] = pos
|
||||
|
||||
# Sort the dictionary by value
|
||||
resulting_bitfield.length = FactoryBitfield.calculate_bitsize(bits)
|
||||
resulting_bitfield.bits = sort_dict_by_value(bits)
|
||||
|
||||
return resulting_bitfield
|
||||
22
src/protocol_components/crc.py
Normal file
22
src/protocol_components/crc.py
Normal file
@@ -0,0 +1,22 @@
|
||||
CRC_SIZE = 2
|
||||
|
||||
|
||||
def crc16_calc_inital(data: bytearray) -> int:
|
||||
"""CRC-16/CITT-FALSE
|
||||
from https://stackoverflow.com/questions/35205702/calculating-crc16-in-python
|
||||
|
||||
Args:
|
||||
data (bytearray): protocol hash
|
||||
|
||||
Returns:
|
||||
bytes: initals for crc16_calc_with_initial
|
||||
"""
|
||||
crc = 0xFFFF
|
||||
for i in range(0, len(data)):
|
||||
crc ^= data[i] << 8
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x8000) > 0:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
return crc & 0xFFFF
|
||||
153
src/protocol_components/dtypes.py
Normal file
153
src/protocol_components/dtypes.py
Normal file
@@ -0,0 +1,153 @@
|
||||
import enum
|
||||
from . import AbstractProtocolComponent
|
||||
from errors import BprotoFrontendError
|
||||
|
||||
|
||||
class BprotoFieldBaseType(enum.Enum):
|
||||
BOOL = "bool"
|
||||
CHAR = "char"
|
||||
STRING = "string"
|
||||
|
||||
FLOAT32 = "float32"
|
||||
FLOAT64 = "float64"
|
||||
|
||||
INT8 = "int8"
|
||||
INT16 = "int16"
|
||||
INT32 = "int32"
|
||||
INT64 = "int64"
|
||||
|
||||
UINT8 = "uint8"
|
||||
UINT16 = "uint16"
|
||||
UINT32 = "uint32"
|
||||
UINT64 = "uint64"
|
||||
|
||||
ENUM = "enum"
|
||||
BITFIELD = "bitfield"
|
||||
|
||||
|
||||
class ArrayPolicy(enum.Enum):
|
||||
"""Enum respresenting the options for policy, regarding if an datatype is an array or not.
|
||||
Options: DISALLOW, ALLOW, FORCE
|
||||
"""
|
||||
DISALLOW = 0
|
||||
ALLOW = 1
|
||||
FORCE = 2
|
||||
|
||||
|
||||
DTYPE_SIZE_MAP = {
|
||||
BprotoFieldBaseType.UINT8: 1,
|
||||
BprotoFieldBaseType.UINT16: 2,
|
||||
BprotoFieldBaseType.UINT32: 4,
|
||||
BprotoFieldBaseType.UINT64: 8,
|
||||
|
||||
BprotoFieldBaseType.INT8: 1,
|
||||
BprotoFieldBaseType.INT16: 2,
|
||||
BprotoFieldBaseType.INT32: 4,
|
||||
BprotoFieldBaseType.INT64: 8,
|
||||
|
||||
BprotoFieldBaseType.FLOAT32: 4,
|
||||
BprotoFieldBaseType.FLOAT64: 8,
|
||||
|
||||
BprotoFieldBaseType.BOOL: 1,
|
||||
BprotoFieldBaseType.CHAR: 1,
|
||||
BprotoFieldBaseType.STRING: 1,
|
||||
}
|
||||
|
||||
DTYPE_ARRAY_POLICY_MAP = {
|
||||
BprotoFieldBaseType.UINT8: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.UINT16: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.UINT32: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.UINT64: ArrayPolicy.ALLOW,
|
||||
|
||||
BprotoFieldBaseType.INT8: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.INT16: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.INT32: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.INT64: ArrayPolicy.ALLOW,
|
||||
|
||||
BprotoFieldBaseType.FLOAT32: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.FLOAT64: ArrayPolicy.ALLOW,
|
||||
|
||||
BprotoFieldBaseType.BOOL: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.CHAR: ArrayPolicy.ALLOW,
|
||||
BprotoFieldBaseType.STRING: ArrayPolicy.FORCE,
|
||||
|
||||
BprotoFieldBaseType.ENUM: ArrayPolicy.DISALLOW,
|
||||
BprotoFieldBaseType.BITFIELD: ArrayPolicy.DISALLOW,
|
||||
}
|
||||
|
||||
|
||||
def validate_datatype(dtype: str, array_size: int, field: AbstractProtocolComponent) -> tuple[BprotoFieldBaseType, int]:
|
||||
"""Given a string and array size, validate the datatype and array size.
|
||||
Valid datatypes are defined in BprotoFieldBaseType enum.
|
||||
Array size is validated based on the datatype and the array policy.
|
||||
|
||||
Args:
|
||||
dtype (str): string representation of the datatype
|
||||
array_size (int): size of the array
|
||||
field (AbstractProtocolComponent): Datatypes parent field, for error reporting
|
||||
|
||||
Raises:
|
||||
BprotoUnknownDataTypeError: Throws when the datatype is unknown
|
||||
ValueError: Thrown when something went wrong, this should not happen, like an unimplemented array policy. This is not user error, but a developer error.
|
||||
BprotoArrayPolicyViolationError: When array policy is violated: E.g. an Datatype is declared as an array, but the policy is disallowing it.
|
||||
|
||||
Returns:
|
||||
tuple[BprotoFieldBaseType,int]: (datatype as enum, array size)
|
||||
"""
|
||||
|
||||
if dtype not in BprotoFieldBaseType:
|
||||
raise BprotoUnknownDataTypeError(dtype)
|
||||
|
||||
dtype: BprotoFieldBaseType = BprotoFieldBaseType(dtype)
|
||||
|
||||
array_policy = DTYPE_ARRAY_POLICY_MAP.get(dtype, None)
|
||||
|
||||
if array_policy is None:
|
||||
raise ValueError("Internal Error: Array policy for this type was not found, fix this!")
|
||||
|
||||
match(array_policy):
|
||||
case ArrayPolicy.ALLOW:
|
||||
pass
|
||||
case ArrayPolicy.DISALLOW:
|
||||
if array_size > 1:
|
||||
raise BprotoArrayPolicyViolationError(array_policy, dtype, field)
|
||||
case ArrayPolicy.FORCE:
|
||||
# This is fine, an array size of 1 is allowed
|
||||
pass
|
||||
case _:
|
||||
raise ValueError("Internal Error: Array policy not implemented!")
|
||||
|
||||
return dtype, array_size
|
||||
|
||||
|
||||
# Expections
|
||||
class BprotoUnknownDataTypeError(BprotoFrontendError):
|
||||
"""Error raise when building a field and the datatype is unknown.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, data_type: str):
|
||||
self.data_type = data_type
|
||||
super().__init__(f"Unknown data type {data_type}!")
|
||||
|
||||
|
||||
class BprotoArrayPolicyViolationError(BprotoFrontendError):
|
||||
"""Error raise when building a field and the array policy is violated.
|
||||
This error should be use during the frontend compilation state.
|
||||
|
||||
Inherited from BprotoFrontendError.
|
||||
"""
|
||||
def __init__(self, policy: ArrayPolicy, dtype: BprotoFieldBaseType, field: AbstractProtocolComponent):
|
||||
self.policy = policy
|
||||
self.field = field
|
||||
|
||||
match(policy):
|
||||
case ArrayPolicy.ALLOW:
|
||||
raise ValueError("Internal Error: Array policy ALLOW should not be violated!")
|
||||
case ArrayPolicy.FORCE:
|
||||
super().__init__(f"{field.get_name()} of type {dtype} must be an array!")
|
||||
case ArrayPolicy.DISALLOW:
|
||||
super().__init__(f"{field.get_name()} of type {dtype} must not be an array!")
|
||||
case _:
|
||||
raise ValueError("Internal Error: Array policy not implemented!")
|
||||
232
src/protocol_components/enumeration.py
Normal file
232
src/protocol_components/enumeration.py
Normal file
@@ -0,0 +1,232 @@
|
||||
from . import AbstractProtocolComponent, find_smallset_next_value, sort_dict_by_value
|
||||
from .dtypes import DTYPE_SIZE_MAP, BprotoFieldBaseType
|
||||
from errors import BprotoDuplicateNameError, BprotoDuplicateEnumValueError, BprotoEnumBitsizeTooLargeError
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
|
||||
import math
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class Enumeration(AbstractProtocolComponent):
|
||||
"""Represents an enumeration in a protocol.
|
||||
This should not be instantiated directly, but rather through the FactoryEnumeration class.
|
||||
|
||||
Inherit from AbstractProtocolComponent.
|
||||
"""
|
||||
|
||||
def __init__(self, name: ComponentName, bitsize: int = 0, inline: bool = False):
|
||||
"""Initialize an Enumeration object; should not be used directly.
|
||||
|
||||
This methode should only be called by the FactoryEnumeration class.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the enumeration, should be unique
|
||||
bitsize (int, optional): How many bits are need to represent the largest value? Defaults to 0.
|
||||
inline (bool): If true marks this enum as inline defintion for later renaming
|
||||
"""
|
||||
self.name: ComponentName = name
|
||||
self.values: dict[ComponentName, int] = {}
|
||||
self.bitsize: int = bitsize
|
||||
self.inline: bool = inline
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
new_enum = Enumeration(
|
||||
self.name,
|
||||
self.bitsize,
|
||||
self.inline
|
||||
)
|
||||
|
||||
new_enum.name = deepcopy(self.name, memo)
|
||||
new_enum.bitsize = deepcopy(self.bitsize, memo)
|
||||
new_enum.inline = deepcopy(self.inline, memo)
|
||||
|
||||
new_enum.values = deepcopy(self.values, memo)
|
||||
|
||||
memo[id(self)] = new_enum
|
||||
return new_enum
|
||||
|
||||
# Inherited from AbstractProtocolComponent
|
||||
def get_identifier(self):
|
||||
return self.name
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_type_name(self):
|
||||
return "enum"
|
||||
|
||||
def get_size_bits(self):
|
||||
"""Returns the bits used to represent the enumeration.
|
||||
|
||||
Returns:
|
||||
int: Number of bits used to represent the enumeration
|
||||
"""
|
||||
return self.bitsize
|
||||
|
||||
def get_size_bytes(self):
|
||||
"""Return the number of bytes needed to represent the enumeration.
|
||||
|
||||
Simular to get_bitsize, but returns the number of bytes.
|
||||
Formula: ceil(bitsize / 8)
|
||||
|
||||
Returns:
|
||||
int: Number of bytes needed to represent the enumeration
|
||||
"""
|
||||
return math.ceil(self.bitsize / 8)
|
||||
|
||||
# Methodes specific to this class
|
||||
def apply_naming(self, naming: str):
|
||||
"""TODO: Is this method needed? REFACTOR!!!
|
||||
|
||||
Args:
|
||||
naming (str): _description_
|
||||
"""
|
||||
self.name = f"{naming}_{self.name}"
|
||||
|
||||
def map_auxiliary_datatypes(self) -> BprotoFieldBaseType:
|
||||
"""Return the bproto datatype that best represents the enumeration.
|
||||
|
||||
There is a theoretical limit of 64 bits for an enumeration.
|
||||
TODO: Do we need to handle this case? Probably...
|
||||
|
||||
Returns:
|
||||
BprotoFieldBaseType: Datatype to represent the enumeration
|
||||
"""
|
||||
bytes_size = self.get_size_bytes()
|
||||
|
||||
if bytes_size <= 1:
|
||||
return BprotoFieldBaseType.UINT8
|
||||
elif bytes_size <= 2:
|
||||
return BprotoFieldBaseType.UINT16
|
||||
elif bytes_size <= 4:
|
||||
return BprotoFieldBaseType.UINT32
|
||||
elif bytes_size <= 8:
|
||||
return BprotoFieldBaseType.UINT64
|
||||
else:
|
||||
raise BprotoEnumBitsizeTooLargeError(self)
|
||||
|
||||
def __repr__(self):
|
||||
value_names = ", ".join([
|
||||
f"{name}={val}"
|
||||
for name, val in self.values.items()
|
||||
])
|
||||
return f"Enum({self.name}, {value_names})"
|
||||
|
||||
|
||||
class FactoryEnumeration:
|
||||
"""Factory class for creating Enumeration objects.
|
||||
Used to assemble Enumerations from key-value pairs.
|
||||
|
||||
This is a one-time use factory. After calling assemble, the object should not be used anymore.
|
||||
|
||||
This Factory is ment to be used during traversale of the abstract syntax tree.
|
||||
|
||||
Simular to the FactoryBitfield class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.key_value_pairs: list[tuple[NameStyleBproto, int]] = []
|
||||
|
||||
def add_value(self, name: str, value: int | None):
|
||||
"""Add a key-value pair to the enumeration.
|
||||
Used to build up the enumeration.
|
||||
|
||||
Duplication checks are not done here, but in the assemble
|
||||
|
||||
Args:
|
||||
name (str): Name of the value
|
||||
value (int | None): Value of the enumeration. If None, the value will be filled in later.
|
||||
"""
|
||||
self.key_value_pairs.append((NameStyleBproto.fromStr(name, "enum"), value))
|
||||
|
||||
@staticmethod
|
||||
def calculate_bitsize(values: list[int]) -> int:
|
||||
"""Given a list of unique values, calculate the bitsize needed to represent the largest value/ amount of values.
|
||||
For empty lists, it will return 1.
|
||||
|
||||
Args:
|
||||
values (list[int]): List of UNIQUE values, usually a list values of an enumeration
|
||||
|
||||
Returns:
|
||||
int: Number of bits needed to represent the values
|
||||
"""
|
||||
|
||||
if len(values) == 0:
|
||||
return 1
|
||||
|
||||
max_value = max(values)
|
||||
|
||||
if max_value <= 8589934592:
|
||||
return math.ceil(math.log2(max_value + 1))
|
||||
else:
|
||||
# This is a bit of a hack, but it works for now
|
||||
# The problem is that the log2 function is not accurate enough for large numbers
|
||||
# log2(2**64) = log2(2**64 - 1) = log2(2**64 + 1) = 64
|
||||
# And this makes in this case a difference of 1 bit
|
||||
if max_value <= 18446744073709551615:
|
||||
return 64
|
||||
|
||||
return 128
|
||||
|
||||
def assemble(self, name: ComponentName, inline: bool = False) -> Enumeration:
|
||||
"""Finalize the enumeration and create an Enumeration object.
|
||||
It uses the key-value pairs added with add_values earlier.
|
||||
This will:
|
||||
1. Check for duplicates in name or value
|
||||
2. Fill in missing values
|
||||
3. Sort the dictionary by value
|
||||
4. Calculate the bitsize of the enumeration and ensure it is within the protocol specs
|
||||
|
||||
After calling this method, the FactoryEnumeration object should not be used anymore.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the enumeration, should be unique
|
||||
inline (bool): if True marks this enum as aline
|
||||
|
||||
Raises:
|
||||
BprotoDuplicateNameError: Error when a name of value is duplicated
|
||||
BprotoDuplicateEnumValueError: Error when a value is duplicated
|
||||
BprotoEnumBitsizeTooLargeError: Error when the bitsize of the enumeration is too large for the protocol specs
|
||||
|
||||
Returns:
|
||||
Enumeration: Finished enumeration object to be used in the backend processing step
|
||||
"""
|
||||
enum_dict = {}
|
||||
used_values = set()
|
||||
resulting_enum = Enumeration(NameStyleBproto.fromStr(name), 0, inline=inline)
|
||||
|
||||
# Check for duplicates in name or value
|
||||
for key, value in self.key_value_pairs:
|
||||
if key in enum_dict:
|
||||
raise BprotoDuplicateNameError(key, resulting_enum)
|
||||
if value in used_values and value is not None:
|
||||
raise BprotoDuplicateEnumValueError(value, resulting_enum)
|
||||
|
||||
if value is not None:
|
||||
used_values.add(value)
|
||||
|
||||
enum_dict.update({key: value})
|
||||
|
||||
# Fill in missing values
|
||||
search_start = 0
|
||||
for key, value in enum_dict.items():
|
||||
if value is None:
|
||||
value = find_smallset_next_value(used_values, search_start)
|
||||
search_start = value + 1
|
||||
used_values.add(value)
|
||||
enum_dict[key] = value
|
||||
|
||||
# Setting a few last properties
|
||||
resulting_enum.values = sort_dict_by_value(enum_dict)
|
||||
resulting_enum.bitsize = FactoryEnumeration.calculate_bitsize(list(used_values))
|
||||
|
||||
# Getting the best datatype to represent the enumeration
|
||||
# This will raise an error if the enumeration is too large
|
||||
dtype = resulting_enum.map_auxiliary_datatypes()
|
||||
|
||||
# Mapping datatype to its bitsize
|
||||
resulting_enum.bitsize = DTYPE_SIZE_MAP[dtype] * 8
|
||||
return resulting_enum
|
||||
337
src/protocol_components/field.py
Normal file
337
src/protocol_components/field.py
Normal file
@@ -0,0 +1,337 @@
|
||||
from .dtypes import BprotoFieldBaseType, DTYPE_SIZE_MAP, validate_datatype
|
||||
from .bitfields import Bitfield
|
||||
from .enumeration import Enumeration
|
||||
from . import AbstractProtocolComponent
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class Field(AbstractProtocolComponent):
|
||||
"""Represents a field in a protocol message.
|
||||
|
||||
This object should not be instantiated directly, but rather through the FactoryField class.
|
||||
This is done during the frontend parsing of the protocol definition.
|
||||
|
||||
Extra classes exist for bitfields and enumerations / their references.
|
||||
|
||||
Inherit from AbstractProtocolComponent.
|
||||
"""
|
||||
def __init__(self,
|
||||
name: ComponentName,
|
||||
pos: int,
|
||||
dtype: BprotoFieldBaseType,
|
||||
array_size: int
|
||||
):
|
||||
"""Initialize a Field object; should not be used directly. Use the FactoryField class.
|
||||
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the field, should be unique
|
||||
pos (int): Position of the field in the message
|
||||
dtype (BprotoFieldBaseType): Datatype of the field, this should already be validated
|
||||
array_size (int): Size of the array, 1 for scalar fields
|
||||
"""
|
||||
self.name: ComponentName = name
|
||||
self.pos = pos
|
||||
self.type: BprotoFieldBaseType = dtype
|
||||
self.array_size: int = array_size
|
||||
self.ref = None
|
||||
|
||||
def resolve_reference(self,
|
||||
enum_dict: dict[ComponentName, Enumeration],
|
||||
bitfield_dict: dict[ComponentName, Bitfield]
|
||||
) -> AbstractProtocolComponent:
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
enum_dict (dict[ComponentName, Enumeration]): Dictoray
|
||||
bitfield_dict (dict[ComponentName, Bitfield]): _description_
|
||||
|
||||
Returns:
|
||||
AbstractProtocolComponent: Return the resolved reference object
|
||||
"""
|
||||
return self
|
||||
|
||||
# Inherited from AbstractProtocolComponent
|
||||
def get_identifier(self) -> ComponentName:
|
||||
return self.name
|
||||
|
||||
def get_name(self) -> ComponentName:
|
||||
return self.name
|
||||
|
||||
def get_type_name(self) -> str:
|
||||
return "field"
|
||||
|
||||
def get_size_bytes(self) -> int:
|
||||
return DTYPE_SIZE_MAP[self.type] * self.array_size
|
||||
|
||||
def get_size_bits(self) -> int:
|
||||
return DTYPE_SIZE_MAP[self.type] * self.array_size * 8
|
||||
|
||||
def get_base_size_bytes(self) -> int:
|
||||
return DTYPE_SIZE_MAP[self.type]
|
||||
|
||||
# Dunder methods
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}[{self.pos}]({self.name}, {self.type}, {self.array_size})" + (f" ref:{self.ref}" if self.ref is not None else "")
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
new_field = Field(
|
||||
deepcopy(self.name, memo),
|
||||
deepcopy(self.pos, memo),
|
||||
deepcopy(self.type, memo),
|
||||
deepcopy(self.array_size, memo)
|
||||
)
|
||||
new_field.ref = deepcopy(self.ref, memo)
|
||||
|
||||
memo[id(self)] = new_field
|
||||
return new_field
|
||||
|
||||
|
||||
# Bitfield Fields
|
||||
|
||||
class FieldBitfield(Field):
|
||||
"""Field object specifically for inline bitfields.
|
||||
This object should not be instantiated directly, but rather through the FactoryField class.
|
||||
|
||||
Similar to FieldEnum, but for bitfields.
|
||||
|
||||
Inherit from Field and behaves like a Field object.
|
||||
"""
|
||||
def __init__(self, name: ComponentName, pos: int, bitfield: Bitfield):
|
||||
"""Should not be used directly. Use the FactoryField class.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the field, should be unique
|
||||
pos (int): Position of the field in the message
|
||||
bitfield (Bitfield): Bitfield object that this field represents
|
||||
"""
|
||||
super().__init__(name, pos, BprotoFieldBaseType.BITFIELD, 1)
|
||||
self.bitfield: Bitfield = bitfield
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
new_field = FieldBitfield(
|
||||
deepcopy(self.name, memo),
|
||||
deepcopy(self.pos, memo),
|
||||
deepcopy(self.bitfield, memo)
|
||||
)
|
||||
new_field.type = deepcopy(self.type, memo)
|
||||
new_field.array_size = deepcopy(self.array_size, memo)
|
||||
|
||||
memo[id(self)] = new_field
|
||||
return new_field
|
||||
|
||||
def get_size_bytes(self) -> int:
|
||||
return self.bitfield.get_size_bytes()
|
||||
|
||||
def get_size_bits(self):
|
||||
return self.bitfield.get_bitsize()
|
||||
|
||||
def get_base_size_bytes(self) -> int:
|
||||
return self.bitfield.get_size_bytes()
|
||||
|
||||
|
||||
class FieldBitfieldRef(Field):
|
||||
"""Field object specifically for bitfield references.
|
||||
This object should not be instantiated directly, but rather through the FactoryField class.
|
||||
|
||||
The reference is the name of the bitfield that this field represents.
|
||||
This refrence is not validated.
|
||||
An object of this class should be eventually resolve to a FieldBitfield object.
|
||||
|
||||
Similar to FieldRefEnum, but for bitfield references.
|
||||
|
||||
Inherit from Field and behaves like a Field object.
|
||||
"""
|
||||
def __init__(self, name: ComponentName, pos: int, bitfield: ComponentName):
|
||||
"""Should not be used directly. Use the FactoryField class.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the field, should be unique
|
||||
pos (int): Position of the field in the message
|
||||
bitfield (ComponentName): Name of the bitfield that this field references
|
||||
"""
|
||||
super().__init__(name, pos, BprotoFieldBaseType.BITFIELD, 1)
|
||||
self.ref: ComponentName = bitfield
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return super().__deepcopy__(memo)
|
||||
|
||||
def get_size_bytes(self):
|
||||
return 0
|
||||
|
||||
def get_size_bits(self):
|
||||
return 0
|
||||
|
||||
def get_base_size_bytes(self) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# Enum Fields
|
||||
|
||||
|
||||
class FieldEnum(Field):
|
||||
"""Field object specifically for inline enumerations.
|
||||
This object should not be instantiated directly, but rather through the FactoryField class.
|
||||
|
||||
Similar to FieldBitfield, but for enumerations.
|
||||
|
||||
Inherit from Field and behaves like a Field object.
|
||||
"""
|
||||
def __init__(self, name: ComponentName, pos: int, enum: Enumeration):
|
||||
"""Should not be used directly. Use the FactoryField class.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the field, should be unique
|
||||
pos (int): Position of the field in the message
|
||||
enum (Enumeration): Enumeration object that this field represents
|
||||
"""
|
||||
super().__init__(name, pos, BprotoFieldBaseType.ENUM, 1)
|
||||
self.enum: Enumeration = enum
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
new_field = FieldEnum(
|
||||
deepcopy(self.name, memo),
|
||||
deepcopy(self.pos, memo),
|
||||
deepcopy(self.enum, memo)
|
||||
)
|
||||
new_field.type = deepcopy(self.type, memo)
|
||||
new_field.array_size = deepcopy(self.array_size, memo)
|
||||
|
||||
memo[id(self)] = new_field
|
||||
return new_field
|
||||
|
||||
def get_size_bytes(self) -> int:
|
||||
return self.enum.get_size_bytes()
|
||||
|
||||
def get_size_bits(self) -> int:
|
||||
return self.enum.get_bitsize()
|
||||
|
||||
def get_base_size_bytes(self) -> int:
|
||||
return self.enum.get_size_bytes()
|
||||
|
||||
|
||||
class FieldEnumRef(Field):
|
||||
"""Field object specifically for enumeration references.
|
||||
This object should not be instantiated directly, but rather through the FactoryField class.
|
||||
|
||||
The reference is the name of the enumeration that this field represents.
|
||||
This refrence is not validated.
|
||||
An object of this class should be eventually resolve to a BprotoFieldEnum object.
|
||||
|
||||
Similar to FieldRefBitfield, but for enumerations.
|
||||
|
||||
Inherit from Field and behaves like a Field object.
|
||||
"""
|
||||
def __init__(self, name: ComponentName, pos: int, enum: ComponentName):
|
||||
"""Should not be used directly. Use the FactoryField class.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the field, should be unique
|
||||
pos (int): Position of the field in the message
|
||||
enum (ComponentName): Name of the enumeration that this field references
|
||||
"""
|
||||
super().__init__(name, pos, BprotoFieldBaseType.ENUM, 1)
|
||||
self.ref: ComponentName = enum
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return super().__deepcopy__(memo)
|
||||
|
||||
def get_size_bytes(self):
|
||||
return 0
|
||||
|
||||
def get_size_bits(self):
|
||||
return 0
|
||||
|
||||
def get_base_size_bytes(self) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# Factory class
|
||||
class FactoryField():
|
||||
"""Factory class for building Field objects representing a bproto field.
|
||||
This is ment to be used during the frontend compilation stage, during the traversal of the abstract syntax tree.
|
||||
|
||||
Its a one-time use class, so create a new instance for each Field object.
|
||||
|
||||
This class is compleatly statless (yet)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the FactoryField object. Nothing to do here yet.
|
||||
"""
|
||||
pass
|
||||
|
||||
def assemble(self,
|
||||
name: str,
|
||||
pos: int,
|
||||
dtype: str,
|
||||
array_size: int,
|
||||
ref: None | str | Bitfield | Enumeration) -> Field:
|
||||
"""Finalize the field object and return it. Builds Enum and Bitfield Field objects if needed.
|
||||
|
||||
If the datatype is a bitfield or enumeration, the reference object should be provided.
|
||||
The reference object should be a Bitfield or Enumeration object, or the name of the object.
|
||||
Acorrding to the reference (and datatype), the correct specialized Field object is created.
|
||||
Otherwise, the reference should be None.
|
||||
|
||||
If the datatype is a standard type, a Field object is created.
|
||||
|
||||
This methode will:
|
||||
1. Validate the datatype and array size (array policy)
|
||||
2. Create the correct Field object based on the datatype and reference
|
||||
|
||||
Args:
|
||||
name (str): Name of the field, should be unique
|
||||
pos (int): Position of the field in the message
|
||||
dtype (str): Datatype of the field in string form
|
||||
array_size (int): Size of the array, 1 for scalar fields
|
||||
ref (None | str | Bitfield | Enumeration): For bitfields and enumerations, the reference object / reference name
|
||||
|
||||
Raises:
|
||||
ValueError: Thrown when a reference type is provided that is neither a string nor a Bitfield or Enumeration object, or when an unimplemented array policy is encountered.
|
||||
BprotoUnknownDataTypeError: Throws when the datatype is unknown
|
||||
BprotoArrayPolicyViolationError: When array policy is violated: E.g. an Datatype is declared as an array, but the policy is disallowing it.
|
||||
|
||||
Returns:
|
||||
Field: The finalized Field object, can also be a specialized Field object like FieldBitfield or BprotoFieldEnum
|
||||
"""
|
||||
converted_name = NameStyleBproto.fromStr(name)
|
||||
converted_ref = NameStyleBproto.fromStr(ref) if isinstance(ref, str) else ref
|
||||
|
||||
resulting_field = Field(
|
||||
converted_name,
|
||||
pos,
|
||||
dtype,
|
||||
array_size
|
||||
)
|
||||
dtype, array_size = validate_datatype(dtype, array_size, resulting_field)
|
||||
|
||||
if dtype == BprotoFieldBaseType.BITFIELD:
|
||||
if isinstance(ref, Bitfield):
|
||||
resulting_field = FieldBitfield(converted_name, pos, ref)
|
||||
elif isinstance(ref, str):
|
||||
resulting_field = FieldBitfieldRef(converted_name, pos, converted_ref)
|
||||
else:
|
||||
raise ValueError("Internal Error: Invalid reference type for bitfield")
|
||||
elif dtype == BprotoFieldBaseType.ENUM:
|
||||
if isinstance(ref, Enumeration):
|
||||
resulting_field = FieldEnum(converted_name, pos, ref)
|
||||
elif isinstance(ref, str):
|
||||
resulting_field = FieldEnumRef(converted_name, pos, converted_ref)
|
||||
else:
|
||||
raise ValueError("Internal Error: Invalid reference type for enum")
|
||||
else:
|
||||
resulting_field = Field(converted_name, pos, dtype, array_size)
|
||||
|
||||
return resulting_field
|
||||
144
src/protocol_components/message.py
Normal file
144
src/protocol_components/message.py
Normal file
@@ -0,0 +1,144 @@
|
||||
from .field import Field
|
||||
from . import AbstractProtocolComponent
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from errors import BprotoDuplicateNameError, BprotoMessageIDAlreadyUsed
|
||||
|
||||
from copy import deepcopy
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class Message(AbstractProtocolComponent):
|
||||
"""Representation of a bproto message.
|
||||
Should not be instantiated directly, but rather through the FactoryMessage class.
|
||||
|
||||
Contains a dict of fields, where the key is the field name and the value is the Field object.
|
||||
|
||||
Inherit from AbstractProtocolComponent
|
||||
"""
|
||||
def __init__(self, name: ComponentName, index_number: int):
|
||||
"""Should not be used directly. Use the FactoryMessage class.
|
||||
|
||||
Args:
|
||||
name (str): Name of the message, should be unique
|
||||
index_number (int): Index number of message, used for ordering, should be unique
|
||||
"""
|
||||
# field: {name: (type, array_size)}
|
||||
self.name: ComponentName = name
|
||||
self.message_index_number = index_number
|
||||
|
||||
self.fields: dict[ComponentName, Field] = {}
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
new_message = Message(
|
||||
deepcopy(self.name, memo),
|
||||
deepcopy(self.message_index_number, memo)
|
||||
)
|
||||
new_message.fields = {
|
||||
deepcopy(k, memo): deepcopy(v, memo)
|
||||
for k, v in self.fields.items()
|
||||
}
|
||||
|
||||
memo[id(self)] = new_message
|
||||
return new_message
|
||||
|
||||
# Inherited from AbstractProtocolComponent
|
||||
def get_identifier(self):
|
||||
return self.message_index_number
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_type_name(self):
|
||||
return "message"
|
||||
|
||||
def get_size_bytes(self) -> int:
|
||||
return sum([field.get_size_bytes() for field in self.fields.values()])
|
||||
|
||||
def get_size_bits(self) -> int:
|
||||
return sum([field.get_size_bits() for field in self.fields.values()])
|
||||
|
||||
# Methodes specific to this class
|
||||
def apply_naming(self, naming: str):
|
||||
"""Refactor this, should not be used.
|
||||
|
||||
Args:
|
||||
naming (str): _description_
|
||||
"""
|
||||
self.name = f"{naming}_{self.name}"
|
||||
|
||||
|
||||
class FactoryMessage():
|
||||
"""Factory class for build Message objects representing a bproto message.
|
||||
This is ment to be used during the frontend compilation stage, during the traversal of the abstract syntax tree.
|
||||
|
||||
Fields are added to the message using the add_field method.
|
||||
The message is finalized using the assemble method.
|
||||
After that the Factory should not be used anymore.
|
||||
Its a one-time use class, so create a new instance for each Message object.
|
||||
|
||||
This class should be used to create Message objects.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.fields: list[Field] = []
|
||||
|
||||
def add_field(self, field: Field):
|
||||
"""Adds a finished field to the message.
|
||||
|
||||
This does not check for duplications, this is done in the assemble method.
|
||||
|
||||
Args:
|
||||
field (BprotoField): The field to add to the message.
|
||||
"""
|
||||
self.fields.append(field)
|
||||
|
||||
@staticmethod
|
||||
def sort_fields_dict(fields: dict[ComponentName, Field]) -> dict[ComponentName, Field]:
|
||||
"""Static methode for sorting a dictionary of bproto fields by their position.
|
||||
|
||||
Args:
|
||||
fields (dict[ComponentName, BprotoField]): The fields to sort.
|
||||
|
||||
Returns:
|
||||
dict[ComponentName, BprotoField]: The sorted fields.
|
||||
"""
|
||||
return OrderedDict(sorted(fields.items(), key=lambda x: x[1].pos))
|
||||
|
||||
def assemble(self, name: ComponentName, index_number: int) -> Message:
|
||||
"""Finalize the message and create the Message object, returning it.
|
||||
After this method is called, the Factory should not be used anymore.
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the message, should be unique. (No uniqueness checks are done here)
|
||||
index_number (int): Index number of message, used for ordering, should be unique. (No uniqueness checks are done here)
|
||||
|
||||
Raises:
|
||||
BprotoDuplicateNameError: Raise if a field name is used more than once.
|
||||
|
||||
Returns:
|
||||
BprotoMessage: The finished message object.
|
||||
"""
|
||||
resulting_message = Message(NameStyleBproto.fromStr(name), index_number)
|
||||
|
||||
field_names: set[ComponentName] = set()
|
||||
field_positions: set[int] = set()
|
||||
fields_dict: dict[ComponentName, Field] = {}
|
||||
|
||||
for i in self.fields:
|
||||
if i.name in field_names:
|
||||
raise BprotoDuplicateNameError(i.name, resulting_message)
|
||||
|
||||
if i.pos in field_positions:
|
||||
raise BprotoMessageIDAlreadyUsed(i.pos, resulting_message)
|
||||
|
||||
field_positions.add(i.pos)
|
||||
field_names.add(i.name)
|
||||
fields_dict[i.name] = i
|
||||
|
||||
fields_dict = FactoryMessage.sort_fields_dict(fields_dict)
|
||||
resulting_message.fields = fields_dict
|
||||
|
||||
return resulting_message
|
||||
57
src/protocol_components/protocol.py
Normal file
57
src/protocol_components/protocol.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from protocol_components.message import Message
|
||||
from protocol_components.bitfields import Bitfield
|
||||
from protocol_components.enumeration import Enumeration
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class ProtocolDefinitions():
|
||||
"""Incapuslates all the components of a protocol definition
|
||||
"""
|
||||
def __init__(self, name: ComponentName, version: int,
|
||||
enums: dict[ComponentName, Enumeration],
|
||||
bitfields: dict[ComponentName, Bitfield],
|
||||
messages: dict[ComponentName, Message]
|
||||
):
|
||||
"""Initializes the ProtocolDefinitions class; Should not be called directly
|
||||
Use FactoryProtocolDefition.assemble instead
|
||||
|
||||
Args:
|
||||
name (ComponentName): Name of the protocol
|
||||
version (int): Version of the protocol
|
||||
enums (dict[ComponentName, Enumeration]): Enums of the protocol
|
||||
bitfields (dict[ComponentName, Bitfield]): Bitfields of the protocol
|
||||
messages (dict[ComponentName, Message]): Messages of the protocol
|
||||
"""
|
||||
self.name: ComponentName = name
|
||||
self.version = version
|
||||
self.enums = enums
|
||||
self.bitfields = bitfields
|
||||
self.messages = messages
|
||||
self.protocol_hash_inital: None | int = None
|
||||
|
||||
def __deepcopy__(self, memo={}):
|
||||
if id(self) in memo:
|
||||
return memo[id(self)]
|
||||
|
||||
res = ProtocolDefinitions(
|
||||
deepcopy(self.name, memo),
|
||||
deepcopy(self.version, memo),
|
||||
enums={
|
||||
deepcopy(k, memo): deepcopy(v, memo)
|
||||
for k, v in self.enums.items()
|
||||
},
|
||||
bitfields={
|
||||
deepcopy(k, memo): deepcopy(v, memo)
|
||||
for k, v in self.bitfields.items()
|
||||
},
|
||||
messages={
|
||||
deepcopy(k, memo): deepcopy(v, memo)
|
||||
for k, v in self.messages.items()
|
||||
}
|
||||
)
|
||||
res.protocol_hash_inital = self.protocol_hash_inital
|
||||
|
||||
memo[id(self)] = res
|
||||
return res
|
||||
107
src/protocol_components/protocolFactory.py
Normal file
107
src/protocol_components/protocolFactory.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from protocol_components.message import Message
|
||||
from protocol_components.bitfields import Bitfield
|
||||
from protocol_components.enumeration import Enumeration
|
||||
from protocol_components.protocol import ProtocolDefinitions
|
||||
from protocol_components import AbstractProtocolComponent
|
||||
from nameHandling.resolver import ProtocolReferenceResolver
|
||||
from errors import BprotoAlreadyDefinedError
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class FactoryProtocolDefition:
|
||||
"""Factory class for creating ProtocolDefinitions
|
||||
This a one-time use class, once the ProtocolDefinitions object is created, the factory should not be used anymore
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initializes the FactoryProtocolDefition class; Nothing special here
|
||||
"""
|
||||
self.bitfields: dict[ComponentName, Bitfield] = {}
|
||||
self.enums: dict[ComponentName, Enumeration] = {}
|
||||
self.messages: dict[ComponentName, Message] = {}
|
||||
|
||||
def add_message(self, message: Message):
|
||||
"""Add a message to the factory, name must be unique
|
||||
|
||||
Args:
|
||||
message (Message): message to add
|
||||
|
||||
Raises:
|
||||
BprotoAlreadyDefinedError: Thrown if message name was already defined
|
||||
"""
|
||||
if message.name in self.messages:
|
||||
raise BprotoAlreadyDefinedError(message)
|
||||
self.messages.update({message.name: message})
|
||||
|
||||
def add_bitfield(self, bitfield: Bitfield):
|
||||
"""Add a bitfield to the factory, name must be unique
|
||||
|
||||
Args:
|
||||
bitfield (Bitfield): Bitfield to add with unique name
|
||||
|
||||
Raises:
|
||||
BprotoAlreadyDefinedError: Thrown if bitfield name was already defined
|
||||
"""
|
||||
if bitfield.name in self.bitfields:
|
||||
raise BprotoAlreadyDefinedError(bitfield)
|
||||
self.bitfields.update({bitfield.name: bitfield})
|
||||
|
||||
def add_enum(self, enum: Enumeration):
|
||||
"""Add an enumeration to the factory, name must be unique
|
||||
|
||||
Args:
|
||||
enum (Enumeration): Enumeration to add with unique name
|
||||
|
||||
Raises:
|
||||
BprotoAlreadyDefinedError: Thrown if enum name was already defined
|
||||
"""
|
||||
if enum.name in self.enums:
|
||||
raise BprotoAlreadyDefinedError(enum)
|
||||
self.enums.update({enum.name: enum})
|
||||
|
||||
@staticmethod
|
||||
def sort_components(components: dict[str, AbstractProtocolComponent]) -> dict[str, AbstractProtocolComponent]:
|
||||
"""Sorts the components of a protocol definitiown by their identifier (can be integer or string)
|
||||
|
||||
Args:
|
||||
components (dict[str, AbstractProtocolComponent]): Components, dictionary of messages
|
||||
|
||||
Returns:
|
||||
dict[str,AbstractProtocolComponent]: Sorted dictionary of components, is a OrderedDict
|
||||
"""
|
||||
return OrderedDict(sorted(components.items(), key=lambda x: x[1].get_identifier()))
|
||||
|
||||
def assemble(self, name: str, version: int) -> ProtocolDefinitions:
|
||||
"""Finalizes the ProtocolDefinitions object and returns it
|
||||
After calling this method, the factory should not be used anymore
|
||||
|
||||
This:
|
||||
1. Resolves refrence fields
|
||||
2. Sorts the messages
|
||||
3. Creates the ProtocolDefinitions object
|
||||
|
||||
Args:
|
||||
name (str): Name of the protocol, gets converted to ComponentName
|
||||
version (int): Version of the protocol
|
||||
|
||||
Returns:
|
||||
ProtocolDefinitions: final ProtocolDefinitions object
|
||||
"""
|
||||
|
||||
self.messages = self.sort_components(self.messages)
|
||||
|
||||
resulting_protocol_defition = ProtocolDefinitions(
|
||||
NameStyleBproto.fromStr(name, "protocol_name"),
|
||||
version,
|
||||
self.enums,
|
||||
self.bitfields,
|
||||
FactoryProtocolDefition.sort_components(self.messages)
|
||||
)
|
||||
|
||||
resolver = ProtocolReferenceResolver(resulting_protocol_defition)
|
||||
resolver.resolve_refrence_protocol()
|
||||
resolver.back_resolve_inlines()
|
||||
|
||||
return resulting_protocol_defition
|
||||
14
template/c/template/bproto.cmake.jinja2
Normal file
14
template/c/template/bproto.cmake.jinja2
Normal file
@@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Add the library
|
||||
add_library(bproto STATIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/{{protocol.name}}_bitfield.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/{{protocol.name}}_crc.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/{{protocol.name}}_mapping.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/{{protocol.name}}_message_fromBytes.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/{{protocol.name}}_message_toBytes.c
|
||||
)
|
||||
|
||||
target_include_directories(bproto PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/include
|
||||
)
|
||||
18
template/c/template/include/bitfield/bitfield.h.jinja2
Normal file
18
template/c/template/include/bitfield/bitfield.h.jinja2
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
{% for bitfield in bitfields %}
|
||||
#define {{bitfield.size_name}} {{bitfield.size}}
|
||||
{%- endfor %}
|
||||
{% for bitfield in bitfields %}
|
||||
typedef struct {{bitfield.name}} {
|
||||
{%- for bit in bitfield.bits %}
|
||||
unsigned int {{bit.name}} : {{bit.length}};
|
||||
{%- endfor %}
|
||||
} {{bitfield.name}};
|
||||
{% endfor -%}
|
||||
{% for bitfield in bitfields %}
|
||||
size_t fromBytes_{{bitfield.name}}(uint8_t * data, {{bitfield.name}} * bitfield);
|
||||
|
||||
size_t toBytes_{{bitfield.name}}(uint8_t * data, {{bitfield.name}} * bitfield);
|
||||
{% endfor %}
|
||||
18
template/c/template/include/crc.h.jinja2
Normal file
18
template/c/template/include/crc.h.jinja2
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
#ifndef _CRC16_H_
|
||||
#define _CRC16_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
uint16_t {{protocol.name}}_crc16_ccitt(const uint8_t* buffer, size_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _CRC16_H_
|
||||
26
template/c/template/include/enums.h.jinja2
Normal file
26
template/c/template/include/enums.h.jinja2
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
typedef enum {
|
||||
{{protocol.name}}_PARSER_RESULT_OK,
|
||||
{{protocol.name}}_PARSER_RESULT_ERROR_INVALID_SIZE,
|
||||
{{protocol.name}}_PARSER_RESULT_ERROR_INVALID_ID,
|
||||
{{protocol.name}}_PARSER_RESULT_ERROR_INVALID_CRC,
|
||||
} {{protocol.name}}_PARSER_RESULT;
|
||||
|
||||
typedef enum {
|
||||
{%- for msg in messages %}
|
||||
{{msg.id_name}} = {{msg.id}}{% if not loop.last %},{% endif %}
|
||||
{%- endfor %}
|
||||
} {{protocol.name}}_MSG_ID ;
|
||||
|
||||
typedef enum {
|
||||
{%- for msg in messages %}
|
||||
{{msg.size_name}} = {{msg.size}}{% if not loop.last %},{% endif %}
|
||||
{%- endfor %}
|
||||
} {{protocol.name}}_MSG_SIZE;
|
||||
{% for enum in enums %}
|
||||
typedef enum {
|
||||
{%- for v in enum.consts %}
|
||||
{{ v[0] }} = {{ v[1] }}{% if not loop.last %},{% endif %}
|
||||
{%- endfor %}
|
||||
} {{enum.name}};
|
||||
{% endfor %}
|
||||
27
template/c/template/include/message/message.h.jinja2
Normal file
27
template/c/template/include/message/message.h.jinja2
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "{{protocol.name}}_bitfield.h"
|
||||
#include "{{protocol.name}}_enum.h"
|
||||
#include "{{protocol.name}}_crc.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define MAX_MESSAGE_STRUCT_SIZE {{ protocol.max_struct_size_expr }}
|
||||
#define MAX_MESSAGE_DATA_SIZE {{ protocol.max_data_size }}
|
||||
{% for msg in messages %}
|
||||
typedef struct {{msg.name}} {
|
||||
{%- for field in msg.fields %}
|
||||
{{ field.type.name }} {{ field.name }}{{ field.type.array }};
|
||||
{%- endfor %}
|
||||
} {{msg.name}};
|
||||
{% endfor %}
|
||||
|
||||
{%- for msg in messages %}
|
||||
size_t toBytes_{{msg.name}}({{msg.name}} * msg, uint8_t * data);
|
||||
|
||||
{{protocol.name}}_PARSER_RESULT fromBytes_{{msg.name}}({{msg.name}} * msg, uint8_t * data, size_t data_size, size_t * parsed_size);
|
||||
{% endfor %}
|
||||
|
||||
{{protocol.name}}_PARSER_RESULT fromBytes(void * msg, uint8_t * data, size_t data_size, size_t * parsed_size, {{protocol.name}}_MSG_ID * msg_id);
|
||||
|
||||
{{protocol.name}}_MSG_SIZE {{protocol.name}}_mapMessageToSize(uint8_t msg_id);
|
||||
6
template/c/template/src/bitfield.c.jinja2
Normal file
6
template/c/template/src/bitfield.c.jinja2
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "{{protocol.name}}_bitfield.h"
|
||||
{% for bitfield in bitfields %}
|
||||
{% include 'c/template/src/bitfield/fromBytes.c.jinja2' %}
|
||||
|
||||
{% include 'c/template/src/bitfield/toBytes.c.jinja2' %}
|
||||
{% endfor %}
|
||||
10
template/c/template/src/bitfield/fromBytes.c.jinja2
Normal file
10
template/c/template/src/bitfield/fromBytes.c.jinja2
Normal file
@@ -0,0 +1,10 @@
|
||||
size_t fromBytes_{{bitfield.name}}(uint8_t * data, {{bitfield.name}} * bitfield) {
|
||||
{%- for bit in bitfield.bits %}
|
||||
{%- if bit.spacer %}
|
||||
// {{ bit.length }} bits of nothingness
|
||||
{%- else %}
|
||||
bitfield->{{ bit.name }} = (data[{{ bit.byte_pos }}] >> {{ bit.bit_pos }}) & 0b1;
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
return {{bitfield.size}};
|
||||
}
|
||||
12
template/c/template/src/bitfield/toBytes.c.jinja2
Normal file
12
template/c/template/src/bitfield/toBytes.c.jinja2
Normal file
@@ -0,0 +1,12 @@
|
||||
size_t toBytes_{{bitfield.name}}(uint8_t * data, {{bitfield.name}} * bitfield) {
|
||||
memset(data, 0, {{bitfield.size}});
|
||||
{%- for bit in bitfield.bits %}
|
||||
{%- if bit.spacer %}
|
||||
// {{ bit.length }} bits of nothingness
|
||||
{%- else %}
|
||||
data[{{ bit.byte_pos }}] |= (bitfield->{{ bit.name }} & 0b1) << {{ bit.bit_pos }};
|
||||
{%- endif -%}
|
||||
|
||||
{% endfor %}
|
||||
return {{bitfield.size}};
|
||||
}
|
||||
48
template/c/template/src/crc.c.jinja2
Normal file
48
template/c/template/src/crc.c.jinja2
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "{{protocol.name}}_crc.h"
|
||||
|
||||
// From: https://gist.github.com/rafacouto/59326c90d6a55f86a3ba
|
||||
|
||||
static const uint16_t {{protocol.name}}_ccitt_hash[] = {
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
|
||||
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
|
||||
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
|
||||
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
|
||||
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
|
||||
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
|
||||
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
|
||||
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
|
||||
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
|
||||
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
|
||||
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
|
||||
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
|
||||
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
|
||||
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
|
||||
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
|
||||
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0,
|
||||
};
|
||||
|
||||
uint16_t {{protocol.name}}_crc16_ccitt(const uint8_t* buffer, size_t size)
|
||||
{
|
||||
uint16_t crc = {{protocol.crc_initial}};
|
||||
while (size-- > 0)
|
||||
{
|
||||
crc = (crc << 8) ^ {{protocol.name}}_ccitt_hash[((crc >> 8) ^ *(buffer++)) & 0x00FF];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
70
template/c/template/src/message/fromBytes.c.jinja2
Normal file
70
template/c/template/src/message/fromBytes.c.jinja2
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "{{protocol.name}}_bitfield.h"
|
||||
#include "{{protocol.name}}_enum.h"
|
||||
#include "{{protocol.name}}_message.h"
|
||||
#include "{{protocol.name}}_crc.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
{% for msg in messages %}
|
||||
{{protocol.name}}_PARSER_RESULT fromBytes_{{msg.name}}({{msg.name}} * msg, uint8_t * data, size_t data_size, size_t * parsed_size) {
|
||||
data += 1;
|
||||
{% for field in msg.fields %}
|
||||
{%- if field.decompose_mode == "for" %}
|
||||
for(int i=0; i<{{field.array_size}}; i++) {
|
||||
{% if field.pre_init_value != None %}msg->{{field.name}}[i] = {{field.pre_init_value}};{% endif %}
|
||||
memcpy(&msg->{{field.name}}[i], data, sizeof({{field.type.aux or field.type.name}}));
|
||||
data += {{field.base_size}};
|
||||
}
|
||||
{%- elif field.decompose_mode == "inline_for" %}
|
||||
{%- for i in range(field.array_size) %}
|
||||
{% if field.pre_init_value != None %}msg->{{field.name}}[{{i}}] = {{field.pre_init_value}};{% endif %}
|
||||
{% if field.fromBytes_overwride %}{{ field.fromBytes_overwride }}{% else %}memcpy(&msg->{{field.name}}[{{i}}], data, sizeof({{field.type.aux or field.type.name}}));{% endif %};
|
||||
data += {{field.base_size}};
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
{% if field.pre_init_value != None %}msg->{{field.name}} = {{field.pre_init_value}};{% endif %}
|
||||
{% if field.fromBytes_overwride %}{{ field.fromBytes_overwride }}{% else %}memcpy(&msg->{{field.name}}, data, sizeof({{field.type.aux or field.type.name}}));{% endif %};
|
||||
data += {{field.base_size}};
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
*parsed_size = {{msg.size}};
|
||||
return {{protocol.name}}_PARSER_RESULT_OK;
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
{{protocol.name}}_PARSER_RESULT fromBytes(void * msg, uint8_t * data, size_t data_size, size_t * parsed_size, {{protocol.name}}_MSG_ID * msg_id) {
|
||||
if(data_size < 1) {
|
||||
return {{protocol.name}}_PARSER_RESULT_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
uint8_t incomming_msg_id= data[0];
|
||||
size_t expected_size = {{protocol.name}}_mapMessageToSize(incomming_msg_id);
|
||||
|
||||
if(expected_size <= 0) {
|
||||
return {{protocol.name}}_PARSER_RESULT_ERROR_INVALID_ID;
|
||||
}
|
||||
|
||||
if(data_size < expected_size) {
|
||||
return {{protocol.name}}_PARSER_RESULT_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
uint16_t crc;
|
||||
memcpy(&crc, data + expected_size - {{ protocol.crc_size }}, sizeof(uint16_t));
|
||||
|
||||
if(HCP_crc16_ccitt(data, expected_size - {{ protocol.crc_size }}) != crc) {
|
||||
return {{protocol.name}}_PARSER_RESULT_ERROR_INVALID_CRC;
|
||||
}
|
||||
|
||||
*msg_id = incomming_msg_id;
|
||||
|
||||
switch(incomming_msg_id) {
|
||||
{%- for msg in messages %}
|
||||
case {{msg.id_name}}:
|
||||
return fromBytes_{{msg.name}}(msg, data, data_size, parsed_size);
|
||||
break;
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
return {{protocol.name}}_PARSER_RESULT_OK;
|
||||
}
|
||||
14
template/c/template/src/message/mapping.c.jinja2
Normal file
14
template/c/template/src/message/mapping.c.jinja2
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "{{protocol.name}}_enum.h"
|
||||
#include <stdint.h>
|
||||
|
||||
{{protocol.name}}_MSG_SIZE {{protocol.name}}_mapMessageToSize(uint8_t msg_id) {
|
||||
switch(msg_id) {
|
||||
{%- for msg in messages %}
|
||||
case {{msg.id_name}}:
|
||||
return {{msg.size_name}};
|
||||
{%- endfor %}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
34
template/c/template/src/message/toBytes.c.jinja2
Normal file
34
template/c/template/src/message/toBytes.c.jinja2
Normal file
@@ -0,0 +1,34 @@
|
||||
#include "{{protocol.name}}_bitfield.h"
|
||||
#include "{{protocol.name}}_enum.h"
|
||||
#include "{{protocol.name}}_message.h"
|
||||
#include "{{protocol.name}}_crc.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
{% for msg in messages %}
|
||||
size_t toBytes_{{msg.name}}({{msg.name}} * msg, uint8_t * data) {
|
||||
uint8_t * data_begin = data;
|
||||
*data = (uint8_t)({{msg.id_name}});
|
||||
data += 1;
|
||||
{% for field in msg.fields %}
|
||||
{%- if field.decompose_mode == "for" %}
|
||||
for(int i=0; i<{{field.array_size}}; i++) {
|
||||
memcpy(data, &msg->{{field.name}}[i], sizeof({{field.type.aux or field.type.name}}));
|
||||
data += {{field.base_size}};
|
||||
}
|
||||
{%- elif field.decompose_mode == "inline_for" %}
|
||||
{%- for i in range(field.array_size) %}
|
||||
{% if field.toBytes_overwride %}{{field.toBytes_overwride}}{% else %}memcpy(data, &msg->{{field.name}}[{{i}}], sizeof({{field.type.aux or field.type.name}}));{% endif %};
|
||||
data += {{field.base_size}};
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
{% if field.toBytes_overwride %}{{field.toBytes_overwride}}{% else %}memcpy(data, &msg->{{field.name}}, sizeof({{field.type.aux or field.type.name}}));{% endif %};
|
||||
data += {{field.base_size}};
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
uint16_t crc = {{protocol.name}}_crc16_ccitt(data_begin, {{msg.size_name}} - {{protocol.crc_size}});
|
||||
memcpy(data, &crc, sizeof(uint16_t));
|
||||
|
||||
return {{msg.size_name}};
|
||||
}
|
||||
{% endfor %}
|
||||
67
template/python/static/bproto_base.py
Normal file
67
template/python/static/bproto_base.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import abc
|
||||
|
||||
|
||||
class bproto_Package(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def to_bytes(self) -> bytes:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_bytes(self, data: bytes):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class bproto_Bitfield(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def to_bytes(self) -> bytes:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def from_bytes(self, data: bytes):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
def crc16_calc_inital(data: bytearray) -> int:
|
||||
"""CRC-16/CITT-FALSE
|
||||
from https://stackoverflow.com/questions/35205702/calculating-crc16-in-python
|
||||
|
||||
Args:
|
||||
data (bytearray): protocol hash
|
||||
|
||||
Returns:
|
||||
bytes: initals for crc16_calc_with_initial
|
||||
"""
|
||||
crc = 0xFFFF
|
||||
for i in range(0, len(data)):
|
||||
crc ^= data[i] << 8
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x8000) > 0:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
return crc
|
||||
|
||||
|
||||
def calc_crc_sum(data: bytearray, inital: int) -> bytes:
|
||||
"""CRC-16/CITT-FALSE
|
||||
form https://stackoverflow.com/questions/35205702/calculating-crc16-in-python
|
||||
|
||||
Args:
|
||||
data (bytearray): protocol data
|
||||
inital (int): Inital calculated from protocol hash
|
||||
|
||||
Returns:
|
||||
bytes: 2 byte CRC Checksum
|
||||
"""
|
||||
crc = inital
|
||||
for i in range(0, len(data)):
|
||||
crc ^= data[i] << 8
|
||||
for j in range(0, 8):
|
||||
if (crc & 0x8000) > 0:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
return crc & 0xFFFF
|
||||
19
template/python/static/bproto_error.py
Normal file
19
template/python/static/bproto_error.py
Normal file
@@ -0,0 +1,19 @@
|
||||
class bproto_Error(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class bproto_PackageError(bproto_Error):
|
||||
pass
|
||||
|
||||
|
||||
class bproto_PackageErrorInvalidSize(bproto_PackageError):
|
||||
pass
|
||||
|
||||
|
||||
class bproto_PackageErrorInvalidMessageID(bproto_PackageError):
|
||||
pass
|
||||
|
||||
|
||||
class bproto_PackageErrorInvalidCRC(bproto_PackageError):
|
||||
pass
|
||||
38
template/python/template/bproto_protocol_bitfield.py.jinja2
Normal file
38
template/python/template/bproto_protocol_bitfield.py.jinja2
Normal file
@@ -0,0 +1,38 @@
|
||||
from bproto_base import bproto_Bitfield
|
||||
|
||||
{% for bitfield in bitfields %}
|
||||
class {{bitfield.name}}(bproto_Bitfield):
|
||||
BYTE_SIZE = {{bitfield.size}}
|
||||
|
||||
def __init__(self,
|
||||
{%- for bit in bitfield.bits %}
|
||||
{{bit.name}}=False,
|
||||
{%- endfor %}
|
||||
):
|
||||
{%- for bit in bitfield.bits %}
|
||||
self.{{bit.name}} = {{bit.name}}
|
||||
{%- endfor %}
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = 0
|
||||
{%- for bit in bitfield.bits %}
|
||||
res |= (1 << {{bit.pos}}) if self.{{bit.name}} else 0
|
||||
{%- endfor %}
|
||||
return int.to_bytes(res, self.BYTE_SIZE, '{{protocol.endian_str}}')
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
bits = int.from_bytes(data[:self.BYTE_SIZE], '{{protocol.endian_str}}')
|
||||
{%- for bit in bitfield.bits %}
|
||||
self.{{bit.name}} = (bits & (1 << {{bit.pos}})) != 0
|
||||
{%- endfor %}
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
value_str = ", ".join([
|
||||
{%- for bit in bitfield.bits %}
|
||||
"{{bit.name}}=" + str(self.{{bit.name}}),
|
||||
{%- endfor %}
|
||||
])
|
||||
return "{{bitfield.name}}(" + value_str + ")"
|
||||
|
||||
{% endfor %}
|
||||
19
template/python/template/bproto_protocol_packets.py.jinja2
Normal file
19
template/python/template/bproto_protocol_packets.py.jinja2
Normal file
@@ -0,0 +1,19 @@
|
||||
from bproto_base import bproto_Package, calc_crc_sum
|
||||
from bproto_error import bproto_Error, bproto_PackageErrorInvalidSize, bproto_PackageErrorInvalidMessageID, bproto_PackageErrorInvalidCRC
|
||||
|
||||
from {{ import_name }}_protocol_enum import {% for e in enums %}{{e.name}}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
from {{ import_name }}_protocol_bitfield import {% for b in bitfields %}{{b.name}}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
from {{ import_name }}_protocol_message_ids import MessageIds_{{ protocol.name }}, MESSAGE_SIZE_MAP_{{ protocol.name }}
|
||||
|
||||
from typing import Annotated, List
|
||||
import struct
|
||||
|
||||
CRC_INITAL = {{ protocol.crc_initial }}
|
||||
|
||||
|
||||
{% for m in messages %}
|
||||
{%- include 'python/template/message/class.jinja2' -%}
|
||||
{%- if not loop.last %}
|
||||
|
||||
{% else %}{%- endif -%}{%- endfor %}
|
||||
{% include 'python/template/message/parse_generic.jinja2' -%}
|
||||
@@ -0,0 +1,15 @@
|
||||
import enum
|
||||
|
||||
|
||||
class MessageIds_{{ protocol.name }}(enum.Enum):
|
||||
{%- for msg in messages %}
|
||||
{{ msg.id_name }} = {{ msg.id }}
|
||||
{%- endfor %}
|
||||
|
||||
|
||||
MESSAGE_SIZE_MAP_{{ protocol.name }} = {
|
||||
{%- for msg in messages %}
|
||||
MessageIds_{{ protocol.name }}.{{ msg.id_name }}: {{ msg.size }},
|
||||
{%- endfor %}
|
||||
}
|
||||
|
||||
8
template/python/template/enum.py.jinja2
Normal file
8
template/python/template/enum.py.jinja2
Normal file
@@ -0,0 +1,8 @@
|
||||
import enum
|
||||
{% for e in enums %}
|
||||
|
||||
class {{ e.name }}(enum.Enum):
|
||||
{%- for k,v in e.consts %}
|
||||
{{ k }} = {{ v }}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
20
template/python/template/message/class.jinja2
Normal file
20
template/python/template/message/class.jinja2
Normal file
@@ -0,0 +1,20 @@
|
||||
class {{m.name}}(bproto_Package):
|
||||
ID = MessageIds_{{ protocol.name }}.{{m.id_name}}
|
||||
SIZE = {{ m.size }}
|
||||
{% for f in m.fields %}
|
||||
{{f.name}}: {{f.type}}
|
||||
{%- endfor %}
|
||||
|
||||
def __init__(self,
|
||||
{% for f in m.fields -%}
|
||||
{{f.name}}: {{f.type}} = {{f.default_value}},
|
||||
{% endfor -%}
|
||||
):
|
||||
{% for f in m.fields -%}
|
||||
self.{{f.name}} = {{f.name}}
|
||||
{% endfor -%}
|
||||
|
||||
|
||||
{% include 'python/template/message/toBytes.jinja2' %}
|
||||
{% include 'python/template/message/fromBytes.jinja2' %}
|
||||
|
||||
21
template/python/template/message/fromBytes.jinja2
Normal file
21
template/python/template/message/fromBytes.jinja2
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
def from_bytes(self, data: bytes):
|
||||
|
||||
if len(data) < self.SIZE:
|
||||
raise bproto_PackageErrorInvalidSize(f"A size of {len(data)} is to small for {{ protocol.name }} (min. {self.SIZE})")
|
||||
|
||||
msg_id = struct.unpack('{{protocol.endian_format}}B', data[:1])[0]
|
||||
crc_data = struct.unpack('{{protocol.endian_format}}H', data[-{{ protocol.crc_size }}:])[0]
|
||||
|
||||
if calc_crc_sum(data[:-{{ protocol.crc_size }}], CRC_INITAL) != crc_data:
|
||||
raise bproto_PackageErrorInvalidCRC(f"CRC {crc_data:04x} did not match calculated")
|
||||
|
||||
if msg_id != self.ID.value:
|
||||
raise bproto_PackageErrorInvalidMessageID("Message ID {msd_id} does not match {{ protocol.name }}'s id {self.ID.value}")
|
||||
|
||||
data = data[1:]
|
||||
{% for f in m.fields -%}
|
||||
self.{{ f.name }} = {{ f.from_bytes_conversion }}
|
||||
data = data[{{ f.size }}:]
|
||||
{% endfor -%}
|
||||
return self
|
||||
31
template/python/template/message/parse_generic.jinja2
Normal file
31
template/python/template/message/parse_generic.jinja2
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
def parse_package(data: bytes) -> tuple[bproto_Package, int]:
|
||||
if len(data) < 1:
|
||||
raise bproto_PackageErrorInvalidSize("Package has no data")
|
||||
|
||||
msg_id = struct.unpack('{{protocol.endian_format}}B', data[:1])[0]
|
||||
|
||||
if msg_id not in MessageIds_{{ protocol.name }}:
|
||||
raise bproto_PackageErrorInvalidMessageID(f"Message ID '{msg_id}' is invaild")
|
||||
|
||||
msg_id = MessageIds_{{ protocol.name }}(msg_id)
|
||||
expected_size = MESSAGE_SIZE_MAP_{{ protocol.name }}.get(msg_id) or 0
|
||||
|
||||
if expected_size > len(data):
|
||||
raise bproto_PackageErrorInvalidSize(f"Package is to short for '{msg_id}' ({expected_size} > {len(data)})")
|
||||
|
||||
pkg_data = data[:expected_size]
|
||||
|
||||
crc_data = struct.unpack('{{protocol.endian_format}}H', pkg_data[-2:])[0]
|
||||
|
||||
if calc_crc_sum(pkg_data[:-2], CRC_INITAL) != crc_data:
|
||||
raise bproto_PackageErrorInvalidCRC(f"CRC {crc_data:04x} did not match calculated")
|
||||
|
||||
match(msg_id):
|
||||
{%- for msg in messages %}
|
||||
case MessageIds_{{ protocol.name }}.{{ msg.id_name }}:
|
||||
return {{msg.name}}().from_bytes(pkg_data), expected_size
|
||||
{%- endfor %}
|
||||
case _:
|
||||
raise bproto_Error("Message ID could not be interpreted; this should not have happen; its a developer error! Create a issue")
|
||||
|
||||
8
template/python/template/message/toBytes.jinja2
Normal file
8
template/python/template/message/toBytes.jinja2
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
res = struct.pack("{{protocol.endian_format}}B", self.ID.value)
|
||||
{% for f in m.fields -%}
|
||||
res += {{ f.to_bytes_conversion }}
|
||||
{% endfor -%}
|
||||
res += struct.pack("{{protocol.endian_format}}H", calc_crc_sum(res, CRC_INITAL))
|
||||
return res
|
||||
26
template/txt/protocolSummary.txt.jinja2
Normal file
26
template/txt/protocolSummary.txt.jinja2
Normal file
@@ -0,0 +1,26 @@
|
||||
Name: {{name}}
|
||||
Version: {{version}}
|
||||
|
||||
Enums:
|
||||
{%- for enum in enums %}
|
||||
{{enum.name}}:
|
||||
{%- for v in enum.consts %}
|
||||
- {{v[0]}} = {{v[1]}}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
Bitfield:
|
||||
{%- for bitfield in bitfields %}
|
||||
{{bitfield.name}}:
|
||||
{%- for v in bitfield.fields %}
|
||||
- {{v[0]}}: {{v[1]}}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
Messages:
|
||||
{%- for message in messages %}
|
||||
[{{message.id}}] {{message.name}}:
|
||||
Size: {{message.size}} bytes
|
||||
Fields:
|
||||
{%- for field in message.fields %}
|
||||
- ({{field.size}}) {{field.name}}: {{field.type}}[{{field.array_size}}] {% if field.ref != None %}-> {{field.ref}}{% endif %}
|
||||
{%- endfor %}
|
||||
{% endfor -%}
|
||||
38
test/compiler/ast/test_ast_message.py
Normal file
38
test/compiler/ast/test_ast_message.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
import pytest
|
||||
from typing import Any
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from parser.parser import create_ast_parser, bproto_ErrorListener
|
||||
from parser.ast_visitor import BprotoASTVisitor
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source_text,expected_output", [
|
||||
("uint64\n", (BprotoFieldBaseType.UINT64, 1, None)),
|
||||
("float32\n", (BprotoFieldBaseType.FLOAT32, 1, None)),
|
||||
("float32[8]\n", (BprotoFieldBaseType.FLOAT32, 8, None)),
|
||||
])
|
||||
def test_ast_field_dtype_defintion(
|
||||
source_text: str,
|
||||
expected_output: tuple[BprotoFieldBaseType, int, Any]):
|
||||
|
||||
err_listner = bproto_ErrorListener()
|
||||
parser = create_ast_parser(source_text, err_listner)
|
||||
ast = parser.dtype()
|
||||
vinterp = BprotoASTVisitor()
|
||||
|
||||
assert len(err_listner.syntax_error_list) == 0
|
||||
|
||||
res = vinterp.visit(ast)
|
||||
assert isinstance(res, tuple)
|
||||
assert len(res) == 3
|
||||
|
||||
# Correct dtype
|
||||
assert res[0] == expected_output[0].value
|
||||
|
||||
# Correct array size
|
||||
assert res[1] == expected_output[1]
|
||||
|
||||
# Correct refrerence; Should normally be None be Non
|
||||
assert res[2] == expected_output[2]
|
||||
23
test/compiler/ast/test_ast_tag_line.py
Normal file
23
test/compiler/ast/test_ast_tag_line.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from parser.parser import create_ast_parser, bproto_ErrorListener
|
||||
from parser.ast_visitor import BprotoASTVisitor
|
||||
|
||||
|
||||
def test_ast_tag_line():
|
||||
source = "protocol HCP version 25\n"
|
||||
|
||||
err_listner = bproto_ErrorListener()
|
||||
parser = create_ast_parser(source, err_listner)
|
||||
ast = parser.protocol_header()
|
||||
vinterp = BprotoASTVisitor()
|
||||
|
||||
assert len(err_listner.syntax_error_list) == 0
|
||||
|
||||
res = vinterp.visit(ast)
|
||||
assert isinstance(res, tuple)
|
||||
assert len(res) == 2
|
||||
name, version = res
|
||||
assert name == "HCP"
|
||||
assert version == 25
|
||||
26
test/compiler/backend/test_comiler_c.py
Normal file
26
test/compiler/backend/test_comiler_c.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import sys
|
||||
from compiler import BprotoCompiler
|
||||
sys.path.append('src/')
|
||||
|
||||
from errors import BprotoCompilerError
|
||||
from backend.rendering.cBackend import CBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
def test_compiler_c_smoke_test():
|
||||
# Load Refernces
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
CBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
output.saveToDisk("test/backend_output/c")
|
||||
output.toString()
|
||||
59
test/compiler/backend/test_compiler_python.py
Normal file
59
test/compiler/backend/test_compiler_python.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import sys
|
||||
import pytest
|
||||
from compiler import BprotoCompiler
|
||||
sys.path.append('src/')
|
||||
|
||||
from errors import BprotoCompilerError
|
||||
from backend.rendering.pythonBackend import PythonBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_compiler_python_against_ref():
|
||||
# Load Refernces
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
PythonBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
output.saveToDisk("test/backend_output/python")
|
||||
string_output = output.toString()
|
||||
|
||||
with open("test/reference/python/HCP_protocol_enum.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_enum.py")
|
||||
|
||||
with open("test/reference/python/HCP_protocol_bitfield.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_bitfield.py")
|
||||
|
||||
with open("test/reference/python/HCP_protocol_message_ids.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_message_ids.py")
|
||||
|
||||
with open("test/reference/python/HCP_protocol_packets.py") as f:
|
||||
assert f.read() == string_output.get("HCP_protocol_packets.py")
|
||||
|
||||
|
||||
def test_compiler_python_smoke_test():
|
||||
# Load Refernces
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
PythonBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
output.saveToDisk("test/backend_output/python")
|
||||
output.toString()
|
||||
32
test/compiler/backend/test_compiler_txt.py
Normal file
32
test/compiler/backend/test_compiler_txt.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
from compiler import BprotoCompiler
|
||||
sys.path.append('src/')
|
||||
|
||||
from errors import BprotoCompilerError
|
||||
from backend.rendering.txtBackend import TxtBackendRenderer
|
||||
from backend.fsOutput import BackendFileSystemOutput
|
||||
|
||||
|
||||
def test_compiler_txt_against_ref():
|
||||
with open("test/test_protocol.bproto") as f:
|
||||
source_text = f.read()
|
||||
|
||||
# Comile Stuff
|
||||
compiler = BprotoCompiler([
|
||||
TxtBackendRenderer(".")
|
||||
], "template")
|
||||
|
||||
try:
|
||||
output: BackendFileSystemOutput = compiler.compile(source_text, ".")
|
||||
except BprotoCompilerError:
|
||||
assert False, "This should run througth"
|
||||
|
||||
string_output = output.toString()
|
||||
output.saveToDisk("test/backend_output/txt")
|
||||
|
||||
# Load Refernces
|
||||
with open("test/reference/txt/protocolSummary.txt") as f:
|
||||
expected_output = f.read()
|
||||
|
||||
assert isinstance(string_output.get("protocolSummary.txt"), str)
|
||||
assert string_output.get("protocolSummary.txt") == expected_output
|
||||
168
test/compiler/backend/test_fsOutput.py
Normal file
168
test/compiler/backend/test_fsOutput.py
Normal file
@@ -0,0 +1,168 @@
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
sys.path.append('src/')
|
||||
|
||||
from backend.fsOutput import BackendFSOutFile, BackendFSOutStaticConent, BackendFSOutFolder, BackendFSOutputJinjaFile
|
||||
|
||||
TEST_FOLDER = "/tmp/protocolGenerator_test_data"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def filesystem_folder():
|
||||
yield TEST_FOLDER
|
||||
shutil.rmtree(TEST_FOLDER)
|
||||
|
||||
print("Cleanup done")
|
||||
|
||||
|
||||
def test_fsOutput_saveToDisk(filesystem_folder: str):
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("test/data/templates/"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
# Setup
|
||||
root_folder = BackendFSOutFolder("root", [
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
BackendFSOutFolder("subfolder", [
|
||||
BackendFSOutFile("file3", "some more content #2"),
|
||||
BackendFSOutFile("file4", "even more content"),
|
||||
]),
|
||||
BackendFSOutStaticConent("test/data/static_folder/static_content2.txt", "static_content2.txt"),
|
||||
BackendFSOutStaticConent("test/data/static_content1.txt", "static_content1.txt"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja2", "template.txt", {"name": "World"})
|
||||
])
|
||||
|
||||
root_folder.saveToDisk(filesystem_folder)
|
||||
|
||||
# Test Structure
|
||||
assert os.path.isdir(filesystem_folder)
|
||||
assert os.path.isfile(filesystem_folder + "/root/file1")
|
||||
assert os.path.isfile(filesystem_folder + "/root/file2")
|
||||
assert os.path.isdir(filesystem_folder + "/root/subfolder")
|
||||
assert os.path.isfile(filesystem_folder + "/root/subfolder/file3")
|
||||
assert os.path.isfile(filesystem_folder + "/root/subfolder/file4")
|
||||
assert os.path.isfile(filesystem_folder + "/root/static_content1.txt")
|
||||
assert os.path.isfile(filesystem_folder + "/root/static_content2.txt")
|
||||
|
||||
assert os.path.isfile(filesystem_folder + "/root/template.txt")
|
||||
|
||||
# Test Content
|
||||
|
||||
with open(filesystem_folder + "/root/file1", "r") as f:
|
||||
assert f.read() == "some content"
|
||||
|
||||
with open(filesystem_folder + "/root/file2", "r") as f:
|
||||
assert f.read() == "some other content"
|
||||
|
||||
with open(filesystem_folder + "/root/subfolder/file3", "r") as f:
|
||||
assert f.read() == "some more content #2"
|
||||
|
||||
with open(filesystem_folder + "/root/subfolder/file4", "r") as f:
|
||||
assert f.read() == "even more content"
|
||||
|
||||
with open(filesystem_folder + "/root/static_content1.txt", "r") as f:
|
||||
assert f.read() == "test1"
|
||||
|
||||
with open(filesystem_folder + "/root/static_content2.txt", "r") as f:
|
||||
assert f.read() == "test2"
|
||||
|
||||
with open(filesystem_folder + "/root/template.txt", "r") as f:
|
||||
assert f.read() == "Hello World!"
|
||||
|
||||
|
||||
def test_fsOutput_treeString():
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("test/data/templates/"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
root_folder = BackendFSOutFolder("root", [
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
BackendFSOutFolder("subfolder", [
|
||||
BackendFSOutFile("file3", "some more content #2"),
|
||||
BackendFSOutFile("file4", "even more content"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja", "generate", {"name": "Jinja2"})
|
||||
]),
|
||||
BackendFSOutStaticConent("test/data/static_folder", "data"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja", None, {"name": "World"})
|
||||
])
|
||||
|
||||
string = root_folder.assemble_file_tree_string()
|
||||
assert string == """└── root
|
||||
├── [file1]
|
||||
├── [file2]
|
||||
├── subfolder
|
||||
│ ├── [file3]
|
||||
│ ├── [file4]
|
||||
│ └── Template [generate]
|
||||
├── Static [data]
|
||||
└── Template [template]
|
||||
"""
|
||||
|
||||
|
||||
def test_fsOutput_toString():
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("test/data/templates/"),
|
||||
autoescape=select_autoescape()
|
||||
)
|
||||
|
||||
root_folder = BackendFSOutFolder("root", [
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
BackendFSOutFolder("subfolder", [
|
||||
BackendFSOutFile("file3", "some more content #2"),
|
||||
BackendFSOutFile("file4", "even more content"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja2", "generate", {"name": "Jinja2"})
|
||||
]),
|
||||
BackendFSOutStaticConent("test/data/static_folder", "data/static_folder"),
|
||||
BackendFSOutStaticConent("test/data/static_content1.txt", "data"),
|
||||
BackendFSOutputJinjaFile(jinja_env, "template.jinja2", None, {"name": "World"})
|
||||
])
|
||||
|
||||
res = root_folder.toString()
|
||||
|
||||
assert res == {
|
||||
"root/file1": "some content",
|
||||
"root/file2": "some other content",
|
||||
"root/subfolder/file3": "some more content #2",
|
||||
"root/subfolder/file4": "even more content",
|
||||
"root/subfolder/generate": "Hello Jinja2!",
|
||||
"root/data/static_content1.txt": "test1",
|
||||
"root/data/static_folder/static_content2.txt": "test2",
|
||||
"root/template": "Hello World!"
|
||||
}
|
||||
|
||||
|
||||
def test_fsOutput_pathnormalization(filesystem_folder: str):
|
||||
|
||||
root_folder = BackendFSOutFolder(".", [
|
||||
BackendFSOutFolder(".", [
|
||||
BackendFSOutFile("file2", "some other content"),
|
||||
]),
|
||||
BackendFSOutFile("file1", "some content"),
|
||||
])
|
||||
|
||||
# Check if this works for toString
|
||||
res = root_folder.toString()
|
||||
assert res == {
|
||||
"file2": "some other content",
|
||||
"file1": "some content"
|
||||
}
|
||||
|
||||
# Check if this works for saveToDisk
|
||||
root_folder.saveToDisk(filesystem_folder)
|
||||
|
||||
assert os.path.isfile(filesystem_folder + "/file1")
|
||||
assert os.path.isfile(filesystem_folder + "/file2")
|
||||
|
||||
with open(filesystem_folder + "/file1", "r") as f:
|
||||
assert f.read() == "some content"
|
||||
|
||||
with open(filesystem_folder + "/file2", "r") as f:
|
||||
assert f.read() == "some other content"
|
||||
49
test/compiler/name_conversion/test_name_bproto_style.py
Normal file
49
test/compiler/name_conversion/test_name_bproto_style.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
|
||||
|
||||
def test_component_name():
|
||||
assert ComponentName(["test"]) != 1234
|
||||
assert ComponentName(["test"]) == ComponentName(["test"])
|
||||
assert ComponentName(["test"]) != ComponentName(["test", "test"])
|
||||
assert hash(ComponentName(["test"])) == hash(ComponentName(["test"]))
|
||||
assert hash(ComponentName(["test"])) != hash(ComponentName(["test", "test"]))
|
||||
assert repr(ComponentName(["test"])) == "ComponentName(Test)"
|
||||
assert repr(ComponentName(["test", "test"])) == "ComponentName(Test-Test)"
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto():
|
||||
assert NameStyleBproto.fromStr("test") == ComponentName(["test"])
|
||||
assert NameStyleBproto.fromStr("ThisIsATest") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("thisIsATest") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this-is-a-test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this-Is-A-test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this_is_a_test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this_Is_a_Test") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("this-Is_ATest") == ComponentName(["this", "is", "a", "test"])
|
||||
assert NameStyleBproto.fromStr("ThisIs-A_test") == ComponentName(["this", "is", "a", "test"])
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto_context_enum():
|
||||
assert NameStyleBproto.fromStr("test", "enum") == ComponentName(["test"])
|
||||
assert NameStyleBproto.fromStr("ThisIsATest", "enum") == ComponentName(["thisisatest"])
|
||||
assert NameStyleBproto.fromStr("TESTTEST", "enum") == ComponentName(["testtest"])
|
||||
assert NameStyleBproto.fromStr("TEST_TEST", "enum") == ComponentName(["test", "test"])
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto_comparison():
|
||||
c = NameStyleBproto.fromStr("ThisIsATest")
|
||||
assert c == "this-is-a-test"
|
||||
assert c == "this_is_a_test"
|
||||
assert c == "thisIsATest"
|
||||
assert c == "ThisIsATest"
|
||||
assert c == "This_Is_A_Test"
|
||||
|
||||
|
||||
def test_ComponentNameStyleBproto_from_Component():
|
||||
c = ComponentName(["this", "is", "a", "test"])
|
||||
c2 = NameStyleBproto.fromStr(c)
|
||||
|
||||
assert c == c2
|
||||
40
test/compiler/name_conversion/test_name_brand_applier.py
Normal file
40
test/compiler/name_conversion/test_name_brand_applier.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.brand_applier import BrandApplier
|
||||
from protocol_components.message import Message
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
|
||||
def test_name_applier():
|
||||
protocolNameApplier = BrandApplier("brand")
|
||||
|
||||
name = ComponentName(["test"])
|
||||
name = protocolNameApplier.apply(name)
|
||||
assert name == ComponentName(["brand", "test"])
|
||||
|
||||
name = ComponentName(["test", "test"])
|
||||
name = protocolNameApplier.apply(name)
|
||||
assert name == ComponentName(["brand", "test", "test"])
|
||||
|
||||
msg = Message(ComponentName(["this", "is", "a", "test"]), 0)
|
||||
msg: Message = protocolNameApplier.apply(msg)
|
||||
assert msg.name == ComponentName(["brand", "this", "is", "a", "test"])
|
||||
|
||||
protocolNameApplier = BrandApplier(ComponentName(["happy", "brand"]))
|
||||
|
||||
msg = Message(ComponentName(["this", "is", "a", "test"]), 0)
|
||||
msg: Message = protocolNameApplier.apply(msg)
|
||||
assert msg.name == ComponentName(["happy", "brand", "this", "is", "a", "test"])
|
||||
|
||||
try:
|
||||
protocolNameApplier.apply(None)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, TypeError)
|
||||
|
||||
try:
|
||||
protocolNameApplier = BrandApplier(None)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, TypeError)
|
||||
73
test/compiler/name_conversion/test_name_c_style.py
Normal file
73
test/compiler/name_conversion/test_name_c_style.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.field import FactoryField
|
||||
from protocol_components.message import FactoryMessage, Message
|
||||
from nameHandling.style.cNameStyle import NameStyleC
|
||||
|
||||
|
||||
def test_style_c_fromStr():
|
||||
try:
|
||||
NameStyleC.fromStr("")
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, NotImplementedError)
|
||||
|
||||
|
||||
def test_style_c_toStr_enum():
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "Values"]), "enum_name") == "MyCustomValues"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "enum", "value"]), "enum_item") == "MY_CUSTOM_ENUM_VALUE"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class"]), "struct_name") == "MyCustomClass"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class", "member"]), "struct_member") == "myCustomClassMember"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class"]), "bitfield_name") == "MyCustomClass"
|
||||
assert NameStyleC.toStr(ComponentName(["my", "custom", "class", "member"]), "bitfield_member") == "myCustomClassMember"
|
||||
|
||||
|
||||
def test_style_c_preprocessor():
|
||||
protocol_factory = FactoryProtocolDefition()
|
||||
|
||||
enum_factory = FactoryEnumeration()
|
||||
enum_factory.add_value("first-Value", 1)
|
||||
enum_factory.add_value("second-Important-Value", 2)
|
||||
protocol_factory.add_enum(enum_factory.assemble("mode"))
|
||||
|
||||
bitfield_factory = FactoryBitfield()
|
||||
bitfield_factory.add_bit("FirstBit", 0)
|
||||
bitfield_factory.add_bit("SecondBit", 1)
|
||||
protocol_factory.add_bitfield(bitfield_factory.assemble("bitfield1"))
|
||||
|
||||
message_factory = FactoryMessage()
|
||||
message_factory.add_field(FactoryField().assemble("x", 1, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
message_factory.add_field(FactoryField().assemble("y", 2, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
protocol_factory.add_message(message_factory.assemble("cordUpdate", 1))
|
||||
|
||||
protocol = protocol_factory.assemble("ABCP", 5)
|
||||
protocol = NameStyleC.preprocess(protocol)
|
||||
|
||||
enum: Enumeration = list(protocol.enums.values())[0]
|
||||
bitfield: Bitfield = list(protocol.bitfields.values())[0]
|
||||
message: Message = list(protocol.messages.values())[0]
|
||||
|
||||
assert isinstance(enum, Enumeration)
|
||||
assert isinstance(bitfield, Bitfield)
|
||||
assert isinstance(message, Message)
|
||||
|
||||
assert NameStyleC.toStr(enum.name, "enum_name") == "Abcp_enum_Mode"
|
||||
assert NameStyleC.toStr(bitfield.name, "bitfield_name") == "Abcp_bitfield_Bitfield1"
|
||||
assert NameStyleC.toStr(message.name, "struct_name") == "Abcp_message_CordUpdate"
|
||||
|
||||
assert enum.values == {
|
||||
ComponentName(["abcp", "_enum_", "mode", "value", "first", "value"]): 1,
|
||||
ComponentName(["abcp", "_enum_", "mode", "value", "second", "important", "value"]): 2
|
||||
}
|
||||
|
||||
assert bitfield.bits == {
|
||||
ComponentName(["first", "bit"]): 0,
|
||||
ComponentName(["second", "bit"]): 1
|
||||
}
|
||||
|
||||
59
test/compiler/name_conversion/test_name_python_style.py
Normal file
59
test/compiler/name_conversion/test_name_python_style.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.field import FactoryField
|
||||
from protocol_components.message import FactoryMessage, Message
|
||||
from nameHandling.style.pythonNameStyle import NameStylePython
|
||||
|
||||
|
||||
def test_style_python_fromStr():
|
||||
try:
|
||||
NameStylePython.fromStr("")
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, NotImplementedError)
|
||||
|
||||
|
||||
def test_style_python_toStr_enum():
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "enum"]), "enum_name") == "MyCustomEnum"
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "enum", "value"]), "enum_item") == "MY_CUSTOM_ENUM_VALUE"
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "class"]), "class_name") == "MyCustomClass"
|
||||
assert NameStylePython.toStr(ComponentName(["my", "custom", "class", "member"]), "class_member") == "my_custom_class_member"
|
||||
|
||||
|
||||
def test_style_python_preprocessor():
|
||||
protocol_factory = FactoryProtocolDefition()
|
||||
|
||||
enum_factory = FactoryEnumeration()
|
||||
enum_factory.add_value("first-Value", 1)
|
||||
enum_factory.add_value("second-Important-Value", 2)
|
||||
protocol_factory.add_enum(enum_factory.assemble("mode"))
|
||||
|
||||
bitfield_factory = FactoryBitfield()
|
||||
bitfield_factory.add_bit("FirstBit", 0)
|
||||
bitfield_factory.add_bit("SecondBit", 1)
|
||||
protocol_factory.add_bitfield(bitfield_factory.assemble("bitfield1"))
|
||||
|
||||
message_factory = FactoryMessage()
|
||||
message_factory.add_field(FactoryField().assemble("x", 1, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
message_factory.add_field(FactoryField().assemble("y", 2, BprotoFieldBaseType.FLOAT32, 2, None))
|
||||
protocol_factory.add_message(message_factory.assemble("cordUpdate", 1))
|
||||
|
||||
protocol = protocol_factory.assemble("HCPT", 5)
|
||||
protocol = NameStylePython.preprocess(protocol)
|
||||
|
||||
enum: Enumeration = list(protocol.enums.values())[0]
|
||||
bitfield: Bitfield = list(protocol.bitfields.values())[0]
|
||||
message: Message = list(protocol.messages.values())[0]
|
||||
|
||||
assert isinstance(enum, Enumeration)
|
||||
assert isinstance(bitfield, Bitfield)
|
||||
|
||||
assert NameStylePython.toStr(enum.name, "enum_name") == "HcptEnumMode"
|
||||
assert NameStylePython.toStr(bitfield.name, "class_name") == "HcptBitfieldBitfield1"
|
||||
assert NameStylePython.toStr(message.name, "class_name") == "HcptMessageCordUpdate"
|
||||
80
test/compiler/test_bitfields.py
Normal file
80
test/compiler/test_bitfields.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from errors import BprotoDuplicateNameError, BprotoDuplicateBitfieldPositionError, BprotoCompilerError
|
||||
from nameHandling.base import NameStyleBproto
|
||||
|
||||
|
||||
def test_bitfield_factory():
|
||||
fatory = FactoryBitfield()
|
||||
fatory.add_bit("BIT #1", 0)
|
||||
fatory.add_bit("BIT #4", None)
|
||||
fatory.add_bit("BIT #2", 1)
|
||||
fatory.add_bit("BIT #5", 4)
|
||||
fatory.add_bit("BIT #6", None)
|
||||
fatory.add_bit("BIT #3", 2)
|
||||
fatory.add_bit("BIT #7", 6)
|
||||
|
||||
bitfield: Bitfield = fatory.assemble("TestBitfield")
|
||||
|
||||
assert bitfield.name == "TestBitfield"
|
||||
assert isinstance(bitfield.name, ComponentName)
|
||||
assert isinstance(bitfield.get_name(), ComponentName)
|
||||
assert isinstance(bitfield.get_identifier(), ComponentName)
|
||||
|
||||
assert bitfield.bits == {
|
||||
NameStyleBproto.fromStr("BIT #1"): 0,
|
||||
NameStyleBproto.fromStr("BIT #2"): 1,
|
||||
NameStyleBproto.fromStr("BIT #3"): 2,
|
||||
NameStyleBproto.fromStr("BIT #4"): 3,
|
||||
NameStyleBproto.fromStr("BIT #5"): 4,
|
||||
NameStyleBproto.fromStr("BIT #6"): 5,
|
||||
NameStyleBproto.fromStr("BIT #7"): 6,
|
||||
}
|
||||
assert isinstance(list(bitfield.bits.keys())[0], ComponentName)
|
||||
|
||||
|
||||
def test_bitfield_factory_failure_cases_duplicate_name():
|
||||
factory = FactoryBitfield()
|
||||
|
||||
factory.add_bit("BIT #1", 0)
|
||||
factory.add_bit("BIT #1", 1)
|
||||
factory.add_bit("BIT #2", 2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestBitfield")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError)
|
||||
|
||||
|
||||
def test_bitfield_factory_failure_cases_duplicate_position():
|
||||
factory = FactoryBitfield()
|
||||
|
||||
factory.add_bit("BIT #1", 0)
|
||||
factory.add_bit("BIT #2", 1)
|
||||
factory.add_bit("BIT #3", 1)
|
||||
|
||||
try:
|
||||
factory.assemble("TestBitfield")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateBitfieldPositionError)
|
||||
|
||||
|
||||
def test_bitfield_size_calculation():
|
||||
bitfield = Bitfield("TestBitfield")
|
||||
|
||||
bitfield.length = 7
|
||||
assert bitfield.get_size_bytes() == 1
|
||||
|
||||
bitfield.length = 0
|
||||
assert bitfield.get_size_bytes() == 1
|
||||
|
||||
bitfield.length = 8
|
||||
assert bitfield.get_size_bytes() == 1
|
||||
|
||||
bitfield.length = 9
|
||||
assert bitfield.get_size_bytes() == 2
|
||||
73
test/compiler/test_deep_copy.py
Normal file
73
test/compiler/test_deep_copy.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from nameHandling.style.pythonNameStyle import NameStylePython
|
||||
from protocol_components.enumeration import FactoryEnumeration
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def test_deepcopy_component_name():
|
||||
name = ComponentName(["This", "Is", "Some", "text"], NameStylePython, source_string="ThisIsSomeText")
|
||||
|
||||
new_name = deepcopy(name)
|
||||
|
||||
name.name_parts = ["Some", "Other", "Stuff"]
|
||||
name.source_string = "SomeOtherStuff"
|
||||
name.source_style = NameStyleBproto
|
||||
|
||||
# Verify Values did not change
|
||||
assert name.name_parts != new_name.name_parts
|
||||
assert name.source_string != new_name.source_string
|
||||
assert name.source_style != new_name.source_style
|
||||
|
||||
|
||||
def test_deepcopy_enum():
|
||||
old_enum_factory = FactoryEnumeration()
|
||||
old_enum_factory.add_value("Value1", 1)
|
||||
old_enum_factory.add_value("Value2", 2)
|
||||
old_enum_factory.add_value("Value3", 3)
|
||||
old_enum = old_enum_factory.assemble("OldEnum")
|
||||
|
||||
new_enum = deepcopy(old_enum)
|
||||
|
||||
# Check ids
|
||||
assert id(new_enum) != id(old_enum)
|
||||
assert id(new_enum.name) != id(old_enum.name)
|
||||
|
||||
assert id(old_enum.values) != id(new_enum.values)
|
||||
|
||||
old_keys = list(old_enum.values.keys())
|
||||
new_keys = list(new_enum.values.keys())
|
||||
assert id(old_keys[0]) != id(new_keys[0])
|
||||
assert id(old_keys[1]) != id(new_keys[1])
|
||||
assert id(old_keys[2]) != id(new_keys[2])
|
||||
|
||||
|
||||
def test_deepcopy_component_names_memo():
|
||||
name1 = NameStyleBproto.fromStr("Value1")
|
||||
name2 = NameStyleBproto.fromStr("Value2")
|
||||
|
||||
assert id(name1) != id(name2)
|
||||
|
||||
name1_copy = deepcopy(name1)
|
||||
assert isinstance(name1_copy, ComponentName)
|
||||
|
||||
assert name1_copy == name1
|
||||
assert id(name1_copy) != id(name1)
|
||||
|
||||
name1_copy2 = deepcopy(name1, {
|
||||
id(name1): name1_copy,
|
||||
id(name1_copy): name1_copy
|
||||
})
|
||||
|
||||
name1_copy3 = deepcopy(name1_copy, {
|
||||
id(name1): name1_copy,
|
||||
id(name1_copy): name1_copy
|
||||
})
|
||||
|
||||
assert isinstance(name1_copy2, ComponentName)
|
||||
assert isinstance(name1_copy3, ComponentName)
|
||||
|
||||
assert id(name1_copy) == id(name1_copy2)
|
||||
assert id(name1_copy2) != id(name1)
|
||||
|
||||
assert id(name1_copy) == id(name1_copy3)
|
||||
assert id(name1_copy3) != id(name1)
|
||||
228
test/compiler/test_emums.py
Normal file
228
test/compiler/test_emums.py
Normal file
@@ -0,0 +1,228 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from errors import BprotoDuplicateNameError, BprotoDuplicateEnumValueError, BprotoCompilerError, BprotoEnumBitsizeTooLargeError
|
||||
from nameHandling.base import NameStyleBproto, ComponentName
|
||||
|
||||
|
||||
def test_enum_factory():
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("A", 1)
|
||||
factory.add_value("C", 3)
|
||||
factory.add_value("B", 2)
|
||||
factory.add_value("D", None)
|
||||
factory.add_value("E", 5)
|
||||
factory.add_value("F", None)
|
||||
|
||||
enum = factory.assemble("TestEnum")
|
||||
|
||||
assert enum.name == "TestEnum"
|
||||
assert enum.values == {
|
||||
NameStyleBproto.fromStr("X"): 0,
|
||||
NameStyleBproto.fromStr("A"): 1,
|
||||
NameStyleBproto.fromStr("B"): 2,
|
||||
NameStyleBproto.fromStr("C"): 3,
|
||||
NameStyleBproto.fromStr("D"): 4,
|
||||
NameStyleBproto.fromStr("E"): 5,
|
||||
NameStyleBproto.fromStr("F"): 6
|
||||
}
|
||||
|
||||
assert isinstance(enum.name, ComponentName)
|
||||
assert isinstance(enum.get_name(), ComponentName)
|
||||
assert isinstance(enum.get_identifier(), ComponentName)
|
||||
assert isinstance(list(enum.values.keys())[0], ComponentName)
|
||||
|
||||
|
||||
def test_enum_factory_failure_cases_dubplicate_value():
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("Y", 0)
|
||||
factory.add_value("Z", 1)
|
||||
|
||||
try:
|
||||
factory.assemble("TestEnum")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateEnumValueError)
|
||||
|
||||
|
||||
def test_enum_factory_failure_cases_duplicate_name():
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("Y", 1)
|
||||
factory.add_value("Y", 2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestEnum")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError)
|
||||
|
||||
|
||||
def test_enum_factory_failure_cases_duplicate_name_and_value():
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("X", 0)
|
||||
|
||||
try:
|
||||
factory.assemble("TestEnum")
|
||||
assert False
|
||||
except BprotoCompilerError as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError) or isinstance(e, BprotoDuplicateEnumValueError)
|
||||
|
||||
|
||||
def test_enum_sorted():
|
||||
factory = FactoryEnumeration()
|
||||
|
||||
factory.add_value("X", 0)
|
||||
factory.add_value("D", None)
|
||||
factory.add_value("C", 3)
|
||||
factory.add_value("E", 5)
|
||||
factory.add_value("F", None)
|
||||
factory.add_value("B", 2)
|
||||
factory.add_value("A", 1)
|
||||
|
||||
enum = factory.assemble("TestEnum")
|
||||
|
||||
assert list(enum.values.values()) == list(range(7))
|
||||
|
||||
|
||||
def test_enum_size_calc():
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(17))) == 5
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(16))) == 4
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(9))) == 4
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(8))) == 3
|
||||
assert FactoryEnumeration.calculate_bitsize(list(range(7))) == 3
|
||||
|
||||
assert FactoryEnumeration.calculate_bitsize([1, 2, 31]) == 5
|
||||
assert FactoryEnumeration.calculate_bitsize([1, 32, 2]) == 6
|
||||
assert FactoryEnumeration.calculate_bitsize([127, 0, 2]) == 7
|
||||
|
||||
assert FactoryEnumeration.calculate_bitsize([]) == 1
|
||||
|
||||
|
||||
def test_enum_auxiliary_data_typemap():
|
||||
|
||||
e = Enumeration("TestEnum", 8)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT8
|
||||
|
||||
e = Enumeration("TestEnum", 16)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT16
|
||||
|
||||
e = Enumeration("TestEnum", 24)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT32
|
||||
|
||||
e = Enumeration("TestEnum", 32)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT32
|
||||
|
||||
e = Enumeration("TestEnum", 64)
|
||||
assert e.map_auxiliary_datatypes() == BprotoFieldBaseType.UINT64
|
||||
|
||||
e = Enumeration("TestEnum", 65)
|
||||
try:
|
||||
e.map_auxiliary_datatypes()
|
||||
assert False
|
||||
except Exception as err:
|
||||
assert isinstance(err, BprotoEnumBitsizeTooLargeError)
|
||||
|
||||
|
||||
def test_enum_size_calculation():
|
||||
# Uint8
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(3):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 8
|
||||
assert e.get_size_bytes() == 1
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(2**8):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 8
|
||||
assert e.get_size_bytes() == 1
|
||||
|
||||
# Uint16
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(2**8 + 1):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 16
|
||||
assert e.get_size_bytes() == 2
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(2**16):
|
||||
factory.add_value(f"#{i}", i)
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 16
|
||||
assert e.get_size_bytes() == 2
|
||||
|
||||
# Uint32
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**16}", 2**16)
|
||||
# We start couinting from 0 -> 2**16 does not fit in 16 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 32
|
||||
assert e.get_size_bytes() == 4
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**32 - 1}", 2**32 - 1)
|
||||
# We start couinting from 0 2**32 - 1 does barely fit in 32 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 32
|
||||
assert e.get_size_bytes() == 4
|
||||
|
||||
# Uint64
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**32}", 2**32)
|
||||
# We start couinting from 0 -> 2**32 does not fit in 32 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 64
|
||||
assert e.get_size_bytes() == 8
|
||||
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**64 - 1}", 2**64 - 1)
|
||||
# We start couinting from 0 2**64 - 1 does barely fit in 64 bits
|
||||
|
||||
e = factory.assemble("TestEnum")
|
||||
assert e.get_size_bits() == 64
|
||||
assert e.get_size_bytes() == 8
|
||||
|
||||
# Too large
|
||||
factory = FactoryEnumeration()
|
||||
for i in range(17):
|
||||
factory.add_value(f"#{i}", i)
|
||||
factory.add_value(f"#{2**64}", 2**64)
|
||||
|
||||
# This can cause rounding errors
|
||||
# log2(2**64) ~ log2(2**64 - 1) ~ 64
|
||||
# This is btw not a problem for 2**32
|
||||
# stupid floating point errors
|
||||
|
||||
# We start couinting from 0 -> 2**64 does not fit in 64 bits
|
||||
|
||||
try:
|
||||
e = factory.assemble("TestEnum")
|
||||
assert False
|
||||
except Exception as err:
|
||||
assert isinstance(err, BprotoEnumBitsizeTooLargeError)
|
||||
98
test/compiler/test_field.py
Normal file
98
test/compiler/test_field.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType, BprotoArrayPolicyViolationError, BprotoUnknownDataTypeError
|
||||
from protocol_components.field import Field, FactoryField, FieldBitfield, FieldBitfieldRef
|
||||
from protocol_components.bitfields import Bitfield
|
||||
from nameHandling.base import ComponentName
|
||||
|
||||
|
||||
def test_field_object():
|
||||
field = Field("field1", 3, BprotoFieldBaseType.UINT64, 4)
|
||||
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.UINT64
|
||||
assert field.array_size == 4
|
||||
|
||||
assert field.get_identifier() == "field1"
|
||||
assert field.get_name() == "field1"
|
||||
assert field.get_type_name() == "field"
|
||||
assert field.get_size_bytes() == 8 * 4
|
||||
assert field.get_size_bits() == 64 * 4
|
||||
|
||||
|
||||
def test_field_factory():
|
||||
factory = FactoryField()
|
||||
|
||||
field: Field = factory.assemble("field1", 3, BprotoFieldBaseType.UINT64.value, 4, None)
|
||||
|
||||
assert isinstance(field, Field)
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.UINT64
|
||||
assert field.array_size == 4
|
||||
|
||||
assert isinstance(field.name, ComponentName)
|
||||
assert isinstance(field.get_identifier(), ComponentName)
|
||||
assert isinstance(field.get_name(), ComponentName)
|
||||
assert isinstance(field.get_type_name(), str)
|
||||
|
||||
|
||||
def test_field_factory_errors_invalid_datatype():
|
||||
factory = FactoryField()
|
||||
try:
|
||||
factory.assemble(
|
||||
"field1", 3, "junkDataType", 4, None
|
||||
)
|
||||
assert False
|
||||
except BprotoUnknownDataTypeError:
|
||||
assert True
|
||||
except Exception as e:
|
||||
assert False, f"Unexpected exception: {e}"
|
||||
|
||||
|
||||
def test_field_factory_errors_invalid_array_policy():
|
||||
factory = FactoryField()
|
||||
try:
|
||||
factory.assemble(
|
||||
"field1", 1, BprotoFieldBaseType.BITFIELD, 4, None
|
||||
)
|
||||
assert False
|
||||
except BprotoArrayPolicyViolationError:
|
||||
assert True
|
||||
except Exception as e:
|
||||
assert False, f"Unexpected exception: {e}"
|
||||
|
||||
|
||||
def test_field_factory_bitfield():
|
||||
factory = FactoryField()
|
||||
|
||||
field: FieldBitfield = factory.assemble(
|
||||
"field1", 3, BprotoFieldBaseType.BITFIELD, 1,
|
||||
Bitfield("test")
|
||||
)
|
||||
|
||||
assert isinstance(field, FieldBitfield)
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.BITFIELD
|
||||
assert field.array_size == 1
|
||||
assert isinstance(field.bitfield, Bitfield)
|
||||
assert field.bitfield.get_name() == "test"
|
||||
|
||||
|
||||
def test_field_factory_bitfield_ref():
|
||||
factory = FactoryField()
|
||||
|
||||
field: FieldBitfield = factory.assemble(
|
||||
"field1", 3, BprotoFieldBaseType.BITFIELD, 1,
|
||||
"test"
|
||||
)
|
||||
|
||||
assert isinstance(field, FieldBitfieldRef)
|
||||
assert field.name == "field1"
|
||||
assert field.pos == 3
|
||||
assert field.type == BprotoFieldBaseType.BITFIELD
|
||||
assert field.array_size == 1
|
||||
assert field.ref == "test"
|
||||
71
test/compiler/test_message.py
Normal file
71
test/compiler/test_message.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from protocol_components.message import FactoryMessage, Message
|
||||
from protocol_components.field import FactoryField
|
||||
from errors import BprotoDuplicateNameError, BprotoMessageIDAlreadyUsed
|
||||
|
||||
|
||||
def test_message_factory():
|
||||
factory = FactoryMessage()
|
||||
field1 = FactoryField().assemble("field1", 0, "uint64", 4, None)
|
||||
field2 = FactoryField().assemble("field2", 1, "uint64", 4, None)
|
||||
field3 = FactoryField().assemble("field3", 2, "uint64", 4, None)
|
||||
field4 = FactoryField().assemble("field4", 3, "uint64", 4, None)
|
||||
|
||||
factory.add_field(field3)
|
||||
factory.add_field(field2)
|
||||
factory.add_field(field4)
|
||||
factory.add_field(field1)
|
||||
|
||||
message: Message = factory.assemble("TestMessage", 42)
|
||||
|
||||
assert message.name == "TestMessage"
|
||||
assert isinstance(message.name, ComponentName)
|
||||
assert isinstance(message.get_name(), ComponentName)
|
||||
|
||||
assert message.message_index_number == 42
|
||||
assert message.get_identifier() == 42
|
||||
|
||||
assert message.fields == {
|
||||
NameStyleBproto.fromStr("field1"): field1,
|
||||
NameStyleBproto.fromStr("field2"): field2,
|
||||
NameStyleBproto.fromStr("field3"): field3,
|
||||
NameStyleBproto.fromStr("field4"): field4
|
||||
}
|
||||
|
||||
assert isinstance(list(message.fields.keys())[0], ComponentName)
|
||||
|
||||
assert message.get_size_bytes() == 8 * 4 * 4
|
||||
assert message.get_size_bits() == 64 * 4 * 4
|
||||
|
||||
|
||||
def test_message_factory_error_duplicate_name():
|
||||
field1 = FactoryField().assemble("field1", 0, "uint64", 4, None)
|
||||
field2 = FactoryField().assemble("field1", 1, "uint64", 4, None)
|
||||
|
||||
factory = FactoryMessage()
|
||||
factory.add_field(field1)
|
||||
factory.add_field(field2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestMessage", 42)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoDuplicateNameError)
|
||||
|
||||
|
||||
def test_message_factory_error_duplicate_index():
|
||||
field1 = FactoryField().assemble("field1", 0, "uint64", 4, None)
|
||||
field2 = FactoryField().assemble("field2", 0, "uint64", 4, None)
|
||||
|
||||
factory = FactoryMessage()
|
||||
factory.add_field(field1)
|
||||
factory.add_field(field2)
|
||||
|
||||
try:
|
||||
factory.assemble("TestMessage", 42)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoMessageIDAlreadyUsed)
|
||||
181
test/compiler/test_protocol.py
Normal file
181
test/compiler/test_protocol.py
Normal file
@@ -0,0 +1,181 @@
|
||||
import sys
|
||||
sys.path.append('src/')
|
||||
|
||||
from protocol_components.dtypes import BprotoFieldBaseType
|
||||
from nameHandling.base import ComponentName, NameStyleBproto
|
||||
from protocol_components.message import FactoryMessage
|
||||
from protocol_components.enumeration import FactoryEnumeration, Enumeration
|
||||
from protocol_components.bitfields import FactoryBitfield, Bitfield
|
||||
from protocol_components.protocolFactory import FactoryProtocolDefition
|
||||
from protocol_components.field import FactoryField, Field, FieldEnum, FieldBitfield, FieldEnumRef, FieldBitfieldRef
|
||||
from errors import BprotoUnresolvedReferenceError, BprotoAlreadyDefinedError
|
||||
|
||||
from nameHandling.resolver import ProtocolReferenceResolver
|
||||
|
||||
|
||||
def test_protocol_factory():
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(FactoryMessage().assemble("Message1", 42))
|
||||
factory.add_message(FactoryMessage().assemble("Message2", 43))
|
||||
|
||||
message_with_fields_factory = FactoryMessage()
|
||||
message_with_fields_factory.add_field(FactoryField().assemble("field1", 0, "uint64", 1, None))
|
||||
message_with_fields_factory.add_field(FactoryField().assemble("enum_ref", 1, BprotoFieldBaseType.ENUM, 1, "Enum1"))
|
||||
message_with_fields_factory.add_field(FactoryField().assemble("bitfield_ref", 2, BprotoFieldBaseType.BITFIELD, 1, "Bitfield2"))
|
||||
|
||||
factory.add_message(
|
||||
message_with_fields_factory.assemble("Message3", 44)
|
||||
)
|
||||
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum1"))
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum2"))
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum3"))
|
||||
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield1"))
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield2"))
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield3"))
|
||||
|
||||
protocol = factory.assemble("TESTPROTOCOL", 1)
|
||||
|
||||
assert protocol.name.name_parts == ["testprotocol"]
|
||||
assert isinstance(protocol.name, ComponentName)
|
||||
assert protocol.version == 1
|
||||
|
||||
assert protocol.messages == {
|
||||
NameStyleBproto.fromStr("Message1"): factory.messages.get(NameStyleBproto.fromStr("Message1")),
|
||||
NameStyleBproto.fromStr("Message2"): factory.messages.get(NameStyleBproto.fromStr("Message2")),
|
||||
NameStyleBproto.fromStr("Message3"): factory.messages.get(NameStyleBproto.fromStr("Message3"))
|
||||
}
|
||||
|
||||
assert protocol.enums == {
|
||||
NameStyleBproto.fromStr("Enum1"): factory.enums.get(NameStyleBproto.fromStr("Enum1")),
|
||||
NameStyleBproto.fromStr("Enum2"): factory.enums.get(NameStyleBproto.fromStr("Enum2")),
|
||||
NameStyleBproto.fromStr("Enum3"): factory.enums.get(NameStyleBproto.fromStr("Enum3"))
|
||||
}
|
||||
|
||||
assert protocol.bitfields == {
|
||||
NameStyleBproto.fromStr("Bitfield1"): factory.bitfields.get(NameStyleBproto.fromStr("Bitfield1")),
|
||||
NameStyleBproto.fromStr("Bitfield2"): factory.bitfields.get(NameStyleBproto.fromStr("Bitfield2")),
|
||||
NameStyleBproto.fromStr("Bitfield3"): factory.bitfields.get(NameStyleBproto.fromStr("Bitfield3"))
|
||||
}
|
||||
|
||||
# Check if references are resolved works
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message3")).fields.get(NameStyleBproto.fromStr("enum_ref")), FieldEnum)
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message3")).fields.get(NameStyleBproto.fromStr("bitfield_ref")), FieldBitfield)
|
||||
|
||||
|
||||
def test_protocol_resolve_refernces():
|
||||
enum1 = FactoryEnumeration().assemble("Enum1")
|
||||
bitfield1 = FactoryBitfield().assemble("Bitfield1")
|
||||
|
||||
message1Factory = FactoryMessage()
|
||||
message1Factory.add_field(FactoryField().assemble("field1", 0, "uint64", 1, None))
|
||||
message1Factory.add_field(FactoryField().assemble("enum_field", 1, BprotoFieldBaseType.ENUM, 1, enum1))
|
||||
message1Factory.add_field(FactoryField().assemble("enum_ref_field", 2, BprotoFieldBaseType.ENUM, 1, "Enum1"))
|
||||
message1Factory.add_field(FactoryField().assemble("bitfield_field", 3, BprotoFieldBaseType.BITFIELD, 1, bitfield1))
|
||||
message1Factory.add_field(FactoryField().assemble("bitfield_ref_field", 4, BprotoFieldBaseType.BITFIELD, 1, "Bitfield1"))
|
||||
message1 = message1Factory.assemble("Message1", 42)
|
||||
|
||||
# Check if message is correctly assembled
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("field1")), Field)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("field1")).type == BprotoFieldBaseType.UINT64
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("field1")).array_size == 1
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("field1")).ref is None
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("field1")).ref, type(None))
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("enum_field")), FieldEnum)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("enum_field")).enum, Enumeration)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_field")).enum == enum1
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("enum_ref_field")), FieldEnumRef)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_ref_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("enum_ref_field")).ref == "Enum1"
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("bitfield_field")), FieldBitfield)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("bitfield_field")).bitfield, Bitfield)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_field")).bitfield == bitfield1
|
||||
|
||||
assert isinstance(message1.fields.get(NameStyleBproto.fromStr("bitfield_ref_field")), FieldBitfieldRef)
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert message1.fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).ref == "Bitfield1"
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(message1)
|
||||
factory.add_enum(enum1)
|
||||
factory.add_bitfield(bitfield1)
|
||||
protocol = factory.assemble("TestProtocol", 1)
|
||||
|
||||
# Check if references are resolved works
|
||||
|
||||
resolver = ProtocolReferenceResolver(protocol)
|
||||
resolver.resolve_refrence_protocol()
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_field")), FieldEnum)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_field")).enum == enum1
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_ref_field")).type == BprotoFieldBaseType.ENUM
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_ref_field")), FieldEnum)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("enum_ref_field")).enum == enum1
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_field")), FieldBitfield)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_field")).bitfield == bitfield1
|
||||
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).type == BprotoFieldBaseType.BITFIELD
|
||||
assert isinstance(protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_ref_field")), FieldBitfield)
|
||||
assert protocol.messages.get(NameStyleBproto.fromStr("Message1")).fields.get(NameStyleBproto.fromStr("bitfield_ref_field")).bitfield == bitfield1
|
||||
|
||||
|
||||
def test_protocol_resolve_refernces_unknown_refernces():
|
||||
message1Factory = FactoryMessage()
|
||||
message1Factory.add_field(FactoryField().assemble("field1", 0, "uint64", 1, None))
|
||||
message1Factory.add_field(FactoryField().assemble("enum_ref_field", 1, BprotoFieldBaseType.ENUM, 1, "Enum1"))
|
||||
message1Factory.add_field(FactoryField().assemble("bitfield_ref_field", 2, BprotoFieldBaseType.BITFIELD, 1, "Bitfield1",))
|
||||
message1 = message1Factory.assemble("Message1", 42)
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(message1)
|
||||
|
||||
try:
|
||||
factory.assemble("TestProtocol", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoUnresolvedReferenceError)
|
||||
|
||||
try:
|
||||
factory.assemble("TestProtocol", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoUnresolvedReferenceError)
|
||||
|
||||
|
||||
def test_protocol_factory_already_define_error():
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_message(FactoryMessage().assemble("Message1", 42))
|
||||
try:
|
||||
factory.add_message(FactoryMessage().assemble("Message1", 42))
|
||||
factory.assemble("A", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoAlreadyDefinedError)
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum1"))
|
||||
try:
|
||||
factory.assemble("A", 1)
|
||||
factory.add_enum(FactoryEnumeration().assemble("Enum1"))
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoAlreadyDefinedError)
|
||||
|
||||
factory = FactoryProtocolDefition()
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield1"))
|
||||
try:
|
||||
factory.add_bitfield(FactoryBitfield().assemble("Bitfield1"))
|
||||
factory.assemble("A", 1)
|
||||
assert False
|
||||
except Exception as e:
|
||||
assert isinstance(e, BprotoAlreadyDefinedError)
|
||||
1
test/data/static_content1.txt
Normal file
1
test/data/static_content1.txt
Normal file
@@ -0,0 +1 @@
|
||||
test1
|
||||
1
test/data/static_folder/static_content2.txt
Normal file
1
test/data/static_folder/static_content2.txt
Normal file
@@ -0,0 +1 @@
|
||||
test2
|
||||
1
test/data/templates/template.jinja2
Normal file
1
test/data/templates/template.jinja2
Normal file
@@ -0,0 +1 @@
|
||||
Hello {{ name }}!
|
||||
48
test/output/c/CMakeLists.txt
Normal file
48
test/output/c/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(bproto_test)
|
||||
enable_testing()
|
||||
|
||||
include("../../backend_output/c/HCP.cmake")
|
||||
|
||||
# include CTest support
|
||||
include(CTest)
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
|
||||
# Adding Unity test framework
|
||||
add_library(unity STATIC Unity/src/unity.c)
|
||||
target_include_directories(unity PUBLIC Unity/src)
|
||||
|
||||
# Normal test target
|
||||
add_executable(test_runner
|
||||
src/main.c
|
||||
src/test_messages.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_runner PUBLIC
|
||||
bproto
|
||||
unity
|
||||
gcov
|
||||
)
|
||||
|
||||
target_include_directories(test_runner PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/src
|
||||
)
|
||||
|
||||
target_compile_options(test_runner PRIVATE -fprofile-arcs -ftest-coverage -fPIC -O0 --coverage)
|
||||
target_compile_options(bproto PRIVATE -fprofile-arcs -ftest-coverage -fPIC -O0 --coverage)
|
||||
target_link_options(test_runner PRIVATE -fprofile-arcs -ftest-coverage -fPIC --coverage)
|
||||
target_link_options(bproto PRIVATE -fprofile-arcs -ftest-coverage -fPIC --coverage)
|
||||
|
||||
# Python Integration test build
|
||||
add_executable(test_integration_python
|
||||
src/test_python_integration.c
|
||||
)
|
||||
|
||||
target_link_libraries(test_integration_python PUBLIC
|
||||
bproto
|
||||
gcov
|
||||
)
|
||||
|
||||
target_include_directories(test_integration_python PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/src
|
||||
)
|
||||
1
test/output/c/src/Unity
Submodule
1
test/output/c/src/Unity
Submodule
Submodule test/output/c/src/Unity added at 23e8edbd64
17
test/output/c/src/main.c
Normal file
17
test/output/c/src/main.c
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "unity.h"
|
||||
#include "suit.h"
|
||||
|
||||
void setUp(void) {};
|
||||
void tearDown(void) {};
|
||||
|
||||
int main() {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_encdec_generic_Error);
|
||||
RUN_TEST(test_encdec_generic_MotionUpdate);
|
||||
|
||||
RUN_TEST(test_encdec_generic_Error_invalid_id);
|
||||
RUN_TEST(test_encdec_generic_Error_invalid_size);
|
||||
RUN_TEST(test_encdec_generic_Error_large_size);
|
||||
RUN_TEST(test_encdec_generic_Error_invalid_crc);
|
||||
return UNITY_END();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user