From 710cbba29507ef2702734075525dc218376cff42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jasper=20Gr=C3=A4flich?= <graeflich@cyberagentur.de>
Date: Wed, 11 Oct 2023 14:22:16 +0200
Subject: [PATCH] Use thiserror

---
 use-based-refs/interpreter/Cargo.lock    |  1 +
 use-based-refs/interpreter/Cargo.toml    |  1 +
 use-based-refs/interpreter/src/eval.rs   | 61 +++++++++++++++++++-----
 use-based-refs/interpreter/src/lib.rs    | 17 +++++++
 use-based-refs/interpreter/src/main.rs   |  8 ++--
 use-based-refs/interpreter/src/parser.rs | 57 +++++++++++++---------
 6 files changed, 107 insertions(+), 38 deletions(-)

diff --git a/use-based-refs/interpreter/Cargo.lock b/use-based-refs/interpreter/Cargo.lock
index f42dc39..112f911 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 8906867..0e35438 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 2de13ed..f2b16d3 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 4db9fcf..8058430 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 53938a5..d549f5f 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 8431110..967238f 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]
-- 
GitLab