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 The large features remaining are
``` ```
builtin functions content-exists, min, max builtin functions min, max
@extend (~600 tests) @extend (~600 tests)
indented syntax (27 tests) indented syntax (27 tests)
css imports css imports
@ -39,7 +39,6 @@ css imports
@forward (~400 tests) @forward (~400 tests)
@keyframes (~30 tests) @keyframes (~30 tests)
@supports (~128 tests) @supports (~128 tests)
@each inside @function
``` ```
## Features ## Features
@ -74,6 +73,13 @@ cargo b --release
These numbers come from a default run of the sass specification as shown above. 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 2020-06-07
PASSING: 2442 PASSING: 2442

View File

@ -1,21 +1,13 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::mem;
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator; use crate::{
common::Identifier,
use crate::common::Identifier; error::SassResult,
use crate::error::SassResult; parse::Parser,
use crate::scope::Scope; {Cow, Token},
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::value::Value;
use crate::Cow;
use crate::Token;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct FuncArgs(pub Vec<FuncArg>); pub(crate) struct FuncArgs(pub Vec<FuncArg>);
@ -34,10 +26,10 @@ impl FuncArgs {
} }
#[derive(Debug, Clone)] #[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)] #[derive(Debug, Clone, Hash, Eq, PartialEq)]
enum CallArg { pub(crate) enum CallArg {
Named(Identifier), Named(Identifier),
Positional(usize), Positional(usize),
} }
@ -63,11 +55,7 @@ impl CallArgs {
CallArgs(HashMap::new(), span) CallArgs(HashMap::new(), span)
} }
pub fn to_css_string( pub fn to_css_string(self, parser: &mut Parser<'_>) -> SassResult<Spanned<String>> {
self,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Spanned<String>> {
let mut string = String::with_capacity(2 + self.len() * 10); let mut string = String::with_capacity(2 + self.len() * 10);
string.push('('); string.push('(');
let mut span = self.1; 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, Ok(v) => v,
Err(..) => { Err(..) => {
return Err(("Plain CSS functions don't support keyword arguments.", span).into()) return Err(("Plain CSS functions don't support keyword arguments.", span).into())
@ -103,73 +91,32 @@ impl CallArgs {
/// Get argument by name /// Get argument by name
/// ///
/// Removes the argument /// Removes the argument
pub fn get_named<T: Into<Identifier>>( pub fn get_named<T: Into<Identifier>>(&mut self, val: T) -> Option<Vec<Token>> {
&mut self, self.0.remove(&CallArg::Named(val.into()))
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,
}
} }
/// Get a positional argument by 0-indexed position /// Get a positional argument by 0-indexed position
/// ///
/// Removes the argument /// Removes the argument
pub fn get_positional( pub fn get_positional(&mut self, val: usize) -> Option<Vec<Token>> {
&mut self, self.0.remove(&CallArg::Positional(val))
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<T: Into<Identifier>>( pub fn get<T: Into<Identifier>>(&mut self, position: usize, name: T) -> Option<Vec<Token>> {
&mut self, match self.get_named(name) {
position: usize,
name: T,
scope: &Scope,
super_selector: &Selector,
) -> Option<SassResult<Spanned<Value>>> {
match self.get_named(name, scope, super_selector) {
Some(v) => Some(v), Some(v) => Some(v),
None => self.get_positional(position, scope, super_selector), None => self.get_positional(position),
} }
} }
pub fn get_variadic( pub fn get_err(&mut self, position: usize, name: &'static str) -> SassResult<Vec<Token>> {
self, match self.get_named(name) {
scope: &Scope, Some(v) => Ok(v),
super_selector: &Selector, None => match self.get_positional(position) {
) -> SassResult<Vec<Spanned<Value>>> { Some(v) => Ok(v),
let mut vals = Vec::new(); None => Err((format!("Missing argument ${}.", name), self.span()).into()),
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)?);
} }
Ok(vals)
} }
/// Decrement all positional arguments by 1 /// Decrement all positional arguments by 1
@ -219,201 +166,3 @@ impl CallArgs {
Ok(()) 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 crate::{args::FuncArgs, scope::Scope, Token};
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};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Function { pub(crate) struct Function {
scope: Scope, pub scope: Scope,
args: FuncArgs, pub args: FuncArgs,
body: Vec<Token>, pub body: Vec<Token>,
pos: Span, pos: Span,
} }
@ -41,151 +27,4 @@ impl Function {
pos, 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 std::vec::IntoIter;
use codemap::{Span, Spanned};
use peekmore::{PeekMore, PeekMoreIterator}; use peekmore::{PeekMore, PeekMoreIterator};
use super::ruleset_eval; use crate::{args::FuncArgs, scope::Scope, Token};
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};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Mixin { pub(crate) struct Mixin {
scope: Scope, pub scope: Scope,
args: FuncArgs, pub args: FuncArgs,
body: PeekMoreIterator<IntoIter<Token>>, pub body: PeekMoreIterator<IntoIter<Token>>,
pub accepts_content_block: bool,
} }
impl Mixin { 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(); let body = body.into_iter().peekmore();
Mixin { scope, args, body } Mixin {
} scope,
args,
pub fn decl_from_tokens<I: Iterator<Item = Token>>( body,
toks: &mut PeekMoreIterator<I>, accepts_content_block,
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)?;
} }
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 function::Function;
pub(crate) use if_rule::If;
pub(crate) use kind::AtRuleKind; pub(crate) use kind::AtRuleKind;
use media::Media; pub(crate) use mixin::Mixin;
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};
mod each_rule;
mod for_rule;
mod function; mod function;
mod if_rule;
mod kind; mod kind;
mod media;
mod mixin; 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 num_traits::One;
use crate::args::CallArgs; use crate::{
use crate::color::Color; args::CallArgs,
use crate::common::QuoteKind; color::Color,
use crate::error::SassResult; common::QuoteKind,
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; unit::Unit,
use crate::value::{Number, Value}; value::{Number, Value},
};
fn inner_hsl( fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
name: &'static str,
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
if args.is_empty() { if args.is_empty() {
return Err(("Missing argument $channels.", args.span()).into()); return Err(("Missing argument $channels.", args.span()).into());
} }
if args.len() == 1 { 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, Value::List(v, ..) => v,
_ => return Err(("Missing argument $channels.", args.span()).into()), _ => return Err(("Missing argument $channels.", args.span()).into()),
}; };
@ -87,11 +83,11 @@ fn inner_hsl(
Number::one(), Number::one(),
)))) ))))
} else { } else {
let hue = match arg!(args, scope, super_selector, 0, "hue") { let hue = match parser.arg(&mut args, 0, "hue")? {
Value::Dimension(n, _) => n, Value::Dimension(n, _) => n,
v if v.is_special_function() => { v if v.is_special_function() => {
let saturation = arg!(args, scope, super_selector, 1, "saturation"); let saturation = parser.arg(&mut args, 1, "saturation")?;
let lightness = arg!(args, scope, super_selector, 2, "lightness"); let lightness = parser.arg(&mut args, 2, "lightness")?;
let mut string = format!( let mut string = format!(
"{}({}, {}, {}", "{}({}, {}, {}",
name, name,
@ -102,7 +98,8 @@ fn inner_hsl(
if !args.is_empty() { if !args.is_empty() {
string.push_str(", "); string.push_str(", ");
string.push_str( string.push_str(
&arg!(args, scope, super_selector, 3, "alpha") &parser
.arg(&mut args, 3, "alpha")?
.to_css_string(args.span())?, .to_css_string(args.span())?,
); );
} }
@ -117,10 +114,10 @@ fn inner_hsl(
.into()) .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), Value::Dimension(n, _) => n / Number::from(100),
v if v.is_special_function() => { 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!( let mut string = format!(
"{}({}, {}, {}", "{}({}, {}, {}",
name, name,
@ -131,7 +128,8 @@ fn inner_hsl(
if !args.is_empty() { if !args.is_empty() {
string.push_str(", "); string.push_str(", ");
string.push_str( string.push_str(
&arg!(args, scope, super_selector, 3, "alpha") &parser
.arg(&mut args, 3, "alpha")?
.to_css_string(args.span())?, .to_css_string(args.span())?,
); );
} }
@ -149,7 +147,7 @@ fn inner_hsl(
.into()) .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), Value::Dimension(n, _) => n / Number::from(100),
v if v.is_special_function() => { v if v.is_special_function() => {
let mut string = format!( let mut string = format!(
@ -162,7 +160,8 @@ fn inner_hsl(
if !args.is_empty() { if !args.is_empty() {
string.push_str(", "); string.push_str(", ");
string.push_str( string.push_str(
&arg!(args, scope, super_selector, 3, "alpha") &parser
.arg(&mut args, 3, "alpha")?
.to_css_string(args.span())?, .to_css_string(args.span())?,
); );
} }
@ -180,13 +179,12 @@ fn inner_hsl(
.into()) .into())
} }
}; };
let alpha = match arg!( let alpha = match parser.default_arg(
args, &mut args,
scope,
super_selector,
3, 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::None) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100), Value::Dimension(n, Unit::Percent) => n / Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -226,17 +224,17 @@ fn inner_hsl(
} }
} }
fn hsl(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn hsl(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
inner_hsl("hsl", args, scope, super_selector) inner_hsl("hsl", args, parser)
} }
fn hsla(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
inner_hsl("hsla", args, scope, super_selector) 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)?; 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)), Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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)), Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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)), Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -282,7 +280,7 @@ fn adjust_hue(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
.into()) .into())
} }
}; };
let degrees = match arg!(args, scope, super_selector, 1, "degrees") { let degrees = match parser.arg(&mut args, 1, "degrees")? {
Value::Dimension(n, _) => n, Value::Dimension(n, _) => n,
v => { v => {
return Err(( 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)))) 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -310,7 +308,7 @@ fn lighten(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( 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)))) 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -338,7 +336,7 @@ fn darken(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( 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)))) 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)?; args.max_args(2)?;
if args.len() == 1 { if args.len() == 1 {
return Ok(Value::String( return Ok(Value::String(
format!( format!(
"saturate({})", "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, 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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
@ -379,7 +379,7 @@ fn saturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
.into()) .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::Color(c) => c,
Value::Dimension(n, u) => { Value::Dimension(n, u) => {
return Ok(Value::String( 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)))) 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -410,7 +410,7 @@ fn desaturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( 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)))) 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)?; 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::Color(c) => c,
Value::Dimension(n, u) => { Value::Dimension(n, u) => {
return Ok(Value::String( 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())))) 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)?; 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::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -462,15 +462,14 @@ fn complement(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
Ok(Value::Color(Box::new(color.complement()))) 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)?; args.max_args(2)?;
let weight = match arg!( let weight = match parser.default_arg(
args, &mut args,
scope,
super_selector,
1, 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), Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((
@ -483,7 +482,7 @@ fn invert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
.into()) .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::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))),
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent) => {
Ok(Value::String(format!("invert({}%)", n), QuoteKind::None)) Ok(Value::String(format!("invert({}%)", n), QuoteKind::None))

View File

@ -1,17 +1,13 @@
use super::{Builtin, GlobalFunctionMap}; use super::{Builtin, GlobalFunctionMap};
use crate::args::CallArgs; use crate::{
use crate::common::QuoteKind; args::CallArgs, common::QuoteKind, error::SassResult, parse::Parser, unit::Unit, value::Number,
use crate::error::SassResult; value::Value,
use crate::scope::Scope; };
use crate::selector::Selector;
use crate::unit::Unit;
use crate::value::Number;
use crate::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)?; 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::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
Value::Dimension(num, unit) => Ok(Value::String( Value::Dimension(num, unit) => Ok(Value::String(
format!("opacity({}{})", num, unit), 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -49,7 +45,7 @@ fn opacify(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( 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)))) 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -77,7 +73,7 @@ fn fade_in(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( 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)))) Ok(Value::Color(Box::new(color.fade_in(amount))))
} }
fn transparentize( fn transparentize(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -109,7 +101,7 @@ fn transparentize(
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( return Err((
@ -125,9 +117,9 @@ fn transparentize(
Ok(Value::Color(Box::new(color.fade_out(amount)))) 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -137,7 +129,7 @@ fn fade_out(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
.into()) .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), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => { v => {
return Err(( return Err((

View File

@ -2,18 +2,19 @@ use super::{Builtin, GlobalFunctionMap};
use num_traits::{One, Signed, Zero}; use num_traits::{One, Signed, Zero};
use crate::args::CallArgs; use crate::{
use crate::color::Color; args::CallArgs,
use crate::common::QuoteKind; color::Color,
use crate::error::SassResult; common::QuoteKind,
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; unit::Unit,
use crate::value::{Number, Value}; value::{Number, Value},
};
macro_rules! opt_rgba { macro_rules! opt_rgba {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { 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::Dimension(n, u) => Some(bound!($args, $arg, n, u, $low, $high)),
Value::Null => None, Value::Null => None,
v => { v => {
@ -32,8 +33,8 @@ macro_rules! opt_rgba {
} }
macro_rules! opt_hsl { macro_rules! opt_hsl {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
Value::Dimension(n, u) => { Value::Dimension(n, u) => {
Some(bound!($args, $arg, n, u, $low, $high) / Number::from(100)) 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> { fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
if args.get_positional(1, scope, super_selector).is_some() { if parser.positional_arg(&mut args, 1).is_some() {
return Err(( return Err((
"Only one positional argument is allowed. All other arguments must be passed by name.", "Only one positional argument is allowed. All other arguments must be passed by name.",
args.span(), args.span(),
@ -62,7 +63,7 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
.into()); .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::Color(c) => c,
v => { v => {
return Err(( 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, alpha, "alpha", 0, 1, parser);
opt_rgba!(args, red, "red", 0, 255, scope, super_selector); opt_rgba!(args, red, "red", 0, 255, parser);
opt_rgba!(args, green, "green", 0, 255, scope, super_selector); opt_rgba!(args, green, "green", 0, 255, parser);
opt_rgba!(args, blue, "blue", 0, 255, scope, super_selector); opt_rgba!(args, blue, "blue", 0, 255, parser);
if red.is_some() || green.is_some() || blue.is_some() { if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba( 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::Dimension(n, _) => Some(n),
Value::Null => None, Value::Null => None,
v => { v => {
@ -99,16 +100,8 @@ fn change_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
} }
}; };
opt_hsl!( opt_hsl!(args, saturation, "saturation", 0, 100, parser);
args, opt_hsl!(args, luminance, "lightness", 0, 100, parser);
saturation,
"saturation",
0,
100,
scope,
super_selector
);
opt_hsl!(args, luminance, "lightness", 0, 100, scope, super_selector);
if hue.is_some() || saturation.is_some() || luminance.is_some() { if hue.is_some() || saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc. // 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> { fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match parser.arg(&mut args, 0, "color")? {
Value::Color(c) => c, Value::Color(c) => c,
v => { v => {
return Err(( 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, alpha, "alpha", -1, 1, parser);
opt_rgba!(args, red, "red", -255, 255, scope, super_selector); opt_rgba!(args, red, "red", -255, 255, parser);
opt_rgba!(args, green, "green", -255, 255, scope, super_selector); opt_rgba!(args, green, "green", -255, 255, parser);
opt_rgba!(args, blue, "blue", -255, 255, scope, super_selector); opt_rgba!(args, blue, "blue", -255, 255, parser);
if red.is_some() || green.is_some() || blue.is_some() { if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba( 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::Dimension(n, _) => Some(n),
Value::Null => None, Value::Null => None,
v => { v => {
@ -166,24 +159,8 @@ fn adjust_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
} }
}; };
opt_hsl!( opt_hsl!(args, saturation, "saturation", -100, 100, parser);
args, opt_hsl!(args, luminance, "lightness", -100, 100, parser);
saturation,
"saturation",
-100,
100,
scope,
super_selector
);
opt_hsl!(
args,
luminance,
"lightness",
-100,
100,
scope,
super_selector
);
if hue.is_some() || saturation.is_some() || luminance.is_some() { if hue.is_some() || saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc. // 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 { fn scale(val: Number, by: Number, max: Number) -> Number {
if by.is_zero() { if by.is_zero() {
return val; return val;
@ -213,7 +192,7 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
} }
let span = args.span(); 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -225,8 +204,8 @@ fn scale_color(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
}; };
macro_rules! opt_scale_arg { macro_rules! opt_scale_arg {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $scope:ident, $super_selector:ident) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal, $parser:ident) => {
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { let $name = match $parser.default_named_arg(&mut $args, $arg, Value::Null)? {
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent) => {
Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100)) 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, alpha, "alpha", -100, 100, parser);
opt_scale_arg!(args, red, "red", -100, 100, scope, super_selector); opt_scale_arg!(args, red, "red", -100, 100, parser);
opt_scale_arg!(args, green, "green", -100, 100, scope, super_selector); opt_scale_arg!(args, green, "green", -100, 100, parser);
opt_scale_arg!(args, blue, "blue", -100, 100, scope, super_selector); opt_scale_arg!(args, blue, "blue", -100, 100, parser);
if red.is_some() || green.is_some() || blue.is_some() { if red.is_some() || green.is_some() || blue.is_some() {
return Ok(Value::Color(Box::new(Color::from_rgba( 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!( opt_scale_arg!(args, saturation, "saturation", -100, 100, parser);
args, opt_scale_arg!(args, luminance, "lightness", -100, 100, parser);
saturation,
"saturation",
-100,
100,
scope,
super_selector
);
opt_scale_arg!(
args,
luminance,
"lightness",
-100,
100,
scope,
super_selector
);
if saturation.is_some() || luminance.is_some() { if saturation.is_some() || luminance.is_some() {
// Color::as_hsla() returns more exact values than Color::hue(), etc. // 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)?; 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::Color(c) => c,
v => { v => {
return Err(( return Err((

View File

@ -2,28 +2,26 @@ use super::{Builtin, GlobalFunctionMap};
use num_traits::One; use num_traits::One;
use crate::args::CallArgs; use crate::{
use crate::color::Color; args::CallArgs,
use crate::common::QuoteKind; color::Color,
use crate::error::SassResult; common::QuoteKind,
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; unit::Unit,
use crate::value::{Number, Value}; value::{Number, Value},
};
/// name: Either `rgb` or `rgba` depending on the caller /// name: Either `rgb` or `rgba` depending on the caller
fn inner_rgb( // todo: refactor into smaller functions
name: &'static str, #[allow(clippy::cognitive_complexity)]
mut args: CallArgs, fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
if args.is_empty() { if args.is_empty() {
return Err(("Missing argument $channels.", args.span()).into()); return Err(("Missing argument $channels.", args.span()).into());
} }
if args.len() == 1 { 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, Value::List(v, ..) => v,
_ => return Err(("Missing argument $channels.", args.span()).into()), _ => return Err(("Missing argument $channels.", args.span()).into()),
}; };
@ -121,10 +119,10 @@ fn inner_rgb(
Ok(Value::Color(Box::new(color))) Ok(Value::Color(Box::new(color)))
} else if args.len() == 2 { } 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, Value::Color(c) => c,
v if v.is_special_function() => { 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( return Ok(Value::String(
format!( format!(
"{}({}, {})", "{}({}, {})",
@ -143,7 +141,7 @@ fn inner_rgb(
.into()) .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::None) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100), Value::Dimension(n, Unit::Percent) => n / Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -179,7 +177,7 @@ fn inner_rgb(
}; };
Ok(Value::Color(Box::new(color.with_alpha(alpha)))) Ok(Value::Color(Box::new(color.with_alpha(alpha))))
} else { } 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::None) => n,
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -193,8 +191,8 @@ fn inner_rgb(
.into()) .into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let green = arg!(args, scope, super_selector, 1, "green"); let green = parser.arg(&mut args, 1, "green")?;
let blue = arg!(args, scope, super_selector, 2, "blue"); let blue = parser.arg(&mut args, 2, "blue")?;
let mut string = format!( let mut string = format!(
"{}({}, {}, {}", "{}({}, {}, {}",
name, name,
@ -205,7 +203,8 @@ fn inner_rgb(
if !args.is_empty() { if !args.is_empty() {
string.push_str(", "); string.push_str(", ");
string.push_str( string.push_str(
&arg!(args, scope, super_selector, 3, "alpha") &parser
.arg(&mut args, 3, "alpha")?
.to_css_string(args.span())?, .to_css_string(args.span())?,
); );
} }
@ -220,7 +219,7 @@ fn inner_rgb(
.into()) .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::None) => n,
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -234,7 +233,7 @@ fn inner_rgb(
.into()) .into())
} }
v if v.is_special_function() => { 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!( let mut string = format!(
"{}({}, {}, {}", "{}({}, {}, {}",
name, name,
@ -245,7 +244,8 @@ fn inner_rgb(
if !args.is_empty() { if !args.is_empty() {
string.push_str(", "); string.push_str(", ");
string.push_str( string.push_str(
&arg!(args, scope, super_selector, 3, "alpha") &parser
.arg(&mut args, 3, "alpha")?
.to_css_string(args.span())?, .to_css_string(args.span())?,
); );
} }
@ -260,7 +260,7 @@ fn inner_rgb(
.into()) .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::None) => n,
Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255), Value::Dimension(n, Unit::Percent) => (n / Number::from(100)) * Number::from(255),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -284,7 +284,8 @@ fn inner_rgb(
if !args.is_empty() { if !args.is_empty() {
string.push_str(", "); string.push_str(", ");
string.push_str( string.push_str(
&arg!(args, scope, super_selector, 3, "alpha") &parser
.arg(&mut args, 3, "alpha")?
.to_css_string(args.span())?, .to_css_string(args.span())?,
); );
} }
@ -299,13 +300,12 @@ fn inner_rgb(
.into()) .into())
} }
}; };
let alpha = match arg!( let alpha = match parser.default_arg(
args, &mut args,
scope,
super_selector,
3, 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::None) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100), Value::Dimension(n, Unit::Percent) => n / Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
@ -343,17 +343,17 @@ fn inner_rgb(
} }
} }
fn rgb(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn rgb(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
inner_rgb("rgb", args, scope, super_selector) inner_rgb("rgb", args, parser)
} }
fn rgba(args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
inner_rgb("rgba", args, scope, super_selector) 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)?; 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)), Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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)), Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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)), Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None)),
v => Err(( v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?), 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)?; 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, Value::Color(c) => c,
v => { v => {
return Err(( 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, Value::Color(c) => c,
v => { v => {
return Err(( return Err((
@ -411,13 +411,12 @@ fn mix(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
} }
}; };
let weight = match arg!( let weight = match parser.default_arg(
args, &mut args,
scope,
super_selector,
2, 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), Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => { v => {
return Err(( return Err((

View File

@ -2,17 +2,18 @@ use super::{Builtin, GlobalFunctionMap};
use num_traits::{One, Signed, ToPrimitive, Zero}; use num_traits::{One, Signed, ToPrimitive, Zero};
use crate::args::CallArgs; use crate::{
use crate::common::{Brackets, ListSeparator, QuoteKind}; args::CallArgs,
use crate::error::SassResult; common::{Brackets, ListSeparator, QuoteKind},
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; unit::Unit,
use crate::value::{Number, Value}; 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)?; 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::List(v, ..) => Number::from(v.len()),
Value::Map(m) => Number::from(m.len()), Value::Map(m) => Number::from(m.len()),
_ => Number::one(), _ => Number::one(),
@ -20,10 +21,10 @@ fn length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
Ok(Value::Dimension(len, Unit::None)) 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)?; args.max_args(2)?;
let mut list = arg!(args, scope, super_selector, 0, "list").as_list(); let mut list = parser.arg(&mut args, 0, "list")?.as_list();
let n = match arg!(args, scope, super_selector, 1, "n") { let n = match parser.arg(&mut args, 1, "n")? {
Value::Dimension(num, _) => num, Value::Dimension(num, _) => num,
v => { v => {
return Err(( return Err((
@ -61,14 +62,10 @@ fn nth(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResu
})) }))
} }
fn list_separator( fn list_separator(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
Ok(Value::String( Ok(Value::String(
match arg!(args, scope, super_selector, 0, "list") { match parser.arg(&mut args, 0, "list")? {
Value::List(_, sep, ..) => sep.name(), Value::List(_, sep, ..) => sep.name(),
_ => ListSeparator::Space.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)?; 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::List(v, sep, b) => (v, sep, b),
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
v => (vec![v], ListSeparator::Space, 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, Value::Dimension(num, _) => num,
v => { v => {
return Err(( 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()); 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() { if n.is_positive() {
list[n.to_integer().to_usize().unwrap_or(std::usize::MAX) - 1] = val; 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)) 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)?; 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::List(v, sep, b) => (v, sep, b),
v => (vec![v], ListSeparator::Space, Brackets::None), v => (vec![v], ListSeparator::Space, Brackets::None),
}; };
let val = arg!(args, scope, super_selector, 1, "val"); let val = parser.arg(&mut args, 1, "val")?;
let sep = match arg!( let sep = match parser.default_arg(
args, &mut args,
scope,
super_selector,
2, 2,
"separator" = Value::String("auto".to_owned(), QuoteKind::None) "separator",
) { Value::String("auto".to_owned(), QuoteKind::None),
)? {
Value::String(s, ..) => match s.as_str() { Value::String(s, ..) => match s.as_str() {
"auto" => sep, "auto" => sep,
"comma" => ListSeparator::Comma, "comma" => ListSeparator::Comma,
@ -167,25 +163,24 @@ fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
Ok(Value::List(list, sep, brackets)) 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)?; 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::List(v, sep, brackets) => (v, sep, brackets),
Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None), Value::Map(m) => (m.entries(), ListSeparator::Comma, Brackets::None),
v => (vec![v], ListSeparator::Space, 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::List(v, sep, ..) => (v, sep),
Value::Map(m) => (m.entries(), ListSeparator::Comma), Value::Map(m) => (m.entries(), ListSeparator::Comma),
v => (vec![v], ListSeparator::Space), v => (vec![v], ListSeparator::Space),
}; };
let sep = match arg!( let sep = match parser.default_arg(
args, &mut args,
scope,
super_selector,
2, 2,
"separator" = Value::String("auto".to_owned(), QuoteKind::None) "separator",
) { Value::String("auto".to_owned(), QuoteKind::None),
)? {
Value::String(s, ..) => match s.as_str() { Value::String(s, ..) => match s.as_str() {
"auto" => { "auto" => {
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) { 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!( let brackets = match parser.default_arg(
args, &mut args,
scope,
super_selector,
3, 3,
"bracketed" = Value::String("auto".to_owned(), QuoteKind::None) "bracketed",
) { Value::String("auto".to_owned(), QuoteKind::None),
)? {
Value::String(s, ..) => match s.as_str() { Value::String(s, ..) => match s.as_str() {
"auto" => brackets, "auto" => brackets,
_ => Brackets::Bracketed, _ => Brackets::Bracketed,
@ -241,23 +235,21 @@ fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
Ok(Value::List(list1, sep, brackets)) 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)?; args.max_args(1)?;
Ok(Value::bool( Ok(Value::bool(match parser.arg(&mut args, 0, "list")? {
match arg!(args, scope, super_selector, 0, "list") { Value::List(.., brackets) => match brackets {
Value::List(.., brackets) => match brackets { Brackets::Bracketed => true,
Brackets::Bracketed => true, Brackets::None => false,
Brackets::None => false,
},
_ => 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)?; args.max_args(2)?;
let list = arg!(args, scope, super_selector, 0, "list").as_list(); let list = parser.arg(&mut args, 0, "list")?.as_list();
let value = arg!(args, scope, super_selector, 1, "value"); let value = parser.arg(&mut args, 1, "value")?;
// TODO: find a way around this unwrap. // TODO: find a way around this unwrap.
// It should be impossible to hit as the arg is // It should be impossible to hit as the arg is
// evaluated prior to checking equality, but // 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)) 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 span = args.span();
let lists = args let lists = parser
.get_variadic(scope, super_selector)? .variadic_args(args)?
.into_iter() .into_iter()
.map(|x| Ok(x.node.eval(span)?.node.as_list())) .map(|x| Ok(x.node.eval(span)?.node.as_list()))
.collect::<SassResult<Vec<Vec<Value>>>>()?; .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 { macro_rules! bound {
($args:ident, $name:literal, $arg:ident, $unit:ident, $low:literal, $high:literal) => { ($args:ident, $name:literal, $arg:ident, $unit:ident, $low:literal, $high:literal) => {
if $arg > Number::from($high) || $arg < Number::from($low) { if $arg > Number::from($high) || $arg < Number::from($low) {

View File

@ -1,16 +1,17 @@
use super::{Builtin, GlobalFunctionMap}; use super::{Builtin, GlobalFunctionMap};
use crate::args::CallArgs; use crate::{
use crate::common::{Brackets, ListSeparator}; args::CallArgs,
use crate::error::SassResult; common::{Brackets, ListSeparator},
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::value::{SassMap, Value}; 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)?; args.max_args(2)?;
let key = arg!(args, scope, super_selector, 1, "key"); let key = parser.arg(&mut args, 1, "key")?;
let map = match arg!(args, scope, super_selector, 0, "map") { let map = match parser.arg(&mut args, 0, "map")? {
Value::Map(m) => m, Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { 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)) 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)?; args.max_args(2)?;
let key = arg!(args, scope, super_selector, 1, "key"); let key = parser.arg(&mut args, 1, "key")?;
let map = match arg!(args, scope, super_selector, 0, "map") { let map = match parser.arg(&mut args, 0, "map")? {
Value::Map(m) => m, Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { 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())) 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)?; 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::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { 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)?; 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::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { 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)?; 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::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { v => {
@ -94,7 +95,7 @@ fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
.into()) .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::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { v => {
@ -109,8 +110,8 @@ fn map_merge(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
Ok(Value::Map(map1)) Ok(Value::Map(map1))
} }
fn map_remove(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn map_remove(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
let mut map = match arg!(args, scope, super_selector, 0, "map") { let mut map = match parser.arg(&mut args, 0, "map")? {
Value::Map(m) => m, Value::Map(m) => m,
Value::List(v, ..) if v.is_empty() => SassMap::new(), Value::List(v, ..) if v.is_empty() => SassMap::new(),
v => { v => {
@ -121,7 +122,7 @@ fn map_remove(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
.into()) .into())
} }
}; };
let keys = args.get_variadic(scope, super_selector)?; let keys = parser.variadic_args(args)?;
for key in keys { for key in keys {
map.remove(&key); map.remove(&key);
} }

View File

@ -5,16 +5,17 @@ use num_traits::{One, Signed, ToPrimitive, Zero};
#[cfg(feature = "random")] #[cfg(feature = "random")]
use rand::Rng; use rand::Rng;
use crate::args::CallArgs; use crate::{
use crate::error::SassResult; args::CallArgs,
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; unit::Unit,
use crate::value::{Number, Value}; 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)?; 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), Value::Dimension(n, Unit::None) => n * Number::from(100),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(( return Err((
@ -40,9 +41,9 @@ fn percentage(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
Ok(Value::Dimension(num, Unit::Percent)) 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)?; 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)), Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)),
v => Err(( v => Err((
format!( 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)?; 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)), Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)),
v => Err(( v => Err((
format!( 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)?; 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)), Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)),
v => Err(( v => Err((
format!( 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)?; 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)), Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)),
v => Err(( v => Err((
format!( 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)?; 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, Value::Dimension(_, u) => u,
v => { v => {
return Err(( return Err((
@ -115,7 +116,7 @@ fn comparable(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
.into()) .into())
} }
}; };
let unit2 = match arg!(args, scope, super_selector, 1, "number2") { let unit2 = match parser.arg(&mut args, 1, "number2")? {
Value::Dimension(_, u) => u, Value::Dimension(_, u) => u,
v => { v => {
return Err(( return Err((
@ -134,9 +135,9 @@ fn comparable(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
// TODO: write tests for this // TODO: write tests for this
#[cfg(feature = "random")] #[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)?; 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::Dimension(n, _) => n,
Value::Null => { Value::Null => {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();

View File

@ -2,31 +2,30 @@ use super::{Builtin, GlobalFunctionMap, GLOBAL_FUNCTIONS};
use codemap::Spanned; use codemap::Spanned;
use crate::args::CallArgs; use crate::{
use crate::common::QuoteKind; args::CallArgs,
use crate::error::SassResult; common::QuoteKind,
use crate::scope::global_var_exists; error::SassResult,
use crate::scope::Scope; parse::Parser,
use crate::selector::Selector; unit::Unit,
use crate::unit::Unit; value::{SassFunction, Value},
use crate::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)?; args.max_args(3)?;
if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? { if parser
Ok(arg!(args, scope, super_selector, 1, "if-true")) .arg(&mut args, 0, "condition")?
.is_true(args.span())?
{
Ok(parser.arg(&mut args, 1, "if-true")?)
} else { } else {
Ok(arg!(args, scope, super_selector, 2, "if-false")) Ok(parser.arg(&mut args, 2, "if-false")?)
} }
} }
fn feature_exists( fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "feature") { match parser.arg(&mut args, 0, "feature")? {
#[allow(clippy::match_same_arms)] #[allow(clippy::match_same_arms)]
Value::String(s, _) => Ok(match s.as_str() { Value::String(s, _) => Ok(match s.as_str() {
// A local variable will shadow a global variable unless // 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)?; 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(), Value::Dimension(_, u) => u.to_string(),
v => { v => {
return Err(( return Err((
@ -75,88 +74,44 @@ fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
Ok(Value::String(unit, QuoteKind::Quoted)) 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)?; args.max_args(1)?;
let value = arg!(args, scope, super_selector, 0, "value"); let value = parser.arg(&mut args, 0, "value")?;
Ok(Value::String( Ok(Value::String(
value.kind(args.span())?.to_owned(), value.kind(args.span())?.to_owned(),
QuoteKind::None, 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)?; args.max_args(1)?;
#[allow(clippy::match_same_arms)] #[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(_, Unit::None) => Value::True,
Value::Dimension(_, _) => Value::False, Value::Dimension(_, _) => Value::False,
_ => Value::True, _ => 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)?; args.max_args(1)?;
Ok(Value::String( Ok(Value::String(
arg!(args, scope, super_selector, 0, "value") parser
.arg(&mut args, 0, "value")?
.inspect(args.span())? .inspect(args.span())?
.into_owned(), .into_owned(),
QuoteKind::None, QuoteKind::None,
)) ))
} }
fn variable_exists( fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "name") { match parser.arg(&mut args, 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") {
Value::String(s, _) => Ok(Value::bool( 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(( v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?), 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)?; 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, Value::String(s, _) => s,
v => { v => {
return Err(( return Err((
@ -178,8 +175,10 @@ fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
.into()) .into())
} }
}; };
let css = arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?; let css = parser
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) { .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::String(s, ..) => Some(s),
Value::Null => None, Value::Null => None,
v => { v => {
@ -202,10 +201,13 @@ fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
.into()); .into());
} }
let func = match scope.get_fn(Spanned { let func = match parser.scopes.last().get_fn(
node: &name, Spanned {
span: args.span(), node: &name,
}) { span: args.span(),
},
parser.global_scope,
) {
Ok(f) => SassFunction::UserDefined(Box::new(f), name.into()), Ok(f) => SassFunction::UserDefined(Box::new(f), name.into()),
Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) { Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) {
Some(f) => SassFunction::Builtin(f.clone(), name.into()), 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)) Ok(Value::Function(func))
} }
fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn call(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
let func = match arg!(args, scope, super_selector, 0, "function") { let func = match parser.arg(&mut args, 0, "function")? {
Value::Function(f) => f, Value::Function(f) => f,
v => { v => {
return Err(( return Err((
@ -230,7 +232,20 @@ fn call(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
.into()) .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) { 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("function-exists", Builtin::new(function_exists));
f.insert("get-function", Builtin::new(get_function)); f.insert("get-function", Builtin::new(get_function));
f.insert("call", Builtin::new(call)); 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::{
use std::collections::HashMap; collections::HashMap,
use std::sync::atomic::{AtomicUsize, Ordering}; sync::atomic::{AtomicUsize, Ordering},
};
use crate::args::CallArgs; use once_cell::sync::Lazy;
use crate::error::SassResult;
use crate::scope::Scope; use crate::{args::CallArgs, error::SassResult, parse::Parser, value::Value};
use crate::selector::Selector;
use crate::value::Value;
#[macro_use] #[macro_use]
mod macros; mod macros;
@ -26,12 +25,12 @@ static FUNCTION_COUNT: AtomicUsize = AtomicUsize::new(0);
// TODO: impl Fn // TODO: impl Fn
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct Builtin( pub(crate) struct Builtin(
pub fn(CallArgs, &Scope, &Selector) -> SassResult<Value>, pub fn(CallArgs, &mut Parser<'_>) -> SassResult<Value>,
usize, usize,
); );
impl Builtin { 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); let count = FUNCTION_COUNT.fetch_add(1, Ordering::Relaxed);
Self(body, count) Self(body, count)
} }

View File

@ -1,54 +1,34 @@
use super::{Builtin, GlobalFunctionMap}; use super::{Builtin, GlobalFunctionMap};
use crate::args::CallArgs; use crate::{
use crate::common::{Brackets, ListSeparator, QuoteKind}; args::CallArgs,
use crate::error::SassResult; common::{Brackets, ListSeparator, QuoteKind},
use crate::scope::Scope; error::SassResult,
use crate::selector::{ parse::Parser,
ComplexSelector, ComplexSelectorComponent, Extender, Selector, SelectorList, selector::{ComplexSelector, ComplexSelectorComponent, Extender, Selector, SelectorList},
value::Value,
}; };
use crate::value::Value;
fn is_superselector( fn is_superselector(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let parent_selector = arg!(args, scope, super_selector, 0, "super").to_selector( let parent_selector = parser
args.span(), .arg(&mut args, 0, "super")?
scope, .to_selector(parser, "super", false)?;
super_selector, let child_selector = parser
"super", .arg(&mut args, 1, "sub")?
false, .to_selector(parser, "sub", false)?;
)?;
let child_selector = arg!(args, scope, super_selector, 1, "sub").to_selector(
args.span(),
scope,
super_selector,
"sub",
false,
)?;
Ok(Value::bool( Ok(Value::bool(
parent_selector.is_super_selector(&child_selector), parent_selector.is_super_selector(&child_selector),
)) ))
} }
fn simple_selectors( fn simple_selectors(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
// todo: Value::to_compound_selector // todo: Value::to_compound_selector
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector( let selector = parser
args.span(), .arg(&mut args, 0, "selector")?
scope, .to_selector(parser, "selector", false)?;
super_selector,
"selector",
false,
)?;
if selector.0.components.len() != 1 { if selector.0.components.len() != 1 {
return Err(("$selector: expected selector.", args.span()).into()); return Err(("$selector: expected selector.", args.span()).into());
@ -73,30 +53,24 @@ fn simple_selectors(
)) ))
} }
fn selector_parse( fn selector_parse(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
Ok(arg!(args, scope, super_selector, 0, "selector") Ok(parser
.to_selector(args.span(), scope, super_selector, "selector", false)? .arg(&mut args, 0, "selector")?
.to_selector(parser, "selector", false)?
.into_value()) .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 span = args.span();
let selectors = args.get_variadic(scope, super_selector)?; let selectors = parser.variadic_args(args)?;
if selectors.is_empty() { if selectors.is_empty() {
return Err(("$selectors: At least one selector must be passed.", span).into()); return Err(("$selectors: At least one selector must be passed.", span).into());
} }
Ok(selectors Ok(selectors
.into_iter() .into_iter()
.map(|sel| { .map(|sel| sel.node.to_selector(parser, "selectors", true))
sel.node
.to_selector(span, scope, super_selector, "selectors", true)
})
.collect::<SassResult<Vec<Selector>>>()? .collect::<SassResult<Vec<Selector>>>()?
.into_iter() .into_iter()
.fold(Selector::new(), |parent, child| { .fold(Selector::new(), |parent, child| {
@ -105,9 +79,9 @@ fn selector_nest(args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
.into_value()) .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 span = args.span();
let selectors = args.get_variadic(scope, super_selector)?; let selectors = parser.variadic_args(args)?;
if selectors.is_empty() { if selectors.is_empty() {
return Err(("$selectors: At least one selector must be passed.", span).into()); 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 let mut parsed_selectors = selectors
.into_iter() .into_iter()
.map(|s| { .map(|s| {
let tmp = s let tmp = s.node.to_selector(parser, "selectors", false)?;
.node
.to_selector(span, scope, super_selector, "selectors", false)?;
if tmp.contains_parent_selector() { if tmp.contains_parent_selector() {
Err(("Parent selectors aren't allowed here.", span).into()) Err(("Parent selectors aren't allowed here.", span).into())
} else { } else {
@ -164,80 +136,42 @@ fn selector_append(args: CallArgs, scope: &Scope, super_selector: &Selector) ->
.into_value()) .into_value())
} }
fn selector_extend( fn selector_extend(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(3)?; args.max_args(3)?;
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector( let selector = parser
args.span(), .arg(&mut args, 0, "selector")?
scope, .to_selector(parser, "selector", false)?;
super_selector, let target = parser
"selector", .arg(&mut args, 1, "extendee")?
false, .to_selector(parser, "extendee", false)?;
)?; let source = parser
let target = arg!(args, scope, super_selector, 1, "extendee").to_selector( .arg(&mut args, 2, "extender")?
args.span(), .to_selector(parser, "extender", false)?;
scope,
super_selector,
"extendee",
false,
)?;
let source = arg!(args, scope, super_selector, 2, "extender").to_selector(
args.span(),
scope,
super_selector,
"extender",
false,
)?;
Ok(Extender::extend(selector.0, source.0, target.0).to_sass_list()) Ok(Extender::extend(selector.0, source.0, target.0).to_sass_list())
} }
fn selector_replace( fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(3)?; args.max_args(3)?;
let selector = arg!(args, scope, super_selector, 0, "selector").to_selector( let selector = parser
args.span(), .arg(&mut args, 0, "selector")?
scope, .to_selector(parser, "selector", false)?;
super_selector, let target = parser
"selector", .arg(&mut args, 1, "original")?
false, .to_selector(parser, "original", false)?;
)?; let source =
let target = arg!(args, scope, super_selector, 1, "original").to_selector( parser
args.span(), .arg(&mut args, 2, "replacement")?
scope, .to_selector(parser, "replacement", false)?;
super_selector,
"original",
false,
)?;
let source = arg!(args, scope, super_selector, 2, "replacement").to_selector(
args.span(),
scope,
super_selector,
"replacement",
false,
)?;
Ok(Extender::replace(selector.0, source.0, target.0).to_sass_list()) Ok(Extender::replace(selector.0, source.0, target.0).to_sass_list())
} }
fn selector_unify( fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let selector1 = arg!(args, scope, super_selector, 0, "selector1").to_selector( let selector1 =
args.span(), parser
scope, .arg(&mut args, 0, "selector1")?
super_selector, .to_selector(parser, "selector1", false)?;
"selector1",
false,
)?;
if selector1.contains_parent_selector() { if selector1.contains_parent_selector() {
return Err(( return Err((
@ -247,13 +181,10 @@ fn selector_unify(
.into()); .into());
} }
let selector2 = arg!(args, scope, super_selector, 1, "selector2").to_selector( let selector2 =
args.span(), parser
scope, .arg(&mut args, 1, "selector2")?
super_selector, .to_selector(parser, "selector2", false)?;
"selector2",
false,
)?;
if selector2.contains_parent_selector() { if selector2.contains_parent_selector() {
return Err(( return Err((

View File

@ -6,21 +6,18 @@ use num_traits::{Signed, ToPrimitive, Zero};
#[cfg(feature = "random")] #[cfg(feature = "random")]
use rand::{distributions::Alphanumeric, thread_rng, Rng}; use rand::{distributions::Alphanumeric, thread_rng, Rng};
use crate::args::CallArgs; use crate::{
use crate::common::QuoteKind; args::CallArgs,
use crate::error::SassResult; common::QuoteKind,
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; unit::Unit,
use crate::value::{Number, Value}; value::{Number, Value},
};
fn to_upper_case( fn to_upper_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") { match parser.arg(&mut args, 0, "string")? {
Value::String(mut i, q) => { Value::String(mut i, q) => {
i.make_ascii_uppercase(); i.make_ascii_uppercase();
Ok(Value::String(i, q)) Ok(Value::String(i, q))
@ -36,13 +33,9 @@ fn to_upper_case(
} }
} }
fn to_lower_case( fn to_lower_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
mut args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") { match parser.arg(&mut args, 0, "string")? {
Value::String(mut i, q) => { Value::String(mut i, q) => {
i.make_ascii_lowercase(); i.make_ascii_lowercase();
Ok(Value::String(i, q)) 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)?; 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( Value::String(i, _) => Ok(Value::Dimension(
Number::from(i.chars().count()), Number::from(i.chars().count()),
Unit::None, 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)?; 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)), Value::String(i, _) => Ok(Value::String(i, QuoteKind::Quoted)),
v => Err(( v => Err((
format!( 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)?; 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()), i @ Value::String(..) => Ok(i.unquote()),
v => Err(( v => Err((
format!( 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)?; 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), Value::String(s, q) => (s, q),
v => { v => {
return Err(( 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 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() => { Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into()) 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()) .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() => { Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into()) 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)?; 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, Value::String(i, _) => i,
v => { v => {
return Err(( 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, Value::String(i, _) => i,
v => { v => {
return Err(( 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)?; 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), Value::String(i, q) => (i, q),
v => { v => {
return Err(( 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, Value::String(i, _) => i,
v => { v => {
return Err(( 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() => { Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("$index: {} is not an int.", n), args.span()).into()) 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")] #[cfg(feature = "random")]
#[allow(clippy::needless_pass_by_value)] #[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)?; args.max_args(0)?;
let mut rng = thread_rng(); let mut rng = thread_rng();
let string = std::iter::repeat(()) let string = std::iter::repeat(())

View File

@ -15,8 +15,10 @@
//! Named colors retain their original casing, //! Named colors retain their original casing,
//! so `rEd` should be emitted as `rEd`. //! so `rEd` should be emitted as `rEd`.
use std::cmp::{max, min}; use std::{
use std::fmt::{self, Display}; cmp::{max, min},
fmt::{self, Display},
};
use crate::value::Number; use crate::value::Number;
pub(crate) use name::NAMED_COLORS; pub(crate) use name::NAMED_COLORS;

View File

@ -1,7 +1,9 @@
use std::error::Error; use std::{
use std::fmt::{self, Display}; error::Error,
use std::io; fmt::{self, Display},
use std::string::FromUtf8Error; io,
string::FromUtf8Error,
};
use codemap::{Span, SpanLoc}; 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::{iter::Peekable, str::Chars, sync::Arc};
use std::str::Chars;
use std::sync::Arc;
use codemap::File; use codemap::File;

View File

@ -11,10 +11,8 @@ Spec progress as of 2020-05-01:
## Use as library ## Use as library
``` ```
use grass::{SassResult, StyleSheet}; fn main() -> Result<(), grass::Error> {
let sass = grass::from_string("a { b { color: &; } }".to_string())?;
fn main() -> SassResult<()> {
let sass = StyleSheet::new("a { b { color: &; } }".to_string())?;
assert_eq!(sass, "a b {\n color: a b;\n}\n"); assert_eq!(sass, "a b {\n color: a b;\n}\n");
Ok(()) Ok(())
} }
@ -88,32 +86,26 @@ grass input.scss
)] )]
#![cfg_attr(feature = "nightly", feature(track_caller))] #![cfg_attr(feature = "nightly", feature(track_caller))]
#![cfg_attr(feature = "profiling", inline(never))] #![cfg_attr(feature = "profiling", inline(never))]
use std::{fs, path::Path};
use std::convert::TryFrom;
use std::iter::Iterator;
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
pub(crate) use beef::lean::Cow; pub(crate) use beef::lean::Cow;
#[cfg(not(target_pointer_width = "64"))] #[cfg(not(target_pointer_width = "64"))]
pub(crate) use beef::Cow; 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 as Error, SassResult as Result};
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(crate) use crate::token::Token; pub(crate) use crate::token::Token;
use crate::utils::{ use crate::{
devour_whitespace, eat_comment, eat_ident, eat_variable_value, peek_ident_no_interpolation, lexer::Lexer,
peek_whitespace, read_until_closing_curly_brace, read_until_closing_paren, read_until_newline, output::Css,
VariableDecl, parse::{common::NeverEmptyVec, Parser},
scope::Scope,
selector::Selector,
}; };
use crate::value::Value;
mod args; mod args;
mod atrule; mod atrule;
@ -121,294 +113,101 @@ mod builtin;
mod color; mod color;
mod common; mod common;
mod error; mod error;
mod imports;
mod lexer; mod lexer;
mod output; mod output;
mod parse;
mod scope; mod scope;
mod selector; mod selector;
mod style; mod style;
mod stylesheet;
mod token; mod token;
mod unit; mod unit;
mod utils; mod utils;
mod value; mod value;
#[derive(Clone, Debug)] fn raw_to_parse_error(map: &CodeMap, err: Error) -> Error {
pub(crate) enum Stmt { let (message, span) = err.raw();
/// A [`Style`](/grass/style/struct.Style) Error::from_loc(message, map.look_up_span(span))
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),
} }
impl Stmt { /// Write CSS to `buf`, constructed from a path
const fn span(self, span: Span) -> Spanned<Self> {
Spanned { node: self, span }
}
}
/// Represents a single rule set. Rule sets can contain other rule sets
/// ///
/// ```scss /// ```
/// a { /// fn main() -> Result<(), grass::Error> {
/// color: blue; /// let sass = grass::from_path("input.scss")?;
/// b { /// Ok(())
/// color: red;
/// }
/// } /// }
/// ``` /// ```
#[derive(Clone, Debug)] #[cfg_attr(feature = "profiling", inline(never))]
pub(crate) struct RuleSet { #[cfg_attr(not(feature = "profiling"), inline)]
selector: Selector, #[cfg(not(feature = "wasm"))]
rules: Vec<Spanned<Stmt>>, pub fn from_path(p: &str) -> Result<String> {
// potential optimization: we don't *need* to own the selector let mut map = CodeMap::new();
super_selector: Selector, 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 /// Write CSS to `buf`, constructed from a string
/// todo! rename this ///
#[derive(Clone, Debug)] /// ```
enum Expr { /// fn main() -> Result<(), grass::Error> {
/// A style: `color: red` /// let sass = grass::from_string("a { b { color: &; } }".to_string())?;
Style(Box<Style>), /// assert_eq!(sass, "a b {\n color: a b;\n}\n");
/// Several styles /// Ok(())
Styles(Vec<Style>), /// }
/// A full selector `a > h1` /// ```
Selector(Selector), #[cfg_attr(feature = "wasm", wasm_bindgen)]
/// A variable declaration `$var: 1px` #[cfg_attr(feature = "profiling", inline(never))]
VariableDecl(String, Box<Spanned<Value>>), #[cfg_attr(not(feature = "profiling"), inline)]
/// A mixin declaration `@mixin foo {}` pub fn from_string(p: String) -> Result<String> {
MixinDecl(String, Box<Mixin>), let mut map = CodeMap::new();
FunctionDecl(String, Box<Function>), let file = map.add_file("stdin".into(), p);
/// A multiline comment: `/* foobar */` Css::from_stmts(
MultilineComment(String), Parser {
AtRule(AtRule), toks: &mut Lexer::new(&file)
} .collect::<Vec<Token>>()
.into_iter()
pub(crate) fn eat_expr<I: Iterator<Item = Token>>( .peekmore(),
toks: &mut PeekMoreIterator<I>, map: &mut map,
scope: &mut Scope, path: Path::new(""),
super_selector: &Selector, scopes: &mut NeverEmptyVec::new(Scope::new()),
content: Option<&[Spanned<Stmt>]>, global_scope: &mut Scope::new(),
) -> SassResult<Option<Spanned<Expr>>> { super_selectors: &mut NeverEmptyVec::new(Selector::new()),
let mut values = Vec::with_capacity(5); span_before: file.span.subspan(0, 0),
let mut span = match toks.peek() { content: None,
Some(tok) => tok.pos(), in_mixin: false,
None => return Ok(None), in_function: false,
}; in_control_flow: false,
while let Some(tok) = toks.peek() { at_root: true,
span = span.merge(tok.pos()); at_root_has_selector: false,
match tok.kind { }
':' => { .parse()
let tok = toks.next().unwrap(); .map_err(|e| raw_to_parse_error(&map, e))?,
values.push(tok); )
if devour_whitespace(toks) { .map_err(|e| raw_to_parse_error(&map, e))?
let prop = Style::parse_property( .pretty_print(&map)
&mut values.into_iter().peekmore(), .map_err(|e| raw_to_parse_error(&map, e))
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)
} }

View File

@ -1,9 +1,11 @@
use std::fs::File; use std::{
use std::io::{stdout, BufWriter, Write}; fs::File,
io::{stdout, BufWriter, Write},
};
use clap::{arg_enum, App, Arg}; use clap::{arg_enum, App, Arg};
use grass::StyleSheet; use grass::from_path;
arg_enum! { arg_enum! {
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -165,7 +167,7 @@ fn main() -> std::io::Result<()> {
if let Some(path) = matches.value_of("OUTPUT") { if let Some(path) = matches.value_of("OUTPUT") {
let mut buf = BufWriter::new(File::open(path).unwrap_or(File::create(path)?)); let mut buf = BufWriter::new(File::open(path).unwrap_or(File::create(path)?));
buf.write_all( buf.write_all(
StyleSheet::from_path(name) from_path(name)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
eprintln!("{}", e); eprintln!("{}", e);
std::process::exit(1) std::process::exit(1)
@ -175,7 +177,7 @@ fn main() -> std::io::Result<()> {
} else { } else {
let mut stdout = BufWriter::new(stdout()); let mut stdout = BufWriter::new(stdout());
stdout.write_all( stdout.write_all(
StyleSheet::from_path(name) from_path(name)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
eprintln!("{}", e); eprintln!("{}", e);
std::process::exit(1) std::process::exit(1)

View File

@ -1,17 +1,23 @@
//! # Convert from SCSS AST to CSS //! # Convert from SCSS AST to CSS
use std::io::Write; use std::io::Write;
use codemap::{CodeMap, Span}; use codemap::CodeMap;
use crate::atrule::AtRule; use crate::{error::SassResult, parse::Stmt, selector::Selector, style::Style};
use crate::error::SassResult;
use crate::{RuleSet, Selector, Stmt, Style, StyleSheet};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Toplevel { enum Toplevel {
RuleSet(Selector, Vec<BlockEntry>), RuleSet(Selector, Vec<BlockEntry>),
MultilineComment(String), MultilineComment(String),
AtRule(AtRule), UnknownAtRule {
name: String,
params: String,
body: Vec<Stmt>,
},
Media {
params: String,
body: Vec<Stmt>,
},
Newline, Newline,
Style(Box<Style>), Style(Box<Style>),
} }
@ -64,17 +70,17 @@ impl Css {
Css { blocks: Vec::new() } 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) Css::new().parse_stylesheet(s)
} }
fn parse_stmt(&mut self, stmt: Stmt) -> SassResult<Vec<Toplevel>> { fn parse_stmt(&mut self, stmt: Stmt) -> SassResult<Vec<Toplevel>> {
Ok(match stmt { Ok(match stmt {
Stmt::RuleSet(RuleSet { Stmt::RuleSet {
selector, selector,
super_selector, super_selector,
rules, body,
}) => { } => {
let selector = selector let selector = selector
.resolve_parent_selectors(&super_selector, true) .resolve_parent_selectors(&super_selector, true)
.remove_placeholders(); .remove_placeholders();
@ -82,30 +88,40 @@ impl Css {
return Ok(Vec::new()); return Ok(Vec::new());
} }
let mut vals = vec![Toplevel::new_rule(selector)]; let mut vals = vec![Toplevel::new_rule(selector)];
for rule in rules { for rule in body {
match rule.node { match rule {
Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule.node)?), Stmt::RuleSet { .. } => vals.extend(self.parse_stmt(rule)?),
Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(*s)?, Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(*s)?,
Stmt::MultilineComment(s) => vals.get_mut(0).unwrap().push_comment(s), Stmt::Comment(s) => vals.get_mut(0).unwrap().push_comment(s),
Stmt::AtRule(AtRule::AtRoot(stmts)) => stmts 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() .into_iter()
.map(|r| Ok(vals.extend(self.parse_stmt(r.node)?))) .map(|r| Ok(vals.extend(self.parse_stmt(r)?)))
.collect::<SassResult<()>>()?, .collect::<SassResult<()>>()?,
Stmt::AtRule(r) => vals.push(Toplevel::AtRule(r)),
}; };
} }
vals vals
} }
Stmt::MultilineComment(s) => vec![Toplevel::MultilineComment(s)], Stmt::Comment(s) => vec![Toplevel::MultilineComment(s)],
Stmt::Style(s) => vec![Toplevel::Style(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; let mut is_first = true;
for stmt in s.0 { for stmt in stmts {
let v = self.parse_stmt(stmt.node)?; let v = self.parse_stmt(stmt)?;
// this is how we print newlines between unrelated styles // this is how we print newlines between unrelated styles
// it could probably be refactored // it could probably be refactored
if !v.is_empty() { if !v.is_empty() {
@ -132,27 +148,6 @@ impl Css {
Ok(unsafe { String::from_utf8_unchecked(string) }) 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( fn _inner_pretty_print(
self, self,
buf: &mut Vec<u8>, buf: &mut Vec<u8>,
@ -178,39 +173,30 @@ impl Css {
has_written = true; has_written = true;
writeln!(buf, "{}/*{}*/", padding, s)?; writeln!(buf, "{}/*{}*/", padding, s)?;
} }
Toplevel::AtRule(r) => { Toplevel::UnknownAtRule { params, name, body } => {
match r { if params.is_empty() {
AtRule::Unknown(u) => { write!(buf, "{}@{}", padding, name)?;
if u.params.is_empty() { } else {
write!(buf, "{}@{}", padding, u.name)?; write!(buf, "{}@{} {}", padding, name, params)?;
} 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),
} }
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) => { Toplevel::Style(s) => {
writeln!(buf, "{}{}", padding, s.to_string()?)?; 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 std::collections::HashMap;
use codemap::Spanned; use codemap::Spanned;
use crate::atrule::{Function, Mixin}; use crate::{
use crate::common::Identifier; atrule::{Function, Mixin},
use crate::error::SassResult; builtin::GLOBAL_FUNCTIONS,
use crate::value::Value; common::Identifier,
error::SassResult,
value::Value,
};
thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new())); #[derive(Debug, Clone, Default)]
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)]
pub(crate) struct Scope { pub(crate) struct Scope {
vars: HashMap<Identifier, Spanned<Value>>, vars: HashMap<Identifier, Spanned<Value>>,
mixins: HashMap<Identifier, Mixin>, mixins: HashMap<Identifier, Mixin>,
functions: HashMap<Identifier, Function>, functions: HashMap<Identifier, Function>,
} }
// todo: separate struct for global scope?
impl Scope { impl Scope {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
@ -77,23 +28,22 @@ impl Scope {
} }
} }
pub const fn vars(&self) -> &HashMap<Identifier, Spanned<Value>> { fn get_var_no_global(&self, name: &Spanned<Identifier>) -> SassResult<Spanned<Value>> {
&self.vars 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> { pub fn get_var<T: Into<Identifier>>(
&self.functions &self,
} name: Spanned<T>,
global_scope: &Scope,
pub const fn mixins(&self) -> &HashMap<Identifier, Mixin> { ) -> SassResult<Spanned<Value>> {
&self.mixins
}
pub fn get_var<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Spanned<Value>> {
let name = name.map_node(Into::into); let name = name.map_node(Into::into);
match self.vars.get(&name.node) { match self.vars.get(&name.node) {
Some(v) => Ok(v.clone()), 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)?)) Ok(self.vars.insert(s.into(), node.eval(span)?))
} }
pub fn var_exists<T: Into<Identifier>>(&self, v: T) -> bool { pub fn var_exists_no_global(&self, name: &Identifier) -> bool {
let name = v.into(); self.vars.contains_key(name)
self.vars.contains_key(&name) || global_var_exists(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); let name = name.map_node(Into::into);
match self.mixins.get(&name.node) { match self.mixins.get(&name.node) {
Some(v) => Ok(v.clone()), 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) self.mixins.insert(s.into(), v)
} }
pub fn mixin_exists<T: Into<Identifier>>(&self, v: T) -> bool { fn mixin_exists_no_global(&self, name: &Identifier) -> bool {
let name = v.into(); self.mixins.contains_key(name)
self.mixins.contains_key(&name) || global_mixin_exists(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); let name = name.map_node(Into::into);
match self.functions.get(&name.node) { match self.functions.get(&name.node) {
Some(v) => Ok(v.clone()), 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) self.functions.insert(s.into(), v)
} }
pub fn fn_exists<T: Into<Identifier>>(&self, v: T) -> bool { fn fn_exists_no_global(&self, name: &Identifier) -> bool {
let name = v.into(); self.functions.contains_key(name)
self.functions.contains_key(&name) || global_fn_exists(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) { pub fn extend(&mut self, other: Scope) {
self.vars.extend(other.vars); self.vars.extend(other.vars);
self.mixins.extend(other.mixins); self.mixins.extend(other.mixins);

View File

@ -1,16 +1,10 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use peekmore::PeekMoreIterator;
use codemap::Span; use codemap::Span;
use super::{Namespace, QualifiedName, Selector}; use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value};
use crate::common::QuoteKind;
use crate::error::SassResult; use super::{Namespace, QualifiedName};
use crate::scope::Scope;
use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string};
use crate::value::Value;
use crate::Token;
#[derive(Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct Attribute { pub(crate) struct Attribute {
@ -21,31 +15,26 @@ pub(crate) struct Attribute {
span: Span, span: Span,
} }
fn attribute_name<I: Iterator<Item = Token>>( fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult<QualifiedName> {
toks: &mut PeekMoreIterator<I>, let next = parser.toks.peek().ok_or(("Expected identifier.", start))?;
scope: &Scope,
super_selector: &Selector,
start: Span,
) -> SassResult<QualifiedName> {
let next = toks.peek().ok_or(("Expected identifier.", start))?;
if next.kind == '*' { if next.kind == '*' {
let pos = next.pos; let pos = next.pos;
toks.next(); parser.toks.next();
if toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' { if parser.toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' {
return Err(("expected \"|\".", pos).into()); 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 { return Ok(QualifiedName {
ident, ident,
namespace: Namespace::Asterisk, namespace: Namespace::Asterisk,
}); });
} }
let span_before = next.pos; parser.span_before = next.pos;
let name_or_namespace = eat_ident(toks, scope, super_selector, span_before)?; let name_or_namespace = parser.parse_identifier()?;
match toks.peek() { match parser.toks.peek() {
Some(v) if v.kind != '|' => { Some(v) if v.kind != '|' => {
return Ok(QualifiedName { return Ok(QualifiedName {
ident: name_or_namespace.node, ident: name_or_namespace.node,
@ -55,32 +44,30 @@ fn attribute_name<I: Iterator<Item = Token>>(
Some(..) => {} Some(..) => {}
None => return Err(("expected more input.", name_or_namespace.span).into()), 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 == '=' => { Some(v) if v.kind == '=' => {
toks.reset_view(); parser.toks.reset_view();
return Ok(QualifiedName { return Ok(QualifiedName {
ident: name_or_namespace.node, ident: name_or_namespace.node,
namespace: Namespace::None, namespace: Namespace::None,
}); });
} }
Some(..) => { Some(..) => {
toks.reset_view(); parser.toks.reset_view();
} }
None => return Err(("expected more input.", name_or_namespace.span).into()), None => return Err(("expected more input.", name_or_namespace.span).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;
Ok(QualifiedName { Ok(QualifiedName {
ident, ident,
namespace: Namespace::Other(name_or_namespace.node), namespace: Namespace::Other(name_or_namespace.node),
}) })
} }
fn attribute_operator<I: Iterator<Item = Token>>( fn attribute_operator(parser: &mut Parser<'_>) -> SassResult<AttributeOp> {
toks: &mut PeekMoreIterator<I>, let start = parser.span_before;
start: Span, let op = match parser.toks.next().ok_or(("Expected \"]\".", start))?.kind {
) -> SassResult<AttributeOp> {
let op = match toks.next().ok_or(("Expected \"]\".", start))?.kind {
'=' => return Ok(AttributeOp::Equals), '=' => return Ok(AttributeOp::Equals),
'~' => AttributeOp::Include, '~' => AttributeOp::Include,
'|' => AttributeOp::Dash, '|' => AttributeOp::Dash,
@ -89,23 +76,25 @@ fn attribute_operator<I: Iterator<Item = Token>>(
'*' => AttributeOp::Contains, '*' => AttributeOp::Contains,
_ => return Err(("Expected \"]\".", start).into()), _ => 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()); return Err(("expected \"=\".", start).into());
} }
Ok(op) Ok(op)
} }
impl Attribute { impl Attribute {
pub fn from_tokens<I: Iterator<Item = Token>>( pub fn from_tokens(parser: &mut Parser<'_>) -> SassResult<Attribute> {
toks: &mut PeekMoreIterator<I>, let start = parser.span_before;
scope: &Scope, parser.whitespace();
super_selector: &Selector, let attr = attribute_name(parser, start)?;
start: Span, parser.whitespace();
) -> SassResult<Attribute> { if parser
devour_whitespace(toks); .toks
let attr = attribute_name(toks, scope, super_selector, start)?; .peek()
devour_whitespace(toks); .ok_or(("expected more input.", start))?
if toks.peek().ok_or(("expected more input.", start))?.kind == ']' { .kind
toks.next(); == ']'
{
parser.toks.next();
return Ok(Attribute { return Ok(Attribute {
attr, attr,
value: String::new(), value: String::new(),
@ -115,24 +104,25 @@ impl Attribute {
}); });
} }
let op = attribute_operator(toks, start)?; parser.span_before = start;
devour_whitespace(toks); let op = attribute_operator(parser)?;
parser.whitespace();
let peek = toks.peek().ok_or(("expected more input.", start))?; let peek = parser.toks.peek().ok_or(("expected more input.", start))?;
let span_before = peek.pos; parser.span_before = peek.pos;
let value = match peek.kind { let value = match peek.kind {
q @ '\'' | q @ '"' => { q @ '\'' | q @ '"' => {
toks.next(); parser.toks.next();
match parse_quoted_string(toks, scope, q, super_selector, span_before)?.node { match parser.parse_quoted_string(q)?.node {
Value::String(s, ..) => s, Value::String(s, ..) => s,
_ => unreachable!(), _ => 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 { let modifier = match peek.kind {
c if c.is_alphabetic() => Some(c), c if c.is_alphabetic() => Some(c),
@ -142,15 +132,15 @@ impl Attribute {
let pos = peek.pos(); let pos = peek.pos();
if modifier.is_some() { if modifier.is_some() {
toks.next(); parser.toks.next();
devour_whitespace(toks); parser.whitespace();
} }
if toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' { if parser.toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' {
return Err(("expected \"]\".", pos).into()); return Err(("expected \"]\".", pos).into());
} }
toks.next(); parser.toks.next();
Ok(Attribute { Ok(Attribute {
op, op,

View File

@ -1,4 +1,4 @@
use std::fmt::{self, Display}; use std::fmt;
/// The selector namespace. /// The selector namespace.
/// ///
@ -14,7 +14,7 @@ pub(crate) enum Namespace {
None, None,
} }
impl Display for Namespace { impl fmt::Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Empty => write!(f, "|"), Self::Empty => write!(f, "|"),
@ -31,7 +31,7 @@ pub(crate) struct QualifiedName {
pub namespace: Namespace, pub namespace: Namespace,
} }
impl Display for QualifiedName { impl fmt::Display for QualifiedName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.namespace)?; write!(f, "{}", self.namespace)?;
f.write_str(&self.ident) 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 std::fmt::{self, Display, Write};
use super::{CompoundSelector, Pseudo, SelectorList, SimpleSelector, Specificity};
/// A complex selector. /// A complex selector.
/// ///
/// A complex selector is composed of `CompoundSelector`s separated by /// A complex selector is composed of `CompoundSelector`s separated by

View File

@ -1,13 +1,15 @@
use std::collections::VecDeque;
use std::{ use std::{
collections::VecDeque,
fmt::{self, Write}, fmt::{self, Write},
mem, mem,
}; };
use super::{unify_complex, ComplexSelector, ComplexSelectorComponent}; use super::{unify_complex, ComplexSelector, ComplexSelectorComponent};
use crate::common::{Brackets, ListSeparator, QuoteKind}; use crate::{
use crate::value::Value; common::{Brackets, ListSeparator, QuoteKind},
value::Value,
};
/// A selector list. /// 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::value::Value;
use crate::Token;
pub(crate) use attribute::Attribute; pub(crate) use attribute::Attribute;
pub(crate) use common::*; pub(crate) use common::*;
@ -29,87 +23,13 @@ mod simple;
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Selector(pub SelectorList); pub(crate) struct Selector(pub SelectorList);
impl Display for Selector { impl fmt::Display for Selector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }
impl Selector { 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 /// 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. /// into `None`. This is a hack and in the future should be replaced.
// todo: take Option<Self> for parent // todo: take Option<Self> for parent

View File

@ -1,19 +1,16 @@
use peekmore::PeekMoreIterator;
use codemap::Span; use codemap::Span;
use crate::{
error::SassResult,
parse::Parser,
utils::{devour_whitespace, is_name, is_name_start, read_until_closing_paren},
Token,
};
use super::{ use super::{
Attribute, Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace, Attribute, Combinator, ComplexSelector, ComplexSelectorComponent, CompoundSelector, Namespace,
Pseudo, QualifiedName, SelectorList, SimpleSelector, 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)] #[derive(PartialEq)]
enum DevouredWhitespace { enum DevouredWhitespace {
@ -51,35 +48,27 @@ const SELECTOR_PSEUDO_CLASSES: [&str; 7] = [
/// Pseudo-element selectors that take unadorned selectors as arguments. /// Pseudo-element selectors that take unadorned selectors as arguments.
const SELECTOR_PSEUDO_ELEMENTS: [&str; 1] = ["slotted"]; 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 `&`. /// Whether this parser allows the parent selector `&`.
allows_parent: bool, allows_parent: bool,
/// Whether this parser allows placeholder selectors beginning with `%`. /// Whether this parser allows placeholder selectors beginning with `%`.
allows_placeholder: bool, allows_placeholder: bool,
toks: &'a mut PeekMoreIterator<I>, parser: &'a mut Parser<'b>,
scope: &'a Scope,
super_selector: &'a Selector,
span: Span, span: Span,
} }
impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> { impl<'a, 'b> SelectorParser<'a, 'b> {
pub fn new( pub fn new(
toks: &'a mut PeekMoreIterator<I>, parser: &'a mut Parser<'b>,
scope: &'a Scope,
super_selector: &'a Selector,
allows_parent: bool, allows_parent: bool,
allows_placeholder: bool, allows_placeholder: bool,
span: Span, span: Span,
) -> Self { ) -> Self {
Self { Self {
toks, parser,
scope,
super_selector,
allows_parent, allows_parent,
allows_placeholder, allows_placeholder,
span, span,
@ -88,7 +77,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
pub fn parse(mut self) -> SassResult<SelectorList> { pub fn parse(mut self) -> SassResult<SelectorList> {
let tmp = self.parse_selector_list()?; 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()); return Err(("expected selector.", self.span).into());
} }
Ok(tmp) Ok(tmp)
@ -97,14 +86,14 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
fn parse_selector_list(&mut self) -> SassResult<SelectorList> { fn parse_selector_list(&mut self) -> SassResult<SelectorList> {
let mut components = vec![self.parse_complex_selector(false)?]; let mut components = vec![self.parse_complex_selector(false)?];
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
let mut line_break = false; let mut line_break = false;
while let Some(Token { kind: ',', .. }) = self.toks.peek() { while let Some(Token { kind: ',', .. }) = self.parser.toks.peek() {
self.toks.next(); self.parser.toks.next();
line_break = self.eat_whitespace() == DevouredWhitespace::Newline || line_break; line_break = self.eat_whitespace() == DevouredWhitespace::Newline || line_break;
match self.toks.peek() { match self.parser.toks.peek() {
Some(Token { kind: ',', .. }) => continue, Some(Token { kind: ',', .. }) => continue,
Some(..) => {} Some(..) => {}
None => break, None => break,
@ -119,13 +108,13 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
fn eat_whitespace(&mut self) -> DevouredWhitespace { fn eat_whitespace(&mut self) -> DevouredWhitespace {
let mut whitespace_devoured = DevouredWhitespace::None; let mut whitespace_devoured = DevouredWhitespace::None;
while let Some(tok) = self.toks.peek() { while let Some(tok) = self.parser.toks.peek() {
match tok.kind { match tok.kind {
' ' | '\t' => whitespace_devoured.found_whitespace(), ' ' | '\t' => whitespace_devoured.found_whitespace(),
'\n' => whitespace_devoured.found_newline(), '\n' => whitespace_devoured.found_newline(),
_ => break, _ => break,
} }
self.toks.next(); self.parser.toks.next();
} }
whitespace_devoured whitespace_devoured
@ -140,22 +129,22 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
// todo: or patterns // todo: or patterns
loop { loop {
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
// todo: can we do while let Some(..) = self.toks.peek() ? // todo: can we do while let Some(..) = self.parser.toks.peek() ?
match self.toks.peek() { match self.parser.toks.peek() {
Some(Token { kind: '+', .. }) => { Some(Token { kind: '+', .. }) => {
self.toks.next(); self.parser.toks.next();
components.push(ComplexSelectorComponent::Combinator( components.push(ComplexSelectorComponent::Combinator(
Combinator::NextSibling, Combinator::NextSibling,
)); ));
} }
Some(Token { kind: '>', .. }) => { Some(Token { kind: '>', .. }) => {
self.toks.next(); self.parser.toks.next();
components.push(ComplexSelectorComponent::Combinator(Combinator::Child)) components.push(ComplexSelectorComponent::Combinator(Combinator::Child))
} }
Some(Token { kind: '~', .. }) => { Some(Token { kind: '~', .. }) => {
self.toks.next(); self.parser.toks.next();
components.push(ComplexSelectorComponent::Combinator( components.push(ComplexSelectorComponent::Combinator(
Combinator::FollowingSibling, Combinator::FollowingSibling,
)); ));
@ -172,7 +161,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
components.push(ComplexSelectorComponent::Compound( components.push(ComplexSelectorComponent::Compound(
self.parse_compound_selector()?, 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()); 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( components.push(ComplexSelectorComponent::Compound(
self.parse_compound_selector()?, 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()); 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> { fn parse_compound_selector(&mut self) -> SassResult<CompoundSelector> {
let mut components = vec![self.parse_simple_selector(true)?]; 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) { if !is_simple_selector_start(*kind) {
break; 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 /// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier
fn looking_at_identifier(&mut self) -> bool { 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, .. }) if is_name_start(*kind) || kind == &'\\' => return true,
Some(Token { kind: '-', .. }) => {} Some(Token { kind: '-', .. }) => {}
Some(..) | None => return false, 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 == &'\\' => { Some(Token { kind, .. }) if is_name_start(*kind) || kind == &'-' || kind == &'\\' => {
self.toks.reset_view(); self.parser.toks.reset_view();
true true
} }
Some(..) | None => { Some(..) | None => {
self.toks.reset_view(); self.parser.toks.reset_view();
false false
} }
} }
} }
fn looking_at_identifier_body(&mut self) -> bool { 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. /// Consumes a simple selector.
fn parse_simple_selector(&mut self, allow_parent: bool) -> SassResult<SimpleSelector> { 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_attribute_selector(),
Some(Token { kind: '.', .. }) => self.parse_class_selector(), Some(Token { kind: '.', .. }) => self.parse_class_selector(),
Some(Token { kind: '#', .. }) => self.parse_id_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> { fn parse_attribute_selector(&mut self) -> SassResult<SimpleSelector> {
self.toks.next(); self.parser.toks.next();
Ok(SimpleSelector::Attribute(Attribute::from_tokens( Ok(SimpleSelector::Attribute(Attribute::from_tokens(
self.toks, self.parser,
self.scope,
self.super_selector,
self.span,
)?)) )?))
} }
fn parse_class_selector(&mut self) -> SassResult<SimpleSelector> { fn parse_class_selector(&mut self) -> SassResult<SimpleSelector> {
self.toks.next(); self.parser.toks.next();
Ok(SimpleSelector::Class( Ok(SimpleSelector::Class(self.parser.parse_identifier()?.node))
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node,
))
} }
fn parse_id_selector(&mut self) -> SassResult<SimpleSelector> { fn parse_id_selector(&mut self) -> SassResult<SimpleSelector> {
self.toks.next(); self.parser.toks.next();
Ok(SimpleSelector::Id( Ok(SimpleSelector::Id(self.parser.parse_identifier()?.node))
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node,
))
} }
fn parse_pseudo_selector(&mut self) -> SassResult<SimpleSelector> { fn parse_pseudo_selector(&mut self) -> SassResult<SimpleSelector> {
self.toks.next(); self.parser.toks.next();
let element = match self.toks.peek() { let element = match self.parser.toks.peek() {
Some(Token { kind: ':', .. }) => { Some(Token { kind: ':', .. }) => {
self.toks.next(); self.parser.toks.next();
true true
} }
_ => false, _ => false,
}; };
let name = eat_ident(self.toks, self.scope, self.super_selector, self.span)?; let name = self.parser.parse_identifier()?;
match self.toks.peek() { match self.parser.toks.peek() {
Some(Token { kind: '(', .. }) => self.toks.next(), Some(Token { kind: '(', .. }) => self.parser.toks.next(),
_ => { _ => {
return Ok(SimpleSelector::Pseudo(Pseudo { return Ok(SimpleSelector::Pseudo(Pseudo {
// todo: we can store the reference to this // 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); let unvendored = unvendor(&name);
@ -332,25 +314,25 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
// todo: lowercase? // todo: lowercase?
if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) { if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) {
selector = Some(self.parse_selector_list()?); selector = Some(self.parse_selector_list()?);
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
self.expect_closing_paren()?; self.expect_closing_paren()?;
} else { } else {
argument = Some(self.declaration_value()?); argument = Some(self.declaration_value()?);
} }
} else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) { } else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) {
selector = Some(self.parse_selector_list()?); selector = Some(self.parse_selector_list()?);
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
self.expect_closing_paren()?; self.expect_closing_paren()?;
} else if unvendored == "nth-child" || unvendored == "nth-last-child" { } else if unvendored == "nth-child" || unvendored == "nth-last-child" {
let mut this_arg = self.parse_a_n_plus_b()?; 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)] #[allow(clippy::match_same_arms)]
match (found_whitespace, self.toks.peek()) { match (found_whitespace, self.parser.toks.peek()) {
(_, Some(Token { kind: ')', .. })) => {} (_, Some(Token { kind: ')', .. })) => {}
(true, _) => { (true, _) => {
self.expect_identifier("of")?; self.expect_identifier("of")?;
this_arg.push_str(" of"); this_arg.push_str(" of");
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
selector = Some(self.parse_selector_list()?); 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> { fn parse_parent_selector(&mut self) -> SassResult<SimpleSelector> {
self.toks.next(); self.parser.toks.next();
let suffix = if self.looking_at_identifier_body() { 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 { } else {
None None
}; };
@ -383,9 +365,9 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
} }
fn parse_placeholder_selector(&mut self) -> SassResult<SimpleSelector> { fn parse_placeholder_selector(&mut self) -> SassResult<SimpleSelector> {
self.toks.next(); self.parser.toks.next();
Ok(SimpleSelector::Placeholder( 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 `*`. /// These are combined because either one could start with `*`.
fn parse_type_or_universal_selector(&mut self) -> SassResult<SimpleSelector> { 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 }) => { Some(Token { kind: '*', pos }) => {
self.toks.next(); self.parser.span_before = self.parser.span_before.merge(*pos);
if let Some(Token { kind: '|', .. }) = self.toks.peek() { self.parser.toks.next();
self.toks.next(); if let Some(Token { kind: '|', .. }) = self.parser.toks.peek() {
if let Some(Token { kind: '*', .. }) = self.toks.peek() { self.parser.toks.next();
self.toks.next(); if let Some(Token { kind: '*', .. }) = self.parser.toks.peek() {
self.parser.toks.next();
return Ok(SimpleSelector::Universal(Namespace::Asterisk)); return Ok(SimpleSelector::Universal(Namespace::Asterisk));
} else { } else {
return Ok(SimpleSelector::Type(QualifiedName { 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, namespace: Namespace::Asterisk,
})); }));
} }
@ -414,15 +397,16 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
} }
} }
Some(Token { kind: '|', pos }) => { Some(Token { kind: '|', pos }) => {
self.toks.next(); self.parser.span_before = self.parser.span_before.merge(*pos);
match self.toks.peek() { self.parser.toks.next();
match self.parser.toks.peek() {
Some(Token { kind: '*', .. }) => { Some(Token { kind: '*', .. }) => {
self.toks.next(); self.parser.toks.next();
return Ok(SimpleSelector::Universal(Namespace::Empty)); return Ok(SimpleSelector::Universal(Namespace::Empty));
} }
_ => { _ => {
return Ok(SimpleSelector::Type(QualifiedName { 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, namespace: Namespace::Empty,
})); }));
} }
@ -431,19 +415,17 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
_ => {} _ => {}
} }
let name_or_namespace = let name_or_namespace = self.parser.parse_identifier()?.node;
eat_ident(self.toks, self.scope, self.super_selector, self.span)?.node;
Ok(match self.toks.peek() { Ok(match self.parser.toks.peek() {
Some(Token { kind: '|', .. }) => { Some(Token { kind: '|', .. }) => {
self.toks.next(); self.parser.toks.next();
if let Some(Token { kind: '*', .. }) = self.toks.peek() { if let Some(Token { kind: '*', .. }) = self.parser.toks.peek() {
self.toks.next(); self.parser.toks.next();
SimpleSelector::Universal(Namespace::Other(name_or_namespace)) SimpleSelector::Universal(Namespace::Other(name_or_namespace))
} else { } else {
SimpleSelector::Type(QualifiedName { SimpleSelector::Type(QualifiedName {
ident: eat_ident(self.toks, self.scope, self.super_selector, self.span)? ident: self.parser.parse_identifier()?.node,
.node,
namespace: Namespace::Other(name_or_namespace), 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> { fn parse_a_n_plus_b(&mut self) -> SassResult<String> {
let mut buf = String::new(); let mut buf = String::new();
match self.toks.peek() { match self.parser.toks.peek() {
Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => { Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => {
self.expect_identifier("even")?; self.expect_identifier("even")?;
return Ok("even".to_string()); 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: '-', .. }) => { Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) => {
buf.push(t.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() => { 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() { if !t.kind.is_ascii_digit() {
break; break;
} }
buf.push(t.kind); buf.push(t.kind);
self.toks.next(); self.parser.toks.next();
} }
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
if let Some(t) = self.toks.peek() { if let Some(t) = self.parser.toks.peek() {
if t.kind != 'n' && t.kind != 'N' { if t.kind != 'n' && t.kind != 'N' {
return Ok(buf); return Ok(buf);
} }
self.toks.next(); self.parser.toks.next();
} }
} }
Some(t) => { Some(t) => {
if t.kind == 'n' || t.kind == 'N' { if t.kind == 'n' || t.kind == 'N' {
self.toks.next(); self.parser.toks.next();
} else { } else {
return Err(("Expected \"n\".", self.span).into()); return Err(("Expected \"n\".", self.span).into());
} }
@ -506,15 +488,15 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
buf.push('n'); buf.push('n');
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
if let Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) = if let Some(t @ Token { kind: '+', .. }) | Some(t @ Token { kind: '-', .. }) =
self.toks.peek() self.parser.toks.peek()
{ {
buf.push(t.kind); buf.push(t.kind);
self.toks.next(); self.parser.toks.next();
devour_whitespace(self.toks); devour_whitespace(self.parser.toks);
match self.toks.peek() { match self.parser.toks.peek() {
Some(t) if !t.kind.is_ascii_digit() => { Some(t) if !t.kind.is_ascii_digit() => {
return Err(("Expected a number.", self.span).into()) return Err(("Expected a number.", self.span).into())
} }
@ -522,12 +504,12 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
Some(..) => {} Some(..) => {}
} }
while let Some(t) = self.toks.peek() { while let Some(t) = self.parser.toks.peek() {
if !t.kind.is_ascii_digit() { if !t.kind.is_ascii_digit() {
break; break;
} }
buf.push(t.kind); buf.push(t.kind);
self.toks.next(); self.parser.toks.next();
} }
} }
Ok(buf) Ok(buf)
@ -535,7 +517,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
fn declaration_value(&mut self) -> SassResult<String> { fn declaration_value(&mut self) -> SassResult<String> {
// todo: this consumes the closing paren // 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() { if let Some(Token { kind: ')', .. }) = tmp.pop() {
} else { } else {
return Err(("expected \")\".", self.span).into()); 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<()> { 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(); ident.make_ascii_lowercase();
if ident == s { if ident == s {
Ok(()) Ok(())
@ -554,7 +536,7 @@ impl<'a, I: Iterator<Item = Token>> SelectorParser<'a, I> {
} }
fn expect_closing_paren(&mut self) -> SassResult<()> { 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(()) Ok(())
} else { } else {
Err(("expected \")\".", self.span).into()) 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, value::Value};
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};
/// A style: `color: red` /// A style: `color: red`
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -17,16 +10,6 @@ pub(crate) struct Style {
} }
impl 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> { pub fn to_string(&self) -> SassResult<String> {
Ok(format!( 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. //! Arbitrary precision is retained.
use std::collections::HashMap; use std::{collections::HashMap, f64::consts::PI};
use std::f64::consts::PI;
use num_traits::One; use num_traits::One;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;

View File

@ -1,12 +1,8 @@
use std::iter::Iterator;
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
use crate::{error::SassResult, Token};
use super::read_until_closing_quote; use super::read_until_closing_quote;
use crate::error::SassResult;
use crate::Token;
/// Reads until the char is found, consuming the char, /// Reads until the char is found, consuming the char,
/// or until the end of the iterator is hit /// or until the end of the iterator is hit
pub(crate) fn read_until_char<I: Iterator<Item = Token>>( 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 peekmore::PeekMoreIterator;
use crate::error::SassResult; use crate::Token;
use crate::selector::Selector;
use crate::{Scope, Token};
use super::parse_interpolation;
pub(crate) trait IsWhitespace { pub(crate) trait IsWhitespace {
fn is_whitespace(&self) -> bool; fn is_whitespace(&self) -> bool;
@ -48,80 +40,6 @@ pub(crate) fn peek_whitespace<I: Iterator<Item = W>, W: IsWhitespace>(
peek_counter 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 /// Eat tokens until a newline
/// ///
/// This exists largely to eat silent comments, "//" /// 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 chars::*;
pub(crate) use comment_whitespace::*; pub(crate) use comment_whitespace::*;
pub(crate) use interpolation::*;
pub(crate) use number::*; pub(crate) use number::*;
pub(crate) use peek_until::*; pub(crate) use peek_until::*;
pub(crate) use read_until::*; pub(crate) use read_until::*;
pub(crate) use strings::*; pub(crate) use strings::*;
pub(crate) use variables::*;
mod chars; mod chars;
mod comment_whitespace; mod comment_whitespace;
mod interpolation;
mod number; mod number;
mod peek_until; mod peek_until;
mod read_until; mod read_until;
mod strings; mod strings;
mod variables;

View File

@ -1,11 +1,8 @@
use std::iter::Iterator;
use codemap::Spanned; use codemap::Spanned;
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
use crate::error::SassResult; use crate::{error::SassResult, Token};
use crate::Token;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ParsedNumber { pub(crate) struct ParsedNumber {

View File

@ -1,11 +1,8 @@
use std::iter::Iterator;
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
use peekmore::PeekMoreIterator; use peekmore::PeekMoreIterator;
use crate::error::SassResult; use crate::{error::SassResult, Token};
use crate::Token;
use super::{as_hex, hex_char_for, is_name, is_name_start, IsWhitespace}; 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 peekmore::PeekMoreIterator;
use crate::error::SassResult; use crate::{error::SassResult, Token};
use crate::Token;
use super::{devour_whitespace, read_until_newline}; use super::{devour_whitespace, read_until_newline};

View File

@ -1,17 +1,4 @@
use std::borrow::Borrow; use super::{is_name, is_name_start};
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};
pub(crate) fn is_ident(s: &str) -> bool { pub(crate) fn is_ident(s: &str) -> bool {
let mut chars = s.chars().peekable(); let mut chars = s.chars().peekable();
@ -44,318 +31,3 @@ pub(crate) fn is_ident(s: &str) -> bool {
} }
true 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 { pub(crate) fn is_special_function(s: &str) -> bool {
s.starts_with("calc(") s.starts_with("calc(")
|| s.starts_with("var(") || s.starts_with("var(")
@ -65,105 +5,3 @@ pub(crate) fn is_special_function(s: &str) -> bool {
|| s.starts_with("min(") || s.starts_with("min(")
|| s.starts_with("max(") || 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::{slice::Iter, vec::IntoIter};
use std::vec::IntoIter;
use codemap::Span; use codemap::Span;
use super::Value; use crate::{
use crate::common::{Brackets, ListSeparator}; common::{Brackets, ListSeparator},
use crate::error::SassResult; error::SassResult,
value::Value,
};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SassMap(Vec<(Value, Value)>); pub(crate) struct SassMap(Vec<(Value, Value)>);

View File

@ -1,28 +1,27 @@
use std::iter::Iterator;
use peekmore::PeekMore; use peekmore::PeekMore;
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
use crate::color::Color; use crate::{
use crate::common::{Brackets, ListSeparator, Op, QuoteKind}; color::Color,
use crate::error::SassResult; common::{Brackets, ListSeparator, Op, QuoteKind},
use crate::scope::Scope; error::SassResult,
use crate::selector::Selector; parse::Parser,
use crate::unit::Unit; selector::Selector,
use crate::utils::hex_char_for; unit::Unit,
use crate::{Cow, Token}; utils::hex_char_for,
{Cow, Token},
};
use css_function::is_special_function; use css_function::is_special_function;
pub(crate) use map::SassMap; pub(crate) use map::SassMap;
pub(crate) use number::Number; pub(crate) use number::Number;
pub(crate) use sass_function::SassFunction; pub(crate) use sass_function::SassFunction;
mod css_function; pub(crate) mod css_function;
mod map; mod map;
mod number; mod number;
mod ops; mod ops;
mod parse;
mod sass_function; mod sass_function;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -332,22 +331,35 @@ impl Value {
/// `name` is the argument name. It's used for error reporting. /// `name` is the argument name. It's used for error reporting.
pub fn to_selector( pub fn to_selector(
self, self,
span: Span, parser: &mut Parser<'_>,
scope: &Scope,
super_selector: &Selector,
name: &str, name: &str,
allows_parent: bool, allows_parent: bool,
) -> SassResult<Selector> { ) -> SassResult<Selector> {
let string = match self.clone().selector_string(span)? { let string = match self.clone().selector_string(parser.span_before)? {
Some(v) => v, 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( Parser {
&mut string.chars().map(|c| Token::new(span, c)).peekmore(), toks: &mut string
scope, .chars()
super_selector, .map(|c| Token::new(parser.span_before, c))
allows_parent, .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>> { fn selector_string(self, span: Span) -> SassResult<Option<String>> {

View File

@ -1,6 +1,8 @@
use std::fmt::{self, Display, UpperHex}; use std::{
use std::mem; fmt::{self, Display, UpperHex},
use std::ops::{Add, AddAssign}; mem,
ops::{Add, AddAssign},
};
use num_bigint::BigInt; use num_bigint::BigInt;
use num_traits::{Signed, ToPrimitive, Zero}; use num_traits::{Signed, ToPrimitive, Zero};

View File

@ -1,9 +1,9 @@
use std::cmp::Ordering; use std::{
use std::convert::{From, TryFrom}; cmp::Ordering,
use std::fmt::{self, Display, Write}; convert::{From, TryFrom},
use std::mem; fmt::{self, Display, Write},
use std::ops::{ mem,
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
}; };
use num_bigint::BigInt; use num_bigint::BigInt;

View File

@ -2,10 +2,12 @@ use std::cmp::Ordering;
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
use crate::common::{Op, QuoteKind}; use crate::{
use crate::error::SassResult; common::{Op, QuoteKind},
use crate::unit::{Unit, UNIT_CONVERSION_TABLE}; error::SassResult,
use crate::value::Value; unit::{Unit, UNIT_CONVERSION_TABLE},
value::Value,
};
impl Value { impl Value {
pub fn equals(mut self, mut other: Value, span: Span) -> SassResult<Spanned<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 std::fmt;
use crate::args::CallArgs; use crate::{
use crate::atrule::Function; args::CallArgs, atrule::Function, builtin::Builtin, common::Identifier, error::SassResult,
use crate::builtin::Builtin; parse::Parser, value::Value,
use crate::common::Identifier; };
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::value::Value;
/// A SASS function /// A SASS function
/// ///
@ -52,15 +48,10 @@ impl SassFunction {
} }
} }
pub fn call( pub fn call(self, args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
self,
args: CallArgs,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
match self { match self {
Self::Builtin(f, ..) => f.0(args, scope, super_selector), Self::Builtin(f, ..) => f.0(args, parser),
Self::UserDefined(f, ..) => f.eval(args, scope, super_selector), 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", "@function foo($a, $b: $a) {\n @return $b;\n}\n\na {\n color: foo(2);\n}\n",
"a {\n color: 2;\n}\n" "a {\n color: 2;\n}\n"
); );
// todo: this should have a space after :
// and should be "expected \")\"."
error!( error!(
#[ignore = "does not fail"]
nothing_after_open, 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, question_mark_inside_value,
"a {foo: bar?}", "Error: expected \";\"." "a {foo: bar?}", "Error: expected \";\"."
); );
// TODO: special parsing rules for variable names error!(
// error!( interpolation_in_variable_declaration,
// interpolation_in_variable_declaration, "$base-#{lor}: #036;", "Error: expected \":\"."
// "$base-#{lor}: #036;", "Error: expected \":\"." );
// ); error!(
// error!( backslash_as_last_character,
// backslash_as_last_character, "a {colo\\: red;}", "Error: expected \"{\"."
// "a {colo\\: red;}", "Error: expected \"{\"." );
// );
error!( error!(
close_paren_without_opening, close_paren_without_opening,
"a {color: foo);}", "Error: expected \";\"." "a {color: foo);}", "Error: expected \";\"."
@ -64,7 +63,7 @@ error!(
); );
error!( error!(
missing_colon_in_style, missing_colon_in_style,
"a {color, red;}", "Error: Expected \":\"." "a {color, red;}", "Error: expected \"{\"."
); );
error!( error!(
toplevel_forward_slash, toplevel_forward_slash,
@ -86,12 +85,13 @@ error!(toplevel_comma, "a {},", "Error: expected \"{\".");
error!(toplevel_exclamation_alone, "!", "Error: expected \"}\"."); error!(toplevel_exclamation_alone, "!", "Error: expected \"}\".");
error!(toplevel_exclamation, "! {}", "Error: expected \"}\"."); error!(toplevel_exclamation, "! {}", "Error: expected \"}\".");
error!(toplevel_backtick, "` {}", "Error: expected selector."); error!(toplevel_backtick, "` {}", "Error: expected selector.");
// note that the message dart-sass gives is: `Error: expected "}".`
error!( error!(
toplevel_open_curly_brace, toplevel_open_curly_brace,
"{ {color: red;}", "Error: expected \"}\"." "{ {color: red;}", "Error: expected \"}\"."
); );
error!(toplevel_open_paren, "(", "Error: expected \"{\"."); error!(toplevel_open_paren, "(", "Error: expected \"{\".");
error!(toplevel_close_paren, "(", "Error: expected \"{\"."); error!(toplevel_close_paren, ")", "Error: expected \"{\".");
error!( error!(
backtick_in_value, backtick_in_value,
"a {color:`red;}", "Error: Expected expression." "a {color:`red;}", "Error: Expected expression."
@ -100,7 +100,8 @@ error!(
comma_begins_value, comma_begins_value,
"a {color:,red;}", "Error: Expected expression." "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!( error!(
nothing_after_hyphen_variable, nothing_after_hyphen_variable,
"a {$-", "Error: expected \":\"." "a {$-", "Error: expected \":\"."
@ -123,16 +124,21 @@ error!(
); );
error!( error!(
toplevel_hash_no_closing_curly_brace_no_value, toplevel_hash_no_closing_curly_brace_no_value,
"#{", "Error: expected \"}\"." "#{", "Error: Expected expression."
); );
error!(toplevel_hash, "#", "Error: expected \"{\"."); 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!(toplevel_at, "@", "Error: Expected identifier.");
error!( error!(
#[ignore = "this panics until we can return a result in selector parsing"]
toplevel_ampersand, toplevel_ampersand,
"& {}", "Error: Top-level selectors may not contain the parent selector \"&\"." "& {}", "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!(toplevel_var_no_colon, "$r", "Error: expected \":\".");
error!(bar_in_value, "a {color: a|b;}", "Error: expected \";\"."); error!(bar_in_value, "a {color: a|b;}", "Error: expected \";\".");
error!( error!(
@ -214,14 +220,17 @@ error!(
"a {$color: {ed;}", "Error: Expected expression." "a {$color: {ed;}", "Error: Expected expression."
); );
error!( error!(
#[ignore = "this test does not fail because the closing brace is included in the value"]
empty_style_value_no_semicolon, empty_style_value_no_semicolon,
"a {color:}", "Error: Expected expression." "a {color:}", "Error: Expected expression."
); );
error!( error!(
#[ignore = "this test does not fail because the semicolon is included in the value"]
empty_style_value_semicolon, empty_style_value_semicolon,
"a {color:;}", "Error: Expected expression." "a {color:;}", "Error: Expected expression."
); );
error!( error!(
#[ignore = "this does not fail"]
ident_colon_closing_brace, ident_colon_closing_brace,
"r:}", "Error: Expected expression." "r:}", "Error: Expected expression."
); );
@ -239,3 +248,4 @@ error!(
improperly_terminated_nested_style, improperly_terminated_nested_style,
"a {foo: {bar: red", "Error: Expected identifier." "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" "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) {}", @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!( error!(
nothing_after_escape, nothing_after_escape,
"@if \\", "Error: Expected expression." "@if \\", "Error: Expected expression."

View File

@ -1,8 +1,6 @@
use std::io::Write; use std::io::Write;
use tempfile::Builder; use tempfile::Builder;
use grass::StyleSheet;
/// Create a temporary file with the given name /// Create a temporary file with the given name
/// and contents. /// and contents.
/// ///
@ -42,7 +40,7 @@ fn imports_variable() {
tempfile!("imports_variable", "$a: red;"); tempfile!("imports_variable", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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;"); tempfile!("import_no_semicolon", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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() { fn import_no_quotes() {
let input = "@import import_no_quotes"; let input = "@import import_no_quotes";
tempfile!("import_no_quotes", "$a: red;"); 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"), Ok(..) => panic!("did not fail"),
Err(e) => assert_eq!( Err(e) => assert_eq!(
"Error: Expected string.", "Error: Expected string.",
@ -79,7 +77,7 @@ fn single_quotes_import() {
tempfile!("single_quotes_import", "$a: red;"); tempfile!("single_quotes_import", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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;"); tempfile!("finds_name_scss.scss", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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;"); tempfile!("_finds_underscore_name_scss.scss", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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;"); tempfile!("chained_imports__c.scss", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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;"); tempfile!("chained_imports_in_directory__c.scss", "$a: red;");
assert_eq!( assert_eq!(
"a {\n color: red;\n}\n", "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] #[test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn $func() { 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)); .expect(concat!("failed to parse on ", $input));
assert_eq!( assert_eq!(
String::from($input), String::from($input),
@ -20,7 +20,7 @@ macro_rules! test {
#[test] #[test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn $func() { 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)); .expect(concat!("failed to parse on ", $input));
assert_eq!( assert_eq!(
String::from($output), String::from($output),
@ -39,7 +39,7 @@ macro_rules! error {
#[test] #[test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn $func() { fn $func() {
match grass::StyleSheet::new($input.to_string()) { match grass::from_string($input.to_string()) {
Ok(..) => panic!("did not fail"), Ok(..) => panic!("did not fail"),
Err(e) => assert_eq!($err, e.to_string() Err(e) => assert_eq!($err, e.to_string()
.chars() .chars()

View File

@ -233,4 +233,9 @@ test!(
"a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n", "a {\n color: selector-extend(\".c .d\", \".c\", \".e +\");\n}\n",
"a {\n color: .c .d, .e + .d;\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/ // 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: {\n webkit: {\n color: red;\n }\n }\n}\n",
"a {\n webkit-webkit-color: red;\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!( test!(
no_space_between_colon, no_space_between_colon,
"a {\n color:red;\n}\n", "a {\n color:red;\n}\n",