diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 7bc2f25..5577915 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -232,6 +232,65 @@ impl<'a> Parser<'a> { ) } + fn parse_fn_call( + &mut self, + mut s: String, + lower: String, + ) -> SassResult> { + if lower == "min" || lower == "max" { + match self.try_parse_min_max(&lower, true)? { + Some(val) => { + self.toks.truncate_iterator_to_cursor(); + return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( + Value::String(val, QuoteKind::None), + )) + .span(self.span_before)); + } + None => { + self.toks.reset_cursor(); + } + } + } + + let as_ident = Identifier::from(&s); + let func = match self.scopes.get_fn(as_ident, self.global_scope) { + Some(f) => f, + None => { + if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) { + return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( + SassFunction::Builtin(f.clone(), as_ident), + self.parse_call_args()?, + )) + .span(self.span_before)); + } else { + // check for special cased CSS functions + match unvendor(&lower) { + "calc" | "element" | "expression" => { + s = lower; + self.parse_calc_args(&mut s)?; + } + "url" => match self.try_parse_url()? { + Some(val) => s = val, + None => s.push_str(&self.parse_call_args()?.to_css_string()?), + }, + _ => s.push_str(&self.parse_call_args()?.to_css_string()?), + } + + return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( + Value::String(s, QuoteKind::None), + )) + .span(self.span_before)); + } + } + }; + + let call_args = self.parse_call_args()?; + Ok( + IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args)) + .span(self.span_before), + ) + } + fn parse_ident_value( &mut self, predicate: &dyn Fn(&mut PeekMoreIterator>) -> bool, @@ -255,72 +314,22 @@ impl<'a> Parser<'a> { }); } - match self.toks.peek() { - Some(Token { kind: '(', .. }) => { - self.toks.next(); - - if lower == "min" || lower == "max" { - match self.try_parse_min_max(&lower, true)? { - Some(val) => { - self.toks.truncate_iterator_to_cursor(); - return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( - Value::String(val, QuoteKind::None), - )) - .span(span)); - } - None => { - self.toks.reset_cursor(); - } - } - } - - let as_ident = Identifier::from(&s); - let func = match self.scopes.get_fn(as_ident, self.global_scope) { - Some(f) => f, - None => { - if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) { - return Ok(IntermediateValue::Value( - HigherIntermediateValue::Function( - SassFunction::Builtin(f.clone(), as_ident), - self.parse_call_args()?, - ), - ) - .span(span)); - } else { - // check for special cased CSS functions - match unvendor(&lower) { - "calc" | "element" | "expression" => { - s = lower; - self.parse_calc_args(&mut s)?; - } - "url" => match self.try_parse_url()? { - Some(val) => s = val, - None => s.push_str(&self.parse_call_args()?.to_css_string()?), - }, - _ => s.push_str(&self.parse_call_args()?.to_css_string()?), - } - - return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( - Value::String(s, QuoteKind::None), - )) - .span(span)); - } - } - }; - - let call_args = self.parse_call_args()?; - return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( - func, call_args, - )) - .span(span)); - } - Some(Token { kind: '.', .. }) => { - if !predicate(self.toks) { + if !is_keyword_operator(&s) { + match self.toks.peek() { + Some(Token { kind: '(', .. }) => { + self.span_before = span; self.toks.next(); - return self.parse_module_item(&s, span); + + return self.parse_fn_call(s, lower); } + Some(Token { kind: '.', .. }) => { + if !predicate(self.toks) { + self.toks.next(); + return self.parse_module_item(&s, span); + } + } + _ => {} } - _ => {} } // check for named colors @@ -1356,3 +1365,7 @@ fn parse_i64(s: &str) -> i64 { .iter() .fold(0, |total, this| total * 10 + i64::from(this - b'0')) } + +fn is_keyword_operator(s: &str) -> bool { + matches!(s, "and" | "or" | "not") +} diff --git a/tests/plain-css-fn.rs b/tests/plain-css-fn.rs index bdbacec..41935a3 100644 --- a/tests/plain-css-fn.rs +++ b/tests/plain-css-fn.rs @@ -41,3 +41,45 @@ test!( "a {\n $primary: #f2ece4;\n $accent: #e1d7d2;\n color: radial-gradient($primary, $accent);\n}\n", "a {\n color: radial-gradient(#f2ece4, #e1d7d2);\n}\n" ); +test!( + fn_named_not_is_evaluated_as_unary_op, + "a {\n color: not(true);\n}\n", + "a {\n color: false;\n}\n" +); +test!( + fn_named_true_is_plain_css, + "a {\n color: true(true);\n}\n", + "a {\n color: true(true);\n}\n" +); +test!( + fn_named_false_is_plain_css, + "a {\n color: false(true);\n}\n", + "a {\n color: false(true);\n}\n" +); +test!( + fn_named_null_is_plain_css, + "a {\n color: null(true);\n}\n", + "a {\n color: null(true);\n}\n" +); +test!( + fn_named_and_is_evaluated_as_binop, + "a {\n color: true and(foo);\n}\n", + "a {\n color: foo;\n}\n" +); +test!( + fn_named_or_is_evaluated_as_binop, + "a {\n color: true or(foo);\n}\n", + "a {\n color: true;\n}\n" +); +test!( + #[ignore = "this is not currently parsed correctly"] + fn_named_and_alone_is_not_evaluated_as_binop, + "a {\n color: and(foo);\n}\n", + "a {\n color: and(foo);\n}\n" +); +test!( + #[ignore = "this is not currently parsed correctly"] + fn_named_or_alone_is_not_evaluated_as_binop, + "a {\n color: or(foo);\n}\n", + "a {\n color: or(foo);\n}\n" +);