From 68294f44faa3d52674122623d8b52596c3e1fea1 Mon Sep 17 00:00:00 2001
From: Kubat <maelle.martin@proton.me>
Date: Thu, 1 May 2025 14:46:03 +0200
Subject: [PATCH] [AST] Separate the BinOp into three categories to ease the
 implementation of binop evaluation

---
 grimoire/src/ast/expr.rs     | 162 ++++++++++++++------------
 grimoire/src/ast/mod.rs      |   1 +
 grimoire/src/ast/operator.rs | 159 ++++++++++++++++++++++++++
 grimoire/src/parser/expr.rs  |  67 ++++++-----
 grimoire/src/state.rs        | 213 +++++++++++++++++++++++++++--------
 grimoire/src/types.rs        |   4 +
 6 files changed, 466 insertions(+), 140 deletions(-)
 create mode 100644 grimoire/src/ast/operator.rs

diff --git a/grimoire/src/ast/expr.rs b/grimoire/src/ast/expr.rs
index ff04c04..7a6f74c 100644
--- a/grimoire/src/ast/expr.rs
+++ b/grimoire/src/ast/expr.rs
@@ -1,5 +1,9 @@
+use crate::{
+    ast::{AstSpan, operator},
+    error::Error,
+    types::Type,
+};
 use derive_more::Display;
