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);
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
|
@ -34,7 +34,7 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
||||
.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(v) => {
|
||||
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()),
|
||||
};
|
||||
|
||||
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(v) => {
|
||||
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()),
|
||||
};
|
||||
|
||||
let hue = match channels.pop().map(|v| v.eval(args.span()).unwrap().node) {
|
||||
let hue = match channels.pop() {
|
||||
Some(Value::Dimension(n, _)) => n,
|
||||
Some(v) => {
|
||||
return Err((
|
||||
|
@ -37,7 +37,7 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) ->
|
||||
.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::Percent)) => (n / Number::from(100)) * Number::from(255),
|
||||
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()),
|
||||
};
|
||||
|
||||
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::Percent)) => (n / Number::from(100)) * Number::from(255),
|
||||
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()),
|
||||
};
|
||||
|
||||
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::Percent)) => (n / Number::from(100)) * Number::from(255),
|
||||
Some(v) if v.is_special_function() => {
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
args::CallArgs,
|
||||
common::{Brackets, ListSeparator, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
parse::{HigherIntermediateValue, Parser, ValueVisitor},
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
@ -220,7 +220,7 @@ fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
_ => Brackets::Bracketed,
|
||||
},
|
||||
v => {
|
||||
if v.is_true(args.span())? {
|
||||
if v.is_true() {
|
||||
Brackets::Bracketed
|
||||
} else {
|
||||
Brackets::None
|
||||
@ -248,16 +248,15 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(2)?;
|
||||
let list = parser.arg(&mut args, 0, "list")?.as_list();
|
||||
let value = parser.arg(&mut args, 1, "value")?;
|
||||
// TODO: find a way around this unwrap.
|
||||
// It should be impossible to hit as the arg is
|
||||
// evaluated prior to checking equality, but
|
||||
// it is still dirty.
|
||||
// TODO: find a way to propagate any errors here
|
||||
// Potential input to fuzz: index(1px 1in 1cm, 96px + 1rem)
|
||||
let index = match list.into_iter().position(|v| {
|
||||
v.equals(value.clone(), args.span())
|
||||
.unwrap()
|
||||
.is_true(args.span())
|
||||
.unwrap()
|
||||
ValueVisitor::new(parser, args.span())
|
||||
.equal(
|
||||
HigherIntermediateValue::Literal(v),
|
||||
HigherIntermediateValue::Literal(value.clone()),
|
||||
)
|
||||
.map_or(false, |v| v.is_true())
|
||||
}) {
|
||||
Some(v) => Number::from(v + 1),
|
||||
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> {
|
||||
let span = args.span();
|
||||
let lists = parser
|
||||
.variadic_args(args)?
|
||||
.into_iter()
|
||||
.map(|x| Ok(x.node.eval(span)?.node.as_list()))
|
||||
.collect::<SassResult<Vec<Vec<Value>>>>()?;
|
||||
.map(|x| x.node.as_list())
|
||||
.collect::<Vec<Vec<Value>>>();
|
||||
|
||||
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())
|
||||
}
|
||||
};
|
||||
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> {
|
||||
@ -39,7 +39,7 @@ fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
|
||||
.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> {
|
||||
|
@ -7,9 +7,8 @@ use rand::Rng;
|
||||
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
common::Op,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
parse::{HigherIntermediateValue, Parser, ValueVisitor},
|
||||
unit::Unit,
|
||||
value::{Number, Value},
|
||||
};
|
||||
@ -207,14 +206,12 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let mut min = nums.next().unwrap();
|
||||
|
||||
for num in nums {
|
||||
if Value::Dimension(num.0.clone(), num.1.clone())
|
||||
.cmp(
|
||||
Value::Dimension(min.0.clone(), min.1.clone()),
|
||||
Op::LessThan,
|
||||
span,
|
||||
if ValueVisitor::new(parser, span)
|
||||
.less_than(
|
||||
HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())),
|
||||
HigherIntermediateValue::Literal(Value::Dimension(min.0.clone(), min.1.clone())),
|
||||
)?
|
||||
.node
|
||||
.is_true(span)?
|
||||
.is_true()
|
||||
{
|
||||
min = num;
|
||||
}
|
||||
@ -239,14 +236,12 @@ fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
let mut max = nums.next().unwrap();
|
||||
|
||||
for num in nums {
|
||||
if Value::Dimension(num.0.clone(), num.1.clone())
|
||||
.cmp(
|
||||
Value::Dimension(max.0.clone(), max.1.clone()),
|
||||
Op::GreaterThan,
|
||||
span,
|
||||
if ValueVisitor::new(parser, span)
|
||||
.greater_than(
|
||||
HigherIntermediateValue::Literal(Value::Dimension(num.0.clone(), num.1.clone())),
|
||||
HigherIntermediateValue::Literal(Value::Dimension(max.0.clone(), max.1.clone())),
|
||||
)?
|
||||
.node
|
||||
.is_true(span)?
|
||||
.is_true()
|
||||
{
|
||||
max = num;
|
||||
}
|
||||
|
@ -13,10 +13,7 @@ use crate::{
|
||||
|
||||
fn if_(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
if parser
|
||||
.arg(&mut args, 0, "condition")?
|
||||
.is_true(args.span())?
|
||||
{
|
||||
if parser.arg(&mut args, 0, "condition")?.is_true() {
|
||||
Ok(parser.arg(&mut args, 1, "if-true")?)
|
||||
} else {
|
||||
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> {
|
||||
args.max_args(1)?;
|
||||
let value = parser.arg(&mut args, 0, "value")?;
|
||||
Ok(Value::String(
|
||||
value.kind(args.span())?.to_owned(),
|
||||
QuoteKind::None,
|
||||
))
|
||||
Ok(Value::String(value.kind().to_owned(), QuoteKind::None))
|
||||
}
|
||||
|
||||
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
|
||||
.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) {
|
||||
Value::String(s, ..) => Some(s),
|
||||
Value::Null => None,
|
||||
|
@ -77,7 +77,7 @@ grass input.scss
|
||||
|
||||
clippy::string_add,
|
||||
clippy::get_unwrap,
|
||||
clippy::unit_arg,
|
||||
// clippy::unit_arg,
|
||||
clippy::wrong_self_convention,
|
||||
clippy::items_after_statements,
|
||||
clippy::shadow_reuse,
|
||||
|
@ -50,15 +50,13 @@ impl Toplevel {
|
||||
Toplevel::RuleSet(selector, Vec::new())
|
||||
}
|
||||
|
||||
fn push_style(&mut self, mut s: Style) -> SassResult<()> {
|
||||
s = s.eval()?;
|
||||
if s.value.is_null(s.value.span)? {
|
||||
return Ok(());
|
||||
fn push_style(&mut self, s: Style) {
|
||||
if s.value.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Toplevel::RuleSet(_, entries) = self {
|
||||
entries.push(BlockEntry::Style(Box::new(s)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_comment(&mut self, s: String) {
|
||||
@ -96,7 +94,7 @@ impl Css {
|
||||
for rule in body {
|
||||
match rule {
|
||||
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::Media(m) => {
|
||||
let MediaRule { query, body, .. } = *m;
|
||||
@ -117,10 +115,12 @@ impl Css {
|
||||
})))
|
||||
}
|
||||
Stmt::Return(..) => unreachable!(),
|
||||
Stmt::AtRoot { body } => body
|
||||
.into_iter()
|
||||
.map(|r| Ok(vals.extend(self.parse_stmt(r, extender)?)))
|
||||
.collect::<SassResult<()>>()?,
|
||||
Stmt::AtRoot { body } => {
|
||||
body.into_iter().try_for_each(|r| -> SassResult<()> {
|
||||
vals.append(&mut self.parse_stmt(r, extender)?);
|
||||
Ok(())
|
||||
})?
|
||||
}
|
||||
};
|
||||
}
|
||||
vals
|
||||
|
@ -178,10 +178,7 @@ impl<'a> Parser<'a> {
|
||||
} else {
|
||||
CallArg::Named(name.into())
|
||||
},
|
||||
{
|
||||
let val = self.parse_value_from_vec(val)?;
|
||||
val.node.eval(val.span)?
|
||||
},
|
||||
self.parse_value_from_vec(val)?,
|
||||
);
|
||||
span = span.merge(tok.pos());
|
||||
return Ok(CallArgs(args, span));
|
||||
@ -221,10 +218,7 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
if is_splat {
|
||||
let val = {
|
||||
let val = self.parse_value_from_vec(mem::take(&mut val))?;
|
||||
val.node.eval(val.span)?
|
||||
};
|
||||
match val.node {
|
||||
Value::ArgList(v) => {
|
||||
for arg in v {
|
||||
@ -233,13 +227,13 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
Value::List(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) => {
|
||||
for (name, arg) in v.entries() {
|
||||
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 {
|
||||
CallArg::Named(name.as_str().into())
|
||||
},
|
||||
{
|
||||
let val = self.parse_value_from_vec(mem::take(&mut val))?;
|
||||
val.node.eval(val.span)?
|
||||
},
|
||||
self.parse_value_from_vec(mem::take(&mut val))?,
|
||||
);
|
||||
}
|
||||
|
||||
@ -354,16 +345,13 @@ impl<'a> Parser<'a> {
|
||||
node: arg_list,
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
);
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone()) {
|
||||
Some(v) => v,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => {
|
||||
let val = self.parse_value_from_vec(mem::take(v))?;
|
||||
val.node.eval(val.span)?
|
||||
}
|
||||
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
@ -373,8 +361,8 @@ impl<'a> Parser<'a> {
|
||||
};
|
||||
self.scopes
|
||||
.last_mut()
|
||||
.insert_var(arg.name.clone(), val.clone())?;
|
||||
scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
.insert_var(arg.name.clone(), val.clone());
|
||||
scope.insert_var(mem::take(&mut arg.name), val);
|
||||
}
|
||||
self.scopes.pop();
|
||||
Ok(())
|
||||
|
@ -24,6 +24,8 @@ use crate::{
|
||||
|
||||
use common::{Branch, NeverEmptyVec, SelectorOrStyle};
|
||||
|
||||
pub(crate) use value::{HigherIntermediateValue, ValueVisitor};
|
||||
|
||||
mod args;
|
||||
pub mod common;
|
||||
mod function;
|
||||
@ -405,10 +407,7 @@ impl<'a> Parser<'a> {
|
||||
Some(Token { kind: '}', .. }) => {}
|
||||
Some(..) | None => return Err(("expected \"}\".", val.span).into()),
|
||||
}
|
||||
Ok(Spanned {
|
||||
node: val.node.eval(val.span)?.node.unquote(),
|
||||
span: val.span,
|
||||
})
|
||||
Ok(val.map_node(Value::unquote))
|
||||
}
|
||||
|
||||
pub fn parse_interpolation_as_string(&mut self) -> SassResult<Cow<'static, str>> {
|
||||
@ -560,8 +559,7 @@ impl<'a> Parser<'a> {
|
||||
|
||||
for branch in branches {
|
||||
self.span_before = branch.cond.first().unwrap().pos;
|
||||
let cond = self.parse_value_from_vec(branch.cond)?;
|
||||
if cond.node.is_true(cond.span)? {
|
||||
if self.parse_value_from_vec(branch.cond)?.node.is_true() {
|
||||
return Parser {
|
||||
toks: &mut branch.toks.into_iter().peekmore(),
|
||||
map: self.map,
|
||||
@ -668,7 +666,7 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
self.whitespace();
|
||||
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() {
|
||||
Some(v) => v,
|
||||
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)?;
|
||||
self.toks.next();
|
||||
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() {
|
||||
Some(v) => v,
|
||||
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),
|
||||
span: var.span,
|
||||
},
|
||||
)?;
|
||||
);
|
||||
if self.in_function {
|
||||
let these_stmts = Parser {
|
||||
toks: &mut body.clone().into_iter().peekmore(),
|
||||
@ -797,7 +795,7 @@ impl<'a> Parser<'a> {
|
||||
let mut stmts = Vec::new();
|
||||
let mut val = self.parse_value_from_vec(cond.clone())?;
|
||||
self.scopes.push(self.scopes.last().clone());
|
||||
while val.node.is_true(val.span)? {
|
||||
while val.node.is_true() {
|
||||
if self.in_function {
|
||||
let these_stmts = Parser {
|
||||
toks: &mut body.clone().into_iter().peekmore(),
|
||||
@ -881,8 +879,7 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
self.whitespace();
|
||||
let iter_val_toks = read_until_open_curly_brace(self.toks)?;
|
||||
let iter_val = self.parse_value_from_vec(iter_val_toks)?;
|
||||
let iter = iter_val.node.eval(iter_val.span)?.node.as_list();
|
||||
let iter = self.parse_value_from_vec(iter_val_toks)?.node.as_list();
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||||
@ -905,7 +902,7 @@ impl<'a> Parser<'a> {
|
||||
node: this_iterator[0].clone(),
|
||||
span: vars[0].span,
|
||||
},
|
||||
)?;
|
||||
);
|
||||
} else {
|
||||
self.scopes.last_mut().insert_var(
|
||||
&vars[0].node,
|
||||
@ -913,7 +910,7 @@ impl<'a> Parser<'a> {
|
||||
node: Value::List(this_iterator, ListSeparator::Space, Brackets::None),
|
||||
span: vars[0].span,
|
||||
},
|
||||
)?;
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (var, val) in vars.clone().into_iter().zip(
|
||||
@ -927,7 +924,7 @@ impl<'a> Parser<'a> {
|
||||
node: val,
|
||||
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;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,32 +42,32 @@ impl<'a> Parser<'a> {
|
||||
|
||||
if value.global && !value.default {
|
||||
self.global_scope
|
||||
.insert_var(ident.clone(), value.value.clone())?;
|
||||
.insert_var(ident.clone(), value.value.clone());
|
||||
}
|
||||
|
||||
if value.default {
|
||||
if self.at_root && !self.in_control_flow {
|
||||
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 {
|
||||
if value.global && !self.global_scope.var_exists_no_global(&ident) {
|
||||
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) {
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
self.scopes.last_mut().insert_var(ident, value.value);
|
||||
}
|
||||
}
|
||||
} else if self.at_root {
|
||||
if self.in_control_flow {
|
||||
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 {
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
self.scopes.last_mut().insert_var(ident, value.value);
|
||||
}
|
||||
} else {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
self.global_scope.insert_var(ident, value.value);
|
||||
}
|
||||
} else {
|
||||
let len = self.scopes.len();
|
||||
@ -78,15 +78,15 @@ impl<'a> Parser<'a> {
|
||||
.filter(|(i, _)| *i != len)
|
||||
{
|
||||
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) {
|
||||
self.scopes
|
||||
.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(())
|
||||
}
|
||||
|
@ -51,9 +51,8 @@ impl Scope {
|
||||
&mut self,
|
||||
s: T,
|
||||
v: Spanned<Value>,
|
||||
) -> SassResult<Option<Spanned<Value>>> {
|
||||
let Spanned { node, span } = v;
|
||||
Ok(self.vars.insert(s.into(), node.eval(span)?))
|
||||
) -> Option<Spanned<Value>> {
|
||||
self.vars.insert(s.into(), v)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
Ok(match self {
|
||||
match self {
|
||||
Self::Type(name) => name.ident.push_str(suffix),
|
||||
Self::Placeholder(name)
|
||||
| Self::Id(name)
|
||||
@ -140,7 +140,8 @@ impl SimpleSelector {
|
||||
}) => name.push_str(suffix),
|
||||
// todo: add test for this?
|
||||
_ => return Err((format!("Invalid parent selector \"{}\"", self), span).into()),
|
||||
})
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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)?
|
||||
))
|
||||
}
|
||||
|
||||
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::{
|
||||
common::{Brackets, ListSeparator},
|
||||
error::SassResult,
|
||||
parse::{HigherIntermediateValue, Parser, ValueVisitor},
|
||||
value::Value,
|
||||
};
|
||||
|
||||
@ -16,16 +17,26 @@ impl SassMap {
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn remove(&mut self, key: &Value) {
|
||||
self.0.retain(|(ref k, ..)| k != key);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use codemap::{Span, Spanned};
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
common::{Brackets, ListSeparator, Op, QuoteKind},
|
||||
common::{Brackets, ListSeparator, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
selector::Selector,
|
||||
@ -21,7 +21,6 @@ pub(crate) use sass_function::SassFunction;
|
||||
pub(crate) mod css_function;
|
||||
mod map;
|
||||
mod number;
|
||||
mod ops;
|
||||
mod sass_function;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -33,9 +32,6 @@ pub(crate) enum Value {
|
||||
Dimension(Number, Unit),
|
||||
List(Vec<Value>, ListSeparator, Brackets),
|
||||
Color(Box<Color>),
|
||||
UnaryOp(Op, Box<Value>),
|
||||
BinaryOp(Box<Value>, Op, Box<Value>),
|
||||
Paren(Box<Value>),
|
||||
String(String, QuoteKind),
|
||||
Map(SassMap),
|
||||
ArgList(Vec<Spanned<Value>>),
|
||||
@ -43,7 +39,7 @@ pub(crate) enum Value {
|
||||
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_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);
|
||||
}
|
||||
buf.push_str(&buffer);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn is_null(&self, span: Span) -> SassResult<bool> {
|
||||
Ok(match self {
|
||||
pub fn is_null(&self) -> bool {
|
||||
match self {
|
||||
Value::Null => 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, ..) => v
|
||||
.iter()
|
||||
.map(|f| Ok(f.is_null(span)?))
|
||||
.collect::<SassResult<Vec<bool>>>()?
|
||||
.map(Value::is_null)
|
||||
.collect::<Vec<bool>>()
|
||||
.into_iter()
|
||||
.all(|f| f),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Brackets::None => Cow::owned(
|
||||
vals.iter()
|
||||
.filter(|x| !x.is_null(span).unwrap_or(false))
|
||||
.filter(|x| !x.is_null())
|
||||
.map(|x| x.to_css_string(span))
|
||||
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
||||
.join(sep.as_str()),
|
||||
@ -156,17 +148,13 @@ impl Value {
|
||||
Brackets::Bracketed => Cow::owned(format!(
|
||||
"[{}]",
|
||||
vals.iter()
|
||||
.filter(|x| !x.is_null(span).unwrap_or(false))
|
||||
.filter(|x| !x.is_null())
|
||||
.map(|x| x.to_css_string(span))
|
||||
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
||||
.join(sep.as_str()),
|
||||
)),
|
||||
},
|
||||
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) => {
|
||||
let mut after_newline = false;
|
||||
let mut buf = String::with_capacity(string.len());
|
||||
@ -191,7 +179,7 @@ impl Value {
|
||||
}
|
||||
Self::String(string, QuoteKind::Quoted) => {
|
||||
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)
|
||||
}
|
||||
Self::True => Cow::const_str("true"),
|
||||
@ -199,7 +187,7 @@ impl Value {
|
||||
Self::Null => Cow::const_str(""),
|
||||
Self::ArgList(args) => Cow::owned(
|
||||
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)?))
|
||||
.collect::<SassResult<Vec<Cow<'static, str>>>>()?
|
||||
.join(", "),
|
||||
@ -207,13 +195,10 @@ impl Value {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_true(&self, span: Span) -> SassResult<bool> {
|
||||
pub fn is_true(&self) -> bool {
|
||||
match self {
|
||||
Value::Null | Value::False => Ok(false),
|
||||
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
|
||||
self.clone().eval(span)?.is_true(span)
|
||||
}
|
||||
_ => Ok(true),
|
||||
Value::Null | Value::False => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,20 +216,17 @@ impl Value {
|
||||
Spanned { node: self, span }
|
||||
}
|
||||
|
||||
pub fn kind(&self, span: Span) -> SassResult<&'static str> {
|
||||
pub fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Color(..) => Ok("color"),
|
||||
Self::String(..) | Self::Important => Ok("string"),
|
||||
Self::Dimension(..) => Ok("number"),
|
||||
Self::List(..) => Ok("list"),
|
||||
Self::Function(..) => Ok("function"),
|
||||
Self::ArgList(..) => Ok("arglist"),
|
||||
Self::True | Self::False => Ok("bool"),
|
||||
Self::Null => Ok("null"),
|
||||
Self::Map(..) => Ok("map"),
|
||||
Self::BinaryOp(..) | Self::Paren(..) | Self::UnaryOp(..) => {
|
||||
self.clone().eval(span)?.kind(span)
|
||||
}
|
||||
Self::Color(..) => "color",
|
||||
Self::String(..) | Self::Important => "string",
|
||||
Self::Dimension(..) => "number",
|
||||
Self::List(..) => "list",
|
||||
Self::Function(..) => "function",
|
||||
Self::ArgList(..) => "arglist",
|
||||
Self::True | Self::False => "bool",
|
||||
Self::Null => "null",
|
||||
Self::Map(..) => "map",
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,7 +290,6 @@ impl Value {
|
||||
.collect::<SassResult<Vec<String>>>()?
|
||||
.join(", ")
|
||||
)),
|
||||
Value::Paren(v) => v.inspect(span)?,
|
||||
v => v.to_css_string(span)?,
|
||||
})
|
||||
}
|
||||
@ -365,7 +346,7 @@ impl Value {
|
||||
}
|
||||
|
||||
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::List(list, sep, ..) if !list.is_empty() => {
|
||||
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!(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!(
|
||||
num_plus_div,
|
||||
"a {\n color: 1 + 3/4;\n}\n",
|
||||
|
Loading…
x
Reference in New Issue
Block a user