refactor value evaluation

This commit is contained in:
Connor Skees 2020-07-03 12:38:20 -04:00
parent 0f590b5cd2
commit 596def3906
24 changed files with 1710 additions and 1546 deletions

View File

@ -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)]

View File

@ -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((

View File

@ -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() => {

View File

@ -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);

View File

@ -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> {

View File

@ -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;
} }

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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(())

View File

@ -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,
}, },
)?; );
} }
} }

View 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
View 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)
}
}

View File

@ -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

View File

@ -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(())
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
}),
})
}
} }

View File

@ -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);
} }

View File

@ -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();

View File

@ -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
View 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"
);

View File

@ -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",