diff --git a/src/args.rs b/src/args.rs
index 2ebb8cb..cb4300b 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -35,7 +35,7 @@ impl FuncArgs {
     }
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Eq, PartialEq)]
 pub(crate) struct CallArgs(pub HashMap<CallArg, Spanned<Value>>, pub Span);
 
 #[derive(Debug, Clone, Hash, Eq, PartialEq)]
diff --git a/src/builtin/color/hsl.rs b/src/builtin/color/hsl.rs
index 79bd612..cb49993 100644
--- a/src/builtin/color/hsl.rs
+++ b/src/builtin/color/hsl.rs
@@ -34,7 +34,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
                 .into());
         }
 
-        let lightness = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
+        let lightness = match channels.pop() {
             Some(Value::Dimension(n, _)) => n / Number::from(100),
             Some(v) => {
                 return Err((
@@ -49,7 +49,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
             None => return Err(("Missing element $lightness.", args.span()).into()),
         };
 
-        let saturation = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
+        let saturation = match channels.pop() {
             Some(Value::Dimension(n, _)) => n / Number::from(100),
             Some(v) => {
                 return Err((
@@ -64,7 +64,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
             None => return Err(("Missing element $saturation.", args.span()).into()),
         };
 
-        let hue = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
+        let hue = match channels.pop() {
             Some(Value::Dimension(n, _)) => n,
             Some(v) => {
                 return Err((
diff --git a/src/builtin/color/rgb.rs b/src/builtin/color/rgb.rs
index 4fdc5e6..d0e7934 100644
--- a/src/builtin/color/rgb.rs
+++ b/src/builtin/color/rgb.rs
@@ -37,7 +37,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
                 .into());
         }
 
-        let blue = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
+        let blue = match channels.pop() {
             Some(Value::Dimension(n, Unit::None)) => n,
             Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
             Some(v) if v.is_special_function() => {
@@ -64,7 +64,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
             None => return Err(("Missing element $blue.", args.span()).into()),
         };
 
-        let green = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
+        let green = match channels.pop() {
             Some(Value::Dimension(n, Unit::None)) => n,
             Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
             Some(v) if v.is_special_function() => {
@@ -90,7 +90,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
             None => return Err(("Missing element $green.", args.span()).into()),
         };
 
-        let red = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
+        let red = match channels.pop() {
             Some(Value::Dimension(n, Unit::None)) => n,
             Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
             Some(v) if v.is_special_function() => {
diff --git a/src/builtin/list.rs b/src/builtin/list.rs
index e1a9149..57349cc 100644
--- a/src/builtin/list.rs
+++ b/src/builtin/list.rs
@@ -6,7 +6,7 @@ use crate::{
     args::CallArgs,
     common::{Brackets, ListSeparator, QuoteKind},
     error::SassResult,
-    parse::Parser,
+    parse::{HigherIntermediateValue, Parser, ValueVisitor},
     unit::Unit,
     value::{Number, Value},
 };
@@ -220,7 +220,7 @@ fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
             _ => Brackets::Bracketed,
         },
         v => {
-            if v.is_true(args.span())? {
+            if v.is_true() {
                 Brackets::Bracketed
             } else {
                 Brackets::None
@@ -248,16 +248,15 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
     args.max_args(2)?;
     let list = parser.arg(&mut args, 0, "list")?.as_list();
     let value = parser.arg(&mut args, 1, "value")?;
-    // TODO: find a way around this unwrap.
-    // It should be impossible to hit as the arg is
-    // evaluated prior to checking equality, but
-    // it is still dirty.
+    // TODO: find a way to propagate any errors here
     // Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
     let index = match list.into_iter().position(|v| {
-        v.equals(value.clone(), args.span())
-            .unwrap()
-            .is_true(args.span())
-            .unwrap()
+        ValueVisitor::new(parser, args.span())
+            .equal(
+                HigherIntermediateValue::Literal(v),
+                HigherIntermediateValue::Literal(value.clone()),
+            )
+            .map_or(false, |v| v.is_true())
     }) {
         Some(v) => Number::from(v + 1),
         None => return Ok(Value::Null),
@@ -266,12 +265,11 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
 }
 
 fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
-    let span = args.span();
     let lists = parser
         .variadic_args(args)?
         .into_iter()
-        .map(|x| Ok(x.node.eval(span)?.node.as_list()))
-        .collect::<SassResult<Vec<Vec<Value>>>>()?;
+        .map(|x| x.node.as_list())
+        .collect::<Vec<Vec<Value>>>();
 
     let len = lists.iter().map(Vec::len).min().unwrap_or(0);
 
diff --git a/src/builtin/map.rs b/src/builtin/map.rs
index 0b10fd8..3d240f3 100644
--- a/src/builtin/map.rs
+++ b/src/builtin/map.rs
@@ -22,7 +22,7 @@ fn map_get(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
                 .into())
         }
     };
-    Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
+    Ok(map.get(&key, args.span(), parser)?.unwrap_or(Value::Null))
 }
 
 fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
@@ -39,7 +39,7 @@ fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
                 .into())
         }
     };
-    Ok(Value::bool(map.get(&key, args.span())?.is_some()))
+    Ok(Value::bool(map.get(&key, args.span(), parser)?.is_some()))
 }
 
 fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
diff --git a/src/builtin/math.rs b/src/builtin/math.rs
index dacf5f5..d1a06fc 100644
--- a/src/builtin/math.rs
+++ b/src/builtin/math.rs
@@ -7,9 +7,8 @@ use rand::Rng;
 
 use crate::{
     args::CallArgs,
-    common::Op,
     error::SassResult,
-    parse::Parser,
+    parse::{HigherIntermediateValue, Parser, ValueVisitor},
     unit::Unit,
     value::{Number, Value},
 };
@@ -207,14 +206,12 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
     let mut min = nums.next().unwrap();
 
     for num in nums {
-        if Value::Dimension(num.0.clone(), num.1.clone())
-            .cmp(
-                Value::Dimension(min.0.clone(), min.1.clone()),
-                Op::LessThan,
-                span,
+        if ValueVisitor::new(parser, span)
+            .less_than(
+                HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())),
+                HigherIntermediateValue::Literal(Value::Dimension(min.0.clone(), min.1.clone())),
             )?
-            .node
-            .is_true(span)?
+            .is_true()
         {
             min = num;
         }
@@ -239,14 +236,12 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
     let mut max = nums.next().unwrap();
 
     for num in nums {
-        if Value::Dimension(num.0.clone(), num.1.clone())
-            .cmp(
-                Value::Dimension(max.0.clone(), max.1.clone()),
-                Op::GreaterThan,
-                span,
+        if ValueVisitor::new(parser, span)
+            .greater_than(
+                HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())),
+                HigherIntermediateValue::Literal(Value::Dimension(max.0.clone(), max.1.clone())),
             )?
-            .node
-            .is_true(span)?
+            .is_true()
         {
             max = num;
         }
diff --git a/src/builtin/meta.rs b/src/builtin/meta.rs
index 248e3f0..e0d93e5 100644
--- a/src/builtin/meta.rs
+++ b/src/builtin/meta.rs
@@ -13,10 +13,7 @@ use crate::{
 
 fn if_(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
     args.max_args(3)?;
-    if parser
-        .arg(&mut args, 0, "condition")?
-        .is_true(args.span())?
-    {
+    if parser.arg(&mut args, 0, "condition")?.is_true() {
         Ok(parser.arg(&mut args, 1, "if-true")?)
     } else {
         Ok(parser.arg(&mut args, 2, "if-false")?)
@@ -77,10 +74,7 @@ fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
 fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
     args.max_args(1)?;
     let value = parser.arg(&mut args, 0, "value")?;
-    Ok(Value::String(
-        value.kind(args.span())?.to_owned(),
-        QuoteKind::None,
-    ))
+    Ok(Value::String(value.kind().to_owned(), QuoteKind::None))
 }
 
 fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
@@ -177,7 +171,7 @@ fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
     };
     let css = parser
         .default_arg(&mut args, 1, "css", Value::False)
-        .is_true(args.span())?;
+        .is_true();
     let module = match parser.default_arg(&mut args, 2, "module", Value::Null) {
         Value::String(s, ..) => Some(s),
         Value::Null => None,
diff --git a/src/lib.rs b/src/lib.rs
index 854d7b5..8efeff5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -77,7 +77,7 @@ grass input.scss
 
     clippy::string_add,
     clippy::get_unwrap,
-    clippy::unit_arg,
+    // clippy::unit_arg,
     clippy::wrong_self_convention,
     clippy::items_after_statements,
     clippy::shadow_reuse,
diff --git a/src/output.rs b/src/output.rs
index af69731..5d5778e 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -50,15 +50,13 @@ impl Toplevel {
         Toplevel::RuleSet(selector, Vec::new())
     }
 
-    fn push_style(&mut self, mut s: Style) -> SassResult<()> {
-        s = s.eval()?;
-        if s.value.is_null(s.value.span)? {
-            return Ok(());
+    fn push_style(&mut self, s: Style) {
+        if s.value.is_null() {
+            return;
         }
         if let Toplevel::RuleSet(_, entries) = self {
             entries.push(BlockEntry::Style(Box::new(s)));
         }
-        Ok(())
     }
 
     fn push_comment(&mut self, s: String) {
@@ -96,7 +94,7 @@ impl Css {
                 for rule in body {
                     match rule {
                         Stmt::RuleSet { .. } => vals.extend(self.parse_stmt(rule, extender)?),
-                        Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(s)?,
+                        Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(s),
                         Stmt::Comment(s) => vals.get_mut(0).unwrap().push_comment(s),
                         Stmt::Media(m) => {
                             let MediaRule { query, body, .. } = *m;
@@ -117,10 +115,12 @@ impl Css {
                             })))
                         }
                         Stmt::Return(..) => unreachable!(),
-                        Stmt::AtRoot { body } => body
-                            .into_iter()
-                            .map(|r| Ok(vals.extend(self.parse_stmt(r, extender)?)))
-                            .collect::<SassResult<()>>()?,
+                        Stmt::AtRoot { body } => {
+                            body.into_iter().try_for_each(|r| -> SassResult<()> {
+                                vals.append(&mut self.parse_stmt(r, extender)?);
+                                Ok(())
+                            })?
+                        }
                     };
                 }
                 vals
diff --git a/src/parse/args.rs b/src/parse/args.rs
index 391cb11..ca3842b 100644
--- a/src/parse/args.rs
+++ b/src/parse/args.rs
@@ -178,10 +178,7 @@ impl<'a> Parser<'a> {
                             } else {
                                 CallArg::Named(name.into())
                             },
-                            {
-                                let val = self.parse_value_from_vec(val)?;
-                                val.node.eval(val.span)?
-                            },
+                            self.parse_value_from_vec(val)?,
                         );
                         span = span.merge(tok.pos());
                         return Ok(CallArgs(args, span));
@@ -221,10 +218,7 @@ impl<'a> Parser<'a> {
             }
 
             if is_splat {
-                let val = {
-                    let val = self.parse_value_from_vec(mem::take(&mut val))?;
-                    val.node.eval(val.span)?
-                };
+                let val = self.parse_value_from_vec(mem::take(&mut val))?;
                 match val.node {
                     Value::ArgList(v) => {
                         for arg in v {
@@ -233,13 +227,13 @@ impl<'a> Parser<'a> {
                     }
                     Value::List(v, ..) => {
                         for arg in v {
-                            args.insert(CallArg::Positional(args.len()), arg.eval(val.span)?);
+                            args.insert(CallArg::Positional(args.len()), arg.span(val.span));
                         }
                     }
                     Value::Map(v) => {
                         for (name, arg) in v.entries() {
                             let name = name.to_css_string(val.span)?.to_string();
-                            args.insert(CallArg::Named(name.into()), arg.eval(val.span)?);
+                            args.insert(CallArg::Named(name.into()), arg.span(val.span));
                         }
                     }
                     _ => {
@@ -253,10 +247,7 @@ impl<'a> Parser<'a> {
                     } else {
                         CallArg::Named(name.as_str().into())
                     },
-                    {
-                        let val = self.parse_value_from_vec(mem::take(&mut val))?;
-                        val.node.eval(val.span)?
-                    },
+                    self.parse_value_from_vec(mem::take(&mut val))?,
                 );
             }
 
@@ -354,16 +345,13 @@ impl<'a> Parser<'a> {
                         node: arg_list,
                         span,
                     },
-                )?;
+                );
                 break;
             }
             let val = match args.get(idx, arg.name.clone()) {
                 Some(v) => v,
                 None => match arg.default.as_mut() {
-                    Some(v) => {
-                        let val = self.parse_value_from_vec(mem::take(v))?;
-                        val.node.eval(val.span)?
-                    }
+                    Some(v) => self.parse_value_from_vec(mem::take(v))?,
                     None => {
                         return Err(
                             (format!("Missing argument ${}.", &arg.name), args.span()).into()
@@ -373,8 +361,8 @@ impl<'a> Parser<'a> {
             };
             self.scopes
                 .last_mut()
-                .insert_var(arg.name.clone(), val.clone())?;
-            scope.insert_var(mem::take(&mut arg.name), val)?;
+                .insert_var(arg.name.clone(), val.clone());
+            scope.insert_var(mem::take(&mut arg.name), val);
         }
         self.scopes.pop();
         Ok(())
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index ab8b3d7..e1df829 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -24,6 +24,8 @@ use crate::{
 
 use common::{Branch, NeverEmptyVec, SelectorOrStyle};
 
+pub(crate) use value::{HigherIntermediateValue, ValueVisitor};
+
 mod args;
 pub mod common;
 mod function;
@@ -405,10 +407,7 @@ impl<'a> Parser<'a> {
             Some(Token { kind: '}', .. }) => {}
             Some(..) | None => return Err(("expected \"}\".", val.span).into()),
         }
-        Ok(Spanned {
-            node: val.node.eval(val.span)?.node.unquote(),
-            span: val.span,
-        })
+        Ok(val.map_node(Value::unquote))
     }
 
     pub fn parse_interpolation_as_string(&mut self) -> SassResult<Cow<'static, str>> {
@@ -560,8 +559,7 @@ impl<'a> Parser<'a> {
 
         for branch in branches {
             self.span_before = branch.cond.first().unwrap().pos;
-            let cond = self.parse_value_from_vec(branch.cond)?;
-            if cond.node.is_true(cond.span)? {
+            if self.parse_value_from_vec(branch.cond)?.node.is_true() {
                 return Parser {
                     toks: &mut branch.toks.into_iter().peekmore(),
                     map: self.map,
@@ -668,7 +666,7 @@ impl<'a> Parser<'a> {
         }
         self.whitespace();
         let from_val = self.parse_value_from_vec(from_toks)?;
-        let from = match from_val.node.eval(from_val.span)?.node {
+        let from = match from_val.node {
             Value::Dimension(n, _) => match n.to_integer().to_isize() {
                 Some(v) => v,
                 None => return Err((format!("{} is not a int.", n), from_val.span).into()),
@@ -685,7 +683,7 @@ impl<'a> Parser<'a> {
         let to_toks = read_until_open_curly_brace(self.toks)?;
         self.toks.next();
         let to_val = self.parse_value_from_vec(to_toks)?;
-        let to = match to_val.node.eval(to_val.span)?.node {
+        let to = match to_val.node {
             Value::Dimension(n, _) => match n.to_integer().to_isize() {
                 Some(v) => v,
                 None => return Err((format!("{} is not a int.", n), to_val.span).into()),
@@ -725,7 +723,7 @@ impl<'a> Parser<'a> {
                     node: Value::Dimension(Number::from(i), Unit::None),
                     span: var.span,
                 },
-            )?;
+            );
             if self.in_function {
                 let these_stmts = Parser {
                     toks: &mut body.clone().into_iter().peekmore(),
@@ -797,7 +795,7 @@ impl<'a> Parser<'a> {
         let mut stmts = Vec::new();
         let mut val = self.parse_value_from_vec(cond.clone())?;
         self.scopes.push(self.scopes.last().clone());
-        while val.node.is_true(val.span)? {
+        while val.node.is_true() {
             if self.in_function {
                 let these_stmts = Parser {
                     toks: &mut body.clone().into_iter().peekmore(),
@@ -881,8 +879,7 @@ impl<'a> Parser<'a> {
         }
         self.whitespace();
         let iter_val_toks = read_until_open_curly_brace(self.toks)?;
-        let iter_val = self.parse_value_from_vec(iter_val_toks)?;
-        let iter = iter_val.node.eval(iter_val.span)?.node.as_list();
+        let iter = self.parse_value_from_vec(iter_val_toks)?.node.as_list();
         self.toks.next();
         self.whitespace();
         let mut body = read_until_closing_curly_brace(self.toks)?;
@@ -905,7 +902,7 @@ impl<'a> Parser<'a> {
                             node: this_iterator[0].clone(),
                             span: vars[0].span,
                         },
-                    )?;
+                    );
                 } else {
                     self.scopes.last_mut().insert_var(
                         &vars[0].node,
@@ -913,7 +910,7 @@ impl<'a> Parser<'a> {
                             node: Value::List(this_iterator, ListSeparator::Space, Brackets::None),
                             span: vars[0].span,
                         },
-                    )?;
+                    );
                 }
             } else {
                 for (var, val) in vars.clone().into_iter().zip(
@@ -927,7 +924,7 @@ impl<'a> Parser<'a> {
                             node: val,
                             span: var.span,
                         },
-                    )?;
+                    );
                 }
             }
 
diff --git a/src/parse/value/css_function.rs b/src/parse/value/css_function.rs
new file mode 100644
index 0000000..1445642
--- /dev/null
+++ b/src/parse/value/css_function.rs
@@ -0,0 +1,395 @@
+use std::{borrow::Borrow, iter::Iterator};
+
+use codemap::Spanned;
+
+use crate::{
+    error::SassResult,
+    utils::{
+        as_hex, hex_char_for, is_name, peek_ident_no_interpolation, peek_until_closing_curly_brace,
+        peek_whitespace,
+    },
+    value::Value,
+    Token,
+};
+
+use super::super::Parser;
+
+impl<'a> Parser<'a> {
+    pub(super) fn eat_calc_args(&mut self, buf: &mut String) -> SassResult<()> {
+        buf.reserve(2);
+        buf.push('(');
+        let mut nesting = 0;
+        while let Some(tok) = self.toks.next() {
+            match tok.kind {
+                ' ' | '\t' | '\n' => {
+                    self.whitespace();
+                    buf.push(' ');
+                }
+                '#' => {
+                    if let Some(Token { kind: '{', pos }) = self.toks.peek() {
+                        self.span_before = *pos;
+                        self.toks.next();
+                        let interpolation = self.parse_interpolation()?;
+                        buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
+                    } else {
+                        buf.push('#');
+                    }
+                }
+                '(' => {
+                    nesting += 1;
+                    buf.push('(');
+                }
+                ')' => {
+                    if nesting == 0 {
+                        break;
+                    } else {
+                        nesting -= 1;
+                        buf.push(')');
+                    }
+                }
+                c => buf.push(c),
+            }
+        }
+        buf.push(')');
+        Ok(())
+    }
+
+    pub(super) fn eat_progid(&mut self) -> SassResult<String> {
+        let mut string = String::new();
+        let mut span = self.toks.peek().unwrap().pos();
+        while let Some(tok) = self.toks.next() {
+            span = span.merge(tok.pos());
+            match tok.kind {
+                'a'..='z' | 'A'..='Z' | '.' => {
+                    string.push(tok.kind);
+                }
+                '(' => {
+                    self.eat_calc_args(&mut string)?;
+                    break;
+                }
+                _ => return Err(("expected \"(\".", span).into()),
+            }
+        }
+        Ok(string)
+    }
+
+    pub(super) fn try_eat_url(&mut self) -> SassResult<Option<String>> {
+        let mut buf = String::from("url(");
+        peek_whitespace(self.toks);
+        while let Some(tok) = self.toks.peek() {
+            let kind = tok.kind;
+            self.toks.advance_cursor();
+            if kind == '!'
+                || kind == '%'
+                || kind == '&'
+                || (kind >= '*' && kind <= '~')
+                || kind as u32 >= 0x0080
+            {
+                buf.push(kind);
+            } else if kind == '\\' {
+                buf.push_str(&self.peek_escape()?);
+            } else if kind == '#' {
+                if let Some(Token { kind: '{', .. }) = self.toks.peek() {
+                    self.toks.advance_cursor();
+                    let interpolation = self.peek_interpolation()?;
+                    match interpolation.node {
+                        Value::String(ref s, ..) => buf.push_str(s),
+                        v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
+                    };
+                } else {
+                    buf.push('#');
+                }
+            } else if kind == ')' {
+                buf.push(')');
+                self.toks.truncate_iterator_to_cursor();
+                self.toks.next();
+                return Ok(Some(buf));
+            } else if kind.is_whitespace() {
+                peek_whitespace(self.toks);
+                let next = match self.toks.peek() {
+                    Some(v) => v,
+                    None => break,
+                };
+                if next.kind == ')' {
+                    buf.push(')');
+                    self.toks.truncate_iterator_to_cursor();
+                    self.toks.next();
+                    return Ok(Some(buf));
+                } else {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+        self.toks.reset_cursor();
+        Ok(None)
+    }
+
+    pub(super) fn try_parse_min_max(
+        &mut self,
+        fn_name: &str,
+        allow_comma: bool,
+    ) -> SassResult<Option<String>> {
+        let mut buf = if allow_comma {
+            format!("{}(", fn_name)
+        } else {
+            String::new()
+        };
+        peek_whitespace(self.toks);
+        while let Some(tok) = self.toks.peek() {
+            let kind = tok.kind;
+            match kind {
+                '+' | '-' | '0'..='9' => {
+                    self.toks.advance_cursor();
+                    if let Some(number) = self.peek_number()? {
+                        buf.push(kind);
+                        buf.push_str(&number);
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                '#' => {
+                    self.toks.advance_cursor();
+                    if let Some(Token { kind: '{', .. }) = self.toks.peek() {
+                        self.toks.advance_cursor();
+                        let interpolation = self.peek_interpolation()?;
+                        match interpolation.node {
+                            Value::String(ref s, ..) => buf.push_str(s),
+                            v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
+                        };
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                'c' | 'C' => {
+                    if let Some(name) = self.try_parse_min_max_function("calc")? {
+                        buf.push_str(&name);
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                'e' | 'E' => {
+                    if let Some(name) = self.try_parse_min_max_function("env")? {
+                        buf.push_str(&name);
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                'v' | 'V' => {
+                    if let Some(name) = self.try_parse_min_max_function("var")? {
+                        buf.push_str(&name);
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                '(' => {
+                    self.toks.advance_cursor();
+                    buf.push('(');
+                    if let Some(val) = self.try_parse_min_max(fn_name, false)? {
+                        buf.push_str(&val);
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                'm' | 'M' => {
+                    self.toks.advance_cursor();
+                    match self.toks.peek() {
+                        Some(Token { kind: 'i', .. }) | Some(Token { kind: 'I', .. }) => {
+                            self.toks.advance_cursor();
+                            if !matches!(self.toks.peek(), Some(Token { kind: 'n', .. }) | Some(Token { kind: 'N', .. }))
+                            {
+                                return Ok(None);
+                            }
+                            buf.push_str("min(")
+                        }
+                        Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => {
+                            self.toks.advance_cursor();
+                            if !matches!(self.toks.peek(), Some(Token { kind: 'x', .. }) | Some(Token { kind: 'X', .. }))
+                            {
+                                return Ok(None);
+                            }
+                            buf.push_str("max(")
+                        }
+                        _ => return Ok(None),
+                    }
+
+                    self.toks.advance_cursor();
+
+                    if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
+                        return Ok(None);
+                    }
+
+                    if let Some(val) = self.try_parse_min_max(fn_name, false)? {
+                        buf.push_str(&val);
+                    } else {
+                        return Ok(None);
+                    }
+                }
+                _ => return Ok(None),
+            }
+
+            peek_whitespace(self.toks);
+
+            let next = match self.toks.peek() {
+                Some(tok) => tok,
+                None => return Ok(None),
+            };
+
+            match next.kind {
+                ')' => {
+                    self.toks.advance_cursor();
+                    buf.push(')');
+                    return Ok(Some(buf));
+                }
+                '+' | '-' | '*' | '/' => {
+                    buf.push(' ');
+                    buf.push(next.kind);
+                    buf.push(' ');
+                    self.toks.advance_cursor();
+                }
+                ',' => {
+                    if !allow_comma {
+                        return Ok(None);
+                    }
+                    self.toks.advance_cursor();
+                    buf.push(',');
+                    buf.push(' ');
+                }
+                _ => return Ok(None),
+            }
+
+            peek_whitespace(self.toks);
+        }
+
+        Ok(Some(buf))
+    }
+
+    #[allow(dead_code, unused_mut, unused_variables, unused_assignments)]
+    fn try_parse_min_max_function(&mut self, fn_name: &'static str) -> SassResult<Option<String>> {
+        let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?.node;
+        ident.make_ascii_lowercase();
+        if ident != fn_name {
+            return Ok(None);
+        }
+        if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
+            return Ok(None);
+        }
+        self.toks.advance_cursor();
+        ident.push('(');
+        todo!("special functions inside `min()` or `max()`")
+    }
+}
+
+/// Methods required to do arbitrary lookahead
+impl<'a> Parser<'a> {
+    fn peek_number(&mut self) -> SassResult<Option<String>> {
+        let mut buf = String::new();
+
+        let num = self.peek_whole_number();
+        buf.push_str(&num);
+
+        self.toks.advance_cursor();
+
+        if let Some(Token { kind: '.', .. }) = self.toks.peek() {
+            self.toks.advance_cursor();
+            let num = self.peek_whole_number();
+            if num.is_empty() {
+                return Ok(None);
+            }
+            buf.push_str(&num);
+        } else {
+            self.toks.move_cursor_back().unwrap();
+        }
+
+        let next = match self.toks.peek() {
+            Some(tok) => tok,
+            None => return Ok(Some(buf)),
+        };
+
+        match next.kind {
+            'a'..='z' | 'A'..='Z' | '-' | '_' | '\\' => {
+                let unit = peek_ident_no_interpolation(self.toks, true, self.span_before)?.node;
+
+                buf.push_str(&unit);
+            }
+            '%' => {
+                self.toks.advance_cursor();
+                buf.push('%');
+            }
+            _ => {}
+        }
+
+        Ok(Some(buf))
+    }
+
+    fn peek_whole_number(&mut self) -> String {
+        let mut buf = String::new();
+        while let Some(tok) = self.toks.peek() {
+            if tok.kind.is_ascii_digit() {
+                buf.push(tok.kind);
+                self.toks.advance_cursor();
+            } else {
+                return buf;
+            }
+        }
+        buf
+    }
+
+    fn peek_interpolation(&mut self) -> SassResult<Spanned<Value>> {
+        let vec = peek_until_closing_curly_brace(self.toks)?;
+        self.toks.advance_cursor();
+        let val = self.parse_value_from_vec(vec)?;
+        Ok(Spanned {
+            node: val.node.unquote(),
+            span: val.span,
+        })
+    }
+
+    fn peek_escape(&mut self) -> SassResult<String> {
+        let mut value = 0;
+        let first = match self.toks.peek() {
+            Some(t) => *t,
+            None => return Ok(String::new()),
+        };
+        let mut span = first.pos;
+        if first.kind == '\n' {
+            return Err(("Expected escape sequence.", first.pos()).into());
+        } else if first.kind.is_ascii_hexdigit() {
+            for _ in 0..6 {
+                let next = match self.toks.peek() {
+                    Some(t) => t,
+                    None => break,
+                };
+                if !next.kind.is_ascii_hexdigit() {
+                    break;
+                }
+                value *= 16;
+                value += as_hex(next.kind);
+                span = span.merge(next.pos);
+                self.toks.peek_forward(1);
+            }
+            if self.toks.peek().is_some() && self.toks.peek().unwrap().kind.is_whitespace() {
+                self.toks.peek_forward(1);
+            }
+        } else {
+            value = self.toks.peek_forward(1).unwrap().kind as u32;
+        }
+
+        let c = std::char::from_u32(value).ok_or(("Invalid escape sequence.", span))?;
+        if is_name(c) {
+            Ok(c.to_string())
+        } else if value <= 0x1F || value == 0x7F {
+            let mut buf = String::with_capacity(4);
+            buf.push('\\');
+            if value > 0xF {
+                buf.push(hex_char_for(value >> 4));
+            }
+            buf.push(hex_char_for(value & 0xF));
+            buf.push(' ');
+            Ok(buf)
+        } else {
+            Ok(format!("\\{}", c))
+        }
+    }
+}
diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs
new file mode 100644
index 0000000..6caf401
--- /dev/null
+++ b/src/parse/value/eval.rs
@@ -0,0 +1,905 @@
+#![allow(unused_variables)]
+
+use std::cmp::Ordering;
+
+use codemap::{Span, Spanned};
+
+use crate::{
+    args::CallArgs,
+    common::{Op, QuoteKind},
+    error::SassResult,
+    unit::{Unit, UNIT_CONVERSION_TABLE},
+    value::{SassFunction, Value},
+};
+
+use super::super::Parser;
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) enum HigherIntermediateValue {
+    Literal(Value),
+    /// A function that hasn't yet been evaluated
+    Function(SassFunction, CallArgs),
+    BinaryOp(Box<Self>, Op, Box<Self>),
+    UnaryOp(Op, Box<Self>),
+    Paren(Box<Self>),
+}
+
+impl HigherIntermediateValue {
+    pub const fn span(self, span: Span) -> Spanned<Self> {
+        Spanned { node: self, span }
+    }
+}
+
+impl<'a> Parser<'a> {
+    fn call_function(&mut self, function: SassFunction, args: CallArgs) -> SassResult<Value> {
+        function.call(args, self)
+    }
+}
+
+pub(crate) struct ValueVisitor<'a, 'b: 'a> {
+    parser: &'a mut Parser<'b>,
+    span: Span,
+}
+
+impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
+    pub fn new(parser: &'a mut Parser<'b>, span: Span) -> Self {
+        Self { parser, span }
+    }
+
+    pub fn eval(&mut self, value: HigherIntermediateValue) -> SassResult<Value> {
+        match value {
+            HigherIntermediateValue::Literal(v) => Ok(v),
+            HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2),
+            HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val),
+            HigherIntermediateValue::Paren(val) => self.eval(*val),
+            HigherIntermediateValue::Function(function, args) => {
+                self.parser.call_function(function, args)
+            }
+        }
+    }
+
+    fn bin_op(
+        &mut self,
+        val1: HigherIntermediateValue,
+        op: Op,
+        val2: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let mut val1 = self.paren_or_unary(val1)?;
+        let val2 = self.paren_or_unary(val2)?;
+
+        if let HigherIntermediateValue::BinaryOp(val1_1, op2, val1_2) = val1 {
+            if op2.precedence() > op.precedence() {
+                val1 = HigherIntermediateValue::Literal(self.bin_op(*val1_1, op2, *val1_2)?);
+            } else {
+                let val2 = HigherIntermediateValue::Literal(self.bin_op(*val1_2, op, val2)?);
+                return self.bin_op(*val1_1, op2, val2);
+            }
+        }
+
+        Ok(match op {
+            Op::Plus => self.add(val1, val2)?,
+            Op::Minus => self.sub(val1, val2)?,
+            Op::Mul => self.mul(val1, val2)?,
+            Op::Div => self.div(val1, val2)?,
+            Op::Rem => self.rem(val1, val2)?,
+            Op::And => Self::and(val1, val2)?,
+            Op::Or => Self::or(val1, val2)?,
+            Op::Equal => self.equal(val1, val2)?,
+            Op::NotEqual => self.not_equal(val1, val2)?,
+            Op::GreaterThan => self.greater_than(val1, val2)?,
+            Op::GreaterThanEqual => self.greater_than_or_equal(val1, val2)?,
+            Op::LessThan => self.less_than(val1, val2)?,
+            Op::LessThanEqual => self.less_than_or_equal(val1, val2)?,
+            Op::Not => unreachable!(),
+        })
+    }
+
+    fn unary_op(&mut self, op: Op, val: HigherIntermediateValue) -> SassResult<Value> {
+        let val = self.eval(val)?;
+        match op {
+            Op::Minus => self.unary_minus(val),
+            Op::Not => Self::unary_not(&val),
+            Op::Plus => self.unary_plus(val),
+            _ => unreachable!(),
+        }
+    }
+
+    fn unary_minus(&self, val: Value) -> SassResult<Value> {
+        Ok(match val {
+            Value::Dimension(n, u) => Value::Dimension(-n, u),
+            v => Value::String(format!("-{}", v.to_css_string(self.span)?), QuoteKind::None),
+        })
+    }
+
+    fn unary_plus(&self, val: Value) -> SassResult<Value> {
+        Ok(match val {
+            v @ Value::Dimension(..) => v,
+            v => Value::String(format!("+{}", v.to_css_string(self.span)?), QuoteKind::None),
+        })
+    }
+
+    fn unary_not(val: &Value) -> SassResult<Value> {
+        Ok(Value::bool(!val.is_true()))
+    }
+
+    fn paren(&mut self, val: HigherIntermediateValue) -> SassResult<HigherIntermediateValue> {
+        Ok(if let HigherIntermediateValue::Paren(v) = val {
+            HigherIntermediateValue::Literal(self.eval(*v)?)
+        } else {
+            val
+        })
+    }
+
+    fn paren_or_unary(
+        &mut self,
+        val: HigherIntermediateValue,
+    ) -> SassResult<HigherIntermediateValue> {
+        let val = self.paren(val)?;
+        Ok(match val {
+            HigherIntermediateValue::UnaryOp(op, val) => {
+                HigherIntermediateValue::Literal(self.unary_op(op, *val)?)
+            }
+            HigherIntermediateValue::Function(function, args) => {
+                HigherIntermediateValue::Literal(self.parser.call_function(function, args)?)
+            }
+            val => val,
+        })
+    }
+
+    fn add(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(match left {
+            Value::Map(..) | Value::Function(..) => {
+                return Err((
+                    format!("{} isn't a valid CSS value.", left.inspect(self.span)?),
+                    self.span,
+                )
+                    .into())
+            }
+            Value::ArgList(..) => todo!(),
+            Value::Important | Value::True | Value::False => match right {
+                Value::String(s, QuoteKind::Quoted) => Value::String(
+                    format!("{}{}", left.to_css_string(self.span)?, s),
+                    QuoteKind::Quoted,
+                ),
+                Value::Null => {
+                    Value::String(left.to_css_string(self.span)?.into_owned(), QuoteKind::None)
+                }
+                _ => Value::String(
+                    format!(
+                        "{}{}",
+                        left.to_css_string(self.span)?,
+                        right.to_css_string(self.span)?
+                    ),
+                    QuoteKind::None,
+                ),
+            },
+            Value::Null => match right {
+                Value::Null => Value::Null,
+                _ => Value::String(
+                    right.to_css_string(self.span)?.into_owned(),
+                    QuoteKind::None,
+                ),
+            },
+            Value::Dimension(num, unit) => match right {
+                Value::Dimension(num2, unit2) => {
+                    if !unit.comparable(&unit2) {
+                        return Err((
+                            format!("Incompatible units {} and {}.", unit2, unit),
+                            self.span,
+                        )
+                            .into());
+                    }
+                    if unit == unit2 {
+                        Value::Dimension(num + num2, unit)
+                    } else if unit == Unit::None {
+                        Value::Dimension(num + num2, unit2)
+                    } else if unit2 == Unit::None {
+                        Value::Dimension(num + num2, unit)
+                    } else {
+                        Value::Dimension(
+                            num + num2
+                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
+                                    [unit2.to_string().as_str()]
+                                .clone(),
+                            unit,
+                        )
+                    }
+                }
+                Value::String(s, q) => Value::String(format!("{}{}{}", num, unit, s), q),
+                Value::Null => Value::String(format!("{}{}", num, unit), QuoteKind::None),
+                Value::True | Value::False | Value::List(..) => Value::String(
+                    format!("{}{}{}", num, unit, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                Value::Map(..) | Value::Function(..) => {
+                    return Err((
+                        format!("{} isn't a valid CSS value.", right.inspect(self.span)?),
+                        self.span,
+                    )
+                        .into())
+                }
+                _ => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{}{} + {}\".",
+                            num,
+                            unit,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+            },
+            Value::Color(c) => match right {
+                Value::String(s, q) => Value::String(format!("{}{}", c, s), q),
+                Value::Null => Value::String(c.to_string(), QuoteKind::None),
+                Value::List(..) => Value::String(
+                    format!("{}{}", c, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                _ => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{} + {}\".",
+                            c,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+            },
+            Value::String(text, quotes) => match right {
+                Value::String(text2, ..) => Value::String(text + &text2, quotes),
+                _ => Value::String(text + &right.to_css_string(self.span)?, quotes),
+            },
+            Value::List(..) => match right {
+                Value::String(s, q) => {
+                    Value::String(format!("{}{}", left.to_css_string(self.span)?, s), q)
+                }
+                _ => Value::String(
+                    format!(
+                        "{}{}",
+                        left.to_css_string(self.span)?,
+                        right.to_css_string(self.span)?
+                    ),
+                    QuoteKind::None,
+                ),
+            },
+        })
+    }
+
+    fn sub(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(match left {
+            Value::Null => Value::String(
+                format!("-{}", right.to_css_string(self.span)?),
+                QuoteKind::None,
+            ),
+            Value::Dimension(num, unit) => match right {
+                Value::Dimension(num2, unit2) => {
+                    if !unit.comparable(&unit2) {
+                        return Err((
+                            format!("Incompatible units {} and {}.", unit2, unit),
+                            self.span,
+                        )
+                            .into());
+                    }
+                    if unit == unit2 {
+                        Value::Dimension(num - num2, unit)
+                    } else if unit == Unit::None {
+                        Value::Dimension(num - num2, unit2)
+                    } else if unit2 == Unit::None {
+                        Value::Dimension(num - num2, unit)
+                    } else {
+                        Value::Dimension(
+                            num - num2
+                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
+                                    [unit2.to_string().as_str()]
+                                .clone(),
+                            unit,
+                        )
+                    }
+                }
+                Value::List(..) | Value::String(..) => Value::String(
+                    format!("{}{}-{}", num, unit, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                Value::Map(..) | Value::Function(..) => {
+                    return Err((
+                        format!("{} isn't a valid CSS value.", right.inspect(self.span)?),
+                        self.span,
+                    )
+                        .into())
+                }
+                _ => todo!(),
+            },
+            Value::Color(c) => match right {
+                Value::String(s, q) => {
+                    Value::String(format!("{}-{}{}{}", c, q, s, q), QuoteKind::None)
+                }
+                Value::Null => Value::String(format!("{}-", c), QuoteKind::None),
+                Value::Dimension(..) | Value::Color(..) => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{} - {}\".",
+                            c,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+                _ => Value::String(
+                    format!("{}-{}", c, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+            },
+            Value::String(..) => Value::String(
+                format!(
+                    "{}-{}",
+                    left.to_css_string(self.span)?,
+                    right.to_css_string(self.span)?
+                ),
+                QuoteKind::None,
+            ),
+            Value::List(..) => match right {
+                Value::String(s, q) => Value::String(
+                    format!("{}-{}{}{}", left.to_css_string(self.span)?, q, s, q),
+                    QuoteKind::None,
+                ),
+                _ => Value::String(
+                    format!(
+                        "{}-{}",
+                        left.to_css_string(self.span)?,
+                        right.to_css_string(self.span)?
+                    ),
+                    QuoteKind::None,
+                ),
+            },
+            _ => match right {
+                Value::String(s, q) => Value::String(
+                    format!("{}-{}{}{}", left.to_css_string(self.span)?, q, s, q),
+                    QuoteKind::None,
+                ),
+                Value::Null => Value::String(
+                    format!("{}-", left.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                _ => Value::String(
+                    format!(
+                        "{}-{}",
+                        left.to_css_string(self.span)?,
+                        right.to_css_string(self.span)?
+                    ),
+                    QuoteKind::None,
+                ),
+            },
+        })
+    }
+
+    fn mul(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(match left {
+            Value::Null => todo!(),
+            Value::Dimension(num, unit) => match right {
+                Value::Dimension(num2, unit2) => {
+                    if unit == Unit::None {
+                        Value::Dimension(num * num2, unit2)
+                    } else if unit2 == Unit::None {
+                        Value::Dimension(num * num2, unit)
+                    } else if let Unit::Mul(u) = unit {
+                        let mut unit1 = u.into_vec();
+                        unit1.push(unit2);
+                        Value::Dimension(num * num2, Unit::Mul(unit1.into_boxed_slice()))
+                    } else if let Unit::Mul(u2) = unit2 {
+                        let mut u = vec![unit];
+                        u.append(&mut u2.into_vec());
+                        Value::Dimension(num * num2, Unit::Mul(u.into_boxed_slice()))
+                    } else {
+                        Value::Dimension(
+                            num * num2,
+                            Unit::Mul(vec![unit, unit2].into_boxed_slice()),
+                        )
+                    }
+                }
+                _ => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{}{} * {}\".",
+                            num,
+                            unit,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+            },
+            _ => {
+                return Err((
+                    format!(
+                        "Undefined operation \"{} * {}\".",
+                        left.inspect(self.span)?,
+                        right.inspect(self.span)?
+                    ),
+                    self.span,
+                )
+                    .into())
+            }
+        })
+    }
+
+    fn div(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(match left {
+            Value::Null => todo!(),
+            Value::Dimension(num, unit) => match right {
+                Value::Dimension(num2, unit2) => {
+                    if !unit.comparable(&unit2) {
+                        return Err((
+                            format!("Incompatible units {} and {}.", unit2, unit),
+                            self.span,
+                        )
+                            .into());
+                    }
+                    if unit == unit2 {
+                        Value::Dimension(num / num2, Unit::None)
+                    } else if unit == Unit::None {
+                        todo!("inverse units")
+                    } else if unit2 == Unit::None {
+                        Value::Dimension(num / num2, unit)
+                    } else {
+                        Value::Dimension(
+                            num / (num2
+                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
+                                    [unit2.to_string().as_str()]
+                                .clone()),
+                            Unit::None,
+                        )
+                    }
+                }
+                Value::String(s, q) => {
+                    Value::String(format!("{}{}/{}{}{}", num, unit, q, s, q), QuoteKind::None)
+                }
+                Value::List(..)
+                | Value::True
+                | Value::False
+                | Value::Important
+                | Value::Color(..) => Value::String(
+                    format!("{}{}/{}", num, unit, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                Value::Null => Value::String(format!("{}{}/", num, unit), QuoteKind::None),
+                Value::Map(..) | Value::Function(..) => {
+                    return Err((
+                        format!("{} isn't a valid CSS value.", right.inspect(self.span)?),
+                        self.span,
+                    )
+                        .into())
+                }
+                Value::ArgList(..) => todo!(),
+            },
+            Value::Color(c) => match right {
+                Value::String(s, q) => {
+                    Value::String(format!("{}/{}{}{}", c, q, s, q), QuoteKind::None)
+                }
+                Value::Null => Value::String(format!("{}/", c), QuoteKind::None),
+                Value::Dimension(..) | Value::Color(..) => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{} / {}\".",
+                            c,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+                _ => Value::String(
+                    format!("{}/{}", c, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+            },
+            Value::String(s1, q1) => match right {
+                Value::String(s2, q2) => Value::String(
+                    format!("{}{}{}/{}{}{}", q1, s1, q1, q2, s2, q2),
+                    QuoteKind::None,
+                ),
+                Value::Important
+                | Value::True
+                | Value::False
+                | Value::Dimension(..)
+                | Value::Color(..) => Value::String(
+                    format!("{}{}{}/{}", q1, s1, q1, right.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                Value::Null => Value::String(format!("{}{}{}/", q1, s1, q1), QuoteKind::None),
+                _ => todo!(),
+            },
+            _ => match right {
+                Value::String(s, q) => Value::String(
+                    format!("{}/{}{}{}", left.to_css_string(self.span)?, q, s, q),
+                    QuoteKind::None,
+                ),
+                Value::Null => Value::String(
+                    format!("{}/", left.to_css_string(self.span)?),
+                    QuoteKind::None,
+                ),
+                _ => Value::String(
+                    format!(
+                        "{}/{}",
+                        left.to_css_string(self.span)?,
+                        right.to_css_string(self.span)?
+                    ),
+                    QuoteKind::None,
+                ),
+            },
+        })
+    }
+
+    fn rem(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(match left {
+            Value::Dimension(n, u) => match right {
+                Value::Dimension(n2, u2) => {
+                    if !u.comparable(&u2) {
+                        return Err(
+                            (format!("Incompatible units {} and {}.", u2, u), self.span).into()
+                        );
+                    }
+                    if u == u2 {
+                        Value::Dimension(n % n2, u)
+                    } else if u == Unit::None {
+                        Value::Dimension(n % n2, u2)
+                    } else if u2 == Unit::None {
+                        Value::Dimension(n % n2, u)
+                    } else {
+                        Value::Dimension(n, u)
+                    }
+                }
+                _ => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{} % {}\".",
+                            Value::Dimension(n, u).inspect(self.span)?,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+            },
+            _ => {
+                return Err((
+                    format!(
+                        "Undefined operation \"{} % {}\".",
+                        left.inspect(self.span)?,
+                        right.inspect(self.span)?
+                    ),
+                    self.span,
+                )
+                    .into())
+            }
+        })
+    }
+
+    fn and(left: HigherIntermediateValue, right: HigherIntermediateValue) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(if left.is_true() { right } else { left })
+    }
+
+    fn or(left: HigherIntermediateValue, right: HigherIntermediateValue) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(if left.is_true() { left } else { right })
+    }
+
+    pub fn equal(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(Value::bool(match left {
+            Value::String(s1, ..) => match right {
+                Value::String(s2, ..) => s1 == s2,
+                _ => false,
+            },
+            Value::Dimension(n, unit) => match right {
+                Value::Dimension(n2, unit2) => {
+                    if !unit.comparable(&unit2) {
+                        false
+                    } else if unit == unit2 {
+                        n == n2
+                    } else if unit == Unit::None || unit2 == Unit::None {
+                        false
+                    } else {
+                        n == (n2
+                            * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
+                                [unit2.to_string().as_str()]
+                            .clone())
+                    }
+                }
+                _ => false,
+            },
+            Value::List(list1, sep1, brackets1) => match right {
+                Value::List(list2, sep2, brackets2) => {
+                    if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
+                        false
+                    } else {
+                        let mut equals = true;
+                        for (a, b) in list1.into_iter().zip(list2) {
+                            if !self
+                                .equal(
+                                    HigherIntermediateValue::Literal(a),
+                                    HigherIntermediateValue::Literal(b),
+                                )?
+                                .is_true()
+                            {
+                                equals = false;
+                                break;
+                            }
+                        }
+                        equals
+                    }
+                }
+                _ => false,
+            },
+            s => s == right,
+        }))
+    }
+
+    fn not_equal(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        Ok(Value::bool(match left {
+            Value::String(s1, ..) => match right {
+                Value::String(s2, ..) => s1 != s2,
+                _ => true,
+            },
+            Value::Dimension(n, unit) => match right {
+                Value::Dimension(n2, unit2) => {
+                    if !unit.comparable(&unit2) {
+                        true
+                    } else if unit == unit2 {
+                        n != n2
+                    } else if unit == Unit::None || unit2 == Unit::None {
+                        true
+                    } else {
+                        n != (n2
+                            * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
+                                [unit2.to_string().as_str()]
+                            .clone())
+                    }
+                }
+                _ => true,
+            },
+            Value::List(list1, sep1, brackets1) => match right {
+                Value::List(list2, sep2, brackets2) => {
+                    if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
+                        true
+                    } else {
+                        let mut equals = false;
+                        for (a, b) in list1.into_iter().zip(list2) {
+                            if self
+                                .not_equal(
+                                    HigherIntermediateValue::Literal(a),
+                                    HigherIntermediateValue::Literal(b),
+                                )?
+                                .is_true()
+                            {
+                                equals = true;
+                                break;
+                            }
+                        }
+                        equals
+                    }
+                }
+                _ => true,
+            },
+            s => s != right,
+        }))
+    }
+
+    fn cmp(
+        &self,
+        left: HigherIntermediateValue,
+        op: Op,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        let left = match left {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let right = match right {
+            HigherIntermediateValue::Literal(v) => v,
+            v => panic!("{:?}", v),
+        };
+        let ordering = match left {
+            Value::Dimension(num, unit) => match &right {
+                Value::Dimension(num2, unit2) => {
+                    if !unit.comparable(unit2) {
+                        return Err((
+                            format!("Incompatible units {} and {}.", unit2, unit),
+                            self.span,
+                        )
+                            .into());
+                    }
+                    if &unit == unit2 || unit == Unit::None || unit2 == &Unit::None {
+                        num.cmp(num2)
+                    } else {
+                        num.cmp(
+                            &(num2.clone()
+                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
+                                    [unit2.to_string().as_str()]
+                                .clone()),
+                        )
+                    }
+                }
+                v => {
+                    return Err((
+                        format!(
+                            "Undefined operation \"{} {} {}\".",
+                            v.inspect(self.span)?,
+                            op,
+                            right.inspect(self.span)?
+                        ),
+                        self.span,
+                    )
+                        .into())
+                }
+            },
+            _ => {
+                return Err((
+                    format!(
+                        "Undefined operation \"{} {} {}\".",
+                        left.inspect(self.span)?,
+                        op,
+                        right.inspect(self.span)?
+                    ),
+                    self.span,
+                )
+                    .into())
+            }
+        };
+        Ok(match op {
+            Op::GreaterThan => match ordering {
+                Ordering::Greater => Value::True,
+                Ordering::Less | Ordering::Equal => Value::False,
+            },
+            Op::GreaterThanEqual => match ordering {
+                Ordering::Greater | Ordering::Equal => Value::True,
+                Ordering::Less => Value::False,
+            },
+            Op::LessThan => match ordering {
+                Ordering::Less => Value::True,
+                Ordering::Greater | Ordering::Equal => Value::False,
+            },
+            Op::LessThanEqual => match ordering {
+                Ordering::Less | Ordering::Equal => Value::True,
+                Ordering::Greater => Value::False,
+            },
+            _ => unreachable!(),
+        })
+    }
+
+    pub fn greater_than(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        self.cmp(left, Op::GreaterThan, right)
+    }
+
+    fn greater_than_or_equal(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        self.cmp(left, Op::GreaterThanEqual, right)
+    }
+
+    pub fn less_than(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        self.cmp(left, Op::LessThan, right)
+    }
+
+    fn less_than_or_equal(
+        &self,
+        left: HigherIntermediateValue,
+        right: HigherIntermediateValue,
+    ) -> SassResult<Value> {
+        self.cmp(left, Op::LessThanEqual, right)
+    }
+}
diff --git a/src/parse/value/mod.rs b/src/parse/value/mod.rs
index 06f1a3c..d2de526 100644
--- a/src/parse/value/mod.rs
+++ b/src/parse/value/mod.rs
@@ -1 +1,5 @@
+pub(crate) use eval::{HigherIntermediateValue, ValueVisitor};
+
+mod css_function;
+mod eval;
 mod parse;
diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs
index 04b7093..bb3c012 100644
--- a/src/parse/value/parse.rs
+++ b/src/parse/value/parse.rs
@@ -1,4 +1,4 @@
-use std::{borrow::Borrow, iter::Iterator, mem};
+use std::{iter::Iterator, mem};
 
 use num_bigint::BigInt;
 use num_rational::{BigRational, Rational64};
@@ -15,16 +15,42 @@ use crate::{
     error::SassResult,
     unit::Unit,
     utils::{
-        as_hex, devour_whitespace, eat_number, hex_char_for, is_name, peek_ident_no_interpolation,
-        peek_until_closing_curly_brace, peek_whitespace, read_until_char, read_until_closing_paren,
+        devour_whitespace, eat_number, read_until_char, read_until_closing_paren,
         read_until_closing_square_brace, IsWhitespace,
     },
-    value::{Number, SassMap, Value},
+    value::{Number, SassFunction, SassMap, Value},
     Token,
 };
 
+use super::eval::{HigherIntermediateValue, ValueVisitor};
+
 use super::super::Parser;
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+enum IntermediateValue {
+    Value(HigherIntermediateValue),
+    Op(Op),
+    Bracketed(Vec<Token>),
+    Paren(Vec<Token>),
+    Comma,
+    Whitespace,
+}
+
+impl IntermediateValue {
+    const fn span(self, span: Span) -> Spanned<Self> {
+        Spanned { node: self, span }
+    }
+}
+
+impl IsWhitespace for IntermediateValue {
+    fn is_whitespace(&self) -> bool {
+        if self == &IntermediateValue::Whitespace {
+            return true;
+        }
+        false
+    }
+}
+
 impl<'a> Parser<'a> {
     pub(crate) fn parse_value(&mut self) -> SassResult<Spanned<Value>> {
         self.whitespace();
@@ -68,17 +94,18 @@ impl<'a> Parser<'a> {
                             .ok_or(("Expected expression.", val.span))?
                             .span;
                         comma_separated.push(
-                            Value::List(
+                            HigherIntermediateValue::Literal(Value::List(
                                 mem::take(&mut space_separated)
                                     .into_iter()
-                                    .map(|a| {
+                                    .map(move |a| {
                                         span = span.merge(a.span);
                                         a.node
                                     })
-                                    .collect(),
+                                    .map(|a| ValueVisitor::new(iter.parser, span).eval(a))
+                                    .collect::<SassResult<Vec<Value>>>()?,
                                 ListSeparator::Space,
                                 Brackets::None,
-                            )
+                            ))
                             .span(span),
                         );
                     }
@@ -87,18 +114,28 @@ impl<'a> Parser<'a> {
                     last_was_whitespace = false;
                     if t.is_empty() {
                         space_separated.push(
-                            Value::List(Vec::new(), ListSeparator::Space, Brackets::Bracketed)
-                                .span(val.span),
+                            HigherIntermediateValue::Literal(Value::List(
+                                Vec::new(),
+                                ListSeparator::Space,
+                                Brackets::Bracketed,
+                            ))
+                            .span(val.span),
                         );
                         continue;
                     }
-                    space_separated.push(match iter.parser.parse_value_from_vec(t)?.node {
-                        Value::List(v, sep, Brackets::None) => {
-                            Value::List(v, sep, Brackets::Bracketed).span(val.span)
-                        }
-                        v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed)
-                            .span(val.span),
-                    })
+                    space_separated.push(
+                        HigherIntermediateValue::Literal(
+                            match iter.parser.parse_value_from_vec(t)?.node {
+                                Value::List(v, sep, Brackets::None) => {
+                                    Value::List(v, sep, Brackets::Bracketed)
+                                }
+                                v => {
+                                    Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed)
+                                }
+                            },
+                        )
+                        .span(val.span),
+                    )
                 }
                 IntermediateValue::Paren(t) => {
                     last_was_whitespace = false;
@@ -115,25 +152,36 @@ impl<'a> Parser<'a> {
                 comma_separated.push(space_separated.pop().unwrap());
             } else if !space_separated.is_empty() {
                 comma_separated.push(
-                    Value::List(
-                        space_separated.into_iter().map(|a| a.node).collect(),
+                    HigherIntermediateValue::Literal(Value::List(
+                        space_separated
+                            .into_iter()
+                            .map(|a| ValueVisitor::new(self, span).eval(a.node))
+                            .collect::<SassResult<Vec<Value>>>()?,
                         ListSeparator::Space,
                         Brackets::None,
-                    )
+                    ))
                     .span(span),
                 );
             }
             Value::List(
-                comma_separated.into_iter().map(|a| a.node).collect(),
+                comma_separated
+                    .into_iter()
+                    .map(|a| ValueVisitor::new(self, span).eval(a.node))
+                    .collect::<SassResult<Vec<Value>>>()?,
                 ListSeparator::Comma,
                 Brackets::None,
             )
             .span(span)
         } else if space_separated.len() == 1 {
-            space_separated.pop().unwrap()
+            ValueVisitor::new(self, span)
+                .eval(space_separated.pop().unwrap().node)?
+                .span(span)
         } else {
             Value::List(
-                space_separated.into_iter().map(|a| a.node).collect(),
+                space_separated
+                    .into_iter()
+                    .map(|a| ValueVisitor::new(self, span).eval(a.node))
+                    .collect::<SassResult<Vec<Value>>>()?,
                 ListSeparator::Space,
                 Brackets::None,
             )
@@ -175,7 +223,10 @@ impl<'a> Parser<'a> {
             s.push(':');
             s.push_str(&self.eat_progid()?);
             return Ok(Spanned {
-                node: IntermediateValue::Value(Value::String(s, QuoteKind::None)),
+                node: IntermediateValue::Value(HigherIntermediateValue::Literal(Value::String(
+                    s,
+                    QuoteKind::None,
+                ))),
                 span,
             });
         }
@@ -188,10 +239,10 @@ impl<'a> Parser<'a> {
                     Some(val) => {
                         self.toks.truncate_iterator_to_cursor();
                         self.toks.next();
-                        return Ok(
-                            IntermediateValue::Value(Value::String(val, QuoteKind::None))
-                                .span(span),
-                        );
+                        return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
+                            Value::String(val, QuoteKind::None),
+                        ))
+                        .span(span));
                     }
                     None => {
                         self.toks.reset_cursor();
@@ -202,10 +253,10 @@ impl<'a> Parser<'a> {
                     Some(val) => {
                         self.toks.truncate_iterator_to_cursor();
                         self.toks.next();
-                        return Ok(
-                            IntermediateValue::Value(Value::String(val, QuoteKind::None))
-                                .span(span),
-                        );
+                        return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
+                            Value::String(val, QuoteKind::None),
+                        ))
+                        .span(span));
                     }
                     None => {
                         self.toks.reset_cursor();
@@ -217,7 +268,7 @@ impl<'a> Parser<'a> {
             let ident_as_string = as_ident.clone().into_inner();
             let func = match self.scopes.last().get_fn(
                 Spanned {
-                    node: as_ident,
+                    node: as_ident.clone(),
                     span,
                 },
                 self.global_scope,
@@ -225,10 +276,11 @@ impl<'a> Parser<'a> {
                 Ok(f) => f,
                 Err(_) => {
                     if let Some(f) = GLOBAL_FUNCTIONS.get(ident_as_string.as_str()) {
-                        return Ok(
-                            IntermediateValue::Value(f.0(self.parse_call_args()?, self)?)
-                                .span(span),
-                        );
+                        return Ok(IntermediateValue::Value(HigherIntermediateValue::Function(
+                            SassFunction::Builtin(f.clone(), as_ident),
+                            self.parse_call_args()?,
+                        ))
+                        .span(span));
                     } else {
                         // check for special cased CSS functions
                         match lower.as_str() {
@@ -243,34 +295,44 @@ impl<'a> Parser<'a> {
                             _ => s.push_str(&self.parse_call_args()?.to_css_string(self)?),
                         }
 
-                        return Ok(
-                            IntermediateValue::Value(Value::String(s, QuoteKind::None)).span(span)
-                        );
+                        return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
+                            Value::String(s, QuoteKind::None),
+                        ))
+                        .span(span));
                     }
                 }
             };
 
             let call_args = self.parse_call_args()?;
-            return Ok(IntermediateValue::Value(self.eval_function(func, call_args)?).span(span));
+            return Ok(IntermediateValue::Value(HigherIntermediateValue::Function(
+                SassFunction::UserDefined(Box::new(func), as_ident),
+                call_args,
+            ))
+            .span(span));
         }
 
         // check for named colors
         if let Some(c) = NAMED_COLORS.get_by_name(lower.as_str()) {
-            return Ok(IntermediateValue::Value(Value::Color(Box::new(Color::new(
-                c[0], c[1], c[2], c[3], s,
-            ))))
-            .span(span));
+            return Ok(
+                IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Color(Box::new(
+                    Color::new(c[0], c[1], c[2], c[3], s),
+                ))))
+                .span(span),
+            );
         }
 
         // check for keywords
         Ok(match lower.as_str() {
-            "true" => IntermediateValue::Value(Value::True),
-            "false" => IntermediateValue::Value(Value::False),
-            "null" => IntermediateValue::Value(Value::Null),
+            "true" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::True)),
+            "false" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::False)),
+            "null" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Null)),
             "not" => IntermediateValue::Op(Op::Not),
             "and" => IntermediateValue::Op(Op::And),
             "or" => IntermediateValue::Op(Op::Or),
-            _ => IntermediateValue::Value(Value::String(s, QuoteKind::None)),
+            _ => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::String(
+                s,
+                QuoteKind::None,
+            ))),
         }
         .span(span))
     }
@@ -336,30 +398,36 @@ impl<'a> Parser<'a> {
                 let n = if val.dec_len == 0 {
                     if val.num.len() <= 18 && val.times_ten.is_empty() {
                         let n = Rational64::new_raw(parse_i64(&val.num), 1);
-                        return Some(Ok(IntermediateValue::Value(Value::Dimension(
-                            Number::new_small(n),
-                            unit,
-                        ))
+                        return Some(Ok(IntermediateValue::Value(
+                            HigherIntermediateValue::Literal(Value::Dimension(
+                                Number::new_small(n),
+                                unit,
+                            )),
+                        )
                         .span(span)));
                     }
                     BigRational::new_raw(val.num.parse::<BigInt>().unwrap(), BigInt::one())
                 } else {
                     if val.num.len() <= 18 && val.times_ten.is_empty() {
                         let n = Rational64::new(parse_i64(&val.num), pow(10, val.dec_len));
-                        return Some(Ok(IntermediateValue::Value(Value::Dimension(
-                            Number::new_small(n),
-                            unit,
-                        ))
+                        return Some(Ok(IntermediateValue::Value(
+                            HigherIntermediateValue::Literal(Value::Dimension(
+                                Number::new_small(n),
+                                unit,
+                            )),
+                        )
                         .span(span)));
                     }
                     BigRational::new(val.num.parse().unwrap(), pow(BigInt::from(10), val.dec_len))
                 };
 
                 if val.times_ten.is_empty() {
-                    return Some(Ok(IntermediateValue::Value(Value::Dimension(
-                        Number::new_big(n),
-                        unit,
-                    ))
+                    return Some(Ok(IntermediateValue::Value(
+                        HigherIntermediateValue::Literal(Value::Dimension(
+                            Number::new_big(n),
+                            unit,
+                        )),
+                    )
                     .span(span)));
                 }
 
@@ -383,8 +451,11 @@ impl<'a> Parser<'a> {
                     BigRational::new(BigInt::one(), times_ten)
                 };
 
-                IntermediateValue::Value(Value::Dimension(Number::new_big(n * times_ten), unit))
-                    .span(span)
+                IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Dimension(
+                    Number::new_big(n * times_ten),
+                    unit,
+                )))
+                .span(span)
             }
             '(' => {
                 let mut span = self.toks.next().unwrap().pos();
@@ -404,10 +475,13 @@ impl<'a> Parser<'a> {
             '&' => {
                 let span = self.toks.next().unwrap().pos();
                 if self.super_selectors.is_empty() && !self.at_root_has_selector && !self.at_root {
-                    IntermediateValue::Value(Value::Null).span(span)
-                } else {
-                    IntermediateValue::Value(self.super_selectors.last().clone().into_value())
+                    IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Null))
                         .span(span)
+                } else {
+                    IntermediateValue::Value(HigherIntermediateValue::Literal(
+                        self.super_selectors.last().clone().into_value(),
+                    ))
+                    .span(span)
                 }
             }
             '#' => {
@@ -422,7 +496,7 @@ impl<'a> Parser<'a> {
                     Ok(v) => v,
                     Err(e) => return Some(Err(e)),
                 };
-                IntermediateValue::Value(hex.node).span(hex.span)
+                IntermediateValue::Value(HigherIntermediateValue::Literal(hex.node)).span(hex.span)
             }
             q @ '"' | q @ '\'' => {
                 let span_start = self.toks.next().unwrap().pos();
@@ -430,7 +504,8 @@ impl<'a> Parser<'a> {
                     Ok(v) => v,
                     Err(e) => return Some(Err(e)),
                 };
-                IntermediateValue::Value(node).span(span_start.merge(span))
+                IntermediateValue::Value(HigherIntermediateValue::Literal(node))
+                    .span(span_start.merge(span))
             }
             '[' => {
                 let mut span = self.toks.next().unwrap().pos();
@@ -453,13 +528,13 @@ impl<'a> Parser<'a> {
                     Err(e) => return Some(Err(e)),
                 };
                 let span = val.span;
-                IntermediateValue::Value(
+                IntermediateValue::Value(HigherIntermediateValue::Literal(
                     match self.scopes.last().get_var(val, self.global_scope) {
                         Ok(v) => v,
                         Err(e) => return Some(Err(e)),
                     }
                     .node,
-                )
+                ))
                 .span(span)
             }
             '+' => {
@@ -526,7 +601,10 @@ impl<'a> Parser<'a> {
                 // supporting `!optional` in `@extend`. In the future, we should have a better
                 // check for `!optional` as this technically allows `!optional` everywhere
                 match v.node.to_ascii_lowercase().as_str() {
-                    "important" => IntermediateValue::Value(Value::Important).span(span),
+                    "important" => {
+                        IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Important))
+                            .span(span)
+                    }
                     "optional" => return None,
                     _ => return Some(Err(("Expected \"important\".", span).into())),
                 }
@@ -631,381 +709,6 @@ impl<'a> Parser<'a> {
         let color = Color::new(red, green, blue, alpha, s);
         Ok(Value::Color(Box::new(color)).span(self.span_before))
     }
-
-    fn eat_calc_args(&mut self, buf: &mut String) -> SassResult<()> {
-        buf.reserve(2);
-        buf.push('(');
-        let mut nesting = 0;
-        while let Some(tok) = self.toks.next() {
-            match tok.kind {
-                ' ' | '\t' | '\n' => {
-                    self.whitespace();
-                    buf.push(' ');
-                }
-                '#' => {
-                    if let Some(Token { kind: '{', pos }) = self.toks.peek() {
-                        self.span_before = *pos;
-                        self.toks.next();
-                        let interpolation = self.parse_interpolation()?;
-                        buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
-                    } else {
-                        buf.push('#');
-                    }
-                }
-                '(' => {
-                    nesting += 1;
-                    buf.push('(');
-                }
-                ')' => {
-                    if nesting == 0 {
-                        break;
-                    } else {
-                        nesting -= 1;
-                        buf.push(')');
-                    }
-                }
-                c => buf.push(c),
-            }
-        }
-        buf.push(')');
-        Ok(())
-    }
-
-    fn eat_progid(&mut self) -> SassResult<String> {
-        let mut string = String::new();
-        let mut span = self.toks.peek().unwrap().pos();
-        while let Some(tok) = self.toks.next() {
-            span = span.merge(tok.pos());
-            match tok.kind {
-                'a'..='z' | 'A'..='Z' | '.' => {
-                    string.push(tok.kind);
-                }
-                '(' => {
-                    self.eat_calc_args(&mut string)?;
-                    break;
-                }
-                _ => return Err(("expected \"(\".", span).into()),
-            }
-        }
-        Ok(string)
-    }
-
-    fn try_eat_url(&mut self) -> SassResult<Option<String>> {
-        let mut buf = String::from("url(");
-        peek_whitespace(self.toks);
-        while let Some(tok) = self.toks.peek() {
-            let kind = tok.kind;
-            self.toks.advance_cursor();
-            if kind == '!'
-                || kind == '%'
-                || kind == '&'
-                || (kind >= '*' && kind <= '~')
-                || kind as u32 >= 0x0080
-            {
-                buf.push(kind);
-            } else if kind == '\\' {
-                buf.push_str(&self.peek_escape()?);
-            } else if kind == '#' {
-                if let Some(Token { kind: '{', .. }) = self.toks.peek() {
-                    self.toks.advance_cursor();
-                    let interpolation = self.peek_interpolation()?;
-                    match interpolation.node {
-                        Value::String(ref s, ..) => buf.push_str(s),
-                        v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
-                    };
-                } else {
-                    buf.push('#');
-                }
-            } else if kind == ')' {
-                buf.push(')');
-                self.toks.truncate_iterator_to_cursor();
-                self.toks.next();
-                return Ok(Some(buf));
-            } else if kind.is_whitespace() {
-                peek_whitespace(self.toks);
-                let next = match self.toks.peek() {
-                    Some(v) => v,
-                    None => break,
-                };
-                if next.kind == ')' {
-                    buf.push(')');
-                    self.toks.truncate_iterator_to_cursor();
-                    self.toks.next();
-                    return Ok(Some(buf));
-                } else {
-                    break;
-                }
-            } else {
-                break;
-            }
-        }
-        self.toks.reset_cursor();
-        Ok(None)
-    }
-
-    fn peek_number(&mut self) -> SassResult<Option<String>> {
-        let mut buf = String::new();
-
-        let num = self.peek_whole_number();
-        buf.push_str(&num);
-
-        self.toks.advance_cursor();
-
-        if let Some(Token { kind: '.', .. }) = self.toks.peek() {
-            self.toks.advance_cursor();
-            let num = self.peek_whole_number();
-            if num.is_empty() {
-                return Ok(None);
-            }
-            buf.push_str(&num);
-        } else {
-            self.toks.move_cursor_back().unwrap();
-        }
-
-        let next = match self.toks.peek() {
-            Some(tok) => tok,
-            None => return Ok(Some(buf)),
-        };
-
-        match next.kind {
-            'a'..='z' | 'A'..='Z' | '-' | '_' | '\\' => {
-                let unit = peek_ident_no_interpolation(self.toks, true, self.span_before)?.node;
-
-                buf.push_str(&unit);
-            }
-            '%' => {
-                self.toks.advance_cursor();
-                buf.push('%');
-            }
-            _ => {}
-        }
-
-        Ok(Some(buf))
-    }
-
-    fn peek_whole_number(&mut self) -> String {
-        let mut buf = String::new();
-        while let Some(tok) = self.toks.peek() {
-            if tok.kind.is_ascii_digit() {
-                buf.push(tok.kind);
-                self.toks.advance_cursor();
-            } else {
-                return buf;
-            }
-        }
-        buf
-    }
-
-    fn try_parse_min_max(
-        &mut self,
-        fn_name: &str,
-        allow_comma: bool,
-    ) -> SassResult<Option<String>> {
-        let mut buf = if allow_comma {
-            format!("{}(", fn_name)
-        } else {
-            String::new()
-        };
-        peek_whitespace(self.toks);
-        while let Some(tok) = self.toks.peek() {
-            let kind = tok.kind;
-            match kind {
-                '+' | '-' | '0'..='9' => {
-                    self.toks.advance_cursor();
-                    if let Some(number) = self.peek_number()? {
-                        buf.push(kind);
-                        buf.push_str(&number);
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                '#' => {
-                    self.toks.advance_cursor();
-                    if let Some(Token { kind: '{', .. }) = self.toks.peek() {
-                        self.toks.advance_cursor();
-                        let interpolation = self.peek_interpolation()?;
-                        match interpolation.node {
-                            Value::String(ref s, ..) => buf.push_str(s),
-                            v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
-                        };
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                'c' | 'C' => {
-                    if let Some(name) = self.try_parse_min_max_function("calc")? {
-                        buf.push_str(&name);
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                'e' | 'E' => {
-                    if let Some(name) = self.try_parse_min_max_function("env")? {
-                        buf.push_str(&name);
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                'v' | 'V' => {
-                    if let Some(name) = self.try_parse_min_max_function("var")? {
-                        buf.push_str(&name);
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                '(' => {
-                    self.toks.advance_cursor();
-                    buf.push('(');
-                    if let Some(val) = self.try_parse_min_max(fn_name, false)? {
-                        buf.push_str(&val);
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                'm' | 'M' => {
-                    self.toks.advance_cursor();
-                    match self.toks.peek() {
-                        Some(Token { kind: 'i', .. }) | Some(Token { kind: 'I', .. }) => {
-                            self.toks.advance_cursor();
-                            if !matches!(self.toks.peek(), Some(Token { kind: 'n', .. }) | Some(Token { kind: 'N', .. }))
-                            {
-                                return Ok(None);
-                            }
-                            buf.push_str("min(")
-                        }
-                        Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => {
-                            self.toks.advance_cursor();
-                            if !matches!(self.toks.peek(), Some(Token { kind: 'x', .. }) | Some(Token { kind: 'X', .. }))
-                            {
-                                return Ok(None);
-                            }
-                            buf.push_str("max(")
-                        }
-                        _ => return Ok(None),
-                    }
-
-                    self.toks.advance_cursor();
-
-                    if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
-                        return Ok(None);
-                    }
-
-                    if let Some(val) = self.try_parse_min_max(fn_name, false)? {
-                        buf.push_str(&val);
-                    } else {
-                        return Ok(None);
-                    }
-                }
-                _ => return Ok(None),
-            }
-
-            peek_whitespace(self.toks);
-
-            let next = match self.toks.peek() {
-                Some(tok) => tok,
-                None => return Ok(None),
-            };
-
-            match next.kind {
-                ')' => {
-                    self.toks.advance_cursor();
-                    buf.push(')');
-                    return Ok(Some(buf));
-                }
-                '+' | '-' | '*' | '/' => {
-                    buf.push(' ');
-                    buf.push(next.kind);
-                    buf.push(' ');
-                    self.toks.advance_cursor();
-                }
-                ',' => {
-                    if !allow_comma {
-                        return Ok(None);
-                    }
-                    self.toks.advance_cursor();
-                    buf.push(',');
-                    buf.push(' ');
-                }
-                _ => return Ok(None),
-            }
-
-            peek_whitespace(self.toks);
-        }
-
-        Ok(Some(buf))
-    }
-
-    #[allow(dead_code, unused_mut, unused_variables, unused_assignments)]
-    fn try_parse_min_max_function(&mut self, fn_name: &'static str) -> SassResult<Option<String>> {
-        let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?.node;
-        ident.make_ascii_lowercase();
-        if ident != fn_name {
-            return Ok(None);
-        }
-        if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
-            return Ok(None);
-        }
-        self.toks.advance_cursor();
-        ident.push('(');
-        todo!("special functions inside `min()` or `max()`")
-    }
-
-    fn peek_interpolation(&mut self) -> SassResult<Spanned<Value>> {
-        let vec = peek_until_closing_curly_brace(self.toks)?;
-        self.toks.advance_cursor();
-        let val = self.parse_value_from_vec(vec)?;
-        Ok(Spanned {
-            node: val.node.eval(val.span)?.node.unquote(),
-            span: val.span,
-        })
-    }
-
-    fn peek_escape(&mut self) -> SassResult<String> {
-        let mut value = 0;
-        let first = match self.toks.peek() {
-            Some(t) => *t,
-            None => return Ok(String::new()),
-        };
-        let mut span = first.pos;
-        if first.kind == '\n' {
-            return Err(("Expected escape sequence.", first.pos()).into());
-        } else if first.kind.is_ascii_hexdigit() {
-            for _ in 0..6 {
-                let next = match self.toks.peek() {
-                    Some(t) => t,
-                    None => break,
-                };
-                if !next.kind.is_ascii_hexdigit() {
-                    break;
-                }
-                value *= 16;
-                value += as_hex(next.kind);
-                span = span.merge(next.pos);
-                self.toks.peek_forward(1);
-            }
-            if self.toks.peek().is_some() && self.toks.peek().unwrap().kind.is_whitespace() {
-                self.toks.peek_forward(1);
-            }
-        } else {
-            value = self.toks.peek_forward(1).unwrap().kind as u32;
-        }
-
-        let c = std::char::from_u32(value).ok_or(("Invalid escape sequence.", span))?;
-        if is_name(c) {
-            Ok(c.to_string())
-        } else if value <= 0x1F || value == 0x7F {
-            let mut buf = String::with_capacity(4);
-            buf.push('\\');
-            if value > 0xF {
-                buf.push(hex_char_for(value >> 4));
-            }
-            buf.push(hex_char_for(value & 0xF));
-            buf.push(' ');
-            Ok(buf)
-        } else {
-            Ok(format!("\\{}", c))
-        }
-    }
 }
 
 struct IntermediateValueIterator<'a, 'b: 'a> {
@@ -1049,7 +752,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
     fn eat_op(
         &mut self,
         op: Spanned<Op>,
-        space_separated: &mut Vec<Spanned<Value>>,
+        space_separated: &mut Vec<Spanned<HigherIntermediateValue>>,
         last_was_whitespace: bool,
     ) -> SassResult<()> {
         match op.node {
@@ -1057,7 +760,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                 self.whitespace();
                 let right = self.single_value()?;
                 space_separated.push(Spanned {
-                    node: Value::UnaryOp(op.node, Box::new(right.node)),
+                    node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)),
                     span: right.span,
                 });
             }
@@ -1066,16 +769,25 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                 let right = self.single_value()?;
                 if let Some(left) = space_separated.pop() {
                     space_separated.push(Spanned {
-                        node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
+                        node: HigherIntermediateValue::BinaryOp(
+                            Box::new(left.node),
+                            op.node,
+                            Box::new(right.node),
+                        ),
                         span: left.span.merge(right.span),
                     });
                 } else {
                     self.whitespace();
                     space_separated.push(Spanned {
-                        node: Value::String(
-                            format!("/{}", right.node.to_css_string(right.span)?),
+                        node: HigherIntermediateValue::Literal(Value::String(
+                            format!(
+                                "/{}",
+                                ValueVisitor::new(self.parser, right.span)
+                                    .eval(right.node)?
+                                    .to_css_string(right.span)?
+                            ),
                             QuoteKind::None,
-                        ),
+                        )),
                         span: op.span.merge(right.span),
                     });
                 }
@@ -1085,14 +797,18 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     self.whitespace();
                     let right = self.single_value()?;
                     space_separated.push(Spanned {
-                        node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
+                        node: HigherIntermediateValue::BinaryOp(
+                            Box::new(left.node),
+                            op.node,
+                            Box::new(right.node),
+                        ),
                         span: left.span.merge(right.span),
                     });
                 } else {
                     self.whitespace();
                     let right = self.single_value()?;
                     space_separated.push(Spanned {
-                        node: Value::UnaryOp(op.node, Box::new(right.node)),
+                        node: HigherIntermediateValue::UnaryOp(op.node, Box::new(right.node)),
                         span: right.span,
                     });
                 }
@@ -1102,7 +818,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     let right = self.single_value()?;
                     if let Some(left) = space_separated.pop() {
                         space_separated.push(Spanned {
-                            node: Value::BinaryOp(
+                            node: HigherIntermediateValue::BinaryOp(
                                 Box::new(left.node),
                                 op.node,
                                 Box::new(right.node),
@@ -1110,27 +826,44 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                             span: left.span.merge(right.span),
                         });
                     } else {
-                        space_separated
-                            .push(right.map_node(|n| Value::UnaryOp(op.node, Box::new(n))));
+                        space_separated.push(
+                            right.map_node(|n| {
+                                HigherIntermediateValue::UnaryOp(op.node, Box::new(n))
+                            }),
+                        );
                     }
                 } else {
                     let right = self.single_value()?;
-                    space_separated.push(right.map_node(|n| Value::UnaryOp(op.node, Box::new(n))));
+                    space_separated.push(
+                        right.map_node(|n| HigherIntermediateValue::UnaryOp(op.node, Box::new(n))),
+                    );
                 }
             }
             Op::And => {
                 self.whitespace();
                 // special case when the value is literally "and"
                 if self.peek().is_none() {
-                    space_separated
-                        .push(Value::String(op.to_string(), QuoteKind::None).span(op.span));
+                    space_separated.push(
+                        HigherIntermediateValue::Literal(Value::String(
+                            op.to_string(),
+                            QuoteKind::None,
+                        ))
+                        .span(op.span),
+                    );
                 } else if let Some(left) = space_separated.pop() {
                     self.whitespace();
-                    if left.node.is_true(left.span)? {
+                    if ValueVisitor::new(self.parser, left.span)
+                        .eval(left.node.clone())?
+                        .is_true()
+                    {
                         let right = self.single_value()?;
                         space_separated.push(
-                            Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
-                                .span(left.span.merge(right.span)),
+                            HigherIntermediateValue::BinaryOp(
+                                Box::new(left.node),
+                                op.node,
+                                Box::new(right.node),
+                            )
+                            .span(left.span.merge(right.span)),
                         );
                     } else {
                         // we explicitly ignore errors here as a workaround for short circuiting
@@ -1154,11 +887,19 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                 self.whitespace();
                 // special case when the value is literally "or"
                 if self.peek().is_none() {
-                    space_separated
-                        .push(Value::String(op.to_string(), QuoteKind::None).span(op.span));
+                    space_separated.push(
+                        HigherIntermediateValue::Literal(Value::String(
+                            op.to_string(),
+                            QuoteKind::None,
+                        ))
+                        .span(op.span),
+                    );
                 } else if let Some(left) = space_separated.pop() {
                     self.whitespace();
-                    if left.node.is_true(left.span)? {
+                    if ValueVisitor::new(self.parser, left.span)
+                        .eval(left.node.clone())?
+                        .is_true()
+                    {
                         // we explicitly ignore errors here as a workaround for short circuiting
                         while let Some(value) = self.peek() {
                             match value {
@@ -1180,8 +921,12 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     } else {
                         let right = self.single_value()?;
                         space_separated.push(
-                            Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
-                                .span(left.span.merge(right.span)),
+                            HigherIntermediateValue::BinaryOp(
+                                Box::new(left.node),
+                                op.node,
+                                Box::new(right.node),
+                            )
+                            .span(left.span.merge(right.span)),
                         );
                     }
                 } else {
@@ -1193,8 +938,12 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     self.whitespace();
                     let right = self.single_value()?;
                     space_separated.push(
-                        Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
-                            .span(left.span.merge(right.span)),
+                        HigherIntermediateValue::BinaryOp(
+                            Box::new(left.node),
+                            op.node,
+                            Box::new(right.node),
+                        )
+                        .span(left.span.merge(right.span)),
                     );
                 } else {
                     return Err(("Expected expression.", op.span).into());
@@ -1204,7 +953,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
         Ok(())
     }
 
-    fn single_value(&mut self) -> SassResult<Spanned<Value>> {
+    fn single_value(&mut self) -> SassResult<Spanned<HigherIntermediateValue>> {
         let next = self
             .next()
             .ok_or(("Expected expression.", self.parser.span_before))??;
@@ -1215,7 +964,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     self.whitespace();
                     let val = self.single_value()?;
                     Spanned {
-                        node: val.node.neg(val.span)?,
+                        node: HigherIntermediateValue::UnaryOp(Op::Minus, Box::new(val.node)),
                         span: next.span.merge(val.span),
                     }
                 }
@@ -1223,7 +972,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     self.whitespace();
                     let val = self.single_value()?;
                     Spanned {
-                        node: Value::UnaryOp(Op::Not, Box::new(val.node)),
+                        node: HigherIntermediateValue::UnaryOp(Op::Not, Box::new(val.node)),
                         span: next.span.merge(val.span),
                     }
                 }
@@ -1235,19 +984,30 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     self.whitespace();
                     let val = self.single_value()?;
                     Spanned {
-                        node: Value::String(
-                            format!("/{}", val.node.to_css_string(val.span)?),
+                        node: HigherIntermediateValue::Literal(Value::String(
+                            format!(
+                                "/{}",
+                                ValueVisitor::new(self.parser, val.span)
+                                    .eval(val.node)?
+                                    .to_css_string(val.span)?
+                            ),
                             QuoteKind::None,
-                        ),
+                        )),
                         span: next.span.merge(val.span),
                     }
                 }
                 Op::And => Spanned {
-                    node: Value::String("and".into(), QuoteKind::None),
+                    node: HigherIntermediateValue::Literal(Value::String(
+                        "and".into(),
+                        QuoteKind::None,
+                    )),
                     span: next.span,
                 },
                 Op::Or => Spanned {
-                    node: Value::String("or".into(), QuoteKind::None),
+                    node: HigherIntermediateValue::Literal(Value::String(
+                        "or".into(),
+                        QuoteKind::None,
+                    )),
                     span: next.span,
                 },
                 _ => {
@@ -1260,10 +1020,10 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
             }
             IntermediateValue::Bracketed(t) => {
                 let v = self.parser.parse_value_from_vec(t)?;
-                match v.node {
+                HigherIntermediateValue::Literal(match v.node {
                     Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed),
                     v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
-                }
+                })
                 .span(v.span)
             }
             IntermediateValue::Paren(t) => {
@@ -1272,16 +1032,24 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
                     span: next.span,
                 })?;
                 Spanned {
-                    node: Value::Paren(Box::new(val.node)),
+                    node: HigherIntermediateValue::Paren(Box::new(val.node)),
                     span: val.span,
                 }
             }
         })
     }
 
-    fn parse_paren(&mut self, t: Spanned<Vec<Token>>) -> SassResult<Spanned<Value>> {
+    fn parse_paren(
+        &mut self,
+        t: Spanned<Vec<Token>>,
+    ) -> SassResult<Spanned<HigherIntermediateValue>> {
         if t.is_empty() {
-            return Ok(Value::List(Vec::new(), ListSeparator::Space, Brackets::None).span(t.span));
+            return Ok(HigherIntermediateValue::Literal(Value::List(
+                Vec::new(),
+                ListSeparator::Space,
+                Brackets::None,
+            ))
+            .span(t.span));
         }
 
         let paren_toks = &mut t.node.into_iter().peekmore();
@@ -1293,7 +1061,9 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
 
         if paren_toks.peek().is_none() {
             return Ok(Spanned {
-                node: Value::Paren(Box::new(key.node)),
+                node: HigherIntermediateValue::Paren(Box::new(HigherIntermediateValue::Literal(
+                    key.node,
+                ))),
                 span: key.span,
             });
         }
@@ -1308,7 +1078,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
 
         if paren_toks.peek().is_none() {
             return Ok(Spanned {
-                node: Value::Map(map),
+                node: HigherIntermediateValue::Literal(Value::Map(map)),
                 span: key.span.merge(val.span),
             });
         }
@@ -1333,7 +1103,7 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
             }
         }
         Ok(Spanned {
-            node: Value::Map(map),
+            node: HigherIntermediateValue::Literal(Value::Map(map)),
             span,
         })
     }
@@ -1348,31 +1118,6 @@ impl IsWhitespace for SassResult<Spanned<IntermediateValue>> {
     }
 }
 
-#[derive(Clone, Debug, Eq, PartialEq)]
-enum IntermediateValue {
-    Value(Value),
-    Op(Op),
-    Bracketed(Vec<Token>),
-    Paren(Vec<Token>),
-    Comma,
-    Whitespace,
-}
-
-impl IntermediateValue {
-    const fn span(self, span: Span) -> Spanned<Self> {
-        Spanned { node: self, span }
-    }
-}
-
-impl IsWhitespace for IntermediateValue {
-    fn is_whitespace(&self) -> bool {
-        if self == &IntermediateValue::Whitespace {
-            return true;
-        }
-        false
-    }
-}
-
 fn parse_i64(s: &str) -> i64 {
     s.as_bytes()
         .iter()
diff --git a/src/parse/variable.rs b/src/parse/variable.rs
index 89d98ea..4bd9791 100644
--- a/src/parse/variable.rs
+++ b/src/parse/variable.rs
@@ -42,32 +42,32 @@ impl<'a> Parser<'a> {
 
         if value.global && !value.default {
             self.global_scope
-                .insert_var(ident.clone(), value.value.clone())?;
+                .insert_var(ident.clone(), value.value.clone());
         }
 
         if value.default {
             if self.at_root && !self.in_control_flow {
                 if !self.global_scope.var_exists_no_global(&ident) {
-                    self.global_scope.insert_var(ident, value.value)?;
+                    self.global_scope.insert_var(ident, value.value);
                 }
             } else {
                 if value.global && !self.global_scope.var_exists_no_global(&ident) {
                     self.global_scope
-                        .insert_var(ident.clone(), value.value.clone())?;
+                        .insert_var(ident.clone(), value.value.clone());
                 }
                 if !self.scopes.last().var_exists_no_global(&ident) {
-                    self.scopes.last_mut().insert_var(ident, value.value)?;
+                    self.scopes.last_mut().insert_var(ident, value.value);
                 }
             }
         } else if self.at_root {
             if self.in_control_flow {
                 if self.global_scope.var_exists_no_global(&ident) {
-                    self.global_scope.insert_var(ident, value.value)?;
+                    self.global_scope.insert_var(ident, value.value);
                 } else {
-                    self.scopes.last_mut().insert_var(ident, value.value)?;
+                    self.scopes.last_mut().insert_var(ident, value.value);
                 }
             } else {
-                self.global_scope.insert_var(ident, value.value)?;
+                self.global_scope.insert_var(ident, value.value);
             }
         } else {
             let len = self.scopes.len();
@@ -78,15 +78,15 @@ impl<'a> Parser<'a> {
                 .filter(|(i, _)| *i != len)
             {
                 if scope.var_exists_no_global(&ident) {
-                    scope.insert_var(ident.clone(), value.value.clone())?;
+                    scope.insert_var(ident.clone(), value.value.clone());
                 }
             }
             if self.scopes.first().var_exists_no_global(&ident) {
                 self.scopes
                     .first_mut()
-                    .insert_var(ident.clone(), value.value.clone())?;
+                    .insert_var(ident.clone(), value.value.clone());
             }
-            self.scopes.last_mut().insert_var(ident, value.value)?;
+            self.scopes.last_mut().insert_var(ident, value.value);
         }
         Ok(())
     }
diff --git a/src/scope.rs b/src/scope.rs
index 587376c..6787283 100644
--- a/src/scope.rs
+++ b/src/scope.rs
@@ -51,9 +51,8 @@ impl Scope {
         &mut self,
         s: T,
         v: Spanned<Value>,
-    ) -> SassResult<Option<Spanned<Value>>> {
-        let Spanned { node, span } = v;
-        Ok(self.vars.insert(s.into(), node.eval(span)?))
+    ) -> Option<Spanned<Value>> {
+        self.vars.insert(s.into(), v)
     }
 
     pub fn var_exists_no_global(&self, name: &Identifier) -> bool {
diff --git a/src/selector/simple.rs b/src/selector/simple.rs
index 2e43b2f..6fbd40b 100644
--- a/src/selector/simple.rs
+++ b/src/selector/simple.rs
@@ -127,7 +127,7 @@ impl SimpleSelector {
     }
 
     pub fn add_suffix(&mut self, suffix: &str, span: Span) -> SassResult<()> {
-        Ok(match self {
+        match self {
             Self::Type(name) => name.ident.push_str(suffix),
             Self::Placeholder(name)
             | Self::Id(name)
@@ -140,7 +140,8 @@ impl SimpleSelector {
             }) => name.push_str(suffix),
             // todo: add test for this?
             _ => return Err((format!("Invalid parent selector \"{}\"", self), span).into()),
-        })
+        };
+        Ok(())
     }
 
     pub fn is_universal(&self) -> bool {
diff --git a/src/style.rs b/src/style.rs
index 1191bf7..3c261d4 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -17,14 +17,4 @@ impl Style {
             self.value.node.to_css_string(self.value.span)?
         ))
     }
-
-    pub(crate) fn eval(self) -> SassResult<Self> {
-        Ok(Style {
-            property: self.property,
-            value: Box::new(Spanned {
-                span: self.value.span,
-                node: self.value.node.eval(self.value.span)?.node,
-            }),
-        })
-    }
 }
diff --git a/src/value/map.rs b/src/value/map.rs
index bf216be..b6865bf 100644
--- a/src/value/map.rs
+++ b/src/value/map.rs
@@ -5,6 +5,7 @@ use codemap::Span;
 use crate::{
     common::{Brackets, ListSeparator},
     error::SassResult,
+    parse::{HigherIntermediateValue, Parser, ValueVisitor},
     value::Value,
 };
 
@@ -16,16 +17,26 @@ impl SassMap {
         SassMap(Vec::new())
     }
 
-    pub fn get(self, key: &Value, span: Span) -> SassResult<Option<Value>> {
+    pub fn get(
+        self,
+        key: &Value,
+        span: Span,
+        parser: &mut Parser<'_>,
+    ) -> SassResult<Option<Value>> {
         for (k, v) in self.0 {
-            if k.equals(key.clone(), span)?.node.is_true(span)? {
+            if ValueVisitor::new(parser, span)
+                .equal(
+                    HigherIntermediateValue::Literal(k),
+                    HigherIntermediateValue::Literal(key.clone()),
+                )?
+                .is_true()
+            {
                 return Ok(Some(v));
             }
         }
         Ok(None)
     }
 
-    #[allow(dead_code)]
     pub fn remove(&mut self, key: &Value) {
         self.0.retain(|(ref k, ..)| k != key);
     }
diff --git a/src/value/mod.rs b/src/value/mod.rs
index 66a786e..795d2c9 100644
--- a/src/value/mod.rs
+++ b/src/value/mod.rs
@@ -4,7 +4,7 @@ use codemap::{Span, Spanned};
 
 use crate::{
     color::Color,
-    common::{Brackets, ListSeparator, Op, QuoteKind},
+    common::{Brackets, ListSeparator, QuoteKind},
     error::SassResult,
     parse::Parser,
     selector::Selector,
@@ -21,7 +21,6 @@ pub(crate) use sass_function::SassFunction;
 pub(crate) mod css_function;
 mod map;
 mod number;
-mod ops;
 mod sass_function;
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -33,9 +32,6 @@ pub(crate) enum Value {
     Dimension(Number, Unit),
     List(Vec<Value>, ListSeparator, Brackets),
     Color(Box<Color>),
-    UnaryOp(Op, Box<Value>),
-    BinaryOp(Box<Value>, Op, Box<Value>),
-    Paren(Box<Value>),
     String(String, QuoteKind),
     Map(SassMap),
     ArgList(Vec<Spanned<Value>>),
@@ -43,7 +39,7 @@ pub(crate) enum Value {
     Function(SassFunction),
 }
 
-fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str) -> SassResult<()> {
+fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str) {
     let mut has_single_quote = false;
     let mut has_double_quote = false;
 
@@ -107,26 +103,22 @@ fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str)
         buffer = format!("{}{}{}", quote, buffer, quote);
     }
     buf.push_str(&buffer);
-    Ok(())
 }
 
 impl Value {
-    pub fn is_null(&self, span: Span) -> SassResult<bool> {
-        Ok(match self {
+    pub fn is_null(&self) -> bool {
+        match self {
             Value::Null => true,
             Value::String(i, QuoteKind::None) if i.is_empty() => true,
-            Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
-                self.clone().eval(span)?.is_null(span)?
-            }
             Self::List(v, _, Brackets::Bracketed) if v.is_empty() => false,
             Self::List(v, ..) => v
                 .iter()
-                .map(|f| Ok(f.is_null(span)?))
-                .collect::<SassResult<Vec<bool>>>()?
+                .map(Value::is_null)
+                .collect::<Vec<bool>>()
                 .into_iter()
                 .all(|f| f),
             _ => false,
-        })
+        }
     }
 
     pub fn to_css_string(&self, span: Span) -> SassResult<Cow<'static, str>> {
@@ -148,7 +140,7 @@ impl Value {
             Self::List(vals, sep, brackets) => match brackets {
                 Brackets::None => Cow::owned(
                     vals.iter()
-                        .filter(|x| !x.is_null(span).unwrap_or(false))
+                        .filter(|x| !x.is_null())
                         .map(|x| x.to_css_string(span))
                         .collect::<SassResult<Vec<Cow<'static, str>>>>()?
                         .join(sep.as_str()),
@@ -156,17 +148,13 @@ impl Value {
                 Brackets::Bracketed => Cow::owned(format!(
                     "[{}]",
                     vals.iter()
-                        .filter(|x| !x.is_null(span).unwrap_or(false))
+                        .filter(|x| !x.is_null())
                         .map(|x| x.to_css_string(span))
                         .collect::<SassResult<Vec<Cow<'static, str>>>>()?
                         .join(sep.as_str()),
                 )),
             },
             Self::Color(c) => Cow::owned(c.to_string()),
-            Self::UnaryOp(..) | Self::BinaryOp(..) => {
-                self.clone().eval(span)?.to_css_string(span)?
-            }
-            Self::Paren(val) => val.to_css_string(span)?,
             Self::String(string, QuoteKind::None) => {
                 let mut after_newline = false;
                 let mut buf = String::with_capacity(string.len());
@@ -191,7 +179,7 @@ impl Value {
             }
             Self::String(string, QuoteKind::Quoted) => {
                 let mut buf = String::with_capacity(string.len());
-                visit_quoted_string(&mut buf, false, string)?;
+                visit_quoted_string(&mut buf, false, string);
                 Cow::owned(buf)
             }
             Self::True => Cow::const_str("true"),
@@ -199,7 +187,7 @@ impl Value {
             Self::Null => Cow::const_str(""),
             Self::ArgList(args) => Cow::owned(
                 args.iter()
-                    .filter(|x| !x.is_null(span).unwrap_or(false))
+                    .filter(|x| !x.is_null())
                     .map(|a| Ok(a.node.to_css_string(span)?))
                     .collect::<SassResult<Vec<Cow<'static, str>>>>()?
                     .join(", "),
@@ -207,13 +195,10 @@ impl Value {
         })
     }
 
-    pub fn is_true(&self, span: Span) -> SassResult<bool> {
+    pub fn is_true(&self) -> bool {
         match self {
-            Value::Null | Value::False => Ok(false),
-            Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
-                self.clone().eval(span)?.is_true(span)
-            }
-            _ => Ok(true),
+            Value::Null | Value::False => false,
+            _ => true,
         }
     }
 
@@ -231,20 +216,17 @@ impl Value {
         Spanned { node: self, span }
     }
 
-    pub fn kind(&self, span: Span) -> SassResult<&'static str> {
+    pub fn kind(&self) -> &'static str {
         match self {
-            Self::Color(..) => Ok("color"),
-            Self::String(..) | Self::Important => Ok("string"),
-            Self::Dimension(..) => Ok("number"),
-            Self::List(..) => Ok("list"),
-            Self::Function(..) => Ok("function"),
-            Self::ArgList(..) => Ok("arglist"),
-            Self::True | Self::False => Ok("bool"),
-            Self::Null => Ok("null"),
-            Self::Map(..) => Ok("map"),
-            Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
-                self.clone().eval(span)?.kind(span)
-            }
+            Self::Color(..) => "color",
+            Self::String(..) | Self::Important => "string",
+            Self::Dimension(..) => "number",
+            Self::List(..) => "list",
+            Self::Function(..) => "function",
+            Self::ArgList(..) => "arglist",
+            Self::True | Self::False => "bool",
+            Self::Null => "null",
+            Self::Map(..) => "map",
         }
     }
 
@@ -308,7 +290,6 @@ impl Value {
                     .collect::<SassResult<Vec<String>>>()?
                     .join(", ")
             )),
-            Value::Paren(v) => v.inspect(span)?,
             v => v.to_css_string(span)?,
         })
     }
@@ -365,7 +346,7 @@ impl Value {
     }
 
     fn selector_string(self, span: Span) -> SassResult<Option<String>> {
-        Ok(Some(match self.eval(span)?.node {
+        Ok(Some(match self {
             Self::String(text, ..) => text,
             Self::List(list, sep, ..) if !list.is_empty() => {
                 let mut result = Vec::new();
diff --git a/src/value/ops.rs b/src/value/ops.rs
deleted file mode 100644
index 61b48e1..0000000
--- a/src/value/ops.rs
+++ /dev/null
@@ -1,843 +0,0 @@
-use std::cmp::Ordering;
-
-use codemap::{Span, Spanned};
-
-use crate::{
-    common::{Op, QuoteKind},
-    error::SassResult,
-    unit::{Unit, UNIT_CONVERSION_TABLE},
-    value::Value,
-};
-
-impl Value {
-    pub fn equals(mut self, mut other: Value, span: Span) -> SassResult<Spanned<Value>> {
-        if let Self::Paren(..) = self {
-            self = self.eval(span)?.node
-        } else if let Self::UnaryOp(..) = self {
-            self = self.eval(span)?.node
-        }
-        if let Self::Paren(..) = other {
-            other = other.eval(span)?.node
-        } else if let Self::UnaryOp(..) = other {
-            other = other.eval(span)?.node
-        }
-
-        let precedence = Op::Equal.precedence();
-
-        Ok(Value::bool(match self {
-            // todo: why don't we eval the other?
-            Self::String(s1, ..) => match other {
-                Self::String(s2, ..) => s1 == s2,
-                _ => false,
-            },
-            Self::Dimension(n, unit) => match other {
-                Self::Dimension(n2, unit2) => {
-                    if !unit.comparable(&unit2) {
-                        false
-                    } else if unit == unit2 {
-                        n == n2
-                    } else if unit == Unit::None || unit2 == Unit::None {
-                        false
-                    } else {
-                        n == (n2
-                            * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
-                                [unit2.to_string().as_str()]
-                            .clone())
-                    }
-                }
-                _ => false,
-            },
-            Self::BinaryOp(left, op2, right) => {
-                if op2.precedence() >= precedence {
-                    Self::BinaryOp(left, op2, right).eval(span)?.node == other
-                } else {
-                    return Self::BinaryOp(
-                        left,
-                        op2,
-                        Box::new(
-                            Self::BinaryOp(right, Op::Equal, Box::new(other))
-                                .eval(span)?
-                                .node,
-                        ),
-                    )
-                    .eval(span);
-                }
-            }
-            Self::List(list1, sep1, brackets1) => match other.eval(span)?.node {
-                Self::List(list2, sep2, brackets2) => {
-                    if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
-                        false
-                    } else {
-                        let mut equals = true;
-                        for (a, b) in list1.into_iter().zip(list2) {
-                            if !a.equals(b, span)?.node.is_true(span)? {
-                                equals = false;
-                                break;
-                            }
-                        }
-                        equals
-                    }
-                }
-                _ => false,
-            },
-            s => s == other.eval(span)?.node,
-        })
-        .span(span))
-    }
-
-    pub fn not_equals(mut self, mut other: Value, span: Span) -> SassResult<Spanned<Value>> {
-        if let Self::Paren(..) = self {
-            self = self.eval(span)?.node
-        } else if let Self::UnaryOp(..) = self {
-            self = self.eval(span)?.node
-        }
-        if let Self::Paren(..) = other {
-            other = other.eval(span)?.node
-        } else if let Self::UnaryOp(..) = other {
-            other = other.eval(span)?.node
-        }
-
-        let precedence = Op::Equal.precedence();
-
-        Ok(Value::bool(match self {
-            Self::String(s1, ..) => match other {
-                Self::String(s2, ..) => s1 != s2,
-                _ => true,
-            },
-            Self::Dimension(n, unit) => match other {
-                Self::Dimension(n2, unit2) => {
-                    if !unit.comparable(&unit2) {
-                        true
-                    } else if unit == unit2 {
-                        n != n2
-                    } else if unit == Unit::None || unit2 == Unit::None {
-                        true
-                    } else {
-                        n != (n2
-                            * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
-                                [unit2.to_string().as_str()]
-                            .clone())
-                    }
-                }
-                _ => true,
-            },
-            Self::BinaryOp(left, op2, right) => {
-                if op2.precedence() >= precedence {
-                    Self::BinaryOp(left, op2, right).eval(span)?.node != other
-                } else {
-                    return Self::BinaryOp(
-                        left,
-                        op2,
-                        Box::new(
-                            Self::BinaryOp(right, Op::NotEqual, Box::new(other))
-                                .eval(span)?
-                                .node,
-                        ),
-                    )
-                    .eval(span);
-                }
-            }
-            Self::List(list1, sep1, brackets1) => match other.eval(span)?.node {
-                Self::List(list2, sep2, brackets2) => {
-                    if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
-                        true
-                    } else {
-                        let mut equals = false;
-                        for (a, b) in list1.into_iter().zip(list2) {
-                            if a.not_equals(b, span)?.node.is_true(span)? {
-                                equals = true;
-                                break;
-                            }
-                        }
-                        equals
-                    }
-                }
-                _ => true,
-            },
-            s => s != other.eval(span)?.node,
-        })
-        .span(span))
-    }
-
-    pub fn unary_op_plus(self, span: Span) -> SassResult<Self> {
-        Ok(match self.eval(span)?.node {
-            v @ Value::Dimension(..) => v,
-            v => Value::String(format!("+{}", v.to_css_string(span)?), QuoteKind::None),
-        })
-    }
-
-    pub fn eval(self, span: Span) -> SassResult<Spanned<Self>> {
-        Ok(match self {
-            Self::BinaryOp(lhs, op, rhs) => match op {
-                Op::Plus => lhs.add(*rhs, span)?,
-                Op::Minus => lhs.sub(*rhs, span)?,
-                Op::Equal => lhs.equals(*rhs, span)?.node,
-                Op::NotEqual => lhs.not_equals(*rhs, span)?.node,
-                Op::Mul => lhs.mul(*rhs, span)?,
-                Op::Div => lhs.div(*rhs, span)?,
-                Op::Rem => lhs.rem(*rhs, span)?,
-                Op::GreaterThan | Op::GreaterThanEqual | Op::LessThan | Op::LessThanEqual => {
-                    return lhs.cmp(*rhs, op, span)
-                }
-                Op::Not => unreachable!(),
-                Op::And => {
-                    if lhs.is_true(span)? {
-                        rhs.eval(span)?.node
-                    } else {
-                        lhs.eval(span)?.node
-                    }
-                }
-                Op::Or => {
-                    if lhs.is_true(span)? {
-                        lhs.eval(span)?.node
-                    } else {
-                        rhs.eval(span)?.node
-                    }
-                }
-            },
-            Self::Paren(v) => v.eval(span)?.node,
-            Self::UnaryOp(op, val) => match op {
-                Op::Plus => val.unary_op_plus(span)?,
-                Op::Minus => val.neg(span)?,
-                Op::Not => Self::bool(!val.eval(span)?.is_true(span)?),
-                _ => unreachable!(),
-            },
-            _ => self,
-        }
-        .span(span))
-    }
-
-    pub fn cmp(self, mut other: Self, op: Op, span: Span) -> SassResult<Spanned<Value>> {
-        if let Self::Paren(..) = other {
-            other = other.eval(span)?.node
-        } else if let Self::UnaryOp(..) = other {
-            other = other.eval(span)?.node
-        }
-        let precedence = op.precedence();
-        let ordering = match self {
-            Self::Dimension(num, unit) => match &other {
-                Self::Dimension(num2, unit2) => {
-                    if !unit.comparable(unit2) {
-                        return Err(
-                            (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
-                        );
-                    }
-                    if &unit == unit2 || unit == Unit::None || unit2 == &Unit::None {
-                        num.cmp(num2)
-                    } else {
-                        num.cmp(
-                            &(num2.clone()
-                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
-                                    [unit2.to_string().as_str()]
-                                .clone()),
-                        )
-                    }
-                }
-                Self::BinaryOp(..) => todo!(),
-                v => {
-                    return Err((
-                        format!(
-                            "Undefined operation \"{} {} {}\".",
-                            v.inspect(span)?,
-                            op,
-                            other.inspect(span)?
-                        ),
-                        span,
-                    )
-                        .into())
-                }
-            },
-            Self::BinaryOp(left, op2, right) => {
-                return if op2.precedence() >= precedence {
-                    Self::BinaryOp(left, op2, right)
-                        .eval(span)?
-                        .node
-                        .cmp(other, op, span)
-                } else {
-                    Self::BinaryOp(
-                        left,
-                        op2,
-                        Box::new(Self::BinaryOp(right, op, Box::new(other)).eval(span)?.node),
-                    )
-                    .eval(span)
-                }
-            }
-            Self::UnaryOp(..) | Self::Paren(..) => {
-                return self.eval(span)?.node.cmp(other, op, span)
-            }
-            _ => {
-                return Err((
-                    format!(
-                        "Undefined operation \"{} {} {}\".",
-                        self.inspect(span)?,
-                        op,
-                        other.inspect(span)?
-                    ),
-                    span,
-                )
-                    .into())
-            }
-        };
-        Ok(match op {
-            Op::GreaterThan => match ordering {
-                Ordering::Greater => Self::True,
-                Ordering::Less | Ordering::Equal => Self::False,
-            },
-            Op::GreaterThanEqual => match ordering {
-                Ordering::Greater | Ordering::Equal => Self::True,
-                Ordering::Less => Self::False,
-            },
-            Op::LessThan => match ordering {
-                Ordering::Less => Self::True,
-                Ordering::Greater | Ordering::Equal => Self::False,
-            },
-            Op::LessThanEqual => match ordering {
-                Ordering::Less | Ordering::Equal => Self::True,
-                Ordering::Greater => Self::False,
-            },
-            _ => unreachable!(),
-        }
-        .span(span))
-    }
-
-    pub fn add(mut self, mut other: Self, span: Span) -> SassResult<Self> {
-        if let Self::Paren(..) = other {
-            other = other.eval(span)?.node
-        } else if let Self::UnaryOp(..) = other {
-            other = other.eval(span)?.node
-        }
-        if let Self::Paren(..) = self {
-            self = self.eval(span)?.node
-        } else if let Self::UnaryOp(..) = self {
-            self = self.eval(span)?.node
-        }
-        let precedence = Op::Plus.precedence();
-        Ok(match self {
-            Self::Map(..) | Self::Function(..) => {
-                return Err((
-                    format!("{} isn't a valid CSS value.", self.inspect(span)?),
-                    span,
-                )
-                    .into())
-            }
-            Self::ArgList(..) => todo!(),
-            Self::Important | Self::True | Self::False => match other {
-                Self::String(s, QuoteKind::Quoted) => Value::String(
-                    format!("{}{}", self.to_css_string(span)?, s),
-                    QuoteKind::Quoted,
-                ),
-                Self::Null => {
-                    Value::String(self.to_css_string(span)?.into_owned(), QuoteKind::None)
-                }
-                _ => Value::String(
-                    format!(
-                        "{}{}",
-                        self.to_css_string(span)?,
-                        other.to_css_string(span)?
-                    ),
-                    QuoteKind::None,
-                ),
-            },
-            Self::Null => match other {
-                Self::Null => Self::Null,
-                _ => Value::String(other.to_css_string(span)?.into_owned(), QuoteKind::None),
-            },
-            Self::Dimension(num, unit) => match other {
-                Self::Dimension(num2, unit2) => {
-                    if !unit.comparable(&unit2) {
-                        return Err(
-                            (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
-                        );
-                    }
-                    if unit == unit2 {
-                        Value::Dimension(num + num2, unit)
-                    } else if unit == Unit::None {
-                        Value::Dimension(num + num2, unit2)
-                    } else if unit2 == Unit::None {
-                        Value::Dimension(num + num2, unit)
-                    } else {
-                        Value::Dimension(
-                            num + num2
-                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
-                                    [unit2.to_string().as_str()]
-                                .clone(),
-                            unit,
-                        )
-                    }
-                }
-                Self::String(s, q) => Value::String(format!("{}{}{}", num, unit, s), q),
-                Self::Null => Value::String(format!("{}{}", num, unit), QuoteKind::None),
-                Self::List(..) => Value::String(
-                    format!("{}{}{}", num, unit, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-                Self::True | Self::False => Self::String(
-                    format!("{}{}{}", num, unit, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-                Self::Map(..) | Self::Function(..) => {
-                    return Err((
-                        format!("{} isn't a valid CSS value.", other.inspect(span)?),
-                        span,
-                    )
-                        .into())
-                }
-                _ => {
-                    return Err((
-                        format!(
-                            "Undefined operation \"{}{} + {}\".",
-                            num,
-                            unit,
-                            other.inspect(span)?
-                        ),
-                        span,
-                    )
-                        .into())
-                }
-            },
-            Self::Color(c) => match other {
-                Self::String(s, q) => Value::String(format!("{}{}", c, s), q),
-                Self::Null => Value::String(c.to_string(), QuoteKind::None),
-                Self::List(..) => Value::String(
-                    format!("{}{}", c, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-                _ => {
-                    return Err((
-                        format!("Undefined operation \"{} + {}\".", c, other.inspect(span)?),
-                        span,
-                    )
-                        .into())
-                }
-            },
-            Self::BinaryOp(left, op, right) => {
-                if op.precedence() >= precedence {
-                    Self::BinaryOp(left, op, right)
-                        .eval(span)?
-                        .node
-                        .add(other, span)?
-                } else {
-                    Self::BinaryOp(
-                        left,
-                        op,
-                        Box::new(
-                            Self::BinaryOp(right, Op::Plus, Box::new(other))
-                                .eval(span)?
-                                .node,
-                        ),
-                    )
-                    .eval(span)?
-                    .node
-                }
-            }
-            Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.add(other, span)?,
-            Self::String(text, quotes) => match other {
-                Self::String(text2, ..) => Self::String(text + &text2, quotes),
-                _ => Value::String(text + &other.to_css_string(span)?, quotes),
-            },
-            Self::List(..) => match other {
-                Self::String(s, q) => {
-                    Value::String(format!("{}{}", self.to_css_string(span)?, s), q)
-                }
-                Self::Paren(..) => (self.add(other.eval(span)?.node, span))?,
-                _ => Value::String(
-                    format!(
-                        "{}{}",
-                        self.to_css_string(span)?,
-                        other.to_css_string(span)?
-                    ),
-                    QuoteKind::None,
-                ),
-            },
-        })
-    }
-
-    pub fn sub(mut self, mut other: Self, span: Span) -> SassResult<Self> {
-        if let Self::Paren(..) = other {
-            other = other.eval(span)?.node
-        }
-        if let Self::Paren(..) = self {
-            self = self.eval(span)?.node
-        } else if let Self::UnaryOp(..) = self {
-            self = self.eval(span)?.node
-        }
-        let precedence = Op::Mul.precedence();
-        Ok(match self {
-            Self::Null => {
-                Value::String(format!("-{}", other.to_css_string(span)?), QuoteKind::None)
-            }
-            Self::Dimension(num, unit) => match other {
-                Self::Dimension(num2, unit2) => {
-                    if !unit.comparable(&unit2) {
-                        return Err(
-                            (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
-                        );
-                    }
-                    if unit == unit2 {
-                        Value::Dimension(num - num2, unit)
-                    } else if unit == Unit::None {
-                        Value::Dimension(num - num2, unit2)
-                    } else if unit2 == Unit::None {
-                        Value::Dimension(num - num2, unit)
-                    } else {
-                        Value::Dimension(
-                            num - num2
-                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
-                                    [unit2.to_string().as_str()]
-                                .clone(),
-                            unit,
-                        )
-                    }
-                }
-                Self::List(..) | Self::String(..) => Value::String(
-                    format!("{}{}-{}", num, unit, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-                Self::Map(..) | Self::Function(..) => {
-                    return Err((
-                        format!("{} isn't a valid CSS value.", other.inspect(span)?),
-                        span,
-                    )
-                        .into())
-                }
-                _ => todo!(),
-            },
-            Self::Color(c) => match other {
-                Self::String(s, q) => {
-                    Value::String(format!("{}-{}{}{}", c, q, s, q), QuoteKind::None)
-                }
-                Self::Null => Value::String(format!("{}-", c), QuoteKind::None),
-                Self::Dimension(..) | Self::Color(..) => {
-                    return Err((
-                        format!("Undefined operation \"{} - {}\".", c, other.inspect(span)?),
-                        span,
-                    )
-                        .into())
-                }
-                _ => Value::String(
-                    format!("{}-{}", c, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-            },
-            Self::BinaryOp(left, op, right) => {
-                if op.precedence() >= precedence {
-                    Self::BinaryOp(left, op, right)
-                        .eval(span)?
-                        .node
-                        .sub(other, span)?
-                } else {
-                    Self::BinaryOp(
-                        left,
-                        op,
-                        Box::new(
-                            Self::BinaryOp(right, Op::Minus, Box::new(other))
-                                .eval(span)?
-                                .node,
-                        ),
-                    )
-                    .eval(span)?
-                    .node
-                }
-            }
-            Self::Paren(..) => self.eval(span)?.node.sub(other, span)?,
-            Self::String(..) => Self::String(
-                format!(
-                    "{}-{}",
-                    self.to_css_string(span)?,
-                    other.to_css_string(span)?
-                ),
-                QuoteKind::None,
-            ),
-            Self::List(..) => match other {
-                Self::String(s, q) => Value::String(
-                    format!("{}-{}{}{}", self.to_css_string(span)?, q, s, q),
-                    QuoteKind::None,
-                ),
-                _ => Value::String(
-                    format!(
-                        "{}-{}",
-                        self.to_css_string(span)?,
-                        other.to_css_string(span)?
-                    ),
-                    QuoteKind::None,
-                ),
-            },
-            _ => match other {
-                Self::String(s, q) => Value::String(
-                    format!("{}-{}{}{}", self.to_css_string(span)?, q, s, q),
-                    QuoteKind::None,
-                ),
-                Self::Null => {
-                    Value::String(format!("{}-", self.to_css_string(span)?), QuoteKind::None)
-                }
-                _ => Value::String(
-                    format!(
-                        "{}-{}",
-                        self.to_css_string(span)?,
-                        other.to_css_string(span)?
-                    ),
-                    QuoteKind::None,
-                ),
-            },
-        })
-    }
-
-    pub fn mul(mut self, mut other: Self, span: Span) -> SassResult<Self> {
-        if let Self::Paren(..) = other {
-            other = other.eval(span)?.node
-        }
-        if let Self::Paren(..) = self {
-            self = self.eval(span)?.node
-        } else if let Self::UnaryOp(..) = self {
-            self = self.eval(span)?.node
-        }
-        let precedence = Op::Mul.precedence();
-        Ok(match self {
-            Self::Null => todo!(),
-            Self::Dimension(num, unit) => match other {
-                Self::Dimension(num2, unit2) => {
-                    if unit == Unit::None {
-                        Value::Dimension(num * num2, unit2)
-                    } else if unit2 == Unit::None {
-                        Value::Dimension(num * num2, unit)
-                    } else if let Unit::Mul(u) = unit {
-                        let mut unit1 = u.into_vec();
-                        unit1.push(unit2);
-                        Value::Dimension(num * num2, Unit::Mul(unit1.into_boxed_slice()))
-                    } else if let Unit::Mul(u2) = unit2 {
-                        let mut u = vec![unit];
-                        u.append(&mut u2.into_vec());
-                        Value::Dimension(num * num2, Unit::Mul(u.into_boxed_slice()))
-                    } else {
-                        Value::Dimension(
-                            num * num2,
-                            Unit::Mul(vec![unit, unit2].into_boxed_slice()),
-                        )
-                    }
-                }
-                _ => {
-                    return Err((
-                        format!(
-                            "Undefined operation \"{}{} * {}\".",
-                            num,
-                            unit,
-                            other.inspect(span)?
-                        ),
-                        span,
-                    )
-                        .into())
-                }
-            },
-            Self::BinaryOp(left, op, right) => {
-                if op.precedence() >= precedence {
-                    Self::BinaryOp(left, op, right)
-                        .eval(span)?
-                        .node
-                        .mul(other, span)?
-                } else {
-                    Self::BinaryOp(
-                        left,
-                        op,
-                        Box::new(
-                            Self::BinaryOp(right, Op::Mul, Box::new(other))
-                                .eval(span)?
-                                .node,
-                        ),
-                    )
-                    .eval(span)?
-                    .node
-                }
-            }
-            Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.mul(other, span)?,
-            _ => {
-                return Err((
-                    format!(
-                        "Undefined operation \"{} * {}\".",
-                        self.inspect(span)?,
-                        other.inspect(span)?
-                    ),
-                    span,
-                )
-                    .into())
-            }
-        })
-    }
-
-    pub fn div(mut self, other: Self, span: Span) -> SassResult<Self> {
-        if let Self::Paren(..) = self {
-            self = self.eval(span)?.node
-        } else if let Self::UnaryOp(..) = self {
-            self = self.eval(span)?.node
-        }
-        let precedence = Op::Div.precedence();
-        Ok(match self {
-            Self::Null => todo!(),
-            Self::Dimension(num, unit) => match other {
-                Self::Dimension(num2, unit2) => {
-                    if !unit.comparable(&unit2) {
-                        return Err(
-                            (format!("Incompatible units {} and {}.", unit2, unit), span).into(),
-                        );
-                    }
-                    if unit == unit2 {
-                        Value::Dimension(num / num2, Unit::None)
-                    } else if unit == Unit::None {
-                        todo!("inverse units")
-                    } else if unit2 == Unit::None {
-                        Value::Dimension(num / num2, unit)
-                    } else {
-                        Value::Dimension(
-                            num / (num2
-                                * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
-                                    [unit2.to_string().as_str()]
-                                .clone()),
-                            Unit::None,
-                        )
-                    }
-                }
-                Self::String(s, q) => {
-                    Value::String(format!("{}{}/{}{}{}", num, unit, q, s, q), QuoteKind::None)
-                }
-                Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
-                    Self::Dimension(num, unit).div(other.eval(span)?.node, span)?
-                }
-                Self::List(..) | Self::True | Self::False | Self::Important | Self::Color(..) => {
-                    Value::String(
-                        format!("{}{}/{}", num, unit, other.to_css_string(span)?),
-                        QuoteKind::None,
-                    )
-                }
-                Self::Null => Value::String(format!("{}{}/", num, unit), QuoteKind::None),
-                Self::Map(..) | Self::Function(..) => {
-                    return Err((
-                        format!("{} isn't a valid CSS value.", other.inspect(span)?),
-                        span,
-                    )
-                        .into())
-                }
-                Self::ArgList(..) => todo!(),
-            },
-            Self::Color(c) => match other {
-                Self::String(s, q) => {
-                    Value::String(format!("{}/{}{}{}", c, q, s, q), QuoteKind::None)
-                }
-                Self::Null => Value::String(format!("{}/", c), QuoteKind::None),
-                Self::Dimension(..) | Self::Color(..) => {
-                    return Err((
-                        format!("Undefined operation \"{} / {}\".", c, other.inspect(span)?),
-                        span,
-                    )
-                        .into())
-                }
-                _ => Value::String(
-                    format!("{}/{}", c, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-            },
-            Self::BinaryOp(left, op, right) => {
-                if op.precedence() >= precedence {
-                    Self::BinaryOp(left, op, right)
-                        .eval(span)?
-                        .node
-                        .div(other, span)?
-                } else {
-                    Self::BinaryOp(
-                        left,
-                        op,
-                        Box::new(
-                            Self::BinaryOp(right, Op::Div, Box::new(other))
-                                .eval(span)?
-                                .node,
-                        ),
-                    )
-                    .eval(span)?
-                    .node
-                }
-            }
-            Self::Paren(..) => self.eval(span)?.node.div(other, span)?,
-            Self::String(s1, q1) => match other {
-                Self::String(s2, q2) => Value::String(
-                    format!("{}{}{}/{}{}{}", q1, s1, q1, q2, s2, q2),
-                    QuoteKind::None,
-                ),
-                Self::Important
-                | Self::True
-                | Self::False
-                | Self::Dimension(..)
-                | Self::Color(..) => Value::String(
-                    format!("{}{}{}/{}", q1, s1, q1, other.to_css_string(span)?),
-                    QuoteKind::None,
-                ),
-                Self::Null => Value::String(format!("{}{}{}/", q1, s1, q1), QuoteKind::None),
-                _ => todo!(),
-            },
-            _ => match other {
-                Self::String(s, q) => Value::String(
-                    format!("{}/{}{}{}", self.to_css_string(span)?, q, s, q),
-                    QuoteKind::None,
-                ),
-                Self::Null => {
-                    Value::String(format!("{}/", self.to_css_string(span)?), QuoteKind::None)
-                }
-                _ => Value::String(
-                    format!(
-                        "{}/{}",
-                        self.to_css_string(span)?,
-                        other.to_css_string(span)?
-                    ),
-                    QuoteKind::None,
-                ),
-            },
-        })
-    }
-
-    pub fn rem(self, other: Self, span: Span) -> SassResult<Self> {
-        Ok(match self {
-            Value::Dimension(n, u) => match other {
-                Value::Dimension(n2, u2) => {
-                    if !u.comparable(&u2) {
-                        return Err((format!("Incompatible units {} and {}.", u2, u), span).into());
-                    }
-                    if u == u2 {
-                        Value::Dimension(n % n2, u)
-                    } else if u == Unit::None {
-                        Value::Dimension(n % n2, u2)
-                    } else if u2 == Unit::None {
-                        Value::Dimension(n % n2, u)
-                    } else {
-                        Value::Dimension(n, u)
-                    }
-                }
-                _ => {
-                    return Err((
-                        format!(
-                            "Undefined operation \"{} % {}\".",
-                            Value::Dimension(n, u).inspect(span)?,
-                            other.inspect(span)?
-                        ),
-                        span,
-                    )
-                        .into())
-                }
-            },
-            _ => {
-                return Err((
-                    format!(
-                        "Undefined operation \"{} % {}\".",
-                        self.inspect(span)?,
-                        other.inspect(span)?
-                    ),
-                    span,
-                )
-                    .into())
-            }
-        })
-    }
-
-    pub fn neg(self, span: Span) -> SassResult<Self> {
-        Ok(match self.eval(span)?.node {
-            Value::Dimension(n, u) => Value::Dimension(-n, u),
-            v => Value::String(format!("-{}", v.to_css_string(span)?), QuoteKind::None),
-        })
-    }
-}
diff --git a/tests/modulo.rs b/tests/modulo.rs
new file mode 100644
index 0000000..c708348
--- /dev/null
+++ b/tests/modulo.rs
@@ -0,0 +1,30 @@
+#![cfg(test)]
+
+#[macro_use]
+mod macros;
+
+test!(
+    px_mod_px,
+    "a {\n  color: 10px % 2px;\n}\n",
+    "a {\n  color: 0px;\n}\n"
+);
+test!(
+    px_mod_in,
+    "a {\n  color: 10px % 2in;\n}\n",
+    "a {\n  color: 10px;\n}\n"
+);
+test!(
+    px_mod_none,
+    "a {\n  color: 10px % 2;\n}\n",
+    "a {\n  color: 0px;\n}\n"
+);
+test!(
+    none_mod_px,
+    "a {\n  color: 10 % 2px;\n}\n",
+    "a {\n  color: 0px;\n}\n"
+);
+test!(
+    none_mod_none,
+    "a {\n  color: 10 % 2;\n}\n",
+    "a {\n  color: 0;\n}\n"
+);
diff --git a/tests/number.rs b/tests/number.rs
index 5bcff80..d98419d 100644
--- a/tests/number.rs
+++ b/tests/number.rs
@@ -50,32 +50,6 @@ test!(
 );
 test!(positive_float_leading_zero, "a {\n  color: 0.1;\n}\n");
 test!(negative_float_leading_zero, "a {\n  color: -0.1;\n}\n");
-
-test!(
-    px_mod_px,
-    "a {\n  color: 10px % 2px;\n}\n",
-    "a {\n  color: 0px;\n}\n"
-);
-test!(
-    px_mod_in,
-    "a {\n  color: 10px % 2in;\n}\n",
-    "a {\n  color: 10px;\n}\n"
-);
-test!(
-    px_mod_none,
-    "a {\n  color: 10px % 2;\n}\n",
-    "a {\n  color: 0px;\n}\n"
-);
-test!(
-    none_mod_px,
-    "a {\n  color: 10 % 2px;\n}\n",
-    "a {\n  color: 0px;\n}\n"
-);
-test!(
-    none_mod_none,
-    "a {\n  color: 10 % 2;\n}\n",
-    "a {\n  color: 0;\n}\n"
-);
 test!(
     num_plus_div,
     "a {\n  color: 1 + 3/4;\n}\n",