Skip to content

Commit

Permalink
Global math (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaj authored Oct 16, 2023
2 parents 121fc2e + 6609b18 commit 5a35868
Show file tree
Hide file tree
Showing 47 changed files with 948 additions and 567 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ project adheres to
* Remove separate backref member from `css::Selectors` for cleanup before
implementing more selector functions. Instead, add it to an internal
struct `SelectorCtx` (PR #179).
* Implemented a bunch of css math functions. They differ from the
functions in the math module in that they fallback to themself if
the answer cant be calculated but isn't obviously wrong
(e.g. `min(var(--gap), 2em)` is preserved while `min(1em, 2em)` is
evaluated to `1em` and `min(1s, 1em)` yields an error). This also
includes some improvements in handling numeric zeroes and
infinities. (PR #184).
* Simplify units early in numeric division and multiplication. I
think this is a bit uglier, but it is more consistent with dart
sass.
Expand Down
6 changes: 2 additions & 4 deletions rsass/src/css/binop.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{is_function_name, InvalidCss, Value};
use crate::output::{Format, Formatted};
use crate::value::{CssDimension, Numeric, Operator};
use crate::value::{CssDimensionSet, Numeric, Operator};
use std::fmt::{self, Display, Write};

/// A binary operation.
Expand Down Expand Up @@ -59,9 +59,7 @@ impl BinOp {
if self.a.is_calculation() || self.b.is_calculation() {
Err(InvalidCss::UndefOp(self.into()))
} else {
fn cmp_dim(
x: &Numeric,
) -> Option<Vec<(CssDimension, i8)>> {
fn cmp_dim(x: &Numeric) -> Option<CssDimensionSet> {
let u = &x.unit;
if u.is_known() && !u.is_percent() {
Some(u.css_dimension())
Expand Down
16 changes: 13 additions & 3 deletions rsass/src/css/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ impl CssString {
}
/// If the value is name-like, make it unquoted.
pub fn opt_unquote(self) -> Self {
let mut chars = self.value.chars();
let t = chars.next().map_or(false, char::is_alphabetic)
&& chars.all(|c| c.is_alphanumeric() || c == '-');
let t = is_namelike(&self.value);
CssString {
value: self.value,
quotes: if t { Quotes::None } else { self.quotes },
Expand Down Expand Up @@ -118,6 +116,10 @@ impl CssString {
pub fn is_null(&self) -> bool {
self.value.is_empty() && self.quotes.is_none()
}
pub(crate) fn is_name(&self) -> bool {
self.quotes == Quotes::None && is_namelike(&self.value)
}

/// Return true if this is a css special function call.
pub(crate) fn is_css_fn(&self) -> bool {
let value = self.value();
Expand Down Expand Up @@ -205,6 +207,14 @@ impl From<CssString> for crate::sass::Name {
}
}

fn is_namelike(s: &str) -> bool {
let mut chars = s.chars();
chars
.next()
.map_or(false, |c| c.is_alphabetic() || c == '_')
&& chars.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
}

fn is_private_use(c: char) -> bool {
// https://en.wikipedia.org/wiki/Private_Use_Areas
matches!(c as u32, 0xE000..=0xF8FF | 0xF0000..=0xFFFFD | 0x100000..=0x10FFFD)
Expand Down
36 changes: 36 additions & 0 deletions rsass/src/css/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::Value;
use crate::output::{Format, Formatted};

pub fn is_not<'a, T>(value: &'a T, expected: &str) -> String
Expand All @@ -14,6 +15,41 @@ where
)
}

#[derive(Debug)]
pub struct IsNot {
got: Value,
expected: &'static str,
}
impl IsNot {
pub fn new(got: Value, expected: &'static str) -> Self {
IsNot { got, expected }
}
pub fn value(&self) -> &Value {
&self.got
}
}

impl std::fmt::Display for IsNot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} is not {}.",
Formatted {
value: &self.got,
format: Format::introspect()
},
self.expected,
)
}
}

// Needed for error propagation
impl From<IsNot> for String {
fn from(value: IsNot) -> Self {
value.to_string()
}
}

/// Return true iff s is a valid _css_ function name.
pub fn is_function_name(s: &str) -> bool {
is_calc_name(s) || s == "var"
Expand Down
13 changes: 10 additions & 3 deletions rsass/src/css/value.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::util::IsNot;
use super::{is_calc_name, is_not, BinOp, CallArgs, CssString};
use crate::ordermap::OrderMap;
use crate::output::{Format, Formatted};
Expand Down Expand Up @@ -97,7 +98,13 @@ impl Value {
match *self {
ref v if v.is_calculation() => "calculation",
Value::ArgList(..) => "arglist",
Value::Call(..) => "string",
Value::Call(ref f, _) => {
if f == "var" {
"variable"
} else {
"string"
}
}
Value::Color(..) => "color",
Value::Literal(..) => "string",
Value::BinOp(_) => "string",
Expand Down Expand Up @@ -376,12 +383,12 @@ impl TryFrom<Value> for Color {
}

impl TryFrom<Value> for Numeric {
type Error = String;
type Error = IsNot;

fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::Numeric(num, ..) => Ok(num),
v => Err(is_not(&v, "a number")),
v => Err(IsNot::new(v, "a number")),
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions rsass/src/parser/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,12 @@ pub fn function_call_or_string(input: Span) -> PResult<Value> {

fn literal_or_color(s: SassString) -> Value {
if let Some(val) = s.single_raw() {
if val == "infinity" {
return Value::scalar(f64::INFINITY);
}
if val == "NaN" {
return Value::scalar(f64::NAN);
}
if let Some(rgba) = Rgba::from_name(val) {
return Value::Color(rgba, Some(val.to_string()));
}
Expand Down
12 changes: 11 additions & 1 deletion rsass/src/sass/functions/call_error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::super::{ArgsError, Name};
use crate::css::BadSelector;
use crate::css::{BadSelector, Value};
use crate::input::SourcePos;
use crate::output::Format;
use crate::{Error, Invalid, ScopeError};
use std::fmt;

Expand All @@ -26,6 +27,15 @@ impl CallError {
CallError::Invalid(Invalid::AtError(msg.to_string()))
}

/// The values were expected to be compatible, but wasn't.
pub fn incompatible_values(a: &Value, b: &Value) -> Self {
Self::msg(format!(
"{} and {} are incompatible.",
a.format(Format::introspect()),
b.format(Format::introspect()),
))
}

/// Map this error to a [`crate::Error`].
pub fn called_from(self, call_pos: &SourcePos, name: &str) -> Error {
match self {
Expand Down
Loading

0 comments on commit 5a35868

Please sign in to comment.