diff --git a/src/value/mod.rs b/src/value/mod.rs index 69e7d41..1eaca86 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -20,6 +20,7 @@ pub(crate) enum Value { Dimension(Number, Unit), List(Vec, ListSeparator), Color(Color), + UnaryOp(Op, Box), BinaryOp(Box, Op, Box), Paren(Box), Ident(String, QuoteKind), @@ -45,7 +46,7 @@ impl Display for Value { .join(sep.as_str()) ), Self::Color(c) => write!(f, "{}", c), - Self::BinaryOp(..) => write!( + Self::UnaryOp(..) | Self::BinaryOp(..) => write!( f, "{}", match self.clone().eval() { @@ -148,6 +149,13 @@ impl Value { }) } + pub fn unary_op_plus(self) -> SassResult { + Ok(match self.eval()? { + v @ Value::Dimension(..) => v, + v => Value::Ident(format!("+{}", v), QuoteKind::None), + }) + } + pub fn eval(self) -> SassResult { match self { Self::BinaryOp(lhs, op, rhs) => match op { @@ -161,6 +169,11 @@ impl Value { _ => Ok(Self::BinaryOp(lhs, op, rhs)), }, Self::Paren(v) => v.eval(), + Self::UnaryOp(op, val) => match op { + Op::Plus => val.unary_op_plus(), + Op::Minus => -*val, + _ => unreachable!(), + }, _ => Ok(self), } } diff --git a/src/value/ops.rs b/src/value/ops.rs index c0c841a..bf2379b 100644 --- a/src/value/ops.rs +++ b/src/value/ops.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Div, Mul, Rem, Sub}; +use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; use crate::common::QuoteKind; use crate::error::SassResult; @@ -70,7 +70,7 @@ impl Add for Value { Self::List(..) => Value::Ident(format!("{}{}", c, other), QuoteKind::None), _ => return Err(format!("Undefined operation \"{} + {}\".", c, other).into()), }, - Self::BinaryOp(..) | Self::Paren(..) => (self.eval()? + other)?, + Self::UnaryOp(..) | Self::BinaryOp(..) | Self::Paren(..) => (self.eval()? + other)?, Self::Ident(s1, quotes1) => match other { Self::Ident(s2, quotes2) => { let quotes = match (quotes1, quotes2) { @@ -104,7 +104,7 @@ impl Add for Value { Value::Ident(format!("{}{}", s1, c), quotes) } Self::List(..) => Value::Ident(format!("{}{}", s1, other), quotes1), - Self::BinaryOp(..) | Self::Paren(..) => todo!(), + Self::UnaryOp(..) | Self::BinaryOp(..) | Self::Paren(..) => todo!(), }, Self::List(..) => match other { Self::Ident(s, q) => { @@ -417,3 +417,14 @@ impl Rem for Value { }) } } + +impl Neg for Value { + type Output = SassResult; + + fn neg(self) -> Self::Output { + Ok(match self.eval()? { + Value::Dimension(n, u) => Value::Dimension(-n, u), + v => Value::Ident(format!("-{}", v), QuoteKind::None), + }) + } +} diff --git a/src/value/parse.rs b/src/value/parse.rs index 3804220..78f2f6f 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -292,7 +292,17 @@ impl Value { TokenKind::Keyword(Keyword::From(s)) => Ok(Value::Ident(s, QuoteKind::None)), TokenKind::Keyword(Keyword::Through(s)) => Ok(Value::Ident(s, QuoteKind::None)), TokenKind::Keyword(Keyword::To(s)) => Ok(Value::Ident(s, QuoteKind::None)), - TokenKind::AtRule(_) => return Err("expected \";\".".into()), + TokenKind::AtRule(_) => Err("expected \";\".".into()), + TokenKind::Op(Op::Plus) | TokenKind::Symbol(Symbol::Plus) => { + devour_whitespace_or_comment(toks); + let v = Self::_from_tokens(toks, scope, super_selector)?; + Ok(Value::UnaryOp(Op::Plus, Box::new(v))) + } + TokenKind::Op(Op::Minus) | TokenKind::Symbol(Symbol::Minus) => { + devour_whitespace_or_comment(toks); + let v = Self::_from_tokens(toks, scope, super_selector)?; + Ok(Value::UnaryOp(Op::Minus, Box::new(v))) + } v => { dbg!(v); panic!("Unexpected token in value parsing") diff --git a/tests/unary.rs b/tests/unary.rs new file mode 100644 index 0000000..f9fd2f3 --- /dev/null +++ b/tests/unary.rs @@ -0,0 +1,47 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!(unary_pos_unquoted_ident, "a {\n color: +foo;\n}\n"); +test!( + unary_pos_whitespace, + "a {\n color: + foo;\n}\n", + "a {\n color: +foo;\n}\n" +); +test!(unary_pos_dblquoted_ident, "a {\n color: +\"foo\";\n}\n"); +test!( + unary_pos_sglquoted_ident, + "a {\n color: +'foo';\n}\n", + "a {\n color: +\"foo\";\n}\n" +); +test!(unary_pos_color, "a {\n color: +\"foo\";\n}\n"); +test!( + unary_pos_number, + "a {\n color: +1px;\n}\n", + "a {\n color: 1px;\n}\n" +); +test!( + unary_pos_in_list, + "a {\n color: bar,+ \"bar\" - foo;\n}\n", + "a {\n color: bar, +\"bar\"-foo;\n}\n" +); +test!(unary_neg_unquoted_ident, "a {\n color: -foo;\n}\n"); +test!(unary_neg_dblquoted_ident, "a {\n color: -\"foo\";\n}\n"); +test!( + unary_neg_sglquoted_ident, + "a {\n color: -'foo';\n}\n", + "a {\n color: -\"foo\";\n}\n" +); +test!(unary_neg_color, "a {\n color: -\"foo\";\n}\n"); +test!(unary_neg_number, "a {\n color: -1px;\n}\n"); +test!( + unary_neg_whitespace, + "a {\n color: - 1px;\n}\n", + "a {\n color: -1px;\n}\n" +); +test!( + unary_neg_number_type, + "a {\n color: type-of(- 1px);\n}\n", + "a {\n color: number;\n}\n" +);