integrate error handling with codemap
This commit is contained in:
parent
e833650af0
commit
62f9f7da4f
107
src/args.rs
107
src/args.rs
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
super_selector: super_selector.clone(),
|
||||
selector,
|
||||
rules,
|
||||
}));
|
||||
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)?
|
||||
|
@ -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 {
|
||||
selector: selector.clone(),
|
||||
rules: styles,
|
||||
super_selector: Selector::new(),
|
||||
})];
|
||||
.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(
|
||||
toks,
|
||||
name,
|
||||
scope,
|
||||
super_selector,
|
||||
)?),
|
||||
AtRuleKind::Content => AtRule::Content,
|
||||
AtRuleKind::Unknown(name) => Spanned {
|
||||
node: AtRule::Unknown(UnknownAtRule::from_tokens(
|
||||
toks,
|
||||
name,
|
||||
scope,
|
||||
super_selector,
|
||||
kind_span,
|
||||
)?),
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::Content => Spanned {
|
||||
node: AtRule::Content,
|
||||
span: kind_span,
|
||||
},
|
||||
AtRuleKind::Include => Spanned {
|
||||
node: AtRule::Include(eat_include(toks, scope, super_selector)?),
|
||||
span: kind_span,
|
||||
},
|
||||
_ => todo!("encountered unimplemented at rule"),
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
super_selector: super_selector.clone(),
|
||||
selector,
|
||||
rules,
|
||||
}));
|
||||
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,20 +74,23 @@ 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 {
|
||||
super_selector: if nesting > 1 {
|
||||
super_selector.clone()
|
||||
} else {
|
||||
Selector::new()
|
||||
},
|
||||
selector,
|
||||
rules,
|
||||
}));
|
||||
stmts.push(
|
||||
Stmt::RuleSet(RuleSet {
|
||||
super_selector: if nesting > 1 {
|
||||
super_selector.clone()
|
||||
} else {
|
||||
Selector::new()
|
||||
},
|
||||
selector,
|
||||
rules,
|
||||
})
|
||||
.span(span),
|
||||
);
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
scope.insert_var(&name, *val)?;
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)),
|
||||
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s).span(span)),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
|
@ -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 {
|
||||
selector: super_selector.clone(),
|
||||
rules,
|
||||
super_selector: Selector::new(),
|
||||
});
|
||||
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(),
|
||||
|
@ -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!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
return Err((
|
||||
format!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.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!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
return Err((
|
||||
format!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.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()),
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -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)))
|
||||
}),
|
||||
|
@ -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))
|
||||
}),
|
||||
|
@ -13,21 +13,24 @@ 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!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
return Err((
|
||||
format!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.into());
|
||||
}
|
||||
|
||||
let blue = match channels.pop() {
|
||||
@ -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,21 +338,24 @@ 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!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
return Err((
|
||||
format!(
|
||||
"Only 3 elements allowed, but {} were passed.",
|
||||
channels.len()
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.into());
|
||||
}
|
||||
|
||||
let blue = match channels.pop() {
|
||||
@ -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)))
|
||||
}),
|
||||
|
@ -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!(
|
||||
"$n: Invalid index {} for a list with {} elements.",
|
||||
n,
|
||||
list.len()
|
||||
return Err((
|
||||
format!(
|
||||
"$n: Invalid index {} for a list with {} elements.",
|
||||
n,
|
||||
list.len()
|
||||
),
|
||||
args.span(),
|
||||
)
|
||||
.into());
|
||||
.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],
|
||||
|
@ -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,19 +50,22 @@ 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!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
return Err((
|
||||
format!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into());
|
||||
.into());
|
||||
} else {
|
||||
$arg
|
||||
}
|
||||
@ -68,24 +73,30 @@ 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!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
return Err((
|
||||
format!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into());
|
||||
.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!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
return Err((
|
||||
format!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into());
|
||||
.into());
|
||||
} else {
|
||||
$arg
|
||||
}
|
||||
@ -93,13 +104,16 @@ 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!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
return Err((
|
||||
format!(
|
||||
"${}: Expected {}{} to be within {}{} and {}{}.",
|
||||
$name, $arg, $unit, $low, $unit, $high, $unit,
|
||||
),
|
||||
$args.span(),
|
||||
)
|
||||
.into());
|
||||
.into());
|
||||
} else {
|
||||
$arg
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
}),
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
|
95
src/error.rs
95
src/error.rs
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
);
|
||||
|
@ -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 `..`");
|
||||
|
33
src/lexer.rs
33
src/lexer.rs
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
400
src/lib.rs
400
src/lib.rs
@ -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(),
|
||||
}
|
||||
.parse_toplevel()?)
|
||||
) -> 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())
|
||||
{
|
||||
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,37 +380,39 @@ impl<'a> StyleSheetParser<'a> {
|
||||
});
|
||||
}
|
||||
v => {
|
||||
match AtRule::from_tokens(&v, Pos::new(), &mut self.lexer, &mut Scope::new(), &Selector::new())? {
|
||||
AtRule::Mixin(name, mixin) => {
|
||||
insert_global_mixin(&name, *mixin);
|
||||
}
|
||||
AtRule::Function(name, func) => {
|
||||
insert_global_fn(&name, *func);
|
||||
}
|
||||
AtRule::Charset => continue,
|
||||
AtRule::Warn(pos, message) => self.warn(pos, &message),
|
||||
AtRule::Debug(pos, message) => self.debug(pos, &message),
|
||||
AtRule::Return(_) => {
|
||||
return Err("This at-rule is not allowed here.".into())
|
||||
}
|
||||
AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => rules.extend(s),
|
||||
AtRule::Content => return Err("@content is only allowed within mixin declarations.".into()),
|
||||
AtRule::If(i) => {
|
||||
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
|
||||
}
|
||||
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
|
||||
u @ AtRule::Unknown(..) => rules.push(Stmt::AtRule(u)),
|
||||
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);
|
||||
}
|
||||
AtRule::Function(name, func) => {
|
||||
insert_global_fn(&name, *func);
|
||||
}
|
||||
AtRule::Charset => continue,
|
||||
AtRule::Warn(message) => self.warn(rule.span, &message),
|
||||
AtRule::Debug(message) => self.debug(rule.span, &message),
|
||||
AtRule::Return(_) => {
|
||||
return Err(("This at-rule is not allowed here.", rule.span).into())
|
||||
}
|
||||
AtRule::Include(s) | AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => rules.extend(s),
|
||||
AtRule::Content => return Err(("@content is only allowed within mixin declarations.", rule.span).into()),
|
||||
AtRule::If(i) => {
|
||||
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
|
||||
}
|
||||
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
|
||||
u @ AtRule::Unknown(..) => rules.push(Spanned { node: Stmt::AtRule(u), span: rule.span }),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'&' => {
|
||||
return Err(
|
||||
"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 {
|
||||
super_selector: super_selector.clone(),
|
||||
selector: s,
|
||||
rules,
|
||||
}));
|
||||
self.scope -= 1;
|
||||
if self.scope == 0 {
|
||||
stmts.push(Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
super_selector: super_selector.clone(),
|
||||
selector: s,
|
||||
rules,
|
||||
}),
|
||||
span,
|
||||
});
|
||||
self.nesting -= 1;
|
||||
if self.nesting == 0 {
|
||||
return Ok(stmts);
|
||||
}
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
if self.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 {
|
||||
property: String::new(),
|
||||
value: Value::Null,
|
||||
}))));
|
||||
return Ok(Some(Spanned {
|
||||
node: Expr::Style(Box::new(Style {
|
||||
property: String::new(),
|
||||
value: Value::Null.span(span),
|
||||
})),
|
||||
span,
|
||||
}));
|
||||
}
|
||||
let property = Style::parse_property(&mut v, scope, super_selector, String::new())?;
|
||||
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(
|
||||
&mut values.into_iter().peekable(),
|
||||
scope,
|
||||
super_selector,
|
||||
)?)));
|
||||
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 => {
|
||||
devour_whitespace(toks);
|
||||
return Ok(Some(Expr::Include(eat_include(
|
||||
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)))
|
||||
}
|
||||
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))),
|
||||
};
|
||||
}
|
||||
}
|
||||
toks.next();
|
||||
let Spanned { node: ident, span } = eat_ident(toks, scope, super_selector)?;
|
||||
devour_whitespace(toks);
|
||||
let rule = AtRule::from_tokens(
|
||||
&AtRuleKind::from(ident.as_str()),
|
||||
span,
|
||||
toks,
|
||||
scope,
|
||||
super_selector,
|
||||
)?;
|
||||
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"),
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)?;
|
||||
}
|
||||
|
49
src/scope.rs
49
src/scope.rs
@ -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),
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
43
src/style.rs
43
src/style.rs
@ -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();
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
137
src/utils.rs
137
src/utils.rs
@ -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 {
|
||||
'#' => {
|
||||
toks.next();
|
||||
if toks.peek().ok_or("Expected identifier.")?.kind == '{' {
|
||||
let tok = toks.next().unwrap();
|
||||
if toks.peek().ok_or(("Expected identifier.", tok.pos()))?.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>>(
|
||||
|
@ -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)
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
361
src/value/mod.rs
361
src/value/mod.rs
@ -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()?
|
||||
} else {
|
||||
lhs.eval()?
|
||||
}),
|
||||
Op::Or => Ok(if lhs.is_true()? {
|
||||
lhs.eval()?
|
||||
} else {
|
||||
rhs.eval()?
|
||||
}),
|
||||
Op::And => {
|
||||
if lhs.clone().is_true(span)? {
|
||||
rhs.eval(span)?.node
|
||||
} else {
|
||||
lhs.eval(span)?.node
|
||||
}
|
||||
}
|
||||
Op::Or => {
|
||||
if lhs.is_true(span)? {
|
||||
lhs.eval(span)?.node
|
||||
} else {
|
||||
rhs.eval(span)?.node
|
||||
}
|
||||
}
|
||||
},
|
||||
Self::Paren(v) => v.eval(),
|
||||
Self::Paren(v) => v.eval(span)?.node,
|
||||
Self::UnaryOp(op, val) => match op {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
385
src/value/ops.rs
385
src/value/ops.rs
@ -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!(
|
||||
"Undefined operation \"{} % {}\".",
|
||||
Value::Dimension(n, u),
|
||||
other
|
||||
return Err((
|
||||
format!(
|
||||
"Undefined operation \"{} % {}\".",
|
||||
Value::Dimension(n, u).to_css_string(span)?,
|
||||
other.to_css_string(span)?
|
||||
),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
.into())
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Undefined operation \"{} % {}\".", self, other).into()),
|
||||
_ => {
|
||||
return Err((
|
||||
format!(
|
||||
"Undefined operation \"{} % {}\".",
|
||||
self.to_css_string(span)?,
|
||||
other.to_css_string(span)?
|
||||
),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Value {
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)? {
|
||||
Value::List(v, sep, Brackets::None) => Value::List(v, sep, Brackets::Bracketed),
|
||||
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
|
||||
},
|
||||
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),
|
||||
ListSeparator::Space,
|
||||
Brackets::None,
|
||||
));
|
||||
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)
|
||||
}
|
||||
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed),
|
||||
})
|
||||
if t.node.is_empty() {
|
||||
space_separated.push(
|
||||
Value::List(Vec::new(), ListSeparator::Space, Brackets::Bracketed)
|
||||
.span(t.span),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
space_separated.push(
|
||||
match Value::from_vec(t.node, scope, super_selector)?.node {
|
||||
Value::List(v, sep, Brackets::None) => {
|
||||
Value::List(v, sep, Brackets::Bracketed).span(t.span)
|
||||
}
|
||||
v => Value::List(vec![v], ListSeparator::Space, Brackets::Bracketed)
|
||||
.span(t.span),
|
||||
},
|
||||
)
|
||||
}
|
||||
IntermediateValue::Paren(t) => {
|
||||
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,
|
||||
ListSeparator::Space,
|
||||
Brackets::None,
|
||||
));
|
||||
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(
|
||||
eat_call_args(toks, scope, super_selector)?,
|
||||
scope,
|
||||
super_selector,
|
||||
)?))
|
||||
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());
|
||||
}
|
||||
span = span.merge(last_tok.pos());
|
||||
}
|
||||
Ok(IntermediateValue::Paren(inner))
|
||||
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,86 +628,119 @@ 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();
|
||||
match q {
|
||||
'>' => Op::GreaterThanEqual,
|
||||
'<' => Op::LessThanEqual,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
match q {
|
||||
'>' => Op::GreaterThan,
|
||||
'<' => Op::LessThan,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
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,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
match q {
|
||||
'>' => Op::GreaterThan,
|
||||
'<' => 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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user