use std::collections::HashMap; use crate::{Error, Node, Result, Value}; /// Evaluate given AST using given context. fn eval_ctx(ast: Node, ctx: &mut HashMap<String, Value>) -> Result<Value> { Ok(match ast { crate::Node::Num(n) => Value::Num(n), crate::Node::Unit => Value::Unit, crate::Node::Seq(nodes) => { let mut result = Value::Unit; for node in nodes { result = eval_ctx(node, ctx)?; } result } crate::Node::Let { ident, expr, .. } => { let value = eval_ctx(*expr, ctx)?; // Shadowing is caught by the typechecker, no check here. ctx.insert(ident.clone(), value); Value::Unit } crate::Node::Ident(ident) => *ctx.get(&ident).ok_or(Error::Eval(format!( "Ident error: `{ident}` is not defined" )))?, }) } /// Evaluate given AST using a fresh context. /// /// # Errors /// /// Returns error if evaluation fails. pub fn eval(ast: Node) -> Result<Value> { eval_ctx(ast, &mut HashMap::new()) } #[cfg(test)] mod tests { use crate::parser::ast; use super::*; fn test(input: &str, result: impl Into<Value>) { assert_eq!( eval(ast(input).expect("Parse in test unsuccessful")) .expect("Eval in test unsuccessful"), result.into() ) } #[test] fn unit() { test("{}", ()); } #[test] fn unit_with_space() { test("{ }", ()); } #[test] fn block_of_unit_with_space() { test("{ { } }", ()); } #[test] fn num() { test("42", 42); } #[test] fn block_of_num() { test("{42}", 42); } #[test] fn block_of_num_whitespace() { test("{ 42 }", 42); } #[test] fn seq_of_unit() { test("{}; { }", ()); } #[test] fn block_of_seq_of_unit() { test("{{ }; {}}", ()); } #[test] fn seq_of_num() { test("1; 2", 2); } #[test] #[should_panic] fn empty_seq_snd_fail() { test("1;", ()); } #[test] #[should_panic] fn empty_seq_fst_fail() { test(";1", ()); } #[test] #[should_panic] fn empty_seq_fail() { test(";", ()); } #[test] fn block_of_seq_of_num() { test("{1; 2}", 2); } #[test] fn block_of_seq_of_mixed() { test("{{ }; 2}", 2); } #[test] fn block_of_seq_len_3() { test("{1; 3; 2}", 2); } #[test] fn seq_len_3() { test("1; 3; 2", 2); } #[test] fn block_of_seq_len_3_mixed() { test("{1; { } ; 2}", 2); } #[test] fn seq_len_3_mixed() { test("{} ; 3; { }", ()); } #[test] fn let_char() { test("let x = {}", ()); } #[test] fn let_long() { test("let foo = 42", ()); } #[test] fn let_ident_with_number() { test("let foo1 = 42", ()); } #[test] fn let_then_unit() { test("let x = 10; {}", ()); } #[test] #[should_panic] fn bad_lookup() { test("x", ()) } #[test] #[should_panic] fn bad_lookup_long() { test("foo", ()) } #[test] #[should_panic] fn bad_lookup_in_seq() { test("let foo = 42; bar", ()) } #[test] fn lookup() { test("let foo = 42; foo", 42) } #[test] #[should_panic] fn shadowing() { test("let foo = {}; let foo = 42; foo", ()) } #[test] fn program_of_block_unit() { test("{let foo = {}; foo}", ()) } #[test] fn program_of_block_number() { test("{let foo = 42; foo}", 42) } #[test] fn number_with_leading_zeros() { test("001", 1) } #[test] fn two_blocks() { test("{1; 2}; {3; 4}", 4) } #[test] fn nested_let_blocks() { test("let foo = {let bar = 4; {let baz = 5}; bar}; foo", 4) } }