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
14pub fn parse(input: &str) -> Result<Expression, ParseError> {
23 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]
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 List(vec![
256 Expression::Atom(String("0".to_string().into())),
257 Expression::Atom(Int(1)),
258 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]
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 }
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}