"""Shared typed data structures used across STRATA modules.""" from __future__ import annotations from dataclasses import dataclass @dataclass(frozen=True, slots=True) class CharSpan: """Half-open character span in Python string coordinates.""" start: int end: int def __post_init__(self) -> None: if self.start < 0: raise ValueError(f"char span start must be non-negative, got {self.start}") if self.end < self.start: raise ValueError( f"char span end must be >= start, got start={self.start}, end={self.end}" ) @property def length(self) -> int: return self.end - self.start def overlaps(self, other: "CharSpan") -> bool: return self.start < other.end and other.start < self.end def contains(self, other: "CharSpan") -> bool: return self.start <= other.start and other.end <= self.end @dataclass(frozen=True, slots=True) class ByteSpan: """Half-open byte span in UTF-8 encoded coordinates.""" start: int end: int def __post_init__(self) -> None: if self.start < 0: raise ValueError(f"byte span start must be non-negative, got {self.start}") if self.end < self.start: raise ValueError( f"byte span end must be >= start, got start={self.start}, end={self.end}" ) @property def length(self) -> int: return self.end - self.start def overlaps(self, other: "ByteSpan") -> bool: return self.start < other.end and other.start < self.end def contains(self, other: "ByteSpan") -> bool: return self.start <= other.start and other.end <= self.end @dataclass(frozen=True, slots=True) class AnchoredSpan: """Span carrying both character and byte coordinates.""" char: CharSpan byte: ByteSpan @classmethod def from_offsets( cls, *, char_start: int, char_end: int, byte_start: int, byte_end: int, ) -> "AnchoredSpan": return cls( char=CharSpan(char_start, char_end), byte=ByteSpan(byte_start, byte_end), ) def overlaps(self, other: "AnchoredSpan") -> bool: return self.byte.overlaps(other.byte) and self.char.overlaps(other.char)