integrate error handling with codemap

This commit is contained in:
ConnorSkees 2020-04-12 19:37:12 -04:00
parent e833650af0
commit 62f9f7da4f
36 changed files with 3171 additions and 1365 deletions

View File

@ -1,7 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::iter::Peekable; use std::iter::Peekable;
use crate::common::Pos; use codemap::{Span, Spanned};
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -29,7 +30,7 @@ impl FuncArgs {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct CallArgs(HashMap<CallArg, Vec<Token>>); pub(crate) struct CallArgs(HashMap<CallArg, Vec<Token>>, Span);
#[derive(Debug, Clone, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Hash, Eq, PartialEq)]
enum CallArg { enum CallArg {
@ -38,9 +39,9 @@ enum CallArg {
} }
impl CallArg { impl CallArg {
pub fn position(&self) -> SassResult<usize> { pub fn position(&self) -> Result<usize, String> {
match self { match self {
Self::Named(..) => Err("found named".into()), Self::Named(ref name) => Err(name.clone()),
Self::Positional(p) => Ok(*p), Self::Positional(p) => Ok(*p),
} }
} }
@ -54,26 +55,45 @@ impl CallArg {
} }
impl CallArgs { impl CallArgs {
pub fn new() -> Self { pub fn new(span: Span) -> Self {
CallArgs(HashMap::new()) CallArgs(HashMap::new(), span)
} }
pub fn to_css_string(self, scope: &Scope, super_selector: &Selector) -> SassResult<String> { pub fn to_css_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;
if self.is_empty() {
return Ok(Spanned {
node: "()".to_string(),
span,
});
}
let args = match self.get_variadic(scope, super_selector) { let args = match self.get_variadic(scope, super_selector) {
Ok(v) => v, Ok(v) => v,
Err(..) => return Err("Plain CSS functions don't support keyword arguments.".into()), Err(..) => {
return Err(("Plain CSS functions don't support keyword arguments.", span).into())
}
}; };
string.push_str( string.push_str(
&args &args
.iter() .iter()
.map(std::string::ToString::to_string) .map(|a| {
.collect::<Vec<String>>() span = span.merge(a.span);
Ok(a.node.to_css_string(a.span)?)
})
.collect::<SassResult<Vec<String>>>()?
.join(", "), .join(", "),
); );
string.push(')'); string.push(')');
Ok(string) Ok(Spanned { node: string, span })
} }
/// Get argument by name /// Get argument by name
@ -84,7 +104,7 @@ impl CallArgs {
val: String, val: String,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> Option<SassResult<Value>> { ) -> Option<SassResult<Spanned<Value>>> {
match self.0.remove(&CallArg::Named(val)) { match self.0.remove(&CallArg::Named(val)) {
Some(v) => Some(Value::from_vec(v, scope, super_selector)), Some(v) => Some(Value::from_vec(v, scope, super_selector)),
None => None, None => None,
@ -99,20 +119,28 @@ impl CallArgs {
val: usize, val: usize,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> Option<SassResult<Value>> { ) -> Option<SassResult<Spanned<Value>>> {
match self.0.remove(&CallArg::Positional(val)) { match self.0.remove(&CallArg::Positional(val)) {
Some(v) => Some(Value::from_vec(v, scope, super_selector)), Some(v) => Some(Value::from_vec(v, scope, super_selector)),
None => None, None => None,
} }
} }
pub fn get_variadic(self, scope: &Scope, super_selector: &Selector) -> SassResult<Vec<Value>> { pub fn get_variadic(
self,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Vec<Spanned<Value>>> {
let mut vals = Vec::new(); let mut vals = Vec::new();
let mut args = self let mut args = match self
.0 .0
.into_iter() .into_iter()
.map(|(a, v)| Ok((a.position()?, v))) .map(|(a, v)| Ok((a.position()?, v)))
.collect::<SassResult<Vec<(usize, Vec<Token>)>>>()?; .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)); args.sort_by(|(a1, _), (a2, _)| a1.cmp(a2));
for arg in args { for arg in args {
vals.push(Value::from_vec(arg.1, scope, super_selector)?); vals.push(Value::from_vec(arg.1, scope, super_selector)?);
@ -126,15 +154,20 @@ impl CallArgs {
.into_iter() .into_iter()
.map(|(k, v)| (k.decrement(), v)) .map(|(k, v)| (k.decrement(), v))
.collect(), .collect(),
self.1,
) )
} }
pub fn span(&self) -> Span {
self.1
}
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.0.len()
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.len() == 0 self.0.is_empty()
} }
} }
@ -155,8 +188,8 @@ pub(crate) fn eat_func_args<I: Iterator<Item = Token>>(
let mut default: Vec<Token> = Vec::new(); let mut default: Vec<Token> = Vec::new();
let mut is_variadic = false; let mut is_variadic = false;
devour_whitespace(toks); devour_whitespace(toks);
let kind = match toks.next() { let (kind, span) = match toks.next() {
Some(Token { kind, .. }) => kind, Some(Token { kind, pos }) => (kind, pos),
_ => todo!("unexpected eof"), _ => todo!("unexpected eof"),
}; };
match kind { match kind {
@ -189,15 +222,18 @@ pub(crate) fn eat_func_args<I: Iterator<Item = Token>>(
} }
} }
'.' => { '.' => {
if toks.next().ok_or("expected \".\".")?.kind != '.' { let next = toks.next().ok_or(("expected \".\".", span))?;
return Err("expected \".\".".into()); if next.kind != '.' {
return Err(("expected \".\".", next.pos()).into());
} }
if toks.next().ok_or("expected \".\".")?.kind != '.' { let next = toks.next().ok_or(("expected \".\".", next.pos()))?;
return Err("expected \".\".".into()); if next.kind != '.' {
return Err(("expected \".\".", next.pos()).into());
} }
devour_whitespace(toks); devour_whitespace(toks);
if toks.next().ok_or("expected \")\".")?.kind != ')' { let next = toks.next().ok_or(("expected \")\".", next.pos()))?;
return Err("expected \")\".".into()); if next.kind != ')' {
return Err(("expected \")\".", next.pos()).into());
} }
is_variadic = true; is_variadic = true;
@ -247,24 +283,31 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let mut name = String::new(); let mut name = String::new();
let mut val: Vec<Token> = Vec::new(); let mut val: Vec<Token> = Vec::new();
let span = toks.peek().unwrap().pos();
loop { loop {
match toks.peek().unwrap().kind { match toks.peek().unwrap().kind {
'$' => { '$' => {
toks.next(); let Token { pos, .. } = toks.next().unwrap();
let v = eat_ident(toks, scope, super_selector)?; let v = eat_ident(toks, scope, super_selector)?;
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
if toks.peek().unwrap().kind == ':' { if toks.peek().unwrap().kind == ':' {
toks.next(); toks.next();
name = v; name = v.node;
} else { } else {
val.push(Token::new(Pos::new(), '$')); val.push(Token::new(pos, '$'));
val.extend(v.chars().map(|x| Token::new(Pos::new(), x))); 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
}));
name.clear(); name.clear();
} }
} }
')' => { ')' => {
toks.next(); toks.next();
return Ok(CallArgs(args)); return Ok(CallArgs(args, span));
} }
_ => name.clear(), _ => name.clear(),
} }
@ -281,7 +324,7 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
}, },
val, val,
); );
return Ok(CallArgs(args)); return Ok(CallArgs(args, span));
} }
',' => break, ',' => break,
'[' => { '[' => {
@ -312,7 +355,7 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
devour_whitespace(toks); devour_whitespace(toks);
if toks.peek().is_none() { if toks.peek().is_none() {
return Ok(CallArgs(args)); return Ok(CallArgs(args, span));
} }
} }
} }

View File

@ -1,5 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use codemap::{Span, Spanned};
use num_traits::cast::ToPrimitive; use num_traits::cast::ToPrimitive;
use super::parse::eat_stmts; use super::parse::eat_stmts;
@ -19,18 +21,19 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &mut Scope, scope: &mut Scope,
super_selector: &Selector, super_selector: &Selector,
span: Span,
) -> SassResult<AtRule> { ) -> SassResult<AtRule> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
devour_whitespace(toks); devour_whitespace(toks);
let var = match toks.next().ok_or("expected \"$\".")?.kind { let var = match toks.next().ok_or(("expected \"$\".", span))?.kind {
'$' => eat_ident(toks, scope, super_selector)?, '$' => eat_ident(toks, scope, super_selector)?,
_ => return Err("expected \"$\".".into()), _ => return Err(("expected \"$\".", span).into()),
}; };
devour_whitespace(toks); devour_whitespace(toks);
if toks.peek().is_none() if toks.peek().is_none()
|| eat_ident(toks, scope, super_selector)?.to_ascii_lowercase() != "from" || eat_ident(toks, scope, super_selector)?.to_ascii_lowercase() != "from"
{ {
return Err("Expected \"from\".".into()); return Err(("Expected \"from\".", var.span).into());
} }
devour_whitespace(toks); devour_whitespace(toks);
let mut from_toks = Vec::new(); let mut from_toks = Vec::new();
@ -93,27 +96,41 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
} }
} }
'{' => { '{' => {
return Err("Expected \"to\" or \"through\".".into()); return Err(("Expected \"to\" or \"through\".", tok.pos()).into());
} }
_ => from_toks.extend(these_toks), _ => from_toks.extend(these_toks),
} }
} }
let from = match Value::from_vec(from_toks, scope, super_selector)? { let from_val = Value::from_vec(from_toks, scope, super_selector)?;
let from = match from_val.node {
Value::Dimension(n, _) => match n.to_integer().to_usize() { Value::Dimension(n, _) => match n.to_integer().to_usize() {
Some(v) => v, Some(v) => v,
None => return Err(format!("{} is not a int.", n).into()), None => return Err((format!("{} is not a int.", n), from_val.span).into()),
}, },
v => return Err(format!("{} is not an integer.", v).into()), v => {
return Err((
format!("{} is not an integer.", v.to_css_string(from_val.span)?),
from_val.span,
)
.into())
}
}; };
devour_whitespace(toks); devour_whitespace(toks);
let to_toks = read_until_open_curly_brace(toks); let to_toks = read_until_open_curly_brace(toks);
toks.next(); toks.next();
let to = match Value::from_vec(to_toks, scope, super_selector)? { let to_val = Value::from_vec(to_toks, scope, super_selector)?;
let to = match to_val.node {
Value::Dimension(n, _) => match n.to_integer().to_usize() { Value::Dimension(n, _) => match n.to_integer().to_usize() {
Some(v) => v, Some(v) => v,
None => return Err(format!("{} is not a int.", n).into()), None => return Err((format!("{} is not a int.", n), to_val.span).into()),
}, },
v => return Err(format!("{} is not an integer.", v).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); let body = read_until_closing_curly_brace(toks);
toks.next(); toks.next();
@ -130,7 +147,13 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
}; };
for i in iter { for i in iter {
scope.insert_var(&var, Value::Dimension(Number::from(i), Unit::None))?; scope.insert_var(
&var,
Spanned {
node: Value::Dimension(Number::from(i), Unit::None),
span: var.span,
},
)?;
stmts.extend(eat_stmts( stmts.extend(eat_stmts(
&mut body.clone().into_iter().peekable(), &mut body.clone().into_iter().peekable(),
scope, scope,

View File

@ -2,9 +2,10 @@ use std::iter::Peekable;
use super::eat_stmts; use super::eat_stmts;
use codemap::{Span, Spanned};
use crate::args::{eat_func_args, CallArgs, FuncArgs}; use crate::args::{eat_func_args, CallArgs, FuncArgs};
use crate::atrule::AtRule; use crate::atrule::AtRule;
use crate::common::Pos;
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -16,8 +17,8 @@ use crate::{Stmt, Token};
pub(crate) struct Function { pub(crate) struct Function {
scope: Scope, scope: Scope,
args: FuncArgs, args: FuncArgs,
body: Vec<Stmt>, body: Vec<Spanned<Stmt>>,
pos: Pos, pos: Span,
} }
impl PartialEq for Function { impl PartialEq for Function {
@ -29,7 +30,7 @@ impl PartialEq for Function {
impl Eq for Function {} impl Eq for Function {}
impl Function { impl Function {
pub fn new(scope: Scope, args: FuncArgs, body: Vec<Stmt>, pos: Pos) -> Self { pub fn new(scope: Scope, args: FuncArgs, body: Vec<Spanned<Stmt>>, pos: Span) -> Self {
Function { Function {
scope, scope,
args, args,
@ -43,12 +44,12 @@ impl Function {
scope: Scope, scope: Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<(String, Function)> { ) -> SassResult<(String, Function)> {
let pos = toks.peek().unwrap().pos; let Spanned { node: name, span } = eat_ident(toks, &scope, super_selector)?;
let name = eat_ident(toks, &scope, super_selector)?;
devour_whitespace(toks); devour_whitespace(toks);
let args = match toks.next() { let args = match toks.next() {
Some(Token { kind: '(', .. }) => eat_func_args(toks, &scope, super_selector)?, Some(Token { kind: '(', .. }) => eat_func_args(toks, &scope, super_selector)?,
_ => return Err("expected \"(\".".into()), Some(Token { pos, .. }) => return Err(("expected \"(\".", pos).into()),
None => return Err(("expected \"(\".", span).into()),
}; };
devour_whitespace(toks); devour_whitespace(toks);
@ -56,7 +57,7 @@ impl Function {
let body = eat_stmts(toks, &mut scope.clone(), super_selector)?; let body = eat_stmts(toks, &mut scope.clone(), super_selector)?;
devour_whitespace(toks); devour_whitespace(toks);
Ok((name, Function::new(scope, args, body, pos))) Ok((name, Function::new(scope, args, body, span)))
} }
pub fn args( pub fn args(
@ -67,9 +68,13 @@ impl Function {
) -> SassResult<Function> { ) -> SassResult<Function> {
for (idx, arg) in self.args.0.iter().enumerate() { for (idx, arg) in self.args.0.iter().enumerate() {
if arg.is_variadic { if arg.is_variadic {
let span = args.span();
self.scope.insert_var( self.scope.insert_var(
&arg.name, &arg.name,
Value::ArgList(args.get_variadic(scope, super_selector)?), Spanned {
node: Value::ArgList(args.get_variadic(scope, super_selector)?),
span,
},
)?; )?;
break; break;
} }
@ -83,7 +88,11 @@ impl Function {
scope, scope,
super_selector, super_selector,
)?, )?,
None => return Err(format!("Missing argument ${}.", &arg.name).into()), None => {
return Err(
(format!("Missing argument ${}.", &arg.name), args.span()).into()
)
}
}, },
}, },
}; };
@ -92,19 +101,20 @@ impl Function {
Ok(self) Ok(self)
} }
pub fn body(&self) -> Vec<Stmt> { pub fn body(&self) -> Vec<Spanned<Stmt>> {
self.body.clone() self.body.clone()
} }
pub fn call(&self, super_selector: &Selector, stmts: Vec<Stmt>) -> SassResult<Value> { pub fn call(&self, super_selector: &Selector, stmts: Vec<Spanned<Stmt>>) -> SassResult<Value> {
for stmt in stmts { for stmt in stmts {
match stmt { match stmt.node {
Stmt::AtRule(AtRule::Return(toks)) => { Stmt::AtRule(AtRule::Return(toks)) => {
return Value::from_tokens( return Ok(Value::from_tokens(
&mut toks.into_iter().peekable(), &mut toks.into_iter().peekable(),
&self.scope, &self.scope,
super_selector, super_selector,
) )?
.node)
} }
Stmt::AtRule(AtRule::For(..)) => todo!("@for in function"), Stmt::AtRule(AtRule::For(..)) => todo!("@for in function"),
Stmt::AtRule(AtRule::If(i)) => { Stmt::AtRule(AtRule::If(i)) => {
@ -115,9 +125,9 @@ impl Function {
return Ok(v); return Ok(v);
} }
} }
_ => return Err("This at-rule is not allowed here.".into()), _ => return Err(("This at-rule is not allowed here.", stmt.span).into()),
} }
} }
Err("Function finished without @return.".into()) Err(("Function finished without @return.", self.pos).into())
} }
} }

View File

@ -1,5 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use codemap::Spanned;
use super::{eat_stmts, AtRule}; use super::{eat_stmts, AtRule};
use crate::error::SassResult; use crate::error::SassResult;
@ -72,7 +74,7 @@ impl If {
break; break;
} }
_ => { _ => {
return Err("expected \"{\".".into()); return Err(("expected \"{\".", tok.pos()).into());
} }
} }
} else { } else {
@ -90,12 +92,17 @@ impl If {
Ok(If { branches, else_ }) Ok(If { branches, else_ })
} }
pub fn eval(self, scope: &mut Scope, super_selector: &Selector) -> SassResult<Vec<Stmt>> { pub fn eval(
self,
scope: &mut Scope,
super_selector: &Selector,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
let mut toks = Vec::new(); let mut toks = Vec::new();
let mut found_true = false; let mut found_true = false;
for branch in self.branches { for branch in self.branches {
if Value::from_vec(branch.cond, scope, super_selector)?.is_true()? { let val = Value::from_vec(branch.cond, scope, super_selector)?;
if val.node.is_true(val.span)? {
toks = branch.toks; toks = branch.toks;
found_true = true; found_true = true;
break; break;
@ -105,10 +112,10 @@ impl If {
toks = self.else_; toks = self.else_;
} }
for stmt in eat_stmts(&mut toks.into_iter().peekable(), scope, super_selector)? { for stmt in eat_stmts(&mut toks.into_iter().peekable(), scope, super_selector)? {
match stmt { match stmt.node {
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?), Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?),
Stmt::RuleSet(r) if r.selector.is_empty() => stmts.extend(r.rules), Stmt::RuleSet(r) if r.selector.is_empty() => stmts.extend(r.rules),
v => stmts.push(v), _ => stmts.push(stmt),
} }
} }
Ok(stmts) Ok(stmts)

View File

@ -1,6 +1,8 @@
use std::iter::Peekable; use std::iter::Peekable;
use std::vec::IntoIter; use std::vec::IntoIter;
use codemap::Spanned;
use super::eat_stmts; use super::eat_stmts;
use crate::args::{eat_call_args, eat_func_args, CallArgs, FuncArgs}; use crate::args::{eat_call_args, eat_func_args, CallArgs, FuncArgs};
@ -19,11 +21,16 @@ pub(crate) struct Mixin {
scope: Scope, scope: Scope,
args: FuncArgs, args: FuncArgs,
body: Peekable<IntoIter<Token>>, body: Peekable<IntoIter<Token>>,
content: Vec<Stmt>, content: Vec<Spanned<Stmt>>,
} }
impl Mixin { impl Mixin {
pub fn new(scope: Scope, args: FuncArgs, body: Vec<Token>, content: Vec<Stmt>) -> Self { pub fn new(
scope: Scope,
args: FuncArgs,
body: Vec<Token>,
content: Vec<Spanned<Stmt>>,
) -> Self {
let body = body.into_iter().peekable(); let body = body.into_iter().peekable();
Mixin { Mixin {
scope, scope,
@ -37,14 +44,15 @@ impl Mixin {
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<(String, Mixin)> { ) -> SassResult<Spanned<(String, Mixin)>> {
devour_whitespace(toks); devour_whitespace(toks);
let name = eat_ident(toks, scope, super_selector)?; let Spanned { node: name, span } = eat_ident(toks, scope, super_selector)?;
devour_whitespace(toks); devour_whitespace(toks);
let args = match toks.next() { let args = match toks.next() {
Some(Token { kind: '(', .. }) => eat_func_args(toks, scope, super_selector)?, Some(Token { kind: '(', .. }) => eat_func_args(toks, scope, super_selector)?,
Some(Token { kind: '{', .. }) => FuncArgs::new(), Some(Token { kind: '{', .. }) => FuncArgs::new(),
_ => return Err("expected \"{\".".into()), Some(t) => return Err(("expected \"{\".", t.pos()).into()),
None => return Err(("expected \"{\".", span).into()),
}; };
devour_whitespace(toks); devour_whitespace(toks);
@ -52,10 +60,13 @@ impl Mixin {
let mut body = read_until_closing_curly_brace(toks); let mut body = read_until_closing_curly_brace(toks);
body.push(toks.next().unwrap()); body.push(toks.next().unwrap());
Ok((name, Mixin::new(scope.clone(), args, body, Vec::new()))) Ok(Spanned {
node: (name, Mixin::new(scope.clone(), args, body, Vec::new())),
span,
})
} }
pub fn content(mut self, content: Vec<Stmt>) -> Mixin { pub fn content(mut self, content: Vec<Spanned<Stmt>>) -> Mixin {
self.content = content; self.content = content;
self self
} }
@ -68,9 +79,13 @@ impl Mixin {
) -> SassResult<Mixin> { ) -> SassResult<Mixin> {
for (idx, arg) in self.args.0.iter().enumerate() { for (idx, arg) in self.args.0.iter().enumerate() {
if arg.is_variadic { if arg.is_variadic {
let span = args.span();
self.scope.insert_var( self.scope.insert_var(
&arg.name, &arg.name,
Value::ArgList(args.get_variadic(scope, super_selector)?), Spanned {
node: Value::ArgList(args.get_variadic(scope, super_selector)?),
span,
},
)?; )?;
break; break;
} }
@ -84,7 +99,11 @@ impl Mixin {
scope, scope,
super_selector, super_selector,
)?, )?,
None => return Err(format!("Missing argument ${}.", &arg.name).into()), None => {
return Err(
(format!("Missing argument ${}.", &arg.name), args.span()).into()
)
}
}, },
}, },
}; };
@ -93,43 +112,64 @@ impl Mixin {
Ok(self) Ok(self)
} }
pub fn call(mut self, super_selector: &Selector) -> SassResult<Vec<Stmt>> { pub fn call(mut self, super_selector: &Selector) -> SassResult<Vec<Spanned<Stmt>>> {
self.eval(super_selector) self.eval(super_selector)
} }
fn eval(&mut self, super_selector: &Selector) -> SassResult<Vec<Stmt>> { fn eval(&mut self, super_selector: &Selector) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? { while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? {
match expr { let span = expr.span;
match expr.node {
Expr::AtRule(a) => match a { Expr::AtRule(a) => match a {
AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => stmts.extend(s), AtRule::Include(s) | AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => {
stmts.extend(s)
}
AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?), AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?),
AtRule::Content => stmts.extend(self.content.clone()), AtRule::Content => stmts.extend(self.content.clone()),
AtRule::Return(..) => return Err("This at-rule is not allowed here.".into()), AtRule::Return(..) => {
r => stmts.push(Stmt::AtRule(r)), 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(Stmt::Style(s)), Expr::Style(s) => stmts.push(Spanned {
Expr::Styles(s) => stmts.extend(s.into_iter().map(Box::new).map(Stmt::Style)), node: Stmt::Style(s),
Expr::Include(s) => stmts.extend(s), span,
}),
Expr::Styles(s) => stmts.extend(
s.into_iter()
.map(Box::new)
.map(Stmt::Style)
.map(|style| Spanned { node: style, span }),
),
Expr::FunctionDecl(..) => { Expr::FunctionDecl(..) => {
return Err("Mixins may not contain function declarations.".into()) return Err(("Mixins may not contain function declarations.", span).into())
} }
Expr::MixinDecl(..) => { Expr::MixinDecl(..) => {
return Err("Mixins may not contain mixin declarations.".into()) return Err(("Mixins may not contain mixin declarations.", span).into())
} }
Expr::Debug(..) | Expr::Warn(..) => todo!(),
Expr::Selector(selector) => { Expr::Selector(selector) => {
let rules = self.eval(&super_selector.zip(&selector))?; let rules = self.eval(&super_selector.zip(&selector))?;
stmts.push(Stmt::RuleSet(RuleSet { stmts.push(Spanned {
super_selector: super_selector.clone(), node: Stmt::RuleSet(RuleSet {
selector, super_selector: super_selector.clone(),
rules, selector,
})); rules,
}),
span,
});
} }
Expr::VariableDecl(name, val) => { Expr::VariableDecl(name, val) => {
self.scope.insert_var(&name, *val)?; self.scope.insert_var(&name, *val)?;
} }
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)), Expr::MultilineComment(s) => stmts.push(Spanned {
node: Stmt::MultilineComment(s),
span,
}),
} }
} }
Ok(stmts) Ok(stmts)
@ -140,37 +180,37 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Vec<Stmt>> { ) -> SassResult<Vec<Spanned<Stmt>>> {
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let name = eat_ident(toks, scope, super_selector)?; let name = eat_ident(toks, scope, super_selector)?;
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let mut has_include = false; let mut has_content = false;
let args = if let Some(tok) = toks.next() { let args = if let Some(tok) = toks.next() {
match tok.kind { match tok.kind {
';' => CallArgs::new(), ';' => CallArgs::new(name.span),
'(' => { '(' => {
let tmp = eat_call_args(toks, scope, super_selector)?; let tmp = eat_call_args(toks, scope, super_selector)?;
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
if let Some(tok) = toks.next() { if let Some(tok) = toks.next() {
match tok.kind { match tok.kind {
';' => {} ';' => {}
'{' => has_include = true, '{' => has_content = true,
_ => todo!(), _ => todo!(),
} }
} }
tmp tmp
} }
'{' => { '{' => {
has_include = true; has_content = true;
CallArgs::new() CallArgs::new(name.span)
} }
_ => return Err("expected \"{\".".into()), _ => return Err(("expected \"{\".", tok.pos()).into()),
} }
} else { } else {
return Err("unexpected EOF".into()); return Err(("unexpected EOF", name.span).into());
}; };
devour_whitespace(toks); devour_whitespace(toks);
@ -179,7 +219,7 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
if tok.kind == '{' { if tok.kind == '{' {
toks.next(); toks.next();
eat_stmts(toks, &mut scope.clone(), super_selector)? eat_stmts(toks, &mut scope.clone(), super_selector)?
} else if has_include { } else if has_content {
eat_stmts(toks, &mut scope.clone(), super_selector)? eat_stmts(toks, &mut scope.clone(), super_selector)?
} else { } else {
Vec::new() Vec::new()
@ -188,7 +228,7 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
Vec::new() Vec::new()
}; };
let mixin = scope.get_mixin(&name)?.clone(); let mixin = scope.get_mixin(name)?.clone();
let rules = mixin let rules = mixin
.args(args, scope, super_selector)? .args(args, scope, super_selector)?

View File

@ -1,6 +1,8 @@
use std::iter::Peekable; use std::iter::Peekable;
use crate::common::{Brackets, ListSeparator, Pos}; use codemap::{Span, Spanned};
use crate::common::{Brackets, ListSeparator};
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -28,71 +30,98 @@ mod unknown;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) enum AtRule { pub(crate) enum AtRule {
Warn(Pos, String), Warn(String),
Debug(Pos, String), Debug(String),
Mixin(String, Box<Mixin>), Mixin(String, Box<Mixin>),
Function(String, Box<Function>), Function(String, Box<Function>),
Return(Vec<Token>), Return(Vec<Token>),
Charset, Charset,
Content, Content,
Unknown(UnknownAtRule), Unknown(UnknownAtRule),
For(Vec<Stmt>), For(Vec<Spanned<Stmt>>),
Each(Vec<Stmt>), Each(Vec<Spanned<Stmt>>),
While(Vec<Stmt>), While(Vec<Spanned<Stmt>>),
Include(Vec<Spanned<Stmt>>),
If(If), If(If),
AtRoot(Vec<Stmt>), AtRoot(Vec<Spanned<Stmt>>),
} }
impl AtRule { impl AtRule {
pub fn from_tokens<I: Iterator<Item = Token>>( pub fn from_tokens<I: Iterator<Item = Token>>(
rule: &AtRuleKind, rule: &AtRuleKind,
pos: Pos, kind_span: Span,
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &mut Scope, scope: &mut Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<AtRule> { ) -> SassResult<Spanned<AtRule>> {
devour_whitespace(toks); devour_whitespace(toks);
Ok(match rule { Ok(match rule {
AtRuleKind::Error => { AtRuleKind::Error => {
let message = Value::from_vec( let Spanned {
node: message,
span,
} = Value::from_vec(
read_until_semicolon_or_closing_curly_brace(toks), read_until_semicolon_or_closing_curly_brace(toks),
scope, scope,
super_selector, super_selector,
)?; )?;
return Err(message.to_string().into()); return Err((message.to_css_string(span)?, span.merge(kind_span)).into());
} }
AtRuleKind::Warn => { AtRuleKind::Warn => {
let message = Value::from_vec( let Spanned {
node: message,
span,
} = Value::from_vec(
read_until_semicolon_or_closing_curly_brace(toks), read_until_semicolon_or_closing_curly_brace(toks),
scope, scope,
super_selector, super_selector,
)?; )?;
span.merge(kind_span);
if toks.peek().unwrap().kind == ';' { if toks.peek().unwrap().kind == ';' {
toks.next(); kind_span.merge(toks.next().unwrap().pos());
} }
devour_whitespace(toks); devour_whitespace(toks);
AtRule::Warn(pos, message.to_string()) Spanned {
node: AtRule::Warn(message.to_css_string(span)?),
span,
}
} }
AtRuleKind::Debug => { AtRuleKind::Debug => {
let message = Value::from_vec( let Spanned {
node: message,
span,
} = Value::from_vec(
read_until_semicolon_or_closing_curly_brace(toks), read_until_semicolon_or_closing_curly_brace(toks),
scope, scope,
super_selector, super_selector,
)?; )?;
span.merge(kind_span);
if toks.peek().unwrap().kind == ';' { if toks.peek().unwrap().kind == ';' {
toks.next(); kind_span.merge(toks.next().unwrap().pos());
} }
devour_whitespace(toks); devour_whitespace(toks);
AtRule::Debug(pos, message.inspect()) Spanned {
node: AtRule::Debug(message.inspect(span)?),
span,
}
} }
AtRuleKind::Mixin => { AtRuleKind::Mixin => {
let (name, mixin) = Mixin::decl_from_tokens(toks, scope, super_selector)?; let Spanned {
AtRule::Mixin(name, Box::new(mixin)) node: (name, mixin),
span,
} = Mixin::decl_from_tokens(toks, scope, super_selector)?;
Spanned {
node: AtRule::Mixin(name, Box::new(mixin)),
span,
}
} }
AtRuleKind::Function => { AtRuleKind::Function => {
let (name, func) = Function::decl_from_tokens(toks, scope.clone(), super_selector)?; let (name, func) = Function::decl_from_tokens(toks, scope.clone(), super_selector)?;
AtRule::Function(name, Box::new(func)) Spanned {
node: AtRule::Function(name, Box::new(func)),
span: kind_span,
}
} }
AtRuleKind::Return => { AtRuleKind::Return => {
let v = read_until_semicolon_or_closing_curly_brace(toks); let v = read_until_semicolon_or_closing_curly_brace(toks);
@ -100,7 +129,10 @@ impl AtRule {
toks.next(); toks.next();
} }
devour_whitespace(toks); devour_whitespace(toks);
AtRule::Return(v) Spanned {
node: AtRule::Return(v),
span: kind_span,
}
} }
AtRuleKind::Use => todo!("@use not yet implemented"), AtRuleKind::Use => todo!("@use not yet implemented"),
AtRuleKind::Annotation => todo!("@annotation not yet implemented"), AtRuleKind::Annotation => todo!("@annotation not yet implemented"),
@ -132,21 +164,27 @@ impl AtRule {
is_some, is_some,
)? )?
.into_iter() .into_iter()
.filter_map(|s| match s { .filter_map(|s| match s.node {
Stmt::Style(..) => { Stmt::Style(..) => {
styles.push(s); styles.push(s);
None None
} }
_ => Some(s), _ => Some(s),
}) })
.collect::<Vec<Stmt>>(); .collect::<Vec<Spanned<Stmt>>>();
let mut stmts = vec![Stmt::RuleSet(RuleSet { let mut stmts = vec![Spanned {
selector: selector.clone(), node: Stmt::RuleSet(RuleSet {
rules: styles, selector: selector.clone(),
super_selector: Selector::new(), rules: styles,
})]; super_selector: Selector::new(),
}),
span: kind_span,
}];
stmts.extend(raw_stmts); stmts.extend(raw_stmts);
AtRule::AtRoot(stmts) Spanned {
node: AtRule::AtRoot(stmts),
span: kind_span,
}
} }
AtRuleKind::Charset => { AtRuleKind::Charset => {
read_until_semicolon_or_closing_curly_brace(toks); read_until_semicolon_or_closing_curly_brace(toks);
@ -154,36 +192,51 @@ impl AtRule {
toks.next(); toks.next();
} }
devour_whitespace(toks); devour_whitespace(toks);
AtRule::Charset Spanned {
node: AtRule::Charset,
span: kind_span,
}
} }
AtRuleKind::Each => { AtRuleKind::Each => {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
devour_whitespace(toks); devour_whitespace(toks);
let mut vars = Vec::new(); let mut vars = Vec::new();
let mut span = kind_span;
loop { loop {
match toks.next().ok_or("expected \"$\".")?.kind { let next = toks.next().ok_or(("expected \"$\".", span))?;
span = next.pos();
match next.kind {
'$' => vars.push(eat_ident(toks, scope, super_selector)?), '$' => vars.push(eat_ident(toks, scope, super_selector)?),
_ => return Err("expected \"$\".".into()), _ => return Err(("expected \"$\".", next.pos()).into()),
} }
devour_whitespace(toks); devour_whitespace(toks);
if toks.peek().ok_or("expected \"$\".")?.kind == ',' { if toks
.peek()
.ok_or(("expected \"$\".", vars[vars.len() - 1].span))?
.kind
== ','
{
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
} else { } else {
break; break;
} }
} }
if toks.peek().is_none() if toks.peek().is_none() {
|| eat_ident(toks, scope, super_selector)?.to_ascii_lowercase() != "in" todo!()
{ }
return Err("Expected \"in\".".into()); let i = eat_ident(toks, scope, super_selector)?;
if i.node.to_ascii_lowercase() != "in" {
return Err(("Expected \"in\".", i.span).into());
} }
devour_whitespace(toks); devour_whitespace(toks);
let iterator = match Value::from_vec( let iterator = match Value::from_vec(
read_until_open_curly_brace(toks), read_until_open_curly_brace(toks),
scope, scope,
super_selector, super_selector,
)? { )?
.node
{
Value::List(v, ..) => v, Value::List(v, ..) => v,
Value::Map(m) => m Value::Map(m) => m
.into_iter() .into_iter()
@ -212,7 +265,14 @@ impl AtRule {
if vars.len() == 1 { if vars.len() == 1 {
scope.insert_var( scope.insert_var(
&vars[0], &vars[0],
Value::List(this_iterator, ListSeparator::Space, Brackets::None), Spanned {
node: Value::List(
this_iterator,
ListSeparator::Space,
Brackets::None,
),
span: vars[0].span,
},
)?; )?;
} else { } else {
for (var, val) in vars.clone().into_iter().zip( for (var, val) in vars.clone().into_iter().zip(
@ -220,7 +280,7 @@ impl AtRule {
.into_iter() .into_iter()
.chain(std::iter::once(Value::Null).cycle()), .chain(std::iter::once(Value::Null).cycle()),
) { ) {
scope.insert_var(&var, val)?; scope.insert_var(&var, Spanned { node: val, span })?;
} }
} }
@ -230,19 +290,28 @@ impl AtRule {
super_selector, super_selector,
)?); )?);
} }
AtRule::Each(stmts) Spanned {
node: AtRule::Each(stmts),
span: kind_span,
}
} }
AtRuleKind::Extend => todo!("@extend not yet implemented"), AtRuleKind::Extend => todo!("@extend not yet implemented"),
AtRuleKind::If => AtRule::If(If::from_tokens(toks)?), AtRuleKind::If => Spanned {
node: AtRule::If(If::from_tokens(toks)?),
span: kind_span,
},
AtRuleKind::Else => todo!("@else not yet implemented"), AtRuleKind::Else => todo!("@else not yet implemented"),
AtRuleKind::For => for_rule::parse_for(toks, scope, super_selector)?, AtRuleKind::For => Spanned {
node: for_rule::parse_for(toks, scope, super_selector, kind_span)?,
span: kind_span,
},
AtRuleKind::While => { AtRuleKind::While => {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
devour_whitespace(toks); devour_whitespace(toks);
let cond = read_until_open_curly_brace(toks); let cond = read_until_open_curly_brace(toks);
if cond.is_empty() { if cond.is_empty() {
return Err("Expected expression.".into()); return Err(("Expected expression.", kind_span).into());
} }
toks.next(); toks.next();
@ -252,23 +321,39 @@ impl AtRule {
devour_whitespace(toks); devour_whitespace(toks);
while Value::from_vec(cond.clone(), scope, super_selector)?.is_true()? { let mut val = Value::from_vec(cond.clone(), scope, super_selector)?;
while val.node.is_true(val.span)? {
stmts.extend(eat_stmts( stmts.extend(eat_stmts(
&mut body.clone().into_iter().peekable(), &mut body.clone().into_iter().peekable(),
scope, scope,
super_selector, super_selector,
)?); )?);
val = Value::from_vec(cond.clone(), scope, super_selector)?;
}
Spanned {
node: AtRule::While(stmts),
span: kind_span,
} }
AtRule::While(stmts)
} }
AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"), AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"),
AtRuleKind::Unknown(name) => AtRule::Unknown(UnknownAtRule::from_tokens( AtRuleKind::Unknown(name) => Spanned {
toks, node: AtRule::Unknown(UnknownAtRule::from_tokens(
name, toks,
scope, name,
super_selector, scope,
)?), super_selector,
AtRuleKind::Content => AtRule::Content, kind_span,
)?),
span: kind_span,
},
AtRuleKind::Content => Spanned {
node: AtRule::Content,
span: kind_span,
},
AtRuleKind::Include => Spanned {
node: AtRule::Include(eat_include(toks, scope, super_selector)?),
span: kind_span,
},
_ => todo!("encountered unimplemented at rule"), _ => todo!("encountered unimplemented at rule"),
}) })
} }

View File

@ -1,5 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use codemap::Spanned;
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -9,29 +11,35 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &mut Scope, scope: &mut Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Vec<Stmt>> { ) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while let Some(expr) = eat_expr(toks, scope, super_selector)? { while let Some(expr) = eat_expr(toks, scope, super_selector)? {
match expr { let span = expr.span;
Expr::AtRule(a) => stmts.push(Stmt::AtRule(a)), match expr.node {
Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)),
Expr::Styles(s) => stmts.extend(s.into_iter().map(Box::new).map(Stmt::Style)), Expr::Style(s) => stmts.push(Stmt::Style(s).span(span)),
Expr::Include(s) => stmts.extend(s), Expr::Styles(s) => stmts.extend(
Expr::MixinDecl(..) | Expr::FunctionDecl(..) | Expr::Debug(..) | Expr::Warn(..) => { s.into_iter()
todo!() .map(Box::new)
} .map(Stmt::Style)
.map(|style| Spanned { node: style, span }),
),
Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(),
Expr::Selector(selector) => { Expr::Selector(selector) => {
let rules = eat_stmts(toks, scope, &super_selector.zip(&selector))?; let rules = eat_stmts(toks, scope, &super_selector.zip(&selector))?;
stmts.push(Stmt::RuleSet(RuleSet { stmts.push(
super_selector: super_selector.clone(), Stmt::RuleSet(RuleSet {
selector, super_selector: super_selector.clone(),
rules, selector,
})); rules,
})
.span(span),
);
} }
Expr::VariableDecl(name, val) => { Expr::VariableDecl(name, val) => {
scope.insert_var(&name, *val)?; scope.insert_var(&name, *val)?;
} }
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)), Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s).span(span)),
} }
} }
Ok(stmts) Ok(stmts)
@ -43,17 +51,20 @@ pub(crate) fn eat_stmts_at_root<I: Iterator<Item = Token>>(
super_selector: &Selector, super_selector: &Selector,
mut nesting: usize, mut nesting: usize,
is_some: bool, is_some: bool,
) -> SassResult<Vec<Stmt>> { ) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while let Some(expr) = eat_expr(toks, scope, super_selector)? { while let Some(expr) = eat_expr(toks, scope, super_selector)? {
match expr { let span = expr.span;
Expr::AtRule(a) => stmts.push(Stmt::AtRule(a)), match expr.node {
Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::AtRule(a) => stmts.push(Stmt::AtRule(a).span(span)),
Expr::Styles(s) => stmts.extend(s.into_iter().map(Box::new).map(Stmt::Style)), Expr::Style(s) => stmts.push(Stmt::Style(s).span(span)),
Expr::Include(s) => stmts.extend(s), Expr::Styles(s) => stmts.extend(
Expr::MixinDecl(..) | Expr::FunctionDecl(..) | Expr::Debug(..) | Expr::Warn(..) => { s.into_iter()
todo!() .map(Box::new)
} .map(Stmt::Style)
.map(|style| Spanned { node: style, span }),
),
Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(),
Expr::Selector(mut selector) => { Expr::Selector(mut selector) => {
if nesting > 1 || is_some { if nesting > 1 || is_some {
selector = super_selector.zip(&selector); selector = super_selector.zip(&selector);
@ -63,20 +74,23 @@ pub(crate) fn eat_stmts_at_root<I: Iterator<Item = Token>>(
nesting += 1; nesting += 1;
let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true)?; let rules = eat_stmts_at_root(toks, scope, &selector, nesting, true)?;
nesting -= 1; nesting -= 1;
stmts.push(Stmt::RuleSet(RuleSet { stmts.push(
super_selector: if nesting > 1 { Stmt::RuleSet(RuleSet {
super_selector.clone() super_selector: if nesting > 1 {
} else { super_selector.clone()
Selector::new() } else {
}, Selector::new()
selector, },
rules, selector,
})); rules,
})
.span(span),
);
} }
Expr::VariableDecl(name, val) => { Expr::VariableDecl(name, val) => {
scope.insert_var(&name, *val)?; scope.insert_var(&name, *val)?;
} }
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)), Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s).span(span)),
} }
} }
Ok(stmts) Ok(stmts)

