1use std::sync::Arc;
8
9use chumsky::input::{Input as _, Stream, ValueInput};
10use chumsky::pratt::{infix, left, prefix, right};
11use chumsky::prelude::*;
12use chumsky::span::SimpleSpan;
13
14use panproto_expr::{BuiltinOp, Expr, Literal, Pattern};
15
16use crate::token::Token;
17
18pub type ParseError = Rich<'static, Token, SimpleSpan>;
20
21pub fn parse(tokens: &[crate::Spanned]) -> Result<Expr, Vec<ParseError>> {
29 let mapped: Vec<(Token, SimpleSpan)> = tokens
30 .iter()
31 .filter(|s| s.token != Token::Eof)
32 .map(|s| (s.token.clone(), SimpleSpan::new(s.span.start, s.span.end)))
33 .collect();
34 let eoi = tokens.last().map_or_else(
35 || SimpleSpan::new(0, 0),
36 |s| SimpleSpan::new(s.span.start, s.span.end),
37 );
38 let stream = Stream::from_iter(mapped).map(eoi, |(tok, span)| (tok, span));
39 expr_parser().parse(stream).into_result().map_err(|errs| {
40 errs.into_iter()
41 .map(chumsky::error::Rich::into_owned)
42 .collect()
43 })
44}
45
46fn ident<'t, 'src: 't, I>()
50-> impl Parser<'t, I, Arc<str>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
51where
52 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
53{
54 select! { Token::Ident(s) => Arc::from(s.as_str()) }.labelled("identifier")
55}
56
57fn upper_ident<'t, 'src: 't, I>()
59-> impl Parser<'t, I, Arc<str>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
60where
61 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
62{
63 select! { Token::UpperIdent(s) => Arc::from(s.as_str()) }.labelled("constructor")
64}
65
66fn layout_block<'t, 'src: 't, I, T: 't>(
71 item: impl Parser<'t, I, T, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone,
72) -> impl Parser<'t, I, Vec<T>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
73where
74 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
75{
76 let explicit = item
77 .clone()
78 .separated_by(just(Token::Newline).or(just(Token::Comma)))
79 .allow_trailing()
80 .collect::<Vec<_>>()
81 .delimited_by(just(Token::LBrace), just(Token::RBrace));
82
83 let implicit = item
84 .separated_by(just(Token::Newline))
85 .allow_trailing()
86 .collect::<Vec<_>>()
87 .delimited_by(just(Token::Indent), just(Token::Dedent));
88
89 explicit.or(implicit)
90}
91
92fn pattern_parser<'t, 'src: 't, I>()
96-> impl Parser<'t, I, Pattern, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
97where
98 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
99{
100 recursive(|pat| {
101 let wildcard = select! { Token::Ident(s) if s == "_" => Pattern::Wildcard };
102
103 let var = ident().map(Pattern::Var);
104
105 let literal_pat = literal_parser().map(Pattern::Lit);
106
107 let paren = pat
108 .clone()
109 .delimited_by(just(Token::LParen), just(Token::RParen));
110
111 let list_pat = pat
112 .clone()
113 .separated_by(just(Token::Comma))
114 .collect::<Vec<_>>()
115 .delimited_by(just(Token::LBracket), just(Token::RBracket))
116 .map(Pattern::List);
117
118 let field_pat = ident()
119 .then(just(Token::Eq).ignore_then(pat.clone()).or_not())
120 .map(|(name, maybe_pat): (Arc<str>, Option<Pattern>)| {
121 let p = maybe_pat.unwrap_or_else(|| Pattern::Var(name.clone()));
122 (name, p)
123 });
124
125 let record_pat = field_pat
126 .separated_by(just(Token::Comma))
127 .collect::<Vec<_>>()
128 .delimited_by(just(Token::LBrace), just(Token::RBrace))
129 .map(Pattern::Record);
130
131 let constructor = upper_ident()
132 .then(pat.clone().repeated().collect::<Vec<_>>())
133 .map(|(name, args): (Arc<str>, Vec<Pattern>)| Pattern::Constructor(name, args));
134
135 choice((
136 wildcard,
137 literal_pat,
138 paren,
139 list_pat,
140 record_pat,
141 constructor,
142 var,
143 ))
144 })
145}
146
147fn literal_parser<'t, 'src: 't, I>()
151-> impl Parser<'t, I, Literal, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
152where
153 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
154{
155 select! {
156 Token::Int(n) => Literal::Int(n),
157 Token::Float(f) => Literal::Float(f),
158 Token::Str(s) => Literal::Str(s),
159 Token::True => Literal::Bool(true),
160 Token::False => Literal::Bool(false),
161 Token::Nothing => Literal::Null,
162 }
163 .labelled("literal")
164}
165
166fn resolve_builtin(name: &str) -> Option<BuiltinOp> {
170 BuiltinOp::from_name(name)
171}
172
173#[allow(clippy::too_many_lines)]
186fn expr_parser<'t, 'src: 't, I>()
187-> impl Parser<'t, I, Expr, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
188where
189 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
190{
191 recursive(|expr| {
192 let pattern = pattern_parser();
193
194 let lit = literal_parser().map(Expr::Lit);
197
198 let var_or_builtin = ident().map(Expr::Var);
199
200 let constructor = upper_ident().map(Expr::Var);
201
202 let paren_expr = expr
203 .clone()
204 .delimited_by(just(Token::LParen), just(Token::RParen));
205
206 let list_expr = {
208 let plain_list = expr
209 .clone()
210 .separated_by(just(Token::Comma))
211 .collect::<Vec<_>>()
212 .map(Expr::List);
213
214 let comprehension = expr
216 .clone()
217 .then_ignore(just(Token::Pipe))
218 .then(
219 ident()
220 .then_ignore(just(Token::LeftArrow))
221 .then(expr.clone())
222 .map(|(n, e): (Arc<str>, Expr)| Qual::Generator(n, e))
223 .or(expr.clone().map(Qual::Guard))
224 .separated_by(just(Token::Comma))
225 .at_least(1)
226 .collect::<Vec<Qual>>(),
227 )
228 .map(|(body, quals): (Expr, Vec<Qual>)| desugar_comprehension(body, &quals));
229
230 let range = expr
238 .clone()
239 .then_ignore(just(Token::DotDot))
240 .then(expr.clone().or_not())
241 .validate(|(start, end): (Expr, Option<Expr>), extra, emitter| {
242 if let Some(stop) = end {
243 return Expr::Builtin(BuiltinOp::Range, vec![start, stop]);
244 }
245 emitter.emit(Rich::custom(
246 extra.span(),
247 "open-ended range `[a..]` is not supported: the expression \
248 language has no lazy lists. Give an upper bound, as in `[a..b]`.",
249 ));
250 Expr::List(vec![start])
255 });
256
257 choice((comprehension, range, plain_list))
258 .delimited_by(just(Token::LBracket), just(Token::RBracket))
259 };
260
261 let record_expr = {
263 let field_bind = ident()
264 .then(just(Token::Eq).ignore_then(expr.clone()).or_not())
265 .map(|(name, val): (Arc<str>, Option<Expr>)| {
266 let v = val.unwrap_or_else(|| Expr::Var(name.clone()));
267 (name, v)
268 });
269
270 field_bind
271 .separated_by(just(Token::Comma))
272 .allow_trailing()
273 .collect::<Vec<_>>()
274 .delimited_by(just(Token::LBrace), just(Token::RBrace))
275 .map(Expr::Record)
276 };
277
278 let atom = choice((
279 lit,
280 paren_expr,
281 list_expr,
282 record_expr,
283 constructor,
284 var_or_builtin,
285 ));
286
287 let postfix_chain = atom.foldl(
290 choice((
291 just(Token::Dot).ignore_then(ident()).map(PostfixOp::Field),
292 just(Token::Arrow).ignore_then(ident()).map(PostfixOp::Edge),
293 ))
294 .repeated(),
295 |expr, postfix| match postfix {
296 PostfixOp::Field(name) => Expr::Field(Box::new(expr), name),
297 PostfixOp::Edge(edge) => Expr::Builtin(
298 BuiltinOp::Edge,
299 vec![expr, Expr::Lit(Literal::Str(edge.to_string()))],
300 ),
301 },
302 );
303
304 let app = postfix_chain
307 .clone()
308 .foldl(postfix_chain.repeated(), resolve_application);
309
310 let pratt = app.pratt((
313 infix(left(1), just(Token::Ampersand), |l, _, r, _| {
315 Expr::App(Box::new(r), Box::new(l))
316 }),
317 infix(left(3), just(Token::OrOr), |l, _, r, _| {
319 Expr::Builtin(BuiltinOp::Or, vec![l, r])
320 }),
321 infix(left(4), just(Token::AndAnd), |l, _, r, _| {
323 Expr::Builtin(BuiltinOp::And, vec![l, r])
324 }),
325 infix(right(5), just(Token::EqEq), |l, _, r, _| {
327 Expr::Builtin(BuiltinOp::Eq, vec![l, r])
328 }),
329 infix(right(5), just(Token::Neq), |l, _, r, _| {
330 Expr::Builtin(BuiltinOp::Neq, vec![l, r])
331 }),
332 infix(right(5), just(Token::Lt), |l, _, r, _| {
333 Expr::Builtin(BuiltinOp::Lt, vec![l, r])
334 }),
335 infix(right(5), just(Token::Lte), |l, _, r, _| {
336 Expr::Builtin(BuiltinOp::Lte, vec![l, r])
337 }),
338 infix(right(5), just(Token::Gt), |l, _, r, _| {
339 Expr::Builtin(BuiltinOp::Gt, vec![l, r])
340 }),
341 infix(right(5), just(Token::Gte), |l, _, r, _| {
342 Expr::Builtin(BuiltinOp::Gte, vec![l, r])
343 }),
344 infix(right(6), just(Token::PlusPlus), |l, _, r, _| {
346 Expr::Builtin(BuiltinOp::Concat, vec![l, r])
347 }),
348 infix(left(7), just(Token::Plus), |l, _, r, _| {
350 Expr::Builtin(BuiltinOp::Add, vec![l, r])
351 }),
352 infix(left(7), just(Token::Minus), |l, _, r, _| {
353 Expr::Builtin(BuiltinOp::Sub, vec![l, r])
354 }),
355 infix(left(8), just(Token::Star), |l, _, r, _| {
357 Expr::Builtin(BuiltinOp::Mul, vec![l, r])
358 }),
359 infix(left(8), just(Token::Slash), |l, _, r, _| {
360 Expr::Builtin(BuiltinOp::Div, vec![l, r])
361 }),
362 infix(left(8), just(Token::Percent), |l, _, r, _| {
363 Expr::Builtin(BuiltinOp::Mod, vec![l, r])
364 }),
365 infix(left(8), just(Token::ModKw), |l, _, r, _| {
366 Expr::Builtin(BuiltinOp::Mod, vec![l, r])
367 }),
368 infix(left(8), just(Token::DivKw), |l, _, r, _| {
369 Expr::Builtin(BuiltinOp::Div, vec![l, r])
370 }),
371 prefix(9, just(Token::Minus), |_, rhs, _| {
373 Expr::Builtin(BuiltinOp::Neg, vec![rhs])
374 }),
375 prefix(9, just(Token::Not), |_, rhs, _| {
376 Expr::Builtin(BuiltinOp::Not, vec![rhs])
377 }),
378 ));
379
380 let lambda = just(Token::Backslash)
384 .ignore_then(
385 pattern
386 .clone()
387 .repeated()
388 .at_least(1)
389 .collect::<Vec<Pattern>>(),
390 )
391 .then_ignore(just(Token::Arrow))
392 .then(expr.clone())
393 .map(|(params, body): (Vec<Pattern>, Expr)| desugar_lambda(¶ms, body));
394
395 let let_bind = ident()
397 .then(pattern.clone().repeated().collect::<Vec<Pattern>>())
398 .then_ignore(just(Token::Eq))
399 .then(expr.clone())
400 .map(|((name, params), val): ((Arc<str>, Vec<Pattern>), Expr)| {
401 if params.is_empty() {
402 (name, val)
403 } else {
404 (name, desugar_lambda(¶ms, val))
405 }
406 });
407
408 let let_expr = just(Token::Let)
409 .ignore_then(layout_block(let_bind.clone()).or(let_bind.clone().map(|b| vec![b])))
410 .then_ignore(just(Token::In))
411 .then(expr.clone())
412 .map(|(binds, body)| desugar_let_binds(binds, body));
413
414 let if_expr = just(Token::If)
416 .ignore_then(expr.clone())
417 .then_ignore(just(Token::Then))
418 .then(expr.clone())
419 .then_ignore(just(Token::Else))
420 .then(expr.clone())
421 .map(|((cond, then_branch), else_branch)| Expr::Match {
422 scrutinee: Box::new(cond),
423 arms: vec![
424 (Pattern::Lit(Literal::Bool(true)), then_branch),
425 (Pattern::Wildcard, else_branch),
426 ],
427 });
428
429 let case_arm = pattern
431 .clone()
432 .then_ignore(just(Token::Arrow))
433 .then(expr.clone());
434
435 let case_expr = just(Token::Case)
436 .ignore_then(expr.clone())
437 .then_ignore(just(Token::Of))
438 .then(layout_block(case_arm))
439 .map(|(scrutinee, arms)| Expr::Match {
440 scrutinee: Box::new(scrutinee),
441 arms,
442 });
443
444 let do_stmt = choice((
446 ident()
447 .then_ignore(just(Token::LeftArrow))
448 .then(expr.clone())
449 .map(|(name, e): (Arc<str>, Expr)| DoStmt::Bind(name, e)),
450 just(Token::Let)
451 .ignore_then(let_bind.clone())
452 .map(|(name, val)| DoStmt::Let(name, val)),
453 expr.clone().map(DoStmt::Expr),
454 ));
455
456 let do_expr = just(Token::Do)
457 .ignore_then(layout_block(do_stmt))
458 .map(desugar_do);
459
460 let full_expr = choice((do_expr, let_expr, if_expr, case_expr, lambda, pratt));
463
464 let where_bind = ident()
466 .then(pattern.repeated().collect::<Vec<Pattern>>())
467 .then_ignore(just(Token::Eq))
468 .then(expr.clone())
469 .map(|((name, params), val): ((Arc<str>, Vec<Pattern>), Expr)| {
470 if params.is_empty() {
471 (name, val)
472 } else {
473 (name, desugar_lambda(¶ms, val))
474 }
475 });
476
477 let where_clause = just(Token::Where)
478 .ignore_then(layout_block(where_bind.clone()).or(where_bind.map(|b| vec![b])));
479
480 full_expr
481 .then(where_clause.or_not())
482 .map(|(body, where_binds)| match where_binds {
483 Some(binds) => desugar_let_binds(binds, body),
484 None => body,
485 })
486 })
487}
488
489#[derive(Debug, Clone)]
493enum PostfixOp {
494 Field(Arc<str>),
496 Edge(Arc<str>),
498}
499
500#[derive(Debug, Clone)]
502enum Qual {
503 Generator(Arc<str>, Expr),
505 Guard(Expr),
507}
508
509#[derive(Debug, Clone)]
511enum DoStmt {
512 Bind(Arc<str>, Expr),
514 Let(Arc<str>, Expr),
516 Expr(Expr),
518}
519
520fn desugar_lambda(params: &[Pattern], body: Expr) -> Expr {
524 params.iter().rev().fold(body, |acc, pat| match pat {
525 Pattern::Var(name) => Expr::Lam(name.clone(), Box::new(acc)),
526 Pattern::Wildcard => Expr::Lam(Arc::from("_"), Box::new(acc)),
527 other => {
528 let fresh: Arc<str> = Arc::from("_arg");
529 Expr::Lam(
530 fresh.clone(),
531 Box::new(Expr::Match {
532 scrutinee: Box::new(Expr::Var(fresh)),
533 arms: vec![(other.clone(), acc)],
534 }),
535 )
536 }
537 })
538}
539
540fn desugar_let_binds(binds: Vec<(Arc<str>, Expr)>, body: Expr) -> Expr {
542 binds
543 .into_iter()
544 .rev()
545 .fold(body, |acc, (name, val)| Expr::Let {
546 name,
547 value: Box::new(val),
548 body: Box::new(acc),
549 })
550}
551
552fn desugar_comprehension(body: Expr, quals: &[Qual]) -> Expr {
554 quals
555 .iter()
556 .rev()
557 .fold(Expr::List(vec![body]), |acc, qual| match qual {
558 Qual::Generator(name, source) => Expr::Builtin(
559 BuiltinOp::FlatMap,
560 vec![source.clone(), Expr::Lam(name.clone(), Box::new(acc))],
561 ),
562 Qual::Guard(pred) => Expr::Match {
563 scrutinee: Box::new(pred.clone()),
564 arms: vec![
565 (Pattern::Lit(Literal::Bool(true)), acc),
566 (Pattern::Wildcard, Expr::List(vec![])),
567 ],
568 },
569 })
570}
571
572fn desugar_do(stmts: Vec<DoStmt>) -> Expr {
574 if stmts.is_empty() {
575 return Expr::List(vec![]);
576 }
577 let mut iter = stmts.into_iter().rev();
578 let Some(last) = iter.next() else {
580 return Expr::List(vec![]);
581 };
582 let init = match last {
583 DoStmt::Expr(e) | DoStmt::Bind(_, e) => e,
584 DoStmt::Let(name, val) => Expr::Let {
585 name,
586 value: Box::new(val),
587 body: Box::new(Expr::List(vec![])),
588 },
589 };
590 iter.fold(init, |acc, stmt| match stmt {
591 DoStmt::Bind(name, source) => Expr::Builtin(
592 BuiltinOp::FlatMap,
593 vec![source, Expr::Lam(name, Box::new(acc))],
594 ),
595 DoStmt::Let(name, val) => Expr::Let {
596 name,
597 value: Box::new(val),
598 body: Box::new(acc),
599 },
600 DoStmt::Expr(e) => Expr::Builtin(
601 BuiltinOp::FlatMap,
602 vec![e, Expr::Lam(Arc::from("_"), Box::new(acc))],
603 ),
604 })
605}
606
607fn lower_list_builtin_args(op: BuiltinOp, args: Vec<Expr>) -> Vec<Expr> {
622 op.surface_args_to_expr_args(args)
623}
624
625fn resolve_application(func: Expr, arg: Expr) -> Expr {
627 match &func {
628 Expr::Var(name) => {
629 if let Some(op) = resolve_builtin(name) {
630 let args = lower_list_builtin_args(op, vec![arg]);
631 Expr::Builtin(op, args)
632 } else {
633 Expr::App(Box::new(func), Box::new(arg))
634 }
635 }
636 Expr::Builtin(op, args) if args.len() < op.arity() => {
637 let mut new_args = args.clone();
638 new_args.push(arg);
639 Expr::Builtin(*op, lower_list_builtin_args(*op, new_args))
640 }
641 _ => Expr::App(Box::new(func), Box::new(arg)),
642 }
643}
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648 use crate::tokenize;
649
650 fn parse_ok(input: &str) -> Expr {
651 let tokens = tokenize(input).unwrap_or_else(|e| panic!("lex failed: {e}"));
652 parse(&tokens).unwrap_or_else(|e| panic!("parse failed: {e:?}"))
653 }
654
655 #[test]
656 fn parse_literal_int() {
657 assert_eq!(parse_ok("42"), Expr::Lit(Literal::Int(42)));
658 }
659
660 #[test]
661 fn parse_literal_string() {
662 assert_eq!(
663 parse_ok(r#""hello""#),
664 Expr::Lit(Literal::Str("hello".into()))
665 );
666 }
667
668 #[test]
669 fn parse_literal_bool() {
670 assert_eq!(parse_ok("True"), Expr::Lit(Literal::Bool(true)));
671 assert_eq!(parse_ok("False"), Expr::Lit(Literal::Bool(false)));
672 }
673
674 #[test]
675 fn parse_nothing() {
676 assert_eq!(parse_ok("Nothing"), Expr::Lit(Literal::Null));
677 }
678
679 #[test]
680 fn parse_variable() {
681 assert_eq!(parse_ok("x"), Expr::Var(Arc::from("x")));
682 }
683
684 #[test]
685 fn parse_arithmetic() {
686 assert_eq!(
687 parse_ok("1 + 2"),
688 Expr::Builtin(
689 BuiltinOp::Add,
690 vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(2))]
691 )
692 );
693 }
694
695 #[test]
696 fn parse_precedence() {
697 assert_eq!(
698 parse_ok("1 + 2 * 3"),
699 Expr::Builtin(
700 BuiltinOp::Add,
701 vec![
702 Expr::Lit(Literal::Int(1)),
703 Expr::Builtin(
704 BuiltinOp::Mul,
705 vec![Expr::Lit(Literal::Int(2)), Expr::Lit(Literal::Int(3))]
706 ),
707 ]
708 )
709 );
710 }
711
712 #[test]
713 fn parse_comparison() {
714 assert_eq!(
715 parse_ok("x == 1"),
716 Expr::Builtin(
717 BuiltinOp::Eq,
718 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
719 )
720 );
721 }
722
723 #[test]
724 fn parse_logical() {
725 assert_eq!(
726 parse_ok("a && b || c"),
727 Expr::Builtin(
728 BuiltinOp::Or,
729 vec![
730 Expr::Builtin(
731 BuiltinOp::And,
732 vec![Expr::Var(Arc::from("a")), Expr::Var(Arc::from("b"))]
733 ),
734 Expr::Var(Arc::from("c")),
735 ]
736 )
737 );
738 }
739
740 #[test]
741 fn parse_negation() {
742 assert_eq!(
743 parse_ok("-x"),
744 Expr::Builtin(BuiltinOp::Neg, vec![Expr::Var(Arc::from("x"))])
745 );
746 }
747
748 #[test]
749 fn parse_not() {
750 assert_eq!(
751 parse_ok("not True"),
752 Expr::Builtin(BuiltinOp::Not, vec![Expr::Lit(Literal::Bool(true))])
753 );
754 }
755
756 #[test]
757 fn parse_field_access() {
758 assert_eq!(
759 parse_ok("x.name"),
760 Expr::Field(Box::new(Expr::Var(Arc::from("x"))), Arc::from("name"))
761 );
762 }
763
764 #[test]
765 fn parse_edge_traversal() {
766 assert_eq!(
767 parse_ok("doc -> layers"),
768 Expr::Builtin(
769 BuiltinOp::Edge,
770 vec![
771 Expr::Var(Arc::from("doc")),
772 Expr::Lit(Literal::Str("layers".into())),
773 ]
774 )
775 );
776 }
777
778 #[test]
779 fn parse_lambda() {
780 assert_eq!(
781 parse_ok("\\x -> x + 1"),
782 Expr::Lam(
783 Arc::from("x"),
784 Box::new(Expr::Builtin(
785 BuiltinOp::Add,
786 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
787 ))
788 )
789 );
790 }
791
792 #[test]
793 fn parse_multi_param_lambda() {
794 let e = parse_ok("\\x y -> x + y");
795 match &e {
796 Expr::Lam(x, inner) => {
797 assert_eq!(&**x, "x");
798 assert!(matches!(&**inner, Expr::Lam(y, _) if &**y == "y"));
799 }
800 _ => panic!("expected nested Lam, got {e:?}"),
801 }
802 }
803
804 #[test]
805 fn parse_let_in() {
806 assert_eq!(
807 parse_ok("let x = 1 in x + 1"),
808 Expr::Let {
809 name: Arc::from("x"),
810 value: Box::new(Expr::Lit(Literal::Int(1))),
811 body: Box::new(Expr::Builtin(
812 BuiltinOp::Add,
813 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
814 )),
815 }
816 );
817 }
818
819 #[test]
820 fn parse_if_then_else() {
821 let e = parse_ok("if True then 1 else 0");
822 assert!(matches!(e, Expr::Match { .. }));
823 }
824
825 #[test]
826 fn parse_case_of() {
827 let e = parse_ok("case x of\n True -> 1\n False -> 0");
828 match e {
829 Expr::Match { arms, .. } => assert_eq!(arms.len(), 2),
830 _ => panic!("expected Match"),
831 }
832 }
833
834 #[test]
835 fn parse_list() {
836 assert_eq!(
837 parse_ok("[1, 2, 3]"),
838 Expr::List(vec![
839 Expr::Lit(Literal::Int(1)),
840 Expr::Lit(Literal::Int(2)),
841 Expr::Lit(Literal::Int(3)),
842 ])
843 );
844 }
845
846 #[test]
847 fn parse_empty_list() {
848 assert_eq!(parse_ok("[]"), Expr::List(vec![]));
849 }
850
851 #[test]
852 fn parse_record() {
853 assert_eq!(
854 parse_ok("{ name = x, age = 30 }"),
855 Expr::Record(vec![
856 (Arc::from("name"), Expr::Var(Arc::from("x"))),
857 (Arc::from("age"), Expr::Lit(Literal::Int(30))),
858 ])
859 );
860 }
861
862 #[test]
863 fn parse_record_punning() {
864 assert_eq!(
865 parse_ok("{ name, age }"),
866 Expr::Record(vec![
867 (Arc::from("name"), Expr::Var(Arc::from("name"))),
868 (Arc::from("age"), Expr::Var(Arc::from("age"))),
869 ])
870 );
871 }
872
873 #[test]
874 #[allow(clippy::expect_used)]
875 fn ranges_lower_to_the_range_builtin_and_evaluate() {
876 let config = panproto_expr::EvalConfig::default();
877 let eval =
878 |src: &str| panproto_expr::eval(&parse_ok(src), &panproto_expr::Env::new(), &config);
879
880 assert_eq!(
881 parse_ok("[1..3]"),
882 Expr::Builtin(
883 BuiltinOp::Range,
884 vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(3)),]
885 )
886 );
887 assert_eq!(
888 eval("[1..3]").expect("range should evaluate"),
889 panproto_expr::Literal::List(vec![
890 panproto_expr::Literal::Int(1),
891 panproto_expr::Literal::Int(2),
892 panproto_expr::Literal::Int(3),
893 ]),
894 "both bounds are inclusive"
895 );
896 assert_eq!(
897 eval("[0..0]").expect("singleton range should evaluate"),
898 panproto_expr::Literal::List(vec![panproto_expr::Literal::Int(0)])
899 );
900 assert_eq!(
901 eval("[3..1]").expect("descending range should evaluate"),
902 panproto_expr::Literal::List(vec![]),
903 "a descending range is empty, not an error"
904 );
905 assert_eq!(parse_ok("range 1 3"), parse_ok("[1..3]"));
907 }
908
909 #[test]
910 #[allow(clippy::expect_used)]
911 fn ranges_compose_with_the_list_builtins() {
912 let config = panproto_expr::EvalConfig::default();
913 let eval =
914 |src: &str| panproto_expr::eval(&parse_ok(src), &panproto_expr::Env::new(), &config);
915
916 assert_eq!(
917 eval("map (\\x -> x * x) [1..4]").expect("map over a range should evaluate"),
918 panproto_expr::Literal::List(vec![
919 panproto_expr::Literal::Int(1),
920 panproto_expr::Literal::Int(4),
921 panproto_expr::Literal::Int(9),
922 panproto_expr::Literal::Int(16),
923 ])
924 );
925 assert_eq!(
926 eval("fold (\\a -> \\b -> a + b) 0 [1..100]").expect("fold over a range"),
927 panproto_expr::Literal::Int(5050)
928 );
929 }
930
931 #[test]
932 fn an_oversized_range_is_rejected_before_it_allocates() {
933 let config = panproto_expr::EvalConfig::default();
937 let result = panproto_expr::eval(
938 &parse_ok("[0..99999999]"),
939 &panproto_expr::Env::new(),
940 &config,
941 );
942 assert!(
943 matches!(result, Err(panproto_expr::ExprError::ListLengthExceeded(_))),
944 "expected ListLengthExceeded, got {result:?}"
945 );
946 }
947
948 #[test]
949 #[allow(clippy::expect_used)]
950 fn an_open_ended_range_is_rejected() {
951 let tokens = tokenize("[1..]").unwrap_or_else(|e| panic!("lex failed: {e}"));
955 let errs = parse(&tokens).expect_err("an open-ended range must not parse");
956 let rendered = errs
957 .iter()
958 .map(std::string::ToString::to_string)
959 .collect::<Vec<_>>()
960 .join("; ");
961 assert!(
962 rendered.contains("open-ended range"),
963 "the error should name the construct, got: {rendered}"
964 );
965 }
966
967 #[test]
968 fn parse_builtin_application() {
969 assert_eq!(
972 parse_ok("map f xs"),
973 Expr::Builtin(
974 BuiltinOp::Map,
975 vec![Expr::Var(Arc::from("xs")), Expr::Var(Arc::from("f"))]
976 )
977 );
978 }
979
980 #[test]
981 fn parse_fold_lowers_list_first() {
982 assert_eq!(
983 parse_ok("fold f z xs"),
984 Expr::Builtin(
985 BuiltinOp::Fold,
986 vec![
987 Expr::Var(Arc::from("xs")),
988 Expr::Var(Arc::from("z")),
989 Expr::Var(Arc::from("f")),
990 ]
991 )
992 );
993 }
994
995 #[test]
996 #[allow(clippy::expect_used)]
997 fn parsed_list_builtins_evaluate() {
998 let env = panproto_expr::Env::new().extend(
1002 Arc::from("xs"),
1003 panproto_expr::Literal::List(vec![
1004 panproto_expr::Literal::Int(1),
1005 panproto_expr::Literal::Int(2),
1006 panproto_expr::Literal::Int(3),
1007 ]),
1008 );
1009 let config = panproto_expr::EvalConfig::default();
1010 let eval = |src: &str| panproto_expr::eval(&parse_ok(src), &env, &config);
1011
1012 assert_eq!(
1013 eval("map (\\x -> x * 2) xs").expect("map should evaluate"),
1014 panproto_expr::Literal::List(vec![
1015 panproto_expr::Literal::Int(2),
1016 panproto_expr::Literal::Int(4),
1017 panproto_expr::Literal::Int(6),
1018 ])
1019 );
1020 assert_eq!(
1021 eval("filter (\\x -> x > 1) xs").expect("filter should evaluate"),
1022 panproto_expr::Literal::List(vec![
1023 panproto_expr::Literal::Int(2),
1024 panproto_expr::Literal::Int(3),
1025 ])
1026 );
1027 assert_eq!(
1028 eval("fold (\\a -> \\b -> a + b) 0 xs").expect("fold should evaluate"),
1029 panproto_expr::Literal::Int(6)
1030 );
1031 }
1032
1033 #[test]
1034 fn parse_string_concat() {
1035 assert_eq!(
1036 parse_ok(r#""hello" ++ " world""#),
1037 Expr::Builtin(
1038 BuiltinOp::Concat,
1039 vec![
1040 Expr::Lit(Literal::Str("hello".into())),
1041 Expr::Lit(Literal::Str(" world".into())),
1042 ]
1043 )
1044 );
1045 }
1046
1047 #[test]
1048 fn parse_pipe() {
1049 assert_eq!(
1050 parse_ok("x & f"),
1051 Expr::App(
1052 Box::new(Expr::Var(Arc::from("f"))),
1053 Box::new(Expr::Var(Arc::from("x"))),
1054 )
1055 );
1056 }
1057
1058 #[test]
1059 fn parse_chained_field_access() {
1060 assert_eq!(
1061 parse_ok("x.a.b"),
1062 Expr::Field(
1063 Box::new(Expr::Field(
1064 Box::new(Expr::Var(Arc::from("x"))),
1065 Arc::from("a"),
1066 )),
1067 Arc::from("b"),
1068 )
1069 );
1070 }
1071
1072 #[test]
1073 fn parse_comprehension() {
1074 let e = parse_ok("[ x + 1 | x <- xs ]");
1075 assert!(matches!(e, Expr::Builtin(BuiltinOp::FlatMap, _)));
1076 }
1077}