#![allow(
    clippy::todo,
    clippy::cast_precision_loss,
    clippy::unnecessary_cast,
    trivial_casts
)]

use std::{borrow::Cow, collections::BTreeMap};

use camino::Utf8Path;
use chumsky::span::SimpleSpan;
use jiff::{SignedDuration, civil::DateTime};

use crate::filter::ast::{BinOp, Expr, UnOp};

macro_rules! bast {
    ($name:ident: f64 ) => {
        if $name { 1.0 } else { 0.0 }
    };
    ($name:ident: i64 ) => {
        if $name { 1 } else { 0 }
    };
}

#[derive(Debug, PartialEq)]
struct TypeError {
    kind: ErrorKind,
    pos: SimpleSpan,
}

#[derive(Debug, PartialEq)]
enum ErrorKind {}

#[derive(Debug, PartialEq)]
struct Array<'r> {
    scope: Vec<ScopeEntry<'r>>,
}

impl<'r> Array<'r> {
    fn push<E: Into<ScopeEntry<'r>>>(&mut self, entry: E) {
        self.scope.push(entry.into());
    }
}

impl<'r> From<Array<'r>> for ScopeEntry<'r> {
    fn from(value: Array<'r>) -> Self {
        Self::Array(value)
    }
}

#[derive(Debug, PartialEq)]
struct Object<'r> {
    scope: BTreeMap<&'r str, ScopeEntry<'r>>,
}

impl<'r> Object<'r> {
    fn insert<E: Into<ScopeEntry<'r>>>(&mut self, ident: &'r str, entry: E) {
        self.scope.insert(ident, entry.into());
    }
}

impl<'r> From<Object<'r>> for ScopeEntry<'r> {
    fn from(value: Object<'r>) -> Self {
        Self::Object(value)
    }
}

type Date = DateTime;
type Duration = SignedDuration;

#[derive(Debug, PartialEq)]
struct File<'r> {
    path: &'r Utf8Path,
    creation_time: DateTime,
    mondified_time: DateTime,
}

#[derive(Debug, PartialEq)]
enum Value<'r, 'vm> {
    Null,
    Bool(bool),
    Integer(i64),
    Decimal(f64),
    String(Cow<'r, str>),
    Array(&'vm Array<'r>),
    Object(&'vm Object<'r>),
    Date(Date),
    Duration(Duration),
    Link(),
    File(),
}

impl<'r, 'vm> Value<'r, 'vm> {
    #[must_use]
    const fn is_bool(&self) -> bool {
        matches!(self, Self::Bool(..))
    }

    #[must_use]
    const fn is_integer(&self) -> bool {
        matches!(self, Self::Integer(..))
    }

    #[must_use]
    const fn is_decimal(&self) -> bool {
        matches!(self, Self::Decimal(..))
    }

    fn as_string(&self) -> Result<Cow<'r, str>, TypeError> {
        if let Value::String(str) = self {
            return Ok(str.clone());
        }

        todo!()
    }

    fn into_bool(self) -> Result<Self, TypeError> {
        match self {
            Value::Null => todo!(),
            Value::Bool(value) => Ok(Value::Bool(value)),
            Value::Integer(value) => Ok(Value::Bool(value != 0)),
            Value::Decimal(value) => Ok(Value::Bool(value != 0.0)),
            _ => todo!(),
        }
    }

    fn into_integer(self) -> Result<Self, TypeError> {
        match self {
            Value::Null => todo!(),
            Value::Bool(value) => Ok(Value::Integer(bast!(value: i64))),
            Value::Integer(value) => Ok(Value::Integer(value)),
            Value::Decimal(value) => Ok(Value::Integer(value.round() as i64)),
            _ => todo!(),
        }
    }

    fn into_decimal(self) -> Result<Self, TypeError> {
        match self {
            Value::Null => todo!(),
            Value::Bool(value) => Ok(Value::Decimal(bast!(value: f64))),
            Value::Integer(value) => Ok(Value::Decimal(value as f64)),
            Value::Decimal(value) => Ok(Value::Decimal(value)),
            _ => todo!(),
        }
    }

    fn into_string(self) -> Result<Self, TypeError> {
        match self {
            Value::Null => todo!(),
            Value::Bool(value) => Ok(Value::Bool(value)),
            Value::Integer(value) => Ok(Value::String(Cow::from(format!("{value}")))),
            Value::Decimal(value) => Ok(Value::String(Cow::from(format!("{value}")))),
            Value::String(value) => Ok(Value::String(value)),
            _ => todo!(),
        }
    }
}

