implement keyword operators
This commit is contained in:
parent
07c8f7b2a9
commit
f46d53d3cc
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
src/css.rs
28
src/css.rs
@ -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)?;
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
42
tests/and.rs
Normal 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
40
tests/not.rs
Normal 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
50
tests/or.rs
Normal 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"
|
||||
);
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user