resolve merge conflicts with master
This commit is contained in:
commit
734e0875da
5
.gitignore
vendored
5
.gitignore
vendored
@ -23,3 +23,8 @@ flamegraph.svg
|
|||||||
susy
|
susy
|
||||||
bulma*
|
bulma*
|
||||||
bootstrap*
|
bootstrap*
|
||||||
|
materialize
|
||||||
|
uikit
|
||||||
|
bourbon
|
||||||
|
foundation-sites
|
||||||
|
sassline
|
||||||
|
@ -2,6 +2,10 @@ use crate::parse::Stmt;
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Keyframes {
|
pub(crate) struct Keyframes {
|
||||||
|
/// `@keyframes` can contain a browser prefix,
|
||||||
|
/// e.g. `@-webkit-keyframes { ... }`, and therefore
|
||||||
|
/// we cannot be certain of the name of the at-rule
|
||||||
|
pub rule: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub body: Vec<Stmt>,
|
pub body: Vec<Stmt>,
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use std::convert::TryFrom;
|
|||||||
|
|
||||||
use codemap::Spanned;
|
use codemap::Spanned;
|
||||||
|
|
||||||
use crate::error::SassError;
|
use crate::{common::unvendor, error::SassError};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AtRuleKind {
|
pub enum AtRuleKind {
|
||||||
@ -72,31 +72,35 @@ pub enum AtRuleKind {
|
|||||||
impl TryFrom<&Spanned<String>> for AtRuleKind {
|
impl TryFrom<&Spanned<String>> for AtRuleKind {
|
||||||
type Error = Box<SassError>;
|
type Error = Box<SassError>;
|
||||||
fn try_from(c: &Spanned<String>) -> Result<Self, Box<SassError>> {
|
fn try_from(c: &Spanned<String>) -> Result<Self, Box<SassError>> {
|
||||||
Ok(match c.node.as_str() {
|
match c.node.as_str() {
|
||||||
"use" => Self::Use,
|
"use" => return Ok(Self::Use),
|
||||||
"forward" => Self::Forward,
|
"forward" => return Ok(Self::Forward),
|
||||||
"import" => Self::Import,
|
"import" => return Ok(Self::Import),
|
||||||
"mixin" => Self::Mixin,
|
"mixin" => return Ok(Self::Mixin),
|
||||||
"include" => Self::Include,
|
"include" => return Ok(Self::Include),
|
||||||
"function" => Self::Function,
|
"function" => return Ok(Self::Function),
|
||||||
"return" => Self::Return,
|
"return" => return Ok(Self::Return),
|
||||||
"extend" => Self::Extend,
|
"extend" => return Ok(Self::Extend),
|
||||||
"at-root" => Self::AtRoot,
|
"at-root" => return Ok(Self::AtRoot),
|
||||||
"error" => Self::Error,
|
"error" => return Ok(Self::Error),
|
||||||
"warn" => Self::Warn,
|
"warn" => return Ok(Self::Warn),
|
||||||
"debug" => Self::Debug,
|
"debug" => return Ok(Self::Debug),
|
||||||
"if" => Self::If,
|
"if" => return Ok(Self::If),
|
||||||
"each" => Self::Each,
|
"each" => return Ok(Self::Each),
|
||||||
"for" => Self::For,
|
"for" => return Ok(Self::For),
|
||||||
"while" => Self::While,
|
"while" => return Ok(Self::While),
|
||||||
"charset" => Self::Charset,
|
"charset" => return Ok(Self::Charset),
|
||||||
"supports" => Self::Supports,
|
"supports" => return Ok(Self::Supports),
|
||||||
"keyframes" => Self::Keyframes,
|
"content" => return Ok(Self::Content),
|
||||||
"content" => Self::Content,
|
"media" => return Ok(Self::Media),
|
||||||
"media" => Self::Media,
|
|
||||||
"else" => return Err(("This at-rule is not allowed here.", c.span).into()),
|
"else" => return Err(("This at-rule is not allowed here.", c.span).into()),
|
||||||
"" => return Err(("Expected identifier.", c.span).into()),
|
"" => return Err(("Expected identifier.", c.span).into()),
|
||||||
s => Self::Unknown(s.to_owned()),
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match unvendor(&c.node) {
|
||||||
|
"keyframes" => Self::Keyframes,
|
||||||
|
_ => Self::Unknown(c.node.to_owned()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,16 +5,84 @@ use crate::{
|
|||||||
value::Value,
|
value::Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn is_ms_filter(s: &str) -> bool {
|
||||||
|
let mut chars = s.chars();
|
||||||
|
|
||||||
|
if let Some(c) = chars.next() {
|
||||||
|
if !matches!(c, 'a'..='z' | 'A'..='Z') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in &mut chars {
|
||||||
|
match c {
|
||||||
|
' ' | '\t' | '\n' => break,
|
||||||
|
'a'..='z' | 'A'..='Z' => continue,
|
||||||
|
'=' => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in chars {
|
||||||
|
match c {
|
||||||
|
' ' | '\t' | '\n' => continue,
|
||||||
|
'=' => return true,
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::is_ms_filter;
|
||||||
|
#[test]
|
||||||
|
fn test_is_ms_filter() {
|
||||||
|
assert!(is_ms_filter("a=a"));
|
||||||
|
assert!(is_ms_filter("a="));
|
||||||
|
assert!(is_ms_filter("a \t\n =a"));
|
||||||
|
assert!(!is_ms_filter("a \t\n a=a"));
|
||||||
|
assert!(!is_ms_filter("aa"));
|
||||||
|
assert!(!is_ms_filter(" aa"));
|
||||||
|
assert!(!is_ms_filter("=a"));
|
||||||
|
assert!(!is_ms_filter("1=a"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
pub(crate) fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
if args.len() <= 1 {
|
||||||
match args.get_err(0, "color")? {
|
match args.get_err(0, "color")? {
|
||||||
Value::Color(c) => Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)),
|
Value::Color(c) => Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)),
|
||||||
Value::Dimension(None, ..) => todo!(),
|
Value::String(s, QuoteKind::None) if is_ms_filter(&s) => {
|
||||||
v => Err((
|
Ok(Value::String(format!("alpha({})", s), QuoteKind::None))
|
||||||
format!("$color: {} is not a color.", v.inspect(args.span())?),
|
}
|
||||||
args.span(),
|
v => Err((
|
||||||
)
|
format!("$color: {} is not a color.", v.inspect(args.span())?),
|
||||||
.into()),
|
args.span(),
|
||||||
|
)
|
||||||
|
.into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let err = args.max_args(1);
|
||||||
|
let args = args
|
||||||
|
.get_variadic()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| match arg.node {
|
||||||
|
Value::String(s, QuoteKind::None) if is_ms_filter(&s) => Ok(s),
|
||||||
|
_ => {
|
||||||
|
err.clone()?;
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<SassResult<Vec<String>>>()?;
|
||||||
|
|
||||||
|
Ok(Value::String(
|
||||||
|
format!("alpha({})", args.join(", "),),
|
||||||
|
QuoteKind::None,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,8 +157,12 @@ impl Css {
|
|||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
Stmt::Keyframes(k) => {
|
Stmt::Keyframes(k) => {
|
||||||
let Keyframes { name, body } = *k;
|
let Keyframes { rule, name, body } = *k;
|
||||||
vals.push(Toplevel::Keyframes(Box::new(Keyframes { name, body })))
|
vals.push(Toplevel::Keyframes(Box::new(Keyframes {
|
||||||
|
rule,
|
||||||
|
name,
|
||||||
|
body,
|
||||||
|
})))
|
||||||
}
|
}
|
||||||
k @ Stmt::KeyframesRuleSet(..) => {
|
k @ Stmt::KeyframesRuleSet(..) => {
|
||||||
unreachable!("@keyframes ruleset {:?}", k)
|
unreachable!("@keyframes ruleset {:?}", k)
|
||||||
@ -324,13 +328,13 @@ impl Css {
|
|||||||
writeln!(buf, "{}}}", padding)?;
|
writeln!(buf, "{}}}", padding)?;
|
||||||
}
|
}
|
||||||
Toplevel::Keyframes(k) => {
|
Toplevel::Keyframes(k) => {
|
||||||
let Keyframes { name, body } = *k;
|
let Keyframes { rule, name, body } = *k;
|
||||||
if should_emit_newline {
|
if should_emit_newline {
|
||||||
should_emit_newline = false;
|
should_emit_newline = false;
|
||||||
writeln!(buf)?;
|
writeln!(buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(buf, "{}@keyframes", padding)?;
|
write!(buf, "{}@{}", padding, rule)?;
|
||||||
|
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
write!(buf, " {}", name)?;
|
write!(buf, " {}", name)?;
|
||||||
|
@ -4,12 +4,10 @@ use codemap::Span;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::{CallArg, CallArgs, FuncArg, FuncArgs},
|
args::{CallArg, CallArgs, FuncArg, FuncArgs},
|
||||||
|
common::QuoteKind,
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
scope::Scope,
|
scope::Scope,
|
||||||
utils::{
|
utils::{peek_ident_no_interpolation, peek_whitespace_or_comment, read_until_closing_paren},
|
||||||
peek_ident_no_interpolation, peek_whitespace_or_comment, read_until_closing_paren,
|
|
||||||
read_until_closing_quote, read_until_closing_square_brace,
|
|
||||||
},
|
|
||||||
value::Value,
|
value::Value,
|
||||||
Token,
|
Token,
|
||||||
};
|
};
|
||||||
@ -48,6 +46,7 @@ impl<'a> Parser<'a> {
|
|||||||
match &tok.kind {
|
match &tok.kind {
|
||||||
',' => {
|
',' => {
|
||||||
self.toks.next();
|
self.toks.next();
|
||||||
|
self.whitespace_or_comment();
|
||||||
args.push(FuncArg {
|
args.push(FuncArg {
|
||||||
name: name.node.into(),
|
name: name.node.into(),
|
||||||
default: Some(default),
|
default: Some(default),
|
||||||
@ -131,143 +130,226 @@ impl<'a> Parser<'a> {
|
|||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
let mut val: Vec<Token> = Vec::new();
|
|
||||||
let mut span = self
|
let mut span = self
|
||||||
.toks
|
.toks
|
||||||
.peek()
|
.peek()
|
||||||
.ok_or(("expected \")\".", self.span_before))?
|
.ok_or(("expected \")\".", self.span_before))?
|
||||||
.pos();
|
.pos();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match self.toks.peek().cloned() {
|
|
||||||
Some(Token { kind: '$', pos }) => {
|
|
||||||
span = span.merge(pos);
|
|
||||||
self.toks.next();
|
|
||||||
let v = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
|
||||||
|
|
||||||
peek_whitespace_or_comment(self.toks);
|
|
||||||
|
|
||||||
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
|
||||||
self.toks.truncate_iterator_to_cursor();
|
|
||||||
self.toks.next();
|
|
||||||
name = v.node;
|
|
||||||
} else {
|
|
||||||
val.push(Token::new(pos, '$'));
|
|
||||||
self.toks.reset_cursor();
|
|
||||||
name.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(Token { kind: ')', .. }) => {
|
|
||||||
self.toks.next();
|
|
||||||
return Ok(CallArgs(args, span));
|
|
||||||
}
|
|
||||||
Some(..) | None => name.clear(),
|
|
||||||
}
|
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
let mut is_splat = false;
|
if matches!(self.toks.peek(), Some(Token { kind: ')', .. })) {
|
||||||
|
self.toks.next();
|
||||||
while let Some(tok) = self.toks.next() {
|
return Ok(CallArgs(args, span));
|
||||||
match tok.kind {
|
|
||||||
')' => {
|
|
||||||
args.insert(
|
|
||||||
if name.is_empty() {
|
|
||||||
CallArg::Positional(args.len())
|
|
||||||
} else {
|
|
||||||
CallArg::Named(mem::take(&mut name).into())
|
|
||||||
},
|
|
||||||
self.parse_value_from_vec(val, true),
|
|
||||||
);
|
|
||||||
span = span.merge(tok.pos());
|
|
||||||
return Ok(CallArgs(args, span));
|
|
||||||
}
|
|
||||||
',' => break,
|
|
||||||
'[' => {
|
|
||||||
val.push(tok);
|
|
||||||
val.append(&mut read_until_closing_square_brace(self.toks)?);
|
|
||||||
}
|
|
||||||
'(' => {
|
|
||||||
val.push(tok);
|
|
||||||
val.append(&mut read_until_closing_paren(self.toks)?);
|
|
||||||
}
|
|
||||||
'"' | '\'' => {
|
|
||||||
val.push(tok);
|
|
||||||
val.append(&mut read_until_closing_quote(self.toks, tok.kind)?);
|
|
||||||
}
|
|
||||||
'.' => {
|
|
||||||
if let Some(Token { kind: '.', pos }) = self.toks.peek().cloned() {
|
|
||||||
if !name.is_empty() {
|
|
||||||
return Err(("expected \")\".", pos).into());
|
|
||||||
}
|
|
||||||
self.toks.next();
|
|
||||||
if let Some(Token { kind: '.', .. }) = self.toks.peek() {
|
|
||||||
self.toks.next();
|
|
||||||
is_splat = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
return Err(("expected \".\".", pos).into());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val.push(tok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => val.push(tok),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_splat {
|
if let Some(Token { kind: '$', pos }) = self.toks.peek() {
|
||||||
let val = self.parse_value_from_vec(mem::take(&mut val), true)?;
|
span = span.merge(*pos);
|
||||||
match val.node {
|
self.toks.advance_cursor();
|
||||||
Value::ArgList(v) => {
|
|
||||||
for arg in v {
|
let v = peek_ident_no_interpolation(self.toks, false, self.span_before)?;
|
||||||
args.insert(CallArg::Positional(args.len()), Ok(arg));
|
|
||||||
}
|
peek_whitespace_or_comment(self.toks);
|
||||||
}
|
|
||||||
Value::List(v, ..) => {
|
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
||||||
for arg in v {
|
self.toks.truncate_iterator_to_cursor();
|
||||||
args.insert(CallArg::Positional(args.len()), Ok(arg.span(val.span)));
|
self.toks.next();
|
||||||
}
|
name = v.node;
|
||||||
}
|
} else {
|
||||||
Value::Map(v) => {
|
self.toks.reset_cursor();
|
||||||
// NOTE: we clone the map here because it is used
|
name.clear();
|
||||||
// later for error reporting. perhaps there is
|
|
||||||
// some way around this?
|
|
||||||
for (name, arg) in v.clone().entries() {
|
|
||||||
let name = match name {
|
|
||||||
Value::String(s, ..) => s,
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"{} is not a string in {}.",
|
|
||||||
name.inspect(val.span)?,
|
|
||||||
Value::Map(v).inspect(val.span)?
|
|
||||||
),
|
|
||||||
val.span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
args.insert(CallArg::Named(name.into()), Ok(arg.span(val.span)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
args.insert(CallArg::Positional(args.len()), Ok(val));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args.insert(
|
name.clear();
|
||||||
if name.is_empty() {
|
|
||||||
CallArg::Positional(args.len())
|
|
||||||
} else {
|
|
||||||
CallArg::Named(mem::take(&mut name).into())
|
|
||||||
},
|
|
||||||
self.parse_value_from_vec(mem::take(&mut val), true),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.whitespace_or_comment();
|
self.whitespace_or_comment();
|
||||||
|
|
||||||
if self.toks.peek().is_none() {
|
let value = self.parse_value(true, &|c| match c.peek() {
|
||||||
return Err(("expected \")\".", span).into());
|
Some(Token { kind: ')', .. }) | Some(Token { kind: ',', .. }) => true,
|
||||||
|
Some(Token { kind: '.', .. }) => {
|
||||||
|
if matches!(c.peek_next(), Some(Token { kind: '.', .. })) {
|
||||||
|
c.reset_cursor();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
c.reset_cursor();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Token { kind: '=', .. }) => {
|
||||||
|
if matches!(c.peek_next(), Some(Token { kind: '=', .. })) {
|
||||||
|
c.reset_cursor();
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
c.reset_cursor();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(..) | None => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
match self.toks.peek() {
|
||||||
|
Some(Token { kind: ')', .. }) => {
|
||||||
|
self.toks.next();
|
||||||
|
args.insert(
|
||||||
|
if name.is_empty() {
|
||||||
|
CallArg::Positional(args.len())
|
||||||
|
} else {
|
||||||
|
CallArg::Named(mem::take(&mut name).into())
|
||||||
|
},
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
return Ok(CallArgs(args, span));
|
||||||
|
}
|
||||||
|
Some(Token { kind: ',', .. }) => {
|
||||||
|
self.toks.next();
|
||||||
|
args.insert(
|
||||||
|
if name.is_empty() {
|
||||||
|
CallArg::Positional(args.len())
|
||||||
|
} else {
|
||||||
|
CallArg::Named(mem::take(&mut name).into())
|
||||||
|
},
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(Token { kind: '.', pos }) => {
|
||||||
|
let pos = *pos;
|
||||||
|
self.toks.next();
|
||||||
|
|
||||||
|
if let Some(Token { kind: '.', pos }) = self.toks.peek().cloned() {
|
||||||
|
if !name.is_empty() {
|
||||||
|
return Err(("expected \")\".", pos).into());
|
||||||
|
}
|
||||||
|
self.toks.next();
|
||||||
|
if let Some(Token { kind: '.', .. }) = self.toks.peek() {
|
||||||
|
self.toks.next();
|
||||||
|
} else {
|
||||||
|
return Err(("expected \".\".", pos).into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(("expected \")\".", pos).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = value?;
|
||||||
|
match val.node {
|
||||||
|
Value::ArgList(v) => {
|
||||||
|
for arg in v {
|
||||||
|
args.insert(CallArg::Positional(args.len()), Ok(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::List(v, ..) => {
|
||||||
|
for arg in v {
|
||||||
|
args.insert(
|
||||||
|
CallArg::Positional(args.len()),
|
||||||
|
Ok(arg.span(val.span)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Map(v) => {
|
||||||
|
// NOTE: we clone the map here because it is used
|
||||||
|
// later for error reporting. perhaps there is
|
||||||
|
// some way around this?
|
||||||
|
for (name, arg) in v.clone().entries() {
|
||||||
|
let name = match name {
|
||||||
|
Value::String(s, ..) => s,
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"{} is not a string in {}.",
|
||||||
|
name.inspect(val.span)?,
|
||||||
|
Value::Map(v).inspect(val.span)?
|
||||||
|
),
|
||||||
|
val.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
args.insert(CallArg::Named(name.into()), Ok(arg.span(val.span)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
args.insert(CallArg::Positional(args.len()), Ok(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Token { kind: '=', .. }) => {
|
||||||
|
self.toks.next();
|
||||||
|
let left = value?;
|
||||||
|
|
||||||
|
let right = self.parse_value(true, &|c| match c.peek() {
|
||||||
|
Some(Token { kind: ')', .. }) | Some(Token { kind: ',', .. }) => true,
|
||||||
|
Some(Token { kind: '.', .. }) => {
|
||||||
|
if matches!(c.peek_next(), Some(Token { kind: '.', .. })) {
|
||||||
|
c.reset_cursor();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
c.reset_cursor();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(..) | None => false,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let value_span = left.span.merge(right.span);
|
||||||
|
span = span.merge(value_span);
|
||||||
|
|
||||||
|
let value = format!(
|
||||||
|
"{}={}",
|
||||||
|
left.node.to_css_string(left.span)?,
|
||||||
|
right.node.to_css_string(right.span)?
|
||||||
|
);
|
||||||
|
|
||||||
|
args.insert(
|
||||||
|
if name.is_empty() {
|
||||||
|
CallArg::Positional(args.len())
|
||||||
|
} else {
|
||||||
|
CallArg::Named(mem::take(&mut name).into())
|
||||||
|
},
|
||||||
|
Ok(Value::String(value, QuoteKind::None).span(value_span)),
|
||||||
|
);
|
||||||
|
|
||||||
|
match self.toks.peek() {
|
||||||
|
Some(Token { kind: ')', .. }) => {
|
||||||
|
self.toks.next();
|
||||||
|
return Ok(CallArgs(args, span));
|
||||||
|
}
|
||||||
|
Some(Token { kind: ',', pos }) => {
|
||||||
|
span = span.merge(*pos);
|
||||||
|
self.toks.next();
|
||||||
|
self.whitespace_or_comment();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(Token { kind: '.', pos }) => {
|
||||||
|
let pos = *pos;
|
||||||
|
self.toks.next();
|
||||||
|
|
||||||
|
if let Some(Token { kind: '.', pos }) = self.toks.peek().cloned() {
|
||||||
|
if !name.is_empty() {
|
||||||
|
return Err(("expected \")\".", pos).into());
|
||||||
|
}
|
||||||
|
self.toks.next();
|
||||||
|
if let Some(Token { kind: '.', .. }) = self.toks.peek() {
|
||||||
|
self.toks.next();
|
||||||
|
} else {
|
||||||
|
return Err(("expected \".\".", pos).into());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(("expected \")\".", pos).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(..) => unreachable!(),
|
||||||
|
None => return Err(("expected \")\".", span).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(..) => {
|
||||||
|
value?;
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
None => return Err(("expected \")\".", span).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ impl<'a> Parser<'a> {
|
|||||||
let mut found_true = false;
|
let mut found_true = false;
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
|
|
||||||
let init_cond = self.parse_value(true)?.node;
|
let init_cond = self.parse_value(true, &|_| false)?.node;
|
||||||
|
|
||||||
// consume the open curly brace
|
// consume the open curly brace
|
||||||
let span_before = match self.toks.next() {
|
let span_before = match self.toks.next() {
|
||||||
@ -87,7 +87,7 @@ impl<'a> Parser<'a> {
|
|||||||
self.throw_away_until_open_curly_brace()?;
|
self.throw_away_until_open_curly_brace()?;
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
let v = self.parse_value(true)?.node.is_true();
|
let v = self.parse_value(true, &|_| false)?.node.is_true();
|
||||||
match self.toks.next() {
|
match self.toks.next() {
|
||||||
Some(Token { kind: '{', .. }) => {}
|
Some(Token { kind: '{', .. }) => {}
|
||||||
Some(..) | None => {
|
Some(..) | None => {
|
||||||
@ -259,7 +259,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let to_val = self.parse_value(true)?;
|
let to_val = self.parse_value(true, &|_| false)?;
|
||||||
let to = match to_val.node {
|
let to = match to_val.node {
|
||||||
Value::Dimension(Some(n), ..) => match n.to_integer().to_isize() {
|
Value::Dimension(Some(n), ..) => match n.to_integer().to_isize() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
|
@ -124,7 +124,7 @@ impl<'a> Parser<'a> {
|
|||||||
let Spanned {
|
let Spanned {
|
||||||
node: file_name_as_value,
|
node: file_name_as_value,
|
||||||
span,
|
span,
|
||||||
} = self.parse_value(true)?;
|
} = self.parse_value(true, &|_| false)?;
|
||||||
|
|
||||||
match file_name_as_value {
|
match file_name_as_value {
|
||||||
Value::String(s, QuoteKind::Quoted) => {
|
Value::String(s, QuoteKind::Quoted) => {
|
||||||
|
@ -55,7 +55,14 @@ impl<'a, 'b> KeyframesSelectorParser<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
'0'..='9' => {
|
'0'..='9' => {
|
||||||
let num = eat_whole_number(self.parser.toks);
|
let mut num = eat_whole_number(self.parser.toks);
|
||||||
|
|
||||||
|
if let Some(Token { kind: '.', .. }) = self.parser.toks.peek() {
|
||||||
|
self.parser.toks.next();
|
||||||
|
num.push('.');
|
||||||
|
num.push_str(&eat_whole_number(self.parser.toks));
|
||||||
|
}
|
||||||
|
|
||||||
if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) {
|
if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) {
|
||||||
return Err(("expected \"%\".", tok.pos).into());
|
return Err(("expected \"%\".", tok.pos).into());
|
||||||
}
|
}
|
||||||
@ -178,7 +185,7 @@ impl<'a> Parser<'a> {
|
|||||||
Err(("expected \"{\".", span).into())
|
Err(("expected \"{\".", span).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn parse_keyframes(&mut self) -> SassResult<Stmt> {
|
pub(super) fn parse_keyframes(&mut self, rule: String) -> SassResult<Stmt> {
|
||||||
let name = self.parse_keyframes_name()?;
|
let name = self.parse_keyframes_name()?;
|
||||||
|
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
@ -202,6 +209,6 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
.parse_stmt()?;
|
.parse_stmt()?;
|
||||||
|
|
||||||
Ok(Stmt::Keyframes(Box::new(Keyframes { name, body })))
|
Ok(Stmt::Keyframes(Box::new(Keyframes { rule, name, body })))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ impl<'a> Parser<'a> {
|
|||||||
let Spanned {
|
let Spanned {
|
||||||
node: message,
|
node: message,
|
||||||
span,
|
span,
|
||||||
} = self.parse_value(false)?;
|
} = self.parse_value(false, &|_| false)?;
|
||||||
|
|
||||||
return Err((
|
return Err((
|
||||||
message.inspect(span)?.to_string(),
|
message.inspect(span)?.to_string(),
|
||||||
@ -294,7 +294,7 @@ impl<'a> Parser<'a> {
|
|||||||
let Spanned {
|
let Spanned {
|
||||||
node: message,
|
node: message,
|
||||||
span,
|
span,
|
||||||
} = self.parse_value(false)?;
|
} = self.parse_value(false, &|_| false)?;
|
||||||
span.merge(kind_string.span);
|
span.merge(kind_string.span);
|
||||||
if let Some(Token { kind: ';', pos }) = self.toks.peek() {
|
if let Some(Token { kind: ';', pos }) = self.toks.peek() {
|
||||||
kind_string.span.merge(*pos);
|
kind_string.span.merge(*pos);
|
||||||
@ -309,7 +309,7 @@ impl<'a> Parser<'a> {
|
|||||||
let Spanned {
|
let Spanned {
|
||||||
node: message,
|
node: message,
|
||||||
span,
|
span,
|
||||||
} = self.parse_value(false)?;
|
} = self.parse_value(false, &|_| false)?;
|
||||||
span.merge(kind_string.span);
|
span.merge(kind_string.span);
|
||||||
if let Some(Token { kind: ';', pos }) = self.toks.peek() {
|
if let Some(Token { kind: ';', pos }) = self.toks.peek() {
|
||||||
kind_string.span.merge(*pos);
|
kind_string.span.merge(*pos);
|
||||||
@ -345,7 +345,9 @@ impl<'a> Parser<'a> {
|
|||||||
AtRuleKind::Forward => todo!("@forward not yet implemented"),
|
AtRuleKind::Forward => todo!("@forward not yet implemented"),
|
||||||
AtRuleKind::Extend => self.parse_extend()?,
|
AtRuleKind::Extend => self.parse_extend()?,
|
||||||
AtRuleKind::Supports => stmts.push(self.parse_supports()?),
|
AtRuleKind::Supports => stmts.push(self.parse_supports()?),
|
||||||
AtRuleKind::Keyframes => stmts.push(self.parse_keyframes()?),
|
AtRuleKind::Keyframes => {
|
||||||
|
stmts.push(self.parse_keyframes(kind_string.node)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'$' => self.parse_variable_declaration()?,
|
'$' => self.parse_variable_declaration()?,
|
||||||
@ -580,7 +582,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> {
|
pub fn parse_interpolation(&mut self) -> SassResult<Spanned<Value>> {
|
||||||
let val = self.parse_value(true)?;
|
let val = self.parse_value(true, &|_| false)?;
|
||||||
match self.toks.next() {
|
match self.toks.next() {
|
||||||
Some(Token { kind: '}', .. }) => {}
|
Some(Token { kind: '}', .. }) => {}
|
||||||
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
|
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
|
||||||
|
@ -173,7 +173,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_style_value(&mut self) -> SassResult<Spanned<Value>> {
|
fn parse_style_value(&mut self) -> SassResult<Spanned<Value>> {
|
||||||
self.parse_value(false)
|
self.parse_value(false, &|_| false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn parse_style_group(
|
pub(super) fn parse_style_group(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{iter::Iterator, mem};
|
use std::{iter::Iterator, mem, vec::IntoIter};
|
||||||
|
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use num_rational::{BigRational, Rational64};
|
use num_rational::{BigRational, Rational64};
|
||||||
@ -6,7 +6,7 @@ use num_traits::{pow, One, ToPrimitive};
|
|||||||
|
|
||||||
use codemap::{Span, Spanned};
|
use codemap::{Span, Spanned};
|
||||||
|
|
||||||
use peekmore::PeekMore;
|
use peekmore::{PeekMore, PeekMoreIterator};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::GLOBAL_FUNCTIONS,
|
builtin::GLOBAL_FUNCTIONS,
|
||||||
@ -15,8 +15,8 @@ use crate::{
|
|||||||
error::SassResult,
|
error::SassResult,
|
||||||
unit::Unit,
|
unit::Unit,
|
||||||
utils::{
|
utils::{
|
||||||
devour_whitespace, eat_number, read_until_char, read_until_closing_paren,
|
devour_whitespace, eat_whole_number, read_until_closing_paren,
|
||||||
read_until_closing_square_brace, IsWhitespace,
|
read_until_closing_square_brace, IsWhitespace, ParsedNumber,
|
||||||
},
|
},
|
||||||
value::{Number, SassFunction, SassMap, Value},
|
value::{Number, SassFunction, SassMap, Value},
|
||||||
Token,
|
Token,
|
||||||
@ -52,8 +52,16 @@ impl IsWhitespace for IntermediateValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
pub(crate) fn parse_value(&mut self, in_paren: bool) -> SassResult<Spanned<Value>> {
|
/// Parse a value from a stream of tokens
|
||||||
|
///
|
||||||
|
/// This function will cease parsing if the predicate returns true.
|
||||||
|
pub(crate) fn parse_value(
|
||||||
|
&mut self,
|
||||||
|
in_paren: bool,
|
||||||
|
predicate: &dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
|
||||||
|
) -> SassResult<Spanned<Value>> {
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
|
|
||||||
let span = match self.toks.peek() {
|
let span = match self.toks.peek() {
|
||||||
Some(Token { kind: '}', .. })
|
Some(Token { kind: '}', .. })
|
||||||
| Some(Token { kind: ';', .. })
|
| Some(Token { kind: ';', .. })
|
||||||
@ -61,10 +69,15 @@ impl<'a> Parser<'a> {
|
|||||||
| None => return Err(("Expected expression.", self.span_before).into()),
|
| None => return Err(("Expected expression.", self.span_before).into()),
|
||||||
Some(Token { pos, .. }) => *pos,
|
Some(Token { pos, .. }) => *pos,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if predicate(self.toks) {
|
||||||
|
return Err(("Expected expression.", span).into());
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_was_whitespace = false;
|
let mut last_was_whitespace = false;
|
||||||
let mut space_separated = Vec::new();
|
let mut space_separated = Vec::new();
|
||||||
let mut comma_separated = Vec::new();
|
let mut comma_separated = Vec::new();
|
||||||
let mut iter = IntermediateValueIterator::new(self);
|
let mut iter = IntermediateValueIterator::new(self, &predicate);
|
||||||
while let Some(val) = iter.next() {
|
while let Some(val) = iter.next() {
|
||||||
let val = val?;
|
let val = val?;
|
||||||
match val.node {
|
match val.node {
|
||||||
@ -182,6 +195,32 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_value_with_body(
|
||||||
|
&mut self,
|
||||||
|
toks: &mut PeekMoreIterator<IntoIter<Token>>,
|
||||||
|
in_paren: bool,
|
||||||
|
predicate: &dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
|
||||||
|
) -> SassResult<Spanned<Value>> {
|
||||||
|
Parser {
|
||||||
|
toks,
|
||||||
|
map: self.map,
|
||||||
|
path: self.path,
|
||||||
|
scopes: self.scopes,
|
||||||
|
global_scope: self.global_scope,
|
||||||
|
super_selectors: self.super_selectors,
|
||||||
|
span_before: self.span_before,
|
||||||
|
content: self.content,
|
||||||
|
flags: self.flags,
|
||||||
|
at_root: self.at_root,
|
||||||
|
at_root_has_selector: self.at_root_has_selector,
|
||||||
|
extender: self.extender,
|
||||||
|
content_scopes: self.content_scopes,
|
||||||
|
options: self.options,
|
||||||
|
modules: self.modules,
|
||||||
|
}
|
||||||
|
.parse_value(in_paren, predicate)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_value_from_vec(
|
pub(crate) fn parse_value_from_vec(
|
||||||
&mut self,
|
&mut self,
|
||||||
toks: Vec<Token>,
|
toks: Vec<Token>,
|
||||||
@ -204,7 +243,7 @@ impl<'a> Parser<'a> {
|
|||||||
options: self.options,
|
options: self.options,
|
||||||
modules: self.modules,
|
modules: self.modules,
|
||||||
}
|
}
|
||||||
.parse_value(in_paren)
|
.parse_value(in_paren, &|_| false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::eval_order_dependence)]
|
#[allow(clippy::eval_order_dependence)]
|
||||||
@ -400,7 +439,85 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_intermediate_value(&mut self) -> Option<SassResult<Spanned<IntermediateValue>>> {
|
fn parse_number(
|
||||||
|
&mut self,
|
||||||
|
predicate: &dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
|
||||||
|
) -> SassResult<Spanned<ParsedNumber>> {
|
||||||
|
let mut span = self.toks.peek().unwrap().pos;
|
||||||
|
let mut whole = eat_whole_number(self.toks);
|
||||||
|
|
||||||
|
if self.toks.peek().is_none() || predicate(self.toks) {
|
||||||
|
return Ok(Spanned {
|
||||||
|
node: ParsedNumber::new(whole, 0, String::new(), true),
|
||||||
|
span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_tok = *self.toks.peek().unwrap();
|
||||||
|
|
||||||
|
let dec_len = if next_tok.kind == '.' {
|
||||||
|
self.toks.next();
|
||||||
|
|
||||||
|
let dec = eat_whole_number(self.toks);
|
||||||
|
if dec.is_empty() {
|
||||||
|
return Err(("Expected digit.", next_tok.pos()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
whole.push_str(&dec);
|
||||||
|
|
||||||
|
dec.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut times_ten = String::new();
|
||||||
|
let mut times_ten_is_postive = true;
|
||||||
|
if let Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) = self.toks.peek() {
|
||||||
|
if let Some(&tok) = self.toks.peek_next() {
|
||||||
|
if tok.kind == '-' {
|
||||||
|
self.toks.next();
|
||||||
|
times_ten_is_postive = false;
|
||||||
|
|
||||||
|
self.toks.next();
|
||||||
|
times_ten = eat_whole_number(self.toks);
|
||||||
|
|
||||||
|
if times_ten.is_empty() {
|
||||||
|
return Err(
|
||||||
|
("Expected digit.", self.toks.peek().unwrap_or(&tok).pos).into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if matches!(tok.kind, '0'..='9') {
|
||||||
|
self.toks.next();
|
||||||
|
times_ten = eat_whole_number(self.toks);
|
||||||
|
|
||||||
|
if times_ten.len() > 2 {
|
||||||
|
return Err(
|
||||||
|
("Exponent too large.", self.toks.peek().unwrap_or(&tok).pos).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(Some(Token { pos, .. })) = self.toks.peek_previous() {
|
||||||
|
span = span.merge(*pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.toks.reset_cursor();
|
||||||
|
|
||||||
|
Ok(Spanned {
|
||||||
|
node: ParsedNumber::new(whole, dec_len, times_ten, times_ten_is_postive),
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_intermediate_value(
|
||||||
|
&mut self,
|
||||||
|
predicate: &dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
|
||||||
|
) -> Option<SassResult<Spanned<IntermediateValue>>> {
|
||||||
|
if predicate(self.toks) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let (kind, span) = match self.toks.peek() {
|
let (kind, span) = match self.toks.peek() {
|
||||||
Some(v) => (v.kind, v.pos()),
|
Some(v) => (v.kind, v.pos()),
|
||||||
None => return None,
|
None => return None,
|
||||||
@ -428,7 +545,7 @@ impl<'a> Parser<'a> {
|
|||||||
let Spanned {
|
let Spanned {
|
||||||
node: val,
|
node: val,
|
||||||
mut span,
|
mut span,
|
||||||
} = match eat_number(self.toks) {
|
} = match self.parse_number(predicate) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => return Some(Err(e)),
|
Err(e) => return Some(Err(e)),
|
||||||
};
|
};
|
||||||
@ -780,6 +897,7 @@ impl<'a> Parser<'a> {
|
|||||||
struct IntermediateValueIterator<'a, 'b: 'a> {
|
struct IntermediateValueIterator<'a, 'b: 'a> {
|
||||||
parser: &'a mut Parser<'b>,
|
parser: &'a mut Parser<'b>,
|
||||||
peek: Option<SassResult<Spanned<IntermediateValue>>>,
|
peek: Option<SassResult<Spanned<IntermediateValue>>>,
|
||||||
|
predicate: &'a dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> Iterator for IntermediateValueIterator<'a, 'b> {
|
impl<'a, 'b: 'a> Iterator for IntermediateValueIterator<'a, 'b> {
|
||||||
@ -788,14 +906,21 @@ impl<'a, 'b: 'a> Iterator for IntermediateValueIterator<'a, 'b> {
|
|||||||
if self.peek.is_some() {
|
if self.peek.is_some() {
|
||||||
self.peek.take()
|
self.peek.take()
|
||||||
} else {
|
} else {
|
||||||
self.parser.parse_intermediate_value()
|
self.parser.parse_intermediate_value(self.predicate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
|
impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
|
||||||
pub fn new(parser: &'a mut Parser<'b>) -> Self {
|
pub fn new(
|
||||||
Self { parser, peek: None }
|
parser: &'a mut Parser<'b>,
|
||||||
|
predicate: &'a dyn Fn(&mut PeekMoreIterator<IntoIter<Token>>) -> bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
parser,
|
||||||
|
peek: None,
|
||||||
|
predicate,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek(&mut self) -> &Option<SassResult<Spanned<IntermediateValue>>> {
|
fn peek(&mut self) -> &Option<SassResult<Spanned<IntermediateValue>>> {
|
||||||
@ -1122,9 +1247,13 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
|
|||||||
let paren_toks = &mut t.node.into_iter().peekmore();
|
let paren_toks = &mut t.node.into_iter().peekmore();
|
||||||
|
|
||||||
let mut map = SassMap::new();
|
let mut map = SassMap::new();
|
||||||
let key = self
|
let key = self.parser.parse_value_with_body(paren_toks, true, &|c| {
|
||||||
.parser
|
matches!(c.peek(), Some(Token { kind: ':', .. }))
|
||||||
.parse_value_from_vec(read_until_char(paren_toks, ':')?, true)?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(Token { kind: ':', .. }) = paren_toks.peek() {
|
||||||
|
paren_toks.next();
|
||||||
|
}
|
||||||
|
|
||||||
if paren_toks.peek().is_none() {
|
if paren_toks.peek().is_none() {
|
||||||
return Ok(Spanned {
|
return Ok(Spanned {
|
||||||
@ -1135,9 +1264,13 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = self
|
let val = self.parser.parse_value_with_body(paren_toks, true, &|c| {
|
||||||
.parser
|
matches!(c.peek(), Some(Token { kind: ',', .. }))
|
||||||
.parse_value_from_vec(read_until_char(paren_toks, ',')?, true)?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(Token { kind: ',', .. }) = paren_toks.peek() {
|
||||||
|
paren_toks.next();
|
||||||
|
}
|
||||||
|
|
||||||
map.insert(key.node, val.node);
|
map.insert(key.node, val.node);
|
||||||
|
|
||||||
@ -1153,16 +1286,25 @@ impl<'a, 'b: 'a> IntermediateValueIterator<'a, 'b> {
|
|||||||
let mut span = key.span;
|
let mut span = key.span;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let key = self
|
let key = self.parser.parse_value_with_body(paren_toks, true, &|c| {
|
||||||
.parser
|
matches!(c.peek(), Some(Token { kind: ':', .. }))
|
||||||
.parse_value_from_vec(read_until_char(paren_toks, ':')?, true)?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(Token { kind: ':', .. }) = paren_toks.peek() {
|
||||||
|
paren_toks.next();
|
||||||
|
}
|
||||||
|
|
||||||
devour_whitespace(paren_toks);
|
devour_whitespace(paren_toks);
|
||||||
let val = self
|
let val = self.parser.parse_value_with_body(paren_toks, true, &|c| {
|
||||||
.parser
|
matches!(c.peek(), Some(Token { kind: ',', .. }))
|
||||||
.parse_value_from_vec(read_until_char(paren_toks, ',')?, true)?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(Token { kind: ',', .. }) = paren_toks.peek() {
|
||||||
|
paren_toks.next();
|
||||||
|
}
|
||||||
span = span.merge(val.span);
|
span = span.merge(val.span);
|
||||||
devour_whitespace(paren_toks);
|
devour_whitespace(paren_toks);
|
||||||
if map.insert(key.node, val.node) {
|
if map.insert(key.node.clone(), val.node) {
|
||||||
return Err(("Duplicate key.", key.span).into());
|
return Err(("Duplicate key.", key.span).into());
|
||||||
}
|
}
|
||||||
if paren_toks.peek().is_none() {
|
if paren_toks.peek().is_none() {
|
||||||
|
@ -1,37 +1,3 @@
|
|||||||
use std::vec::IntoIter;
|
|
||||||
|
|
||||||
use peekmore::PeekMoreIterator;
|
|
||||||
|
|
||||||
use crate::{error::SassResult, Token};
|
|
||||||
|
|
||||||
use super::{read_until_closing_paren, read_until_closing_quote};
|
|
||||||
/// Reads until the char is found, consuming the char,
|
|
||||||
/// or until the end of the iterator is hit
|
|
||||||
pub(crate) fn read_until_char(
|
|
||||||
toks: &mut PeekMoreIterator<IntoIter<Token>>,
|
|
||||||
c: char,
|
|
||||||
) -> SassResult<Vec<Token>> {
|
|
||||||
let mut v = Vec::new();
|
|
||||||
while let Some(tok) = toks.next() {
|
|
||||||
match tok.kind {
|
|
||||||
'"' | '\'' => {
|
|
||||||
v.push(tok);
|
|
||||||
v.extend(read_until_closing_quote(toks, tok.kind)?);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
'(' => {
|
|
||||||
v.push(tok);
|
|
||||||
v.extend(read_until_closing_paren(toks)?);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
t if t == c => break,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
v.push(tok)
|
|
||||||
}
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn hex_char_for(number: u32) -> char {
|
pub(crate) fn hex_char_for(number: u32) -> char {
|
||||||
debug_assert!(number < 0x10);
|
debug_assert!(number < 0x10);
|
||||||
std::char::from_u32(if number < 0xA {
|
std::char::from_u32(if number < 0xA {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use std::vec::IntoIter;
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
use codemap::Spanned;
|
|
||||||
use peekmore::PeekMoreIterator;
|
use peekmore::PeekMoreIterator;
|
||||||
|
|
||||||
use crate::{error::SassResult, Token};
|
use crate::Token;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct ParsedNumber {
|
pub(crate) struct ParsedNumber {
|
||||||
@ -47,73 +46,6 @@ impl ParsedNumber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn eat_number(
|
|
||||||
toks: &mut PeekMoreIterator<IntoIter<Token>>,
|
|
||||||
) -> SassResult<Spanned<ParsedNumber>> {
|
|
||||||
let mut span = toks.peek().unwrap().pos;
|
|
||||||
let mut whole = eat_whole_number(toks);
|
|
||||||
|
|
||||||
if toks.peek().is_none() {
|
|
||||||
return Ok(Spanned {
|
|
||||||
node: ParsedNumber::new(whole, 0, String::new(), true),
|
|
||||||
span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let next_tok = *toks.peek().unwrap();
|
|
||||||
|
|
||||||
let dec_len = if next_tok.kind == '.' {
|
|
||||||
toks.next();
|
|
||||||
|
|
||||||
let dec = eat_whole_number(toks);
|
|
||||||
if dec.is_empty() {
|
|
||||||
return Err(("Expected digit.", next_tok.pos()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
whole.push_str(&dec);
|
|
||||||
|
|
||||||
dec.len()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut times_ten = String::new();
|
|
||||||
let mut times_ten_is_postive = true;
|
|
||||||
if let Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) = toks.peek() {
|
|
||||||
if let Some(&tok) = toks.peek_next() {
|
|
||||||
if tok.kind == '-' {
|
|
||||||
toks.next();
|
|
||||||
times_ten_is_postive = false;
|
|
||||||
|
|
||||||
toks.next();
|
|
||||||
times_ten = eat_whole_number(toks);
|
|
||||||
|
|
||||||
if times_ten.is_empty() {
|
|
||||||
return Err(("Expected digit.", toks.peek().unwrap_or(&tok).pos).into());
|
|
||||||
}
|
|
||||||
} else if matches!(tok.kind, '0'..='9') {
|
|
||||||
toks.next();
|
|
||||||
times_ten = eat_whole_number(toks);
|
|
||||||
|
|
||||||
if times_ten.len() > 2 {
|
|
||||||
return Err(("Exponent too large.", toks.peek().unwrap_or(&tok).pos).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(Some(Token { pos, .. })) = toks.peek_previous() {
|
|
||||||
span = span.merge(*pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
toks.reset_cursor();
|
|
||||||
|
|
||||||
Ok(Spanned {
|
|
||||||
node: ParsedNumber::new(whole, dec_len, times_ten, times_ten_is_postive),
|
|
||||||
span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn eat_whole_number(toks: &mut PeekMoreIterator<IntoIter<Token>>) -> String {
|
pub(crate) fn eat_whole_number(toks: &mut PeekMoreIterator<IntoIter<Token>>) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
while let Some(c) = toks.peek() {
|
while let Some(c) = toks.peek() {
|
||||||
|
@ -86,3 +86,85 @@ test!(
|
|||||||
}",
|
}",
|
||||||
"a {\n color: red;\n}\n"
|
"a {\n color: red;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
comment_after_comma_in_func_args,
|
||||||
|
"@mixin a(
|
||||||
|
$foo,//foo
|
||||||
|
) {
|
||||||
|
color: $foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include a(red);
|
||||||
|
}",
|
||||||
|
"a {\n color: red;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_one_arg,
|
||||||
|
"a {\n color: foo(a=a);\n}\n",
|
||||||
|
"a {\n color: foo(a=a);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_two_args,
|
||||||
|
"a {\n color: foo(a=a, b=b);\n}\n",
|
||||||
|
"a {\n color: foo(a=a, b=b);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_whitespace,
|
||||||
|
"a {\n color: foo( a = a );\n}\n",
|
||||||
|
"a {\n color: foo(a=a);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_whitespace_list,
|
||||||
|
"a {\n color: foo( A a = a );\n}\n",
|
||||||
|
"a {\n color: foo(A a=a);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_function_call,
|
||||||
|
"a {\n color: foo(hue(green)=hue(green));\n}\n",
|
||||||
|
"a {\n color: foo(120deg=120deg);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_addition,
|
||||||
|
"a {\n color: foo(1+1=1+1);\n}\n",
|
||||||
|
"a {\n color: foo(2=2);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_splat_of_single_value,
|
||||||
|
"a {\n color: foo(a=a...);\n}\n",
|
||||||
|
"a {\n color: foo(a=a);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_splat_of_list,
|
||||||
|
"a {\n color: foo(a=[a, b]...);\n}\n",
|
||||||
|
"a {\n color: foo(a=[a, b]);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
filter_both_null,
|
||||||
|
"a {\n color: foo(null=null);\n}\n",
|
||||||
|
"a {\n color: foo(=);\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
filter_splat_missing_third_period,
|
||||||
|
"a {\n color: foo(1 + 1 = a..);\n}\n", "Error: expected \".\"."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
filter_invalid_css_value,
|
||||||
|
"a {\n color: foo((a: b)=a);\n}\n", "Error: (a: b) isn't a valid CSS value."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
filter_nothing_before_equal,
|
||||||
|
"a {\n color: foo(=a);\n}\n", "Error: Expected expression."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
filter_nothing_after_equal,
|
||||||
|
"a {\n color: foo(a=);\n}\n", "Error: Expected expression."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
filter_equal_is_last_char,
|
||||||
|
"a {\n color: foo(a=", "Error: Expected expression."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
filter_value_after_equal_is_last_char,
|
||||||
|
"a {\n color: foo(a=a", "Error: expected \")\"."
|
||||||
|
);
|
||||||
|
@ -638,3 +638,39 @@ test!(
|
|||||||
"a {\n color: hsla(0deg, 100%, 50%);\n}\n",
|
"a {\n color: hsla(0deg, 100%, 50%);\n}\n",
|
||||||
"a {\n color: red;\n}\n"
|
"a {\n color: red;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
alpha_filter_one_arg,
|
||||||
|
"a {\n color: alpha(a=a);\n}\n",
|
||||||
|
"a {\n color: alpha(a=a);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
alpha_filter_multiple_args,
|
||||||
|
"a {\n color: alpha(a=a, b=b, c=d, d=d);\n}\n",
|
||||||
|
"a {\n color: alpha(a=a, b=b, c=d, d=d);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
alpha_filter_whitespace,
|
||||||
|
"a {\n color: alpha(a = a);\n}\n",
|
||||||
|
"a {\n color: alpha(a=a);\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
alpha_filter_named,
|
||||||
|
"a {\n color: alpha($color: a=a);\n}\n",
|
||||||
|
"a {\n color: alpha(a=a);\n}\n"
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
alpha_filter_both_null,
|
||||||
|
"a {\n color: alpha(null=null);\n}\n", "Error: $color: = is not a color."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
alpha_filter_multiple_args_one_not_valid_filter,
|
||||||
|
"a {\n color: alpha(a=a, b);\n}\n", "Error: Only 1 argument allowed, but 2 were passed."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
alpha_filter_invalid_from_whitespace,
|
||||||
|
"a {\n color: alpha( A a = a );\n}\n", "Error: $color: A a=a is not a color."
|
||||||
|
);
|
||||||
|
error!(
|
||||||
|
alpha_filter_invalid_non_alphabetic_start,
|
||||||
|
"a {\n color: alpha(1=a);\n}\n", "Error: $color: 1=a is not a color."
|
||||||
|
);
|
||||||
|
@ -169,7 +169,10 @@ error!(
|
|||||||
);
|
);
|
||||||
error!(unclosed_dbl_quote, "@if true \" {}", "Error: Expected \".");
|
error!(unclosed_dbl_quote, "@if true \" {}", "Error: Expected \".");
|
||||||
error!(unclosed_sgl_quote, "@if true ' {}", "Error: Expected '.");
|
error!(unclosed_sgl_quote, "@if true ' {}", "Error: Expected '.");
|
||||||
error!(unclosed_call_args, "@if a({}", "Error: expected \")\".");
|
error!(
|
||||||
|
unclosed_call_args,
|
||||||
|
"@if a({}", "Error: Expected expression."
|
||||||
|
);
|
||||||
error!(nothing_after_div, "@if a/", "Error: Expected expression.");
|
error!(nothing_after_div, "@if a/", "Error: Expected expression.");
|
||||||
error!(multiline_error, "@if \"\n\"{}", "Error: Expected \".");
|
error!(multiline_error, "@if \"\n\"{}", "Error: Expected \".");
|
||||||
error!(
|
error!(
|
||||||
|
@ -121,3 +121,21 @@ test!(
|
|||||||
}",
|
}",
|
||||||
"@keyframes {\n to {\n color: red;\n }\n from {\n color: green;\n }\n}\n"
|
"@keyframes {\n to {\n color: red;\n }\n from {\n color: green;\n }\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
keyframes_vendor_prefix,
|
||||||
|
"@-webkit-keyframes foo {
|
||||||
|
0% {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}",
|
||||||
|
"@-webkit-keyframes foo {\n 0% {\n color: red;\n }\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
keyframes_allow_decimal_selector,
|
||||||
|
"@keyframes foo {
|
||||||
|
12.5% {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}",
|
||||||
|
"@-webkit-keyframes foo {\n 0% {\n color: red;\n }\n}\n"
|
||||||
|
);
|
||||||
|
@ -197,3 +197,11 @@ test!(
|
|||||||
"a {\n color: (a: b)==(a: c);\n}\n",
|
"a {\n color: (a: b)==(a: c);\n}\n",
|
||||||
"a {\n color: false;\n}\n"
|
"a {\n color: false;\n}\n"
|
||||||
);
|
);
|
||||||
|
test!(
|
||||||
|
empty_with_single_line_comments,
|
||||||
|
"$foo: (\n \n // :/a.b\n \n );
|
||||||
|
a {
|
||||||
|
color: inspect($foo);
|
||||||
|
}",
|
||||||
|
"a {\n color: ();\n}\n"
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user