#[derive(Debug, PartialEq)]
enum ScopeEntry<'r> {
    Bool(bool),
    Integer(i64),
    Decimal(f64),
    String(&'r str),
    Array(Array<'r>),
    Object(Object<'r>),
    Function(),
}

struct Vm<'r> {
    scope: BTreeMap<&'r str, ScopeEntry<'r>>,
    fallback: Option<&'r str>,
}

impl<'r> Vm<'r> {
    const fn new() -> Self {
        Self {
            scope: BTreeMap::new(),
            fallback: None,
        }
    }

    fn add_scope<E: Into<ScopeEntry<'r>>>(&mut self, ident: &'r str, entry: E) {
        self.scope.insert(ident, entry.into());
    }

    const fn set_fallback(&mut self, ident: &'r str) {
        self.fallback = Some(ident);
    }

    fn eval<'vm, 'src: 'r>(&'vm self, expr: &Expr<'src>) -> Result<Value<'r, 'vm>, TypeError> {
        match expr {
            Expr::Ident(ident) => {
                let entry = match (self.scope.get(ident.0.as_ref()), self.fallback.as_ref()) {
                    (Some(entry), _) => Some(entry),
                    (None, Some(fallback)) => self.scope.get(fallback),
                    (None, None) => return Ok(Value::Null),
                };

                if let Some(entry) = entry {
                    return match entry {
                        ScopeEntry::Bool(value) => Ok(Value::Bool(*value)),
                        ScopeEntry::Integer(value) => Ok(Value::Integer(*value)),
                        ScopeEntry::Decimal(value) => Ok(Value::Decimal(*value)),
                        ScopeEntry::String(value) => Ok(Value::String(Cow::from(*value))),
                        ScopeEntry::Array(value) => Ok(Value::Array(value)),
                        ScopeEntry::Object(value) => Ok(Value::Object(value)),
                        ScopeEntry::Function() => todo!(),
                    };
                }

                Ok(Value::Null)
            }
            Expr::Bool(value) => Ok(Value::Bool(*value)),
            Expr::Integer(value) => Ok(Value::Integer(*value)),
            Expr::Decimal(value) => Ok(Value::Decimal(*value)),
            Expr::String(value) => Ok(Value::String(value.clone())),
            Expr::Function(ident, params) => {
                let ident = self.eval(&ident.inner)?;
                let params = params
                    .inner
                    .iter()
                    .map(|param| self.eval(&param.inner))
                    .collect::<Result<Vec<_>, _>>()?;

                todo!()
            }
            Expr::Method(object, ident, params) => {
                let object = self.eval(&object.inner)?;
                let params = params
                    .inner
                    .iter()
                    .map(|param| self.eval(&param.inner))
                    .collect::<Result<Vec<_>, _>>()?;

                match object {
                    Value::Array(object) => todo!(),
                    Value::Object(object) => todo!(),
                    _ => todo!(),
                }
            }
            Expr::Member(object, ident) => {
                let object = self.eval(&object.inner)?;

                match object {
                    Value::Array(object) => todo!(),
                    Value::Object(object) => todo!(),
                    _ => todo!(),
                }
            }
            Expr::Index(ident, index) => {
                let ident = self.eval(&ident.inner)?;

                match ident {
                    Value::Array(object) => todo!(),
                    Value::Object(object) => todo!(),
                    _ => todo!(),
                }
            }
            // TODO: this is the worst way to do this, use the cast methods on Value to clean this up
            Expr::BinOp(op, lhs, rhs) => {
                let lhs = self.eval(&lhs.inner)?;
                let rhs = self.eval(&rhs.inner)?;

                match op.inner {
                    BinOp::Add => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) + bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) + rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(bast!(lhs: f64) + rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs + bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs + rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs as f64 + rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Decimal(lhs + bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Decimal(lhs + rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs + rhs)),
                            _ => todo!(),
                        },
                        Value::String(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::String(Cow::from(format!("{lhs}{rhs}")))),
                            Value::Integer(rhs) => {
                                Ok(Value::String(Cow::from(format!("{lhs}{rhs}"))))
                            }
                            Value::Decimal(rhs) => {
                                Ok(Value::String(Cow::from(format!("{lhs}{rhs}"))))
                            }
                            Value::String(rhs) => {
                                Ok(Value::String(Cow::from(format!("{lhs}{rhs}"))))
                            }
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::Sub => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) - bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) - rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(bast!(lhs: f64) - rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs - bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs - rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs as f64 - rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Decimal(lhs - bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Decimal(lhs - rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs - rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::Mul => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) * bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) * rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(bast!(lhs: f64) * rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs * bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs * rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs as f64 * rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Decimal(lhs * bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Decimal(lhs * rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs * rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::Div => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) / bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) / rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(bast!(lhs: f64) / rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs / bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs / rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs as f64 / rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Decimal(lhs / bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Decimal(lhs / rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs / rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::Mod => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) % bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) % rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(bast!(lhs: f64) % rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs % bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs % rhs)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs as f64 % rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Decimal(lhs % bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Decimal(lhs % rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Decimal(lhs % rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::Equal => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs == rhs)),
                            Value::Integer(rhs) => Ok(Value::Bool(bast!(lhs: i64) == rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(bast!(lhs: f64) == rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs == bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs == rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs as f64 == rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs == bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs == rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs == rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::NotEqual => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs != rhs)),
                            Value::Integer(rhs) => Ok(Value::Bool(bast!(lhs: i64) != rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(bast!(lhs: f64) != rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs != bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs != rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs as f64 != rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs != bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs != rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs != rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::GreaterThan => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs > rhs)),
                            Value::Integer(rhs) => Ok(Value::Bool(bast!(lhs: i64) > rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(bast!(lhs: f64) > rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs > bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs > rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool((lhs as f64) > rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs > bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs > rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs > rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::LessThan => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs < rhs)),
                            Value::Integer(rhs) => Ok(Value::Bool(bast!(lhs: i64) < rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(bast!(lhs: f64) < rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs < bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs < rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool((lhs as f64) < rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs < bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs < rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs < rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::GreaterEqual => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs >= rhs)),
                            Value::Integer(rhs) => Ok(Value::Bool(bast!(lhs: i64) >= rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(bast!(lhs: f64) >= rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs >= bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs >= rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool((lhs as f64) >= rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs >= bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs >= rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs >= rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::LessEqual => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs <= rhs)),
                            Value::Integer(rhs) => Ok(Value::Bool(bast!(lhs: i64) <= rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(bast!(lhs: f64) <= rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs <= bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs <= rhs)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs as f64 <= rhs)),
                            _ => todo!(),
                        },
                        Value::Decimal(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Bool(lhs <= bast!(rhs: f64))),
                            Value::Integer(rhs) => Ok(Value::Bool(lhs <= rhs as f64)),
                            Value::Decimal(rhs) => Ok(Value::Bool(lhs <= rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::And => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) & bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) & rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs & bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs & rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                    BinOp::Or => match lhs {
                        Value::Bool(lhs) => match rhs {
                            Value::Bool(rhs) => {
                                Ok(Value::Integer(bast!(lhs: i64) | bast!(rhs: i64)))
                            }
                            Value::Integer(rhs) => Ok(Value::Integer(bast!(lhs: i64) | rhs)),
                            _ => todo!(),
                        },
                        Value::Integer(lhs) => match rhs {
                            Value::Bool(rhs) => Ok(Value::Integer(lhs | bast!(rhs: i64))),
                            Value::Integer(rhs) => Ok(Value::Integer(lhs | rhs)),
                            _ => todo!(),
                        },
                        _ => todo!(),
                    },
                }
            }
            Expr::UnOp(op, expr) => {
                let value = self.eval(&expr.inner)?;

                match op.inner {
                    UnOp::Not => match value {
                        Value::Bool(value) => Ok(Value::Bool(!value)),
                        Value::Integer(value) => Ok(Value::Bool(value == 0)),
                        Value::Decimal(value) => Ok(Value::Bool(value == 0.0)),
                        _ => todo!(),
                    },
                    UnOp::Plus => todo!(),
                    UnOp::Neg => match value {
                        Value::Bool(value) => Ok(Value::Integer(-bast!(value: i64))),
                        Value::Integer(value) => Ok(Value::Integer(-value)),
                        Value::Decimal(value) => Ok(Value::Decimal(-value)),
                        _ => todo!(),
                    },
                }
            }
            Expr::Group(expr) => self.eval(&expr.inner),
        }
    }
}

#[cfg(test)]
mod tests {
    use chumsky::span::WrappingSpan as _;

    use crate::filter::ast::Ident;

    use super::*;

    fn binop<'src>(op: BinOp, lhs: Expr<'src>, rhs: Expr<'src>) -> Expr<'src> {
        Expr::BinOp(
            SimpleSpan::from(0..1).make_wrapped(op),
            Box::new(SimpleSpan::from(0..1).make_wrapped(lhs)),
            Box::new(SimpleSpan::from(0..1).make_wrapped(rhs)),
        )
    }

    fn unop(op: UnOp, lhs: Expr<'_>) -> Expr<'_> {
        Expr::UnOp(
            SimpleSpan::from(0..1).make_wrapped(op),
            Box::new(SimpleSpan::from(0..1).make_wrapped(lhs)),
        )
    }

    #[test]
    fn bool() {
        let mut vm = Vm::new();

        vm.add_scope("bool_false", ScopeEntry::Bool(false));
        vm.add_scope("bool_true", ScopeEntry::Bool(true));

        assert_eq!(Ok(Value::Bool(false)), vm.eval(&Expr::Bool(false)));
        assert_eq!(Ok(Value::Bool(true)), vm.eval(&Expr::Bool(true)));

        assert_eq!(
            Ok(Value::Bool(true)),
            vm.eval(&unop(UnOp::Not, Expr::Bool(false)))
        );
        assert_eq!(
            Ok(Value::Bool(false)),
            vm.eval(&unop(UnOp::Not, Expr::Bool(true)))
        );

        assert_eq!(
            Ok(Value::Bool(false)),
            vm.eval(&Expr::Ident(Ident("bool_false".into())))
        );
        assert_eq!(
            Ok(Value::Bool(true)),
            vm.eval(&Expr::Ident(Ident("bool_true".into())))
        );

        assert_eq!(
            Ok(Value::Bool(true)),
            vm.eval(&unop(UnOp::Not, Expr::Ident(Ident("bool_false".into()))))
        );
        assert_eq!(
            Ok(Value::Bool(false)),
            vm.eval(&unop(UnOp::Not, Expr::Ident(Ident("bool_true".into()))))
        );
    }

    #[test]
    fn integer() {
        let mut vm = Vm::new();

        vm.add_scope("integer_zero", ScopeEntry::Integer(0));
        vm.add_scope("integer_one", ScopeEntry::Integer(1));

        assert_eq!(Ok(Value::Integer(0)), vm.eval(&Expr::Integer(0)));
        assert_eq!(Ok(Value::Integer(1)), vm.eval(&Expr::Integer(1)));

        assert_eq!(
            Ok(Value::Integer(2)),
            vm.eval(&binop(BinOp::Add, Expr::Integer(1), Expr::Integer(1)))
        );
        assert_eq!(
            Ok(Value::Integer(0)),
            vm.eval(&binop(BinOp::Sub, Expr::Integer(1), Expr::Integer(1)))
        );

        assert_eq!(
            Ok(Value::Integer(2)),
            vm.eval(&binop(
                BinOp::Add,
                Expr::Ident(Ident("integer_one".into())),
                Expr::Ident(Ident("integer_one".into()))
            ))
        );
        assert_eq!(
            Ok(Value::Integer(0)),
            vm.eval(&binop(
                BinOp::Sub,
                Expr::Ident(Ident("integer_one".into())),
                Expr::Ident(Ident("integer_one".into()))
            ))
        );
    }

    #[test]
    fn decimal() {
        let mut vm = Vm::new();

        vm.add_scope("decimal_zero", ScopeEntry::Decimal(0.0));
        vm.add_scope("decimal_one", ScopeEntry::Decimal(1.0));

        assert_eq!(Ok(Value::Decimal(0.0)), vm.eval(&Expr::Decimal(0.0)));
        assert_eq!(Ok(Value::Decimal(1.0)), vm.eval(&Expr::Decimal(1.0)));

        assert_eq!(
            Ok(Value::Decimal(2.0)),
            vm.eval(&binop(BinOp::Add, Expr::Decimal(1.0), Expr::Decimal(1.0)))
        );
        assert_eq!(
            Ok(Value::Decimal(0.0)),
            vm.eval(&binop(BinOp::Sub, Expr::Decimal(1.0), Expr::Decimal(1.0)))
        );

        assert_eq!(
            Ok(Value::Decimal(2.0)),
            vm.eval(&binop(
                BinOp::Add,
                Expr::Ident(Ident("decimal_one".into())),
                Expr::Ident(Ident("decimal_one".into()))
            ))
        );
        assert_eq!(
            Ok(Value::Decimal(0.0)),
            vm.eval(&binop(
                BinOp::Sub,
                Expr::Ident(Ident("decimal_one".into())),
                Expr::Ident(Ident("decimal_one".into()))
            ))
        );
    }
}
