File size: 2,309 Bytes
e69b72a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"""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)