tot/
parser.rs

1use std::collections::HashMap;
2
3use nom::{
4    branch::alt,
5    bytes::complete::{is_not, tag, take_till, take_till1, take_until},
6    character::complete::multispace1,
7    combinator::{map, value},
8    multi::many0,
9    number::complete::double,
10    sequence::{delimited, pair, separated_pair, tuple},
11    IResult,
12};
13
14#[derive(thiserror::Error, Debug)]
15pub enum Error {
16    #[error("error ocurred while parsing")]
17    ParseError,
18}
19
20#[derive(Debug, PartialEq, Clone)]
21pub enum TotValue {
22    Unit,
23    Boolean(bool),
24    String(String),
25    Number(f64),
26    List(Vec<TotValue>),
27    Dict(HashMap<String, TotValue>),
28}
29
30pub type PResult<'a, T> = IResult<&'a str, T>;
31
32fn token(i: &str) -> PResult<&str> {
33    take_till1(|c: char| c.is_whitespace())(i)
34}
35
36pub(crate) fn unit(i: &str) -> PResult<()> {
37    value((), tag("null"))(i)
38}
39
40pub(crate) fn boolean(i: &str) -> PResult<bool> {
41    alt((value(true, tag("true")), value(false, tag("false"))))(i)
42}
43
44pub(crate) fn number(i: &str) -> PResult<f64> {
45    double(i)
46}
47
48pub(crate) fn string(i: &str) -> PResult<String> {
49    map(
50        delimited(tag("\""), take_till(|c: char| c == '"'), tag("\"")),
51        String::from,
52    )(i)
53}
54
55fn whitespace(i: &str) -> PResult<()> {
56    map(multispace1, |_| ())(i)
57}
58
59fn comma(i: &str) -> PResult<()> {
60    value((), tag(","))(i)
61}
62
63fn line_comment(i: &str) -> PResult<()> {
64    value((), pair(tag("//"), is_not("\r\n")))(i)
65}
66
67fn block_comment(i: &str) -> PResult<()> {
68    value((), tuple((tag("/*"), take_until("*/"), tag("*/"))))(i)
69}
70
71pub(crate) fn all_ignored(i: &str) -> PResult<()> {
72    map(
73        many0(alt((line_comment, block_comment, comma, whitespace))),
74        |_| (),
75    )(i)
76}
77
78fn list(i: &str) -> PResult<TotValue> {
79    delimited(tag("["), list_contents, tag("]"))(i)
80}
81
82fn list_contents(i: &str) -> PResult<TotValue> {
83    map(many0(delimited(all_ignored, scalar, all_ignored)), |v| {
84        TotValue::List(v)
85    })(i)
86}
87
88fn dict(i: &str) -> PResult<TotValue> {
89    delimited(tag("{"), dict_contents, tag("}"))(i)
90}
91
92fn dict_contents(i: &str) -> PResult<TotValue> {
93    map(many0(key_value), |v| TotValue::Dict(HashMap::from_iter(v)))(i)
94}
95
96pub(crate) fn key(i: &str) -> PResult<String> {
97    alt((map(string, String::from), map(token, String::from)))(i)
98}
99
100pub(crate) fn expression(i: &str) -> PResult<TotValue> {
101    todo!()
102}
103
104// TODO missing s-expressions
105fn scalar(i: &str) -> PResult<TotValue> {
106    alt((
107        map(unit, |_| TotValue::Unit),
108        map(boolean, |v| TotValue::Boolean(v)),
109        map(number, |v| TotValue::Number(v)),
110        map(string, |v| TotValue::String(v)),
111        list,
112        dict,
113    ))(i)
114}
115
116fn key_value(i: &str) -> PResult<(String, TotValue)> {
117    delimited(
118        all_ignored,
119        separated_pair(key, all_ignored, scalar),
120        all_ignored,
121    )(i)
122}
123
124pub fn parse(i: &str) -> Result<TotValue, Error> {
125    if let Ok((rem, v)) = dict_contents(i) {
126        if rem.is_empty() {
127            return Ok(v);
128        }
129    }
130
131    Err(Error::ParseError)
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use std::collections::HashMap;
138
139    #[test]
140    fn test_parse() {
141        if let TotValue::Dict(v) = parse("test 1").unwrap() {
142            assert_eq!(
143                v.get_key_value("test").unwrap(),
144                (&"test".to_string(), &TotValue::Number(1.0))
145            );
146        } else {
147            assert!(false);
148        }
149
150        if let TotValue::Dict(v) = parse("test 1 blah true").unwrap() {
151            assert_eq!(v.get("test").unwrap(), &TotValue::Number(1.0));
152            assert_eq!(v.get("blah").unwrap(), &TotValue::Boolean(true));
153        } else {
154            assert!(false);
155        }
156
157        if let TotValue::Dict(v) = parse(
158            "\
159test 1
160blah true
161dict {
162    hello \"world\"
163}
164",
165        )
166        .unwrap()
167        {
168            assert_eq!(v.get("test").unwrap(), &TotValue::Number(1.0));
169            assert_eq!(v.get("blah").unwrap(), &TotValue::Boolean(true));
170            assert_eq!(
171                v.get("dict").unwrap(),
172                &TotValue::Dict({
173                    let mut m = HashMap::new();
174                    m.insert("hello".to_string(), TotValue::String("world".to_string()));
175
176                    m
177                })
178            );
179        } else {
180            assert!(false);
181        }
182    }
183
184    #[test]
185    fn test_unit() {
186        let (rem, _) = unit("null// hello").unwrap();
187        assert_eq!(rem, "// hello");
188    }
189
190    #[test]
191    fn test_token() {
192        let (rem, par) = token("my-key 2").unwrap();
193        assert_eq!(rem, " 2");
194        assert_eq!(par, "my-key");
195
196        assert!(token("").is_err());
197    }
198
199    #[test]
200    fn test_boolean() {
201        let (_, par) = boolean("true").unwrap();
202        assert_eq!(par, true);
203
204        let (_, par) = boolean("false").unwrap();
205        assert_eq!(par, false);
206
207        assert!(boolean("True").is_err());
208        assert!(boolean("False").is_err());
209        assert!(boolean("").is_err());
210    }
211
212    #[test]
213    fn test_number() {
214        let (_, par) = number("10").unwrap();
215        assert_eq!(par, f64::from(10));
216
217        let (_, par) = number("10.").unwrap();
218        assert_eq!(par, f64::from(10));
219
220        let (_, par) = number("10.0").unwrap();
221        assert_eq!(par, f64::from(10));
222
223        let (_, par) = number("0").unwrap();
224        assert_eq!(par, f64::from(0));
225
226        let (_, par) = number("0.1").unwrap();
227        assert_eq!(par, f64::from(0.1));
228
229        let (_, par) = number(".1").unwrap();
230        assert_eq!(par, f64::from(0.1));
231
232        let (_, par) = number("10]").unwrap();
233        assert_eq!(par, f64::from(10));
234
235        assert!(number("one").is_err());
236        assert!(number("").is_err());
237    }
238
239    #[test]
240    fn test_string() {
241        let (rem, par) = string("\"hello world\"foo").unwrap();
242        assert_eq!(rem, "foo");
243        assert_eq!(par, "hello world");
244
245        assert!(string("hello world").is_err());
246        assert!(string("1").is_err());
247    }
248
249    #[test]
250    fn test_whitespace() {
251        let (rem, _) = whitespace(" hello").unwrap();
252        assert_eq!(rem, "hello");
253
254        let (rem, _) = whitespace(" ").unwrap();
255        assert_eq!(rem, "");
256
257        assert!(whitespace("hello").is_err());
258    }
259
260    #[test]
261    fn test_line_comment() {
262        let (rem, _) = line_comment("// blah").unwrap();
263        assert_eq!(rem, "");
264
265        let (rem, _) = line_comment("// this is a comment\ntext").unwrap();
266        assert_eq!(rem, "\ntext");
267    }
268
269    #[test]
270    fn test_block_comment() {
271        let (rem, _) = block_comment("/* moo */\nhello world").unwrap();
272        assert_eq!(rem, "\nhello world");
273
274        let (rem, _) = block_comment("/* moo\n\n\t\r */\nhello world").unwrap();
275        assert_eq!(rem, "\nhello world");
276    }
277
278    #[test]
279    fn test_all_ignored() {
280        let (rem, _) = all_ignored("/* hello world *///hello").unwrap();
281        assert_eq!(rem, "");
282
283        let (rem, _) = all_ignored("/* hello world */    //hello").unwrap();
284        assert_eq!(rem, "");
285
286        let (rem, _) = all_ignored("//hello /* hello world */").unwrap();
287        assert_eq!(rem, "");
288
289        let (rem, _) = all_ignored("/* hello world */ woot").unwrap();
290        assert_eq!(rem, "woot");
291    }
292
293    #[test]
294    fn test_list() {
295        let (rem, par) = list("[]").unwrap();
296        assert_eq!(rem, "");
297        assert_eq!(par, TotValue::List(vec![]));
298
299        let (rem, par) = list("[1]").unwrap();
300        assert_eq!(rem, "");
301        assert_eq!(par, TotValue::List(vec![TotValue::Number(1.0)]));
302
303        let (rem, par) = list("[] blah []").unwrap();
304        assert_eq!(rem, " blah []");
305        assert_eq!(par, TotValue::List(vec![]));
306
307        let (rem, par) = list("[1] blah []").unwrap();
308        assert_eq!(rem, " blah []");
309        assert_eq!(par, TotValue::List(vec![TotValue::Number(1.0)]));
310
311        let (rem, par) = list("[1, 2\n , /* inner comment */ 3.1 4] blah []").unwrap();
312        assert_eq!(rem, " blah []");
313        assert_eq!(
314            par,
315            TotValue::List(vec![
316                TotValue::Number(1.0),
317                TotValue::Number(2.0),
318                TotValue::Number(3.1),
319                TotValue::Number(4.0)
320            ])
321        );
322
323        assert!(list("").is_err());
324        // Not a list
325        assert!(list("hello").is_err());
326        // Invalid identifier
327        assert!(list("[hello]").is_err());
328        // Unterminated list
329        assert!(list("[").is_err());
330        assert!(list("[ 1 ").is_err());
331    }
332
333    #[test]
334    fn test_dict() {
335        let (rem, par) = dict("{}").unwrap();
336        assert_eq!(rem, "");
337        assert_eq!(par, TotValue::Dict(HashMap::default()));
338
339        let (_, par) = dict("{hello \"world\"}").unwrap();
340        assert_eq!(
341            par,
342            TotValue::Dict({
343                let mut map = HashMap::new();
344                map.insert("hello".to_string(), TotValue::String("world".to_string()));
345
346                map
347            })
348        );
349
350        let (_, par) = dict("{hello \"world\" inner-list [true 10]}").unwrap();
351        assert_eq!(
352            par,
353            TotValue::Dict({
354                let mut map = HashMap::new();
355                map.insert("hello".to_string(), TotValue::String("world".to_string()));
356                map.insert(
357                    "inner-list".to_string(),
358                    TotValue::List(vec![TotValue::Boolean(true), TotValue::Number(10.0)]),
359                );
360
361                map
362            })
363        );
364    }
365
366    #[test]
367    fn test_key() {
368        let (rem, par) = key("my-key").unwrap();
369        assert_eq!(rem, "");
370        assert_eq!(par, "my-key");
371
372        let (rem, par) = key("my-key 2").unwrap();
373        assert_eq!(rem, " 2");
374        assert_eq!(par, "my-key");
375
376        let (rem, par) = key("\"my-key\" 2").unwrap();
377        assert_eq!(rem, " 2");
378        assert_eq!(par, "my-key");
379
380        let (rem, par) = key("\"my key\" 2").unwrap();
381        assert_eq!(rem, " 2");
382        assert_eq!(par, "my key");
383    }
384
385    #[test]
386    fn test_scalar() {
387        let (_, par) = scalar("true").unwrap();
388        assert_eq!(par, TotValue::Boolean(true));
389
390        let (_, par) = scalar("1").unwrap();
391        assert_eq!(par, TotValue::Number(1.0));
392
393        let (_, par) = scalar("\"hello\"").unwrap();
394        assert_eq!(par, TotValue::String("hello".to_string()));
395
396        let (_, par) = scalar("[false]").unwrap();
397        assert_eq!(par, TotValue::List(vec![TotValue::Boolean(false)]));
398    }
399
400    #[test]
401    fn test_key_value() {
402        let (_, par) = key_value("hello true").unwrap();
403        assert_eq!(par.0, "hello");
404        assert_eq!(par.1, TotValue::Boolean(true));
405
406        let (_, par) = key_value("\"hello world\" false").unwrap();
407        assert_eq!(par.0, "hello world");
408        assert_eq!(par.1, TotValue::Boolean(false));
409
410        let (_, par) = key_value("hello 10").unwrap();
411        assert_eq!(par.0, "hello");
412        assert_eq!(par.1, TotValue::Number(10.0));
413
414        let (_, par) = key_value("hello \"world\"").unwrap();
415        assert_eq!(par.0, "hello");
416        assert_eq!(par.1, TotValue::String("world".to_string()));
417
418        let (_, par) = key_value("hello [0 true [\"hello\"]]").unwrap();
419        assert_eq!(par.0, "hello");
420        assert_eq!(
421            par.1,
422            TotValue::List(vec![
423                TotValue::Number(0.0),
424                TotValue::Boolean(true),
425                TotValue::List(vec![TotValue::String("hello".to_string())])
426            ])
427        );
428    }
429}