View File

@ -1,5 +1,7 @@
use std::iter::Peekable; use std::iter::Peekable;
use codemap::{Span, Spanned};
use super::parse::eat_stmts; use super::parse::eat_stmts;
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
@ -12,7 +14,7 @@ pub(crate) struct UnknownAtRule {
pub name: String, pub name: String,
pub super_selector: Selector, pub super_selector: Selector,
pub params: String, pub params: String,
pub body: Vec<Stmt>, pub body: Vec<Spanned<Stmt>>,
} }
impl UnknownAtRule { impl UnknownAtRule {
@ -21,6 +23,7 @@ impl UnknownAtRule {
name: &str, name: &str,
scope: &mut Scope, scope: &mut Scope,
super_selector: &Selector, super_selector: &Selector,
kind_span: Span,
) -> SassResult<UnknownAtRule> { ) -> SassResult<UnknownAtRule> {
let mut params = String::new(); let mut params = String::new();
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
@ -29,9 +32,8 @@ impl UnknownAtRule {
'#' => { '#' => {
if toks.peek().unwrap().kind == '{' { if toks.peek().unwrap().kind == '{' {
toks.next(); toks.next();
params.push_str( let interpolation = parse_interpolation(toks, scope, super_selector)?;
&parse_interpolation(toks, scope, super_selector)?.to_string(), params.push_str(&interpolation.node.to_css_string(interpolation.span)?);
);
continue; continue;
} else { } else {
params.push(tok.kind); params.push(tok.kind);
@ -49,20 +51,26 @@ impl UnknownAtRule {
let raw_body = eat_stmts(toks, scope, super_selector)?; let raw_body = eat_stmts(toks, scope, super_selector)?;
let mut body = Vec::with_capacity(raw_body.len()); let mut body = Vec::with_capacity(raw_body.len());
body.push(Stmt::RuleSet(RuleSet::new())); body.push(Spanned {
node: Stmt::RuleSet(RuleSet::new()),
span: kind_span,
});
let mut rules = Vec::new(); let mut rules = Vec::new();
for stmt in raw_body { for stmt in raw_body {
match stmt { match stmt.node {
s @ Stmt::Style(..) => rules.push(s), Stmt::Style(..) => rules.push(stmt),
s => body.push(s), _ => body.push(stmt),
} }
} }
body[0] = Stmt::RuleSet(RuleSet { body[0] = Spanned {
selector: super_selector.clone(), node: Stmt::RuleSet(RuleSet {
rules, selector: super_selector.clone(),
super_selector: Selector::new(), rules,
}); super_selector: Selector::new(),
}),
span: kind_span,
};
Ok(UnknownAtRule { Ok(UnknownAtRule {
name: name.to_owned(), name: name.to_owned(),

View File

@ -13,39 +13,66 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"hsl".to_owned(), "hsl".to_owned(),
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
if args.is_empty() { if args.is_empty() {
return Err("Missing argument $channels.".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 arg!(args, scope, super_selector, 0, "channels") {
Value::List(v, ..) => v, Value::List(v, ..) => v,
_ => return Err("Missing argument $channels.".into()), _ => return Err(("Missing argument $channels.", args.span()).into()),
}; };
if channels.len() > 3 { if channels.len() > 3 {
return Err(format!( return Err((
"Only 3 elements allowed, but {} were passed.", format!(
channels.len() "Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
) )
.into()); .into());
} }
let lightness = match channels.pop() { let lightness = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100), Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => return Err(format!("$lightness: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $lightness.".into()), return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $lightness.", args.span()).into()),
}; };
let saturation = match channels.pop() { let saturation = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100), Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => return Err(format!("$saturation: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $saturation.".into()), return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $saturation.", args.span()).into()),
}; };
let hue = match channels.pop() { let hue = match channels.pop() {
Some(Value::Dimension(n, _)) => n, Some(Value::Dimension(n, _)) => n,
Some(v) => return Err(format!("$hue: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $hue.".into()), return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $hue.", args.span()).into()),
}; };
Ok(Value::Color(Color::from_hsla( Ok(Value::Color(Color::from_hsla(
@ -60,48 +87,90 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
v if v.is_special_function() => { v if v.is_special_function() => {
let saturation = arg!(args, scope, super_selector, 1, "saturation"); let saturation = arg!(args, scope, super_selector, 1, "saturation");
let lightness = arg!(args, scope, super_selector, 2, "lightness"); let lightness = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!("hsl({}, {}, {}", v, saturation, lightness); let mut string = format!(
"hsl({}, {}, {}",
v.to_css_string(args.span())?,
saturation.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$hue: {} is not a number.", v).into()), v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let saturation = match arg!(args, scope, super_selector, 1, "saturation") { let saturation = match arg!(args, scope, super_selector, 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 = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!("hsl({}, {}, {}", hue, v, lightness); let mut string = format!(
"hsl({}, {}, {}",
hue,
v.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$saturation: {} is not a number.", v).into()), v => {
return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let lightness = match arg!(args, scope, super_selector, 2, "lightness") { let lightness = match arg!(args, scope, super_selector, 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!("hsl({}, {}, {}", hue, saturation, v); let mut string = format!(
"hsl({}, {}, {}",
hue,
saturation,
v.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$lightness: {} is not a number.", v).into()), v => {
return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let alpha = match arg!( let alpha = match arg!(
args, args,
@ -113,17 +182,34 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
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(..) => {
return Err( return Err((
format!("$alpha: Expected {} to have no units or \"%\".", v).into() format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!("hsl({}, {}, {}, {})", hue, saturation, lightness, v), format!(
"hsl({}, {}, {}, {})",
hue,
saturation,
lightness,
v.to_css_string(args.span())?
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
v => return Err(format!("$alpha: {} is not a number.", v).into()), v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(Color::from_hsla( Ok(Value::Color(Color::from_hsla(
hue, saturation, lightness, alpha, hue, saturation, lightness, alpha,
@ -135,39 +221,66 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"hsla".to_owned(), "hsla".to_owned(),
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
if args.is_empty() { if args.is_empty() {
return Err("Missing argument $channels.".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 arg!(args, scope, super_selector, 0, "channels") {
Value::List(v, ..) => v, Value::List(v, ..) => v,
_ => return Err("Missing argument $channels.".into()), _ => return Err(("Missing argument $channels.", args.span()).into()),
}; };
if channels.len() > 3 { if channels.len() > 3 {
return Err(format!( return Err((
"Only 3 elements allowed, but {} were passed.", format!(
channels.len() "Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
) )
.into()); .into());
} }
let lightness = match channels.pop() { let lightness = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100), Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => return Err(format!("$lightness: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $lightness.".into()), return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $lightness.", args.span()).into()),
}; };
let saturation = match channels.pop() { let saturation = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100), Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => return Err(format!("$saturation: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $saturation.".into()), return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $saturation.", args.span()).into()),
}; };
let hue = match channels.pop() { let hue = match channels.pop() {
Some(Value::Dimension(n, _)) => n, Some(Value::Dimension(n, _)) => n,
Some(v) => return Err(format!("$hue: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $hue.".into()), return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $hue.", args.span()).into()),
}; };
Ok(Value::Color(Color::from_hsla( Ok(Value::Color(Color::from_hsla(
@ -182,48 +295,90 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
v if v.is_special_function() => { v if v.is_special_function() => {
let saturation = arg!(args, scope, super_selector, 1, "saturation"); let saturation = arg!(args, scope, super_selector, 1, "saturation");
let lightness = arg!(args, scope, super_selector, 2, "lightness"); let lightness = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!("hsla({}, {}, {}", v, saturation, lightness); let mut string = format!(
"hsla({}, {}, {}",
v.to_css_string(args.span())?,
saturation.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$hue: {} is not a number.", v).into()), v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let saturation = match arg!(args, scope, super_selector, 1, "saturation") { let saturation = match arg!(args, scope, super_selector, 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 = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!("hsla({}, {}, {}", hue, v, lightness); let mut string = format!(
"hsla({}, {}, {}",
hue,
v.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$saturation: {} is not a number.", v).into()), v => {
return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let lightness = match arg!(args, scope, super_selector, 2, "lightness") { let lightness = match arg!(args, scope, super_selector, 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!("hsla({}, {}, {}", hue, saturation, v); let mut string = format!(
"hsla({}, {}, {}",
hue,
saturation,
v.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$lightness: {} is not a number.", v).into()), v => {
return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let alpha = match arg!( let alpha = match arg!(
args, args,
@ -235,17 +390,34 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
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(..) => {
return Err( return Err((
format!("$alpha: Expected {} to have no units or \"%\".", v).into() format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!("hsl({}, {}, {}, {})", hue, saturation, lightness, v), format!(
"hsl({}, {}, {}, {})",
hue,
saturation,
lightness,
v.to_css_string(args.span())?
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
v => return Err(format!("$alpha: {} is not a number.", v).into()), v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(Color::from_hsla( Ok(Value::Color(Color::from_hsla(
hue, saturation, lightness, alpha, hue, saturation, lightness, alpha,
@ -259,7 +431,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)), Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -269,7 +445,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)), Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -279,7 +459,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)), Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -289,11 +473,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let degrees = match arg!(args, scope, super_selector, 1, "degrees") { let degrees = match arg!(args, scope, super_selector, 1, "degrees") {
Value::Dimension(n, _) => n, Value::Dimension(n, _) => n,
v => return Err(format!("$degrees: {} is not a number.", v).into()), v => {
return Err((
format!(
"$degrees: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.adjust_hue(degrees))) Ok(Value::Color(color.adjust_hue(degrees)))
}), }),
@ -304,11 +503,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.lighten(amount))) Ok(Value::Color(color.lighten(amount)))
}), }),
@ -319,11 +533,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.darken(amount))) Ok(Value::Color(color.darken(amount)))
}), }),
@ -337,14 +566,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
format!( format!(
"saturate({})", "saturate({})",
arg!(args, scope, super_selector, 0, "amount") arg!(args, scope, super_selector, 0, "amount")
.to_css_string(args.span())?
), ),
QuoteKind::None, QuoteKind::None,
)); ));
} }
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
@ -354,7 +593,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
QuoteKind::None, QuoteKind::None,
)) ))
} }
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.saturate(amount))) Ok(Value::Color(color.saturate(amount)))
}), }),
@ -365,11 +610,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.desaturate(amount))) Ok(Value::Color(color.desaturate(amount)))
}), }),
@ -386,7 +646,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
QuoteKind::None, QuoteKind::None,
)) ))
} }
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.desaturate(Number::one()))) Ok(Value::Color(color.desaturate(Number::one())))
}), }),
@ -397,7 +663,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.complement())) Ok(Value::Color(color.complement()))
}), }),
@ -413,18 +685,33 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
1, 1,
"weight" = Value::Dimension(Number::from(100), Unit::Percent) "weight" = Value::Dimension(Number::from(100), Unit::Percent)
) { ) {
Value::Dimension(n, u) => bound!("weight", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => return Err(format!("$weight: {} is not a number.", v).into()), v => {
return Err((
format!(
"$weight: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Color(c.invert(weight))), Value::Color(c) => Ok(Value::Color(c.invert(weight))),
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent) => {
Ok(Value::Ident(format!("invert({}%)", n), QuoteKind::None)) Ok(Value::Ident(format!("invert({}%)", n), QuoteKind::None))
} }
Value::Dimension(..) => Err( Value::Dimension(..) => Err((
"Only one argument may be passed to the plain-CSS invert() function.".into(), "Only one argument may be passed to the plain-CSS invert() function.",
), args.span(),
v => Err(format!("$color: {} is not a color.", v).into()), )
.into()),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );

View File

@ -13,7 +13,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -27,7 +31,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
format!("opacity({}{})", num, unit), format!("opacity({}{})", num, unit),
QuoteKind::None, QuoteKind::None,
)), )),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -37,11 +45,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.fade_in(amount))) Ok(Value::Color(color.fade_in(amount)))
}), }),
@ -52,11 +75,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.fade_in(amount))) Ok(Value::Color(color.fade_in(amount)))
}), }),
@ -67,11 +105,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.fade_out(amount))) Ok(Value::Color(color.fade_out(amount)))
}), }),
@ -82,11 +135,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let amount = match arg!(args, scope, super_selector, 1, "amount") { let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!("amount", n, u, 0, 1), Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 1),
v => return Err(format!("$amount: {} is not a number.", v).into()), v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.fade_out(amount))) Ok(Value::Color(color.fade_out(amount)))
}), }),

View File

@ -12,9 +12,19 @@ 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, $scope:ident, $super_selector:ident) => {
let x = $low; let x = $low;
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high)), Value::Dimension(n, u) => Some(bound!($args, $arg, n, u, x, $high)),
Value::Null => None, Value::Null => None,
v => return Err(format!("${}: {} is not a number.", $arg, v).into()), v => {
return Err((
format!(
"${}: {} is not a number.",
$arg,
v.to_css_string($args.span())?
),
$args.span(),
)
.into())
}
}; };
}; };
} }
@ -23,9 +33,19 @@ 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, $scope:ident, $super_selector:ident) => {
let x = $low; let x = $low;
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high) / Number::from(100)), Value::Dimension(n, u) => Some(bound!($args, $arg, n, u, x, $high) / Number::from(100)),
Value::Null => None, Value::Null => None,
v => return Err(format!("${}: {} is not a number.", $arg, v).into()), v => {
return Err((
format!(
"${}: {} is not a number.",
$arg,
v.to_css_string($args.span())?
),
$args.span(),
)
.into())
}
}; };
}; };
} }
@ -33,12 +53,12 @@ macro_rules! opt_hsl {
pub(crate) fn register(f: &mut HashMap<String, Builtin>) { pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
f.insert("change-color".to_owned(), Builtin::new(|mut args, scope, super_selector| { f.insert("change-color".to_owned(), Builtin::new(|mut args, scope, super_selector| {
if args.get_positional(1, scope, super_selector).is_some() { if args.get_positional(1, scope, super_selector).is_some() {
return Err("Only one positional argument is allowed. All other arguments must be passed by name.".into()); return Err(("Only one positional argument is allowed. All other arguments must be passed by name.", args.span()).into());
} }
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => return Err((format!("$color: {} is not a color.", v.to_css_string(args.span())?), args.span()).into()),
}; };
opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector); opt_rgba!(args, alpha, "alpha", 0, 1, scope, super_selector);
@ -53,7 +73,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let hue = match named_arg!(args, scope, super_selector, "hue"=Value::Null) { let hue = match named_arg!(args, scope, super_selector, "hue"=Value::Null) {
Value::Dimension(n, _) => Some(n), Value::Dimension(n, _) => Some(n),
Value::Null => None, Value::Null => None,
v => return Err(format!("$hue: {} is not a number.", v).into()), v => return Err((format!("$hue: {} is not a number.", v.to_css_string(args.span())?), args.span()).into()),
}; };
opt_hsl!(args, saturation, "saturation", 0, 100, scope, super_selector); opt_hsl!(args, saturation, "saturation", 0, 100, scope, super_selector);
@ -76,7 +96,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector); opt_rgba!(args, alpha, "alpha", -1, 1, scope, super_selector);
@ -96,7 +122,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) { let hue = match named_arg!(args, scope, super_selector, "hue" = Value::Null) {
Value::Dimension(n, _) => Some(n), Value::Dimension(n, _) => Some(n),
Value::Null => None, Value::Null => None,
v => return Err(format!("$hue: {} is not a number.", v).into()), v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
opt_hsl!( opt_hsl!(
@ -142,7 +174,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => return Err((format!("$color: {} is not a color.", v.to_css_string(args.span())?), args.span()).into()),
}; };
macro_rules! opt_scale_arg { macro_rules! opt_scale_arg {
@ -150,15 +182,15 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let x = $low; let x = $low;
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) { let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent) => {
Some(bound!($arg, n, Unit::Percent, x, $high) / Number::from(100)) Some(bound!($args, $arg, n, Unit::Percent, x, $high) / Number::from(100))
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err(
format!("${}: Expected {} to have unit \"%\".", $arg, v).into() (format!("${}: Expected {} to have unit \"%\".", $arg, v.to_css_string($args.span())?), $args.span()).into()
) )
} }
Value::Null => None, Value::Null => None,
v => return Err(format!("${}: {} is not a number.", $arg, v).into()), v => return Err((format!("${}: {} is not a number.", $arg, v.to_css_string($args.span())?), $args.span()).into()),
}; };
}; };
} }
@ -229,7 +261,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
let color = match arg!(args, scope, super_selector, 0, "color") { let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Ident(color.to_ie_hex_str(), QuoteKind::None)) Ok(Value::Ident(color.to_ie_hex_str(), QuoteKind::None))
}), }),

