1use crate::int_stream::{EOF, IntStream, UNKNOWN_SOURCE_NAME};
2use std::rc::Rc;
3
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct TextInterval {
6 pub start: usize,
7 pub stop: usize,
8}
9
10impl TextInterval {
11 pub const fn new(start: usize, stop: usize) -> Self {
12 Self { start, stop }
13 }
14
15 pub const fn empty() -> Self {
16 Self { start: 1, stop: 0 }
17 }
18
19 pub const fn is_empty(self) -> bool {
20 self.start > self.stop
21 }
22}
23
24#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
26pub struct PositionSummary {
27 pub line_breaks: usize,
29 pub trailing_columns: usize,
32}
33
34impl PositionSummary {
35 pub const fn apply(self, line: usize, column: usize) -> (usize, usize) {
37 let line = line.saturating_add(self.line_breaks);
38 let column = if self.line_breaks == 0 {
39 column.saturating_add(self.trailing_columns)
40 } else {
41 self.trailing_columns
42 };
43 (line, column)
44 }
45}
46
47pub trait CharStream: IntStream {
48 fn text(&self, interval: TextInterval) -> String;
49
50 fn symbol_at(&self, _index: usize) -> Option<i32> {
57 None
58 }
59
60 fn contiguous_ascii(&self) -> Option<&[u8]> {
63 None
64 }
65
66 fn position_summary(&self, _start: usize, _end: usize) -> Option<PositionSummary> {
72 None
73 }
74
75 fn source_text(&self) -> Option<Rc<str>> {
81 None
82 }
83
84 fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
85 self.text_source_interval(interval)
86 .map(|(_, start, stop)| (start, stop))
87 }
88
89 fn text_source_interval(&self, _interval: TextInterval) -> Option<(Rc<str>, usize, usize)> {
90 None
91 }
92}
93
94#[derive(Clone, Debug)]
95pub struct InputStream {
96 source: Rc<str>,
97 data: InputData,
98 cursor: usize,
99 source_name: String,
100}
101
102#[derive(Clone, Debug)]
103enum InputData {
104 Ascii,
105 Unicode {
106 chars: Vec<char>,
107 byte_offsets: Vec<usize>,
108 },
109}
110
111impl InputData {
112 fn new(input: &str) -> Self {
113 if input.is_ascii() {
114 Self::Ascii
115 } else {
116 Self::Unicode {
117 chars: input.chars().collect(),
118 byte_offsets: input.char_indices().map(|(index, _)| index).collect(),
119 }
120 }
121 }
122
123 const fn len(&self, source: &str) -> usize {
124 match self {
125 Self::Ascii => source.len(),
126 Self::Unicode { chars, .. } => chars.len(),
127 }
128 }
129
130 fn get(&self, source: &str, index: usize) -> Option<char> {
131 match self {
132 Self::Ascii => source.as_bytes().get(index).map(|byte| char::from(*byte)),
133 Self::Unicode { chars, .. } => chars.get(index).copied(),
134 }
135 }
136
137 fn byte_bounds(&self, source: &str, start: usize, stop: usize) -> Option<(usize, usize)> {
138 match self {
139 Self::Ascii => Some((start, stop + 1)),
140 Self::Unicode { byte_offsets, .. } => {
141 let start_byte = *byte_offsets.get(start)?;
142 let stop_byte = byte_offsets.get(stop + 1).copied().unwrap_or(source.len());
143 Some((start_byte, stop_byte))
144 }
145 }
146 }
147}
148
149impl InputStream {
150 pub fn new(input: impl AsRef<str>) -> Self {
153 Self::with_source_name(input, UNKNOWN_SOURCE_NAME)
154 }
155
156 pub fn with_source_name(input: impl AsRef<str>, source_name: impl Into<String>) -> Self {
159 let input = input.as_ref();
160 Self {
161 source: Rc::from(input),
162 data: InputData::new(input),
163 cursor: 0,
164 source_name: source_name.into(),
165 }
166 }
167
168 pub fn is_eof(&self) -> bool {
170 self.cursor >= self.data.len(&self.source)
171 }
172}
173
174impl IntStream for InputStream {
175 fn consume(&mut self) {
176 if !self.is_eof() {
177 self.cursor += 1;
178 }
179 }
180
181 fn la(&mut self, offset: isize) -> i32 {
182 if offset == 0 {
183 return 0;
184 }
185
186 let absolute = if offset > 0 {
187 self.cursor.checked_add((offset - 1).cast_unsigned())
188 } else {
189 offset
190 .checked_neg()
191 .and_then(|distance| usize::try_from(distance).ok())
192 .and_then(|distance| self.cursor.checked_sub(distance))
193 };
194
195 absolute
196 .and_then(|index| self.data.get(&self.source, index))
197 .map_or(EOF, |ch| ch as i32)
198 }
199
200 fn index(&self) -> usize {
201 self.cursor
202 }
203
204 fn seek(&mut self, index: usize) {
205 self.cursor = index.min(self.data.len(&self.source));
206 }
207
208 fn size(&self) -> usize {
209 self.data.len(&self.source)
210 }
211
212 fn source_name(&self) -> &str {
213 &self.source_name
214 }
215}
216
217impl CharStream for InputStream {
218 fn text(&self, interval: TextInterval) -> String {
220 if let Some((source, start, stop)) = self.text_source_interval(interval) {
221 return source[start..stop].to_owned();
222 }
223 String::new()
224 }
225
226 fn symbol_at(&self, index: usize) -> Option<i32> {
227 Some(
228 self.data
229 .get(&self.source, index)
230 .map_or(EOF, |ch| u32::from(ch).cast_signed()),
231 )
232 }
233
234 fn contiguous_ascii(&self) -> Option<&[u8]> {
235 matches!(self.data, InputData::Ascii).then(|| self.source.as_bytes())
236 }
237
238 fn position_summary(&self, start: usize, end: usize) -> Option<PositionSummary> {
239 if start > end {
240 return None;
241 }
242 let len = self.data.len(&self.source);
243 let start = start.min(len);
244 let end = end.min(len);
245
246 let mut summary = PositionSummary::default();
247 let mut note = |is_newline| {
248 if is_newline {
249 summary.line_breaks += 1;
250 summary.trailing_columns = 0;
251 } else {
252 summary.trailing_columns += 1;
253 }
254 };
255 match &self.data {
256 InputData::Ascii => {
257 for &byte in &self.source.as_bytes()[start..end] {
258 note(byte == b'\n');
259 }
260 }
261 InputData::Unicode { chars, .. } => {
262 for &ch in &chars[start..end] {
263 note(ch == '\n');
264 }
265 }
266 }
267 Some(summary)
268 }
269
270 fn text_source_interval(&self, interval: TextInterval) -> Option<(Rc<str>, usize, usize)> {
271 let len = self.data.len(&self.source);
272 if interval.is_empty() || len == 0 {
273 return None;
274 }
275
276 let start = interval.start.min(len);
277 let stop = interval.stop.min(len.saturating_sub(1));
278 if start > stop {
279 return None;
280 }
281
282 let (start_byte, stop_byte) = self.data.byte_bounds(&self.source, start, stop)?;
283 Some((Rc::clone(&self.source), start_byte, stop_byte))
284 }
285
286 fn source_text(&self) -> Option<Rc<str>> {
287 Some(Rc::clone(&self.source))
288 }
289
290 fn byte_interval(&self, interval: TextInterval) -> Option<(usize, usize)> {
291 let len = self.data.len(&self.source);
292 if interval.is_empty() || len == 0 {
293 return None;
294 }
295 let start = interval.start.min(len);
296 let stop = interval.stop.min(len.saturating_sub(1));
297 (start <= stop)
298 .then(|| self.data.byte_bounds(&self.source, start, stop))
299 .flatten()
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn lookahead_and_text_are_codepoint_indexed() {
309 let mut input = InputStream::with_source_name("aβ\n", "sample");
310 assert_eq!(input.source_name(), "sample");
311 assert_eq!(input.size(), 3);
312 assert_eq!(input.la(1), 'a' as i32);
313 assert_eq!(input.la(2), 'β' as i32);
314 assert_eq!(input.text(TextInterval::new(0, 1)), "aβ");
315 input.consume();
316 assert_eq!(input.index(), 1);
317 assert_eq!(input.la(-1), 'a' as i32);
318 assert_eq!(input.la(isize::MIN), EOF);
319 input.seek(99);
320 assert_eq!(input.la(1), EOF);
321 }
322
323 #[test]
324 fn optional_fast_paths_preserve_scalar_indexes_and_positions() {
325 let ascii = InputStream::new("ab\ncd");
326 assert_eq!(ascii.contiguous_ascii(), Some(&b"ab\ncd"[..]));
327 assert_eq!(ascii.symbol_at(2), Some('\n' as i32));
328 assert_eq!(ascii.symbol_at(5), Some(EOF));
329 assert_eq!(
330 ascii.position_summary(1, 5),
331 Some(PositionSummary {
332 line_breaks: 1,
333 trailing_columns: 2,
334 })
335 );
336 assert_eq!(
337 ascii.position_summary(5, 99),
338 Some(PositionSummary::default())
339 );
340 assert_eq!(ascii.position_summary(4, 2), None);
341 assert_eq!(ascii.position_summary(7, 6), None);
342
343 let unicode = InputStream::new("aβ\nγ");
344 assert_eq!(unicode.contiguous_ascii(), None);
345 assert_eq!(unicode.symbol_at(1), Some('β' as i32));
346 assert_eq!(unicode.symbol_at(4), Some(EOF));
347 assert_eq!(
348 unicode.position_summary(1, 4),
349 Some(PositionSummary {
350 line_breaks: 1,
351 trailing_columns: 1,
352 })
353 );
354 }
355
356 #[test]
357 fn position_summary_applies_to_existing_coordinates() {
358 assert_eq!(
359 PositionSummary {
360 line_breaks: 0,
361 trailing_columns: 3,
362 }
363 .apply(4, 7),
364 (4, 10)
365 );
366 assert_eq!(
367 PositionSummary {
368 line_breaks: 2,
369 trailing_columns: 3,
370 }
371 .apply(4, 7),
372 (6, 3)
373 );
374 }
375}