xiss_map/
parser.rs

1use std::{error, fmt, io, str::CharIndices};
2
3use crate::id::IdKind;
4
5#[derive(Debug)]
6pub enum ErrorKind {
7    InvalidChar(usize, char),
8    UnexpectedEOL,
9    IOError(io::Error),
10}
11
12#[derive(Debug)]
13pub struct Error {
14    kind: ErrorKind,
15    line_num: usize,
16}
17
18impl Error {
19    fn new(kind: ErrorKind) -> Self {
20        Self { kind, line_num: 0 }
21    }
22
23    fn with_line_num(self, line_num: usize) -> Self {
24        Self { line_num, ..self }
25    }
26}
27
28impl error::Error for Error {}
29
30impl fmt::Display for Error {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match &self.kind {
33            ErrorKind::InvalidChar(pos, c) => {
34                write!(f, "Invalid char '{}' at [{};{}]", self.line_num, pos, c)
35            }
36            ErrorKind::UnexpectedEOL => write!(f, "Unexpected end of line {}", self.line_num),
37            ErrorKind::IOError(err) => err.fmt(f),
38        }
39    }
40}
41
42impl From<io::Error> for Error {
43    fn from(value: io::Error) -> Self {
44        Error {
45            kind: ErrorKind::IOError(value),
46            line_num: 0,
47        }
48    }
49}
50
51pub type ParseResult<V> = Result<V, Error>;
52
53pub struct Parser<'a> {
54    s: &'a str,
55    iter: CharIndices<'a>,
56    line_num: usize,
57    prev_module_id: &'a str,
58}
59
60impl<'a> Parser<'a> {
61    pub fn new(s: &'a str) -> Self {
62        Self {
63            s,
64            iter: s.char_indices(),
65            line_num: 1,
66            prev_module_id: "",
67        }
68    }
69
70    pub fn next_id(
71        &mut self,
72    ) -> Result<Option<(IdKind, Option<&'a str>, &'a str, &'a str)>, Error> {
73        if let Some((kind, module_id, local_id, global_id)) =
74            parse_line(&self.s, &mut self.iter).map_err(|err| err.with_line_num(self.line_num))?
75        {
76            let module_id = if self.prev_module_id == module_id {
77                None
78            } else {
79                self.prev_module_id = module_id;
80                Some(module_id)
81            };
82            self.line_num += 1;
83            Ok(Some((kind, module_id, local_id, global_id)))
84        } else {
85            Ok(None)
86        }
87    }
88}
89
90fn parse_line<'a>(
91    s: &'a str,
92    iter: &mut CharIndices<'a>,
93) -> ParseResult<Option<(IdKind, &'a str, &'a str, &'a str)>> {
94    if let (2, kind) = parse_id_kind(iter)? {
95        let (i, module_id) = parse_module_id(s, iter)?;
96        let (i, local_id) = parse_local_id(s, i, iter)?;
97        let global_id = parse_global_id(s, i, iter)?;
98        Ok(Some((kind, module_id, local_id, global_id)))
99    } else {
100        Ok(None)
101    }
102}
103
104fn parse_id_kind<'a>(iter: &mut CharIndices<'a>) -> ParseResult<(usize, IdKind)> {
105    let kind = if let Some((i, c)) = iter.next() {
106        match c {
107            'C' => IdKind::Class,
108            'V' => IdKind::Var,
109            'K' => IdKind::Keyframes,
110            _ => return Err(Error::new(ErrorKind::InvalidChar(i, c))),
111        }
112    } else {
113        return Ok((0, IdKind::Class));
114    };
115
116    if let Some((i, c)) = iter.next() {
117        if c == ',' {
118            Ok((2, kind))
119        } else {
120            Err(Error::new(ErrorKind::InvalidChar(i, c)))
121        }
122    } else {
123        Err(Error::new(ErrorKind::UnexpectedEOL))
124    }
125}
126
127fn parse_module_id<'a>(s: &'a str, iter: &mut CharIndices<'a>) -> ParseResult<(usize, &'a str)> {
128    if let Some((i, c)) = iter.next() {
129        match c {
130            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {
131                for (i, c) in iter {
132                    match c {
133                        'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '/' => {}
134                        ',' => {
135                            return Ok((i + 1, &s[2..i]));
136                        }
137                        _ => return Err(Error::new(ErrorKind::InvalidChar(i, c))),
138                    }
139                }
140            }
141            _ => return Err(Error::new(ErrorKind::InvalidChar(i, c))),
142        }
143    }
144    Err(Error::new(ErrorKind::UnexpectedEOL))
145}
146
147fn parse_local_id<'a>(
148    s: &'a str,
149    start: usize,
150    iter: &mut CharIndices<'a>,
151) -> ParseResult<(usize, &'a str)> {
152    if let Some((i, c)) = iter.next() {
153        match c {
154            'a'..='z' | 'A'..='Z' | '_' => {
155                for (i, c) in iter {
156                    match c {
157                        'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {}
158                        ',' => {
159                            return Ok((i + 1, &s[start..i]));
160                        }
161                        _ => return Err(Error::new(ErrorKind::InvalidChar(i, c))),
162                    }
163                }
164            }
165            _ => return Err(Error::new(ErrorKind::InvalidChar(i, c))),
166        }
167    }
168
169    Err(Error::new(ErrorKind::UnexpectedEOL))
170}
171
172fn parse_global_id<'a>(
173    s: &'a str,
174    start: usize,
175    iter: &mut CharIndices<'a>,
176) -> ParseResult<&'a str> {
177    if let Some((i, c)) = iter.next() {
178        match c {
179            'a'..='z' | 'A'..='Z' => {
180                for (i, c) in iter {
181                    match c {
182                        'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' => {}
183                        '\n' => {
184                            return Ok(&s[start..i]);
185                        }
186                        _ => return Err(Error::new(ErrorKind::InvalidChar(i, c))),
187                    }
188                }
189                Ok(&s[start..])
190            }
191            _ => Err(Error::new(ErrorKind::InvalidChar(i, c))),
192        }
193    } else {
194        Err(Error::new(ErrorKind::UnexpectedEOL))
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    mod parse_line {
201        use std::fmt::Write;
202
203        use super::super::*;
204
205        #[test]
206        fn single_chars() {
207            let line = "C,m,l,g\n";
208            let mut iter = line.char_indices();
209            let (kind, module_id, local_id, global_id) =
210                parse_line(line, &mut iter).unwrap().unwrap();
211            assert_eq!(kind, IdKind::Class);
212            assert_eq!(module_id, "m");
213            assert_eq!(local_id, "l");
214            assert_eq!(global_id, "g");
215        }
216
217        #[test]
218        fn multiple_chars() {
219            let line = "C,m2,l2,g2\n";
220            let mut iter = line.char_indices();
221            let (kind, module_id, local_id, global_id) =
222                parse_line(line, &mut iter).unwrap().unwrap();
223            assert_eq!(kind, IdKind::Class);
224            assert_eq!(module_id, "m2");
225            assert_eq!(local_id, "l2");
226            assert_eq!(global_id, "g2");
227        }
228
229        #[test]
230        fn var() {
231            let line = "V,m,l,g\n";
232            let mut iter = line.char_indices();
233            let (kind, module_id, local_id, global_id) =
234                parse_line(line, &mut iter).unwrap().unwrap();
235            assert_eq!(kind, IdKind::Var);
236            assert_eq!(module_id, "m");
237            assert_eq!(local_id, "l");
238            assert_eq!(global_id, "g");
239        }
240
241        #[test]
242        fn keyframes() {
243            let line = "K,m,l,g\n";
244            let mut iter = line.char_indices();
245            let (kind, module_id, local_id, global_id) =
246                parse_line(line, &mut iter).unwrap().unwrap();
247            assert_eq!(kind, IdKind::Keyframes);
248            assert_eq!(module_id, "m");
249            assert_eq!(local_id, "l");
250            assert_eq!(global_id, "g");
251        }
252
253        #[test]
254        fn module_id_valid_chars() {
255            let mut line = String::with_capacity(10);
256            let mut expected_module_id = String::with_capacity(3);
257            let mut test = move |c1: char, c2: Option<char>, c3: Option<char>| {
258                expected_module_id.push(c1);
259                if let Some(c2) = c2 {
260                    expected_module_id.push(c2);
261                    if let Some(c3) = c3 {
262                        expected_module_id.push(c3);
263                    }
264                }
265                write!(&mut line, "K,{},l,g\n", &expected_module_id).unwrap();
266                let mut iter = line.char_indices();
267                let (_, module_id, _, _) = parse_line(&line, &mut iter).unwrap().unwrap();
268                assert_eq!(module_id, &expected_module_id);
269                line.clear();
270                expected_module_id.clear();
271            };
272
273            for c in 'a'..='z' {
274                test(c, None, None);
275                test('a', Some(c), None);
276                test('a', Some('/'), Some(c));
277            }
278            for c in 'A'..='Z' {
279                test(c, None, None);
280                test('a', Some(c), None);
281                test('a', Some('/'), Some(c));
282            }
283            for c in '0'..='9' {
284                test(c, None, None);
285                test('a', Some(c), None);
286                test('a', Some('/'), Some(c));
287            }
288            test('_', None, None);
289            test('a', Some('_'), None);
290            test('a', Some('/'), Some('_'));
291
292            test('a', Some('-'), None);
293            test('a', Some('/'), Some('-'));
294        }
295    }
296}