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
36struct EvalState {
38 steps_remaining: u64,
39 max_steps: u64,
40 max_depth: u32,
41 max_list_len: usize,
42}
43
44impl EvalState {
45 const fn new(config: &EvalConfig) -> Self {
46 Self {
47 steps_remaining: config.max_steps,
48 max_steps: config.max_steps,
49 max_depth: config.max_depth,
50 max_list_len: config.max_list_len,
51 }
52 }
53
54 const fn tick(&mut self) -> Result<(), ExprError> {
55 if self.steps_remaining == 0 {
56 return Err(ExprError::StepLimitExceeded(self.max_steps));
57 }
58 self.steps_remaining -= 1;
59 Ok(())
60 }
61}
62
63pub fn eval(expr: &Expr, env: &Env, config: &EvalConfig) -> Result<Literal, ExprError> {
70 let mut state = EvalState::new(config);
71 eval_inner(expr, env, 0, &mut state)
72}
73
74fn eval_inner(
75 expr: &Expr,
76 env: &Env,
77 depth: u32,
78 state: &mut EvalState,
79) -> Result<Literal, ExprError> {
80 if depth > state.max_depth {
81 return Err(ExprError::DepthExceeded(state.max_depth));
82 }
83 state.tick()?;
84
85 match expr {
86 Expr::Var(name) => env
87 .get(name)
88 .cloned()
89 .ok_or_else(|| ExprError::UnboundVariable(name.to_string())),
90
91 Expr::Lit(lit) => Ok(lit.clone()),
92
93 Expr::Lam(param, body) => {
94 let captured: Vec<(Arc<str>, Literal)> = env
97 .iter()
98 .map(|(k, v)| (Arc::clone(k), v.clone()))
99 .collect();
100 Ok(Literal::Closure {
101 param: Arc::clone(param),
102 body: body.clone(),
103 env: captured,
104 })
105 }
106
107 Expr::App(func, arg) => eval_app(func, arg, env, depth, state),
108
109 Expr::Record(fields) => {
110 let mut result = Vec::with_capacity(fields.len());
111 for (name, expr) in fields {
112 let val = eval_inner(expr, env, depth + 1, state)?;
113 result.push((Arc::clone(name), val));
114 }
115 Ok(Literal::Record(result))
116 }
117
118 Expr::List(items) => {
119 let mut result = Vec::with_capacity(items.len());
120 for item in items {
121 let val = eval_inner(item, env, depth + 1, state)?;
122 result.push(val);
123 }
124 if result.len() > state.max_list_len {
125 return Err(ExprError::ListLengthExceeded(result.len()));
126 }
127 Ok(Literal::List(result))
128 }
129
130 Expr::Field(expr, field) => {
131 let val = eval_inner(expr, env, depth + 1, state)?;
132 match &val {
133 Literal::Record(fields) => fields
134 .iter()
135 .find(|(k, _)| k == field)
136 .map(|(_, v)| v.clone())
137 .ok_or_else(|| ExprError::FieldNotFound(field.to_string())),
138 _ => Err(ExprError::TypeError {
139 expected: "record".into(),
140 got: val.type_name().into(),
141 }),
142 }
143 }
144
145 Expr::Index(expr, idx_expr) => eval_index(expr, idx_expr, env, depth, state),
146
147 Expr::Match { scrutinee, arms } => eval_match(scrutinee, arms, env, depth, state),
148
149 Expr::Let { name, value, body } => {
150 let val = eval_inner(value, env, depth + 1, state)?;
151 let new_env = env.extend(Arc::clone(name), val);
152 eval_inner(body, &new_env, depth + 1, state)
153 }
154
155 Expr::Builtin(op, args) => {
156 match op {
161 BuiltinOp::Map => eval_map(args, env, depth, state),
162 BuiltinOp::Filter => eval_filter(args, env, depth, state),
163 BuiltinOp::Fold => eval_fold(args, env, depth, state),
164 BuiltinOp::FlatMap => eval_flat_map(args, env, depth, state),
165 BuiltinOp::Range => eval_range(args, env, depth, state),
166 _ => {
167 let evaluated: Result<Vec<_>, _> = args
168 .iter()
169 .map(|a| eval_inner(a, env, depth + 1, state))
170 .collect();
171 apply_builtin(*op, &evaluated?)
172 }
173 }
174 }
175 }
176}
177
178fn eval_range(
191 args: &[Expr],
192 env: &Env,
193 depth: u32,
194 state: &mut EvalState,
195) -> Result<Literal, ExprError> {
196 if args.len() != 2 {
197 return Err(ExprError::ArityMismatch {
198 op: "Range".into(),
199 expected: 2,
200 got: args.len(),
201 });
202 }
203 let start = match eval_inner(&args[0], env, depth + 1, state)? {
204 Literal::Int(n) => n,
205 other => {
206 return Err(ExprError::TypeError {
207 expected: "int".into(),
208 got: other.type_name().into(),
209 });
210 }
211 };
212 let stop = match eval_inner(&args[1], env, depth + 1, state)? {
213 Literal::Int(n) => n,
214 other => {
215 return Err(ExprError::TypeError {
216 expected: "int".into(),
217 got: other.type_name().into(),
218 });
219 }
220 };
221
222 if stop < start {
223 return Ok(Literal::List(Vec::new()));
224 }
225 let len = (i128::from(stop) - i128::from(start)) + 1;
229 let max = i128::try_from(state.max_list_len).unwrap_or(i128::MAX);
230 if len > max {
231 return Err(ExprError::ListLengthExceeded(
232 usize::try_from(len).unwrap_or(usize::MAX),
233 ));
234 }
235 let len_usize = usize::try_from(len).unwrap_or(usize::MAX);
236 let mut result = Vec::with_capacity(len_usize);
237 for n in start..=stop {
238 result.push(Literal::Int(n));
239 }
240 Ok(Literal::List(result))
241}
242
243fn eval_app(
249 func: &Expr,
250 arg: &Expr,
251 env: &Env,
252 depth: u32,
253 state: &mut EvalState,
254) -> Result<Literal, ExprError> {
255 let func_val = eval_inner(func, env, depth + 1, state)?;
257 let arg_val = eval_inner(arg, env, depth + 1, state)?;
259 apply_closure(&func_val, &arg_val, depth, state)
261}
262
263fn apply_closure(
269 func: &Literal,
270 arg: &Literal,
271 depth: u32,
272 state: &mut EvalState,
273) -> Result<Literal, ExprError> {
274 match func {
275 Literal::Closure { param, body, env } => {
276 let mut closure_env: Env = env
278 .iter()
279 .map(|(k, v)| (Arc::clone(k), v.clone()))
280 .collect();
281 closure_env = closure_env.extend(Arc::clone(param), arg.clone());
283 eval_inner(body, &closure_env, depth + 1, state)
285 }
286 _ => Err(ExprError::NotAFunction),
287 }
288}
289
290#[allow(
292 clippy::cast_possible_wrap,
293 clippy::cast_possible_truncation,
294 clippy::cast_sign_loss
295)]
296fn eval_index(
297 expr: &Expr,
298 idx_expr: &Expr,
299 env: &Env,
300 depth: u32,
301 state: &mut EvalState,
302) -> Result<Literal, ExprError> {
303 let val = eval_inner(expr, env, depth + 1, state)?;
304 let idx = eval_inner(idx_expr, env, depth + 1, state)?;
305 match (&val, &idx) {
306 (Literal::List(items), Literal::Int(i)) => {
307 let index = if *i < 0 {
308 (items.len() as i64 + i) as usize
309 } else {
310 *i as usize
311 };
312 items
313 .get(index)
314 .cloned()
315 .ok_or(ExprError::IndexOutOfBounds {
316 index: *i,
317 len: items.len(),
318 })
319 }
320 _ => Err(ExprError::TypeError {
321 expected: "(list, int)".into(),
322 got: format!("({}, {})", val.type_name(), idx.type_name()),
323 }),
324 }
325}
326
327fn eval_match(
329 scrutinee: &Expr,
330 arms: &[(Pattern, Expr)],
331 env: &Env,
332 depth: u32,
333 state: &mut EvalState,
334) -> Result<Literal, ExprError> {
335 let val = eval_inner(scrutinee, env, depth + 1, state)?;
336 for (pattern, body) in arms {
337 if let Some(bindings) = match_pattern(pattern, &val) {
338 let mut new_env = env.clone();
339 for (name, bound_val) in bindings {
340 new_env = new_env.extend(name, bound_val);
341 }
342 return eval_inner(body, &new_env, depth + 1, state);
343 }
344 }
345 Err(ExprError::NonExhaustiveMatch)
346}
347
348fn eval_map(
350 args: &[Expr],
351 env: &Env,
352 depth: u32,
353 state: &mut EvalState,
354) -> Result<Literal, ExprError> {
355 if args.len() != 2 {
356 return Err(ExprError::ArityMismatch {
357 op: "Map".into(),
358 expected: 2,
359 got: args.len(),
360 });
361 }
362 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
363 let items = match list_val {
364 Literal::List(items) => items,
365 other => {
366 return Err(ExprError::TypeError {
367 expected: "list".into(),
368 got: other.type_name().into(),
369 });
370 }
371 };
372
373 let func = &args[1];
374 let mut result = Vec::with_capacity(items.len());
375 for item in &items {
376 let val = apply_lambda(func, item, env, depth + 1, state)?;
377 result.push(val);
378 }
379 if result.len() > state.max_list_len {
380 return Err(ExprError::ListLengthExceeded(result.len()));
381 }
382 Ok(Literal::List(result))
383}
384
385fn eval_filter(
387 args: &[Expr],
388 env: &Env,
389 depth: u32,
390 state: &mut EvalState,
391) -> Result<Literal, ExprError> {
392 if args.len() != 2 {
393 return Err(ExprError::ArityMismatch {
394 op: "Filter".into(),
395 expected: 2,
396 got: args.len(),
397 });
398 }
399 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
400 let items = match list_val {
401 Literal::List(items) => items,
402 other => {
403 return Err(ExprError::TypeError {
404 expected: "list".into(),
405 got: other.type_name().into(),
406 });
407 }
408 };
409
410 let pred = &args[1];
411 let mut result = Vec::new();
412 for item in &items {
413 let keep = apply_lambda(pred, item, env, depth + 1, state)?;
414 match keep {
415 Literal::Bool(true) => result.push(item.clone()),
416 Literal::Bool(false) => {}
417 other => {
418 return Err(ExprError::TypeError {
419 expected: "bool".into(),
420 got: other.type_name().into(),
421 });
422 }
423 }
424 }
425 Ok(Literal::List(result))
426}
427
428fn eval_fold(
430 args: &[Expr],
431 env: &Env,
432 depth: u32,
433 state: &mut EvalState,
434) -> Result<Literal, ExprError> {
435 if args.len() != 3 {
436 return Err(ExprError::ArityMismatch {
437 op: "Fold".into(),
438 expected: 3,
439 got: args.len(),
440 });
441 }
442 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
443 let items = match list_val {
444 Literal::List(items) => items,
445 other => {
446 return Err(ExprError::TypeError {
447 expected: "list".into(),
448 got: other.type_name().into(),
449 });
450 }
451 };
452
453 let mut acc = eval_inner(&args[1], env, depth + 1, state)?;
454 let func = &args[2];
455
456 for item in &items {
457 acc = apply_lambda_2(func, &acc, item, env, depth + 1, state)?;
460 }
461 Ok(acc)
462}
463
464fn eval_flat_map(
466 args: &[Expr],
467 env: &Env,
468 depth: u32,
469 state: &mut EvalState,
470) -> Result<Literal, ExprError> {
471 if args.len() != 2 {
472 return Err(ExprError::ArityMismatch {
473 op: "FlatMap".into(),
474 expected: 2,
475 got: args.len(),
476 });
477 }
478 let list_val = eval_inner(&args[0], env, depth + 1, state)?;
479 let items = match list_val {
480 Literal::List(items) => items,
481 other => {
482 return Err(ExprError::TypeError {
483 expected: "list".into(),
484 got: other.type_name().into(),
485 });
486 }
487 };
488
489 let func = &args[1];
490 let mut result = Vec::new();
491 for item in &items {
492 let sub_list = apply_lambda(func, item, env, depth + 1, state)?;
493 match sub_list {
494 Literal::List(sub_items) => result.extend(sub_items),
495 other => {
496 return Err(ExprError::TypeError {
497 expected: "list".into(),
498 got: other.type_name().into(),
499 });
500 }
501 }
502 if result.len() > state.max_list_len {
503 return Err(ExprError::ListLengthExceeded(result.len()));
504 }
505 }
506 Ok(Literal::List(result))
507}
508
509fn apply_lambda(
514 func_expr: &Expr,
515 arg: &Literal,
516 env: &Env,
517 depth: u32,
518 state: &mut EvalState,
519) -> Result<Literal, ExprError> {
520 let func_val = eval_inner(func_expr, env, depth + 1, state)?;
521 apply_closure(&func_val, arg, depth, state)
522}
523
524fn apply_lambda_2(
529 func_expr: &Expr,
530 arg1: &Literal,
531 arg2: &Literal,
532 env: &Env,
533 depth: u32,
534 state: &mut EvalState,
535) -> Result<Literal, ExprError> {
536 let func_val = eval_inner(func_expr, env, depth + 1, state)?;
537 let partial = apply_closure(&func_val, arg1, depth, state)?;
538 apply_closure(&partial, arg2, depth, state)
539}
540
541fn match_pattern(pattern: &Pattern, value: &Literal) -> Option<Vec<(Arc<str>, Literal)>> {
543 let mut bindings = Vec::new();
544 if match_inner(pattern, value, &mut bindings) {
545 Some(bindings)
546 } else {
547 None
548 }
549}
550
551fn match_inner(
552 pattern: &Pattern,
553 value: &Literal,
554 bindings: &mut Vec<(Arc<str>, Literal)>,
555) -> bool {
556 match pattern {
557 Pattern::Wildcard => true,
558 Pattern::Var(name) => {
559 bindings.push((Arc::clone(name), value.clone()));
560 true
561 }
562 Pattern::Lit(lit) => lit == value,
563 Pattern::Record(field_pats) => {
564 if let Literal::Record(fields) = value {
565 for (pat_name, pat) in field_pats {
566 let field_val = fields.iter().find(|(k, _)| k == pat_name);
567 match field_val {
568 Some((_, v)) => {
569 if !match_inner(pat, v, bindings) {
570 return false;
571 }
572 }
573 None => return false,
574 }
575 }
576 true
577 } else {
578 false
579 }
580 }
581 Pattern::List(item_pats) => {
582 if let Literal::List(items) = value {
583 if items.len() != item_pats.len() {
584 return false;
585 }
586 for (pat, val) in item_pats.iter().zip(items.iter()) {
587 if !match_inner(pat, val, bindings) {
588 return false;
589 }
590 }
591 true
592 } else {
593 false
594 }
595 }
596 Pattern::Constructor(tag, arg_pats) => {
597 if let Literal::Record(fields) = value {
599 let tag_field = fields.iter().find(|(k, _)| &**k == "$tag");
600 if let Some((_, Literal::Str(t))) = tag_field {
601 if t.as_str() != &**tag {
602 return false;
603 }
604 for (i, pat) in arg_pats.iter().enumerate() {
606 let key = format!("${i}");
607 let field_val = fields.iter().find(|(k, _)| k.as_ref() == key.as_str());
608 match field_val {
609 Some((_, v)) => {
610 if !match_inner(pat, v, bindings) {
611 return false;
612 }
613 }
614 None => return false,
615 }
616 }
617 true
618 } else {
619 false
620 }
621 } else {
622 false
623 }
624 }
625 }
626}
627
628#[cfg(test)]
629#[allow(clippy::unwrap_used)]
630mod tests {
631 use super::*;
632
633 fn default_config() -> EvalConfig {
634 EvalConfig::default()
635 }
636
637 #[test]
638 fn eval_literal() {
639 let result = eval(&Expr::Lit(Literal::Int(42)), &Env::new(), &default_config());
640 assert_eq!(result.unwrap(), Literal::Int(42));
641 }
642
643 #[test]
644 fn eval_variable() {
645 let env = Env::new().extend(Arc::from("x"), Literal::Int(10));
646 let result = eval(&Expr::var("x"), &env, &default_config());
647 assert_eq!(result.unwrap(), Literal::Int(10));
648 }
649
650 #[test]
651 fn eval_unbound_variable() {
652 let result = eval(&Expr::var("x"), &Env::new(), &default_config());
653 assert!(matches!(result, Err(ExprError::UnboundVariable(_))));
654 }
655
656 #[test]
657 fn eval_lambda_application() {
658 let expr = Expr::App(
660 Box::new(Expr::lam(
661 "x",
662 Expr::builtin(
663 BuiltinOp::Add,
664 vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
665 ),
666 )),
667 Box::new(Expr::Lit(Literal::Int(41))),
668 );
669 let result = eval(&expr, &Env::new(), &default_config());
670 assert_eq!(result.unwrap(), Literal::Int(42));
671 }
672
673 #[test]
674 fn eval_let_binding() {
675 let expr = Expr::let_in(
677 "x",
678 Expr::Lit(Literal::Int(10)),
679 Expr::builtin(
680 BuiltinOp::Add,
681 vec![Expr::var("x"), Expr::Lit(Literal::Int(5))],
682 ),
683 );
684 let result = eval(&expr, &Env::new(), &default_config());
685 assert_eq!(result.unwrap(), Literal::Int(15));
686 }
687
688 #[test]
689 fn eval_record_and_field() {
690 let expr = Expr::field(
691 Expr::Record(vec![
692 (Arc::from("name"), Expr::Lit(Literal::Str("alice".into()))),
693 (Arc::from("age"), Expr::Lit(Literal::Int(30))),
694 ]),
695 "age",
696 );
697 let result = eval(&expr, &Env::new(), &default_config());
698 assert_eq!(result.unwrap(), Literal::Int(30));
699 }
700
701 #[test]
702 fn eval_list_index() {
703 let expr = Expr::Index(
704 Box::new(Expr::List(vec![
705 Expr::Lit(Literal::Int(10)),
706 Expr::Lit(Literal::Int(20)),
707 Expr::Lit(Literal::Int(30)),
708 ])),
709 Box::new(Expr::Lit(Literal::Int(1))),
710 );
711 let result = eval(&expr, &Env::new(), &default_config());
712 assert_eq!(result.unwrap(), Literal::Int(20));
713 }
714
715 #[test]
716 fn eval_pattern_match() {
717 let expr = Expr::Match {
719 scrutinee: Box::new(Expr::Lit(Literal::Int(42))),
720 arms: vec![
721 (
722 Pattern::Lit(Literal::Int(0)),
723 Expr::Lit(Literal::Str("zero".into())),
724 ),
725 (
726 Pattern::Var(Arc::from("x")),
727 Expr::builtin(
728 BuiltinOp::Concat,
729 vec![
730 Expr::Lit(Literal::Str("num:".into())),
731 Expr::builtin(BuiltinOp::IntToStr, vec![Expr::var("x")]),
732 ],
733 ),
734 ),
735 ],
736 };
737 let result = eval(&expr, &Env::new(), &default_config());
738 assert_eq!(result.unwrap(), Literal::Str("num:42".into()));
739 }
740
741 #[test]
742 fn eval_map() {
743 let expr = Expr::builtin(
745 BuiltinOp::Map,
746 vec![
747 Expr::List(vec![
748 Expr::Lit(Literal::Int(1)),
749 Expr::Lit(Literal::Int(2)),
750 Expr::Lit(Literal::Int(3)),
751 ]),
752 Expr::lam(
753 "x",
754 Expr::builtin(
755 BuiltinOp::Mul,
756 vec![Expr::var("x"), Expr::Lit(Literal::Int(2))],
757 ),
758 ),
759 ],
760 );
761 let result = eval(&expr, &Env::new(), &default_config());
762 assert_eq!(
763 result.unwrap(),
764 Literal::List(vec![Literal::Int(2), Literal::Int(4), Literal::Int(6)])
765 );
766 }
767
768 #[test]
769 fn eval_filter() {
770 let expr = Expr::builtin(
772 BuiltinOp::Filter,
773 vec![
774 Expr::List(vec![
775 Expr::Lit(Literal::Int(1)),
776 Expr::Lit(Literal::Int(2)),
777 Expr::Lit(Literal::Int(3)),
778 Expr::Lit(Literal::Int(4)),
779 ]),
780 Expr::lam(
781 "x",
782 Expr::builtin(
783 BuiltinOp::Gt,
784 vec![Expr::var("x"), Expr::Lit(Literal::Int(2))],
785 ),
786 ),
787 ],
788 );
789 let result = eval(&expr, &Env::new(), &default_config());
790 assert_eq!(
791 result.unwrap(),
792 Literal::List(vec![Literal::Int(3), Literal::Int(4)])
793 );
794 }
795
796 #[test]
797 fn eval_fold() {
798 let expr = Expr::builtin(
800 BuiltinOp::Fold,
801 vec![
802 Expr::List(vec![
803 Expr::Lit(Literal::Int(1)),
804 Expr::Lit(Literal::Int(2)),
805 Expr::Lit(Literal::Int(3)),
806 ]),
807 Expr::Lit(Literal::Int(0)),
808 Expr::lam(
809 "acc",
810 Expr::lam(
811 "x",
812 Expr::builtin(BuiltinOp::Add, vec![Expr::var("acc"), Expr::var("x")]),
813 ),
814 ),
815 ],
816 );
817 let result = eval(&expr, &Env::new(), &default_config());
818 assert_eq!(result.unwrap(), Literal::Int(6));
819 }
820
821 #[test]
822 fn eval_step_limit() {
823 let config = EvalConfig {
825 max_steps: 5,
826 ..EvalConfig::default()
827 };
828 let items: Vec<_> = (1..=10).map(|i| Expr::Lit(Literal::Int(i))).collect();
830 let expr = Expr::builtin(
831 BuiltinOp::Map,
832 vec![
833 Expr::List(items),
834 Expr::lam(
835 "x",
836 Expr::builtin(
837 BuiltinOp::Add,
838 vec![Expr::var("x"), Expr::Lit(Literal::Int(1))],
839 ),
840 ),
841 ],
842 );
843 let result = eval(&expr, &Env::new(), &config);
844 assert!(matches!(result, Err(ExprError::StepLimitExceeded(_))));
845 }
846
847 #[test]
848 fn eval_merge_example() {
849 let merge_fn = Expr::lam(
852 "first",
853 Expr::lam(
854 "last",
855 Expr::builtin(
856 BuiltinOp::Concat,
857 vec![
858 Expr::var("first"),
859 Expr::builtin(
860 BuiltinOp::Concat,
861 vec![Expr::Lit(Literal::Str(" ".into())), Expr::var("last")],
862 ),
863 ],
864 ),
865 ),
866 );
867 let expr = Expr::App(
869 Box::new(Expr::App(
870 Box::new(merge_fn),
871 Box::new(Expr::Lit(Literal::Str("Alice".into()))),
872 )),
873 Box::new(Expr::Lit(Literal::Str("Smith".into()))),
874 );
875 let result = eval(&expr, &Env::new(), &default_config());
876 assert_eq!(result.unwrap(), Literal::Str("Alice Smith".into()));
877 }
878
879 #[test]
880 fn eval_split_example() {
881 let split_fn = Expr::lam(
885 "full",
886 Expr::let_in(
887 "parts",
888 Expr::builtin(
889 BuiltinOp::Split,
890 vec![Expr::var("full"), Expr::Lit(Literal::Str(" ".into()))],
891 ),
892 Expr::Record(vec![
893 (
894 Arc::from("firstName"),
895 Expr::builtin(BuiltinOp::Head, vec![Expr::var("parts")]),
896 ),
897 (
898 Arc::from("lastName"),
899 Expr::builtin(
900 BuiltinOp::Join,
901 vec![
902 Expr::builtin(BuiltinOp::Tail, vec![Expr::var("parts")]),
903 Expr::Lit(Literal::Str(" ".into())),
904 ],
905 ),
906 ),
907 ]),
908 ),
909 );
910 let expr = Expr::App(
911 Box::new(split_fn),
912 Box::new(Expr::Lit(Literal::Str("Alice B Smith".into()))),
913 );
914 let result = eval(&expr, &Env::new(), &default_config());
915 let expected = Literal::Record(vec![
916 (Arc::from("firstName"), Literal::Str("Alice".into())),
917 (Arc::from("lastName"), Literal::Str("B Smith".into())),
918 ]);
919 assert_eq!(result.unwrap(), expected);
920 }
921
922 #[test]
923 fn eval_coercion_example() {
924 let coerce = Expr::lam(
926 "v",
927 Expr::builtin(BuiltinOp::StrToInt, vec![Expr::var("v")]),
928 );
929 let expr = Expr::App(
930 Box::new(coerce),
931 Box::new(Expr::Lit(Literal::Str("42".into()))),
932 );
933 let result = eval(&expr, &Env::new(), &default_config());
934 assert_eq!(result.unwrap(), Literal::Int(42));
935 }
936}