1use std::sync::Arc;
8
9use crate::builtin::apply_builtin;
10use crate::env::Env;
11use crate::error::ExprError;
12use crate::expr::{BuiltinOp, Expr, Pattern};
13use crate::literal::Literal;
14
15#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
17pub struct EvalConfig {
18 pub max_steps: u64,
20 pub max_depth: u32,
22 pub max_list_len: usize,
24}
25
26impl Default for EvalConfig {
27 fn default() -> Self {
28 Self {
29 max_steps: 100_000,
30 max_depth: 256,
31 max_list_len: 10_000,
32 }
33 }
34}
35
36pub trait BuiltinResolver {
44 fn handles(&self, op: BuiltinOp) -> bool;
47
48 fn apply(&self, op: BuiltinOp, args: &[Literal]) -> Result<Literal, ExprError>;
55}
56
57struct NoResolver;
60
61impl BuiltinResolver for NoResolver {
62 fn handles(&self, _op: BuiltinOp) -> bool {
63 false
64 }
65
66 fn apply(&self, op: BuiltinOp, _args: &[Literal]) -> Result<Literal, ExprError> {
67 Err(ExprError::NoInstanceContext {
68 op: format!("{op:?}"),
69 })
70 }
71}
72
73struct EvalState<'a> {
75 steps_remaining: u64,
76 max_steps: u64,
77 max_depth: u32,
78 max_list_len: usize,
79 resolver: &'a dyn BuiltinResolver,
80}
81
82impl<'a> EvalState<'a> {
83 const fn new(config: &EvalConfig, resolver: &'a dyn BuiltinResolver) -> Self {
84 Self {
85 steps_remaining: config.max_steps,
86 max_steps: config.max_steps,
87 max_depth: config.max_depth,
88 max_list_len: config.max_list_len,
89 resolver,
90 }
91 }
92
93 const fn tick(&mut self) -> Result<(), ExprError> {
94 if self.steps_remaining == 0 {
95 return Err(ExprError::StepLimitExceeded(self.max_steps));
96 }
97 self.steps_remaining -= 1;
98 Ok(())
99 }
100}
101
102pub fn eval(expr: &Expr, env: &Env, config: &EvalConfig) -> Result<Literal, ExprError> {
109 eval_with_resolver(expr, env, config, &NoResolver)
110}
111
112pub fn eval_with_resolver(
123 expr: &Expr,
124 env: &Env,
125 config: &EvalConfig,
126 resolver: &dyn BuiltinResolver,
127) -> Result<Literal, ExprError> {
128 let mut state = EvalState::new(config, resolver);
129 eval_inner(expr, env, 0, &mut state)
130}
131
132fn eval_inner(
133 expr: &Expr,
134 env: &Env,
135 depth: u32,
136 state: &mut EvalState<'_>,
137) -> Result<Literal, ExprError> {
138 if depth > state.max_depth {
139 return Err(ExprError::DepthExceeded(state.max_depth));
140 }
141 state.tick()?;
142
143 match expr {
144 Expr::Var(name) => env.get(name).cloned().map_or_else(
148 || {
149 BuiltinOp::from_name(name)
150 .map(builtin_as_value)
151 .ok_or_else(|| ExprError::UnboundVariable(name.to_string()))
152 },
153 Ok,
154 ),
155
156 Expr::Lit(lit) => Ok(lit.clone()),
157
158 Expr::Lam(param, body) => {
159 Ok(Literal::Closure {
163 param: Arc::clone(param),
164 body: body.clone(),
165 env: env.clone(),
166 })
167 }
168
169 Expr::App(func, arg) => eval_app(func, arg, env, depth, state),
170
171 Expr::Record(fields) => {
172 let mut result = Vec::with_capacity(fields.len());
173 for (name, expr) in fields {
174 let val = eval_inner(expr, env, depth + 1, state)?;
175 result.push((Arc::clone(name), val));
176 }
177 Ok(Literal::Record(result))
178 }
179
180 Expr::List(items) => {
181 let mut result = Vec::with_capacity(items.len());
182 for item in items {
183 let val = eval_inner(item, env, depth + 1, state)?;
184 result.push(val);
185 }
186 if result.len() > state.max_list_len {
187 return Err(ExprError::ListLengthExceeded(result.len()));
188 }
189 Ok(Literal::List(result))
190 }
191
192 Expr::Field(expr, field) => {
193 let val = eval_inner(expr, env, depth + 1, state)?;
194 match &val {
195 Literal::Record(fields) => fields
196 .iter()
197 .find(|(k, _)| k == field)
198 .map(|(_, v)| v.clone())
199 .ok_or_else(|| ExprError::FieldNotFound(field.to_string())),
200 _ => Err(ExprError::TypeError {
201 expected: "record".into(),
202 got: val.type_name().into(),
203 }),
204 }
205 }
206
207 Expr::Index(expr, idx_expr) => eval_index(expr, idx_expr, env, depth, state),
208
209 Expr::Match { scrutinee, arms } => eval_match(scrutinee, arms, env, depth, state),
210
211 Expr::Let { name, value, body } => {
212 let val = eval_inner(value, env, depth + 1, state)?;
213 let new_env = env.extend(Arc::clone(name), val);
214 eval_inner(body, &new_env, depth + 1, state)
215 }
216
217 Expr::Builtin(op, args) if args.len() < op.arity() => {
218 eval_partial_builtin(*op, args, env, depth, state)
221 }
222
223 Expr::Builtin(op, args) => {
224 match op {
229 BuiltinOp::Map => eval_map(args, env, depth, state),
230 BuiltinOp::Filter => eval_filter(args, env, depth, state),
231 BuiltinOp::Fold => eval_fold(args, env, depth, state),
232 BuiltinOp::FlatMap => eval_flat_map(args, env, depth, state),
233 BuiltinOp::Range => eval_range(args, env, depth, state),
234 _ => {
235 let evaluated: Result<Vec<_>, _> = args
236 .iter()
237 .map(|a| eval_inner(a, env, depth + 1, state))
238 .collect();
239 let evaluated = evaluated?;
240 if state.resolver.handles(*op) {
241 state.resolver.apply(*op, &evaluated)
242 } else {
243 apply_builtin(*op, &evaluated)
244 }
245 }
246 }
247 }
248 }
249}
250
251fn builtin_param(index: usize) -> Arc<str> {
256 Arc::from(format!("__builtin_arg{index}"))
257}
258
259fn curried_builtin(op: BuiltinOp, mut surface: Vec<Expr>) -> Literal {
266 let arity = op.arity();
267 let first_missing = surface.len();
268 for index in first_missing..arity {
269 surface.push(Expr::Var(builtin_param(index)));
270 }
271
272 let mut body = Expr::Builtin(op, op.surface_args_to_expr_args(surface));
273 for index in (first_missing + 1..arity).rev() {
275 body = Expr::Lam(builtin_param(index), Box::new(body));
276 }
277 Literal::Closure {
278 param: builtin_param(first_missing),
279 body: Box::new(body),
280 env: Env::new(),
281 }
282}
283
284fn builtin_as_value(op: BuiltinOp) -> Literal {
287 curried_builtin(op, Vec::new())
288}
289
290fn eval_partial_builtin(
299 op: BuiltinOp,
300 args: &[Expr],
301 env: &Env,
302 depth: u32,
303 state: &mut EvalState<'_>,
304) -> Result<Literal, ExprError> {
305 let mut surface = Vec::with_capacity(op.arity());
306 for arg in args {
307 surface.push(Expr::Lit(eval_inner(arg, env, depth + 1, state)?));
308 }
309 Ok(curried_builtin(op, surface))
310}
311
312fn eval_range(
325 args: &[Expr],
326 env: &Env,
327 depth: u32,
328 state: &mut EvalState<'_>,
329) -> Result<Literal, ExprError> {
330 if args.len() != 2 {
331 return Err(ExprError::ArityMismatch {
332 op: "Range".into(),
333 expected: 2,
334 got: args.len(),
335 });
336 }
337 let start = match eval_inner(&args[0], env, depth + 1, state)? {
338 Literal::Int(n) => n,
339 other => {
340 return Err(ExprError::TypeError {
341 expected: "int".into(),
342 got: other.type_name().into(),
343 });
344 }
345 };
346 let stop = match eval_inner(&args[1], env, depth + 1, state)? {
347 Literal::Int(n) => n,
348 other => {
349 return Err(ExprError::TypeError {
350 expected: "int".into(),
351 got: other.type_name().into(),
352 });
353 }
354 };
355
356 if stop < start {
357 return Ok(Literal::List(Vec::new()));
358 }
359 let len = (i128::from(stop) - i128::from(start)) + 1;
363 let max = i128::try_from(state.max_list_len).unwrap_or(i128::MAX);
364 if len > max {
365 return Err(ExprError::ListLengthExceeded(
366 usize::try_from(len).unwrap_or(usize::MAX),
367 ));
368 }
369 let len_usize = usize::try_from(len).unwrap_or(usize::MAX);
370 let mut result = Vec::with_capacity(len_usize);
371 for n in start..=stop {
372 result.push(Literal::Int(n));
373 }
374 Ok(Literal::List(result))
375}
376
377fn eval_app(
383 func: &Expr,
384 arg: &Expr,
385 env: &Env,
386 depth: u32,
387 state: &mut EvalState<'_>,
388) -> Result<Literal, ExprError> {
389 let func_val = eval_inner(func, env, depth + 1, state)?;
391 let arg_val = eval_inner(arg, env, depth + 1, state)?;
393 apply_closure(&func_val, &arg_val, depth, state)
395}
396
397fn apply_closure(
403 func: &Literal,
404 arg: &Literal,
405 depth: u32,
406 state: &mut EvalState<'_>,
407) -> Result<Literal, ExprError> {
408 match func {
409 Literal::Closure { param, body, env } => {
410 let closure_env = env.extend(Arc::clone(param), arg.clone());
413 eval_inner(body, &closure_env, depth + 1, state)
414 }
415 _ => Err(ExprError::NotAFunction),
416 }
417}
418
419#[allow(
421 clippy::cast_possible_wrap,
422 clippy::cast_possible_truncation,
423 clippy::cast_sign_loss
424)]
425fn eval_index(
426 expr: &Expr,
427 idx_expr: &Expr,
428 env: &Env,
429 depth: u32,
430 state: &mut EvalState<'_>,
431) -> Result<Literal, ExprError> {
432 let val = eval_inner(expr, env, depth + 1, state)?;
433 let idx = eval_inner(idx_expr, env, depth + 1, state)?;
434 match (&val, &idx) {
435 (Literal::List(items), Literal::Int(i)) => {
436 let index = if *i < 0 {
437 (items.len() as i64 + i) as usize
438 } else {
439 *i as usize
440 };
441 items
442 .get(index)
443 .cloned()
444 .ok_or(ExprError::IndexOutOfBounds {
445 index: *i,
446 len: items.len(),
447 })
448 }
449 _ => Err(ExprError::TypeError {
450 expected: "(list, int)".into(),
451 got: format!("({}, {})", val.type_name(), idx.type_name()),
452 }),
453 }
454}
455
456fn eval_match(
458 scrutinee: &Expr,
459 arms: &[(Pattern, Expr)],
460 env: &Env,
461 depth: u32,
462 state: &mut EvalState<'_>,
463) -> Result<Literal, ExprError> {
464 let val = eval_inner(scrutinee, env, depth + 1, state)?;
465 for (pattern, body) in arms {
466 if let Some(bindings) = match_pattern(pattern, &val) {
467 let mut new_env = env.clone();
468 for (name, bound_val) in bindings {
469 new_env = new_env.extend(name, bound_val);
470 }
471 return eval_inner(body, &new_env, depth + 1, state);
472 }
473 }
474 Err(ExprError::NonExhaustiveMatch)
475}
476
477fn eval_map(
479 args: &[Expr],
480 env: &Env,
481 depth: u32,
482 state: &mut EvalState<'_>,
483) -> Result<Literal, ExprError> {
484 if args.len() != 2 {
485 return Err(ExprError::ArityMismatch {
486 op: "Map".into(),
487 expected: 2,
488 got: args.len(),
489 });
490 }
491 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
492 let items = match list_val {
493 Literal::List(items) => items,
494 other => {
495 return Err(ExprError::TypeError {
496 expected: "list".into(),
497 got: other.type_name().into(),
498 });
499 }
500 };
501
502 let func = &args[1];
503 let mut result = Vec::with_capacity(items.len());
504 for item in &items {
505 let val = apply_lambda(func, item, env, depth + 1, state)?;
506 result.push(val);
507 }
508 if result.len() > state.max_list_len {
509 return Err(ExprError::ListLengthExceeded(result.len()));
510 }
511 Ok(Literal::List(result))
512}
513
514fn eval_filter(
516 args: &[Expr],
517 env: &Env,
518 depth: u32,
519 state: &mut EvalState<'_>,
520) -> Result<Literal, ExprError> {
521 if args.len() != 2 {
522 return Err(ExprError::ArityMismatch {
523 op: "Filter".into(),
524 expected: 2,
525 got: args.len(),
526 });
527 }
528 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
529 let items = match list_val {
530 Literal::List(items) => items,
531 other => {
532 return Err(ExprError::TypeError {
533 expected: "list".into(),
534 got: other.type_name().into(),
535 });
536 }
537 };
538
539 let pred = &args[1];
540 let mut result = Vec::new();
541 for item in &items {
542 let keep = apply_lambda(pred, item, env, depth + 1, state)?;
543 match keep {
544 Literal::Bool(true) => result.push(item.clone()),
545 Literal::Bool(false) => {}
546 other => {
547 return Err(ExprError::TypeError {
548 expected: "bool".into(),
549 got: other.type_name().into(),
550 });
551 }
552 }
553 }
554 Ok(Literal::List(result))
555}
556
557fn eval_fold(
559 args: &[Expr],
560 env: &Env,
561 depth: u32,
562 state: &mut EvalState<'_>,
563) -> Result<Literal, ExprError> {
564 if args.len() != 3 {
565 return Err(ExprError::ArityMismatch {
566 op: "Fold".into(),
567 expected: 3,
568 got: args.len(),
569 });
570 }
571 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
572 let items = match list_val {
573 Literal::List(items) => items,
574 other => {
575 return Err(ExprError::TypeError {
576 expected: "list".into(),
577 got: other.type_name().into(),
578 });
579 }
580 };
581
582 let mut acc = eval_inner(&args[1], env, depth + 1, state)?;
583 let func = &args[2];
584
585 for item in &items {
586 acc = apply_lambda_2(func, &acc, item, env, depth + 1, state)?;
589 }
590 Ok(acc)
591}
592
593fn eval_flat_map(
595 args: &[Expr],
596 env: &Env,
597 depth: u32,
598 state: &mut EvalState<'_>,
599) -> Result<Literal, ExprError> {
600 if args.len() != 2 {
601 return Err(ExprError::ArityMismatch {
602 op: "FlatMap".into(),
603 expected: 2,
604 got: args.len(),
605 });
606 }
607 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
608 let items = match list_val {
609 Literal::List(items) => items,
610 other => {
611 return Err(ExprError::TypeError {
612 expected: "list".into(),
613 got: other.type_name().into(),
614 });
615 }
616 };
617
618 let func = &args[1];
619 let mut result = Vec::new();
620 for item in &items {
621 let sub_list = apply_lambda(func, item, env, depth + 1, state)?;
622 match sub_list {
623 Literal::List(sub_items) => result.extend(sub_items),
624 other => {
625 return Err(ExprError::TypeError {
626 expected: "list".into(),
627 got: other.type_name().into(),
628 });
629 }
630 }
631 if result.len() > state.max_list_len {
632 return Err(ExprError::ListLengthExceeded(result.len()));
633 }
634 }
635 Ok(Literal::List(result))
636}
637
638fn apply_lambda(
643 func_expr: &Expr,
644 arg: &Literal,
645 env: &Env,
646 depth: u32,
647 state: &mut EvalState<'_>,
648) -> Result<Literal, ExprError> {
649 let func_val = eval_inner(func_expr, env, depth + 1, state)?;
650 apply_closure(&func_val, arg, depth, state)
651}
652
653fn apply_lambda_2(
658 func_expr: &Expr,
659 arg1: &Literal,
660 arg2: &Literal,
661 env: &Env,
662 depth: u32,
663 state: &mut EvalState<'_>,
664) -> Result<Literal, ExprError> {
665 let func_val = eval_inner(func_expr, env, depth + 1, state)?;
666 let partial = apply_closure(&func_val, arg1, depth, state)?;
667 apply_closure(&partial, arg2, depth, state)
668}
669
670fn match_pattern(pattern: &Pattern, value: &Literal) -> Option<Vec<(Arc<str>, Literal)>> {
672 let mut bindings = Vec::new();
673 if match_inner(pattern, value, &mut bindings) {
674 Some(bindings)
675 } else {
676 None
677 }
678}
679
680fn match_inner(
681 pattern: &Pattern,
682 value: &Literal,
683 bindings: &mut Vec<(Arc<str>, Literal)>,
684) -> bool {
685 match pattern {
686 Pattern::Wildcard => true,
687 Pattern::Var(name) => {
688 bindings.push((Arc::clone(name), value.clone()));
689 true
690 }
691 Pattern::Lit(lit) => lit == value,
692 Pattern::Record(field_pats) => {
693 if let Literal::Record(fields) = value {
694 for (pat_name, pat) in field_pats {
695 let field_val = fields.iter().find(|(k, _)| k == pat_name);
696 match field_val {
697 Some((_, v)) => {
698 if !match_inner(pat, v, bindings) {
699 return false;
700 }
701 }
702 None => return false,
703 }
704 }
705 true
706 } else {
707 false
708 }
709 }
710 Pattern::List(item_pats) => {
711 if let Literal::List(items) = value {
712 if items.len() != item_pats.len() {
713 return false;
714 }
715 for (pat, val) in item_pats.iter().zip(items.iter()) {
716 if !match_inner(pat, val, bindings) {
717 return false;
718 }
719 }
720 true
721 } else {
722 false
723 }
724 }
725 Pattern::Constructor(tag, arg_pats) => {
726 if let Literal::Record(fields) = value {
728 let tag_field = fields.iter().find(|(k, _)| &**k == "$tag");
729 if let Some((_, Literal::Str(t))) = tag_field {
730 if t.as_str() != &**tag {
731 return false;
732 }
733 for (i, pat) in arg_pats.iter().enumerate() {
735 let key = format!("${i}");
736 let field_val = fields.iter().find(|(k, _)| k.as_ref() == key.as_str());
737 match field_val {
738 Some((_, v)) => {
739 if !match_inner(pat, v, bindings) {
740 return false;
741 }
742 }
743 None => return false,
744 }
745 }
746 true
747 } else {
748 false
749 }
750 } else {
751 false
752 }
753 }
754 }
755}
756
757#[cfg(test)]
758#[allow(clippy::unwrap_used)]
759mod tests {
760 use super::*;
761
762 fn default_config() -> EvalConfig {
763 EvalConfig::default()
764 }
765
766 #[test]
767 fn eval_literal() {
768 let result = eval(&Expr::Lit(Literal::Int(42)), &Env::new(), &default_config());
769 assert_eq!(result.unwrap(), Literal::Int(42));
770 }
771
772 #[test]
773 fn eval_variable() {
774 let env = Env::new().extend(Arc::from("x"), Literal::Int(10));
775 let result = eval(&Expr::var("x"), &env, &default_config());
776 assert_eq!(result.unwrap(), Literal::Int(10));
777 }
778
779 #[test]
780 fn eval_unbound_variable() {
781 let result = eval(&Expr::var("x"), &Env::new(), &default_config());
782 assert!(matches!(result, Err(ExprError::UnboundVariable(_))));
783 }
784
785 #[test]
786 fn eval_lambda_application() {
787 let expr = Expr::App(
789 Box::new(Expr::lam(
790 "x",
791 Expr::builtin(
792 BuiltinOp::Add,
793 vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
794 ),
795 )),
796 Box::new(Expr::Lit(Literal::Int(41))),
797 );
798 let result = eval(&expr, &Env::new(), &default_config());
799 assert_eq!(result.unwrap(), Literal::Int(42));
800 }
801
802 #[test]
803 fn eval_let_binding() {
804 let expr = Expr::let_in(
806 "x",
807 Expr::Lit(Literal::Int(10)),
808 Expr::builtin(
809 BuiltinOp::Add,
810 vec![Expr::var("x"), Expr::Lit(Literal::Int(5))],
811 ),
812 );
813 let result = eval(&expr, &Env::new(), &default_config());
814 assert_eq!(result.unwrap(), Literal::Int(15));
815 }
816
817 #[test]
818 fn eval_record_and_field() {
819 let expr = Expr::field(
820 Expr::Record(vec![
821 (Arc::from("name"), Expr::Lit(Literal::Str("alice".into()))),
822 (Arc::from("age"), Expr::Lit(Literal::Int(30))),
823 ]),
824 "age",
825 );
826 let result = eval(&expr, &Env::new(), &default_config());
827 assert_eq!(result.unwrap(), Literal::Int(30));
828 }
829
830 #[test]
831 fn eval_list_index() {
832 let expr = Expr::Index(
833 Box::new(Expr::List(vec![
834 Expr::Lit(Literal::Int(10)),
835 Expr::Lit(Literal::Int(20)),
836 Expr::Lit(Literal::Int(30)),
837 ])),
838 Box::new(Expr::Lit(Literal::Int(1))),
839 );
840 let result = eval(&expr, &Env::new(), &default_config());
841 assert_eq!(result.unwrap(), Literal::Int(20));
842 }
843
844 #[test]
845 fn eval_pattern_match() {
846 let expr = Expr::Match {
848 scrutinee: Box::new(Expr::Lit(Literal::Int(42))),
849 arms: vec![
850 (
851 Pattern::Lit(Literal::Int(0)),
852 Expr::Lit(Literal::Str("zero".into())),
853 ),
854 (
855 Pattern::Var(Arc::from("x")),
856 Expr::builtin(
857 BuiltinOp::Concat,
858 vec![
859 Expr::Lit(Literal::Str("num:".into())),
860 Expr::builtin(BuiltinOp::IntToStr, vec![Expr::var("x")]),
861 ],
862 ),
863 ),
864 ],
865 };
866 let result = eval(&expr, &Env::new(), &default_config());
867 assert_eq!(result.unwrap(), Literal::Str("num:42".into()));
868 }
869
870 #[test]
871 fn eval_map() {
872 let expr = Expr::builtin(
874 BuiltinOp::Map,
875 vec![
876 Expr::List(vec![
877 Expr::Lit(Literal::Int(1)),
878 Expr::Lit(Literal::Int(2)),
879 Expr::Lit(Literal::Int(3)),
880 ]),
881 Expr::lam(
882 "x",
883 Expr::builtin(
884 BuiltinOp::Mul,
885 vec![Expr::var("x"), Expr::Lit(Literal::Int(2))],
886 ),
887 ),
888 ],
889 );
890 let result = eval(&expr, &Env::new(), &default_config());
891 assert_eq!(
892 result.unwrap(),
893 Literal::List(vec![Literal::Int(2), Literal::Int(4), Literal::Int(6)])
894 );
895 }
896
897 #[test]
898 fn eval_filter() {
899 let expr = Expr::builtin(
901 BuiltinOp::Filter,
902 vec![
903 Expr::List(vec![
904 Expr::Lit(Literal::Int(1)),
905 Expr::Lit(Literal::Int(2)),
906 Expr::Lit(Literal::Int(3)),
907 Expr::Lit(Literal::Int(4)),
908 ]),
909 Expr::lam(
910 "x",
911 Expr::builtin(
912 BuiltinOp::Gt,
913 vec![Expr::var("x"), Expr::Lit(Literal::Int(2))],
914 ),
915 ),
916 ],
917 );
918 let result = eval(&expr, &Env::new(), &default_config());
919 assert_eq!(
920 result.unwrap(),
921 Literal::List(vec![Literal::Int(3), Literal::Int(4)])
922 );
923 }
924
925 #[test]
926 fn eval_fold() {
927 let expr = Expr::builtin(
929 BuiltinOp::Fold,
930 vec![
931 Expr::List(vec![
932 Expr::Lit(Literal::Int(1)),
933 Expr::Lit(Literal::Int(2)),
934 Expr::Lit(Literal::Int(3)),
935 ]),
936 Expr::Lit(Literal::Int(0)),
937 Expr::lam(
938 "acc",
939 Expr::lam(
940 "x",
941 Expr::builtin(BuiltinOp::Add, vec![Expr::var("acc"), Expr::var("x")]),
942 ),
943 ),
944 ],
945 );
946 let result = eval(&expr, &Env::new(), &default_config());
947 assert_eq!(result.unwrap(), Literal::Int(6));
948 }
949
950 #[test]
951 fn eval_step_limit() {
952 let config = EvalConfig {
954 max_steps: 5,
955 ..EvalConfig::default()
956 };
957 let items: Vec<_> = (1..=10).map(|i| Expr::Lit(Literal::Int(i))).collect();
959 let expr = Expr::builtin(
960 BuiltinOp::Map,
961 vec![
962 Expr::List(items),
963 Expr::lam(
964 "x",
965 Expr::builtin(
966 BuiltinOp::Add,
967 vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
968 ),
969 ),
970 ],
971 );
972 let result = eval(&expr, &Env::new(), &config);
973 assert!(matches!(result, Err(ExprError::StepLimitExceeded(_))));
974 }
975
976 #[test]
977 fn eval_merge_example() {
978 let merge_fn = Expr::lam(
981 "first",
982 Expr::lam(
983 "last",
984 Expr::builtin(
985 BuiltinOp::Concat,
986 vec![
987 Expr::var("first"),
988 Expr::builtin(
989 BuiltinOp::Concat,
990 vec![Expr::Lit(Literal::Str(" ".into())), Expr::var("last")],
991 ),
992 ],
993 ),
994 ),
995 );
996 let expr = Expr::App(
998 Box::new(Expr::App(
999 Box::new(merge_fn),
1000 Box::new(Expr::Lit(Literal::Str("Alice".into()))),
1001 )),
1002 Box::new(Expr::Lit(Literal::Str("Smith".into()))),
1003 );
1004 let result = eval(&expr, &Env::new(), &default_config());
1005 assert_eq!(result.unwrap(), Literal::Str("Alice Smith".into()));
1006 }
1007
1008 #[test]
1009 fn eval_split_example() {
1010 let split_fn = Expr::lam(
1014 "full",
1015 Expr::let_in(
1016 "parts",
1017 Expr::builtin(
1018 BuiltinOp::Split,
1019 vec![Expr::var("full"), Expr::Lit(Literal::Str(" ".into()))],
1020 ),
1021 Expr::Record(vec![
1022 (
1023 Arc::from("firstName"),
1024 Expr::builtin(BuiltinOp::Head, vec![Expr::var("parts")]),
1025 ),
1026 (
1027 Arc::from("lastName"),
1028 Expr::builtin(
1029 BuiltinOp::Join,
1030 vec![
1031 Expr::builtin(BuiltinOp::Tail, vec![Expr::var("parts")]),
1032 Expr::Lit(Literal::Str(" ".into())),
1033 ],
1034 ),
1035 ),
1036 ]),
1037 ),
1038 );
1039 let expr = Expr::App(
1040 Box::new(split_fn),
1041 Box::new(Expr::Lit(Literal::Str("Alice B Smith".into()))),
1042 );
1043 let result = eval(&expr, &Env::new(), &default_config());
1044 let expected = Literal::Record(vec![
1045 (Arc::from("firstName"), Literal::Str("Alice".into())),
1046 (Arc::from("lastName"), Literal::Str("B Smith".into())),
1047 ]);
1048 assert_eq!(result.unwrap(), expected);
1049 }
1050
1051 #[test]
1052 fn eval_coercion_example() {
1053 let coerce = Expr::lam(
1055 "v",
1056 Expr::builtin(BuiltinOp::StrToInt, vec![Expr::var("v")]),
1057 );
1058 let expr = Expr::App(
1059 Box::new(coerce),
1060 Box::new(Expr::Lit(Literal::Str("42".into()))),
1061 );
1062 let result = eval(&expr, &Env::new(), &default_config());
1063 assert_eq!(result.unwrap(), Literal::Int(42));
1064 }
1065}