Skip to main content

hara_native/kernel/
reader.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub struct Position {
3    pub offset: usize,
4    pub line: usize,
5    pub column: usize,
6}
7
8#[derive(Debug, Clone)]
9pub struct Reader<'a> {
10    source: &'a str,
11    cursor: usize,
12    line: usize,
13    column: usize,
14    history: Vec<Position>,
15}
16impl<'a> Reader<'a> {
17    pub fn new(source: &'a str) -> Self {
18        Self {
19            source,
20            cursor: 0,
21            line: 1,
22            column: 1,
23            history: Vec::new(),
24        }
25    }
26    pub fn position(&self) -> Position {
27        Position {
28            offset: self.cursor,
29            line: self.line,
30            column: self.column,
31        }
32    }
33    pub fn peek_char(&self) -> Option<char> {
34        self.source[self.cursor..].chars().next()
35    }
36    pub fn read_char(&mut self) -> Option<char> {
37        let ch = self.peek_char()?;
38        self.history.push(self.position());
39        self.cursor += ch.len_utf8();
40        if ch == '\n' {
41            self.line += 1;
42            self.column = 1
43        } else {
44            self.column += 1
45        }
46        Some(ch)
47    }
48    pub fn unread_char(&mut self) -> Option<char> {
49        let position = self.history.pop()?;
50        let ch = self.source[position.offset..self.cursor].chars().next();
51        self.cursor = position.offset;
52        self.line = position.line;
53        self.column = position.column;
54        ch
55    }
56    pub fn read_while(&mut self, predicate: impl Fn(char) -> bool) -> String {
57        let mut value = String::new();
58        while let Some(ch) = self.read_char() {
59            if predicate(ch) {
60                value.push(ch)
61            } else {
62                self.unread_char();
63                break;
64            }
65        }
66        value
67    }
68    pub fn read_until(&mut self, predicate: impl Fn(char) -> bool) -> String {
69        self.read_while(|ch| !predicate(ch))
70    }
71    pub fn is_eof(&self) -> bool {
72        self.cursor >= self.source.len()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::{Position, Reader};
79    #[test]
80    fn tracks_unicode_lines_columns_and_unread() {
81        let mut r = Reader::new("λ\nx");
82        assert_eq!(
83            r.position(),
84            Position {
85                offset: 0,
86                line: 1,
87                column: 1
88            }
89        );
90        assert_eq!(r.read_char(), Some('λ'));
91        assert_eq!(r.position().column, 2);
92        r.unread_char();
93        assert_eq!(r.position().column, 1);
94        r.read_char();
95        r.read_char();
96        assert_eq!(
97            r.position(),
98            Position {
99                offset: 3,
100                line: 2,
101                column: 1
102            }
103        );
104    }
105}