sully_input/
lib.rs

1use std::ops::RangeInclusive;
2
3#[derive(Clone, Copy, PartialEq)]
4pub struct Input<'a> {
5    string: &'a str,
6    pub index: usize,
7    line: usize,
8}
9
10impl<'a> Input<'a> {
11    pub fn new(string: &'a str) -> Self {
12        Self {
13            string,
14            index: 0,
15            line: 1,
16        }
17    }
18
19    pub fn span_to(&self, other: Self) -> Span<'a> {
20        let string = self.string;
21        let start = self.index;
22        let end = other.index;
23        let line = self.line;
24
25        Span { string, start, end, line }
26    }
27
28    pub fn advance(mut self, offset: usize) -> Self {
29        self.index += offset;
30        self
31    }
32
33    fn curr(&self) -> &'a str {
34        self.string.get(self.index..).unwrap_or("")
35    }
36
37    pub fn exact<E: Exact>(&self, exact: E) -> Option<(Self, ())> {
38        let curr = self.curr();
39        let rest = exact.exact(curr)?;
40        let delta = curr.len() - rest.len();
41        let interim = &curr[..delta];
42        let newlines = interim.chars().fold(0, |acc, c| if c == '\n' {
43            acc + 1
44        } else {
45            acc
46        });
47        let input = Self {
48            string: self.string,
49            index: self.index + delta,
50            line: self.line + newlines,
51        };
52        Some((input, ()))
53    }
54}
55
56impl<'a> std::fmt::Debug for Input<'a> {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        let slice = self.curr();
59        let slice = &slice[..slice.len().min(15)];
60        write!(f, "Input({:?} ({}) {:?})", self.index, self.line, slice)
61    }
62}
63
64#[test]
65fn test_debug_input() {
66    let string = "word\nword\nword";
67    let index = 5;
68    let line = 2;
69    let input = Input { string, index, line };
70    assert_eq!(format!("{:?}", &input), "Input(5 (2) \"word\\nword\")".to_string());
71}
72
73#[derive(Clone, Copy, PartialEq)]
74pub struct Span<'a> {
75    string: &'a str,
76    start: usize,
77    end: usize,
78    line: usize,
79}
80
81impl<'a> Span<'a> {
82    pub fn new(string: &'a str, start: usize, end: usize) -> Self {
83        let line = string
84            .get(..start)
85            .expect("Bad span.")
86            .chars()
87            .filter(|c| *c == '\n')
88            .count() + 1;
89        Self { string, start, end, line }
90    }
91
92    pub fn slice(&self) -> &'a str {
93        self.string.get(self.start..self.end).expect("Bad span.")
94    }
95
96    pub fn column(&self) -> usize {
97        let string = &self.string[..self.start];
98        let index = string.rfind('\n').map(|i| i + 1).unwrap_or(0);
99        self.start - index + 1
100    }
101    
102    pub fn error(&self, message: &str) -> String {
103        let start = self.string
104            .get(..self.start)
105            .unwrap()
106            .rfind('\n')
107            .map(|i| i + 1)
108            .unwrap_or(0);
109        let end = self.string
110            .get(self.end..)
111            .unwrap()
112            .find('\n')
113            .unwrap_or(self.string.len());
114        format!(
115            "[Error {line}:{column}] {message}\n{content}\n{leading}{carets}",
116            line    = self.line,
117            column  = self.column(),
118            message = message,
119            content = &self.string[start..end],
120            leading = " ".repeat(self.column() - 1),
121            carets  = "^".repeat(self.end - self.start),
122        )
123    }
124}
125
126#[test]
127fn test_span_new() {
128    assert_eq!(Span::new("Hello\nthere!", 6, 9).line, 2);
129}
130
131impl<'a> std::fmt::Debug for Span<'a> {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        let slice = self.slice();
134        write!(f, "Span({:?} ({}) {:?})", self.start..self.end, self.line, slice)
135    }
136}
137
138#[test]
139fn test_debug_span() {
140    let string = "word\nword\nword";
141    let start = 5;
142    let end = 9;
143    let line = 2;
144    let span = Span { string, start, end, line };
145    assert_eq!(format!("{:?}", span), "Span(5..9 (2) \"word\")".to_string());
146}
147
148pub trait Exact {
149    fn exact<'a>(&self, input: &'a str) -> Option<&'a str>;
150}
151
152/// Parse a prefix matching the char exactly
153impl Exact for char {
154    fn exact<'a>(&self, input: &'a str) -> Option<&'a str> {
155        input.strip_prefix(*self)
156    }
157}
158
159/// Parse a prefix matching the &str exactly
160impl Exact for &str {
161    fn exact<'a>(&self, input: &'a str) -> Option<&'a str> {
162        input.strip_prefix(self)
163    }
164}
165
166/// Parse *any* of the characters in the inclusive range
167impl Exact for RangeInclusive<char> {
168    fn exact<'a>(&self, input: &'a str) -> Option<&'a str> {
169        let c = input.chars().next()?;
170        if self.contains(&c) {
171            Some(&input[c.len_utf8()..])
172        } else {
173            None
174        }
175    }
176}
177
178/// Parse a single character matching the predicate
179impl<F> Exact for F
180where
181    F: Fn(char) -> bool
182{
183    fn exact<'a>(&self, input: &'a str) -> Option<&'a str> {
184        input.strip_prefix(self)
185    }
186}
187
188#[test]
189fn test_exact() {
190    let input = Input::new("1234");
191    let mut out = input;
192    out.index += 1;
193    assert_eq!(input.exact('0'..='9'), Some((out, ())));
194
195    let input = Input::new("Hello");
196    let mut out = input;
197    out.index += 1;
198    assert_eq!(input.exact('H'), Some((out, ())));
199
200    let input = Input::new("Hello");
201    let mut out = input;
202    out.index += 5;
203    assert_eq!(input.exact("Hello"), Some((out, ())));
204}
205
206#[test]
207fn test_span_to() {
208    let input = Input::new("1234");
209    let mut rest = input;
210    rest.index += 2;
211    let span = input.span_to(rest);
212    assert_eq!(span, Span {
213        string: "1234",
214        start: 0,
215        end: 2,
216        line: 1,
217    });
218}
219
220#[test]
221fn test_advance() {
222    let input = Input::new("1234");
223    assert_eq!(input.advance(2).index, 2);
224}