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::iter::Peekable;
use crate::common::Pos;
use codemap::{Span, Spanned};
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
@ -29,7 +30,7 @@ impl FuncArgs {
}
#[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)]
enum CallArg {
@ -38,9 +39,9 @@ enum CallArg {
}
impl CallArg {
pub fn position(&self) -> SassResult<usize> {
pub fn position(&self) -> Result<usize, String> {
match self {
Self::Named(..) => Err("found named".into()),
Self::Named(ref name) => Err(name.clone()),
Self::Positional(p) => Ok(*p),
}
}
@ -54,26 +55,45 @@ impl CallArg {
}
impl CallArgs {
pub fn new() -> Self {
CallArgs(HashMap::new())
pub fn new(span: Span) -> Self {
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);
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) {
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(
&args
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.map(|a| {
span = span.merge(a.span);
Ok(a.node.to_css_string(a.span)?)
})
.collect::<SassResult<Vec<String>>>()?
.join(", "),
);
string.push(')');
Ok(string)
Ok(Spanned { node: string, span })
}
/// Get argument by name
@ -84,7 +104,7 @@ impl CallArgs {
val: String,
scope: &Scope,
super_selector: &Selector,
) -> Option<SassResult<Value>> {
) -> Option<SassResult<Spanned<Value>>> {
match self.0.remove(&CallArg::Named(val)) {
Some(v) => Some(Value::from_vec(v, scope, super_selector)),
None => None,
@ -99,20 +119,28 @@ impl CallArgs {
val: usize,
scope: &Scope,
super_selector: &Selector,
) -> Option<SassResult<Value>> {
) -> Option<SassResult<Spanned<Value>>> {
match self.0.remove(&CallArg::Positional(val)) {
Some(v) => Some(Value::from_vec(v, scope, super_selector)),
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 args = self
let mut args = match self
.0
.into_iter()
.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));
for arg in args {
vals.push(Value::from_vec(arg.1, scope, super_selector)?);
@ -126,15 +154,20 @@ impl CallArgs {
.into_iter()
.map(|(k, v)| (k.decrement(), v))
.collect(),
self.1,
)
}
pub fn span(&self) -> Span {
self.1
}
pub fn len(&self) -> usize {
self.0.len()
}
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 is_variadic = false;
devour_whitespace(toks);
let kind = match toks.next() {
Some(Token { kind, .. }) => kind,
let (kind, span) = match toks.next() {
Some(Token { kind, pos }) => (kind, pos),
_ => todo!("unexpected eof"),
};
match kind {
@ -189,15 +222,18 @@ pub(crate) fn eat_func_args<I: Iterator<Item = Token>>(
}
}
'.' => {
if toks.next().ok_or("expected \".\".")?.kind != '.' {
return Err("expected \".\".".into());
let next = toks.next().ok_or(("expected \".\".", span))?;
if next.kind != '.' {
return Err(("expected \".\".", next.pos()).into());
}
if toks.next().ok_or("expected \".\".")?.kind != '.' {
return Err("expected \".\".".into());
let next = toks.next().ok_or(("expected \".\".", next.pos()))?;
if next.kind != '.' {
return Err(("expected \".\".", next.pos()).into());
}
devour_whitespace(toks);
if toks.next().ok_or("expected \")\".")?.kind != ')' {
return Err("expected \")\".".into());
let next = toks.next().ok_or(("expected \")\".", next.pos()))?;
if next.kind != ')' {
return Err(("expected \")\".", next.pos()).into());
}
is_variadic = true;
@ -247,24 +283,31 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
devour_whitespace_or_comment(toks)?;
let mut name = String::new();
let mut val: Vec<Token> = Vec::new();
let span = toks.peek().unwrap().pos();
loop {
match toks.peek().unwrap().kind {
'$' => {
toks.next();
let Token { pos, .. } = toks.next().unwrap();
let v = eat_ident(toks, scope, super_selector)?;
devour_whitespace_or_comment(toks)?;
if toks.peek().unwrap().kind == ':' {
toks.next();
name = v;
name = v.node;
} else {
val.push(Token::new(Pos::new(), '$'));
val.extend(v.chars().map(|x| Token::new(Pos::new(), x)));
val.push(Token::new(pos, '$'));
let mut current_pos = 0;
val.extend(v.chars().map(|x| {
let len = x.len_utf8() as u64;
let tok = Token::new(v.span.subspan(current_pos, current_pos + len), x);
current_pos += len;
tok
}));
name.clear();
}
}
')' => {
toks.next();
return Ok(CallArgs(args));
return Ok(CallArgs(args, span));
}
_ => name.clear(),
}
@ -281,7 +324,7 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
},
val,
);
return Ok(CallArgs(args));
return Ok(CallArgs(args, span));
}
',' => break,
'[' => {
@ -312,7 +355,7 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
devour_whitespace(toks);
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 codemap::{Span, Spanned};
use num_traits::cast::ToPrimitive;
use super::parse::eat_stmts;
@ -19,18 +21,19 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &mut Scope,
super_selector: &Selector,
span: Span,
) -> SassResult<AtRule> {
let mut stmts = Vec::new();
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)?,
_ => return Err("expected \"$\".".into()),
_ => return Err(("expected \"$\".", span).into()),
};
devour_whitespace(toks);
if toks.peek().is_none()
|| 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);
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),
}
}
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() {
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);
let to_toks = read_until_open_curly_brace(toks);
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() {
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);
toks.next();
@ -130,7 +147,13 @@ pub(crate) fn parse_for<I: Iterator<Item = Token>>(
};
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(
&mut body.clone().into_iter().peekable(),
scope,

View File

@ -2,9 +2,10 @@ use std::iter::Peekable;
use super::eat_stmts;
use codemap::{Span, Spanned};
use crate::args::{eat_func_args, CallArgs, FuncArgs};
use crate::atrule::AtRule;
use crate::common::Pos;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
@ -16,8 +17,8 @@ use crate::{Stmt, Token};
pub(crate) struct Function {
scope: Scope,
args: FuncArgs,
body: Vec<Stmt>,
pos: Pos,
body: Vec<Spanned<Stmt>>,
pos: Span,
}
impl PartialEq for Function {
@ -29,7 +30,7 @@ impl PartialEq for Function {
impl Eq for 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 {
scope,
args,
@ -43,12 +44,12 @@ impl Function {
scope: Scope,
super_selector: &Selector,
) -> SassResult<(String, Function)> {
let pos = toks.peek().unwrap().pos;
let name = eat_ident(toks, &scope, super_selector)?;
let Spanned { node: name, span } = eat_ident(toks, &scope, super_selector)?;
devour_whitespace(toks);
let args = match toks.next() {
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);
@ -56,7 +57,7 @@ impl Function {
let body = eat_stmts(toks, &mut scope.clone(), super_selector)?;
devour_whitespace(toks);
Ok((name, Function::new(scope, args, body, pos)))
Ok((name, Function::new(scope, args, body, span)))
}
pub fn args(
@ -67,9 +68,13 @@ impl Function {
) -> SassResult<Function> {
for (idx, arg) in self.args.0.iter().enumerate() {
if arg.is_variadic {
let span = args.span();
self.scope.insert_var(
&arg.name,
Value::ArgList(args.get_variadic(scope, super_selector)?),
Spanned {
node: Value::ArgList(args.get_variadic(scope, super_selector)?),
span,
},
)?;
break;
}
@ -83,7 +88,11 @@ impl Function {
scope,
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)
}
pub fn body(&self) -> Vec<Stmt> {
pub fn body(&self) -> Vec<Spanned<Stmt>> {
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 {
match stmt {
match stmt.node {
Stmt::AtRule(AtRule::Return(toks)) => {
return Value::from_tokens(
return Ok(Value::from_tokens(
&mut toks.into_iter().peekable(),
&self.scope,
super_selector,
)
)?
.node)
}
Stmt::AtRule(AtRule::For(..)) => todo!("@for in function"),
Stmt::AtRule(AtRule::If(i)) => {
@ -115,9 +125,9 @@ impl Function {
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 codemap::Spanned;
use super::{eat_stmts, AtRule};
use crate::error::SassResult;
@ -72,7 +74,7 @@ impl If {
break;
}
_ => {
return Err("expected \"{\".".into());
return Err(("expected \"{\".", tok.pos()).into());
}
}
} else {
@ -90,12 +92,17 @@ impl If {
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 toks = Vec::new();
let mut found_true = false;
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;
found_true = true;
break;
@ -105,10 +112,10 @@ impl If {
toks = self.else_;
}
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::RuleSet(r) if r.selector.is_empty() => stmts.extend(r.rules),
v => stmts.push(v),
_ => stmts.push(stmt),
}
}
Ok(stmts)

View File

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

View File

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

View File

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

View File

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

View File

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

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) => {
let x = $low;
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,
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) => {
let x = $low;
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,
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>) {
f.insert("change-color".to_owned(), Builtin::new(|mut args, scope, super_selector| {
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") {
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);
@ -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) {
Value::Dimension(n, _) => Some(n),
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);
@ -76,7 +96,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| {
let color = match arg!(args, scope, super_selector, 0, "color") {
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);
@ -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) {
Value::Dimension(n, _) => Some(n),
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!(
@ -142,7 +174,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| {
let color = match arg!(args, scope, super_selector, 0, "color") {
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 {
@ -150,15 +182,15 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let x = $low;
let $name = match named_arg!($args, $scope, $super_selector, $arg = Value::Null) {
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(..) => {
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,
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);
let color = match arg!(args, scope, super_selector, 0, "color") {
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))
}),

View File

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

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") {
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() {
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()) {
return Err(format!(
return Err((
format!(
"$n: Invalid index {} for a list with {} elements.",
n,
list.len()
),
args.span(),
)
.into());
}
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() {
@ -83,23 +92,31 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
};
let n = match arg!(args, scope, super_selector, 1, "n") {
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() {
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();
if n.abs() > Number::from(len) {
return Err(
format!("$n: Invalid index {} for a list with {} elements.", n, len).into(),
);
return Err((
format!("$n: Invalid index {} for a list with {} elements.", n, len),
args.span(),
)
.into());
}
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");
@ -134,10 +151,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"comma" => ListSeparator::Comma,
"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);
@ -177,10 +207,23 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"comma" => ListSeparator::Comma,
"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!(
@ -195,7 +238,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
_ => Brackets::Bracketed,
},
v => {
if v.is_true()? {
if v.is_true(args.span())? {
Brackets::Bracketed
} else {
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)
let index = match list
.into_iter()
.position(|v| v.equals(value.clone()).unwrap())
.position(|v| v.equals(value.clone(), args.span()).unwrap())
{
Some(v) => Number::from(v + 1),
None => return Ok(Value::Null),
@ -254,7 +297,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let lists = args
.get_variadic(scope, super_selector)?
.into_iter()
.map(|x| match x {
.map(|x| match x.node {
Value::List(v, ..) => v,
Value::Map(m) => m.entries(),
v => vec![v],

View File

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

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") {
Value::Map(m) => m,
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(
@ -26,9 +32,15 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
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(
@ -38,7 +50,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
let map = match arg!(args, scope, super_selector, 0, "map") {
Value::Map(m) => m,
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(
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") {
Value::Map(m) => m,
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(
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") {
Value::Map(m) => m,
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") {
Value::Map(m) => m,
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);
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") {
Value::Map(m) => m,
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)?;
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") {
Value::Dimension(n, Unit::None) => n * Number::from(100),
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))
}),
@ -30,7 +46,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "number") {
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);
match arg!(args, scope, super_selector, 0, "number") {
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);
match arg!(args, scope, super_selector, 0, "number") {
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);
match arg!(args, scope, super_selector, 0, "number") {
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);
let unit1 = match arg!(args, scope, super_selector, 0, "number1") {
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") {
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)))
@ -95,7 +157,13 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
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() {
@ -103,19 +171,25 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
}
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() {
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() {
Some(n) => n,
None => {
return Err(
format!("max must be in range 0 < max ≤ 2^32, was {}", limit).into(),
return Err((
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 codemap::Spanned;
use super::{Builtin, GLOBAL_FUNCTIONS};
use crate::common::QuoteKind;
use crate::scope::global_var_exists;
@ -11,7 +13,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
"if".to_owned(),
Builtin::new(|mut args, scope, super_selector| {
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"))
} else {
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),
_ => 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);
let unit = match arg!(args, scope, super_selector, 0, "number") {
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))
}),
@ -61,7 +79,10 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| {
max_args!(args, 1);
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(
@ -80,7 +101,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
Builtin::new(|mut args, scope, super_selector| {
max_args!(args, 1);
Ok(Value::Ident(
arg!(args, scope, super_selector, 0, "value").inspect(),
arg!(args, scope, super_selector, 0, "value").inspect(args.span())?,
QuoteKind::None,
))
}),
@ -91,7 +112,11 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "name") {
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);
match arg!(args, scope, super_selector, 0, "name") {
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);
match arg!(args, scope, super_selector, 0, "name") {
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(
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);
let name = match arg!(args, scope, super_selector, 0, "name") {
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) {
Value::Ident(s, ..) => Some(s),
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 {
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),
Err(..) => match GLOBAL_FUNCTIONS.get(&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| {
let func = match arg!(args, scope, super_selector, 0, "function") {
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)
}),

View File

@ -18,7 +18,14 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 1);
match arg!(args, scope, super_selector, 0, "string") {
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);
match arg!(args, scope, super_selector, 0, "string") {
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()),
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);
match arg!(args, scope, super_selector, 0, "string") {
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);
match arg!(args, scope, super_selector, 0, "string") {
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);
let (string, quotes) = match arg!(args, scope, super_selector, 0, "string") {
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 start = match arg!(args, scope, super_selector, 1, "start-at") {
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() => {
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()
.unwrap(),
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) {
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() => {
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()
.unwrap_or(str_len + 1),
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,
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 {
@ -134,12 +210,30 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 2);
let s1 = match arg!(args, scope, super_selector, 0, "string") {
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") {
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) {
@ -154,23 +248,54 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
max_args!(args, 3);
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
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") {
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") {
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,
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() {

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)]
pub(crate) enum QuoteKind {
Single,

View File

@ -3,36 +3,81 @@ use std::fmt::{self, Display};
use std::io;
use std::string::FromUtf8Error;
use crate::common::Pos;
use codemap::{Span, SpanLoc};
pub type SassResult<T> = Result<T, SassError>;
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug)]
pub struct SassError {
message: String,
pos: Pos,
kind: SassErrorKind,
}
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 {
message: message.into(),
pos,
kind: SassErrorKind::ParseError { message, loc },
}
}
}
#[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 {
// TODO: trim whitespace
// TODO: color errors
// TODO: integrate with codemap-diagnostics
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 {
fn from(error: io::Error) -> Self {
SassError {
pos: Pos::new(),
message: format!("{}", error),
kind: SassErrorKind::IoError(error),
}
}
}
@ -40,8 +85,7 @@ impl From<io::Error> for SassError {
impl From<std::fmt::Error> for SassError {
fn from(error: std::fmt::Error) -> Self {
SassError {
pos: Pos::new(),
message: format!("{}", error),
kind: SassErrorKind::FmtError(error),
}
}
}
@ -49,35 +93,28 @@ impl From<std::fmt::Error> for SassError {
impl From<FromUtf8Error> for SassError {
fn from(error: FromUtf8Error) -> Self {
SassError {
pos: Pos::new(),
message: format!("Invalid UTF-8 character \"\\x{:X?}\"", error.as_bytes()[0]),
kind: SassErrorKind::FromUtf8Error(format!(
"Invalid UTF-8 character \"\\x{:X?}\"",
error.as_bytes()[0]
)),
}
}
}
impl From<SassError> for String {
impl From<(&str, Span)> for SassError {
#[inline]
fn from(error: SassError) -> String {
error.message
}
}
impl From<&str> for SassError {
#[inline]
fn from(error: &str) -> SassError {
fn from(error: (&str, Span)) -> SassError {
SassError {
pos: Pos::new(),
message: error.to_string(),
kind: SassErrorKind::Raw(error.0.to_owned(), error.1),
}
}
}
impl From<String> for SassError {
impl From<(String, Span)> for SassError {
#[inline]
fn from(error: String) -> SassError {
fn from(error: (String, Span)) -> SassError {
SassError {
pos: Pos::new(),
message: error,
kind: SassErrorKind::Raw(error.0, error.1),
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,18 @@
use std::cell::RefCell;
use std::collections::HashMap;
use codemap::Spanned;
use crate::atrule::{Function, Mixin};
use crate::error::SassResult;
use crate::value::Value;
thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new()));
pub(crate) fn get_global_var(s: &str) -> SassResult<Value> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(s) {
pub(crate) fn get_global_var(s: Spanned<String>) -> SassResult<Spanned<Value>> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(&s.node) {
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))
}
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))
}
pub(crate) fn get_global_fn(s: &str) -> SassResult<Function> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().functions().get(s) {
pub(crate) fn get_global_fn(s: Spanned<String>) -> SassResult<Function> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().functions().get(&s.node) {
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))
}
pub(crate) fn get_global_mixin(s: &str) -> SassResult<Mixin> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(s) {
pub(crate) fn get_global_mixin(s: Spanned<String>) -> SassResult<Mixin> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(&s.node) {
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)]
pub(crate) struct Scope {
vars: HashMap<String, Value>,
vars: HashMap<String, Spanned<Value>>,
mixins: HashMap<String, Mixin>,
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
}
@ -81,16 +83,17 @@ impl Scope {
&self.mixins
}
pub fn get_var(&self, v: &str) -> SassResult<Value> {
let name = &v.replace('_', "-");
match self.vars.get(name) {
pub fn get_var(&self, mut name: Spanned<String>) -> SassResult<Spanned<Value>> {
name.node = name.node.replace('_', "-");
match self.vars.get(&name.node) {
Some(v) => Ok(v.clone()),
None => get_global_var(name),
}
}
pub fn insert_var(&mut self, s: &str, v: Value) -> SassResult<Option<Value>> {
Ok(self.vars.insert(s.replace('_', "-"), v.eval()?))
pub fn insert_var(&mut self, s: &str, v: Spanned<Value>) -> SassResult<Option<Spanned<Value>>> {
let Spanned { node, span } = v;
Ok(self.vars.insert(s.replace('_', "-"), node.eval(span)?))
}
pub fn var_exists(&self, v: &str) -> bool {
@ -98,9 +101,9 @@ impl Scope {
self.vars.contains_key(name) || global_var_exists(name)
}
pub fn get_mixin(&self, v: &str) -> SassResult<Mixin> {
let name = &v.replace('_', "-");
match self.mixins.get(name) {
pub fn get_mixin(&self, mut name: Spanned<String>) -> SassResult<Mixin> {
name.node = name.node.replace('_', "-");
match self.mixins.get(&name.node) {
Some(v) => Ok(v.clone()),
None => get_global_mixin(name),
}
@ -115,9 +118,9 @@ impl Scope {
self.mixins.contains_key(name) || global_mixin_exists(name)
}
pub fn get_fn(&self, v: &str) -> SassResult<Function> {
let name = &v.replace('_', "-");
match self.functions.get(name) {
pub fn get_fn(&self, mut name: Spanned<String>) -> SassResult<Function> {
name.node = name.node.replace('_', "-");
match self.functions.get(&name.node) {
Some(v) => Ok(v.clone()),
None => get_global_fn(name),
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
use std::iter::{Iterator, Peekable};
use codemap::Spanned;
use crate::common::QuoteKind;
use crate::error::SassResult;
use crate::selector::Selector;
@ -35,27 +37,27 @@ pub(crate) trait IsComment {
}
pub(crate) fn devour_whitespace_or_comment<I: Iterator<Item = Token>>(
s: &mut Peekable<I>,
toks: &mut Peekable<I>,
) -> SassResult<bool> {
let mut found_whitespace = false;
while let Some(w) = s.peek() {
if w.kind == '/' {
s.next();
match s.peek().unwrap().kind {
while let Some(tok) = toks.peek() {
if tok.kind == '/' {
let pos = toks.next().unwrap().pos();
match toks.peek().unwrap().kind {
'*' => {
eat_comment(s, &Scope::new(), &Selector::new())?;
eat_comment(toks, &Scope::new(), &Selector::new())?;
}
'/' => read_until_newline(s),
_ => return Err("Expected expression.".into()),
'/' => read_until_newline(toks),
_ => return Err(("Expected expression.", pos).into()),
};
found_whitespace = true;
continue;
}
if !w.is_whitespace() {
if !tok.is_whitespace() {
break;
}
found_whitespace = true;
s.next();
toks.next();
}
Ok(found_whitespace)
}
@ -64,20 +66,23 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
) -> SassResult<Spanned<Value>> {
let val = Value::from_vec(read_until_closing_curly_brace(toks), scope, super_selector)?;
toks.next();
Ok(val.eval()?.unquote())
Ok(Spanned {
node: val.node.eval(val.span)?.node.unquote(),
span: val.span,
})
}
pub(crate) struct VariableDecl {
pub val: Value,
pub val: Spanned<Value>,
pub default: bool,
pub global: bool,
}
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 {
val,
default,
@ -327,28 +332,22 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
match next.kind {
'i' => todo!("!important"),
'g' => {
if eat_ident(&mut raw, scope, super_selector)?
.to_ascii_lowercase()
.as_str()
== "lobal"
{
let s = eat_ident(&mut raw, scope, super_selector)?;
if s.node.to_ascii_lowercase().as_str() == "lobal" {
global = true;
} else {
return Err("Invalid flag name.".into());
return Err(("Invalid flag name.", s.span).into());
}
}
'd' => {
if eat_ident(&mut raw, scope, super_selector)?
.to_ascii_lowercase()
.as_str()
== "efault"
{
let s = eat_ident(&mut raw, scope, super_selector)?;
if s.to_ascii_lowercase().as_str() == "efault" {
default = true;
} 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),
@ -364,17 +363,21 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<String> {
) -> SassResult<Spanned<String>> {
let mut s = String::new();
let mut span = toks.peek().unwrap().pos();
while let Some(tok) = toks.peek() {
span = span.merge(tok.pos());
match tok.kind {
'#' => {
let tok = toks.next().unwrap();
if toks.peek().ok_or(("Expected identifier.", tok.pos()))?.kind == '{' {
toks.next();
if toks.peek().ok_or("Expected identifier.")?.kind == '{' {
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 {
return Err("Expected identifier.".into());
return Err(("Expected identifier.", tok.pos()).into());
}
}
_ 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)
}
'\\' => {
toks.next();
let span_start = toks.next().unwrap().pos();
let mut n = String::new();
while let Some(c) = toks.peek() {
if !c.kind.is_ascii_hexdigit() || n.len() > 6 {
@ -395,7 +398,7 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
toks.next();
}
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() {
s.push(c);
} else {
@ -418,14 +421,20 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
_ => break,
}
}
Ok(s)
Ok(Spanned { node: s, span })
}
pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> SassResult<String> {
) -> SassResult<Spanned<String>> {
let mut s = String::new();
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(tok) = toks.peek() {
span = span.merge(tok.pos());
match tok.kind {
'#' => {
break;
@ -471,26 +480,36 @@ pub(crate) fn eat_ident_no_interpolation<I: Iterator<Item = Token>>(
_ => 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 span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(c) = toks.peek() {
if !c.kind.is_numeric() {
break;
}
let tok = toks.next().unwrap();
span = span.merge(tok.pos());
whole.push(tok.kind);
}
if toks.peek().is_none() {
return Ok(whole);
return Ok(Spanned { node: whole, span });
}
let mut dec = String::new();
if toks.peek().unwrap().kind == '.' {
let next_tok = toks.peek().unwrap().clone();
if next_tok.kind == '.' {
toks.next();
dec.push('.');
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;
}
let tok = toks.next().unwrap();
span = span.merge(tok.pos());
dec.push(tok.kind);
}
}
if dec.len() == 1 {
return Err("Expected digit.".into());
return Err(("Expected digit.", next_tok.pos()).into());
}
whole.push_str(&dec);
Ok(whole)
Ok(Spanned { node: whole, span })
}
/// 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
/// The entirety of the comment, including the ending "*/" is consumed.
/// 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>>(
toks: &mut Peekable<I>,
_scope: &Scope,
_super_selector: &Selector,
) -> SassResult<String> {
) -> SassResult<Spanned<String>> {
let mut comment = String::new();
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
todo!()
};
while let Some(tok) = toks.next() {
span = span.merge(tok.pos());
if tok.kind == '*' && toks.peek().unwrap().kind == '/' {
toks.next();
break;
@ -543,7 +571,10 @@ pub(crate) fn eat_comment<I: Iterator<Item = Token>>(
comment.push(tok.kind);
}
devour_whitespace(toks);
Ok(comment)
Ok(Spanned {
node: comment,
span,
})
}
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,
q: char,
super_selector: &Selector,
) -> SassResult<Value> {
) -> SassResult<Spanned<Value>> {
let mut s = String::new();
let mut is_escaped = 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() {
span = span.merge(tok.pos());
match tok.kind {
'"' if !is_escaped && q == '"' => break,
'"' if is_escaped => {
@ -579,14 +616,15 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
if toks.peek().unwrap().kind == '{' {
toks.next();
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;
} else {
s.push('#');
continue;
}
}
'\n' => return Err("Expected \".".into()),
'\n' => return Err(("Expected \".", tok.pos()).into()),
v if v.is_ascii_hexdigit() && is_escaped => {
let mut n = v.to_string();
while let Some(c) = toks.peek() {
@ -628,7 +666,10 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
_ => 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>>(

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

View File

@ -1,6 +1,8 @@
use std::slice::Iter;
use std::vec::IntoIter;
use codemap::Span;
use super::Value;
use crate::common::{Brackets, ListSeparator};
use crate::error::SassResult;
@ -13,9 +15,9 @@ impl SassMap {
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 {
if k.equals(key.clone())? {
if k.equals(key.clone(), span)? {
return Ok(Some(v));
}
}

View File

@ -1,7 +1,8 @@
use std::cmp::Ordering;
use std::fmt::{self, Display, Write};
use std::iter::Iterator;
use codemap::{Span, Spanned};
use crate::color::Color;
use crate::common::{Brackets, ListSeparator, Op, QuoteKind};
use crate::error::SassResult;
@ -33,112 +34,11 @@ pub(crate) enum Value {
Paren(Box<Value>),
Ident(String, QuoteKind),
Map(SassMap),
ArgList(Vec<Value>),
ArgList(Vec<Spanned<Value>>),
/// Returned by `get-function()`
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 {
pub fn is_null(&self) -> bool {
match self {
@ -147,12 +47,106 @@ impl Value {
_ => 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 {
Value::Null | Value::False => Ok(false),
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
self.clone().eval()?.is_true()
self.clone().eval(span)?.is_true(span)
}
_ => 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 {
Self::Color(..) => Ok("color"),
Self::Ident(..) | Self::Important => Ok("string"),
@ -176,7 +174,9 @@ impl Value {
Self::True | Self::False => Ok("bool"),
Self::Null => Ok("null"),
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 {
match self {
pub fn inspect(&self, span: Span) -> SassResult<String> {
Ok(match self {
Value::List(v, _, brackets) if v.is_empty() => match brackets {
Brackets::None => "()".to_string(),
Brackets::Bracketed => "[]".to_string(),
},
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> {
Ok(match self.eval()? {
pub fn equals(self, other: Value, span: Span) -> SassResult<bool> {
Ok(match self.eval(span)?.node {
Self::Ident(s1, ..) => match other {
Self::Ident(s2, ..) => s1 == s2,
_ => false,
@ -227,64 +227,71 @@ impl Value {
}
_ => false,
},
s => s == other.eval()?,
s => s == other.eval(span)?.node,
})
}
pub fn unary_op_plus(self) -> SassResult<Self> {
Ok(match self.eval()? {
pub fn unary_op_plus(self, span: Span) -> SassResult<Self> {
Ok(match self.eval(span)?.node {
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> {
match self {
pub fn eval(self, span: Span) -> SassResult<Spanned<Self>> {
Ok(match self {
Self::BinaryOp(lhs, op, rhs) => match op {
Op::Plus => *lhs + *rhs,
Op::Minus => *lhs - *rhs,
Op::Equal => Ok(Self::bool(lhs.equals(*rhs)?)),
Op::NotEqual => Ok(Self::bool(!lhs.equals(*rhs)?)),
Op::Mul => *lhs * *rhs,
Op::Div => *lhs / *rhs,
Op::Rem => *lhs % *rhs,
Op::GreaterThan => lhs.cmp(*rhs, op),
Op::GreaterThanEqual => lhs.cmp(*rhs, op),
Op::LessThan => lhs.cmp(*rhs, op),
Op::LessThanEqual => lhs.cmp(*rhs, op),
Op::Plus => lhs.add(*rhs, span)?,
Op::Minus => lhs.sub(*rhs, span)?,
Op::Equal => Self::bool(lhs.equals(*rhs, span)?),
Op::NotEqual => Self::bool(!lhs.equals(*rhs, span)?),
Op::Mul => lhs.mul(*rhs, span)?,
Op::Div => lhs.div(*rhs, span)?,
Op::Rem => lhs.rem(*rhs, span)?,
Op::GreaterThan => return lhs.cmp(*rhs, op, span),
Op::GreaterThanEqual => return lhs.cmp(*rhs, op, span),
Op::LessThan => return lhs.cmp(*rhs, op, span),
Op::LessThanEqual => return lhs.cmp(*rhs, op, span),
Op::Not => unreachable!(),
Op::And => Ok(if lhs.clone().is_true()? {
rhs.eval()?
Op::And => {
if lhs.clone().is_true(span)? {
rhs.eval(span)?.node
} else {
lhs.eval()?
}),
Op::Or => Ok(if lhs.is_true()? {
lhs.eval()?
lhs.eval(span)?.node
}
}
Op::Or => {
if lhs.is_true(span)? {
lhs.eval(span)?.node
} else {
rhs.eval()?
}),
rhs.eval(span)?.node
}
}
},
Self::Paren(v) => v.eval(),
Self::Paren(v) => v.eval(span)?.node,
Self::UnaryOp(op, val) => match op {
Op::Plus => val.unary_op_plus(),
Op::Minus => -*val,
Op::Not => Ok(Self::bool(!val.eval()?.is_true()?)),
Op::Plus => val.unary_op_plus(span)?,
Op::Minus => val.neg(span)?,
Op::Not => Self::bool(!val.eval(span)?.is_true(span)?),
_ => 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 {
other = other.eval()?
other = other.eval(span)?.node
}
let precedence = op.precedence();
let ordering = match self {
Self::Dimension(num, unit) => match &other {
Self::Dimension(num2, 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 {
num.cmp(num2)
@ -301,41 +308,69 @@ impl Value {
}
}
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) => {
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 {
Self::BinaryOp(
left,
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),
_ => return Err(format!("Undefined operation \"{} {} {}\".", self, op, other).into()),
Self::UnaryOp(..) | Self::Paren(..) => {
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 {
Ordering::Greater => Ok(Self::True),
Ordering::Less | Ordering::Equal => Ok(Self::False),
Ordering::Greater => Self::True,
Ordering::Less | Ordering::Equal => Self::False,
},
Op::GreaterThanEqual => match ordering {
Ordering::Greater | Ordering::Equal => Ok(Self::True),
Ordering::Less => Ok(Self::False),
Ordering::Greater | Ordering::Equal => Self::True,
Ordering::Less => Self::False,
},
Op::LessThan => match ordering {
Ordering::Less => Ok(Self::True),
Ordering::Greater | Ordering::Equal => Ok(Self::False),
Ordering::Less => Self::True,
Ordering::Greater | Ordering::Equal => Self::False,
},
Op::LessThanEqual => match ordering {
Ordering::Less | Ordering::Equal => Ok(Self::True),
Ordering::Greater => Ok(Self::False),
Ordering::Less | Ordering::Equal => Self::True,
Ordering::Greater => Self::False,
},
_ => 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::error::SassResult;
use crate::unit::{Unit, UNIT_CONVERSION_TABLE};
use crate::value::Value;
impl Add for Value {
type Output = SassResult<Self>;
fn add(self, mut other: Self) -> Self::Output {
impl Value {
pub fn add(self, mut other: Self, span: Span) -> SassResult<Self> {
if let Self::Paren(..) = other {
other = other.eval()?
other = other.eval(span)?.node
}
let precedence = Op::Plus.precedence();
Ok(match self {
Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(),
Self::Important | Self::True | Self::False => match other {
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),
_ => Value::Ident(format!("{}{}", self, other), QuoteKind::None),
Self::Null => Value::Ident(self.to_css_string(span)?, QuoteKind::None),
_ => Value::Ident(
format!(
"{}{}",
self.to_css_string(span)?,
other.to_css_string(span)?
),
QuoteKind::None,
),
},
Self::Null => match other {
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(num2, 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 {
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::Null => Value::Ident(format!("{}{}", num, unit), QuoteKind::None),
Self::List(..) => {
Value::Ident(format!("{}{}{}", num, unit, other), QuoteKind::None)
}
Self::List(..) => Value::Ident(
format!("{}{}{}", num, unit, other.to_css_string(span)?),
QuoteKind::None,
),
_ => {
return Err(
format!("Undefined operation \"{}{} + {}\".", num, unit, other).into(),
return Err((
format!(
"Undefined operation \"{}{} + {}\".",
num,
unit,
other.to_css_string(span)?
),
span,
)
.into())
}
},
Self::Color(c) => match other {
Self::Ident(s, q) => Value::Ident(format!("{}{}", c, s), q.normalize()),
Self::Null => Value::Ident(c.to_string(), QuoteKind::None),
Self::List(..) => Value::Ident(format!("{}{}", c, other), QuoteKind::None),
_ => return Err(format!("Undefined operation \"{} + {}\".", c, other).into()),
Self::List(..) => Value::Ident(
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) => {
if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? + other)?
Self::BinaryOp(left, op, right)
.eval(span)?
.node
.add(other, span)?
} else {
Self::BinaryOp(
left,
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(s2, _) => Value::Ident(format!("{}{}", s1, s2), quotes1.normalize()),
Self::Important | Self::True | Self::False | Self::Dimension(..) => {
Value::Ident(format!("{}{}", s1, other), quotes1.normalize())
}
Self::Important | Self::True | Self::False | Self::Dimension(..) => Value::Ident(
format!("{}{}", s1, other.to_css_string(span)?),
quotes1.normalize(),
),
Self::Null => Value::Ident(s1, 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::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::List(..) => match other {
Self::Ident(s, q) => Value::Ident(format!("{}{}", self, s), q.normalize()),
Self::Paren(..) => (self + other.eval()?)?,
_ => Value::Ident(format!("{}{}", self, other), QuoteKind::None),
Self::Ident(s, q) => {
Value::Ident(format!("{}{}", self.to_css_string(span)?, s), q.normalize())
}
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 {
type Output = SassResult<Self>;
fn sub(self, mut other: Self) -> Self::Output {
pub fn sub(self, mut other: Self, span: Span) -> SassResult<Self> {
if let Self::Paren(..) = other {
other = other.eval()?
other = other.eval(span)?.node
}
let precedence = Op::Mul.precedence();
Ok(match self {
@ -110,7 +157,9 @@ impl Sub for Value {
Self::Dimension(num, unit) => match other {
Self::Dimension(num2, 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 {
Value::Dimension(num - num2, unit)
@ -127,12 +176,14 @@ impl Sub for Value {
)
}
}
Self::List(..) => {
Value::Ident(format!("{}{}-{}", num, unit, other), QuoteKind::None)
}
Self::Ident(..) => {
Value::Ident(format!("{}{}-{}", num, unit, other), QuoteKind::None)
}
Self::List(..) => Value::Ident(
format!("{}{}-{}", num, unit, other.to_css_string(span)?),
QuoteKind::None,
),
Self::Ident(..) => Value::Ident(
format!("{}{}-{}", num, unit, other.to_css_string(span)?),
QuoteKind::None,
),
_ => todo!(),
},
Self::Color(c) => match other {
@ -142,23 +193,42 @@ impl Sub for Value {
),
Self::Null => Value::Ident(format!("{}-", c), QuoteKind::None),
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) => {
if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? - other)?
Self::BinaryOp(left, op, right)
.eval(span)?
.node
.sub(other, span)?
} else {
Self::BinaryOp(
left,
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(s2, q2) => Value::Ident(
format!(
@ -177,7 +247,13 @@ impl Sub for Value {
| Self::False
| Self::Dimension(..)
| Self::Color(..) => Value::Ident(
format!("{}{}{}-{}", q1.normalize(), s1, q1.normalize(), other),
format!(
"{}{}{}-{}",
q1.normalize(),
s1,
q1.normalize(),
other.to_css_string(span)?
),
QuoteKind::None,
),
Self::Null => Value::Ident(
@ -185,37 +261,66 @@ impl Sub for Value {
QuoteKind::None,
),
Self::List(..) => Value::Ident(
format!("{}{}{}-{}", q1.normalize(), s1, q1.normalize(), other),
format!(
"{}{}{}-{}",
q1.normalize(),
s1,
q1.normalize(),
other.to_css_string(span)?
),
QuoteKind::None,
),
_ => todo!(),
},
Self::List(..) => match other {
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,
),
Self::Paren(..) => (self - other.eval()?)?,
_ => Value::Ident(format!("{}-{}", self, other), QuoteKind::None),
},
_ => match other {
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,
),
Self::Null => Value::Ident(format!("{}-", self), QuoteKind::None),
_ => Value::Ident(format!("{}-{}", self, other), QuoteKind::None),
},
})
}
}
impl Mul for Value {
type Output = SassResult<Self>;
fn mul(self, mut other: Self) -> Self::Output {
pub fn mul(self, mut other: Self, span: Span) -> SassResult<Self> {
if let Self::Paren(..) = other {
other = other.eval()?
other = other.eval(span)?.node
}
let precedence = Op::Mul.precedence();
Ok(match self {
@ -238,40 +343,63 @@ impl Mul for Value {
}
}
_ => {
return Err(
format!("Undefined operation \"{}{} * {}\".", num, unit, other).into(),
return Err((
format!(
"Undefined operation \"{}{} * {}\".",
num,
unit,
other.to_css_string(span)?
),
span,
)
.into())
}
},
Self::BinaryOp(left, op, right) => {
if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? * other)?
Self::BinaryOp(left, op, right)
.eval(span)?
.node
.mul(other, span)?
} else {
Self::BinaryOp(
left,
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)?,
_ => return Err(format!("Undefined operation \"{} * {}\".", self, other).into()),
Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.mul(other, span)?,
_ => {
return Err((
format!(
"Undefined operation \"{} * {}\".",
self.to_css_string(span)?,
other.to_css_string(span)?
),
span,
)
.into())
}
})
}
}
impl Div for Value {
type Output = SassResult<Self>;
fn div(self, other: Self) -> Self::Output {
pub fn div(self, other: Self, span: Span) -> SassResult<Self> {
let precedence = Op::Div.precedence();
Ok(match self {
Self::Null => todo!(),
Self::Dimension(num, unit) => match other {
Self::Dimension(num2, 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 {
Value::Dimension(num / num2, Unit::None)
@ -293,7 +421,7 @@ impl Div for Value {
QuoteKind::None,
),
Self::BinaryOp(..) | Self::Paren(..) => {
(Self::Dimension(num, unit) / other.eval()?)?
Self::Dimension(num, unit).div(other.eval(span)?.node, span)?
}
_ => todo!(),
},
@ -304,23 +432,42 @@ impl Div for Value {
),
Self::Null => Value::Ident(format!("{}/", c), QuoteKind::None),
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) => {
if op.precedence() >= precedence {
(Self::BinaryOp(left, op, right).eval()? / other)?
Self::BinaryOp(left, op, right)
.eval(span)?
.node
.div(other, span)?
} else {
Self::BinaryOp(
left,
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(s2, q2) => Value::Ident(
format!(
@ -339,7 +486,13 @@ impl Div for Value {
| Self::False
| Self::Dimension(..)
| Self::Color(..) => Value::Ident(
format!("{}{}{}/{}", q1.normalize(), s1, q1.normalize(), other),
format!(
"{}{}{}/{}",
q1.normalize(),
s1,
q1.normalize(),
other.to_css_string(span)?
),
QuoteKind::None,
),
Self::Null => Value::Ident(
@ -350,25 +503,36 @@ impl Div for Value {
},
_ => match other {
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,
),
Self::Null => Value::Ident(format!("{}/", self), QuoteKind::None),
_ => Value::Ident(format!("{}/{}", self, other), QuoteKind::None),
},
})
}
}
impl Rem for Value {
type Output = SassResult<Self>;
fn rem(self, other: Self) -> Self::Output {
pub fn rem(self, other: Self, span: Span) -> SassResult<Self> {
Ok(match self {
Value::Dimension(n, u) => match other {
Value::Dimension(n2, 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 {
Value::Dimension(n % n2, u)
@ -381,26 +545,35 @@ impl Rem for Value {
}
}
_ => {
return Err(format!(
return Err((
format!(
"Undefined operation \"{} % {}\".",
Value::Dimension(n, u),
other
Value::Dimension(n, u).to_css_string(span)?,
other.to_css_string(span)?
),
span,
)
.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 {
type Output = SassResult<Self>;
fn neg(self) -> Self::Output {
Ok(match self.eval()? {
pub fn neg(self, span: Span) -> SassResult<Self> {
Ok(match self.eval(span)?.node {
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_traits::pow;
use codemap::{Span, Spanned};
use super::css_function::{eat_calc_args, eat_progid};
use crate::args::eat_call_args;
@ -31,11 +33,12 @@ fn parse_hex<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
mut span: Span,
) -> SassResult<Spanned<Value>> {
let mut s = String::with_capacity(8);
if toks
.peek()
.ok_or("Expected identifier.")?
.ok_or(("Expected identifier.", span))?
.kind
.is_ascii_digit()
{
@ -43,93 +46,75 @@ fn parse_hex<I: Iterator<Item = Token>>(
if !c.kind.is_ascii_hexdigit() || s.len() == 8 {
break;
}
s.push(toks.next().unwrap().kind);
let tok = toks.next().unwrap();
span = span.merge(tok.pos());
s.push(tok.kind);
}
} else {
let i = eat_ident(toks, scope, super_selector)?;
if i.chars().all(|c| c.is_ascii_hexdigit()) {
s = i;
if i.node.chars().all(|c| c.is_ascii_hexdigit()) {
s = i.node;
span = span.merge(i.span);
} 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() {
3 => {
let v = match u16::from_str_radix(&s, 16) {
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 green = (((v & 0x0f0) >> 4) * 0x11) as u8;
let blue = ((v & 0x00f) * 0x11) as u8;
Ok(Value::Color(Color::new(
red,
green,
blue,
1,
format!("#{}", s),
)))
Ok(Value::Color(Color::new(red, green, blue, 1, format!("#{}", s))).span(span))
}
4 => {
let v = match u16::from_str_radix(&s, 16) {
Ok(a) => a,
Err(_) => return Ok(Value::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 green = (((v & 0x0f00) >> 8) * 0x11) as u8;
let blue = (((v & 0x00f0) >> 4) * 0x11) as u8;
let alpha = ((v & 0x000f) * 0x11) as u8;
Ok(Value::Color(Color::new(
red,
green,
blue,
alpha,
format!("#{}", s),
)))
Ok(Value::Color(Color::new(red, green, blue, alpha, format!("#{}", s))).span(span))
}
6 => {
let v = match u32::from_str_radix(&s, 16) {
Ok(a) => a,
Err(_) => return Ok(Value::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 green = ((v & 0x0000_ff00) >> 8) as u8;
let blue = (v & 0x0000_00ff) as u8;
Ok(Value::Color(Color::new(
red,
green,
blue,
1,
format!("#{}", s),
)))
Ok(Value::Color(Color::new(red, green, blue, 1, format!("#{}", s))).span(span))
}
8 => {
let v = match u32::from_str_radix(&s, 16) {
Ok(a) => a,
Err(_) => return Ok(Value::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 green = ((v & 0x00ff_0000) >> 16) as u8;
let blue = ((v & 0x0000_ff00) >> 8) as u8;
let alpha = (v & 0x0000_00ff) as u8;
Ok(Value::Color(Color::new(
red,
green,
blue,
alpha,
format!("#{}", s),
)))
Ok(Value::Color(Color::new(red, green, blue, alpha, format!("#{}", s))).span(span))
}
_ => Err("Expected hex digit.".into()),
_ => Err(("Expected hex digit.", span).into()),
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum IntermediateValue {
Value(Value),
Op(Op),
Bracketed(Vec<Token>),
Paren(Vec<Token>),
Value(Spanned<Value>),
Op(Spanned<Op>),
Bracketed(Spanned<Vec<Token>>),
Paren(Spanned<Vec<Token>>),
Comma,
Whitespace,
}
@ -144,52 +129,61 @@ impl IsWhitespace for IntermediateValue {
}
fn parse_paren(
t: Vec<Token>,
t: Spanned<Vec<Token>>,
scope: &Scope,
super_selector: &Selector,
space_separated: &mut Vec<Value>,
space_separated: &mut Vec<Spanned<Value>>,
) -> SassResult<()> {
if t.is_empty() {
space_separated.push(Value::List(
Vec::new(),
ListSeparator::Space,
Brackets::None,
));
space_separated
.push(Value::List(Vec::new(), ListSeparator::Space, Brackets::None).span(t.span));
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 key = Value::from_vec(read_until_char(paren_toks, ':'), scope, super_selector)?;
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(());
}
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() {
space_separated.push(Value::Map(map));
space_separated.push(Spanned {
node: Value::Map(map),
span: key.span.merge(val.span),
});
return Ok(());
}
let mut span = key.span;
loop {
let key = Value::from_vec(read_until_char(paren_toks, ':'), scope, super_selector)?;
devour_whitespace(paren_toks);
let val = Value::from_vec(read_until_char(paren_toks, ','), scope, super_selector)?;
span = span.merge(val.span);
devour_whitespace(paren_toks);
if map.insert(key, val) {
return Err("Duplicate key.".into());
if map.insert(key.node, val.node) {
return Err(("Duplicate key.", key.span).into());
}
if paren_toks.peek().is_none() {
break;
}
}
space_separated.push(Value::Map(map));
space_separated.push(Spanned {
node: Value::Map(map),
span,
});
Ok(())
}
@ -197,58 +191,82 @@ fn eat_op<I: Iterator<Item = IntermediateValue>>(
iter: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
op: Op,
space_separated: &mut Vec<Value>,
op: Spanned<Op>,
space_separated: &mut Vec<Spanned<Value>>,
) -> SassResult<()> {
match op {
match op.node {
Op::Not => {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::UnaryOp(op, Box::new(right)));
let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
}
Op::Plus => {
if let Some(left) = space_separated.pop() {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right)));
let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Spanned {
node: Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node)),
span: left.span.merge(right.span),
});
} else {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::UnaryOp(op, Box::new(right)));
let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
}
}
Op::Minus => {
if devour_whitespace(iter) {
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() {
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 {
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 {
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::UnaryOp(op, Box::new(right)));
let right = single_value(iter, scope, super_selector, op.span)?;
space_separated.push(Spanned {
node: Value::UnaryOp(op.node, Box::new(right.node)),
span: right.span,
});
}
}
Op::And | Op::Or => {
devour_whitespace(iter);
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() {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right)));
let right = single_value(iter, scope, super_selector, left.span)?;
space_separated.push(
Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
.span(left.span.merge(right.span)),
);
} else {
return Err("Expected expression.".into());
return Err(("Expected expression.", op.span).into());
}
}
_ => {
if let Some(left) = space_separated.pop() {
devour_whitespace(iter);
let right = single_value(iter, scope, super_selector)?;
space_separated.push(Value::BinaryOp(Box::new(left), op, Box::new(right)));
let right = single_value(iter, scope, super_selector, left.span)?;
space_separated.push(
Value::BinaryOp(Box::new(left.node), op.node, Box::new(right.node))
.span(left.span.merge(right.span)),
);
} else {
return Err("Expected expression.".into());
return Err(("Expected expression.", op.span).into());
}
}
}
@ -259,28 +277,45 @@ fn single_value<I: Iterator<Item = IntermediateValue>>(
iter: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
Ok(match iter.next().ok_or("Expected expression.")? {
span: Span,
) -> SassResult<Spanned<Value>> {
Ok(match iter.next().ok_or(("Expected expression.", span))? {
IntermediateValue::Value(v) => v,
IntermediateValue::Op(op) => match op {
IntermediateValue::Op(op) => match op.node {
Op::Minus => {
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 => {
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!(),
},
IntermediateValue::Whitespace => unreachable!(),
IntermediateValue::Comma => return Err("Expected expression.".into()),
IntermediateValue::Bracketed(t) => match Value::from_vec(t, scope, super_selector)? {
IntermediateValue::Comma => return Err(("Expected expression.", span).into()),
IntermediateValue::Bracketed(t) => {
let v = Value::from_vec(t.node, scope, super_selector)?;
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) => {
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>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Self> {
) -> SassResult<Spanned<Self>> {
let mut intermediate_values = Vec::new();
let span = match toks.peek() {
Some(Token { pos, .. }) => *pos,
None => todo!("Expected expression."),
};
while toks.peek().is_some() {
intermediate_values.push(Self::parse_intermediate_value(toks, scope, super_selector)?);
}
@ -309,20 +348,40 @@ impl Value {
if space_separated.len() == 1 {
comma_separated.push(space_separated.pop().unwrap());
} else {
comma_separated.push(Value::List(
mem::take(&mut space_separated),
let mut span = space_separated[0].span;
comma_separated.push(
Value::List(
mem::take(&mut space_separated)
.into_iter()
.map(|a| {
span = span.merge(a.span);
a.node
})
.collect(),
ListSeparator::Space,
Brackets::None,
));
)
.span(span),
);
}
}
IntermediateValue::Bracketed(t) => {
space_separated.push(match Value::from_vec(t, scope, super_selector)? {
Value::List(v, sep, Brackets::None) => {
Value::List(v, sep, Brackets::Bracketed)
if t.node.is_empty() {
space_separated.push(
Value::List(Vec::new(), ListSeparator::Space, Brackets::Bracketed)
.span(t.span),
);
continue;
}
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
})
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) => {
parse_paren(t, scope, super_selector, &mut space_separated)?;
@ -334,17 +393,30 @@ impl Value {
if space_separated.len() == 1 {
comma_separated.push(space_separated.pop().unwrap());
} else if !space_separated.is_empty() {
comma_separated.push(Value::List(
space_separated,
comma_separated.push(
Value::List(
space_separated.into_iter().map(|a| a.node).collect(),
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 {
space_separated.pop().unwrap()
} 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>,
scope: &Scope,
super_selector: &Selector,
) -> SassResult<Value> {
) -> SassResult<Spanned<Value>> {
Self::from_tokens(&mut toks.into_iter().peekable(), scope, super_selector)
}
@ -361,25 +433,34 @@ impl Value {
scope: &Scope,
super_selector: &Selector,
) -> 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 == ':' {
toks.next();
s.push(':');
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() {
Some(Token { kind: '(', .. }) => {
toks.next();
let func = match scope.get_fn(&s) {
let func = match scope.get_fn(Spanned {
node: s.clone(),
span,
}) {
Ok(f) => f,
Err(_) => match GLOBAL_FUNCTIONS.get(&s) {
Some(f) => {
return Ok(IntermediateValue::Value(f.0(
return Ok(IntermediateValue::Value(Spanned {
node: f.0(
eat_call_args(toks, scope, super_selector)?,
scope,
super_selector,
)?))
)?,
span,
}))
}
None => {
match s.as_str() {
@ -394,7 +475,10 @@ impl Value {
.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,
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()) {
Ok(IntermediateValue::Value(Value::Color(c.into_color(s))))
Ok(IntermediateValue::Value(Spanned {
node: Value::Color(c.into_color(s)),
span,
}))
} else {
match s.to_ascii_lowercase().as_str() {
"true" => Ok(IntermediateValue::Value(Value::True)),
"false" => Ok(IntermediateValue::Value(Value::False)),
"null" => Ok(IntermediateValue::Value(Value::Null)),
"not" => Ok(IntermediateValue::Op(Op::Not)),
"and" => Ok(IntermediateValue::Op(Op::And)),
"or" => Ok(IntermediateValue::Op(Op::Or)),
_ => Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))),
}
Ok(match s.to_ascii_lowercase().as_str() {
"true" => IntermediateValue::Value(Value::True.span(span)),
"false" => IntermediateValue::Value(Value::False.span(span)),
"null" => IntermediateValue::Value(Value::Null.span(span)),
"not" => IntermediateValue::Op(Spanned {
node: Op::Not,
span,
}),
"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) {
return Ok(IntermediateValue::Whitespace);
}
let kind = match toks.peek() {
Some(v) => v.kind,
let (kind, span) = match toks.peek() {
Some(v) => (v.kind, v.pos()),
None => panic!("unexpected eof"),
};
match kind {
@ -444,14 +541,19 @@ impl Value {
Ok(IntermediateValue::Comma)
}
'0'..='9' | '.' => {
let val = eat_number(toks)?;
let Spanned {
node: val,
mut span,
} = eat_number(toks)?;
let unit = if let Some(tok) = toks.peek() {
match tok.kind {
'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::None,
@ -479,35 +581,42 @@ impl Value {
}
BigRational::new(num.parse().unwrap(), pow(BigInt::from(10), num_dec))
};
Ok(IntermediateValue::Value(Value::Dimension(
Number::new(n),
unit,
)))
Ok(IntermediateValue::Value(
Value::Dimension(Number::new(n), unit).span(span),
))
}
'(' => {
toks.next();
let mut span = toks.next().unwrap().pos();
let mut inner = read_until_closing_paren(toks);
// todo: the above shouldn't eat the closing paren
if !inner.is_empty() && inner.pop().unwrap().kind != ')' {
return Err("expected \")\".".into());
if !inner.is_empty() {
let last_tok = inner.pop().unwrap();
if last_tok.kind != ')' {
return Err(("expected \")\".", span).into());
}
Ok(IntermediateValue::Paren(inner))
span = span.merge(last_tok.pos());
}
Ok(IntermediateValue::Paren(Spanned { node: inner, span }))
}
'&' => {
toks.next();
Ok(IntermediateValue::Value(Value::Ident(
super_selector.to_string(),
QuoteKind::None,
)))
let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Value(Spanned {
node: Value::Ident(super_selector.to_string(), QuoteKind::None),
span,
}))
}
'#' => {
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 {
Ok(IntermediateValue::Value(parse_hex(
toks,
scope,
super_selector,
span,
)?))
}
}
@ -519,47 +628,67 @@ impl Value {
Self::ident(toks, scope, super_selector)
}
q @ '"' | q @ '\'' => {
toks.next();
Ok(IntermediateValue::Value(parse_quoted_string(
toks,
scope,
q,
super_selector,
)?))
let span_start = toks.next().unwrap().pos();
let Spanned { node, span } = parse_quoted_string(toks, scope, q, super_selector)?;
Ok(IntermediateValue::Value(Spanned {
node,
span: span_start.merge(span),
}))
}
'[' => {
toks.next();
let mut span = toks.next().unwrap().pos();
let mut inner = read_until_closing_square_brace(toks);
inner.pop();
Ok(IntermediateValue::Bracketed(inner))
if !inner.is_empty() {
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();
Ok(IntermediateValue::Value(
scope.get_var(&eat_ident_no_interpolation(toks)?)?,
))
let val = 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();
Ok(IntermediateValue::Op(Op::Plus))
let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Spanned {
node: Op::Plus,
span,
}))
}
'-' => {
toks.next();
Ok(IntermediateValue::Op(Op::Minus))
let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Spanned {
node: Op::Minus,
span,
}))
}
'*' => {
toks.next();
Ok(IntermediateValue::Op(Op::Mul))
let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Spanned {
node: Op::Mul,
span,
}))
}
'%' => {
toks.next();
Ok(IntermediateValue::Op(Op::Rem))
let span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Spanned {
node: Op::Rem,
span,
}))
}
q @ '>' | q @ '<' => {
toks.next();
Ok(IntermediateValue::Op(if toks.peek().unwrap().kind == '=' {
toks.next();
let mut span = toks.next().unwrap().pos();
Ok(IntermediateValue::Op(Spanned {
node: if toks.peek().unwrap().kind == '=' {
span = span.merge(toks.next().unwrap().pos());
match q {
'>' => Op::GreaterThanEqual,
'<' => Op::LessThanEqual,
@ -571,34 +700,47 @@ impl Value {
'<' => Op::LessThan,
_ => unreachable!(),
}
},
span,
}))
}
'=' => {
toks.next();
if toks.next().unwrap().kind == '=' {
Ok(IntermediateValue::Op(Op::Equal))
let mut span = toks.next().unwrap().pos();
if let Token { kind: '=', pos } = toks.next().unwrap() {
span = span.merge(pos);
Ok(IntermediateValue::Op(Spanned {
node: Op::Equal,
span,
}))
} 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 == '=' {
toks.next();
return Ok(IntermediateValue::Op(Op::NotEqual));
span = span.merge(toks.next().unwrap().pos());
return Ok(IntermediateValue::Op(Spanned {
node: Op::NotEqual,
span,
}));
}
devour_whitespace(toks);
let v = eat_ident(toks, scope, super_selector)?;
if v.to_ascii_lowercase().as_str() == "important" {
Ok(IntermediateValue::Value(Value::Important))
span = span.merge(v.span);
if v.node.to_ascii_lowercase().as_str() == "important" {
Ok(IntermediateValue::Value(Spanned {
node: Value::Important,
span,
}))
} else {
Err("Expected \"important\".".into())
Err(("Expected \"important\".", span).into())
}
}
'/' => {
toks.next();
let span = toks.next().unwrap().pos();
if toks.peek().is_none() {
return Err("Expected expression.".into());
return Err(("Expected expression.", span).into());
}
if '*' == toks.peek().unwrap().kind {
toks.next();
@ -609,11 +751,14 @@ impl Value {
devour_whitespace(toks);
Ok(IntermediateValue::Whitespace)
} else {
Ok(IntermediateValue::Op(Op::Div))
Ok(IntermediateValue::Op(Spanned {
node: Op::Div,
span,
}))
}
}
':' | '?' | ')' => Err("expected \";\".".into()),
v if v.is_control() => Err("Expected expression.".into()),
':' | '?' | ')' => Err(("expected \";\".", span).into()),
v if v.is_control() => Err(("Expected expression.", span).into()),
v => {
dbg!(v);
panic!("Unexpected token in value parsing")