diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 0b7939d..b924b26 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -102,8 +102,11 @@ impl Mixin { while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? { match expr { Expr::AtRule(a) => match a { + AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => stmts.extend(s), + AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?), AtRule::Content => stmts.extend(self.content.clone()), - _ => stmts.push(Stmt::AtRule(a)), + AtRule::Return(..) => return Err("This at-rule is not allowed here.".into()), + r => stmts.push(Stmt::AtRule(r)), }, Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::Styles(s) => stmts.extend(s.into_iter().map(Box::new).map(Stmt::Style)), diff --git a/src/value/mod.rs b/src/value/mod.rs index 24ae5b0..5a0a330 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -228,24 +228,12 @@ impl Value { Op::Mul => *lhs * *rhs, Op::Div => *lhs / *rhs, Op::Rem => *lhs % *rhs, - Op::GreaterThan => match lhs.cmp(&rhs, op)? { - Ordering::Greater => Ok(Self::True), - Ordering::Less | Ordering::Equal => Ok(Self::False), - }, - Op::GreaterThanEqual => match lhs.cmp(&rhs, op)? { - Ordering::Greater | Ordering::Equal => Ok(Self::True), - Ordering::Less => Ok(Self::False), - }, - Op::LessThan => match lhs.cmp(&rhs, op)? { - Ordering::Less => Ok(Self::True), - Ordering::Greater | Ordering::Equal => Ok(Self::False), - }, - Op::LessThanEqual => match lhs.cmp(&rhs, op)? { - Ordering::Less | Ordering::Equal => Ok(Self::True), - Ordering::Greater => Ok(Self::False), - }, + Op::GreaterThan => lhs.cmp(*rhs, op), + Op::GreaterThanEqual => lhs.cmp(*rhs, op), + Op::LessThan => lhs.cmp(*rhs, op), + Op::LessThanEqual => lhs.cmp(*rhs, op), Op::Not => unreachable!(), - Op::And => Ok(if lhs.is_true()? { + Op::And => Ok(if lhs.clone().is_true()? { rhs.eval()? } else { lhs.eval()? @@ -267,16 +255,20 @@ impl Value { } } - pub fn cmp(&self, other: &Self, op: Op) -> SassResult { - Ok(match self { - Self::Dimension(num, ref unit) => match other { + pub fn cmp(self, mut other: Self, op: Op) -> SassResult { + if let Self::Paren(..) = other { + other = other.eval()? + } + let precedence = op.precedence(); + let ordering = match self { + Self::Dimension(num, unit) => match &other { Self::Dimension(num2, unit2) => { - if !unit.comparable(unit2) { + if !unit.comparable(&unit2) { return Err(format!("Incompatible units {} and {}.", unit2, unit).into()); } - if unit == unit2 { + if &unit == unit2 { num.cmp(num2) - } else if unit == &Unit::None { + } else if unit == Unit::None { num.cmp(num2) } else if unit2 == &Unit::None { num.cmp(num2) @@ -288,13 +280,42 @@ impl Value { ) } } - _ => { - return Err( - format!("Undefined operation \"{} {} {}\".", self, op, other).into(), - ) - } + Self::BinaryOp(..) => todo!(), + v => return Err(format!("Undefined operation \"{} {} {}\".", v, op, other).into()), }, + Self::BinaryOp(left, op2, right) => { + return if op2.precedence() >= precedence { + Self::BinaryOp(left, op2, right).eval()?.cmp(other, op) + } else { + Self::BinaryOp( + left, + op2, + Box::new(Self::BinaryOp(right, op, Box::new(other)).eval()?), + ) + .eval() + } + } + Self::UnaryOp(..) | Self::Paren(..) => return self.eval()?.cmp(other, op), _ => return Err(format!("Undefined operation \"{} {} {}\".", self, op, other).into()), - }) + }; + match op { + Op::GreaterThan => match ordering { + Ordering::Greater => Ok(Self::True), + Ordering::Less | Ordering::Equal => Ok(Self::False), + }, + Op::GreaterThanEqual => match ordering { + Ordering::Greater | Ordering::Equal => Ok(Self::True), + Ordering::Less => Ok(Self::False), + }, + Op::LessThan => match ordering { + Ordering::Less => Ok(Self::True), + Ordering::Greater | Ordering::Equal => Ok(Self::False), + }, + Op::LessThanEqual => match ordering { + Ordering::Less | Ordering::Equal => Ok(Self::True), + Ordering::Greater => Ok(Self::False), + }, + _ => unreachable!(), + } } } diff --git a/src/value/number.rs b/src/value/number.rs index 4f1ecba..1fbd011 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -12,7 +12,7 @@ use crate::error::SassError; const PRECISION: usize = 10; -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)] pub(crate) struct Number { val: BigRational, } @@ -164,6 +164,12 @@ from_integer!(i32); from_integer!(u32); from_integer!(u8); +impl fmt::Debug for Number { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Number {{ {} }}", self) + } +} + impl Display for Number { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut whole = self.val.to_integer().abs(); diff --git a/src/value/ops.rs b/src/value/ops.rs index 709ec54..c155957 100644 --- a/src/value/ops.rs +++ b/src/value/ops.rs @@ -101,7 +101,10 @@ impl Sub for Value { type Output = SassResult; fn sub(self, mut other: Self) -> Self::Output { - other = other.eval()?; + if let Self::Paren(..) = other { + other = other.eval()? + } + let precedence = Op::Mul.precedence(); Ok(match self { Self::Null => todo!(), Self::Dimension(num, unit) => match other { @@ -143,7 +146,19 @@ impl Sub for Value { } _ => Value::Ident(format!("{}-{}", c, other), QuoteKind::None), }, - Self::BinaryOp(..) | Self::Paren(..) => (self.eval()? - other)?, + Self::BinaryOp(left, op, right) => { + if op.precedence() >= precedence { + (Self::BinaryOp(left, op, right).eval()? - other)? + } else { + Self::BinaryOp( + left, + op, + Box::new(Self::BinaryOp(right, Op::Minus, Box::new(other)).eval()?), + ) + .eval()? + } + } + Self::Paren(..) => (self.eval()? - other)?, Self::Ident(s1, q1) => match other { Self::Ident(s2, q2) => Value::Ident( format!( @@ -199,7 +214,10 @@ impl Mul for Value { type Output = SassResult; fn mul(self, mut other: Self) -> Self::Output { - other = other.eval()?; + if let Self::Paren(..) = other { + other = other.eval()? + } + let precedence = Op::Mul.precedence(); Ok(match self { Self::Null => todo!(), Self::Dimension(num, unit) => match other { @@ -225,8 +243,19 @@ impl Mul for Value { ) } }, - Self::BinaryOp(..) | Self::Paren(..) => (self.eval()? * other)?, - Self::UnaryOp(..) => (self.eval()? * other)?, + Self::BinaryOp(left, op, right) => { + if op.precedence() >= precedence { + (Self::BinaryOp(left, op, right).eval()? * other)? + } else { + Self::BinaryOp( + left, + op, + Box::new(Self::BinaryOp(right, Op::Mul, Box::new(other)).eval()?), + ) + .eval()? + } + } + Self::UnaryOp(..) | Self::Paren(..) => (self.eval()? * other)?, _ => return Err(format!("Undefined operation \"{} * {}\".", self, other).into()), }) } diff --git a/tests/order-of-operations.rs b/tests/order-of-operations.rs new file mode 100644 index 0000000..72a4949 --- /dev/null +++ b/tests/order-of-operations.rs @@ -0,0 +1,30 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + addition_then_division, + "a {\n color: 3+3/4;\n}\n", + "a {\n color: 3.75;\n}\n" +); +test!( + division_then_addition, + "a {\n color: 3/4 + 3;\n}\n", + "a {\n color: 3.75;\n}\n" +); +test!( + addition_then_multiplication, + "a {\n color: 4 + 2 * 3;\n}\n", + "a {\n color: 10;\n}\n" +); +test!( + multiplication_then_addition, + "a {\n color: 4 * 2 + 3;\n}\n", + "a {\n color: 11;\n}\n" +); +test!( + comparison, + "a {\n color: 1 < 1 and 1 < 1;;\n}\n", + "a {\n color: false;\n}\n" +);