Refactor parsing into struct, rather than standalone functions
reimplement parsing
This commit is contained in:
commit
2ad1b70f61
10
README.md
10
README.md
@ -31,7 +31,7 @@ for this version will be provided when the library becomes more stable.
|
||||
The large features remaining are
|
||||
|
||||
```
|
||||
builtin functions content-exists, min, max
|
||||
builtin functions min, max
|
||||
@extend (~600 tests)
|
||||
indented syntax (27 tests)
|
||||
css imports
|
||||
@ -39,7 +39,6 @@ css imports
|
||||
@forward (~400 tests)
|
||||
@keyframes (~30 tests)
|
||||
@supports (~128 tests)
|
||||
@each inside @function
|
||||
```
|
||||
|
||||
## Features
|
||||
@ -74,6 +73,13 @@ cargo b --release
|
||||
|
||||
These numbers come from a default run of the sass specification as shown above.
|
||||
|
||||
```
|
||||
2020-06-16
|
||||
PASSING: 2489
|
||||
FAILING: 2604
|
||||
TOTAL: 5093
|
||||
```
|
||||
|
||||
```
|
||||
2020-06-07
|
||||
PASSING: 2442
|
||||
|
297
src/args.rs
297
src/args.rs
@ -1,21 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::common::Identifier;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, devour_whitespace_or_comment, eat_ident, eat_ident_no_interpolation,
|
||||
read_until_closing_paren, read_until_closing_quote, read_until_closing_square_brace,
|
||||
use crate::{
|
||||
common::Identifier,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
{Cow, Token},
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::Cow;
|
||||
use crate::Token;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) struct FuncArgs(pub Vec<FuncArg>);
|
||||
@ -34,10 +26,10 @@ impl FuncArgs {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CallArgs(HashMap<CallArg, Vec<Token>>, Span);
|
||||
pub(crate) struct CallArgs(pub HashMap<CallArg, Vec<Token>>, pub Span);
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
enum CallArg {
|
||||
pub(crate) enum CallArg {
|
||||
Named(Identifier),
|
||||
Positional(usize),
|
||||
}
|
||||
@ -63,11 +55,7 @@ impl CallArgs {
|
||||
CallArgs(HashMap::new(), span)
|
||||
}
|
||||
|
||||
pub fn to_css_string(
|
||||
self,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Spanned<String>> {
|
||||
pub fn to_css_string(self, parser: &mut Parser<'_>) -> SassResult<Spanned<String>> {
|
||||
let mut string = String::with_capacity(2 + self.len() * 10);
|
||||
string.push('(');
|
||||
let mut span = self.1;
|
||||
@ -79,7 +67,7 @@ impl CallArgs {
|
||||
});
|
||||
}
|
||||
|
||||
let args = match self.get_variadic(scope, super_selector) {
|
||||
let args = match parser.variadic_args(self) {
|
||||
Ok(v) => v,
|
||||
Err(..) => {
|
||||
return Err(("Plain CSS functions don't support keyword arguments.", span).into())
|
||||
@ -103,73 +91,32 @@ impl CallArgs {
|
||||
/// Get argument by name
|
||||
///
|
||||
/// Removes the argument
|
||||
pub fn get_named<T: Into<Identifier>>(
|
||||
&mut self,
|
||||
val: T,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> Option<SassResult<Spanned<Value>>> {
|
||||
match self.0.remove(&CallArg::Named(val.into())) {
|
||||
Some(v) => {
|
||||
let span_before = v[0].pos;
|
||||
Some(Value::from_vec(v, scope, super_selector, span_before))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
pub fn get_named<T: Into<Identifier>>(&mut self, val: T) -> Option<Vec<Token>> {
|
||||
self.0.remove(&CallArg::Named(val.into()))
|
||||
}
|
||||
|
||||
/// Get a positional argument by 0-indexed position
|
||||
///
|
||||
/// Removes the argument
|
||||
pub fn get_positional(
|
||||
&mut self,
|
||||
val: usize,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> Option<SassResult<Spanned<Value>>> {
|
||||
match self.0.remove(&CallArg::Positional(val)) {
|
||||
Some(v) => {
|
||||
let span_before = v[0].pos;
|
||||
Some(Value::from_vec(v, scope, super_selector, span_before))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
pub fn get_positional(&mut self, val: usize) -> Option<Vec<Token>> {
|
||||
self.0.remove(&CallArg::Positional(val))
|
||||
}
|
||||
|
||||
pub fn get<T: Into<Identifier>>(
|
||||
&mut self,
|
||||
position: usize,
|
||||
name: T,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> Option<SassResult<Spanned<Value>>> {
|
||||
match self.get_named(name, scope, super_selector) {
|
||||
pub fn get<T: Into<Identifier>>(&mut self, position: usize, name: T) -> Option<Vec<Token>> {
|
||||
match self.get_named(name) {
|
||||
Some(v) => Some(v),
|
||||
None => self.get_positional(position, scope, super_selector),
|
||||
None => self.get_positional(position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_variadic(
|
||||
self,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Vec<Spanned<Value>>> {
|
||||
let mut vals = Vec::new();
|
||||
let mut args = match self
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(a, v)| Ok((a.position()?, v)))
|
||||
.collect::<Result<Vec<(usize, Vec<Token>)>, String>>()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err((format!("No argument named ${}.", e), self.1).into()),
|
||||
};
|
||||
args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2));
|
||||
for arg in args {
|
||||
let span_before = arg.1[0].pos;
|
||||
vals.push(Value::from_vec(arg.1, scope, super_selector, span_before)?);
|
||||
pub fn get_err(&mut self, position: usize, name: &'static str) -> SassResult<Vec<Token>> {
|
||||
match self.get_named(name) {
|
||||
Some(v) => Ok(v),
|
||||
None => match self.get_positional(position) {
|
||||
Some(v) => Ok(v),
|
||||
None => Err((format!("Missing argument ${}.", name), self.span()).into()),
|
||||
},
|
||||
}
|
||||
Ok(vals)
|
||||
}
|
||||
|
||||
/// Decrement all positional arguments by 1
|
||||
@ -219,201 +166,3 @@ impl CallArgs {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eat_func_args<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<FuncArgs> {
|
||||
let mut args: Vec<FuncArg> = Vec::new();
|
||||
let mut close_paren_span: Span = toks.peek().unwrap().pos();
|
||||
|
||||
devour_whitespace(toks);
|
||||
while let Some(Token { kind, pos }) = toks.next() {
|
||||
let name = match kind {
|
||||
'$' => eat_ident(toks, scope, super_selector, pos)?,
|
||||
')' => {
|
||||
close_paren_span = pos;
|
||||
break;
|
||||
}
|
||||
_ => return Err(("expected \")\".", pos).into()),
|
||||
};
|
||||
let mut default: Vec<Token> = Vec::new();
|
||||
let mut is_variadic = false;
|
||||
devour_whitespace(toks);
|
||||
let (kind, span) = match toks.next() {
|
||||
Some(Token { kind, pos }) => (kind, pos),
|
||||
_ => todo!("unexpected eof"),
|
||||
};
|
||||
match kind {
|
||||
':' => {
|
||||
devour_whitespace(toks);
|
||||
while let Some(tok) = toks.peek() {
|
||||
match &tok.kind {
|
||||
',' => {
|
||||
toks.next();
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: Some(default),
|
||||
is_variadic,
|
||||
});
|
||||
break;
|
||||
}
|
||||
')' => {
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: Some(default),
|
||||
is_variadic,
|
||||
});
|
||||
close_paren_span = tok.pos();
|
||||
break;
|
||||
}
|
||||
'(' => {
|
||||
default.push(toks.next().unwrap());
|
||||
default.extend(read_until_closing_paren(toks)?);
|
||||
}
|
||||
_ => default.push(toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
'.' => {
|
||||
let next = toks.next().ok_or(("expected \".\".", span))?;
|
||||
if next.kind != '.' {
|
||||
return Err(("expected \".\".", next.pos()).into());
|
||||
}
|
||||
let next = toks.next().ok_or(("expected \".\".", next.pos()))?;
|
||||
if next.kind != '.' {
|
||||
return Err(("expected \".\".", next.pos()).into());
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
let next = toks.next().ok_or(("expected \")\".", next.pos()))?;
|
||||
if next.kind != ')' {
|
||||
return Err(("expected \")\".", next.pos()).into());
|
||||
}
|
||||
|
||||
is_variadic = true;
|
||||
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: Some(default),
|
||||
is_variadic,
|
||||
});
|
||||
break;
|
||||
}
|
||||
')' => {
|
||||
close_paren_span = span;
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: if default.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(default)
|
||||
},
|
||||
is_variadic,
|
||||
});
|
||||
break;
|
||||
}
|
||||
',' => args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: None,
|
||||
is_variadic,
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
match toks.next() {
|
||||
Some(v) if v.kind == '{' => {}
|
||||
Some(..) | None => return Err(("expected \"{\".", close_paren_span).into()),
|
||||
};
|
||||
Ok(FuncArgs(args))
|
||||
}
|
||||
|
||||
pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
span_before: Span,
|
||||
) -> SassResult<CallArgs> {
|
||||
let mut args: HashMap<CallArg, Vec<Token>> = HashMap::new();
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
let mut name = String::new();
|
||||
let mut val: Vec<Token> = Vec::new();
|
||||
let mut span = toks.peek().ok_or(("expected \")\".", span_before))?.pos();
|
||||
loop {
|
||||
match toks.peek() {
|
||||
Some(Token { kind: '$', .. }) => {
|
||||
let Token { pos, .. } = toks.next().unwrap();
|
||||
let v = eat_ident_no_interpolation(toks, false, pos)?;
|
||||
let whitespace = devour_whitespace_or_comment(toks)?;
|
||||
if let Some(Token { kind: ':', .. }) = toks.peek() {
|
||||
toks.next();
|
||||
name = v.node;
|
||||
} else {
|
||||
val.push(Token::new(pos, '$'));
|
||||
let mut current_pos = 0;
|
||||
val.extend(v.chars().map(|x| {
|
||||
let len = x.len_utf8() as u64;
|
||||
let tok = Token::new(v.span.subspan(current_pos, current_pos + len), x);
|
||||
current_pos += len;
|
||||
tok
|
||||
}));
|
||||
if whitespace {
|
||||
val.push(Token::new(pos, ' '));
|
||||
}
|
||||
name.clear();
|
||||
}
|
||||
}
|
||||
Some(Token { kind: ')', .. }) => {
|
||||
toks.next();
|
||||
return Ok(CallArgs(args, span));
|
||||
}
|
||||
Some(..) | None => name.clear(),
|
||||
}
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
|
||||
while let Some(tok) = toks.next() {
|
||||
match tok.kind {
|
||||
')' => {
|
||||
args.insert(
|
||||
if name.is_empty() {
|
||||
CallArg::Positional(args.len())
|
||||
} else {
|
||||
CallArg::Named(name.into())
|
||||
},
|
||||
val,
|
||||
);
|
||||
span = span.merge(tok.pos());
|
||||
return Ok(CallArgs(args, span));
|
||||
}
|
||||
',' => break,
|
||||
'[' => {
|
||||
val.push(tok);
|
||||
val.extend(read_until_closing_square_brace(toks)?);
|
||||
}
|
||||
'(' => {
|
||||
val.push(tok);
|
||||
val.extend(read_until_closing_paren(toks)?);
|
||||
}
|
||||
'"' | '\'' => {
|
||||
val.push(tok);
|
||||
val.extend(read_until_closing_quote(toks, tok.kind)?);
|
||||
}
|
||||
_ => val.push(tok),
|
||||
}
|
||||
}
|
||||
|
||||
args.insert(
|
||||
if name.is_empty() {
|
||||
CallArg::Positional(args.len())
|
||||
} else {
|
||||
CallArg::Named(name.as_str().into())
|
||||
},
|
||||
mem::take(&mut val),
|
||||
);
|
||||
devour_whitespace(toks);
|
||||
|
||||
if toks.peek().is_none() {
|
||||
return Ok(CallArgs(args, span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,143 +0,0 @@
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use crate::common::{Brackets, ListSeparator};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_ident, read_until_closing_curly_brace, read_until_open_curly_brace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::{Stmt, Token};
|
||||
|
||||
use super::{ruleset_eval, AtRule};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Each {
|
||||
vars: Vec<Spanned<String>>,
|
||||
iter: Vec<Value>,
|
||||
body: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Each {
|
||||
pub fn ruleset_eval(
|
||||
self,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
for row in self.iter {
|
||||
let this_iterator = match row {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m
|
||||
.into_iter()
|
||||
.map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None))
|
||||
.collect(),
|
||||
v => vec![v],
|
||||
};
|
||||
|
||||
if self.vars.len() == 1 {
|
||||
if this_iterator.len() == 1 {
|
||||
scope.insert_var(
|
||||
&self.vars[0].node,
|
||||
Spanned {
|
||||
node: this_iterator[0].clone(),
|
||||
span: self.vars[0].span,
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
scope.insert_var(
|
||||
&self.vars[0].node,
|
||||
Spanned {
|
||||
node: Value::List(this_iterator, ListSeparator::Space, Brackets::None),
|
||||
span: self.vars[0].span,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
for (var, val) in self.vars.clone().into_iter().zip(
|
||||
this_iterator
|
||||
.into_iter()
|
||||
.chain(std::iter::once(Value::Null).cycle()),
|
||||
) {
|
||||
scope.insert_var(
|
||||
&var.node,
|
||||
Spanned {
|
||||
node: val,
|
||||
span: var.span,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
ruleset_eval(
|
||||
&mut self.body.clone().into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
false,
|
||||
content,
|
||||
&mut stmts,
|
||||
)?;
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_each<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
mut span: Span,
|
||||
) -> SassResult<AtRule> {
|
||||
devour_whitespace(toks);
|
||||
let mut vars = Vec::new();
|
||||
loop {
|
||||
let next = toks.next().ok_or(("expected \"$\".", span))?;
|
||||
span = next.pos();
|
||||
match next.kind {
|
||||
'$' => vars.push(eat_ident(toks, scope, super_selector, next.pos)?),
|
||||
_ => return Err(("expected \"$\".", next.pos()).into()),
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
if toks
|
||||
.peek()
|
||||
.ok_or(("expected \"$\".", vars[vars.len() - 1].span))?
|
||||
.kind
|
||||
== ','
|
||||
{
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let i = eat_ident(toks, scope, super_selector, span)?;
|
||||
if i.node.to_ascii_lowercase() != "in" {
|
||||
return Err(("Expected \"in\".", i.span).into());
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
let iter_val = Value::from_vec(
|
||||
read_until_open_curly_brace(toks)?,
|
||||
scope,
|
||||
super_selector,
|
||||
i.span,
|
||||
)?;
|
||||
let iter = match iter_val.node.eval(iter_val.span)?.node {
|
||||
Value::List(v, ..) => v,
|
||||
Value::Map(m) => m
|
||||
.into_iter()
|
||||
.map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None))
|
||||
.collect(),
|
||||
v => vec![v],
|
||||
};
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
let mut body = read_until_closing_curly_brace(toks)?;
|
||||
body.push(toks.next().unwrap());
|
||||
devour_whitespace(toks);
|
||||
|
||||
Ok(AtRule::Each(Each { vars, iter, body }))
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
use std::iter::{Iterator, Rev, Skip};
|
||||
use std::ops::Range;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
use super::parse::ruleset_eval;
|
||||
use super::AtRule;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_ident, peek_ident_no_interpolation, read_until_closing_curly_brace,
|
||||
read_until_open_curly_brace,
|
||||
};
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{Stmt, Token};
|
||||
|
||||
pub(crate) enum ForIterator {
|
||||
Forward(Range<isize>),
|
||||
Backward(Rev<Skip<Range<isize>>>),
|
||||
}
|
||||
|
||||
impl Iterator for ForIterator {
|
||||
type Item = isize;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Forward(i) => i.next(),
|
||||
Self::Backward(i) => i.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct For {
|
||||
pub var: Spanned<String>,
|
||||
from: isize,
|
||||
to: isize,
|
||||
through: isize,
|
||||
pub body: Vec<Token>,
|
||||
}
|
||||
|
||||
impl For {
|
||||
pub fn ruleset_eval(
|
||||
self,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
for i in self.iter() {
|
||||
scope.insert_var(
|
||||
&self.var.node,
|
||||
Spanned {
|
||||
node: Value::Dimension(Number::from(i), Unit::None),
|
||||
span: self.var.span,
|
||||
},
|
||||
)?;
|
||||
ruleset_eval(
|
||||
&mut self.body.iter().cloned().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
false,
|
||||
content,
|
||||
&mut stmts,
|
||||
)?;
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
#[allow(clippy::range_plus_one)]
|
||||
pub fn iter(&self) -> ForIterator {
|
||||
if self.from < self.to {
|
||||
ForIterator::Forward(self.from..(self.to + self.through))
|
||||
} else {
|
||||
ForIterator::Backward(((self.to - self.through)..(self.from + 1)).skip(1).rev())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_for<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
span: Span,
|
||||
) -> SassResult<AtRule> {
|
||||
devour_whitespace(toks);
|
||||
let next = toks.next().ok_or(("expected \"$\".", span))?;
|
||||
let var = match next.kind {
|
||||
'$' => eat_ident(toks, scope, super_selector, next.pos)?,
|
||||
_ => return Err(("expected \"$\".", span).into()),
|
||||
};
|
||||
devour_whitespace(toks);
|
||||
if toks.peek().is_none() {
|
||||
return Err(("Expected \"from\".", var.span).into());
|
||||
}
|
||||
let span_before = toks.peek().unwrap().pos;
|
||||
if eat_ident(toks, scope, super_selector, span_before)?.to_ascii_lowercase() != "from" {
|
||||
return Err(("Expected \"from\".", var.span).into());
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
let mut from_toks = Vec::new();
|
||||
let mut through = 0;
|
||||
while let Some(tok) = toks.peek().cloned() {
|
||||
match tok.kind {
|
||||
't' | 'T' | '\\' => {
|
||||
let ident = peek_ident_no_interpolation(toks, false, tok.pos)?;
|
||||
match ident.node.to_ascii_lowercase().as_str() {
|
||||
"through" => {
|
||||
through = 1;
|
||||
// todo: it should take more if there were escapes
|
||||
toks.take(7).for_each(drop);
|
||||
break;
|
||||
}
|
||||
"to" => {
|
||||
// todo: it should take more if there were escapes
|
||||
toks.take(2).for_each(drop);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
return Err(("Invalid flag name.", ident.span).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
'{' => {
|
||||
return Err(("Expected \"to\" or \"through\".", tok.pos()).into());
|
||||
}
|
||||
_ => from_toks.push(toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
let from_val = Value::from_vec(from_toks, scope, super_selector, span_before)?;
|
||||
let from = match from_val.node.eval(from_val.span)?.node {
|
||||
Value::Dimension(n, _) => match n.to_integer().to_isize() {
|
||||
Some(v) => v,
|
||||
None => return Err((format!("{} is not a int.", n), from_val.span).into()),
|
||||
},
|
||||
v => {
|
||||
return Err((
|
||||
format!("{} is not an integer.", v.inspect(from_val.span)?),
|
||||
from_val.span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let to_toks = read_until_open_curly_brace(toks)?;
|
||||
toks.next();
|
||||
let to_val = Value::from_vec(to_toks, scope, super_selector, from_val.span)?;
|
||||
let to = match to_val.node.eval(to_val.span)?.node {
|
||||
Value::Dimension(n, _) => match n.to_integer().to_isize() {
|
||||
Some(v) => v,
|
||||
None => return Err((format!("{} is not a int.", n), to_val.span).into()),
|
||||
},
|
||||
v => {
|
||||
return Err((
|
||||
format!("{} is not an integer.", v.to_css_string(to_val.span)?),
|
||||
to_val.span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let body = read_until_closing_curly_brace(toks)?;
|
||||
toks.next();
|
||||
|
||||
devour_whitespace(toks);
|
||||
|
||||
Ok(AtRule::For(For {
|
||||
from,
|
||||
to,
|
||||
through,
|
||||
body,
|
||||
var,
|
||||
}))
|
||||
}
|
@ -1,26 +1,12 @@
|
||||
use std::mem;
|
||||
use codemap::Span;
|
||||
|
||||
use super::eat_stmts;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use crate::args::{eat_func_args, CallArgs, FuncArgs};
|
||||
use crate::atrule::AtRule;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::utils::{devour_whitespace, eat_ident, read_until_closing_curly_brace};
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{Stmt, Token};
|
||||
use crate::{args::FuncArgs, scope::Scope, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Function {
|
||||
scope: Scope,
|
||||
args: FuncArgs,
|
||||
body: Vec<Token>,
|
||||
pub scope: Scope,
|
||||
pub args: FuncArgs,
|
||||
pub body: Vec<Token>,
|
||||
pos: Span,
|
||||
}
|
||||
|
||||
@ -41,151 +27,4 @@ impl Function {
|
||||
pos,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decl_from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<(String, Function)> {
|
||||
let Spanned { node: name, span } = eat_ident(toks, &scope, super_selector, span_before)?;
|
||||
devour_whitespace(toks);
|
||||
let args = match toks.next() {
|
||||
Some(Token { kind: '(', .. }) => eat_func_args(toks, &scope, super_selector)?,
|
||||
Some(Token { pos, .. }) => return Err(("expected \"(\".", pos).into()),
|
||||
None => return Err(("expected \"(\".", span).into()),
|
||||
};
|
||||
|
||||
devour_whitespace(toks);
|
||||
|
||||
let mut body = read_until_closing_curly_brace(toks)?; //eat_stmts(toks, &mut scope.clone(), super_selector)?;
|
||||
body.push(toks.next().unwrap());
|
||||
devour_whitespace(toks);
|
||||
|
||||
Ok((name, Function::new(scope, args, body, span)))
|
||||
}
|
||||
|
||||
pub fn args(
|
||||
&mut self,
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<()> {
|
||||
let mut scope = scope.clone();
|
||||
for (idx, arg) in self.args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
let arg_list = Value::ArgList(args.get_variadic(&scope, super_selector)?);
|
||||
self.scope.insert_var(
|
||||
arg.name.clone(),
|
||||
Spanned {
|
||||
node: arg_list,
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone(), &scope, super_selector) {
|
||||
Some(v) => v?,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => Value::from_vec(mem::take(v), &scope, super_selector, args.span())?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.insert_var(arg.name.clone(), val.clone())?;
|
||||
self.scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_body(&mut self, super_selector: &Selector) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
eat_stmts(
|
||||
&mut std::mem::take(&mut self.body).into_iter().peekmore(),
|
||||
&mut self.scope,
|
||||
super_selector,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
mut self,
|
||||
args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
self.args(args, scope, super_selector)?;
|
||||
let stmts = self.eval_body(super_selector)?;
|
||||
self.call(super_selector, stmts)?
|
||||
.ok_or_else(|| ("Function finished without @return.", self.pos).into())
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&mut self,
|
||||
super_selector: &Selector,
|
||||
stmts: Vec<Spanned<Stmt>>,
|
||||
) -> SassResult<Option<Value>> {
|
||||
for stmt in stmts {
|
||||
match stmt.node {
|
||||
Stmt::AtRule(AtRule::Return(toks)) => {
|
||||
return Ok(Some(
|
||||
Value::from_vec(toks, &self.scope, super_selector, stmt.span)?.node,
|
||||
));
|
||||
}
|
||||
Stmt::AtRule(AtRule::For(f)) => {
|
||||
for i in f.iter() {
|
||||
self.scope.insert_var(
|
||||
&f.var.node,
|
||||
Spanned {
|
||||
node: Value::Dimension(Number::from(i), Unit::None),
|
||||
span: f.var.span,
|
||||
},
|
||||
)?;
|
||||
let for_stmts = eat_stmts(
|
||||
&mut f.body.clone().into_iter().peekmore(),
|
||||
&mut self.scope,
|
||||
super_selector,
|
||||
false,
|
||||
None,
|
||||
)?;
|
||||
if let Some(v) = self.call(super_selector, for_stmts)? {
|
||||
return Ok(Some(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
Stmt::AtRule(AtRule::If(i)) => {
|
||||
let if_stmts = i.eval(&mut self.scope, super_selector, None)?;
|
||||
if let Some(v) = self.call(super_selector, if_stmts)? {
|
||||
return Ok(Some(v));
|
||||
}
|
||||
}
|
||||
Stmt::AtRule(AtRule::While(w)) => {
|
||||
let scope = &mut self.scope.clone();
|
||||
let mut val =
|
||||
Value::from_vec(w.cond.clone(), scope, super_selector, stmt.span)?;
|
||||
while val.node.is_true(val.span)? {
|
||||
let while_stmts = eat_stmts(
|
||||
&mut w.body.clone().into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
false,
|
||||
None,
|
||||
)?;
|
||||
if let Some(v) = self.call(super_selector, while_stmts)? {
|
||||
return Ok(Some(v));
|
||||
}
|
||||
val = Value::from_vec(w.cond.clone(), scope, super_selector, val.span)?;
|
||||
}
|
||||
}
|
||||
Stmt::AtRule(AtRule::Each(..)) => todo!("@each in @function"),
|
||||
// todo: multiline comments
|
||||
_ => return Err(("This at-rule is not allowed here.", stmt.span).into()),
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
@ -1,137 +0,0 @@
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use super::ruleset_eval;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, devour_whitespace_or_comment, peek_ident_no_interpolation,
|
||||
read_until_closing_curly_brace, read_until_open_curly_brace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::{Stmt, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct If {
|
||||
pub branches: Vec<Branch>,
|
||||
pub else_: Vec<Token>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Branch {
|
||||
pub cond: Vec<Token>,
|
||||
pub toks: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
pub fn new(cond: Vec<Token>, toks: Vec<Token>) -> Branch {
|
||||
Branch { cond, toks }
|
||||
}
|
||||
}
|
||||
|
||||
impl If {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
span_before: Span,
|
||||
) -> SassResult<If> {
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
let mut branches = Vec::new();
|
||||
let init_cond_toks = read_until_open_curly_brace(toks)?;
|
||||
if init_cond_toks.is_empty() {
|
||||
return Err(("Expected expression.", span_before).into());
|
||||
}
|
||||
let span_before = match toks.next() {
|
||||
Some(t) => t.pos,
|
||||
None => return Err(("Expected expression.", span_before).into()),
|
||||
};
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
let mut init_toks = read_until_closing_curly_brace(toks)?;
|
||||
if let Some(tok) = toks.next() {
|
||||
init_toks.push(tok);
|
||||
} else {
|
||||
return Err(("expected \"}\".", span_before).into());
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
|
||||
branches.push(Branch::new(init_cond_toks, init_toks));
|
||||
|
||||
let mut else_ = Vec::new();
|
||||
|
||||
loop {
|
||||
if let Some(Token { kind: '@', pos }) = toks.peek().cloned() {
|
||||
toks.peek_forward(1);
|
||||
let ident = peek_ident_no_interpolation(toks, false, pos)?;
|
||||
if ident.as_str() != "else" {
|
||||
toks.reset_view();
|
||||
break;
|
||||
}
|
||||
toks.take(4).for_each(drop);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
if let Some(tok) = toks.next() {
|
||||
devour_whitespace(toks);
|
||||
match tok.kind.to_ascii_lowercase() {
|
||||
'i' if toks.next().unwrap().kind.to_ascii_lowercase() == 'f' => {
|
||||
toks.next();
|
||||
let cond = read_until_open_curly_brace(toks)?;
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
branches.push(Branch::new(cond, read_until_closing_curly_brace(toks)?));
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
}
|
||||
'{' => {
|
||||
else_ = read_until_closing_curly_brace(toks)?;
|
||||
toks.next();
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
return Err(("expected \"{\".", tok.pos()).into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
|
||||
Ok(If { branches, else_ })
|
||||
}
|
||||
|
||||
pub fn eval(
|
||||
self,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
let mut toks = Vec::new();
|
||||
let mut found_true = false;
|
||||
for branch in self.branches {
|
||||
let span_before = branch.cond.first().unwrap().pos;
|
||||
let cond = Value::from_vec(branch.cond, scope, super_selector, span_before)?;
|
||||
if cond.node.is_true(cond.span)? {
|
||||
toks = branch.toks;
|
||||
found_true = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found_true {
|
||||
toks = self.else_;
|
||||
}
|
||||
ruleset_eval(
|
||||
&mut toks.into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
false,
|
||||
content,
|
||||
&mut stmts,
|
||||
)?;
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use super::parse::ruleset_eval;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{devour_whitespace, parse_interpolation};
|
||||
use crate::{RuleSet, Stmt, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Media {
|
||||
pub super_selector: Selector,
|
||||
pub params: String,
|
||||
pub body: Vec<Spanned<Stmt>>,
|
||||
}
|
||||
|
||||
impl Media {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
kind_span: Span,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Media> {
|
||||
let mut params = String::new();
|
||||
while let Some(tok) = toks.next() {
|
||||
match tok.kind {
|
||||
'{' => break,
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
|
||||
toks.next();
|
||||
let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
|
||||
params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
|
||||
continue;
|
||||
} else {
|
||||
params.push(tok.kind);
|
||||
}
|
||||
}
|
||||
'\n' | ' ' | '\t' => {
|
||||
devour_whitespace(toks);
|
||||
params.push(' ');
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
params.push(tok.kind);
|
||||
}
|
||||
|
||||
if params.is_empty() {
|
||||
return Err(("Expected identifier.", kind_span).into());
|
||||
}
|
||||
|
||||
let mut raw_body = Vec::new();
|
||||
ruleset_eval(toks, scope, super_selector, false, content, &mut raw_body)?;
|
||||
let mut rules = Vec::with_capacity(raw_body.len());
|
||||
let mut body = Vec::new();
|
||||
|
||||
for stmt in raw_body {
|
||||
match stmt.node {
|
||||
Stmt::Style(..) => body.push(stmt),
|
||||
_ => rules.push(stmt),
|
||||
}
|
||||
}
|
||||
|
||||
if !super_selector.is_empty() {
|
||||
body = vec![Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
selector: super_selector.clone(),
|
||||
rules: body,
|
||||
super_selector: Selector::new(),
|
||||
}),
|
||||
span: kind_span,
|
||||
}];
|
||||
}
|
||||
body.append(&mut rules);
|
||||
|
||||
Ok(Media {
|
||||
super_selector: Selector::new(),
|
||||
params: params.trim().to_owned(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,261 +1,30 @@
|
||||
use std::mem;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use super::ruleset_eval;
|
||||
|
||||
use crate::args::{eat_call_args, eat_func_args, CallArgs, FuncArgs};
|
||||
use crate::atrule::AtRule;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, devour_whitespace_or_comment, eat_ident, read_until_closing_curly_brace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::{eat_expr, Expr, RuleSet, Stmt, Token};
|
||||
use crate::{args::FuncArgs, scope::Scope, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Mixin {
|
||||
scope: Scope,
|
||||
args: FuncArgs,
|
||||
body: PeekMoreIterator<IntoIter<Token>>,
|
||||
pub scope: Scope,
|
||||
pub args: FuncArgs,
|
||||
pub body: PeekMoreIterator<IntoIter<Token>>,
|
||||
pub accepts_content_block: bool,
|
||||
}
|
||||
|
||||
impl Mixin {
|
||||
pub fn new(scope: Scope, args: FuncArgs, body: Vec<Token>) -> Self {
|
||||
pub fn new(
|
||||
scope: Scope,
|
||||
args: FuncArgs,
|
||||
body: Vec<Token>,
|
||||
accepts_content_block: bool,
|
||||
) -> Self {
|
||||
let body = body.into_iter().peekmore();
|
||||
Mixin { scope, args, body }
|
||||
}
|
||||
|
||||
pub fn decl_from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<(String, Mixin)>> {
|
||||
devour_whitespace(toks);
|
||||
let Spanned { node: name, span } = eat_ident(toks, scope, super_selector, span_before)?;
|
||||
devour_whitespace(toks);
|
||||
let args = match toks.next() {
|
||||
Some(Token { kind: '(', .. }) => eat_func_args(toks, scope, super_selector)?,
|
||||
Some(Token { kind: '{', .. }) => FuncArgs::new(),
|
||||
Some(t) => return Err(("expected \"{\".", t.pos()).into()),
|
||||
None => return Err(("expected \"{\".", span).into()),
|
||||
};
|
||||
|
||||
devour_whitespace(toks);
|
||||
|
||||
let mut body = read_until_closing_curly_brace(toks)?;
|
||||
body.push(toks.next().unwrap());
|
||||
|
||||
Ok(Spanned {
|
||||
node: (name, Mixin::new(scope.clone(), args, body)),
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn args(
|
||||
mut self,
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Mixin> {
|
||||
for (idx, arg) in self.args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
self.scope.insert_var(
|
||||
mem::take(&mut arg.name),
|
||||
Spanned {
|
||||
node: Value::ArgList(args.get_variadic(scope, super_selector)?),
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone(), scope, super_selector) {
|
||||
Some(v) => v?,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => Value::from_vec(mem::take(v), scope, super_selector, args.span())?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
self.scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
Mixin {
|
||||
scope,
|
||||
args,
|
||||
body,
|
||||
accepts_content_block,
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
mut self,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
self.eval(super_selector, content)
|
||||
}
|
||||
|
||||
fn eval(
|
||||
&mut self,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector, content)? {
|
||||
let span = expr.span;
|
||||
match expr.node {
|
||||
Expr::AtRule(a) => match a {
|
||||
AtRule::For(f) => {
|
||||
stmts.extend(f.ruleset_eval(&mut self.scope, super_selector, content)?)
|
||||
}
|
||||
AtRule::Each(e) => {
|
||||
stmts.extend(e.ruleset_eval(&mut self.scope, super_selector, content)?)
|
||||
}
|
||||
AtRule::While(w) => stmts.extend(w.ruleset_eval(
|
||||
&mut self.scope,
|
||||
super_selector,
|
||||
false,
|
||||
content,
|
||||
)?),
|
||||
AtRule::Include(s) => stmts.extend(s),
|
||||
AtRule::If(i) => {
|
||||
stmts.extend(i.eval(&mut self.scope.clone(), super_selector, content)?)
|
||||
}
|
||||
AtRule::Content => {
|
||||
stmts.extend(content.unwrap_or_default().iter().cloned());
|
||||
}
|
||||
AtRule::Return(..) => {
|
||||
return Err(("This at-rule is not allowed here.", span).into())
|
||||
}
|
||||
AtRule::Debug(..) | AtRule::Warn(..) => todo!(),
|
||||
r => stmts.push(Spanned {
|
||||
node: Stmt::AtRule(r),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Expr::Style(s) => stmts.push(Spanned {
|
||||
node: Stmt::Style(s),
|
||||
span,
|
||||
}),
|
||||
Expr::Styles(s) => stmts.extend(
|
||||
s.into_iter()
|
||||
.map(Box::new)
|
||||
.map(Stmt::Style)
|
||||
.map(|style| Spanned { node: style, span }),
|
||||
),
|
||||
Expr::FunctionDecl(..) => {
|
||||
return Err(("Mixins may not contain function declarations.", span).into())
|
||||
}
|
||||
Expr::MixinDecl(..) => {
|
||||
return Err(("Mixins may not contain mixin declarations.", span).into())
|
||||
}
|
||||
Expr::Selector(selector) => {
|
||||
let rules = self.eval(
|
||||
&selector.resolve_parent_selectors(super_selector, true),
|
||||
content,
|
||||
)?;
|
||||
stmts.push(Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
super_selector: super_selector.clone(),
|
||||
selector,
|
||||
rules,
|
||||
}),
|
||||
span,
|
||||
});
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
self.scope.insert_var(&name, *val)?;
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Spanned {
|
||||
node: Stmt::MultilineComment(s),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eat_include<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
span_before: Span,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
let name = eat_ident(toks, scope, super_selector, span_before)?;
|
||||
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
|
||||
let mut has_content = false;
|
||||
|
||||
let args = if let Some(tok) = toks.next() {
|
||||
match tok.kind {
|
||||
';' => CallArgs::new(name.span),
|
||||
'(' => {
|
||||
let tmp = eat_call_args(toks, tok.pos)?;
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
if let Some(tok) = toks.peek() {
|
||||
match tok.kind {
|
||||
';' => {
|
||||
toks.next();
|
||||
}
|
||||
'{' => {
|
||||
toks.next();
|
||||
has_content = true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
tmp
|
||||
}
|
||||
'{' => {
|
||||
has_content = true;
|
||||
CallArgs::new(name.span)
|
||||
}
|
||||
_ => return Err(("expected \"{\".", tok.pos()).into()),
|
||||
}
|
||||
} else {
|
||||
return Err(("unexpected EOF", name.span).into());
|
||||
};
|
||||
|
||||
devour_whitespace(toks);
|
||||
|
||||
let mut this_content = Vec::new();
|
||||
|
||||
if let Some(tok) = toks.peek() {
|
||||
if tok.kind == '{' {
|
||||
toks.next();
|
||||
ruleset_eval(
|
||||
toks,
|
||||
&mut scope.clone(),
|
||||
super_selector,
|
||||
false,
|
||||
content,
|
||||
&mut this_content,
|
||||
)?;
|
||||
} else if has_content {
|
||||
ruleset_eval(
|
||||
toks,
|
||||
&mut scope.clone(),
|
||||
super_selector,
|
||||
false,
|
||||
content,
|
||||
&mut this_content,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mixin = scope.get_mixin(name)?;
|
||||
|
||||
let rules = mixin
|
||||
.args(args, scope, super_selector)?
|
||||
.call(super_selector, Some(&this_content))?;
|
||||
Ok(rules)
|
||||
}
|
||||
|
@ -1,273 +1,7 @@
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, read_until_closing_curly_brace, read_until_open_curly_brace,
|
||||
read_until_semicolon_or_closing_curly_brace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::{Cow, RuleSet, Stmt, Token};
|
||||
|
||||
use each_rule::{parse_each, Each};
|
||||
use for_rule::For;
|
||||
pub(crate) use function::Function;
|
||||
pub(crate) use if_rule::If;
|
||||
pub(crate) use kind::AtRuleKind;
|
||||
use media::Media;
|
||||
pub(crate) use mixin::{eat_include, Mixin};
|
||||
use parse::{eat_stmts, eat_stmts_at_root, ruleset_eval};
|
||||
use unknown::UnknownAtRule;
|
||||
use while_rule::{parse_while, While};
|
||||
pub(crate) use mixin::Mixin;
|
||||
|
||||
mod each_rule;
|
||||
mod for_rule;
|
||||
mod function;
|
||||
mod if_rule;
|
||||
mod kind;
|
||||
mod media;
|
||||
mod mixin;
|
||||
mod parse;
|
||||
mod unknown;
|
||||
mod while_rule;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum AtRule {
|
||||
Warn(Spanned<Cow<'static, str>>),
|
||||
Debug(Spanned<Cow<'static, str>>),
|
||||
Mixin(String, Box<Mixin>),
|
||||
Function(String, Box<Function>),
|
||||
Return(Vec<Token>),
|
||||
Charset,
|
||||
Content,
|
||||
Unknown(UnknownAtRule),
|
||||
For(For),
|
||||
Each(Each),
|
||||
While(While),
|
||||
Include(Vec<Spanned<Stmt>>),
|
||||
If(If),
|
||||
Media(Media),
|
||||
AtRoot(Vec<Spanned<Stmt>>),
|
||||
}
|
||||
|
||||
impl AtRule {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
rule: AtRuleKind,
|
||||
kind_span: Span,
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Spanned<AtRule>> {
|
||||
devour_whitespace(toks);
|
||||
Ok(match rule {
|
||||
AtRuleKind::Error => {
|
||||
let Spanned {
|
||||
node: message,
|
||||
span,
|
||||
} = Value::from_vec(
|
||||
read_until_semicolon_or_closing_curly_brace(toks)?,
|
||||
scope,
|
||||
super_selector,
|
||||
kind_span,
|
||||
)?;
|
||||
|
||||
return Err((message.inspect(span)?.to_string(), span.merge(kind_span)).into());
|
||||
}
|
||||
AtRuleKind::Warn => {
|
||||
let Spanned {
|
||||
node: message,
|
||||
span,
|
||||
} = Value::from_vec(
|
||||
read_until_semicolon_or_closing_curly_brace(toks)?,
|
||||
scope,
|
||||
super_selector,
|
||||
kind_span,
|
||||
)?;
|
||||
span.merge(kind_span);
|
||||
if let Some(Token { kind: ';', .. }) = toks.peek() {
|
||||
kind_span.merge(toks.next().unwrap().pos());
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
Spanned {
|
||||
node: AtRule::Warn(Spanned {
|
||||
node: message.to_css_string(span)?,
|
||||
span,
|
||||
}),
|
||||
span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::Debug => {
|
||||
let Spanned {
|
||||
node: message,
|
||||
span,
|
||||
} = Value::from_vec(
|
||||
read_until_semicolon_or_closing_curly_brace(toks)?,
|
||||
scope,
|
||||
super_selector,
|
||||
kind_span,
|
||||
)?;
|
||||
span.merge(kind_span);
|
||||
if let Some(Token { kind: ';', .. }) = toks.peek() {
|
||||
kind_span.merge(toks.next().unwrap().pos());
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
Spanned {
|
||||
node: AtRule::Debug(Spanned {
|
||||
node: message.inspect(span)?,
|
||||
span,
|
||||
}),
|
||||
span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::Mixin => {
|
||||
let Spanned {
|
||||
node: (name, mixin),
|
||||
span,
|
||||
} = Mixin::decl_from_tokens(toks, scope, super_selector, kind_span)?;
|
||||
Spanned {
|
||||
node: AtRule::Mixin(name, Box::new(mixin)),
|
||||
span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::Function => {
|
||||
let (name, func) =
|
||||
Function::decl_from_tokens(toks, scope.clone(), super_selector, kind_span)?;
|
||||
Spanned {
|
||||
node: AtRule::Function(name, Box::new(func)),
|
||||
span: kind_span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::Return => {
|
||||
let v = read_until_semicolon_or_closing_curly_brace(toks)?;
|
||||
if let Some(Token { kind: ';', .. }) = toks.peek() {
|
||||
toks.next();
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
Spanned {
|
||||
node: AtRule::Return(v),
|
||||
span: kind_span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::AtRoot => {
|
||||
let mut selector = &Selector::from_tokens(
|
||||
&mut read_until_open_curly_brace(toks)?.into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
true,
|
||||
)?
|
||||
.resolve_parent_selectors(super_selector, false);
|
||||
let mut is_some = true;
|
||||
if selector.is_empty() {
|
||||
is_some = false;
|
||||
selector = super_selector;
|
||||
}
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
let mut body = read_until_closing_curly_brace(toks)?;
|
||||
body.push(toks.next().unwrap());
|
||||
devour_whitespace(toks);
|
||||
let mut styles = Vec::new();
|
||||
#[allow(clippy::unnecessary_filter_map)]
|
||||
let raw_stmts = eat_stmts_at_root(
|
||||
&mut body.into_iter().peekmore(),
|
||||
scope,
|
||||
selector,
|
||||
0,
|
||||
is_some,
|
||||
content,
|
||||
)?
|
||||
.into_iter()
|
||||
.filter_map(|s| match s.node {
|
||||
Stmt::Style(..) => {
|
||||
styles.push(s);
|
||||
None
|
||||
}
|
||||
_ => Some(s),
|
||||
})
|
||||
.collect::<Vec<Spanned<Stmt>>>();
|
||||
let mut stmts = vec![Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
selector: selector.clone(),
|
||||
rules: styles,
|
||||
super_selector: Selector::new(),
|
||||
}),
|
||||
span: kind_span,
|
||||
}];
|
||||
stmts.extend(raw_stmts);
|
||||
Spanned {
|
||||
node: AtRule::AtRoot(stmts),
|
||||
span: kind_span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::Charset => {
|
||||
read_until_semicolon_or_closing_curly_brace(toks)?;
|
||||
if let Some(Token { kind: ';', .. }) = toks.peek() {
|
||||
toks.next();
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
Spanned {
|
||||
node: AtRule::Charset,
|
||||
span: kind_span,
|
||||
}
|
||||
}
|
||||
AtRuleKind::Each => Spanned {
|
||||
node: parse_each(toks, scope, super_selector, kind_span)?,
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::If => Spanned {
|
||||
node: AtRule::If(If::from_tokens(toks, kind_span)?),
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::For => Spanned {
|
||||
node: for_rule::parse_for(toks, scope, super_selector, kind_span)?,
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::While => parse_while(toks, kind_span)?,
|
||||
AtRuleKind::Unknown(name) => Spanned {
|
||||
node: AtRule::Unknown(UnknownAtRule::from_tokens(
|
||||
toks,
|
||||
name,
|
||||
scope,
|
||||
super_selector,
|
||||
kind_span,
|
||||
content,
|
||||
)?),
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::Content => Spanned {
|
||||
node: AtRule::Content,
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::Include => Spanned {
|
||||
node: AtRule::Include(eat_include(
|
||||
toks,
|
||||
scope,
|
||||
super_selector,
|
||||
content,
|
||||
kind_span,
|
||||
)?),
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::Media => Spanned {
|
||||
node: AtRule::Media(Media::from_tokens(
|
||||
toks,
|
||||
scope,
|
||||
super_selector,
|
||||
kind_span,
|
||||
content,
|
||||
)?),
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::Import => todo!("@import not yet implemented"),
|
||||
AtRuleKind::Forward => todo!("@forward not yet implemented"),
|
||||
AtRuleKind::Supports => todo!("@supports not yet implemented"),
|
||||
AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"),
|
||||
AtRuleKind::Extend => todo!("@extend not yet implemented"),
|
||||
AtRuleKind::Use => todo!("@use not yet implemented"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +0,0 @@
|
||||
use codemap::Spanned;
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use super::AtRule;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::{global_var_exists, insert_global_var, Scope};
|
||||
use crate::selector::Selector;
|
||||
use crate::{eat_expr, Expr, RuleSet, Stmt, Token};
|
||||
|
||||
pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
at_root: bool,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(expr) = eat_expr(toks, scope, super_selector, content)? {
|
||||
let span = expr.span;
|
||||
match expr.node {
|
||||
Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)),
|
||||
Expr::Style(s) => stmts.push(Stmt::Style(s).span(span)),
|
||||
Expr::Styles(s) => stmts.extend(
|
||||
s.into_iter()
|
||||
.map(Box::new)
|
||||
.map(Stmt::Style)
|
||||
.map(|style| Spanned { node: style, span }),
|
||||
),
|
||||
Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(),
|
||||
Expr::Selector(selector) => {
|
||||
let rules = eat_stmts(
|
||||
toks,
|
||||
scope,
|
||||
&selector.resolve_parent_selectors(super_selector, true),
|
||||
at_root,
|
||||
content,
|
||||
)?;
|
||||
stmts.push(
|
||||
Stmt::RuleSet(RuleSet {
|
||||
super_selector: super_selector.clone(),
|
||||
selector,
|
||||
rules,
|
||||
})
|
||||
.span(span),
|
||||
);
|
||||
}
|
||||
//TODO: refactor handling of `Expr::VariableDecl`, as most is already handled in `eat_expr`
|
||||
Expr::VariableDecl(name, val) => {
|
||||
if at_root && global_var_exists(&name) {
|
||||
insert_global_var(&name, *val.clone())?;
|
||||
}
|
||||
scope.insert_var(&name, *val)?;
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s).span(span)),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
pub(crate) fn eat_stmts_at_root<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
mut nesting: usize,
|
||||
is_some: bool,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(expr) = eat_expr(toks, scope, super_selector, content)? {
|
||||
let span = expr.span;
|
||||
match expr.node {
|
||||
Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)),
|
||||
Expr::Style(s) => stmts.push(Stmt::Style(s).span(span)),
|
||||
Expr::Styles(s) => stmts.extend(
|
||||
s.into_iter()
|
||||
.map(Box::new)
|
||||
.map(Stmt::Style)
|
||||
.map(|style| Spanned { node: style, span }),
|
||||
),
|
||||
Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(),
|
||||
Expr::Selector(mut selector) => {
|
||||
selector =
|
||||
selector.resolve_parent_selectors(super_selector, nesting > 1 || is_some);
|
||||
nesting += 1;
|
||||
let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true, content)?;
|
||||
nesting -= 1;
|
||||
stmts.push(
|
||||
Stmt::RuleSet(RuleSet {
|
||||
super_selector: if nesting > 1 {
|
||||
super_selector.clone()
|
||||
} else {
|
||||
Selector::new()
|
||||
},
|
||||
selector,
|
||||
rules,
|
||||
})
|
||||
.span(span),
|
||||
);
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
scope.insert_var(&name, *val)?;
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s).span(span)),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
pub(crate) fn ruleset_eval<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
at_root: bool,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
stmts: &mut Vec<Spanned<Stmt>>,
|
||||
) -> SassResult<()> {
|
||||
for stmt in eat_stmts(toks, scope, super_selector, at_root, content)? {
|
||||
match stmt.node {
|
||||
Stmt::AtRule(AtRule::For(f)) => {
|
||||
stmts.extend(f.ruleset_eval(scope, super_selector, content)?)
|
||||
}
|
||||
Stmt::AtRule(AtRule::Each(e)) => {
|
||||
stmts.extend(e.ruleset_eval(scope, super_selector, content)?)
|
||||
}
|
||||
Stmt::AtRule(AtRule::While(w)) => {
|
||||
// TODO: should at_root be false? scoping
|
||||
stmts.extend(w.ruleset_eval(scope, super_selector, at_root, content)?)
|
||||
}
|
||||
Stmt::AtRule(AtRule::Include(s)) => stmts.extend(s),
|
||||
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector, content)?),
|
||||
Stmt::AtRule(AtRule::Content) => {
|
||||
if let Some(c) = content {
|
||||
stmts.extend(c.iter().cloned());
|
||||
} else {
|
||||
return Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
stmt.span,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
_ => stmts.push(stmt),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use super::parse::ruleset_eval;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{devour_whitespace, parse_interpolation};
|
||||
use crate::{RuleSet, Stmt, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct UnknownAtRule {
|
||||
pub name: String,
|
||||
pub super_selector: Selector,
|
||||
pub params: String,
|
||||
pub body: Vec<Spanned<Stmt>>,
|
||||
}
|
||||
|
||||
impl UnknownAtRule {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
name: String,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
kind_span: Span,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<UnknownAtRule> {
|
||||
let mut params = String::new();
|
||||
devour_whitespace(toks);
|
||||
if let Some(Token { kind: ';', .. }) | None = toks.peek() {
|
||||
toks.next();
|
||||
return Ok(UnknownAtRule {
|
||||
name,
|
||||
super_selector: Selector::new(),
|
||||
params: String::new(),
|
||||
body: Vec::new(),
|
||||
});
|
||||
}
|
||||
while let Some(tok) = toks.next() {
|
||||
match tok.kind {
|
||||
'{' => break,
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
|
||||
toks.next();
|
||||
let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
|
||||
params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
|
||||
} else {
|
||||
params.push(tok.kind);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
'\n' | ' ' | '\t' => {
|
||||
devour_whitespace(toks);
|
||||
params.push(' ');
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
params.push(tok.kind);
|
||||
}
|
||||
|
||||
let mut raw_body = Vec::new();
|
||||
ruleset_eval(toks, scope, super_selector, false, content, &mut raw_body)?;
|
||||
let mut rules = Vec::with_capacity(raw_body.len());
|
||||
let mut body = Vec::new();
|
||||
|
||||
for stmt in raw_body {
|
||||
match stmt.node {
|
||||
Stmt::Style(..) => body.push(stmt),
|
||||
_ => rules.push(stmt),
|
||||
}
|
||||
}
|
||||
|
||||
if super_selector.is_empty() {
|
||||
body.append(&mut rules);
|
||||
} else {
|
||||
body = vec![Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
selector: super_selector.clone(),
|
||||
rules: body,
|
||||
super_selector: Selector::new(),
|
||||
}),
|
||||
span: kind_span,
|
||||
}];
|
||||
body.append(&mut rules);
|
||||
}
|
||||
|
||||
Ok(UnknownAtRule {
|
||||
name,
|
||||
super_selector: Selector::new(),
|
||||
params: params.trim().to_owned(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use super::{ruleset_eval, AtRule};
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, read_until_closing_curly_brace, read_until_open_curly_brace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::{Stmt, Token};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct While {
|
||||
pub cond: Vec<Token>,
|
||||
pub body: Vec<Token>,
|
||||
}
|
||||
|
||||
impl While {
|
||||
pub fn ruleset_eval(
|
||||
self,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
at_root: bool,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
let mut val = Value::from_vec(self.cond.clone(), scope, super_selector, self.cond[0].pos)?;
|
||||
let scope = &mut scope.clone();
|
||||
while val.node.is_true(val.span)? {
|
||||
ruleset_eval(
|
||||
&mut self.body.clone().into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
at_root,
|
||||
content,
|
||||
&mut stmts,
|
||||
)?;
|
||||
val = Value::from_vec(self.cond.clone(), scope, super_selector, self.cond[0].pos)?;
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_while<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
span: Span,
|
||||
) -> SassResult<Spanned<AtRule>> {
|
||||
devour_whitespace(toks);
|
||||
let cond = read_until_open_curly_brace(toks)?;
|
||||
|
||||
if cond.is_empty() {
|
||||
return Err(("Expected expression.", span).into());
|
||||
}
|
||||
|
||||
toks.next();
|
||||
|
||||
let mut body = read_until_closing_curly_brace(toks)?;
|
||||
|
||||
body.push(toks.next().unwrap());
|
||||
|
||||
devour_whitespace(toks);
|
||||
Ok(Spanned {
|
||||
node: AtRule::While(While { cond, body }),
|
||||
span,
|
||||
})
|
||||
}
|
@ -2,27 +2,23 @@ use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use num_traits::One;
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::color::Color;
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
color::Color,
|
||||
common::QuoteKind,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
|
||||
fn inner_hsl(
|
||||
name: &'static str,
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
if args.is_empty() {
|
||||
return Err(("Missing argument $channels.", args.span()).into());
|
||||
}
|
||||
|
||||
if args.len() == 1 {
|
||||
let mut channels = match arg!(args, scope, super_selector, 0, "channels") {
|
||||
let mut channels = match parser.arg(&mut args, 0, "channels")? {
|
||||
Value::List(v, ..) => v,
|
||||
_ => return Err(("Missing argument $channels.", args.span()).into()),
|
||||
};
|
||||
@ -87,11 +83,11 @@ fn inner_hsl(
|
||||
Number::one(),
|
||||
))))
|
||||
} else {
|
||||
let hue = match arg!(args, scope, super_selector, 0, "hue") {
|
||||
let hue = match parser.arg(&mut args, 0, "hue")? {
|
||||
Value::Dimension(n, _) => n,
|
||||
v if v.is_special_function() => {
|
||||
let saturation = arg!(args, scope, super_selector, 1, "saturation");
|
||||
let lightness = arg!(args, scope, super_selector, 2, "lightness");
|
||||
let saturation = parser.arg(&mut args, 1, "saturation")?;
|
||||
let lightness = parser.arg(&mut args, 2, "lightness")?;
|
||||
let mut string = format!(
|
||||
"{}({}, {}, {}",
|
||||
name,
|
||||
@ -102,7 +98,8 @@ fn inner_hsl(
|
||||
if !args.is_empty() {
|
||||
string.push_str(", ");
|
||||
string.push_str(
|
||||
&arg!(args, scope, super_selector, 3, "alpha")
|
||||
&parser
|
||||
.arg(&mut args, 3, "alpha")?
|
||||
.to_css_string(args.span())?,
|
||||
);
|
||||
}
|
||||
@ -117,10 +114,10 @@ fn inner_hsl(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let saturation = match arg!(args, scope, super_selector, 1, "saturation") {
|
||||
let saturation = match parser.arg(&mut args, 1, "saturation")? {
|
||||
Value::Dimension(n, _) => n / Number::from(100),
|
||||
v if v.is_special_function() => {
|
||||
let lightness = arg!(args, scope, super_selector, 2, "lightness");
|
||||
let lightness = parser.arg(&mut args, 2, "lightness")?;
|
||||
let mut string = format!(
|
||||
"{}({}, {}, {}",
|
||||
name,
|
||||
@ -131,7 +128,8 @@ fn inner_hsl(
|
||||
if !args.is_empty() {
|
||||
string.push_str(", ");
|
||||
string.push_str(
|
||||
&arg!(args, scope, super_selector, 3, "alpha")
|
||||
&parser
|
||||
.arg(&mut args, 3, "alpha")?
|
||||
.to_css_string(args.span())?,
|
||||
);
|
||||
}
|
||||
@ -149,7 +147,7 @@ fn inner_hsl(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let lightness = match arg!(args, scope, super_selector, 2, "lightness") {
|
||||
let lightness = match parser.arg(&mut args, 2, "lightness")? {
|
||||
Value::Dimension(n, _) => n / Number::from(100),
|
||||
v if v.is_special_function() => {
|
||||
let mut string = format!(
|
||||
@ -162,7 +160,8 @@ fn inner_hsl(
|
||||
if !args.is_empty() {
|
||||
string.push_str(", ");
|
||||
string.push_str(
|
||||
&arg!(args, scope, super_selector, 3, "alpha")
|
||||
&parser
|
||||
.arg(&mut args, 3, "alpha")?
|
||||
.to_css_string(args.span())?,
|
||||
);
|
||||
}
|
||||
@ -180,13 +179,12 @@ fn inner_hsl(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let alpha = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let alpha = match parser.default_arg(
|
||||
&mut args,
|
||||
3,
|
||||
"alpha" = Value::Dimension(Number::one(), Unit::None)
|
||||
) {
|
||||
"alpha",
|
||||
Value::Dimension(Number::one(), Unit::None),
|
||||
)? {
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
|
||||
v @ Value::Dimension(..) => {
|
||||
@ -226,17 +224,17 @@ fn inner_hsl(
|
||||
}
|
||||
}
|
||||
|
||||
fn hsl(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
inner_hsl("hsl", args, scope, super_selector)
|
||||
fn hsl(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
inner_hsl("hsl", args, parser)
|
||||
}
|
||||
|
||||
fn hsla(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
inner_hsl("hsla", args, scope, super_selector)
|
||||
fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
inner_hsl("hsla", args, parser)
|
||||
}
|
||||
|
||||
fn hue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -246,9 +244,9 @@ fn hue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
|
||||
}
|
||||
}
|
||||
|
||||
fn saturation(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -258,9 +256,9 @@ fn saturation(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
}
|
||||
}
|
||||
|
||||
fn lightness(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -270,9 +268,9 @@ fn lightness(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_hue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -282,7 +280,7 @@ fn adjust_hue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let degrees = match arg!(args, scope, super_selector, 1, "degrees") {
|
||||
let degrees = match parser.arg(&mut args, 1, "degrees")? {
|
||||
Value::Dimension(n, _) => n,
|
||||
v => {
|
||||
return Err((
|
||||
@ -298,9 +296,9 @@ fn adjust_hue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
Ok(Value::Color(Box::new(color.adjust_hue(degrees))))
|
||||
}
|
||||
|
||||
fn lighten(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn lighten(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -310,7 +308,7 @@ fn lighten(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
|
||||
v => {
|
||||
return Err((
|
||||
@ -326,9 +324,9 @@ fn lighten(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
Ok(Value::Color(Box::new(color.lighten(amount))))
|
||||
}
|
||||
|
||||
fn darken(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn darken(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -338,7 +336,7 @@ fn darken(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
|
||||
v => {
|
||||
return Err((
|
||||
@ -354,19 +352,21 @@ fn darken(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
|
||||
Ok(Value::Color(Box::new(color.darken(amount))))
|
||||
}
|
||||
|
||||
fn saturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
if args.len() == 1 {
|
||||
return Ok(Value::String(
|
||||
format!(
|
||||
"saturate({})",
|
||||
arg!(args, scope, super_selector, 0, "amount").to_css_string(args.span())?
|
||||
parser
|
||||
.arg(&mut args, 0, "amount")?
|
||||
.to_css_string(args.span())?
|
||||
),
|
||||
QuoteKind::None,
|
||||
));
|
||||
}
|
||||
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
|
||||
v => {
|
||||
return Err((
|
||||
@ -379,7 +379,7 @@ fn saturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
Value::Dimension(n, u) => {
|
||||
return Ok(Value::String(
|
||||
@ -398,9 +398,9 @@ fn saturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
|
||||
Ok(Value::Color(Box::new(color.saturate(amount))))
|
||||
}
|
||||
|
||||
fn desaturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn desaturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -410,7 +410,7 @@ fn desaturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
|
||||
v => {
|
||||
return Err((
|
||||
@ -426,9 +426,9 @@ fn desaturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
Ok(Value::Color(Box::new(color.desaturate(amount))))
|
||||
}
|
||||
|
||||
fn grayscale(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
Value::Dimension(n, u) => {
|
||||
return Ok(Value::String(
|
||||
@ -447,9 +447,9 @@ fn grayscale(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
Ok(Value::Color(Box::new(color.desaturate(Number::one()))))
|
||||
}
|
||||
|
||||
fn complement(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn complement(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -462,15 +462,14 @@ fn complement(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
Ok(Value::Color(Box::new(color.complement())))
|
||||
}
|
||||
|
||||
fn invert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let weight = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let weight = match parser.default_arg(
|
||||
&mut args,
|
||||
1,
|
||||
"weight" = Value::Dimension(Number::from(100), Unit::Percent)
|
||||
) {
|
||||
"weight",
|
||||
Value::Dimension(Number::from(100), Unit::Percent),
|
||||
)? {
|
||||
Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
|
||||
v => {
|
||||
return Err((
|
||||
@ -483,7 +482,7 @@ fn invert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
|
||||
.into())
|
||||
}
|
||||
};
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))),
|
||||
Value::Dimension(n, Unit::Percent) => {
|
||||
Ok(Value::String(format!("invert({}%)", n), QuoteKind::None))
|
||||
|
@ -1,17 +1,13 @@
|
||||
use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::Number;
|
||||
use crate::value::Value;
|
||||
use crate::{
|
||||
args::CallArgs, common::QuoteKind, error::SassResult, parse::Parser, unit::Unit, value::Number,
|
||||
value::Value,
|
||||
};
|
||||
|
||||
fn alpha(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -21,9 +17,9 @@ fn alpha(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRe
|
||||
}
|
||||
}
|
||||
|
||||
fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
|
||||
Value::Dimension(num, unit) => Ok(Value::String(
|
||||
format!("opacity({}{})", num, unit),
|
||||
@ -37,9 +33,9 @@ fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
}
|
||||
}
|
||||
|
||||
fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn opacify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -49,7 +45,7 @@ fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
@ -65,9 +61,9 @@ fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
Ok(Value::Color(Box::new(color.fade_in(amount))))
|
||||
}
|
||||
|
||||
fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn fade_in(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -77,7 +73,7 @@ fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
@ -93,13 +89,9 @@ fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
Ok(Value::Color(Box::new(color.fade_in(amount))))
|
||||
}
|
||||
|
||||
fn transparentize(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn transparentize(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -109,7 +101,7 @@ fn transparentize(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
@ -125,9 +117,9 @@ fn transparentize(
|
||||
Ok(Value::Color(Box::new(color.fade_out(amount))))
|
||||
}
|
||||
|
||||
fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn fade_out(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -137,7 +129,7 @@ fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let amount = match arg!(args, scope, super_selector, 1, "amount") {
|
||||
let amount = match parser.arg(&mut args, 1, "amount")? {
|
||||
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
|
||||
v => {
|
||||
return Err((
|
||||
|
@ -2,18 +2,19 @@ use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use num_traits::{One, Signed, Zero};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::color::Color;
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
color::Color,
|
||||
common::QuoteKind,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
|
||||
macro_rules! opt_rgba {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => {
|
||||
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
|
||||
let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
|
||||
Value::Dimension(n, u) => Some(bound!($args, $arg, n, u, $low, $high)),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
@ -32,8 +33,8 @@ macro_rules! opt_rgba {
|
||||
}
|
||||
|
||||
macro_rules! opt_hsl {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => {
|
||||
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
|
||||
let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
|
||||
Value::Dimension(n, u) => {
|
||||
Some(bound!($args, $arg, n, u, $low, $high) / Number::from(100))
|
||||
}
|
||||
@ -53,8 +54,8 @@ macro_rules! opt_hsl {
|
||||
};
|
||||
}
|
||||
|
||||
fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
if args.get_positional(1, scope, super_selector).is_some() {
|
||||
fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
if parser.positional_arg(&mut args, 1).is_some() {
|
||||
return Err((
|
||||
"Only one positional argument is allowed. All other arguments must be passed by name.",
|
||||
args.span(),
|
||||
@ -62,7 +63,7 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
.into());
|
||||
}
|
||||
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -73,10 +74,10 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}
|
||||
};
|
||||
|
||||
opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector);
|
||||
opt_rgba!(args, red, "red", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, green, "green", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector);
|
||||
opt_rgba!(args, alpha, "alpha", 0, 1, parser);
|
||||
opt_rgba!(args, red, "red", 0, 255, parser);
|
||||
opt_rgba!(args, green, "green", 0, 255, parser);
|
||||
opt_rgba!(args, blue, "blue", 0, 255, parser);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
@ -87,7 +88,7 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
))));
|
||||
}
|
||||
|
||||
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
|
||||
let hue = match parser.default_named_arg(&mut args, "hue", Value::Null)? {
|
||||
Value::Dimension(n, _) => Some(n),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
@ -99,16 +100,8 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}
|
||||
};
|
||||
|
||||
opt_hsl!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
0,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector);
|
||||
opt_hsl!(args, saturation, "saturation", 0, 100, parser);
|
||||
opt_hsl!(args, luminance, "lightness", 0, 100, parser);
|
||||
|
||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
@ -128,8 +121,8 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}))
|
||||
}
|
||||
|
||||
fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -140,10 +133,10 @@ fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}
|
||||
};
|
||||
|
||||
opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector);
|
||||
opt_rgba!(args, red, "red", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, green, "green", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector);
|
||||
opt_rgba!(args, alpha, "alpha", -1, 1, parser);
|
||||
opt_rgba!(args, red, "red", -255, 255, parser);
|
||||
opt_rgba!(args, green, "green", -255, 255, parser);
|
||||
opt_rgba!(args, blue, "blue", -255, 255, parser);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
@ -154,7 +147,7 @@ fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
))));
|
||||
}
|
||||
|
||||
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
|
||||
let hue = match parser.default_named_arg(&mut args, "hue", Value::Null)? {
|
||||
Value::Dimension(n, _) => Some(n),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
@ -166,24 +159,8 @@ fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}
|
||||
};
|
||||
|
||||
opt_hsl!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(
|
||||
args,
|
||||
luminance,
|
||||
"lightness",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_hsl!(args, saturation, "saturation", -100, 100, parser);
|
||||
opt_hsl!(args, luminance, "lightness", -100, 100, parser);
|
||||
|
||||
if hue.is_some() || saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
@ -204,7 +181,9 @@ fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}))
|
||||
}
|
||||
|
||||
fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
// todo: refactor into rgb and hsl?
|
||||
fn scale_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
fn scale(val: Number, by: Number, max: Number) -> Number {
|
||||
if by.is_zero() {
|
||||
return val;
|
||||
@ -213,7 +192,7 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}
|
||||
|
||||
let span = args.span();
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -225,8 +204,8 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
};
|
||||
|
||||
macro_rules! opt_scale_arg {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => {
|
||||
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
|
||||
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
|
||||
let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
|
||||
Value::Dimension(n, Unit::Percent) => {
|
||||
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100))
|
||||
}
|
||||
@ -257,10 +236,10 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
};
|
||||
}
|
||||
|
||||
opt_scale_arg!(args, alpha, "alpha", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector);
|
||||
opt_scale_arg!(args, alpha, "alpha", -100, 100, parser);
|
||||
opt_scale_arg!(args, red, "red", -100, 100, parser);
|
||||
opt_scale_arg!(args, green, "green", -100, 100, parser);
|
||||
opt_scale_arg!(args, blue, "blue", -100, 100, parser);
|
||||
|
||||
if red.is_some() || green.is_some() || blue.is_some() {
|
||||
return Ok(Value::Color(Box::new(Color::from_rgba(
|
||||
@ -287,24 +266,8 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
))));
|
||||
}
|
||||
|
||||
opt_scale_arg!(
|
||||
args,
|
||||
saturation,
|
||||
"saturation",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_scale_arg!(
|
||||
args,
|
||||
luminance,
|
||||
"lightness",
|
||||
-100,
|
||||
100,
|
||||
scope,
|
||||
super_selector
|
||||
);
|
||||
opt_scale_arg!(args, saturation, "saturation", -100, 100, parser);
|
||||
opt_scale_arg!(args, luminance, "lightness", -100, 100, parser);
|
||||
|
||||
if saturation.is_some() || luminance.is_some() {
|
||||
// Color::as_hsla() returns more exact values than Color::hue(), etc.
|
||||
@ -337,9 +300,9 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
}))
|
||||
}
|
||||
|
||||
fn ie_hex_str(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn ie_hex_str(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
|
@ -2,28 +2,26 @@ use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use num_traits::One;
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::color::Color;
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
color::Color,
|
||||
common::QuoteKind,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
|
||||
/// name: Either `rgb` or `rgba` depending on the caller
|
||||
fn inner_rgb(
|
||||
name: &'static str,
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
// todo: refactor into smaller functions
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
if args.is_empty() {
|
||||
return Err(("Missing argument $channels.", args.span()).into());
|
||||
}
|
||||
|
||||
if args.len() == 1 {
|
||||
let mut channels = match arg!(args, scope, super_selector, 0, "channels") {
|
||||
let mut channels = match parser.arg(&mut args, 0, "channels")? {
|
||||
Value::List(v, ..) => v,
|
||||
_ => return Err(("Missing argument $channels.", args.span()).into()),
|
||||
};
|
||||
@ -121,10 +119,10 @@ fn inner_rgb(
|
||||
|
||||
Ok(Value::Color(Box::new(color)))
|
||||
} else if args.len() == 2 {
|
||||
let color = match arg!(args, scope, super_selector, 0, "color") {
|
||||
let color = match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => c,
|
||||
v if v.is_special_function() => {
|
||||
let alpha = arg!(args, scope, super_selector, 1, "alpha");
|
||||
let alpha = parser.arg(&mut args, 1, "alpha")?;
|
||||
return Ok(Value::String(
|
||||
format!(
|
||||
"{}({}, {})",
|
||||
@ -143,7 +141,7 @@ fn inner_rgb(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let alpha = match arg!(args, scope, super_selector, 1, "alpha") {
|
||||
let alpha = match parser.arg(&mut args, 1, "alpha")? {
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
|
||||
v @ Value::Dimension(..) => {
|
||||
@ -179,7 +177,7 @@ fn inner_rgb(
|
||||
};
|
||||
Ok(Value::Color(Box::new(color.with_alpha(alpha))))
|
||||
} else {
|
||||
let red = match arg!(args, scope, super_selector, 0, "red") {
|
||||
let red = match parser.arg(&mut args, 0, "red")? {
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255),
|
||||
v @ Value::Dimension(..) => {
|
||||
@ -193,8 +191,8 @@ fn inner_rgb(
|
||||
.into())
|
||||
}
|
||||
v if v.is_special_function() => {
|
||||
let green = arg!(args, scope, super_selector, 1, "green");
|
||||
let blue = arg!(args, scope, super_selector, 2, "blue");
|
||||
let green = parser.arg(&mut args, 1, "green")?;
|
||||
let blue = parser.arg(&mut args, 2, "blue")?;
|
||||
let mut string = format!(
|
||||
"{}({}, {}, {}",
|
||||
name,
|
||||
@ -205,7 +203,8 @@ fn inner_rgb(
|
||||
if !args.is_empty() {
|
||||
string.push_str(", ");
|
||||
string.push_str(
|
||||
&arg!(args, scope, super_selector, 3, "alpha")
|
||||
&parser
|
||||
.arg(&mut args, 3, "alpha")?
|
||||
.to_css_string(args.span())?,
|
||||
);
|
||||
}
|
||||
@ -220,7 +219,7 @@ fn inner_rgb(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let green = match arg!(args, scope, super_selector, 1, "green") {
|
||||
let green = match parser.arg(&mut args, 1, "green")? {
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255),
|
||||
v @ Value::Dimension(..) => {
|
||||
@ -234,7 +233,7 @@ fn inner_rgb(
|
||||
.into())
|
||||
}
|
||||
v if v.is_special_function() => {
|
||||
let blue = arg!(args, scope, super_selector, 2, "blue");
|
||||
let blue = parser.arg(&mut args, 2, "blue")?;
|
||||
let mut string = format!(
|
||||
"{}({}, {}, {}",
|
||||
name,
|
||||
@ -245,7 +244,8 @@ fn inner_rgb(
|
||||
if !args.is_empty() {
|
||||
string.push_str(", ");
|
||||
string.push_str(
|
||||
&arg!(args, scope, super_selector, 3, "alpha")
|
||||
&parser
|
||||
.arg(&mut args, 3, "alpha")?
|
||||
.to_css_string(args.span())?,
|
||||
);
|
||||
}
|
||||
@ -260,7 +260,7 @@ fn inner_rgb(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let blue = match arg!(args, scope, super_selector, 2, "blue") {
|
||||
let blue = match parser.arg(&mut args, 2, "blue")? {
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255),
|
||||
v @ Value::Dimension(..) => {
|
||||
@ -284,7 +284,8 @@ fn inner_rgb(
|
||||
if !args.is_empty() {
|
||||
string.push_str(", ");
|
||||
string.push_str(
|
||||
&arg!(args, scope, super_selector, 3, "alpha")
|
||||
&parser
|
||||
.arg(&mut args, 3, "alpha")?
|
||||
.to_css_string(args.span())?,
|
||||
);
|
||||
}
|
||||
@ -299,13 +300,12 @@ fn inner_rgb(
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let alpha = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let alpha = match parser.default_arg(
|
||||
&mut args,
|
||||
3,
|
||||
"alpha" = Value::Dimension(Number::one(), Unit::None)
|
||||
) {
|
||||
"alpha",
|
||||
Value::Dimension(Number::one(), Unit::None),
|
||||
)? {
|
||||
Value::Dimension(n, Unit::None) => n,
|
||||
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
|
||||
v @ Value::Dimension(..) => {
|
||||
@ -343,17 +343,17 @@ fn inner_rgb(
|
||||
}
|
||||
}
|
||||
|
||||
fn rgb(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
inner_rgb("rgb", args, scope, super_selector)
|
||||
fn rgb(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
inner_rgb("rgb", args, parser)
|
||||
}
|
||||
|
||||
fn rgba(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
inner_rgb("rgba", args, scope, super_selector)
|
||||
fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
inner_rgb("rgba", args, parser)
|
||||
}
|
||||
|
||||
fn red(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -363,9 +363,9 @@ fn red(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
|
||||
}
|
||||
}
|
||||
|
||||
fn green(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -375,9 +375,9 @@ fn green(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRe
|
||||
}
|
||||
}
|
||||
|
||||
fn blue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "color") {
|
||||
match parser.arg(&mut args, 0, "color")? {
|
||||
Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None)),
|
||||
v => Err((
|
||||
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
|
||||
@ -387,9 +387,9 @@ fn blue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
|
||||
}
|
||||
}
|
||||
|
||||
fn mix(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let color1 = match arg!(args, scope, super_selector, 0, "color1") {
|
||||
let color1 = match parser.arg(&mut args, 0, "color1")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -400,7 +400,7 @@ fn mix(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
|
||||
}
|
||||
};
|
||||
|
||||
let color2 = match arg!(args, scope, super_selector, 1, "color2") {
|
||||
let color2 = match parser.arg(&mut args, 1, "color2")? {
|
||||
Value::Color(c) => c,
|
||||
v => {
|
||||
return Err((
|
||||
@ -411,13 +411,12 @@ fn mix(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
|
||||
}
|
||||
};
|
||||
|
||||
let weight = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let weight = match parser.default_arg(
|
||||
&mut args,
|
||||
2,
|
||||
"weight" = Value::Dimension(Number::from(50), Unit::None)
|
||||
) {
|
||||
"weight",
|
||||
Value::Dimension(Number::from(50), Unit::None),
|
||||
)? {
|
||||
Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
|
||||
v => {
|
||||
return Err((
|
||||
|
@ -2,17 +2,18 @@ use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use num_traits::{One, Signed, ToPrimitive, Zero};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::common::{Brackets, ListSeparator, QuoteKind};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::{Brackets, ListSeparator, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
|
||||
fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let len = match arg!(args, scope, super_selector, 0, "list") {
|
||||
let len = match parser.arg(&mut args, 0, "list")? {
|
||||
Value::List(v, ..) => Number::from(v.len()),
|
||||
Value::Map(m) => Number::from(m.len()),
|
||||
_ => Number::one(),
|
||||
@ -20,10 +21,10 @@ fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
|
||||
Ok(Value::Dimension(len, Unit::None))
|
||||
}
|
||||
|
||||
fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let mut list = arg!(args, scope, super_selector, 0, "list").as_list();
|
||||
let n = match arg!(args, scope, super_selector, 1, "n") {
|
||||
let mut list = parser.arg(&mut args, 0, "list")?.as_list();
|
||||
let n = match parser.arg(&mut args, 1, "n")? {
|
||||
Value::Dimension(num, _) => num,
|
||||
v => {
|
||||
return Err((
|
||||
@ -61,14 +62,10 @@ fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
|
||||
}))
|
||||
}
|
||||
|
||||
fn list_separator(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn list_separator(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::String(
|
||||
match arg!(args, scope, super_selector, 0, "list") {
|
||||
match parser.arg(&mut args, 0, "list")? {
|
||||
Value::List(_, sep, ..) => sep.name(),
|
||||
_ => ListSeparator::Space.name(),
|
||||
}
|
||||
@ -77,14 +74,14 @@ fn list_separator(
|
||||
))
|
||||
}
|
||||
|
||||
fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
|
||||
let (mut list, sep, brackets) = match parser.arg(&mut args, 0, "list")? {
|
||||
Value::List(v, sep, b) => (v, sep, b),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let n = match arg!(args, scope, super_selector, 1, "n") {
|
||||
let n = match parser.arg(&mut args, 1, "n")? {
|
||||
Value::Dimension(num, _) => num,
|
||||
v => {
|
||||
return Err((
|
||||
@ -113,7 +110,7 @@ fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
return Err((format!("$n: {} is not an int.", n), args.span()).into());
|
||||
}
|
||||
|
||||
let val = arg!(args, scope, super_selector, 2, "value");
|
||||
let val = parser.arg(&mut args, 2, "value")?;
|
||||
|
||||
if n.is_positive() {
|
||||
list[n.to_integer().to_usize().unwrap_or(std::usize::MAX) - 1] = val;
|
||||
@ -124,20 +121,19 @@ fn set_nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
Ok(Value::List(list, sep, brackets))
|
||||
}
|
||||
|
||||
fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn append(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (mut list, sep, brackets) = match arg!(args, scope, super_selector, 0, "list") {
|
||||
let (mut list, sep, brackets) = match parser.arg(&mut args, 0, "list")? {
|
||||
Value::List(v, sep, b) => (v, sep, b),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let val = arg!(args, scope, super_selector, 1, "val");
|
||||
let sep = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let val = parser.arg(&mut args, 1, "val")?;
|
||||
let sep = match parser.default_arg(
|
||||
&mut args,
|
||||
2,
|
||||
"separator" = Value::String("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
"separator",
|
||||
Value::String("auto".to_owned(), QuoteKind::None),
|
||||
)? {
|
||||
Value::String(s, ..) => match s.as_str() {
|
||||
"auto" => sep,
|
||||
"comma" => ListSeparator::Comma,
|
||||
@ -167,25 +163,24 @@ fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
|
||||
Ok(Value::List(list, sep, brackets))
|
||||
}
|
||||
|
||||
fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(4)?;
|
||||
let (mut list1, sep1, brackets) = match arg!(args, scope, super_selector, 0, "list1") {
|
||||
let (mut list1, sep1, brackets) = match parser.arg(&mut args, 0, "list1")? {
|
||||
Value::List(v, sep, brackets) => (v, sep, brackets),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
|
||||
v => (vec![v], ListSeparator::Space, Brackets::None),
|
||||
};
|
||||
let (list2, sep2) = match arg!(args, scope, super_selector, 1, "list2") {
|
||||
let (list2, sep2) = match parser.arg(&mut args, 1, "list2")? {
|
||||
Value::List(v, sep, ..) => (v, sep),
|
||||
Value::Map(m) => (m.entries(), ListSeparator::Comma),
|
||||
v => (vec![v], ListSeparator::Space),
|
||||
};
|
||||
let sep = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let sep = match parser.default_arg(
|
||||
&mut args,
|
||||
2,
|
||||
"separator" = Value::String("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
"separator",
|
||||
Value::String("auto".to_owned(), QuoteKind::None),
|
||||
)? {
|
||||
Value::String(s, ..) => match s.as_str() {
|
||||
"auto" => {
|
||||
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
|
||||
@ -216,13 +211,12 @@ fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
|
||||
}
|
||||
};
|
||||
|
||||
let brackets = match arg!(
|
||||
args,
|
||||
scope,
|
||||
super_selector,
|
||||
let brackets = match parser.default_arg(
|
||||
&mut args,
|
||||
3,
|
||||
"bracketed" = Value::String("auto".to_owned(), QuoteKind::None)
|
||||
) {
|
||||
"bracketed",
|
||||
Value::String("auto".to_owned(), QuoteKind::None),
|
||||
)? {
|
||||
Value::String(s, ..) => match s.as_str() {
|
||||
"auto" => brackets,
|
||||
_ => Brackets::Bracketed,
|
||||
@ -241,23 +235,21 @@ fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
|
||||
Ok(Value::List(list1, sep, brackets))
|
||||
}
|
||||
|
||||
fn is_bracketed(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn is_bracketed(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::bool(
|
||||
match arg!(args, scope, super_selector, 0, "list") {
|
||||
Value::List(.., brackets) => match brackets {
|
||||
Brackets::Bracketed => true,
|
||||
Brackets::None => false,
|
||||
},
|
||||
_ => false,
|
||||
Ok(Value::bool(match parser.arg(&mut args, 0, "list")? {
|
||||
Value::List(.., brackets) => match brackets {
|
||||
Brackets::Bracketed => true,
|
||||
Brackets::None => false,
|
||||
},
|
||||
))
|
||||
_ => false,
|
||||
}))
|
||||
}
|
||||
|
||||
fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let list = arg!(args, scope, super_selector, 0, "list").as_list();
|
||||
let value = arg!(args, scope, super_selector, 1, "value");
|
||||
let list = parser.arg(&mut args, 0, "list")?.as_list();
|
||||
let value = parser.arg(&mut args, 1, "value")?;
|
||||
// TODO: find a way around this unwrap.
|
||||
// It should be impossible to hit as the arg is
|
||||
// evaluated prior to checking equality, but
|
||||
@ -275,10 +267,10 @@ fn index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRe
|
||||
Ok(Value::Dimension(index, Unit::None))
|
||||
}
|
||||
|
||||
fn zip(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let span = args.span();
|
||||
let lists = args
|
||||
.get_variadic(scope, super_selector)?
|
||||
let lists = parser
|
||||
.variadic_args(args)?
|
||||
.into_iter()
|
||||
.map(|x| Ok(x.node.eval(span)?.node.as_list()))
|
||||
.collect::<SassResult<Vec<Vec<Value>>>>()?;
|
||||
|
@ -1,41 +1,3 @@
|
||||
macro_rules! arg {
|
||||
($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal) => {
|
||||
match $args.get_positional($idx, $scope, $super_selector) {
|
||||
Some(v) => v?.node.eval($args.span())?.node,
|
||||
None => match $args.get_named($name.to_owned(), $scope, $super_selector) {
|
||||
Some(v) => v?.node.eval($args.span())?.node,
|
||||
None => {
|
||||
return Err((concat!("Missing argument $", $name, "."), $args.span()).into())
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal=$default:expr) => {
|
||||
match $args.get_positional($idx, $scope, $super_selector) {
|
||||
Some(v) => v?.node.eval($args.span())?.node,
|
||||
None => match $args.get_named($name.to_owned(), $scope, $super_selector) {
|
||||
Some(v) => v?.node.eval($args.span())?.node,
|
||||
None => $default,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! named_arg {
|
||||
($args:ident, $scope:ident, $super_selector:ident, $name:literal) => {
|
||||
match $args.get_named($name.to_owned(), $scope, $super_selector) {
|
||||
Some(v) => v?.node.eval($args.span())?.node,
|
||||
None => return Err((concat!("Missing argument $", $name, "."), $args.span()).into()),
|
||||
};
|
||||
};
|
||||
($args:ident, $scope:ident, $super_selector:ident, $name:literal=$default:expr) => {
|
||||
match $args.get_named($name.to_owned(), $scope, $super_selector) {
|
||||
Some(v) => v?.node.eval($args.span())?.node,
|
||||
None => $default,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bound {
|
||||
($args:ident, $name:literal, $arg:ident, $unit:ident, $low:literal, $high:literal) => {
|
||||
if $arg > Number::from($high) || $arg < Number::from($low) {
|
||||
|
@ -1,16 +1,17 @@
|
||||
use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::common::{Brackets, ListSeparator};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::{SassMap, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::{Brackets, ListSeparator},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
value::{SassMap, Value},
|
||||
};
|
||||
|
||||
fn map_get(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn map_get(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let key = arg!(args, scope, super_selector, 1, "key");
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
let key = parser.arg(&mut args, 1, "key")?;
|
||||
let map = match parser.arg(&mut args, 0, "map")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -24,10 +25,10 @@ fn map_get(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
fn map_has_key(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let key = arg!(args, scope, super_selector, 1, "key");
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
let key = parser.arg(&mut args, 1, "key")?;
|
||||
let map = match parser.arg(&mut args, 0, "map")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -41,9 +42,9 @@ fn map_has_key(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
Ok(Value::bool(map.get(&key, args.span())?.is_some()))
|
||||
}
|
||||
|
||||
fn map_keys(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
let map = match parser.arg(&mut args, 0, "map")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -61,9 +62,9 @@ fn map_keys(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
|
||||
))
|
||||
}
|
||||
|
||||
fn map_values(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn map_values(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
let map = match parser.arg(&mut args, 0, "map")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -81,9 +82,9 @@ fn map_values(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
))
|
||||
}
|
||||
|
||||
fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let mut map1 = match arg!(args, scope, super_selector, 0, "map1") {
|
||||
let mut map1 = match parser.arg(&mut args, 0, "map1")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -94,7 +95,7 @@ fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let map2 = match arg!(args, scope, super_selector, 1, "map2") {
|
||||
let map2 = match parser.arg(&mut args, 1, "map2")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -109,8 +110,8 @@ fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
Ok(Value::Map(map1))
|
||||
}
|
||||
|
||||
fn map_remove(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let mut map = match arg!(args, scope, super_selector, 0, "map") {
|
||||
fn map_remove(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let mut map = match parser.arg(&mut args, 0, "map")? {
|
||||
Value::Map(m) => m,
|
||||
Value::List(v, ..) if v.is_empty() => SassMap::new(),
|
||||
v => {
|
||||
@ -121,7 +122,7 @@ fn map_remove(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let keys = args.get_variadic(scope, super_selector)?;
|
||||
let keys = parser.variadic_args(args)?;
|
||||
for key in keys {
|
||||
map.remove(&key);
|
||||
}
|
||||
|
@ -5,16 +5,17 @@ use num_traits::{One, Signed, ToPrimitive, Zero};
|
||||
#[cfg(feature = "random")]
|
||||
use rand::Rng;
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
|
||||
fn percentage(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let num = match arg!(args, scope, super_selector, 0, "number") {
|
||||
let num = match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(n, Unit::None) => n * Number::from(100),
|
||||
v @ Value::Dimension(..) => {
|
||||
return Err((
|
||||
@ -40,9 +41,9 @@ fn percentage(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
Ok(Value::Dimension(num, Unit::Percent))
|
||||
}
|
||||
|
||||
fn round(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
@ -55,9 +56,9 @@ fn round(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRe
|
||||
}
|
||||
}
|
||||
|
||||
fn ceil(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
@ -70,9 +71,9 @@ fn ceil(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
|
||||
}
|
||||
}
|
||||
|
||||
fn floor(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
@ -85,9 +86,9 @@ fn floor(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRe
|
||||
}
|
||||
}
|
||||
|
||||
fn abs(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "number") {
|
||||
match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)),
|
||||
v => Err((
|
||||
format!(
|
||||
@ -100,9 +101,9 @@ fn abs(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
|
||||
}
|
||||
}
|
||||
|
||||
fn comparable(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let unit1 = match arg!(args, scope, super_selector, 0, "number1") {
|
||||
let unit1 = match parser.arg(&mut args, 0, "number1")? {
|
||||
Value::Dimension(_, u) => u,
|
||||
v => {
|
||||
return Err((
|
||||
@ -115,7 +116,7 @@ fn comparable(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let unit2 = match arg!(args, scope, super_selector, 1, "number2") {
|
||||
let unit2 = match parser.arg(&mut args, 1, "number2")? {
|
||||
Value::Dimension(_, u) => u,
|
||||
v => {
|
||||
return Err((
|
||||
@ -134,9 +135,9 @@ fn comparable(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
|
||||
// TODO: write tests for this
|
||||
#[cfg(feature = "random")]
|
||||
fn random(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let limit = match arg!(args, scope, super_selector, 0, "limit" = Value::Null) {
|
||||
let limit = match parser.default_arg(&mut args, 0, "limit", Value::Null)? {
|
||||
Value::Dimension(n, _) => n,
|
||||
Value::Null => {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
@ -2,31 +2,30 @@ use super::{Builtin, GlobalFunctionMap, GLOBAL_FUNCTIONS};
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::global_var_exists;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{SassFunction, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::QuoteKind,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{SassFunction, Value},
|
||||
};
|
||||
|
||||
fn if_(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn if_(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? {
|
||||
Ok(arg!(args, scope, super_selector, 1, "if-true"))
|
||||
if parser
|
||||
.arg(&mut args, 0, "condition")?
|
||||
.is_true(args.span())?
|
||||
{
|
||||
Ok(parser.arg(&mut args, 1, "if-true")?)
|
||||
} else {
|
||||
Ok(arg!(args, scope, super_selector, 2, "if-false"))
|
||||
Ok(parser.arg(&mut args, 2, "if-false")?)
|
||||
}
|
||||
}
|
||||
|
||||
fn feature_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "feature") {
|
||||
match parser.arg(&mut args, 0, "feature")? {
|
||||
#[allow(clippy::match_same_arms)]
|
||||
Value::String(s, _) => Ok(match s.as_str() {
|
||||
// A local variable will shadow a global variable unless
|
||||
@ -57,9 +56,9 @@ fn feature_exists(
|
||||
}
|
||||
}
|
||||
|
||||
fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let unit = match arg!(args, scope, super_selector, 0, "number") {
|
||||
let unit = match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(_, u) => u.to_string(),
|
||||
v => {
|
||||
return Err((
|
||||
@ -75,88 +74,44 @@ fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
|
||||
Ok(Value::String(unit, QuoteKind::Quoted))
|
||||
}
|
||||
|
||||
fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
let value = arg!(args, scope, super_selector, 0, "value");
|
||||
let value = parser.arg(&mut args, 0, "value")?;
|
||||
Ok(Value::String(
|
||||
value.kind(args.span())?.to_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn unitless(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
#[allow(clippy::match_same_arms)]
|
||||
Ok(match arg!(args, scope, super_selector, 0, "number") {
|
||||
Ok(match parser.arg(&mut args, 0, "number")? {
|
||||
Value::Dimension(_, Unit::None) => Value::True,
|
||||
Value::Dimension(_, _) => Value::False,
|
||||
_ => Value::True,
|
||||
})
|
||||
}
|
||||
|
||||
fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(Value::String(
|
||||
arg!(args, scope, super_selector, 0, "value")
|
||||
parser
|
||||
.arg(&mut args, 0, "value")?
|
||||
.inspect(args.span())?
|
||||
.into_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
}
|
||||
|
||||
fn variable_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::String(s, _) => Ok(Value::bool(scope.var_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn global_variable_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::String(s, _) => Ok(Value::bool(global_var_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn mixin_exists(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
Value::String(s, _) => Ok(Value::bool(scope.mixin_exists(&s))),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_exists(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match arg!(args, scope, super_selector, 0, "name") {
|
||||
match parser.arg(&mut args, 0, "name")? {
|
||||
Value::String(s, _) => Ok(Value::bool(
|
||||
scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(s.as_str()),
|
||||
parser
|
||||
.scopes
|
||||
.last()
|
||||
.var_exists(&s.into(), parser.global_scope),
|
||||
)),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
@ -166,9 +121,51 @@ fn function_exists(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn global_variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match parser.arg(&mut args, 0, "name")? {
|
||||
Value::String(s, _) => Ok(Value::bool(
|
||||
parser.global_scope.var_exists_no_global(&s.into()),
|
||||
)),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match parser.arg(&mut args, 0, "name")? {
|
||||
Value::String(s, _) => Ok(Value::bool(
|
||||
parser.scopes.last().mixin_exists(&s, parser.global_scope),
|
||||
)),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
match parser.arg(&mut args, 0, "name")? {
|
||||
Value::String(s, _) => Ok(Value::bool(
|
||||
parser.scopes.last().fn_exists(&s, parser.global_scope),
|
||||
)),
|
||||
v => Err((
|
||||
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
|
||||
args.span(),
|
||||
)
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let name = match arg!(args, scope, super_selector, 0, "name") {
|
||||
let name = match parser.arg(&mut args, 0, "name")? {
|
||||
Value::String(s, _) => s,
|
||||
v => {
|
||||
return Err((
|
||||
@ -178,8 +175,10 @@ fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let css = arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?;
|
||||
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) {
|
||||
let css = parser
|
||||
.default_arg(&mut args, 1, "css", Value::False)?
|
||||
.is_true(args.span())?;
|
||||
let module = match parser.default_arg(&mut args, 2, "module", Value::Null)? {
|
||||
Value::String(s, ..) => Some(s),
|
||||
Value::Null => None,
|
||||
v => {
|
||||
@ -202,10 +201,13 @@ fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
.into());
|
||||
}
|
||||
|
||||
let func = match scope.get_fn(Spanned {
|
||||
node: &name,
|
||||
span: args.span(),
|
||||
}) {
|
||||
let func = match parser.scopes.last().get_fn(
|
||||
Spanned {
|
||||
node: &name,
|
||||
span: args.span(),
|
||||
},
|
||||
parser.global_scope,
|
||||
) {
|
||||
Ok(f) => SassFunction::UserDefined(Box::new(f), name.into()),
|
||||
Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) {
|
||||
Some(f) => SassFunction::Builtin(f.clone(), name.into()),
|
||||
@ -216,8 +218,8 @@ fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
Ok(Value::Function(func))
|
||||
}
|
||||
|
||||
fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
let func = match arg!(args, scope, super_selector, 0, "function") {
|
||||
fn call(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let func = match parser.arg(&mut args, 0, "function")? {
|
||||
Value::Function(f) => f,
|
||||
v => {
|
||||
return Err((
|
||||
@ -230,7 +232,20 @@ fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
|
||||
.into())
|
||||
}
|
||||
};
|
||||
func.call(args.decrement(), scope, super_selector)
|
||||
func.call(args.decrement(), parser)
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(0)?;
|
||||
if !parser.in_mixin {
|
||||
return Err((
|
||||
"content-exists() may only be called within a mixin.",
|
||||
parser.span_before,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
Ok(Value::bool(parser.content.is_some()))
|
||||
}
|
||||
|
||||
pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
||||
@ -249,4 +264,5 @@ pub(crate) fn declare(f: &mut GlobalFunctionMap) {
|
||||
f.insert("function-exists", Builtin::new(function_exists));
|
||||
f.insert("get-function", Builtin::new(get_function));
|
||||
f.insert("call", Builtin::new(call));
|
||||
f.insert("content-exists", Builtin::new(content_exists));
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::Value;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{args::CallArgs, error::SassResult, parse::Parser, value::Value};
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@ -26,12 +25,12 @@ static FUNCTION_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
// TODO: impl Fn
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Builtin(
|
||||
pub fn(CallArgs, &Scope, &Selector) -> SassResult<Value>,
|
||||
pub fn(CallArgs, &mut Parser<'_>) -> SassResult<Value>,
|
||||
usize,
|
||||
);
|
||||
|
||||
impl Builtin {
|
||||
pub fn new(body: fn(CallArgs, &Scope, &Selector) -> SassResult<Value>) -> Builtin {
|
||||
pub fn new(body: fn(CallArgs, &mut Parser<'_>) -> SassResult<Value>) -> Builtin {
|
||||
let count = FUNCTION_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
Self(body, count)
|
||||
}
|
||||
|
@ -1,54 +1,34 @@
|
||||
use super::{Builtin, GlobalFunctionMap};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::common::{Brackets, ListSeparator, QuoteKind};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::{
|
||||
ComplexSelector, ComplexSelectorComponent, Extender, Selector, SelectorList,
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::{Brackets, ListSeparator, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
selector::{ComplexSelector, ComplexSelectorComponent, Extender, Selector, SelectorList},
|
||||
value::Value,
|
||||
};
|
||||
use crate::value::Value;
|
||||
|
||||
fn is_superselector(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn is_superselector(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let parent_selector = arg!(args, scope, super_selector, 0, "super").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"super",
|
||||
false,
|
||||
)?;
|
||||
let child_selector = arg!(args, scope, super_selector, 1, "sub").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"sub",
|
||||
false,
|
||||
)?;
|
||||
let parent_selector = parser
|
||||
.arg(&mut args, 0, "super")?
|
||||
.to_selector(parser, "super", false)?;
|
||||
let child_selector = parser
|
||||
.arg(&mut args, 1, "sub")?
|
||||
.to_selector(parser, "sub", false)?;
|
||||
|
||||
Ok(Value::bool(
|
||||
parent_selector.is_super_selector(&child_selector),
|
||||
))
|
||||
}
|
||||
|
||||
fn simple_selectors(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn simple_selectors(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
// todo: Value::to_compound_selector
|
||||
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"selector",
|
||||
false,
|
||||
)?;
|
||||
let selector = parser
|
||||
.arg(&mut args, 0, "selector")?
|
||||
.to_selector(parser, "selector", false)?;
|
||||
|
||||
if selector.0.components.len() != 1 {
|
||||
return Err(("$selector: expected selector.", args.span()).into());
|
||||
@ -73,30 +53,24 @@ fn simple_selectors(
|
||||
))
|
||||
}
|
||||
|
||||
fn selector_parse(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn selector_parse(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
Ok(arg!(args, scope, super_selector, 0, "selector")
|
||||
.to_selector(args.span(), scope, super_selector, "selector", false)?
|
||||
Ok(parser
|
||||
.arg(&mut args, 0, "selector")?
|
||||
.to_selector(parser, "selector", false)?
|
||||
.into_value())
|
||||
}
|
||||
|
||||
fn selector_nest(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn selector_nest(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let span = args.span();
|
||||
let selectors = args.get_variadic(scope, super_selector)?;
|
||||
let selectors = parser.variadic_args(args)?;
|
||||
if selectors.is_empty() {
|
||||
return Err(("$selectors: At least one selector must be passed.", span).into());
|
||||
}
|
||||
|
||||
Ok(selectors
|
||||
.into_iter()
|
||||
.map(|sel| {
|
||||
sel.node
|
||||
.to_selector(span, scope, super_selector, "selectors", true)
|
||||
})
|
||||
.map(|sel| sel.node.to_selector(parser, "selectors", true))
|
||||
.collect::<SassResult<Vec<Selector>>>()?
|
||||
.into_iter()
|
||||
.fold(Selector::new(), |parent, child| {
|
||||
@ -105,9 +79,9 @@ fn selector_nest(args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
.into_value())
|
||||
}
|
||||
|
||||
fn selector_append(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let span = args.span();
|
||||
let selectors = args.get_variadic(scope, super_selector)?;
|
||||
let selectors = parser.variadic_args(args)?;
|
||||
if selectors.is_empty() {
|
||||
return Err(("$selectors: At least one selector must be passed.", span).into());
|
||||
}
|
||||
@ -115,9 +89,7 @@ fn selector_append(args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
let mut parsed_selectors = selectors
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let tmp = s
|
||||
.node
|
||||
.to_selector(span, scope, super_selector, "selectors", false)?;
|
||||
let tmp = s.node.to_selector(parser, "selectors", false)?;
|
||||
if tmp.contains_parent_selector() {
|
||||
Err(("Parent selectors aren't allowed here.", span).into())
|
||||
} else {
|
||||
@ -164,80 +136,42 @@ fn selector_append(args: CallArgs, scope: &Scope, super_selector: &Selector) ->
|
||||
.into_value())
|
||||
}
|
||||
|
||||
fn selector_extend(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn selector_extend(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"selector",
|
||||
false,
|
||||
)?;
|
||||
let target = arg!(args, scope, super_selector, 1, "extendee").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"extendee",
|
||||
false,
|
||||
)?;
|
||||
let source = arg!(args, scope, super_selector, 2, "extender").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"extender",
|
||||
false,
|
||||
)?;
|
||||
let selector = parser
|
||||
.arg(&mut args, 0, "selector")?
|
||||
.to_selector(parser, "selector", false)?;
|
||||
let target = parser
|
||||
.arg(&mut args, 1, "extendee")?
|
||||
.to_selector(parser, "extendee", false)?;
|
||||
let source = parser
|
||||
.arg(&mut args, 2, "extender")?
|
||||
.to_selector(parser, "extender", false)?;
|
||||
|
||||
Ok(Extender::extend(selector.0, source.0, target.0).to_sass_list())
|
||||
}
|
||||
|
||||
fn selector_replace(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"selector",
|
||||
false,
|
||||
)?;
|
||||
let target = arg!(args, scope, super_selector, 1, "original").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"original",
|
||||
false,
|
||||
)?;
|
||||
let source = arg!(args, scope, super_selector, 2, "replacement").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"replacement",
|
||||
false,
|
||||
)?;
|
||||
let selector = parser
|
||||
.arg(&mut args, 0, "selector")?
|
||||
.to_selector(parser, "selector", false)?;
|
||||
let target = parser
|
||||
.arg(&mut args, 1, "original")?
|
||||
.to_selector(parser, "original", false)?;
|
||||
let source =
|
||||
parser
|
||||
.arg(&mut args, 2, "replacement")?
|
||||
.to_selector(parser, "replacement", false)?;
|
||||
Ok(Extender::replace(selector.0, source.0, target.0).to_sass_list())
|
||||
}
|
||||
|
||||
fn selector_unify(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let selector1 = arg!(args, scope, super_selector, 0, "selector1").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"selector1",
|
||||
false,
|
||||
)?;
|
||||
let selector1 =
|
||||
parser
|
||||
.arg(&mut args, 0, "selector1")?
|
||||
.to_selector(parser, "selector1", false)?;
|
||||
|
||||
if selector1.contains_parent_selector() {
|
||||
return Err((
|
||||
@ -247,13 +181,10 @@ fn selector_unify(
|
||||
.into());
|
||||
}
|
||||
|
||||
let selector2 = arg!(args, scope, super_selector, 1, "selector2").to_selector(
|
||||
args.span(),
|
||||
scope,
|
||||
super_selector,
|
||||
"selector2",
|
||||
false,
|
||||
)?;
|
||||
let selector2 =
|
||||
parser
|
||||
.arg(&mut args, 1, "selector2")?
|
||||
.to_selector(parser, "selector2", false)?;
|
||||
|
||||
if selector2.contains_parent_selector() {
|
||||
return Err((
|
||||
|
@ -6,21 +6,18 @@ use num_traits::{Signed, ToPrimitive, Zero};
|
||||
#[cfg(feature = "random")]
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::value::{Number, Value};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::QuoteKind,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
|
||||
fn to_upper_case(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn to_upper_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(mut i, q) => {
|
||||
i.make_ascii_uppercase();
|
||||
Ok(Value::String(i, q))
|
||||
@ -36,13 +33,9 @@ fn to_upper_case(
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lower_case(
|
||||
mut args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
fn to_lower_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(mut i, q) => {
|
||||
i.make_ascii_lowercase();
|
||||
Ok(Value::String(i, q))
|
||||
@ -58,9 +51,9 @@ fn to_lower_case(
|
||||
}
|
||||
}
|
||||
|
||||
fn str_length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn str_length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(i, _) => Ok(Value::Dimension(
|
||||
Number::from(i.chars().count()),
|
||||
Unit::None,
|
||||
@ -76,9 +69,9 @@ fn str_length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
}
|
||||
}
|
||||
|
||||
fn quote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn quote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(i, _) => Ok(Value::String(i, QuoteKind::Quoted)),
|
||||
v => Err((
|
||||
format!(
|
||||
@ -91,9 +84,9 @@ fn quote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRe
|
||||
}
|
||||
}
|
||||
|
||||
fn unquote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn unquote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
match arg!(args, scope, super_selector, 0, "string") {
|
||||
match parser.arg(&mut args, 0, "string")? {
|
||||
i @ Value::String(..) => Ok(i.unquote()),
|
||||
v => Err((
|
||||
format!(
|
||||
@ -106,9 +99,9 @@ fn unquote(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
|
||||
}
|
||||
}
|
||||
|
||||
fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") {
|
||||
let (string, quotes) = match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(s, q) => (s, q),
|
||||
v => {
|
||||
return Err((
|
||||
@ -122,7 +115,7 @@ fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
}
|
||||
};
|
||||
let str_len = string.chars().count();
|
||||
let start = match arg!(args, scope, super_selector, 1, "start-at") {
|
||||
let start = match parser.arg(&mut args, 1, "start-at")? {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("{} is not an int.", n), args.span()).into())
|
||||
}
|
||||
@ -155,7 +148,7 @@ fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) {
|
||||
let mut end = match parser.default_arg(&mut args, 2, "end-at", Value::Null)? {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("{} is not an int.", n), args.span()).into())
|
||||
}
|
||||
@ -208,9 +201,9 @@ fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
}
|
||||
}
|
||||
|
||||
fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let s1 = match arg!(args, scope, super_selector, 0, "string") {
|
||||
let s1 = match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
@ -224,7 +217,7 @@ fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
}
|
||||
};
|
||||
|
||||
let substr = match arg!(args, scope, super_selector, 1, "substring") {
|
||||
let substr = match parser.arg(&mut args, 1, "substring")? {
|
||||
Value::String(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
@ -244,9 +237,9 @@ fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
|
||||
})
|
||||
}
|
||||
|
||||
fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
|
||||
fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
|
||||
let (s1, quotes) = match parser.arg(&mut args, 0, "string")? {
|
||||
Value::String(i, q) => (i, q),
|
||||
v => {
|
||||
return Err((
|
||||
@ -260,7 +253,7 @@ fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
}
|
||||
};
|
||||
|
||||
let substr = match arg!(args, scope, super_selector, 1, "insert") {
|
||||
let substr = match parser.arg(&mut args, 1, "insert")? {
|
||||
Value::String(i, _) => i,
|
||||
v => {
|
||||
return Err((
|
||||
@ -274,7 +267,7 @@ fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
}
|
||||
};
|
||||
|
||||
let index = match arg!(args, scope, super_selector, 2, "index") {
|
||||
let index = match parser.arg(&mut args, 2, "index")? {
|
||||
Value::Dimension(n, Unit::None) if n.is_decimal() => {
|
||||
return Err((format!("$index: {} is not an int.", n), args.span()).into())
|
||||
}
|
||||
@ -347,7 +340,7 @@ fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
|
||||
|
||||
#[cfg(feature = "random")]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> {
|
||||
fn unique_id(args: CallArgs, _: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(0)?;
|
||||
let mut rng = thread_rng();
|
||||
let string = std::iter::repeat(())
|
||||
|
@ -15,8 +15,10 @@
|
||||
//! Named colors retain their original casing,
|
||||
//! so `rEd` should be emitted as `rEd`.
|
||||
|
||||
use std::cmp::{max, min};
|
||||
use std::fmt::{self, Display};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
use crate::value::Number;
|
||||
pub(crate) use name::NAMED_COLORS;
|
||||
|
10
src/error.rs
10
src/error.rs
@ -1,7 +1,9 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
io,
|
||||
string::FromUtf8Error,
|
||||
};
|
||||
|
||||
use codemap::{Span, SpanLoc};
|
||||
|
||||
|
@ -1,46 +0,0 @@
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::path::Path;
|
||||
|
||||
use codemap::{CodeMap, Spanned};
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::{Stmt, StyleSheet};
|
||||
|
||||
pub(crate) fn import(
|
||||
ctx: &Path,
|
||||
path: &Path,
|
||||
map: &mut CodeMap,
|
||||
) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let mut rules = Vec::new();
|
||||
let mut scope = Scope::new();
|
||||
if path.is_absolute() {
|
||||
todo!("absolute import")
|
||||
}
|
||||
let path_buf = ctx.parent().unwrap_or_else(|| Path::new("")).join(path);
|
||||
// "todo: will panic if path ended in `..`"
|
||||
let name = path_buf.file_name().unwrap();
|
||||
if path_buf.extension() == Some(OsStr::new(".css")) {
|
||||
// || name.starts_with("http://") || name.starts_with("https://") {
|
||||
todo!("handle css imports")
|
||||
}
|
||||
let mut p1 = path_buf.clone();
|
||||
p1.push(OsString::from("index.scss"));
|
||||
let mut p2 = path_buf.clone();
|
||||
p2.push(OsString::from("_index.scss"));
|
||||
let paths = [
|
||||
path_buf.with_file_name(name).with_extension("scss"),
|
||||
path_buf.with_file_name(format!("_{}.scss", name.to_str().unwrap())),
|
||||
path_buf,
|
||||
p1,
|
||||
p2,
|
||||
];
|
||||
for name in &paths {
|
||||
if name.is_file() {
|
||||
let (rules2, scope2) = StyleSheet::export_from_path(&name.to_str().unwrap(), map)?;
|
||||
rules.extend(rules2);
|
||||
scope.extend(scope2);
|
||||
}
|
||||
}
|
||||
Ok((rules, scope))
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use std::sync::Arc;
|
||||
use std::{iter::Peekable, str::Chars, sync::Arc};
|
||||
|
||||
use codemap::File;
|
||||
|
||||
|
385
src/lib.rs
385
src/lib.rs
@ -11,10 +11,8 @@ Spec progress as of 2020-05-01:
|
||||
|
||||
## Use as library
|
||||
```
|
||||
use grass::{SassResult, StyleSheet};
|
||||
|
||||
fn main() -> SassResult<()> {
|
||||
let sass = StyleSheet::new("a { b { color: &; } }".to_string())?;
|
||||
fn main() -> Result<(), grass::Error> {
|
||||
let sass = grass::from_string("a { b { color: &; } }".to_string())?;
|
||||
assert_eq!(sass, "a b {\n color: a b;\n}\n");
|
||||
Ok(())
|
||||
}
|
||||
@ -88,32 +86,26 @@ grass input.scss
|
||||
)]
|
||||
#![cfg_attr(feature = "nightly", feature(track_caller))]
|
||||
#![cfg_attr(feature = "profiling", inline(never))]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::iter::Iterator;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub(crate) use beef::lean::Cow;
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
pub(crate) use beef::Cow;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
use codemap::CodeMap;
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
use peekmore::PeekMore;
|
||||
|
||||
use crate::atrule::{AtRule, AtRuleKind, Function, Mixin};
|
||||
pub use crate::error::{SassError, SassResult};
|
||||
use crate::scope::{insert_global_var, Scope};
|
||||
use crate::selector::Selector;
|
||||
use crate::style::Style;
|
||||
pub use crate::stylesheet::StyleSheet;
|
||||
pub use crate::error::{SassError as Error, SassResult as Result};
|
||||
pub(crate) use crate::token::Token;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_comment, eat_ident, eat_variable_value, peek_ident_no_interpolation,
|
||||
peek_whitespace, read_until_closing_curly_brace, read_until_closing_paren, read_until_newline,
|
||||
VariableDecl,
|
||||
use crate::{
|
||||
lexer::Lexer,
|
||||
output::Css,
|
||||
parse::{common::NeverEmptyVec, Parser},
|
||||
scope::Scope,
|
||||
selector::Selector,
|
||||
};
|
||||
use crate::value::Value;
|
||||
|
||||
mod args;
|
||||
mod atrule;
|
||||
@ -121,294 +113,101 @@ mod builtin;
|
||||
mod color;
|
||||
mod common;
|
||||
mod error;
|
||||
mod imports;
|
||||
mod lexer;
|
||||
mod output;
|
||||
mod parse;
|
||||
mod scope;
|
||||
mod selector;
|
||||
mod style;
|
||||
mod stylesheet;
|
||||
mod token;
|
||||
mod unit;
|
||||
mod utils;
|
||||
mod value;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Stmt {
|
||||
/// A [`Style`](/grass/style/struct.Style)
|
||||
Style(Box<Style>),
|
||||
/// A [`RuleSet`](/grass/struct.RuleSet.html)
|
||||
RuleSet(RuleSet),
|
||||
/// A multiline comment: `/* foo bar */`
|
||||
MultilineComment(String),
|
||||
/// A CSS rule: `@charset "UTF-8";`
|
||||
AtRule(AtRule),
|
||||
fn raw_to_parse_error(map: &CodeMap, err: Error) -> Error {
|
||||
let (message, span) = err.raw();
|
||||
Error::from_loc(message, map.look_up_span(span))
|
||||
}
|
||||
|
||||
impl Stmt {
|
||||
const fn span(self, span: Span) -> Spanned<Self> {
|
||||
Spanned { node: self, span }
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a single rule set. Rule sets can contain other rule sets
|
||||
/// Write CSS to `buf`, constructed from a path
|
||||
///
|
||||
/// ```scss
|
||||
/// a {
|
||||
/// color: blue;
|
||||
/// b {
|
||||
/// color: red;
|
||||
/// }
|
||||
/// ```
|
||||
/// fn main() -> Result<(), grass::Error> {
|
||||
/// let sass = grass::from_path("input.scss")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RuleSet {
|
||||
selector: Selector,
|
||||
rules: Vec<Spanned<Stmt>>,
|
||||
// potential optimization: we don't *need* to own the selector
|
||||
super_selector: Selector,
|
||||
#[cfg_attr(feature = "profiling", inline(never))]
|
||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn from_path(p: &str) -> Result<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file(p.into(), String::from_utf8(fs::read(p)?)?);
|
||||
Css::from_stmts(
|
||||
Parser {
|
||||
toks: &mut Lexer::new(&file)
|
||||
.collect::<Vec<Token>>()
|
||||
.into_iter()
|
||||
.peekmore(),
|
||||
map: &mut map,
|
||||
path: p.as_ref(),
|
||||
scopes: &mut NeverEmptyVec::new(Scope::new()),
|
||||
global_scope: &mut Scope::new(),
|
||||
super_selectors: &mut NeverEmptyVec::new(Selector::new()),
|
||||
span_before: file.span.subspan(0, 0),
|
||||
content: None,
|
||||
in_mixin: false,
|
||||
in_function: false,
|
||||
in_control_flow: false,
|
||||
at_root: true,
|
||||
at_root_has_selector: false,
|
||||
}
|
||||
.parse()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?,
|
||||
)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
/// An intermediate representation of what are essentially single lines
|
||||
/// todo! rename this
|
||||
#[derive(Clone, Debug)]
|
||||
enum Expr {
|
||||
/// A style: `color: red`
|
||||
Style(Box<Style>),
|
||||
/// Several styles
|
||||
Styles(Vec<Style>),
|
||||
/// A full selector `a > h1`
|
||||
Selector(Selector),
|
||||
/// A variable declaration `$var: 1px`
|
||||
VariableDecl(String, Box<Spanned<Value>>),
|
||||
/// A mixin declaration `@mixin foo {}`
|
||||
MixinDecl(String, Box<Mixin>),
|
||||
FunctionDecl(String, Box<Function>),
|
||||
/// A multiline comment: `/* foobar */`
|
||||
MultilineComment(String),
|
||||
AtRule(AtRule),
|
||||
}
|
||||
|
||||
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
super_selector: &Selector,
|
||||
content: Option<&[Spanned<Stmt>]>,
|
||||
) -> SassResult<Option<Spanned<Expr>>> {
|
||||
let mut values = Vec::with_capacity(5);
|
||||
let mut span = match toks.peek() {
|
||||
Some(tok) => tok.pos(),
|
||||
None => return Ok(None),
|
||||
};
|
||||
while let Some(tok) = toks.peek() {
|
||||
span = span.merge(tok.pos());
|
||||
match tok.kind {
|
||||
':' => {
|
||||
let tok = toks.next().unwrap();
|
||||
values.push(tok);
|
||||
if devour_whitespace(toks) {
|
||||
let prop = Style::parse_property(
|
||||
&mut values.into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
String::new(),
|
||||
tok.pos,
|
||||
)?;
|
||||
return Ok(Some(Spanned {
|
||||
node: Style::from_tokens(toks, scope, super_selector, prop)?,
|
||||
span,
|
||||
}));
|
||||
}
|
||||
}
|
||||
';' => {
|
||||
let span_before = toks.next().unwrap().pos;
|
||||
devour_whitespace(toks);
|
||||
// special edge case where there was no space between the colon
|
||||
// in a style, e.g. `color:red`. todo: refactor
|
||||
let mut v = values.into_iter().peekmore();
|
||||
devour_whitespace(&mut v);
|
||||
if v.peek().is_none() {
|
||||
devour_whitespace(toks);
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::Style(Box::new(Style {
|
||||
property: String::new(),
|
||||
value: Value::Null.span(span),
|
||||
})),
|
||||
span,
|
||||
}));
|
||||
}
|
||||
let property = Style::parse_property(
|
||||
&mut v,
|
||||
scope,
|
||||
super_selector,
|
||||
String::new(),
|
||||
span_before,
|
||||
)?;
|
||||
let value = Style::parse_value(&mut v, scope, super_selector, span_before)?;
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::Style(Box::new(Style { property, value })),
|
||||
span,
|
||||
}));
|
||||
}
|
||||
'}' => {
|
||||
if values.is_empty() {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
if let Some(Token { kind: ';', .. }) = toks.peek() {
|
||||
toks.next();
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
return Ok(None);
|
||||
} else {
|
||||
// special edge case where there was no space between the colon
|
||||
// and no semicolon following the style
|
||||
// in a style `color:red`. todo: refactor
|
||||
let mut v = values.into_iter().peekmore();
|
||||
let property = Style::parse_property(
|
||||
&mut v,
|
||||
scope,
|
||||
super_selector,
|
||||
String::new(),
|
||||
tok.pos,
|
||||
)?;
|
||||
let value = Style::parse_value(&mut v, scope, super_selector, tok.pos)?;
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::Style(Box::new(Style { property, value })),
|
||||
span,
|
||||
}));
|
||||
}
|
||||
}
|
||||
'{' => {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::Selector(Selector::from_tokens(
|
||||
&mut values.into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
true,
|
||||
)?),
|
||||
span,
|
||||
}));
|
||||
}
|
||||
'$' => {
|
||||
let tok = toks.next().unwrap();
|
||||
|
||||
if toks.peek().ok_or(("Expected identifier.", tok.pos))?.kind == '=' {
|
||||
values.push(tok);
|
||||
values.push(toks.next().unwrap());
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = peek_ident_no_interpolation(toks, false, tok.pos)?;
|
||||
let whitespace = peek_whitespace(toks);
|
||||
|
||||
if toks.peek().ok_or(("expected \":\".", name.span))?.kind == ':' {
|
||||
toks.take(name.node.chars().count() + whitespace + 1)
|
||||
.for_each(drop);
|
||||
devour_whitespace(toks);
|
||||
let VariableDecl {
|
||||
val,
|
||||
default,
|
||||
global,
|
||||
} = eat_variable_value(toks, scope, super_selector, name.span)?;
|
||||
if global {
|
||||
insert_global_var(&name.node, val.clone())?;
|
||||
}
|
||||
let var_exists = scope.var_exists(&name.node);
|
||||
if !(default && var_exists) {
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::VariableDecl(name.node, Box::new(val)),
|
||||
span,
|
||||
}));
|
||||
}
|
||||
if !values.is_empty() {
|
||||
todo!()
|
||||
}
|
||||
} else {
|
||||
values.push(tok);
|
||||
toks.reset_view();
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
let tok = toks.next().unwrap();
|
||||
let peeked = toks.peek().ok_or(("expected more input.", tok.pos()))?;
|
||||
if peeked.kind == '/' {
|
||||
read_until_newline(toks);
|
||||
devour_whitespace(toks);
|
||||
continue;
|
||||
} else if values.is_empty() && peeked.kind == '*' {
|
||||
toks.next();
|
||||
let comment = eat_comment(toks, scope, super_selector)?;
|
||||
devour_whitespace(toks);
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::MultilineComment(comment.node),
|
||||
span: comment.span,
|
||||
}));
|
||||
} else {
|
||||
values.push(tok);
|
||||
}
|
||||
}
|
||||
'@' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
let rule = eat_ident(toks, scope, super_selector, span)?;
|
||||
devour_whitespace(toks);
|
||||
let rule = AtRule::from_tokens(
|
||||
AtRuleKind::try_from(&rule)?,
|
||||
span,
|
||||
toks,
|
||||
scope,
|
||||
super_selector,
|
||||
content,
|
||||
)?;
|
||||
return Ok(Some(Spanned {
|
||||
node: match rule.node {
|
||||
AtRule::Mixin(name, mixin) => Expr::MixinDecl(name, mixin),
|
||||
AtRule::Function(name, func) => Expr::FunctionDecl(name, func),
|
||||
AtRule::Charset => todo!("@charset as expr"),
|
||||
a => Expr::AtRule(a),
|
||||
},
|
||||
span,
|
||||
}));
|
||||
}
|
||||
'#' => {
|
||||
let next = toks.next().unwrap();
|
||||
values.push(next);
|
||||
match toks.peek() {
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
let next = toks.next().unwrap();
|
||||
values.push(next);
|
||||
values.extend(read_until_closing_curly_brace(toks)?);
|
||||
if let Some(tok) = toks.next() {
|
||||
values.push(tok);
|
||||
} else {
|
||||
return Err(("expected \"}\".", next.pos).into());
|
||||
}
|
||||
}
|
||||
Some(..) => {}
|
||||
None => return Err(("expected \"{\".", next.pos).into()),
|
||||
}
|
||||
}
|
||||
'\\' => {
|
||||
let next = toks.next().unwrap();
|
||||
values.push(next);
|
||||
values.push(toks.next().ok_or(("expected \"}\".", next.pos))?);
|
||||
}
|
||||
// todo: this should only apply to special functions
|
||||
// it is causing us to emit nothing on malformed input
|
||||
'(' => {
|
||||
values.push(toks.next().unwrap());
|
||||
values.extend(read_until_closing_paren(toks)?);
|
||||
}
|
||||
_ => values.push(toks.next().unwrap()),
|
||||
};
|
||||
}
|
||||
|
||||
// if `values` is not empty, there was an unexpected toplevel token
|
||||
// that should be part of a selector
|
||||
if let Some(v) = values.pop() {
|
||||
return Err(("expected \"{\".", v.pos).into());
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
/// Write CSS to `buf`, constructed from a string
|
||||
///
|
||||
/// ```
|
||||
/// fn main() -> Result<(), grass::Error> {
|
||||
/// let sass = grass::from_string("a { b { color: &; } }".to_string())?;
|
||||
/// assert_eq!(sass, "a b {\n color: a b;\n}\n");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "profiling", inline(never))]
|
||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||
pub fn from_string(p: String) -> Result<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), p);
|
||||
Css::from_stmts(
|
||||
Parser {
|
||||
toks: &mut Lexer::new(&file)
|
||||
.collect::<Vec<Token>>()
|
||||
.into_iter()
|
||||
.peekmore(),
|
||||
map: &mut map,
|
||||
path: Path::new(""),
|
||||
scopes: &mut NeverEmptyVec::new(Scope::new()),
|
||||
global_scope: &mut Scope::new(),
|
||||
super_selectors: &mut NeverEmptyVec::new(Selector::new()),
|
||||
span_before: file.span.subspan(0, 0),
|
||||
content: None,
|
||||
in_mixin: false,
|
||||
in_function: false,
|
||||
in_control_flow: false,
|
||||
at_root: true,
|
||||
at_root_has_selector: false,
|
||||
}
|
||||
.parse()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?,
|
||||
)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
12
src/main.rs
12
src/main.rs
@ -1,9 +1,11 @@
|
||||
use std::fs::File;
|
||||
use std::io::{stdout, BufWriter, Write};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stdout, BufWriter, Write},
|
||||
};
|
||||
|
||||
use clap::{arg_enum, App, Arg};
|
||||
|
||||
use grass::StyleSheet;
|
||||
use grass::from_path;
|
||||
|
||||
arg_enum! {
|
||||
#[derive(PartialEq, Debug)]
|
||||
@ -165,7 +167,7 @@ fn main() -> std::io::Result<()> {
|
||||
if let Some(path) = matches.value_of("OUTPUT") {
|
||||
let mut buf = BufWriter::new(File::open(path).unwrap_or(File::create(path)?));
|
||||
buf.write_all(
|
||||
StyleSheet::from_path(name)
|
||||
from_path(name)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(1)
|
||||
@ -175,7 +177,7 @@ fn main() -> std::io::Result<()> {
|
||||
} else {
|
||||
let mut stdout = BufWriter::new(stdout());
|
||||
stdout.write_all(
|
||||
StyleSheet::from_path(name)
|
||||
from_path(name)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(1)
|
||||
|
134
src/output.rs
134
src/output.rs
@ -1,17 +1,23 @@
|
||||
//! # Convert from SCSS AST to CSS
|
||||
use std::io::Write;
|
||||
|
||||
use codemap::{CodeMap, Span};
|
||||
use codemap::CodeMap;
|
||||
|
||||
use crate::atrule::AtRule;
|
||||
use crate::error::SassResult;
|
||||
use crate::{RuleSet, Selector, Stmt, Style, StyleSheet};
|
||||
use crate::{error::SassResult, parse::Stmt, selector::Selector, style::Style};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Toplevel {
|
||||
RuleSet(Selector, Vec<BlockEntry>),
|
||||
MultilineComment(String),
|
||||
AtRule(AtRule),
|
||||
UnknownAtRule {
|
||||
name: String,
|
||||
params: String,
|
||||
body: Vec<Stmt>,
|
||||
},
|
||||
Media {
|
||||
params: String,
|
||||
body: Vec<Stmt>,
|
||||
},
|
||||
Newline,
|
||||
Style(Box<Style>),
|
||||
}
|
||||
@ -64,17 +70,17 @@ impl Css {
|
||||
Css { blocks: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn from_stylesheet(s: StyleSheet) -> SassResult<Self> {
|
||||
pub(crate) fn from_stmts(s: Vec<Stmt>) -> SassResult<Self> {
|
||||
Css::new().parse_stylesheet(s)
|
||||
}
|
||||
|
||||
fn parse_stmt(&mut self, stmt: Stmt) -> SassResult<Vec<Toplevel>> {
|
||||
Ok(match stmt {
|
||||
Stmt::RuleSet(RuleSet {
|
||||
Stmt::RuleSet {
|
||||
selector,
|
||||
super_selector,
|
||||
rules,
|
||||
}) => {
|
||||
body,
|
||||
} => {
|
||||
let selector = selector
|
||||
.resolve_parent_selectors(&super_selector, true)
|
||||
.remove_placeholders();
|
||||
@ -82,30 +88,40 @@ impl Css {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let mut vals = vec![Toplevel::new_rule(selector)];
|
||||
for rule in rules {
|
||||
match rule.node {
|
||||
Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule.node)?),
|
||||
for rule in body {
|
||||
match rule {
|
||||
Stmt::RuleSet { .. } => vals.extend(self.parse_stmt(rule)?),
|
||||
Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(*s)?,
|
||||
Stmt::MultilineComment(s) => vals.get_mut(0).unwrap().push_comment(s),
|
||||
Stmt::AtRule(AtRule::AtRoot(stmts)) => stmts
|
||||
Stmt::Comment(s) => vals.get_mut(0).unwrap().push_comment(s),
|
||||
Stmt::Media { params, body, .. } => {
|
||||
vals.push(Toplevel::Media { params, body })
|
||||
}
|
||||
Stmt::UnknownAtRule {
|
||||
params, body, name, ..
|
||||
} => vals.push(Toplevel::UnknownAtRule { params, body, name }),
|
||||
Stmt::Return(..) => unreachable!(),
|
||||
Stmt::AtRoot { body } => body
|
||||
.into_iter()
|
||||
.map(|r| Ok(vals.extend(self.parse_stmt(r.node)?)))
|
||||
.map(|r| Ok(vals.extend(self.parse_stmt(r)?)))
|
||||
.collect::<SassResult<()>>()?,
|
||||
Stmt::AtRule(r) => vals.push(Toplevel::AtRule(r)),
|
||||
};
|
||||
}
|
||||
vals
|
||||
}
|
||||
Stmt::MultilineComment(s) => vec![Toplevel::MultilineComment(s)],
|
||||
Stmt::Comment(s) => vec![Toplevel::MultilineComment(s)],
|
||||
Stmt::Style(s) => vec![Toplevel::Style(s)],
|
||||
Stmt::AtRule(r) => vec![Toplevel::AtRule(r)],
|
||||
Stmt::Media { params, body, .. } => vec![Toplevel::Media { params, body }],
|
||||
Stmt::UnknownAtRule {
|
||||
params, name, body, ..
|
||||
} => vec![Toplevel::UnknownAtRule { params, name, body }],
|
||||
Stmt::Return(..) | Stmt::AtRoot { .. } => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_stylesheet(mut self, s: StyleSheet) -> SassResult<Css> {
|
||||
fn parse_stylesheet(mut self, stmts: Vec<Stmt>) -> SassResult<Css> {
|
||||
let mut is_first = true;
|
||||
for stmt in s.0 {
|
||||
let v = self.parse_stmt(stmt.node)?;
|
||||
for stmt in stmts {
|
||||
let v = self.parse_stmt(stmt)?;
|
||||
// this is how we print newlines between unrelated styles
|
||||
// it could probably be refactored
|
||||
if !v.is_empty() {
|
||||
@ -132,27 +148,6 @@ impl Css {
|
||||
Ok(unsafe { String::from_utf8_unchecked(string) })
|
||||
}
|
||||
|
||||
fn debug(map: &CodeMap, span: Span, message: &str) {
|
||||
let loc = map.look_up_span(span);
|
||||
eprintln!(
|
||||
"{}:{} Debug: {}",
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
fn warn(map: &CodeMap, span: Span, message: &str) {
|
||||
let loc = map.look_up_span(span);
|
||||
eprintln!(
|
||||
"Warning: {}\n {} {}:{} root stylesheet",
|
||||
message,
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
loc.begin.column + 1
|
||||
);
|
||||
}
|
||||
|
||||
fn _inner_pretty_print(
|
||||
self,
|
||||
buf: &mut Vec<u8>,
|
||||
@ -178,39 +173,30 @@ impl Css {
|
||||
has_written = true;
|
||||
writeln!(buf, "{}/*{}*/", padding, s)?;
|
||||
}
|
||||
Toplevel::AtRule(r) => {
|
||||
match r {
|
||||
AtRule::Unknown(u) => {
|
||||
if u.params.is_empty() {
|
||||
write!(buf, "{}@{}", padding, u.name)?;
|
||||
} else {
|
||||
write!(buf, "{}@{} {}", padding, u.name, u.params)?;
|
||||
}
|
||||
|
||||
if u.body.is_empty() {
|
||||
writeln!(buf, ";")?;
|
||||
continue;
|
||||
} else {
|
||||
writeln!(buf, " {{")?;
|
||||
}
|
||||
|
||||
Css::from_stylesheet(StyleSheet::from_stmts(u.body))?
|
||||
._inner_pretty_print(buf, map, nesting + 1)?;
|
||||
writeln!(buf, "{}}}", padding)?;
|
||||
}
|
||||
AtRule::Media(m) => {
|
||||
if m.body.is_empty() {
|
||||
continue;
|
||||
}
|
||||
writeln!(buf, "{}@media {} {{", padding, m.params)?;
|
||||
Css::from_stylesheet(StyleSheet::from_stmts(m.body))?
|
||||
._inner_pretty_print(buf, map, nesting + 1)?;
|
||||
writeln!(buf, "{}}}", padding)?;
|
||||
}
|
||||
AtRule::Debug(e) => Self::debug(map, e.span, &e.node),
|
||||
AtRule::Warn(e) => Self::warn(map, e.span, &e.node),
|
||||
_ => todo!("at-rule other than unknown at toplevel: {:?}", r),
|
||||
Toplevel::UnknownAtRule { params, name, body } => {
|
||||
if params.is_empty() {
|
||||
write!(buf, "{}@{}", padding, name)?;
|
||||
} else {
|
||||
write!(buf, "{}@{} {}", padding, name, params)?;
|
||||
}
|
||||
|
||||
if body.is_empty() {
|
||||
writeln!(buf, ";")?;
|
||||
continue;
|
||||
} else {
|
||||
writeln!(buf, " {{")?;
|
||||
}
|
||||
|
||||
Css::from_stmts(body)?._inner_pretty_print(buf, map, nesting + 1)?;
|
||||
writeln!(buf, "{}}}", padding)?;
|
||||
}
|
||||
Toplevel::Media { params, body } => {
|
||||
if body.is_empty() {
|
||||
continue;
|
||||
}
|
||||
writeln!(buf, "{}@media {} {{", padding, params)?;
|
||||
Css::from_stmts(body)?._inner_pretty_print(buf, map, nesting + 1)?;
|
||||
writeln!(buf, "{}}}", padding)?;
|
||||
}
|
||||
Toplevel::Style(s) => {
|
||||
writeln!(buf, "{}{}", padding, s.to_string()?)?;
|
||||
|
295
src/parse/args.rs
Normal file
295
src/parse/args.rs
Normal file
@ -0,0 +1,295 @@
|
||||
use std::{collections::HashMap, mem};
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use crate::{
|
||||
args::{CallArg, CallArgs, FuncArg, FuncArgs},
|
||||
error::SassResult,
|
||||
utils::{read_until_closing_paren, read_until_closing_quote, read_until_closing_square_brace},
|
||||
value::Value,
|
||||
Token,
|
||||
};
|
||||
|
||||
use super::Parser;
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub(super) fn parse_func_args(&mut self) -> SassResult<FuncArgs> {
|
||||
let mut args: Vec<FuncArg> = Vec::new();
|
||||
let mut close_paren_span: Span = self.toks.peek().unwrap().pos();
|
||||
|
||||
self.whitespace();
|
||||
while let Some(Token { kind, pos }) = self.toks.next() {
|
||||
let name = match kind {
|
||||
'$' => self.parse_identifier_no_interpolation(false)?,
|
||||
')' => {
|
||||
close_paren_span = pos;
|
||||
break;
|
||||
}
|
||||
_ => return Err(("expected \")\".", pos).into()),
|
||||
};
|
||||
let mut default: Vec<Token> = Vec::new();
|
||||
let mut is_variadic = false;
|
||||
self.whitespace();
|
||||
let (kind, span) = match self.toks.next() {
|
||||
Some(Token { kind, pos }) => (kind, pos),
|
||||
None => return Err(("expected \")\".", pos).into()),
|
||||
};
|
||||
match kind {
|
||||
':' => {
|
||||
self.whitespace();
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match &tok.kind {
|
||||
',' => {
|
||||
self.toks.next();
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: Some(default),
|
||||
is_variadic,
|
||||
});
|
||||
break;
|
||||
}
|
||||
')' => {
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: Some(default),
|
||||
is_variadic,
|
||||
});
|
||||
close_paren_span = tok.pos();
|
||||
break;
|
||||
}
|
||||
'(' => {
|
||||
default.push(self.toks.next().unwrap());
|
||||
default.extend(read_until_closing_paren(self.toks)?);
|
||||
}
|
||||
_ => default.push(self.toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
'.' => {
|
||||
let next = self.toks.next().ok_or(("expected \".\".", span))?;
|
||||
if next.kind != '.' {
|
||||
return Err(("expected \".\".", next.pos()).into());
|
||||
}
|
||||
let next = self.toks.next().ok_or(("expected \".\".", next.pos()))?;
|
||||
if next.kind != '.' {
|
||||
return Err(("expected \".\".", next.pos()).into());
|
||||
}
|
||||
self.whitespace();
|
||||
let next = self.toks.next().ok_or(("expected \")\".", next.pos()))?;
|
||||
if next.kind != ')' {
|
||||
return Err(("expected \")\".", next.pos()).into());
|
||||
}
|
||||
|
||||
is_variadic = true;
|
||||
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: Some(default),
|
||||
is_variadic,
|
||||
});
|
||||
break;
|
||||
}
|
||||
')' => {
|
||||
close_paren_span = span;
|
||||
args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: if default.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(default)
|
||||
},
|
||||
is_variadic,
|
||||
});
|
||||
break;
|
||||
}
|
||||
',' => args.push(FuncArg {
|
||||
name: name.node.into(),
|
||||
default: None,
|
||||
is_variadic,
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
self.whitespace();
|
||||
}
|
||||
self.whitespace();
|
||||
match self.toks.next() {
|
||||
Some(v) if v.kind == '{' => {}
|
||||
Some(..) | None => return Err(("expected \"{\".", close_paren_span).into()),
|
||||
};
|
||||
Ok(FuncArgs(args))
|
||||
}
|
||||
|
||||
pub(super) fn parse_call_args(&mut self) -> SassResult<CallArgs> {
|
||||
let mut args: HashMap<CallArg, Vec<Token>> = HashMap::new();
|
||||
self.whitespace_or_comment();
|
||||
let mut name = String::new();
|
||||
let mut val: Vec<Token> = Vec::new();
|
||||
let mut span = self
|
||||
.toks
|
||||
.peek()
|
||||
.ok_or(("expected \")\".", self.span_before))?
|
||||
.pos();
|
||||
loop {
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '$', .. }) => {
|
||||
let Token { pos, .. } = self.toks.next().unwrap();
|
||||
let v = self.parse_identifier_no_interpolation(false)?;
|
||||
let whitespace = self.whitespace_or_comment();
|
||||
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
name = v.node;
|
||||
} else {
|
||||
val.push(Token::new(pos, '$'));
|
||||
let mut current_pos = 0;
|
||||
val.extend(v.chars().map(|x| {
|
||||
let len = x.len_utf8() as u64;
|
||||
let tok = Token::new(v.span.subspan(current_pos, current_pos + len), x);
|
||||
current_pos += len;
|
||||
tok
|
||||
}));
|
||||
if whitespace {
|
||||
val.push(Token::new(pos, ' '));
|
||||
}
|
||||
name.clear();
|
||||
}
|
||||
}
|
||||
Some(Token { kind: ')', .. }) => {
|
||||
self.toks.next();
|
||||
return Ok(CallArgs(args, span));
|
||||
}
|
||||
Some(..) | None => name.clear(),
|
||||
}
|
||||
self.whitespace_or_comment();
|
||||
|
||||
while let Some(tok) = self.toks.next() {
|
||||
match tok.kind {
|
||||
')' => {
|
||||
args.insert(
|
||||
if name.is_empty() {
|
||||
CallArg::Positional(args.len())
|
||||
} else {
|
||||
CallArg::Named(name.into())
|
||||
},
|
||||
val,
|
||||
);
|
||||
span = span.merge(tok.pos());
|
||||
return Ok(CallArgs(args, span));
|
||||
}
|
||||
',' => break,
|
||||
'[' => {
|
||||
val.push(tok);
|
||||
val.extend(read_until_closing_square_brace(self.toks)?);
|
||||
}
|
||||
'(' => {
|
||||
val.push(tok);
|
||||
val.extend(read_until_closing_paren(self.toks)?);
|
||||
}
|
||||
'"' | '\'' => {
|
||||
val.push(tok);
|
||||
val.extend(read_until_closing_quote(self.toks, tok.kind)?);
|
||||
}
|
||||
_ => val.push(tok),
|
||||
}
|
||||
}
|
||||
|
||||
args.insert(
|
||||
if name.is_empty() {
|
||||
CallArg::Positional(args.len())
|
||||
} else {
|
||||
CallArg::Named(name.as_str().into())
|
||||
},
|
||||
mem::take(&mut val),
|
||||
);
|
||||
self.whitespace();
|
||||
|
||||
if self.toks.peek().is_none() {
|
||||
return Ok(CallArgs(args, span));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub fn arg(
|
||||
&mut self,
|
||||
args: &mut CallArgs,
|
||||
position: usize,
|
||||
name: &'static str,
|
||||
) -> SassResult<Value> {
|
||||
Ok(self
|
||||
.parse_value_from_vec(args.get_err(position, name)?)?
|
||||
.node
|
||||
.eval(args.span())?
|
||||
.node)
|
||||
}
|
||||
|
||||
pub fn default_arg(
|
||||
&mut self,
|
||||
args: &mut CallArgs,
|
||||
position: usize,
|
||||
name: &'static str,
|
||||
default: Value,
|
||||
) -> SassResult<Value> {
|
||||
Ok(match args.get(position, name) {
|
||||
Some(toks) => {
|
||||
self.parse_value_from_vec(toks)?
|
||||
.node
|
||||
.eval(args.span())?
|
||||
.node
|
||||
}
|
||||
None => default,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn positional_arg(
|
||||
&mut self,
|
||||
args: &mut CallArgs,
|
||||
position: usize,
|
||||
) -> Option<SassResult<Spanned<Value>>> {
|
||||
Some(self.parse_value_from_vec(args.get_positional(position)?))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn named_arg(
|
||||
&mut self,
|
||||
args: &mut CallArgs,
|
||||
name: &'static str,
|
||||
) -> Option<SassResult<Spanned<Value>>> {
|
||||
Some(self.parse_value_from_vec(args.get_named(name)?))
|
||||
}
|
||||
|
||||
pub fn default_named_arg(
|
||||
&mut self,
|
||||
args: &mut CallArgs,
|
||||
name: &'static str,
|
||||
default: Value,
|
||||
) -> SassResult<Value> {
|
||||
Ok(match args.get_named(name) {
|
||||
Some(toks) => {
|
||||
self.parse_value_from_vec(toks)?
|
||||
.node
|
||||
.eval(args.span())?
|
||||
.node
|
||||
}
|
||||
None => default,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn variadic_args(&mut self, args: CallArgs) -> SassResult<Vec<Spanned<Value>>> {
|
||||
let mut vals = Vec::new();
|
||||
let mut args = match args
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(a, v)| Ok((a.position()?, v)))
|
||||
.collect::<Result<Vec<(usize, Vec<Token>)>, String>>()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err((format!("No argument named ${}.", e), args.1).into()),
|
||||
};
|
||||
args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2));
|
||||
for arg in args {
|
||||
vals.push(self.parse_value_from_vec(arg.1)?);
|
||||
}
|
||||
Ok(vals)
|
||||
}
|
||||
}
|
74
src/parse/common.rs
Normal file
74
src/parse/common.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use std::slice::IterMut;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{value::Value, Token};
|
||||
|
||||
pub(crate) struct NeverEmptyVec<T> {
|
||||
first: T,
|
||||
rest: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> NeverEmptyVec<T> {
|
||||
pub const fn new(first: T) -> Self {
|
||||
Self {
|
||||
first,
|
||||
rest: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last(&self) -> &T {
|
||||
match self.rest.last() {
|
||||
Some(v) => v,
|
||||
None => &self.first,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_mut(&mut self) -> &mut T {
|
||||
match self.rest.last_mut() {
|
||||
Some(v) => v,
|
||||
None => &mut self.first,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) {
|
||||
self.rest.push(value)
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
self.rest.pop()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
|
||||
self.rest.iter_mut()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.rest.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.rest.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A toplevel element beginning with something other than
|
||||
/// `$`, `@`, `/`, whitespace, or a control character is either a
|
||||
/// selector or a style.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum SelectorOrStyle {
|
||||
Selector(String),
|
||||
Style(String, Option<Box<Spanned<Value>>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct Branch {
|
||||
pub cond: Vec<Token>,
|
||||
pub toks: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
pub fn new(cond: Vec<Token>, toks: Vec<Token>) -> Branch {
|
||||
Branch { cond, toks }
|
||||
}
|
||||
}
|
327
src/parse/ident.rs
Normal file
327
src/parse/ident.rs
Normal file
@ -0,0 +1,327 @@
|
||||
use std::{borrow::Borrow, iter::Iterator};
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{
|
||||
common::QuoteKind,
|
||||
error::SassResult,
|
||||
utils::{as_hex, hex_char_for, is_name, is_name_start},
|
||||
value::Value,
|
||||
Token,
|
||||
};
|
||||
|
||||
use super::Parser;
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn ident_body_no_interpolation(&mut self, unit: bool) -> SassResult<Spanned<String>> {
|
||||
let mut text = String::new();
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
self.span_before = self.span_before.merge(tok.pos());
|
||||
if unit && tok.kind == '-' {
|
||||
// Disallow `-` followed by a dot or a digit digit in units.
|
||||
let second = match self.toks.peek_forward(1) {
|
||||
Some(v) => *v,
|
||||
None => break,
|
||||
};
|
||||
|
||||
self.toks.peek_backward(1).unwrap();
|
||||
|
||||
if second.kind == '.' || second.kind.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
|
||||
self.toks.next();
|
||||
text.push('-');
|
||||
} else if is_name(tok.kind) {
|
||||
text.push(self.toks.next().unwrap().kind);
|
||||
} else if tok.kind == '\\' {
|
||||
self.toks.next();
|
||||
text.push_str(&self.escape(false)?);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Spanned {
|
||||
node: text,
|
||||
span: self.span_before,
|
||||
})
|
||||
}
|
||||
|
||||
fn interpolated_ident_body(&mut self, buf: &mut String) -> SassResult<()> {
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\u{80}'..=std::char::MAX => {
|
||||
self.span_before = self.span_before.merge(tok.pos());
|
||||
buf.push(self.toks.next().unwrap().kind);
|
||||
}
|
||||
'\\' => {
|
||||
self.toks.next();
|
||||
buf.push_str(&self.escape(false)?);
|
||||
}
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', .. }) = self.toks.peek_forward(1).cloned() {
|
||||
self.toks.next();
|
||||
self.toks.next();
|
||||
// TODO: if ident, interpolate literally
|
||||
let interpolation = self.parse_interpolation()?;
|
||||
buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
|
||||
} else {
|
||||
self.toks.reset_view();
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn escape(&mut self, identifier_start: bool) -> SassResult<String> {
|
||||
let mut value = 0;
|
||||
let first = match self.toks.peek() {
|
||||
Some(t) => t,
|
||||
None => return Ok(String::new()),
|
||||
};
|
||||
let mut span = first.pos();
|
||||
if first.kind == '\n' {
|
||||
return Err(("Expected escape sequence.", span).into());
|
||||
} else if first.kind.is_ascii_hexdigit() {
|
||||
for _ in 0..6 {
|
||||
let next = match self.toks.peek() {
|
||||
Some(t) => t,
|
||||
None => break,
|
||||
};
|
||||
if !next.kind.is_ascii_hexdigit() {
|
||||
break;
|
||||
}
|
||||
value *= 16;
|
||||
span = span.merge(next.pos());
|
||||
value += as_hex(self.toks.next().unwrap().kind)
|
||||
}
|
||||
if self.toks.peek().is_some() && self.toks.peek().unwrap().kind.is_whitespace() {
|
||||
self.toks.next();
|
||||
}
|
||||
} else {
|
||||
let next = self.toks.next().unwrap();
|
||||
span = span.merge(next.pos());
|
||||
value = next.kind as u32;
|
||||
}
|
||||
|
||||
let c = std::char::from_u32(value).ok_or(("Invalid escape sequence.", span))?;
|
||||
if (identifier_start && is_name_start(c) && !c.is_digit(10))
|
||||
|| (!identifier_start && is_name(c))
|
||||
{
|
||||
Ok(c.to_string())
|
||||
} else if value <= 0x1F || value == 0x7F || (identifier_start && c.is_digit(10)) {
|
||||
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 parse_identifier(&mut self) -> SassResult<Spanned<String>> {
|
||||
let Token { kind, pos } = self
|
||||
.toks
|
||||
.peek()
|
||||
.cloned()
|
||||
.ok_or(("Expected identifier.", self.span_before))?;
|
||||
let mut text = String::new();
|
||||
if kind == '-' {
|
||||
self.toks.next();
|
||||
text.push('-');
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '-', .. }) => {
|
||||
self.toks.next();
|
||||
text.push('-');
|
||||
self.interpolated_ident_body(&mut text)?;
|
||||
return Ok(Spanned {
|
||||
node: text,
|
||||
span: pos,
|
||||
});
|
||||
}
|
||||
Some(..) => {}
|
||||
None => {
|
||||
return Ok(Spanned {
|
||||
node: text,
|
||||
span: self.span_before,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let Token { kind: first, pos } = match self.toks.peek() {
|
||||
Some(v) => *v,
|
||||
None => return Err(("Expected identifier.", self.span_before).into()),
|
||||
};
|
||||
|
||||
if is_name_start(first) {
|
||||
text.push(self.toks.next().unwrap().kind);
|
||||
} else if first == '\\' {
|
||||
self.toks.next();
|
||||
text.push_str(&self.escape(true)?);
|
||||
// TODO: peekmore
|
||||
// (first == '#' && scanner.peekChar(1) == $lbrace)
|
||||
} else if first == '#' {
|
||||
self.toks.next();
|
||||
let Token { kind, pos } = if let Some(tok) = self.toks.peek() {
|
||||
*tok
|
||||
} else {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
};
|
||||
if kind == '{' {
|
||||
self.toks.next();
|
||||
match self.parse_interpolation()?.node {
|
||||
Value::String(ref s, ..) => text.push_str(s),
|
||||
v => text.push_str(v.to_css_string(self.span_before)?.borrow()),
|
||||
}
|
||||
} else {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
} else {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
|
||||
self.interpolated_ident_body(&mut text)?;
|
||||
Ok(Spanned {
|
||||
node: text,
|
||||
span: self.span_before,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_identifier_no_interpolation(
|
||||
&mut self,
|
||||
unit: bool,
|
||||
) -> SassResult<Spanned<String>> {
|
||||
let Token {
|
||||
kind,
|
||||
pos: mut span,
|
||||
} = self
|
||||
.toks
|
||||
.peek()
|
||||
.ok_or(("Expected identifier.", self.span_before))?;
|
||||
let mut text = String::new();
|
||||
if kind == &'-' {
|
||||
self.toks.next();
|
||||
text.push('-');
|
||||
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '-', .. }) => {
|
||||
self.toks.next();
|
||||
text.push('-');
|
||||
text.push_str(&self.ident_body_no_interpolation(unit)?.node);
|
||||
return Ok(Spanned { node: text, span });
|
||||
}
|
||||
Some(..) => {}
|
||||
None => return Ok(Spanned { node: text, span }),
|
||||
}
|
||||
}
|
||||
|
||||
let first = match self.toks.next() {
|
||||
Some(v) => v,
|
||||
None => return Err(("Expected identifier.", span).into()),
|
||||
};
|
||||
|
||||
if is_name_start(first.kind) {
|
||||
text.push(first.kind);
|
||||
} else if first.kind == '\\' {
|
||||
text.push_str(&self.escape(true)?);
|
||||
} else {
|
||||
return Err(("Expected identifier.", first.pos).into());
|
||||
}
|
||||
|
||||
let body = self.ident_body_no_interpolation(unit)?;
|
||||
span = span.merge(body.span);
|
||||
text.push_str(&body.node);
|
||||
Ok(Spanned { node: text, span })
|
||||
}
|
||||
|
||||
pub(crate) fn parse_quoted_string(&mut self, q: char) -> SassResult<Spanned<Value>> {
|
||||
let mut s = String::new();
|
||||
let mut span = self
|
||||
.toks
|
||||
.peek()
|
||||
.ok_or((format!("Expected {}.", q), self.span_before))?
|
||||
.pos();
|
||||
while let Some(tok) = self.toks.next() {
|
||||
span = span.merge(tok.pos());
|
||||
match tok.kind {
|
||||
'"' if q == '"' => break,
|
||||
'\'' if q == '\'' => break,
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = self.toks.peek() {
|
||||
self.span_before = self.span_before.merge(*pos);
|
||||
self.toks.next();
|
||||
let interpolation = self.parse_interpolation()?;
|
||||
match interpolation.node {
|
||||
Value::String(ref v, ..) => s.push_str(v),
|
||||
v => s.push_str(v.to_css_string(interpolation.span)?.borrow()),
|
||||
};
|
||||
continue;
|
||||
} else {
|
||||
s.push('#');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
'\n' => return Err(("Expected \".", tok.pos()).into()),
|
||||
'\\' => {
|
||||
let first = match self.toks.peek() {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
s.push('\u{FFFD}');
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if first.kind == '\n' {
|
||||
self.toks.next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if first.kind.is_ascii_hexdigit() {
|
||||
let mut value = 0;
|
||||
for _ in 0..6 {
|
||||
// todo: or patterns
|
||||
let next = match self.toks.peek() {
|
||||
Some(c) => c,
|
||||
None => break,
|
||||
};
|
||||
if !next.kind.is_ascii_hexdigit() {
|
||||
break;
|
||||
}
|
||||
value = (value << 4) + as_hex(self.toks.next().unwrap().kind);
|
||||
}
|
||||
|
||||
if self.toks.peek().is_some()
|
||||
&& self.toks.peek().unwrap().kind.is_ascii_whitespace()
|
||||
{
|
||||
self.toks.next();
|
||||
}
|
||||
|
||||
if value == 0
|
||||
|| (value >= 0xD800 && value <= 0xDFFF)
|
||||
|| value >= 0x0010_FFFF
|
||||
{
|
||||
s.push('\u{FFFD}');
|
||||
} else {
|
||||
s.push(std::char::from_u32(value).unwrap());
|
||||
}
|
||||
} else {
|
||||
s.push(self.toks.next().unwrap().kind);
|
||||
}
|
||||
}
|
||||
_ => s.push(tok.kind),
|
||||
}
|
||||
}
|
||||
Ok(Spanned {
|
||||
node: Value::String(s, QuoteKind::Quoted),
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
1937
src/parse/mod.rs
Normal file
1937
src/parse/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
1121
src/parse/value.rs
Normal file
1121
src/parse/value.rs
Normal file
File diff suppressed because it is too large
Load Diff
164
src/scope.rs
164
src/scope.rs
@ -1,72 +1,23 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::atrule::{Function, Mixin};
|
||||
use crate::common::Identifier;
|
||||
use crate::error::SassResult;
|
||||
use crate::value::Value;
|
||||
use crate::{
|
||||
atrule::{Function, Mixin},
|
||||
builtin::GLOBAL_FUNCTIONS,
|
||||
common::Identifier,
|
||||
error::SassResult,
|
||||
value::Value,
|
||||
};
|
||||
|
||||
thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new()));
|
||||
|
||||
pub(crate) fn get_global_var<T: Into<Identifier>>(s: Spanned<T>) -> SassResult<Spanned<Value>> {
|
||||
GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(&s.node.into()) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Err(("Undefined variable.", s.span).into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn global_var_exists<T: Into<Identifier>>(v: T) -> bool {
|
||||
GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(&v.into()))
|
||||
}
|
||||
|
||||
pub(crate) fn insert_global_var<T: Into<Identifier>>(
|
||||
s: T,
|
||||
v: Spanned<Value>,
|
||||
) -> SassResult<Option<Spanned<Value>>> {
|
||||
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_var(s.into(), v))
|
||||
}
|
||||
|
||||
pub(crate) fn get_global_fn<T: Into<Identifier>>(s: Spanned<T>) -> SassResult<Function> {
|
||||
GLOBAL_SCOPE.with(
|
||||
|scope| match scope.borrow().functions().get(&s.node.into()) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Err(("Undefined function.", s.span).into()),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn global_fn_exists<T: Into<Identifier>>(v: T) -> bool {
|
||||
GLOBAL_SCOPE.with(|scope| scope.borrow().functions().contains_key(&v.into()))
|
||||
}
|
||||
|
||||
pub(crate) fn insert_global_fn<T: Into<Identifier>>(s: T, v: Function) -> Option<Function> {
|
||||
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_fn(s.into(), v))
|
||||
}
|
||||
|
||||
pub(crate) fn get_global_mixin<T: Into<Identifier>>(s: Spanned<T>) -> SassResult<Mixin> {
|
||||
GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(&s.node.into()) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Err(("Undefined mixin.", s.span).into()),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn global_mixin_exists<T: Into<Identifier>>(v: T) -> bool {
|
||||
GLOBAL_SCOPE.with(|scope| scope.borrow().mixins().contains_key(&v.into()))
|
||||
}
|
||||
|
||||
pub(crate) fn insert_global_mixin<T: Into<Identifier>>(s: T, v: Mixin) -> Option<Mixin> {
|
||||
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_mixin(s.into(), v))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct Scope {
|
||||
vars: HashMap<Identifier, Spanned<Value>>,
|
||||
mixins: HashMap<Identifier, Mixin>,
|
||||
functions: HashMap<Identifier, Function>,
|
||||
}
|
||||
|
||||
// todo: separate struct for global scope?
|
||||
impl Scope {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
@ -77,23 +28,22 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn vars(&self) -> &HashMap<Identifier, Spanned<Value>> {
|
||||
&self.vars
|
||||
fn get_var_no_global(&self, name: &Spanned<Identifier>) -> SassResult<Spanned<Value>> {
|
||||
match self.vars.get(&name.node) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Err(("Undefined variable.", name.span).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn functions(&self) -> &HashMap<Identifier, Function> {
|
||||
&self.functions
|
||||
}
|
||||
|
||||
pub const fn mixins(&self) -> &HashMap<Identifier, Mixin> {
|
||||
&self.mixins
|
||||
}
|
||||
|
||||
pub fn get_var<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Spanned<Value>> {
|
||||
pub fn get_var<T: Into<Identifier>>(
|
||||
&self,
|
||||
name: Spanned<T>,
|
||||
global_scope: &Scope,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
let name = name.map_node(Into::into);
|
||||
match self.vars.get(&name.node) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => get_global_var(name),
|
||||
None => global_scope.get_var_no_global(&name),
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,16 +56,31 @@ impl Scope {
|
||||
Ok(self.vars.insert(s.into(), node.eval(span)?))
|
||||
}
|
||||
|
||||
pub fn var_exists<T: Into<Identifier>>(&self, v: T) -> bool {
|
||||
let name = v.into();
|
||||
self.vars.contains_key(&name) || global_var_exists(name)
|
||||
pub fn var_exists_no_global(&self, name: &Identifier) -> bool {
|
||||
self.vars.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn get_mixin<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Mixin> {
|
||||
pub fn var_exists<'a, T: Into<&'a Identifier>>(&self, v: T, global_scope: &Scope) -> bool {
|
||||
let name = v.into();
|
||||
self.vars.contains_key(name) || global_scope.var_exists_no_global(name)
|
||||
}
|
||||
|
||||
fn get_mixin_no_global(&self, name: &Spanned<Identifier>) -> SassResult<Mixin> {
|
||||
match self.mixins.get(&name.node) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Err(("Undefined mixin.", name.span).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mixin<T: Into<Identifier>>(
|
||||
&self,
|
||||
name: Spanned<T>,
|
||||
global_scope: &Scope,
|
||||
) -> SassResult<Mixin> {
|
||||
let name = name.map_node(Into::into);
|
||||
match self.mixins.get(&name.node) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => get_global_mixin(name),
|
||||
None => global_scope.get_mixin_no_global(&name),
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,16 +88,31 @@ impl Scope {
|
||||
self.mixins.insert(s.into(), v)
|
||||
}
|
||||
|
||||
pub fn mixin_exists<T: Into<Identifier>>(&self, v: T) -> bool {
|
||||
let name = v.into();
|
||||
self.mixins.contains_key(&name) || global_mixin_exists(name)
|
||||
fn mixin_exists_no_global(&self, name: &Identifier) -> bool {
|
||||
self.mixins.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn get_fn<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Function> {
|
||||
pub fn mixin_exists<T: Into<Identifier>>(&self, v: T, global_scope: &Scope) -> bool {
|
||||
let name = v.into();
|
||||
self.mixins.contains_key(&name) || global_scope.mixin_exists_no_global(&name)
|
||||
}
|
||||
|
||||
fn get_fn_no_global(&self, name: &Spanned<Identifier>) -> SassResult<Function> {
|
||||
match self.functions.get(&name.node) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Err(("Undefined function.", name.span).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fn<T: Into<Identifier>>(
|
||||
&self,
|
||||
name: Spanned<T>,
|
||||
global_scope: &Scope,
|
||||
) -> SassResult<Function> {
|
||||
let name = name.map_node(Into::into);
|
||||
match self.functions.get(&name.node) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => get_global_fn(name),
|
||||
None => global_scope.get_fn_no_global(&name),
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,11 +120,29 @@ impl Scope {
|
||||
self.functions.insert(s.into(), v)
|
||||
}
|
||||
|
||||
pub fn fn_exists<T: Into<Identifier>>(&self, v: T) -> bool {
|
||||
let name = v.into();
|
||||
self.functions.contains_key(&name) || global_fn_exists(name)
|
||||
fn fn_exists_no_global(&self, name: &Identifier) -> bool {
|
||||
self.functions.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn fn_exists<T: Into<Identifier>>(&self, v: T, global_scope: &Scope) -> bool {
|
||||
let name = v.into();
|
||||
self.functions.contains_key(&name)
|
||||
|| global_scope.fn_exists_no_global(&name)
|
||||
|| GLOBAL_FUNCTIONS.contains_key(name.clone().into_inner().as_str())
|
||||
// special functions not in the `GLOBAL_FUNCTIONS` map
|
||||
|| matches!(
|
||||
name.into_inner().as_str(),
|
||||
"function-exists"
|
||||
| "content-exists"
|
||||
| "mixin-exists"
|
||||
| "variable-exists"
|
||||
| "global-variable-exists"
|
||||
| "get-function"
|
||||
| "call"
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn extend(&mut self, other: Scope) {
|
||||
self.vars.extend(other.vars);
|
||||
self.mixins.extend(other.mixins);
|
||||
|
@ -1,16 +1,10 @@
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use codemap::Span;
|
||||
|
||||
use super::{Namespace, QualifiedName, Selector};
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string};
|
||||
use crate::value::Value;
|
||||
use crate::Token;
|
||||
use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value};
|
||||
|
||||
use super::{Namespace, QualifiedName};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub(crate) struct Attribute {
|
||||
@ -21,31 +15,26 @@ pub(crate) struct Attribute {
|
||||
span: Span,
|
||||
}
|
||||
|
||||
fn attribute_name<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
start: Span,
|
||||
) -> SassResult<QualifiedName> {
|
||||
let next = toks.peek().ok_or(("Expected identifier.", start))?;
|
||||
fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult<QualifiedName> {
|
||||
let next = parser.toks.peek().ok_or(("Expected identifier.", start))?;
|
||||
if next.kind == '*' {
|
||||
let pos = next.pos;
|
||||
toks.next();
|
||||
if toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' {
|
||||
parser.toks.next();
|
||||
if parser.toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' {
|
||||
return Err(("expected \"|\".", pos).into());
|
||||
}
|
||||
|
||||
let span_before = toks.next().unwrap().pos();
|
||||
parser.span_before = parser.toks.next().unwrap().pos();
|
||||
|
||||
let ident = eat_ident(toks, scope, super_selector, span_before)?.node;
|
||||
let ident = parser.parse_identifier()?.node;
|
||||
return Ok(QualifiedName {
|
||||
ident,
|
||||
namespace: Namespace::Asterisk,
|
||||
});
|
||||
}
|
||||
let span_before = next.pos;
|
||||
let name_or_namespace = eat_ident(toks, scope, super_selector, span_before)?;
|
||||
match toks.peek() {
|
||||
parser.span_before = next.pos;
|
||||
let name_or_namespace = parser.parse_identifier()?;
|
||||
match parser.toks.peek() {
|
||||
Some(v) if v.kind != '|' => {
|
||||
return Ok(QualifiedName {
|
||||
ident: name_or_namespace.node,
|
||||
@ -55,32 +44,30 @@ fn attribute_name<I: Iterator<Item = Token>>(
|
||||
Some(..) => {}
|
||||
None => return Err(("expected more input.", name_or_namespace.span).into()),
|
||||
}
|
||||
match toks.peek_forward(1) {
|
||||
match parser.toks.peek_forward(1) {
|
||||
Some(v) if v.kind == '=' => {
|
||||
toks.reset_view();
|
||||
parser.toks.reset_view();
|
||||
return Ok(QualifiedName {
|
||||
ident: name_or_namespace.node,
|
||||
namespace: Namespace::None,
|
||||
});
|
||||
}
|
||||
Some(..) => {
|
||||
toks.reset_view();
|
||||
parser.toks.reset_view();
|
||||
}
|
||||
None => return Err(("expected more input.", name_or_namespace.span).into()),
|
||||
}
|
||||
let span_before = toks.next().unwrap().pos();
|
||||
let ident = eat_ident(toks, scope, super_selector, span_before)?.node;
|
||||
parser.span_before = parser.toks.next().unwrap().pos();
|
||||
let ident = parser.parse_identifier()?.node;
|
||||
Ok(QualifiedName {
|
||||
ident,
|
||||
namespace: Namespace::Other(name_or_namespace.node),
|
||||
})
|
||||
}
|
||||
|
||||
fn attribute_operator<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
start: Span,
|
||||
) -> SassResult<AttributeOp> {
|
||||
let op = match toks.next().ok_or(("Expected \"]\".", start))?.kind {
|
||||
fn attribute_operator(parser: &mut Parser<'_>) -> SassResult<AttributeOp> {
|
||||
let start = parser.span_before;
|
||||
let op = match parser.toks.next().ok_or(("Expected \"]\".", start))?.kind {
|
||||
'=' => return Ok(AttributeOp::Equals),
|
||||
'~' => AttributeOp::Include,
|
||||
'|' => AttributeOp::Dash,
|
||||
@ -89,23 +76,25 @@ fn attribute_operator<I: Iterator<Item = Token>>(
|
||||
'*' => AttributeOp::Contains,
|
||||
_ => return Err(("Expected \"]\".", start).into()),
|
||||
};
|
||||
if toks.next().ok_or(("expected \"=\".", start))?.kind != '=' {
|
||||
if parser.toks.next().ok_or(("expected \"=\".", start))?.kind != '=' {
|
||||
return Err(("expected \"=\".", start).into());
|
||||
}
|
||||
Ok(op)
|
||||
}
|
||||
impl Attribute {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
start: Span,
|
||||
) -> SassResult<Attribute> {
|
||||
devour_whitespace(toks);
|
||||
let attr = attribute_name(toks, scope, super_selector, start)?;
|
||||
devour_whitespace(toks);
|
||||
if toks.peek().ok_or(("expected more input.", start))?.kind == ']' {
|
||||
toks.next();
|
||||
pub fn from_tokens(parser: &mut Parser<'_>) -> SassResult<Attribute> {
|
||||
let start = parser.span_before;
|
||||
parser.whitespace();
|
||||
let attr = attribute_name(parser, start)?;
|
||||
parser.whitespace();
|
||||
if parser
|
||||
.toks
|
||||
.peek()
|
||||
.ok_or(("expected more input.", start))?
|
||||
.kind
|
||||
== ']'
|
||||
{
|
||||
parser.toks.next();
|
||||
return Ok(Attribute {
|
||||
attr,
|
||||
value: String::new(),
|
||||
@ -115,24 +104,25 @@ impl Attribute {
|
||||
});
|
||||
}
|
||||
|
||||
let op = attribute_operator(toks, start)?;
|
||||
devour_whitespace(toks);
|
||||
parser.span_before = start;
|
||||
let op = attribute_operator(parser)?;
|
||||
parser.whitespace();
|
||||
|
||||
let peek = toks.peek().ok_or(("expected more input.", start))?;
|
||||
let span_before = peek.pos;
|
||||
let peek = parser.toks.peek().ok_or(("expected more input.", start))?;
|
||||
parser.span_before = peek.pos;
|
||||
let value = match peek.kind {
|
||||
q @ '\'' | q @ '"' => {
|
||||
toks.next();
|
||||
match parse_quoted_string(toks, scope, q, super_selector, span_before)?.node {
|
||||
parser.toks.next();
|
||||
match parser.parse_quoted_string(q)?.node {
|
||||
Value::String(s, ..) => s,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => eat_ident(toks, scope, super_selector, span_before)?.node,
|
||||
_ => parser.parse_identifier()?.node,
|
||||
};
|
||||
devour_whitespace(toks);
|
||||
parser.whitespace();
|
||||
|
||||
let peek = toks.peek().ok_or(("expected more input.", start))?;
|
||||
let peek = parser.toks.peek().ok_or(("expected more input.", start))?;
|
||||
|
||||
let modifier = match peek.kind {
|
||||
c if c.is_alphabetic() => Some(c),
|
||||
@ -142,15 +132,15 @@ impl Attribute {
|
||||
let pos = peek.pos();
|
||||
|
||||
if modifier.is_some() {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
parser.toks.next();
|
||||
parser.whitespace();
|
||||
}
|
||||
|
||||
if toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' {
|
||||
if parser.toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' {
|
||||
return Err(("expected \"]\".", pos).into());
|
||||
}
|
||||
|
||||
toks.next();
|
||||
parser.toks.next();
|
||||
|
||||
Ok(Attribute {
|
||||
op,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt;
|
||||
|
||||
/// The selector namespace.
|
||||
///
|
||||
@ -14,7 +14,7 @@ pub(crate) enum Namespace {
|
||||
None,
|
||||
}
|
||||
|
||||
impl Display for Namespace {
|
||||
impl fmt::Display for Namespace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "|"),
|
||||
@ -31,7 +31,7 @@ pub(crate) struct QualifiedName {
|
||||
pub namespace: Namespace,
|
||||
}
|
||||
|
||||
impl Display for QualifiedName {
|
||||
impl fmt::Display for QualifiedName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.namespace)?;
|
||||
f.write_str(&self.ident)
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity};
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity};
|
||||
|
||||
/// A complex selector.
|
||||
///
|
||||
/// A complex selector is composed of `CompoundSelector`s separated by
|
||||
|
@ -1,13 +1,15 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
fmt::{self, Write},
|
||||
mem,
|
||||
};
|
||||
|
||||
use super::{unify_complex, ComplexSelector, ComplexSelectorComponent};
|
||||
|
||||
use crate::common::{Brackets, ListSeparator, QuoteKind};
|
||||
use crate::value::Value;
|
||||
use crate::{
|
||||
common::{Brackets, ListSeparator, QuoteKind},
|
||||
value::Value,
|
||||
};
|
||||
|
||||
/// A selector list.
|
||||
///
|
||||
|
@ -1,12 +1,6 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::fmt;
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::utils::{devour_whitespace, eat_comment, parse_interpolation, read_until_newline};
|
||||
use crate::value::Value;
|
||||
use crate::Token;
|
||||
|
||||
pub(crate) use attribute::Attribute;
|
||||
pub(crate) use common::*;
|
||||
@ -29,87 +23,13 @@ mod simple;
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct Selector(pub SelectorList);
|
||||
|
||||
impl Display for Selector {
|
||||
impl fmt::Display for Selector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
allows_parent: bool,
|
||||
) -> SassResult<Selector> {
|
||||
let mut string = String::new();
|
||||
|
||||
let mut span = if let Some(tok) = toks.peek() {
|
||||
tok.pos()
|
||||
} else {
|
||||
return Ok(Selector::new());
|
||||
};
|
||||
|
||||
while let Some(tok) = toks.next() {
|
||||
span = span.merge(tok.pos());
|
||||
match tok.kind {
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
|
||||
toks.next();
|
||||
string.push_str(
|
||||
&parse_interpolation(toks, scope, super_selector, pos)?
|
||||
.to_css_string(span)?,
|
||||
);
|
||||
} else {
|
||||
string.push('#');
|
||||
}
|
||||
}
|
||||
',' => {
|
||||
while let Some(c) = string.pop() {
|
||||
if c == ' ' || c == ',' {
|
||||
continue;
|
||||
}
|
||||
string.push(c);
|
||||
string.push(',');
|
||||
break;
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
if toks.peek().is_none() {
|
||||
return Err(("Expected selector.", tok.pos()).into());
|
||||
} else if '*' == toks.peek().unwrap().kind {
|
||||
toks.next();
|
||||
eat_comment(toks, &Scope::new(), &Selector::new())?;
|
||||
} else if '/' == toks.peek().unwrap().kind {
|
||||
read_until_newline(toks);
|
||||
devour_whitespace(toks);
|
||||
} else {
|
||||
return Err(("Expected selector.", tok.pos()).into());
|
||||
}
|
||||
string.push(' ');
|
||||
}
|
||||
c => string.push(c),
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(c) = string.pop() {
|
||||
if c == ' ' || c == ',' || c == '\t' {
|
||||
continue;
|
||||
}
|
||||
string.push(c);
|
||||
break;
|
||||
}
|
||||
|
||||
let sel_toks: Vec<Token> = string.chars().map(|x| Token::new(span, x)).collect();
|
||||
|
||||
let mut iter = sel_toks.into_iter().peekmore();
|
||||
|
||||
Ok(Selector(
|
||||
SelectorParser::new(&mut iter, scope, super_selector, allows_parent, true, span)
|
||||
.parse()?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Small wrapper around `SelectorList`'s method that turns an empty parent selector
|
||||
/// into `None`. This is a hack and in the future should be replaced.
|
||||
// todo: take Option<Self> for parent
|
||||
|
@ -1,19 +1,16 @@
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use codemap::Span;
|
||||
|
||||
use crate::{
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
utils::{devour_whitespace, is_name, is_name_start, read_until_closing_paren},
|
||||
Token,
|
||||
};
|
||||
|
||||
use super::{
|
||||
Attribute, Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace,
|
||||
Pseudo, QualifiedName, SelectorList, SimpleSelector,
|
||||
};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_ident, eat_ident_no_interpolation, is_name, is_name_start,
|
||||
read_until_closing_paren,
|
||||
};
|
||||
use crate::Token;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum DevouredWhitespace {
|
||||
@ -51,35 +48,27 @@ const SELECTOR_PSEUDO_CLASSES: [&str; 7] = [
|
||||
/// Pseudo-element selectors that take unadorned selectors as arguments.
|
||||
const SELECTOR_PSEUDO_ELEMENTS: [&str; 1] = ["slotted"];
|
||||
|
||||
pub(crate) struct SelectorParser<'a, I: Iterator<Item = Token>> {
|
||||
pub(crate) struct SelectorParser<'a, 'b> {
|
||||
/// Whether this parser allows the parent selector `&`.
|
||||
allows_parent: bool,
|
||||
|
||||
/// Whether this parser allows placeholder selectors beginning with `%`.
|
||||
allows_placeholder: bool,
|
||||
|
||||
toks: &'a mut PeekMoreIterator<I>,
|
||||
|
||||
scope: &'a Scope,
|
||||
|
||||
super_selector: &'a Selector,
|
||||
parser: &'a mut Parser<'b>,
|
||||
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
impl<'a, 'b> SelectorParser<'a, 'b> {
|
||||
pub fn new(
|
||||
toks: &'a mut PeekMoreIterator<I>,
|
||||
scope: &'a Scope,
|
||||
super_selector: &'a Selector,
|
||||
parser: &'a mut Parser<'b>,
|
||||
allows_parent: bool,
|
||||
allows_placeholder: bool,
|
||||
span: Span,
|
||||
) -> Self {
|
||||
Self {
|
||||
toks,
|
||||
scope,
|
||||
super_selector,
|
||||
parser,
|
||||
allows_parent,
|
||||
allows_placeholder,
|
||||
span,
|
||||
@ -88,7 +77,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
|
||||
pub fn parse(mut self) -> SassResult<SelectorList> {
|
||||
let tmp = self.parse_selector_list()?;
|
||||
if self.toks.peek().is_some() {
|
||||
if self.parser.toks.peek().is_some() {
|
||||
return Err(("expected selector.", self.span).into());
|
||||
}
|
||||
Ok(tmp)
|
||||
@ -97,14 +86,14 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
fn parse_selector_list(&mut self) -> SassResult<SelectorList> {
|
||||
let mut components = vec![self.parse_complex_selector(false)?];
|
||||
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
|
||||
let mut line_break = false;
|
||||
|
||||
while let Some(Token { kind: ',', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
while let Some(Token { kind: ',', .. }) = self.parser.toks.peek() {
|
||||
self.parser.toks.next();
|
||||
line_break = self.eat_whitespace() == DevouredWhitespace::Newline || line_break;
|
||||
match self.toks.peek() {
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: ',', .. }) => continue,
|
||||
Some(..) => {}
|
||||
None => break,
|
||||
@ -119,13 +108,13 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
|
||||
fn eat_whitespace(&mut self) -> DevouredWhitespace {
|
||||
let mut whitespace_devoured = DevouredWhitespace::None;
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
while let Some(tok) = self.parser.toks.peek() {
|
||||
match tok.kind {
|
||||
' ' | '\t' => whitespace_devoured.found_whitespace(),
|
||||
'\n' => whitespace_devoured.found_newline(),
|
||||
_ => break,
|
||||
}
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
}
|
||||
|
||||
whitespace_devoured
|
||||
@ -140,22 +129,22 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
|
||||
// todo: or patterns
|
||||
loop {
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
|
||||
// todo: can we do while let Some(..) = self.toks.peek() ?
|
||||
match self.toks.peek() {
|
||||
// todo: can we do while let Some(..) = self.parser.toks.peek() ?
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: '+', .. }) => {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
components.push(ComplexSelectorComponent::Combinator(
|
||||
Combinator::NextSibling,
|
||||
));
|
||||
}
|
||||
Some(Token { kind: '>', .. }) => {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
components.push(ComplexSelectorComponent::Combinator(Combinator::Child))
|
||||
}
|
||||
Some(Token { kind: '~', .. }) => {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
components.push(ComplexSelectorComponent::Combinator(
|
||||
Combinator::FollowingSibling,
|
||||
));
|
||||
@ -172,7 +161,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
components.push(ComplexSelectorComponent::Compound(
|
||||
self.parse_compound_selector()?,
|
||||
));
|
||||
if let Some(Token { kind: '&', .. }) = self.toks.peek() {
|
||||
if let Some(Token { kind: '&', .. }) = self.parser.toks.peek() {
|
||||
return Err(("\"&\" may only used at the beginning of a compound selector.", self.span).into());
|
||||
}
|
||||
}
|
||||
@ -183,7 +172,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
components.push(ComplexSelectorComponent::Compound(
|
||||
self.parse_compound_selector()?,
|
||||
));
|
||||
if let Some(Token { kind: '&', .. }) = self.toks.peek() {
|
||||
if let Some(Token { kind: '&', .. }) = self.parser.toks.peek() {
|
||||
return Err(("\"&\" may only used at the beginning of a compound selector.", self.span).into());
|
||||
}
|
||||
}
|
||||
@ -204,7 +193,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
fn parse_compound_selector(&mut self) -> SassResult<CompoundSelector> {
|
||||
let mut components = vec![self.parse_simple_selector(true)?];
|
||||
|
||||
while let Some(Token { kind, .. }) = self.toks.peek() {
|
||||
while let Some(Token { kind, .. }) = self.parser.toks.peek() {
|
||||
if !is_simple_selector_start(*kind) {
|
||||
break;
|
||||
}
|
||||
@ -225,31 +214,31 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
///
|
||||
/// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier
|
||||
fn looking_at_identifier(&mut self) -> bool {
|
||||
match self.toks.peek() {
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind, .. }) if is_name_start(*kind) || kind == &'\\' => return true,
|
||||
Some(Token { kind: '-', .. }) => {}
|
||||
Some(..) | None => return false,
|
||||
}
|
||||
|
||||
match self.toks.peek_forward(1) {
|
||||
match self.parser.toks.peek_forward(1) {
|
||||
Some(Token { kind, .. }) if is_name_start(*kind) || kind == &'-' || kind == &'\\' => {
|
||||
self.toks.reset_view();
|
||||
self.parser.toks.reset_view();
|
||||
true
|
||||
}
|
||||
Some(..) | None => {
|
||||
self.toks.reset_view();
|
||||
self.parser.toks.reset_view();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn looking_at_identifier_body(&mut self) -> bool {
|
||||
matches!(self.toks.peek(), Some(t) if is_name(t.kind) || t.kind == '\\')
|
||||
matches!(self.parser.toks.peek(), Some(t) if is_name(t.kind) || t.kind == '\\')
|
||||
}
|
||||
|
||||
/// Consumes a simple selector.
|
||||
fn parse_simple_selector(&mut self, allow_parent: bool) -> SassResult<SimpleSelector> {
|
||||
match self.toks.peek() {
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: '[', .. }) => self.parse_attribute_selector(),
|
||||
Some(Token { kind: '.', .. }) => self.parse_class_selector(),
|
||||
Some(Token { kind: '#', .. }) => self.parse_id_selector(),
|
||||
@ -271,43 +260,36 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
|
||||
fn parse_attribute_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
Ok(SimpleSelector::Attribute(Attribute::from_tokens(
|
||||
self.toks,
|
||||
self.scope,
|
||||
self.super_selector,
|
||||
self.span,
|
||||
self.parser,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn parse_class_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.next();
|
||||
Ok(SimpleSelector::Class(
|
||||
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node,
|
||||
))
|
||||
self.parser.toks.next();
|
||||
Ok(SimpleSelector::Class(self.parser.parse_identifier()?.node))
|
||||
}
|
||||
|
||||
fn parse_id_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.next();
|
||||
Ok(SimpleSelector::Id(
|
||||
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node,
|
||||
))
|
||||
self.parser.toks.next();
|
||||
Ok(SimpleSelector::Id(self.parser.parse_identifier()?.node))
|
||||
}
|
||||
|
||||
fn parse_pseudo_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.next();
|
||||
let element = match self.toks.peek() {
|
||||
self.parser.toks.next();
|
||||
let element = match self.parser.toks.peek() {
|
||||
Some(Token { kind: ':', .. }) => {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let name = eat_ident(self.toks, self.scope, self.super_selector, self.span)?;
|
||||
let name = self.parser.parse_identifier()?;
|
||||
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '(', .. }) => self.toks.next(),
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: '(', .. }) => self.parser.toks.next(),
|
||||
_ => {
|
||||
return Ok(SimpleSelector::Pseudo(Pseudo {
|
||||
// todo: we can store the reference to this
|
||||
@ -321,7 +303,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
};
|
||||
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
|
||||
let unvendored = unvendor(&name);
|
||||
|
||||
@ -332,25 +314,25 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
// todo: lowercase?
|
||||
if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) {
|
||||
selector = Some(self.parse_selector_list()?);
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
self.expect_closing_paren()?;
|
||||
} else {
|
||||
argument = Some(self.declaration_value()?);
|
||||
}
|
||||
} else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) {
|
||||
selector = Some(self.parse_selector_list()?);
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
self.expect_closing_paren()?;
|
||||
} else if unvendored == "nth-child" || unvendored == "nth-last-child" {
|
||||
let mut this_arg = self.parse_a_n_plus_b()?;
|
||||
let found_whitespace = devour_whitespace(self.toks);
|
||||
let found_whitespace = devour_whitespace(self.parser.toks);
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match (found_whitespace, self.toks.peek()) {
|
||||
match (found_whitespace, self.parser.toks.peek()) {
|
||||
(_, Some(Token { kind: ')', .. })) => {}
|
||||
(true, _) => {
|
||||
self.expect_identifier("of")?;
|
||||
this_arg.push_str(" of");
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
selector = Some(self.parse_selector_list()?);
|
||||
}
|
||||
_ => {}
|
||||
@ -373,9 +355,9 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
|
||||
fn parse_parent_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
let suffix = if self.looking_at_identifier_body() {
|
||||
Some(eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node)
|
||||
Some(self.parser.parse_identifier()?.node)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -383,9 +365,9 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
|
||||
fn parse_placeholder_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
Ok(SimpleSelector::Placeholder(
|
||||
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node,
|
||||
self.parser.parse_identifier()?.node,
|
||||
))
|
||||
}
|
||||
|
||||
@ -393,19 +375,20 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
///
|
||||
/// These are combined because either one could start with `*`.
|
||||
fn parse_type_or_universal_selector(&mut self) -> SassResult<SimpleSelector> {
|
||||
self.toks.peek();
|
||||
self.parser.toks.peek();
|
||||
|
||||
match self.toks.peek().cloned() {
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: '*', pos }) => {
|
||||
self.toks.next();
|
||||
if let Some(Token { kind: '|', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
if let Some(Token { kind: '*', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
self.parser.span_before = self.parser.span_before.merge(*pos);
|
||||
self.parser.toks.next();
|
||||
if let Some(Token { kind: '|', .. }) = self.parser.toks.peek() {
|
||||
self.parser.toks.next();
|
||||
if let Some(Token { kind: '*', .. }) = self.parser.toks.peek() {
|
||||
self.parser.toks.next();
|
||||
return Ok(SimpleSelector::Universal(Namespace::Asterisk));
|
||||
} else {
|
||||
return Ok(SimpleSelector::Type(QualifiedName {
|
||||
ident: eat_ident(self.toks, self.scope, self.super_selector, pos)?.node,
|
||||
ident: self.parser.parse_identifier()?.node,
|
||||
namespace: Namespace::Asterisk,
|
||||
}));
|
||||
}
|
||||
@ -414,15 +397,16 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
}
|
||||
Some(Token { kind: '|', pos }) => {
|
||||
self.toks.next();
|
||||
match self.toks.peek() {
|
||||
self.parser.span_before = self.parser.span_before.merge(*pos);
|
||||
self.parser.toks.next();
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: '*', .. }) => {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
return Ok(SimpleSelector::Universal(Namespace::Empty));
|
||||
}
|
||||
_ => {
|
||||
return Ok(SimpleSelector::Type(QualifiedName {
|
||||
ident: eat_ident(self.toks, self.scope, self.super_selector, pos)?.node,
|
||||
ident: self.parser.parse_identifier()?.node,
|
||||
namespace: Namespace::Empty,
|
||||
}));
|
||||
}
|
||||
@ -431,19 +415,17 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let name_or_namespace =
|
||||
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node;
|
||||
let name_or_namespace = self.parser.parse_identifier()?.node;
|
||||
|
||||
Ok(match self.toks.peek() {
|
||||
Ok(match self.parser.toks.peek() {
|
||||
Some(Token { kind: '|', .. }) => {
|
||||
self.toks.next();
|
||||
if let Some(Token { kind: '*', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
if let Some(Token { kind: '*', .. }) = self.parser.toks.peek() {
|
||||
self.parser.toks.next();
|
||||
SimpleSelector::Universal(Namespace::Other(name_or_namespace))
|
||||
} else {
|
||||
SimpleSelector::Type(QualifiedName {
|
||||
ident: eat_ident(self.toks, self.scope, self.super_selector, self.span)?
|
||||
.node,
|
||||
ident: self.parser.parse_identifier()?.node,
|
||||
namespace: Namespace::Other(name_or_namespace),
|
||||
})
|
||||
}
|
||||
@ -461,7 +443,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
fn parse_a_n_plus_b(&mut self) -> SassResult<String> {
|
||||
let mut buf = String::new();
|
||||
|
||||
match self.toks.peek() {
|
||||
match self.parser.toks.peek() {
|
||||
Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => {
|
||||
self.expect_identifier("even")?;
|
||||
return Ok("even".to_string());
|
||||
@ -472,31 +454,31 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) => {
|
||||
buf.push(t.kind);
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.toks.peek() {
|
||||
match self.parser.toks.peek() {
|
||||
Some(t) if t.kind.is_ascii_digit() => {
|
||||
while let Some(t) = self.toks.peek() {
|
||||
while let Some(t) = self.parser.toks.peek() {
|
||||
if !t.kind.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
buf.push(t.kind);
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
}
|
||||
devour_whitespace(self.toks);
|
||||
if let Some(t) = self.toks.peek() {
|
||||
devour_whitespace(self.parser.toks);
|
||||
if let Some(t) = self.parser.toks.peek() {
|
||||
if t.kind != 'n' && t.kind != 'N' {
|
||||
return Ok(buf);
|
||||
}
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
}
|
||||
}
|
||||
Some(t) => {
|
||||
if t.kind == 'n' || t.kind == 'N' {
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
} else {
|
||||
return Err(("Expected \"n\".", self.span).into());
|
||||
}
|
||||
@ -506,15 +488,15 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
|
||||
buf.push('n');
|
||||
|
||||
devour_whitespace(self.toks);
|
||||
devour_whitespace(self.parser.toks);
|
||||
|
||||
if let Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) =
|
||||
self.toks.peek()
|
||||
self.parser.toks.peek()
|
||||
{
|
||||
buf.push(t.kind);
|
||||
self.toks.next();
|
||||
devour_whitespace(self.toks);
|
||||
match self.toks.peek() {
|
||||
self.parser.toks.next();
|
||||
devour_whitespace(self.parser.toks);
|
||||
match self.parser.toks.peek() {
|
||||
Some(t) if !t.kind.is_ascii_digit() => {
|
||||
return Err(("Expected a number.", self.span).into())
|
||||
}
|
||||
@ -522,12 +504,12 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
Some(..) => {}
|
||||
}
|
||||
|
||||
while let Some(t) = self.toks.peek() {
|
||||
while let Some(t) = self.parser.toks.peek() {
|
||||
if !t.kind.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
buf.push(t.kind);
|
||||
self.toks.next();
|
||||
self.parser.toks.next();
|
||||
}
|
||||
}
|
||||
Ok(buf)
|
||||
@ -535,7 +517,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
|
||||
fn declaration_value(&mut self) -> SassResult<String> {
|
||||
// todo: this consumes the closing paren
|
||||
let mut tmp = read_until_closing_paren(self.toks)?;
|
||||
let mut tmp = read_until_closing_paren(self.parser.toks)?;
|
||||
if let Some(Token { kind: ')', .. }) = tmp.pop() {
|
||||
} else {
|
||||
return Err(("expected \")\".", self.span).into());
|
||||
@ -544,7 +526,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
|
||||
fn expect_identifier(&mut self, s: &str) -> SassResult<()> {
|
||||
let mut ident = eat_ident_no_interpolation(self.toks, false, self.span)?.node;
|
||||
let mut ident = self.parser.parse_identifier_no_interpolation(false)?.node;
|
||||
ident.make_ascii_lowercase();
|
||||
if ident == s {
|
||||
Ok(())
|
||||
@ -554,7 +536,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
|
||||
}
|
||||
|
||||
fn expect_closing_paren(&mut self) -> SassResult<()> {
|
||||
if let Some(Token { kind: ')', .. }) = self.toks.next() {
|
||||
if let Some(Token { kind: ')', .. }) = self.parser.toks.next() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(("expected \")\".", self.span).into())
|
||||
|
195
src/style.rs
195
src/style.rs
@ -1,13 +1,6 @@
|
||||
use peekmore::PeekMoreIterator;
|
||||
use codemap::Spanned;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{devour_whitespace, devour_whitespace_or_comment, eat_ident};
|
||||
use crate::value::Value;
|
||||
use crate::{Expr, Token};
|
||||
use crate::{error::SassResult, value::Value};
|
||||
|
||||
/// A style: `color: red`
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
@ -17,16 +10,6 @@ pub(crate) struct Style {
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn parse_property<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
super_property: String,
|
||||
span_before: Span,
|
||||
) -> SassResult<String> {
|
||||
StyleParser::new(scope, super_selector).parse_property(toks, super_property, span_before)
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> SassResult<String> {
|
||||
Ok(format!(
|
||||
"{}: {};",
|
||||
@ -44,178 +27,4 @@ impl Style {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_value<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
StyleParser::new(scope, super_selector).parse_style_value(toks, scope, span_before)
|
||||
}
|
||||
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
super_property: String,
|
||||
) -> SassResult<Expr> {
|
||||
StyleParser::new(scope, super_selector).eat_style_group(toks, super_property, scope)
|
||||
}
|
||||
}
|
||||
|
||||
struct StyleParser<'a> {
|
||||
scope: &'a Scope,
|
||||
super_selector: &'a Selector,
|
||||
}
|
||||
|
||||
impl<'a> StyleParser<'a> {
|
||||
const fn new(scope: &'a Scope, super_selector: &'a Selector) -> Self {
|
||||
StyleParser {
|
||||
scope,
|
||||
super_selector,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_style_value<I: Iterator<Item = Token>>(
|
||||
&self,
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
devour_whitespace(toks);
|
||||
Value::from_tokens(toks, scope, self.super_selector, span_before)
|
||||
}
|
||||
|
||||
pub(crate) fn eat_style_group<I: Iterator<Item = Token>>(
|
||||
&self,
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
super_property: String,
|
||||
scope: &Scope,
|
||||
) -> SassResult<Expr> {
|
||||
let mut styles = Vec::new();
|
||||
devour_whitespace(toks);
|
||||
while let Some(tok) = toks.peek().cloned() {
|
||||
match tok.kind {
|
||||
'{' => {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
loop {
|
||||
let property =
|
||||
self.parse_property(toks, super_property.clone(), tok.pos)?;
|
||||
if let Some(tok) = toks.peek() {
|
||||
if tok.kind == '{' {
|
||||
match self.eat_style_group(toks, property, scope)? {
|
||||
Expr::Styles(s) => styles.extend(s),
|
||||
Expr::Style(s) => styles.push(*s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
if let Some(tok) = toks.peek() {
|
||||
if tok.kind == '}' {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
return Ok(Expr::Styles(styles));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let value = self.parse_style_value(toks, scope, tok.pos)?;
|
||||
match toks.peek() {
|
||||
Some(Token { kind: '}', .. }) => {
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
Some(Token { kind: ';', .. }) => {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
styles.push(Style {
|
||||
property: property.clone(),
|
||||
value,
|
||||
});
|
||||
match self.eat_style_group(toks, property, scope)? {
|
||||
Expr::Style(s) => styles.push(*s),
|
||||
Expr::Styles(s) => styles.extend(s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Some(..) | None => {
|
||||
devour_whitespace(toks);
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
}
|
||||
if let Some(tok) = toks.peek() {
|
||||
match tok.kind {
|
||||
'}' => {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
return Ok(Expr::Styles(styles));
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = self.parse_style_value(toks, scope, tok.pos)?;
|
||||
let t = toks.peek().ok_or(("expected more input.", value.span))?;
|
||||
match t.kind {
|
||||
';' => {
|
||||
toks.next();
|
||||
devour_whitespace(toks);
|
||||
}
|
||||
'{' => {
|
||||
let mut v = vec![Style {
|
||||
property: super_property.clone(),
|
||||
value,
|
||||
}];
|
||||
match self.eat_style_group(toks, super_property, scope)? {
|
||||
Expr::Style(s) => v.push(*s),
|
||||
Expr::Styles(s) => v.extend(s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
return Ok(Expr::Styles(v));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Ok(Expr::Style(Box::new(Style {
|
||||
property: super_property,
|
||||
value,
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Expr::Styles(styles))
|
||||
}
|
||||
|
||||
pub(crate) fn parse_property<I: Iterator<Item = Token>>(
|
||||
&self,
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
mut super_property: String,
|
||||
span_before: Span,
|
||||
) -> SassResult<String> {
|
||||
devour_whitespace(toks);
|
||||
let property = eat_ident(toks, self.scope, self.super_selector, span_before)?;
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
if let Some(Token { kind: ':', .. }) = toks.peek() {
|
||||
toks.next();
|
||||
devour_whitespace_or_comment(toks)?;
|
||||
} else {
|
||||
return Err(("Expected \":\".", property.span).into());
|
||||
}
|
||||
|
||||
if super_property.is_empty() {
|
||||
Ok(property.node)
|
||||
} else {
|
||||
super_property.reserve(1 + property.node.len());
|
||||
super_property.push('-');
|
||||
super_property.push_str(&property.node);
|
||||
Ok(super_property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,479 +0,0 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fs;
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
|
||||
use codemap::{CodeMap, Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::atrule::{eat_include, AtRule, AtRuleKind};
|
||||
use crate::error::{SassError, SassResult};
|
||||
use crate::imports::import;
|
||||
use crate::lexer::Lexer;
|
||||
use crate::output::Css;
|
||||
use crate::scope::{
|
||||
global_var_exists, insert_global_fn, insert_global_mixin, insert_global_var, Scope,
|
||||
GLOBAL_SCOPE,
|
||||
};
|
||||
use crate::selector::Selector;
|
||||
use crate::token::Token;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_comment, eat_ident, eat_variable_value, parse_quoted_string,
|
||||
peek_ident_no_interpolation, peek_whitespace, read_until_newline, VariableDecl,
|
||||
};
|
||||
use crate::{eat_expr, Expr, RuleSet, Stmt};
|
||||
|
||||
/// Represents a parsed SASS stylesheet with nesting
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyleSheet(pub(crate) Vec<Spanned<Stmt>>);
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[wasm_bindgen]
|
||||
impl StyleSheet {
|
||||
pub fn new(input: String) -> Result<String, JsValue> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), input);
|
||||
Ok(Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: &mut Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: Path::new(""),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?)
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_to_parse_error(map: &CodeMap, err: SassError) -> SassError {
|
||||
let (message, span) = err.raw();
|
||||
SassError::from_loc(message, map.look_up_span(span))
|
||||
}
|
||||
|
||||
impl StyleSheet {
|
||||
/// Write CSS to `buf`, constructed from a string
|
||||
///
|
||||
/// ```
|
||||
/// use grass::{SassResult, StyleSheet};
|
||||
///
|
||||
/// fn main() -> SassResult<()> {
|
||||
/// let sass = StyleSheet::new("a { b { color: red; } }".to_string())?;
|
||||
/// assert_eq!(sass, "a b {\n color: red;\n}\n");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "profiling", inline(never))]
|
||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn new(input: String) -> SassResult<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), input);
|
||||
Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: &mut Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: Path::new(""),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
/// Write CSS to `buf`, constructed from a path
|
||||
///
|
||||
/// ```
|
||||
/// use grass::{SassResult, StyleSheet};
|
||||
///
|
||||
/// fn main() -> SassResult<()> {
|
||||
/// let sass = StyleSheet::from_path("input.scss")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "profiling", inline(never))]
|
||||
#[cfg_attr(not(feature = "profiling"), inline)]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn from_path(p: &str) -> SassResult<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file(p.into(), String::from_utf8(fs::read(p)?)?);
|
||||
Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: &mut Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: p.as_ref(),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
pub(crate) fn export_from_path<P: AsRef<Path> + Into<String> + Clone>(
|
||||
p: &P,
|
||||
map: &mut CodeMap,
|
||||
) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p)?)?);
|
||||
Ok(StyleSheetParser {
|
||||
lexer: &mut Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map,
|
||||
path: p.as_ref(),
|
||||
}
|
||||
.parse_toplevel()?)
|
||||
}
|
||||
|
||||
pub(crate) fn from_stmts(s: Vec<Spanned<Stmt>>) -> StyleSheet {
|
||||
StyleSheet(s)
|
||||
}
|
||||
}
|
||||
|
||||
struct StyleSheetParser<'a> {
|
||||
lexer: &'a mut PeekMoreIterator<Lexer<'a>>,
|
||||
nesting: u32,
|
||||
map: &'a mut CodeMap,
|
||||
path: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> StyleSheetParser<'a> {
|
||||
fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let mut rules: Vec<Spanned<Stmt>> = Vec::new();
|
||||
devour_whitespace(self.lexer);
|
||||
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
||||
match kind {
|
||||
'a'..='z'
|
||||
| 'A'..='Z'
|
||||
| '0'..='9'
|
||||
| '['
|
||||
| '\\'
|
||||
| ']'
|
||||
| '^'
|
||||
| '_'
|
||||
| '-'
|
||||
| '#'
|
||||
| ':'
|
||||
| '*'
|
||||
| '%'
|
||||
| '.'
|
||||
| '>'
|
||||
| '+'
|
||||
| '='
|
||||
| ','
|
||||
| '('
|
||||
| ')'
|
||||
| '<'
|
||||
| '?'
|
||||
| '~'
|
||||
| '|'
|
||||
| '`'
|
||||
| '\''
|
||||
| '"'
|
||||
| '\u{7f}'..=std::char::MAX => {
|
||||
rules.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?)
|
||||
}
|
||||
'\t' | '\n' | ' ' | ';' => {
|
||||
self.lexer.next();
|
||||
continue;
|
||||
}
|
||||
'$' => {
|
||||
let span_before = self.lexer.next().unwrap().pos;
|
||||
let name = peek_ident_no_interpolation(self.lexer, false, span_before)?;
|
||||
let whitespace = peek_whitespace(self.lexer);
|
||||
|
||||
match self.lexer.peek() {
|
||||
Some(Token { kind: ':', pos }) => {
|
||||
let pos = *pos;
|
||||
self.lexer
|
||||
.take(name.node.chars().count() + whitespace + 1)
|
||||
.for_each(drop);
|
||||
devour_whitespace(self.lexer);
|
||||
|
||||
let VariableDecl { val, default, .. } = eat_variable_value(
|
||||
self.lexer,
|
||||
&Scope::new(),
|
||||
&Selector::new(),
|
||||
pos,
|
||||
)?;
|
||||
|
||||
if !(default && global_var_exists(&name.node)) {
|
||||
insert_global_var(&name.node, val)?;
|
||||
}
|
||||
}
|
||||
Some(..) | None => return Err(("expected \":\".", name.span).into()),
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
let pos = self.lexer.next().unwrap().pos;
|
||||
match self.lexer.next() {
|
||||
Some(Token { kind: '/', .. }) => {
|
||||
read_until_newline(self.lexer);
|
||||
devour_whitespace(self.lexer);
|
||||
}
|
||||
Some(Token { kind: '*', .. }) => {
|
||||
let comment = eat_comment(self.lexer, &Scope::new(), &Selector::new())?;
|
||||
rules.push(comment.map_node(Stmt::MultilineComment));
|
||||
}
|
||||
_ => return Err(("expected selector.", pos).into()),
|
||||
}
|
||||
}
|
||||
'@' => {
|
||||
let span_before = self.lexer.next().unwrap().pos();
|
||||
let rule = eat_ident(self.lexer, &Scope::new(), &Selector::new(), span_before)?;
|
||||
match AtRuleKind::try_from(&rule)? {
|
||||
AtRuleKind::Include => rules.extend(eat_include(
|
||||
self.lexer,
|
||||
&Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
rule.span,
|
||||
)?),
|
||||
AtRuleKind::Import => {
|
||||
devour_whitespace(self.lexer);
|
||||
let mut file_name = String::new();
|
||||
let next = match self.lexer.next() {
|
||||
Some(v) => v,
|
||||
None => todo!("expected input after @import"),
|
||||
};
|
||||
match next.kind {
|
||||
q @ '"' | q @ '\'' => {
|
||||
file_name.push_str(
|
||||
&parse_quoted_string(
|
||||
self.lexer,
|
||||
&Scope::new(),
|
||||
q,
|
||||
&Selector::new(),
|
||||
next.pos,
|
||||
)?
|
||||
.node
|
||||
.unquote()
|
||||
.to_css_string(rule.span)?,
|
||||
);
|
||||
}
|
||||
_ => return Err(("Expected string.", next.pos()).into()),
|
||||
}
|
||||
if let Some(t) = self.lexer.peek() {
|
||||
if t.kind == ';' {
|
||||
self.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
devour_whitespace(self.lexer);
|
||||
|
||||
let (new_rules, new_scope) =
|
||||
import(self.path, file_name.as_ref(), &mut self.map)?;
|
||||
rules.extend(new_rules);
|
||||
GLOBAL_SCOPE.with(|s| {
|
||||
s.borrow_mut().extend(new_scope);
|
||||
});
|
||||
}
|
||||
v => {
|
||||
let rule = AtRule::from_tokens(
|
||||
v,
|
||||
rule.span,
|
||||
self.lexer,
|
||||
&mut Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
)?;
|
||||
match rule.node {
|
||||
AtRule::Mixin(name, mixin) => {
|
||||
insert_global_mixin(&name, *mixin);
|
||||
}
|
||||
AtRule::Function(name, func) => {
|
||||
insert_global_fn(&name, *func);
|
||||
}
|
||||
AtRule::Charset => continue,
|
||||
AtRule::Warn(message) => self.warn(rule.span, &message),
|
||||
AtRule::Debug(message) => self.debug(rule.span, &message),
|
||||
AtRule::Return(_) => {
|
||||
return Err(
|
||||
("This at-rule is not allowed here.", rule.span).into()
|
||||
)
|
||||
}
|
||||
AtRule::For(f) => rules.extend(f.ruleset_eval(
|
||||
&mut Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
)?),
|
||||
AtRule::While(w) => rules.extend(w.ruleset_eval(
|
||||
&mut Scope::new(),
|
||||
&Selector::new(),
|
||||
true,
|
||||
None,
|
||||
)?),
|
||||
AtRule::Each(e) => rules.extend(e.ruleset_eval(
|
||||
&mut Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
)?),
|
||||
AtRule::Include(s) => rules.extend(s),
|
||||
AtRule::Content => {
|
||||
return Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
rule.span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
AtRule::If(i) => {
|
||||
rules.extend(i.eval(
|
||||
&mut Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
)?);
|
||||
}
|
||||
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
|
||||
AtRule::Unknown(..) | AtRule::Media(..) => {
|
||||
rules.push(rule.map_node(Stmt::AtRule))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'&' => {
|
||||
return Err((
|
||||
"Top-level selectors may not contain the parent selector \"&\".",
|
||||
self.lexer.next().unwrap().pos(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
'\u{0}'..='\u{8}' | '\u{b}'..='\u{1f}' => {
|
||||
return Err(("expected selector.", self.lexer.next().unwrap().pos).into());
|
||||
}
|
||||
'{' | '!' => {
|
||||
return Err(("expected \"}\".", self.lexer.next().unwrap().pos).into());
|
||||
}
|
||||
'}' => return Err(("unmatched \"}\".", self.lexer.next().unwrap().pos).into()),
|
||||
};
|
||||
}
|
||||
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))
|
||||
}
|
||||
|
||||
fn eat_rules(
|
||||
&mut self,
|
||||
super_selector: &Selector,
|
||||
scope: &mut Scope,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(expr) = eat_expr(self.lexer, scope, super_selector, None)? {
|
||||
let span = expr.span;
|
||||
match expr.node {
|
||||
Expr::Style(s) => stmts.push(Spanned {
|
||||
node: Stmt::Style(s),
|
||||
span,
|
||||
}),
|
||||
#[allow(clippy::match_same_arms)]
|
||||
Expr::AtRule(a) => match a {
|
||||
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector, None)?),
|
||||
AtRule::While(w) => {
|
||||
stmts.extend(w.ruleset_eval(scope, super_selector, false, None)?)
|
||||
}
|
||||
AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector, None)?),
|
||||
AtRule::Include(s) => stmts.extend(s),
|
||||
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector, None)?),
|
||||
AtRule::Content => {
|
||||
return Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
expr.span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
AtRule::Return(..) => {
|
||||
return Err(("This at-rule is not allowed here.", expr.span).into())
|
||||
}
|
||||
AtRule::AtRoot(root_stmts) => stmts.extend(root_stmts),
|
||||
AtRule::Debug(ref message) => self.debug(expr.span, message),
|
||||
AtRule::Warn(ref message) => self.warn(expr.span, message),
|
||||
AtRule::Mixin(..) | AtRule::Function(..) => todo!(),
|
||||
AtRule::Charset => todo!(),
|
||||
r @ AtRule::Unknown(..) | r @ AtRule::Media(..) => stmts.push(Spanned {
|
||||
node: Stmt::AtRule(r),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Expr::Styles(s) => stmts.extend(
|
||||
s.into_iter()
|
||||
.map(Box::new)
|
||||
.map(Stmt::Style)
|
||||
.map(|style| Spanned { node: style, span }),
|
||||
),
|
||||
Expr::MixinDecl(name, mixin) => {
|
||||
scope.insert_mixin(&name, *mixin);
|
||||
}
|
||||
Expr::FunctionDecl(name, func) => {
|
||||
scope.insert_fn(&name, *func);
|
||||
}
|
||||
Expr::Selector(s) => {
|
||||
self.nesting += 1;
|
||||
let rules =
|
||||
self.eat_rules(&s.resolve_parent_selectors(super_selector, true), scope)?;
|
||||
stmts.push(Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
super_selector: super_selector.clone(),
|
||||
selector: s,
|
||||
rules,
|
||||
}),
|
||||
span,
|
||||
});
|
||||
self.nesting -= 1;
|
||||
if self.nesting == 0 {
|
||||
return Ok(stmts);
|
||||
}
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
if self.nesting == 0 {
|
||||
scope.insert_var(&name, *val.clone())?;
|
||||
insert_global_var(&name, *val)?;
|
||||
} else {
|
||||
scope.insert_var(&name, *val)?;
|
||||
}
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Spanned {
|
||||
node: Stmt::MultilineComment(s),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that print to stdout or stderr
|
||||
impl<'a> StyleSheetParser<'a> {
|
||||
fn debug(&self, span: Span, message: &str) {
|
||||
let loc = self.map.look_up_span(span);
|
||||
eprintln!(
|
||||
"{}:{} Debug: {}",
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
fn warn(&self, span: Span, message: &str) {
|
||||
let loc = self.map.look_up_span(span);
|
||||
eprintln!(
|
||||
"Warning: {}\n {} {}:{} root stylesheet",
|
||||
message,
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
loc.begin.column + 1
|
||||
);
|
||||
}
|
||||
}
|
@ -2,8 +2,7 @@
|
||||
//!
|
||||
//! Arbitrary precision is retained.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::f64::consts::PI;
|
||||
use std::{collections::HashMap, f64::consts::PI};
|
||||
|
||||
use num_traits::One;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -1,12 +1,8 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::{error::SassResult, Token};
|
||||
|
||||
use super::read_until_closing_quote;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::Token;
|
||||
|
||||
/// Reads until the char is found, consuming the char,
|
||||
/// or until the end of the iterator is hit
|
||||
pub(crate) fn read_until_char<I: Iterator<Item = Token>>(
|
||||
|
@ -1,14 +1,6 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::selector::Selector;
|
||||
use crate::{Scope, Token};
|
||||
|
||||
use super::parse_interpolation;
|
||||
use crate::Token;
|
||||
|
||||
pub(crate) trait IsWhitespace {
|
||||
fn is_whitespace(&self) -> bool;
|
||||
@ -48,80 +40,6 @@ pub(crate) fn peek_whitespace<I: Iterator<Item = W>, W: IsWhitespace>(
|
||||
peek_counter
|
||||
}
|
||||
|
||||
pub(crate) fn devour_whitespace_or_comment<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
) -> SassResult<bool> {
|
||||
let mut found_whitespace = false;
|
||||
while let Some(tok) = toks.peek() {
|
||||
if tok.kind == '/' {
|
||||
let next = match toks.peek_forward(1) {
|
||||
Some(v) => v,
|
||||
None => return Ok(found_whitespace),
|
||||
};
|
||||
match next.kind {
|
||||
'*' => {
|
||||
toks.next();
|
||||
eat_comment(toks, &Scope::new(), &Selector::new())?;
|
||||
}
|
||||
'/' => read_until_newline(toks),
|
||||
_ => {
|
||||
toks.reset_view();
|
||||
return Ok(found_whitespace);
|
||||
}
|
||||
};
|
||||
found_whitespace = true;
|
||||
continue;
|
||||
}
|
||||
if !tok.is_whitespace() {
|
||||
break;
|
||||
}
|
||||
found_whitespace = true;
|
||||
toks.next();
|
||||
}
|
||||
Ok(found_whitespace)
|
||||
}
|
||||
|
||||
/// 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 PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Spanned<String>> {
|
||||
let mut comment = String::new();
|
||||
let mut span = if let Some(tok) = toks.peek() {
|
||||
tok.pos()
|
||||
} else {
|
||||
// Hit this path: "/*"
|
||||
todo!("")
|
||||
};
|
||||
while let Some(tok) = toks.next() {
|
||||
span = span.merge(tok.pos());
|
||||
match (tok.kind, toks.peek()) {
|
||||
('*', Some(Token { kind: '/', .. })) => {
|
||||
toks.next();
|
||||
break;
|
||||
}
|
||||
('#', Some(Token { kind: '{', .. })) => {
|
||||
toks.next();
|
||||
comment.push_str(
|
||||
&parse_interpolation(toks, scope, super_selector, span)?.to_css_string(span)?,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
(..) => comment.push(tok.kind),
|
||||
}
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
Ok(Spanned {
|
||||
node: comment,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Eat tokens until a newline
|
||||
///
|
||||
/// This exists largely to eat silent comments, "//"
|
||||
|
@ -1,31 +0,0 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::Value;
|
||||
use crate::{Scope, Token};
|
||||
|
||||
use super::read_until_closing_curly_brace;
|
||||
|
||||
pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
let val = Value::from_vec(
|
||||
read_until_closing_curly_brace(toks)?,
|
||||
scope,
|
||||
super_selector,
|
||||
span_before,
|
||||
)?;
|
||||
toks.next();
|
||||
Ok(Spanned {
|
||||
node: val.node.eval(val.span)?.node.unquote(),
|
||||
span: val.span,
|
||||
})
|
||||
}
|
@ -1,17 +1,13 @@
|
||||
pub(crate) use chars::*;
|
||||
pub(crate) use comment_whitespace::*;
|
||||
pub(crate) use interpolation::*;
|
||||
pub(crate) use number::*;
|
||||
pub(crate) use peek_until::*;
|
||||
pub(crate) use read_until::*;
|
||||
pub(crate) use strings::*;
|
||||
pub(crate) use variables::*;
|
||||
|
||||
mod chars;
|
||||
mod comment_whitespace;
|
||||
mod interpolation;
|
||||
mod number;
|
||||
mod peek_until;
|
||||
mod read_until;
|
||||
mod strings;
|
||||
mod variables;
|
||||
|
@ -1,11 +1,8 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::Token;
|
||||
use crate::{error::SassResult, Token};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParsedNumber {
|
||||
|
@ -1,11 +1,8 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::Token;
|
||||
use crate::{error::SassResult, Token};
|
||||
|
||||
use super::{as_hex, hex_char_for, is_name, is_name_start, IsWhitespace};
|
||||
|
||||
|
@ -2,8 +2,7 @@ use std::iter::Iterator;
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::Token;
|
||||
use crate::{error::SassResult, Token};
|
||||
|
||||
use super::{devour_whitespace, read_until_newline};
|
||||
|
||||
|
@ -1,17 +1,4 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::common::QuoteKind;
|
||||
use crate::error::SassResult;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::Value;
|
||||
use crate::{Scope, Token};
|
||||
|
||||
use super::{as_hex, hex_char_for, is_name, is_name_start, parse_interpolation};
|
||||
use super::{is_name, is_name_start};
|
||||
|
||||
pub(crate) fn is_ident(s: &str) -> bool {
|
||||
let mut chars = s.chars().peekable();
|
||||
@ -44,318 +31,3 @@ pub(crate) fn is_ident(s: &str) -> bool {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn 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.next();
|
||||
text.push('-');
|
||||
} else if is_name(tok.kind) {
|
||||
text.push(toks.next().unwrap().kind);
|
||||
} else if tok.kind == '\\' {
|
||||
toks.next();
|
||||
text.push_str(&escape(toks, false)?);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Spanned { node: text, span })
|
||||
}
|
||||
|
||||
fn interpolated_ident_body<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
mut span: Span,
|
||||
buf: &mut String,
|
||||
) -> SassResult<Span> {
|
||||
while let Some(tok) = toks.peek() {
|
||||
match tok.kind {
|
||||
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '\u{80}'..=std::char::MAX => {
|
||||
span = span.merge(tok.pos());
|
||||
buf.push(toks.next().unwrap().kind);
|
||||
}
|
||||
'\\' => {
|
||||
toks.next();
|
||||
buf.push_str(&escape(toks, false)?);
|
||||
}
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek_forward(1).cloned() {
|
||||
toks.next();
|
||||
toks.next();
|
||||
// TODO: if ident, interpolate literally
|
||||
let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
|
||||
buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
|
||||
} else {
|
||||
toks.reset_view();
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(span)
|
||||
}
|
||||
|
||||
fn escape<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
identifier_start: bool,
|
||||
) -> SassResult<String> {
|
||||
let mut value = 0;
|
||||
let first = match toks.peek() {
|
||||
Some(t) => t,
|
||||
None => return Ok(String::new()),
|
||||
};
|
||||
let mut span = first.pos();
|
||||
if first.kind == '\n' {
|
||||
return Err(("Expected escape sequence.", span).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;
|
||||
span = span.merge(next.pos());
|
||||
value += as_hex(toks.next().unwrap().kind)
|
||||
}
|
||||
if toks.peek().is_some() && toks.peek().unwrap().kind.is_whitespace() {
|
||||
toks.next();
|
||||
}
|
||||
} else {
|
||||
let next = toks.next().unwrap();
|
||||
span = span.merge(next.pos());
|
||||
value = next.kind as u32;
|
||||
}
|
||||
|
||||
let c = std::char::from_u32(value).ok_or(("Invalid escape sequence.", span))?;
|
||||
if (identifier_start && is_name_start(c) && !c.is_digit(10))
|
||||
|| (!identifier_start && is_name(c))
|
||||
{
|
||||
Ok(c.to_string())
|
||||
} else if value <= 0x1F || value == 0x7F || (identifier_start && c.is_digit(10)) {
|
||||
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 eat_ident<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<String>> {
|
||||
let Token {
|
||||
kind,
|
||||
pos: mut span,
|
||||
} = toks.peek().ok_or(("Expected identifier.", span_before))?;
|
||||
let mut text = String::new();
|
||||
if kind == &'-' {
|
||||
toks.next();
|
||||
text.push('-');
|
||||
match toks.peek() {
|
||||
Some(Token { kind: '-', .. }) => {
|
||||
toks.next();
|
||||
text.push('-');
|
||||
let body_span =
|
||||
interpolated_ident_body(toks, scope, super_selector, span, &mut text)?;
|
||||
span = span.merge(body_span);
|
||||
return Ok(Spanned { node: text, span });
|
||||
}
|
||||
Some(..) => {}
|
||||
None => return Ok(Spanned { node: text, span }),
|
||||
}
|
||||
}
|
||||
|
||||
let Token { kind: first, pos } = match toks.peek() {
|
||||
Some(v) => *v,
|
||||
None => return Err(("Expected identifier.", span).into()),
|
||||
};
|
||||
|
||||
if is_name_start(first) {
|
||||
text.push(toks.next().unwrap().kind);
|
||||
} else if first == '\\' {
|
||||
toks.next();
|
||||
text.push_str(&escape(toks, true)?);
|
||||
// TODO: peekmore
|
||||
// (first == '#' && scanner.peekChar(1) == $lbrace)
|
||||
} else if first == '#' {
|
||||
toks.next();
|
||||
let Token { kind, pos } = if let Some(tok) = toks.peek() {
|
||||
*tok
|
||||
} else {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
};
|
||||
if kind == '{' {
|
||||
toks.next();
|
||||
match parse_interpolation(toks, scope, super_selector, pos)?.node {
|
||||
Value::String(ref s, ..) => text.push_str(s),
|
||||
v => text.push_str(v.to_css_string(span)?.borrow()),
|
||||
}
|
||||
} else {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
} else {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
|
||||
let body_span = interpolated_ident_body(toks, scope, super_selector, pos, &mut text)?;
|
||||
span = span.merge(body_span);
|
||||
Ok(Spanned { node: text, span })
|
||||
}
|
||||
|
||||
pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
unit: bool,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<String>> {
|
||||
let Token {
|
||||
kind,
|
||||
pos: mut span,
|
||||
} = toks.peek().ok_or(("Expected identifier.", span_before))?;
|
||||
let mut text = String::new();
|
||||
if kind == &'-' {
|
||||
toks.next();
|
||||
text.push('-');
|
||||
|
||||
match toks.peek() {
|
||||
Some(Token { kind: '-', .. }) => {
|
||||
toks.next();
|
||||
text.push('-');
|
||||
text.push_str(&ident_body_no_interpolation(toks, unit, span)?.node);
|
||||
return Ok(Spanned { node: text, span });
|
||||
}
|
||||
Some(..) => {}
|
||||
None => return Ok(Spanned { node: text, span }),
|
||||
}
|
||||
}
|
||||
|
||||
let first = match toks.next() {
|
||||
Some(v) => v,
|
||||
None => return Err(("Expected identifier.", span).into()),
|
||||
};
|
||||
|
||||
if is_name_start(first.kind) {
|
||||
text.push(first.kind);
|
||||
} else if first.kind == '\\' {
|
||||
text.push_str(&escape(toks, true)?);
|
||||
} else {
|
||||
return Err(("Expected identifier.", first.pos).into());
|
||||
}
|
||||
|
||||
let body = ident_body_no_interpolation(toks, unit, span)?;
|
||||
span = span.merge(body.span);
|
||||
text.push_str(&body.node);
|
||||
Ok(Spanned { node: text, span })
|
||||
}
|
||||
|
||||
pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
q: char,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
let mut s = String::new();
|
||||
let mut span = toks
|
||||
.peek()
|
||||
.ok_or((format!("Expected {}.", q), span_before))?
|
||||
.pos();
|
||||
while let Some(tok) = toks.next() {
|
||||
span = span.merge(tok.pos());
|
||||
match tok.kind {
|
||||
'"' if q == '"' => break,
|
||||
'\'' if q == '\'' => break,
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek().cloned() {
|
||||
toks.next();
|
||||
let interpolation = parse_interpolation(toks, scope, super_selector, pos)?;
|
||||
match interpolation.node {
|
||||
Value::String(ref v, ..) => s.push_str(v),
|
||||
v => s.push_str(v.to_css_string(interpolation.span)?.borrow()),
|
||||
};
|
||||
continue;
|
||||
} else {
|
||||
s.push('#');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
'\n' => return Err(("Expected \".", tok.pos()).into()),
|
||||
'\\' => {
|
||||
let first = match toks.peek() {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
s.push('\u{FFFD}');
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if first.kind == '\n' {
|
||||
toks.next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if first.kind.is_ascii_hexdigit() {
|
||||
let mut value = 0;
|
||||
for _ in 0..6 {
|
||||
// todo: or patterns
|
||||
let next = match toks.peek() {
|
||||
Some(c) => c,
|
||||
None => break,
|
||||
};
|
||||
if !next.kind.is_ascii_hexdigit() {
|
||||
break;
|
||||
}
|
||||
value = (value << 4) + as_hex(toks.next().unwrap().kind);
|
||||
}
|
||||
|
||||
if toks.peek().is_some() && toks.peek().unwrap().kind.is_ascii_whitespace() {
|
||||
toks.next();
|
||||
}
|
||||
|
||||
if value == 0 || (value >= 0xD800 && value <= 0xDFFF) || value >= 0x0010_FFFF {
|
||||
s.push('\u{FFFD}');
|
||||
} else {
|
||||
s.push(std::char::from_u32(value).unwrap());
|
||||
}
|
||||
} else {
|
||||
s.push(toks.next().unwrap().kind);
|
||||
}
|
||||
}
|
||||
_ => s.push(tok.kind),
|
||||
}
|
||||
}
|
||||
Ok(Spanned {
|
||||
node: Value::String(s, QuoteKind::Quoted),
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
@ -1,132 +0,0 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::Value;
|
||||
use crate::{Scope, Token};
|
||||
|
||||
use super::{
|
||||
devour_whitespace, peek_ident_no_interpolation, read_until_closing_paren,
|
||||
read_until_closing_quote, read_until_newline,
|
||||
};
|
||||
|
||||
pub(crate) struct VariableDecl {
|
||||
pub val: Spanned<Value>,
|
||||
pub default: bool,
|
||||
pub global: bool,
|
||||
}
|
||||
|
||||
impl VariableDecl {
|
||||
pub const fn new(val: Spanned<Value>, default: bool, global: bool) -> VariableDecl {
|
||||
VariableDecl {
|
||||
val,
|
||||
default,
|
||||
global,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<VariableDecl> {
|
||||
devour_whitespace(toks);
|
||||
let mut default = false;
|
||||
let mut global = false;
|
||||
|
||||
let mut val_toks = Vec::new();
|
||||
let mut nesting = 0;
|
||||
while let Some(tok) = toks.peek() {
|
||||
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() {
|
||||
Some(Token { kind: '{', .. }) => nesting += 1,
|
||||
Some(Token { kind: ';', .. }) => break,
|
||||
Some(Token { kind: '}', .. }) => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
}
|
||||
}
|
||||
Some(..) | None => {}
|
||||
}
|
||||
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() {
|
||||
Some(Token { kind: '/', .. }) => read_until_newline(toks),
|
||||
Some(..) | None => val_toks.push(next),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
'(' => {
|
||||
val_toks.push(toks.next().unwrap());
|
||||
val_toks.extend(read_until_closing_paren(toks)?);
|
||||
}
|
||||
'!' => {
|
||||
let pos = tok.pos();
|
||||
if toks.peek_forward(1).is_none() {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
// todo: it should not be possible to declare the same flag more than once
|
||||
let mut ident = peek_ident_no_interpolation(toks, false, pos)?;
|
||||
ident.node.make_ascii_lowercase();
|
||||
match ident.node.as_str() {
|
||||
"global" => {
|
||||
toks.take(7).for_each(drop);
|
||||
global = true;
|
||||
}
|
||||
"default" => {
|
||||
toks.take(8).for_each(drop);
|
||||
default = true;
|
||||
}
|
||||
"important" => {
|
||||
toks.reset_view();
|
||||
val_toks.push(toks.next().unwrap());
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return Err(("Invalid flag name.", ident.span).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => val_toks.push(toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
let val = Value::from_vec(val_toks, scope, super_selector, span_before)?;
|
||||
Ok(VariableDecl::new(val, default, global))
|
||||
}
|
@ -1,63 +1,3 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::PeekMoreIterator;
|
||||
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::utils::{
|
||||
devour_whitespace, parse_interpolation, peek_escape, peek_until_closing_curly_brace,
|
||||
peek_whitespace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::Token;
|
||||
|
||||
pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
buf: &mut String,
|
||||
) -> SassResult<()> {
|
||||
buf.reserve(2);
|
||||
buf.push('(');
|
||||
let mut nesting = 0;
|
||||
while let Some(tok) = toks.next() {
|
||||
match tok.kind {
|
||||
' ' | '\t' | '\n' => {
|
||||
devour_whitespace(toks);
|
||||
buf.push(' ');
|
||||
}
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', .. }) = toks.peek() {
|
||||
let span_before = toks.next().unwrap().pos();
|
||||
let interpolation =
|
||||
parse_interpolation(toks, scope, super_selector, span_before)?;
|
||||
buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
|
||||
} else {
|
||||
buf.push('#');
|
||||
}
|
||||
}
|
||||
'(' => {
|
||||
nesting += 1;
|
||||
buf.push('(');
|
||||
}
|
||||
')' => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
c => buf.push(c),
|
||||
}
|
||||
}
|
||||
buf.push(')');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_special_function(s: &str) -> bool {
|
||||
s.starts_with("calc(")
|
||||
|| s.starts_with("var(")
|
||||
@ -65,105 +5,3 @@ pub(crate) fn is_special_function(s: &str) -> bool {
|
||||
|| s.starts_with("min(")
|
||||
|| s.starts_with("max(")
|
||||
}
|
||||
|
||||
pub(crate) fn eat_progid<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<String> {
|
||||
let mut string = String::new();
|
||||
let mut span = toks.peek().unwrap().pos();
|
||||
while let Some(tok) = toks.next() {
|
||||
span = span.merge(tok.pos());
|
||||
match tok.kind {
|
||||
'a'..='z' | 'A'..='Z' | '.' => {
|
||||
string.push(tok.kind);
|
||||
}
|
||||
'(' => {
|
||||
eat_calc_args(toks, scope, super_selector, &mut string)?;
|
||||
break;
|
||||
}
|
||||
_ => return Err(("expected \"(\".", span).into()),
|
||||
}
|
||||
}
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
pub(crate) fn try_eat_url<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Option<String>> {
|
||||
let mut buf = String::from("url(");
|
||||
let mut peek_counter = 0;
|
||||
peek_counter += peek_whitespace(toks);
|
||||
while let Some(tok) = toks.peek() {
|
||||
let kind = tok.kind;
|
||||
toks.move_forward(1);
|
||||
peek_counter += 1;
|
||||
if kind == '!'
|
||||
|| kind == '%'
|
||||
|| kind == '&'
|
||||
|| (kind >= '*' && kind <= '~')
|
||||
|| kind as u32 >= 0x0080
|
||||
{
|
||||
buf.push(kind);
|
||||
} else if kind == '\\' {
|
||||
buf.push_str(&peek_escape(toks)?);
|
||||
} else if kind == '#' {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek() {
|
||||
let pos = *pos;
|
||||
toks.move_forward(1);
|
||||
peek_counter += 1;
|
||||
let (interpolation, count) = peek_interpolation(toks, scope, super_selector, pos)?;
|
||||
peek_counter += count;
|
||||
match interpolation.node {
|
||||
Value::String(ref s, ..) => buf.push_str(s),
|
||||
v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
|
||||
};
|
||||
} else {
|
||||
buf.push('#');
|
||||
}
|
||||
} else if kind == ')' {
|
||||
buf.push(')');
|
||||
toks.take(peek_counter).for_each(drop);
|
||||
return Ok(Some(buf));
|
||||
} else if kind.is_whitespace() {
|
||||
peek_counter += peek_whitespace(toks);
|
||||
let next = match toks.peek() {
|
||||
Some(v) => v,
|
||||
None => break,
|
||||
};
|
||||
if next.kind == ')' {
|
||||
buf.push(')');
|
||||
toks.take(peek_counter + 1).for_each(drop);
|
||||
return Ok(Some(buf));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
toks.reset_view();
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn peek_interpolation<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<(Spanned<Value>, usize)> {
|
||||
let vec = peek_until_closing_curly_brace(toks);
|
||||
let peek_counter = vec.len();
|
||||
toks.move_forward(1);
|
||||
let val = Value::from_vec(vec, scope, super_selector, span_before)?;
|
||||
Ok((
|
||||
Spanned {
|
||||
node: val.node.eval(val.span)?.node.unquote(),
|
||||
span: val.span,
|
||||
},
|
||||
peek_counter,
|
||||
))
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
use std::slice::Iter;
|
||||
use std::vec::IntoIter;
|
||||
use std::{slice::Iter, vec::IntoIter};
|
||||
|
||||
use codemap::Span;
|
||||
|
||||
use super::Value;
|
||||
use crate::common::{Brackets, ListSeparator};
|
||||
use crate::error::SassResult;
|
||||
use crate::{
|
||||
common::{Brackets, ListSeparator},
|
||||
error::SassResult,
|
||||
value::Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SassMap(Vec<(Value, Value)>);
|
||||
|
@ -1,28 +1,27 @@
|
||||
use std::iter::Iterator;
|
||||
|
||||
use peekmore::PeekMore;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::common::{Brackets, ListSeparator, Op, QuoteKind};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::utils::hex_char_for;
|
||||
use crate::{Cow, Token};
|
||||
use crate::{
|
||||
color::Color,
|
||||
common::{Brackets, ListSeparator, Op, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
selector::Selector,
|
||||
unit::Unit,
|
||||
utils::hex_char_for,
|
||||
{Cow, Token},
|
||||
};
|
||||
|
||||
use css_function::is_special_function;
|
||||
pub(crate) use map::SassMap;
|
||||
pub(crate) use number::Number;
|
||||
pub(crate) use sass_function::SassFunction;
|
||||
|
||||
mod css_function;
|
||||
pub(crate) mod css_function;
|
||||
mod map;
|
||||
mod number;
|
||||
mod ops;
|
||||
mod parse;
|
||||
mod sass_function;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -332,22 +331,35 @@ impl Value {
|
||||
/// `name` is the argument name. It's used for error reporting.
|
||||
pub fn to_selector(
|
||||
self,
|
||||
span: Span,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
parser: &mut Parser<'_>,
|
||||
name: &str,
|
||||
allows_parent: bool,
|
||||
) -> SassResult<Selector> {
|
||||
let string = match self.clone().selector_string(span)? {
|
||||
let string = match self.clone().selector_string(parser.span_before)? {
|
||||
Some(v) => v,
|
||||
None => return Err((format!("${}: {} is not a valid selector: it must be a string, a list of strings, or a list of lists of strings.", name, self.inspect(span)?), span).into()),
|
||||
None => return Err((format!("${}: {} is not a valid selector: it must be a string, a list of strings, or a list of lists of strings.", name, self.inspect(parser.span_before)?), parser.span_before).into()),
|
||||
};
|
||||
Selector::from_tokens(
|
||||
&mut string.chars().map(|c| Token::new(span, c)).peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
allows_parent,
|
||||
)
|
||||
Parser {
|
||||
toks: &mut string
|
||||
.chars()
|
||||
.map(|c| Token::new(parser.span_before, c))
|
||||
.collect::<Vec<Token>>()
|
||||
.into_iter()
|
||||
.peekmore(),
|
||||
map: parser.map,
|
||||
path: parser.path,
|
||||
scopes: parser.scopes,
|
||||
global_scope: parser.global_scope,
|
||||
super_selectors: parser.super_selectors,
|
||||
span_before: parser.span_before,
|
||||
content: parser.content.clone(),
|
||||
in_mixin: parser.in_mixin,
|
||||
in_function: parser.in_function,
|
||||
in_control_flow: parser.in_control_flow,
|
||||
at_root: parser.at_root,
|
||||
at_root_has_selector: parser.at_root_has_selector,
|
||||
}
|
||||
.parse_selector(allows_parent, true, String::new())
|
||||
}
|
||||
|
||||
fn selector_string(self, span: Span) -> SassResult<Option<String>> {
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::fmt::{self, Display, UpperHex};
|
||||
use std::mem;
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::{
|
||||
fmt::{self, Display, UpperHex},
|
||||
mem,
|
||||
ops::{Add, AddAssign},
|
||||
};
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{Signed, ToPrimitive, Zero};
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::convert::{From, TryFrom};
|
||||
use std::fmt::{self, Display, Write};
|
||||
use std::mem;
|
||||
use std::ops::{
|
||||
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
convert::{From, TryFrom},
|
||||
fmt::{self, Display, Write},
|
||||
mem,
|
||||
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
|
||||
};
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
@ -2,10 +2,12 @@ use std::cmp::Ordering;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use crate::common::{Op, QuoteKind};
|
||||
use crate::error::SassResult;
|
||||
use crate::unit::{Unit, UNIT_CONVERSION_TABLE};
|
||||
use crate::value::Value;
|
||||
use crate::{
|
||||
common::{Op, QuoteKind},
|
||||
error::SassResult,
|
||||
unit::{Unit, UNIT_CONVERSION_TABLE},
|
||||
value::Value,
|
||||
};
|
||||
|
||||
impl Value {
|
||||
pub fn equals(mut self, mut other: Value, span: Span) -> SassResult<Spanned<Value>> {
|
||||
|
@ -1,965 +0,0 @@
|
||||
use std::iter::Iterator;
|
||||
use std::mem;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_rational::{BigRational, Rational64};
|
||||
use num_traits::{pow, One, ToPrimitive};
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use super::css_function::{eat_calc_args, eat_progid, try_eat_url};
|
||||
|
||||
use crate::args::eat_call_args;
|
||||
use crate::builtin::GLOBAL_FUNCTIONS;
|
||||
use crate::color::{Color, NAMED_COLORS};
|
||||
use crate::common::{Brackets, Identifier, ListSeparator, Op, QuoteKind};
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::unit::Unit;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_comment, eat_ident, eat_ident_no_interpolation, eat_number,
|
||||
parse_quoted_string, read_until_char, read_until_closing_paren,
|
||||
read_until_closing_square_brace, read_until_newline, IsWhitespace,
|
||||
};
|
||||
use crate::value::Value;
|
||||
use crate::Token;
|
||||
|
||||
use super::map::SassMap;
|
||||
use super::number::Number;
|
||||
|
||||
fn parse_hex<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
mut span: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
let mut s = String::with_capacity(8);
|
||||
if toks
|
||||
.peek()
|
||||
.ok_or(("Expected identifier.", span))?
|
||||
.kind
|
||||
.is_ascii_digit()
|
||||
{
|
||||
while let Some(c) = toks.peek() {
|
||||
if !c.kind.is_ascii_hexdigit() || s.len() == 8 {
|
||||
break;
|
||||
}
|
||||
let tok = toks.next().unwrap();
|
||||
span = span.merge(tok.pos());
|
||||
s.push(tok.kind);
|
||||
}
|
||||
} else {
|
||||
let i = eat_ident(toks, scope, super_selector, span)?;
|
||||
if i.node.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
s = i.node;
|
||||
span = span.merge(i.span);
|
||||
} else {
|
||||
return Ok(Spanned {
|
||||
node: Value::String(format!("#{}", i.node), QuoteKind::None),
|
||||
span: i.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
match s.len() {
|
||||
3 => {
|
||||
let v = match u16::from_str_radix(&s, 16) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return Ok(Value::String(format!("#{}", s), QuoteKind::None).span(span)),
|
||||
};
|
||||
let red = (((v & 0xf00) >> 8) * 0x11) as u8;
|
||||
let green = (((v & 0x0f0) >> 4) * 0x11) as u8;
|
||||
let blue = ((v & 0x00f) * 0x11) as u8;
|
||||
Ok(
|
||||
Value::Color(Box::new(Color::new(red, green, blue, 1, format!("#{}", s))))
|
||||
.span(span),
|
||||
)
|
||||
}
|
||||
4 => {
|
||||
let v = match u16::from_str_radix(&s, 16) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return Ok(Value::String(format!("#{}", s), QuoteKind::None).span(span)),
|
||||
};
|
||||
let red = (((v & 0xf000) >> 12) * 0x11) as u8;
|
||||
let green = (((v & 0x0f00) >> 8) * 0x11) as u8;
|
||||
let blue = (((v & 0x00f0) >> 4) * 0x11) as u8;
|
||||
let alpha = ((v & 0x000f) * 0x11) as u8;
|
||||
Ok(Value::Color(Box::new(Color::new(
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
alpha,
|
||||
format!("#{}", s),
|
||||
)))
|
||||
.span(span))
|
||||
}
|
||||
6 => {
|
||||
let v = match u32::from_str_radix(&s, 16) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return Ok(Value::String(format!("#{}", s), QuoteKind::None).span(span)),
|
||||
};
|
||||
let red = ((v & 0x00ff_0000) >> 16) as u8;
|
||||
let green = ((v & 0x0000_ff00) >> 8) as u8;
|
||||
let blue = (v & 0x0000_00ff) as u8;
|
||||
Ok(
|
||||
Value::Color(Box::new(Color::new(red, green, blue, 1, format!("#{}", s))))
|
||||
.span(span),
|
||||
)
|
||||
}
|
||||
8 => {
|
||||
let v = match u32::from_str_radix(&s, 16) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return Ok(Value::String(format!("#{}", s), QuoteKind::None).span(span)),
|
||||
};
|
||||
let red = ((v & 0xff00_0000) >> 24) as u8;
|
||||
let green = ((v & 0x00ff_0000) >> 16) as u8;
|
||||
let blue = ((v & 0x0000_ff00) >> 8) as u8;
|
||||
let alpha = (v & 0x0000_00ff) as u8;
|
||||
Ok(Value::Color(Box::new(Color::new(
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
alpha,
|
||||
format!("#{}", s),
|
||||
)))
|
||||
.span(span))
|
||||
}
|
||||
_ => Err(("Expected hex digit.", span).into()),
|
||||
}
|
||||
}
|
||||
|
||||
struct IntermediateValueIterator<'a, I: Iterator<Item = Token>> {
|
||||
toks: &'a mut PeekMoreIterator<I>,
|
||||
scope: &'a Scope,
|
||||
super_selector: &'a Selector,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = Token>> Iterator for IntermediateValueIterator<'a, I> {
|
||||
type Item = SassResult<Spanned<IntermediateValue>>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Value::parse_intermediate_value(self.toks, self.scope, self.super_selector)
|
||||
}
|
||||
}
|
||||
|
||||
impl IsWhitespace for SassResult<Spanned<IntermediateValue>> {
|
||||
fn is_whitespace(&self) -> bool {
|
||||
match self {
|
||||
Ok(v) => v.node.is_whitespace(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum IntermediateValue {
|
||||
Value(Value),
|
||||
Op(Op),
|
||||
Bracketed(Vec<Token>),
|
||||
Paren(Vec<Token>),
|
||||
Comma,
|
||||
Whitespace,
|
||||
}
|
||||
|
||||
impl IntermediateValue {
|
||||
const fn span(self, span: Span) -> Spanned<Self> {
|
||||
Spanned { node: self, span }
|
||||
}
|
||||
}
|
||||
|
||||
impl IsWhitespace for IntermediateValue {
|
||||
fn is_whitespace(&self) -> bool {
|
||||
if self == &IntermediateValue::Whitespace {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_paren(
|
||||
t: Spanned<Vec<Token>>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
if t.is_empty() {
|
||||
return Ok(Value::List(Vec::new(), ListSeparator::Space, Brackets::None).span(t.span));
|
||||
}
|
||||
|
||||
let paren_toks = &mut t.node.into_iter().peekmore();
|
||||
|
||||
let mut map = SassMap::new();
|
||||
let key = Value::from_vec(
|
||||
read_until_char(paren_toks, ':')?,
|
||||
scope,
|
||||
super_selector,
|
||||
t.span,
|
||||
)?;
|
||||
|
||||
if paren_toks.peek().is_none() {
|
||||
return Ok(Spanned {
|
||||
node: Value::Paren(Box::new(key.node)),
|
||||
span: key.span,
|
||||
});
|
||||
}
|
||||
|
||||
let val = Value::from_vec(
|
||||
read_until_char(paren_toks, ',')?,
|
||||
scope,
|
||||
super_selector,
|
||||
key.span,
|
||||
)?;
|
||||
|
||||
map.insert(key.node, val.node);
|
||||
|
||||
if paren_toks.peek().is_none() {
|
||||
return Ok(Spanned {
|
||||
node: Value::Map(map),
|
||||
span: key.span.merge(val.span),
|
||||
});
|
||||
}
|
||||
|
||||
let mut span = key.span;
|
||||
|
||||
loop {
|
||||
let key = Value::from_vec(
|
||||
read_until_char(paren_toks, ':')?,
|
||||
scope,
|
||||
super_selector,
|
||||
span,
|
||||
)?;
|
||||
devour_whitespace(paren_toks);
|
||||
let val = Value::from_vec(
|
||||
read_until_char(paren_toks, ',')?,
|
||||
scope,
|
||||
super_selector,
|
||||
key.span,
|
||||
)?;
|
||||
span = span.merge(val.span);
|
||||
devour_whitespace(paren_toks);
|
||||
if map.insert(key.node, val.node) {
|
||||
return Err(("Duplicate key.", key.span).into());
|
||||
}
|
||||
if paren_toks.peek().is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Spanned {
|
||||
node: Value::Map(map),
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
fn eat_op<I: Iterator<Item = Token>>(
|
||||
iter: &mut PeekMoreIterator<IntermediateValueIterator<I>>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
op: Spanned<Op>,
|
||||
space_separated: &mut Vec<Spanned<Value>>,
|
||||
last_was_whitespace: bool,
|
||||
) -> SassResult<()> {
|
||||
match op.node {
|
||||
Op::Not => {
|
||||
devour_whitespace(iter);
|
||||
let right = single_value(iter, scope, super_selector, op.span)?;
|
||||
space_separated.push(Spanned {
|
||||
node: Value::UnaryOp(op.node, Box::new(right.node)),
|
||||
span: right.span,
|
||||
});
|
||||
}
|
||||
Op::Div => {
|
||||
devour_whitespace(iter);
|
||||
let right = single_value(iter, scope, super_selector, op.span)?;
|
||||
if let Some(left) = space_separated.pop() {
|
||||
space_separated.push(Spanned {
|
||||
node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
|
||||
span: left.span.merge(right.span),
|
||||
});
|
||||
} else {
|
||||
devour_whitespace(iter);
|
||||
space_separated.push(Spanned {
|
||||
node: Value::String(
|
||||
format!("/{}", right.node.to_css_string(right.span)?),
|
||||
QuoteKind::None,
|
||||
),
|
||||
span: op.span.merge(right.span),
|
||||
});
|
||||
}
|
||||
}
|
||||
Op::Plus => {
|
||||
if let Some(left) = space_separated.pop() {
|
||||
devour_whitespace(iter);
|
||||
let right = single_value(iter, scope, super_selector, op.span)?;
|
||||
space_separated.push(Spanned {
|
||||
node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
|
||||
span: left.span.merge(right.span),
|
||||
});
|
||||
} else {
|
||||
devour_whitespace(iter);
|
||||
let right = single_value(iter, scope, super_selector, op.span)?;
|
||||
space_separated.push(Spanned {
|
||||
node: Value::UnaryOp(op.node, Box::new(right.node)),
|
||||
span: right.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
Op::Minus => {
|
||||
if devour_whitespace(iter) || !last_was_whitespace {
|
||||
let right = single_value(iter, scope, super_selector, op.span)?;
|
||||
if let Some(left) = space_separated.pop() {
|
||||
space_separated.push(Spanned {
|
||||
node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
|
||||
span: left.span.merge(right.span),
|
||||
});
|
||||
} else {
|
||||
space_separated.push(right.map_node(|n| Value::UnaryOp(op.node, Box::new(n))));
|
||||
}
|
||||
} else {
|
||||
let right = single_value(iter, scope, super_selector, op.span)?;
|
||||
space_separated.push(right.map_node(|n| Value::UnaryOp(op.node, Box::new(n))));
|
||||
}
|
||||
}
|
||||
Op::And | Op::Or => {
|
||||
devour_whitespace(iter);
|
||||
// special case when the value is literally "and" or "or"
|
||||
if iter.peek().is_none() {
|
||||
space_separated.push(Value::String(op.to_string(), QuoteKind::None).span(op.span));
|
||||
} else if let Some(left) = space_separated.pop() {
|
||||
devour_whitespace(iter);
|
||||
let right = single_value(iter, scope, super_selector, left.span)?;
|
||||
space_separated.push(
|
||||
Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
|
||||
.span(left.span.merge(right.span)),
|
||||
);
|
||||
} else {
|
||||
return Err(("Expected expression.", op.span).into());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(left) = space_separated.pop() {
|
||||
devour_whitespace(iter);
|
||||
let right = single_value(iter, scope, super_selector, left.span)?;
|
||||
space_separated.push(
|
||||
Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
|
||||
.span(left.span.merge(right.span)),
|
||||
);
|
||||
} else {
|
||||
return Err(("Expected expression.", op.span).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn single_value<I: Iterator<Item = Token>>(
|
||||
iter: &mut PeekMoreIterator<IntermediateValueIterator<I>>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
let next = iter.next().ok_or(("Expected expression.", span))??;
|
||||
Ok(match next.node {
|
||||
IntermediateValue::Value(v) => v.span(next.span),
|
||||
IntermediateValue::Op(op) => match op {
|
||||
Op::Minus => {
|
||||
devour_whitespace(iter);
|
||||
let val = single_value(iter, scope, super_selector, span)?;
|
||||
Spanned {
|
||||
node: val.node.neg(val.span)?,
|
||||
span: next.span.merge(val.span),
|
||||
}
|
||||
}
|
||||
Op::Not => {
|
||||
devour_whitespace(iter);
|
||||
let val = single_value(iter, scope, super_selector, span)?;
|
||||
Spanned {
|
||||
node: Value::UnaryOp(Op::Not, Box::new(val.node)),
|
||||
span: next.span.merge(val.span),
|
||||
}
|
||||
}
|
||||
Op::Plus => {
|
||||
devour_whitespace(iter);
|
||||
single_value(iter, scope, super_selector, span)?
|
||||
}
|
||||
Op::Div => {
|
||||
devour_whitespace(iter);
|
||||
let val = single_value(iter, scope, super_selector, span)?;
|
||||
Spanned {
|
||||
node: Value::String(
|
||||
format!("/{}", val.node.to_css_string(val.span)?),
|
||||
QuoteKind::None,
|
||||
),
|
||||
span: next.span.merge(val.span),
|
||||
}
|
||||
}
|
||||
Op::And => Spanned {
|
||||
node: Value::String("and".into(), QuoteKind::None),
|
||||
span: next.span,
|
||||
},
|
||||
Op::Or => Spanned {
|
||||
node: Value::String("or".into(), QuoteKind::None),
|
||||
span: next.span,
|
||||
},
|
||||
_ => {
|
||||
return Err(("Expected expression.", next.span).into());
|
||||
}
|
||||
},
|
||||
IntermediateValue::Whitespace => unreachable!(),
|
||||
IntermediateValue::Comma => return Err(("Expected expression.", span).into()),
|
||||
IntermediateValue::Bracketed(t) => {
|
||||
let v = Value::from_vec(t, scope, super_selector, span)?;
|
||||
match v.node {
|
||||
Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed),
|
||||
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
|
||||
}
|
||||
.span(v.span)
|
||||
}
|
||||
IntermediateValue::Paren(t) => {
|
||||
let val = parse_paren(
|
||||
Spanned {
|
||||
node: t,
|
||||
span: next.span,
|
||||
},
|
||||
scope,
|
||||
super_selector,
|
||||
)?;
|
||||
Spanned {
|
||||
node: Value::Paren(Box::new(val.node)),
|
||||
span: val.span,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_i64(s: &str) -> i64 {
|
||||
s.as_bytes()
|
||||
.iter()
|
||||
.fold(0, |total, this| total * 10 + i64::from(this - b'0'))
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn from_tokens<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<Self>> {
|
||||
let span = match toks.peek() {
|
||||
Some(Token { pos, .. }) => *pos,
|
||||
None => return Err(("Expected expression.", span_before).into()),
|
||||
};
|
||||
devour_whitespace(toks);
|
||||
let mut last_was_whitespace = false;
|
||||
let mut space_separated = Vec::new();
|
||||
let mut comma_separated = Vec::new();
|
||||
let mut iter = IntermediateValueIterator {
|
||||
toks,
|
||||
scope,
|
||||
super_selector,
|
||||
}
|
||||
.peekmore();
|
||||
while let Some(val) = iter.next() {
|
||||
let val = val?;
|
||||
match val.node {
|
||||
IntermediateValue::Value(v) => {
|
||||
last_was_whitespace = false;
|
||||
space_separated.push(v.span(val.span))
|
||||
}
|
||||
IntermediateValue::Op(op) => {
|
||||
eat_op(
|
||||
&mut iter,
|
||||
scope,
|
||||
super_selector,
|
||||
Spanned {
|
||||
node: op,
|
||||
span: val.span,
|
||||
},
|
||||
&mut space_separated,
|
||||
last_was_whitespace,
|
||||
)?;
|
||||
}
|
||||
IntermediateValue::Whitespace => {
|
||||
last_was_whitespace = true;
|
||||
continue;
|
||||
}
|
||||
IntermediateValue::Comma => {
|
||||
last_was_whitespace = false;
|
||||
|
||||
if space_separated.len() == 1 {
|
||||
comma_separated.push(space_separated.pop().unwrap());
|
||||
} else {
|
||||
let mut span = space_separated
|
||||
.get(0)
|
||||
.ok_or(("Expected expression.", val.span))?
|
||||
.span;
|
||||
comma_separated.push(
|
||||
Value::List(
|
||||
mem::take(&mut space_separated)
|
||||
.into_iter()
|
||||
.map(|a| {
|
||||
span = span.merge(a.span);
|
||||
a.node
|
||||
})
|
||||
.collect(),
|
||||
ListSeparator::Space,
|
||||
Brackets::None,
|
||||
)
|
||||
.span(span),
|
||||
);
|
||||
}
|
||||
}
|
||||
IntermediateValue::Bracketed(t) => {
|
||||
last_was_whitespace = false;
|
||||
if t.is_empty() {
|
||||
space_separated.push(
|
||||
Value::List(Vec::new(), ListSeparator::Space, Brackets::Bracketed)
|
||||
.span(val.span),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
space_separated.push(
|
||||
match Value::from_vec(t, scope, super_selector, val.span)?.node {
|
||||
Value::List(v, sep, Brackets::None) => {
|
||||
Value::List(v, sep, Brackets::Bracketed).span(val.span)
|
||||
}
|
||||
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed)
|
||||
.span(val.span),
|
||||
},
|
||||
)
|
||||
}
|
||||
IntermediateValue::Paren(t) => {
|
||||
last_was_whitespace = false;
|
||||
space_separated.push(parse_paren(
|
||||
Spanned {
|
||||
node: t,
|
||||
span: val.span,
|
||||
},
|
||||
scope,
|
||||
super_selector,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(if !comma_separated.is_empty() {
|
||||
if space_separated.len() == 1 {
|
||||
comma_separated.push(space_separated.pop().unwrap());
|
||||
} else if !space_separated.is_empty() {
|
||||
comma_separated.push(
|
||||
Value::List(
|
||||
space_separated.into_iter().map(|a| a.node).collect(),
|
||||
ListSeparator::Space,
|
||||
Brackets::None,
|
||||
)
|
||||
.span(span),
|
||||
);
|
||||
}
|
||||
Value::List(
|
||||
comma_separated.into_iter().map(|a| a.node).collect(),
|
||||
ListSeparator::Comma,
|
||||
Brackets::None,
|
||||
)
|
||||
.span(span)
|
||||
} else if space_separated.len() == 1 {
|
||||
space_separated.pop().unwrap()
|
||||
} else {
|
||||
Value::List(
|
||||
space_separated.into_iter().map(|a| a.node).collect(),
|
||||
ListSeparator::Space,
|
||||
Brackets::None,
|
||||
)
|
||||
.span(span)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_vec(
|
||||
toks: Vec<Token>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<Value>> {
|
||||
if toks.is_empty() {
|
||||
return Err(("Expected expression.", span_before).into());
|
||||
}
|
||||
Self::from_tokens(
|
||||
&mut toks.into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
span_before,
|
||||
)
|
||||
}
|
||||
|
||||
fn ident<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
span_before: Span,
|
||||
) -> SassResult<Spanned<IntermediateValue>> {
|
||||
let Spanned { node: mut s, span } = eat_ident(toks, scope, super_selector, span_before)?;
|
||||
|
||||
let lower = s.to_ascii_lowercase();
|
||||
|
||||
if lower == "progid" && toks.peek().is_some() && toks.peek().unwrap().kind == ':' {
|
||||
s = lower;
|
||||
toks.next();
|
||||
s.push(':');
|
||||
s.push_str(&eat_progid(toks, scope, super_selector)?);
|
||||
return Ok(Spanned {
|
||||
node: IntermediateValue::Value(Value::String(s, QuoteKind::None)),
|
||||
span,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(Token { kind: '(', pos }) = toks.peek() {
|
||||
let pos = *pos;
|
||||
let as_ident = Identifier::from(&s);
|
||||
toks.next();
|
||||
let func = match scope.get_fn(Spanned {
|
||||
node: as_ident.clone(),
|
||||
span,
|
||||
}) {
|
||||
Ok(f) => f,
|
||||
Err(_) => match GLOBAL_FUNCTIONS.get(as_ident.into_inner().as_str()) {
|
||||
Some(f) => {
|
||||
return Ok(IntermediateValue::Value(f.0(
|
||||
eat_call_args(toks, pos)?,
|
||||
scope,
|
||||
super_selector,
|
||||
)?)
|
||||
.span(span))
|
||||
}
|
||||
None => {
|
||||
match lower.as_str() {
|
||||
"calc" | "element" | "expression" => {
|
||||
s = lower;
|
||||
eat_calc_args(toks, scope, super_selector, &mut s)?;
|
||||
}
|
||||
// "min" => {}
|
||||
// "max" => {}
|
||||
"url" => match try_eat_url(toks, scope, super_selector)? {
|
||||
Some(val) => s = val,
|
||||
None => s.push_str(
|
||||
&eat_call_args(toks, pos)?
|
||||
.to_css_string(scope, super_selector)?,
|
||||
),
|
||||
},
|
||||
_ => s.push_str(
|
||||
&eat_call_args(toks, pos)?.to_css_string(scope, super_selector)?,
|
||||
),
|
||||
}
|
||||
return Ok(
|
||||
IntermediateValue::Value(Value::String(s, QuoteKind::None)).span(span)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
return Ok(IntermediateValue::Value(func.eval(
|
||||
eat_call_args(toks, pos)?,
|
||||
scope,
|
||||
super_selector,
|
||||
)?)
|
||||
.span(span));
|
||||
}
|
||||
|
||||
if let Some(c) = NAMED_COLORS.get_by_name(lower.as_str()) {
|
||||
return Ok(IntermediateValue::Value(Value::Color(Box::new(Color::new(
|
||||
c[0], c[1], c[2], c[3], s,
|
||||
))))
|
||||
.span(span));
|
||||
}
|
||||
|
||||
Ok(match lower.as_str() {
|
||||
"true" => IntermediateValue::Value(Value::True),
|
||||
"false" => IntermediateValue::Value(Value::False),
|
||||
"null" => IntermediateValue::Value(Value::Null),
|
||||
"not" => IntermediateValue::Op(Op::Not),
|
||||
"and" => IntermediateValue::Op(Op::And),
|
||||
"or" => IntermediateValue::Op(Op::Or),
|
||||
_ => IntermediateValue::Value(Value::String(s, QuoteKind::None)),
|
||||
}
|
||||
.span(span))
|
||||
}
|
||||
|
||||
fn parse_intermediate_value<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> Option<SassResult<Spanned<IntermediateValue>>> {
|
||||
let (kind, span) = match toks.peek() {
|
||||
Some(v) => (v.kind, v.pos()),
|
||||
None => return None,
|
||||
};
|
||||
|
||||
if devour_whitespace(toks) {
|
||||
return Some(Ok(Spanned {
|
||||
node: IntermediateValue::Whitespace,
|
||||
span,
|
||||
}));
|
||||
}
|
||||
|
||||
let next_is_hypen = |toks: &mut PeekMoreIterator<I>| {
|
||||
toks.peek_forward(1).is_some()
|
||||
&& matches!(toks.peek().unwrap().kind, '-' | '_' | 'a'..='z' | 'A'..='Z')
|
||||
};
|
||||
Some(Ok(match kind {
|
||||
_ if kind.is_ascii_alphabetic()
|
||||
|| kind == '_'
|
||||
|| kind == '\\'
|
||||
|| (!kind.is_ascii() && !kind.is_control())
|
||||
|| (kind == '-' && next_is_hypen(toks)) =>
|
||||
{
|
||||
return Some(Self::ident(toks, scope, super_selector, span));
|
||||
}
|
||||
'0'..='9' | '.' => {
|
||||
let Spanned {
|
||||
node: val,
|
||||
mut span,
|
||||
} = match eat_number(toks) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
let unit = if let Some(tok) = toks.peek() {
|
||||
let Token { kind, pos } = *tok;
|
||||
match kind {
|
||||
'a'..='z' | 'A'..='Z' | '_' | '\\' | '\u{7f}'..=std::char::MAX => {
|
||||
let u = match eat_ident_no_interpolation(toks, true, pos) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
span = span.merge(u.span);
|
||||
Unit::from(u.node)
|
||||
}
|
||||
'%' => {
|
||||
span = span.merge(toks.next().unwrap().pos());
|
||||
Unit::Percent
|
||||
}
|
||||
_ => Unit::None,
|
||||
}
|
||||
} else {
|
||||
Unit::None
|
||||
};
|
||||
|
||||
let n = if val.dec_len == 0 {
|
||||
if val.num.len() <= 18 && val.times_ten.is_empty() {
|
||||
let n = Rational64::new_raw(parse_i64(&val.num), 1);
|
||||
return Some(Ok(IntermediateValue::Value(Value::Dimension(
|
||||
Number::new_machine(n),
|
||||
unit,
|
||||
))
|
||||
.span(span)));
|
||||
}
|
||||
BigRational::new_raw(val.num.parse::<BigInt>().unwrap(), BigInt::one())
|
||||
} else {
|
||||
if val.num.len() <= 18 && val.times_ten.is_empty() {
|
||||
let n = Rational64::new(parse_i64(&val.num), pow(10, val.dec_len));
|
||||
return Some(Ok(IntermediateValue::Value(Value::Dimension(
|
||||
Number::new_machine(n),
|
||||
unit,
|
||||
))
|
||||
.span(span)));
|
||||
}
|
||||
BigRational::new(val.num.parse().unwrap(), pow(BigInt::from(10), val.dec_len))
|
||||
};
|
||||
|
||||
if val.times_ten.is_empty() {
|
||||
return Some(Ok(IntermediateValue::Value(Value::Dimension(
|
||||
Number::new_big(n),
|
||||
unit,
|
||||
))
|
||||
.span(span)));
|
||||
}
|
||||
|
||||
let times_ten = pow(
|
||||
BigInt::from(10),
|
||||
match val
|
||||
.times_ten
|
||||
.parse::<BigInt>()
|
||||
.unwrap()
|
||||
.to_usize()
|
||||
.ok_or(("Exponent too large (expected usize).", span))
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
},
|
||||
);
|
||||
|
||||
let times_ten = if val.times_ten_is_postive {
|
||||
BigRational::new_raw(times_ten, BigInt::one())
|
||||
} else {
|
||||
BigRational::new(BigInt::one(), times_ten)
|
||||
};
|
||||
|
||||
IntermediateValue::Value(Value::Dimension(Number::new_big(n * times_ten), unit))
|
||||
.span(span)
|
||||
}
|
||||
'(' => {
|
||||
let mut span = toks.next().unwrap().pos();
|
||||
let mut inner = match read_until_closing_paren(toks) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
// todo: the above shouldn't eat the closing paren
|
||||
if let Some(last_tok) = inner.pop() {
|
||||
if last_tok.kind != ')' {
|
||||
return Some(Err(("expected \")\".", span).into()));
|
||||
}
|
||||
span = span.merge(last_tok.pos());
|
||||
}
|
||||
IntermediateValue::Paren(inner).span(span)
|
||||
}
|
||||
'&' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
IntermediateValue::Value(super_selector.clone().into_value()).span(span)
|
||||
}
|
||||
'#' => {
|
||||
if let Some(Token { kind: '{', pos }) = toks.peek_forward(1) {
|
||||
let span_before = *pos;
|
||||
toks.reset_view();
|
||||
return Some(Self::ident(toks, scope, super_selector, span_before));
|
||||
}
|
||||
toks.reset_view();
|
||||
toks.next();
|
||||
let hex = match parse_hex(toks, scope, super_selector, span) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
IntermediateValue::Value(hex.node).span(hex.span)
|
||||
}
|
||||
q @ '"' | q @ '\'' => {
|
||||
let span_start = toks.next().unwrap().pos();
|
||||
let Spanned { node, span } =
|
||||
match parse_quoted_string(toks, scope, q, super_selector, span_start) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
IntermediateValue::Value(node).span(span_start.merge(span))
|
||||
}
|
||||
'[' => {
|
||||
let mut span = toks.next().unwrap().pos();
|
||||
let mut inner = match read_until_closing_square_brace(toks) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
if let Some(last_tok) = inner.pop() {
|
||||
if last_tok.kind != ']' {
|
||||
return Some(Err(("expected \"]\".", span).into()));
|
||||
}
|
||||
span = span.merge(last_tok.pos());
|
||||
}
|
||||
IntermediateValue::Bracketed(inner).span(span)
|
||||
}
|
||||
'$' => {
|
||||
toks.next();
|
||||
let val = match eat_ident_no_interpolation(toks, false, span) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
IntermediateValue::Value(
|
||||
match scope.get_var(val.clone()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
}
|
||||
.node,
|
||||
)
|
||||
.span(val.span)
|
||||
}
|
||||
'+' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
IntermediateValue::Op(Op::Plus).span(span)
|
||||
}
|
||||
'-' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
IntermediateValue::Op(Op::Minus).span(span)
|
||||
}
|
||||
'*' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
IntermediateValue::Op(Op::Mul).span(span)
|
||||
}
|
||||
'%' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
IntermediateValue::Op(Op::Rem).span(span)
|
||||
}
|
||||
',' => {
|
||||
toks.next();
|
||||
IntermediateValue::Comma.span(span)
|
||||
}
|
||||
q @ '>' | q @ '<' => {
|
||||
let mut span = toks.next().unwrap().pos;
|
||||
#[allow(clippy::eval_order_dependence)]
|
||||
IntermediateValue::Op(if let Some(Token { kind: '=', .. }) = toks.peek() {
|
||||
span = span.merge(toks.next().unwrap().pos);
|
||||
match q {
|
||||
'>' => Op::GreaterThanEqual,
|
||||
'<' => Op::LessThanEqual,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
match q {
|
||||
'>' => Op::GreaterThan,
|
||||
'<' => Op::LessThan,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
.span(span)
|
||||
}
|
||||
'=' => {
|
||||
let mut span = toks.next().unwrap().pos();
|
||||
if let Some(Token { kind: '=', pos }) = toks.next() {
|
||||
span = span.merge(pos);
|
||||
IntermediateValue::Op(Op::Equal).span(span)
|
||||
} else {
|
||||
return Some(Err(("expected \"=\".", span).into()));
|
||||
}
|
||||
}
|
||||
'!' => {
|
||||
let mut span = toks.next().unwrap().pos();
|
||||
if let Some(Token { kind: '=', .. }) = toks.peek() {
|
||||
span = span.merge(toks.next().unwrap().pos());
|
||||
return Some(Ok(IntermediateValue::Op(Op::NotEqual).span(span)));
|
||||
}
|
||||
devour_whitespace(toks);
|
||||
let v = match eat_ident(toks, scope, super_selector, span) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
span = span.merge(v.span);
|
||||
if v.node.to_ascii_lowercase().as_str() == "important" {
|
||||
IntermediateValue::Value(Value::Important).span(span)
|
||||
} else {
|
||||
return Some(Err(("Expected \"important\".", span).into()));
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
let span = toks.next().unwrap().pos();
|
||||
match toks.peek() {
|
||||
Some(Token { kind: '/', .. }) => {
|
||||
read_until_newline(toks);
|
||||
devour_whitespace(toks);
|
||||
IntermediateValue::Whitespace.span(span)
|
||||
}
|
||||
Some(Token { kind: '*', .. }) => {
|
||||
toks.next();
|
||||
match eat_comment(toks, &Scope::new(), &Selector::new()) {
|
||||
Ok(..) => {}
|
||||
Err(e) => return Some(Err(e)),
|
||||
}
|
||||
IntermediateValue::Whitespace.span(span)
|
||||
}
|
||||
Some(..) => IntermediateValue::Op(Op::Div).span(span),
|
||||
None => return Some(Err(("Expected expression.", span).into())),
|
||||
}
|
||||
}
|
||||
';' | '}' | '{' => return None,
|
||||
':' | '?' | ')' | '@' | '^' | ']' | '|' => {
|
||||
return Some(Err(("expected \";\".", span).into()))
|
||||
}
|
||||
'\u{0}'..='\u{8}' | '\u{b}'..='\u{1f}' | '\u{7f}'..=std::char::MAX | '`' | '~' => {
|
||||
return Some(Err(("Expected expression.", span).into()))
|
||||
}
|
||||
' ' | '\n' | '\t' => unreachable!("whitespace is checked prior to this match"),
|
||||
'A'..='Z' | 'a'..='z' | '_' | '\\' => {
|
||||
unreachable!("these chars are checked in an if stmt")
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
@ -11,14 +11,10 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::args::CallArgs;
|
||||
use crate::atrule::Function;
|
||||
use crate::builtin::Builtin;
|
||||
use crate::common::Identifier;
|
||||
use crate::error::SassResult;
|
||||
use crate::scope::Scope;
|
||||
use crate::selector::Selector;
|
||||
use crate::value::Value;
|
||||
use crate::{
|
||||
args::CallArgs, atrule::Function, builtin::Builtin, common::Identifier, error::SassResult,
|
||||
parse::Parser, value::Value,
|
||||
};
|
||||
|
||||
/// A SASS function
|
||||
///
|
||||
@ -52,15 +48,10 @@ impl SassFunction {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
self,
|
||||
args: CallArgs,
|
||||
scope: &Scope,
|
||||
super_selector: &Selector,
|
||||
) -> SassResult<Value> {
|
||||
pub fn call(self, args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
match self {
|
||||
Self::Builtin(f, ..) => f.0(args, scope, super_selector),
|
||||
Self::UserDefined(f, ..) => f.eval(args, scope, super_selector),
|
||||
Self::Builtin(f, ..) => f.0(args, parser),
|
||||
Self::UserDefined(f, ..) => parser.eval_function(*f, args),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,9 +45,8 @@ test!(
|
||||
"@function foo($a, $b: $a) {\n @return $b;\n}\n\na {\n color: foo(2);\n}\n",
|
||||
"a {\n color: 2;\n}\n"
|
||||
);
|
||||
// todo: this should have a space after :
|
||||
// and should be "expected \")\"."
|
||||
error!(
|
||||
#[ignore = "does not fail"]
|
||||
nothing_after_open,
|
||||
"a { color:rgb(; }", "Error: expected \"{\"."
|
||||
"a { color:rgb(; }", "Error: expected \")\"."
|
||||
);
|
||||
|
41
tests/content-exists.rs
Normal file
41
tests/content-exists.rs
Normal file
@ -0,0 +1,41 @@
|
||||
#![cfg(test)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
error!(
|
||||
outside_mixin,
|
||||
"a {\n color: content-exists();\n}\n",
|
||||
"Error: content-exists() may only be called within a mixin."
|
||||
);
|
||||
test!(
|
||||
include_no_braces_no_args,
|
||||
"@mixin foo {\n color: content-exists();\n}\n\na {\n @include foo;\n}\n",
|
||||
"a {\n color: false;\n}\n"
|
||||
);
|
||||
test!(
|
||||
include_no_braces_empty_args,
|
||||
"@mixin foo {\n color: content-exists();\n}\n\na {\n @include foo();\n}\n",
|
||||
"a {\n color: false;\n}\n"
|
||||
);
|
||||
test!(
|
||||
include_empty_braces_no_args,
|
||||
"@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{};\n}\n",
|
||||
"a {\n color: true;\n}\n"
|
||||
);
|
||||
test!(
|
||||
include_style_inside_braces_no_args,
|
||||
"@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{color: red;};\n}\n",
|
||||
"a {\n color: true;\n color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
include_style_inside_braces_missing_semicolon_no_args,
|
||||
"@mixin foo {\n color: content-exists();\n @content;\n}\n\na {\n @include foo{color: red};\n}\n",
|
||||
"a {\n color: true;\n color: red;\n}\n"
|
||||
);
|
||||
error!(
|
||||
#[ignore = "haven't yet figured out a good way to check for whether an @content block exists"]
|
||||
include_empty_braces_no_args_no_at_content,
|
||||
"@mixin foo {\n color: content-exists();\n}\n\na {\n @include foo{};\n}\n",
|
||||
"Error: Mixin doesn't accept a content block."
|
||||
);
|
@ -27,15 +27,14 @@ error!(
|
||||
question_mark_inside_value,
|
||||
"a {foo: bar?}", "Error: expected \";\"."
|
||||
);
|
||||
// TODO: special parsing rules for variable names
|
||||
// error!(
|
||||
// interpolation_in_variable_declaration,
|
||||
// "$base-#{lor}: #036;", "Error: expected \":\"."
|
||||
// );
|
||||
// error!(
|
||||
// backslash_as_last_character,
|
||||
// "a {colo\\: red;}", "Error: expected \"{\"."
|
||||
// );
|
||||
error!(
|
||||
interpolation_in_variable_declaration,
|
||||
"$base-#{lor}: #036;", "Error: expected \":\"."
|
||||
);
|
||||
error!(
|
||||
backslash_as_last_character,
|
||||
"a {colo\\: red;}", "Error: expected \"{\"."
|
||||
);
|
||||
error!(
|
||||
close_paren_without_opening,
|
||||
"a {color: foo);}", "Error: expected \";\"."
|
||||
@ -64,7 +63,7 @@ error!(
|
||||
);
|
||||
error!(
|
||||
missing_colon_in_style,
|
||||
"a {color, red;}", "Error: Expected \":\"."
|
||||
"a {color, red;}", "Error: expected \"{\"."
|
||||
);
|
||||
error!(
|
||||
toplevel_forward_slash,
|
||||
@ -86,12 +85,13 @@ error!(toplevel_comma, "a {},", "Error: expected \"{\".");
|
||||
error!(toplevel_exclamation_alone, "!", "Error: expected \"}\".");
|
||||
error!(toplevel_exclamation, "! {}", "Error: expected \"}\".");
|
||||
error!(toplevel_backtick, "` {}", "Error: expected selector.");
|
||||
// note that the message dart-sass gives is: `Error: expected "}".`
|
||||
error!(
|
||||
toplevel_open_curly_brace,
|
||||
"{ {color: red;}", "Error: expected \"}\"."
|
||||
);
|
||||
error!(toplevel_open_paren, "(", "Error: expected \"{\".");
|
||||
error!(toplevel_close_paren, "(", "Error: expected \"{\".");
|
||||
error!(toplevel_close_paren, ")", "Error: expected \"{\".");
|
||||
error!(
|
||||
backtick_in_value,
|
||||
"a {color:`red;}", "Error: Expected expression."
|
||||
@ -100,7 +100,8 @@ error!(
|
||||
comma_begins_value,
|
||||
"a {color:,red;}", "Error: Expected expression."
|
||||
);
|
||||
error!(nothing_after_hyphen, "a {-}", "Error: Expected \":\".");
|
||||
// dart-sass gives `Error: expected "{".`
|
||||
error!(nothing_after_hyphen, "a {-}", "Error: Expected identifier.");
|
||||
error!(
|
||||
nothing_after_hyphen_variable,
|
||||
"a {$-", "Error: expected \":\"."
|
||||
@ -123,16 +124,21 @@ error!(
|
||||
);
|
||||
error!(
|
||||
toplevel_hash_no_closing_curly_brace_no_value,
|
||||
"#{", "Error: expected \"}\"."
|
||||
"#{", "Error: Expected expression."
|
||||
);
|
||||
error!(toplevel_hash, "#", "Error: expected \"{\".");
|
||||
error!(toplevel_closing_brace, "}", "Error: unmatched \"}\".");
|
||||
error!(
|
||||
#[ignore = "we use closing brace to end scope"]
|
||||
toplevel_closing_brace,
|
||||
"}", "Error: unmatched \"}\"."
|
||||
);
|
||||
error!(toplevel_at, "@", "Error: Expected identifier.");
|
||||
error!(
|
||||
#[ignore = "this panics until we can return a result in selector parsing"]
|
||||
toplevel_ampersand,
|
||||
"& {}", "Error: Top-level selectors may not contain the parent selector \"&\"."
|
||||
);
|
||||
error!(toplevel_backslash, "\\", "Error: expected \"}\".");
|
||||
error!(toplevel_backslash, "\\", "Error: expected \"{\".");
|
||||
error!(toplevel_var_no_colon, "$r", "Error: expected \":\".");
|
||||
error!(bar_in_value, "a {color: a|b;}", "Error: expected \";\".");
|
||||
error!(
|
||||
@ -214,14 +220,17 @@ error!(
|
||||
"a {$color: {ed;}", "Error: Expected expression."
|
||||
);
|
||||
error!(
|
||||
#[ignore = "this test does not fail because the closing brace is included in the value"]
|
||||
empty_style_value_no_semicolon,
|
||||
"a {color:}", "Error: Expected expression."
|
||||
);
|
||||
error!(
|
||||
#[ignore = "this test does not fail because the semicolon is included in the value"]
|
||||
empty_style_value_semicolon,
|
||||
"a {color:;}", "Error: Expected expression."
|
||||
);
|
||||
error!(
|
||||
#[ignore = "this does not fail"]
|
||||
ident_colon_closing_brace,
|
||||
"r:}", "Error: Expected expression."
|
||||
);
|
||||
@ -239,3 +248,4 @@ error!(
|
||||
improperly_terminated_nested_style,
|
||||
"a {foo: {bar: red", "Error: Expected identifier."
|
||||
);
|
||||
error!(toplevel_nullbyte, "\u{0}", "Error: expected selector.");
|
||||
|
@ -116,3 +116,8 @@ test!(
|
||||
}",
|
||||
"a {\n color: get-function(\"-test\");\n color: get-function(\"-test\");\n}\n"
|
||||
);
|
||||
test!(
|
||||
nested_call_and_get_function,
|
||||
"a {\n color: call(call(get-function(get-function), darken), red, 10%);\n}\n",
|
||||
"a {\n color: #cc0000;\n}\n"
|
||||
);
|
||||
|
@ -135,6 +135,11 @@ test!(
|
||||
@else if not comparable($p, 0) {}",
|
||||
""
|
||||
);
|
||||
test!(
|
||||
at_rule_inside_ruleset,
|
||||
"@mixin foo {\n color: red;\n}\n\n@if true {\n a {\n @include foo;\n }\n}\n",
|
||||
"a {\n color: red;\n}\n"
|
||||
);
|
||||
error!(
|
||||
nothing_after_escape,
|
||||
"@if \\", "Error: Expected expression."
|
||||
|
@ -1,8 +1,6 @@
|
||||
use std::io::Write;
|
||||
use tempfile::Builder;
|
||||
|
||||
use grass::StyleSheet;
|
||||
|
||||
/// Create a temporary file with the given name
|
||||
/// and contents.
|
||||
///
|
||||
@ -42,7 +40,7 @@ fn imports_variable() {
|
||||
tempfile!("imports_variable", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
@ -52,7 +50,7 @@ fn import_no_semicolon() {
|
||||
tempfile!("import_no_semicolon", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
@ -60,7 +58,7 @@ fn import_no_semicolon() {
|
||||
fn import_no_quotes() {
|
||||
let input = "@import import_no_quotes";
|
||||
tempfile!("import_no_quotes", "$a: red;");
|
||||
match grass::StyleSheet::new(input.to_string()) {
|
||||
match grass::from_string(input.to_string()) {
|
||||
Ok(..) => panic!("did not fail"),
|
||||
Err(e) => assert_eq!(
|
||||
"Error: Expected string.",
|
||||
@ -79,7 +77,7 @@ fn single_quotes_import() {
|
||||
tempfile!("single_quotes_import", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
@ -89,7 +87,7 @@ fn finds_name_scss() {
|
||||
tempfile!("finds_name_scss.scss", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
@ -99,7 +97,7 @@ fn finds_underscore_name_scss() {
|
||||
tempfile!("_finds_underscore_name_scss.scss", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
@ -111,7 +109,7 @@ fn chained_imports() {
|
||||
tempfile!("chained_imports__c.scss", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
@ -130,7 +128,7 @@ fn chained_imports_in_directory() {
|
||||
tempfile!("chained_imports_in_directory__c.scss", "$a: red;");
|
||||
assert_eq!(
|
||||
"a {\n color: red;\n}\n",
|
||||
&StyleSheet::new(input.to_string()).expect(input)
|
||||
&grass::from_string(input.to_string()).expect(input)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ macro_rules! test {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $func() {
|
||||
let sass = grass::StyleSheet::new($input.to_string())
|
||||
let sass = grass::from_string($input.to_string())
|
||||
.expect(concat!("failed to parse on ", $input));
|
||||
assert_eq!(
|
||||
String::from($input),
|
||||
@ -20,7 +20,7 @@ macro_rules! test {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $func() {
|
||||
let sass = grass::StyleSheet::new($input.to_string())
|
||||
let sass = grass::from_string($input.to_string())
|
||||
.expect(concat!("failed to parse on ", $input));
|
||||
assert_eq!(
|
||||
String::from($output),
|
||||
@ -39,7 +39,7 @@ macro_rules! error {
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $func() {
|
||||
match grass::StyleSheet::new($input.to_string()) {
|
||||
match grass::from_string($input.to_string()) {
|
||||
Ok(..) => panic!("did not fail"),
|
||||
Err(e) => assert_eq!($err, e.to_string()
|
||||
.chars()
|
||||
|
@ -233,4 +233,9 @@ test!(
|
||||
"a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n",
|
||||
"a {\n color: .c .d, .e + .d;\n}\n"
|
||||
);
|
||||
test!(
|
||||
list_partial_no_op,
|
||||
"a {\n color: selector-extend(\"c, d\", \"d\", \"e\");\n}\n",
|
||||
"a {\n color: c, d, e;\n}\n"
|
||||
);
|
||||
// todo: https://github.com/sass/sass-spec/tree/master/spec/core_functions/selector/extend/simple/pseudo/selector/
|
||||
|
@ -124,6 +124,11 @@ test!(
|
||||
"a {\n webkit: {\n webkit: {\n color: red;\n }\n }\n}\n",
|
||||
"a {\n webkit-webkit-color: red;\n}\n"
|
||||
);
|
||||
test!(
|
||||
no_space_after_colon_before_nested_style,
|
||||
"a {\n foo:{\n bar: baz\n }\n}\n",
|
||||
"a {\n foo-bar: baz;\n}\n"
|
||||
);
|
||||
test!(
|
||||
no_space_between_colon,
|
||||
"a {\n color:red;\n}\n",
|
||||
|
Loading…
x
Reference in New Issue
Block a user