Init Commit: Moved bproto to seperate repo
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user