Source code for sofab.visitor
"""Visitor-pattern decode driver — the language-idiomatic pull alternative.
ARCHITECTURE.md lists the visitor pattern as a recommended decoder shape:
"The decoder calls typed visitor methods on a user-supplied object.
Pull-reading becomes the visitor receives the value or chooses to skip."
:class:`Visitor` is a base class whose hooks all default to *no-op* (the value
is still consumed, so an unhandled field is transparently skipped). Subclass it
and override only the fields you care about, then hand it to
:meth:`sofab.Decoder.drive`.
Two control hooks let a visitor decline work *before* the value is decoded — so
skipping a 10k-element array or a deep sub-tree costs nothing:
* :meth:`Visitor.on_field` — return ``False`` to skip a scalar/fixlen/array
field instead of decoding it.
* :meth:`Visitor.on_sequence_begin` — return ``False`` to skip the entire
nested sequence (its matching end is consumed too, so ``on_sequence_end`` is
*not* called for a skipped sequence).
The driver itself is layered on the public pull API, so it inherits the same
"advance a cursor over a contiguous buffer" hot path as direct pull decoding.
"""
from __future__ import annotations
from .types import Field
[docs]
class Visitor:
"""Base visitor: override the hooks for the fields you handle.
Every hook is keyed by the wire type the decoder recovered. ``field_id`` is
the decoded field id. Unhandled hooks default to a no-op, which still
consumes the value (so unknown fields are skipped safely)."""
# --- control hooks (return False to skip before decoding) ---------------
[docs]
def on_field(self, field: Field) -> bool | None:
"""Called for every non-sequence field before its value is decoded.
Return ``False`` to skip the value entirely; any other return proceeds
to decode it and dispatch to the typed hook below."""
return None
[docs]
def on_sequence_begin(self, field_id: int) -> bool | None:
"""A nested sequence is opening. Return ``False`` to skip the whole
sub-tree (its end is consumed, ``on_sequence_end`` is not called)."""
return None
[docs]
def on_sequence_end(self) -> None:
"""The current nested sequence closed."""
# --- typed value hooks --------------------------------------------------
[docs]
def on_unsigned(self, field_id: int, value: int) -> None:
"""Handle a decoded unsigned-integer field."""
[docs]
def on_signed(self, field_id: int, value: int) -> None:
"""Handle a decoded signed-integer field."""
[docs]
def on_float32(self, field_id: int, value: float) -> None:
"""Handle a decoded 32-bit float field."""
[docs]
def on_float64(self, field_id: int, value: float) -> None:
"""Handle a decoded 64-bit float field."""
[docs]
def on_string(self, field_id: int, value: str) -> None:
"""Handle a decoded UTF-8 string field."""
[docs]
def on_bytes(self, field_id: int, value: bytes) -> None:
"""Handle a decoded raw byte-blob field."""
[docs]
def on_unsigned_array(self, field_id: int, values: list[int]) -> None:
"""Handle a decoded unsigned-integer array field."""
[docs]
def on_signed_array(self, field_id: int, values: list[int]) -> None:
"""Handle a decoded signed-integer array field."""
[docs]
def on_float32_array(self, field_id: int, values: list[float]) -> None:
"""Handle a decoded 32-bit float array field."""
[docs]
def on_float64_array(self, field_id: int, values: list[float]) -> None:
"""Handle a decoded 64-bit float array field."""