Handle !default
This commit is contained in:
parent
849cddeea4
commit
d464124ae0
@ -218,6 +218,7 @@ pub enum Keyword {
|
||||
True,
|
||||
False,
|
||||
Null,
|
||||
Default,
|
||||
// Infinity,
|
||||
// NaN,
|
||||
// Auto,
|
||||
@ -237,6 +238,7 @@ impl Display for Keyword {
|
||||
Self::True => write!(f, "true"),
|
||||
Self::False => write!(f, "false"),
|
||||
Self::Null => write!(f, "null"),
|
||||
Self::Default => write!(f, "!default"),
|
||||
// Self::Infinity => write!(f, "Infinity"),
|
||||
// Self::NaN => write!(f, "NaN"),
|
||||
// Self::Auto => write!(f, "auto"),
|
||||
@ -258,6 +260,7 @@ impl Into<&'static str> for Keyword {
|
||||
Self::True => "true",
|
||||
Self::False => "false",
|
||||
Self::Null => "null",
|
||||
Self::Default => "!default",
|
||||
// Self::Infinity => "Infinity",
|
||||
// Self::NaN => "NaN",
|
||||
// Self::Auto => "auto",
|
||||
@ -282,6 +285,7 @@ impl TryFrom<&str> for Keyword {
|
||||
"true" => Ok(Self::True),
|
||||
"false" => Ok(Self::False),
|
||||
"null" => Ok(Self::Null),
|
||||
"default" => Ok(Self::Default),
|
||||
// "infinity" => Ok(Self::Infinity),
|
||||
// "nan" => Ok(Self::NaN),
|
||||
// "auto" => Ok(Self::Auto),
|
||||
|
11
src/lexer.rs
11
src/lexer.rs
@ -117,14 +117,19 @@ impl<'a> Lexer<'a> {
|
||||
Some('i') | Some('I') => {
|
||||
self.buf.next();
|
||||
assert_char!(self, 'm' 'p' 'o' 'r' 't' 'a' 'n' 't');
|
||||
TokenKind::Keyword(Keyword::Important)
|
||||
}
|
||||
Some('d') | Some('D') => {
|
||||
self.buf.next();
|
||||
assert_char!(self, 'e' 'f' 'a' 'u' 'l' 't');
|
||||
TokenKind::Keyword(Keyword::Default)
|
||||
}
|
||||
Some('=') => {
|
||||
self.buf.next();
|
||||
return TokenKind::Op(Op::NotEqual);
|
||||
TokenKind::Op(Op::NotEqual)
|
||||
}
|
||||
_ => todo!("expected either `i` or `=` after `!`"),
|
||||
};
|
||||
TokenKind::Keyword(Keyword::Important)
|
||||
}
|
||||
}
|
||||
|
||||
fn devour_whitespace(&mut self) -> bool {
|
||||
|
16
src/lib.rs
16
src/lib.rs
@ -58,7 +58,7 @@ use crate::lexer::Lexer;
|
||||
use crate::mixin::{eat_include, Mixin};
|
||||
use crate::selector::{Attribute, Selector};
|
||||
use crate::style::Style;
|
||||
use crate::utils::{devour_whitespace, eat_variable_value, IsComment, IsWhitespace};
|
||||
use crate::utils::{devour_whitespace, eat_variable_value, IsComment, IsWhitespace, VariableDecl};
|
||||
use crate::value::Value;
|
||||
|
||||
mod args;
|
||||
@ -346,9 +346,11 @@ impl<'a> StyleSheetParser<'a> {
|
||||
{
|
||||
self.error(pos, "unexpected variable use at toplevel");
|
||||
}
|
||||
let val = eat_variable_value(&mut self.lexer, &self.global_scope)
|
||||
let VariableDecl { val, default } = eat_variable_value(&mut self.lexer, &self.global_scope)
|
||||
.unwrap_or_else(|err| self.error(err.0, &err.1));
|
||||
self.global_scope.vars.insert(name, val);
|
||||
if !default || self.global_scope.vars.get(&name).is_none() {
|
||||
self.global_scope.vars.insert(name, val);
|
||||
}
|
||||
}
|
||||
TokenKind::MultilineComment(_) => {
|
||||
let comment = match self
|
||||
@ -533,10 +535,10 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
||||
{
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
return Ok(Some(Expr::VariableDecl(
|
||||
name,
|
||||
eat_variable_value(toks, scope)?,
|
||||
)));
|
||||
let VariableDecl { val, default } = eat_variable_value(toks, scope)?;
|
||||
if !default || scope.vars.get(&name).is_none() {
|
||||
return Ok(Some(Expr::VariableDecl(name, val)));
|
||||
}
|
||||
} else {
|
||||
values.push(Token {
|
||||
kind: TokenKind::Variable(name),
|
||||
|
@ -272,7 +272,9 @@ impl<'a> SelectorParser<'a> {
|
||||
TokenKind::Symbol(Symbol::Plus) => self.selectors.push(SelectorKind::Following),
|
||||
TokenKind::Symbol(Symbol::Tilde) => self.selectors.push(SelectorKind::Preceding),
|
||||
TokenKind::Symbol(Symbol::Mul) => self.selectors.push(SelectorKind::Universal),
|
||||
TokenKind::Symbol(Symbol::Percent) => self.selectors.push(SelectorKind::Placeholder),
|
||||
TokenKind::Symbol(Symbol::Percent) => {
|
||||
self.selectors.push(SelectorKind::Placeholder)
|
||||
}
|
||||
TokenKind::Symbol(Symbol::BitAnd) => self.selectors.push(if self.is_interpolated {
|
||||
SelectorKind::InterpolatedSuper
|
||||
} else {
|
||||
|
50
src/utils.rs
50
src/utils.rs
@ -1,4 +1,4 @@
|
||||
use crate::common::{Pos, Symbol};
|
||||
use crate::common::{Keyword, Pos, Symbol};
|
||||
use crate::lexer::Lexer;
|
||||
use crate::value::Value;
|
||||
use crate::{Scope, Token, TokenKind};
|
||||
@ -64,15 +64,51 @@ pub(crate) fn eat_interpolation<I: Iterator<Item = Token>>(
|
||||
val
|
||||
}
|
||||
|
||||
pub(crate) struct VariableDecl {
|
||||
pub val: Value,
|
||||
pub default: bool,
|
||||
}
|
||||
|
||||
impl VariableDecl {
|
||||
pub fn new(val: Value, default: bool) -> VariableDecl {
|
||||
VariableDecl { val, default }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
|
||||
toks: &mut Peekable<I>,
|
||||
scope: &Scope,
|
||||
) -> Result<Value, (Pos, String)> {
|
||||
) -> Result<VariableDecl, (Pos, String)> {
|
||||
devour_whitespace(toks);
|
||||
// todo!(line might not end with semicolon)
|
||||
let iter1: Vec<Token> = toks
|
||||
.take_while(|x| x.kind != TokenKind::Symbol(Symbol::SemiColon))
|
||||
.collect();
|
||||
let mut default = false;
|
||||
let mut raw: Vec<Token> = Vec::new();
|
||||
let mut nesting = 0;
|
||||
while let Some(tok) = toks.peek() {
|
||||
match tok.kind {
|
||||
TokenKind::Symbol(Symbol::SemiColon) => {
|
||||
toks.next();
|
||||
break;
|
||||
}
|
||||
TokenKind::Keyword(Keyword::Default) => {
|
||||
toks.next();
|
||||
default = true
|
||||
}
|
||||
TokenKind::Interpolation | TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
|
||||
nesting += 1;
|
||||
raw.push(toks.next().unwrap());
|
||||
}
|
||||
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
raw.push(toks.next().unwrap());
|
||||
}
|
||||
}
|
||||
_ => raw.push(toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
Ok(Value::from_tokens(&mut iter1.into_iter().peekable(), scope).unwrap())
|
||||
let val = Value::from_tokens(&mut raw.into_iter().peekable(), scope).unwrap();
|
||||
Ok(VariableDecl::new(val, default))
|
||||
}
|
||||
|
@ -304,7 +304,9 @@ impl Value {
|
||||
}
|
||||
Some(Value::Ident(s, QuoteKind::Single))
|
||||
}
|
||||
TokenKind::Variable(ref v) => Some(scope.vars.get(v).expect("expected variable").clone()),
|
||||
TokenKind::Variable(ref v) => {
|
||||
Some(scope.vars.get(v).expect("expected variable").clone())
|
||||
}
|
||||
TokenKind::Interpolation => {
|
||||
let mut s = eat_interpolation(toks, scope)
|
||||
.iter()
|
||||
|
@ -100,6 +100,36 @@ mod test_variables {
|
||||
"$var1: red; a {\n color: $var1;\n}\n",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
default_var_after,
|
||||
"$a: red;\n$a: blue !default;\na {\n color: $a;\n}",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
default_var_before,
|
||||
"$a: red !default;\n$a: blue;\na {\n color: $a;\n}",
|
||||
"a {\n color: blue;\n}\n"
|
||||
);
|
||||
test!(
|
||||
default_var_whitespace,
|
||||
"$a: red !default ;\na {\n color: $a;\n}",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
default_var_inside_rule,
|
||||
"a {\n $a: red;\n $a: blue !default;\n color: $a;\n}",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
interpolation_in_variable,
|
||||
"$a: #{red};\na {\n color: $a\n}\n",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
variable_decl_doesnt_end_in_semicolon,
|
||||
"a {\n $a: red\n}\n\nb {\n color: blue;\n}\n",
|
||||
"b {\n color: blue;\n}\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Loading…
x
Reference in New Issue
Block a user