From 596def3906e70fe2cca7407912668d1d53500fd5 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 3 Jul 2020 12:38:20 -0400 Subject: [PATCH] refactor value evaluation --- src/args.rs | 2 +- src/builtin/color/hsl.rs | 6 +- src/builtin/color/rgb.rs | 6 +- src/builtin/list.rs | 24 +- src/builtin/map.rs | 4 +- src/builtin/math.rs | 27 +- src/builtin/meta.rs | 12 +- src/lib.rs | 2 +- src/output.rs | 20 +- src/parse/args.rs | 30 +- src/parse/mod.rs | 27 +- src/parse/value/css_function.rs | 395 ++++++++++++++ src/parse/value/eval.rs | 905 ++++++++++++++++++++++++++++++++ src/parse/value/mod.rs | 4 + src/parse/value/parse.rs | 767 +++++++++------------------ src/parse/variable.rs | 20 +- src/scope.rs | 5 +- src/selector/simple.rs | 5 +- src/style.rs | 10 - src/value/map.rs | 17 +- src/value/mod.rs | 69 +-- src/value/ops.rs | 843 ----------------------------- tests/modulo.rs | 30 ++ tests/number.rs | 26 - 24 files changed, 1710 insertions(+), 1546 deletions(-) create mode 100644 src/parse/value/css_function.rs create mode 100644 src/parse/value/eval.rs delete mode 100644 src/value/ops.rs create mode 100644 tests/modulo.rs 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>, 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 { _ => 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 { 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 { } fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - let span = args.span(); let lists = parser .variadic_args(args)? .into_iter() - .map(|x| Ok(x.node.eval(span)?.node.as_list())) - .collect::>>>()?; + .map(|x| x.node.as_list()) + .collect::>>(); 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 { .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 { @@ -39,7 +39,7 @@ fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult .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 { 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 { 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 { 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 { 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 { fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { 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 { @@ -177,7 +171,7 @@ fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult 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::>()?, + 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> { @@ -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 { + 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> { + 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> { + 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> { + 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> { + 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> { + 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 { + 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, Op, Box), + UnaryOp(Op, Box), + Paren(Box), +} + +impl HigherIntermediateValue { + pub const fn span(self, span: Span) -> Spanned { + Spanned { node: self, span } + } +} + +impl<'a> Parser<'a> { + fn call_function(&mut self, function: SassFunction, args: CallArgs) -> SassResult { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + Ok(Value::bool(!val.is_true())) + } + + fn paren(&mut self, val: HigherIntermediateValue) -> SassResult { + Ok(if let HigherIntermediateValue::Paren(v) = val { + HigherIntermediateValue::Literal(self.eval(*v)?) + } else { + val + }) + } + + fn paren_or_unary( + &mut self, + val: HigherIntermediateValue, + ) -> SassResult { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + self.cmp(left, Op::GreaterThan, right) + } + + fn greater_than_or_equal( + &self, + left: HigherIntermediateValue, + right: HigherIntermediateValue, + ) -> SassResult { + self.cmp(left, Op::GreaterThanEqual, right) + } + + pub fn less_than( + &self, + left: HigherIntermediateValue, + right: HigherIntermediateValue, + ) -> SassResult { + self.cmp(left, Op::LessThan, right) + } + + fn less_than_or_equal( + &self, + left: HigherIntermediateValue, + right: HigherIntermediateValue, + ) -> SassResult { + 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), + Paren(Vec), + Comma, + Whitespace, +} + +impl IntermediateValue { + const fn span(self, span: Span) -> Spanned { + 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> { 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::>>()?, 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::>>()?, 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::>>()?, 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::>>()?, 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::().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 { - 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> { - 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> { - 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> { - 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> { - 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> { - 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 { - 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, - space_separated: &mut Vec>, + space_separated: &mut Vec>, 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> { + fn single_value(&mut self) -> SassResult> { 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>) -> SassResult> { + fn parse_paren( + &mut self, + t: Spanned>, + ) -> SassResult> { 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> { } } -#[derive(Clone, Debug, Eq, PartialEq)] -enum IntermediateValue { - Value(Value), - Op(Op), - Bracketed(Vec), - Paren(Vec), - Comma, - Whitespace, -} - -impl IntermediateValue { - const fn span(self, span: Span) -> Spanned { - 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, - ) -> SassResult>> { - let Spanned { node, span } = v; - Ok(self.vars.insert(s.into(), node.eval(span)?)) + ) -> Option> { + 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 { - 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> { + pub fn get( + self, + key: &Value, + span: Span, + parser: &mut Parser<'_>, + ) -> SassResult> { 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, ListSeparator, Brackets), Color(Box), - UnaryOp(Op, Box), - BinaryOp(Box, Op, Box), - Paren(Box), String(String, QuoteKind), Map(SassMap), ArgList(Vec>), @@ -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 { - 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::>>()? + .map(Value::is_null) + .collect::>() .into_iter() .all(|f| f), _ => false, - }) + } } pub fn to_css_string(&self, span: Span) -> SassResult> { @@ -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::>>>()? .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::>>>()? .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::>>>()? .join(", "), @@ -207,13 +195,10 @@ impl Value { }) } - pub fn is_true(&self, span: Span) -> SassResult { + 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::>>()? .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> { - 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> { - 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> { - 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 { - 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> { - 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> { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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",