implement builtin sass:math
function clamp
This commit is contained in:
parent
3fae0a9621
commit
eeb0b0a924
@ -7,7 +7,6 @@ use crate::{
|
|||||||
common::{Identifier, QuoteKind},
|
common::{Identifier, QuoteKind},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::Parser,
|
||||||
unit::Unit,
|
|
||||||
value::{SassFunction, Value},
|
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> {
|
pub(crate) fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
args.max_args(1)?;
|
args.max_args(1)?;
|
||||||
#[allow(clippy::match_same_arms)]
|
Ok(Value::bool(args.get_err(0, "number")?.unitless()))
|
||||||
Ok(match args.get_err(0, "number")? {
|
|
||||||
Value::Dimension(_, Unit::None, _) => Value::True,
|
|
||||||
Value::Dimension(..) => Value::False,
|
|
||||||
_ => Value::True,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
pub(crate) fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::CallArgs,
|
args::CallArgs,
|
||||||
builtin::{
|
builtin::{
|
||||||
@ -5,6 +7,7 @@ use crate::{
|
|||||||
meta::{unit, unitless},
|
meta::{unit, unitless},
|
||||||
modules::Module,
|
modules::Module,
|
||||||
},
|
},
|
||||||
|
common::Op,
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::Parser,
|
||||||
unit::Unit,
|
unit::Unit,
|
||||||
@ -16,7 +19,84 @@ use crate::builtin::math::random;
|
|||||||
|
|
||||||
fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
|
||||||
args.max_args(3)?;
|
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> {
|
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("is-unitless", unitless);
|
||||||
f.insert_builtin("unit", unit);
|
f.insert_builtin("unit", unit);
|
||||||
f.insert_builtin("percentage", percentage);
|
f.insert_builtin("percentage", percentage);
|
||||||
|
f.insert_builtin("clamp", clamp);
|
||||||
#[cfg(feature = "random")]
|
#[cfg(feature = "random")]
|
||||||
f.insert_builtin("random", random);
|
f.insert_builtin("random", random);
|
||||||
|
|
||||||
|
@ -735,53 +735,9 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
|
|||||||
HigherIntermediateValue::Literal(v) => v,
|
HigherIntermediateValue::Literal(v) => v,
|
||||||
v => panic!("{:?}", v),
|
v => panic!("{:?}", v),
|
||||||
};
|
};
|
||||||
let ordering = match left {
|
|
||||||
Value::Dimension(num, unit, _) => match &right {
|
let ordering = left.cmp(&right, self.span, op)?;
|
||||||
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 {
|
Ok(match op {
|
||||||
Op::GreaterThan => match ordering {
|
Op::GreaterThan => match ordering {
|
||||||
Ordering::Greater => Value::True,
|
Ordering::Greater => Value::True,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use peekmore::PeekMore;
|
use peekmore::PeekMore;
|
||||||
|
|
||||||
use codemap::{Span, Spanned};
|
use codemap::{Span, Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::Color,
|
color::Color,
|
||||||
common::{Brackets, ListSeparator, QuoteKind},
|
common::{Brackets, ListSeparator, Op, QuoteKind},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Parser,
|
parse::Parser,
|
||||||
selector::Selector,
|
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 {
|
pub fn not_equals(&self, other: &Self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Value::String(s1, ..) => match other {
|
Value::String(s1, ..) => match other {
|
||||||
|
@ -16,7 +16,7 @@ mod integer;
|
|||||||
|
|
||||||
const PRECISION: usize = 10;
|
const PRECISION: usize = 10;
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Ord)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub(crate) enum Number {
|
pub(crate) enum Number {
|
||||||
Small(Rational64),
|
Small(Rational64),
|
||||||
Big(Box<BigRational>),
|
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 {
|
impl Add for Number {
|
||||||
type Output = Self;
|
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: 0 < 1;\n}\n",
|
||||||
"a {\n color: true;\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