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

View File

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

View File

@ -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,10 +346,12 @@ 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));
if !default || self.global_scope.vars.get(&name).is_none() {
self.global_scope.vars.insert(name, val);
}
}
TokenKind::MultilineComment(_) => {
let comment = match self
.lexer
@ -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),

View File

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

View File

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

View File

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

View File

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