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,
Div,
Rem,
And,
Or,
Not,
}
impl Display for Op {
@ -29,6 +32,9 @@ impl Display for Op {
Self::Mul => write!(f, "*"),
Self::Div => 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
pub fn precedence(&self) -> usize {
match self {
Self::And | Self::Or | Self::Not => 0,
Self::Equal
| Self::NotEqual
| Self::GreaterThan
| Self::GreaterThanEqual
| Self::LessThan
| Self::LessThanEqual => 0,
Self::Plus | Self::Minus => 1,
Self::Mul | Self::Div | Self::Rem => 2,
| Self::LessThanEqual => 1,
Self::Plus | Self::Minus => 2,
Self::Mul | Self::Div | Self::Rem => 3,
}
}
}

View File

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

View File

@ -264,7 +264,7 @@ impl StyleSheet {
/// ```
#[inline]
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::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::UnaryOp(op, val) => match op {
Op::Plus => val.unary_op_plus(),
Op::Minus => -*val,
Op::Not => Ok(Self::bool(!val.eval()?.is_true()?)),
_ => unreachable!(),
},
_ => Ok(self),

View File

@ -144,6 +144,11 @@ fn eat_op<I: Iterator<Item = IntermediateValue>>(
space_separated: &mut Vec<Value>,
) -> SassResult<()> {
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 => {
if let Some(left) = space_separated.pop() {
devour_whitespace(iter);
@ -168,6 +173,18 @@ fn eat_op<I: Iterator<Item = IntermediateValue>>(
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() {
devour_whitespace(iter);
@ -186,13 +203,17 @@ fn single_value<I: Iterator<Item = IntermediateValue>>(
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
Ok(match iter.next().unwrap() {
Ok(match iter.next().ok_or("Expected expression.")? {
IntermediateValue::Value(v) => v,
IntermediateValue::Op(op) => match op {
Op::Minus => {
devour_whitespace(iter);
(-single_value(iter, scope, super_selector)?)?
}
Op::Not => {
devour_whitespace(iter);
Value::UnaryOp(op, Box::new(single_value(iter, scope, super_selector)?))
}
_ => todo!(),
},
IntermediateValue::Whitespace => unreachable!(),
@ -484,9 +505,9 @@ impl Value {
"true" => Ok(IntermediateValue::Value(Value::True)),
"false" => Ok(IntermediateValue::Value(Value::False)),
"null" => Ok(IntermediateValue::Value(Value::Null)),
// "not" => Ok(IntermediateValue::Op(Op::Not)),
// "and" => Ok(IntermediateValue::Op(Op::And)),
// "or" => Ok(IntermediateValue::Op(Op::Or)),
"not" => Ok(IntermediateValue::Op(Op::Not)),
"and" => Ok(IntermediateValue::Op(Op::And)),
"or" => Ok(IntermediateValue::Op(Op::Or)),
_ => 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_initial, "a {\n color: initial;\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_or, "a {\n color: or;\n}\n");
test!(preserves_keyword_unset, "a {\n color: unset;\n}\n");