implement keyword operators

This commit is contained in:
ConnorSkees 2020-04-01 17:37:07 -04:00
parent 07c8f7b2a9
commit f46d53d3cc
9 changed files with 199 additions and 22 deletions

View File

@ -13,6 +13,9 @@ pub enum Op {
Mul, Mul,
Div, Div,
Rem, Rem,
And,
Or,
Not,
} }
impl Display for Op { impl Display for Op {
@ -29,6 +32,9 @@ impl Display for Op {
Self::Mul => write!(f, "*"), Self::Mul => write!(f, "*"),
Self::Div => write!(f, "/"), Self::Div => write!(f, "/"),
Self::Rem => write!(f, "%"), Self::Rem => write!(f, "%"),
Self::And => write!(f, "and"),
Self::Or => write!(f, "or"),
Self::Not => write!(f, "not"),
} }
} }
} }
@ -42,14 +48,15 @@ impl Op {
/// If precedence is equal, the leftmost operation is evaluated first /// If precedence is equal, the leftmost operation is evaluated first
pub fn precedence(&self) -> usize { pub fn precedence(&self) -> usize {
match self { match self {
Self::And | Self::Or | Self::Not => 0,
Self::Equal Self::Equal
| Self::NotEqual | Self::NotEqual
| Self::GreaterThan | Self::GreaterThan
| Self::GreaterThanEqual | Self::GreaterThanEqual
| Self::LessThan | Self::LessThan
| Self::LessThanEqual => 0, | Self::LessThanEqual => 1,
Self::Plus | Self::Minus => 1, Self::Plus | Self::Minus => 2,
Self::Mul | Self::Div | Self::Rem => 2, Self::Mul | Self::Div | Self::Rem => 3,
} }
} }
} }

View File

