Skip to main content

delsum_lib/
utils.rs

1use std::{
2    convert::{TryFrom, TryInto},
3    fmt::Display,
4    num::ParseIntError,
5    str::FromStr,
6};
7
8use crate::checksum::Relativity;
9
10/// Turns Result<Iterator, Error> into Iterator<Result<Iterator::Item, Error>> so that
11/// on Err, only the single error is iterated, and else the items of the iterator
12pub(crate) fn unresult_iter<I, E>(x: Result<I, E>) -> impl Iterator<Item = Result<I::Item, E>>
13where
14    I: std::iter::Iterator,
15{
16    let (i, e) = match x {
17        Ok(i) => (Some(i.map(Ok)), None),
18        Err(e) => (None, Some(std::iter::once(Err(e)))),
19    };
20    e.into_iter().flatten().chain(i.into_iter().flatten())
21}
22
23/// Small function for small cartesian products of small data types
24pub(crate) fn cart_prod<T: Clone, U: Clone>(a: &[T], b: &[U]) -> Vec<(T, U)> {
25    let mut v = Vec::new();
26    for x in a {
27        for y in b {
28            v.push((x.clone(), y.clone()))
29        }
30    }
31    v
32}
33
34#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
35pub struct InclRange<T: Copy + Ord> {
36    start: T,
37    end: T,
38}
39
40impl<T: Copy + Ord> InclRange<T> {
41    pub fn start(&self) -> T {
42        self.start
43    }
44    pub fn end(&self) -> T {
45        self.end
46    }
47}
48
49pub type SignedInclRange = InclRange<isize>;
50
51impl SignedInclRange {
52    pub fn new(start: isize, end: isize) -> Option<Self> {
53        SignedInclRange { start, end }.valid()
54    }
55    fn valid(self) -> Option<Self> {
56        if (self.start >= 0) == (self.end >= 0) && self.start > self.end {
57            None
58        } else {
59            Some(self)
60        }
61    }
62    pub fn set_start(mut self, start: isize) -> Option<Self> {
63        self.start = start;
64        self.valid()
65    }
66    pub fn set_end(mut self, end: isize) -> Option<Self> {
67        self.end = end;
68        self.valid()
69    }
70    pub fn to_unsigned(self, len: usize) -> Option<UnsignedInclRange> {
71        let unsigned_index = |idx: isize| {
72            if idx < 0 {
73                len.checked_sub(idx.wrapping_neg() as usize)
74            } else {
75                Some(idx as usize)
76            }
77            .and_then(|x| if x < len { Some(x) } else { None })
78        };
79        UnsignedInclRange::new(unsigned_index(self.start)?, unsigned_index(self.end)?)
80    }
81
82    pub fn limit(mut self, len: usize) -> Option<Self> {
83        let signed_max = match isize::try_from(len - 1) {
84            // if the len does not fit in an isize, limiting will do nothing anyway
85            Err(_) => return Some(self),
86            Ok(l) => l,
87        };
88        self.start = self.start.max(-signed_max - 1);
89        self.end = self.end.min(signed_max);
90        self.valid()
91    }
92
93    pub fn slice<T>(self, slice: &[T]) -> Option<&[T]> {
94        let unsigned = self.to_unsigned(slice.len())?;
95        Some(&slice[unsigned.start..=unsigned.end])
96    }
97}
98
99pub fn read_signed_maybe_hex(s: &str) -> Result<isize, ParseIntError> {
100    s.strip_prefix("0x")
101        .map(|s0x| isize::from_str_radix(s0x, 16))
102        .unwrap_or_else(|| s.parse())
103}
104
105impl FromStr for SignedInclRange {
106    // should be enough for now
107    type Err = String;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        let from_maybe_hex = |s: &str| match s {
111            "" => Ok(None),
112            otherwise => read_signed_maybe_hex(otherwise).map(Some),
113        };
114        let split = s
115            .split(':')
116            .map(from_maybe_hex)
117            .collect::<Result<Vec<_>, _>>()
118            .map_err(|e| e.to_string())?;
119        let invalid = String::from("Range with non-positive length");
120        match *split.as_slice() {
121            [Some(x)] => SignedInclRange::new(x, x).ok_or(invalid),
122            [Some(start), Some(end)] => SignedInclRange::new(start, end).ok_or(invalid),
123            [None, None] => SignedInclRange::new(0, -1).ok_or(invalid),
124            [Some(start), None] => SignedInclRange::new(start, -1).ok_or(invalid),
125            [None, Some(end)] => SignedInclRange::new(0, end).ok_or(invalid),
126            _ => Err(String::from(
127                "Wrong number of colons, range must be either 0xab:0xcd or 0xab by itself",
128            )),
129        }
130        .and_then(|x| {
131            if (x.start >= 0) != (x.end >= 0) {
132                Err(String::from("Range start sign is mismatched with end sign"))
133            } else {
134                Ok(x)
135            }
136        })
137    }
138}
139
140impl Display for SignedInclRange {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        let print_signed = |x: isize, f: &mut std::fmt::Formatter| {
143            if x < 0 {
144                write!(f, "-0x{:x}", -x)
145            } else {
146                write!(f, "0x{:x}", x)
147            }
148        };
149        print_signed(self.start, f)?;
150        write!(f, ":")?;
151        print_signed(self.end, f)
152    }
153}
154
155pub type UnsignedInclRange = InclRange<usize>;
156
157impl UnsignedInclRange {
158    pub fn new(start: usize, end: usize) -> Option<Self> {
159        UnsignedInclRange { start, end }.valid()
160    }
161    fn valid(self) -> Option<Self> {
162        if self.start > self.end {
163            None
164        } else {
165            Some(self)
166        }
167    }
168    pub fn set_start(mut self, start: usize) -> Option<Self> {
169        self.start = start;
170        self.valid()
171    }
172    pub fn set_end(mut self, end: usize) -> Option<Self> {
173        self.end = end;
174        self.valid()
175    }
176    pub fn contains(&self, idx: usize) -> bool {
177        idx >= self.start && idx <= self.end
178    }
179    pub fn len(&self) -> usize {
180        self.end - self.start + 1
181    }
182    pub fn is_empty(&self) -> bool {
183        false
184    }
185    pub fn to_signed(
186        self,
187        start_rel: Relativity,
188        end_rel: Relativity,
189        len: usize,
190    ) -> Option<SignedInclRange> {
191        if self.start >= len || self.end >= len {
192            return None;
193        }
194        let signed_index = |idx: usize, rel| match rel {
195            Relativity::Start => idx.try_into().ok(),
196            Relativity::End => (len - idx)
197                .try_into()
198                .ok()
199                .map(<isize as std::ops::Neg>::neg),
200        };
201        SignedInclRange::new(
202            signed_index(self.start, start_rel)?,
203            signed_index(self.end, end_rel)?,
204        )
205    }
206}