1use std::fmt::Write;
11use std::sync::Arc;
12
13use panproto_expr::{BuiltinOp, Expr, Literal, Pattern};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19enum Prec {
20 Top = 0,
22 Pipe = 1,
24 Or = 3,
26 And = 4,
28 Cmp = 5,
30 Concat = 6,
32 AddSub = 7,
34 MulDiv = 8,
36 Unary = 9,
38 App = 10,
40 Atom = 11,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46enum Assoc {
47 Left,
48 Right,
49}
50
51#[must_use]
69pub fn pretty_print(expr: &Expr) -> String {
70 let mut buf = String::new();
71 write_expr(&mut buf, expr, Prec::Top);
72 buf
73}
74
75fn write_expr(buf: &mut String, expr: &Expr, ctx: Prec) {
79 match expr {
80 Expr::Var(name) => buf.push_str(name),
81
82 Expr::Lit(lit) => write_literal(buf, lit),
83
84 Expr::Lam(param, body) => {
85 let needs_parens = ctx > Prec::Top;
86 if needs_parens {
87 buf.push('(');
88 }
89 write_lambda_chain(buf, param, body);
90 if needs_parens {
91 buf.push(')');
92 }
93 }
94
95 Expr::App(func, arg) => {
96 write_app(buf, expr, ctx);
97 let _ = (func, arg); }
99
100 Expr::Record(fields) => {
101 write_record_expr(buf, fields);
102 }
103
104 Expr::List(items) => {
105 buf.push('[');
106 for (i, item) in items.iter().enumerate() {
107 if i > 0 {
108 buf.push_str(", ");
109 }
110 write_expr(buf, item, Prec::Top);
111 }
112 buf.push(']');
113 }
114
115 Expr::Field(inner, name) => {
116 write_expr(buf, inner, Prec::Atom);
117 buf.push('.');
118 buf.push_str(name);
119 }
120
121 Expr::Index(inner, idx) => {
122 write_expr(buf, inner, Prec::Atom);
123 buf.push('[');
124 write_expr(buf, idx, Prec::Top);
125 buf.push(']');
126 }
127
128 Expr::Match { scrutinee, arms } => {
129 write_match(buf, scrutinee, arms, ctx);
130 }
131
132 Expr::Let { name, value, body } => {
133 write_let(buf, name, value, body, ctx);
134 }
135
136 Expr::Builtin(op, args) => {
137 write_builtin(buf, *op, args, ctx);
138 }
139 }
140}
141
142fn write_lambda_chain(buf: &mut String, first_param: &Arc<str>, first_body: &Expr) {
144 buf.push('\\');
145 buf.push_str(first_param);
146 let mut body = first_body;
147 while let Expr::Lam(param, inner) = body {
148 buf.push(' ');
149 buf.push_str(param);
150 body = inner;
151 }
152 buf.push_str(" -> ");
153 write_expr(buf, body, Prec::Top);
154}
155
156fn write_app(buf: &mut String, expr: &Expr, ctx: Prec) {
158 let needs_parens = ctx > Prec::App;
159 if needs_parens {
160 buf.push('(');
161 }
162
163 let mut spine: Vec<&Expr> = Vec::new();
165 let mut head = expr;
166 while let Expr::App(func, arg) = head {
167 spine.push(arg);
168 head = func;
169 }
170 spine.reverse();
171
172 write_expr(buf, head, Prec::App);
173 for arg in &spine {
174 buf.push(' ');
175 write_expr(buf, arg, Prec::Atom);
176 }
177
178 if needs_parens {
179 buf.push(')');
180 }
181}
182
183fn write_record_expr(buf: &mut String, fields: &[(Arc<str>, Expr)]) {
185 buf.push_str("{ ");
186 for (i, (name, val)) in fields.iter().enumerate() {
187 if i > 0 {
188 buf.push_str(", ");
189 }
190 if let Expr::Var(v) = val {
192 if v == name {
193 buf.push_str(name);
194 continue;
195 }
196 }
197 buf.push_str(name);
198 buf.push_str(" = ");
199 write_expr(buf, val, Prec::Top);
200 }
201 buf.push_str(" }");
202}
203
204fn write_match(buf: &mut String, scrutinee: &Expr, arms: &[(Pattern, Expr)], ctx: Prec) {
209 if arms.len() == 2 {
211 if let (Pattern::Lit(Literal::Bool(true)), then_branch) = &arms[0] {
212 if let (Pattern::Wildcard, else_branch) = &arms[1] {
213 let needs_parens = ctx > Prec::Top;
214 if needs_parens {
215 buf.push('(');
216 }
217 buf.push_str("if ");
218 write_expr(buf, scrutinee, Prec::Top);
219 buf.push_str(" then ");
220 write_expr(buf, then_branch, Prec::Top);
221 buf.push_str(" else ");
222 write_expr(buf, else_branch, Prec::Top);
223 if needs_parens {
224 buf.push(')');
225 }
226 return;
227 }
228 }
229 }
230
231 let needs_parens = ctx > Prec::Top;
232 if needs_parens {
233 buf.push('(');
234 }
235 buf.push_str("case ");
236 write_expr(buf, scrutinee, Prec::Top);
237 buf.push_str(" of\n");
238 for (i, (pat, body)) in arms.iter().enumerate() {
239 if i > 0 {
240 buf.push('\n');
241 }
242 buf.push_str(" ");
243 write_pattern(buf, pat);
244 buf.push_str(" -> ");
245 write_expr(buf, body, Prec::Top);
246 }
247 if needs_parens {
248 buf.push(')');
249 }
250}
251
252fn write_let(buf: &mut String, name: &Arc<str>, value: &Expr, body: &Expr, ctx: Prec) {
254 let needs_parens = ctx > Prec::Top;
255 if needs_parens {
256 buf.push('(');
257 }
258
259 let mut bindings: Vec<(&Arc<str>, &Expr)> = vec![(name, value)];
261 let mut final_body = body;
262 while let Expr::Let {
263 name: n,
264 value: v,
265 body: b,
266 } = final_body
267 {
268 bindings.push((n, v));
269 final_body = b;
270 }
271
272 if bindings.len() == 1 {
273 buf.push_str("let ");
274 buf.push_str(name);
275 buf.push_str(" = ");
276 write_expr(buf, value, Prec::Top);
277 buf.push_str(" in ");
278 } else {
279 buf.push_str("let\n");
280 for (n, v) in &bindings {
281 buf.push_str(" ");
282 buf.push_str(n);
283 buf.push_str(" = ");
284 write_expr(buf, v, Prec::Top);
285 buf.push('\n');
286 }
287 buf.push_str("in ");
288 }
289 write_expr(buf, final_body, Prec::Top);
290
291 if needs_parens {
292 buf.push(')');
293 }
294}
295
296fn write_builtin(buf: &mut String, op: BuiltinOp, args: &[Expr], ctx: Prec) {
298 if let Some((sym, prec, assoc)) = infix_info(op) {
300 if args.len() == 2 {
301 let needs_parens = ctx > prec;
302 if needs_parens {
303 buf.push('(');
304 }
305 let (left_ctx, right_ctx) = match assoc {
309 Assoc::Left => (prec, next_prec(prec)),
310 Assoc::Right => (next_prec(prec), prec),
311 };
312 write_expr(buf, &args[0], left_ctx);
313 buf.push(' ');
314 buf.push_str(sym);
315 buf.push(' ');
316 write_expr(buf, &args[1], right_ctx);
317 if needs_parens {
318 buf.push(')');
319 }
320 return;
321 }
322 }
323
324 if op == BuiltinOp::Edge && args.len() == 2 {
326 if let Expr::Lit(Literal::Str(edge_name)) = &args[1] {
327 let needs_parens = ctx > Prec::Atom;
328 if needs_parens {
329 buf.push('(');
330 }
331 write_expr(buf, &args[0], Prec::Atom);
332 buf.push_str(" -> ");
333 buf.push_str(edge_name);
334 if needs_parens {
335 buf.push(')');
336 }
337 return;
338 }
339 }
340
341 if op == BuiltinOp::Neg && args.len() == 1 {
343 let needs_parens = ctx > Prec::Unary;
344 if needs_parens {
345 buf.push('(');
346 }
347 buf.push('-');
348 write_expr(buf, &args[0], Prec::Atom);
349 if needs_parens {
350 buf.push(')');
351 }
352 return;
353 }
354
355 if op == BuiltinOp::Not && args.len() == 1 {
356 let needs_parens = ctx > Prec::Unary;
357 if needs_parens {
358 buf.push('(');
359 }
360 buf.push_str("not ");
361 write_expr(buf, &args[0], Prec::Atom);
362 if needs_parens {
363 buf.push(')');
364 }
365 return;
366 }
367
368 let needs_parens = ctx > Prec::App && !args.is_empty();
370 if needs_parens {
371 buf.push('(');
372 }
373 buf.push_str(builtin_name(op));
374 let surface: Vec<&Expr> = surface_arg_order(op, args);
378 for arg in surface {
379 buf.push(' ');
380 write_expr(buf, arg, Prec::Atom);
381 }
382 if needs_parens {
383 buf.push(')');
384 }
385}
386
387fn surface_arg_order(op: BuiltinOp, args: &[Expr]) -> Vec<&Expr> {
395 match (op, args.len()) {
396 (BuiltinOp::Map | BuiltinOp::Filter | BuiltinOp::FlatMap, 2) => {
397 vec![&args[1], &args[0]]
398 }
399 (BuiltinOp::Fold, 3) => vec![&args[2], &args[1], &args[0]],
400 _ => args.iter().collect(),
401 }
402}
403
404const fn infix_info(op: BuiltinOp) -> Option<(&'static str, Prec, Assoc)> {
408 match op {
409 BuiltinOp::Or => Some(("||", Prec::Or, Assoc::Left)),
410 BuiltinOp::And => Some(("&&", Prec::And, Assoc::Left)),
411 BuiltinOp::Eq => Some(("==", Prec::Cmp, Assoc::Right)),
412 BuiltinOp::Neq => Some(("/=", Prec::Cmp, Assoc::Right)),
413 BuiltinOp::Lt => Some(("<", Prec::Cmp, Assoc::Right)),
414 BuiltinOp::Lte => Some(("<=", Prec::Cmp, Assoc::Right)),
415 BuiltinOp::Gt => Some((">", Prec::Cmp, Assoc::Right)),
416 BuiltinOp::Gte => Some((">=", Prec::Cmp, Assoc::Right)),
417 BuiltinOp::Concat => Some(("++", Prec::Concat, Assoc::Right)),
418 BuiltinOp::Add => Some(("+", Prec::AddSub, Assoc::Left)),
419 BuiltinOp::Sub => Some(("-", Prec::AddSub, Assoc::Left)),
420 BuiltinOp::Mul => Some(("*", Prec::MulDiv, Assoc::Left)),
421 BuiltinOp::Div => Some(("/", Prec::MulDiv, Assoc::Left)),
422 BuiltinOp::Mod => Some(("%", Prec::MulDiv, Assoc::Left)),
423 _ => None,
424 }
425}
426
427const fn next_prec(p: Prec) -> Prec {
429 match p {
430 Prec::Top => Prec::Pipe,
431 Prec::Pipe => Prec::Or,
432 Prec::Or => Prec::And,
433 Prec::And => Prec::Cmp,
434 Prec::Cmp => Prec::Concat,
435 Prec::Concat => Prec::AddSub,
436 Prec::AddSub => Prec::MulDiv,
437 Prec::MulDiv => Prec::Unary,
438 Prec::Unary => Prec::App,
439 Prec::App | Prec::Atom => Prec::Atom,
440 }
441}
442
443const fn builtin_name(op: BuiltinOp) -> &'static str {
445 match op {
446 BuiltinOp::Add => "add",
447 BuiltinOp::Sub => "sub",
448 BuiltinOp::Mul => "mul",
449 BuiltinOp::Div => "div",
450 BuiltinOp::Mod => "mod",
451 BuiltinOp::Neg => "neg",
452 BuiltinOp::Abs => "abs",
453 BuiltinOp::Floor => "floor",
454 BuiltinOp::Ceil => "ceil",
455 BuiltinOp::Round => "round",
456 BuiltinOp::Eq => "eq",
457 BuiltinOp::Neq => "neq",
458 BuiltinOp::Lt => "lt",
459 BuiltinOp::Lte => "lte",
460 BuiltinOp::Gt => "gt",
461 BuiltinOp::Gte => "gte",
462 BuiltinOp::And => "and",
463 BuiltinOp::Or => "or",
464 BuiltinOp::Not => "not",
465 BuiltinOp::Concat => "concat",
466 BuiltinOp::Len => "len",
467 BuiltinOp::Slice => "slice",
468 BuiltinOp::Upper => "upper",
469 BuiltinOp::Lower => "lower",
470 BuiltinOp::Trim => "trim",
471 BuiltinOp::Split => "split",
472 BuiltinOp::Join => "join",
473 BuiltinOp::Replace => "replace",
474 BuiltinOp::Contains => "contains",
475 BuiltinOp::Map => "map",
476 BuiltinOp::Filter => "filter",
477 BuiltinOp::Fold => "fold",
478 BuiltinOp::Append => "append",
479 BuiltinOp::Head => "head",
480 BuiltinOp::Tail => "tail",
481 BuiltinOp::Reverse => "reverse",
482 BuiltinOp::FlatMap => "flat_map",
483 BuiltinOp::Length => "length",
484 BuiltinOp::Range => "range",
485 BuiltinOp::MergeRecords => "merge",
486 BuiltinOp::Keys => "keys",
487 BuiltinOp::Values => "values",
488 BuiltinOp::HasField => "has_field",
489 BuiltinOp::DefaultVal => "default",
490 BuiltinOp::Clamp => "clamp",
491 BuiltinOp::TruncateStr => "truncate_str",
492 BuiltinOp::IntToFloat => "int_to_float",
493 BuiltinOp::FloatToInt => "float_to_int",
494 BuiltinOp::IntToStr => "int_to_str",
495 BuiltinOp::FloatToStr => "float_to_str",
496 BuiltinOp::StrToInt => "str_to_int",
497 BuiltinOp::StrToFloat => "str_to_float",
498 BuiltinOp::TypeOf => "type_of",
499 BuiltinOp::IsNull => "is_null",
500 BuiltinOp::IsList => "is_list",
501 BuiltinOp::Edge => "edge",
502 BuiltinOp::Children => "children",
503 BuiltinOp::HasEdge => "has_edge",
504 BuiltinOp::EdgeCount => "edge_count",
505 BuiltinOp::Anchor => "anchor",
506 }
507}
508
509fn write_literal(buf: &mut String, lit: &Literal) {
511 match lit {
512 Literal::Bool(true) => buf.push_str("True"),
513 Literal::Bool(false) => buf.push_str("False"),
514 Literal::Int(n) => {
515 let _ = write!(buf, "{n}");
516 }
517 Literal::Float(f) => {
518 let s = format!("{f}");
521 if s.contains('.') {
522 buf.push_str(&s);
523 } else {
524 let _ = write!(buf, "{f}.0");
525 }
526 }
527 Literal::Str(s) => {
528 buf.push('"');
529 for ch in s.chars() {
531 match ch {
532 '\\' => buf.push_str("\\\\"),
533 '"' => buf.push_str("\\\""),
534 '\n' => buf.push_str("\\n"),
535 '\r' => buf.push_str("\\r"),
536 '\t' => buf.push_str("\\t"),
537 c => buf.push(c),
538 }
539 }
540 buf.push('"');
541 }
542 Literal::Bytes(bytes) => {
543 buf.push('[');
545 for (i, b) in bytes.iter().enumerate() {
546 if i > 0 {
547 buf.push_str(", ");
548 }
549 let _ = write!(buf, "{b}");
550 }
551 buf.push(']');
552 }
553 Literal::Null => buf.push_str("Nothing"),
554 Literal::Record(fields) => {
555 buf.push_str("{ ");
556 for (i, (name, val)) in fields.iter().enumerate() {
557 if i > 0 {
558 buf.push_str(", ");
559 }
560 buf.push_str(name);
561 buf.push_str(" = ");
562 write_literal(buf, val);
563 }
564 buf.push_str(" }");
565 }
566 Literal::List(items) => {
567 buf.push('[');
568 for (i, item) in items.iter().enumerate() {
569 if i > 0 {
570 buf.push_str(", ");
571 }
572 write_literal(buf, item);
573 }
574 buf.push(']');
575 }
576 Literal::Closure { param, body, .. } => {
577 buf.push('\\');
580 buf.push_str(param);
581 buf.push_str(" -> ");
582 write_expr(buf, body, Prec::Top);
583 }
584 }
585}
586
587fn write_pattern(buf: &mut String, pat: &Pattern) {
589 match pat {
590 Pattern::Wildcard => buf.push('_'),
591 Pattern::Var(name) => buf.push_str(name),
592 Pattern::Lit(lit) => write_literal(buf, lit),
593 Pattern::Record(fields) => {
594 buf.push_str("{ ");
595 for (i, (name, p)) in fields.iter().enumerate() {
596 if i > 0 {
597 buf.push_str(", ");
598 }
599 if let Pattern::Var(v) = p {
601 if v == name {
602 buf.push_str(name);
603 continue;
604 }
605 }
606 buf.push_str(name);
607 buf.push_str(" = ");
608 write_pattern(buf, p);
609 }
610 buf.push_str(" }");
611 }
612 Pattern::List(pats) => {
613 buf.push('[');
614 for (i, p) in pats.iter().enumerate() {
615 if i > 0 {
616 buf.push_str(", ");
617 }
618 write_pattern(buf, p);
619 }
620 buf.push(']');
621 }
622 Pattern::Constructor(name, args) => {
623 buf.push_str(name);
624 for arg in args {
625 buf.push(' ');
626 let needs_parens = matches!(arg, Pattern::Constructor(_, a) if !a.is_empty());
629 if needs_parens {
630 buf.push('(');
631 }
632 write_pattern(buf, arg);
633 if needs_parens {
634 buf.push(')');
635 }
636 }
637 }
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use crate::{parse, tokenize};
645
646 fn round_trip(input: &str) {
648 let tokens1 = tokenize(input).unwrap_or_else(|e| panic!("first lex failed: {e}"));
649 let expr1 = parse(&tokens1).unwrap_or_else(|e| panic!("first parse failed: {e:?}"));
650 let printed = pretty_print(&expr1);
651 let tokens2 = tokenize(&printed).unwrap_or_else(|e| {
652 panic!("re-lex failed for {printed:?}: {e}");
653 });
654 let expr2 = parse(&tokens2).unwrap_or_else(|e| {
655 panic!("re-parse failed for {printed:?}: {e:?}");
656 });
657 assert_eq!(
658 expr1, expr2,
659 "round trip failed.\n input: {input:?}\n printed: {printed:?}"
660 );
661 }
662
663 fn prints_as(expr: &Expr, expected: &str) {
665 let actual = pretty_print(expr);
666 assert_eq!(actual, expected, "pretty_print mismatch");
667 }
668
669 #[test]
672 fn lit_int() {
673 prints_as(&Expr::Lit(Literal::Int(42)), "42");
674 }
675
676 #[test]
677 fn lit_negative_int() {
678 prints_as(&Expr::Lit(Literal::Int(-5)), "-5");
679 }
680
681 #[test]
682 fn lit_float() {
683 prints_as(&Expr::Lit(Literal::Float(3.125)), "3.125");
684 }
685
686 #[test]
687 fn lit_string() {
688 prints_as(&Expr::Lit(Literal::Str("hello".into())), r#""hello""#);
689 }
690
691 #[test]
692 fn lit_string_escapes() {
693 prints_as(
694 &Expr::Lit(Literal::Str("say \"hi\"".into())),
695 r#""say \"hi\"""#,
696 );
697 }
698
699 #[test]
700 fn lit_bool() {
701 prints_as(&Expr::Lit(Literal::Bool(true)), "True");
702 prints_as(&Expr::Lit(Literal::Bool(false)), "False");
703 }
704
705 #[test]
706 fn lit_null() {
707 prints_as(&Expr::Lit(Literal::Null), "Nothing");
708 }
709
710 #[test]
711 fn lit_bytes() {
712 prints_as(&Expr::Lit(Literal::Bytes(vec![1, 2, 3])), "[1, 2, 3]");
713 }
714
715 #[test]
718 fn variable() {
719 prints_as(&Expr::Var(Arc::from("x")), "x");
720 }
721
722 #[test]
725 fn lambda_simple() {
726 prints_as(
727 &Expr::Lam(Arc::from("x"), Box::new(Expr::Var(Arc::from("x")))),
728 "\\x -> x",
729 );
730 }
731
732 #[test]
733 fn lambda_multi_param() {
734 prints_as(
735 &Expr::Lam(
736 Arc::from("x"),
737 Box::new(Expr::Lam(
738 Arc::from("y"),
739 Box::new(Expr::Builtin(
740 BuiltinOp::Add,
741 vec![Expr::Var(Arc::from("x")), Expr::Var(Arc::from("y"))],
742 )),
743 )),
744 ),
745 "\\x y -> x + y",
746 );
747 }
748
749 #[test]
750 fn lambda_round_trip() {
751 round_trip("\\x -> x + 1");
752 round_trip("\\x y -> x + y");
753 }
754
755 #[test]
758 fn app_simple() {
759 prints_as(
760 &Expr::App(
761 Box::new(Expr::Var(Arc::from("f"))),
762 Box::new(Expr::Var(Arc::from("x"))),
763 ),
764 "f x",
765 );
766 }
767
768 #[test]
769 fn app_chain() {
770 prints_as(
771 &Expr::App(
772 Box::new(Expr::App(
773 Box::new(Expr::Var(Arc::from("f"))),
774 Box::new(Expr::Var(Arc::from("x"))),
775 )),
776 Box::new(Expr::Var(Arc::from("y"))),
777 ),
778 "f x y",
779 );
780 }
781
782 #[test]
783 fn app_complex_arg() {
784 prints_as(
786 &Expr::App(
787 Box::new(Expr::Var(Arc::from("f"))),
788 Box::new(Expr::App(
789 Box::new(Expr::Var(Arc::from("g"))),
790 Box::new(Expr::Var(Arc::from("x"))),
791 )),
792 ),
793 "f (g x)",
794 );
795 }
796
797 #[test]
800 fn record_simple() {
801 prints_as(
802 &Expr::Record(vec![
803 (Arc::from("x"), Expr::Lit(Literal::Int(1))),
804 (Arc::from("y"), Expr::Lit(Literal::Int(2))),
805 ]),
806 "{ x = 1, y = 2 }",
807 );
808 }
809
810 #[test]
811 fn record_punning() {
812 prints_as(
813 &Expr::Record(vec![
814 (Arc::from("x"), Expr::Var(Arc::from("x"))),
815 (Arc::from("y"), Expr::Var(Arc::from("y"))),
816 ]),
817 "{ x, y }",
818 );
819 }
820
821 #[test]
822 fn record_mixed_punning() {
823 prints_as(
824 &Expr::Record(vec![
825 (Arc::from("x"), Expr::Var(Arc::from("x"))),
826 (Arc::from("y"), Expr::Lit(Literal::Int(42))),
827 ]),
828 "{ x, y = 42 }",
829 );
830 }
831
832 #[test]
833 fn record_round_trip() {
834 round_trip("{ name = x, age = 30 }");
835 round_trip("{ x, y }");
836 }
837
838 #[test]
841 fn list_simple() {
842 prints_as(
843 &Expr::List(vec![
844 Expr::Lit(Literal::Int(1)),
845 Expr::Lit(Literal::Int(2)),
846 Expr::Lit(Literal::Int(3)),
847 ]),
848 "[1, 2, 3]",
849 );
850 }
851
852 #[test]
853 fn list_empty() {
854 prints_as(&Expr::List(vec![]), "[]");
855 }
856
857 #[test]
858 fn list_round_trip() {
859 round_trip("[1, 2, 3]");
860 round_trip("[]");
861 }
862
863 #[test]
866 fn field_access() {
867 prints_as(
868 &Expr::Field(Box::new(Expr::Var(Arc::from("x"))), Arc::from("name")),
869 "x.name",
870 );
871 }
872
873 #[test]
874 fn field_chain() {
875 prints_as(
876 &Expr::Field(
877 Box::new(Expr::Field(
878 Box::new(Expr::Var(Arc::from("x"))),
879 Arc::from("a"),
880 )),
881 Arc::from("b"),
882 ),
883 "x.a.b",
884 );
885 }
886
887 #[test]
888 fn field_round_trip() {
889 round_trip("x.name");
890 round_trip("x.a.b");
891 }
892
893 #[test]
896 fn edge_traversal() {
897 prints_as(
898 &Expr::Builtin(
899 BuiltinOp::Edge,
900 vec![
901 Expr::Var(Arc::from("doc")),
902 Expr::Lit(Literal::Str("layers".into())),
903 ],
904 ),
905 "doc -> layers",
906 );
907 }
908
909 #[test]
910 fn edge_chain() {
911 prints_as(
912 &Expr::Builtin(
913 BuiltinOp::Edge,
914 vec![
915 Expr::Builtin(
916 BuiltinOp::Edge,
917 vec![
918 Expr::Var(Arc::from("doc")),
919 Expr::Lit(Literal::Str("layers".into())),
920 ],
921 ),
922 Expr::Lit(Literal::Str("annotations".into())),
923 ],
924 ),
925 "doc -> layers -> annotations",
926 );
927 }
928
929 #[test]
930 fn edge_round_trip() {
931 round_trip("doc -> layers");
932 round_trip("doc -> layers -> annotations");
933 }
934
935 #[test]
938 fn infix_add() {
939 prints_as(
940 &Expr::Builtin(
941 BuiltinOp::Add,
942 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))],
943 ),
944 "x + 1",
945 );
946 }
947
948 #[test]
949 fn infix_precedence_no_parens() {
950 prints_as(
952 &Expr::Builtin(
953 BuiltinOp::Add,
954 vec![
955 Expr::Lit(Literal::Int(1)),
956 Expr::Builtin(
957 BuiltinOp::Mul,
958 vec![Expr::Lit(Literal::Int(2)), Expr::Lit(Literal::Int(3))],
959 ),
960 ],
961 ),
962 "1 + 2 * 3",
963 );
964 }
965
966 #[test]
967 fn infix_precedence_needs_parens() {
968 prints_as(
970 &Expr::Builtin(
971 BuiltinOp::Mul,
972 vec![
973 Expr::Builtin(
974 BuiltinOp::Add,
975 vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(2))],
976 ),
977 Expr::Lit(Literal::Int(3)),
978 ],
979 ),
980 "(1 + 2) * 3",
981 );
982 }
983
984 #[test]
985 fn infix_left_assoc_no_parens() {
986 prints_as(
988 &Expr::Builtin(
989 BuiltinOp::Add,
990 vec![
991 Expr::Builtin(
992 BuiltinOp::Add,
993 vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(2))],
994 ),
995 Expr::Lit(Literal::Int(3)),
996 ],
997 ),
998 "1 + 2 + 3",
999 );
1000 }
1001
1002 #[test]
1003 fn infix_right_assoc_needs_parens() {
1004 prints_as(
1006 &Expr::Builtin(
1007 BuiltinOp::Add,
1008 vec![
1009 Expr::Lit(Literal::Int(1)),
1010 Expr::Builtin(
1011 BuiltinOp::Add,
1012 vec![Expr::Lit(Literal::Int(2)), Expr::Lit(Literal::Int(3))],
1013 ),
1014 ],
1015 ),
1016 "1 + (2 + 3)",
1017 );
1018 }
1019
1020 #[test]
1021 fn infix_concat_right_assoc() {
1022 prints_as(
1024 &Expr::Builtin(
1025 BuiltinOp::Concat,
1026 vec![
1027 Expr::Var(Arc::from("a")),
1028 Expr::Builtin(
1029 BuiltinOp::Concat,
1030 vec![Expr::Var(Arc::from("b")), Expr::Var(Arc::from("c"))],
1031 ),
1032 ],
1033 ),
1034 "a ++ b ++ c",
1035 );
1036 }
1037
1038 #[test]
1039 fn infix_comparison() {
1040 prints_as(
1041 &Expr::Builtin(
1042 BuiltinOp::Eq,
1043 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))],
1044 ),
1045 "x == 1",
1046 );
1047 prints_as(
1048 &Expr::Builtin(
1049 BuiltinOp::Neq,
1050 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))],
1051 ),
1052 "x /= 1",
1053 );
1054 prints_as(
1055 &Expr::Builtin(
1056 BuiltinOp::Lt,
1057 vec![Expr::Var(Arc::from("x")), Expr::Var(Arc::from("y"))],
1058 ),
1059 "x < y",
1060 );
1061 }
1062
1063 #[test]
1064 fn infix_logical() {
1065 prints_as(
1066 &Expr::Builtin(
1067 BuiltinOp::And,
1068 vec![Expr::Var(Arc::from("a")), Expr::Var(Arc::from("b"))],
1069 ),
1070 "a && b",
1071 );
1072 prints_as(
1073 &Expr::Builtin(
1074 BuiltinOp::Or,
1075 vec![Expr::Var(Arc::from("a")), Expr::Var(Arc::from("b"))],
1076 ),
1077 "a || b",
1078 );
1079 }
1080
1081 #[test]
1082 fn infix_round_trips() {
1083 round_trip("1 + 2");
1084 round_trip("1 + 2 * 3");
1085 round_trip("(1 + 2) * 3");
1086 round_trip("a && b || c");
1087 round_trip("x == 1");
1088 round_trip("x /= 1");
1089 }
1090
1091 #[test]
1094 fn prefix_neg() {
1095 prints_as(
1096 &Expr::Builtin(BuiltinOp::Neg, vec![Expr::Var(Arc::from("x"))]),
1097 "-x",
1098 );
1099 }
1100
1101 #[test]
1102 fn prefix_not() {
1103 prints_as(
1104 &Expr::Builtin(BuiltinOp::Not, vec![Expr::Lit(Literal::Bool(true))]),
1105 "not True",
1106 );
1107 }
1108
1109 #[test]
1110 fn prefix_round_trip() {
1111 round_trip("-x");
1112 round_trip("not True");
1113 }
1114
1115 #[test]
1118 fn builtin_function_call() {
1119 prints_as(
1121 &Expr::Builtin(
1122 BuiltinOp::Map,
1123 vec![Expr::Var(Arc::from("xs")), Expr::Var(Arc::from("f"))],
1124 ),
1125 "map f xs",
1126 );
1127 }
1128
1129 #[test]
1130 fn fold_prints_function_first() {
1131 prints_as(
1132 &Expr::Builtin(
1133 BuiltinOp::Fold,
1134 vec![
1135 Expr::Var(Arc::from("xs")),
1136 Expr::Var(Arc::from("z")),
1137 Expr::Var(Arc::from("f")),
1138 ],
1139 ),
1140 "fold f z xs",
1141 );
1142 }
1143
1144 #[test]
1145 fn builtin_unary() {
1146 prints_as(
1147 &Expr::Builtin(BuiltinOp::Head, vec![Expr::Var(Arc::from("xs"))]),
1148 "head xs",
1149 );
1150 }
1151
1152 #[test]
1153 fn builtin_round_trip() {
1154 round_trip("map f xs");
1155 round_trip("head xs");
1156 round_trip("filter f xs");
1157 round_trip("fold f z xs");
1158 round_trip("flat_map f xs");
1159 }
1160
1161 #[test]
1164 fn let_simple() {
1165 prints_as(
1166 &Expr::Let {
1167 name: Arc::from("x"),
1168 value: Box::new(Expr::Lit(Literal::Int(1))),
1169 body: Box::new(Expr::Builtin(
1170 BuiltinOp::Add,
1171 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))],
1172 )),
1173 },
1174 "let x = 1 in x + 1",
1175 );
1176 }
1177
1178 #[test]
1179 fn let_round_trip() {
1180 round_trip("let x = 1 in x + 1");
1181 }
1182
1183 #[test]
1186 fn if_then_else() {
1187 let expr = Expr::Match {
1188 scrutinee: Box::new(Expr::Lit(Literal::Bool(true))),
1189 arms: vec![
1190 (
1191 Pattern::Lit(Literal::Bool(true)),
1192 Expr::Lit(Literal::Int(1)),
1193 ),
1194 (Pattern::Wildcard, Expr::Lit(Literal::Int(0))),
1195 ],
1196 };
1197 prints_as(&expr, "if True then 1 else 0");
1198 }
1199
1200 #[test]
1201 fn if_round_trip() {
1202 round_trip("if True then 1 else 0");
1203 }
1204
1205 #[test]
1208 fn case_of() {
1209 let expr = Expr::Match {
1210 scrutinee: Box::new(Expr::Var(Arc::from("x"))),
1211 arms: vec![
1212 (
1213 Pattern::Lit(Literal::Bool(true)),
1214 Expr::Lit(Literal::Int(1)),
1215 ),
1216 (
1217 Pattern::Lit(Literal::Bool(false)),
1218 Expr::Lit(Literal::Int(0)),
1219 ),
1220 ],
1221 };
1222 prints_as(&expr, "case x of\n True -> 1\n False -> 0");
1223 }
1224
1225 #[test]
1226 fn case_round_trip() {
1227 round_trip("case x of\n True -> 1\n False -> 0");
1228 }
1229
1230 #[test]
1233 fn nested_let_in_lambda() {
1234 round_trip("\\x -> let y = x + 1 in y * 2");
1235 }
1236
1237 #[test]
1238 fn nested_if_in_let() {
1239 round_trip("let x = if True then 1 else 0 in x + 1");
1240 }
1241
1242 #[test]
1243 fn lambda_as_arg() {
1244 prints_as(
1246 &Expr::App(
1247 Box::new(Expr::Var(Arc::from("f"))),
1248 Box::new(Expr::Lam(
1249 Arc::from("x"),
1250 Box::new(Expr::Var(Arc::from("x"))),
1251 )),
1252 ),
1253 "f (\\x -> x)",
1254 );
1255 }
1256
1257 #[test]
1258 fn complex_expression_round_trip() {
1259 round_trip("\\f xs -> map (\\x -> f x + 1) xs");
1260 }
1261
1262 #[test]
1265 fn pattern_wildcard() {
1266 let mut buf = String::new();
1267 write_pattern(&mut buf, &Pattern::Wildcard);
1268 assert_eq!(buf, "_");
1269 }
1270
1271 #[test]
1272 fn pattern_var() {
1273 let mut buf = String::new();
1274 write_pattern(&mut buf, &Pattern::Var(Arc::from("x")));
1275 assert_eq!(buf, "x");
1276 }
1277
1278 #[test]
1279 fn pattern_lit() {
1280 let mut buf = String::new();
1281 write_pattern(&mut buf, &Pattern::Lit(Literal::Int(42)));
1282 assert_eq!(buf, "42");
1283 }
1284
1285 #[test]
1286 fn pattern_list() {
1287 let mut buf = String::new();
1288 write_pattern(
1289 &mut buf,
1290 &Pattern::List(vec![
1291 Pattern::Var(Arc::from("x")),
1292 Pattern::Var(Arc::from("y")),
1293 ]),
1294 );
1295 assert_eq!(buf, "[x, y]");
1296 }
1297
1298 #[test]
1299 fn pattern_record_punning() {
1300 let mut buf = String::new();
1301 write_pattern(
1302 &mut buf,
1303 &Pattern::Record(vec![
1304 (Arc::from("x"), Pattern::Var(Arc::from("x"))),
1305 (Arc::from("y"), Pattern::Var(Arc::from("y"))),
1306 ]),
1307 );
1308 assert_eq!(buf, "{ x, y }");
1309 }
1310
1311 #[test]
1312 fn pattern_constructor() {
1313 let mut buf = String::new();
1314 write_pattern(
1315 &mut buf,
1316 &Pattern::Constructor(Arc::from("Just"), vec![Pattern::Var(Arc::from("x"))]),
1317 );
1318 assert_eq!(buf, "Just x");
1319 }
1320
1321 #[test]
1324 fn index_access() {
1325 prints_as(
1326 &Expr::Index(
1327 Box::new(Expr::Var(Arc::from("xs"))),
1328 Box::new(Expr::Lit(Literal::Int(0))),
1329 ),
1330 "xs[0]",
1331 );
1332 }
1333
1334 #[test]
1337 fn literal_record() {
1338 prints_as(
1339 &Expr::Lit(Literal::Record(vec![
1340 (Arc::from("x"), Literal::Int(1)),
1341 (Arc::from("y"), Literal::Int(2)),
1342 ])),
1343 "{ x = 1, y = 2 }",
1344 );
1345 }
1346
1347 #[test]
1348 fn literal_list() {
1349 prints_as(
1350 &Expr::Lit(Literal::List(vec![Literal::Int(1), Literal::Int(2)])),
1351 "[1, 2]",
1352 );
1353 }
1354
1355 #[test]
1358 fn precedence_logical_and_comparison() {
1359 round_trip("x == 1 && y == 2");
1360 }
1361
1362 #[test]
1363 fn precedence_arithmetic_in_comparison() {
1364 round_trip("x + 1 == y * 2");
1365 }
1366
1367 #[test]
1368 fn concat_round_trip() {
1369 round_trip(r#""hello" ++ " world""#);
1370 }
1371}