use std::collections::HashMap; use pest::iterators::Pair; use crate::{ parser::{tokenize, Rule}, Value, }; fn eval_ctx(input: &str, ctx: &mut HashMap<String, Value>) -> Result<Value, String> { fn eval_node(pair: Pair<Rule>, ctx: &mut HashMap<String, Value>) -> Value { match pair.as_rule() { Rule::Num => Value::Num(pair.as_str().parse().unwrap()), Rule::Unit => Value::Unit, Rule::Seq => { let it = pair.into_inner(); let mut result = Value::Unit; for pair in it { result = eval_node(pair, ctx); } result } Rule::Ident => { let ident = pair .into_inner() .next() .expect("No identifier found") .to_string(); ctx.get(&ident) .unwrap_or_else(|| panic!("Ident error: `{ident}` not defined.")) .clone() } Rule::Let => { let mut pair = pair.into_inner(); let key = pair.next().expect("Let expressions has no LHS").to_string(); let value = eval_node(pair.next().expect("Let expression has no RHS"), ctx); // TODO: Remove shadowing ctx.try_insert(key.clone(), value).unwrap_or_else(|_| { panic!("Let error: `{key}` shadows previously defined variable.") }); ().into() } // The rest of the rules are silent, producing no tokens: Rule::EOI | Rule::WHITESPACE | Rule::COMMENT | Rule::Block | Rule::Expr | Rule::Value | Rule::Char => unreachable!(), } } let ast = tokenize(input).map_err(|e| e.to_string())?; Ok(eval_node(ast, ctx)) } /// Evaluate program from input string /// /// # Errors /// /// Errors if parse is not successful. pub fn eval(input: &str) -> Result<Value, String> { eval_ctx(input, &mut HashMap::new()) } #[cfg(test)] mod tests { use pest::Parser; use crate::parser::UrParser; use super::*; fn test(input: &str, result: impl Into<Value>) { assert_eq!(eval(input).unwrap(), result.into()) } #[test] #[should_panic] fn empty_should_not_parse() { _ = eval("").unwrap(); } #[test] #[should_panic] fn whitespace_should_not_parse() { _ = eval(" ").unwrap(); } #[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] #[should_panic] fn ident_cannot_start_with_number() { UrParser::parse(Rule::Let, "let 1foo = 10").unwrap(); } #[test] fn let_then_unit() { test("let x = 10; {}", ()); } }