nl_parser/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(feature = "std")]
4extern crate std as core;
5
6use core::fmt;
7
8/// Represents either a float or an integer
9#[derive(Debug, PartialEq)]
10pub enum Number {
11    Float(f64),
12    Integer(i64),
13}
14
15/// Result of parsing a string
16#[derive(Debug, PartialEq, Eq)]
17pub enum Parsed<'a> {
18    /// A one word string Token
19    Token(&'a str),
20    /// A multi-word string Token
21    Str(&'a str),
22    /// A number-like Token
23    Number(Number),
24}
25
26/// An Error which may occur during parsing
27#[derive(Debug)]
28pub enum ParseError {
29    /// The end of the string occured during parsing the token
30    UnexpectedEof,
31    /// No valid String was found
32    InvalidString(usize),
33    /// No valid Number was found
34    InvalidNumber(usize),
35    /// No whitespace was found after the end of the string before the next token
36    ExpectedWhitespace(usize),
37}
38
39/// A helper for the result of parsing. Holds a tuple of the index of the found result, the type
40/// parsed as well as the remaining unparsed string
41pub type ParseResult<'a, T> = Result<(usize, T, &'a str), ParseError>;
42
43/// Attempt to parse a `Parsed::Token`
44pub fn parse_token(src: &str) -> ParseResult<&str> {
45    let mut t_start = None;
46    let mut t_end = None;
47    let mut end = None;
48    for (i, c) in src.chars().enumerate() {
49        if t_start.is_none() {
50            if !c.is_whitespace() {
51                t_start = Some(i);
52            }
53            continue;
54        }
55        if t_end.is_none() {
56            if c.is_whitespace() {
57                t_end = Some(i);
58            }
59            continue;
60        }
61        if !c.is_whitespace() {
62            end = Some(i);
63            break;
64        }
65    }
66    if t_start.is_none() && t_end.is_none() {
67        return Err(ParseError::UnexpectedEof);
68    }
69
70    let t_start = t_start.unwrap();
71    if t_end.is_none() {
72        Ok((t_start, &src[t_start..], ""))
73    } else if end.is_none() {
74        Ok((t_start, &src[t_start..t_end.unwrap()], ""))
75    } else {
76        Ok((t_start, &src[t_start..t_end.unwrap()], &src[end.unwrap()..]))
77    }
78}
79
80/// Attempt to parse a delimited string
81fn parse_delimited(
82    src: &str,
83    start_char: char,
84    end_char: char,
85    escape_char: char,
86) -> ParseResult<&str> {
87    let mut s_start = None;
88    let mut s_end = None;
89    let mut end = None;
90    let mut was_start_char = false;
91    let mut was_end_char = false;
92    let mut was_escape_char = false;
93    for (i, c) in src.chars().enumerate() {
94        if s_start.is_none() {
95            if was_start_char {
96                s_start = Some(i);
97                was_start_char = false;
98            } else if c == start_char {
99                was_start_char = true;
100                continue;
101            } else if c.is_whitespace() {
102                continue;
103            } else {
104                return Err(ParseError::InvalidString(i));
105            }
106        }
107        if s_end.is_none() {
108            if !was_escape_char && c == end_char {
109                s_end = Some(i);
110                was_end_char = true;
111            } else if c == escape_char {
112                was_escape_char = true;
113                continue;
114            }
115            was_escape_char = false;
116            continue;
117        }
118        if !c.is_whitespace() {
119            if was_end_char {
120                return Err(ParseError::ExpectedWhitespace(i));
121            }
122            end = Some(i);
123            break;
124        }
125        was_end_char = false;
126    }
127
128    if s_start.is_none() || s_end.is_none() {
129        return Err(ParseError::UnexpectedEof);
130    }
131
132    let s_start = s_start.unwrap();
133    let s_end = s_end.unwrap();
134    if end.is_none() {
135        Ok((s_start, &src[s_start..s_end], ""))
136    } else {
137        Ok((s_start, &src[s_start..s_end], &src[end.unwrap()..]))
138    }
139}
140
141/// Attempt to parse a `Parsed::String`
142#[inline]
143pub fn parse_string(src: &str) -> ParseResult<&str> {
144    parse_delimited(src, '`', '`', '\\')
145}
146
147/// Attempt to parse a `Parsed::Number`
148pub fn parse_number(src: &str) -> ParseResult<Number> {
149    let (index, token, rest) = parse_token(src)?;
150    if let Ok(num) = token.parse() {
151        Ok((index, Number::Integer(num), rest))
152    } else if let Ok(num) = token.parse() {
153        Ok((index, Number::Float(num), rest))
154    } else {
155        return Err(ParseError::InvalidNumber(index));
156    }
157}
158
159/// Attempt to parse a `Parsed`. Precedence is string, then number and then token
160pub fn parse_next(src: &str) -> ParseResult<Parsed> {
161    if let Ok((index, string, rest)) = parse_string(src) {
162        Ok((index, Parsed::Str(string), rest))
163    } else if let Ok((index, num, rest)) = parse_number(src) {
164        Ok((index, Parsed::Number(num), rest))
165    } else {
166        parse_token(src).map(|(index, token, rest)| (index, Parsed::Token(token), rest))
167    }
168}
169
170impl fmt::Display for ParseError {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        match self {
173            ParseError::UnexpectedEof => f.write_str("unexpected end of file"),
174            ParseError::InvalidString(i) => {
175                f.write_fmt(format_args!("invalid string at character {}", i + 1))
176            }
177            ParseError::InvalidNumber(i) => {
178                f.write_fmt(format_args!("invalid number at character {}", i + 1))
179            }
180            ParseError::ExpectedWhitespace(i) => {
181                f.write_fmt(format_args!("expected whitespace at character {}", i + 1))
182            }
183        }
184    }
185}
186
187#[cfg(feature = "std")]
188impl std::error::Error for ParseError {}
189
190impl Eq for Number {}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn parse_tokens() -> Result<(), ParseError> {
198        assert_eq!((0, "a", ""), parse_token("a")?);
199        assert_eq!((0, "ab", ""), parse_token("ab")?);
200        assert_eq!((0, "the", ""), parse_token("the")?);
201        assert_eq!((1, "the", ""), parse_token(" the")?);
202        assert_eq!((0, "the", ""), parse_token("the ")?);
203        assert_eq!((1, "the", ""), parse_token(" the ")?);
204        assert_eq!((3, "the", ""), parse_token("   the")?);
205        assert_eq!((0, "the", ""), parse_token("the   ")?);
206        assert_eq!((3, "the", ""), parse_token("   the   ")?);
207        assert_eq!((0, "the", "list"), parse_token("the list")?);
208        assert_eq!((1, "the", "list"), parse_token(" the list")?);
209        assert_eq!((0, "the", "list"), parse_token("the   list")?);
210        assert_eq!((3, "the", "list"), parse_token("   the   list")?);
211        assert_eq!((0, "the", "list "), parse_token("the list ")?);
212        assert_eq!((1, "the", "list "), parse_token(" the list ")?);
213        assert_eq!((0, "the", "list "), parse_token("the   list ")?);
214        assert_eq!((3, "the", "list "), parse_token("   the   list ")?);
215        assert!(matches!(parse_token(""), Err(ParseError::UnexpectedEof)));
216        assert!(matches!(parse_token(" "), Err(ParseError::UnexpectedEof)));
217        assert!(matches!(parse_token("   "), Err(ParseError::UnexpectedEof)));
218        Ok(())
219    }
220
221    #[test]
222    fn parse_strings() -> Result<(), ParseError> {
223        assert_eq!((1, "", ""), parse_string("``")?);
224        assert_eq!((1, "a", ""), parse_string("`a`")?);
225        assert_eq!((1, " ", ""), parse_string("` `")?);
226        assert_eq!((1, "hello, world", ""), parse_string("`hello, world`")?);
227        assert_eq!((2, "hello, world", ""), parse_string(" `hello, world`")?);
228        assert_eq!((1, "hello, world", ""), parse_string("`hello, world` ")?);
229        assert_eq!((2, "hello, world", ""), parse_string(" `hello, world` ")?);
230        assert_eq!((4, "hello, world", ""), parse_string("   `hello, world`")?);
231        assert_eq!((1, "hello, world", ""), parse_string("`hello, world`   ")?);
232        assert_eq!(
233            (4, "hello, world", ""),
234            parse_string("   `hello, world`   ")?
235        );
236
237        assert_eq!(
238            (1, "hello, world", "token"),
239            parse_string("`hello, world` token")?
240        );
241        assert_eq!(
242            (2, "hello, world", "token"),
243            parse_string(" `hello, world` token")?
244        );
245        assert_eq!(
246            (1, "hello, world", "token"),
247            parse_string("`hello, world`   token")?
248        );
249        assert_eq!(
250            (4, "hello, world", "token"),
251            parse_string("   `hello, world`   token")?
252        );
253
254        assert_eq!(
255            (1, "hello, world", "token "),
256            parse_string("`hello, world` token ")?
257        );
258        assert_eq!(
259            (2, "hello, world", "token "),
260            parse_string(" `hello, world` token ")?
261        );
262        assert_eq!(
263            (1, "hello, world", "token "),
264            parse_string("`hello, world`   token ")?
265        );
266        assert_eq!(
267            (4, "hello, world", "token "),
268            parse_string("   `hello, world`   token ")?
269        );
270
271        assert_eq!((1, "hello", "`world`"), parse_string("`hello` `world`")?);
272
273        assert!(matches!(parse_string(""), Err(ParseError::UnexpectedEof)));
274        assert!(matches!(parse_string(" "), Err(ParseError::UnexpectedEof)));
275        assert!(matches!(
276            parse_string("   "),
277            Err(ParseError::UnexpectedEof)
278        ));
279
280        assert!(matches!(parse_string("`"), Err(ParseError::UnexpectedEof)));
281        assert!(matches!(parse_string("` "), Err(ParseError::UnexpectedEof)));
282        assert!(matches!(parse_string(" `"), Err(ParseError::UnexpectedEof)));
283        assert!(matches!(
284            parse_string(" ` "),
285            Err(ParseError::UnexpectedEof)
286        ));
287
288        assert!(matches!(
289            parse_string("a"),
290            Err(ParseError::InvalidString(0))
291        ));
292        assert!(matches!(
293            parse_string("a "),
294            Err(ParseError::InvalidString(0))
295        ));
296        assert!(matches!(
297            parse_string(" a"),
298            Err(ParseError::InvalidString(1))
299        ));
300        assert!(matches!(
301            parse_string(" a "),
302            Err(ParseError::InvalidString(1))
303        ));
304
305        assert_eq!((1, r#"\`"#, ""), parse_string(r#"`\``"#)?);
306        assert_eq!((1, r#"escaped\`"#, ""), parse_string(r#"`escaped\``"#)?);
307        assert_eq!(
308            (1, r#"escaped\`text"#, ""),
309            parse_string(r#"`escaped\`text`"#)?
310        );
311        assert_eq!((1, r#" \`"#, ""), parse_string(r#"` \``"#)?);
312        assert_eq!((1, r#"\` "#, ""), parse_string(r#"`\` `"#)?);
313        assert_eq!((2, r#"\`"#, ""), parse_string(r#" `\``"#)?);
314        assert_eq!((1, r#"\`"#, ""), parse_string(r#"`\`` "#)?);
315        assert_eq!((2, r#"\`"#, ""), parse_string(r#" `\`` "#)?);
316
317        assert!(matches!(
318            parse_string("``a"),
319            Err(ParseError::ExpectedWhitespace(2))
320        ));
321        assert!(matches!(
322            parse_string("`hello`world"),
323            Err(ParseError::ExpectedWhitespace(7))
324        ));
325        assert!(matches!(
326            parse_string("`hello`world`"),
327            Err(ParseError::ExpectedWhitespace(7))
328        ));
329
330        Ok(())
331    }
332
333    #[test]
334    fn parse_numbers() -> Result<(), ParseError> {
335        assert_eq!((0, Number::Integer(0), ""), parse_number("0")?);
336        assert_eq!((0, Number::Integer(1), ""), parse_number("1")?);
337        assert_eq!((0, Number::Integer(-1), ""), parse_number("-1")?);
338        assert_eq!((0, Number::Float(0.), ""), parse_number("0.0")?);
339        assert_eq!((0, Number::Float(1.), ""), parse_number("1.0")?);
340        assert_eq!((0, Number::Float(-1.), ""), parse_number("-1.0")?);
341        assert_eq!((0, Number::Integer(123), ""), parse_number("123")?);
342        assert_eq!((0, Number::Integer(-123), ""), parse_number("-123")?);
343        assert_eq!((0, Number::Float(123.123), ""), parse_number("123.123")?);
344        assert_eq!((0, Number::Float(-123.123), ""), parse_number("-123.123")?);
345
346        assert_eq!((1, Number::Integer(1), ""), parse_number(" 1")?);
347        assert_eq!((0, Number::Integer(1), ""), parse_number("1 ")?);
348        assert_eq!((1, Number::Integer(1), ""), parse_number(" 1 ")?);
349        assert_eq!((1, Number::Float(1.), ""), parse_number(" 1.0")?);
350        assert_eq!((0, Number::Float(1.), ""), parse_number("1.0 ")?);
351        assert_eq!((1, Number::Float(1.), ""), parse_number(" 1.0 ")?);
352
353        assert_eq!((0, Number::Integer(1), "token"), parse_number("1 token")?);
354        assert_eq!((0, Number::Float(1.), "token"), parse_number("1.0 token")?);
355
356        assert!(matches!(
357            parse_number("a"),
358            Err(ParseError::InvalidNumber(0))
359        ));
360        assert!(matches!(
361            parse_number("a "),
362            Err(ParseError::InvalidNumber(0))
363        ));
364        assert!(matches!(
365            parse_number(" a"),
366            Err(ParseError::InvalidNumber(1))
367        ));
368        assert!(matches!(
369            parse_number(" a "),
370            Err(ParseError::InvalidNumber(1))
371        ));
372
373        Ok(())
374    }
375
376    #[test]
377    fn parse_nexts() -> Result<(), ParseError> {
378        assert_eq!(
379            (0, Parsed::Number(Number::Integer(0)), ""),
380            parse_next("0")?
381        );
382        assert_eq!(
383            (0, Parsed::Number(Number::Float(1.)), ""),
384            parse_next("1.0")?
385        );
386        assert_eq!(
387            (1, Parsed::Str("hello, world"), ""),
388            parse_next("`hello, world`")?
389        );
390        assert_eq!((0, Parsed::Token("token"), ""), parse_next("token")?);
391
392        assert_eq!(
393            (0, Parsed::Number(Number::Integer(0)), "token"),
394            parse_next("0 token")?
395        );
396        assert_eq!(
397            (0, Parsed::Number(Number::Float(1.)), "token"),
398            parse_next("1.0 token")?
399        );
400        assert_eq!(
401            (1, Parsed::Str("hello, world"), "token"),
402            parse_next("`hello, world` token")?
403        );
404        assert_eq!(
405            (0, Parsed::Token("token"), "token2"),
406            parse_next("token token2")?
407        );
408
409        Ok(())
410    }
411}