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}