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" ) ])