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