Skip to content
Snippets Groups Projects
eval.rs 2.88 KiB
Newer Older
Jasper Gräflich's avatar
Jasper Gräflich committed
use std::collections::HashMap;

use crate::{Node, Value};

/// Evaluate given AST using given context.
fn eval_ctx(ast: Node, ctx: &mut HashMap<String, Value>) -> Value {
    match ast {
        crate::Node::Num(n) => n.into(),
        crate::Node::Unit => ().into(),
        crate::Node::Seq(nodes) => {
            let mut result = ().into();
            for node in nodes {
                result = eval_ctx(node, ctx);
Jasper Gräflich's avatar
Jasper Gräflich committed
            }
Jasper Gräflich's avatar
Jasper Gräflich committed
        }
        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.")
            });
            ().into()
        }
        crate::Node::Ident(ident) => ctx
            .get(&ident)
            .unwrap_or_else(|| panic!("Ident error: `{ident}` is not defined"))
            .clone(),
Jasper Gräflich's avatar
Jasper Gräflich committed
    }
}

/// Evaluate given AST using a fresh context.
pub fn eval(ast: Node) -> Value {
    eval_ctx(ast, &mut HashMap::new())
Jasper Gräflich's avatar
Jasper Gräflich committed
}

#[cfg(test)]
mod tests {
    use crate::parser::ast;
Jasper Gräflich's avatar
Jasper Gräflich committed

    use super::*;

    fn test(input: &str, result: impl Into<Value>) {
        assert_eq!(eval(ast(input)), result.into())
Jasper Gräflich's avatar
Jasper Gräflich committed
    }

    #[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; {}", ());
    }
}