Skip to main content

pipa/value/
span.rs

1// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
2// SPDX-License-Identifier: MPL-2.0
3
4//! A byte span.
5
6use crate::value::parse;
7use core::ops::Range;
8use core::str::FromStr;
9use serde::Deserialize;
10use serde::Serialize;
11
12#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13#[derive(Serialize, Deserialize)]
14pub struct Span(pub(crate) usize, pub(crate) usize);
15
16impl core::fmt::Debug for Span {
17    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
18        write!(f, "Span({}, {})", self.0, self.1)
19    }
20}
21
22impl core::fmt::Display for Span {
23    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
24        write!(f, "{}-{}", self.0, self.1)
25    }
26}
27
28impl FromStr for Span {
29    type Err = String;
30
31    fn from_str(value: &str) -> Result<Self, Self::Err> {
32        value
33            .split_once('-')
34            .and_then(|(lo, hi)| parse::usize(lo.as_bytes()).map(|lo| (lo, hi)).ok())
35            .and_then(|(lo, hi)| parse::usize(hi.as_bytes()).map(|hi| (lo, hi)).ok())
36            .map(|(lo, hi)| Span::new(lo, hi))
37            .ok_or_else(|| value.to_string())
38    }
39}
40
41impl From<&[u8]> for Span {
42    fn from(value: &[u8]) -> Self {
43        Self::new(0, value.len())
44    }
45}
46
47impl From<usize> for Span {
48    fn from(value: usize) -> Self {
49        Self::new(0, value)
50    }
51}
52
53impl From<Range<usize>> for Span {
54    fn from(value: Range<usize>) -> Self {
55        Self::new(value.start, value.end)
56    }
57}
58
59impl Span {
60    pub fn new(lo: usize, hi: usize) -> Self {
61        if lo > hi {
62            Self::default()
63        } else {
64            Self(lo, hi)
65        }
66    }
67
68    pub fn lo(&self) -> usize {
69        self.0
70    }
71
72    pub fn hi(&self) -> usize {
73        self.1
74    }
75
76    pub fn len(&self) -> usize {
77        self.1.saturating_sub(self.0)
78    }
79
80    pub fn is_empty(&self) -> bool {
81        self.0 == self.1
82    }
83
84    pub fn with_lo(self, lo: usize) -> Self {
85        Self(lo, self.1)
86    }
87
88    pub fn with_hi(self, hi: usize) -> Self {
89        Self(self.0, hi)
90    }
91
92    pub fn into_lo(self) -> Self {
93        Self(self.0, self.0)
94    }
95
96    pub fn into_hi(self) -> Self {
97        Self(self.1, self.1)
98    }
99
100    pub fn read<'a, T: Default>(&self, list: &'a [T]) -> &'a [T] {
101        list.get(self.range()).unwrap_or_default()
102    }
103
104    pub fn range(self) -> Range<usize> {
105        self.0..self.1
106    }
107
108    pub fn lo_range(self) -> Range<usize> {
109        self.0..self.0
110    }
111
112    pub fn hi_range(self) -> Range<usize> {
113        self.1..self.1
114    }
115
116    pub fn contains(self, other: Span) -> bool {
117        self.0 <= other.0 && self.1 >= other.1
118    }
119
120    #[doc(hidden)]
121    pub fn diff(self, diff: isize) -> Self {
122        Span::new(self.0, self.1.saturating_add_signed(diff))
123    }
124
125    #[doc(hidden)]
126    pub fn expand(self, left: usize, right: usize) -> Self {
127        Span::new(self.0.saturating_sub(left), self.1.saturating_add(right))
128    }
129
130    #[doc(hidden)]
131    pub fn contract(self, left: usize, right: usize) -> Self {
132        Span::new(self.0.saturating_add(left), self.1.saturating_sub(right))
133    }
134
135    #[doc(hidden)]
136    pub(crate) fn zoom(self, span: Span) -> Self {
137        Span::new(
138            self.0.saturating_add(span.lo()),
139            self.0.saturating_add(span.hi()),
140        )
141    }
142
143    #[doc(hidden)]
144    pub fn left(self, span: Span) -> Self {
145        Span::new(
146            self.0.saturating_sub(span.lo()),
147            self.1.saturating_sub(span.lo()),
148        )
149    }
150
151    #[doc(hidden)]
152    pub fn right(self, span: Span) -> Self {
153        Span::new(
154            self.0.saturating_add(span.lo()),
155            self.1.saturating_add(span.lo()),
156        )
157    }
158}