properly parse variable flags
This commit is contained in:
parent
99ae3ae30a
commit
a86d717f26
@ -53,12 +53,11 @@ pub(crate) fn is_name_start(c: char) -> bool {
|
|||||||
c == '_' || c.is_alphanumeric() || c as u32 >= 0x0080
|
c == '_' || c.is_alphanumeric() || c as u32 >= 0x0080
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_hex(c: u32) -> u32 {
|
pub(crate) fn as_hex(c: char) -> u32 {
|
||||||
if c <= '9' as u32 {
|
match c {
|
||||||
c - '0' as u32
|
'0'..='9' => c as u32 - '0' as u32,
|
||||||
} else if c <= 'F' as u32 {
|
'A'..='F' => 10 + c as u32 - 'A' as u32,
|
||||||
10 + c - 'A' as u32
|
'a'..='f' => 10 + c as u32 - 'a' as u32,
|
||||||
} else {
|
_ => panic!(),
|
||||||
10 + c - 'a' as u32
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
|
|
||||||
|
use codemap::{Span, Spanned};
|
||||||
|
|
||||||
use peekmore::PeekMoreIterator;
|
use peekmore::PeekMoreIterator;
|
||||||
|
|
||||||
|
use crate::error::SassResult;
|
||||||
use crate::Token;
|
use crate::Token;
|
||||||
|
|
||||||
use super::IsWhitespace;
|
use super::{as_hex, hex_char_for, is_name, is_name_start, IsWhitespace};
|
||||||
|
|
||||||
pub(crate) fn peek_until_closing_curly_brace<I: Iterator<Item = Token>>(
|
pub(crate) fn peek_until_closing_curly_brace<I: Iterator<Item = Token>>(
|
||||||
toks: &mut PeekMoreIterator<I>,
|
toks: &mut PeekMoreIterator<I>,
|
||||||
@ -106,3 +109,131 @@ fn peek_whitespace<I: Iterator<Item = W>, W: IsWhitespace>(s: &mut PeekMoreItera
|
|||||||
}
|
}
|
||||||
found_whitespace
|
found_whitespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn peek_escape<I: Iterator<Item = Token>>(
|
||||||
|
toks: &mut PeekMoreIterator<I>,
|
||||||
|
) -> SassResult<String> {
|
||||||
|
let mut value = 0;
|
||||||
|
let first = match toks.peek() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => return Ok(String::new()),
|
||||||
|
};
|
||||||
|
if first.kind == '\n' {
|
||||||
|
return Err(("Expected escape sequence.", first.pos()).into());
|
||||||
|
} else if first.kind.is_ascii_hexdigit() {
|
||||||
|
for _ in 0..6 {
|
||||||
|
let next = match toks.peek() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
if !next.kind.is_ascii_hexdigit() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
value *= 16;
|
||||||
|
value += as_hex(next.kind);
|
||||||
|
toks.peek_forward(1);
|
||||||
|
}
|
||||||
|
if toks.peek().is_some() && toks.peek().unwrap().kind.is_whitespace() {
|
||||||
|
toks.peek_forward(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = toks.peek_forward(1).unwrap().kind as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tabs are emitted literally
|
||||||
|
// TODO: figure out where this check is done
|
||||||
|
// in the source dart
|
||||||
|
if value == 0x9 {
|
||||||
|
return Ok("\\\t".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = std::char::from_u32(value).unwrap();
|
||||||
|
if is_name(c) {
|
||||||
|
Ok(c.to_string())
|
||||||
|
} else if value <= 0x1F || value == 0x7F {
|
||||||
|
let mut buf = String::with_capacity(4);
|
||||||
|
buf.push('\\');
|
||||||
|
if value > 0xF {
|
||||||
|
buf.push(hex_char_for(value >> 4));
|
||||||
|
}
|
||||||
|
buf.push(hex_char_for(value & 0xF));
|
||||||
|
buf.push(' ');
|
||||||
|
Ok(buf)
|
||||||
|
} else {
|
||||||
|
Ok(format!("\\{}", c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn peek_ident_no_interpolation<I: Iterator<Item = Token>>(
|
||||||
|
toks: &mut PeekMoreIterator<I>,
|
||||||
|
unit: bool,
|
||||||
|
) -> SassResult<Spanned<String>> {
|
||||||
|
let mut span = toks.peek().unwrap().pos();
|
||||||
|
let mut text = String::new();
|
||||||
|
if toks.peek().unwrap().kind == '-' {
|
||||||
|
toks.peek_forward(1);
|
||||||
|
text.push('-');
|
||||||
|
if toks.peek().unwrap().kind == '-' {
|
||||||
|
toks.peek_forward(1);
|
||||||
|
text.push('-');
|
||||||
|
text.push_str(&peek_ident_body_no_interpolation(toks, unit, span)?.node);
|
||||||
|
return Ok(Spanned { node: text, span });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let first = match toks.peek() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(("Expected identifier.", span).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_name_start(first.kind) {
|
||||||
|
text.push(first.kind);
|
||||||
|
toks.peek_forward(1);
|
||||||
|
} else if first.kind == '\\' {
|
||||||
|
toks.peek_forward(1);
|
||||||
|
text.push_str(&peek_escape(toks)?);
|
||||||
|
} else {
|
||||||
|
return Err(("Expected identifier.", first.pos()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = peek_ident_body_no_interpolation(toks, unit, span)?;
|
||||||
|
span = span.merge(body.span);
|
||||||
|
text.push_str(&body.node);
|
||||||
|
Ok(Spanned { node: text, span })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_ident_body_no_interpolation<I: Iterator<Item = Token>>(
|
||||||
|
toks: &mut PeekMoreIterator<I>,
|
||||||
|
unit: bool,
|
||||||
|
mut span: Span,
|
||||||
|
) -> SassResult<Spanned<String>> {
|
||||||
|
let mut text = String::new();
|
||||||
|
while let Some(tok) = toks.peek() {
|
||||||
|
span = span.merge(tok.pos());
|
||||||
|
if unit && tok.kind == '-' {
|
||||||
|
// Disallow `-` followed by a dot or a digit digit in units.
|
||||||
|
let second = match toks.peek_forward(1) {
|
||||||
|
Some(v) => *v,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
toks.peek_backward(1).unwrap();
|
||||||
|
|
||||||
|
if second.kind == '.' || second.kind.is_ascii_digit() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
toks.peek_forward(1);
|
||||||
|
text.push('-');
|
||||||
|
text.push(toks.peek_forward(1).unwrap().kind);
|
||||||
|
} else if is_name(tok.kind) {
|
||||||
|
text.push(tok.kind);
|
||||||
|
toks.peek_forward(1);
|
||||||
|
} else if tok.kind == '\\' {
|
||||||
|
toks.peek_forward(1);
|
||||||
|
text.push_str(&peek_escape(toks)?);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Spanned { node: text, span })
|
||||||
|
}
|
||||||
|
@ -102,7 +102,7 @@ fn escape<I: Iterator<Item = Token>>(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value *= 16;
|
value *= 16;
|
||||||
value += as_hex(toks.next().unwrap().kind as u32)
|
value += as_hex(toks.next().unwrap().kind)
|
||||||
}
|
}
|
||||||
if toks.peek().is_some() && toks.peek().unwrap().kind.is_whitespace() {
|
if toks.peek().is_some() && toks.peek().unwrap().kind.is_whitespace() {
|
||||||
toks.next();
|
toks.next();
|
||||||
@ -287,7 +287,7 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
|
|||||||
if !next.kind.is_ascii_hexdigit() {
|
if !next.kind.is_ascii_hexdigit() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
value = (value << 4) + as_hex(toks.next().unwrap().kind as u32);
|
value = (value << 4) + as_hex(toks.next().unwrap().kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
if toks.peek().is_some() && toks.peek().unwrap().kind.is_ascii_whitespace() {
|
if toks.peek().is_some() && toks.peek().unwrap().kind.is_ascii_whitespace() {
|
||||||
|
@ -2,14 +2,17 @@ use std::iter::Iterator;
|
|||||||
|
|
||||||
use codemap::Spanned;
|
use codemap::Spanned;
|
||||||
|
|
||||||
use peekmore::{PeekMore, PeekMoreIterator};
|
use peekmore::PeekMoreIterator;
|
||||||
|
|
||||||
use crate::error::SassResult;
|
use crate::error::SassResult;
|
||||||
use crate::selector::Selector;
|
use crate::selector::Selector;
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::{Scope, Token};
|
use crate::{Scope, Token};
|
||||||
|
|
||||||
use super::{devour_whitespace, eat_ident, read_until_semicolon_or_closing_curly_brace};
|
use super::{
|
||||||
|
devour_whitespace, peek_ident_no_interpolation, read_until_closing_paren,
|
||||||
|
read_until_closing_quote, read_until_newline,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct VariableDecl {
|
pub(crate) struct VariableDecl {
|
||||||
pub val: Spanned<Value>,
|
pub val: Spanned<Value>,
|
||||||
@ -35,43 +38,93 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
|
|||||||
devour_whitespace(toks);
|
devour_whitespace(toks);
|
||||||
let mut default = false;
|
let mut default = false;
|
||||||
let mut global = false;
|
let mut global = false;
|
||||||
let mut raw = read_until_semicolon_or_closing_curly_brace(toks)
|
|
||||||
.into_iter()
|
|
||||||
.peekmore();
|
|
||||||
if toks.peek().is_some() && toks.peek().unwrap().kind == ';' {
|
|
||||||
toks.next();
|
|
||||||
}
|
|
||||||
let mut val_toks = Vec::new();
|
let mut val_toks = Vec::new();
|
||||||
while let Some(tok) = raw.next() {
|
let mut nesting = 0;
|
||||||
|
while let Some(tok) = toks.peek() {
|
||||||
match tok.kind {
|
match tok.kind {
|
||||||
|
';' => {
|
||||||
|
toks.next();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
'\\' => {
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
if toks.peek().is_some() {
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'"' | '\'' => {
|
||||||
|
let quote = toks.next().unwrap();
|
||||||
|
val_toks.push(quote);
|
||||||
|
val_toks.extend(read_until_closing_quote(toks, quote.kind));
|
||||||
|
}
|
||||||
|
'#' => {
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
match toks.peek().unwrap().kind {
|
||||||
|
'{' => nesting += 1,
|
||||||
|
';' => break,
|
||||||
|
'}' => {
|
||||||
|
if nesting == 0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nesting -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
}
|
||||||
|
'{' => break,
|
||||||
|
'}' => {
|
||||||
|
if nesting == 0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nesting -= 1;
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'/' => {
|
||||||
|
let next = toks.next().unwrap();
|
||||||
|
match toks.peek().unwrap().kind {
|
||||||
|
'/' => read_until_newline(toks),
|
||||||
|
_ => val_toks.push(next),
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
val_toks.extend(read_until_closing_paren(toks));
|
||||||
|
}
|
||||||
'!' => {
|
'!' => {
|
||||||
let next = raw.next().unwrap();
|
let pos = tok.pos();
|
||||||
match next.kind {
|
if toks.peek_forward(1).is_none() {
|
||||||
'i' => todo!("!important"),
|
return Err(("Expected identifier.", pos).into());
|
||||||
'g' => {
|
}
|
||||||
let s = eat_ident(&mut raw, scope, super_selector)?;
|
// todo: it should not be possible to declare the same flag more than once
|
||||||
if s.node.to_ascii_lowercase().as_str() == "lobal" {
|
let ident = peek_ident_no_interpolation(toks, false)?;
|
||||||
|
match ident.node.to_ascii_lowercase().as_str() {
|
||||||
|
"global" => {
|
||||||
|
toks.take(7).for_each(drop);
|
||||||
global = true;
|
global = true;
|
||||||
} else {
|
|
||||||
return Err(("Invalid flag name.", s.span).into());
|
|
||||||
}
|
}
|
||||||
}
|
"default" => {
|
||||||
'd' => {
|
toks.take(8).for_each(drop);
|
||||||
let s = eat_ident(&mut raw, scope, super_selector)?;
|
|
||||||
if s.to_ascii_lowercase().as_str() == "efault" {
|
|
||||||
default = true;
|
default = true;
|
||||||
} else {
|
}
|
||||||
return Err(("Invalid flag name.", s.span).into());
|
"important" => {
|
||||||
|
toks.reset_view();
|
||||||
|
val_toks.push(toks.next().unwrap());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(("Invalid flag name.", ident.span).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(("Invalid flag name.", next.pos()).into()),
|
|
||||||
}
|
}
|
||||||
}
|
_ => val_toks.push(toks.next().unwrap()),
|
||||||
_ => val_toks.push(tok),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
devour_whitespace(toks);
|
devour_whitespace(toks);
|
||||||
|
|
||||||
let val = Value::from_vec(val_toks, scope, super_selector)?;
|
let val = Value::from_vec(val_toks, scope, super_selector)?;
|
||||||
Ok(VariableDecl::new(val, default, global))
|
Ok(VariableDecl::new(val, default, global))
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@ use peekmore::PeekMoreIterator;
|
|||||||
use crate::error::SassResult;
|
use crate::error::SassResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
use crate::selector::Selector;
|
use crate::selector::Selector;
|
||||||
use crate::utils::{devour_whitespace, parse_interpolation, peek_until_closing_curly_brace};
|
use crate::utils::{
|
||||||
|
devour_whitespace, parse_interpolation, peek_escape, peek_until_closing_curly_brace,
|
||||||
|
};
|
||||||
use crate::Token;
|
use crate::Token;
|
||||||
|
|
||||||
pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>(
|
pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>(
|
||||||
@ -127,59 +129,6 @@ pub(crate) fn try_eat_url<I: Iterator<Item = Token>>(
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::utils::{as_hex, hex_char_for, is_name};
|
|
||||||
|
|
||||||
fn peek_escape<I: Iterator<Item = Token>>(toks: &mut PeekMoreIterator<I>) -> SassResult<String> {
|
|
||||||
let mut value = 0;
|
|
||||||
let first = match toks.peek() {
|
|
||||||
Some(t) => t,
|
|
||||||
None => return Ok(String::new()),
|
|
||||||
};
|
|
||||||
if first.kind == '\n' {
|
|
||||||
return Err(("Expected escape sequence.", first.pos()).into());
|
|
||||||
} else if first.kind.is_ascii_hexdigit() {
|
|
||||||
for _ in 0..6 {
|
|
||||||
let next = match toks.peek_forward(1) {
|
|
||||||
Some(t) => t,
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
if !next.kind.is_ascii_hexdigit() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
value *= 16;
|
|
||||||
value += as_hex(toks.next().unwrap().kind as u32)
|
|
||||||
}
|
|
||||||
if toks.peek().is_some() && toks.peek().unwrap().kind.is_whitespace() {
|
|
||||||
toks.peek_forward(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value = toks.peek_forward(1).unwrap().kind as u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tabs are emitted literally
|
|
||||||
// TODO: figure out where this check is done
|
|
||||||
// in the source dart
|
|
||||||
if value == 0x9 {
|
|
||||||
return Ok("\\\t".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let c = std::char::from_u32(value).unwrap();
|
|
||||||
if is_name(c) {
|
|
||||||
Ok(c.to_string())
|
|
||||||
} else if value <= 0x1F || value == 0x7F {
|
|
||||||
let mut buf = String::with_capacity(4);
|
|
||||||
buf.push('\\');
|
|
||||||
if value > 0xF {
|
|
||||||
buf.push(hex_char_for(value >> 4));
|
|
||||||
}
|
|
||||||
buf.push(hex_char_for(value & 0xF));
|
|
||||||
buf.push(' ');
|
|
||||||
Ok(buf)
|
|
||||||
} else {
|
|
||||||
Ok(format!("\\{}", c))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use codemap::Spanned;
|
use codemap::Spanned;
|
||||||
|
|
||||||
|
@ -113,3 +113,25 @@ test!(
|
|||||||
"a {\n $x : true;\n color: $x;\n}\n",
|
"a {\n $x : true;\n color: $x;\n}\n",
|
||||||
"a {\n color: true;\n}\n"
|
"a {\n color: true;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
important_in_variable,
|
||||||
|
"$a: !important;\n\na {\n color: $a;\n}\n",
|
||||||
|
"a {\n color: !important;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
important_in_variable_casing,
|
||||||
|
"$a: !ImPoRtAnT;\n\na {\n color: $a;\n}\n",
|
||||||
|
"a {\n color: !important;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
exclamation_in_quoted_string,
|
||||||
|
"$a: \"big bang!\";\n\na {\n color: $a;\n}\n",
|
||||||
|
"a {\n color: \"big bang!\";\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
flag_uses_escape_sequence,
|
||||||
|
"$a: red;\n\na {\n $a: green !\\67 lobal;\n}\n\na {\n color: $a;\n}\n",
|
||||||
|
"a {\n color: green;\n}\n"
|
||||||
|
);
|
||||||
|
error!(ends_with_bang, "$a: red !;", "Error: Expected identifier.");
|
||||||
|
error!(unknown_flag, "$a: red !foo;", "Error: Invalid flag name.");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user