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); pub 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#[derive(Clone, Debug, PartialEq)]
76pub enum Val {
77 Dict(HashMap<TagName, Box<Val>>),
78 List(Vec<Val>),
79 Lit(Lit),
80}
81
82#[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#[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#[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 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 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 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 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}