HACK: somewhat handle unicode escapes
This commit is contained in:
parent
2f0fbd2415
commit
f9730b9ec5
76
src/lexer.rs
76
src/lexer.rs
@ -17,6 +17,7 @@ pub(crate) struct Lexer<'a> {
|
|||||||
tokens: Vec<Token>,
|
tokens: Vec<Token>,
|
||||||
buf: Peekable<Chars<'a>>,
|
buf: Peekable<Chars<'a>>,
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
|
should_emit_backslash: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Lexer<'a> {
|
impl<'a> Iterator for Lexer<'a> {
|
||||||
@ -36,6 +37,13 @@ impl<'a> Iterator for Lexer<'a> {
|
|||||||
TokenKind::Whitespace(Whitespace::$whitespace)
|
TokenKind::Whitespace(Whitespace::$whitespace)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
if self.should_emit_backslash > 0 {
|
||||||
|
self.should_emit_backslash -= 1;
|
||||||
|
return Some(Token {
|
||||||
|
kind: TokenKind::Symbol(Symbol::BackSlash),
|
||||||
|
pos: self.pos,
|
||||||
|
});
|
||||||
|
}
|
||||||
let kind: TokenKind = match self.buf.peek().unwrap_or(&'\0') {
|
let kind: TokenKind = match self.buf.peek().unwrap_or(&'\0') {
|
||||||
'a'..='z' | 'A'..='Z' | '_' => self.lex_ident(),
|
'a'..='z' | 'A'..='Z' | '_' => self.lex_ident(),
|
||||||
'-' => {
|
'-' => {
|
||||||
@ -107,7 +115,7 @@ impl<'a> Iterator for Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
'?' => symbol!(self, QuestionMark),
|
'?' => symbol!(self, QuestionMark),
|
||||||
'\\' => symbol!(self, BackSlash),
|
'\\' => self.lex_back_slash().0,
|
||||||
'~' => symbol!(self, Tilde),
|
'~' => symbol!(self, Tilde),
|
||||||
'\'' => symbol!(self, SingleQuote),
|
'\'' => symbol!(self, SingleQuote),
|
||||||
'"' => symbol!(self, DoubleQuote),
|
'"' => symbol!(self, DoubleQuote),
|
||||||
@ -158,6 +166,7 @@ impl<'a> Lexer<'a> {
|
|||||||
tokens: Vec::with_capacity(buf.len()),
|
tokens: Vec::with_capacity(buf.len()),
|
||||||
buf: buf.chars().peekable(),
|
buf: buf.chars().peekable(),
|
||||||
pos: Pos::new(),
|
pos: Pos::new(),
|
||||||
|
should_emit_backslash: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +218,51 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lex_back_slash(&mut self) -> (TokenKind, bool) {
|
||||||
|
self.buf.next();
|
||||||
|
self.pos.next_char();
|
||||||
|
if self.buf.peek() == Some(&'\\') {
|
||||||
|
self.buf.next();
|
||||||
|
self.pos.next_char();
|
||||||
|
self.should_emit_backslash = 1;
|
||||||
|
(TokenKind::Symbol(Symbol::BackSlash), true)
|
||||||
|
} else {
|
||||||
|
let mut n = String::new();
|
||||||
|
while let Some(c) = self.buf.peek() {
|
||||||
|
if !c.is_ascii_hexdigit() || n.len() > 6 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
n.push(*c);
|
||||||
|
self.buf.next();
|
||||||
|
self.pos.next_char();
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.is_empty() {
|
||||||
|
return (TokenKind::Symbol(Symbol::BackSlash), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut string = std::char::from_u32(u32::from_str_radix(&n, 16).unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
self.devour_whitespace();
|
||||||
|
if let TokenKind::Ident(s) = self.lex_ident() {
|
||||||
|
string.push_str(&s);
|
||||||
|
}
|
||||||
|
(TokenKind::Ident(string), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn devour_whitespace(&mut self) {
|
||||||
|
while let Some(c) = self.buf.peek() {
|
||||||
|
if c.is_ascii_whitespace() {
|
||||||
|
self.buf.next();
|
||||||
|
self.pos.next_char();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn lex_forward_slash(&mut self) -> TokenKind {
|
fn lex_forward_slash(&mut self) -> TokenKind {
|
||||||
self.buf.next();
|
self.buf.next();
|
||||||
self.pos.next_char();
|
self.pos.next_char();
|
||||||
@ -331,18 +385,30 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle weird characters that *are* ascii
|
|
||||||
// e.g. how do we handle `color: ;`
|
|
||||||
fn lex_ident(&mut self) -> TokenKind {
|
fn lex_ident(&mut self) -> TokenKind {
|
||||||
let mut string = String::with_capacity(99);
|
let mut string = String::with_capacity(99);
|
||||||
while let Some(c) = self.buf.peek() {
|
while let Some(c) = self.buf.peek() {
|
||||||
// we know that the first char is alphabetic from peeking
|
if !c.is_alphanumeric() && c != &'-' && c != &'_' && c != &'\\' && c.is_ascii() {
|
||||||
if !c.is_alphanumeric() && c != &'-' && c != &'_' && c.is_ascii() {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if !c.is_ascii() {
|
if !c.is_ascii() {
|
||||||
IS_UTF8.store(true, Ordering::Relaxed);
|
IS_UTF8.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
if c == &'\\' {
|
||||||
|
match self.lex_back_slash() {
|
||||||
|
(TokenKind::Ident(s), _) => string.push_str(&s),
|
||||||
|
(TokenKind::Symbol(..), true) => {
|
||||||
|
self.should_emit_backslash = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(TokenKind::Symbol(..), false) => {
|
||||||
|
self.should_emit_backslash = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let tok = self
|
let tok = self
|
||||||
.buf
|
.buf
|
||||||
.next()
|
.next()
|
||||||
|
13
src/utils.rs
13
src/utils.rs
@ -160,6 +160,19 @@ pub(crate) fn flatten_ident<I: Iterator<Item = Token>>(
|
|||||||
toks.next();
|
toks.next();
|
||||||
s.push_str(n)
|
s.push_str(n)
|
||||||
}
|
}
|
||||||
|
TokenKind::Symbol(Symbol::BackSlash) => {
|
||||||
|
s.push('\\');
|
||||||
|
toks.next();
|
||||||
|
if let Some(tok) = toks.next() {
|
||||||
|
match tok.kind {
|
||||||
|
TokenKind::Symbol(Symbol::Plus) => s.push('+'),
|
||||||
|
TokenKind::Symbol(Symbol::BackSlash) => s.push('\\'),
|
||||||
|
_ => todo!("value after \\"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,6 +294,23 @@ impl Value {
|
|||||||
TokenKind::Keyword(Keyword::To(s)) => Ok(Value::Ident(s, QuoteKind::None)),
|
TokenKind::Keyword(Keyword::To(s)) => Ok(Value::Ident(s, QuoteKind::None)),
|
||||||
TokenKind::AtRule(_) => Err("expected \";\".".into()),
|
TokenKind::AtRule(_) => Err("expected \";\".".into()),
|
||||||
TokenKind::Error(e) => return Err(e),
|
TokenKind::Error(e) => return Err(e),
|
||||||
|
TokenKind::Symbol(Symbol::BackSlash) => {
|
||||||
|
if let Some(tok) = toks.next() {
|
||||||
|
match tok.kind {
|
||||||
|
TokenKind::Symbol(Symbol::Plus) => Ok(Value::Ident(
|
||||||
|
"\\+".to_string() + &flatten_ident(toks, scope, super_selector)?,
|
||||||
|
QuoteKind::None,
|
||||||
|
)),
|
||||||
|
TokenKind::Symbol(Symbol::BackSlash) => Ok(Value::Ident(
|
||||||
|
"\\\\".to_string() + &flatten_ident(toks, scope, super_selector)?,
|
||||||
|
QuoteKind::None,
|
||||||
|
)),
|
||||||
|
_ => todo!("value after \\"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
TokenKind::Op(Op::Plus) | TokenKind::Symbol(Symbol::Plus) => {
|
TokenKind::Op(Op::Plus) | TokenKind::Symbol(Symbol::Plus) => {
|
||||||
devour_whitespace_or_comment(toks);
|
devour_whitespace_or_comment(toks);
|
||||||
let v = Self::_from_tokens(toks, scope, super_selector)?;
|
let v = Self::_from_tokens(toks, scope, super_selector)?;
|
||||||
|
@ -66,3 +66,19 @@ test!(
|
|||||||
"a {\n color: red😁\n}\n",
|
"a {\n color: red😁\n}\n",
|
||||||
"@charset \"UTF-8\";\na {\n color: red😁;\n}\n"
|
"@charset \"UTF-8\";\na {\n color: red😁;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
escape_recognized_as_at_rule,
|
||||||
|
"@\\69 f true {\n a {\n b: c;\n }\n}\n",
|
||||||
|
"a {\n b: c;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
escape_in_middle,
|
||||||
|
"a {\n color: b\\6cue;\n}\n",
|
||||||
|
"a {\n color: blue;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
escape_at_end,
|
||||||
|
"a {\n color: blu\\65;\n}\n",
|
||||||
|
"a {\n color: blue;\n}\n"
|
||||||
|
);
|
||||||
|
test!(double_escape_is_preserved, "a {\n color: r\\\\65;\n}\n");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user