use crate::compile::{
    block::{Compile, CompileContext, CompileValue},
    error::CompileError,
    syntax::Expression,
};

mod binding;
mod flow;
mod function;

impl Compile for Expression {
    fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
        match self {
            Self::Nil => Ok(CompileValue::Nil),
            Self::Break => flow::compile_break(cx),
            Self::Continue => flow::compile_continue(cx),
            Self::Call(call) => call.compile(cx),
            Self::If(if_) => if_.compile(cx),
            Self::Cond(cond) => cond.compile(cx),
            Self::Let(let_) => let_.compile(cx),
            Self::Setq(setq) => setq.compile(cx),
            Self::While(while_) => while_.compile(cx),
            Self::Loop(loop_) => loop_.compile(cx),
            Self::Defun(defun) => defun.compile(cx),
            Self::Lambda(lambda) => lambda.compile(cx),
            Self::Progn(progn) => progn.compile(cx),
            Self::Defmacro(defmacro) => defmacro.compile(cx),
            Self::Identifier(identifier) => Ok(CompileValue::Identifier(identifier.clone())),
            &Self::IntegerLiteral(value) => Ok(CompileValue::Integer(value)),
            &Self::BooleanLiteral(value) => Ok(CompileValue::Boolean(value)),
            Self::StringLiteral(value) => Ok(CompileValue::String(value.clone())),
            Self::Vector(vector) => Ok(CompileValue::Value(vector.clone().into())),
            Self::Quote(value) => Ok(CompileValue::Quote(value.clone())),
            Self::SyntaxError(_) => unreachable!(),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::compile::{
        block::{CompileValue, test_compile},
        syntax::Expression,
    };

    #[test]
    fn test_nil_compile() {
        let e = Expression::Nil;
        let (cx, v) = test_compile(&e).unwrap();
        assert_eq!(cx.current, 0);
        assert!(cx.function_blocks[0].instructions.is_empty());
        assert_eq!(v, CompileValue::Nil);
    }

    #[test]
    fn test_integer_literal_compile() {
        let e = Expression::IntegerLiteral(1.into());
        let (cx, v) = test_compile(&e).unwrap();
        assert_eq!(cx.current, 0);
        assert!(cx.function_blocks[0].instructions.is_empty());
        assert_eq!(v, CompileValue::Integer(1.into()));
    }
}
