refactor value evaluation
This commit is contained in:
parent
0f590b5cd2
commit
596def3906
@ -35,7 +35,7 @@ impl FuncArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) struct CallArgs(pub HashMap<CallArg, Spanned<Value>>, pub Span);
|
pub(crate) struct CallArgs(pub HashMap<CallArg, Spanned<Value>>, pub Span);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||||
|
@ -34,7 +34,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let lightness = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
let lightness = match channels.pop() {
|
||||||
Some(Value::Dimension(n, _)) => n / Number::from(100),
|
Some(Value::Dimension(n, _)) => n / Number::from(100),
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
return Err((
|
return Err((
|
||||||
@ -49,7 +49,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
|||||||
None => return Err(("Missing element $lightness.", args.span()).into()),
|
None => return Err(("Missing element $lightness.", args.span()).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let saturation = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
let saturation = match channels.pop() {
|
||||||
Some(Value::Dimension(n, _)) => n / Number::from(100),
|
Some(Value::Dimension(n, _)) => n / Number::from(100),
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
return Err((
|
return Err((
|
||||||
@ -64,7 +64,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
|||||||
None => return Err(("Missing element $saturation.", args.span()).into()),
|
None => return Err(("Missing element $saturation.", args.span()).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let hue = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
let hue = match channels.pop() {
|
||||||
Some(Value::Dimension(n, _)) => n,
|
Some(Value::Dimension(n, _)) => n,
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
return Err((
|
return Err((
|
||||||
|
@ -37,7 +37,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let blue = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
let blue = match channels.pop() {
|
||||||
Some(Value::Dimension(n, Unit::None)) => n,
|
Some(Value::Dimension(n, Unit::None)) => n,
|
||||||
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
|
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
|
||||||
Some(v) if v.is_special_function() => {
|
Some(v) if v.is_special_function() => {
|
||||||
@ -64,7 +64,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
|||||||
None => return Err(("Missing element $blue.", args.span()).into()),
|
None => return Err(("Missing element $blue.", args.span()).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let green = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
let green = match channels.pop() {
|
||||||
Some(Value::Dimension(n, Unit::None)) => n,
|
Some(Value::Dimension(n, Unit::None)) => n,
|
||||||
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
|
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
|
||||||
Some(v) if v.is_special_function() => {
|
Some(v) if v.is_special_function() => {
|
||||||
@ -90,7 +90,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
|||||||
None => return Err(("Missing element $green.", args.span()).into()),
|
None => return Err(("Missing element $green.", args.span()).into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let red = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
let red = match channels.pop() {
|
||||||
Some(Value::Dimension(n, Unit::None)) => n,
|
Some(Value::Dimension(n, Unit::None)) => n,
|
||||||
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
|
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
|
||||||
Some(v) if v.is_special_function() => {
|
Some(v) if v.is_special_function() => {
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
args::CallArgs,
|
args::CallArgs,
|
||||||
common::{Brackets, ListSeparator, QuoteKind},
|
common::{Brackets, ListSeparator, QuoteKind},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::{HigherIntermediateValue, Parser, ValueVisitor},
|
||||||
unit::Unit,
|
unit::Unit,
|
||||||
value::{Number, Value},
|
value::{Number, Value},
|
||||||
};
|
};
|
||||||
@ -220,7 +220,7 @@ fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
_ => Brackets::Bracketed,
|
_ => Brackets::Bracketed,
|
||||||
},
|
},
|
||||||
v => {
|
v => {
|
||||||
if v.is_true(args.span())? {
|
if v.is_true() {
|
||||||
Brackets::Bracketed
|
Brackets::Bracketed
|
||||||
} else {
|
} else {
|
||||||
Brackets::None
|
Brackets::None
|
||||||
@ -248,16 +248,15 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
args.max_args(2)?;
|
args.max_args(2)?;
|
||||||
let list = parser.arg(&mut args, 0, "list")?.as_list();
|
let list = parser.arg(&mut args, 0, "list")?.as_list();
|
||||||
let value = parser.arg(&mut args, 1, "value")?;
|
let value = parser.arg(&mut args, 1, "value")?;
|
||||||
// TODO: find a way around this unwrap.
|
// TODO: find a way to propagate any errors here
|
||||||
// It should be impossible to hit as the arg is
|
|
||||||
// evaluated prior to checking equality, but
|
|
||||||
// it is still dirty.
|
|
||||||
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
|
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
|
||||||
let index = match list.into_iter().position(|v| {
|
let index = match list.into_iter().position(|v| {
|
||||||
v.equals(value.clone(), args.span())
|
ValueVisitor::new(parser, args.span())
|
||||||
.unwrap()
|
.equal(
|
||||||
.is_true(args.span())
|
HigherIntermediateValue::Literal(v),
|
||||||
.unwrap()
|
HigherIntermediateValue::Literal(value.clone()),
|
||||||
|
)
|
||||||
|
.map_or(false, |v| v.is_true())
|
||||||
}) {
|
}) {
|
||||||
Some(v) => Number::from(v + 1),
|
Some(v) => Number::from(v + 1),
|
||||||
None => return Ok(Value::Null),
|
None => return Ok(Value::Null),
|
||||||
@ -266,12 +265,11 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
let span = args.span();
|
|
||||||
let lists = parser
|
let lists = parser
|
||||||
.variadic_args(args)?
|
.variadic_args(args)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| Ok(x.node.eval(span)?.node.as_list()))
|
.map(|x| x.node.as_list())
|
||||||
.collect::<SassResult<Vec<Vec<Value>>>>()?;
|
.collect::<Vec<Vec<Value>>>();
|
||||||
|
|
||||||
let len = lists.iter().map(Vec::len).min().unwrap_or(0);
|
let len = lists.iter().map(Vec::len).min().unwrap_or(0);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ fn map_get(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(map.get(&key, args.span())?.unwrap_or(Value::Null))
|
Ok(map.get(&key, args.span(), parser)?.unwrap_or(Value::Null))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
@ -39,7 +39,7 @@ fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Value::bool(map.get(&key, args.span())?.is_some()))
|
Ok(Value::bool(map.get(&key, args.span(), parser)?.is_some()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
|
@ -7,9 +7,8 @@ use rand::Rng;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::CallArgs,
|
args::CallArgs,
|
||||||
common::Op,
|
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::{HigherIntermediateValue, Parser, ValueVisitor},
|
||||||
unit::Unit,
|
unit::Unit,
|
||||||
value::{Number, Value},
|
value::{Number, Value},
|
||||||
};
|
};
|
||||||
@ -207,14 +206,12 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
let mut min = nums.next().unwrap();
|
let mut min = nums.next().unwrap();
|
||||||
|
|
||||||
for num in nums {
|
for num in nums {
|
||||||
if Value::Dimension(num.0.clone(), num.1.clone())
|
if ValueVisitor::new(parser, span)
|
||||||
.cmp(
|
.less_than(
|
||||||
Value::Dimension(min.0.clone(), min.1.clone()),
|
HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())),
|
||||||
Op::LessThan,
|
HigherIntermediateValue::Literal(Value::Dimension(min.0.clone(), min.1.clone())),
|
||||||
span,
|
|
||||||
)?
|
)?
|
||||||
.node
|
.is_true()
|
||||||
.is_true(span)?
|
|
||||||
{
|
{
|
||||||
min = num;
|
min = num;
|
||||||
}
|
}
|
||||||
@ -239,14 +236,12 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
let mut max = nums.next().unwrap();
|
let mut max = nums.next().unwrap();
|
||||||
|
|
||||||
for num in nums {
|
for num in nums {
|
||||||
if Value::Dimension(num.0.clone(), num.1.clone())
|
if ValueVisitor::new(parser, span)
|
||||||
.cmp(
|
.greater_than(
|
||||||
Value::Dimension(max.0.clone(), max.1.clone()),
|
HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())),
|
||||||
Op::GreaterThan,
|
HigherIntermediateValue::Literal(Value::Dimension(max.0.clone(), max.1.clone())),
|
||||||
span,
|
|
||||||
)?
|
)?
|
||||||
.node
|
.is_true()
|
||||||
.is_true(span)?
|
|
||||||
{
|
{
|
||||||
max = num;
|
max = num;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,7 @@ use crate::{
|
|||||||
|
|
||||||
fn if_(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn if_(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
args.max_args(3)?;
|
args.max_args(3)?;
|
||||||
if parser
|
if parser.arg(&mut args, 0, "condition")?.is_true() {
|
||||||
.arg(&mut args, 0, "condition")?
|
|
||||||
.is_true(args.span())?
|
|
||||||
{
|
|
||||||
Ok(parser.arg(&mut args, 1, "if-true")?)
|
Ok(parser.arg(&mut args, 1, "if-true")?)
|
||||||
} else {
|
} else {
|
||||||
Ok(parser.arg(&mut args, 2, "if-false")?)
|
Ok(parser.arg(&mut args, 2, "if-false")?)
|
||||||
@ -77,10 +74,7 @@ fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
|||||||
fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
args.max_args(1)?;
|
||||||
let value = parser.arg(&mut args, 0, "value")?;
|
let value = parser.arg(&mut args, 0, "value")?;
|
||||||
Ok(Value::String(
|
Ok(Value::String(value.kind().to_owned(), QuoteKind::None))
|
||||||
value.kind(args.span())?.to_owned(),
|
|
||||||
QuoteKind::None,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
@ -177,7 +171,7 @@ fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
|
|||||||
};
|
};
|
||||||
let css = parser
|
let css = parser
|
||||||
.default_arg(&mut args, 1, "css", Value::False)
|
.default_arg(&mut args, 1, "css", Value::False)
|
||||||
.is_true(args.span())?;
|
.is_true();
|
||||||
let module = match parser.default_arg(&mut args, 2, "module", Value::Null) {
|
let module = match parser.default_arg(&mut args, 2, "module", Value::Null) {
|
||||||
Value::String(s, ..) => Some(s),
|
Value::String(s, ..) => Some(s),
|
||||||
Value::Null => None,
|
Value::Null => None,
|
||||||
|
@ -77,7 +77,7 @@ grass input.scss
|
|||||||
|
|
||||||
clippy::string_add,
|
clippy::string_add,
|
||||||
clippy::get_unwrap,
|
clippy::get_unwrap,
|
||||||
clippy::unit_arg,
|
// clippy::unit_arg,
|
||||||
clippy::wrong_self_convention,
|
clippy::wrong_self_convention,
|
||||||
clippy::items_after_statements,
|
clippy::items_after_statements,
|
||||||
clippy::shadow_reuse,
|
clippy::shadow_reuse,
|
||||||
|
@ -50,15 +50,13 @@ impl Toplevel {
|
|||||||
Toplevel::RuleSet(selector, Vec::new())
|
Toplevel::RuleSet(selector, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_style(&mut self, mut s: Style) -> SassResult<()> {
|
fn push_style(&mut self, s: Style) {
|
||||||
s = s.eval()?;
|
if s.value.is_null() {
|
||||||
if s.value.is_null(s.value.span)? {
|
return;
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
if let Toplevel::RuleSet(_, entries) = self {
|
if let Toplevel::RuleSet(_, entries) = self {
|
||||||
entries.push(BlockEntry::Style(Box::new(s)));
|
entries.push(BlockEntry::Style(Box::new(s)));
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_comment(&mut self, s: String) {
|
fn push_comment(&mut self, s: String) {
|
||||||
@ -96,7 +94,7 @@ impl Css {
|
|||||||
for rule in body {
|
for rule in body {
|
||||||
match rule {
|
match rule {
|
||||||
Stmt::RuleSet { .. } => vals.extend(self.parse_stmt(rule, extender)?),
|
Stmt::RuleSet { .. } => vals.extend(self.parse_stmt(rule, extender)?),
|
||||||
Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(s)?,
|
Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(s),
|
||||||
Stmt::Comment(s) => vals.get_mut(0).unwrap().push_comment(s),
|
Stmt::Comment(s) => vals.get_mut(0).unwrap().push_comment(s),
|
||||||
Stmt::Media(m) => {
|
Stmt::Media(m) => {
|
||||||
let MediaRule { query, body, .. } = *m;
|
let MediaRule { query, body, .. } = *m;
|
||||||
@ -117,10 +115,12 @@ impl Css {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
Stmt::Return(..) => unreachable!(),
|
Stmt::Return(..) => unreachable!(),
|
||||||
Stmt::AtRoot { body } => body
|
Stmt::AtRoot { body } => {
|
||||||
.into_iter()
|
body.into_iter().try_for_each(|r| -> SassResult<()> {
|
||||||
.map(|r| Ok(vals.extend(self.parse_stmt(r, extender)?)))
|
vals.append(&mut self.parse_stmt(r, extender)?);
|
||||||
.collect::<SassResult<()>>()?,
|
Ok(())
|
||||||
|
})?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
vals
|
vals
|
||||||
|
@ -178,10 +178,7 @@ impl<'a> Parser<'a> {
|
|||||||
} else {
|
} else {
|
||||||
CallArg::Named(name.into())
|
CallArg::Named(name.into())
|
||||||
},
|
},
|
||||||
{
|
self.parse_value_from_vec(val)?,
|
||||||
let val = self.parse_value_from_vec(val)?;
|
|
||||||
val.node.eval(val.span)?
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
span = span.merge(tok.pos());
|
span = span.merge(tok.pos());
|
||||||
return Ok(CallArgs(args, span));
|
return Ok(CallArgs(args, span));
|
||||||
@ -221,10 +218,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if is_splat {
|
if is_splat {
|
||||||
let val = {
|
|
||||||
let val = self.parse_value_from_vec(mem::take(&mut val))?;
|
let val = self.parse_value_from_vec(mem::take(&mut val))?;
|
||||||
val.node.eval(val.span)?
|
|
||||||
};
|
|
||||||
match val.node {
|
match val.node {
|
||||||
Value::ArgList(v) => {
|
Value::ArgList(v) => {
|
||||||
for arg in v {
|
for arg in v {
|
||||||
@ -233,13 +227,13 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
Value::List(v, ..) => {
|
Value::List(v, ..) => {
|
||||||
for arg in v {
|
for arg in v {
|
||||||
args.insert(CallArg::Positional(args.len()), arg.eval(val.span)?);
|
args.insert(CallArg::Positional(args.len()), arg.span(val.span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Map(v) => {
|
Value::Map(v) => {
|
||||||
for (name, arg) in v.entries() {
|
for (name, arg) in v.entries() {
|
||||||
let name = name.to_css_string(val.span)?.to_string();
|
let name = name.to_css_string(val.span)?.to_string();
|
||||||
args.insert(CallArg::Named(name.into()), arg.eval(val.span)?);
|
args.insert(CallArg::Named(name.into()), arg.span(val.span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -253,10 +247,7 @@ impl<'a> Parser<'a> {
|
|||||||
} else {
|
} else {
|
||||||
CallArg::Named(name.as_str().into())
|
CallArg::Named(name.as_str().into())
|
||||||
},
|
},
|
||||||
{
|
self.parse_value_from_vec(mem::take(&mut val))?,
|
||||||
let val = self.parse_value_from_vec(mem::take(&mut val))?;
|
|
||||||
val.node.eval(val.span)?
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,16 +345,13 @@ impl<'a> Parser<'a> {
|
|||||||
node: arg_list,
|
node: arg_list,
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
)?;
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let val = match args.get(idx, arg.name.clone()) {
|
let val = match args.get(idx, arg.name.clone()) {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => match arg.default.as_mut() {
|
None => match arg.default.as_mut() {
|
||||||
Some(v) => {
|
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||||||
let val = self.parse_value_from_vec(mem::take(v))?;
|
|
||||||
val.node.eval(val.span)?
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
return Err(
|
return Err(
|
||||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||||
@ -373,8 +361,8 @@ impl<'a> Parser<'a> {
|
|||||||
};
|
};
|
||||||
self.scopes
|
self.scopes
|
||||||
.last_mut()
|
.last_mut()
|
||||||
.insert_var(arg.name.clone(), val.clone())?;
|
.insert_var(arg.name.clone(), val.clone());
|
||||||
scope.insert_var(mem::take(&mut arg.name), val)?;
|
scope.insert_var(mem::take(&mut arg.name), val);
|
||||||
}
|
}
|
||||||
self.scopes.pop();
|
self.scopes.pop();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -24,6 +24,8 @@ use crate::{
|
|||||||
|
|
||||||
use common::{Branch, NeverEmptyVec, SelectorOrStyle};
|
use common::{Branch, NeverEmptyVec, SelectorOrStyle};
|
||||||
|
|
||||||
|
pub(crate) use value::{HigherIntermediateValue, ValueVisitor};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
mod function;
|
mod function;
|
||||||
@ -405,10 +407,7 @@ impl<'a> Parser<'a> {
|
|||||||
Some(Token { kind: '}', .. }) => {}
|
Some(Token { kind: '}', .. }) => {}
|
||||||
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
|
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
|
||||||
}
|
}
|
||||||
Ok(Spanned {
|
Ok(val.map_node(Value::unquote))
|
||||||
node: val.node.eval(val.span)?.node.unquote(),
|
|
||||||
span: val.span,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_interpolation_as_string(&mut self) -> SassResult<Cow<'static, str>> {
|
pub fn parse_interpolation_as_string(&mut self) -> SassResult<Cow<'static, str>> {
|
||||||
@ -560,8 +559,7 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
for branch in branches {
|
for branch in branches {
|
||||||
self.span_before = branch.cond.first().unwrap().pos;
|
self.span_before = branch.cond.first().unwrap().pos;
|
||||||
let cond = self.parse_value_from_vec(branch.cond)?;
|
if self.parse_value_from_vec(branch.cond)?.node.is_true() {
|
||||||
if cond.node.is_true(cond.span)? {
|
|
||||||
return Parser {
|
return Parser {
|
||||||
toks: &mut branch.toks.into_iter().peekmore(),
|
toks: &mut branch.toks.into_iter().peekmore(),
|
||||||
map: self.map,
|
map: self.map,
|
||||||
@ -668,7 +666,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
let from_val = self.parse_value_from_vec(from_toks)?;
|
let from_val = self.parse_value_from_vec(from_toks)?;
|
||||||
let from = match from_val.node.eval(from_val.span)?.node {
|
let from = match from_val.node {
|
||||||
Value::Dimension(n, _) => match n.to_integer().to_isize() {
|
Value::Dimension(n, _) => match n.to_integer().to_isize() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return Err((format!("{} is not a int.", n), from_val.span).into()),
|
None => return Err((format!("{} is not a int.", n), from_val.span).into()),
|
||||||
@ -685,7 +683,7 @@ impl<'a> Parser<'a> {
|
|||||||
let to_toks = read_until_open_curly_brace(self.toks)?;
|
let to_toks = read_until_open_curly_brace(self.toks)?;
|
||||||
self.toks.next();
|
self.toks.next();
|
||||||
let to_val = self.parse_value_from_vec(to_toks)?;
|
let to_val = self.parse_value_from_vec(to_toks)?;
|
||||||
let to = match to_val.node.eval(to_val.span)?.node {
|
let to = match to_val.node {
|
||||||
Value::Dimension(n, _) => match n.to_integer().to_isize() {
|
Value::Dimension(n, _) => match n.to_integer().to_isize() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return Err((format!("{} is not a int.", n), to_val.span).into()),
|
None => return Err((format!("{} is not a int.", n), to_val.span).into()),
|
||||||
@ -725,7 +723,7 @@ impl<'a> Parser<'a> {
|
|||||||
node: Value::Dimension(Number::from(i), Unit::None),
|
node: Value::Dimension(Number::from(i), Unit::None),
|
||||||
span: var.span,
|
span: var.span,
|
||||||
},
|
},
|
||||||
)?;
|
);
|
||||||
if self.in_function {
|
if self.in_function {
|
||||||
let these_stmts = Parser {
|
let these_stmts = Parser {
|
||||||
toks: &mut body.clone().into_iter().peekmore(),
|
toks: &mut body.clone().into_iter().peekmore(),
|
||||||
@ -797,7 +795,7 @@ impl<'a> Parser<'a> {
|
|||||||
let mut stmts = Vec::new();
|
let mut stmts = Vec::new();
|
||||||
let mut val = self.parse_value_from_vec(cond.clone())?;
|
let mut val = self.parse_value_from_vec(cond.clone())?;
|
||||||
self.scopes.push(self.scopes.last().clone());
|
self.scopes.push(self.scopes.last().clone());
|
||||||
while val.node.is_true(val.span)? {
|
while val.node.is_true() {
|
||||||
if self.in_function {
|
if self.in_function {
|
||||||
let these_stmts = Parser {
|
let these_stmts = Parser {
|
||||||
toks: &mut body.clone().into_iter().peekmore(),
|
toks: &mut body.clone().into_iter().peekmore(),
|
||||||
@ -881,8 +879,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
let iter_val_toks = read_until_open_curly_brace(self.toks)?;
|
let iter_val_toks = read_until_open_curly_brace(self.toks)?;
|
||||||
let iter_val = self.parse_value_from_vec(iter_val_toks)?;
|
let iter = self.parse_value_from_vec(iter_val_toks)?.node.as_list();
|
||||||
let iter = iter_val.node.eval(iter_val.span)?.node.as_list();
|
|
||||||
self.toks.next();
|
self.toks.next();
|
||||||
self.whitespace();
|
self.whitespace();
|
||||||
let mut body = read_until_closing_curly_brace(self.toks)?;
|
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||||||
@ -905,7 +902,7 @@ impl<'a> Parser<'a> {
|
|||||||
node: this_iterator[0].clone(),
|
node: this_iterator[0].clone(),
|
||||||
span: vars[0].span,
|
span: vars[0].span,
|
||||||
},
|
},
|
||||||
)?;
|
);
|
||||||
} else {
|
} else {
|
||||||
self.scopes.last_mut().insert_var(
|
self.scopes.last_mut().insert_var(
|
||||||
&vars[0].node,
|
&vars[0].node,
|
||||||
@ -913,7 +910,7 @@ impl<'a> Parser<'a> {
|
|||||||
node: Value::List(this_iterator, ListSeparator::Space, Brackets::None),
|
node: Value::List(this_iterator, ListSeparator::Space, Brackets::None),
|
||||||
span: vars[0].span,
|
span: vars[0].span,
|
||||||
},
|
},
|
||||||
)?;
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (var, val) in vars.clone().into_iter().zip(
|
for (var, val) in vars.clone().into_iter().zip(
|
||||||
@ -927,7 +924,7 @@ impl<'a> Parser<'a> {
|
|||||||
node: val,
|
node: val,
|
||||||
span: var.span,
|
span: var.span,
|
||||||
},
|
},
|
||||||
)?;
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
395
src/parse/value/css_function.rs
Normal file
395
src/parse/value/css_function.rs
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
use std::{borrow::Borrow, iter::Iterator};
|
||||||
|
|
||||||
|
use codemap::Spanned;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::SassResult,
|
||||||
|
utils::{
|
||||||
|
as_hex, hex_char_for, is_name, peek_ident_no_interpolation, peek_until_closing_curly_brace,
|
||||||
|
peek_whitespace,
|
||||||
|
},
|
||||||
|
value::Value,
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::Parser;
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
pub(super) fn eat_calc_args(&mut self, buf: &mut String) -> SassResult<()> {
|
||||||
|
buf.reserve(2);
|
||||||
|
buf.push('(');
|
||||||
|
let mut nesting = 0;
|
||||||
|
while let Some(tok) = self.toks.next() {
|
||||||
|
match tok.kind {
|
||||||
|
' ' | '\t' | '\n' => {
|
||||||
|
self.whitespace();
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
'#' => {
|
||||||
|
if let Some(Token { kind: '{', pos }) = self.toks.peek() {
|
||||||
|
self.span_before = *pos;
|
||||||
|
self.toks.next();
|
||||||
|
let interpolation = self.parse_interpolation()?;
|
||||||
|
buf.push_str(&interpolation.node.to_css_string(interpolation.span)?);
|
||||||
|
} else {
|
||||||
|
buf.push('#');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
nesting += 1;
|
||||||
|
buf.push('(');
|
||||||
|
}
|
||||||
|
')' => {
|
||||||
|
if nesting == 0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
nesting -= 1;
|
||||||
|
buf.push(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c => buf.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.push(')');
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn eat_progid(&mut self) -> SassResult<String> {
|
||||||
|
let mut string = String::new();
|
||||||
|
let mut span = self.toks.peek().unwrap().pos();
|
||||||
|
while let Some(tok) = self.toks.next() {
|
||||||
|
span = span.merge(tok.pos());
|
||||||
|
match tok.kind {
|
||||||
|
'a'..='z' | 'A'..='Z' | '.' => {
|
||||||
|
string.push(tok.kind);
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
self.eat_calc_args(&mut string)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => return Err(("expected \"(\".", span).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn try_eat_url(&mut self) -> SassResult<Option<String>> {
|
||||||
|
let mut buf = String::from("url(");
|
||||||
|
peek_whitespace(self.toks);
|
||||||
|
while let Some(tok) = self.toks.peek() {
|
||||||
|
let kind = tok.kind;
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if kind == '!'
|
||||||
|
|| kind == '%'
|
||||||
|
|| kind == '&'
|
||||||
|
|| (kind >= '*' && kind <= '~')
|
||||||
|
|| kind as u32 >= 0x0080
|
||||||
|
{
|
||||||
|
buf.push(kind);
|
||||||
|
} else if kind == '\\' {
|
||||||
|
buf.push_str(&self.peek_escape()?);
|
||||||
|
} else if kind == '#' {
|
||||||
|
if let Some(Token { kind: '{', .. }) = self.toks.peek() {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
let interpolation = self.peek_interpolation()?;
|
||||||
|
match interpolation.node {
|
||||||
|
Value::String(ref s, ..) => buf.push_str(s),
|
||||||
|
v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
buf.push('#');
|
||||||
|
}
|
||||||
|
} else if kind == ')' {
|
||||||
|
buf.push(')');
|
||||||
|
self.toks.truncate_iterator_to_cursor();
|
||||||
|
self.toks.next();
|
||||||
|
return Ok(Some(buf));
|
||||||
|
} else if kind.is_whitespace() {
|
||||||
|
peek_whitespace(self.toks);
|
||||||
|
let next = match self.toks.peek() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
if next.kind == ')' {
|
||||||
|
buf.push(')');
|
||||||
|
self.toks.truncate_iterator_to_cursor();
|
||||||
|
self.toks.next();
|
||||||
|
return Ok(Some(buf));
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.toks.reset_cursor();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn try_parse_min_max(
|
||||||
|
&mut self,
|
||||||
|
fn_name: &str,
|
||||||
|
allow_comma: bool,
|
||||||
|
) -> SassResult<Option<String>> {
|
||||||
|
let mut buf = if allow_comma {
|
||||||
|
format!("{}(", fn_name)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
peek_whitespace(self.toks);
|
||||||
|
while let Some(tok) = self.toks.peek() {
|
||||||
|
let kind = tok.kind;
|
||||||
|
match kind {
|
||||||
|
'+' | '-' | '0'..='9' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if let Some(number) = self.peek_number()? {
|
||||||
|
buf.push(kind);
|
||||||
|
buf.push_str(&number);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'#' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if let Some(Token { kind: '{', .. }) = self.toks.peek() {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
let interpolation = self.peek_interpolation()?;
|
||||||
|
match interpolation.node {
|
||||||
|
Value::String(ref s, ..) => buf.push_str(s),
|
||||||
|
v => buf.push_str(v.to_css_string(interpolation.span)?.borrow()),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'c' | 'C' => {
|
||||||
|
if let Some(name) = self.try_parse_min_max_function("calc")? {
|
||||||
|
buf.push_str(&name);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'e' | 'E' => {
|
||||||
|
if let Some(name) = self.try_parse_min_max_function("env")? {
|
||||||
|
buf.push_str(&name);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'v' | 'V' => {
|
||||||
|
if let Some(name) = self.try_parse_min_max_function("var")? {
|
||||||
|
buf.push_str(&name);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'(' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push('(');
|
||||||
|
if let Some(val) = self.try_parse_min_max(fn_name, false)? {
|
||||||
|
buf.push_str(&val);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'm' | 'M' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
match self.toks.peek() {
|
||||||
|
Some(Token { kind: 'i', .. }) | Some(Token { kind: 'I', .. }) => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: 'n', .. }) | Some(Token { kind: 'N', .. }))
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
buf.push_str("min(")
|
||||||
|
}
|
||||||
|
Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: 'x', .. }) | Some(Token { kind: 'X', .. }))
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
buf.push_str("max(")
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(val) = self.try_parse_min_max(fn_name, false)? {
|
||||||
|
buf.push_str(&val);
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
|
||||||
|
peek_whitespace(self.toks);
|
||||||
|
|
||||||
|
let next = match self.toks.peek() {
|
||||||
|
Some(tok) => tok,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
match next.kind {
|
||||||
|
')' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push(')');
|
||||||
|
return Ok(Some(buf));
|
||||||
|
}
|
||||||
|
'+' | '-' | '*' | '/' => {
|
||||||
|
buf.push(' ');
|
||||||
|
buf.push(next.kind);
|
||||||
|
buf.push(' ');
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
}
|
||||||
|
',' => {
|
||||||
|
if !allow_comma {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push(',');
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
|
||||||
|
peek_whitespace(self.toks);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, unused_mut, unused_variables, unused_assignments)]
|
||||||
|
fn try_parse_min_max_function(&mut self, fn_name: &'static str) -> SassResult<Option<String>> {
|
||||||
|
let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?.node;
|
||||||
|
ident.make_ascii_lowercase();
|
||||||
|
if ident != fn_name {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
ident.push('(');
|
||||||
|
todo!("special functions inside `min()` or `max()`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Methods required to do arbitrary lookahead
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
fn peek_number(&mut self) -> SassResult<Option<String>> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
let num = self.peek_whole_number();
|
||||||
|
buf.push_str(&num);
|
||||||
|
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
|
||||||
|
if let Some(Token { kind: '.', .. }) = self.toks.peek() {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
let num = self.peek_whole_number();
|
||||||
|
if num.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
buf.push_str(&num);
|
||||||
|
} else {
|
||||||
|
self.toks.move_cursor_back().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = match self.toks.peek() {
|
||||||
|
Some(tok) => tok,
|
||||||
|
None => return Ok(Some(buf)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match next.kind {
|
||||||
|
'a'..='z' | 'A'..='Z' | '-' | '_' | '\\' => {
|
||||||
|
let unit = peek_ident_no_interpolation(self.toks, true, self.span_before)?.node;
|
||||||
|
|
||||||
|
buf.push_str(&unit);
|
||||||
|
}
|
||||||
|
'%' => {
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
buf.push('%');
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_whole_number(&mut self) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
while let Some(tok) = self.toks.peek() {
|
||||||
|
if tok.kind.is_ascii_digit() {
|
||||||
|
buf.push(tok.kind);
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
} else {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_interpolation(&mut self) -> SassResult<Spanned<Value>> {
|
||||||
|
let vec = peek_until_closing_curly_brace(self.toks)?;
|
||||||
|
self.toks.advance_cursor();
|
||||||
|
let val = self.parse_value_from_vec(vec)?;
|
||||||
|
Ok(Spanned {
|
||||||
|
node: val.node.unquote(),
|
||||||
|
span: val.span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_escape(&mut self) -> SassResult<String> {
|
||||||
|
let mut value = 0;
|
||||||
|
let first = match self.toks.peek() {
|
||||||
|
Some(t) => *t,
|
||||||
|
None => return Ok(String::new()),
|
||||||
|
};
|
||||||
|
let mut span = first.pos;
|
||||||
|
if first.kind == '\n' {
|
||||||
|
return Err(("Expected escape sequence.", first.pos()).into());
|
||||||
|
} else if first.kind.is_ascii_hexdigit() {
|
||||||
|
for _ in 0..6 {
|
||||||
|
let next = match self.toks.peek() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
if !next.kind.is_ascii_hexdigit() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
value *= 16;
|
||||||
|
value += as_hex(next.kind);
|
||||||
|
span = span.merge(next.pos);
|
||||||
|
self.toks.peek_forward(1);
|
||||||
|
}
|
||||||
|
if self.toks.peek().is_some() && self.toks.peek().unwrap().kind.is_whitespace() {
|
||||||
|
self.toks.peek_forward(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = self.toks.peek_forward(1).unwrap().kind as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
let c = std::char::from_u32(value).ok_or(("Invalid escape sequence.", span))?;
|
||||||
|
if is_name(c) {
|
||||||
|
Ok(c.to_string())
|
||||||
|
} else if value <= 0x1F || value == 0x7F {
|
||||||
|
let mut buf = String::with_capacity(4);
|
||||||
|
buf.push('\\');
|
||||||
|
if value > 0xF {
|
||||||
|
buf.push(hex_char_for(value >> 4));
|
||||||
|
}
|
||||||
|
buf.push(hex_char_for(value & 0xF));
|
||||||
|
buf.push(' ');
|
||||||
|
Ok(buf)
|
||||||
|
} else {
|
||||||
|
Ok(format!("\\{}", c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
905
src/parse/value/eval.rs
Normal file
905
src/parse/value/eval.rs
Normal file
@ -0,0 +1,905 @@
|
|||||||
|
#![allow(unused_variables)]
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use codemap::{Span, Spanned};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
args::CallArgs,
|
||||||
|
common::{Op, QuoteKind},
|
||||||
|
error::SassResult,
|
||||||
|
unit::{Unit, UNIT_CONVERSION_TABLE},
|
||||||
|
value::{SassFunction, Value},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::super::Parser;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum HigherIntermediateValue {
|
||||||
|
Literal(Value),
|
||||||
|
/// A function that hasn't yet been evaluated
|
||||||
|
Function(SassFunction, CallArgs),
|
||||||
|
BinaryOp(Box<Self>, Op, Box<Self>),
|
||||||
|
UnaryOp(Op, Box<Self>),
|
||||||
|
Paren(Box<Self>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HigherIntermediateValue {
|
||||||
|
pub const fn span(self, span: Span) -> Spanned<Self> {
|
||||||
|
Spanned { node: self, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
fn call_function(&mut self, function: SassFunction, args: CallArgs) -> SassResult<Value> {
|
||||||
|
function.call(args, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ValueVisitor<'a, 'b: 'a> {
|
||||||
|
parser: &'a mut Parser<'b>,
|
||||||
|
span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
|
||||||
|
pub fn new(parser: &'a mut Parser<'b>, span: Span) -> Self {
|
||||||
|
Self { parser, span }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&mut self, value: HigherIntermediateValue) -> SassResult<Value> {
|
||||||
|
match value {
|
||||||
|
HigherIntermediateValue::Literal(v) => Ok(v),
|
||||||
|
HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2),
|
||||||
|
HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val),
|
||||||
|
HigherIntermediateValue::Paren(val) => self.eval(*val),
|
||||||
|
HigherIntermediateValue::Function(function, args) => {
|
||||||
|
self.parser.call_function(function, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bin_op(
|
||||||
|
&mut self,
|
||||||
|
val1: HigherIntermediateValue,
|
||||||
|
op: Op,
|
||||||
|
val2: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let mut val1 = self.paren_or_unary(val1)?;
|
||||||
|
let val2 = self.paren_or_unary(val2)?;
|
||||||
|
|
||||||
|
if let HigherIntermediateValue::BinaryOp(val1_1, op2, val1_2) = val1 {
|
||||||
|
if op2.precedence() > op.precedence() {
|
||||||
|
val1 = HigherIntermediateValue::Literal(self.bin_op(*val1_1, op2, *val1_2)?);
|
||||||
|
} else {
|
||||||
|
let val2 = HigherIntermediateValue::Literal(self.bin_op(*val1_2, op, val2)?);
|
||||||
|
return self.bin_op(*val1_1, op2, val2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match op {
|
||||||
|
Op::Plus => self.add(val1, val2)?,
|
||||||
|
Op::Minus => self.sub(val1, val2)?,
|
||||||
|
Op::Mul => self.mul(val1, val2)?,
|
||||||
|
Op::Div => self.div(val1, val2)?,
|
||||||
|
Op::Rem => self.rem(val1, val2)?,
|
||||||
|
Op::And => Self::and(val1, val2)?,
|
||||||
|
Op::Or => Self::or(val1, val2)?,
|
||||||
|
Op::Equal => self.equal(val1, val2)?,
|
||||||
|
Op::NotEqual => self.not_equal(val1, val2)?,
|
||||||
|
Op::GreaterThan => self.greater_than(val1, val2)?,
|
||||||
|
Op::GreaterThanEqual => self.greater_than_or_equal(val1, val2)?,
|
||||||
|
Op::LessThan => self.less_than(val1, val2)?,
|
||||||
|
Op::LessThanEqual => self.less_than_or_equal(val1, val2)?,
|
||||||
|
Op::Not => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary_op(&mut self, op: Op, val: HigherIntermediateValue) -> SassResult<Value> {
|
||||||
|
let val = self.eval(val)?;
|
||||||
|
match op {
|
||||||
|
Op::Minus => self.unary_minus(val),
|
||||||
|
Op::Not => Self::unary_not(&val),
|
||||||
|
Op::Plus => self.unary_plus(val),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary_minus(&self, val: Value) -> SassResult<Value> {
|
||||||
|
Ok(match val {
|
||||||
|
Value::Dimension(n, u) => Value::Dimension(-n, u),
|
||||||
|
v => Value::String(format!("-{}", v.to_css_string(self.span)?), QuoteKind::None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary_plus(&self, val: Value) -> SassResult<Value> {
|
||||||
|
Ok(match val {
|
||||||
|
v @ Value::Dimension(..) => v,
|
||||||
|
v => Value::String(format!("+{}", v.to_css_string(self.span)?), QuoteKind::None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unary_not(val: &Value) -> SassResult<Value> {
|
||||||
|
Ok(Value::bool(!val.is_true()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paren(&mut self, val: HigherIntermediateValue) -> SassResult<HigherIntermediateValue> {
|
||||||
|
Ok(if let HigherIntermediateValue::Paren(v) = val {
|
||||||
|
HigherIntermediateValue::Literal(self.eval(*v)?)
|
||||||
|
} else {
|
||||||
|
val
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paren_or_unary(
|
||||||
|
&mut self,
|
||||||
|
val: HigherIntermediateValue,
|
||||||
|
) -> SassResult<HigherIntermediateValue> {
|
||||||
|
let val = self.paren(val)?;
|
||||||
|
Ok(match val {
|
||||||
|
HigherIntermediateValue::UnaryOp(op, val) => {
|
||||||
|
HigherIntermediateValue::Literal(self.unary_op(op, *val)?)
|
||||||
|
}
|
||||||
|
HigherIntermediateValue::Function(function, args) => {
|
||||||
|
HigherIntermediateValue::Literal(self.parser.call_function(function, args)?)
|
||||||
|
}
|
||||||
|
val => val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(match left {
|
||||||
|
Value::Map(..) | Value::Function(..) => {
|
||||||
|
return Err((
|
||||||
|
format!("{} isn't a valid CSS value.", left.inspect(self.span)?),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
Value::ArgList(..) => todo!(),
|
||||||
|
Value::Important | Value::True | Value::False => match right {
|
||||||
|
Value::String(s, QuoteKind::Quoted) => Value::String(
|
||||||
|
format!("{}{}", left.to_css_string(self.span)?, s),
|
||||||
|
QuoteKind::Quoted,
|
||||||
|
),
|
||||||
|
Value::Null => {
|
||||||
|
Value::String(left.to_css_string(self.span)?.into_owned(), QuoteKind::None)
|
||||||
|
}
|
||||||
|
_ => Value::String(
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
left.to_css_string(self.span)?,
|
||||||
|
right.to_css_string(self.span)?
|
||||||
|
),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Value::Null => match right {
|
||||||
|
Value::Null => Value::Null,
|
||||||
|
_ => Value::String(
|
||||||
|
right.to_css_string(self.span)?.into_owned(),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Value::Dimension(num, unit) => match right {
|
||||||
|
Value::Dimension(num2, unit2) => {
|
||||||
|
if !unit.comparable(&unit2) {
|
||||||
|
return Err((
|
||||||
|
format!("Incompatible units {} and {}.", unit2, unit),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if unit == unit2 {
|
||||||
|
Value::Dimension(num + num2, unit)
|
||||||
|
} else if unit == Unit::None {
|
||||||
|
Value::Dimension(num + num2, unit2)
|
||||||
|
} else if unit2 == Unit::None {
|
||||||
|
Value::Dimension(num + num2, unit)
|
||||||
|
} else {
|
||||||
|
Value::Dimension(
|
||||||
|
num + num2
|
||||||
|
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
||||||
|
[unit2.to_string().as_str()]
|
||||||
|
.clone(),
|
||||||
|
unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String(s, q) => Value::String(format!("{}{}{}", num, unit, s), q),
|
||||||
|
Value::Null => Value::String(format!("{}{}", num, unit), QuoteKind::None),
|
||||||
|
Value::True | Value::False | Value::List(..) => Value::String(
|
||||||
|
format!("{}{}{}", num, unit, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Map(..) | Value::Function(..) => {
|
||||||
|
return Err((
|
||||||
|
format!("{} isn't a valid CSS value.", right.inspect(self.span)?),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{}{} + {}\".",
|
||||||
|
num,
|
||||||
|
unit,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::Color(c) => match right {
|
||||||
|
Value::String(s, q) => Value::String(format!("{}{}", c, s), q),
|
||||||
|
Value::Null => Value::String(c.to_string(), QuoteKind::None),
|
||||||
|
Value::List(..) => Value::String(
|
||||||
|
format!("{}{}", c, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} + {}\".",
|
||||||
|
c,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::String(text, quotes) => match right {
|
||||||
|
Value::String(text2, ..) => Value::String(text + &text2, quotes),
|
||||||
|
_ => Value::String(text + &right.to_css_string(self.span)?, quotes),
|
||||||
|
},
|
||||||
|
Value::List(..) => match right {
|
||||||
|
Value::String(s, q) => {
|
||||||
|
Value::String(format!("{}{}", left.to_css_string(self.span)?, s), q)
|
||||||
|
}
|
||||||
|
_ => Value::String(
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
left.to_css_string(self.span)?,
|
||||||
|
right.to_css_string(self.span)?
|
||||||
|
),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sub(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(match left {
|
||||||
|
Value::Null => Value::String(
|
||||||
|
format!("-{}", right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Dimension(num, unit) => match right {
|
||||||
|
Value::Dimension(num2, unit2) => {
|
||||||
|
if !unit.comparable(&unit2) {
|
||||||
|
return Err((
|
||||||
|
format!("Incompatible units {} and {}.", unit2, unit),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if unit == unit2 {
|
||||||
|
Value::Dimension(num - num2, unit)
|
||||||
|
} else if unit == Unit::None {
|
||||||
|
Value::Dimension(num - num2, unit2)
|
||||||
|
} else if unit2 == Unit::None {
|
||||||
|
Value::Dimension(num - num2, unit)
|
||||||
|
} else {
|
||||||
|
Value::Dimension(
|
||||||
|
num - num2
|
||||||
|
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
||||||
|
[unit2.to_string().as_str()]
|
||||||
|
.clone(),
|
||||||
|
unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::List(..) | Value::String(..) => Value::String(
|
||||||
|
format!("{}{}-{}", num, unit, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Map(..) | Value::Function(..) => {
|
||||||
|
return Err((
|
||||||
|
format!("{} isn't a valid CSS value.", right.inspect(self.span)?),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
Value::Color(c) => match right {
|
||||||
|
Value::String(s, q) => {
|
||||||
|
Value::String(format!("{}-{}{}{}", c, q, s, q), QuoteKind::None)
|
||||||
|
}
|
||||||
|
Value::Null => Value::String(format!("{}-", c), QuoteKind::None),
|
||||||
|
Value::Dimension(..) | Value::Color(..) => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} - {}\".",
|
||||||
|
c,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
_ => Value::String(
|
||||||
|
format!("{}-{}", c, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Value::String(..) => Value::String(
|
||||||
|
format!(
|
||||||
|
"{}-{}",
|
||||||
|
left.to_css_string(self.span)?,
|
||||||
|
right.to_css_string(self.span)?
|
||||||
|
),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::List(..) => match right {
|
||||||
|
Value::String(s, q) => Value::String(
|
||||||
|
format!("{}-{}{}{}", left.to_css_string(self.span)?, q, s, q),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
_ => Value::String(
|
||||||
|
format!(
|
||||||
|
"{}-{}",
|
||||||
|
left.to_css_string(self.span)?,
|
||||||
|
right.to_css_string(self.span)?
|
||||||
|
),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
_ => match right {
|
||||||
|
Value::String(s, q) => Value::String(
|
||||||
|
format!("{}-{}{}{}", left.to_css_string(self.span)?, q, s, q),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Null => Value::String(
|
||||||
|
format!("{}-", left.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
_ => Value::String(
|
||||||
|
format!(
|
||||||
|
"{}-{}",
|
||||||
|
left.to_css_string(self.span)?,
|
||||||
|
right.to_css_string(self.span)?
|
||||||
|
),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mul(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(match left {
|
||||||
|
Value::Null => todo!(),
|
||||||
|
Value::Dimension(num, unit) => match right {
|
||||||
|
Value::Dimension(num2, unit2) => {
|
||||||
|
if unit == Unit::None {
|
||||||
|
Value::Dimension(num * num2, unit2)
|
||||||
|
} else if unit2 == Unit::None {
|
||||||
|
Value::Dimension(num * num2, unit)
|
||||||
|
} else if let Unit::Mul(u) = unit {
|
||||||
|
let mut unit1 = u.into_vec();
|
||||||
|
unit1.push(unit2);
|
||||||
|
Value::Dimension(num * num2, Unit::Mul(unit1.into_boxed_slice()))
|
||||||
|
} else if let Unit::Mul(u2) = unit2 {
|
||||||
|
let mut u = vec![unit];
|
||||||
|
u.append(&mut u2.into_vec());
|
||||||
|
Value::Dimension(num * num2, Unit::Mul(u.into_boxed_slice()))
|
||||||
|
} else {
|
||||||
|
Value::Dimension(
|
||||||
|
num * num2,
|
||||||
|
Unit::Mul(vec![unit, unit2].into_boxed_slice()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{}{} * {}\".",
|
||||||
|
num,
|
||||||
|
unit,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} * {}\".",
|
||||||
|
left.inspect(self.span)?,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn div(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(match left {
|
||||||
|
Value::Null => todo!(),
|
||||||
|
Value::Dimension(num, unit) => match right {
|
||||||
|
Value::Dimension(num2, unit2) => {
|
||||||
|
if !unit.comparable(&unit2) {
|
||||||
|
return Err((
|
||||||
|
format!("Incompatible units {} and {}.", unit2, unit),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if unit == unit2 {
|
||||||
|
Value::Dimension(num / num2, Unit::None)
|
||||||
|
} else if unit == Unit::None {
|
||||||
|
todo!("inverse units")
|
||||||
|
} else if unit2 == Unit::None {
|
||||||
|
Value::Dimension(num / num2, unit)
|
||||||
|
} else {
|
||||||
|
Value::Dimension(
|
||||||
|
num / (num2
|
||||||
|
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
||||||
|
[unit2.to_string().as_str()]
|
||||||
|
.clone()),
|
||||||
|
Unit::None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String(s, q) => {
|
||||||
|
Value::String(format!("{}{}/{}{}{}", num, unit, q, s, q), QuoteKind::None)
|
||||||
|
}
|
||||||
|
Value::List(..)
|
||||||
|
| Value::True
|
||||||
|
| Value::False
|
||||||
|
| Value::Important
|
||||||
|
| Value::Color(..) => Value::String(
|
||||||
|
format!("{}{}/{}", num, unit, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Null => Value::String(format!("{}{}/", num, unit), QuoteKind::None),
|
||||||
|
Value::Map(..) | Value::Function(..) => {
|
||||||
|
return Err((
|
||||||
|
format!("{} isn't a valid CSS value.", right.inspect(self.span)?),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
Value::ArgList(..) => todo!(),
|
||||||
|
},
|
||||||
|
Value::Color(c) => match right {
|
||||||
|
Value::String(s, q) => {
|
||||||
|
Value::String(format!("{}/{}{}{}", c, q, s, q), QuoteKind::None)
|
||||||
|
}
|
||||||
|
Value::Null => Value::String(format!("{}/", c), QuoteKind::None),
|
||||||
|
Value::Dimension(..) | Value::Color(..) => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} / {}\".",
|
||||||
|
c,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
_ => Value::String(
|
||||||
|
format!("{}/{}", c, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Value::String(s1, q1) => match right {
|
||||||
|
Value::String(s2, q2) => Value::String(
|
||||||
|
format!("{}{}{}/{}{}{}", q1, s1, q1, q2, s2, q2),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Important
|
||||||
|
| Value::True
|
||||||
|
| Value::False
|
||||||
|
| Value::Dimension(..)
|
||||||
|
| Value::Color(..) => Value::String(
|
||||||
|
format!("{}{}{}/{}", q1, s1, q1, right.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Null => Value::String(format!("{}{}{}/", q1, s1, q1), QuoteKind::None),
|
||||||
|
_ => todo!(),
|
||||||
|
},
|
||||||
|
_ => match right {
|
||||||
|
Value::String(s, q) => Value::String(
|
||||||
|
format!("{}/{}{}{}", left.to_css_string(self.span)?, q, s, q),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
Value::Null => Value::String(
|
||||||
|
format!("{}/", left.to_css_string(self.span)?),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
_ => Value::String(
|
||||||
|
format!(
|
||||||
|
"{}/{}",
|
||||||
|
left.to_css_string(self.span)?,
|
||||||
|
right.to_css_string(self.span)?
|
||||||
|
),
|
||||||
|
QuoteKind::None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rem(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(match left {
|
||||||
|
Value::Dimension(n, u) => match right {
|
||||||
|
Value::Dimension(n2, u2) => {
|
||||||
|
if !u.comparable(&u2) {
|
||||||
|
return Err(
|
||||||
|
(format!("Incompatible units {} and {}.", u2, u), self.span).into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if u == u2 {
|
||||||
|
Value::Dimension(n % n2, u)
|
||||||
|
} else if u == Unit::None {
|
||||||
|
Value::Dimension(n % n2, u2)
|
||||||
|
} else if u2 == Unit::None {
|
||||||
|
Value::Dimension(n % n2, u)
|
||||||
|
} else {
|
||||||
|
Value::Dimension(n, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} % {}\".",
|
||||||
|
Value::Dimension(n, u).inspect(self.span)?,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} % {}\".",
|
||||||
|
left.inspect(self.span)?,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn and(left: HigherIntermediateValue, right: HigherIntermediateValue) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(if left.is_true() { right } else { left })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn or(left: HigherIntermediateValue, right: HigherIntermediateValue) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(if left.is_true() { left } else { right })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn equal(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(Value::bool(match left {
|
||||||
|
Value::String(s1, ..) => match right {
|
||||||
|
Value::String(s2, ..) => s1 == s2,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Value::Dimension(n, unit) => match right {
|
||||||
|
Value::Dimension(n2, unit2) => {
|
||||||
|
if !unit.comparable(&unit2) {
|
||||||
|
false
|
||||||
|
} else if unit == unit2 {
|
||||||
|
n == n2
|
||||||
|
} else if unit == Unit::None || unit2 == Unit::None {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
n == (n2
|
||||||
|
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
||||||
|
[unit2.to_string().as_str()]
|
||||||
|
.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Value::List(list1, sep1, brackets1) => match right {
|
||||||
|
Value::List(list2, sep2, brackets2) => {
|
||||||
|
if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let mut equals = true;
|
||||||
|
for (a, b) in list1.into_iter().zip(list2) {
|
||||||
|
if !self
|
||||||
|
.equal(
|
||||||
|
HigherIntermediateValue::Literal(a),
|
||||||
|
HigherIntermediateValue::Literal(b),
|
||||||
|
)?
|
||||||
|
.is_true()
|
||||||
|
{
|
||||||
|
equals = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
equals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
s => s == right,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_equal(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
Ok(Value::bool(match left {
|
||||||
|
Value::String(s1, ..) => match right {
|
||||||
|
Value::String(s2, ..) => s1 != s2,
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
Value::Dimension(n, unit) => match right {
|
||||||
|
Value::Dimension(n2, unit2) => {
|
||||||
|
if !unit.comparable(&unit2) {
|
||||||
|
true
|
||||||
|
} else if unit == unit2 {
|
||||||
|
n != n2
|
||||||
|
} else if unit == Unit::None || unit2 == Unit::None {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
n != (n2
|
||||||
|
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
||||||
|
[unit2.to_string().as_str()]
|
||||||
|
.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
Value::List(list1, sep1, brackets1) => match right {
|
||||||
|
Value::List(list2, sep2, brackets2) => {
|
||||||
|
if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let mut equals = false;
|
||||||
|
for (a, b) in list1.into_iter().zip(list2) {
|
||||||
|
if self
|
||||||
|
.not_equal(
|
||||||
|
HigherIntermediateValue::Literal(a),
|
||||||
|
HigherIntermediateValue::Literal(b),
|
||||||
|
)?
|
||||||
|
.is_true()
|
||||||
|
{
|
||||||
|
equals = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
equals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
s => s != right,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmp(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
op: Op,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
let left = match left {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let right = match right {
|
||||||
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
|
v => panic!("{:?}", v),
|
||||||
|
};
|
||||||
|
let ordering = match left {
|
||||||
|
Value::Dimension(num, unit) => match &right {
|
||||||
|
Value::Dimension(num2, unit2) => {
|
||||||
|
if !unit.comparable(unit2) {
|
||||||
|
return Err((
|
||||||
|
format!("Incompatible units {} and {}.", unit2, unit),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if &unit == unit2 || unit == Unit::None || unit2 == &Unit::None {
|
||||||
|
num.cmp(num2)
|
||||||
|
} else {
|
||||||
|
num.cmp(
|
||||||
|
&(num2.clone()
|
||||||
|
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
||||||
|
[unit2.to_string().as_str()]
|
||||||
|
.clone()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} {} {}\".",
|
||||||
|
v.inspect(self.span)?,
|
||||||
|
op,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err((
|
||||||
|
format!(
|
||||||
|
"Undefined operation \"{} {} {}\".",
|
||||||
|
left.inspect(self.span)?,
|
||||||
|
op,
|
||||||
|
right.inspect(self.span)?
|
||||||
|
),
|
||||||
|
self.span,
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(match op {
|
||||||
|
Op::GreaterThan => match ordering {
|
||||||
|
Ordering::Greater => Value::True,
|
||||||
|
Ordering::Less | Ordering::Equal => Value::False,
|
||||||
|
},
|
||||||
|
Op::GreaterThanEqual => match ordering {
|
||||||
|
Ordering::Greater | Ordering::Equal => Value::True,
|
||||||
|
Ordering::Less => Value::False,
|
||||||
|
},
|
||||||
|
Op::LessThan => match ordering {
|
||||||
|
Ordering::Less => Value::True,
|
||||||
|
Ordering::Greater | Ordering::Equal => Value::False,
|
||||||
|
},
|
||||||
|
Op::LessThanEqual => match ordering {
|
||||||
|
Ordering::Less | Ordering::Equal => Value::True,
|
||||||
|
Ordering::Greater => Value::False,
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn greater_than(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
self.cmp(left, Op::GreaterThan, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn greater_than_or_equal(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
self.cmp(left, Op::GreaterThanEqual, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn less_than(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
self.cmp(left, Op::LessThan, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn less_than_or_equal(
|
||||||
|
&self,
|
||||||
|
left: HigherIntermediateValue,
|
||||||
|
right: HigherIntermediateValue,
|
||||||
|
) -> SassResult<Value> {
|
||||||
|
self.cmp(left, Op::LessThanEqual, right)
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,5 @@
|
|||||||
|
pub(crate) use eval::{HigherIntermediateValue, ValueVisitor};
|
||||||
|
|
||||||
|
mod css_function;
|
||||||
|
mod eval;
|
||||||
mod parse;
|
mod parse;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -42,32 +42,32 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
if value.global && !value.default {
|
if value.global && !value.default {
|
||||||
self.global_scope
|
self.global_scope
|
||||||
.insert_var(ident.clone(), value.value.clone())?;
|
.insert_var(ident.clone(), value.value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.default {
|
if value.default {
|
||||||
if self.at_root && !self.in_control_flow {
|
if self.at_root && !self.in_control_flow {
|
||||||
if !self.global_scope.var_exists_no_global(&ident) {
|
if !self.global_scope.var_exists_no_global(&ident) {
|
||||||
self.global_scope.insert_var(ident, value.value)?;
|
self.global_scope.insert_var(ident, value.value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if value.global && !self.global_scope.var_exists_no_global(&ident) {
|
if value.global && !self.global_scope.var_exists_no_global(&ident) {
|
||||||
self.global_scope
|
self.global_scope
|
||||||
.insert_var(ident.clone(), value.value.clone())?;
|
.insert_var(ident.clone(), value.value.clone());
|
||||||
}
|
}
|
||||||
if !self.scopes.last().var_exists_no_global(&ident) {
|
if !self.scopes.last().var_exists_no_global(&ident) {
|
||||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
self.scopes.last_mut().insert_var(ident, value.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.at_root {
|
} else if self.at_root {
|
||||||
if self.in_control_flow {
|
if self.in_control_flow {
|
||||||
if self.global_scope.var_exists_no_global(&ident) {
|
if self.global_scope.var_exists_no_global(&ident) {
|
||||||
self.global_scope.insert_var(ident, value.value)?;
|
self.global_scope.insert_var(ident, value.value);
|
||||||
} else {
|
} else {
|
||||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
self.scopes.last_mut().insert_var(ident, value.value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.global_scope.insert_var(ident, value.value)?;
|
self.global_scope.insert_var(ident, value.value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let len = self.scopes.len();
|
let len = self.scopes.len();
|
||||||
@ -78,15 +78,15 @@ impl<'a> Parser<'a> {
|
|||||||
.filter(|(i, _)| *i != len)
|
.filter(|(i, _)| *i != len)
|
||||||
{
|
{
|
||||||
if scope.var_exists_no_global(&ident) {
|
if scope.var_exists_no_global(&ident) {
|
||||||
scope.insert_var(ident.clone(), value.value.clone())?;
|
scope.insert_var(ident.clone(), value.value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.scopes.first().var_exists_no_global(&ident) {
|
if self.scopes.first().var_exists_no_global(&ident) {
|
||||||
self.scopes
|
self.scopes
|
||||||
.first_mut()
|
.first_mut()
|
||||||
.insert_var(ident.clone(), value.value.clone())?;
|
.insert_var(ident.clone(), value.value.clone());
|
||||||
}
|
}
|
||||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
self.scopes.last_mut().insert_var(ident, value.value);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -51,9 +51,8 @@ impl Scope {
|
|||||||
&mut self,
|
&mut self,
|
||||||
s: T,
|
s: T,
|
||||||
v: Spanned<Value>,
|
v: Spanned<Value>,
|
||||||
) -> SassResult<Option<Spanned<Value>>> {
|
) -> Option<Spanned<Value>> {
|
||||||
let Spanned { node, span } = v;
|
self.vars.insert(s.into(), v)
|
||||||
Ok(self.vars.insert(s.into(), node.eval(span)?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn var_exists_no_global(&self, name: &Identifier) -> bool {
|
pub fn var_exists_no_global(&self, name: &Identifier) -> bool {
|
||||||
|
@ -127,7 +127,7 @@ impl SimpleSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_suffix(&mut self, suffix: &str, span: Span) -> SassResult<()> {
|
pub fn add_suffix(&mut self, suffix: &str, span: Span) -> SassResult<()> {
|
||||||
Ok(match self {
|
match self {
|
||||||
Self::Type(name) => name.ident.push_str(suffix),
|
Self::Type(name) => name.ident.push_str(suffix),
|
||||||
Self::Placeholder(name)
|
Self::Placeholder(name)
|
||||||
| Self::Id(name)
|
| Self::Id(name)
|
||||||
@ -140,7 +140,8 @@ impl SimpleSelector {
|
|||||||
}) => name.push_str(suffix),
|
}) => name.push_str(suffix),
|
||||||
// todo: add test for this?
|
// todo: add test for this?
|
||||||
_ => return Err((format!("Invalid parent selector \"{}\"", self), span).into()),
|
_ => return Err((format!("Invalid parent selector \"{}\"", self), span).into()),
|
||||||
})
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_universal(&self) -> bool {
|
pub fn is_universal(&self) -> bool {
|
||||||
|
10
src/style.rs
10
src/style.rs
@ -17,14 +17,4 @@ impl Style {
|
|||||||
self.value.node.to_css_string(self.value.span)?
|
self.value.node.to_css_string(self.value.span)?
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn eval(self) -> SassResult<Self> {
|
|
||||||
Ok(Style {
|
|
||||||
property: self.property,
|
|
||||||
value: Box::new(Spanned {
|
|
||||||
span: self.value.span,
|
|
||||||
node: self.value.node.eval(self.value.span)?.node,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use codemap::Span;
|
|||||||
use crate::{
|
use crate::{
|
||||||
common::{Brackets, ListSeparator},
|
common::{Brackets, ListSeparator},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
|
parse::{HigherIntermediateValue, Parser, ValueVisitor},
|
||||||
value::Value,
|
value::Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -16,16 +17,26 @@ impl SassMap {
|
|||||||
SassMap(Vec::new())
|
SassMap(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self, key: &Value, span: Span) -> SassResult<Option<Value>> {
|
pub fn get(
|
||||||
|
self,
|
||||||
|
key: &Value,
|
||||||
|
span: Span,
|
||||||
|
parser: &mut Parser<'_>,
|
||||||
|
) -> SassResult<Option<Value>> {
|
||||||
for (k, v) in self.0 {
|
for (k, v) in self.0 {
|
||||||
if k.equals(key.clone(), span)?.node.is_true(span)? {
|
if ValueVisitor::new(parser, span)
|
||||||
|
.equal(
|
||||||
|
HigherIntermediateValue::Literal(k),
|
||||||
|
HigherIntermediateValue::Literal(key.clone()),
|
||||||
|
)?
|
||||||
|
.is_true()
|
||||||
|
{
|
||||||
return Ok(Some(v));
|
return Ok(Some(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn remove(&mut self, key: &Value) {
|
pub fn remove(&mut self, key: &Value) {
|
||||||
self.0.retain(|(ref k, ..)| k != key);
|
self.0.retain(|(ref k, ..)| k != key);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use codemap::{Span, Spanned};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::Color,
|
color::Color,
|
||||||
common::{Brackets, ListSeparator, Op, QuoteKind},
|
common::{Brackets, ListSeparator, QuoteKind},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::Parser,
|
||||||
selector::Selector,
|
selector::Selector,
|
||||||
@ -21,7 +21,6 @@ pub(crate) use sass_function::SassFunction;
|
|||||||
pub(crate) mod css_function;
|
pub(crate) mod css_function;
|
||||||
mod map;
|
mod map;
|
||||||
mod number;
|
mod number;
|
||||||
mod ops;
|
|
||||||
mod sass_function;
|
mod sass_function;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@ -33,9 +32,6 @@ pub(crate) enum Value {
|
|||||||
Dimension(Number, Unit),
|
Dimension(Number, Unit),
|
||||||
List(Vec<Value>, ListSeparator, Brackets),
|
List(Vec<Value>, ListSeparator, Brackets),
|
||||||
Color(Box<Color>),
|
Color(Box<Color>),
|
||||||
UnaryOp(Op, Box<Value>),
|
|
||||||
BinaryOp(Box<Value>, Op, Box<Value>),
|
|
||||||
Paren(Box<Value>),
|
|
||||||
String(String, QuoteKind),
|
String(String, QuoteKind),
|
||||||
Map(SassMap),
|
Map(SassMap),
|
||||||
ArgList(Vec<Spanned<Value>>),
|
ArgList(Vec<Spanned<Value>>),
|
||||||
@ -43,7 +39,7 @@ pub(crate) enum Value {
|
|||||||
Function(SassFunction),
|
Function(SassFunction),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str) -> SassResult<()> {
|
fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str) {
|
||||||
let mut has_single_quote = false;
|
let mut has_single_quote = false;
|
||||||
let mut has_double_quote = false;
|
let mut has_double_quote = false;
|
||||||
|
|
||||||
@ -107,26 +103,22 @@ fn visit_quoted_string(buf: &mut String, force_double_quote: bool, string: &str)
|
|||||||
buffer = format!("{}{}{}", quote, buffer, quote);
|
buffer = format!("{}{}{}", quote, buffer, quote);
|
||||||
}
|
}
|
||||||
buf.push_str(&buffer);
|
buf.push_str(&buffer);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn is_null(&self, span: Span) -> SassResult<bool> {
|
pub fn is_null(&self) -> bool {
|
||||||
Ok(match self {
|
match self {
|
||||||
Value::Null => true,
|
Value::Null => true,
|
||||||
Value::String(i, QuoteKind::None) if i.is_empty() => true,
|
Value::String(i, QuoteKind::None) if i.is_empty() => true,
|
||||||
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
|
|
||||||
self.clone().eval(span)?.is_null(span)?
|
|
||||||
}
|
|
||||||
Self::List(v, _, Brackets::Bracketed) if v.is_empty() => false,
|
Self::List(v, _, Brackets::Bracketed) if v.is_empty() => false,
|
||||||
Self::List(v, ..) => v
|
Self::List(v, ..) => v
|
||||||
.iter()
|
.iter()
|
||||||
.map(|f| Ok(f.is_null(span)?))
|
.map(Value::is_null)
|
||||||
.collect::<SassResult<Vec<bool>>>()?
|
.collect::<Vec<bool>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.all(|f| f),
|
.all(|f| f),
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_css_string(&self, span: Span) -> SassResult<Cow<'static, str>> {
|
pub fn to_css_string(&self, span: Span) -> SassResult<Cow<'static, str>> {
|
||||||
@ -148,7 +140,7 @@ impl Value {
|
|||||||
Self::List(vals, sep, brackets) => match brackets {
|
Self::List(vals, sep, brackets) => match brackets {
|
||||||
Brackets::None => Cow::owned(
|
Brackets::None => Cow::owned(
|
||||||
vals.iter()
|
vals.iter()
|
||||||
.filter(|x| !x.is_null(span).unwrap_or(false))
|
.filter(|x| !x.is_null())
|
||||||
.map(|x| x.to_css_string(span))
|
.map(|x| x.to_css_string(span))
|
||||||
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
||||||
.join(sep.as_str()),
|
.join(sep.as_str()),
|
||||||
@ -156,17 +148,13 @@ impl Value {
|
|||||||
Brackets::Bracketed => Cow::owned(format!(
|
Brackets::Bracketed => Cow::owned(format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
vals.iter()
|
vals.iter()
|
||||||
.filter(|x| !x.is_null(span).unwrap_or(false))
|
.filter(|x| !x.is_null())
|
||||||
.map(|x| x.to_css_string(span))
|
.map(|x| x.to_css_string(span))
|
||||||
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
||||||
.join(sep.as_str()),
|
.join(sep.as_str()),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
Self::Color(c) => Cow::owned(c.to_string()),
|
Self::Color(c) => Cow::owned(c.to_string()),
|
||||||
Self::UnaryOp(..) | Self::BinaryOp(..) => {
|
|
||||||
self.clone().eval(span)?.to_css_string(span)?
|
|
||||||
}
|
|
||||||
Self::Paren(val) => val.to_css_string(span)?,
|
|
||||||
Self::String(string, QuoteKind::None) => {
|
Self::String(string, QuoteKind::None) => {
|
||||||
let mut after_newline = false;
|
let mut after_newline = false;
|
||||||
let mut buf = String::with_capacity(string.len());
|
let mut buf = String::with_capacity(string.len());
|
||||||
@ -191,7 +179,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
Self::String(string, QuoteKind::Quoted) => {
|
Self::String(string, QuoteKind::Quoted) => {
|
||||||
let mut buf = String::with_capacity(string.len());
|
let mut buf = String::with_capacity(string.len());
|
||||||
visit_quoted_string(&mut buf, false, string)?;
|
visit_quoted_string(&mut buf, false, string);
|
||||||
Cow::owned(buf)
|
Cow::owned(buf)
|
||||||
}
|
}
|
||||||
Self::True => Cow::const_str("true"),
|
Self::True => Cow::const_str("true"),
|
||||||
@ -199,7 +187,7 @@ impl Value {
|
|||||||
Self::Null => Cow::const_str(""),
|
Self::Null => Cow::const_str(""),
|
||||||
Self::ArgList(args) => Cow::owned(
|
Self::ArgList(args) => Cow::owned(
|
||||||
args.iter()
|
args.iter()
|
||||||
.filter(|x| !x.is_null(span).unwrap_or(false))
|
.filter(|x| !x.is_null())
|
||||||
.map(|a| Ok(a.node.to_css_string(span)?))
|
.map(|a| Ok(a.node.to_css_string(span)?))
|
||||||
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
||||||
.join(", "),
|
.join(", "),
|
||||||
@ -207,13 +195,10 @@ impl Value {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_true(&self, span: Span) -> SassResult<bool> {
|
pub fn is_true(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Value::Null | Value::False => Ok(false),
|
Value::Null | Value::False => false,
|
||||||
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
|
_ => true,
|
||||||
self.clone().eval(span)?.is_true(span)
|
|
||||||
}
|
|
||||||
_ => Ok(true),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,20 +216,17 @@ impl Value {
|
|||||||
Spanned { node: self, span }
|
Spanned { node: self, span }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self, span: Span) -> SassResult<&'static str> {
|
pub fn kind(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Color(..) => Ok("color"),
|
Self::Color(..) => "color",
|
||||||
Self::String(..) | Self::Important => Ok("string"),
|
Self::String(..) | Self::Important => "string",
|
||||||
Self::Dimension(..) => Ok("number"),
|
Self::Dimension(..) => "number",
|
||||||
Self::List(..) => Ok("list"),
|
Self::List(..) => "list",
|
||||||
Self::Function(..) => Ok("function"),
|
Self::Function(..) => "function",
|
||||||
Self::ArgList(..) => Ok("arglist"),
|
Self::ArgList(..) => "arglist",
|
||||||
Self::True | Self::False => Ok("bool"),
|
Self::True | Self::False => "bool",
|
||||||
Self::Null => Ok("null"),
|
Self::Null => "null",
|
||||||
Self::Map(..) => Ok("map"),
|
Self::Map(..) => "map",
|
||||||
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
|
|
||||||
self.clone().eval(span)?.kind(span)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +290,6 @@ impl Value {
|
|||||||
.collect::<SassResult<Vec<String>>>()?
|
.collect::<SassResult<Vec<String>>>()?
|
||||||
.join(", ")
|
.join(", ")
|
||||||
)),
|
)),
|
||||||
Value::Paren(v) => v.inspect(span)?,
|
|
||||||
v => v.to_css_string(span)?,
|
v => v.to_css_string(span)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -365,7 +346,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn selector_string(self, span: Span) -> SassResult<Option<String>> {
|
fn selector_string(self, span: Span) -> SassResult<Option<String>> {
|
||||||
Ok(Some(match self.eval(span)?.node {
|
Ok(Some(match self {
|
||||||
Self::String(text, ..) => text,
|
Self::String(text, ..) => text,
|
||||||
Self::List(list, sep, ..) if !list.is_empty() => {
|
Self::List(list, sep, ..) if !list.is_empty() => {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
843
src/value/ops.rs
843
src/value/ops.rs
@ -1,843 +0,0 @@
|
|||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
use codemap::{Span, Spanned};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
common::{Op, QuoteKind},
|
|
||||||
error::SassResult,
|
|
||||||
unit::{Unit, UNIT_CONVERSION_TABLE},
|
|
||||||
value::Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl Value {
|
|
||||||
pub fn equals(mut self, mut other: Value, span: Span) -> SassResult<Spanned<Value>> {
|
|
||||||
if let Self::Paren(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
}
|
|
||||||
if let Self::Paren(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
}
|
|
||||||
|
|
||||||
let precedence = Op::Equal.precedence();
|
|
||||||
|
|
||||||
Ok(Value::bool(match self {
|
|
||||||
// todo: why don't we eval the other?
|
|
||||||
Self::String(s1, ..) => match other {
|
|
||||||
Self::String(s2, ..) => s1 == s2,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
Self::Dimension(n, unit) => match other {
|
|
||||||
Self::Dimension(n2, unit2) => {
|
|
||||||
if !unit.comparable(&unit2) {
|
|
||||||
false
|
|
||||||
} else if unit == unit2 {
|
|
||||||
n == n2
|
|
||||||
} else if unit == Unit::None || unit2 == Unit::None {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
n == (n2
|
|
||||||
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
|
||||||
[unit2.to_string().as_str()]
|
|
||||||
.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op2, right) => {
|
|
||||||
if op2.precedence() >= precedence {
|
|
||||||
Self::BinaryOp(left, op2, right).eval(span)?.node == other
|
|
||||||
} else {
|
|
||||||
return Self::BinaryOp(
|
|
||||||
left,
|
|
||||||
op2,
|
|
||||||
Box::new(
|
|
||||||
Self::BinaryOp(right, Op::Equal, Box::new(other))
|
|
||||||
.eval(span)?
|
|
||||||
.node,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.eval(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::List(list1, sep1, brackets1) => match other.eval(span)?.node {
|
|
||||||
Self::List(list2, sep2, brackets2) => {
|
|
||||||
if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let mut equals = true;
|
|
||||||
for (a, b) in list1.into_iter().zip(list2) {
|
|
||||||
if !a.equals(b, span)?.node.is_true(span)? {
|
|
||||||
equals = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
equals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
s => s == other.eval(span)?.node,
|
|
||||||
})
|
|
||||||
.span(span))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn not_equals(mut self, mut other: Value, span: Span) -> SassResult<Spanned<Value>> {
|
|
||||||
if let Self::Paren(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
}
|
|
||||||
if let Self::Paren(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
}
|
|
||||||
|
|
||||||
let precedence = Op::Equal.precedence();
|
|
||||||
|
|
||||||
Ok(Value::bool(match self {
|
|
||||||
Self::String(s1, ..) => match other {
|
|
||||||
Self::String(s2, ..) => s1 != s2,
|
|
||||||
_ => true,
|
|
||||||
},
|
|
||||||
Self::Dimension(n, unit) => match other {
|
|
||||||
Self::Dimension(n2, unit2) => {
|
|
||||||
if !unit.comparable(&unit2) {
|
|
||||||
true
|
|
||||||
} else if unit == unit2 {
|
|
||||||
n != n2
|
|
||||||
} else if unit == Unit::None || unit2 == Unit::None {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
n != (n2
|
|
||||||
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
|
||||||
[unit2.to_string().as_str()]
|
|
||||||
.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op2, right) => {
|
|
||||||
if op2.precedence() >= precedence {
|
|
||||||
Self::BinaryOp(left, op2, right).eval(span)?.node != other
|
|
||||||
} else {
|
|
||||||
return Self::BinaryOp(
|
|
||||||
left,
|
|
||||||
op2,
|
|
||||||
Box::new(
|
|
||||||
Self::BinaryOp(right, Op::NotEqual, Box::new(other))
|
|
||||||
.eval(span)?
|
|
||||||
.node,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.eval(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::List(list1, sep1, brackets1) => match other.eval(span)?.node {
|
|
||||||
Self::List(list2, sep2, brackets2) => {
|
|
||||||
if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
let mut equals = false;
|
|
||||||
for (a, b) in list1.into_iter().zip(list2) {
|
|
||||||
if a.not_equals(b, span)?.node.is_true(span)? {
|
|
||||||
equals = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
equals
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
},
|
|
||||||
s => s != other.eval(span)?.node,
|
|
||||||
})
|
|
||||||
.span(span))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unary_op_plus(self, span: Span) -> SassResult<Self> {
|
|
||||||
Ok(match self.eval(span)?.node {
|
|
||||||
v @ Value::Dimension(..) => v,
|
|
||||||
v => Value::String(format!("+{}", v.to_css_string(span)?), QuoteKind::None),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval(self, span: Span) -> SassResult<Spanned<Self>> {
|
|
||||||
Ok(match self {
|
|
||||||
Self::BinaryOp(lhs, op, rhs) => match op {
|
|
||||||
Op::Plus => lhs.add(*rhs, span)?,
|
|
||||||
Op::Minus => lhs.sub(*rhs, span)?,
|
|
||||||
Op::Equal => lhs.equals(*rhs, span)?.node,
|
|
||||||
Op::NotEqual => lhs.not_equals(*rhs, span)?.node,
|
|
||||||
Op::Mul => lhs.mul(*rhs, span)?,
|
|
||||||
Op::Div => lhs.div(*rhs, span)?,
|
|
||||||
Op::Rem => lhs.rem(*rhs, span)?,
|
|
||||||
Op::GreaterThan | Op::GreaterThanEqual | Op::LessThan | Op::LessThanEqual => {
|
|
||||||
return lhs.cmp(*rhs, op, span)
|
|
||||||
}
|
|
||||||
Op::Not => unreachable!(),
|
|
||||||
Op::And => {
|
|
||||||
if lhs.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(span)?.node,
|
|
||||||
Self::UnaryOp(op, val) => match op {
|
|
||||||
Op::Plus => val.unary_op_plus(span)?,
|
|
||||||
Op::Minus => val.neg(span)?,
|
|
||||||
Op::Not => Self::bool(!val.eval(span)?.is_true(span)?),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
_ => self,
|
|
||||||
}
|
|
||||||
.span(span))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmp(self, mut other: Self, op: Op, span: Span) -> SassResult<Spanned<Value>> {
|
|
||||||
if let Self::Paren(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = other {
|
|
||||||
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), span).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if &unit == unit2 || unit == Unit::None || unit2 == &Unit::None {
|
|
||||||
num.cmp(num2)
|
|
||||||
} else {
|
|
||||||
num.cmp(
|
|
||||||
&(num2.clone()
|
|
||||||
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
|
||||||
[unit2.to_string().as_str()]
|
|
||||||
.clone()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::BinaryOp(..) => todo!(),
|
|
||||||
v => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{} {} {}\".",
|
|
||||||
v.inspect(span)?,
|
|
||||||
op,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op2, right) => {
|
|
||||||
return if op2.precedence() >= precedence {
|
|
||||||
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(span)?.node),
|
|
||||||
)
|
|
||||||
.eval(span)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::UnaryOp(..) | Self::Paren(..) => {
|
|
||||||
return self.eval(span)?.node.cmp(other, op, span)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{} {} {}\".",
|
|
||||||
self.inspect(span)?,
|
|
||||||
op,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(match op {
|
|
||||||
Op::GreaterThan => match ordering {
|
|
||||||
Ordering::Greater => Self::True,
|
|
||||||
Ordering::Less | Ordering::Equal => Self::False,
|
|
||||||
},
|
|
||||||
Op::GreaterThanEqual => match ordering {
|
|
||||||
Ordering::Greater | Ordering::Equal => Self::True,
|
|
||||||
Ordering::Less => Self::False,
|
|
||||||
},
|
|
||||||
Op::LessThan => match ordering {
|
|
||||||
Ordering::Less => Self::True,
|
|
||||||
Ordering::Greater | Ordering::Equal => Self::False,
|
|
||||||
},
|
|
||||||
Op::LessThanEqual => match ordering {
|
|
||||||
Ordering::Less | Ordering::Equal => Self::True,
|
|
||||||
Ordering::Greater => Self::False,
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
.span(span))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(mut self, mut other: Self, span: Span) -> SassResult<Self> {
|
|
||||||
if let Self::Paren(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
}
|
|
||||||
if let Self::Paren(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
}
|
|
||||||
let precedence = Op::Plus.precedence();
|
|
||||||
Ok(match self {
|
|
||||||
Self::Map(..) | Self::Function(..) => {
|
|
||||||
return Err((
|
|
||||||
format!("{} isn't a valid CSS value.", self.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
Self::ArgList(..) => todo!(),
|
|
||||||
Self::Important | Self::True | Self::False => match other {
|
|
||||||
Self::String(s, QuoteKind::Quoted) => Value::String(
|
|
||||||
format!("{}{}", self.to_css_string(span)?, s),
|
|
||||||
QuoteKind::Quoted,
|
|
||||||
),
|
|
||||||
Self::Null => {
|
|
||||||
Value::String(self.to_css_string(span)?.into_owned(), QuoteKind::None)
|
|
||||||
}
|
|
||||||
_ => Value::String(
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
self.to_css_string(span)?,
|
|
||||||
other.to_css_string(span)?
|
|
||||||
),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Self::Null => match other {
|
|
||||||
Self::Null => Self::Null,
|
|
||||||
_ => Value::String(other.to_css_string(span)?.into_owned(), QuoteKind::None),
|
|
||||||
},
|
|
||||||
Self::Dimension(num, unit) => match other {
|
|
||||||
Self::Dimension(num2, unit2) => {
|
|
||||||
if !unit.comparable(&unit2) {
|
|
||||||
return Err(
|
|
||||||
(format!("Incompatible units {} and {}.", unit2, unit), span).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if unit == unit2 {
|
|
||||||
Value::Dimension(num + num2, unit)
|
|
||||||
} else if unit == Unit::None {
|
|
||||||
Value::Dimension(num + num2, unit2)
|
|
||||||
} else if unit2 == Unit::None {
|
|
||||||
Value::Dimension(num + num2, unit)
|
|
||||||
} else {
|
|
||||||
Value::Dimension(
|
|
||||||
num + num2
|
|
||||||
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
|
||||||
[unit2.to_string().as_str()]
|
|
||||||
.clone(),
|
|
||||||
unit,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::String(s, q) => Value::String(format!("{}{}{}", num, unit, s), q),
|
|
||||||
Self::Null => Value::String(format!("{}{}", num, unit), QuoteKind::None),
|
|
||||||
Self::List(..) => Value::String(
|
|
||||||
format!("{}{}{}", num, unit, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::True | Self::False => Self::String(
|
|
||||||
format!("{}{}{}", num, unit, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::Map(..) | Self::Function(..) => {
|
|
||||||
return Err((
|
|
||||||
format!("{} isn't a valid CSS value.", other.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{}{} + {}\".",
|
|
||||||
num,
|
|
||||||
unit,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::Color(c) => match other {
|
|
||||||
Self::String(s, q) => Value::String(format!("{}{}", c, s), q),
|
|
||||||
Self::Null => Value::String(c.to_string(), QuoteKind::None),
|
|
||||||
Self::List(..) => Value::String(
|
|
||||||
format!("{}{}", c, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!("Undefined operation \"{} + {}\".", c, other.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op, right) => {
|
|
||||||
if op.precedence() >= precedence {
|
|
||||||
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(span)?
|
|
||||||
.node,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.eval(span)?
|
|
||||||
.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.add(other, span)?,
|
|
||||||
Self::String(text, quotes) => match other {
|
|
||||||
Self::String(text2, ..) => Self::String(text + &text2, quotes),
|
|
||||||
_ => Value::String(text + &other.to_css_string(span)?, quotes),
|
|
||||||
},
|
|
||||||
Self::List(..) => match other {
|
|
||||||
Self::String(s, q) => {
|
|
||||||
Value::String(format!("{}{}", self.to_css_string(span)?, s), q)
|
|
||||||
}
|
|
||||||
Self::Paren(..) => (self.add(other.eval(span)?.node, span))?,
|
|
||||||
_ => Value::String(
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
self.to_css_string(span)?,
|
|
||||||
other.to_css_string(span)?
|
|
||||||
),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sub(mut self, mut other: Self, span: Span) -> SassResult<Self> {
|
|
||||||
if let Self::Paren(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
}
|
|
||||||
if let Self::Paren(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
}
|
|
||||||
let precedence = Op::Mul.precedence();
|
|
||||||
Ok(match self {
|
|
||||||
Self::Null => {
|
|
||||||
Value::String(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), span).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if unit == unit2 {
|
|
||||||
Value::Dimension(num - num2, unit)
|
|
||||||
} else if unit == Unit::None {
|
|
||||||
Value::Dimension(num - num2, unit2)
|
|
||||||
} else if unit2 == Unit::None {
|
|
||||||
Value::Dimension(num - num2, unit)
|
|
||||||
} else {
|
|
||||||
Value::Dimension(
|
|
||||||
num - num2
|
|
||||||
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
|
||||||
[unit2.to_string().as_str()]
|
|
||||||
.clone(),
|
|
||||||
unit,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::List(..) | Self::String(..) => Value::String(
|
|
||||||
format!("{}{}-{}", num, unit, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::Map(..) | Self::Function(..) => {
|
|
||||||
return Err((
|
|
||||||
format!("{} isn't a valid CSS value.", other.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
},
|
|
||||||
Self::Color(c) => match other {
|
|
||||||
Self::String(s, q) => {
|
|
||||||
Value::String(format!("{}-{}{}{}", c, q, s, q), QuoteKind::None)
|
|
||||||
}
|
|
||||||
Self::Null => Value::String(format!("{}-", c), QuoteKind::None),
|
|
||||||
Self::Dimension(..) | Self::Color(..) => {
|
|
||||||
return Err((
|
|
||||||
format!("Undefined operation \"{} - {}\".", c, other.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
_ => Value::String(
|
|
||||||
format!("{}-{}", c, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op, right) => {
|
|
||||||
if op.precedence() >= precedence {
|
|
||||||
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(span)?
|
|
||||||
.node,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.eval(span)?
|
|
||||||
.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Paren(..) => self.eval(span)?.node.sub(other, span)?,
|
|
||||||
Self::String(..) => Self::String(
|
|
||||||
format!(
|
|
||||||
"{}-{}",
|
|
||||||
self.to_css_string(span)?,
|
|
||||||
other.to_css_string(span)?
|
|
||||||
),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::List(..) => match other {
|
|
||||||
Self::String(s, q) => Value::String(
|
|
||||||
format!("{}-{}{}{}", self.to_css_string(span)?, q, s, q),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
_ => Value::String(
|
|
||||||
format!(
|
|
||||||
"{}-{}",
|
|
||||||
self.to_css_string(span)?,
|
|
||||||
other.to_css_string(span)?
|
|
||||||
),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
_ => match other {
|
|
||||||
Self::String(s, q) => Value::String(
|
|
||||||
format!("{}-{}{}{}", self.to_css_string(span)?, q, s, q),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::Null => {
|
|
||||||
Value::String(format!("{}-", self.to_css_string(span)?), QuoteKind::None)
|
|
||||||
}
|
|
||||||
_ => Value::String(
|
|
||||||
format!(
|
|
||||||
"{}-{}",
|
|
||||||
self.to_css_string(span)?,
|
|
||||||
other.to_css_string(span)?
|
|
||||||
),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mul(mut self, mut other: Self, span: Span) -> SassResult<Self> {
|
|
||||||
if let Self::Paren(..) = other {
|
|
||||||
other = other.eval(span)?.node
|
|
||||||
}
|
|
||||||
if let Self::Paren(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
}
|
|
||||||
let precedence = Op::Mul.precedence();
|
|
||||||
Ok(match self {
|
|
||||||
Self::Null => todo!(),
|
|
||||||
Self::Dimension(num, unit) => match other {
|
|
||||||
Self::Dimension(num2, unit2) => {
|
|
||||||
if unit == Unit::None {
|
|
||||||
Value::Dimension(num * num2, unit2)
|
|
||||||
} else if unit2 == Unit::None {
|
|
||||||
Value::Dimension(num * num2, unit)
|
|
||||||
} else if let Unit::Mul(u) = unit {
|
|
||||||
let mut unit1 = u.into_vec();
|
|
||||||
unit1.push(unit2);
|
|
||||||
Value::Dimension(num * num2, Unit::Mul(unit1.into_boxed_slice()))
|
|
||||||
} else if let Unit::Mul(u2) = unit2 {
|
|
||||||
let mut u = vec![unit];
|
|
||||||
u.append(&mut u2.into_vec());
|
|
||||||
Value::Dimension(num * num2, Unit::Mul(u.into_boxed_slice()))
|
|
||||||
} else {
|
|
||||||
Value::Dimension(
|
|
||||||
num * num2,
|
|
||||||
Unit::Mul(vec![unit, unit2].into_boxed_slice()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{}{} * {}\".",
|
|
||||||
num,
|
|
||||||
unit,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op, right) => {
|
|
||||||
if op.precedence() >= precedence {
|
|
||||||
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(span)?
|
|
||||||
.node,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.eval(span)?
|
|
||||||
.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.mul(other, span)?,
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{} * {}\".",
|
|
||||||
self.inspect(span)?,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn div(mut self, other: Self, span: Span) -> SassResult<Self> {
|
|
||||||
if let Self::Paren(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
} else if let Self::UnaryOp(..) = self {
|
|
||||||
self = self.eval(span)?.node
|
|
||||||
}
|
|
||||||
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), span).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if unit == unit2 {
|
|
||||||
Value::Dimension(num / num2, Unit::None)
|
|
||||||
} else if unit == Unit::None {
|
|
||||||
todo!("inverse units")
|
|
||||||
} else if unit2 == Unit::None {
|
|
||||||
Value::Dimension(num / num2, unit)
|
|
||||||
} else {
|
|
||||||
Value::Dimension(
|
|
||||||
num / (num2
|
|
||||||
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
|
|
||||||
[unit2.to_string().as_str()]
|
|
||||||
.clone()),
|
|
||||||
Unit::None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::String(s, q) => {
|
|
||||||
Value::String(format!("{}{}/{}{}{}", num, unit, q, s, q), QuoteKind::None)
|
|
||||||
}
|
|
||||||
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
|
|
||||||
Self::Dimension(num, unit).div(other.eval(span)?.node, span)?
|
|
||||||
}
|
|
||||||
Self::List(..) | Self::True | Self::False | Self::Important | Self::Color(..) => {
|
|
||||||
Value::String(
|
|
||||||
format!("{}{}/{}", num, unit, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::Null => Value::String(format!("{}{}/", num, unit), QuoteKind::None),
|
|
||||||
Self::Map(..) | Self::Function(..) => {
|
|
||||||
return Err((
|
|
||||||
format!("{} isn't a valid CSS value.", other.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
Self::ArgList(..) => todo!(),
|
|
||||||
},
|
|
||||||
Self::Color(c) => match other {
|
|
||||||
Self::String(s, q) => {
|
|
||||||
Value::String(format!("{}/{}{}{}", c, q, s, q), QuoteKind::None)
|
|
||||||
}
|
|
||||||
Self::Null => Value::String(format!("{}/", c), QuoteKind::None),
|
|
||||||
Self::Dimension(..) | Self::Color(..) => {
|
|
||||||
return Err((
|
|
||||||
format!("Undefined operation \"{} / {}\".", c, other.inspect(span)?),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
_ => Value::String(
|
|
||||||
format!("{}/{}", c, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Self::BinaryOp(left, op, right) => {
|
|
||||||
if op.precedence() >= precedence {
|
|
||||||
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(span)?
|
|
||||||
.node,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.eval(span)?
|
|
||||||
.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Paren(..) => self.eval(span)?.node.div(other, span)?,
|
|
||||||
Self::String(s1, q1) => match other {
|
|
||||||
Self::String(s2, q2) => Value::String(
|
|
||||||
format!("{}{}{}/{}{}{}", q1, s1, q1, q2, s2, q2),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::Important
|
|
||||||
| Self::True
|
|
||||||
| Self::False
|
|
||||||
| Self::Dimension(..)
|
|
||||||
| Self::Color(..) => Value::String(
|
|
||||||
format!("{}{}{}/{}", q1, s1, q1, other.to_css_string(span)?),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::Null => Value::String(format!("{}{}{}/", q1, s1, q1), QuoteKind::None),
|
|
||||||
_ => todo!(),
|
|
||||||
},
|
|
||||||
_ => match other {
|
|
||||||
Self::String(s, q) => Value::String(
|
|
||||||
format!("{}/{}{}{}", self.to_css_string(span)?, q, s, q),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
Self::Null => {
|
|
||||||
Value::String(format!("{}/", self.to_css_string(span)?), QuoteKind::None)
|
|
||||||
}
|
|
||||||
_ => Value::String(
|
|
||||||
format!(
|
|
||||||
"{}/{}",
|
|
||||||
self.to_css_string(span)?,
|
|
||||||
other.to_css_string(span)?
|
|
||||||
),
|
|
||||||
QuoteKind::None,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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), span).into());
|
|
||||||
}
|
|
||||||
if u == u2 {
|
|
||||||
Value::Dimension(n % n2, u)
|
|
||||||
} else if u == Unit::None {
|
|
||||||
Value::Dimension(n % n2, u2)
|
|
||||||
} else if u2 == Unit::None {
|
|
||||||
Value::Dimension(n % n2, u)
|
|
||||||
} else {
|
|
||||||
Value::Dimension(n, u)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{} % {}\".",
|
|
||||||
Value::Dimension(n, u).inspect(span)?,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err((
|
|
||||||
format!(
|
|
||||||
"Undefined operation \"{} % {}\".",
|
|
||||||
self.inspect(span)?,
|
|
||||||
other.inspect(span)?
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn neg(self, span: Span) -> SassResult<Self> {
|
|
||||||
Ok(match self.eval(span)?.node {
|
|
||||||
Value::Dimension(n, u) => Value::Dimension(-n, u),
|
|
||||||
v => Value::String(format!("-{}", v.to_css_string(span)?), QuoteKind::None),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
30
tests/modulo.rs
Normal file
30
tests/modulo.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
test!(
|
||||||
|
px_mod_px,
|
||||||
|
"a {\n color: 10px % 2px;\n}\n",
|
||||||
|
"a {\n color: 0px;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
px_mod_in,
|
||||||
|
"a {\n color: 10px % 2in;\n}\n",
|
||||||
|
"a {\n color: 10px;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
px_mod_none,
|
||||||
|
"a {\n color: 10px % 2;\n}\n",
|
||||||
|
"a {\n color: 0px;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
none_mod_px,
|
||||||
|
"a {\n color: 10 % 2px;\n}\n",
|
||||||
|
"a {\n color: 0px;\n}\n"
|
||||||
|
);
|
||||||
|
test!(
|
||||||
|
none_mod_none,
|
||||||
|
"a {\n color: 10 % 2;\n}\n",
|
||||||
|
"a {\n color: 0;\n}\n"
|
||||||
|
);
|
@ -50,32 +50,6 @@ test!(
|
|||||||
);
|
);
|
||||||
test!(positive_float_leading_zero, "a {\n color: 0.1;\n}\n");
|
test!(positive_float_leading_zero, "a {\n color: 0.1;\n}\n");
|
||||||
test!(negative_float_leading_zero, "a {\n color: -0.1;\n}\n");
|
test!(negative_float_leading_zero, "a {\n color: -0.1;\n}\n");
|
||||||
|
|
||||||
test!(
|
|
||||||
px_mod_px,
|
|
||||||
"a {\n color: 10px % 2px;\n}\n",
|
|
||||||
"a {\n color: 0px;\n}\n"
|
|
||||||
);
|
|
||||||
test!(
|
|
||||||
px_mod_in,
|
|
||||||
"a {\n color: 10px % 2in;\n}\n",
|
|
||||||
"a {\n color: 10px;\n}\n"
|
|
||||||
);
|
|
||||||
test!(
|
|
||||||
px_mod_none,
|
|
||||||
"a {\n color: 10px % 2;\n}\n",
|
|
||||||
"a {\n color: 0px;\n}\n"
|
|
||||||
);
|
|
||||||
test!(
|
|
||||||
none_mod_px,
|
|
||||||
"a {\n color: 10 % 2px;\n}\n",
|
|
||||||
"a {\n color: 0px;\n}\n"
|
|
||||||
);
|
|
||||||
test!(
|
|
||||||
none_mod_none,
|
|
||||||
"a {\n color: 10 % 2;\n}\n",
|
|
||||||
"a {\n color: 0;\n}\n"
|
|
||||||
);
|
|
||||||
test!(
|
test!(
|
||||||
num_plus_div,
|
num_plus_div,
|
||||||
"a {\n color: 1 + 3/4;\n}\n",
|
"a {\n color: 1 + 3/4;\n}\n",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user