implement builtin sass:math
function clamp
This commit is contained in:
parent
3fae0a9621
commit
eeb0b0a924
@ -7,7 +7,6 @@ use crate::{
|
||||
common::{Identifier, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
value::{SassFunction, Value},
|
||||
};
|
||||
|
||||
@ -73,12 +72,7 @@ pub(crate) fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult
|
||||
|
||||
pub(crate) fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(1)?;
|
||||
#[allow(clippy::match_same_arms)]
|
||||
Ok(match args.get_err(0, "number")? {
|
||||
Value::Dimension(_, Unit::None, _) => Value::True,
|
||||
Value::Dimension(..) => Value::False,
|
||||
_ => Value::True,
|
||||
})
|
||||
Ok(Value::bool(args.get_err(0, "number")?.unitless()))
|
||||
}
|
||||
|
||||
pub(crate) fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
builtin::{
|
||||
@ -5,6 +7,7 @@ use crate::{
|
||||
meta::{unit, unitless},
|
||||
modules::Module,
|
||||
},
|
||||
common::Op,
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
unit::Unit,
|
||||
@ -16,7 +19,84 @@ use crate::builtin::math::random;
|
||||
|
||||
fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
args.max_args(3)?;
|
||||
todo!()
|
||||
let span = args.span();
|
||||
|
||||
let min = match args.get_err(0, "min")? {
|
||||
v @ Value::Dimension(..) => v,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$min: {} is not a number.", v.inspect(args.span())?),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let number = match args.get_err(1, "number")? {
|
||||
v @ Value::Dimension(..) => v,
|
||||
v => {
|
||||
return Err((
|
||||
format!("$number: {} is not a number.", v.inspect(span)?),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
let max = match args.get_err(2, "max")? {
|
||||
v @ Value::Dimension(..) => v,
|
||||
v => return Err((format!("$max: {} is not a number.", v.inspect(span)?), span).into()),
|
||||
};
|
||||
|
||||
// ensure that `min` and `max` are compatible
|
||||
min.cmp(&max, span, Op::LessThan)?;
|
||||
|
||||
let min_unit = match min {
|
||||
Value::Dimension(_, ref u, _) => u,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let number_unit = match number {
|
||||
Value::Dimension(_, ref u, _) => u,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let max_unit = match max {
|
||||
Value::Dimension(_, ref u, _) => u,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if min_unit == &Unit::None && number_unit != &Unit::None {
|
||||
return Err((
|
||||
format!(
|
||||
"$min is unitless but $number has unit {}. Arguments must all have units or all be unitless.",
|
||||
number_unit
|
||||
), span).into());
|
||||
} else if min_unit != &Unit::None && number_unit == &Unit::None {
|
||||
return Err((
|
||||
format!(
|
||||
"$min has unit {} but $number is unitless. Arguments must all have units or all be unitless.",
|
||||
min_unit
|
||||
), span).into());
|
||||
} else if min_unit != &Unit::None && max_unit == &Unit::None {
|
||||
return Err((
|
||||
format!(
|
||||
"$min has unit {} but $max is unitless. Arguments must all have units or all be unitless.",
|
||||
min_unit
|
||||
), span).into());
|
||||
}
|
||||
|
||||
match min.cmp(&number, span, Op::LessThan)? {
|
||||
Ordering::Greater => return Ok(min),
|
||||
Ordering::Equal => return Ok(number),
|
||||
Ordering::Less => {}
|
||||
}
|
||||
|
||||
match max.cmp(&number, span, Op::GreaterThan)? {
|
||||
Ordering::Less => return Ok(max),
|
||||
Ordering::Equal => return Ok(number),
|
||||
Ordering::Greater => {}
|
||||
}
|
||||
|
||||
Ok(number)
|
||||
}
|
||||
|
||||
fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||
@ -84,6 +164,7 @@ pub(crate) fn declare(f: &mut Module) {
|
||||
f.insert_builtin("is-unitless", unitless);
|
||||
f.insert_builtin("unit", unit);
|
||||
f.insert_builtin("percentage", percentage);
|
||||
f.insert_builtin("clamp", clamp);
|
||||
#[cfg(feature = "random")]
|
||||
f.insert_builtin("random", random);
|
||||
|
||||
|
@ -735,53 +735,9 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
|
||||
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())
|
||||
}
|
||||
};
|
||||
|
||||
let ordering = left.cmp(&right, self.span, op)?;
|
||||
|
||||
Ok(match op {
|
||||
Op::GreaterThan => match ordering {
|
||||
Ordering::Greater => Value::True,
|
||||
|
@ -1,10 +1,12 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use peekmore::PeekMore;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
common::{Brackets, ListSeparator, QuoteKind},
|
||||
common::{Brackets, ListSeparator, Op, QuoteKind},
|
||||
error::SassResult,
|
||||
parse::Parser,
|
||||
selector::Selector,
|
||||
@ -322,6 +324,63 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &Self, span: Span, op: Op) -> SassResult<Ordering> {
|
||||
Ok(match self {
|
||||
Value::Dimension(num, unit, _) => match &other {
|
||||
Value::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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
v => {
|
||||
return Err((
|
||||
format!(
|
||||
"Undefined operation \"{} {} {}\".",
|
||||
v.inspect(span)?,
|
||||
op,
|
||||
other.inspect(span)?
|
||||
),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err((
|
||||
format!(
|
||||
"Undefined operation \"{} {} {}\".",
|
||||
self.inspect(span)?,
|
||||
op,
|
||||
other.inspect(span)?
|
||||
),
|
||||
span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unitless(&self) -> bool {
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match self {
|
||||
Value::Dimension(_, Unit::None, _) => true,
|
||||
Value::Dimension(..) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not_equals(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Value::String(s1, ..) => match other {
|
||||
|
@ -16,7 +16,7 @@ mod integer;
|
||||
|
||||
const PRECISION: usize = 10;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Ord)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub(crate) enum Number {
|
||||
Small(Rational64),
|
||||
Big(Box<BigRational>),
|
||||
@ -321,6 +321,30 @@ impl PartialOrd for Number {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Number {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match self {
|
||||
Self::Small(val1) => match other {
|
||||
Self::Small(val2) => val1.cmp(val2),
|
||||
Self::Big(val2) => {
|
||||
let tuple: (i64, i64) = (*val1).into();
|
||||
BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)).cmp(val2)
|
||||
}
|
||||
},
|
||||
Self::Big(val1) => match other {
|
||||
Self::Small(val2) => {
|
||||
let tuple: (i64, i64) = (*val2).into();
|
||||
(**val1).cmp(&BigRational::new_raw(
|
||||
BigInt::from(tuple.0),
|
||||
BigInt::from(tuple.1),
|
||||
))
|
||||
}
|
||||
Self::Big(val2) => val1.cmp(val2),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Number {
|
||||
type Output = Self;
|
||||
|
||||
|
40
tests/math-module.rs
Normal file
40
tests/math-module.rs
Normal file
@ -0,0 +1,40 @@
|
||||
#![cfg(test)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
test!(
|
||||
clamp_in_the_middle,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(0, 1, 2);\n}\n",
|
||||
"a {\n color: 1;\n}\n"
|
||||
);
|
||||
test!(
|
||||
clamp_first_is_bigger,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(2, 1, 0);\n}\n",
|
||||
"a {\n color: 2;\n}\n"
|
||||
);
|
||||
test!(
|
||||
clamp_all_same_unit,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(0px, 1px, 2px);\n}\n",
|
||||
"a {\n color: 1px;\n}\n"
|
||||
);
|
||||
test!(
|
||||
clamp_all_different_but_compatible_unit,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2in);\n}\n",
|
||||
"a {\n color: 1cm;\n}\n"
|
||||
);
|
||||
error!(
|
||||
clamp_only_min_has_no_unit,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(0, 1cm, 2in);\n}\n",
|
||||
"Error: $min is unitless but $number has unit cm. Arguments must all have units or all be unitless."
|
||||
);
|
||||
error!(
|
||||
clamp_only_number_has_no_unit,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(0mm, 1, 2in);\n}\n",
|
||||
"Error: $min has unit mm but $number is unitless. Arguments must all have units or all be unitless."
|
||||
);
|
||||
error!(
|
||||
clamp_only_max_has_no_unit,
|
||||
"@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2);\n}\n",
|
||||
"Error: $min has unit mm but $max is unitless. Arguments must all have units or all be unitless."
|
||||
);
|
@ -63,3 +63,8 @@ test!(
|
||||
"a {\n color: 0 < 1;\n}\n",
|
||||
"a {\n color: true;\n}\n"
|
||||
);
|
||||
test!(
|
||||
ord_the_same_as_partial_ord,
|
||||
"a {\n color: 2in > 1cm;\n}\n",
|
||||
"a {\n color: true;\n}\n"
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user