-use crate::ast::AstSpan;
 
 #[derive(Debug, Clone, PartialEq, Display)]
 pub enum Const {
@@ -22,6 +26,92 @@ pub enum Const {
     Ident(String),
 }
 
+impl Const {
+    pub fn is_null(&self) -> bool {
+        match self {
+            Const::Int(x) => *x == 0,
+            Const::Flt(x) => x.abs() <= f32::EPSILON,
+            Const::Str(x) => x.is_empty() || *x == "false" || *x == "null",
+            Const::Bool(x) => *x,
+            Const::Ident(x) => *x == "Null",
+        }
+    }
+
+    pub fn unwrap_identifier(self) -> String {
+        match self {
+            Const::Ident(x) => x,
+            x => panic!("can't unwrap {x} as i32"),
+        }
+    }
+
+    pub fn unwrap_boolean(self) -> bool {
+        !self.is_null()
+    }
+
+    pub fn unwrap_integer(self) -> i32 {
+        match self {
+            Const::Int(x) => x,
+            Const::Flt(x) => x as i32,
+            Const::Bool(x) => x as i32,
+            x => panic!("can't unwrap {x} as i32"),
+        }
+    }
+
+    pub fn unwrap_float(self) -> f32 {
+        match self {
+            Const::Int(x) => x as f32,
+            Const::Flt(x) => x,
+            Const::Bool(true) => 1.,
+            Const::Bool(false) => 0.,
+            x => panic!("can't unwrap {x} as i32"),
+        }
+    }
+
+    pub fn cast_into(self, ty: Type) -> Result<Self, Error> {
+        if ty == Type::Boolean {
+            return Ok(Self::Bool(self.unwrap_boolean()));
+        }
+        match self {
+            Self::Int(x) => match ty {
+                Type::Float => Ok(Self::Flt(x as f32)),
+                Type::String => Ok(Self::Str(x.to_string())),
+                Type::Number | Type::Integer => Ok(Self::Int(x)),
+                ty => Err(Error::CantCast(Self::Int(x), ty)),
+            },
+
+            Self::Flt(x) => match ty {
+                Type::Number | Type::Float => Ok(Self::Flt(x)),
+                Type::String => Ok(Self::Str(x.to_string())),
+                Type::Integer => Ok(Self::Int(x as i32)),
+                ty => Err(Error::CantCast(Self::Flt(x), ty)),
+            },
+
+            Self::Bool(x) => match ty {
+                Type::Float => Ok(Self::Flt(if x { 0. } else { 1. })),
+                Type::String => Ok(Self::Str(x.to_string())),
+                Type::Number | Type::Integer => Ok(Self::Int(x as i32)),
+                ty => Err(Error::CantCast(Self::Bool(x), ty)),
+            },
+
+            Self::Str(x) => match ty {
+                Type::String => Ok(Self::Str(x)),
+                Type::Number => str::parse::<f32>(&x)
+                    .map(Self::Flt)
+                    .or_else(|_| str::parse::<i32>(&x).map(Self::Int))
+                    .map_err(|_| Error::CantCast(Self::Str(x), Type::Number)),
+                Type::Float => str::parse::<f32>(&x).map(Self::Flt).map_err(Into::into),
+                Type::Integer => str::parse::<i32>(&x).map(Self::Int).map_err(Into::into),
+                _ => unreachable!(),
+            },
+
+            Self::Ident(x) => match ty {
+                Type::Identifier | Type::Actor | Type::Printer => Ok(Self::Ident(x)),
+                ty => Err(Error::CantCast(Self::Ident(x), ty)),
+            },
+        }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Display)]
 pub enum VarOrConst {
     /// A [Const].
@@ -56,73 +146,7 @@ impl VarOrConst {
 
 #[derive(Debug)]
 pub enum Expression {
-    Binary(AstSpan, Box<Expression>, BinOp, Box<Expression>),
-    Unary(AstSpan, UnOp, Box<Expression>),
+    Binary(AstSpan, Box<Expression>, operator::Binary, Box<Expression>),
+    Unary(AstSpan, operator::Unary, Box<Expression>),
     Leaf(AstSpan, VarOrConst),
 }
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum UnOp {
-    Not,
-    Neg,
-}
-
-impl UnOp {
-    pub fn as_str(&self) -> &'static str {
-        match self {
-            UnOp::Not => "!",
-            UnOp::Neg => "-",
-        }
-    }
-}
-
-impl AsRef<str> for UnOp {
-    fn as_ref(&self) -> &str {
-        self.as_str()
-    }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum BinOp {
-    Add,
-    Sub,
-    Mul,
-    Div,
-    Mod,
-    CompGE,
-    CompGT,
-    CompLT,
-    CompLE,
-    Or,
-    And,
-    Xor,
-    CompEQ,
-    CompNEQ,
-}
-
-impl BinOp {
-    pub fn as_str(&self) -> &'static str {
-        match self {
-            BinOp::Add => "+",
-            BinOp::Sub => "-",
-            BinOp::Mul => "*",
-            BinOp::Div => "/",
-            BinOp::Mod => "%",
-            BinOp::CompGE => ">=",
-            BinOp::CompGT => ">",
-            BinOp::CompLT => "<=",
-            BinOp::CompLE => "<",
-            BinOp::Or => "|",
-            BinOp::And => "&",
-            BinOp::Xor => "^",
-            BinOp::CompEQ => "=",
-            BinOp::CompNEQ => "!=",
-        }
-    }
-}
-
-impl AsRef<str> for BinOp {
-    fn as_ref(&self) -> &str {
-        self.as_str()
-    }
-}
diff --git a/grimoire/src/ast/mod.rs b/grimoire/src/ast/mod.rs
index 19b1f1b..2fa8585 100644
--- a/grimoire/src/ast/mod.rs
+++ b/grimoire/src/ast/mod.rs
@@ -1,4 +1,5 @@
 pub mod expr;
+pub mod operator;
 mod span;
 
 pub use span::Span as AstSpan;
diff --git a/grimoire/src/ast/operator.rs b/grimoire/src/ast/operator.rs
new file mode 100644
index 0000000..5537fff
--- /dev/null
+++ b/grimoire/src/ast/operator.rs
@@ -0,0 +1,159 @@
+use derive_more::Display;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
+#[display("{op}", op = self.as_str())]
+pub enum Unary {
+    Not,
+    Neg,
+}
+
+impl Unary {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Unary::Not => "!",
+            Unary::Neg => "-",
+        }
+    }
+}
+
+impl AsRef<str> for Unary {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
+#[display("{op}", op = self.as_str())]
+pub enum Numeric {
+    Add,
+    Sub,
+    Mul,
+    Div,
+    Mod,
+    CompGE,
+    CompGT,
+    CompLT,
+    CompLE,
+}
+
+impl Numeric {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Numeric::Add => "+",
+            Numeric::Sub => "-",
+            Numeric::Mul => "*",
+            Numeric::Div => "/",
+            Numeric::Mod => "%",
+            Numeric::CompGE => ">=",
+            Numeric::CompGT => ">",
+            Numeric::CompLT => "<=",
+            Numeric::CompLE => "<",
+        }
+    }
+}
+
+impl AsRef<str> for Numeric {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
+#[display("{op}", op = self.as_str())]
+pub enum Logic {
+    Or,
+    And,
+    Xor,
+}
+
+impl Logic {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Logic::Or => "|",
+            Logic::And => "&",
+            Logic::Xor => "^",
+        }
+    }
+}
+
+impl AsRef<str> for Logic {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
+#[display("{op}", op = self.as_str())]
+pub enum Equal {
+    CompEQ,
+    CompNEQ,
+}
+
+impl Equal {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Equal::CompEQ => "=",
+            Equal::CompNEQ => "!=",
+        }
+    }
+
+    pub fn apply(self, eq_res: bool) -> bool {
+        (self.check_equals() && eq_res) || (self.check_not_equals() && !eq_res)
+    }
+
+    pub fn check_equals(self) -> bool {
+        !self.check_not_equals()
+    }
+
+    pub fn check_not_equals(self) -> bool {
+        matches!(self, Equal::CompNEQ)
+    }
+}
+
+impl AsRef<str> for Equal {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
+#[display("{op}", op = self.as_str())]
+pub enum Binary {
+    Numeric(Numeric),
+    Logic(Logic),
+    Equal(Equal),
+}
+
+impl Binary {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            Binary::Numeric(op) => op.as_str(),
+            Binary::Logic(op) => op.as_str(),
+            Binary::Equal(op) => op.as_str(),
+        }
+    }
+}
+
+impl AsRef<str> for Binary {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl From<Numeric> for Binary {
+    fn from(value: Numeric) -> Self {
+        Binary::Numeric(value)
+    }
+}
+
+impl From<Logic> for Binary {
+    fn from(value: Logic) -> Self {
+        Binary::Logic(value)
+    }
+}
+
+impl From<Equal> for Binary {
+    fn from(value: Equal) -> Self {
+        Binary::Equal(value)
+    }
+}
diff --git a/grimoire/src/parser/expr.rs b/grimoire/src/parser/expr.rs
index 340f2be..d4849ad 100644
--- a/grimoire/src/parser/expr.rs
+++ b/grimoire/src/parser/expr.rs
@@ -1,5 +1,5 @@
 use crate::{
-    ast::{AstSpan, expr::*},
+    ast::{AstSpan, expr::*, operator},
     parser::*,
 };
 use nom::{
@@ -32,8 +32,8 @@ fn var_or_constant(s: Span) -> ParserResult<Expression> {
 fn leaf(s: Span) -> ParserResult<Expression> {
     // Note: the order is important!
     alt((
-        unop(UnOp::Not, expression),
-        unop(UnOp::Neg, expression),
+        unop(operator::Unary::Not, expression),
+        unop(operator::Unary::Neg, expression),
         utils::parse_paren(expression),
         var_or_constant,
     ))
@@ -41,7 +41,7 @@ fn leaf(s: Span) -> ParserResult<Expression> {
 }
 
 fn unop<'a>(
-    op: UnOp,
+    op: operator::Unary,
     next: impl Parser<Span<'a>, Output = Expression, Error = ParserError>,
 ) -> impl Parser<Span<'a>, Output = Expression, Error = ParserError> {
     utils::map_with_locaiton(
@@ -51,17 +51,29 @@ fn unop<'a>(
 }
 
 fn binop<'a>(
-    op: BinOp,
+    op: impl Into<operator::Binary>,
     next: impl Parser<Span<'a>, Output = Expression, Error = ParserError>,
-) -> impl Parser<Span<'a>, Output = (AstSpan, BinOp, Expression), Error = ParserError> {
-    preceded(
-        multispace0,
-        utils::with_location((map(tag(op.as_str()), move |_| op), next))
-            .map(|(ast_span, (op, expr))| (ast_span, op, expr)),
-    )
+) -> impl Parser<Span<'a>, Output = (AstSpan, operator::Binary, Expression), Error = ParserError> {
+    // Used to mitigate the impact of monomorphisation.
+    fn aux<'a>(
+        op: operator::Binary,
+        next: impl Parser<Span<'a>, Output = Expression, Error = ParserError>,
+    ) -> impl Parser<Span<'a>, Output = (AstSpan, operator::Binary, Expression), Error = ParserError>
+    {
+        preceded(
+            multispace0,
+            utils::with_location((map(tag(op.as_str()), move |_| op), next))
+                .map(|(ast_span, (op, expr))| (ast_span, op, expr)),
+        )
+    }
+
+    aux(op.into(), next)
 }
 