@ -36,13 +36,15 @@ impl Toplevel {
Toplevel::RuleSet(selector, Vec::new()) Toplevel::RuleSet(selector, Vec::new())
} }
fn push_style(&mut self, s: Style) { fn push_style(&mut self, mut s: Style) -> SassResult<()> {
s.value = s.value.eval()?;
if s.value.is_null() { if s.value.is_null() {
return; return Ok(());
} }
if let Toplevel::RuleSet(_, entries) = self { if let Toplevel::RuleSet(_, entries) = self {
entries.push(BlockEntry::Style(Box::new(s))); entries.push(BlockEntry::Style(Box::new(s)));
} }
Ok(())
} }
fn push_comment(&mut self, s: String) { fn push_comment(&mut self, s: String) {
@ -62,12 +64,12 @@ impl Css {
Css { blocks: Vec::new() } Css { blocks: Vec::new() }
} }
pub fn from_stylesheet(s: StyleSheet) -> Self { pub fn from_stylesheet(s: StyleSheet) -> SassResult<Self> {
Css::new().parse_stylesheet(s) Css::new().parse_stylesheet(s)
} }
fn parse_stmt(&mut self, stmt: Stmt) -> Vec<Toplevel> { fn parse_stmt(&mut self, stmt: Stmt) -> SassResult<Vec<Toplevel>> {
match stmt { Ok(match stmt {
Stmt::RuleSet(RuleSet { Stmt::RuleSet(RuleSet {
selector, selector,
super_selector, super_selector,
@ -75,16 +77,16 @@ impl Css {
}) => { }) => {
let selector = super_selector.zip(&selector).remove_placeholders(); let selector = super_selector.zip(&selector).remove_placeholders();
if selector.is_empty() { if selector.is_empty() {
return Vec::new(); return Ok(Vec::new());
} }
let mut vals = vec![Toplevel::new_rule(selector)]; let mut vals = vec![Toplevel::new_rule(selector)];
for rule in rules { for rule in rules {
match rule { match rule {
Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule)), Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule)?),
Stmt::Style(s) => vals Stmt::Style(s) => vals
.get_mut(0) .get_mut(0)
.expect("expected block to exist") .expect("expected block to exist")
.push_style(*s), .push_style(*s)?,
Stmt::MultilineComment(s) => vals Stmt::MultilineComment(s) => vals
.get_mut(0) .get_mut(0)
.expect("expected block to exist") .expect("expected block to exist")
@ -97,13 +99,13 @@ impl Css {
Stmt::MultilineComment(s) => vec![Toplevel::MultilineComment(s)], Stmt::MultilineComment(s) => vec![Toplevel::MultilineComment(s)],
Stmt::Style(_) => panic!("expected toplevel element, found style"), Stmt::Style(_) => panic!("expected toplevel element, found style"),
Stmt::AtRule(r) => vec![Toplevel::AtRule(r)], Stmt::AtRule(r) => vec![Toplevel::AtRule(r)],
} })
} }
fn parse_stylesheet(mut self, s: StyleSheet) -> Css { fn parse_stylesheet(mut self, s: StyleSheet) -> SassResult<Css> {
let mut is_first = true; let mut is_first = true;
for stmt in s.0 { for stmt in s.0 {
let v = self.parse_stmt(stmt); let v = self.parse_stmt(stmt)?;
// this is how we print newlines between unrelated styles // this is how we print newlines between unrelated styles
// it could probably be refactored // it could probably be refactored
if !v.is_empty() { if !v.is_empty() {
@ -116,7 +118,7 @@ impl Css {
self.blocks.extend(v); self.blocks.extend(v);
} }
} }
self Ok(self)
} }
pub fn pretty_print<W: Write>(self, buf: &mut W, nesting: usize) -> SassResult<()> { pub fn pretty_print<W: Write>(self, buf: &mut W, nesting: usize) -> SassResult<()> {
@ -149,7 +151,7 @@ impl Css {
} else { } else {
writeln!(buf, "{}@{} {} {{", padding, u.name, u.params)?; writeln!(buf, "{}@{} {} {{", padding, u.name, u.params)?;
} }
Css::from_stylesheet(StyleSheet::from_stmts(u.body)) Css::from_stylesheet(StyleSheet::from_stmts(u.body))?
.pretty_print(buf, nesting + 1)?; .pretty_print(buf, nesting + 1)?;
writeln!(buf, "{}}}", padding)?; writeln!(buf, "{}}}", padding)?;
} }

View File

@ -264,7 +264,7 @@ impl StyleSheet {
/// ``` /// ```
#[inline] #[inline]
pub fn print_as_css<W: Write>(self, buf: &mut W) -> SassResult<()> { pub fn print_as_css<W: Write>(self, buf: &mut W) -> SassResult<()> {
Css::from_stylesheet(self).pretty_print(buf, 0) Css::from_stylesheet(self)?.pretty_print(buf, 0)
} }
} }

View File

@ -215,11 +215,23 @@ impl Value {
Ordering::Less | Ordering::Equal => Ok(Self::True), Ordering::Less | Ordering::Equal => Ok(Self::True),
Ordering::Greater => Ok(Self::False), Ordering::Greater => Ok(Self::False),
}, },
Op::Not => unreachable!(),
Op::And => Ok(if lhs.is_true()? {
rhs.eval()?
} else {
lhs.eval()?
}),
Op::Or => Ok(if lhs.is_true()? {
lhs.eval()?
} else {
rhs.eval()?
}),
}, },
Self::Paren(v) => v.eval(), Self::Paren(v) => v.eval(),
Self::UnaryOp(op, val) => match op { Self::UnaryOp(op, val) => match op {
Op::Plus => val.unary_op_plus(), Op::Plus => val.unary_op_plus(),
Op::Minus => -*val, Op::Minus => -*val,
Op::Not => Ok(Self::bool(!val.eval()?.is_true()?)),
_ => unreachable!(), _ => unreachable!(),
}, },
_ => Ok(self), _ => Ok(self),

View File

@ -144,6 +144,11 @@ fn eat_op<I: Iterator<Item = IntermediateValue>>(
space_separated: &mut Vec<Value>, space_separated: &mut Vec<Value>,
) -> SassResult<()> { ) -> SassResult<()> {
match op { match op {
Op::Not => {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::UnaryOp(op, Box::new(right)));
}
Op::Plus => { Op::Plus => {
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
devour_whitespace(iter); devour_whitespace(iter);
@ -168,6 +173,18 @@ fn eat_op<I: Iterator<Item = IntermediateValue>>(
space_separated.push(Value::UnaryOp(op, Box::new(right))); space_separated.push(Value::UnaryOp(op, Box::new(right)));
} }
} }
Op::And | Op::Or => {
devour_whitespace(iter);
if iter.peek().is_none() {
space_separated.push(Value::Ident(op.to_string(), QuoteKind::None));
} else if let Some(left) = space_separated.pop() {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right)));
} else {
return Err("Expected expression.".into());
}
}
_ => { _ => {
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
devour_whitespace(iter); devour_whitespace(iter);
@ -186,13 +203,17 @@ fn single_value<I: Iterator<Item = IntermediateValue>>(
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Value> {
Ok(match iter.next().unwrap() { Ok(match iter.next().ok_or("Expected expression.")? {
IntermediateValue::Value(v) => v, IntermediateValue::Value(v) => v,
IntermediateValue::Op(op) => match op { IntermediateValue::Op(op) => match op {
Op::Minus => { Op::Minus => {
devour_whitespace(iter); devour_whitespace(iter);
(-single_value(iter, scope, super_selector)?)? (-single_value(iter, scope, super_selector)?)?
} }
Op::Not => {
devour_whitespace(iter);
Value::UnaryOp(op, Box::new(single_value(iter, scope, super_selector)?))
}
_ => todo!(), _ => todo!(),
}, },
IntermediateValue::Whitespace => unreachable!(), IntermediateValue::Whitespace => unreachable!(),
@ -484,9 +505,9 @@ impl Value {
"true" => Ok(IntermediateValue::Value(Value::True)), "true" => Ok(IntermediateValue::Value(Value::True)),
"false" => Ok(IntermediateValue::Value(Value::False)), "false" => Ok(IntermediateValue::Value(Value::False)),
"null" => Ok(IntermediateValue::Value(Value::Null)), "null" => Ok(IntermediateValue::Value(Value::Null)),
// "not" => Ok(IntermediateValue::Op(Op::Not)), "not" => Ok(IntermediateValue::Op(Op::Not)),
// "and" => Ok(IntermediateValue::Op(Op::And)), "and" => Ok(IntermediateValue::Op(Op::And)),
// "or" => Ok(IntermediateValue::Op(Op::Or)), "or" => Ok(IntermediateValue::Op(Op::Or)),
_ => Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))), _ => Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))),
} }
} }

