Skip to content
Snippets Groups Projects
Commit 710cbba2 authored by Jasper Gräflich's avatar Jasper Gräflich
Browse files

Use thiserror

parent 9ad187ce
No related branches found
No related tags found
No related merge requests found
......@@ -62,6 +62,7 @@ version = "0.1.0"
dependencies = [
"pest",
"pest_derive",
"thiserror",
]
[[package]]
......
......@@ -6,3 +6,4 @@ edition = "2021"
[dependencies]
pest = "2.7.4"
pest_derive = "2.7.4"
thiserror = "1.0.49"
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)
}
}
#![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>;
......@@ -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(())
}
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]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment