Handle !default

This commit is contained in:
ConnorSkees 2020-01-29 21:02:32 -05:00
parent 849cddeea4
commit d464124ae0
7 changed files with 100 additions and 19 deletions

View File

@ -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),

View File

@ -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 {

View File

@ -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,9 +346,11 @@ 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));
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(_) => { TokenKind::MultilineComment(_) => {
let comment = match self let comment = match self
@ -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),

View File

@ -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 {

View File

@ -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))
} }

View File

@ -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()

View File

@ -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)]