diff --git a/src/value/css_function.rs b/src/value/css_function.rs new file mode 100644 index 0000000..8d62094 --- /dev/null +++ b/src/value/css_function.rs @@ -0,0 +1,77 @@ +use std::iter::Peekable; + +use crate::error::SassResult; +use crate::scope::Scope; +use crate::selector::Selector; +use crate::utils::{devour_whitespace, parse_interpolation}; +use crate::Token; + +pub(crate) fn eat_calc_args>( + toks: &mut Peekable, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + let mut string = String::from("("); + let mut nesting = 0; + while let Some(tok) = toks.next() { + match tok.kind { + ' ' | '\t' | '\n' => { + devour_whitespace(toks); + string.push(' '); + } + '#' => { + if toks.peek().is_some() && toks.peek().unwrap().kind == '{' { + toks.next(); + string.push_str(&parse_interpolation(toks, scope, super_selector)?.to_string()); + } else { + string.push('#'); + } + } + '(' => { + nesting += 1; + string.push('('); + } + ')' => { + if nesting == 0 { + break; + } else { + nesting -= 1; + string.push(')'); + } + } + c => string.push(c), + } + } + string.push(')'); + Ok(string) +} + +#[allow(dead_code)] +pub(crate) fn is_special_function(s: &str) -> bool { + s.starts_with("calc(") + || s.starts_with("var(") + || s.starts_with("env(") + || s.starts_with("min(") + || s.starts_with("max(") +} + +pub(crate) fn eat_progid>( + toks: &mut Peekable, + scope: &Scope, + super_selector: &Selector, +) -> SassResult { + let mut string = String::new(); + while let Some(tok) = toks.next() { + match tok.kind { + 'a'..='z' | 'A'..='Z' | '.' => { + string.push(tok.kind); + } + '(' => { + string.push_str(&eat_calc_args(toks, scope, super_selector)?); + break; + } + _ => return Err("expected \"(\".".into()), + } + } + Ok(string) +} diff --git a/src/value/mod.rs b/src/value/mod.rs index 009fe52..97f3da9 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -11,6 +11,7 @@ pub(crate) use map::SassMap; pub(crate) use number::Number; pub(crate) use sass_function::SassFunction; +mod css_function; mod map; mod number; mod ops; diff --git a/src/value/parse.rs b/src/value/parse.rs index 61e2387..f658be8 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -6,6 +6,8 @@ use num_bigint::BigInt; use num_rational::BigRational; use num_traits::pow; +use super::css_function::{eat_calc_args, eat_progid}; + use crate::args::eat_call_args; use crate::builtin::GLOBAL_FUNCTIONS; use crate::color::Color; @@ -360,6 +362,12 @@ impl Value { super_selector: &Selector, ) -> SassResult { let mut s = eat_ident(toks, scope, super_selector)?; + if s == "progid" && toks.peek().is_some() && toks.peek().unwrap().kind == ':' { + toks.next(); + s.push(':'); + s.push_str(&eat_progid(toks, scope, super_selector)?); + return Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))); + } match toks.peek() { Some(Token { kind: '(', .. }) => { toks.next(); @@ -374,10 +382,18 @@ impl Value { )?)) } None => { - s.push_str( - &eat_call_args(toks, scope, super_selector)? - .to_css_string(scope, super_selector)?, - ); + match s.as_str() { + "calc" | "element" | "expression" => { + s.push_str(&eat_calc_args(toks, scope, super_selector)?) + } + // "min" => {} + // "max" => {} + // "url" => {} + _ => s.push_str( + &eat_call_args(toks, scope, super_selector)? + .to_css_string(scope, super_selector)?, + ), + } return Ok(IntermediateValue::Value(Value::Ident(s, QuoteKind::None))); } }, diff --git a/tests/special-functions.rs b/tests/special-functions.rs new file mode 100644 index 0000000..b02684a --- /dev/null +++ b/tests/special-functions.rs @@ -0,0 +1,134 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + calc_whitespace, + "a {\n color: calc( 1 );\n}\n", + "a {\n color: calc( 1 );\n}\n" +); +test!( + calc_multiple_args, + "a {\n color: calc(1, 2, a, b, c);\n}\n" +); +test!( + calc_does_not_evaluate_arithmetic, + "a {\n color: calc(1 + 2);\n}\n" +); +test!( + calc_evaluates_interpolated_arithmetic, + "a {\n color: calc(#{1 + 2});\n}\n", + "a {\n color: calc(3);\n}\n" +); +test!( + #[ignore] + calc_retains_silent_comment, + "a {\n color: calc(//);\n}\n" +); +test!( + calc_retains_multiline_comment, + "a {\n color: calc(/**/);\n}\n" +); +test!(calc_nested_parens, "a {\n color: calc((((()))));\n}\n"); +test!( + element_whitespace, + "a {\n color: element( 1 );\n}\n", + "a {\n color: element( 1 );\n}\n" +); +test!( + element_multiple_args, + "a {\n color: element(1, 2, a, b, c);\n}\n" +); +test!( + element_does_not_evaluate_arithmetic, + "a {\n color: element(1 + 2);\n}\n" +); +test!( + element_evaluates_interpolated_arithmetic, + "a {\n color: element(#{1 + 2});\n}\n", + "a {\n color: element(3);\n}\n" +); +test!( + #[ignore] + element_retains_silent_comment, + "a {\n color: element(//);\n}\n" +); +test!( + element_retains_multiline_comment, + "a {\n color: element(/**/);\n}\n" +); +test!( + element_nested_parens, + "a {\n color: element((((()))));\n}\n" +); +test!( + expression_whitespace, + "a {\n color: expression( 1 );\n}\n", + "a {\n color: expression( 1 );\n}\n" +); +test!( + expression_multiple_args, + "a {\n color: expression(1, 2, a, b, c);\n}\n" +); +test!( + expression_does_not_evaluate_arithmetic, + "a {\n color: expression(1 + 2);\n}\n" +); +test!( + expression_evaluates_interpolated_arithmetic, + "a {\n color: expression(#{1 + 2});\n}\n", + "a {\n color: expression(3);\n}\n" +); +test!( + #[ignore] + expression_retains_silent_comment, + "a {\n color: expression(//);\n}\n" +); +test!( + expression_retains_multiline_comment, + "a {\n color: expression(/**/);\n}\n" +); +test!( + expression_nested_parens, + "a {\n color: expression((((()))));\n}\n" +); +test!( + progid_whitespace, + "a {\n color: progid:( 1 );\n}\n", + "a {\n color: progid:( 1 );\n}\n" +); +test!( + progid_multiple_args, + "a {\n color: progid:(1, 2, a, b, c);\n}\n" +); +test!( + progid_does_not_evaluate_arithmetic, + "a {\n color: progid:(1 + 2);\n}\n" +); +test!( + progid_evaluates_interpolated_arithmetic, + "a {\n color: progid:(#{1 + 2});\n}\n", + "a {\n color: progid:(3);\n}\n" +); +test!( + #[ignore] + progid_retains_silent_comment, + "a {\n color: progid:(//);\n}\n" +); +test!( + progid_retains_multiline_comment, + "a {\n color: progid:(/**/);\n}\n" +); +test!( + progid_nested_parens, + "a {\n color: progid:((((()))));\n}\n" +); +test!( + progid_values_after_colon, + "a {\n color: progid:apple.bottoM..jeans.boots();\n}\n" +); +error!( + progid_number_after_colon, + "a {\n color: progid:ap1ple.bottoM..jeans.boots();\n}\n", "Error: expected \"(\"." +);