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