View File

@ -13,21 +13,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"rgb".to_owned(), "rgb".to_owned(),
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
if args.is_empty() { if args.is_empty() {
return Err("Missing argument $channels.".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 arg!(args, scope, super_selector, 0, "channels") {
Value::List(v, ..) => v, Value::List(v, ..) => v,
_ => return Err("Missing argument $channels.".into()), _ => return Err(("Missing argument $channels.", args.span()).into()),
}; };
if channels.len() > 3 { if channels.len() > 3 {
return Err(format!( return Err((
"Only 3 elements allowed, but {} were passed.", format!(
channels.len() "Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
) )
.into()); .into());
} }
let blue = match channels.pop() { let blue = match channels.pop() {
@ -39,12 +42,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let green = channels.pop().unwrap(); let green = channels.pop().unwrap();
let red = channels.pop().unwrap(); let red = channels.pop().unwrap();
return Ok(Value::Ident( return Ok(Value::Ident(
format!("rgb({}, {}, {})", red, green, v), format!(
"rgb({}, {}, {})",
red.to_css_string(args.span())?,
green.to_css_string(args.span())?,
v.to_css_string(args.span())?
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
Some(v) => return Err(format!("$blue: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $blue.".into()), return Err((
format!("$blue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $blue.", args.span()).into()),
}; };
let green = match channels.pop() { let green = match channels.pop() {
@ -54,13 +68,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
} }
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
let string = match channels.pop() { let string = match channels.pop() {
Some(red) => format!("rgb({}, {}, {})", red, v, blue), Some(red) => format!(
None => format!("rgb({} {})", v, blue), "rgb({}, {}, {})",
red.to_css_string(args.span())?,
v.to_css_string(args.span())?,
blue
),
None => format!("rgb({} {})", v.to_css_string(args.span())?, blue),
}; };
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
Some(v) => return Err(format!("$green: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $green.".into()), return Err((
format!("$green: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $green.", args.span()).into()),
}; };
let red = match channels.pop() { let red = match channels.pop() {
@ -70,12 +95,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
} }
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!("rgb({}, {}, {})", v, green, blue), format!(
"rgb({}, {}, {})",
v.to_css_string(args.span())?,
green,
blue
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
Some(v) => return Err(format!("$red: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $red.".into()), return Err((
format!("$red: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $red.", args.span()).into()),
}; };
let color = Color::from_rgba(red, green, blue, Number::one()); let color = Color::from_rgba(red, green, blue, Number::one());
@ -87,19 +123,34 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
v if v.is_special_function() => { v if v.is_special_function() => {
let alpha = arg!(args, scope, super_selector, 1, "alpha"); let alpha = arg!(args, scope, super_selector, 1, "alpha");
return Ok(Value::Ident( return Ok(Value::Ident(
format!("rgb({}, {})", v, alpha), format!(
"rgb({}, {})",
v.to_css_string(args.span())?,
alpha.to_css_string(args.span())?
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let alpha = match arg!(args, scope, super_selector, 1, "alpha") { let alpha = match arg!(args, scope, super_selector, 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(..) => {
return Err( return Err((
format!("$alpha: Expected {} to have no units or \"%\".", v).into() format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
@ -108,12 +159,18 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
color.red(), color.red(),
color.green(), color.green(),
color.blue(), color.blue(),
v v.to_css_string(args.span())?
), ),
QuoteKind::None, QuoteKind::None,
)); ));
} }
v => return Err(format!("$alpha: {} is not a number.", v).into()), v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.with_alpha(alpha))) Ok(Value::Color(color.with_alpha(alpha)))
} else { } else {
@ -123,24 +180,41 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
(n / Number::from(100)) * Number::from(255) (n / Number::from(100)) * Number::from(255)
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err((
format!("$red: Expected {} to have no units or \"%\".", v).into() format!(
"$red: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let green = arg!(args, scope, super_selector, 1, "green"); let green = arg!(args, scope, super_selector, 1, "green");
let blue = arg!(args, scope, super_selector, 2, "blue"); let blue = arg!(args, scope, super_selector, 2, "blue");
let mut string = format!("rgb({}, {}, {}", v, green, blue); let mut string = format!(
"rgb({}, {}, {}",
v.to_css_string(args.span())?,
green.to_css_string(args.span())?,
blue.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$red: {} is not a number.", v).into()), v => {
return Err((
format!("$red: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let green = match arg!(args, scope, super_selector, 1, "green") { let green = match arg!(args, scope, super_selector, 1, "green") {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None) => n,
@ -148,23 +222,40 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
(n / Number::from(100)) * Number::from(255) (n / Number::from(100)) * Number::from(255)
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err((
format!("$green: Expected {} to have no units or \"%\".", v).into() format!(
"$green: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let blue = arg!(args, scope, super_selector, 2, "blue"); let blue = arg!(args, scope, super_selector, 2, "blue");
let mut string = format!("rgb({}, {}, {}", red, v, blue); let mut string = format!(
"rgb({}, {}, {}",
red,
v.to_css_string(args.span())?,
blue.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$green: {} is not a number.", v).into()), v => {
return Err((
format!("$green: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let blue = match arg!(args, scope, super_selector, 2, "blue") { let blue = match arg!(args, scope, super_selector, 2, "blue") {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None) => n,
@ -172,22 +263,35 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
(n / Number::from(100)) * Number::from(255) (n / Number::from(100)) * Number::from(255)
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err((
format!("$blue: Expected {} to have no units or \"%\".", v).into() format!(
"$blue: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let mut string = format!("rgb({}, {}, {}", red, green, v); let mut string =
format!("rgb({}, {}, {}", red, green, v.to_css_string(args.span())?);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$blue: {} is not a number.", v).into()), v => {
return Err((
format!("$blue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let alpha = match arg!( let alpha = match arg!(
args, args,
@ -199,15 +303,32 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
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(..) => {
return Err( return Err((
format!("$alpha: Expected {} to have no units or \"%\".", v).into() format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let string = format!("rgb({}, {}, {}, {})", red, green, blue, v); let string = format!(
"rgb({}, {}, {}, {})",
red,
green,
blue,
v.to_css_string(args.span())?
);
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$alpha: {} is not a number.", v).into()), v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(Color::from_rgba(red, green, blue, alpha))) Ok(Value::Color(Color::from_rgba(red, green, blue, alpha)))
} }
@ -217,21 +338,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"rgba".to_owned(), "rgba".to_owned(),
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
if args.is_empty() { if args.is_empty() {
return Err("Missing argument $channels.".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 arg!(args, scope, super_selector, 0, "channels") {
Value::List(v, ..) => v, Value::List(v, ..) => v,
_ => return Err("Missing argument $channels.".into()), _ => return Err(("Missing argument $channels.", args.span()).into()),
}; };
if channels.len() > 3 { if channels.len() > 3 {
return Err(format!( return Err((
"Only 3 elements allowed, but {} were passed.", format!(
channels.len() "Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
) )
.into()); .into());
} }
let blue = match channels.pop() { let blue = match channels.pop() {
@ -243,12 +367,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let green = channels.pop().unwrap(); let green = channels.pop().unwrap();
let red = channels.pop().unwrap(); let red = channels.pop().unwrap();
return Ok(Value::Ident( return Ok(Value::Ident(
format!("rgba({}, {}, {})", red, green, v), format!(
"rgba({}, {}, {})",
red.to_css_string(args.span())?,
green.to_css_string(args.span())?,
v.to_css_string(args.span())?
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
Some(v) => return Err(format!("$blue: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $blue.".into()), return Err((
format!("$blue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $blue.", args.span()).into()),
}; };
let green = match channels.pop() { let green = match channels.pop() {
@ -258,13 +393,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
} }
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
let string = match channels.pop() { let string = match channels.pop() {
Some(red) => format!("rgba({}, {}, {})", red, v, blue), Some(red) => format!(
None => format!("rgba({} {})", v, blue), "rgba({}, {}, {})",
red.to_css_string(args.span())?,
v.to_css_string(args.span())?,
blue
),
None => format!("rgba({} {})", v.to_css_string(args.span())?, blue),
}; };
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
Some(v) => return Err(format!("$green: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $green.".into()), return Err((
format!("$green: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $green.", args.span()).into()),
}; };
let red = match channels.pop() { let red = match channels.pop() {
@ -274,12 +420,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
} }
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!("rgba({}, {}, {})", v, green, blue), format!(
"rgba({}, {}, {})",
v.to_css_string(args.span())?,
green,
blue
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
Some(v) => return Err(format!("$red: {} is not a number.", v).into()), Some(v) => {
None => return Err("Missing element $red.".into()), return Err((
format!("$red: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $red.", args.span()).into()),
}; };
let color = Color::from_rgba(red, green, blue, Number::one()); let color = Color::from_rgba(red, green, blue, Number::one());
@ -291,19 +448,34 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
v if v.is_special_function() => { v if v.is_special_function() => {
let alpha = arg!(args, scope, super_selector, 1, "alpha"); let alpha = arg!(args, scope, super_selector, 1, "alpha");
return Ok(Value::Ident( return Ok(Value::Ident(
format!("rgba({}, {})", v, alpha), format!(
"rgba({}, {})",
v.to_css_string(args.span())?,
alpha.to_css_string(args.span())?
),
QuoteKind::None, QuoteKind::None,
)); ));
} }
v => return Err(format!("$color: {} is not a color.", v).into()), v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let alpha = match arg!(args, scope, super_selector, 1, "alpha") { let alpha = match arg!(args, scope, super_selector, 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(..) => {
return Err( return Err((
format!("$alpha: Expected {} to have no units or \"%\".", v).into() format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
@ -312,12 +484,18 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
color.red(), color.red(),
color.green(), color.green(),
color.blue(), color.blue(),
v v.to_css_string(args.span())?
), ),
QuoteKind::None, QuoteKind::None,
)); ));
} }
v => return Err(format!("$alpha: {} is not a number.", v).into()), v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color.with_alpha(alpha))) Ok(Value::Color(color.with_alpha(alpha)))
} else { } else {
@ -327,24 +505,41 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
(n / Number::from(100)) * Number::from(255) (n / Number::from(100)) * Number::from(255)
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err((
format!("$red: Expected {} to have no units or \"%\".", v).into() format!(
"$red: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let green = arg!(args, scope, super_selector, 1, "green"); let green = arg!(args, scope, super_selector, 1, "green");
let blue = arg!(args, scope, super_selector, 2, "blue"); let blue = arg!(args, scope, super_selector, 2, "blue");
let mut string = format!("rgba({}, {}, {}", v, green, blue); let mut string = format!(
"rgba({}, {}, {}",
v.to_css_string(args.span())?,
green.to_css_string(args.span())?,
blue.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$red: {} is not a number.", v).into()), v => {
return Err((
format!("$red: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let green = match arg!(args, scope, super_selector, 1, "green") { let green = match arg!(args, scope, super_selector, 1, "green") {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None) => n,
@ -352,23 +547,40 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
(n / Number::from(100)) * Number::from(255) (n / Number::from(100)) * Number::from(255)
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err((
format!("$green: Expected {} to have no units or \"%\".", v).into() format!(
"$green: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let blue = arg!(args, scope, super_selector, 2, "blue"); let blue = arg!(args, scope, super_selector, 2, "blue");
let mut string = format!("rgba({}, {}, {}", red, v, blue); let mut string = format!(
"rgba({}, {}, {}",
red,
v.to_css_string(args.span())?,
blue.to_css_string(args.span())?
);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$green: {} is not a number.", v).into()), v => {
return Err((
format!("$green: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let blue = match arg!(args, scope, super_selector, 2, "blue") { let blue = match arg!(args, scope, super_selector, 2, "blue") {
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None) => n,
@ -376,22 +588,35 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
(n / Number::from(100)) * Number::from(255) (n / Number::from(100)) * Number::from(255)
} }
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err( return Err((
format!("$blue: Expected {} to have no units or \"%\".", v).into() format!(
"$blue: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let mut string = format!("rgba({}, {}, {}", red, green, v); let mut string =
format!("rgba({}, {}, {}", red, green, v.to_css_string(args.span())?);
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").to_string(), &arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$blue: {} is not a number.", v).into()), v => {
return Err((
format!("$blue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let alpha = match arg!( let alpha = match arg!(
args, args,
@ -403,15 +628,32 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
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(..) => {
return Err( return Err((
format!("$alpha: Expected {} to have no units or \"%\".", v).into() format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
) )
.into())
} }
v if v.is_special_function() => { v if v.is_special_function() => {
let string = format!("rgba({}, {}, {}, {})", red, green, blue, v); let string = format!(
"rgba({}, {}, {}, {})",
red,
green,
blue,
v.to_css_string(args.span())?
);
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(string, QuoteKind::None));
} }
v => return Err(format!("$alpha: {} is not a number.", v).into()), v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(Color::from_rgba(red, green, blue, alpha))) Ok(Value::Color(Color::from_rgba(red, green, blue, alpha)))
} }
@ -423,7 +665,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -433,7 +679,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -443,7 +693,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None)),
v => Err(format!("$color: {} is not a color.", v).into()), v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -453,12 +707,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 3); max_args!(args, 3);
let color1 = match arg!(args, scope, super_selector, 0, "color1") { let color1 = match arg!(args, scope, super_selector, 0, "color1") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color1: {} is not a color.", v).into()), v => {
return Err((
format!("$color1: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let color2 = match arg!(args, scope, super_selector, 1, "color2") { let color2 = match arg!(args, scope, super_selector, 1, "color2") {
Value::Color(c) => c, Value::Color(c) => c,
v => return Err(format!("$color2: {} is not a color.", v).into()), v => {
return Err((
format!("$color2: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let weight = match arg!( let weight = match arg!(
@ -468,8 +734,17 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
2, 2,
"weight" = Value::Dimension(Number::from(50), Unit::None) "weight" = Value::Dimension(Number::from(50), Unit::None)
) { ) {
Value::Dimension(n, u) => bound!("weight", n, u, 0, 100) / Number::from(100), Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => return Err(format!("$weight: {} is not a number.", v).into()), v => {
return Err((
format!(
"$weight: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Color(color1.mix(&color2, weight))) Ok(Value::Color(color1.mix(&color2, weight)))
}), }),

View File

@ -31,24 +31,33 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
}; };
let n = match arg!(args, scope, super_selector, 1, "n") { let n = match arg!(args, scope, super_selector, 1, "n") {
Value::Dimension(num, _) => num, Value::Dimension(num, _) => num,
v => return Err(format!("$n: {} is not a number.", v).into()), v => {
return Err((
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
if n.is_zero() { if n.is_zero() {
return Err("$n: List index may not be 0.".into()); return Err(("$n: List index may not be 0.", args.span()).into());
} }
if n.abs() > Number::from(list.len()) { if n.abs() > Number::from(list.len()) {
return Err(format!( return Err((
"$n: Invalid index {} for a list with {} elements.", format!(
n, "$n: Invalid index {} for a list with {} elements.",
list.len() n,
list.len()
),
args.span(),
) )
.into()); .into());
} }
if n.is_decimal() { if n.is_decimal() {
return Err(format!("$n: {} is not an int.", n).into()); return Err((format!("$n: {} is not an int.", n), args.span()).into());
} }
if n.is_positive() { if n.is_positive() {
@ -83,23 +92,31 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
}; };
let n = match arg!(args, scope, super_selector, 1, "n") { let n = match arg!(args, scope, super_selector, 1, "n") {
Value::Dimension(num, _) => num, Value::Dimension(num, _) => num,
v => return Err(format!("$n: {} is not a number.", v).into()), v => {
return Err((
format!("$n: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
if n.is_zero() { if n.is_zero() {
return Err("$n: List index may not be 0.".into()); return Err(("$n: List index may not be 0.", args.span()).into());
} }
let len = list.len(); let len = list.len();
if n.abs() > Number::from(len) { if n.abs() > Number::from(len) {
return Err( return Err((
format!("$n: Invalid index {} for a list with {} elements.", n, len).into(), format!("$n: Invalid index {} for a list with {} elements.", n, len),
); args.span(),
)
.into());
} }
if n.is_decimal() { if n.is_decimal() {
return Err(format!("$n: {} is not an int.", n).into()); return Err((format!("$n: {} is not an int.", n), args.span()).into());
} }
let val = arg!(args, scope, super_selector, 2, "value"); let val = arg!(args, scope, super_selector, 2, "value");
@ -134,10 +151,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"comma" => ListSeparator::Comma, "comma" => ListSeparator::Comma,
"space" => ListSeparator::Space, "space" => ListSeparator::Space,
_ => { _ => {
return Err("$separator: Must be \"space\", \"comma\", or \"auto\".".into()) return Err((
"$separator: Must be \"space\", \"comma\", or \"auto\".",
args.span(),
)
.into())
} }
}, },
v => return Err(format!("$separator: {} is not a string.", v).into()), v => {
return Err((
format!(
"$separator: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
list.push(val); list.push(val);
@ -177,10 +207,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"comma" => ListSeparator::Comma, "comma" => ListSeparator::Comma,
"space" => ListSeparator::Space, "space" => ListSeparator::Space,
_ => { _ => {
return Err("$separator: Must be \"space\", \"comma\", or \"auto\".".into()) return Err((
"$separator: Must be \"space\", \"comma\", or \"auto\".",
args.span(),
)
.into())
} }
}, },
v => return Err(format!("$separator: {} is not a string.", v).into()), v => {
return Err((
format!(
"$separator: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let brackets = match arg!( let brackets = match arg!(
@ -195,7 +238,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
_ => Brackets::Bracketed, _ => Brackets::Bracketed,
}, },
v => { v => {
if v.is_true()? { if v.is_true(args.span())? {
Brackets::Bracketed Brackets::Bracketed
} else { } else {
Brackets::None Brackets::None
@ -240,7 +283,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem) // Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
let index = match list let index = match list
.into_iter() .into_iter()
.position(|v| v.equals(value.clone()).unwrap()) .position(|v| v.equals(value.clone(), args.span()).unwrap())
{ {
Some(v) => Number::from(v + 1), Some(v) => Number::from(v + 1),
None => return Ok(Value::Null), None => return Ok(Value::Null),
@ -254,7 +297,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let lists = args let lists = args
.get_variadic(scope, super_selector)? .get_variadic(scope, super_selector)?
.into_iter() .into_iter()
.map(|x| match x { .map(|x| match x.node {
Value::List(v, ..) => v, Value::List(v, ..) => v,
Value::Map(m) => m.entries(), Value::Map(m) => m.entries(),
v => vec![v], v => vec![v],

View File

@ -1,18 +1,20 @@
macro_rules! arg { macro_rules! arg {
($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal) => { ($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal) => {
match $args.get_positional($idx, $scope, $super_selector) { match $args.get_positional($idx, $scope, $super_selector) {
Some(v) => v?.eval()?, Some(v) => v?.node.eval($args.span())?.node,
None => match $args.get_named($name.to_owned(), $scope, $super_selector) { None => match $args.get_named($name.to_owned(), $scope, $super_selector) {
Some(v) => v?.eval()?, Some(v) => v?.node.eval($args.span())?.node,
None => return Err(concat!("Missing argument $", $name, ".").into()), None => {
return Err((concat!("Missing argument $", $name, "."), $args.span()).into())
}
}, },
}; };
}; };
($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal=$default:expr) => { ($args:ident, $scope:ident, $super_selector:ident, $idx:literal, $name:literal=$default:expr) => {
match $args.get_positional($idx, $scope, $super_selector) { match $args.get_positional($idx, $scope, $super_selector) {
Some(v) => v?.eval()?, Some(v) => v?.node.eval($args.span())?.node,
None => match $args.get_named($name.to_owned(), $scope, $super_selector) { None => match $args.get_named($name.to_owned(), $scope, $super_selector) {
Some(v) => v?.eval()?, Some(v) => v?.node.eval($args.span())?.node,
None => $default, None => $default,
}, },
}; };
@ -22,13 +24,13 @@ macro_rules! arg {
macro_rules! named_arg { macro_rules! named_arg {
($args:ident, $scope:ident, $super_selector:ident, $name:literal) => { ($args:ident, $scope:ident, $super_selector:ident, $name:literal) => {
match $args.get_named($name.to_owned(), $scope, $super_selector) { match $args.get_named($name.to_owned(), $scope, $super_selector) {
Some(v) => v?.eval()?, Some(v) => v?.node.eval($args.span())?.node,
None => return Err(concat!("Missing argument $", $name, ".").into()), None => return Err((concat!("Missing argument $", $name, "."), $args.span()).into()),
}; };
}; };
($args:ident, $scope:ident, $super_selector:ident, $name:literal=$default:expr) => { ($args:ident, $scope:ident, $super_selector:ident, $name:literal=$default:expr) => {
match $args.get_named($name.to_owned(), $scope, $super_selector) { match $args.get_named($name.to_owned(), $scope, $super_selector) {
Some(v) => v?.eval()?, Some(v) => v?.node.eval($args.span())?.node,
None => $default, None => $default,
}; };
}; };
@ -48,19 +50,22 @@ macro_rules! max_args {
} else { } else {
err.push_str("were passed.") err.push_str("were passed.")
} }
return Err(err.into()); return Err((err, $args.span()).into());
} }
}; };
} }
macro_rules! bound { macro_rules! bound {
($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) {
return Err(format!( return Err((
"${}: Expected {}{} to be within {}{} and {}{}.", format!(
$name, $arg, $unit, $low, $unit, $high, $unit, "${}: Expected {}{} to be within {}{} and {}{}.",
$name, $arg, $unit, $low, $unit, $high, $unit,
),
$args.span(),
) )
.into()); .into());
} else { } else {
$arg $arg
} }
@ -68,24 +73,30 @@ macro_rules! bound {
// HACK: we accept `$low` as an ident here in order to work around // HACK: we accept `$low` as an ident here in order to work around
// a bug in the nightly compiler. // a bug in the nightly compiler.
// https://github.com/rust-lang/rust/issues/70050 // https://github.com/rust-lang/rust/issues/70050
($name:literal, $arg:ident, $unit:ident, $low:ident, $high:literal) => { ($args:ident, $name:literal, $arg:ident, $unit:ident, $low:ident, $high:literal) => {
if $arg > Number::from($high) || $arg < Number::from($low) { if $arg > Number::from($high) || $arg < Number::from($low) {
return Err(format!( return Err((
"${}: Expected {}{} to be within {}{} and {}{}.", format!(
$name, $arg, $unit, $low, $unit, $high, $unit, "${}: Expected {}{} to be within {}{} and {}{}.",
$name, $arg, $unit, $low, $unit, $high, $unit,
),
$args.span(),
) )
.into()); .into());
} else { } else {
$arg $arg
} }
}; };
($name:literal, $arg:ident, $unit:path, $low:literal, $high:literal) => { ($args:ident, $name:literal, $arg:ident, $unit:path, $low:literal, $high:literal) => {
if $arg > Number::from($high) || $arg < Number::from($low) { if $arg > Number::from($high) || $arg < Number::from($low) {
return Err(format!( return Err((
"${}: Expected {}{} to be within {}{} and {}{}.", format!(
$name, $arg, $unit, $low, $unit, $high, $unit, "${}: Expected {}{} to be within {}{} and {}{}.",
$name, $arg, $unit, $low, $unit, $high, $unit,
),
$args.span(),
) )
.into()); .into());
} else { } else {
$arg $arg
} }
@ -93,13 +104,16 @@ macro_rules! bound {
// HACK: we accept `$low` as an ident here in order to work around // HACK: we accept `$low` as an ident here in order to work around
// a bug in the nightly compiler. // a bug in the nightly compiler.
// https://github.com/rust-lang/rust/issues/70050 // https://github.com/rust-lang/rust/issues/70050
($name:literal, $arg:ident, $unit:path, $low:ident, $high:literal) => { ($args:ident, $name:literal, $arg:ident, $unit:path, $low:ident, $high:literal) => {
if $arg > Number::from($high) || $arg < Number::from($low) { if $arg > Number::from($high) || $arg < Number::from($low) {
return Err(format!( return Err((
"${}: Expected {}{} to be within {}{} and {}{}.", format!(
$name, $arg, $unit, $low, $unit, $high, $unit, "${}: Expected {}{} to be within {}{} and {}{}.",
$name, $arg, $unit, $low, $unit, $high, $unit,
),
$args.span(),
) )
.into()); .into());
} else { } else {
$arg $arg
} }

View File

@ -13,9 +13,15 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let map = match arg!(args, scope, super_selector, 0, "map") { let map = match arg!(args, scope, super_selector, 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 => return Err(format!("$map: {} is not a map.", v).into()), v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(map.get(&key)?.unwrap_or(Value::Null)) Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
}), }),
); );
f.insert( f.insert(
@ -26,9 +32,15 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let map = match arg!(args, scope, super_selector, 0, "map") { let map = match arg!(args, scope, super_selector, 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 => return Err(format!("$map: {} is not a map.", v).into()), v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::bool(map.get(&key)?.is_some())) Ok(Value::bool(map.get(&key, args.span())?.is_some()))
}), }),
); );
f.insert( f.insert(
@ -38,7 +50,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let map = match arg!(args, scope, super_selector, 0, "map") { let map = match arg!(args, scope, super_selector, 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 => return Err(format!("$map: {} is not a map.", v).into()), v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::List( Ok(Value::List(
map.keys(), map.keys(),
@ -54,7 +72,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let map = match arg!(args, scope, super_selector, 0, "map") { let map = match arg!(args, scope, super_selector, 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 => return Err(format!("$map: {} is not a map.", v).into()), v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
Ok(Value::List( Ok(Value::List(
map.values(), map.values(),
@ -70,12 +94,24 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let mut map1 = match arg!(args, scope, super_selector, 0, "map1") { let mut map1 = match arg!(args, scope, super_selector, 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 => return Err(format!("$map1: {} is not a map.", v).into()), v => {
return Err((
format!("$map1: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let map2 = match arg!(args, scope, super_selector, 1, "map2") { let map2 = match arg!(args, scope, super_selector, 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 => return Err(format!("$map2: {} is not a map.", v).into()), v => {
return Err((
format!("$map2: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
map1.merge(map2); map1.merge(map2);
Ok(Value::Map(map1)) Ok(Value::Map(map1))
@ -87,7 +123,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let mut map = match arg!(args, scope, super_selector, 0, "map") { let mut map = match arg!(args, scope, super_selector, 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 => return Err(format!("$map: {} is not a map.", v).into()), v => {
return Err((
format!("$map: {} is not a map.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let keys = args.get_variadic(scope, super_selector)?; let keys = args.get_variadic(scope, super_selector)?;
for key in keys { for key in keys {

View File

@ -17,9 +17,25 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let num = match arg!(args, scope, super_selector, 0, "number") { let num = match arg!(args, scope, super_selector, 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(format!("$number: Expected {} to have no units.", v).into()) return Err((
format!(
"$number: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v => {
return Err((
format!(
"$number: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
} }
v => return Err(format!("$number: {} is not a number.", v).into()),
}; };
Ok(Value::Dimension(num, Unit::Percent)) Ok(Value::Dimension(num, Unit::Percent))
}), }),
@ -30,7 +46,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "number") { match arg!(args, scope, super_selector, 0, "number") {
Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)), Value::Dimension(n, u) => Ok(Value::Dimension(n.round(), u)),
v => Err(format!("$number: {} is not a number.", v).into()), v => Err((
format!(
"$number: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -40,7 +63,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "number") { match arg!(args, scope, super_selector, 0, "number") {
Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)), Value::Dimension(n, u) => Ok(Value::Dimension(n.ceil(), u)),
v => Err(format!("$number: {} is not a number.", v).into()), v => Err((
format!(
"$number: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -50,7 +80,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "number") { match arg!(args, scope, super_selector, 0, "number") {
Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)), Value::Dimension(n, u) => Ok(Value::Dimension(n.floor(), u)),
v => Err(format!("$number: {} is not a number.", v).into()), v => Err((
format!(
"$number: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -60,7 +97,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "number") { match arg!(args, scope, super_selector, 0, "number") {
Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)), Value::Dimension(n, u) => Ok(Value::Dimension(n.abs(), u)),
v => Err(format!("$number: {} is not a number.", v).into()), v => Err((
format!(
"$number: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -70,11 +114,29 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let unit1 = match arg!(args, scope, super_selector, 0, "number1") { let unit1 = match arg!(args, scope, super_selector, 0, "number1") {
Value::Dimension(_, u) => u, Value::Dimension(_, u) => u,
v => return Err(format!("$number1: {} is not a number.", v).into()), v => {
return Err((
format!(
"$number1: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let unit2 = match arg!(args, scope, super_selector, 1, "number2") { let unit2 = match arg!(args, scope, super_selector, 1, "number2") {
Value::Dimension(_, u) => u, Value::Dimension(_, u) => u,
v => return Err(format!("$number2: {} is not a number.", v).into()), v => {
return Err((
format!(
"$number2: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::bool(unit1.comparable(&unit2))) Ok(Value::bool(unit1.comparable(&unit2)))
@ -95,7 +157,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Unit::None, Unit::None,
)); ));
} }
v => return Err(format!("$limit: {} is not a number.", v).into()), v => {
return Err((
format!("$limit: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
if limit.is_one() { if limit.is_one() {
@ -103,19 +171,25 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
} }
if limit.is_decimal() { if limit.is_decimal() {
return Err(format!("$limit: {} is not an int.", limit).into()); return Err((format!("$limit: {} is not an int.", limit), args.span()).into());
} }
if limit.is_zero() || limit.is_negative() { if limit.is_zero() || limit.is_negative() {
return Err(format!("$limit: Must be greater than 0, was {}.", limit).into()); return Err((
format!("$limit: Must be greater than 0, was {}.", limit),
args.span(),
)
.into());
} }
let limit = match limit.to_integer().to_u32() { let limit = match limit.to_integer().to_u32() {
Some(n) => n, Some(n) => n,
None => { None => {
return Err( return Err((
format!("max must be in range 0 < max ≤ 2^32, was {}", limit).into(), format!("max must be in range 0 < max ≤ 2^32, was {}", limit),
args.span(),
) )
.into())
} }
}; };

View File

@ -1,5 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use codemap::Spanned;
use super::{Builtin, GLOBAL_FUNCTIONS}; use super::{Builtin, GLOBAL_FUNCTIONS};
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::scope::global_var_exists; use crate::scope::global_var_exists;
@ -11,7 +13,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"if".to_owned(), "if".to_owned(),
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
max_args!(args, 3); max_args!(args, 3);
if arg!(args, scope, super_selector, 0, "condition").is_true()? { if arg!(args, scope, super_selector, 0, "condition").is_true(args.span())? {
Ok(arg!(args, scope, super_selector, 1, "if-true")) Ok(arg!(args, scope, super_selector, 1, "if-true"))
} else { } else {
Ok(arg!(args, scope, super_selector, 2, "if-false")) Ok(arg!(args, scope, super_selector, 2, "if-false"))
@ -41,7 +43,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"custom-property" => Ok(Value::False), "custom-property" => Ok(Value::False),
_ => Ok(Value::False), _ => Ok(Value::False),
}, },
v => Err(format!("$feature: {} is not a string.", v).into()), v => Err((
format!(
"$feature: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -51,7 +60,16 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
let unit = match arg!(args, scope, super_selector, 0, "number") { let unit = match arg!(args, scope, super_selector, 0, "number") {
Value::Dimension(_, u) => u.to_string(), Value::Dimension(_, u) => u.to_string(),
v => return Err(format!("$number: {} is not a number.", v).into()), v => {
return Err((
format!(
"$number: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(Value::Ident(unit, QuoteKind::Double)) Ok(Value::Ident(unit, QuoteKind::Double))
}), }),
@ -61,7 +79,10 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
max_args!(args, 1); max_args!(args, 1);
let value = arg!(args, scope, super_selector, 0, "value"); let value = arg!(args, scope, super_selector, 0, "value");
Ok(Value::Ident(value.kind()?.to_owned(), QuoteKind::None)) Ok(Value::Ident(
value.kind(args.span())?.to_owned(),
QuoteKind::None,
))
}), }),
); );
f.insert( f.insert(
@ -80,7 +101,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
max_args!(args, 1); max_args!(args, 1);
Ok(Value::Ident( Ok(Value::Ident(
arg!(args, scope, super_selector, 0, "value").inspect(), arg!(args, scope, super_selector, 0, "value").inspect(args.span())?,
QuoteKind::None, QuoteKind::None,
)) ))
}), }),
@ -91,7 +112,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))),
v => Err(format!("$name: {} is not a string.", v).into()), v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -101,7 +126,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))),
v => Err(format!("$name: {} is not a string.", v).into()), v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -111,7 +140,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))),
v => Err(format!("$name: {} is not a string.", v).into()), v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -123,7 +156,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Value::Ident(s, _) => Ok(Value::bool( Value::Ident(s, _) => Ok(Value::bool(
scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(&s), scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(&s),
)), )),
v => Err(format!("$name: {} is not a string.", v).into()), v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
} }
}), }),
); );
@ -133,24 +170,49 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 3); max_args!(args, 3);
let name = match arg!(args, scope, super_selector, 0, "name") { let name = match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => s, Value::Ident(s, _) => s,
v => return Err(format!("$name: {} is not a string.", v).into()), v => {
return Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
}; };
let css = arg!(args, scope, super_selector, 1, "css" = Value::False).is_true()?; let css =
arg!(args, scope, super_selector, 1, "css" = Value::False).is_true(args.span())?;
let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) { let module = match arg!(args, scope, super_selector, 2, "module" = Value::Null) {
Value::Ident(s, ..) => Some(s), Value::Ident(s, ..) => Some(s),
Value::Null => None, Value::Null => None,
v => return Err(format!("$module: {} is not a string.", v).into()), v => {
return Err((
format!(
"$module: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
if module.is_some() && css { if module.is_some() && css {
return Err("$css and $module may not both be passed at once.".into()); return Err((
"$css and $module may not both be passed at once.",
args.span(),
)
.into());
} }
let func = match scope.get_fn(&name) { let func = match scope.get_fn(Spanned {
node: name.clone(),
span: args.span(),
}) {
Ok(f) => SassFunction::UserDefined(Box::new(f), name), Ok(f) => SassFunction::UserDefined(Box::new(f), name),
Err(..) => match GLOBAL_FUNCTIONS.get(&name) { Err(..) => match GLOBAL_FUNCTIONS.get(&name) {
Some(f) => SassFunction::Builtin(f.clone(), name), Some(f) => SassFunction::Builtin(f.clone(), name),
None => return Err(format!("Function not found: {}", name).into()), None => {
return Err((format!("Function not found: {}", name), args.span()).into())
}
}, },
}; };
@ -162,7 +224,16 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| { Builtin::new(|mut args, scope, super_selector| {
let func = match arg!(args, scope, super_selector, 0, "function") { let func = match arg!(args, scope, super_selector, 0, "function") {
Value::Function(f) => f, Value::Function(f) => f,
v => return Err(format!("$function: {} is not a function reference.", v).into()), v => {
return Err((
format!(
"$function: {} is not a function reference.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
func.call(args.decrement(), scope, super_selector) func.call(args.decrement(), scope, super_selector)
}), }),

View File

@ -18,7 +18,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, q) => Ok(Value::Ident(i.to_ascii_uppercase(), q)), Value::Ident(i, q) => Ok(Value::Ident(i.to_ascii_uppercase(), q)),
v => Err(format!("$string: {} is not a string.", v).into()), v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -28,7 +35,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, q) => Ok(Value::Ident(i.to_ascii_lowercase(), q)), Value::Ident(i, q) => Ok(Value::Ident(i.to_ascii_lowercase(), q)),
v => Err(format!("$string: {} is not a string.", v).into()), v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -41,7 +55,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Number::from(i.chars().count()), Number::from(i.chars().count()),
Unit::None, Unit::None,
)), )),
v => Err(format!("$string: {} is not a string.", v).into()), v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -51,7 +72,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Double)), Value::Ident(i, _) => Ok(Value::Ident(i, QuoteKind::Double)),
v => Err(format!("$string: {} is not a string.", v).into()), v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -61,7 +89,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1); max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
i @ Value::Ident(..) => Ok(i.unquote()), i @ Value::Ident(..) => Ok(i.unquote()),
v => Err(format!("$string: {} is not a string.", v).into()), v => Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into()),
} }
}), }),
); );
@ -71,12 +106,21 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 3); max_args!(args, 3);
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") { let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(s, q) => (s, q), Value::Ident(s, q) => (s, q),
v => return Err(format!("$string: {} is not a string.", v).into()), v => {
return Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
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 arg!(args, scope, super_selector, 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).into()) return Err((format!("{} is not an int.", n), args.span()).into())
} }
Value::Dimension(n, Unit::None) if n.is_positive() => { Value::Dimension(n, Unit::None) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1) n.to_integer().to_usize().unwrap_or(str_len + 1)
@ -87,13 +131,29 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
.to_usize() .to_usize()
.unwrap(), .unwrap(),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(format!("$start: Expected {} to have no units.", v).into()) return Err((
format!(
"$start: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v => {
return Err((
format!(
"$start-at: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
} }
v => return Err(format!("$start-at: {} is not a number.", v).into()),
}; };
let mut end = match arg!(args, scope, super_selector, 2, "end-at" = Value::Null) { let mut end = match arg!(args, scope, super_selector, 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).into()) return Err((format!("{} is not an int.", n), args.span()).into())
} }
Value::Dimension(n, Unit::None) if n.is_positive() => { Value::Dimension(n, Unit::None) if n.is_positive() => {
n.to_integer().to_usize().unwrap_or(str_len + 1) n.to_integer().to_usize().unwrap_or(str_len + 1)
@ -104,10 +164,26 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
.to_usize() .to_usize()
.unwrap_or(str_len + 1), .unwrap_or(str_len + 1),
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(format!("$end: Expected {} to have no units.", v).into()) return Err((
format!(
"$end: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
} }
Value::Null => str_len, Value::Null => str_len,
v => return Err(format!("$end-at: {} is not a number.", v).into()), v => {
return Err((
format!(
"$end-at: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
if end > str_len { if end > str_len {
@ -134,12 +210,30 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2); max_args!(args, 2);
let s1 = match arg!(args, scope, super_selector, 0, "string") { let s1 = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => i, Value::Ident(i, _) => i,
v => return Err(format!("$string: {} is not a string.", v).into()), v => {
return Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let substr = match arg!(args, scope, super_selector, 1, "substring") { let substr = match arg!(args, scope, super_selector, 1, "substring") {
Value::Ident(i, _) => i, Value::Ident(i, _) => i,
v => return Err(format!("$substring: {} is not a string.", v).into()), v => {
return Err((
format!(
"$substring: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
Ok(match s1.find(&substr) { Ok(match s1.find(&substr) {
@ -154,23 +248,54 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 3); max_args!(args, 3);
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") { let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, q) => (i, q.normalize()), Value::Ident(i, q) => (i, q.normalize()),
v => return Err(format!("$string: {} is not a string.", v).into()), v => {
return Err((
format!(
"$string: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let substr = match arg!(args, scope, super_selector, 1, "insert") { let substr = match arg!(args, scope, super_selector, 1, "insert") {
Value::Ident(i, _) => i, Value::Ident(i, _) => i,
v => return Err(format!("$insert: {} is not a string.", v).into()), v => {
return Err((
format!(
"$insert: {} is not a string.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
}; };
let index = match arg!(args, scope, super_selector, 2, "index") { let index = match arg!(args, scope, super_selector, 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).into()) return Err((format!("$index: {} is not an int.", n), args.span()).into())
} }
Value::Dimension(n, Unit::None) => n, Value::Dimension(n, Unit::None) => n,
v @ Value::Dimension(..) => { v @ Value::Dimension(..) => {
return Err(format!("$index: Expected {} to have no units.", v).into()) return Err((
format!(
"$index: Expected {} to have no units.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v => {
return Err((
format!("$index: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
} }
v => return Err(format!("$index: {} is not a number.", v).into()),
}; };
if s1.is_empty() { if s1.is_empty() {

View File

@ -61,45 +61,6 @@ impl Op {
} }
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Pos {
line: u32,
column: u32,
}
impl Pos {
pub const fn new() -> Self {
Pos { line: 1, column: 1 }
}
pub const fn line(self) -> u32 {
self.line
}
pub const fn column(self) -> u32 {
self.column
}
pub fn newline(&mut self) {
self.line += 1;
self.column = 0;
}
pub fn next_char(&mut self) {
self.column += 1;
}
pub fn chars(&mut self, num: u32) {
self.column += num;
}
}
impl Display for Pos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "line:{} col:{}", self.line, self.column)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum QuoteKind { pub(crate) enum QuoteKind {
Single, Single,

View File

@ -3,36 +3,81 @@ use std::fmt::{self, Display};
use std::io; use std::io;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use crate::common::Pos; use codemap::{Span, SpanLoc};
pub type SassResult<T> = Result<T, SassError>; pub type SassResult<T> = Result<T, SassError>;
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug)]
pub struct SassError { pub struct SassError {
message: String, kind: SassErrorKind,
pos: Pos,
} }
impl SassError { impl SassError {
pub fn new<S: Into<String>>(message: S, pos: Pos) -> Self { pub(crate) fn raw(self) -> (String, Span) {
match self.kind {
SassErrorKind::Raw(string, span) => (string, span),
_ => todo!(),
}
}
pub(crate) fn from_loc(message: String, loc: SpanLoc) -> Self {
SassError { SassError {
message: message.into(), kind: SassErrorKind::ParseError { message, loc },
pos,
} }
} }
} }
#[derive(Debug)]
enum SassErrorKind {
/// A raw error with no additional metadata
/// It contains only a `String` message and
/// a span
Raw(String, Span),
ParseError {
message: String,
loc: SpanLoc,
},
IoError(io::Error),
FmtError(fmt::Error),
FromUtf8Error(String),
}
impl Display for SassError { impl Display for SassError {
// TODO: trim whitespace
// TODO: color errors
// TODO: integrate with codemap-diagnostics
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error: {}", self.message) let (message, loc) = match &self.kind {
SassErrorKind::ParseError { message, loc } => (message, loc),
_ => todo!(),
};
let line = loc.begin.line + 1;
let col = loc.begin.column + 1;
writeln!(f, "Error: {}", message)?;
let padding = vec![' '; format!("{}", line).len() + 1]
.iter()
.collect::<String>();
writeln!(f, "{}|", padding)?;
writeln!(f, "{} | {}", line, loc.file.source_line(loc.begin.line))?;
writeln!(
f,
"{}| {}{}",
padding,
vec![' '; loc.begin.column].iter().collect::<String>(),
vec!['^'; loc.end.column - loc.begin.column]
.iter()
.collect::<String>()
)?;
writeln!(f, "{}|", padding)?;
writeln!(f, "./{}:{}:{}", loc.file.name(), line, col)?;
Ok(())
} }
} }
impl From<io::Error> for SassError { impl From<io::Error> for SassError {
fn from(error: io::Error) -> Self { fn from(error: io::Error) -> Self {
SassError { SassError {
pos: Pos::new(), kind: SassErrorKind::IoError(error),
message: format!("{}", error),
} }
} }
} }
@ -40,8 +85,7 @@ impl From<io::Error> for SassError {
impl From<std::fmt::Error> for SassError { impl From<std::fmt::Error> for SassError {
fn from(error: std::fmt::Error) -> Self { fn from(error: std::fmt::Error) -> Self {
SassError { SassError {
pos: Pos::new(), kind: SassErrorKind::FmtError(error),
message: format!("{}", error),
} }
} }
} }
@ -49,35 +93,28 @@ impl From<std::fmt::Error> for SassError {
impl From<FromUtf8Error> for SassError { impl From<FromUtf8Error> for SassError {
fn from(error: FromUtf8Error) -> Self { fn from(error: FromUtf8Error) -> Self {
SassError { SassError {
pos: Pos::new(), kind: SassErrorKind::FromUtf8Error(format!(
message: format!("Invalid UTF-8 character \"\\x{:X?}\"", error.as_bytes()[0]), "Invalid UTF-8 character \"\\x{:X?}\"",
error.as_bytes()[0]
)),
} }
} }
} }
impl From<SassError> for String { impl From<(&str, Span)> for SassError {
#[inline] #[inline]
fn from(error: SassError) -> String { fn from(error: (&str, Span)) -> SassError {
error.message
}
}
impl From<&str> for SassError {
#[inline]
fn from(error: &str) -> SassError {
SassError { SassError {
pos: Pos::new(), kind: SassErrorKind::Raw(error.0.to_owned(), error.1),
message: error.to_string(),
} }
} }
} }
impl From<String> for SassError { impl From<(String, Span)> for SassError {
#[inline] #[inline]
fn from(error: String) -> SassError { fn from(error: (String, Span)) -> SassError {
SassError { SassError {
pos: Pos::new(), kind: SassErrorKind::Raw(error.0, error.1),
message: error,
} }
} }
} }

View File

@ -32,7 +32,7 @@ impl<W: Write> PrettyPrinter<W> {
self.scope -= 1; self.scope -= 1;
} }
Stmt::Style(s) => { Stmt::Style(s) => {
writeln!(self.buf, "{}{}", padding, s)?; writeln!(self.buf, "{}{}", padding, s.to_string()?)?;
} }
Stmt::AtRule(r) => match r { Stmt::AtRule(r) => match r {
AtRule::Unknown(..) => todo!("Display @rules properly"), AtRule::Unknown(..) => todo!("Display @rules properly"),
@ -63,7 +63,7 @@ mod test_scss {
fn $func() { fn $func() {
assert_eq!( assert_eq!(
String::from($input), String::from($input),
StyleSheet::new($input) StyleSheet::new($input.to_string())
.expect(concat!("failed to parse on ", $input)) .expect(concat!("failed to parse on ", $input))
.to_string() .to_string()
); );
@ -74,7 +74,7 @@ mod test_scss {
fn $func() { fn $func() {
assert_eq!( assert_eq!(
String::from($output), String::from($output),
StyleSheet::new($input) StyleSheet::new($input.to_string())
.expect(concat!("failed to parse on ", $input)) .expect(concat!("failed to parse on ", $input))
.to_string() .to_string()
); );

View File

@ -1,12 +1,14 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::Path; use std::path::Path;
use codemap::Spanned;
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::{Stmt, StyleSheet}; use crate::{Stmt, StyleSheet};
pub(crate) fn import<P: AsRef<Path>>(path: P) -> SassResult<(Vec<Stmt>, Scope)> { pub(crate) fn import<P: AsRef<Path>>(path: P) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
let mut rules: Vec<Stmt> = Vec::new(); let mut rules = Vec::new();
let mut scope = Scope::new(); let mut scope = Scope::new();
let path_buf = path.as_ref().to_path_buf(); let path_buf = path.as_ref().to_path_buf();
let name = path_buf.file_name().expect("todo! path ended in `..`"); let name = path_buf.file_name().expect("todo! path ended in `..`");

View File

@ -1,7 +1,9 @@
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use std::sync::Arc;
use codemap::File;
use crate::common::Pos;
use crate::Token; use crate::Token;
pub const FORM_FEED: char = '\x0C'; pub const FORM_FEED: char = '\x0C';
@ -9,41 +11,42 @@ pub const FORM_FEED: char = '\x0C';
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Lexer<'a> { pub(crate) struct Lexer<'a> {
buf: Peekable<Chars<'a>>, buf: Peekable<Chars<'a>>,
pos: Pos, pos: usize,
file: &'a Arc<File>,
} }
impl<'a> Iterator for Lexer<'a> { impl<'a> Iterator for Lexer<'a> {
type Item = Token; type Item = Token;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let kind = match self.buf.next()? { let kind = match self.buf.next()? {
'\n' | FORM_FEED => { '\n' | FORM_FEED => '\n',
self.pos.newline();
'\n'
}
'\r' => { '\r' => {
if self.buf.peek() == Some(&'\n') { if self.buf.peek() == Some(&'\n') {
self.pos += 1;
self.buf.next(); self.buf.next();
'\n' '\n'
} else { } else {
'\n' '\n'
} }
} }
'\0' => return None,
c => c, c => c,
}; };
self.pos.next_char(); let len = kind.len_utf8();
Some(Token { let pos = self
kind, .file
pos: self.pos, .span
}) .subspan(self.pos as u64, (self.pos + len) as u64);
self.pos += len;
Some(Token { kind, pos })
} }
} }
impl<'a> Lexer<'a> { impl<'a> Lexer<'a> {
pub fn new(buf: &'a str) -> Lexer<'a> { pub fn new(file: &'a Arc<File>) -> Lexer<'a> {
Lexer { Lexer {
buf: buf.chars().peekable(), buf: file.source().clone().chars().peekable(),
pos: Pos::new(), pos: 0,
file,
} }
} }
} }

View File

@ -84,8 +84,9 @@ use std::io::Write;
use std::iter::{Iterator, Peekable}; use std::iter::{Iterator, Peekable};
use std::path::Path; use std::path::Path;
use codemap::{CodeMap, Span, Spanned};
use crate::atrule::{eat_include, AtRule, AtRuleKind, Function, Mixin}; use crate::atrule::{eat_include, AtRule, AtRuleKind, Function, Mixin};
use crate::common::Pos;
pub use crate::error::{SassError, SassResult}; pub use crate::error::{SassError, SassResult};
use crate::format::PrettyPrinter; use crate::format::PrettyPrinter;
use crate::imports::import; use crate::imports::import;
@ -121,7 +122,7 @@ mod value;
/// Represents a parsed SASS stylesheet with nesting /// Represents a parsed SASS stylesheet with nesting
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StyleSheet(Vec<Stmt>); pub struct StyleSheet(Vec<Spanned<Stmt>>);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) enum Stmt { pub(crate) enum Stmt {
@ -135,6 +136,12 @@ pub(crate) enum Stmt {
AtRule(AtRule), AtRule(AtRule),
} }
impl Stmt {
fn span(self, span: Span) -> Spanned<Self> {
Spanned { node: self, span }
}
}
/// Represents a single rule set. Rule sets can contain other rule sets /// Represents a single rule set. Rule sets can contain other rule sets
/// ///
/// ```scss /// ```scss
@ -148,7 +155,7 @@ pub(crate) enum Stmt {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct RuleSet { pub(crate) struct RuleSet {
selector: Selector, selector: Selector,
rules: Vec<Stmt>, rules: Vec<Spanned<Stmt>>,
// potential optimization: we don't *need* to own the selector // potential optimization: we don't *need* to own the selector
super_selector: Selector, super_selector: Selector,
} }
@ -174,19 +181,13 @@ enum Expr {
/// A full selector `a > h1` /// A full selector `a > h1`
Selector(Selector), Selector(Selector),
/// A variable declaration `$var: 1px` /// A variable declaration `$var: 1px`
VariableDecl(String, Box<Value>), VariableDecl(String, Box<Spanned<Value>>),
/// A mixin declaration `@mixin foo {}` /// A mixin declaration `@mixin foo {}`
MixinDecl(String, Box<Mixin>), MixinDecl(String, Box<Mixin>),
FunctionDecl(String, Box<Function>), FunctionDecl(String, Box<Function>),
/// An include statement `@include foo;`
Include(Vec<Stmt>),
/// A multiline comment: `/* foobar */` /// A multiline comment: `/* foobar */`
MultilineComment(String), MultilineComment(String),
Debug(Pos, String),
Warn(Pos, String),
AtRule(AtRule), AtRule(AtRule),
// /// Function call: `calc(10vw - 1px)`
// FuncCall(String, Vec<Token>),
} }
/// Print the internal representation of a parsed stylesheet /// Print the internal representation of a parsed stylesheet
@ -203,51 +204,70 @@ impl Display for StyleSheet {
} }
} }
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 { impl StyleSheet {
#[inline] #[inline]
pub fn new(input: &str) -> SassResult<StyleSheet> { pub fn new(input: String) -> SassResult<StyleSheet> {
let mut map = CodeMap::new();
let file = map.add_file("stdin".into(), input);
Ok(StyleSheet( Ok(StyleSheet(
StyleSheetParser { match (StyleSheetParser {
global_scope: Scope::new(), lexer: Lexer::new(&file).peekable(),
lexer: Lexer::new(input).peekable(), nesting: 0,
rules: Vec::new(), map: &map,
scope: 0, }
file: String::from("stdin"), .parse_toplevel())
{
Ok(v) => v,
Err(e) => return Err(raw_to_parse_error(&map, e)),
} }
.parse_toplevel()?
.0, .0,
)) ))
} }
#[inline] #[inline]
pub fn from_path<P: AsRef<Path> + Into<String>>(p: P) -> SassResult<StyleSheet> { pub fn from_path<P: AsRef<Path> + Into<String> + Clone>(p: P) -> SassResult<StyleSheet> {
let mut map = CodeMap::new();
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p.as_ref())?)?);
Ok(StyleSheet( Ok(StyleSheet(
StyleSheetParser { match (StyleSheetParser {
global_scope: Scope::new(), lexer: Lexer::new(&file).peekable(),
lexer: Lexer::new(&String::from_utf8(fs::read(p.as_ref())?)?).peekable(), nesting: 0,
rules: Vec::new(), map: &map,
scope: 0, }
file: p.into(), .parse_toplevel())
{
Ok(v) => v,
Err(e) => return Err(raw_to_parse_error(&map, e)),
} }
.parse_toplevel()?
.0, .0,
)) ))
} }
pub(crate) fn export_from_path<P: AsRef<Path> + Into<String>>( pub(crate) fn export_from_path<P: AsRef<Path> + Into<String> + Clone>(
p: P, p: P,
) -> SassResult<(Vec<Stmt>, Scope)> { ) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
Ok(StyleSheetParser { let mut map = CodeMap::new();
global_scope: Scope::new(), let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p.as_ref())?)?);
lexer: Lexer::new(&String::from_utf8(fs::read(p.as_ref())?)?).peekable(), Ok(
rules: Vec::new(), match (StyleSheetParser {
scope: 0, lexer: Lexer::new(&file).peekable(),
file: p.into(), nesting: 0,
} map: &map,
.parse_toplevel()?) }
.parse_toplevel())
{
Ok(v) => v,
Err(e) => return Err(raw_to_parse_error(&map, e)),
},
)
} }
pub(crate) fn from_stmts(s: Vec<Stmt>) -> StyleSheet { pub(crate) fn from_stmts(s: Vec<Spanned<Stmt>>) -> StyleSheet {
StyleSheet(s) StyleSheet(s)
} }
@ -268,18 +288,15 @@ impl StyleSheet {
} }
} }
#[derive(Debug, Clone)]
struct StyleSheetParser<'a> { struct StyleSheetParser<'a> {
global_scope: Scope,
lexer: Peekable<Lexer<'a>>, lexer: Peekable<Lexer<'a>>,
rules: Vec<Stmt>, nesting: u32,
scope: u32, map: &'a CodeMap,
file: String,
} }
impl<'a> StyleSheetParser<'a> { impl<'a> StyleSheetParser<'a> {
fn parse_toplevel(mut self) -> SassResult<(Vec<Stmt>, Scope)> { fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
let mut rules: Vec<Stmt> = Vec::new(); let mut rules: Vec<Spanned<Stmt>> = Vec::new();
while let Some(Token { kind, .. }) = self.lexer.peek() { while let Some(Token { kind, .. }) = self.lexer.peek() {
match kind { match kind {
'a'..='z' | 'A'..='Z' | '_' | '-' | '0'..='9' 'a'..='z' | 'A'..='Z' | '_' | '-' | '0'..='9'
@ -293,20 +310,18 @@ impl<'a> StyleSheetParser<'a> {
self.lexer.next(); self.lexer.next();
let name = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?; let name = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
devour_whitespace(&mut self.lexer); devour_whitespace(&mut self.lexer);
if self let Token { kind, pos } = self
.lexer .lexer
.next() .next()
.unwrap() .unwrap();
.kind if kind != ':' {
!= ':' return Err(("expected \":\".", pos).into());
{
return Err("expected \":\".".into());
} }
let VariableDecl { val, default, .. } = let VariableDecl { val, default, .. } =
eat_variable_value(&mut self.lexer, &Scope::new(), &Selector::new())?; eat_variable_value(&mut self.lexer, &Scope::new(), &Selector::new())?;
GLOBAL_SCOPE.with(|s| { GLOBAL_SCOPE.with(|s| {
if !default || s.borrow().get_var(&name).is_err() { if !default || s.borrow().get_var(name.clone()).is_err() {
match s.borrow_mut().insert_var(&name, val) { match s.borrow_mut().insert_var(&name.node, val) {
Ok(..) => Ok(()), Ok(..) => Ok(()),
Err(e) => Err(e), Err(e) => Err(e),
} }
@ -319,7 +334,8 @@ impl<'a> StyleSheetParser<'a> {
self.lexer.next(); self.lexer.next();
if '*' == self.lexer.peek().unwrap().kind { if '*' == self.lexer.peek().unwrap().kind {
self.lexer.next(); self.lexer.next();
rules.push(Stmt::MultilineComment(eat_comment(&mut self.lexer, &Scope::new(), &Selector::new())?)); let comment = eat_comment(&mut self.lexer, &Scope::new(), &Selector::new())?;
rules.push(Spanned { node: Stmt::MultilineComment(comment.node), span: comment.span });
} else if '/' == self.lexer.peek().unwrap().kind { } else if '/' == self.lexer.peek().unwrap().kind {
read_until_newline(&mut self.lexer); read_until_newline(&mut self.lexer);
devour_whitespace(&mut self.lexer); devour_whitespace(&mut self.lexer);
@ -329,9 +345,9 @@ impl<'a> StyleSheetParser<'a> {
} }
'@' => { '@' => {
self.lexer.next(); self.lexer.next();
let at_rule_kind = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?; let Spanned { node: at_rule_kind, span } = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
if at_rule_kind.is_empty() { if at_rule_kind.is_empty() {
return Err("Expected identifier.".into()); return Err(("Expected identifier.", span).into());
} }
match AtRuleKind::from(at_rule_kind.as_str()) { match AtRuleKind::from(at_rule_kind.as_str()) {
AtRuleKind::Include => rules.extend(eat_include( AtRuleKind::Include => rules.extend(eat_include(
@ -349,7 +365,7 @@ impl<'a> StyleSheetParser<'a> {
.kind .kind
{ {
q @ '"' | q @ '\'' => { q @ '"' | q @ '\'' => {
file_name.push_str(&parse_quoted_string(&mut self.lexer, &Scope::new(), q, &Selector::new())?.unquote().to_string()); file_name.push_str(&parse_quoted_string(&mut self.lexer, &Scope::new(), q, &Selector::new())?.node.unquote().to_css_string(span)?);
} }
_ => todo!("expected ' or \" after @import"), _ => todo!("expected ' or \" after @import"),
} }
@ -364,37 +380,39 @@ impl<'a> StyleSheetParser<'a> {
}); });
} }
v => { v => {
match AtRule::from_tokens(&v, Pos::new(), &mut self.lexer, &mut Scope::new(), &Selector::new())? { let pos = self.lexer.next().unwrap().pos();
AtRule::Mixin(name, mixin) => { let rule = AtRule::from_tokens(&v, pos, &mut self.lexer, &mut Scope::new(), &Selector::new())?;
insert_global_mixin(&name, *mixin); match rule.node {
} AtRule::Mixin(name, mixin) => {
AtRule::Function(name, func) => { insert_global_mixin(&name, *mixin);
insert_global_fn(&name, *func);
}
AtRule::Charset => continue,
AtRule::Warn(pos, message) => self.warn(pos, &message),
AtRule::Debug(pos, message) => self.debug(pos, &message),
AtRule::Return(_) => {
return Err("This at-rule is not allowed here.".into())
}
AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => rules.extend(s),
AtRule::Content => return Err("@content is only allowed within mixin declarations.".into()),
AtRule::If(i) => {
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
}
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
u @ AtRule::Unknown(..) => rules.push(Stmt::AtRule(u)),
} }
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::Include(s) | AtRule::While(s) | AtRule::Each(s) | AtRule::For(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())?);
}
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
u @ AtRule::Unknown(..) => rules.push(Spanned { node: Stmt::AtRule(u), span: rule.span }),
} }
}
} }
}, },
'&' => { '&' => {
return Err( return Err(
"Base-level rules cannot contain the parent-selector-referencing character '&'.".into(), ("Base-level rules cannot contain the parent-selector-referencing character '&'.", self.lexer.next().unwrap().pos()).into(),
) )
} }
c if c.is_control() => { c if c.is_control() => {
return Err("expected selector.".into()); return Err(("expected selector.", self.lexer.next().unwrap().pos()).into());
} }
_ => match dbg!(self.lexer.next()) { _ => match dbg!(self.lexer.next()) {
Some(..) => todo!("unexpected toplevel token"), Some(..) => todo!("unexpected toplevel token"),
@ -405,22 +423,48 @@ impl<'a> StyleSheetParser<'a> {
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone()))) Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))
} }
fn eat_rules(&mut self, super_selector: &Selector, scope: &mut Scope) -> SassResult<Vec<Stmt>> { fn eat_rules(
&mut self,
super_selector: &Selector,
scope: &mut Scope,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector)? { while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector)? {
match expr { let span = expr.span;
Expr::Style(s) => stmts.push(Stmt::Style(s)), match expr.node {
Expr::Style(s) => stmts.push(Spanned {
node: Stmt::Style(s),
span,
}),
Expr::AtRule(a) => match a { Expr::AtRule(a) => match a {
AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => stmts.extend(s), AtRule::Include(s) | AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => {
stmts.extend(s)
}
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?), AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
AtRule::Content => { AtRule::Content => {
return Err("@content is only allowed within mixin declarations.".into()) 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::Return(..) => return Err("This at-rule is not allowed here.".into()),
AtRule::AtRoot(root_stmts) => stmts.extend(root_stmts), AtRule::AtRoot(root_stmts) => stmts.extend(root_stmts),
r => stmts.push(Stmt::AtRule(r)), AtRule::Debug(ref message) => self.debug(expr.span, message),
AtRule::Warn(ref message) => self.warn(expr.span, message),
r => stmts.push(Spanned {
node: Stmt::AtRule(r),
span,
}),
}, },
Expr::Styles(s) => stmts.extend(s.into_iter().map(Box::new).map(Stmt::Style)), Expr::Styles(s) => stmts.extend(
s.into_iter()
.map(Box::new)
.map(Stmt::Style)
.map(|style| Spanned { node: style, span }),
),
Expr::MixinDecl(name, mixin) => { Expr::MixinDecl(name, mixin) => {
scope.insert_mixin(&name, *mixin); scope.insert_mixin(&name, *mixin);
} }
@ -428,30 +472,33 @@ impl<'a> StyleSheetParser<'a> {
scope.insert_fn(&name, *func); scope.insert_fn(&name, *func);
} }
Expr::Selector(s) => { Expr::Selector(s) => {
self.scope += 1; self.nesting += 1;
let rules = self.eat_rules(&super_selector.zip(&s), scope)?; let rules = self.eat_rules(&super_selector.zip(&s), scope)?;
stmts.push(Stmt::RuleSet(RuleSet { stmts.push(Spanned {
super_selector: super_selector.clone(), node: Stmt::RuleSet(RuleSet {
selector: s, super_selector: super_selector.clone(),
rules, selector: s,
})); rules,
self.scope -= 1; }),
if self.scope == 0 { span,
});
self.nesting -= 1;
if self.nesting == 0 {
return Ok(stmts); return Ok(stmts);
} }
} }
Expr::VariableDecl(name, val) => { Expr::VariableDecl(name, val) => {
if self.scope == 0 { if self.nesting == 0 {
scope.insert_var(&name, *val.clone())?; scope.insert_var(&name, *val.clone())?;
insert_global_var(&name, *val)?; insert_global_var(&name, *val)?;
} else { } else {
scope.insert_var(&name, *val)?; scope.insert_var(&name, *val)?;
} }
} }
Expr::Include(rules) => stmts.extend(rules), Expr::MultilineComment(s) => stmts.push(Spanned {
Expr::Debug(pos, ref message) => self.debug(pos, message), node: Stmt::MultilineComment(s),
Expr::Warn(pos, ref message) => self.warn(pos, message), span,
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)), }),
} }
} }
Ok(stmts) Ok(stmts)
@ -462,9 +509,15 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &mut Scope, scope: &mut Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Option<Expr>> { ) -> SassResult<Option<Spanned<Expr>>> {
let mut values = Vec::with_capacity(5); let mut values = Vec::with_capacity(5);
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
return Ok(None);
};
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
span = span.merge(tok.pos());
match tok.kind { match tok.kind {
':' => { ':' => {
let tok = toks.next(); let tok = toks.next();
@ -475,7 +528,10 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
super_selector, super_selector,
String::new(), String::new(),
)?; )?;
return Ok(Some(Style::from_tokens(toks, scope, super_selector, prop)?)); return Ok(Some(Spanned {
node: Style::from_tokens(toks, scope, super_selector, prop)?,
span,
}));
} else { } else {
values.push(tok.unwrap()); values.push(tok.unwrap());
} }
@ -489,14 +545,20 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
devour_whitespace(&mut v); devour_whitespace(&mut v);
if v.peek().is_none() { if v.peek().is_none() {
devour_whitespace(toks); devour_whitespace(toks);
return Ok(Some(Expr::Style(Box::new(Style { return Ok(Some(Spanned {
property: String::new(), node: Expr::Style(Box::new(Style {
value: Value::Null, property: String::new(),
})))); value: Value::Null.span(span),
})),
span,
}));
} }
let property = Style::parse_property(&mut v, scope, super_selector, String::new())?; let property = Style::parse_property(&mut v, scope, super_selector, String::new())?;
let value = Style::parse_value(&mut v, scope, super_selector)?; let value = Style::parse_value(&mut v, scope, super_selector)?;
return Ok(Some(Expr::Style(Box::new(Style { property, value })))); return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { property, value })),
span,
}));
} }
'}' => { '}' => {
if values.is_empty() { if values.is_empty() {
@ -515,17 +577,23 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
let property = let property =
Style::parse_property(&mut v, scope, super_selector, String::new())?; Style::parse_property(&mut v, scope, super_selector, String::new())?;
let value = Style::parse_value(&mut v, scope, super_selector)?; let value = Style::parse_value(&mut v, scope, super_selector)?;
return Ok(Some(Expr::Style(Box::new(Style { property, value })))); return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { property, value })),
span,
}));
} }
} }
'{' => { '{' => {
toks.next(); toks.next();
devour_whitespace(toks); devour_whitespace(toks);
return Ok(Some(Expr::Selector(Selector::from_tokens( return Ok(Some(Spanned {
&mut values.into_iter().peekable(), node: Expr::Selector(Selector::from_tokens(
scope, &mut values.into_iter().peekable(),
super_selector, scope,
)?))); super_selector,
)?),
span,
}));
} }
'$' => { '$' => {
let tok = toks.next().unwrap(); let tok = toks.next().unwrap();
@ -545,22 +613,28 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
global, global,
} = eat_variable_value(toks, scope, super_selector)?; } = eat_variable_value(toks, scope, super_selector)?;
if global { if global {
insert_global_var(&name, val.clone())?; insert_global_var(&name.node, val.clone())?;
} }
if !default || scope.get_var(&name).is_err() { if !default || scope.get_var(name.clone()).is_err() {
return Ok(Some(Expr::VariableDecl(name, Box::new(val)))); return Ok(Some(Spanned {
node: Expr::VariableDecl(name.node, Box::new(val)),
span,
}));
} }
} else { } else {
values.push(tok); values.push(tok);
// HACK: we add the name back in, but lose the position information let mut current_pos = 0;
// potentially requires refactoring heuristics for values.extend(name.chars().map(|x| {
// no space between colon and style value let len = x.len_utf8() as u64;
values.extend(name.chars().map(|c| Token::new(Pos::new(), c))); let tok = Token::new(span.subspan(current_pos, current_pos + len), x);
current_pos += len;
tok
}));
} }
} }
'/' => { '/' => {
let tok = toks.next().unwrap(); let tok = toks.next().unwrap();
let peeked = toks.peek().ok_or("expected more input.")?; let peeked = toks.peek().ok_or(("expected more input.", tok.pos()))?;
if peeked.kind == '/' { if peeked.kind == '/' {
read_until_newline(toks); read_until_newline(toks);
devour_whitespace(toks); devour_whitespace(toks);
@ -569,43 +643,44 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
toks.next(); toks.next();
let comment = eat_comment(toks, scope, super_selector)?; let comment = eat_comment(toks, scope, super_selector)?;
devour_whitespace(toks); devour_whitespace(toks);
return Ok(Some(Expr::MultilineComment(comment))); return Ok(Some(Spanned {
node: Expr::MultilineComment(comment.node),
span: comment.span,
}));
} else { } else {
values.push(tok); values.push(tok);
} }
} }
'@' => { '@' => {
let pos = toks.next().unwrap().pos(); toks.next();
match AtRuleKind::from(eat_ident(toks, scope, super_selector)?.as_str()) { let Spanned { node: ident, span } = eat_ident(toks, scope, super_selector)?;
AtRuleKind::Include => { devour_whitespace(toks);
devour_whitespace(toks); let rule = AtRule::from_tokens(
return Ok(Some(Expr::Include(eat_include( &AtRuleKind::from(ident.as_str()),
toks, span,
scope, toks,
super_selector, scope,
)?))); super_selector,
} )?;
v => { return Ok(Some(Spanned {
devour_whitespace(toks); node: match rule.node {
return match AtRule::from_tokens(&v, pos, toks, scope, super_selector)? { AtRule::Mixin(name, mixin) => Expr::MixinDecl(name, mixin),
AtRule::Mixin(name, mixin) => Ok(Some(Expr::MixinDecl(name, mixin))), AtRule::Function(name, func) => Expr::FunctionDecl(name, func),
AtRule::Function(name, func) => { AtRule::Charset => todo!("@charset as expr"),
Ok(Some(Expr::FunctionDecl(name, func))) d @ AtRule::Debug(..) => Expr::AtRule(d),
} w @ AtRule::Warn(..) => Expr::AtRule(w),
AtRule::Charset => todo!("@charset as expr"), a @ AtRule::Return(_) => Expr::AtRule(a),
AtRule::Debug(a, b) => Ok(Some(Expr::Debug(a, b))), c @ AtRule::Content => Expr::AtRule(c),
AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))), f @ AtRule::If(..) => Expr::AtRule(f),
a @ AtRule::Return(_) => Ok(Some(Expr::AtRule(a))), f @ AtRule::For(..) => Expr::AtRule(f),
c @ AtRule::Content => Ok(Some(Expr::AtRule(c))), f @ AtRule::While(..) => Expr::AtRule(f),
f @ AtRule::If(..) => Ok(Some(Expr::AtRule(f))), f @ AtRule::Each(..) => Expr::AtRule(f),
f @ AtRule::For(..) => Ok(Some(Expr::AtRule(f))), u @ AtRule::Unknown(..) => Expr::AtRule(u),
f @ AtRule::While(..) => Ok(Some(Expr::AtRule(f))), u @ AtRule::AtRoot(..) => Expr::AtRule(u),
f @ AtRule::Each(..) => Ok(Some(Expr::AtRule(f))), u @ AtRule::Include(..) => Expr::AtRule(u),
u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))), },
u @ AtRule::AtRoot(..) => Ok(Some(Expr::AtRule(u))), span,
}; }));
}
}
} }
'#' => { '#' => {
values.push(toks.next().unwrap()); values.push(toks.next().unwrap());
@ -639,17 +714,24 @@ fn eat_interpolation<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> Vec<T
/// Functions that print to stdout or stderr /// Functions that print to stdout or stderr
impl<'a> StyleSheetParser<'a> { impl<'a> StyleSheetParser<'a> {
fn debug(&self, pos: Pos, message: &str) { fn debug(&self, span: Span, message: &str) {
eprintln!("{}:{} Debug: {}", self.file, pos.line(), message); let loc = self.map.look_up_span(span);
eprintln!(
"{}:{} Debug: {}",
loc.file.name(),
loc.begin.line + 1,
message
);
} }
fn warn(&self, pos: Pos, message: &str) { fn warn(&self, span: Span, message: &str) {
let loc = self.map.look_up_span(span);
eprintln!( eprintln!(
"Warning: {}\n\t{} {}:{} todo!(scope)", "Warning: {}\n {} {}:{} root stylesheet",
message, message,
self.file, loc.file.name(),
pos.line(), loc.begin.line + 1,
pos.column() loc.begin.column + 1
); );
} }
} }

View File

@ -1,5 +1,4 @@
//! # Convert from SCSS AST to CSS //! # Convert from SCSS AST to CSS
use std::fmt;
use std::io::Write; use std::io::Write;
use crate::atrule::AtRule; use crate::atrule::AtRule;
@ -20,11 +19,11 @@ enum BlockEntry {
MultilineComment(String), MultilineComment(String),
} }
impl fmt::Display for BlockEntry { impl BlockEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { pub fn to_string(&self) -> SassResult<String> {
match self { match self {
BlockEntry::Style(s) => writeln!(f, "{}", s), BlockEntry::Style(s) => s.to_string(),
BlockEntry::MultilineComment(s) => writeln!(f, "/*{}*/", s), BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)),
} }
} }
} }
@ -35,7 +34,7 @@ impl Toplevel {
} }
fn push_style(&mut self, mut s: Style) -> SassResult<()> { fn push_style(&mut self, mut s: Style) -> SassResult<()> {
s.value = s.value.eval()?; s = s.eval()?;
if s.value.is_null() { if s.value.is_null() {
return Ok(()); return Ok(());
} }
@ -79,8 +78,8 @@ impl Css {
} }
let mut vals = vec![Toplevel::new_rule(selector)]; let mut vals = vec![Toplevel::new_rule(selector)];
for rule in rules { for rule in rules {
match rule { match rule.node {
Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule)?), Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule.node)?),
Stmt::Style(s) => vals Stmt::Style(s) => vals
.get_mut(0) .get_mut(0)
.expect("expected block to exist") .expect("expected block to exist")
@ -91,7 +90,7 @@ impl Css {
.push_comment(s), .push_comment(s),
Stmt::AtRule(AtRule::AtRoot(stmts)) => stmts Stmt::AtRule(AtRule::AtRoot(stmts)) => stmts
.into_iter() .into_iter()
.map(|r| Ok(vals.extend(self.parse_stmt(r)?))) .map(|r| Ok(vals.extend(self.parse_stmt(r.node)?)))
.collect::<SassResult<()>>()?, .collect::<SassResult<()>>()?,
Stmt::AtRule(r) => vals.push(Toplevel::AtRule(r)), Stmt::AtRule(r) => vals.push(Toplevel::AtRule(r)),
}; };
@ -107,7 +106,7 @@ impl Css {
fn parse_stylesheet(mut self, s: StyleSheet) -> SassResult<Css> { fn parse_stylesheet(mut self, s: StyleSheet) -> SassResult<Css> {
let mut is_first = true; let mut is_first = true;
for stmt in s.0 { for stmt in s.0 {
let v = self.parse_stmt(stmt)?; let v = self.parse_stmt(stmt.node)?;
// 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() {
@ -145,7 +144,7 @@ impl Css {
has_written = true; has_written = true;
writeln!(buf, "{}{} {{", padding, selector)?; writeln!(buf, "{}{} {{", padding, selector)?;
for style in styles { for style in styles {
write!(buf, "{} {}", padding, style)?; writeln!(buf, "{} {}", padding, style.to_string()?)?;
} }
writeln!(buf, "{}}}", padding)?; writeln!(buf, "{}}}", padding)?;
} }

View File

@ -1,16 +1,18 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use codemap::Spanned;
use crate::atrule::{Function, Mixin}; use crate::atrule::{Function, Mixin};
use crate::error::SassResult; use crate::error::SassResult;
use crate::value::Value; use crate::value::Value;
thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new())); thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new()));
pub(crate) fn get_global_var(s: &str) -> SassResult<Value> { pub(crate) fn get_global_var(s: Spanned<String>) -> SassResult<Spanned<Value>> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(s) { GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(&s.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err("Undefined variable.".into()), None => Err(("Undefined variable.", s.span).into()),
}) })
} }
@ -18,14 +20,14 @@ pub(crate) fn global_var_exists(v: &str) -> bool {
GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(v)) GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(v))
} }
pub(crate) fn insert_global_var(s: &str, v: Value) -> SassResult<Option<Value>> { pub(crate) fn insert_global_var(s: &str, v: Spanned<Value>) -> SassResult<Option<Spanned<Value>>> {
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_var(s, v)) GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_var(s, v))
} }
pub(crate) fn get_global_fn(s: &str) -> SassResult<Function> { pub(crate) fn get_global_fn(s: Spanned<String>) -> SassResult<Function> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().functions().get(s) { GLOBAL_SCOPE.with(|scope| match scope.borrow().functions().get(&s.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err("Undefined function.".into()), None => Err(("Undefined function.", s.span).into()),
}) })
} }
@ -37,10 +39,10 @@ pub(crate) fn insert_global_fn(s: &str, v: Function) -> Option<Function> {
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_fn(s, v)) GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_fn(s, v))
} }
pub(crate) fn get_global_mixin(s: &str) -> SassResult<Mixin> { pub(crate) fn get_global_mixin(s: Spanned<String>) -> SassResult<Mixin> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(s) { GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(&s.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err("Undefined mixin.".into()), None => Err(("Undefined mixin.", s.span).into()),
}) })
} }
@ -54,7 +56,7 @@ pub(crate) fn insert_global_mixin(s: &str, v: Mixin) -> Option<Mixin> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Scope { pub(crate) struct Scope {
vars: HashMap<String, Value>, vars: HashMap<String, Spanned<Value>>,
mixins: HashMap<String, Mixin>, mixins: HashMap<String, Mixin>,
functions: HashMap<String, Function>, functions: HashMap<String, Function>,
} }
@ -69,7 +71,7 @@ impl Scope {
} }
} }
pub const fn vars(&self) -> &HashMap<String, Value> { pub const fn vars(&self) -> &HashMap<String, Spanned<Value>> {
&self.vars &self.vars
} }
@ -81,16 +83,17 @@ impl Scope {
&self.mixins &self.mixins
} }
pub fn get_var(&self, v: &str) -> SassResult<Value> { pub fn get_var(&self, mut name: Spanned<String>) -> SassResult<Spanned<Value>> {
let name = &v.replace('_', "-"); name.node = name.node.replace('_', "-");
match self.vars.get(name) { match self.vars.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => get_global_var(name), None => get_global_var(name),
} }
} }
pub fn insert_var(&mut self, s: &str, v: Value) -> SassResult<Option<Value>> { pub fn insert_var(&mut self, s: &str, v: Spanned<Value>) -> SassResult<Option<Spanned<Value>>> {
Ok(self.vars.insert(s.replace('_', "-"), v.eval()?)) let Spanned { node, span } = v;
Ok(self.vars.insert(s.replace('_', "-"), node.eval(span)?))
} }
pub fn var_exists(&self, v: &str) -> bool { pub fn var_exists(&self, v: &str) -> bool {
@ -98,9 +101,9 @@ impl Scope {
self.vars.contains_key(name) || global_var_exists(name) self.vars.contains_key(name) || global_var_exists(name)
} }
pub fn get_mixin(&self, v: &str) -> SassResult<Mixin> { pub fn get_mixin(&self, mut name: Spanned<String>) -> SassResult<Mixin> {
let name = &v.replace('_', "-"); name.node = name.node.replace('_', "-");
match self.mixins.get(name) { match self.mixins.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => get_global_mixin(name), None => get_global_mixin(name),
} }
@ -115,9 +118,9 @@ impl Scope {
self.mixins.contains_key(name) || global_mixin_exists(name) self.mixins.contains_key(name) || global_mixin_exists(name)
} }
pub fn get_fn(&self, v: &str) -> SassResult<Function> { pub fn get_fn(&self, mut name: Spanned<String>) -> SassResult<Function> {
let name = &v.replace('_', "-"); name.node = name.node.replace('_', "-");
match self.functions.get(name) { match self.functions.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => get_global_fn(name), None => get_global_fn(name),
} }

View File

@ -1,6 +1,7 @@
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::iter::Peekable; use std::iter::Peekable;
use std::string::ToString;
use codemap::Span;
use super::{Selector, SelectorKind}; use super::{Selector, SelectorKind};
use crate::error::SassResult; use crate::error::SassResult;
@ -23,25 +24,34 @@ impl Attribute {
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
mut start: Span,
) -> SassResult<SelectorKind> { ) -> SassResult<SelectorKind> {
devour_whitespace(toks); devour_whitespace(toks);
let attr = match toks.peek().ok_or("Expected identifier.")?.kind { let next_tok = toks.peek().ok_or(("Expected identifier.", start))?;
c if is_ident_char(c) => eat_ident(toks, scope, super_selector)?, let attr = match next_tok.kind {
c if is_ident_char(c) => {
let i = eat_ident(toks, scope, super_selector)?;
start = i.span;
i.node
}
'#' => { '#' => {
toks.next(); start.merge(toks.next().unwrap().pos());
if toks.next().ok_or("Expected expression.")?.kind == '{' { if toks.next().ok_or(("Expected expression.", start))?.kind == '{' {
parse_interpolation(toks, scope, super_selector)?.to_string() let interpolation = parse_interpolation(toks, scope, super_selector)?;
interpolation.node.to_css_string(interpolation.span)?
} else { } else {
return Err("Expected expression.".into()); return Err(("Expected expression.", start).into());
} }
} }
_ => return Err("Expected identifier.".into()), _ => return Err(("Expected identifier.", start).into()),
}; };
devour_whitespace(toks); devour_whitespace(toks);
let kind = match toks.next().ok_or("expected \"{\".")?.kind { let next = toks.next().ok_or(("expected \"]\".", start))?;
c if is_ident_char(c) => return Err("Expected \"]\".".into()),
let kind = match next.kind {
c if is_ident_char(c) => return Err(("Expected \"]\".", next.pos()).into()),
']' => { ']' => {
return Ok(SelectorKind::Attribute(Attribute { return Ok(SelectorKind::Attribute(Attribute {
kind: AttributeKind::Any, kind: AttributeKind::Any,
@ -56,29 +66,36 @@ impl Attribute {
'^' => AttributeKind::Prefix, '^' => AttributeKind::Prefix,
'$' => AttributeKind::Suffix, '$' => AttributeKind::Suffix,
'*' => AttributeKind::Contains, '*' => AttributeKind::Contains,
_ => return Err("expected \"]\".".into()), _ => return Err(("expected \"]\".", next.pos()).into()),
}; };
if kind != AttributeKind::Equals { if kind != AttributeKind::Equals {
match toks.next().ok_or("expected \"=\".")?.kind { let next = toks.next().ok_or(("expected \"=\".", next.pos()))?;
match next.kind {
'=' => {} '=' => {}
_ => return Err("expected \"=\".".into()), _ => return Err(("expected \"=\".", next.pos()).into()),
} }
} }
devour_whitespace(toks); devour_whitespace(toks);
let value = match toks.next().ok_or("Expected identifier.")?.kind { let next = toks.next().ok_or(("Expected identifier.", next.pos()))?;
let value = match next.kind {
v @ 'a'..='z' | v @ 'A'..='Z' | v @ '-' | v @ '_' => { v @ 'a'..='z' | v @ 'A'..='Z' | v @ '-' | v @ '_' => {
format!("{}{}", v, eat_ident(toks, scope, super_selector)?) format!("{}{}", v, eat_ident(toks, scope, super_selector)?.node)
} }
q @ '"' | q @ '\'' => parse_quoted_string(toks, scope, q, super_selector)?.to_string(), q @ '"' | q @ '\'' => {
_ => return Err("Expected identifier.".into()), parse_quoted_string(toks, scope, q, super_selector)?.to_css_string(next.pos())?
}
_ => return Err(("Expected identifier.", next.pos()).into()),
}; };
devour_whitespace(toks); devour_whitespace(toks);
let modifier = match toks.next().ok_or("expected \"]\".")?.kind { let next = toks.next().ok_or(("expected \"]\".", next.pos()))?;
let modifier = match next.kind {
']' => { ']' => {
return Ok(SelectorKind::Attribute(Attribute { return Ok(SelectorKind::Attribute(Attribute {
kind, kind,
@ -88,13 +105,14 @@ impl Attribute {
})) }))
} }
v @ 'a'..='z' | v @ 'A'..='Z' => { v @ 'a'..='z' | v @ 'A'..='Z' => {
match toks.next().ok_or("expected \"]\".")?.kind { let next = toks.next().ok_or(("expected \"]\".", next.pos()))?;
match next.kind {
']' => {} ']' => {}
_ => return Err("expected \"]\".".into()), _ => return Err(("expected \"]\".", next.pos()).into()),
} }
Some(v) Some(v)
} }
_ => return Err("expected \"]\".".into()), _ => return Err(("expected \"]\".", next.pos()).into()),
}; };
Ok(SelectorKind::Attribute(Attribute { Ok(SelectorKind::Attribute(Attribute {

View File

@ -1,9 +1,7 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use std::iter::Peekable; use std::iter::Peekable;
use std::string::ToString;
use crate::error::SassResult; use crate::error::SassResult;
use crate::lexer::Lexer;
use crate::scope::Scope; use crate::scope::Scope;
use crate::utils::{ use crate::utils::{
devour_whitespace, eat_comment, eat_ident_no_interpolation, parse_interpolation, devour_whitespace, eat_comment, eat_ident_no_interpolation, parse_interpolation,
@ -183,13 +181,20 @@ impl Selector {
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Selector> { ) -> SassResult<Selector> {
let mut string = String::new(); 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() { while let Some(tok) = toks.next() {
span = span.merge(tok.pos());
match tok.kind { match tok.kind {
'#' => { '#' => {
if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { if toks.peek().is_some() && toks.peek().unwrap().kind == '{' {
toks.next(); toks.next();
string.push_str( string.push_str(
&parse_interpolation(toks, scope, super_selector)?.to_string(), &parse_interpolation(toks, scope, super_selector)?
.to_css_string(span)?,
); );
} else { } else {
string.push('#'); string.push('#');
@ -207,7 +212,7 @@ impl Selector {
} }
'/' => { '/' => {
if toks.peek().is_none() { if toks.peek().is_none() {
return Err("Expected selector.".into()); return Err(("Expected selector.", tok.pos()).into());
} else if '*' == toks.peek().unwrap().kind { } else if '*' == toks.peek().unwrap().kind {
toks.next(); toks.next();
eat_comment(toks, &Scope::new(), &Selector::new())?; eat_comment(toks, &Scope::new(), &Selector::new())?;
@ -215,7 +220,7 @@ impl Selector {
read_until_newline(toks); read_until_newline(toks);
devour_whitespace(toks); devour_whitespace(toks);
} else { } else {
return Err("Expected selector.".into()); return Err(("Expected selector.", tok.pos()).into());
} }
string.push(' '); string.push(' ');
} }
@ -228,7 +233,6 @@ impl Selector {
continue; continue;
} }
string.push(c); string.push(c);
string.push(',');
break; break;
} }
@ -238,17 +242,24 @@ impl Selector {
let mut contains_super_selector = false; let mut contains_super_selector = false;
let mut parts = Vec::new(); let mut parts = Vec::new();
// HACK: we re-lex here to get access to generic helper functions that let mut sel_toks = Vec::new();
// operate on `Token`s. Ideally, we would in the future not have
// to do this, or at the very least retain the span information. let mut current_pos = 0;
let mut iter = Lexer::new(&string).peekable(); sel_toks.extend(string.chars().map(|x| {
let len = x.len_utf8() as u64;
let tok = Token::new(span.subspan(current_pos, current_pos + len), x);
current_pos += len;
tok
}));
let mut iter = sel_toks.into_iter().peekable();
while let Some(tok) = iter.peek() { while let Some(tok) = iter.peek() {
inner.push(match tok.kind { inner.push(match tok.kind {
_ if is_selector_name_char(tok.kind) => { _ if is_selector_name_char(tok.kind) => {
inner.push(SelectorKind::Element(eat_ident_no_interpolation( inner.push(SelectorKind::Element(
&mut iter, eat_ident_no_interpolation(&mut iter)?.node,
)?)); ));
continue; continue;
} }
'&' => { '&' => {
@ -257,20 +268,24 @@ impl Selector {
} }
'.' => { '.' => {
iter.next(); iter.next();
inner.push(SelectorKind::Class(eat_ident_no_interpolation(&mut iter)?)); inner.push(SelectorKind::Class(
eat_ident_no_interpolation(&mut iter)?.node,
));
continue; continue;
} }
'#' => { '#' => {
iter.next(); iter.next();
inner.push(SelectorKind::Id(eat_ident_no_interpolation(&mut iter)?)); inner.push(SelectorKind::Id(
eat_ident_no_interpolation(&mut iter)?.node,
));
continue; continue;
} }
'%' => { '%' => {
iter.next(); iter.next();
is_invisible = true; is_invisible = true;
inner.push(SelectorKind::Placeholder(eat_ident_no_interpolation( inner.push(SelectorKind::Placeholder(
&mut iter, eat_ident_no_interpolation(&mut iter)?.node,
)?)); ));
continue; continue;
} }
'>' => SelectorKind::ImmediateChild, '>' => SelectorKind::ImmediateChild,
@ -298,8 +313,13 @@ impl Selector {
continue; continue;
} }
'[' => { '[' => {
iter.next(); let span = iter.next().unwrap().pos();
inner.push(Attribute::from_tokens(&mut iter, scope, super_selector)?); inner.push(Attribute::from_tokens(
&mut iter,
scope,
super_selector,
span,
)?);
continue; continue;
} }
':' => { ':' => {
@ -322,7 +342,7 @@ impl Selector {
} }
continue; continue;
} }
_ => return Err("expected selector.".into()), _ => return Err(("expected selector.", tok.pos()).into()),
}); });
iter.next(); iter.next();
} }
@ -351,7 +371,7 @@ impl Selector {
false false
}; };
if is_selector_name_char(toks.peek().unwrap().kind) { if is_selector_name_char(toks.peek().unwrap().kind) {
let name = eat_ident_no_interpolation(toks)?; let name = eat_ident_no_interpolation(toks)?.node;
Ok( Ok(
if toks.peek().is_some() && toks.peek().unwrap().kind == '(' { if toks.peek().is_some() && toks.peek().unwrap().kind == '(' {
toks.next(); toks.next();

View File

@ -1,6 +1,7 @@
use std::fmt::{self, Display};
use std::iter::Peekable; use std::iter::Peekable;
use codemap::Spanned;
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -15,13 +16,7 @@ use crate::{Expr, Token};
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Style { pub(crate) struct Style {
pub property: String, pub property: String,
pub value: Value, pub value: Spanned<Value>,
}
impl Display for Style {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {};", self.property, self.value)
}
} }
impl Style { impl Style {
@ -34,11 +29,29 @@ impl Style {
StyleParser::new(scope, super_selector).parse_property(toks, super_property) StyleParser::new(scope, super_selector).parse_property(toks, super_property)
} }
pub fn to_string(&self) -> SassResult<String> {
Ok(format!(
"{}: {};",
self.property,
self.value.node.to_css_string(self.value.span)?
))
}
pub(crate) fn eval(self) -> SassResult<Self> {
Ok(Style {
property: self.property,
value: Spanned {
span: self.value.span,
node: self.value.node.eval(self.value.span)?.node,
},
})
}
pub fn parse_value<I: Iterator<Item = Token>>( pub fn parse_value<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Spanned<Value>> {
StyleParser::new(scope, super_selector).parse_style_value(toks, scope) StyleParser::new(scope, super_selector).parse_style_value(toks, scope)
} }
@ -69,7 +82,7 @@ impl<'a> StyleParser<'a> {
&self, &self,
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
) -> SassResult<Value> { ) -> SassResult<Spanned<Value>> {
devour_whitespace(toks); devour_whitespace(toks);
Value::from_vec( Value::from_vec(
read_until_semicolon_or_open_or_closing_curly_brace(toks), read_until_semicolon_or_open_or_closing_curly_brace(toks),
@ -152,8 +165,8 @@ impl<'a> StyleParser<'a> {
} }
} }
_ => { _ => {
let val = self.parse_style_value(toks, scope)?; let value = self.parse_style_value(toks, scope)?;
let t = toks.peek().ok_or("expected more input.")?; let t = toks.peek().ok_or(("expected more input.", value.span))?;
match t.kind { match t.kind {
'}' => {} '}' => {}
';' => { ';' => {
@ -163,7 +176,7 @@ impl<'a> StyleParser<'a> {
'{' => { '{' => {
let mut v = vec![Style { let mut v = vec![Style {
property: super_property.clone(), property: super_property.clone(),
value: val, value,
}]; }];
match self.eat_style_group(toks, super_property, scope)? { match self.eat_style_group(toks, super_property, scope)? {
Expr::Style(s) => v.push(*s), Expr::Style(s) => v.push(*s),
@ -176,7 +189,7 @@ impl<'a> StyleParser<'a> {
} }
return Ok(Expr::Style(Box::new(Style { return Ok(Expr::Style(Box::new(Style {
property: super_property, property: super_property,
value: val, value,
}))); })));
} }
} }
@ -190,7 +203,7 @@ impl<'a> StyleParser<'a> {
mut super_property: String, mut super_property: String,
) -> SassResult<String> { ) -> SassResult<String> {
devour_whitespace(toks); devour_whitespace(toks);
let property = eat_ident(toks, self.scope, self.super_selector)?; let property = eat_ident(toks, self.scope, self.super_selector)?.node;
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
if toks.peek().is_some() && toks.peek().unwrap().kind == ':' { if toks.peek().is_some() && toks.peek().unwrap().kind == ':' {
toks.next(); toks.next();

View File

@ -1,18 +1,19 @@
use crate::common::Pos;
use crate::utils::IsWhitespace; use crate::utils::IsWhitespace;
use codemap::Span;
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Token { pub(crate) struct Token {
pub pos: Pos, pub pos: Span,
pub kind: char, pub kind: char,
} }
impl Token { impl Token {
pub const fn new(pos: Pos, kind: char) -> Self { pub const fn new(pos: Span, kind: char) -> Self {
Self { pos, kind } Self { pos, kind }
} }
pub const fn pos(&self) -> Pos { pub const fn pos(&self) -> Span {
self.pos self.pos
} }
} }

View File

@ -1,5 +1,7 @@
use std::iter::{Iterator, Peekable}; use std::iter::{Iterator, Peekable};
use codemap::Spanned;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::selector::Selector; use crate::selector::Selector;
@ -35,27 +37,27 @@ pub(crate) trait IsComment {
} }
pub(crate) fn devour_whitespace_or_comment<I: Iterator<Item = Token>>( pub(crate) fn devour_whitespace_or_comment<I: Iterator<Item = Token>>(
s: &mut Peekable<I>, toks: &mut Peekable<I>,
) -> SassResult<bool> { ) -> SassResult<bool> {
let mut found_whitespace = false; let mut found_whitespace = false;
while let Some(w) = s.peek() { while let Some(tok) = toks.peek() {
if w.kind == '/' { if tok.kind == '/' {
s.next(); let pos = toks.next().unwrap().pos();
match s.peek().unwrap().kind { match toks.peek().unwrap().kind {
'*' => { '*' => {
eat_comment(s, &Scope::new(), &Selector::new())?; eat_comment(toks, &Scope::new(), &Selector::new())?;
} }
'/' => read_until_newline(s), '/' => read_until_newline(toks),
_ => return Err("Expected expression.".into()), _ => return Err(("Expected expression.", pos).into()),
}; };
found_whitespace = true; found_whitespace = true;
continue; continue;
} }
if !w.is_whitespace() { if !tok.is_whitespace() {
break; break;
} }
found_whitespace = true; found_whitespace = true;
s.next(); toks.next();
} }
Ok(found_whitespace) Ok(found_whitespace)
} }
@ -64,20 +66,23 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Spanned<Value>> {
let val = Value::from_vec(read_until_closing_curly_brace(toks), scope, super_selector)?; let val = Value::from_vec(read_until_closing_curly_brace(toks), scope, super_selector)?;
toks.next(); toks.next();
Ok(val.eval()?.unquote()) Ok(Spanned {
node: val.node.eval(val.span)?.node.unquote(),
span: val.span,
})
} }
pub(crate) struct VariableDecl { pub(crate) struct VariableDecl {
pub val: Value, pub val: Spanned<Value>,
pub default: bool, pub default: bool,
pub global: bool, pub global: bool,
} }
impl VariableDecl { impl VariableDecl {
pub const fn new(val: Value, default: bool, global: bool) -> VariableDecl { pub const fn new(val: Spanned<Value>, default: bool, global: bool) -> VariableDecl {
VariableDecl { VariableDecl {
val, val,
default, default,
@ -327,28 +332,22 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
match next.kind { match next.kind {
'i' => todo!("!important"), 'i' => todo!("!important"),
'g' => { 'g' => {
if eat_ident(&mut raw, scope, super_selector)? let s = eat_ident(&mut raw, scope, super_selector)?;
.to_ascii_lowercase() if s.node.to_ascii_lowercase().as_str() == "lobal" {
.as_str()
== "lobal"
{
global = true; global = true;
} else { } else {
return Err("Invalid flag name.".into()); return Err(("Invalid flag name.", s.span).into());
} }
} }
'd' => { 'd' => {
if eat_ident(&mut raw, scope, super_selector)? let s = eat_ident(&mut raw, scope, super_selector)?;
.to_ascii_lowercase() if s.to_ascii_lowercase().as_str() == "efault" {
.as_str()
== "efault"
{
default = true; default = true;
} else { } else {
return Err("Invalid flag name.".into()); return Err(("Invalid flag name.", s.span).into());
} }
} }
_ => return Err("Invalid flag name.".into()), _ => return Err(("Invalid flag name.", next.pos()).into()),
} }
} }
_ => val_toks.push(tok), _ => val_toks.push(tok),
@ -364,17 +363,21 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<String> { ) -> SassResult<Spanned<String>> {
let mut s = String::new(); let mut s = String::new();
let mut span = toks.peek().unwrap().pos();
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
span = span.merge(tok.pos());
match tok.kind { match tok.kind {
'#' => { '#' => {
toks.next(); let tok = toks.next().unwrap();
if toks.peek().ok_or("Expected identifier.")?.kind == '{' { if toks.peek().ok_or(("Expected identifier.", tok.pos()))?.kind == '{' {
toks.next(); toks.next();
s.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()); let interpolation = parse_interpolation(toks, scope, super_selector)?;
span = span.merge(interpolation.span);
s.push_str(&interpolation.node.to_css_string(interpolation.span)?);
} else { } else {
return Err("Expected identifier.".into()); return Err(("Expected identifier.", tok.pos()).into());
} }
} }
_ if tok.kind.is_ascii_alphanumeric() _ if tok.kind.is_ascii_alphanumeric()
@ -385,7 +388,7 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
s.push(toks.next().unwrap().kind) s.push(toks.next().unwrap().kind)
} }
'\\' => { '\\' => {
toks.next(); let span_start = toks.next().unwrap().pos();
let mut n = String::new(); let mut n = String::new();
while let Some(c) = toks.peek() { while let Some(c) = toks.peek() {
if !c.kind.is_ascii_hexdigit() || n.len() > 6 { if !c.kind.is_ascii_hexdigit() || n.len() > 6 {
@ -395,7 +398,7 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
toks.next(); toks.next();
} }
if n.is_empty() { if n.is_empty() {
let c = toks.next().ok_or("expected \"{\".")?.kind; let c = toks.next().ok_or(("expected \"{\".", span_start))?.kind;
if (c == '-' && !s.is_empty()) || c.is_ascii_alphabetic() { if (c == '-' && !s.is_empty()) || c.is_ascii_alphabetic() {
s.push(c); s.push(c);
} else { } else {
@ -418,14 +421,20 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
_ => break, _ => break,
} }
} }
Ok(s) Ok(Spanned { node: s, span })
} }
pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>( pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
) -> SassResult<String> { ) -> SassResult<Spanned<String>> {
let mut s = String::new(); let mut s = String::new();
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(tok) = toks.peek() { while let Some(tok) = toks.peek() {
span = span.merge(tok.pos());
match tok.kind { match tok.kind {
'#' => { '#' => {
break; break;
@ -471,26 +480,36 @@ pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>(
_ => break, _ => break,
} }
} }
Ok(s) Ok(Spanned { node: s, span })
} }
pub(crate) fn eat_number<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> SassResult<String> { pub(crate) fn eat_number<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> SassResult<Spanned<String>> {
let mut whole = String::new(); let mut whole = String::new();
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(c) = toks.peek() { while let Some(c) = toks.peek() {
if !c.kind.is_numeric() { if !c.kind.is_numeric() {
break; break;
} }
let tok = toks.next().unwrap(); let tok = toks.next().unwrap();
span = span.merge(tok.pos());
whole.push(tok.kind); whole.push(tok.kind);
} }
if toks.peek().is_none() { if toks.peek().is_none() {
return Ok(whole); return Ok(Spanned { node: whole, span });
} }
let mut dec = String::new(); let mut dec = String::new();
if toks.peek().unwrap().kind == '.' { let next_tok = toks.peek().unwrap().clone();
if next_tok.kind == '.' {
toks.next(); toks.next();
dec.push('.'); dec.push('.');
while let Some(c) = toks.peek() { while let Some(c) = toks.peek() {
@ -498,16 +517,17 @@ pub(crate) fn eat_number<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> S
break; break;
} }
let tok = toks.next().unwrap(); let tok = toks.next().unwrap();
span = span.merge(tok.pos());
dec.push(tok.kind); dec.push(tok.kind);
} }
} }
if dec.len() == 1 { if dec.len() == 1 {
return Err("Expected digit.".into()); return Err(("Expected digit.", next_tok.pos()).into());
} }
whole.push_str(&dec); whole.push_str(&dec);
Ok(whole) Ok(Spanned { node: whole, span })
} }
/// Eat tokens until a newline /// Eat tokens until a newline
@ -529,13 +549,21 @@ pub(crate) fn read_until_newline<I: Iterator<Item = Token>>(toks: &mut Peekable<
/// This function assumes that the starting "/*" has already been consumed /// This function assumes that the starting "/*" has already been consumed
/// The entirety of the comment, including the ending "*/" is consumed. /// The entirety of the comment, including the ending "*/" is consumed.
/// Note that the ending "*/" is not included in the output. /// Note that the ending "*/" is not included in the output.
///
/// TODO: support interpolation within multiline comments
pub(crate) fn eat_comment<I: Iterator<Item = Token>>( pub(crate) fn eat_comment<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
_scope: &Scope, _scope: &Scope,
_super_selector: &Selector, _super_selector: &Selector,
) -> SassResult<String> { ) -> SassResult<Spanned<String>> {
let mut comment = String::new(); let mut comment = String::new();
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
span = span.merge(tok.pos());
if tok.kind == '*' && toks.peek().unwrap().kind == '/' { if tok.kind == '*' && toks.peek().unwrap().kind == '/' {
toks.next(); toks.next();
break; break;
@ -543,7 +571,10 @@ pub(crate) fn eat_comment<I: Iterator<Item = Token>>(
comment.push(tok.kind); comment.push(tok.kind);
} }
devour_whitespace(toks); devour_whitespace(toks);
Ok(comment) Ok(Spanned {
node: comment,
span,
})
} }
pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>( pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
@ -551,11 +582,17 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
scope: &Scope, scope: &Scope,
q: char, q: char,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Spanned<Value>> {
let mut s = String::new(); let mut s = String::new();
let mut is_escaped = false; let mut is_escaped = false;
let mut found_interpolation = false; let mut found_interpolation = false;
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
span = span.merge(tok.pos());
match tok.kind { match tok.kind {
'"' if !is_escaped && q == '"' => break, '"' if !is_escaped && q == '"' => break,
'"' if is_escaped => { '"' if is_escaped => {
@ -579,14 +616,15 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
if toks.peek().unwrap().kind == '{' { if toks.peek().unwrap().kind == '{' {
toks.next(); toks.next();
found_interpolation = true; found_interpolation = true;
s.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()); let interpolation = parse_interpolation(toks, scope, super_selector)?;
s.push_str(&interpolation.node.to_css_string(interpolation.span)?);
continue; continue;
} else { } else {
s.push('#'); s.push('#');
continue; continue;
} }
} }
'\n' => return Err("Expected \".".into()), '\n' => return Err(("Expected \".", tok.pos()).into()),
v if v.is_ascii_hexdigit() && is_escaped => { v if v.is_ascii_hexdigit() && is_escaped => {
let mut n = v.to_string(); let mut n = v.to_string();
while let Some(c) = toks.peek() { while let Some(c) = toks.peek() {
@ -628,7 +666,10 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
_ => unreachable!(), _ => unreachable!(),
} }
}; };
Ok(Value::Ident(s, quotes)) Ok(Spanned {
node: Value::Ident(s, quotes),
span,
})
} }
pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>( pub(crate) fn read_until_closing_paren<I: Iterator<Item = Token>>(

View File

@ -21,8 +21,10 @@ pub(crate) fn eat_calc_args<I: Iterator<Item = Token>>(
} }
'#' => { '#' => {
if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { if toks.peek().is_some() && toks.peek().unwrap().kind == '{' {
toks.next(); let span = toks.next().unwrap().pos();
string.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()); string.push_str(
&parse_interpolation(toks, scope, super_selector)?.to_css_string(span)?,
);
} else { } else {
string.push('#'); string.push('#');
} }
@ -60,7 +62,9 @@ pub(crate) fn eat_progid<I: Iterator<Item = Token>>(
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<String> { ) -> SassResult<String> {
let mut string = String::new(); let mut string = String::new();
let mut span = toks.peek().unwrap().pos();
while let Some(tok) = toks.next() { while let Some(tok) = toks.next() {
span = span.merge(tok.pos());
match tok.kind { match tok.kind {
'a'..='z' | 'A'..='Z' | '.' => { 'a'..='z' | 'A'..='Z' | '.' => {
string.push(tok.kind); string.push(tok.kind);
@ -69,7 +73,7 @@ pub(crate) fn eat_progid<I: Iterator<Item = Token>>(
string.push_str(&eat_calc_args(toks, scope, super_selector)?); string.push_str(&eat_calc_args(toks, scope, super_selector)?);
break; break;
} }
_ => return Err("expected \"(\".".into()), _ => return Err(("expected \"(\".", span).into()),
} }
} }
Ok(string) Ok(string)

View File

@ -1,6 +1,8 @@
use std::slice::Iter; use std::slice::Iter;
use std::vec::IntoIter; use std::vec::IntoIter;
use codemap::Span;
use super::Value; use super::Value;
use crate::common::{Brackets, ListSeparator}; use crate::common::{Brackets, ListSeparator};
use crate::error::SassResult; use crate::error::SassResult;
@ -13,9 +15,9 @@ impl SassMap {
SassMap(Vec::new()) SassMap(Vec::new())
} }
pub fn get(self, key: &Value) -> SassResult<Option<Value>> { pub fn get(self, key: &Value, span: Span) -> SassResult<Option<Value>> {
for (k, v) in self.0 { for (k, v) in self.0 {
if k.equals(key.clone())? { if k.equals(key.clone(), span)? {
return Ok(Some(v)); return Ok(Some(v));
} }
} }

View File

@ -1,7 +1,8 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::{self, Display, Write};
use std::iter::Iterator; use std::iter::Iterator;
use codemap::{Span, Spanned};
use crate::color::Color; use crate::color::Color;
use crate::common::{Brackets, ListSeparator, Op, QuoteKind}; use crate::common::{Brackets, ListSeparator, Op, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
@ -33,112 +34,11 @@ pub(crate) enum Value {
Paren(Box<Value>), Paren(Box<Value>),
Ident(String, QuoteKind), Ident(String, QuoteKind),
Map(SassMap), Map(SassMap),
ArgList(Vec<Value>), ArgList(Vec<Spanned<Value>>),
/// Returned by `get-function()` /// Returned by `get-function()`
Function(SassFunction), Function(SassFunction),
} }
impl Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Important => write!(f, "!important"),
Self::Dimension(num, unit) => match unit {
Unit::Mul(..) => {
eprintln!("Error: {}{} isn't a valid CSS value.", num, unit);
std::process::exit(1);
}
_ => write!(f, "{}{}", num, unit),
},
Self::Map(map) => write!(
f,
"({})",
map.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.collect::<Vec<String>>()
.join(", ")
),
Self::Function(func) => write!(f, "get-function(\"{}\")", func.name()),
Self::List(vals, sep, brackets) => match brackets {
Brackets::None => write!(
f,
"{}",
vals.iter()
.filter(|x| !x.is_null())
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(sep.as_str()),
),
Brackets::Bracketed => write!(
f,
"[{}]",
vals.iter()
.filter(|x| !x.is_null())
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(sep.as_str()),
),
},
Self::Color(c) => write!(f, "{}", c),
Self::UnaryOp(..) | Self::BinaryOp(..) => write!(
f,
"{}",
match self.clone().eval() {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
}
),
Self::Paren(val) => write!(f, "{}", val),
Self::Ident(val, kind) => {
if kind == &QuoteKind::None {
return write!(f, "{}", val);
}
let has_single_quotes = val.contains(|x| x == '\'');
let has_double_quotes = val.contains(|x| x == '"');
if has_single_quotes && !has_double_quotes {
write!(f, "\"{}\"", val)
} else if !has_single_quotes && has_double_quotes {
write!(f, "'{}'", val)
} else if !has_single_quotes && !has_double_quotes {
write!(f, "\"{}\"", val)
} else {
let quote_char = match kind {
QuoteKind::Double => '"',
QuoteKind::Single => '\'',
_ => unreachable!(),
};
f.write_char(quote_char)?;
for c in val.chars() {
match c {
'"' | '\'' if c == quote_char => {
f.write_char('\\')?;
f.write_char(quote_char)?;
}
v => f.write_char(v)?,
}
}
f.write_char(quote_char)?;
Ok(())
}
}
Self::True => write!(f, "true"),
Self::False => write!(f, "false"),
Self::Null => write!(f, "null"),
Self::ArgList(args) => write!(
f,
"{}",
args.iter()
.filter(|x| !x.is_null())
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(", "),
),
}
}
}
impl Value { impl Value {
pub fn is_null(&self) -> bool { pub fn is_null(&self) -> bool {
match self { match self {
@ -147,12 +47,106 @@ impl Value {
_ => false, _ => false,
} }
} }
pub fn to_css_string(&self, span: Span) -> SassResult<String> {
Ok(match self {
Self::Important => format!("!important"),
Self::Dimension(num, unit) => match unit {
Unit::Mul(..) => {
return Err((
format!("Error: {}{} isn't a valid CSS value.", num, unit),
span,
)
.into());
}
_ => format!("{}{}", num, unit),
},
Self::Map(map) => format!(
"({})",
map.iter()
.map(|(k, v)| Ok(format!(
"{}: {}",
k.to_css_string(span)?,
v.to_css_string(span)?
)))
.collect::<SassResult<Vec<String>>>()?
.join(", ")
),
Self::Function(func) => format!("get-function(\"{}\")", func.name()),
Self::List(vals, sep, brackets) => match brackets {
Brackets::None => format!(
"{}",
vals.iter()
.filter(|x| !x.is_null())
.map(|x| x.to_css_string(span))
.collect::<SassResult<Vec<String>>>()?
.join(sep.as_str()),
),
Brackets::Bracketed => format!(
"[{}]",
vals.iter()
.filter(|x| !x.is_null())
.map(|x| x.to_css_string(span))
.collect::<SassResult<Vec<String>>>()?
.join(sep.as_str()),
),
},
Self::Color(c) => format!("{}", c),
Self::UnaryOp(..) | Self::BinaryOp(..) => {
format!("{}", self.clone().eval(span)?.to_css_string(span)?)
}
Self::Paren(val) => format!("{}", val.to_css_string(span)?),
Self::Ident(val, kind) => {
if kind == &QuoteKind::None {
return Ok(val.clone());
}
let has_single_quotes = val.contains(|x| x == '\'');
let has_double_quotes = val.contains(|x| x == '"');
if has_single_quotes && !has_double_quotes {
format!("\"{}\"", val)
} else if !has_single_quotes && has_double_quotes {
format!("'{}'", val)
} else if !has_single_quotes && !has_double_quotes {
format!("\"{}\"", val)
} else {
let quote_char = match kind {
QuoteKind::Double => '"',
QuoteKind::Single => '\'',
_ => unreachable!(),
};
let mut buf = String::with_capacity(val.len() + 2);
buf.push(quote_char);
for c in val.chars() {
match c {
'"' | '\'' if c == quote_char => {
buf.push('\\');
buf.push(quote_char);
}
v => buf.push(v),
}
}
buf.push(quote_char);
buf
}
}
Self::True => "true".to_string(),
Self::False => "false".to_string(),
Self::Null => "null".to_string(),
Self::ArgList(args) => format!(
"{}",
args.iter()
.filter(|x| !x.is_null())
.map(|a| Ok(a.node.to_css_string(span)?))
.collect::<SassResult<Vec<String>>>()?
.join(", "),
),
})
}
pub fn is_true(&self) -> SassResult<bool> { pub fn is_true(&self, span: Span) -> SassResult<bool> {
match self { match self {
Value::Null | Value::False => Ok(false), Value::Null | Value::False => Ok(false),
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => { Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
self.clone().eval()?.is_true() self.clone().eval(span)?.is_true(span)
} }
_ => Ok(true), _ => Ok(true),
} }
@ -165,7 +159,11 @@ impl Value {
} }
} }
pub fn kind(&self) -> SassResult<&'static str> { pub fn span(self, span: Span) -> Spanned<Self> {
Spanned { node: self, span }
}
pub fn kind(&self, span: Span) -> SassResult<&'static str> {
match self { match self {
Self::Color(..) => Ok("color"), Self::Color(..) => Ok("color"),
Self::Ident(..) | Self::Important => Ok("string"), Self::Ident(..) | Self::Important => Ok("string"),
@ -176,7 +174,9 @@ impl Value {
Self::True | Self::False => Ok("bool"), Self::True | Self::False => Ok("bool"),
Self::Null => Ok("null"), Self::Null => Ok("null"),
Self::Map(..) => Ok("map"), Self::Map(..) => Ok("map"),
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => self.clone().eval()?.kind(), Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
self.clone().eval(span)?.kind(span)
}
} }
} }
@ -195,19 +195,19 @@ impl Value {
} }
} }
pub fn inspect(&self) -> String { pub fn inspect(&self, span: Span) -> SassResult<String> {
match self { Ok(match self {
Value::List(v, _, brackets) if v.is_empty() => match brackets { Value::List(v, _, brackets) if v.is_empty() => match brackets {
Brackets::None => "()".to_string(), Brackets::None => "()".to_string(),
Brackets::Bracketed => "[]".to_string(), Brackets::Bracketed => "[]".to_string(),
}, },
Value::Function(f) => format!("get-function(\"{}\")", f.name()), Value::Function(f) => format!("get-function(\"{}\")", f.name()),
v => v.to_string(), v => v.to_css_string(span)?,
} })
} }
pub fn equals(self, other: Value) -> SassResult<bool> { pub fn equals(self, other: Value, span: Span) -> SassResult<bool> {
Ok(match self.eval()? { Ok(match self.eval(span)?.node {
Self::Ident(s1, ..) => match other { Self::Ident(s1, ..) => match other {
Self::Ident(s2, ..) => s1 == s2, Self::Ident(s2, ..) => s1 == s2,
_ => false, _ => false,
@ -227,64 +227,71 @@ impl Value {
} }
_ => false, _ => false,
}, },
s => s == other.eval()?, s => s == other.eval(span)?.node,
}) })
} }
pub fn unary_op_plus(self) -> SassResult<Self> { pub fn unary_op_plus(self, span: Span) -> SassResult<Self> {
Ok(match self.eval()? { Ok(match self.eval(span)?.node {
v @ Value::Dimension(..) => v, v @ Value::Dimension(..) => v,
v => Value::Ident(format!("+{}", v), QuoteKind::None), v => Value::Ident(format!("+{}", v.to_css_string(span)?), QuoteKind::None),
}) })
} }
pub fn eval(self) -> SassResult<Self> { pub fn eval(self, span: Span) -> SassResult<Spanned<Self>> {
match self { Ok(match self {
Self::BinaryOp(lhs, op, rhs) => match op { Self::BinaryOp(lhs, op, rhs) => match op {
Op::Plus => *lhs + *rhs, Op::Plus => lhs.add(*rhs, span)?,
Op::Minus => *lhs - *rhs, Op::Minus => lhs.sub(*rhs, span)?,
Op::Equal => Ok(Self::bool(lhs.equals(*rhs)?)), Op::Equal => Self::bool(lhs.equals(*rhs, span)?),
Op::NotEqual => Ok(Self::bool(!lhs.equals(*rhs)?)), Op::NotEqual => Self::bool(!lhs.equals(*rhs, span)?),
Op::Mul => *lhs * *rhs, Op::Mul => lhs.mul(*rhs, span)?,
Op::Div => *lhs / *rhs, Op::Div => lhs.div(*rhs, span)?,
Op::Rem => *lhs % *rhs, Op::Rem => lhs.rem(*rhs, span)?,
Op::GreaterThan => lhs.cmp(*rhs, op), Op::GreaterThan => return lhs.cmp(*rhs, op, span),
Op::GreaterThanEqual => lhs.cmp(*rhs, op), Op::GreaterThanEqual => return lhs.cmp(*rhs, op, span),
Op::LessThan => lhs.cmp(*rhs, op), Op::LessThan => return lhs.cmp(*rhs, op, span),
Op::LessThanEqual => lhs.cmp(*rhs, op), Op::LessThanEqual => return lhs.cmp(*rhs, op, span),
Op::Not => unreachable!(), Op::Not => unreachable!(),
Op::And => Ok(if lhs.clone().is_true()? { Op::And => {
rhs.eval()? if lhs.clone().is_true(span)? {
} else { rhs.eval(span)?.node
lhs.eval()? } else {
}), lhs.eval(span)?.node
Op::Or => Ok(if lhs.is_true()? { }
lhs.eval()? }
} else { Op::Or => {
rhs.eval()? if lhs.is_true(span)? {
}), lhs.eval(span)?.node
} else {
rhs.eval(span)?.node
}
}
}, },
Self::Paren(v) => v.eval(), Self::Paren(v) => v.eval(span)?.node,
Self::UnaryOp(op, val) => match op { Self::UnaryOp(op, val) => match op {
Op::Plus => val.unary_op_plus(), Op::Plus => val.unary_op_plus(span)?,
Op::Minus => -*val, Op::Minus => val.neg(span)?,
Op::Not => Ok(Self::bool(!val.eval()?.is_true()?)), Op::Not => Self::bool(!val.eval(span)?.is_true(span)?),
_ => unreachable!(), _ => unreachable!(),
}, },
_ => Ok(self), _ => self,
} }
.span(span))
} }
pub fn cmp(self, mut other: Self, op: Op) -> SassResult<Value> { pub fn cmp(self, mut other: Self, op: Op, span: Span) -> SassResult<Spanned<Value>> {
if let Self::Paren(..) = other { if let Self::Paren(..) = other {
other = other.eval()? other = other.eval(span)?.node
} }
let precedence = op.precedence(); let precedence = op.precedence();
let ordering = match self { let ordering = match self {
Self::Dimension(num, unit) => match &other { Self::Dimension(num, unit) => match &other {
Self::Dimension(num2, unit2) => { Self::Dimension(num2, unit2) => {
if !unit.comparable(&unit2) { if !unit.comparable(&unit2) {
return Err(format!("Incompatible units {} and {}.", unit2, unit).into()); return Err(
(format!("Incompatible units {} and {}.", unit2, unit), span).into(),
);
} }
if &unit == unit2 { if &unit == unit2 {
num.cmp(num2) num.cmp(num2)
@ -301,41 +308,69 @@ impl Value {
} }
} }
Self::BinaryOp(..) => todo!(), Self::BinaryOp(..) => todo!(),
v => return Err(format!("Undefined operation \"{} {} {}\".", v, op, other).into()), v => {
return Err((
format!(
"Undefined operation \"{} {} {}\".",
v.to_css_string(span)?,
op,
other.to_css_string(span)?
),
span,
)
.into())
}
}, },
Self::BinaryOp(left, op2, right) => { Self::BinaryOp(left, op2, right) => {
return if op2.precedence() >= precedence { return if op2.precedence() >= precedence {
Self::BinaryOp(left, op2, right).eval()?.cmp(other, op) Self::BinaryOp(left, op2, right)
.eval(span)?
.node
.cmp(other, op, span)
} else { } else {
Self::BinaryOp( Self::BinaryOp(
left, left,
op2, op2,
Box::new(Self::BinaryOp(right, op, Box::new(other)).eval()?), Box::new(Self::BinaryOp(right, op, Box::new(other)).eval(span)?.node),
) )
.eval() .eval(span)
} }
} }
Self::UnaryOp(..) | Self::Paren(..) => return self.eval()?.cmp(other, op), Self::UnaryOp(..) | Self::Paren(..) => {
_ => return Err(format!("Undefined operation \"{} {} {}\".", self, op, other).into()), return self.eval(span)?.node.cmp(other, op, span)
}
_ => {
return Err((
format!(
"Undefined operation \"{} {} {}\".",
self.to_css_string(span)?,
op,
other.to_css_string(span)?
),
span,
)
.into())
}
}; };
match op { Ok(match op {
Op::GreaterThan => match ordering { Op::GreaterThan => match ordering {
Ordering::Greater => Ok(Self::True), Ordering::Greater => Self::True,
Ordering::Less | Ordering::Equal => Ok(Self::False), Ordering::Less | Ordering::Equal => Self::False,
}, },
Op::GreaterThanEqual => match ordering { Op::GreaterThanEqual => match ordering {
Ordering::Greater | Ordering::Equal => Ok(Self::True), Ordering::Greater | Ordering::Equal => Self::True,
Ordering::Less => Ok(Self::False), Ordering::Less => Self::False,
}, },
Op::LessThan => match ordering { Op::LessThan => match ordering {
Ordering::Less => Ok(Self::True), Ordering::Less => Self::True,
Ordering::Greater | Ordering::Equal => Ok(Self::False), Ordering::Greater | Ordering::Equal => Self::False,
}, },
Op::LessThanEqual => match ordering { Op::LessThanEqual => match ordering {
Ordering::Less | Ordering::Equal => Ok(Self::True), Ordering::Less | Ordering::Equal => Self::True,
Ordering::Greater => Ok(Self::False), Ordering::Greater => Self::False,
}, },
_ => unreachable!(), _ => unreachable!(),
} }
.span(span))
} }
} }

View File

@ -1,35 +1,45 @@
use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; use codemap::Span;
use crate::common::{Op, QuoteKind}; use crate::common::{Op, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::unit::{Unit, UNIT_CONVERSION_TABLE}; use crate::unit::{Unit, UNIT_CONVERSION_TABLE};
use crate::value::Value; use crate::value::Value;
impl Add for Value { impl Value {
type Output = SassResult<Self>; pub fn add(self, mut other: Self, span: Span) -> SassResult<Self> {
fn add(self, mut other: Self) -> Self::Output {
if let Self::Paren(..) = other { if let Self::Paren(..) = other {
other = other.eval()? other = other.eval(span)?.node
} }
let precedence = Op::Plus.precedence(); let precedence = Op::Plus.precedence();
Ok(match self { Ok(match self {
Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(), Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(),
Self::Important | Self::True | Self::False => match other { Self::Important | Self::True | Self::False => match other {
Self::Ident(s, QuoteKind::Double) | Self::Ident(s, QuoteKind::Single) => { Self::Ident(s, QuoteKind::Double) | Self::Ident(s, QuoteKind::Single) => {
Value::Ident(format!("{}{}", self, s), QuoteKind::Double) Value::Ident(
format!("{}{}", self.to_css_string(span)?, s),
QuoteKind::Double,
)
} }
Self::Null => Value::Ident(self.to_string(), QuoteKind::None), Self::Null => Value::Ident(self.to_css_string(span)?, QuoteKind::None),
_ => Value::Ident(format!("{}{}", self, other), QuoteKind::None), _ => Value::Ident(
format!(
"{}{}",
self.to_css_string(span)?,
other.to_css_string(span)?
),
QuoteKind::None,
),
}, },
Self::Null => match other { Self::Null => match other {
Self::Null => Self::Null, Self::Null => Self::Null,
_ => Value::Ident(format!("{}", other), QuoteKind::None), _ => Value::Ident(format!("{}", other.to_css_string(span)?), QuoteKind::None),
}, },
Self::Dimension(num, unit) => match other { Self::Dimension(num, unit) => match other {
Self::Dimension(num2, unit2) => { Self::Dimension(num2, unit2) => {
if !unit.comparable(&unit2) { if !unit.comparable(&unit2) {
return Err(format!("Incompatible units {} and {}.", unit2, unit).into()); return Err(
(format!("Incompatible units {} and {}.", unit2, unit), span).into(),
);
} }
if unit == unit2 { if unit == unit2 {
Value::Dimension(num + num2, unit) Value::Dimension(num + num2, unit)
@ -48,61 +58,98 @@ impl Add for Value {
} }
Self::Ident(s, q) => Value::Ident(format!("{}{}{}", num, unit, s), q.normalize()), Self::Ident(s, q) => Value::Ident(format!("{}{}{}", num, unit, s), q.normalize()),
Self::Null => Value::Ident(format!("{}{}", num, unit), QuoteKind::None), Self::Null => Value::Ident(format!("{}{}", num, unit), QuoteKind::None),
Self::List(..) => { Self::List(..) => Value::Ident(
Value::Ident(format!("{}{}{}", num, unit, other), QuoteKind::None) format!("{}{}{}", num, unit, other.to_css_string(span)?),
} QuoteKind::None,
),
_ => { _ => {
return Err( return Err((
format!("Undefined operation \"{}{} + {}\".", num, unit, other).into(), format!(
"Undefined operation \"{}{} + {}\".",
num,
unit,
other.to_css_string(span)?
),
span,
) )
.into())
} }
}, },
Self::Color(c) => match other { Self::Color(c) => match other {
Self::Ident(s, q) => Value::Ident(format!("{}{}", c, s), q.normalize()), Self::Ident(s, q) => Value::Ident(format!("{}{}", c, s), q.normalize()),
Self::Null => Value::Ident(c.to_string(), QuoteKind::None), Self::Null => Value::Ident(c.to_string(), QuoteKind::None),
Self::List(..) => Value::Ident(format!("{}{}", c, other), QuoteKind::None), Self::List(..) => Value::Ident(
_ => return Err(format!("Undefined operation \"{} + {}\".", c, other).into()), format!("{}{}", c, other.to_css_string(span)?),
QuoteKind::None,
),
_ => {
return Err((
format!(
"Undefined operation \"{} + {}\".",
c,
other.to_css_string(span)?
),
span,
)
.into())
}
}, },
Self::BinaryOp(left, op, right) => { Self::BinaryOp(left, op, right) => {
if op.precedence() >= precedence { if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? + other)? Self::BinaryOp(left, op, right)
.eval(span)?
.node
.add(other, span)?
} else { } else {
Self::BinaryOp( Self::BinaryOp(
left, left,
op, op,
Box::new(Self::BinaryOp(right, Op::Plus, Box::new(other)).eval()?), Box::new(
Self::BinaryOp(right, Op::Plus, Box::new(other))
.eval(span)?
.node,
),
) )
.eval()? .eval(span)?
.node
} }
} }
Self::UnaryOp(..) | Self::Paren(..) => (self.eval()? + other)?, Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.add(other, span)?,
Self::Ident(s1, quotes1) => match other { Self::Ident(s1, quotes1) => match other {
Self::Ident(s2, _) => Value::Ident(format!("{}{}", s1, s2), quotes1.normalize()), Self::Ident(s2, _) => Value::Ident(format!("{}{}", s1, s2), quotes1.normalize()),
Self::Important | Self::True | Self::False | Self::Dimension(..) => { Self::Important | Self::True | Self::False | Self::Dimension(..) => Value::Ident(
Value::Ident(format!("{}{}", s1, other), quotes1.normalize()) format!("{}{}", s1, other.to_css_string(span)?),
} quotes1.normalize(),
),
Self::Null => Value::Ident(s1, quotes1.normalize()), Self::Null => Value::Ident(s1, quotes1.normalize()),
Self::Color(c) => Value::Ident(format!("{}{}", s1, c), quotes1.normalize()), Self::Color(c) => Value::Ident(format!("{}{}", s1, c), quotes1.normalize()),
Self::List(..) => Value::Ident(format!("{}{}", s1, other), quotes1), Self::List(..) => {
Value::Ident(format!("{}{}", s1, other.to_css_string(span)?), quotes1)
}
Self::UnaryOp(..) | Self::BinaryOp(..) => todo!(), Self::UnaryOp(..) | Self::BinaryOp(..) => todo!(),
Self::Paren(..) => (Self::Ident(s1, quotes1) + other.eval()?)?, Self::Paren(..) => Self::Ident(s1, quotes1).add(other.eval(span)?.node, span)?,
Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(), Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(),
}, },
Self::List(..) => match other { Self::List(..) => match other {
Self::Ident(s, q) => Value::Ident(format!("{}{}", self, s), q.normalize()), Self::Ident(s, q) => {
Self::Paren(..) => (self + other.eval()?)?, Value::Ident(format!("{}{}", self.to_css_string(span)?, s), q.normalize())
_ => Value::Ident(format!("{}{}", self, other), QuoteKind::None), }
Self::Paren(..) => (self.add(other.eval(span)?.node, span))?,
_ => Value::Ident(
format!(
"{}{}",
self.to_css_string(span)?,
other.to_css_string(span)?
),
QuoteKind::None,
),
}, },
}) })
} }
}
impl Sub for Value { pub fn sub(self, mut other: Self, span: Span) -> SassResult<Self> {
type Output = SassResult<Self>;
fn sub(self, mut other: Self) -> Self::Output {
if let Self::Paren(..) = other { if let Self::Paren(..) = other {
other = other.eval()? other = other.eval(span)?.node
} }
let precedence = Op::Mul.precedence(); let precedence = Op::Mul.precedence();
Ok(match self { Ok(match self {
@ -110,7 +157,9 @@ impl Sub for Value {
Self::Dimension(num, unit) => match other { Self::Dimension(num, unit) => match other {
Self::Dimension(num2, unit2) => { Self::Dimension(num2, unit2) => {
if !unit.comparable(&unit2) { if !unit.comparable(&unit2) {
return Err(format!("Incompatible units {} and {}.", unit2, unit).into()); return Err(
(format!("Incompatible units {} and {}.", unit2, unit), span).into(),
);
} }
if unit == unit2 { if unit == unit2 {
Value::Dimension(num - num2, unit) Value::Dimension(num - num2, unit)
@ -127,12 +176,14 @@ impl Sub for Value {
) )
} }
} }
Self::List(..) => { Self::List(..) => Value::Ident(
Value::Ident(format!("{}{}-{}", num, unit, other), QuoteKind::None) format!("{}{}-{}", num, unit, other.to_css_string(span)?),
} QuoteKind::None,
Self::Ident(..) => { ),
Value::Ident(format!("{}{}-{}", num, unit, other), QuoteKind::None) Self::Ident(..) => Value::Ident(
} format!("{}{}-{}", num, unit, other.to_css_string(span)?),
QuoteKind::None,
),
_ => todo!(), _ => todo!(),
}, },
Self::Color(c) => match other { Self::Color(c) => match other {
@ -142,23 +193,42 @@ impl Sub for Value {
), ),
Self::Null => Value::Ident(format!("{}-", c), QuoteKind::None), Self::Null => Value::Ident(format!("{}-", c), QuoteKind::None),
Self::Dimension(..) | Self::Color(..) => { Self::Dimension(..) | Self::Color(..) => {
return Err(format!("Undefined operation \"{} - {}\".", c, other).into()) return Err((
format!(
"Undefined operation \"{} - {}\".",
c,
other.to_css_string(span)?
),
span,
)
.into())
} }
_ => Value::Ident(format!("{}-{}", c, other), QuoteKind::None), _ => Value::Ident(
format!("{}-{}", c, other.to_css_string(span)?),
QuoteKind::None,
),
}, },
Self::BinaryOp(left, op, right) => { Self::BinaryOp(left, op, right) => {
if op.precedence() >= precedence { if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? - other)? Self::BinaryOp(left, op, right)
.eval(span)?
.node
.sub(other, span)?
} else { } else {
Self::BinaryOp( Self::BinaryOp(
left, left,
op, op,
Box::new(Self::BinaryOp(right, Op::Minus, Box::new(other)).eval()?), Box::new(
Self::BinaryOp(right, Op::Minus, Box::new(other))
.eval(span)?
.node,
),
) )
.eval()? .eval(span)?
.node
} }
} }
Self::Paren(..) => (self.eval()? - other)?, Self::Paren(..) => self.eval(span)?.node.sub(other, span)?,
Self::Ident(s1, q1) => match other { Self::Ident(s1, q1) => match other {
Self::Ident(s2, q2) => Value::Ident( Self::Ident(s2, q2) => Value::Ident(
format!( format!(
@ -177,7 +247,13 @@ impl Sub for Value {
| Self::False | Self::False
| Self::Dimension(..) | Self::Dimension(..)
| Self::Color(..) => Value::Ident( | Self::Color(..) => Value::Ident(
format!("{}{}{}-{}", q1.normalize(), s1, q1.normalize(), other), format!(
"{}{}{}-{}",
q1.normalize(),
s1,
q1.normalize(),
other.to_css_string(span)?
),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => Value::Ident( Self::Null => Value::Ident(
@ -185,37 +261,66 @@ impl Sub for Value {
QuoteKind::None, QuoteKind::None,
), ),
Self::List(..) => Value::Ident( Self::List(..) => Value::Ident(
format!("{}{}{}-{}", q1.normalize(), s1, q1.normalize(), other), format!(
"{}{}{}-{}",
q1.normalize(),
s1,
q1.normalize(),
other.to_css_string(span)?
),
QuoteKind::None, QuoteKind::None,
), ),
_ => todo!(), _ => todo!(),
}, },
Self::List(..) => match other { Self::List(..) => match other {
Self::Ident(s, q) => Value::Ident( Self::Ident(s, q) => Value::Ident(
format!("{}-{}{}{}", self, q.normalize(), s, q.normalize()), format!(
"{}-{}{}{}",
self.to_css_string(span)?,
q.normalize(),
s,
q.normalize()
),
QuoteKind::None,
),
_ => Value::Ident(
format!(
"{}-{}",
self.to_css_string(span)?,
other.to_css_string(span)?
),
QuoteKind::None, QuoteKind::None,
), ),
Self::Paren(..) => (self - other.eval()?)?,
_ => Value::Ident(format!("{}-{}", self, other), QuoteKind::None),
}, },
_ => match other { _ => match other {
Self::Ident(s, q) => Value::Ident( Self::Ident(s, q) => Value::Ident(
format!("{}-{}{}{}", self, q.normalize(), s, q.normalize()), format!(
"{}-{}{}{}",
self.to_css_string(span)?,
q.normalize(),
s,
q.normalize()
),
QuoteKind::None,
),
Self::Null => {
Value::Ident(format!("{}-", self.to_css_string(span)?), QuoteKind::None)
}
_ => Value::Ident(
format!(
"{}-{}",
self.to_css_string(span)?,
other.to_css_string(span)?
),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => Value::Ident(format!("{}-", self), QuoteKind::None),
_ => Value::Ident(format!("{}-{}", self, other), QuoteKind::None),
}, },
}) })
} }
}
impl Mul for Value { pub fn mul(self, mut other: Self, span: Span) -> SassResult<Self> {
type Output = SassResult<Self>;
fn mul(self, mut other: Self) -> Self::Output {
if let Self::Paren(..) = other { if let Self::Paren(..) = other {
other = other.eval()? other = other.eval(span)?.node
} }
let precedence = Op::Mul.precedence(); let precedence = Op::Mul.precedence();
Ok(match self { Ok(match self {
@ -238,40 +343,63 @@ impl Mul for Value {
} }
} }
_ => { _ => {
return Err( return Err((
format!("Undefined operation \"{}{} * {}\".", num, unit, other).into(), format!(
"Undefined operation \"{}{} * {}\".",
num,
unit,
other.to_css_string(span)?
),
span,
) )
.into())
} }
}, },
Self::BinaryOp(left, op, right) => { Self::BinaryOp(left, op, right) => {
if op.precedence() >= precedence { if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? * other)? Self::BinaryOp(left, op, right)
.eval(span)?
.node
.mul(other, span)?
} else { } else {
Self::BinaryOp( Self::BinaryOp(
left, left,
op, op,
Box::new(Self::BinaryOp(right, Op::Mul, Box::new(other)).eval()?), Box::new(
Self::BinaryOp(right, Op::Mul, Box::new(other))
.eval(span)?
.node,
),
) )
.eval()? .eval(span)?
.node
} }
} }
Self::UnaryOp(..) | Self::Paren(..) => (self.eval()? * other)?, Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.mul(other, span)?,
_ => return Err(format!("Undefined operation \"{} * {}\".", self, other).into()), _ => {
return Err((
format!(
"Undefined operation \"{} * {}\".",
self.to_css_string(span)?,
other.to_css_string(span)?
),
span,
)
.into())
}
}) })
} }
}
impl Div for Value { pub fn div(self, other: Self, span: Span) -> SassResult<Self> {
type Output = SassResult<Self>;
fn div(self, other: Self) -> Self::Output {
let precedence = Op::Div.precedence(); let precedence = Op::Div.precedence();
Ok(match self { Ok(match self {
Self::Null => todo!(), Self::Null => todo!(),
Self::Dimension(num, unit) => match other { Self::Dimension(num, unit) => match other {
Self::Dimension(num2, unit2) => { Self::Dimension(num2, unit2) => {
if !unit.comparable(&unit2) { if !unit.comparable(&unit2) {
return Err(format!("Incompatible units {} and {}.", unit2, unit).into()); return Err(
(format!("Incompatible units {} and {}.", unit2, unit), span).into(),
);
} }
if unit == unit2 { if unit == unit2 {
Value::Dimension(num / num2, Unit::None) Value::Dimension(num / num2, Unit::None)
@ -293,7 +421,7 @@ impl Div for Value {
QuoteKind::None, QuoteKind::None,
), ),
Self::BinaryOp(..) | Self::Paren(..) => { Self::BinaryOp(..) | Self::Paren(..) => {
(Self::Dimension(num, unit) / other.eval()?)? Self::Dimension(num, unit).div(other.eval(span)?.node, span)?
} }
_ => todo!(), _ => todo!(),
}, },
@ -304,23 +432,42 @@ impl Div for Value {
), ),
Self::Null => Value::Ident(format!("{}/", c), QuoteKind::None), Self::Null => Value::Ident(format!("{}/", c), QuoteKind::None),
Self::Dimension(..) | Self::Color(..) => { Self::Dimension(..) | Self::Color(..) => {
return Err(format!("Undefined operation \"{} / {}\".", c, other).into()) return Err((
format!(
"Undefined operation \"{} / {}\".",
c,
other.to_css_string(span)?
),
span,
)
.into())
} }
_ => Value::Ident(format!("{}/{}", c, other), QuoteKind::None), _ => Value::Ident(
format!("{}/{}", c, other.to_css_string(span)?),
QuoteKind::None,
),
}, },
Self::BinaryOp(left, op, right) => { Self::BinaryOp(left, op, right) => {
if op.precedence() >= precedence { if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? / other)? Self::BinaryOp(left, op, right)
.eval(span)?
.node
.div(other, span)?
} else { } else {
Self::BinaryOp( Self::BinaryOp(
left, left,
op, op,
Box::new(Self::BinaryOp(right, Op::Div, Box::new(other)).eval()?), Box::new(
Self::BinaryOp(right, Op::Div, Box::new(other))
.eval(span)?
.node,
),
) )
.eval()? .eval(span)?
.node
} }
} }
Self::Paren(..) => (self.eval()? / other)?, Self::Paren(..) => self.eval(span)?.node.div(other, span)?,
Self::Ident(s1, q1) => match other { Self::Ident(s1, q1) => match other {
Self::Ident(s2, q2) => Value::Ident( Self::Ident(s2, q2) => Value::Ident(
format!( format!(
@ -339,7 +486,13 @@ impl Div for Value {
| Self::False | Self::False
| Self::Dimension(..) | Self::Dimension(..)
| Self::Color(..) => Value::Ident( | Self::Color(..) => Value::Ident(
format!("{}{}{}/{}", q1.normalize(), s1, q1.normalize(), other), format!(
"{}{}{}/{}",
q1.normalize(),
s1,
q1.normalize(),
other.to_css_string(span)?
),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => Value::Ident( Self::Null => Value::Ident(
@ -350,25 +503,36 @@ impl Div for Value {
}, },
_ => match other { _ => match other {
Self::Ident(s, q) => Value::Ident( Self::Ident(s, q) => Value::Ident(
format!("{}/{}{}{}", self, q.normalize(), s, q.normalize()), format!(
"{}/{}{}{}",
self.to_css_string(span)?,
q.normalize(),
s,
q.normalize()
),
QuoteKind::None,
),
Self::Null => {
Value::Ident(format!("{}/", self.to_css_string(span)?), QuoteKind::None)
}
_ => Value::Ident(
format!(
"{}/{}",
self.to_css_string(span)?,
other.to_css_string(span)?
),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => Value::Ident(format!("{}/", self), QuoteKind::None),
_ => Value::Ident(format!("{}/{}", self, other), QuoteKind::None),
}, },
}) })
} }
}
impl Rem for Value { pub fn rem(self, other: Self, span: Span) -> SassResult<Self> {
type Output = SassResult<Self>;
fn rem(self, other: Self) -> Self::Output {
Ok(match self { Ok(match self {
Value::Dimension(n, u) => match other { Value::Dimension(n, u) => match other {
Value::Dimension(n2, u2) => { Value::Dimension(n2, u2) => {
if !u.comparable(&u2) { if !u.comparable(&u2) {
return Err(format!("Incompatible units {} and {}.", u2, u).into()); return Err((format!("Incompatible units {} and {}.", u2, u), span).into());
} }
if u == u2 { if u == u2 {
Value::Dimension(n % n2, u) Value::Dimension(n % n2, u)
@ -381,26 +545,35 @@ impl Rem for Value {
} }
} }
_ => { _ => {
return Err(format!( return Err((
"Undefined operation \"{} % {}\".", format!(
Value::Dimension(n, u), "Undefined operation \"{} % {}\".",
other Value::Dimension(n, u).to_css_string(span)?,
other.to_css_string(span)?
),
span,
) )
.into()) .into())
} }
}, },
_ => return Err(format!("Undefined operation \"{} % {}\".", self, other).into()), _ => {
return Err((
format!(
"Undefined operation \"{} % {}\".",
self.to_css_string(span)?,
other.to_css_string(span)?
),
span,
)
.into())
}
}) })
} }
}
impl Neg for Value { pub fn neg(self, span: Span) -> SassResult<Self> {
type Output = SassResult<Self>; Ok(match self.eval(span)?.node {
fn neg(self) -> Self::Output {
Ok(match self.eval()? {
Value::Dimension(n, u) => Value::Dimension(-n, u), Value::Dimension(n, u) => Value::Dimension(-n, u),
v => Value::Ident(format!("-{}", v), QuoteKind::None), v => Value::Ident(format!("-{}", v.to_css_string(span)?), QuoteKind::None),
}) })
} }
} }

View File

@ -6,6 +6,8 @@ use num_bigint::BigInt;
use num_rational::BigRational; use num_rational::BigRational;
use num_traits::pow; use num_traits::pow;
use codemap::{Span, Spanned};
use super::css_function::{eat_calc_args, eat_progid}; use super::css_function::{eat_calc_args, eat_progid};
use crate::args::eat_call_args; use crate::args::eat_call_args;
@ -31,11 +33,12 @@ fn parse_hex<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { mut span: Span,
) -> SassResult<Spanned<Value>> {
let mut s = String::with_capacity(8); let mut s = String::with_capacity(8);
if toks if toks
.peek() .peek()
.ok_or("Expected identifier.")? .ok_or(("Expected identifier.", span))?
.kind .kind
.is_ascii_digit() .is_ascii_digit()
{ {
@ -43,93 +46,75 @@ fn parse_hex<I: Iterator<Item = Token>>(
if !c.kind.is_ascii_hexdigit() || s.len() == 8 { if !c.kind.is_ascii_hexdigit() || s.len() == 8 {
break; break;
} }
s.push(toks.next().unwrap().kind); let tok = toks.next().unwrap();
span = span.merge(tok.pos());
s.push(tok.kind);
} }
} else { } else {
let i = eat_ident(toks, scope, super_selector)?; let i = eat_ident(toks, scope, super_selector)?;
if i.chars().all(|c| c.is_ascii_hexdigit()) { if i.node.chars().all(|c| c.is_ascii_hexdigit()) {
s = i; s = i.node;
span = span.merge(i.span);
} else { } else {
return Ok(Value::Ident(format!("#{}", i), QuoteKind::None)); return Ok(Spanned {
node: Value::Ident(format!("#{}", i.node), QuoteKind::None),
span: i.span,
});
} }
} }
match s.len() { match s.len() {
3 => { 3 => {
let v = match u16::from_str_radix(&s, 16) { let v = match u16::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None)), Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)),
}; };
let red = (((v & 0xf00) >> 8) * 0x11) as u8; let red = (((v & 0xf00) >> 8) * 0x11) as u8;
let green = (((v & 0x0f0) >> 4) * 0x11) as u8; let green = (((v & 0x0f0) >> 4) * 0x11) as u8;
let blue = ((v & 0x00f) * 0x11) as u8; let blue = ((v & 0x00f) * 0x11) as u8;
Ok(Value::Color(Color::new( Ok(Value::Color(Color::new(red, green, blue, 1, format!("#{}", s))).span(span))
red,
green,
blue,
1,
format!("#{}", s),
)))
} }
4 => { 4 => {
let v = match u16::from_str_radix(&s, 16) { let v = match u16::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None)), Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)),
}; };
let red = (((v & 0xf000) >> 12) * 0x11) as u8; let red = (((v & 0xf000) >> 12) * 0x11) as u8;
let green = (((v & 0x0f00) >> 8) * 0x11) as u8; let green = (((v & 0x0f00) >> 8) * 0x11) as u8;
let blue = (((v & 0x00f0) >> 4) * 0x11) as u8; let blue = (((v & 0x00f0) >> 4) * 0x11) as u8;
let alpha = ((v & 0x000f) * 0x11) as u8; let alpha = ((v & 0x000f) * 0x11) as u8;
Ok(Value::Color(Color::new( Ok(Value::Color(Color::new(red, green, blue, alpha, format!("#{}", s))).span(span))
red,
green,
blue,
alpha,
format!("#{}", s),
)))
} }
6 => { 6 => {
let v = match u32::from_str_radix(&s, 16) { let v = match u32::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None)), Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)),
}; };
let red = ((v & 0x00ff_0000) >> 16) as u8; let red = ((v & 0x00ff_0000) >> 16) as u8;
let green = ((v & 0x0000_ff00) >> 8) as u8; let green = ((v & 0x0000_ff00) >> 8) as u8;
let blue = (v & 0x0000_00ff) as u8; let blue = (v & 0x0000_00ff) as u8;
Ok(Value::Color(Color::new( Ok(Value::Color(Color::new(red, green, blue, 1, format!("#{}", s))).span(span))
red,
green,
blue,
1,
format!("#{}", s),
)))
} }
8 => { 8 => {
let v = match u32::from_str_radix(&s, 16) { let v = match u32::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None)), Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)),
}; };
let red = ((v & 0xff00_0000) >> 24) as u8; let red = ((v & 0xff00_0000) >> 24) as u8;
let green = ((v & 0x00ff_0000) >> 16) as u8; let green = ((v & 0x00ff_0000) >> 16) as u8;
let blue = ((v & 0x0000_ff00) >> 8) as u8; let blue = ((v & 0x0000_ff00) >> 8) as u8;
let alpha = (v & 0x0000_00ff) as u8; let alpha = (v & 0x0000_00ff) as u8;
Ok(Value::Color(Color::new( Ok(Value::Color(Color::new(red, green, blue, alpha, format!("#{}", s))).span(span))
red,
green,
blue,
alpha,
format!("#{}", s),
)))
} }
_ => Err("Expected hex digit.".into()), _ => Err(("Expected hex digit.", span).into()),
} }
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
enum IntermediateValue { enum IntermediateValue {
Value(Value), Value(Spanned<Value>),
Op(Op), Op(Spanned<Op>),
Bracketed(Vec<Token>), Bracketed(Spanned<Vec<Token>>),
Paren(Vec<Token>), Paren(Spanned<Vec<Token>>),
Comma, Comma,
Whitespace, Whitespace,
} }
@ -144,52 +129,61 @@ impl IsWhitespace for IntermediateValue {
} }
fn parse_paren( fn parse_paren(
t: Vec<Token>, t: Spanned<Vec<Token>>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
space_separated: &mut Vec<Value>, space_separated: &mut Vec<Spanned<Value>>,
) -> SassResult<()> { ) -> SassResult<()> {
if t.is_empty() { if t.is_empty() {
space_separated.push(Value::List( space_separated
Vec::new(), .push(Value::List(Vec::new(), ListSeparator::Space, Brackets::None).span(t.span));
ListSeparator::Space,
Brackets::None,
));
return Ok(()); return Ok(());
} }
let paren_toks = &mut t.into_iter().peekable(); let paren_toks = &mut t.node.into_iter().peekable();
let mut map = SassMap::new(); let mut map = SassMap::new();
let key = Value::from_vec(read_until_char(paren_toks, ':'), scope, super_selector)?; let key = Value::from_vec(read_until_char(paren_toks, ':'), scope, super_selector)?;
if paren_toks.peek().is_none() { if paren_toks.peek().is_none() {
space_separated.push(Value::Paren(Box::new(key))); space_separated.push(Spanned {
node: Value::Paren(Box::new(key.node)),
span: key.span,
});
return Ok(()); return Ok(());
} }
let val = Value::from_vec(read_until_char(paren_toks, ','), scope, super_selector)?; let val = Value::from_vec(read_until_char(paren_toks, ','), scope, super_selector)?;
map.insert(key, val); map.insert(key.node, val.node);
if paren_toks.peek().is_none() { if paren_toks.peek().is_none() {
space_separated.push(Value::Map(map)); space_separated.push(Spanned {
node: Value::Map(map),
span: key.span.merge(val.span),
});
return Ok(()); return Ok(());
} }
let mut span = key.span;
loop { loop {
let key = Value::from_vec(read_until_char(paren_toks, ':'), scope, super_selector)?; let key = Value::from_vec(read_until_char(paren_toks, ':'), scope, super_selector)?;
devour_whitespace(paren_toks); devour_whitespace(paren_toks);
let val = Value::from_vec(read_until_char(paren_toks, ','), scope, super_selector)?; let val = Value::from_vec(read_until_char(paren_toks, ','), scope, super_selector)?;
span = span.merge(val.span);
devour_whitespace(paren_toks); devour_whitespace(paren_toks);
if map.insert(key, val) { if map.insert(key.node, val.node) {
return Err("Duplicate key.".into()); return Err(("Duplicate key.", key.span).into());
} }
if paren_toks.peek().is_none() { if paren_toks.peek().is_none() {
break; break;
} }
} }
space_separated.push(Value::Map(map)); space_separated.push(Spanned {
node: Value::Map(map),
span,
});
Ok(()) Ok(())
} }
@ -197,58 +191,82 @@ fn eat_op<I: Iterator<Item = IntermediateValue>>(
iter: &mut Peekable<I>, iter: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
op: Op, op: Spanned<Op>,
space_separated: &mut Vec<Value>, space_separated: &mut Vec<Spanned<Value>>,
) -> SassResult<()> { ) -> SassResult<()> {
match op { match op.node {
Op::Not => { Op::Not => {
devour_whitespace(iter); devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Value::UnaryOp(op, Box::new(right))); space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
} }
Op::Plus => { Op::Plus => {
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
devour_whitespace(iter); devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right))); space_separated.push(Spanned {
node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
span: left.span.merge(right.span),
});
} else { } else {
devour_whitespace(iter); devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Value::UnaryOp(op, Box::new(right))); space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
} }
} }
Op::Minus => { Op::Minus => {
if devour_whitespace(iter) { if devour_whitespace(iter) {
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, op.span)?;
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right))); space_separated.push(Spanned {
node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
span: left.span.merge(right.span),
});
} else { } else {
space_separated.push(Value::UnaryOp(op, Box::new(right))); space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
} }
} else { } else {
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Value::UnaryOp(op, Box::new(right))); space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
} }
} }
Op::And | Op::Or => { Op::And | Op::Or => {
devour_whitespace(iter); devour_whitespace(iter);
if iter.peek().is_none() { if iter.peek().is_none() {
space_separated.push(Value::Ident(op.to_string(), QuoteKind::None)); space_separated.push(Value::Ident(op.to_string(), QuoteKind::None).span(op.span));
} else if let Some(left) = space_separated.pop() { } else if let Some(left) = space_separated.pop() {
devour_whitespace(iter); devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, left.span)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right))); space_separated.push(
Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
.span(left.span.merge(right.span)),
);
} else { } else {
return Err("Expected expression.".into()); return Err(("Expected expression.", op.span).into());
} }
} }
_ => { _ => {
if let Some(left) = space_separated.pop() { if let Some(left) = space_separated.pop() {
devour_whitespace(iter); devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?; let right = single_value(iter, scope, super_selector, left.span)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right))); space_separated.push(
Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
.span(left.span.merge(right.span)),
);
} else { } else {
return Err("Expected expression.".into()); return Err(("Expected expression.", op.span).into());
} }
} }
} }
@ -259,28 +277,45 @@ fn single_value<I: Iterator<Item = IntermediateValue>>(
iter: &mut Peekable<I>, iter: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { span: Span,
Ok(match iter.next().ok_or("Expected expression.")? { ) -> SassResult<Spanned<Value>> {
Ok(match iter.next().ok_or(("Expected expression.", span))? {
IntermediateValue::Value(v) => v, IntermediateValue::Value(v) => v,
IntermediateValue::Op(op) => match op { IntermediateValue::Op(op) => match op.node {
Op::Minus => { Op::Minus => {
devour_whitespace(iter); devour_whitespace(iter);
(-single_value(iter, scope, super_selector)?)? let val = single_value(iter, scope, super_selector, span)?;
Spanned {
node: val.node.neg(val.span)?,
span: op.span.merge(val.span),
}
} }
Op::Not => { Op::Not => {
devour_whitespace(iter); devour_whitespace(iter);
Value::UnaryOp(op, Box::new(single_value(iter, scope, super_selector)?)) let val = single_value(iter, scope, super_selector, span)?;
Spanned {
node: Value::UnaryOp(op.node, Box::new(val.node)),
span: op.span.merge(val.span),
}
} }
_ => todo!(), _ => todo!(),
}, },
IntermediateValue::Whitespace => unreachable!(), IntermediateValue::Whitespace => unreachable!(),
IntermediateValue::Comma => return Err("Expected expression.".into()), IntermediateValue::Comma => return Err(("Expected expression.", span).into()),
IntermediateValue::Bracketed(t) => match Value::from_vec(t, scope, super_selector)? { IntermediateValue::Bracketed(t) => {
Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed), let v = Value::from_vec(t.node, scope, super_selector)?;
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed), 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) => { IntermediateValue::Paren(t) => {
Value::Paren(Box::new(Value::from_vec(t, scope, super_selector)?)) let val = Value::from_vec(t.node, scope, super_selector)?;
Spanned {
node: Value::Paren(Box::new(val.node)),
span: val.span,
}
} }
}) })
} }
@ -290,8 +325,12 @@ impl Value {
toks: &mut Peekable<I>, toks: &mut Peekable<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Self> { ) -> SassResult<Spanned<Self>> {
let mut intermediate_values = Vec::new(); let mut intermediate_values = Vec::new();
let span = match toks.peek() {
Some(Token { pos, .. }) => *pos,
None => todo!("Expected expression."),
};
while toks.peek().is_some() { while toks.peek().is_some() {
intermediate_values.push(Self::parse_intermediate_value(toks, scope, super_selector)?); intermediate_values.push(Self::parse_intermediate_value(toks, scope, super_selector)?);
} }
@ -309,20 +348,40 @@ impl Value {
if space_separated.len() == 1 { if space_separated.len() == 1 {
comma_separated.push(space_separated.pop().unwrap()); comma_separated.push(space_separated.pop().unwrap());
} else { } else {
comma_separated.push(Value::List( let mut span = space_separated[0].span;
mem::take(&mut space_separated), comma_separated.push(
ListSeparator::Space, Value::List(
Brackets::None, 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) => { IntermediateValue::Bracketed(t) => {
space_separated.push(match Value::from_vec(t, scope, super_selector)? { if t.node.is_empty() {
Value::List(v, sep, Brackets::None) => { space_separated.push(
Value::List(v, sep, Brackets::Bracketed) Value::List(Vec::new(), ListSeparator::Space, Brackets::Bracketed)
} .span(t.span),
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed), );
}) continue;
}
space_separated.push(
match Value::from_vec(t.node, scope, super_selector)?.node {
Value::List(v, sep, Brackets::None) => {
Value::List(v, sep, Brackets::Bracketed).span(t.span)
}
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed)
.span(t.span),
},
)
} }
IntermediateValue::Paren(t) => { IntermediateValue::Paren(t) => {
parse_paren(t, scope, super_selector, &mut space_separated)?; parse_paren(t, scope, super_selector, &mut space_separated)?;
@ -334,17 +393,30 @@ impl Value {
if space_separated.len() == 1 { if space_separated.len() == 1 {
comma_separated.push(space_separated.pop().unwrap()); comma_separated.push(space_separated.pop().unwrap());
} else if !space_separated.is_empty() { } else if !space_separated.is_empty() {
comma_separated.push(Value::List( comma_separated.push(
space_separated, Value::List(
ListSeparator::Space, space_separated.into_iter().map(|a| a.node).collect(),
Brackets::None, ListSeparator::Space,
)); Brackets::None,
)
.span(span),
);
} }
Value::List(comma_separated, ListSeparator::Comma, Brackets::None) Value::List(
comma_separated.into_iter().map(|a| a.node).collect(),
ListSeparator::Comma,
Brackets::None,
)
.span(span)
} else if space_separated.len() == 1 { } else if space_separated.len() == 1 {
space_separated.pop().unwrap() space_separated.pop().unwrap()
} else { } else {
Value::List(space_separated, ListSeparator::Space, Brackets::None) Value::List(
space_separated.into_iter().map(|a| a.node).collect(),
ListSeparator::Space,
Brackets::None,
)
.span(span)
}) })
} }
@ -352,7 +424,7 @@ impl Value {
toks: Vec<Token>, toks: Vec<Token>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<Value> { ) -> SassResult<Spanned<Value>> {
Self::from_tokens(&mut toks.into_iter().peekable(), scope, super_selector) Self::from_tokens(&mut toks.into_iter().peekable(), scope, super_selector)
} }
@ -361,25 +433,34 @@ impl Value {
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<IntermediateValue> { ) -> SassResult<IntermediateValue> {
let mut s = eat_ident(toks, scope, super_selector)?; let Spanned { node: mut s, span } = eat_ident(toks, scope, super_selector)?;
if s == "progid" && toks.peek().is_some() && toks.peek().unwrap().kind == ':' { if s == "progid" && toks.peek().is_some() && toks.peek().unwrap().kind == ':' {
toks.next(); toks.next();
s.push(':'); s.push(':');
s.push_str(&eat_progid(toks, scope, super_selector)?); s.push_str(&eat_progid(toks, scope, super_selector)?);
return Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))); return Ok(IntermediateValue::Value(Spanned {
node: Value::Ident(s, QuoteKind::None),
span,
}));
} }
match toks.peek() { match toks.peek() {
Some(Token { kind: '(', .. }) => { Some(Token { kind: '(', .. }) => {
toks.next(); toks.next();
let func = match scope.get_fn(&s) { let func = match scope.get_fn(Spanned {
node: s.clone(),
span,
}) {
Ok(f) => f, Ok(f) => f,
Err(_) => match GLOBAL_FUNCTIONS.get(&s) { Err(_) => match GLOBAL_FUNCTIONS.get(&s) {
Some(f) => { Some(f) => {
return Ok(IntermediateValue::Value(f.0( return Ok(IntermediateValue::Value(Spanned {
eat_call_args(toks, scope, super_selector)?, node: f.0(
scope, eat_call_args(toks, scope, super_selector)?,
super_selector, scope,
)?)) super_selector,
)?,
span,
}))
} }
None => { None => {
match s.as_str() { match s.as_str() {
@ -394,7 +475,10 @@ impl Value {
.to_css_string(scope, super_selector)?, .to_css_string(scope, super_selector)?,
), ),
} }
return Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))); return Ok(IntermediateValue::Value(Spanned {
node: Value::Ident(s, QuoteKind::None),
span,
}));
} }
}, },
}; };
@ -405,22 +489,35 @@ impl Value {
scope, scope,
super_selector, super_selector,
)? )?
.call(super_selector, func.body())?, .call(super_selector, func.body())?
.span(span),
)) ))
} }
_ => { _ => {
if let Ok(c) = crate::color::ColorName::try_from(s.as_ref()) { if let Ok(c) = crate::color::ColorName::try_from(s.as_ref()) {
Ok(IntermediateValue::Value(Value::Color(c.into_color(s)))) Ok(IntermediateValue::Value(Spanned {
node: Value::Color(c.into_color(s)),
span,
}))
} else { } else {
match s.to_ascii_lowercase().as_str() { Ok(match s.to_ascii_lowercase().as_str() {
"true" => Ok(IntermediateValue::Value(Value::True)), "true" => IntermediateValue::Value(Value::True.span(span)),
"false" => Ok(IntermediateValue::Value(Value::False)), "false" => IntermediateValue::Value(Value::False.span(span)),
"null" => Ok(IntermediateValue::Value(Value::Null)), "null" => IntermediateValue::Value(Value::Null.span(span)),
"not" => Ok(IntermediateValue::Op(Op::Not)), "not" => IntermediateValue::Op(Spanned {
"and" => Ok(IntermediateValue::Op(Op::And)), node: Op::Not,
"or" => Ok(IntermediateValue::Op(Op::Or)), span,
_ => Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))), }),
} "and" => IntermediateValue::Op(Spanned {
node: Op::And,
span,
}),
"or" => IntermediateValue::Op(Spanned { node: Op::Or, span }),
_ => IntermediateValue::Value(Spanned {
node: Value::Ident(s, QuoteKind::None),
span,
}),
})
} }
} }
} }
@ -434,8 +531,8 @@ impl Value {
if devour_whitespace(toks) { if devour_whitespace(toks) {
return Ok(IntermediateValue::Whitespace); return Ok(IntermediateValue::Whitespace);
} }
let kind = match toks.peek() { let (kind, span) = match toks.peek() {
Some(v) => v.kind, Some(v) => (v.kind, v.pos()),
None => panic!("unexpected eof"), None => panic!("unexpected eof"),
}; };
match kind { match kind {
@ -444,14 +541,19 @@ impl Value {
Ok(IntermediateValue::Comma) Ok(IntermediateValue::Comma)
} }
'0'..='9' | '.' => { '0'..='9' | '.' => {
let val = eat_number(toks)?; let Spanned {
node: val,
mut span,
} = eat_number(toks)?;
let unit = if let Some(tok) = toks.peek() { let unit = if let Some(tok) = toks.peek() {
match tok.kind { match tok.kind {
'a'..='z' | 'A'..='Z' | '_' => { 'a'..='z' | 'A'..='Z' | '_' => {
Unit::from(&eat_ident(toks, scope, super_selector)?) let u = eat_ident(toks, scope, super_selector)?;
span = span.merge(u.span);
Unit::from(&u.node)
} }
'%' => { '%' => {
toks.next(); span = span.merge(toks.next().unwrap().pos());
Unit::Percent Unit::Percent
} }
_ => Unit::None, _ => Unit::None,
@ -479,35 +581,42 @@ impl Value {
} }
BigRational::new(num.parse().unwrap(), pow(BigInt::from(10), num_dec)) BigRational::new(num.parse().unwrap(), pow(BigInt::from(10), num_dec))
}; };
Ok(IntermediateValue::Value(Value::Dimension( Ok(IntermediateValue::Value(
Number::new(n), Value::Dimension(Number::new(n), unit).span(span),
unit, ))
)))
} }
'(' => { '(' => {
toks.next(); let mut span = toks.next().unwrap().pos();
let mut inner = read_until_closing_paren(toks); let mut inner = read_until_closing_paren(toks);
// todo: the above shouldn't eat the closing paren // todo: the above shouldn't eat the closing paren
if !inner.is_empty() && inner.pop().unwrap().kind != ')' { if !inner.is_empty() {
return Err("expected \")\".".into()); let last_tok = inner.pop().unwrap();
if last_tok.kind != ')' {
return Err(("expected \")\".", span).into());
}
span = span.merge(last_tok.pos());
} }
Ok(IntermediateValue::Paren(inner)) Ok(IntermediateValue::Paren(Spanned { node: inner, span }))
} }
'&' => { '&' => {
toks.next(); let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Value(Value::Ident( Ok(IntermediateValue::Value(Spanned {
super_selector.to_string(), node: Value::Ident(super_selector.to_string(), QuoteKind::None),
QuoteKind::None, span,
))) }))
} }
'#' => { '#' => {
if let Ok(s) = eat_ident(toks, scope, super_selector) { if let Ok(s) = eat_ident(toks, scope, super_selector) {
Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))) Ok(IntermediateValue::Value(Spanned {
node: Value::Ident(s.node, QuoteKind::None),
span: s.span,
}))
} else { } else {
Ok(IntermediateValue::Value(parse_hex( Ok(IntermediateValue::Value(parse_hex(
toks, toks,
scope, scope,
super_selector, super_selector,
span,
)?)) )?))
} }
} }
@ -519,86 +628,119 @@ impl Value {
Self::ident(toks, scope, super_selector) Self::ident(toks, scope, super_selector)
} }
q @ '"' | q @ '\'' => { q @ '"' | q @ '\'' => {
toks.next(); let span_start = toks.next().unwrap().pos();
Ok(IntermediateValue::Value(parse_quoted_string( let Spanned { node, span } = parse_quoted_string(toks, scope, q, super_selector)?;
toks, Ok(IntermediateValue::Value(Spanned {
scope, node,
q, span: span_start.merge(span),
super_selector, }))
)?))
} }
'[' => { '[' => {
toks.next(); let mut span = toks.next().unwrap().pos();
let mut inner = read_until_closing_square_brace(toks); let mut inner = read_until_closing_square_brace(toks);
inner.pop(); if !inner.is_empty() {
Ok(IntermediateValue::Bracketed(inner)) let last_tok = inner.pop().unwrap();
if last_tok.kind != ']' {
return Err(("expected \"]\".", span).into());
}
span = span.merge(last_tok.pos());
}
Ok(IntermediateValue::Bracketed(Spanned { node: inner, span }))
} }
'$' => { '$' => {
toks.next(); toks.next();
Ok(IntermediateValue::Value( let val = eat_ident_no_interpolation(toks)?;
scope.get_var(&eat_ident_no_interpolation(toks)?)?, Ok(IntermediateValue::Value(Spanned {
)) node: scope.get_var(val.clone())?.node,
span: val.span,
}))
} }
'@' => Err("expected \";\".".into()), '@' => Err(("expected \";\".", span).into()),
'+' => { '+' => {
toks.next(); let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Op::Plus)) Ok(IntermediateValue::Op(Spanned {
node: Op::Plus,
span,
}))
} }
'-' => { '-' => {
toks.next(); let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Op::Minus)) Ok(IntermediateValue::Op(Spanned {
node: Op::Minus,
span,
}))
} }
'*' => { '*' => {
toks.next(); let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Op::Mul)) Ok(IntermediateValue::Op(Spanned {
node: Op::Mul,
span,
}))
} }
'%' => { '%' => {
toks.next(); let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Op::Rem)) Ok(IntermediateValue::Op(Spanned {
node: Op::Rem,
span,
}))
} }
q @ '>' | q @ '<' => { q @ '>' | q @ '<' => {
toks.next(); let mut span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(if toks.peek().unwrap().kind == '=' { Ok(IntermediateValue::Op(Spanned {
toks.next(); node: if toks.peek().unwrap().kind == '=' {
match q { span = span.merge(toks.next().unwrap().pos());
'>' => Op::GreaterThanEqual, match q {
'<' => Op::LessThanEqual, '>' => Op::GreaterThanEqual,
_ => unreachable!(), '<' => Op::LessThanEqual,
} _ => unreachable!(),
} else { }
match q { } else {
'>' => Op::GreaterThan, match q {
'<' => Op::LessThan, '>' => Op::GreaterThan,
_ => unreachable!(), '<' => Op::LessThan,
} _ => unreachable!(),
}
},
span,
})) }))
} }
'=' => { '=' => {
toks.next(); let mut span = toks.next().unwrap().pos();
if toks.next().unwrap().kind == '=' { if let Token { kind: '=', pos } = toks.next().unwrap() {
Ok(IntermediateValue::Op(Op::Equal)) span = span.merge(pos);
Ok(IntermediateValue::Op(Spanned {
node: Op::Equal,
span,
}))
} else { } else {
Err("expected \"=\".".into()) Err(("expected \"=\".", span).into())
} }
} }
'!' => { '!' => {
toks.next(); let mut span = toks.next().unwrap().pos();
if toks.peek().is_some() && toks.peek().unwrap().kind == '=' { if toks.peek().is_some() && toks.peek().unwrap().kind == '=' {
toks.next(); span = span.merge(toks.next().unwrap().pos());
return Ok(IntermediateValue::Op(Op::NotEqual)); return Ok(IntermediateValue::Op(Spanned {
node: Op::NotEqual,
span,
}));
} }
devour_whitespace(toks); devour_whitespace(toks);
let v = eat_ident(toks, scope, super_selector)?; let v = eat_ident(toks, scope, super_selector)?;
if v.to_ascii_lowercase().as_str() == "important" { span = span.merge(v.span);
Ok(IntermediateValue::Value(Value::Important)) if v.node.to_ascii_lowercase().as_str() == "important" {
Ok(IntermediateValue::Value(Spanned {
node: Value::Important,
span,
}))
} else { } else {
Err("Expected \"important\".".into()) Err(("Expected \"important\".", span).into())
} }
} }
'/' => { '/' => {
toks.next(); let span = toks.next().unwrap().pos();
if toks.peek().is_none() { if toks.peek().is_none() {
return Err("Expected expression.".into()); return Err(("Expected expression.", span).into());
} }
if '*' == toks.peek().unwrap().kind { if '*' == toks.peek().unwrap().kind {
toks.next(); toks.next();
@ -609,11 +751,14 @@ impl Value {
devour_whitespace(toks); devour_whitespace(toks);
Ok(IntermediateValue::Whitespace) Ok(IntermediateValue::Whitespace)
} else { } else {
Ok(IntermediateValue::Op(Op::Div)) Ok(IntermediateValue::Op(Spanned {
node: Op::Div,
span,
}))
} }
} }
':' | '?' | ')' => Err("expected \";\".".into()), ':' | '?' | ')' => Err(("expected \";\".", span).into()),
v if v.is_control() => Err("Expected expression.".into()), v if v.is_control() => Err(("Expected expression.", span).into()),
v => { v => {
dbg!(v); dbg!(v);
panic!("Unexpected token in value parsing") panic!("Unexpected token in value parsing")