diff --git a/use-based-refs/interpreter/Cargo.lock b/use-based-refs/interpreter/Cargo.lock index f42dc399fa970c661f88240ea4aab9a9ee0c677d..112f9112c81a0ccc7a58b95d0367f726545c118f 100644 --- a/use-based-refs/interpreter/Cargo.lock +++ b/use-based-refs/interpreter/Cargo.lock @@ -62,6 +62,7 @@ version = "0.1.0" dependencies = [ "pest", "pest_derive", + "thiserror", ] [[package]] diff --git a/use-based-refs/interpreter/Cargo.toml b/use-based-refs/interpreter/Cargo.toml index 8906867fa53cb6ef7bee4a573a2e7fcc2f71939e..0e35438bf4c6fa8d4431190e7c0a0e8bab67c119 100644 --- a/use-based-refs/interpreter/Cargo.toml +++ b/use-based-refs/interpreter/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] pest = "2.7.4" pest_derive = "2.7.4" +thiserror = "1.0.49" diff --git a/use-based-refs/interpreter/src/eval.rs b/use-based-refs/interpreter/src/eval.rs index 2de13ed856118bf46da52aeb87493f18a4e50954..f2b16d381d6c72e896d422440286cec3277588c1 100644 --- a/use-based-refs/interpreter/src/eval.rs +++ b/use-based-refs/interpreter/src/eval.rs @@ -1,35 +1,43 @@ use std::collections::HashMap; -use crate::{Node, Value}; +use crate::{Error, Node, Result, Value}; /// Evaluate given AST using given context. -fn eval_ctx(ast: Node, ctx: &mut HashMap<String, Value>) -> Value { - match ast { +fn eval_ctx(ast: Node, ctx: &mut HashMap<String, Value>) -> Result<Value> { + Ok(match ast { crate::Node::Num(n) => n.into(), crate::Node::Unit => ().into(), crate::Node::Seq(nodes) => { - let mut result = ().into(); + let mut result: Value = ().into(); for node in nodes { - result = eval_ctx(node, ctx); + result = eval_ctx(node, ctx)?; } result } crate::Node::Let { ident, expr } => { - let value = eval_ctx(*expr, ctx); - ctx.try_insert(ident.clone(), value).unwrap_or_else(|_| { - panic!("Let error: `{ident}` shadows previously defined variable.") - }); + let value = eval_ctx(*expr, ctx)?; + println!("{ctx:?}"); + ctx.try_insert(ident.clone(), value) + .or(Err(Error::Eval(format!( + "`{ident}` shadows previously defined variable." + ))))?; ().into() } crate::Node::Ident(ident) => ctx .get(&ident) - .unwrap_or_else(|| panic!("Ident error: `{ident}` is not defined")) + .ok_or(Error::Eval(format!( + "Ident error: `{ident}` is not defined" + )))? .clone(), - } + }) } /// Evaluate given AST using a fresh context. -pub fn eval(ast: Node) -> Value { +/// +/// # Errors +/// +/// Returns error if evaluation fails. +pub fn eval(ast: Node) -> Result<Value> { eval_ctx(ast, &mut HashMap::new()) } @@ -40,7 +48,11 @@ mod tests { use super::*; fn test(input: &str, result: impl Into<Value>) { - assert_eq!(eval(ast(input)), result.into()) + assert_eq!( + eval(ast(input).expect("Parse in test unsuccessful")) + .expect("Eval in test unsuccessful"), + result.into() + ) } #[test] @@ -155,4 +167,27 @@ mod tests { 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) + } } diff --git a/use-based-refs/interpreter/src/lib.rs b/use-based-refs/interpreter/src/lib.rs index 4db9fcfbfe7b31f0003197a204970d44c31b1bd2..805843063f8a002f01ee5b83ec0619ef10d7a95b 100644 --- a/use-based-refs/interpreter/src/lib.rs +++ b/use-based-refs/interpreter/src/lib.rs @@ -1,6 +1,8 @@ #![feature(map_try_insert)] #![warn(clippy::pedantic)] +use thiserror::Error; + mod eval; mod parser; @@ -8,6 +10,7 @@ pub use eval::eval; pub use parser::ast; /// A Node of the `λ_UR` AST, simplified from the `UrParser`. +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Node { Num(u32), Unit, @@ -16,6 +19,7 @@ pub enum Node { Ident(String), } +/// `λ_UR` value, result of a computation. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Value { Num(u32), @@ -42,3 +46,16 @@ impl std::fmt::Display for Value { } } } + +/// Generalized interpreter error. +#[derive(Error, Debug)] +pub enum Error { + #[error("Parse unsuccessful: {0}")] + Parse(#[from] Box<pest::error::Error<parser::Rule>>), + #[error("Evaluation error: {0}")] + Eval(String), + #[error("Unknown error: {0}")] + Unknown(String), +} + +pub type Result<T> = std::result::Result<T, Error>; diff --git a/use-based-refs/interpreter/src/main.rs b/use-based-refs/interpreter/src/main.rs index 53938a5e30f1f0cbc2667112c416ce1116691f07..d549f5fbe5886b45b385b12508743e7b42a009bf 100644 --- a/use-based-refs/interpreter/src/main.rs +++ b/use-based-refs/interpreter/src/main.rs @@ -2,8 +2,10 @@ use interpreter::{ast, eval}; -fn main() { - let program = "let foo = 2; 2"; - let val = eval(ast(program)); +fn main() -> interpreter::Result<()> { + let program = "let foo = 2; foo"; + let val = eval(ast(program)?)?; println!("{val}"); + + Ok(()) } diff --git a/use-based-refs/interpreter/src/parser.rs b/use-based-refs/interpreter/src/parser.rs index 84311100ae1c9bc9ab79e56af38a0f7c1efa0c10..967238f6101a900e02e73664fb671240a9076077 100644 --- a/use-based-refs/interpreter/src/parser.rs +++ b/use-based-refs/interpreter/src/parser.rs @@ -1,24 +1,27 @@ use pest::{iterators::Pair, Parser}; use pest_derive::Parser; -use crate::Node; +use crate::{Error, Node, Result}; #[derive(Parser)] #[grammar = "grammar.pest"] pub(crate) struct UrParser; -// TODO: real errors -fn tokenize(input: &str) -> Result<Pair<'_, Rule>, String> { +fn tokenize(input: &str) -> Result<Pair<'_, Rule>> { match UrParser::parse(Rule::Block, input) { - Ok(mut pairs) => pairs.next().ok_or("Error: No tokens.".into()), - Err(e) => Err(format!("{e}")), + Ok(mut pairs) => pairs.next().ok_or(Error::Unknown("Empty Input".to_owned())), + Err(e) => Err(Error::Parse(Box::new(e))), } } -// TODO: Fill -pub fn ast(input: &str) -> Node { - fn ast_pair(pair: Pair<Rule>) -> Node { - match pair.as_rule() { +/// Gemerate AST from a `λ_UR` program. +/// +/// # Errors +/// +/// Returns an error if parse is not successful. +pub fn ast(input: &str) -> Result<Node> { + fn ast_pair(pair: Pair<Rule>) -> Result<Node> { + Ok(match pair.as_rule() { Rule::EOI | Rule::WHITESPACE | Rule::COMMENT @@ -26,26 +29,36 @@ pub fn ast(input: &str) -> Node { | Rule::Expr | Rule::Value | Rule::Char => unreachable!("Rule cannot produce token"), - Rule::Seq => Node::Seq(pair.into_inner().map(|p| ast_pair(p)).collect()), - Rule::Unit => Node::Unit, - Rule::Num => Node::Num(pair.as_str().parse().unwrap()), - Rule::Ident => { - let ident = pair.into_inner().next().unwrap().to_string(); - Node::Ident(ident) + Rule::Seq => { + let nodes: Result<_> = pair.into_inner().map(|p| ast_pair(p)).collect(); + Node::Seq(nodes?) } + Rule::Unit => Node::Unit, + Rule::Num => Node::Num( + pair.as_str() + .parse() + .expect("Sequence of digits can always be parsed"), + ), + Rule::Ident => Node::Ident(pair.as_str().to_string()), Rule::Let => { let mut pair = pair.into_inner(); // `Let` consists of identifier and expression. - let ident = pair.next().unwrap().to_string(); - let expr = pair.next().unwrap(); + let ident = pair + .next() + .expect("Let expression always contains an identifier") + .as_str() + .to_string(); + let expr = pair + .next() + .expect("Lex expression always contains a RHS expression"); Node::Let { ident, - expr: Box::new(ast_pair(expr)), + expr: Box::new(ast_pair(expr)?), } } - } + }) } - let pair = tokenize(input).unwrap(); + let pair = tokenize(input)?; ast_pair(pair) } @@ -56,13 +69,13 @@ mod test { #[test] #[should_panic] fn empty_should_not_parse() { - _ = ast(""); + _ = ast("").expect("Empty Input should fail"); } #[test] #[should_panic] fn whitespace_should_not_parse() { - _ = ast(" "); + _ = ast(" ").expect("Empty Input should fail"); } #[test]