Refactor parsing into struct, rather than standalone functions

reimplement parsing
This commit is contained in:
Connor Skees 2020-06-16 20:37:10 -04:00 committed by GitHub
commit 2ad1b70f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 4870 additions and 5704 deletions

View File

@ -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

View File

@ -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));
}
}
}

View File

@ -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 }))
}

View File

@ -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,
}))
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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,
})
}
}

View File

@ -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)
}

View File

@ -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"),
})
}
}

View File

@ -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(())
}

View File

@ -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,
})
}
}

View File

@ -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,
})
}

View File

@ -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))

View File

@ -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((

View File

@ -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((

View File

@ -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((

View File

@ -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>>>>()?;

View File

@ -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) {

View File

@ -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);
}

View File

@ -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();

View File

@ -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));
}

View File

@ -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)
}

View File

@ -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((

View File

@ -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(())

View File

@ -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;

View File

@ -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};

View File

@ -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))
}

View File

@ -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;

View File

@ -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))
}

View File

@ -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)

View File

@ -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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

1121
src/parse/value.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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.
///

View File

@ -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

View File

@ -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())

View File

@ -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)
}
}
}

View File

@ -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
);
}
}

View File

@ -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;

View File

@ -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>>(

View File

@ -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, "//"

View File

@ -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,
})
}

View File

@ -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;

View File

@ -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 {

View File

@ -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};

View File

@ -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};

View File

@ -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,
})
}

View File

@ -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))
}

View File

@ -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,
))
}

View File

@ -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)>);

View File

@ -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>> {

View File

@ -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};

View File

@ -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;

View File

@ -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>> {

View File

@ -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")
}
}))
}
}

View File

@ -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),
}
}
}

View File

@ -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
View 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."
);

View File

@ -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.");

View File

@ -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"
);

View File

@ -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."

View File

@ -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)
);
}

View File

@ -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()

View File

@ -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/

View File

@ -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",