1use std::sync::Arc;
9
10use chumsky::input::{Input as _, Stream, ValueInput};
11use chumsky::pratt::{infix, left, prefix, right};
12use chumsky::prelude::*;
13use chumsky::span::SimpleSpan;
14
15use panproto_expr::{BuiltinOp, Expr, Literal, Pattern};
16
17use crate::token::Token;
18
19pub type ParseError = Rich<'static, Token, SimpleSpan>;
21
22pub fn parse(tokens: &[crate::Spanned]) -> Result<Expr, Vec<ParseError>> {
30 let mapped: Vec<(Token, SimpleSpan)> = tokens
31 .iter()
32 .filter(|s| s.token != Token::Eof)
33 .map(|s| (s.token.clone(), SimpleSpan::new(s.span.start, s.span.end)))
34 .collect();
35 let eoi = tokens.last().map_or_else(
36 || SimpleSpan::new(0, 0),
37 |s| SimpleSpan::new(s.span.start, s.span.end),
38 );
39 let stream = Stream::from_iter(mapped).map(eoi, |(tok, span)| (tok, span));
40 expr_parser().parse(stream).into_result().map_err(|errs| {
41 errs.into_iter()
42 .map(chumsky::error::Rich::into_owned)
43 .collect()
44 })
45}
46
47fn ident<'t, 'src: 't, I>()
51-> impl Parser<'t, I, Arc<str>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
52where
53 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
54{
55 select! { Token::Ident(s) => Arc::from(s.as_str()) }.labelled("identifier")
56}
57
58fn upper_ident<'t, 'src: 't, I>()
60-> impl Parser<'t, I, Arc<str>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
61where
62 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
63{
64 select! { Token::UpperIdent(s) => Arc::from(s.as_str()) }.labelled("constructor")
65}
66
67fn layout_block<'t, 'src: 't, I, T: 't>(
72 item: impl Parser<'t, I, T, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone,
73) -> impl Parser<'t, I, Vec<T>, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
74where
75 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
76{
77 let explicit = item
78 .clone()
79 .separated_by(just(Token::Newline).or(just(Token::Comma)))
80 .allow_trailing()
81 .collect::<Vec<_>>()
82 .delimited_by(just(Token::LBrace), just(Token::RBrace));
83
84 let implicit = item
85 .separated_by(just(Token::Newline))
86 .allow_trailing()
87 .collect::<Vec<_>>()
88 .delimited_by(just(Token::Indent), just(Token::Dedent));
89
90 explicit.or(implicit)
91}
92
93fn pattern_parser<'t, 'src: 't, I>()
97-> impl Parser<'t, I, Pattern, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
98where
99 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
100{
101 recursive(|pat| {
102 let wildcard = select! { Token::Ident(s) if s == "_" => Pattern::Wildcard };
103
104 let var = ident().map(Pattern::Var);
105
106 let literal_pat = literal_parser().map(Pattern::Lit);
107
108 let paren = pat
109 .clone()
110 .delimited_by(just(Token::LParen), just(Token::RParen));
111
112 let list_pat = pat
113 .clone()
114 .separated_by(just(Token::Comma))
115 .collect::<Vec<_>>()
116 .delimited_by(just(Token::LBracket), just(Token::RBracket))
117 .map(Pattern::List);
118
119 let field_pat = ident()
120 .then(just(Token::Eq).ignore_then(pat.clone()).or_not())
121 .map(|(name, maybe_pat): (Arc<str>, Option<Pattern>)| {
122 let p = maybe_pat.unwrap_or_else(|| Pattern::Var(name.clone()));
123 (name, p)
124 });
125
126 let record_pat = field_pat
127 .separated_by(just(Token::Comma))
128 .collect::<Vec<_>>()
129 .delimited_by(just(Token::LBrace), just(Token::RBrace))
130 .map(Pattern::Record);
131
132 let constructor = upper_ident()
133 .then(pat.clone().repeated().collect::<Vec<_>>())
134 .map(|(name, args): (Arc<str>, Vec<Pattern>)| Pattern::Constructor(name, args));
135
136 choice((
137 wildcard,
138 literal_pat,
139 paren,
140 list_pat,
141 record_pat,
142 constructor,
143 var,
144 ))
145 })
146}
147
148fn literal_parser<'t, 'src: 't, I>()
152-> impl Parser<'t, I, Literal, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
153where
154 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
155{
156 select! {
157 Token::Int(n) => Literal::Int(n),
158 Token::Float(f) => Literal::Float(f),
159 Token::Str(s) => Literal::Str(s),
160 Token::True => Literal::Bool(true),
161 Token::False => Literal::Bool(false),
162 Token::Nothing => Literal::Null,
163 }
164 .labelled("literal")
165}
166
167fn resolve_builtin(name: &str) -> Option<BuiltinOp> {
171 match name {
172 "add" => Some(BuiltinOp::Add),
173 "sub" => Some(BuiltinOp::Sub),
174 "mul" => Some(BuiltinOp::Mul),
175 "abs" => Some(BuiltinOp::Abs),
176 "floor" => Some(BuiltinOp::Floor),
177 "ceil" => Some(BuiltinOp::Ceil),
178 "round" => Some(BuiltinOp::Round),
179 "concat" => Some(BuiltinOp::Concat),
180 "len" => Some(BuiltinOp::Len),
181 "slice" => Some(BuiltinOp::Slice),
182 "upper" => Some(BuiltinOp::Upper),
183 "lower" => Some(BuiltinOp::Lower),
184 "trim" => Some(BuiltinOp::Trim),
185 "split" => Some(BuiltinOp::Split),
186 "join" => Some(BuiltinOp::Join),
187 "replace" => Some(BuiltinOp::Replace),
188 "contains" => Some(BuiltinOp::Contains),
189 "map" => Some(BuiltinOp::Map),
190 "filter" => Some(BuiltinOp::Filter),
191 "fold" => Some(BuiltinOp::Fold),
192 "append" => Some(BuiltinOp::Append),
193 "head" => Some(BuiltinOp::Head),
194 "tail" => Some(BuiltinOp::Tail),
195 "reverse" => Some(BuiltinOp::Reverse),
196 "flat_map" | "flatMap" => Some(BuiltinOp::FlatMap),
197 "length" => Some(BuiltinOp::Length),
198 "range" => Some(BuiltinOp::Range),
199 "merge" | "merge_records" => Some(BuiltinOp::MergeRecords),
200 "keys" => Some(BuiltinOp::Keys),
201 "values" => Some(BuiltinOp::Values),
202 "has_field" | "hasField" => Some(BuiltinOp::HasField),
203 "default" | "default_val" | "defaultVal" => Some(BuiltinOp::DefaultVal),
204 "clamp" => Some(BuiltinOp::Clamp),
205 "truncate_str" | "truncateStr" => Some(BuiltinOp::TruncateStr),
206 "int_to_float" | "intToFloat" => Some(BuiltinOp::IntToFloat),
207 "float_to_int" | "floatToInt" => Some(BuiltinOp::FloatToInt),
208 "int_to_str" | "intToStr" => Some(BuiltinOp::IntToStr),
209 "float_to_str" | "floatToStr" => Some(BuiltinOp::FloatToStr),
210 "str_to_int" | "strToInt" => Some(BuiltinOp::StrToInt),
211 "str_to_float" | "strToFloat" => Some(BuiltinOp::StrToFloat),
212 "type_of" | "typeOf" => Some(BuiltinOp::TypeOf),
213 "is_null" | "isNull" => Some(BuiltinOp::IsNull),
214 "is_list" | "isList" => Some(BuiltinOp::IsList),
215 "edge" => Some(BuiltinOp::Edge),
216 "children" => Some(BuiltinOp::Children),
217 "has_edge" | "hasEdge" => Some(BuiltinOp::HasEdge),
218 "edge_count" | "edgeCount" => Some(BuiltinOp::EdgeCount),
219 "anchor" => Some(BuiltinOp::Anchor),
220 _ => None,
221 }
222}
223
224#[allow(clippy::too_many_lines)]
237fn expr_parser<'t, 'src: 't, I>()
238-> impl Parser<'t, I, Expr, extra::Err<Rich<'t, Token, SimpleSpan>>> + Clone
239where
240 I: ValueInput<'t, Token = Token, Span = SimpleSpan>,
241{
242 recursive(|expr| {
243 let pattern = pattern_parser();
244
245 let lit = literal_parser().map(Expr::Lit);
248
249 let var_or_builtin = ident().map(Expr::Var);
250
251 let constructor = upper_ident().map(Expr::Var);
252
253 let paren_expr = expr
254 .clone()
255 .delimited_by(just(Token::LParen), just(Token::RParen));
256
257 let list_expr = {
259 let plain_list = expr
260 .clone()
261 .separated_by(just(Token::Comma))
262 .collect::<Vec<_>>()
263 .map(Expr::List);
264
265 let comprehension = expr
267 .clone()
268 .then_ignore(just(Token::Pipe))
269 .then(
270 ident()
271 .then_ignore(just(Token::LeftArrow))
272 .then(expr.clone())
273 .map(|(n, e): (Arc<str>, Expr)| Qual::Generator(n, e))
274 .or(expr.clone().map(Qual::Guard))
275 .separated_by(just(Token::Comma))
276 .at_least(1)
277 .collect::<Vec<Qual>>(),
278 )
279 .map(|(body, quals): (Expr, Vec<Qual>)| desugar_comprehension(body, &quals));
280
281 let range = expr
289 .clone()
290 .then_ignore(just(Token::DotDot))
291 .then(expr.clone().or_not())
292 .validate(|(start, end): (Expr, Option<Expr>), extra, emitter| {
293 if let Some(stop) = end {
294 return Expr::Builtin(BuiltinOp::Range, vec![start, stop]);
295 }
296 emitter.emit(Rich::custom(
297 extra.span(),
298 "open-ended range `[a..]` is not supported: the expression \
299 language has no lazy lists. Give an upper bound, as in `[a..b]`.",
300 ));
301 Expr::List(vec![start])
306 });
307
308 choice((comprehension, range, plain_list))
309 .delimited_by(just(Token::LBracket), just(Token::RBracket))
310 };
311
312 let record_expr = {
314 let field_bind = ident()
315 .then(just(Token::Eq).ignore_then(expr.clone()).or_not())
316 .map(|(name, val): (Arc<str>, Option<Expr>)| {
317 let v = val.unwrap_or_else(|| Expr::Var(name.clone()));
318 (name, v)
319 });
320
321 field_bind
322 .separated_by(just(Token::Comma))
323 .allow_trailing()
324 .collect::<Vec<_>>()
325 .delimited_by(just(Token::LBrace), just(Token::RBrace))
326 .map(Expr::Record)
327 };
328
329 let atom = choice((
330 lit,
331 paren_expr,
332 list_expr,
333 record_expr,
334 constructor,
335 var_or_builtin,
336 ));
337
338 let postfix_chain = atom.foldl(
341 choice((
342 just(Token::Dot).ignore_then(ident()).map(PostfixOp::Field),
343 just(Token::Arrow).ignore_then(ident()).map(PostfixOp::Edge),
344 ))
345 .repeated(),
346 |expr, postfix| match postfix {
347 PostfixOp::Field(name) => Expr::Field(Box::new(expr), name),
348 PostfixOp::Edge(edge) => Expr::Builtin(
349 BuiltinOp::Edge,
350 vec![expr, Expr::Lit(Literal::Str(edge.to_string()))],
351 ),
352 },
353 );
354
355 let app = postfix_chain
358 .clone()
359 .foldl(postfix_chain.repeated(), resolve_application);
360
361 let pratt = app.pratt((
364 infix(left(1), just(Token::Ampersand), |l, _, r, _| {
366 Expr::App(Box::new(r), Box::new(l))
367 }),
368 infix(left(3), just(Token::OrOr), |l, _, r, _| {
370 Expr::Builtin(BuiltinOp::Or, vec![l, r])
371 }),
372 infix(left(4), just(Token::AndAnd), |l, _, r, _| {
374 Expr::Builtin(BuiltinOp::And, vec![l, r])
375 }),
376 infix(right(5), just(Token::EqEq), |l, _, r, _| {
378 Expr::Builtin(BuiltinOp::Eq, vec![l, r])
379 }),
380 infix(right(5), just(Token::Neq), |l, _, r, _| {
381 Expr::Builtin(BuiltinOp::Neq, vec![l, r])
382 }),
383 infix(right(5), just(Token::Lt), |l, _, r, _| {
384 Expr::Builtin(BuiltinOp::Lt, vec![l, r])
385 }),
386 infix(right(5), just(Token::Lte), |l, _, r, _| {
387 Expr::Builtin(BuiltinOp::Lte, vec![l, r])
388 }),
389 infix(right(5), just(Token::Gt), |l, _, r, _| {
390 Expr::Builtin(BuiltinOp::Gt, vec![l, r])
391 }),
392 infix(right(5), just(Token::Gte), |l, _, r, _| {
393 Expr::Builtin(BuiltinOp::Gte, vec![l, r])
394 }),
395 infix(right(6), just(Token::PlusPlus), |l, _, r, _| {
397 Expr::Builtin(BuiltinOp::Concat, vec![l, r])
398 }),
399 infix(left(7), just(Token::Plus), |l, _, r, _| {
401 Expr::Builtin(BuiltinOp::Add, vec![l, r])
402 }),
403 infix(left(7), just(Token::Minus), |l, _, r, _| {
404 Expr::Builtin(BuiltinOp::Sub, vec![l, r])
405 }),
406 infix(left(8), just(Token::Star), |l, _, r, _| {
408 Expr::Builtin(BuiltinOp::Mul, vec![l, r])
409 }),
410 infix(left(8), just(Token::Slash), |l, _, r, _| {
411 Expr::Builtin(BuiltinOp::Div, vec![l, r])
412 }),
413 infix(left(8), just(Token::Percent), |l, _, r, _| {
414 Expr::Builtin(BuiltinOp::Mod, vec![l, r])
415 }),
416 infix(left(8), just(Token::ModKw), |l, _, r, _| {
417 Expr::Builtin(BuiltinOp::Mod, vec![l, r])
418 }),
419 infix(left(8), just(Token::DivKw), |l, _, r, _| {
420 Expr::Builtin(BuiltinOp::Div, vec![l, r])
421 }),
422 prefix(9, just(Token::Minus), |_, rhs, _| {
424 Expr::Builtin(BuiltinOp::Neg, vec![rhs])
425 }),
426 prefix(9, just(Token::Not), |_, rhs, _| {
427 Expr::Builtin(BuiltinOp::Not, vec![rhs])
428 }),
429 ));
430
431 let lambda = just(Token::Backslash)
435 .ignore_then(
436 pattern
437 .clone()
438 .repeated()
439 .at_least(1)
440 .collect::<Vec<Pattern>>(),
441 )
442 .then_ignore(just(Token::Arrow))
443 .then(expr.clone())
444 .map(|(params, body): (Vec<Pattern>, Expr)| desugar_lambda(¶ms, body));
445
446 let let_bind = ident()
448 .then(pattern.clone().repeated().collect::<Vec<Pattern>>())
449 .then_ignore(just(Token::Eq))
450 .then(expr.clone())
451 .map(|((name, params), val): ((Arc<str>, Vec<Pattern>), Expr)| {
452 if params.is_empty() {
453 (name, val)
454 } else {
455 (name, desugar_lambda(¶ms, val))
456 }
457 });
458
459 let let_expr = just(Token::Let)
460 .ignore_then(layout_block(let_bind.clone()).or(let_bind.clone().map(|b| vec![b])))
461 .then_ignore(just(Token::In))
462 .then(expr.clone())
463 .map(|(binds, body)| desugar_let_binds(binds, body));
464
465 let if_expr = just(Token::If)
467 .ignore_then(expr.clone())
468 .then_ignore(just(Token::Then))
469 .then(expr.clone())
470 .then_ignore(just(Token::Else))
471 .then(expr.clone())
472 .map(|((cond, then_branch), else_branch)| Expr::Match {
473 scrutinee: Box::new(cond),
474 arms: vec![
475 (Pattern::Lit(Literal::Bool(true)), then_branch),
476 (Pattern::Wildcard, else_branch),
477 ],
478 });
479
480 let case_arm = pattern
482 .clone()
483 .then_ignore(just(Token::Arrow))
484 .then(expr.clone());
485
486 let case_expr = just(Token::Case)
487 .ignore_then(expr.clone())
488 .then_ignore(just(Token::Of))
489 .then(layout_block(case_arm))
490 .map(|(scrutinee, arms)| Expr::Match {
491 scrutinee: Box::new(scrutinee),
492 arms,
493 });
494
495 let do_stmt = choice((
497 ident()
498 .then_ignore(just(Token::LeftArrow))
499 .then(expr.clone())
500 .map(|(name, e): (Arc<str>, Expr)| DoStmt::Bind(name, e)),
501 just(Token::Let)
502 .ignore_then(let_bind.clone())
503 .map(|(name, val)| DoStmt::Let(name, val)),
504 expr.clone().map(DoStmt::Expr),
505 ));
506
507 let do_expr = just(Token::Do)
508 .ignore_then(layout_block(do_stmt))
509 .map(desugar_do);
510
511 let full_expr = choice((do_expr, let_expr, if_expr, case_expr, lambda, pratt));
514
515 let where_bind = ident()
517 .then(pattern.repeated().collect::<Vec<Pattern>>())
518 .then_ignore(just(Token::Eq))
519 .then(expr.clone())
520 .map(|((name, params), val): ((Arc<str>, Vec<Pattern>), Expr)| {
521 if params.is_empty() {
522 (name, val)
523 } else {
524 (name, desugar_lambda(¶ms, val))
525 }
526 });
527
528 let where_clause = just(Token::Where)
529 .ignore_then(layout_block(where_bind.clone()).or(where_bind.map(|b| vec![b])));
530
531 full_expr
532 .then(where_clause.or_not())
533 .map(|(body, where_binds)| match where_binds {
534 Some(binds) => desugar_let_binds(binds, body),
535 None => body,
536 })
537 })
538}
539
540#[derive(Debug, Clone)]
544enum PostfixOp {
545 Field(Arc<str>),
547 Edge(Arc<str>),
549}
550
551#[derive(Debug, Clone)]
553enum Qual {
554 Generator(Arc<str>, Expr),
556 Guard(Expr),
558}
559
560#[derive(Debug, Clone)]
562enum DoStmt {
563 Bind(Arc<str>, Expr),
565 Let(Arc<str>, Expr),
567 Expr(Expr),
569}
570
571fn desugar_lambda(params: &[Pattern], body: Expr) -> Expr {
575 params.iter().rev().fold(body, |acc, pat| match pat {
576 Pattern::Var(name) => Expr::Lam(name.clone(), Box::new(acc)),
577 Pattern::Wildcard => Expr::Lam(Arc::from("_"), Box::new(acc)),
578 other => {
579 let fresh: Arc<str> = Arc::from("_arg");
580 Expr::Lam(
581 fresh.clone(),
582 Box::new(Expr::Match {
583 scrutinee: Box::new(Expr::Var(fresh)),
584 arms: vec![(other.clone(), acc)],
585 }),
586 )
587 }
588 })
589}
590
591fn desugar_let_binds(binds: Vec<(Arc<str>, Expr)>, body: Expr) -> Expr {
593 binds
594 .into_iter()
595 .rev()
596 .fold(body, |acc, (name, val)| Expr::Let {
597 name,
598 value: Box::new(val),
599 body: Box::new(acc),
600 })
601}
602
603fn desugar_comprehension(body: Expr, quals: &[Qual]) -> Expr {
605 quals
606 .iter()
607 .rev()
608 .fold(Expr::List(vec![body]), |acc, qual| match qual {
609 Qual::Generator(name, source) => Expr::Builtin(
610 BuiltinOp::FlatMap,
611 vec![source.clone(), Expr::Lam(name.clone(), Box::new(acc))],
612 ),
613 Qual::Guard(pred) => Expr::Match {
614 scrutinee: Box::new(pred.clone()),
615 arms: vec![
616 (Pattern::Lit(Literal::Bool(true)), acc),
617 (Pattern::Wildcard, Expr::List(vec![])),
618 ],
619 },
620 })
621}
622
623fn desugar_do(stmts: Vec<DoStmt>) -> Expr {
625 if stmts.is_empty() {
626 return Expr::List(vec![]);
627 }
628 let mut iter = stmts.into_iter().rev();
629 let Some(last) = iter.next() else {
631 return Expr::List(vec![]);
632 };
633 let init = match last {
634 DoStmt::Expr(e) | DoStmt::Bind(_, e) => e,
635 DoStmt::Let(name, val) => Expr::Let {
636 name,
637 value: Box::new(val),
638 body: Box::new(Expr::List(vec![])),
639 },
640 };
641 iter.fold(init, |acc, stmt| match stmt {
642 DoStmt::Bind(name, source) => Expr::Builtin(
643 BuiltinOp::FlatMap,
644 vec![source, Expr::Lam(name, Box::new(acc))],
645 ),
646 DoStmt::Let(name, val) => Expr::Let {
647 name,
648 value: Box::new(val),
649 body: Box::new(acc),
650 },
651 DoStmt::Expr(e) => Expr::Builtin(
652 BuiltinOp::FlatMap,
653 vec![e, Expr::Lam(Arc::from("_"), Box::new(acc))],
654 ),
655 })
656}
657
658fn lower_list_builtin_args(op: BuiltinOp, args: Vec<Expr>) -> Vec<Expr> {
673 match (op, args.len()) {
674 (BuiltinOp::Map | BuiltinOp::Filter | BuiltinOp::FlatMap, 2) => {
676 let mut args = args;
677 args.swap(0, 1);
678 args
679 }
680 (BuiltinOp::Fold, 3) => {
682 let mut args = args;
683 args.swap(0, 2);
684 args
685 }
686 _ => args,
687 }
688}
689
690fn resolve_application(func: Expr, arg: Expr) -> Expr {
692 match &func {
693 Expr::Var(name) => {
694 if let Some(op) = resolve_builtin(name) {
695 let args = lower_list_builtin_args(op, vec![arg]);
696 Expr::Builtin(op, args)
697 } else {
698 Expr::App(Box::new(func), Box::new(arg))
699 }
700 }
701 Expr::Builtin(op, args) if args.len() < op.arity() => {
702 let mut new_args = args.clone();
703 new_args.push(arg);
704 Expr::Builtin(*op, lower_list_builtin_args(*op, new_args))
705 }
706 _ => Expr::App(Box::new(func), Box::new(arg)),
707 }
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use crate::tokenize;
714
715 fn parse_ok(input: &str) -> Expr {
716 let tokens = tokenize(input).unwrap_or_else(|e| panic!("lex failed: {e}"));
717 parse(&tokens).unwrap_or_else(|e| panic!("parse failed: {e:?}"))
718 }
719
720 #[test]
721 fn parse_literal_int() {
722 assert_eq!(parse_ok("42"), Expr::Lit(Literal::Int(42)));
723 }
724
725 #[test]
726 fn parse_literal_string() {
727 assert_eq!(
728 parse_ok(r#""hello""#),
729 Expr::Lit(Literal::Str("hello".into()))
730 );
731 }
732
733 #[test]
734 fn parse_literal_bool() {
735 assert_eq!(parse_ok("True"), Expr::Lit(Literal::Bool(true)));
736 assert_eq!(parse_ok("False"), Expr::Lit(Literal::Bool(false)));
737 }
738
739 #[test]
740 fn parse_nothing() {
741 assert_eq!(parse_ok("Nothing"), Expr::Lit(Literal::Null));
742 }
743
744 #[test]
745 fn parse_variable() {
746 assert_eq!(parse_ok("x"), Expr::Var(Arc::from("x")));
747 }
748
749 #[test]
750 fn parse_arithmetic() {
751 assert_eq!(
752 parse_ok("1 + 2"),
753 Expr::Builtin(
754 BuiltinOp::Add,
755 vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(2))]
756 )
757 );
758 }
759
760 #[test]
761 fn parse_precedence() {
762 assert_eq!(
763 parse_ok("1 + 2 * 3"),
764 Expr::Builtin(
765 BuiltinOp::Add,
766 vec![
767 Expr::Lit(Literal::Int(1)),
768 Expr::Builtin(
769 BuiltinOp::Mul,
770 vec![Expr::Lit(Literal::Int(2)), Expr::Lit(Literal::Int(3))]
771 ),
772 ]
773 )
774 );
775 }
776
777 #[test]
778 fn parse_comparison() {
779 assert_eq!(
780 parse_ok("x == 1"),
781 Expr::Builtin(
782 BuiltinOp::Eq,
783 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
784 )
785 );
786 }
787
788 #[test]
789 fn parse_logical() {
790 assert_eq!(
791 parse_ok("a && b || c"),
792 Expr::Builtin(
793 BuiltinOp::Or,
794 vec![
795 Expr::Builtin(
796 BuiltinOp::And,
797 vec![Expr::Var(Arc::from("a")), Expr::Var(Arc::from("b"))]
798 ),
799 Expr::Var(Arc::from("c")),
800 ]
801 )
802 );
803 }
804
805 #[test]
806 fn parse_negation() {
807 assert_eq!(
808 parse_ok("-x"),
809 Expr::Builtin(BuiltinOp::Neg, vec![Expr::Var(Arc::from("x"))])
810 );
811 }
812
813 #[test]
814 fn parse_not() {
815 assert_eq!(
816 parse_ok("not True"),
817 Expr::Builtin(BuiltinOp::Not, vec![Expr::Lit(Literal::Bool(true))])
818 );
819 }
820
821 #[test]
822 fn parse_field_access() {
823 assert_eq!(
824 parse_ok("x.name"),
825 Expr::Field(Box::new(Expr::Var(Arc::from("x"))), Arc::from("name"))
826 );
827 }
828
829 #[test]
830 fn parse_edge_traversal() {
831 assert_eq!(
832 parse_ok("doc -> layers"),
833 Expr::Builtin(
834 BuiltinOp::Edge,
835 vec![
836 Expr::Var(Arc::from("doc")),
837 Expr::Lit(Literal::Str("layers".into())),
838 ]
839 )
840 );
841 }
842
843 #[test]
844 fn parse_lambda() {
845 assert_eq!(
846 parse_ok("\\x -> x + 1"),
847 Expr::Lam(
848 Arc::from("x"),
849 Box::new(Expr::Builtin(
850 BuiltinOp::Add,
851 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
852 ))
853 )
854 );
855 }
856
857 #[test]
858 fn parse_multi_param_lambda() {
859 let e = parse_ok("\\x y -> x + y");
860 match &e {
861 Expr::Lam(x, inner) => {
862 assert_eq!(&**x, "x");
863 assert!(matches!(&**inner, Expr::Lam(y, _) if &**y == "y"));
864 }
865 _ => panic!("expected nested Lam, got {e:?}"),
866 }
867 }
868
869 #[test]
870 fn parse_let_in() {
871 assert_eq!(
872 parse_ok("let x = 1 in x + 1"),
873 Expr::Let {
874 name: Arc::from("x"),
875 value: Box::new(Expr::Lit(Literal::Int(1))),
876 body: Box::new(Expr::Builtin(
877 BuiltinOp::Add,
878 vec![Expr::Var(Arc::from("x")), Expr::Lit(Literal::Int(1))]
879 )),
880 }
881 );
882 }
883
884 #[test]
885 fn parse_if_then_else() {
886 let e = parse_ok("if True then 1 else 0");
887 assert!(matches!(e, Expr::Match { .. }));
888 }
889
890 #[test]
891 fn parse_case_of() {
892 let e = parse_ok("case x of\n True -> 1\n False -> 0");
893 match e {
894 Expr::Match { arms, .. } => assert_eq!(arms.len(), 2),
895 _ => panic!("expected Match"),
896 }
897 }
898
899 #[test]
900 fn parse_list() {
901 assert_eq!(
902 parse_ok("[1, 2, 3]"),
903 Expr::List(vec![
904 Expr::Lit(Literal::Int(1)),
905 Expr::Lit(Literal::Int(2)),
906 Expr::Lit(Literal::Int(3)),
907 ])
908 );
909 }
910
911 #[test]
912 fn parse_empty_list() {
913 assert_eq!(parse_ok("[]"), Expr::List(vec![]));
914 }
915
916 #[test]
917 fn parse_record() {
918 assert_eq!(
919 parse_ok("{ name = x, age = 30 }"),
920 Expr::Record(vec![
921 (Arc::from("name"), Expr::Var(Arc::from("x"))),
922 (Arc::from("age"), Expr::Lit(Literal::Int(30))),
923 ])
924 );
925 }
926
927 #[test]
928 fn parse_record_punning() {
929 assert_eq!(
930 parse_ok("{ name, age }"),
931 Expr::Record(vec![
932 (Arc::from("name"), Expr::Var(Arc::from("name"))),
933 (Arc::from("age"), Expr::Var(Arc::from("age"))),
934 ])
935 );
936 }
937
938 #[test]
939 #[allow(clippy::expect_used)]
940 fn ranges_lower_to_the_range_builtin_and_evaluate() {
941 let config = panproto_expr::EvalConfig::default();
942 let eval =
943 |src: &str| panproto_expr::eval(&parse_ok(src), &panproto_expr::Env::new(), &config);
944
945 assert_eq!(
946 parse_ok("[1..3]"),
947 Expr::Builtin(
948 BuiltinOp::Range,
949 vec![Expr::Lit(Literal::Int(1)), Expr::Lit(Literal::Int(3)),]
950 )
951 );
952 assert_eq!(
953 eval("[1..3]").expect("range should evaluate"),
954 panproto_expr::Literal::List(vec![
955 panproto_expr::Literal::Int(1),
956 panproto_expr::Literal::Int(2),
957 panproto_expr::Literal::Int(3),
958 ]),
959 "both bounds are inclusive"
960 );
961 assert_eq!(
962 eval("[0..0]").expect("singleton range should evaluate"),
963 panproto_expr::Literal::List(vec![panproto_expr::Literal::Int(0)])
964 );
965 assert_eq!(
966 eval("[3..1]").expect("descending range should evaluate"),
967 panproto_expr::Literal::List(vec![]),
968 "a descending range is empty, not an error"
969 );
970 assert_eq!(parse_ok("range 1 3"), parse_ok("[1..3]"));
972 }
973
974 #[test]
975 #[allow(clippy::expect_used)]
976 fn ranges_compose_with_the_list_builtins() {
977 let config = panproto_expr::EvalConfig::default();
978 let eval =
979 |src: &str| panproto_expr::eval(&parse_ok(src), &panproto_expr::Env::new(), &config);
980
981 assert_eq!(
982 eval("map (\\x -> x * x) [1..4]").expect("map over a range should evaluate"),
983 panproto_expr::Literal::List(vec![
984 panproto_expr::Literal::Int(1),
985 panproto_expr::Literal::Int(4),
986 panproto_expr::Literal::Int(9),
987 panproto_expr::Literal::Int(16),
988 ])
989 );
990 assert_eq!(
991 eval("fold (\\a -> \\b -> a + b) 0 [1..100]").expect("fold over a range"),
992 panproto_expr::Literal::Int(5050)
993 );
994 }
995
996 #[test]
997 fn an_oversized_range_is_rejected_before_it_allocates() {
998 let config = panproto_expr::EvalConfig::default();
1002 let result = panproto_expr::eval(
1003 &parse_ok("[0..99999999]"),
1004 &panproto_expr::Env::new(),
1005 &config,
1006 );
1007 assert!(
1008 matches!(result, Err(panproto_expr::ExprError::ListLengthExceeded(_))),
1009 "expected ListLengthExceeded, got {result:?}"
1010 );
1011 }
1012
1013 #[test]
1014 #[allow(clippy::expect_used)]
1015 fn an_open_ended_range_is_rejected() {
1016 let tokens = tokenize("[1..]").unwrap_or_else(|e| panic!("lex failed: {e}"));
1020 let errs = parse(&tokens).expect_err("an open-ended range must not parse");
1021 let rendered = errs
1022 .iter()
1023 .map(std::string::ToString::to_string)
1024 .collect::<Vec<_>>()
1025 .join("; ");
1026 assert!(
1027 rendered.contains("open-ended range"),
1028 "the error should name the construct, got: {rendered}"
1029 );
1030 }
1031
1032 #[test]
1033 fn parse_builtin_application() {
1034 assert_eq!(
1037 parse_ok("map f xs"),
1038 Expr::Builtin(
1039 BuiltinOp::Map,
1040 vec![Expr::Var(Arc::from("xs")), Expr::Var(Arc::from("f"))]
1041 )
1042 );
1043 }
1044
1045 #[test]
1046 fn parse_fold_lowers_list_first() {
1047 assert_eq!(
1048 parse_ok("fold f z xs"),
1049 Expr::Builtin(
1050 BuiltinOp::Fold,
1051 vec![
1052 Expr::Var(Arc::from("xs")),
1053 Expr::Var(Arc::from("z")),
1054 Expr::Var(Arc::from("f")),
1055 ]
1056 )
1057 );
1058 }
1059
1060 #[test]
1061 #[allow(clippy::expect_used)]
1062 fn parsed_list_builtins_evaluate() {
1063 let env = panproto_expr::Env::new().extend(
1067 Arc::from("xs"),
1068 panproto_expr::Literal::List(vec![
1069 panproto_expr::Literal::Int(1),
1070 panproto_expr::Literal::Int(2),
1071 panproto_expr::Literal::Int(3),
1072 ]),
1073 );
1074 let config = panproto_expr::EvalConfig::default();
1075 let eval = |src: &str| panproto_expr::eval(&parse_ok(src), &env, &config);
1076
1077 assert_eq!(
1078 eval("map (\\x -> x * 2) xs").expect("map should evaluate"),
1079 panproto_expr::Literal::List(vec![
1080 panproto_expr::Literal::Int(2),
1081 panproto_expr::Literal::Int(4),
1082 panproto_expr::Literal::Int(6),
1083 ])
1084 );
1085 assert_eq!(
1086 eval("filter (\\x -> x > 1) xs").expect("filter should evaluate"),
1087 panproto_expr::Literal::List(vec![
1088 panproto_expr::Literal::Int(2),
1089 panproto_expr::Literal::Int(3),
1090 ])
1091 );
1092 assert_eq!(
1093 eval("fold (\\a -> \\b -> a + b) 0 xs").expect("fold should evaluate"),
1094 panproto_expr::Literal::Int(6)
1095 );
1096 }
1097
1098 #[test]
1099 fn parse_string_concat() {
1100 assert_eq!(
1101 parse_ok(r#""hello" ++ " world""#),
1102 Expr::Builtin(
1103 BuiltinOp::Concat,
1104 vec![
1105 Expr::Lit(Literal::Str("hello".into())),
1106 Expr::Lit(Literal::Str(" world".into())),
1107 ]
1108 )
1109 );
1110 }
1111
1112 #[test]
1113 fn parse_pipe() {
1114 assert_eq!(
1115 parse_ok("x & f"),
1116 Expr::App(
1117 Box::new(Expr::Var(Arc::from("f"))),
1118 Box::new(Expr::Var(Arc::from("x"))),
1119 )
1120 );
1121 }
1122
1123 #[test]
1124 fn parse_chained_field_access() {
1125 assert_eq!(
1126 parse_ok("x.a.b"),
1127 Expr::Field(
1128 Box::new(Expr::Field(
1129 Box::new(Expr::Var(Arc::from("x"))),
1130 Arc::from("a"),
1131 )),
1132 Arc::from("b"),
1133 )
1134 );
1135 }
1136
1137 #[test]
1138 fn parse_comprehension() {
1139 let e = parse_ok("[ x + 1 | x <- xs ]");
1140 assert!(matches!(e, Expr::Builtin(BuiltinOp::FlatMap, _)));
1141 }
1142}