245 lines
10 KiB
Python
245 lines
10 KiB
Python
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"
|
|
)
|
|
])
|