cel_parser/
lib.rs

1use lalrpop_util::lalrpop_mod;
2
3pub mod ast;
4pub use ast::*;
5
6pub mod parse;
7pub use parse::*;
8
9pub mod error;
10pub use error::ParseError;
11
12lalrpop_mod!(#[allow(clippy::all)] pub parser, "/cel.rs");
13
14/// Parses a CEL expression and returns it.
15///
16/// # Example
17/// ```
18/// use cel_parser::parse;
19/// let expr = parse("1 + 1").unwrap();
20/// println!("{:?}", expr);
21/// ```
22pub fn parse(input: &str) -> Result<Expression, ParseError> {
23    // Wrap the internal parser function - whether larlpop or chumsky
24
25    // Example for a possible new chumsky based parser...
26    // parser().parse(input)
27    //     .into_result()
28    //     .map_err(|e|  {
29    //         ParseError {
30    //             msg: e.iter().map(|e| format!("{}", e)).collect::<Vec<_>>().join("\n")
31    //         }
32    //     })
33
34    // Existing Larlpop Parser:
35    crate::parser::ExpressionParser::new()
36        .parse(input)
37        .map_err(|e| ParseError::from_lalrpop(input, e))
38}
39
40#[cfg(test)]
41mod tests {
42    use crate::{
43        ArithmeticOp, Atom, Atom::*, Expression, Expression::*, Member::*, RelationOp, UnaryOp,
44    };
45
46    fn parse(input: &str) -> Expression {
47        crate::parse(input).unwrap_or_else(|e| panic!("{}", e))
48    }
49
50    fn assert_parse_eq(input: &str, expected: Expression) {
51        assert_eq!(parse(input), expected);
52    }
53
54    #[test]
55    fn ident() {
56        assert_parse_eq("a", Ident("a".to_string().into()));
57        assert_parse_eq("hello ", Ident("hello".to_string().into()));
58    }
59
60    #[test]
61    fn simple_int() {
62        assert_parse_eq("1", Atom(Int(1)))
63    }
64
65    #[test]
66    fn simple_float() {
67        assert_parse_eq("1.0", Atom(Float(1.0)))
68    }
69
70    #[test]
71    fn other_floats() {
72        assert_parse_eq("1e3", Expression::Atom(Atom::Float(1000.0)));
73        assert_parse_eq("1e-3", Expression::Atom(Atom::Float(0.001)));
74        assert_parse_eq("1.4e-3", Expression::Atom(Atom::Float(0.0014)));
75    }
76
77    #[test]
78    fn single_quote_str() {
79        assert_parse_eq("'foobar'", Atom(String("foobar".to_string().into())))
80    }
81
82    #[test]
83    fn double_quote_str() {
84        assert_parse_eq(r#""foobar""#, Atom(String("foobar".to_string().into())))
85    }
86
87    // #[test]
88    // fn single_quote_raw_str() {
89    //     assert_parse_eq(
90    //         "r'\n'",
91    //         Expression::Atom(String("\n".to_string().into())),
92    //     );
93    // }
94
95    #[test]
96    fn single_quote_bytes() {
97        assert_parse_eq("b'foo'", Atom(Bytes(b"foo".to_vec().into())));
98        assert_parse_eq("b''", Atom(Bytes(b"".to_vec().into())));
99    }
100
101    #[test]
102    fn double_quote_bytes() {
103        assert_parse_eq(r#"b"foo""#, Atom(Bytes(b"foo".to_vec().into())));
104        assert_parse_eq(r#"b"""#, Atom(Bytes(b"".to_vec().into())));
105    }
106
107    #[test]
108    fn bools() {
109        assert_parse_eq("true", Atom(Bool(true)));
110        assert_parse_eq("false", Atom(Bool(false)));
111    }
112
113    #[test]
114    fn nulls() {
115        assert_parse_eq("null", Atom(Null));
116    }
117
118    #[test]
119    fn structure() {
120        println!("{:+?}", parse("{1 + a: 3}"));
121    }
122
123    #[test]
124    fn simple_str() {
125        assert_parse_eq(r#"'foobar'"#, Atom(String("foobar".to_string().into())));
126        println!("{:?}", parse(r#"1 == '1'"#))
127    }
128
129    #[test]
130    fn test_parse_map_macro() {
131        assert_parse_eq(
132            "[1, 2, 3].map(x, x * 2)",
133            FunctionCall(
134                Box::new(Ident("map".to_string().into())),
135                Some(Box::new(List(vec![
136                    Atom(Int(1)),
137                    Atom(Int(2)),
138                    Atom(Int(3)),
139                ]))),
140                vec![
141                    Ident("x".to_string().into()),
142                    Arithmetic(
143                        Box::new(Ident("x".to_string().into())),
144                        ArithmeticOp::Multiply,
145                        Box::new(Atom(Int(2))),
146                    ),
147                ],
148            ),
149        )
150    }
151
152    #[test]
153    fn nested_attributes() {
154        assert_parse_eq(
155            "a.b[1]",
156            Member(
157                Member(
158                    Ident("a".to_string().into()).into(),
159                    Attribute("b".to_string().into()).into(),
160                )
161                .into(),
162                Index(Atom(Int(1)).into()).into(),
163            ),
164        )
165    }
166
167    #[test]
168    fn function_call_no_args() {
169        assert_parse_eq(
170            "a()",
171            FunctionCall(Box::new(Ident("a".to_string().into())), None, vec![]),
172        );
173    }
174
175    #[test]
176    fn test_parser_bool_unary_ops() {
177        assert_parse_eq(
178            "!false",
179            Unary(UnaryOp::Not, Box::new(Expression::Atom(Atom::Bool(false)))),
180        );
181        assert_parse_eq(
182            "!true",
183            Unary(UnaryOp::Not, Box::new(Expression::Atom(Atom::Bool(true)))),
184        );
185    }
186
187    #[test]
188    fn test_parser_binary_bool_expressions() {
189        assert_parse_eq(
190            "true == true",
191            Relation(
192                Box::new(Expression::Atom(Atom::Bool(true))),
193                RelationOp::Equals,
194                Box::new(Expression::Atom(Atom::Bool(true))),
195            ),
196        );
197    }
198
199    #[test]
200    fn test_parser_bool_unary_ops_repeated() {
201        assert_eq!(
202            parse("!!true"),
203            (Unary(
204                UnaryOp::DoubleNot,
205                Box::new(Expression::Atom(Atom::Bool(true))),
206            ))
207        );
208    }
209
210    #[test]
211    fn delimited_expressions() {
212        assert_parse_eq(
213            "(-((1)))",
214            Unary(UnaryOp::Minus, Box::new(Expression::Atom(Atom::Int(1)))),
215        );
216    }
217
218    #[test]
219    fn test_empty_list_parsing() {
220        assert_eq!(parse("[]"), (List(vec![])));
221    }
222
223    #[test]
224    fn test_int_list_parsing() {
225        assert_parse_eq(
226            "[1,2,3]",
227            List(vec![
228                Expression::Atom(Atom::Int(1)),
229                Expression::Atom(Atom::Int(2)),
230                Expression::Atom(Atom::Int(3)),
231            ]),
232        );
233    }
234
235    #[test]
236    fn list_index_parsing() {
237        assert_parse_eq(
238            "[1,2,3][0]",
239            Member(
240                Box::new(List(vec![
241                    Expression::Atom(Int(1)),
242                    Expression::Atom(Int(2)),
243                    Expression::Atom(Int(3)),
244                ])),
245                Box::new(Index(Box::new(Expression::Atom(Int(0))))),
246            ),
247        );
248    }
249
250    #[test]
251    fn mixed_type_list() {
252        assert_parse_eq(
253            "['0', 1, 3.0, null]",
254            //"['0', 1, 2u, 3.0, null]",
255            List(vec![
256                Expression::Atom(String("0".to_string().into())),
257                Expression::Atom(Int(1)),
258                //Expression::Atom(UInt(2)),
259                Expression::Atom(Float(3.0)),
260                Expression::Atom(Null),
261            ]),
262        );
263    }
264
265    #[test]
266    fn test_nested_list_parsing() {
267        assert_parse_eq(
268            "[[], [], [[1]]]",
269            List(vec![
270                List(vec![]),
271                List(vec![]),
272                List(vec![List(vec![Expression::Atom(Int(1))])]),
273            ]),
274        );
275    }
276
277    #[test]
278    fn test_in_list_relation() {
279        assert_parse_eq(
280            "2 in [2]",
281            Relation(
282                Box::new(Expression::Atom(Int(2))),
283                RelationOp::In,
284                Box::new(List(vec![Expression::Atom(Int(2))])),
285            ),
286        );
287    }
288
289    #[test]
290    fn test_empty_map_parsing() {
291        assert_eq!(parse("{}"), (Map(vec![])));
292    }
293
294    #[test]
295    fn test_nonempty_map_parsing() {
296        assert_parse_eq(
297            "{'a': 1, 'b': 2}",
298            Map(vec![
299                (
300                    Expression::Atom(String("a".to_string().into())),
301                    Expression::Atom(Int(1)),
302                ),
303                (
304                    Expression::Atom(String("b".to_string().into())),
305                    Expression::Atom(Int(2)),
306                ),
307            ]),
308        );
309    }
310
311    #[test]
312    fn nonempty_map_index_parsing() {
313        assert_parse_eq(
314            "{'a': 1, 'b': 2}[0]",
315            Member(
316                Box::new(Map(vec![
317                    (
318                        Expression::Atom(String("a".to_string().into())),
319                        Expression::Atom(Int(1)),
320                    ),
321                    (
322                        Expression::Atom(String("b".to_string().into())),
323                        Expression::Atom(Int(2)),
324                    ),
325                ])),
326                Box::new(Index(Box::new(Expression::Atom(Int(0))))),
327            ),
328        );
329    }
330
331    #[test]
332    fn integer_relations() {
333        assert_parse_eq(
334            "2 != 3",
335            Relation(
336                Box::new(Expression::Atom(Int(2))),
337                RelationOp::NotEquals,
338                Box::new(Expression::Atom(Int(3))),
339            ),
340        );
341        assert_parse_eq(
342            "2 == 3",
343            Relation(
344                Box::new(Expression::Atom(Int(2))),
345                RelationOp::Equals,
346                Box::new(Expression::Atom(Int(3))),
347            ),
348        );
349
350        assert_parse_eq(
351            "2 < 3",
352            Relation(
353                Box::new(Expression::Atom(Int(2))),
354                RelationOp::LessThan,
355                Box::new(Expression::Atom(Int(3))),
356            ),
357        );
358
359        assert_parse_eq(
360            "2 <= 3",
361            Relation(
362                Box::new(Expression::Atom(Int(2))),
363                RelationOp::LessThanEq,
364                Box::new(Expression::Atom(Int(3))),
365            ),
366        );
367    }
368
369    #[test]
370    fn binary_product_expressions() {
371        assert_parse_eq(
372            "2 * 3",
373            Arithmetic(
374                Box::new(Expression::Atom(Atom::Int(2))),
375                ArithmeticOp::Multiply,
376                Box::new(Expression::Atom(Atom::Int(3))),
377            ),
378        );
379    }
380
381    // #[test]
382    // fn binary_product_negated_expressions() {
383    //     assert_parse_eq(
384    //         "2 * -3",
385    //         Arithmetic(
386    //             Box::new(Expression::Atom(Atom::Int(2))),
387    //             ArithmeticOp::Multiply,
388    //             Box::new(Unary(
389    //                 UnaryOp::Minus,
390    //                 Box::new(Expression::Atom(Atom::Int(3))),
391    //             )),
392    //         ),
393    //     );
394    //
395    //     assert_parse_eq(
396    //         "2 / -3",
397    //         Arithmetic(
398    //             Box::new(Expression::Atom(Int(2))),
399    //             ArithmeticOp::Divide,
400    //             Box::new(Unary(
401    //                 UnaryOp::Minus,
402    //                 Box::new(Expression::Atom(Int(3))),
403    //             )),
404    //         ),
405    //     );
406    // }
407
408    #[test]
409    fn test_parser_sum_expressions() {
410        assert_parse_eq(
411            "2 + 3",
412            Arithmetic(
413                Box::new(Expression::Atom(Atom::Int(2))),
414                ArithmeticOp::Add,
415                Box::new(Expression::Atom(Atom::Int(3))),
416            ),
417        );
418
419        // assert_parse_eq(
420        //     "2 - -3",
421        //     Arithmetic(
422        //         Box::new(Expression::Atom(Atom::Int(2))),
423        //         ArithmeticOp::Subtract,
424        //         Box::new(Unary(
425        //             UnaryOp::Minus,
426        //             Box::new(Expression::Atom(Atom::Int(3))),
427        //         )),
428        //     ),
429        // );
430    }
431
432    #[test]
433    fn conditionals() {
434        assert_parse_eq(
435            "true && true",
436            And(
437                Box::new(Expression::Atom(Bool(true))),
438                Box::new(Expression::Atom(Bool(true))),
439            ),
440        );
441        assert_parse_eq(
442            "false || true",
443            Or(
444                Box::new(Expression::Atom(Bool(false))),
445                Box::new(Expression::Atom(Bool(true))),
446            ),
447        );
448    }
449    #[test]
450    fn test_ternary_true_condition() {
451        assert_parse_eq(
452            "true ? 'result_true' : 'result_false'",
453            Ternary(
454                Box::new(Expression::Atom(Bool(true))),
455                Box::new(Expression::Atom(String("result_true".to_string().into()))),
456                Box::new(Expression::Atom(String("result_false".to_string().into()))),
457            ),
458        );
459
460        assert_parse_eq(
461            "true ? 100 : 200",
462            Ternary(
463                Box::new(Expression::Atom(Bool(true))),
464                Box::new(Expression::Atom(Int(100))),
465                Box::new(Expression::Atom(Int(200))),
466            ),
467        );
468    }
469
470    #[test]
471    fn test_ternary_false_condition() {
472        assert_parse_eq(
473            "false ? 'result_true' : 'result_false'",
474            Ternary(
475                Box::new(Expression::Atom(Bool(false))),
476                Box::new(Expression::Atom(String("result_true".to_string().into()))),
477                Box::new(Expression::Atom(String("result_false".to_string().into()))),
478            ),
479        );
480    }
481
482    #[test]
483    fn test_operator_precedence() {
484        assert_parse_eq(
485            "a && b == 'string'",
486            And(
487                Box::new(Ident("a".to_string().into())),
488                Box::new(Relation(
489                    Box::new(Ident("b".to_string().into())),
490                    RelationOp::Equals,
491                    Box::new(Expression::Atom(String("string".to_string().into()))),
492                )),
493            ),
494        );
495    }
496
497    #[test]
498    fn test_foobar() {
499        println!("{:?}", parse("foo.bar.baz == 10 && size(requests) == 3"))
500    }
501
502    #[test]
503    fn test_unrecognized_token_error() {
504        let source = r#"
505            account.balance == transaction.withdrawal
506                || (account.overdraftProtection
507                    account.overdraftLimit >= transaction.withdrawal  - account.balance)
508        "#;
509
510        let err = crate::parse(source).unwrap_err();
511
512        assert_eq!(err.msg, "unrecognized token: 'account'");
513
514        assert_eq!(err.span.start.as_ref().unwrap().line, 3);
515        assert_eq!(err.span.start.as_ref().unwrap().column, 20);
516        assert_eq!(err.span.end.as_ref().unwrap().line, 3);
517        assert_eq!(err.span.end.as_ref().unwrap().column, 27);
518    }
519
520    #[test]
521    fn test_unrecognized_eof_error() {
522        let source = r#" "#;
523
524        let err = crate::parse(source).unwrap_err();
525
526        assert_eq!(err.msg, "unrecognized eof");
527
528        assert_eq!(err.span.start.as_ref().unwrap().line, 0);
529        assert_eq!(err.span.start.as_ref().unwrap().column, 0);
530        assert_eq!(err.span.end.as_ref().unwrap().line, 0);
531        assert_eq!(err.span.end.as_ref().unwrap().column, 0);
532    }
533
534    #[test]
535    fn test_invalid_token_error() {
536        let source = r#"
537            account.balance == ยง
538        "#;
539
540        let err = crate::parse(source).unwrap_err();
541
542        assert_eq!(err.msg, "invalid token");
543
544        assert_eq!(err.span.start.as_ref().unwrap().line, 1);
545        assert_eq!(err.span.start.as_ref().unwrap().column, 31);
546        assert_eq!(err.span.end.as_ref().unwrap().line, 1);
547        assert_eq!(err.span.end.as_ref().unwrap().column, 31);
548    }
549}