-fn fold_exprs(initial: Expression, remainder: Vec<(AstSpan, BinOp, Expression)>) -> Expression {
+fn fold_exprs(
+    initial: Expression,
+    remainder: Vec<(AstSpan, operator::Binary, Expression)>,
+) -> Expression {
     remainder
         .into_iter()
         .fold(initial, |acc, (ast_span, op, expr)| {
@@ -72,9 +84,9 @@ fn fold_exprs(initial: Expression, remainder: Vec<(AstSpan, BinOp, Expression)>)
 fn terms(s: Span) -> ParserResult<Expression> {
     let (s, initial) = leaf(s)?;
     let (s, remainder): (Span, Vec<_>) = many0(alt((
-        binop(BinOp::Mul, leaf),
-        binop(BinOp::Div, leaf),
-        binop(BinOp::Mod, leaf),
+        binop(operator::Numeric::Mul, leaf),
+        binop(operator::Numeric::Div, leaf),
+        binop(operator::Numeric::Mod, leaf),
     )))
     .parse(s)?;
     Ok((s, fold_exprs(initial, remainder)))
@@ -82,22 +94,25 @@ fn terms(s: Span) -> ParserResult<Expression> {
 
 fn sums(s: Span) -> ParserResult<Expression> {
     let (s, initial) = terms(s)?;
-    let (s, remainder) =
-        many0(alt((binop(BinOp::Add, terms), binop(BinOp::Sub, terms)))).parse(s)?;
+    let (s, remainder) = many0(alt((
+        binop(operator::Numeric::Add, terms),
+        binop(operator::Numeric::Sub, terms),
+    )))
+    .parse(s)?;
     Ok((s, fold_exprs(initial, remainder)))
 }
 
 fn logic_terms(s: Span) -> ParserResult<Expression> {
     let (s, initial) = sums(s)?;
-    let (s, remainder) = many0(binop(BinOp::And, sums)).parse(s)?;
+    let (s, remainder) = many0(binop(operator::Logic::And, sums)).parse(s)?;
     Ok((s, fold_exprs(initial, remainder)))
 }
 
 fn logic_sums(s: Span) -> ParserResult<Expression> {
     let (s, initial) = logic_terms(s)?;
     let (s, remainder) = many0(alt((
-        binop(BinOp::Xor, logic_terms),
-        binop(BinOp::Or, logic_terms),
+        binop(operator::Logic::Xor, logic_terms),
+        binop(operator::Logic::Or, logic_terms),
     )))
     .parse(s)?;
     Ok((s, fold_exprs(initial, remainder)))
@@ -106,10 +121,10 @@ fn logic_sums(s: Span) -> ParserResult<Expression> {
 fn logic_comp(s: Span) -> ParserResult<Expression> {
     let (s, initial) = logic_sums(s)?;
     let (s, remainder) = many0(alt((
-        binop(BinOp::CompLE, logic_sums),
-        binop(BinOp::CompGE, logic_sums),
-        binop(BinOp::CompLT, logic_sums),
-        binop(BinOp::CompGT, logic_sums),
+        binop(operator::Numeric::CompLE, logic_sums),
+        binop(operator::Numeric::CompGE, logic_sums),
+        binop(operator::Numeric::CompLT, logic_sums),
+        binop(operator::Numeric::CompGT, logic_sums),
     )))
     .parse(s)?;
     Ok((s, fold_exprs(initial, remainder)))
@@ -118,8 +133,8 @@ fn logic_comp(s: Span) -> ParserResult<Expression> {
 pub fn expression(s: Span) -> ParserResult<Expression> {
     let (s, initial) = logic_comp(s)?;
     let (s, remainder) = many0(alt((
-        binop(BinOp::CompEQ, logic_comp),
-        binop(BinOp::CompNEQ, logic_comp),
+        binop(operator::Equal::CompEQ, logic_comp),
+        binop(operator::Equal::CompNEQ, logic_comp),
     )))
     .parse(s)?;
     Ok((s, fold_exprs(initial, remainder)))
diff --git a/grimoire/src/state.rs b/grimoire/src/state.rs
index 3fa2f57..1dec380 100644
--- a/grimoire/src/state.rs
+++ b/grimoire/src/state.rs
@@ -1,5 +1,8 @@
 use crate::{
-    ast::expr::{Const, Expression},
+    ast::{
+        expr::{Const, Expression, VarOrConst},
+        operator,
+    },
     error::Error,
     types::Type,
 };
@@ -22,55 +25,175 @@ impl State {
             .ok_or_else(|| Error::Undefined(var.as_ref().to_string()))
     }
 
-    pub fn evaluate(&self, expr: Expression) -> Result<Const, Error> {
-        todo!()
+    fn evaluate_numeric_binop(
+        &self,
+        left: Expression,
+        op: operator::Numeric,
+        right: Expression,
+    ) -> Result<Const, Error> {
+        let left = self.evaluate_as(left, Type::Number)?;
+        let right = self.evaluate_as(right, Type::Number)?;
+
+        match matches!(left, Const::Flt(_)) || matches!(right, Const::Flt(_)) {
+            // Use float operations
+            true => {
+                let left = left.unwrap_float();
+                let right = right.unwrap_float();
+                match op {
+                    operator::Numeric::Add => Ok(Const::Flt(left + right)),
+                    operator::Numeric::Sub => Ok(Const::Flt(left - right)),
+                    operator::Numeric::Mul => Ok(Const::Flt(left * right)),
+                    operator::Numeric::CompGE => Ok(Const::Bool(left >= right)),
+                    operator::Numeric::CompGT => Ok(Const::Bool(left > right)),
+                    operator::Numeric::CompLT => Ok(Const::Bool(left < right)),
+                    operator::Numeric::CompLE => Ok(Const::Bool(left <= right)),
+
+                    operator::Numeric::Div if right.abs() >= f32::EPSILON => {
+                        Ok(Const::Flt(left / right))
+                    }
+                    operator::Numeric::Mod if right.abs() >= f32::EPSILON => {
+                        Ok(Const::Flt(left % right))
+                    }
+
+                    operator::Numeric::Div => Ok(Const::Flt(f32::NAN)),
+                    operator::Numeric::Mod => Ok(Const::Flt(f32::NAN)),
+                }
+            }
+
+            // Use integer operations
+            false => {
+                let left = left.unwrap_integer();
+                let right = right.unwrap_integer();
+                match op {
+                    operator::Numeric::Add => Ok(Const::Int(left + right)),
+                    operator::Numeric::Sub => Ok(Const::Int(left - right)),
+                    operator::Numeric::Mul => Ok(Const::Int(left * right)),
+                    operator::Numeric::CompGE => Ok(Const::Bool(left >= right)),
+                    operator::Numeric::CompGT => Ok(Const::Bool(left > right)),
+                    operator::Numeric::CompLT => Ok(Const::Bool(left < right)),
+                    operator::Numeric::CompLE => Ok(Const::Bool(left <= right)),
+
+                    operator::Numeric::Div if right != 0 => Ok(Const::Int(left / right)),
+                    operator::Numeric::Mod if right != 0 => Ok(Const::Int(left % right)),
+
+                    operator::Numeric::Div => Ok(Const::Flt(f32::NAN)),
+                    operator::Numeric::Mod => Ok(Const::Flt(f32::NAN)),
+                }
+            }
+        }
     }
 
-    pub fn evaluate_as(&self, expr: Expression, ty: Type) -> Result<Const, Error> {
-        match self.evaluate(expr)? {
-            Const::Int(x) => match ty {
-                Type::Float => Ok(Const::Flt(x as f32)),
-                Type::String => Ok(Const::Str(x.to_string())),
-                Type::Boolean => Ok(Const::Bool(x != 0)),
-                Type::Integer => Ok(Const::Int(x)),
-                ty => Err(Error::CantCast(Const::Int(x), ty)),
-            },
-            Const::Flt(x) => match ty {
-                Type::Float => Ok(Const::Flt(x)),
-                Type::String => Ok(Const::Str(x.to_string())),
-                Type::Boolean => Ok(Const::Bool(x.abs() <= f32::EPSILON)),
-                Type::Integer => Ok(Const::Int(x as i32)),
-                ty => Err(Error::CantCast(Const::Flt(x), ty)),
-            },
-            Const::Bool(x) => match ty {
-                Type::Float => Ok(Const::Flt(if x { 0. } else { 1. })),
-                Type::String => Ok(Const::Str(x.to_string())),
-                Type::Boolean => Ok(Const::Bool(x)),
-                Type::Integer => Ok(Const::Int(x as i32)),
-                ty => Err(Error::CantCast(Const::Bool(x), ty)),
-            },
-            Const::Str(x) => match ty {
-                Type::String => Ok(Const::Str(x)),
-                Type::Float => str::parse::<f32>(&x).map(Const::Flt).map_err(Into::into),
-                Type::Integer => str::parse::<i32>(&x).map(Const::Int).map_err(Into::into),
-                Type::Boolean => str::parse::<bool>(&x).map(Const::Bool).map_err(Into::into),
-                Type::Identifier | Type::Actor | Type::Printer => unreachable!(),
+    fn evaluate_logic_binop(
+        &self,
+        left: Expression,
+        op: operator::Logic,
+        right: Expression,
+    ) -> Result<Const, Error> {
+        // Here we evaluate eagerly to catch errors!
+        let left = self.evaluate_as(left, Type::Boolean)?.unwrap_boolean();
+        let right = self.evaluate_as(right, Type::Boolean)?.unwrap_boolean();
+        Ok(Const::Bool(match op {
+            operator::Logic::Or => left || right,
+            operator::Logic::And => left && right,
+            operator::Logic::Xor => left ^ right,
+        }))
+    }
+
+    fn evaluate_equal_binop(
+        &self,
+        left: Expression,
+        op: operator::Equal,
+        right: Expression,
+    ) -> Result<Const, Error> {
+        Ok(Const::Bool(
+            match (self.evaluate(left)?, self.evaluate(right)?) {
+                // Easy!
+                (Const::Int(x), Const::Int(y)) => op.apply(x == y),
+                (Const::Flt(x), Const::Flt(y)) => op.apply(x == y),
+                (Const::Bool(x), Const::Bool(y)) => op.apply(x == y),
+
+                (Const::Str(x), Const::Ident(y))
+                | (Const::Ident(x), Const::Str(y))
+                | (Const::Ident(x), Const::Ident(y))
+                | (Const::Str(x), Const::Str(y)) => op.apply(x == y),
+
+                // Cast, handle permutations and cast priority.
+                (Const::Int(x), Const::Flt(y)) => op.apply(x == y as i32),
+                (Const::Flt(x), Const::Int(y)) => op.apply(x == y as f32),
+
+                // Cast with permutation. Here we cast all into a boolean first!
+                (x @ Const::Int(_), Const::Bool(y)) | (Const::Bool(y), x @ Const::Int(_)) => {
+                    op.apply(x.unwrap_boolean() == y)
+                }
+
+                (x @ Const::Flt(_), Const::Bool(y)) | (Const::Bool(y), x @ Const::Flt(_)) => {
+                    op.apply(x.unwrap_boolean() == y)
+                }
+
+                // Can be inter-casted, they are not equal!
+                _ => op.check_not_equals(),
             },
-            Const::Ident(x) => match ty {
-                Type::Identifier => match self.identifiers.contains(&x) {
-                    true => Ok(Const::Ident(x)),
-                    false => Err(Error::Undefined(x)),
-                },
-                Type::Actor => match self.actors.contains(&x) {
-                    true => Ok(Const::Ident(x)), // TODO: Return the Actor object?
-                    false => Err(Error::Undefined(x)),
-                },
-                Type::Printer => match self.printers.contains(&x) {
-                    true => Ok(Const::Ident(x)), // TODO: Return the Printer object?
-                    false => Err(Error::Undefined(x)),
+        ))
+    }
+
+    pub fn evaluate(&self, expr: Expression) -> Result<Const, Error> {
+        match expr {
+            // Leaf, simple.
+            Expression::Leaf(_, VarOrConst::Const(constant)) => Ok(constant),
+            Expression::Leaf(_, VarOrConst::Var(var)) => self.resolve(var).cloned(),
+
+            // Unary, not complicated.
+            Expression::Unary(_, op, inner) => match op {
+                operator::Unary::Not => Ok(Const::Bool(
+                    !self.evaluate_as(*inner, Type::Boolean)?.unwrap_boolean(),
+                )),
+                operator::Unary::Neg => match self.evaluate_as(*inner, Type::Number)? {
+                    Const::Int(x) => Ok(Const::Int(-x)),
+                    Const::Flt(x) => Ok(Const::Flt(-x)),
+                    _ => unreachable!(),
                 },
-                ty => Err(Error::CantCast(Const::Ident(x), ty)),
             },
+
+            // Binary, just long to write...
+            Expression::Binary(_, left, op, right) => match op {
+                operator::Binary::Numeric(op) => self.evaluate_numeric_binop(*left, op, *right),
+                operator::Binary::Logic(op) => self.evaluate_logic_binop(*left, op, *right),
+                operator::Binary::Equal(op) => self.evaluate_equal_binop(*left, op, *right),
+            },
+        }
+    }
+
+    fn check_identifier(&self, ident: impl Into<String>) -> Result<Const, Error> {
+        let x = ident.into();
+        match self.identifiers.contains(&x) {
+            true => Ok(Const::Ident(x)),
+            false => Err(Error::Undefined(x)),
+        }
+    }
+
+    fn find_actor(&self, actor: impl Into<String>) -> Result<Const, Error> {
+        let x = actor.into();
+        match self.actors.contains(&x) {
+            true => Ok(Const::Ident(x)),
+            false => Err(Error::Undefined(x)),
+        }
+    }
+
+    fn find_printer(&self, printer: impl Into<String>) -> Result<Const, Error> {
+        let x = printer.into();
+        match self.printers.contains(&x) {
+            true => Ok(Const::Ident(x)),
+            false => Err(Error::Undefined(x)),
+        }
+    }
+
+    pub fn evaluate_as(&self, expr: Expression, ty: Type) -> Result<Const, Error> {
+        let constant = self.evaluate(expr)?;
+        match ty {
+            Type::Identifier => self.check_identifier(constant.unwrap_identifier()),
+            Type::Actor => self.find_actor(constant.unwrap_identifier()),
+            Type::Printer => self.find_printer(constant.unwrap_identifier()),
+            ty => constant.cast_into(ty),
         }
     }
 }
diff --git a/grimoire/src/types.rs b/grimoire/src/types.rs
index da91ab8..6f3b138 100644
--- a/grimoire/src/types.rs
+++ b/grimoire/src/types.rs
@@ -6,6 +6,10 @@ pub enum Type {
     Integer,
     Boolean,
 
+    /// Can be [Type::Integer] or [Type::Float]. [Type::Boolean] will be converted into a
+    /// [Type::Integer] constant.
+    Number,
+
     Identifier,
     Actor,
     Printer,
-- 
GitLab