axon_parseast_parser/
lib.rs

1#[macro_use]
2extern crate lalrpop_util;
3
4use chrono::{NaiveDate, NaiveTime};
5use raystack_core::{Number, Ref, Symbol, TagName};
6use regex::Regex;
7use std::collections::HashMap;
8use std::str::FromStr;
9
10lalrpop_mod!(pub grammar); // synthesized by LALRPOP
11
12/// Parse the output of `toAxonCode(parseAst( ... ))` and return a `Val`.
13pub fn parse(axon: &str) -> Result<Val, impl std::error::Error + '_> {
14    let parser = grammar::ValParser::new();
15    parser.parse(axon)
16}
17
18pub(crate) fn str_to_number(s: &str) -> Number {
19    let re = Regex::new(r".+E(-?\d+).*").unwrap();
20    let captures = re.captures(s);
21
22    if let Some(captures) = captures {
23        let exp_str = captures
24            .get(1)
25            .unwrap_or_else(|| {
26                panic!("exponent capture should contain index = 1, {}", s)
27            })
28            .as_str();
29        let exp: i32 = exp_str.parse().unwrap_or_else(|_| {
30            panic!(
31                "exponent capture 1 should be a string containing an i32, {}",
32                s
33            )
34        });
35        let delimiter = format!("E{}", exp);
36        let mut split = s.split(&delimiter);
37        let significand: f64 = split
38            .next()
39            .unwrap_or_else(|| {
40                panic!("splitting on E\\d+ should leave a base, {}", s)
41            })
42            .parse()
43            .unwrap_or_else(|_| {
44                panic!("base should be a string containing a f64")
45            });
46        let unit = split.next().map(|unit_str| unit_str.to_owned());
47        let unit = normalize_unit(unit);
48
49        Number::new_scientific(significand, exp, unit).expect("constructing a new scientific number should work")
50    } else {
51        let no_exp_re = Regex::new(r"(-?\d+(\.\d+)?)([^0-9]*)").unwrap();
52        let captures = no_exp_re.captures(s).unwrap();
53
54        let float_str = captures.get(1).unwrap().as_str();
55        let float = f64::from_str(float_str).unwrap();
56
57        let unit = captures.get(3).map(|cap| cap.as_str().to_owned());
58        let unit = normalize_unit(unit);
59
60        Number::new(float, unit)
61    }
62}
63
64fn normalize_unit(unit: Option<String>) -> Option<String> {
65    match unit {
66        None => None,
67        Some(raw_unit) => match &raw_unit[..] {
68            "" => None,
69            _ => Some(raw_unit),
70        },
71    }
72}
73
74/// An Axon value.
75#[derive(Clone, Debug, PartialEq)]
76pub enum Val {
77    Dict(HashMap<TagName, Box<Val>>),
78    List(Vec<Val>),
79    Lit(Lit),
80}
81
82/// An Axon literal.
83#[derive(Clone, Debug, PartialEq)]
84pub enum Lit {
85    Bool(bool),
86    Date(NaiveDate),
87    DictMarker,
88    DictRemoveMarker,
89    Null,
90    Num(Number),
91    Ref(Ref),
92    Str(String),
93    Symbol(Symbol),
94    Time(NaiveTime),
95    Uri(String),
96    YearMonth(YearMonth),
97}
98
99/// Represents a month in a specific year.
100#[derive(Clone, Debug, PartialEq)]
101pub struct YearMonth {
102    pub year: u32,
103    pub month: Month,
104}
105
106impl YearMonth {
107    pub fn new(year: u32, month: Month) -> Self {
108        Self { year, month }
109    }
110}
111
112/// Represents a month of a year.
113#[derive(Clone, Debug, PartialEq)]
114pub enum Month {
115    Jan,
116    Feb,
117    Mar,
118    Apr,
119    May,
120    Jun,
121    Jul,
122    Aug,
123    Sep,
124    Oct,
125    Nov,
126    Dec,
127}
128
129impl Month {
130    /// Convert from a number between 1 and 12 inclusive.
131    fn from_int(int: u32) -> Option<Self> {
132        match int {
133            1 => Some(Month::Jan),
134            2 => Some(Month::Feb),
135            3 => Some(Month::Mar),
136            4 => Some(Month::Apr),
137            5 => Some(Month::May),
138            6 => Some(Month::Jun),
139            7 => Some(Month::Jul),
140            8 => Some(Month::Aug),
141            9 => Some(Month::Sep),
142            10 => Some(Month::Oct),
143            11 => Some(Month::Nov),
144            12 => Some(Month::Dec),
145            _ => None,
146        }
147    }
148}
149
150#[cfg(test)]
151mod test {
152    use super::grammar;
153    use super::{str_to_number, Lit, Month, Val, YearMonth};
154    use chrono::{NaiveDate, NaiveTime};
155    use raystack_core::{Number, Ref, Symbol, TagName};
156    use std::collections::HashMap;
157
158    const HELLO_WORLD: &str = r###"{type:"func", params:[], body:{type:"block", exprs:[{type:"literal", val:"hello world"}]}}"###;
159    const DELETE_EQUIP: &str = include_str!("../test_input/delete_equip.txt");
160    const MISC_FUNC: &str = include_str!("../test_input/misc_func.txt");
161    const VALIDATE: &str = include_str!("../test_input/validate.txt");
162    const EVAL_FUNC_TEST: &str =
163        include_str!("../test_input/eval_func_test.txt");
164
165    #[test]
166    fn str_to_number_works() {
167        assert_eq!(str_to_number("1"), Number::new(1.0, None));
168        assert_eq!(
169            str_to_number("1min"),
170            Number::new(1.0, Some("min".to_owned()))
171        );
172        assert_eq!(str_to_number("-1"), Number::new(-1.0, None));
173        assert_eq!(
174            str_to_number("-1min"),
175            Number::new(-1.0, Some("min".to_owned()))
176        );
177        assert_eq!(str_to_number("1.2"), Number::new(1.2, None));
178        assert_eq!(
179            str_to_number("1.2min"),
180            Number::new(1.2, Some("min".to_owned()))
181        );
182        assert_eq!(str_to_number("-1.2"), Number::new(-1.2, None));
183        assert_eq!(
184            str_to_number("-1.2min"),
185            Number::new(-1.2, Some("min".to_owned()))
186        );
187    }
188
189    fn tn(s: &str) -> TagName {
190        TagName::new(s.to_owned()).unwrap()
191    }
192
193    fn str_lit_val(s: &str) -> Val {
194        Val::Lit(str_lit(s))
195    }
196
197    fn num_lit_val(n: f64) -> Val {
198        Val::Lit(num_lit(n))
199    }
200
201    fn str_lit(s: &str) -> Lit {
202        Lit::Str(s.to_owned())
203    }
204
205    fn num_lit(n: f64) -> Lit {
206        Lit::Num(Number::new(n, None))
207    }
208
209    #[test]
210    fn time_parser_works() {
211        let p = grammar::TimeParser::new();
212        assert_eq!(
213            p.parse("12:34:56").unwrap(),
214            NaiveTime::from_hms(12, 34, 56)
215        );
216    }
217
218    #[test]
219    fn time_parser_with_fractional_secs_works() {
220        let p = grammar::TimeParser::new();
221        assert_eq!(
222            p.parse("12:34:56.7").unwrap(),
223            NaiveTime::from_hms_nano(12, 34, 56, 700_000_000)
224        );
225        assert_eq!(
226            p.parse("12:34:56.789").unwrap(),
227            NaiveTime::from_hms_nano(12, 34, 56, 789_000_000)
228        );
229    }
230
231    #[test]
232    fn year_month_parser_works() {
233        let p = grammar::YearMonthParser::new();
234        assert_eq!(
235            p.parse("2020-12").unwrap(),
236            YearMonth::new(2020, Month::Dec)
237        )
238    }
239
240    #[test]
241    fn date_parser_works() {
242        let p = grammar::DateParser::new();
243        assert_eq!(
244            p.parse("2020-12-01").unwrap(),
245            NaiveDate::from_ymd(2020, 12, 1)
246        )
247    }
248
249    #[test]
250    fn uri_parser_works() {
251        let p = grammar::UriParser::new();
252        assert_eq!(
253            p.parse(r"`http://www.google.com/search?q=hello&q2=world`")
254                .unwrap(),
255            "http://www.google.com/search?q=hello&q2=world".to_owned()
256        );
257    }
258
259    #[test]
260    fn str_parser_works() {
261        let p = grammar::StrParser::new();
262        assert_eq!(
263            p.parse(r#""hello world""#).unwrap(),
264            "hello world".to_owned()
265        );
266        assert_eq!(p.parse(r#""\n""#).unwrap(), "\n".to_owned());
267        assert_eq!(p.parse(r#""\t""#).unwrap(), "\t".to_owned());
268        assert_eq!(p.parse(r#""\\""#).unwrap(), r"\".to_owned());
269        assert_eq!(
270            p.parse(r#""hello \"world\" quoted""#).unwrap(),
271            r#"hello "world" quoted"#.to_owned()
272        );
273    }
274
275    #[test]
276    fn str_parser_dollar_sign_works() {
277        let p = grammar::StrParser::new();
278        assert_eq!(
279            p.parse(r#""\$equipRef \$navName""#).unwrap(),
280            "\\$equipRef \\$navName".to_owned()
281        );
282    }
283
284    #[test]
285    fn tag_name_parser_works() {
286        let p = grammar::TagNameParser::new();
287        assert_eq!(
288            p.parse("lower").unwrap(),
289            TagName::new("lower".to_owned()).unwrap()
290        );
291        assert_eq!(
292            p.parse("camelCase").unwrap(),
293            TagName::new("camelCase".to_owned()).unwrap()
294        );
295        assert_eq!(
296            p.parse("elundis_core").unwrap(),
297            TagName::new("elundis_core".to_owned()).unwrap()
298        );
299    }
300
301    #[test]
302    fn empty_dict_works() {
303        let p = grammar::ValParser::new();
304        let expected = Val::Dict(HashMap::new());
305        assert_eq!(p.parse("{}").unwrap(), expected);
306    }
307
308    #[test]
309    fn dict1_works() {
310        let p = grammar::ValParser::new();
311        let name = TagName::new("tagName".to_owned()).unwrap();
312        let val = Val::Lit(Lit::Str("hello world".to_owned()));
313        let mut hash_map = HashMap::new();
314        hash_map.insert(name, Box::new(val));
315        let expected = Val::Dict(hash_map);
316        assert_eq!(p.parse(r#"{tagName:"hello world"}"#).unwrap(), expected);
317    }
318
319    #[test]
320    fn dict2_works() {
321        let p = grammar::ValParser::new();
322        let name1 = TagName::new("tagName1".to_owned()).unwrap();
323        let val1 = Val::Lit(Lit::Str("hello world".to_owned()));
324        let mut hash_map = HashMap::new();
325        hash_map.insert(name1, Box::new(val1));
326
327        let name2 = TagName::new("tagName2".to_owned()).unwrap();
328        let val2 = Val::Lit(Lit::Str("test".to_owned()));
329        hash_map.insert(name2, Box::new(val2));
330
331        let expected = Val::Dict(hash_map);
332        assert_eq!(
333            p.parse(r#"{tagName1:"hello world", tagName2:"test"}"#)
334                .unwrap(),
335            expected
336        );
337    }
338
339    #[test]
340    fn empty_list_works() {
341        let p = grammar::ValParser::new();
342        let expected = Val::List(vec![]);
343        assert_eq!(p.parse("[]").unwrap(), expected);
344    }
345
346    #[test]
347    fn list1_works() {
348        let p = grammar::ValParser::new();
349        let val = Val::Lit(Lit::Str("hello world".to_owned()));
350        let expected = Val::List(vec![val]);
351        assert_eq!(p.parse(r#"["hello world"]"#).unwrap(), expected);
352    }
353
354    #[test]
355    fn list2_works() {
356        let p = grammar::ValParser::new();
357        let val1 = Val::Lit(Lit::Str("hello world".to_owned()));
358        let val2 = Val::Lit(Lit::Str("test".to_owned()));
359        let expected = Val::List(vec![val1, val2]);
360        assert_eq!(p.parse(r#"["hello world", "test"]"#).unwrap(), expected);
361    }
362
363    #[test]
364    fn number_parser_no_units_works() {
365        let p = grammar::NumParser::new();
366        assert_eq!(p.parse("123").unwrap(), Number::new(123.0, None));
367        assert_eq!(p.parse("-123").unwrap(), Number::new(-123.0, None));
368        assert_eq!(p.parse("123.45").unwrap(), Number::new(123.45, None));
369        assert_eq!(p.parse("-123.45").unwrap(), Number::new(-123.45, None));
370    }
371
372    #[test]
373    fn number_parser_unicode_units_works() {
374        let p = grammar::NumParser::new();
375        assert_eq!(
376            p.parse("123psi/°F").unwrap(),
377            Number::new(123.0, Some("psi/°F".to_owned()))
378        );
379        assert_eq!(
380            p.parse("-123m²/N").unwrap(),
381            Number::new(-123.0, Some("m²/N".to_owned()))
382        );
383        assert_eq!(
384            p.parse("123.45dBµV").unwrap(),
385            Number::new(123.45, Some("dBµV".to_owned()))
386        );
387        assert_eq!(
388            p.parse("-123.45gH₂O/kgAir").unwrap(),
389            Number::new(-123.45, Some("gH₂O/kgAir".to_owned()))
390        );
391    }
392
393    #[test]
394    fn number_parser_units_works() {
395        let p = grammar::NumParser::new();
396        assert_eq!(
397            p.parse("123percent").unwrap(),
398            Number::new(123.0, Some("percent".to_owned()))
399        );
400        assert_eq!(
401            p.parse("-123db").unwrap(),
402            Number::new(-123.0, Some("db".to_owned()))
403        );
404        assert_eq!(
405            p.parse("123.45db").unwrap(),
406            Number::new(123.45, Some("db".to_owned()))
407        );
408        assert_eq!(
409            p.parse("-123.45%").unwrap(),
410            Number::new(-123.45, Some("%".to_owned()))
411        );
412    }
413
414    #[test]
415    fn number_parser_scientific_notation_works() {
416        let p = grammar::NumParser::new();
417        assert_eq!(
418            p.parse("1E23percent").unwrap(),
419            Number::new_scientific(1.0, 23, Some("percent".to_owned())).unwrap()
420        );
421        assert_eq!(
422            p.parse("-12E3db").unwrap(),
423            Number::new_scientific(-12.0, 3, Some("db".to_owned())).unwrap()
424        );
425        assert_eq!(
426            p.parse("1E23").unwrap(),
427            Number::new_scientific(1.0, 23, None).unwrap()
428        );
429        assert_eq!(
430            p.parse("-12E3").unwrap(),
431            Number::new_scientific(-12.0, 3, None).unwrap()
432        );
433
434        assert_eq!(
435            p.parse("1E-23percent").unwrap(),
436            Number::new_scientific(1.0, -23, Some("percent".to_owned())).unwrap()
437        );
438        assert_eq!(
439            p.parse("-12E-3db").unwrap(),
440            Number::new_scientific(-12.0, -3, Some("db".to_owned())).unwrap()
441        );
442        assert_eq!(
443            p.parse("1E-23").unwrap(),
444            Number::new_scientific(1.0, -23, None).unwrap()
445        );
446        assert_eq!(
447            p.parse("-12E-3").unwrap(),
448            Number::new_scientific(-12.0, -3, None).unwrap()
449        );
450    }
451
452    #[test]
453    fn hello_world_works() {
454        let p = grammar::ValParser::new();
455        p.parse(HELLO_WORLD).unwrap();
456    }
457
458    #[test]
459    fn delete_equip_works() {
460        let p = grammar::ValParser::new();
461        p.parse(DELETE_EQUIP).unwrap();
462    }
463
464    #[test]
465    fn misc_func_works() {
466        let p = grammar::ValParser::new();
467        p.parse(MISC_FUNC).unwrap();
468    }
469
470    #[test]
471    fn validate_works() {
472        let p = grammar::ValParser::new();
473        p.parse(VALIDATE).unwrap();
474    }
475
476    #[test]
477    fn eval_func_test_works() {
478        let p = grammar::ValParser::new();
479        p.parse(EVAL_FUNC_TEST).unwrap();
480    }
481
482    #[test]
483    fn simple_dict_works() {
484        let p = grammar::ValParser::new();
485
486        let mut map = HashMap::new();
487        map.insert(tn("type"), Box::new(str_lit_val("dict")));
488        let names =
489            Val::List(vec![str_lit_val("markerTag"), str_lit_val("numTag")]);
490        map.insert(tn("names"), Box::new(names));
491
492        // Create the literal dict
493        let mut sub_map1 = HashMap::new();
494        sub_map1.insert(tn("type"), Box::new(str_lit_val("literal")));
495        sub_map1.insert(tn("val"), Box::new(Val::Lit(Lit::DictMarker)));
496        let lit_val1 = Val::Dict(sub_map1);
497
498        // Create the literal dict
499        let mut sub_map2 = HashMap::new();
500        sub_map2.insert(tn("type"), Box::new(str_lit_val("literal")));
501        sub_map2.insert(tn("val"), Box::new(num_lit_val(1.0)));
502        let lit_val2 = Val::Dict(sub_map2);
503
504        let vals = Val::List(vec![lit_val1, lit_val2]);
505        map.insert(tn("vals"), Box::new(vals));
506        let expected = Val::Dict(map);
507
508        let val = p.parse(r#"{type:"dict", names:["markerTag", "numTag"], vals:[{type:"literal", val}, {type:"literal", val:1}]}"#).unwrap();
509        assert_eq!(val, expected);
510    }
511
512    #[test]
513    fn dict_with_remove_marker_works() {
514        let p = grammar::ValParser::new();
515
516        let mut map = HashMap::new();
517        map.insert(tn("type"), Box::new(str_lit_val("dict")));
518        let names = Val::List(vec![str_lit_val("deleteThisTag")]);
519        map.insert(tn("names"), Box::new(names));
520
521        // Create the literal dict
522        let mut sub_map = HashMap::new();
523        sub_map.insert(tn("type"), Box::new(str_lit_val("literal")));
524        sub_map.insert(tn("val"), Box::new(Val::Lit(Lit::DictRemoveMarker)));
525        let lit_val = Val::Dict(sub_map);
526
527        let vals = Val::List(vec![lit_val]);
528        map.insert(tn("vals"), Box::new(vals));
529        let expected = Val::Dict(map);
530
531        let val = p.parse(r#"{type:"dict", names:["deleteThisTag"], vals:[{type:"literal", val:removeMarker()}]}"#).unwrap();
532        assert_eq!(val, expected);
533    }
534
535    #[test]
536    fn ref_works() {
537        let p = grammar::RefParser::new();
538        let expected =
539            Ref::new("@p:demo:r:276dcffa-13c94a57".to_owned()).unwrap();
540        let val = p.parse("@p:demo:r:276dcffa-13c94a57").unwrap();
541        assert_eq!(val, expected);
542    }
543
544    #[test]
545    fn ref_literal_works() {
546        let p = grammar::ValParser::new();
547        let r = Ref::new("@p:demo:r:276dcffa-13c94a57".to_owned()).unwrap();
548        let mut map = HashMap::new();
549        map.insert(tn("type"), Box::new(str_lit_val("literal")));
550        map.insert(tn("val"), Box::new(Val::Lit(Lit::Ref(r))));
551        let expected = Val::Dict(map);
552        let val = p
553            .parse(r#"{type:"literal", val:@p:demo:r:276dcffa-13c94a57}"#)
554            .unwrap();
555        assert_eq!(val, expected);
556    }
557
558    #[test]
559    fn symbol_works() {
560        let p = grammar::SymbolParser::new();
561        let expected = Symbol::new("^steam-boiler".to_owned()).unwrap();
562        let val = p.parse("^steam-boiler").unwrap();
563        assert_eq!(val, expected);
564    }
565
566    #[test]
567    fn symbol_literal_works() {
568        let p = grammar::ValParser::new();
569        let sym = Symbol::new("^steam-boiler".to_owned()).unwrap();
570        let mut map = HashMap::new();
571        map.insert(tn("type"), Box::new(str_lit_val("literal")));
572        map.insert(tn("val"), Box::new(Val::Lit(Lit::Symbol(sym))));
573        let expected = Val::Dict(map);
574        let val = p.parse(r#"{type:"literal", val:^steam-boiler}"#).unwrap();
575        assert_eq!(val, expected);
576    }
577
578    #[test]
579    fn func_containing_symbol_works() {
580        let p = grammar::ValParser::new();
581        p.parse(r#"{type:"func", params:[], body:{type:"dict", names:["symTag", "testTag"], vals:[{type:"literal", val:^steam-boiler}, {type:"literal", val}]}}"#).unwrap();
582    }
583
584    #[test]
585    fn dict_containing_qname_works() {
586        let p = grammar::ValParser::new();
587        p.parse(r#"{type:"call", target:{type:"var", name:"core::parseNumber"}, args:[{type:"literal", val:"123"}]}"#).unwrap();
588    }
589
590    #[test]
591    fn dict_containing_partial_call_works() {
592        let p = grammar::ValParser::new();
593        p.parse(r#"{type:"partialCall", target:{type:"var", name:"utilsAssert"}, args:[null, null]}"#).unwrap();
594    }
595
596    #[test]
597    fn compdef_works() {
598        let p = grammar::ValParser::new();
599        p.parse(r#"{type:"compdef", params:[{name:"cells"}], body:{type:"block", exprs:[{type:"assign", lhs:{type:"var", name:"out"}, rhs:{type:"var", name:"in"}}]}, cells:{in:{is:^number, defVal:0}, out:{is:^number, ro}}}"#).unwrap();
600    }
601}