Skip to content

Latest commit

 

History

History
146 lines (122 loc) · 9.89 KB

File metadata and controls

146 lines (122 loc) · 9.89 KB

Python Generated Code Guide

Usage

The Python backend is integrated into the pdlc tool.

Example invocation:

pdlc --output-format python my-protocol.pdl > my-protocol.py

If your PDL file uses custom types or checksums, you must provide the module where they are defined using the --custom-field option:

pdlc --output-format python --custom-field my_protocol.custom_types my-protocol.pdl > my-protocol.py

Language bindings

The generator produces a pure Python implementation of the parser and serializer for the selected grammar, using only builtin features of the Python language. The generated constructs are all type annotated and pass ruff validation.

All packets inherit either from their parent declaration or at the root a blanket Packet class implementation.

@dataclass
class Packet:
    payload: Optional[bytes] = field(repr=False, default_factory=bytes, compare=False)

    @classmethod
    def parse_all(cls, span: bytes) -> 'Packet':
        ...

    @property
    def size(self) -> int:
        return 0

    def show(self, prefix: str = '') -> None:
        ...

Enum declarations

enum TestEnum : 8 {
    A = 1,
    B = 2..3,
    C = 4,
    OTHER = ..,
}
class TestEnum(enum.IntEnum):
    A = 1
    C = 4

    @staticmethod
    def from_int(v: int) -> Union[int, 'TestEnum']:
        pass

Note

Python enums are closed by construction, default cases in enum declarations are ignored. The static method from_int provides validation for enum tag ranges.

Packet declarations

packet TestPacket {
    a: 8,
    b: TestEnum,
}
@dataclass
class TestPacket(Packet):
    a: int = field(kw_only=True, default=0)
    b: TestEnum = field(kw_only=True, default=TestEnum.A)

    def __post_init__(self) -> None:
        pass

    @staticmethod
    def parse(span: bytes) -> Tuple['TestPacket', bytes]:
        pass

    def serialize(self, payload: Optional[bytes] = None)
            -> bytes:
        pass

    @property
    def size(self) -> int:
        pass
packet TestPacket: ParentPacket {
    a: 8,
    b: TestEnum,
}
@dataclass
class TestPacket(ParentPacket):
    a: int = field(kw_only=True, default=0)
    b: TestEnum = field(kw_only=True, default=TestEnum.A)

    @staticmethod
    def parse(span: bytes) -> Tuple['TestPacket', bytes]:
        pass

    def serialize(self, payload: bytes = None) -> bytes:
        pass

    @property
    def size(self) -> int:
        pass

Field declarations

Fields without a binding name do not have a concrete representation in the generated class, but are nonetheless validated during parsing or implicitely generated during serialization.

a: 8
a: int = field(kw_only=True, default=0)
a: TestEnum,
b: TestStruct
a: TestEnum = field(kw_only=True, default=TestEnum.A)
b: TestStruct = field(kw_only=True,
                      default_factory=TestStruct)
a: 8[],
b: 16[128],
c: TestEnum[],
d: TestStruct[]
a: List[int] = field(kw_only=True, default_factory=list)
b: List[int] = field(kw_only=True, default_factory=list)
c: List[TestEnum] = field(kw_only=True,
                          default_factory=list)
d: List[TestStruct] = field(kw_only=True,
                            default_factory=list)
a: 8 if c_a = 1,
b: TestEnum if c_b = 1,
c: TestStruct if c_c = 1,
a: Optional[int] = field(kw_only=True, default=None)
b: Optional[TestEnum] = field(kw_only=True, default=None)
c: Optional[TestStruct] = field(kw_only=True,
                                default=None)