42
tests/and.rs Normal file
View File

@ -0,0 +1,42 @@
#![cfg(test)]
#[macro_use]
mod macros;
test!(
one_and_two,
"a {\n color: 1 and 2;\n}\n",
"a {\n color: 2;\n}\n"
);
test!(
two_and_one,
"a {\n color: 2 and 1;\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
true_and_true,
"a {\n color: true and true;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
true_and_false,
"a {\n color: true and false;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
false_and_true,
"a {\n color: false and true;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
false_and_false,
"a {\n color: false and false;\n}\n",
"a {\n color: false;\n}\n"
);
test!(null_and_one, "a {\n color: null and 1;\n}\n", "");
test!(one_and_null, "a {\n color: 1 and null;\n}\n", "");
test!(
one_and_two_and_three,
"a {\n color: 1 and 2 and 3;\n}\n",
"a {\n color: 3;\n}\n"
);

40
tests/not.rs Normal file
View File

@ -0,0 +1,40 @@
#![cfg(test)]
#[macro_use]
mod macros;
test!(
not_number,
"a {\n color: not 1;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
not_true,
"a {\n color: not true;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
not_false,
"a {\n color: not false;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
not_null,
"a {\n color: not null;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
not_unquoted,
"a {\n color: not foo;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
not_not_true,
"a {\n color: not not true;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
not_not_false,
"a {\n color: not not false;\n}\n",
"a {\n color: false;\n}\n"
);

50
tests/or.rs Normal file
View File

@ -0,0 +1,50 @@
#![cfg(test)]
#[macro_use]
mod macros;
test!(
one_or_two,
"a {\n color: 1 or 2;\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
two_or_one,
"a {\n color: 2 or 1;\n}\n",
"a {\n color: 2;\n}\n"
);
test!(
true_or_true,
"a {\n color: true or true;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
true_or_false,
"a {\n color: true or false;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
false_or_true,
"a {\n color: false or true;\n}\n",
"a {\n color: true;\n}\n"
);
test!(
false_or_false,
"a {\n color: false or false;\n}\n",
"a {\n color: false;\n}\n"
);
test!(
null_or_one,
"a {\n color: null or 1;\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
one_or_null,
"a {\n color: 1 or null;\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
one_or_two_or_three,
"a {\n color: 1 or 2 or 3;\n}\n",
"a {\n color: 1;\n}\n"
);

View File

@ -24,7 +24,10 @@ test!(
test!(preserves_keyword_auto, "a {\n color: auto;\n}\n"); test!(preserves_keyword_auto, "a {\n color: auto;\n}\n");
test!(preserves_keyword_initial, "a {\n color: initial;\n}\n"); test!(preserves_keyword_initial, "a {\n color: initial;\n}\n");
test!(preserves_keyword_infinity, "a {\n color: infinity;\n}\n"); test!(preserves_keyword_infinity, "a {\n color: infinity;\n}\n");
test!(preserves_keyword_not, "a {\n color: not;\n}\n"); error!(
keyword_not_expects_expression,
"a {\n color: not;\n}\n", "Error: Expected expression."
);
test!(preserves_keyword_and, "a {\n color: and;\n}\n"); test!(preserves_keyword_and, "a {\n color: and;\n}\n");
test!(preserves_keyword_or, "a {\n color: or;\n}\n"); test!(preserves_keyword_or, "a {\n color: or;\n}\n");
test!(preserves_keyword_unset, "a {\n color: unset;\n}\n"); test!(preserves_keyword_unset, "a {\n color: unset;\n}\n");