"""Reference system for field cross-references."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from pystructs.struct import Struct
__all__ = (
"Ref",
"RefComparison",
"RefLogical",
)
[docs]
class Ref:
"""Reference to another field's value.
Ref allows fields to reference other fields' current values. This is used
for variable-length fields and conditional logic.
Path formats:
- 'field_name': Same level field
- 'nested.field': Nested struct field (dot notation)
- '../field_name': Parent struct field
- '/header/size': Absolute path from root
Examples:
>>> payload = Bytes(size=Ref('payload_size'))
>>> data = Bytes(size=Ref('header.data_size'))
>>> item_data = Bytes(size=Ref('../item_size'))
"""
def __init__(self, path: str):
"""Initialize a field reference.
Args:
path: Path to the field to reference
"""
self.path = path
[docs]
def resolve(self, instance: Struct) -> Any:
"""Resolve the reference to get the actual value.
Args:
instance: The struct instance to resolve from
Returns:
The value of the referenced field
"""
if self.path.startswith("/"):
# Absolute path: start from root
target = instance._root
parts = self.path[1:].split("/")
elif self.path.startswith("../"):
# Relative path: navigate to parent
target = instance
parts = self.path.split("/")
while parts and parts[0] == "..":
if target._parent is None:
raise ValueError(f"Cannot resolve '../' - no parent for {target}")
target = target._parent
parts.pop(0)
else:
# Current level: use dot notation
target = instance
parts = self.path.split(".")
for part in parts:
if not part:
continue
target = getattr(target, part)
return target
def __repr__(self) -> str:
return f"Ref({self.path!r})"
# Comparison operators for use in Conditional
def __eq__(self, other: Any) -> RefComparison: # type: ignore[override]
return RefComparison(self, "==", other)
def __ne__(self, other: Any) -> RefComparison: # type: ignore[override]
return RefComparison(self, "!=", other)
def __lt__(self, other: Any) -> RefComparison:
return RefComparison(self, "<", other)
def __le__(self, other: Any) -> RefComparison:
return RefComparison(self, "<=", other)
def __gt__(self, other: Any) -> RefComparison:
return RefComparison(self, ">", other)
def __ge__(self, other: Any) -> RefComparison:
return RefComparison(self, ">=", other)
[docs]
class RefComparison:
"""Result of comparing a Ref with a value.
Used in Conditional fields to determine if a field should exist.
Examples:
>>> extra = Conditional(UInt32(), when=Ref('version') >= 2)
"""
def __init__(self, ref: Ref, op: str, value: Any):
"""Initialize a reference comparison.
Args:
ref: The reference to compare
op: The comparison operator ('==', '!=', '<', '<=', '>', '>=')
value: The value to compare against
"""
self.ref = ref
self.op = op
self.value = value
[docs]
def evaluate(self, instance: Struct) -> bool:
"""Evaluate the comparison.
Args:
instance: The struct instance to evaluate against
Returns:
True if the comparison is satisfied
"""
resolved = self.ref.resolve(instance)
if self.op == "==":
return resolved == self.value
elif self.op == "!=":
return resolved != self.value
elif self.op == "<":
return resolved < self.value
elif self.op == "<=":
return resolved <= self.value
elif self.op == ">":
return resolved > self.value
elif self.op == ">=":
return resolved >= self.value
else:
raise ValueError(f"Unknown operator: {self.op}")
def __repr__(self) -> str:
return f"RefComparison({self.ref!r} {self.op} {self.value!r})"
# Logical operators for combining comparisons
def __and__(self, other: RefComparison) -> RefLogical:
return RefLogical(self, "and", other)
def __or__(self, other: RefComparison) -> RefLogical:
return RefLogical(self, "or", other)
[docs]
class RefLogical:
"""Logical combination of RefComparison objects.
Examples:
>>> cond = (Ref('version') >= 2) & (Ref('flags') != 0)
>>> field = Conditional(UInt32(), when=cond)
"""
def __init__(
self,
left: RefComparison | RefLogical,
op: str,
right: RefComparison | RefLogical,
):
self.left = left
self.op = op
self.right = right
[docs]
def evaluate(self, instance: Struct) -> bool:
"""Evaluate the logical expression.
Args:
instance: The struct instance to evaluate against
Returns:
True if the logical expression is satisfied
"""
left_result = self.left.evaluate(instance)
right_result = self.right.evaluate(instance)
if self.op == "and":
return left_result and right_result
elif self.op == "or":
return left_result or right_result
else:
raise ValueError(f"Unknown logical operator: {self.op}")
def __repr__(self) -> str:
return f"({self.left!r} {self.op} {self.right!r})"
def __and__(self, other: RefComparison | RefLogical) -> RefLogical:
return RefLogical(self, "and", other)
def __or__(self, other: RefComparison | RefLogical) -> RefLogical:
return RefLogical(self, "or", other)