grass/src/utils.rs

606 lines
18 KiB
Rust
Raw Normal View History

2020-03-01 14:53:52 -05:00
use std::iter::{Iterator, Peekable};
2020-03-29 13:28:17 -04:00
use crate::common::QuoteKind;
2020-02-16 10:54:25 -05:00
use crate::error::SassResult;
use crate::lexer::Lexer;
2020-03-01 12:03:14 -05:00
use crate::selector::Selector;
2020-01-26 09:28:44 -05:00
use crate::value::Value;
2020-03-29 13:28:17 -04:00
use crate::{Scope, Token};
pub(crate) trait IsWhitespace {
fn is_whitespace(&self) -> bool;
}
2020-01-20 16:00:37 -05:00
pub(crate) fn devour_whitespace<I: Iterator<Item = W>, W: IsWhitespace>(
s: &mut Peekable<I>,
) -> bool {
let mut found_whitespace = false;
while let Some(w) = s.peek() {
if !w.is_whitespace() {
break;
}
found_whitespace = true;
s.next();
}
found_whitespace
}
pub(crate) trait IsComment {
fn is_comment(&self) -> bool;
}
2020-03-29 13:28:17 -04:00
pub(crate) fn devour_whitespace_or_comment<I: Iterator<Item = Token>>(
s: &mut Peekable<I>,
2020-03-29 13:28:17 -04:00
) -> SassResult<bool> {
let mut found_whitespace = false;
while let Some(w) = s.peek() {
2020-03-29 13:28:17 -04:00
if w.kind == '/' {
s.next();
match s.peek().unwrap().kind {
'*' => {
eat_comment(s, &Scope::new(), &Selector::new())?;
}
'/' => read_until_newline(s),
_ => return Err("Expected expression.".into()),
};
found_whitespace = true;
continue;
}
if !w.is_whitespace() {
break;
}
found_whitespace = true;
s.next();
}
2020-03-29 13:28:17 -04:00
Ok(found_whitespace)
}
2020-02-24 17:08:49 -05:00
pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
tokens: &mut Peekable<I>,
scope: &Scope,
2020-03-01 12:03:14 -05:00
super_selector: &Selector,
) -> SassResult<Value> {
let mut val = String::new();
while let Some(tok) = tokens.next() {
2020-01-18 20:24:28 -05:00
match tok.kind {
2020-03-29 13:28:17 -04:00
'}' => break,
'{' => todo!("invalid character in interpolation"),
q @ '"' | q @ '\'' => {
val.push_str(&parse_quoted_string(tokens, scope, q, super_selector)?.to_string())
}
2020-03-29 13:28:17 -04:00
'$' => val.push_str(
&scope
.get_var(&eat_ident(tokens, scope, super_selector)?)?
.clone()
.unquote()
.to_string(),
),
2020-03-29 13:28:17 -04:00
'#' => {
if tokens.next().unwrap().kind == '{' {
val.push_str(
&Lexer::new(
&parse_interpolation(tokens, scope, super_selector)?.to_string(),
)
.map(|x| x.kind.to_string())
.collect::<String>(),
)
} else {
return Err("Expected identifier.".into());
}
}
_ => val.push_str(&tok.kind.to_string()),
}
}
2020-03-01 12:03:14 -05:00
if val.trim().is_empty() {
return Ok(Value::Ident(String::new(), QuoteKind::None));
}
Ok(
Value::from_tokens(&mut Lexer::new(&val).peekable(), scope, super_selector)?
.eval()?
2020-03-01 12:03:14 -05:00
.unquote(),
)
}
2020-01-29 21:02:32 -05:00
pub(crate) struct VariableDecl {
pub val: Value,
pub default: bool,
pub global: bool,
2020-01-29 21:02:32 -05:00
}
impl VariableDecl {
pub const fn new(val: Value, default: bool, global: bool) -> VariableDecl {
VariableDecl {
val,
default,
global,
}
2020-01-29 21:02:32 -05:00
}
}
2020-03-24 22:13:38 -04:00
// Eat tokens until an open curly brace
//
// Does not consume the open curly brace
pub(crate) fn read_until_open_curly_brace<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> Vec<Token> {
let mut val = Vec::new();
let mut n = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
2020-03-29 13:28:17 -04:00
'{' => n += 1,
'}' => n -= 1,
2020-03-24 22:13:38 -04:00
_ => {}
}
if n == 1 {
break;
}
val.push(toks.next().unwrap());
}
val
}
pub(crate) fn read_until_closing_curly_brace<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> Vec<Token> {
let mut t = Vec::new();
let mut nesting = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
2020-03-29 13:28:17 -04:00
q @ '"' | q @ '\'' => {
t.push(toks.next().unwrap());
t.extend(read_until_closing_quote(toks, q));
2020-03-24 22:13:38 -04:00
}
2020-03-29 13:28:17 -04:00
'{' => {
2020-03-24 22:13:38 -04:00
nesting += 1;
t.push(toks.next().unwrap());
}
2020-03-29 13:28:17 -04:00
'}' => {
2020-03-24 22:13:38 -04:00
if nesting == 0 {
break;
} else {
nesting -= 1;
t.push(toks.next().unwrap());
}
}
_ => t.push(toks.next().unwrap()),
}
}
devour_whitespace(toks);
t
}
2020-03-23 16:29:55 -04:00
pub(crate) fn read_until_closing_quote<I: Iterator<Item = Token>>(
2020-01-14 19:34:13 -05:00
toks: &mut Peekable<I>,
2020-03-29 13:28:17 -04:00
q: char,
2020-03-23 16:29:55 -04:00
) -> Vec<Token> {
let mut is_escaped = false;
let mut t = Vec::new();
for tok in toks {
match tok.kind {
2020-03-29 13:28:17 -04:00
'"' if !is_escaped && q == '"' => {
2020-03-23 16:29:55 -04:00
t.push(tok);
break;
}
2020-03-29 13:28:17 -04:00
'"' if is_escaped => {
2020-03-23 16:29:55 -04:00
t.push(tok);
is_escaped = false;
continue;
}
2020-03-29 13:28:17 -04:00
'\'' if !is_escaped && q == '\'' => {
2020-03-23 16:29:55 -04:00
t.push(tok);
break;
}
2020-03-29 13:28:17 -04:00
'\'' if is_escaped => {
2020-03-23 16:29:55 -04:00
t.push(tok);
is_escaped = false;
continue;
}
2020-03-29 13:28:17 -04:00
'\\' if !is_escaped => {
t.push(tok);
is_escaped = true
2020-03-23 22:13:11 -04:00
}
2020-03-29 13:28:17 -04:00
'\\' => {
2020-03-23 16:29:55 -04:00
is_escaped = false;
t.push(tok);
continue;
}
2020-03-29 13:28:17 -04:00
_ if is_escaped => {
is_escaped = false;
t.push(tok);
}
2020-03-23 16:29:55 -04:00
_ => t.push(tok),
}
}
t
}
2020-03-29 13:28:17 -04:00
pub(crate) fn read_until_semicolon_or_closing_curly_brace<I: Iterator<Item = Token>>(
2020-03-23 16:29:55 -04:00
toks: &mut Peekable<I>,
) -> Vec<Token> {
let mut t = Vec::new();
2020-01-29 21:02:32 -05:00
let mut nesting = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
2020-03-29 13:28:17 -04:00
';' => {
2020-01-29 21:02:32 -05:00
break;
}
2020-03-29 23:00:39 -04:00
'\\' => {
t.push(toks.next().unwrap());
t.push(toks.next().unwrap());
}
2020-03-29 13:28:17 -04:00
'"' | '\'' => {
2020-03-23 16:29:55 -04:00
let quote = toks.next().unwrap();
t.push(quote.clone());
2020-03-29 13:28:17 -04:00
t.extend(read_until_closing_quote(toks, quote.kind));
}
2020-03-29 13:28:17 -04:00
'{' => {
2020-01-29 21:02:32 -05:00
nesting += 1;
2020-03-23 16:29:55 -04:00
t.push(toks.next().unwrap());
2020-01-29 21:02:32 -05:00
}
2020-03-29 13:28:17 -04:00
'}' => {
if nesting == 0 {
break;
} else {
nesting -= 1;
t.push(toks.next().unwrap());
}
}
_ => t.push(toks.next().unwrap()),
}
}
devour_whitespace(toks);
t
}
pub(crate) fn read_until_semicolon_or_open_or_closing_curly_brace<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> Vec<Token> {
let mut t = Vec::new();
let mut nesting = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
';' => {
break;
}
2020-03-29 23:00:39 -04:00
'\\' => {
t.push(toks.next().unwrap());
t.push(toks.next().unwrap());
}
2020-03-29 13:28:17 -04:00
'"' | '\'' => {
let quote = toks.next().unwrap();
t.push(quote.clone());
t.extend(read_until_closing_quote(toks, quote.kind));
}
'#' => {
t.push(toks.next().unwrap());
match toks.peek().unwrap().kind {
'{' => nesting += 1,
';' => break,
'}' => {
if nesting == 0 {
break;
} else {
nesting -= 1;
}
}
_ => {}
}
t.push(toks.next().unwrap());
}
'{' => break,
'}' => {
2020-01-29 21:02:32 -05:00
if nesting == 0 {
break;
} else {
nesting -= 1;
2020-03-23 16:29:55 -04:00
t.push(toks.next().unwrap());
2020-01-29 21:02:32 -05:00
}
}
2020-03-23 16:29:55 -04:00
_ => t.push(toks.next().unwrap()),
2020-01-29 21:02:32 -05:00
}
}
2020-01-14 19:34:13 -05:00
devour_whitespace(toks);
2020-03-23 16:29:55 -04:00
t
}
pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<VariableDecl> {
devour_whitespace(toks);
let mut default = false;
let mut global = false;
2020-03-29 13:28:17 -04:00
let mut raw = read_until_semicolon_or_closing_curly_brace(toks)
2020-03-23 16:29:55 -04:00
.into_iter()
.peekable();
2020-03-29 23:00:39 -04:00
if toks.peek().is_some() && toks.peek().unwrap().kind == ';' {
2020-03-29 13:28:17 -04:00
toks.next();
}
let mut x = Vec::new();
while let Some(tok) = raw.next() {
match tok.kind {
'!' => {
let next = raw.next().unwrap();
match next.kind {
'i' => todo!("!important"),
'g' => {
if eat_ident(&mut raw, scope, super_selector)?
.to_ascii_lowercase()
.as_str()
== "lobal"
{
global = true;
} else {
return Err("Invalid flag name.".into());
}
}
'd' => {
if eat_ident(&mut raw, scope, super_selector)?
.to_ascii_lowercase()
.as_str()
== "efault"
{
default = true;
} else {
return Err("Invalid flag name.".into());
}
}
_ => return Err("Invalid flag name.".into()),
}
}
_ => x.push(tok),
}
}
devour_whitespace(toks);
let val = Value::from_tokens(&mut x.into_iter().peekable(), scope, super_selector)?;
Ok(VariableDecl::new(val, default, global))
2020-01-14 19:34:13 -05:00
}
2020-03-29 13:28:17 -04:00
pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
2020-03-01 12:03:14 -05:00
super_selector: &Selector,
) -> SassResult<String> {
let mut s = String::new();
while let Some(tok) = toks.peek() {
2020-03-29 13:28:17 -04:00
match tok.kind {
'#' => {
toks.next();
2020-03-29 13:28:17 -04:00
if toks.peek().unwrap().kind == '{' {
toks.next();
s.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string());
} else {
return Err("Expected identifier.".into());
}
}
'-' | '_' | _ if tok.kind.is_alphanumeric() => s.push(toks.next().unwrap().kind),
2020-03-29 13:28:17 -04:00
'\\' => {
toks.next();
2020-03-29 13:28:17 -04:00
let mut n = String::new();
while let Some(c) = toks.peek() {
if !c.kind.is_ascii_hexdigit() || n.len() > 6 {
break;
}
n.push(c.kind);
toks.next();
}
if n.is_empty() {
let c = toks.next().unwrap().kind;
if (c == '-' && !s.is_empty()) || c.is_ascii_alphabetic() {
s.push(c);
} else {
s.push_str(&format!("\\{}", c));
}
continue;
}
devour_whitespace(toks);
let c = std::char::from_u32(u32::from_str_radix(&n, 16).unwrap()).unwrap();
if c.is_control() && c != '\t' {
s.push_str(&format!("\\{} ", n.to_ascii_lowercase()));
} else if !c.is_ascii_alphanumeric() && s.is_empty() && c.is_ascii() {
s.push_str(&format!("\\{}", c));
} else if c.is_numeric() && s.is_empty() {
s.push_str(&format!("\\{} ", n))
} else {
s.push(c);
};
}
_ if !tok.kind.is_ascii() && !tok.kind.is_control() => {
s.push(toks.next().unwrap().kind)
}
2020-03-29 13:28:17 -04:00
_ => break,
}
}
Ok(s)
}
pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> SassResult<String> {
let mut s = String::new();
while let Some(tok) = toks.peek() {
match tok.kind {
'#' => {
break;
}
'-' | '_' | _ if tok.kind.is_alphanumeric() => s.push(toks.next().unwrap().kind),
2020-03-29 13:28:17 -04:00
'\\' => {
2020-03-22 13:45:41 -04:00
s.push('\\');
toks.next();
if let Some(tok) = toks.next() {
match tok.kind {
2020-03-29 13:28:17 -04:00
'+' => s.push('+'),
'\\' => s.push('\\'),
2020-03-22 13:45:41 -04:00
_ => todo!("value after \\"),
}
} else {
todo!()
}
}
_ if !tok.kind.is_ascii() && !tok.kind.is_control() => {
s.push(toks.next().unwrap().kind)
}
_ => break,
}
}
Ok(s)
}
2020-03-29 13:28:17 -04:00
pub(crate) fn eat_number<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> SassResult<String> {
let mut whole = String::new();
while let Some(c) = toks.peek() {
if !c.kind.is_numeric() {
break;
}
let tok = toks.next().unwrap();
whole.push(tok.kind);
}
if toks.peek().is_none() {
return Ok(whole);
}
let mut dec = String::new();
if toks.peek().unwrap().kind == '.' {
toks.next();
dec.push('.');
while let Some(c) = toks.peek() {
if !c.kind.is_numeric() {
break;
}
let tok = toks.next().unwrap();
dec.push(tok.kind);
}
}
if dec.len() == 1 {
return Err("Expected digit.".into());
}
whole.push_str(&dec);
Ok(whole)
}
/// Eat tokens until a newline
///
/// This exists largely to eat silent comments, "//"
/// We only have to check for \n as the lexing step normalizes all newline characters
///
/// The newline is consumed
pub(crate) fn read_until_newline<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) {
while let Some(tok) = toks.next() {
if tok.kind == '\n' {
break;
}
}
}
/// Eat and return the contents of a comment.
///
/// This function assumes that the starting "/*" has already been consumed
/// The entirety of the comment, including the ending "*/" is consumed.
/// Note that the ending "*/" is not included in the output.
pub(crate) fn eat_comment<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
_scope: &Scope,
_super_selector: &Selector,
) -> SassResult<String> {
let mut comment = String::new();
while let Some(tok) = toks.next() {
if tok.kind == '*' {
if toks.peek().unwrap().kind == '/' {
toks.next();
break;
}
}
comment.push(tok.kind);
}
devour_whitespace(toks);
Ok(comment)
}
pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
2020-03-29 13:28:17 -04:00
q: char,
2020-03-01 12:03:14 -05:00
super_selector: &Selector,
) -> SassResult<Value> {
let mut s = String::new();
let mut is_escaped = false;
let mut found_interpolation = false;
while let Some(tok) = toks.next() {
match tok.kind {
2020-03-29 13:28:17 -04:00
'"' if !is_escaped && q == '"' => break,
'"' if is_escaped => {
s.push('"');
is_escaped = false;
continue;
}
2020-03-29 13:28:17 -04:00
'\'' if !is_escaped && q == '\'' => break,
'\'' if is_escaped => {
s.push('\'');
is_escaped = false;
continue;
}
2020-03-29 13:28:17 -04:00
'\\' if !is_escaped => is_escaped = true,
'\\' => {
2020-02-24 19:13:28 -05:00
is_escaped = false;
s.push('\\');
continue;
}
2020-03-29 13:28:17 -04:00
'#' if !is_escaped => {
if toks.peek().unwrap().kind == '{' {
toks.next();
found_interpolation = true;
s.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string());
continue;
} else {
s.push('#');
}
}
2020-03-29 13:28:17 -04:00
'\n' => return Err("Expected \".".into()),
v if v.is_ascii_hexdigit() && is_escaped => {
let mut n = v.to_string();
while let Some(c) = toks.peek() {
if !c.kind.is_ascii_hexdigit() || n.len() > 6 {
break;
}
n.push(c.kind);
toks.next();
}
let c = std::char::from_u32(u32::from_str_radix(&n, 16).unwrap()).unwrap();
if c.is_control() && c != '\t' && c != '\0' {
s.push_str(&format!("\\{} ", n.to_ascii_lowercase()));
} else {
s.push(c);
}
is_escaped = false;
continue;
}
2020-03-29 13:28:17 -04:00
_ if is_escaped => {
is_escaped = false;
}
_ => {}
}
2020-03-29 13:28:17 -04:00
if is_escaped && tok.kind != '\\' {
is_escaped = false;
}
2020-03-29 13:28:17 -04:00
if tok.kind != '\\' {
s.push_str(&tok.kind.to_string());
}
}
let quotes = if found_interpolation {
QuoteKind::Double
} else {
match q {
2020-03-29 13:28:17 -04:00
'"' => QuoteKind::Double,
'\'' => QuoteKind::Single,
_ => unreachable!(),
}
};
Ok(Value::Ident(s, quotes))
}