From 621ba21c72e09bb0def1e10cff77c9b61965f379 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sat, 28 Apr 2018 20:41:17 +0200 Subject: [PATCH 01/12] The `@each` construct can bind multipe values. --- src/output_style.rs | 9 +++++---- src/parser/mod.rs | 26 +++++++++++++++++++++----- src/sass/item.rs | 2 +- src/variablescope.rs | 30 ++++++++++++++++++++++++++++-- tests/basic.rs | 14 ++++++++++++++ 5 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/output_style.rs b/src/output_style.rs index 33425613b..799ed2110 100644 --- a/src/output_style.rs +++ b/src/output_style.rs @@ -203,10 +203,11 @@ impl OutputStyle { self.handle_root_item(item, scope, file_context, result)?; } } - Item::Each(ref name, ref values, ref body) => for value in + Item::Each(ref names, ref values, ref body) => for value in values.evaluate(scope).iter_items() { - scope.define(name, &value); + // TODO: No local sub-scope here?!? + scope.define_multi(names, &value); for item in body { self.handle_root_item(item, scope, file_context, result)?; } @@ -470,10 +471,10 @@ impl OutputStyle { 0, )?; } - Item::Each(ref name, ref values, ref body) => { + Item::Each(ref names, ref values, ref body) => { for value in values.evaluate(scope).iter_items() { let mut scope = ScopeImpl::sub(scope); - scope.define(name, &value); + scope.define_multi(&names, &value); self.handle_body( direct, sub, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 152764e08..82c3e35b2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -188,11 +188,27 @@ named!( named!( each_loop, - do_parse!( - tag!("@each") >> spacelike >> tag!("$") >> name: name >> spacelike - >> tag!("in") >> spacelike >> values: value_expression - >> opt_spacelike >> body: body_block - >> (Item::Each(name, values, body)) + map!( + tuple!( + preceded!( + terminated!(tag!("@each"), spacelike), + separated_nonempty_list!( + complete!(delimited!( + opt_spacelike, + tag!(","), + opt_spacelike + )), + preceded!(tag!("$"), name) + ) + ), + delimited!( + delimited!(spacelike, tag!("in"), spacelike), + value_expression, + spacelike + ), + body_block + ), + |(names, values, body)| Item::Each(names, values, body) ) ); diff --git a/src/sass/item.rs b/src/sass/item.rs index be8f699e9..76fd17fa6 100644 --- a/src/sass/item.rs +++ b/src/sass/item.rs @@ -39,7 +39,7 @@ pub enum Item { IfStatement(Value, Vec, Vec), /// The value may be or evaluate to a list. - Each(String, Value, Vec), + Each(Vec, Value, Vec), For { name: String, from: Box, diff --git a/src/variablescope.rs b/src/variablescope.rs index 1a25cd9e0..42ed75c57 100644 --- a/src/variablescope.rs +++ b/src/variablescope.rs @@ -24,6 +24,32 @@ pub trait Scope { /// Define a variable in the global scope that is an ultimate /// parent of this scope. fn define_global(&self, name: &str, val: &Value); + + /// Define multiple names from a value that is a list. + /// Special case: in names is a single name, value is used directly. + fn define_multi(&mut self, names: &[String], value: &Value) { + if names.len() == 1 { + self.define(&names[0], &value); + } else if let &Value::List(ref values, ..) = value { + if values.len() != names.len() { + panic!( + "Expected {} values, but got {}", + names.len(), + values.len(), + ) + } else { + for (name, value) in names.iter().zip(values) { + self.define(name, &value); + } + } + } else { + panic!( + "Got multiple bindings {:?}, but non-list value {}", + names, value + ) + } + } + /// Get the Value for a variable. fn get(&self, name: &str) -> Value; fn get_global(&self, name: &str) -> Value; @@ -57,9 +83,9 @@ pub trait Scope { self.eval_body(do_else) } } - Item::Each(ref name, ref values, ref body) => { + Item::Each(ref names, ref values, ref body) => { for value in values.evaluate(self).iter_items() { - self.define(name, &value); + self.define_multi(names, &value); if let Some(r) = self.eval_body(body) { return Some(r); } diff --git a/tests/basic.rs b/tests/basic.rs index 5826989db..e37d8bda1 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1408,6 +1408,20 @@ fn weight_parameter() { ) } +/// From `spec/libsass-closed-issues/issue_1133/normal` +#[test] +fn each_binds_multiple() { + check( + b"@function foo($map) {\n @return $map;\n}\n\n\ + a {\n $map: foo((this: is, my: map));\n \ + @each $k, $v in $map {\n #{$k}: $v;\n }\n}\n\n\ + b {\n $map: call(\"foo\", (this: is, my: map));\n \ + @each $k, $v in $map {\n #{$k}: $v;\n }\n}\n", + "a {\n this: is;\n my: map;\n}\n\n\ + b {\n this: is;\n my: map;\n}\n", + ) +} + fn check(input: &[u8], expected: &str) { assert_eq!( compile_scss(input, OutputStyle::Expanded) From ac51daada65cb8b8f31b5d53c67edc9d5fef80c0 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Apr 2018 15:27:21 +0200 Subject: [PATCH 02/12] Test file structure. --- tests/css.rs | 59 ---------------------------- tests/css/main.rs | 31 +++++++++++++++ tests/{ => css}/unknown_directive.rs | 22 +++-------- 3 files changed, 36 insertions(+), 76 deletions(-) delete mode 100644 tests/css.rs create mode 100644 tests/css/main.rs rename tests/{ => css}/unknown_directive.rs (72%) diff --git a/tests/css.rs b/tests/css.rs deleted file mode 100644 index 9b41bc087..000000000 --- a/tests/css.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! These are from the `css` directory in the sass specification. -extern crate rsass; -use rsass::{compile_scss, OutputStyle}; - -#[test] -fn multi_star_comments() { - // Note, actual expected has single newlines, but the sass-spec - // test runner succeeds my implementation. - check( - "a /***/ b {x: y}\n\ - a /****/ b {x: y}\n\ - a /* **/ b {x: y}\n\ - a /** */ b {x: y}\n", - "a b {\n x: y;\n}\n\n\ - a b {\n x: y;\n}\n\n\ - a b {\n x: y;\n}\n\n\ - a b {\n x: y;\n}\n", - ) -} - -#[test] -fn unknown_directive() { - check( - "// Unknown directives should support almost any sequence of \ - valid tokens,\n\ - // including interpolation.\n\n\ - // By default, characters are passed through unaltered.\n\ - @asdf .~@#$%^&*()_-+=[]|:<>,.?/;\n\n\ - // Strings are tokenized as strings.\n\ - @asdf \"f'o\" 'b\"r' url(baz) url(\"qux\");\n\n\ - // Comments are preserved.\n\ - @asdf foo //\n bar;\n\ - @asdf foo /* bar */ baz;\n\n\ - // Interpolation is supported in plain text, strings, and URLs.\n\ - @asdf #{1 + 2};\n\ - @asdf \"foo #{\"bar\"} baz\";\n\ - @asdf 'foo #{'bar'} baz';\n\ - @asdf url(http://#{\")\"}.com/);\n\ - @asdf url(\"http://#{\")\"}.com/\");\n", - "@asdf .~@#$%^&*()_-+=[]|:<>,.?/;\n\ - @asdf \"f'o\" 'b\"r' url(baz) url(\"qux\");\n\ - @asdf foo //\n bar;\n\ - @asdf foo /* bar */ baz;\n\ - @asdf 3;\n\ - @asdf \"foo bar baz\";\n\ - @asdf 'foo bar baz';\n\ - @asdf url(http://).com/);\n\ - @asdf url(\"http://).com/\");\n", - ) -} - -fn check(input: &str, expected: &str) { - assert_eq!( - compile_scss(input.as_bytes(), OutputStyle::Expanded) - .and_then(|s| Ok(String::from_utf8(s)?)) - .unwrap(), - expected - ); -} diff --git a/tests/css/main.rs b/tests/css/main.rs new file mode 100644 index 000000000..f882d53d0 --- /dev/null +++ b/tests/css/main.rs @@ -0,0 +1,31 @@ +//! Tests from `sass_spec/spec/css/` + +extern crate rsass; +use rsass::{compile_scss, OutputStyle}; + +mod unknown_directive; + +#[test] +fn multi_star_comments() { + // Note, actual expected has single newlines, but the sass-spec + // test runner succeeds my implementation. + check( + "a /***/ b {x: y}\n\ + a /****/ b {x: y}\n\ + a /* **/ b {x: y}\n\ + a /** */ b {x: y}\n", + "a b {\n x: y;\n}\n\n\ + a b {\n x: y;\n}\n\n\ + a b {\n x: y;\n}\n\n\ + a b {\n x: y;\n}\n", + ) +} + +fn check(input: &str, expected: &str) { + assert_eq!( + compile_scss(input.as_bytes(), OutputStyle::Expanded) + .and_then(|s| Ok(String::from_utf8(s)?)) + .unwrap(), + expected + ); +} diff --git a/tests/unknown_directive.rs b/tests/css/unknown_directive.rs similarity index 72% rename from tests/unknown_directive.rs rename to tests/css/unknown_directive.rs index ac4aed071..7d53a956a 100644 --- a/tests/unknown_directive.rs +++ b/tests/css/unknown_directive.rs @@ -1,20 +1,8 @@ -extern crate rsass; - -use rsass::{compile_scss, OutputStyle}; - -/// Tests from `sass_spec/spec/css/unknown_directive` - -// Unknown directives should support almost any sequence of valid tokens, -// including interpolation. - -fn check(input: &str, expected: &str) { - assert_eq!( - compile_scss(input.as_bytes(), OutputStyle::Expanded) - .and_then(|s| Ok(String::from_utf8(s)?)) - .map_err(|e| format!("{:?}", e)), - Ok(expected.to_string()) - ); -} +//! Parts of `sass_spec/spec/css/unknown_directive` as separate tests. +//! +//! Unknown directives should support almost any sequence of valid tokens, +//! including interpolation. +use super::check; #[test] fn t01_characters_are_passed_through_unaltered() { From 412a8023f23586dbec1170db2238805ab0fb4c6c Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Apr 2018 15:33:50 +0200 Subject: [PATCH 03/12] Add support for unicode ranges. --- src/css/value.rs | 4 +++ src/parser/value.rs | 19 ++++++++++++ src/sass/value.rs | 4 +++ src/value/operator.rs | 17 ++++++++++- tests/css/main.rs | 1 + tests/css/unicode_range.rs | 61 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/css/unicode_range.rs diff --git a/src/css/value.rs b/src/css/value.rs index 6b22ebf06..26ea2f3fd 100644 --- a/src/css/value.rs +++ b/src/css/value.rs @@ -42,6 +42,9 @@ pub enum Value { BinOp(Box, Operator, Box), UnaryOp(Operator, Box), Map(OrderMap), + /// A unicode range for font selections. U+NN, U+N?, U+NN-MM. + /// The string is the entire value, including the "U+" tag. + UnicodeRange(String), } impl Value { @@ -427,6 +430,7 @@ impl fmt::Display for Value { .collect::>() .join(", ") ), + Value::UnicodeRange(ref s) => write!(out, "{}", s), } } } diff --git a/src/parser/value.rs b/src/parser/value.rs index 05295e643..f0bb45a71 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -185,6 +185,7 @@ named!( value!(Value::True, tag!("true")) | value!(Value::False, tag!("false")) | value!(Value::HereSelector, tag!("&")) | + unicode_range | do_parse!(tag!("[") >> content: opt!(value_expression) >> tag!("]") >> @@ -263,6 +264,24 @@ named!( ) ); +named!( + unicode_range, + map!( + recognize!(do_parse!( + tag_no_case!("U+") + >> a: many_m_n!(0, 6, one_of!("0123456789ABCDEFabcdef")) + >> alt_complete!( + preceded!( + tag!("-"), + many_m_n!(1, 6, one_of!("0123456789ABCDEFabcdef")) + ) + | many_m_n!(0, 6 - a.len(), one_of!("?")) + ) >> () + )), + |range| Value::UnicodeRange(from_utf8(range).unwrap().into()) + ) +); + named!( decimal_integer, map!( diff --git a/src/sass/value.rs b/src/sass/value.rs index 3d94fc604..48bed4839 100644 --- a/src/sass/value.rs +++ b/src/sass/value.rs @@ -50,6 +50,9 @@ pub enum Value { Map(OrderMap), /// The magic value "&", exanding to the current selectors. HereSelector, + /// A unicode range for font selections. U+NN, U+N?, U+NN-MM. + /// The string is the entire value, including the "U+" tag. + UnicodeRange(String), } impl Value { @@ -285,6 +288,7 @@ impl Value { } } Value::HereSelector => scope.get_selectors().to_value(), + Value::UnicodeRange(ref s) => css::Value::UnicodeRange(s.clone()), } } } diff --git a/src/value/operator.rs b/src/value/operator.rs index 7539c922d..986c8a5e5 100644 --- a/src/value/operator.rs +++ b/src/value/operator.rs @@ -1,7 +1,7 @@ use css::Value; use num_rational::Rational; use std::fmt; -use value::{Quotes, Unit}; +use value::{ListSeparator, Quotes, Unit}; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Operator { @@ -107,6 +107,21 @@ impl Operator { ) } } + // Note: This very special case should probably be much + // more general. + (&Value::UnicodeRange(..), &Value::Literal(..)) => { + Value::List( + vec![ + a.clone(), + Value::UnaryOp( + Operator::Minus, + Box::new(b.clone()), + ), + ], + ListSeparator::Space, + false, + ) + } _ => Value::BinOp( Box::new(a.clone()), Operator::Minus, diff --git a/tests/css/main.rs b/tests/css/main.rs index f882d53d0..492ec5d62 100644 --- a/tests/css/main.rs +++ b/tests/css/main.rs @@ -3,6 +3,7 @@ extern crate rsass; use rsass::{compile_scss, OutputStyle}; +mod unicode_range; mod unknown_directive; #[test] diff --git a/tests/css/unicode_range.rs b/tests/css/unicode_range.rs new file mode 100644 index 000000000..074082ab5 --- /dev/null +++ b/tests/css/unicode_range.rs @@ -0,0 +1,61 @@ +//! Tests from `sass-spec/spec/css/unicode_range` + +use super::check; + +#[test] +fn simple() { + check( + ".simple {\n one-digit: U+1;\n four-digit: U+1234;\n \ + six-digit: U+123456;\n hex-digit: U+1A2B;\n \ + lowercase: u+1a2b;\n}\n", + ".simple {\n one-digit: U+1;\n four-digit: U+1234;\n \ + six-digit: U+123456;\n hex-digit: U+1A2B;\n \ + lowercase: u+1a2b;\n}\n", + ) +} + +#[test] +fn question_mark() { + check( + ".question-mark {\n one-digit-question-only: U+?;\n \ + four-digit-question-only: U+????;\n \ + six-digit-question-only: U+??????;\n \ + two-digit-half-question: U+A?;\n \ + six-digit-half-question: U+123???;\n\n \ + // These are valid CSS, and should parse as a Unicode \ + range followed by an\n // identifier.\n \ + followed-by-ident: U+A?BCDE;\n \ + followed-by-minus: U+A?-BCDE;\n\n \ + // This should parse as (U+A?) - (1234).\n \ + followed-by-number: U+A?-1234;\n}\n", + ".question-mark {\n one-digit-question-only: U+?;\n \ + four-digit-question-only: U+????;\n \ + six-digit-question-only: U+??????;\n \ + two-digit-half-question: U+A?;\n \ + six-digit-half-question: U+123???;\n \ + followed-by-ident: U+A? BCDE;\n \ + followed-by-minus: U+A? -BCDE;\n \ + followed-by-number: U+A?-1234;\n}\n", + ) +} + +#[test] +fn range() { + check( + ".range {\n one-digit-each: U+1-B;\n \ + four-digit-each: U+1A2B-F9E8;\n \ + six-digit-each: U+1A2B3C-10FFFF;\n \ + one-then-six: U+1-000007;\n six-then-one: U+000001-7;\n\n \ + // Ruby and Dart Sass will allow ranges with values above \ + the maximum allowed\n \ + // code point, and ranges whose start values are greater than \ + their end\n \ + // values. These produce invalid CSS, though, and as such are \ + not necessary\n \ + // for all implementations to support.\n}\n", + ".range {\n one-digit-each: U+1-B;\n \ + four-digit-each: U+1A2B-F9E8;\n \ + six-digit-each: U+1A2B3C-10FFFF;\n \ + one-then-six: U+1-000007;\n six-then-one: U+000001-7;\n}\n", + ) +} From 5aa39f7b0f5e3b9f7b81653367bd8c3172b4cea9 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Apr 2018 16:12:23 +0200 Subject: [PATCH 04/12] A css3 pseudo-element selector may have arguments. --- src/output_style.rs | 12 ++++++++---- src/parser/selectors.rs | 10 ++++++++-- src/selectors.rs | 26 ++++++++++++++++++++------ tests/css/main.rs | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/output_style.rs b/src/output_style.rs index 799ed2110..0afb1ffd3 100644 --- a/src/output_style.rs +++ b/src/output_style.rs @@ -656,10 +656,14 @@ fn eval_selectors(s: &Selectors, scope: &Scope) -> Selectors { arg: arg.as_ref() .map(|a| eval_selectors(a, scope)), }, - SelectorPart::PseudoElement(ref e) => { - let e = e.evaluate2(scope); - SelectorPart::PseudoElement(e) - } + SelectorPart::PseudoElement { + ref name, + ref arg, + } => SelectorPart::PseudoElement { + name: name.evaluate2(scope), + arg: arg.as_ref() + .map(|a| eval_selectors(a, scope)), + }, ref sp => sp.clone(), }) .collect(), diff --git a/src/parser/selectors.rs b/src/parser/selectors.rs index 1d30a3b57..9ddefc589 100644 --- a/src/parser/selectors.rs +++ b/src/parser/selectors.rs @@ -23,8 +23,14 @@ named!(selector_part<&[u8], SelectorPart>, alt_complete!( map!(sass_string, SelectorPart::Simple) | value!(SelectorPart::Simple("*".into()), tag!("*")) | - map!(preceded!(tag!("::"), sass_string), - SelectorPart::PseudoElement) | + do_parse!(tag!("::") >> + name: sass_string >> + arg: opt!(delimited!(tag!("("), selectors, + tag!(")"))) >> + (SelectorPart::PseudoElement { + name, + arg, + })) | do_parse!(tag!(":") >> name: sass_string >> arg: opt!(delimited!(tag!("("), selectors, diff --git a/src/selectors.rs b/src/selectors.rs index 0e3ec7539..1ac2a6bd1 100644 --- a/src/selectors.rs +++ b/src/selectors.rs @@ -93,9 +93,12 @@ pub enum SelectorPart { op: String, val: SassString, }, - /// A css3 pseudo-element - PseudoElement(SassString), - /// A pseudo-class or a css2 pseudo-element + /// A css3 pseudo-element (::foo) + PseudoElement { + name: SassString, + arg: Option, + }, + /// A pseudo-class or a css2 pseudo-element (:foo) Pseudo { name: SassString, arg: Option, @@ -109,7 +112,7 @@ impl SelectorPart { SelectorPart::Descendant | SelectorPart::RelOp(_) => true, SelectorPart::Simple(_) | SelectorPart::Attribute { .. } - | SelectorPart::PseudoElement(_) + | SelectorPart::PseudoElement { .. } | SelectorPart::Pseudo { .. } | SelectorPart::BackRef => false, } @@ -170,8 +173,19 @@ impl fmt::Display for SelectorPart { ref op, ref val, } => write!(out, "[{}{}{}]", name, op, val), - SelectorPart::PseudoElement(ref name) => { - write!(out, "::{}", name) + SelectorPart::PseudoElement { + ref name, + ref arg, + } => { + write!(out, "::{}", name)?; + if let Some(ref arg) = *arg { + if out.alternate() { + write!(out, "({:#})", arg)? + } else { + write!(out, "({})", arg)? + } + } + Ok(()) } SelectorPart::Pseudo { ref name, diff --git a/tests/css/main.rs b/tests/css/main.rs index 492ec5d62..9971673b3 100644 --- a/tests/css/main.rs +++ b/tests/css/main.rs @@ -6,6 +6,15 @@ use rsass::{compile_scss, OutputStyle}; mod unicode_range; mod unknown_directive; +#[test] +fn bizarrely_formatted_comments() { + check( + ".foo {\n /* Foo\n Bar\nBaz */\n a: b; }\n", + ".foo {\n /* Foo\n Bar\nBaz */\n a: b;\n}\n", + ) +} + + #[test] fn multi_star_comments() { // Note, actual expected has single newlines, but the sass-spec @@ -22,6 +31,34 @@ fn multi_star_comments() { ) } +#[test] +fn selector_slotted_part() { + check( + "::slotted(.a) {x: y}\n\n\ + ::slotted(.c.d) {x: y}\n\ + ::slotted(.f) {x: y}\n", + "::slotted(.a) {\n x: y;\n}\n\n\ + ::slotted(.c.d) {\n x: y;\n}\n\n\ + ::slotted(.f) {\n x: y;\n}\n", + ) +} + +#[test] +#[ignore] // @extend is not yet supported +fn selector_slotted() { + check( + "::slotted(.a) {x: y}\n\n\ + ::slotted(.c.d) {x: y}\n\ + .e {@extend .c}\n\n\ + ::slotted(.f) {x: y}\n\ + ::slotted(.g) {@extend .f}\n", + "::slotted(.a) {\n x: y;\n}\n\n\ + ::slotted(.c.d, .d.e) {\n x: y;\n}\n\ + ::slotted(.f, ::slotted(.g)) {\n x: y;\n}\n", + ) +} + + fn check(input: &str, expected: &str) { assert_eq!( compile_scss(input.as_bytes(), OutputStyle::Expanded) From e81d42ab6ca103514142c1567e25bfe50c806211 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Apr 2018 19:06:11 +0200 Subject: [PATCH 05/12] Make !important part of value rather than property. This way, !important can be used in fuction / mixin arguments etc. --- src/css/value.rs | 3 +++ src/output_style.rs | 25 +++++++++---------------- src/parser/mod.rs | 28 ++++------------------------ src/parser/value.rs | 7 +++++++ src/sass/item.rs | 2 +- src/sass/value.rs | 3 +++ tests/css/main.rs | 2 -- tests/scss.rs | 12 ++++++++++++ 8 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/css/value.rs b/src/css/value.rs index 26ea2f3fd..32b3cdc57 100644 --- a/src/css/value.rs +++ b/src/css/value.rs @@ -10,6 +10,8 @@ use value::{rgb_to_name, ListSeparator, Operator, Quotes, Unit}; /// A sass value. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Value { + /// A special kind of escape. Only really used for !important. + Bang(String), /// An function call that was not evaluated. Call(String, CallArgs), /// A (callable?) function. @@ -257,6 +259,7 @@ impl Value { impl fmt::Display for Value { fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { match *self { + Value::Bang(ref s) => write!(out, "!{}", s), Value::Literal(ref s, ref q) => match *q { Quotes::Double => write!( out, diff --git a/src/output_style.rs b/src/output_style.rs index 0afb1ffd3..da256e9fe 100644 --- a/src/output_style.rs +++ b/src/output_style.rs @@ -528,11 +528,8 @@ impl OutputStyle { Item::NamespaceRule(ref name, ref value, ref body) => { let value = value.evaluate(scope); if !value.is_null() { - direct.push(CssBodyItem::Property( - name.clone(), - value, - false, - )); + direct + .push(CssBodyItem::Property(name.clone(), value)); } let mut t = Vec::new(); self.handle_body( @@ -545,23 +542,21 @@ impl OutputStyle { )?; for item in t { direct.push(match item { - CssBodyItem::Property(n, v, i) => { + CssBodyItem::Property(n, v) => { CssBodyItem::Property( format!("{}-{}", name, n), v, - i, ) } c => c, }) } } - Item::Property(ref name, ref value, ref important) => { + Item::Property(ref name, ref value) => { let v = value.evaluate(scope); if !v.is_null() { let (name, _q) = name.evaluate(scope); - direct - .push(CssBodyItem::Property(name, v, *important)); + direct.push(CssBodyItem::Property(name, v)); } } Item::Comment(ref c) => { @@ -749,20 +744,18 @@ impl CssWriter { } enum CssBodyItem { - Property(String, Value, bool), + Property(String, Value), Comment(String), } impl fmt::Display for CssBodyItem { fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { match *self { - CssBodyItem::Property(ref name, ref val, ref imp) => { + CssBodyItem::Property(ref name, ref val) => { if out.alternate() { - let important = if *imp { "!important" } else { "" }; - write!(out, "{}:{:#}{};", name, val, important) + write!(out, "{}:{:#};", name, val) } else { - let important = if *imp { " !important" } else { "" }; - write!(out, "{}: {}{};", name, val, important) + write!(out, "{}: {};", name, val) } } CssBodyItem::Comment(ref c) => write!(out, "/*{}*/", c), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 82c3e35b2..641c633c6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -283,10 +283,9 @@ named!(property<&[u8], Item>, do_parse!(opt_spacelike >> name: sass_string >> opt_spacelike >> tag!(":") >> opt_spacelike >> - val: value_expression >> - imp: opt_important >> opt_spacelike >> + val: value_expression >> opt_spacelike >> opt!(tag!(";")) >> opt_spacelike >> - (Item::Property(name, val, imp)))); + (Item::Property(name, val)))); named!( namespace_rule, @@ -298,17 +297,6 @@ named!( ) ); -named!( - opt_important, - map!( - opt!(do_parse!( - opt_spacelike >> tag!("!") >> opt_spacelike >> tag!("important") - >> () - )), - |o: Option<()>| o.is_some() - ) -); - named!( body_block>, delimited!( @@ -362,11 +350,7 @@ fn if_with_no_else() { vec![ Item::Rule( selectors(b"p").unwrap().1, - vec![Item::Property( - "color".into(), - Value::black(), - false, - )], + vec![Item::Property("color".into(), Value::black())], ), Item::None, ], @@ -463,7 +447,6 @@ fn test_mixin_declaration() { false, false, ), - false, )], } ) @@ -493,13 +476,12 @@ fn test_mixin_declaration_default_and_subrules() { false ), body: vec![ - Item::Property("foo-bar".into(), string("baz"), false), + Item::Property("foo-bar".into(), string("baz")), Item::Rule( selectors(b"foo, bar").unwrap().1, vec![Item::Property( "property".into(), Value::Variable("b".into()), - false, )], ), Item::None, @@ -522,7 +504,6 @@ fn test_simple_property() { Item::Property( "color".into(), Value::Color(r(255), r(0), r(0), one, Some("red".into())), - false ) ) ) @@ -541,7 +522,6 @@ fn test_property_2() { false, false ), - false ) ) ) diff --git a/src/parser/value.rs b/src/parser/value.rs index f0bb45a71..9ecf9329d 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -182,6 +182,13 @@ named!(pub single_value<&[u8], Value>, named!( simple_value, alt_complete!( + map!( + preceded!( + pair!(tag!("!"), opt_spacelike), + tag!("important") // TODO Pretty much anythig goes, here? + ), + |s| Value::Bang(from_utf8(s).unwrap().into()) + ) | value!(Value::True, tag!("true")) | value!(Value::False, tag!("false")) | value!(Value::HereSelector, tag!("&")) | diff --git a/src/sass/item.rs b/src/sass/item.rs index 76fd17fa6..9e8e437da 100644 --- a/src/sass/item.rs +++ b/src/sass/item.rs @@ -51,7 +51,7 @@ pub enum Item { Rule(Selectors, Vec), NamespaceRule(String, Value, Vec), - Property(SassString, Value, bool), + Property(SassString, Value), Comment(String), None, } diff --git a/src/sass/value.rs b/src/sass/value.rs index 48bed4839..cad723f6d 100644 --- a/src/sass/value.rs +++ b/src/sass/value.rs @@ -10,6 +10,8 @@ use variablescope::Scope; /// A sass value. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Value { + /// A special kind of escape. Only really used for !important. + Bang(String), /// A call has a name and an argument (which may be multi). Call(SassString, CallArgs), /// Sometimes an actual division, sometimes "a/b". @@ -139,6 +141,7 @@ impl Value { } pub fn do_evaluate(&self, scope: &Scope, arithmetic: bool) -> css::Value { match *self { + Value::Bang(ref s) => css::Value::Bang(s.clone()), Value::Literal(ref s) => { let (s, q) = s.evaluate(scope); if s == "" && q == Quotes::None { diff --git a/tests/css/main.rs b/tests/css/main.rs index 9971673b3..5775717fc 100644 --- a/tests/css/main.rs +++ b/tests/css/main.rs @@ -14,7 +14,6 @@ fn bizarrely_formatted_comments() { ) } - #[test] fn multi_star_comments() { // Note, actual expected has single newlines, but the sass-spec @@ -58,7 +57,6 @@ fn selector_slotted() { ) } - fn check(input: &str, expected: &str) { assert_eq!( compile_scss(input.as_bytes(), OutputStyle::Expanded) diff --git a/tests/scss.rs b/tests/scss.rs index 7fe531d16..6bac6149b 100644 --- a/tests/scss.rs +++ b/tests/scss.rs @@ -279,6 +279,18 @@ fn important_compact_input() { ) } +#[test] +fn important_in_arglist() { + check( + "@mixin foo($x) {\n style: $x;\n}\n\n\ + div {\n \ + @include foo(0px 0px 0px 0px #ef8086 inset !important);\n \ + fludge: foo bar ! important hux;\n}", + "div {\n style: 0px 0px 0px 0px #ef8086 inset !important;\n \ + fludge: foo bar !important hux;\n}\n", + ) +} + #[test] fn interpolation() { check( From e12732e7575e09dea0f74f9f3b02b8d679fbc996 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Apr 2018 21:33:50 +0200 Subject: [PATCH 06/12] Implement the feature-exists function. --- src/functions/introspection.rs | 26 ++++++++++++++++++++++++++ tests/libsass.rs | 8 ++++++++ 2 files changed, 34 insertions(+) diff --git a/src/functions/introspection.rs b/src/functions/introspection.rs index 4fbd47ec0..be09fd4e8 100644 --- a/src/functions/introspection.rs +++ b/src/functions/introspection.rs @@ -4,7 +4,33 @@ use std::collections::BTreeMap; use value::{Quotes, Unit}; use variablescope::Scope; +static IMPLEMENTED_FEATURES: &[&'static str] = &[ + // A local variable will shadow a global variable unless + // `!global` is used. + "global-variable-shadowing", + // "extend-selector-pseudoclass" - nothing with `@extend` is implemented + // Full support for unit arithmetic using units defined in the + // [Values and Units Level 3][] spec. + "units-level-3", + // The Sass `@error` directive is supported. + // "at-error", + // The "Custom Properties Level 1" spec is supported. This means + // that custom properties are parsed statically, with only + // interpolation treated as SassScript. + // "custom-property", +]; + pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { + def!( + f, + feature_exists(name), + |s| match &s.get("name") { + &Value::Literal(ref v, _) => Ok(Value::bool( + IMPLEMENTED_FEATURES.iter().any(|s| s == v) + )), + v => Err(Error::badarg("string", v)), + } + ); def!( f, variable_exists(name), diff --git a/tests/libsass.rs b/tests/libsass.rs index 7206e364e..43aa2747b 100644 --- a/tests/libsass.rs +++ b/tests/libsass.rs @@ -31,6 +31,14 @@ fn t01_arg_eval() { ) } +#[test] +fn features_units_level_3() { + check( + b"foo {\n foo: feature-exists('units-level-3');\n}\n", + "foo {\n foo: true;\n}\n", + ) +} + fn check(input: &[u8], expected: &str) { assert_eq!( compile_scss(input, OutputStyle::Expanded) From 02e26535428cb9335e6238a0c52459f531f3cd56 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 29 Apr 2018 21:51:51 +0200 Subject: [PATCH 07/12] Another path check for imports. --- src/file_context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/file_context.rs b/src/file_context.rs index 8f595923f..2de7b46a8 100644 --- a/src/file_context.rs +++ b/src/file_context.rs @@ -57,6 +57,7 @@ impl FileContext { name, &format!("{}.scss", name), &format!("_{}.scss", name), + &format!("{}/index.scss", name), ] { let full = parent .map(|p| p.join(name)) From 1c472a19ea3485e161c217e45eb58577b93f669e Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Mon, 30 Apr 2018 00:21:31 +0200 Subject: [PATCH 08/12] A peculiar way of not failing rgba function. --- src/functions/colors_rgb.rs | 20 +++++++++++++++++--- tests/basic.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/functions/colors_rgb.rs b/src/functions/colors_rgb.rs index f44feb774..d3831cb3b 100644 --- a/src/functions/colors_rgb.rs +++ b/src/functions/colors_rgb.rs @@ -1,9 +1,9 @@ use super::{Error, SassFunction}; -use css::Value; +use css::{CallArgs, Value}; use num_rational::Rational; use num_traits::One; use std::collections::BTreeMap; -use value::Unit; +use value::{ListSeparator, Unit}; pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { def!(f, rgb(red, green, blue), |s| Ok(Value::rgba( @@ -26,7 +26,17 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { } else { a }; - Ok(Value::rgba(r, g, b, to_rational(a)?)) + match a { + Value::Numeric(a, ..) => Ok(Value::rgba(r, g, b, a)), + _ => Ok(Value::Call( + "rgba".into(), + CallArgs::from_value(Value::List( + vec![int_value(r), int_value(g), int_value(b), a], + ListSeparator::Space, + false, + )), + )), + } } else { Ok(Value::rgba( to_int(red)?, @@ -115,6 +125,10 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { }); } +fn int_value(v: Rational) -> Value { + Value::scalar(v.to_integer()) +} + fn to_int(v: Value) -> Result { match v { Value::Numeric(v, Unit::Percent, ..) => { diff --git a/tests/basic.rs b/tests/basic.rs index e37d8bda1..0a44f767d 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1408,6 +1408,37 @@ fn weight_parameter() { ) } +/// From `spec/core_functions/rgba/success` +#[test] +fn rgba_success() { + check( + b"a {\n calc-1: rgba(calc(1), 2, 3, 0.4);\n \ + calc-2: rgba(1, calc(2), 3, 0.4);\n \ + calc-3: rgba(1, 2, calc(3), 0.4);\n \ + calc-4: rgba(1, 2, 3, calc(0.4));\n\n \ + var-1: rgba(var(--foo), 2, 3, 0.4);\n \ + var-2: rgba(1, var(--foo), 3, 0.4);\n \ + var-3: rgba(1, 2, var(--foo), 0.4);\n \ + var-4: rgba(1, 2, 3, var(0.4));\n\n \ + calc-2-args: rgba(blue, calc(0.4));\n \ + var-2-args-alpha: rgba(blue, var(0.4));\n \ + var-2-args-color: rgba(var(--foo), 0.4);\n \ + var-2-args-both: rgba(var(--foo), var(0.4));\n}\n", + "a {\n calc-1: rgba(calc(1), 2, 3, 0.4);\n \ + calc-2: rgba(1, calc(2), 3, 0.4);\n \ + calc-3: rgba(1, 2, calc(3), 0.4);\n \ + calc-4: rgba(1, 2, 3, calc(0.4));\n \ + var-1: rgba(var(--foo), 2, 3, 0.4);\n \ + var-2: rgba(1, var(--foo), 3, 0.4);\n \ + var-3: rgba(1, 2, var(--foo), 0.4);\n \ + var-4: rgba(1, 2, 3, var(0.4));\n \ + calc-2-args: rgba(0, 0, 255, calc(0.4));\n \ + var-2-args-alpha: rgba(0, 0, 255, var(0.4));\n \ + var-2-args-color: rgba(var(--foo), 0.4);\n \ + var-2-args-both: rgba(var(--foo), var(0.4));\n}\n", + ) +} + /// From `spec/libsass-closed-issues/issue_1133/normal` #[test] fn each_binds_multiple() { From 4217ab45d8daa2ba143dc069070d17496c251f34 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Mon, 30 Apr 2018 13:22:13 +0200 Subject: [PATCH 09/12] Add two simple unit tests. --- src/selectors.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/selectors.rs b/src/selectors.rs index 1ac2a6bd1..9962f4b7d 100644 --- a/src/selectors.rs +++ b/src/selectors.rs @@ -222,4 +222,23 @@ mod test { let s = Selector(vec![SelectorPart::Simple("foo".into())]); assert_eq!(Selector::root().join(&s), s) } + + #[test] + fn simple_join() { + let s = Selector(vec![SelectorPart::Simple("foo".into())]).join( + &Selector(vec![SelectorPart::Simple(".bar".into())]), + ); + assert_eq!(format!("{}", s), "foo .bar") + } + + #[test] + fn backref_join() { + let s = Selector(vec![SelectorPart::Simple("foo".into())]).join( + &Selector(vec![ + SelectorPart::BackRef, + SelectorPart::Simple(".bar".into()), + ]), + ); + assert_eq!(format!("{}", s), "foo.bar") + } } From b8dc01f3d7e1113eab7a92d39bd0a49b33465119 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Tue, 1 May 2018 21:42:28 +0200 Subject: [PATCH 10/12] Introduce a Number type. This is mainly for sharing some abstraction between css and sass values. --- src/css/value.rs | 94 +++++----------------------- src/functions/colors_hsl.rs | 18 ++++-- src/functions/colors_other.rs | 18 +++--- src/functions/colors_rgb.rs | 26 ++++---- src/functions/numbers.rs | 24 +++---- src/functions/strings.rs | 12 ++-- src/lib.rs | 2 +- src/parser/mod.rs | 9 +-- src/parser/value.rs | 34 ++++++---- src/sass/value.rs | 69 ++++++++------------ src/value/mod.rs | 2 + src/value/number.rs | 114 ++++++++++++++++++++++++++++++++++ src/value/operator.rs | 26 +++++--- tests/rust_functions.rs | 49 ++++++--------- 14 files changed, 270 insertions(+), 227 deletions(-) create mode 100644 src/value/number.rs diff --git a/src/css/value.rs b/src/css/value.rs index 32b3cdc57..1e208ba67 100644 --- a/src/css/value.rs +++ b/src/css/value.rs @@ -4,8 +4,8 @@ use functions::SassFunction; use num_rational::Rational; use num_traits::{One, Signed, Zero}; use ordermap::OrderMap; -use std::fmt::{self, Write}; -use value::{rgb_to_name, ListSeparator, Operator, Quotes, Unit}; +use std::fmt; +use value::{rgb_to_name, ListSeparator, Number, Operator, Quotes, Unit}; /// A sass value. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -25,11 +25,9 @@ pub enum Value { /// A Numeric value is a rational value with a Unit (which may be /// Unit::None) and flags. /// - /// The first flag is true for values with an explicit + sign. - /// - /// The second flag is true for calculated values and false for + /// The boolean flag is true for calculated values and false for /// literal values. - Numeric(Rational, Unit, bool, bool), + Numeric(Number, Unit, bool), Color( Rational, Rational, @@ -51,12 +49,7 @@ pub enum Value { impl Value { pub fn scalar(v: isize) -> Self { - Value::Numeric( - Rational::from_integer(v), - Unit::None, - false, - false, - ) + Value::Numeric(Number::from_integer(v), Unit::None, false) } pub fn bool(v: bool) -> Self { if v { @@ -105,7 +98,7 @@ impl Value { pub fn is_calculated(&self) -> bool { match *self { - Value::Numeric(_, _, _, calculated) => calculated, + Value::Numeric(.., calculated) => calculated, Value::Color(_, _, _, _, None) => true, _ => false, } @@ -113,9 +106,7 @@ impl Value { pub fn into_calculated(self) -> Self { match self { - Value::Numeric(v, unit, with_sign, _) => { - Value::Numeric(v, unit, with_sign, true) - } + Value::Numeric(num, unit, _) => Value::Numeric(num, unit, true), Value::List(v, sep, bracketed) => Value::List( v.into_iter() .map(|i| i.into_calculated()) @@ -292,14 +283,9 @@ impl fmt::Display for Value { .collect::(); write!(out, "get-function(\"{}\")", name) } - Value::Numeric(ref v, ref u, ref with_sign, _) => { - let skipzero = out.alternate(); - write!( - out, - "{}{}", - Decimals::new(v, *with_sign, skipzero), - u - ) + Value::Numeric(ref num, ref unit, _) => { + num.fmt(out)?; + unit.fmt(out) } Value::Color(ref r, ref g, ref b, ref a, ref name) => { if let Some(ref name) = *name { @@ -334,13 +320,13 @@ impl fmt::Display for Value { } else if out.alternate() { write!( out, + // I think the last {} should be {:#} here, + // but the test suite says otherwise. "rgba({},{},{},{})", r.round().to_integer() as u8, g.round().to_integer() as u8, b.round().to_integer() as u8, - // I think skip_zero should be true here, - // but the test suite says otherwise. - Decimals::new(a, false, false), + Number::new(a.clone(), false), ) } else { write!( @@ -349,7 +335,7 @@ impl fmt::Display for Value { r.round().to_integer() as u8, g.round().to_integer() as u8, b.round().to_integer() as u8, - Decimals::new(a, false, false), + Number::new(a.clone(), false), ) } } @@ -437,55 +423,3 @@ impl fmt::Display for Value { } } } - -struct Decimals<'a> { - r: &'a Rational, - with_sign: bool, - skip_zero: bool, -} - -impl<'a> Decimals<'a> { - fn new(r: &'a Rational, with_sign: bool, skip_zero: bool) -> Self { - Decimals { - r, - with_sign, - skip_zero, - } - } -} - -impl<'a> fmt::Display for Decimals<'a> { - fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { - let t = self.r.to_integer(); - if t == 0 { - if self.r.is_negative() { - out.write_str("-0")?; - } else if self.with_sign { - out.write_str("+0")?; - } else if self.r.is_zero() || !self.skip_zero { - out.write_char('0')?; - } - } else { - if self.with_sign && !t.is_negative() { - out.write_char('+')?; - } - write!(out, "{}", t)?; - } - let mut f = self.r.fract().abs(); - if !f.is_zero() { - out.write_char('.')?; - for _ in 0..4 { - f *= 10; - write!(out, "{}", f.to_integer())?; - f = f.fract(); - if f.is_zero() { - break; - } - } - if !f.is_zero() { - write!(out, "{}", (f * 10).round().to_integer())?; - } - } - Ok(()) - } -} diff --git a/src/functions/colors_hsl.rs b/src/functions/colors_hsl.rs index a1df1236a..eb4181798 100644 --- a/src/functions/colors_hsl.rs +++ b/src/functions/colors_hsl.rs @@ -3,7 +3,7 @@ use css::Value; use num_rational::Rational; use num_traits::{One, Signed, Zero}; use std::collections::BTreeMap; -use value::Unit; +use value::{Number, Unit}; use variablescope::Scope; pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { @@ -131,7 +131,11 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { |args: &Scope| match &args.get("color") { &Value::Color(ref red, ref green, ref blue, ref _alpha, _) => { let (h, _s, _l) = rgb_to_hsl(red, green, blue); - Ok(Value::Numeric(h, Unit::Deg, false, true)) + Ok(Value::Numeric( + Number::new(h, false), + Unit::Deg, + true, + )) } v => Err(Error::badarg("color", v)), } @@ -198,9 +202,8 @@ pub fn hsla_to_rgba( fn percentage(v: Rational) -> Value { Value::Numeric( - v * Rational::from_integer(100), + Number::new(v * Rational::from_integer(100), false), Unit::Percent, - false, true, ) } @@ -308,7 +311,7 @@ fn cap_u8(n: Rational) -> Rational { fn to_rational(v: Value) -> Result { match v { - Value::Numeric(v, ..) => Ok(v), + Value::Numeric(v, ..) => Ok(v.value), v => Err(Error::badarg("number", &v)), } } @@ -317,8 +320,11 @@ fn to_rational(v: Value) -> Result { /// If v is not a percentage, keep it as it is. fn to_rational_percent(v: Value) -> Result { match v { - Value::Numeric(v, Unit::Percent, ..) => Ok(v * Rational::new(1, 100)), + Value::Numeric(v, Unit::Percent, _) => { + Ok(v.value * Rational::new(1, 100)) + } Value::Numeric(v, ..) => { + let v = v.value; if v <= Rational::new(1, 1) { Ok(v) } else { diff --git a/src/functions/colors_other.rs b/src/functions/colors_other.rs index eada95fe3..42137d835 100644 --- a/src/functions/colors_other.rs +++ b/src/functions/colors_other.rs @@ -4,7 +4,7 @@ use css::Value; use num_rational::Rational; use num_traits::{One, Signed, Zero}; use std::collections::BTreeMap; -use value::{Quotes, Unit}; +use value::{Number, Quotes, Unit}; use variablescope::Scope; pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { @@ -73,9 +73,11 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { fn opacity(color: Value) -> Result { match color { - Value::Color(_r, _g, _b, a, _) => { - Ok(Value::Numeric(a, Unit::None, false, true)) - } + Value::Color(_r, _g, _b, a, _) => Ok(Value::Numeric( + Number::new(a, false), + Unit::None, + true, + )), v => Err(Error::badarg("color", &v)), } } @@ -227,7 +229,7 @@ fn cap_u8(n: Rational) -> Rational { fn to_rational(v: Value) -> Result { match v { - Value::Numeric(v, ..) => Ok(v), + Value::Numeric(v, ..) => Ok(v.value), v => Err(Error::badarg("number", &v)), } } @@ -236,8 +238,10 @@ fn to_rational(v: Value) -> Result { /// If v is not a percentage, keep it as it is. fn to_rational_percent(v: Value) -> Result { match v { - Value::Numeric(v, Unit::Percent, ..) => Ok(v * Rational::new(1, 100)), - Value::Numeric(v, ..) => Ok(v), + Value::Numeric(v, Unit::Percent, _) => { + Ok(v.value * Rational::new(1, 100)) + } + Value::Numeric(v, ..) => Ok(v.value), v => Err(Error::badarg("number", &v)), } } diff --git a/src/functions/colors_rgb.rs b/src/functions/colors_rgb.rs index d3831cb3b..82524ce60 100644 --- a/src/functions/colors_rgb.rs +++ b/src/functions/colors_rgb.rs @@ -3,7 +3,7 @@ use css::{CallArgs, Value}; use num_rational::Rational; use num_traits::One; use std::collections::BTreeMap; -use value::{ListSeparator, Unit}; +use value::{ListSeparator, Number, Unit}; pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { def!(f, rgb(red, green, blue), |s| Ok(Value::rgba( @@ -27,7 +27,7 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { a }; match a { - Value::Numeric(a, ..) => Ok(Value::rgba(r, g, b, a)), + Value::Numeric(a, ..) => Ok(Value::rgba(r, g, b, a.value)), _ => Ok(Value::Call( "rgba".into(), CallArgs::from_value(Value::List( @@ -47,7 +47,11 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { } }); fn num(v: &Rational) -> Result { - Ok(Value::Numeric(*v, Unit::None, false, true)) + Ok(Value::Numeric( + Number::new(*v, false), + Unit::None, + true, + )) } def!(f, red(color), |s| match &s.get("color") { &Value::Color(ref red, _, _, _, _) => num(red), @@ -72,9 +76,9 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { ) = (&color1, &color2, &weight) { let w = if wu == &Unit::Percent { - w / Rational::from_integer(100) + w.value / Rational::from_integer(100) } else { - *w + w.value }; let one = Rational::one(); let w2 = one - (one - w * a1) * a2; @@ -102,9 +106,9 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { ) => { let ff = Rational::new(255, 1); let w = if wu == &Unit::Percent { - w / Rational::from_integer(100) + w.value / Rational::from_integer(100) } else { - *w + w.value }; fn m(v1: &Rational, v2: &Rational, w: Rational) -> Rational { *v1 * w + *v2 * (Rational::one() - w) @@ -131,17 +135,17 @@ fn int_value(v: Rational) -> Value { fn to_int(v: Value) -> Result { match v { - Value::Numeric(v, Unit::Percent, ..) => { - Ok(Rational::new(255, 100) * v) + Value::Numeric(v, Unit::Percent, _) => { + Ok(Rational::new(255, 100) * v.value) } - Value::Numeric(v, ..) => Ok(v), + Value::Numeric(v, ..) => Ok(v.value), v => Err(Error::badarg("number", &v)), } } fn to_rational(v: Value) -> Result { match v { - Value::Numeric(v, ..) => Ok(v), + Value::Numeric(num, ..) => Ok(num.value), v => Err(Error::badarg("number", &v)), } } diff --git a/src/functions/numbers.rs b/src/functions/numbers.rs index 9711f2ccb..04ac9fa8f 100644 --- a/src/functions/numbers.rs +++ b/src/functions/numbers.rs @@ -4,34 +4,34 @@ use num_rational::Rational; use num_traits::Signed; use std::cmp::Ordering; use std::collections::BTreeMap; -use value::Unit; +use value::{Number, Unit}; pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { def!(f, abs(number), |s| match s.get("number") { - Value::Numeric(v, u, ..) => Ok(number(v.abs(), u)), + Value::Numeric(v, u, ..) => Ok(number(v.value.abs(), u)), v => Err(Error::badarg("number", &v)), }); def!(f, ceil(number), |s| match s.get("number") { - Value::Numeric(v, u, ..) => Ok(number(v.ceil(), u)), + Value::Numeric(v, u, ..) => Ok(number(v.value.ceil(), u)), v => Err(Error::badarg("number", &v)), }); def!(f, floor(number), |s| match s.get("number") { - Value::Numeric(v, u, ..) => Ok(number(v.floor(), u)), + Value::Numeric(v, u, ..) => Ok(number(v.value.floor(), u)), v => Err(Error::badarg("number", &v)), }); def!( f, percentage(number), |s| match s.get("number") { - Value::Numeric(val, Unit::None, ..) => Ok(number( - val * Rational::from_integer(100), + Value::Numeric(val, Unit::None, _) => Ok(number( + val.value * Rational::from_integer(100), Unit::Percent )), v => Err(Error::badarg("number", &v)), } ); def!(f, round(number), |s| match s.get("number") { - Value::Numeric(val, unit, ..) => Ok(number(val.round(), unit)), + Value::Numeric(val, unit, _) => Ok(number(val.value.round(), unit)), v => Err(Error::badarg("number", &v)), }); def_va!(f, max(numbers), |s| match s.get("numbers") { @@ -61,7 +61,7 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { } fn number(v: Rational, unit: Unit) -> Value { - Value::Numeric(v, unit, false, true) + Value::Numeric(Number::new(v, false), unit, true) } fn find_extreme(v: &[Value], pref: Ordering) -> &Value { @@ -72,8 +72,8 @@ fn find_extreme(v: &[Value], pref: Ordering) -> &Value { (&Value::Null, b) => b, (a, &Value::Null) => a, ( - &Value::Numeric(ref va, ref ua, ..), - &Value::Numeric(ref vb, ref ub, ..), + &Value::Numeric(ref va, ref ua, _), + &Value::Numeric(ref vb, ref ub, _), ) => { if ua == &Unit::None || ua == ub || ub == &Unit::None { if va.cmp(vb) == pref { @@ -82,8 +82,8 @@ fn find_extreme(v: &[Value], pref: Ordering) -> &Value { second } } else if ua.dimension() == ub.dimension() { - let sa = va * ua.scale_factor(); - let sb = vb * ub.scale_factor(); + let sa = va.value * ua.scale_factor(); + let sb = vb.value * ub.scale_factor(); if sa.cmp(&sb) == pref { first } else { diff --git a/src/functions/strings.rs b/src/functions/strings.rs index cb99c6848..2036fb5aa 100644 --- a/src/functions/strings.rs +++ b/src/functions/strings.rs @@ -1,9 +1,8 @@ use super::{Error, SassFunction}; use css::Value; -use num_rational::Rational; use num_traits::Signed; use std::collections::BTreeMap; -use value::{Quotes, Unit}; +use value::{Number, Quotes, Unit}; pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { def!( @@ -126,9 +125,8 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { fn intvalue(n: usize) -> Value { Value::Numeric( - Rational::from_integer(n as isize), + Number::from_integer(n as isize), Unit::None, - false, true, ) } @@ -136,8 +134,8 @@ fn intvalue(n: usize) -> Value { /// Convert index from sass (rational number, first is one) to rust /// (usize, first is zero). Sass values might be negative, then -1 is /// the last char in the string. -fn index_to_rust(index: Rational, s: &str) -> usize { - if index.is_negative() { +fn index_to_rust(index: Number, s: &str) -> usize { + if index.value.is_negative() { let l = s.chars().count(); let i = index.to_integer().abs() as usize; if l > i { @@ -145,7 +143,7 @@ fn index_to_rust(index: Rational, s: &str) -> usize { } else { 0 } - } else if index.is_positive() { + } else if index.value.is_positive() { index.to_integer() as usize - 1 } else { 0 diff --git a/src/lib.rs b/src/lib.rs index 93ca66051..4ca176061 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,7 +63,7 @@ pub use num_rational::Rational; pub use output_style::OutputStyle; pub use parser::{parse_scss_data, parse_scss_file, parse_value_data}; pub use sass::Item; -pub use value::{ListSeparator, Quotes, Unit}; +pub use value::{ListSeparator, Number, Quotes, Unit}; pub use variablescope::{GlobalScope, Scope}; /// Parse scss data from a buffer and write css in the given style. diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 641c633c6..9fb802bf0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -24,7 +24,7 @@ use std::path::Path; use std::str::from_utf8; use value::ListSeparator; #[cfg(test)] -use value::Unit; +use value::{Number, Unit}; /// Parse a scss value. /// @@ -326,12 +326,7 @@ named!( #[cfg(test)] fn percentage(v: isize) -> Value { - Value::Numeric( - Rational::from_integer(v), - Unit::Percent, - false, - false, - ) + Value::Numeric(Number::from_integer(v), Unit::Percent) } #[cfg(test)] diff --git a/src/parser/value.rs b/src/parser/value.rs index 9ecf9329d..444d68c15 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -8,7 +8,7 @@ use parser::unit::unit; use parser::util::{name, opt_spacelike, spacelike2}; use sass::{SassString, Value}; use std::str::from_utf8; -use value::{name_to_rgb, ListSeparator, Operator}; +use value::{name_to_rgb, ListSeparator, Number, Operator}; named!(pub value_expression<&[u8], Value>, do_parse!( @@ -218,21 +218,25 @@ named!( d: opt!(decimal_decimals) >> u: unit >> (Value::Numeric( - { + Number::new( + { let d = r + d.unwrap_or_else(Rational::zero); if sign == Some(b"-") { -d } else { d } - }, + }, + sign == Some(b"+"), + ), u, - sign == Some(b"+"), - false))) | + ))) | do_parse!(sign: opt!(alt!(tag!("-") | tag!("+"))) >> d: decimal_decimals >> u: unit >> (Value::Numeric( - if sign == Some(b"-") { -d } else { d }, + Number::new( + if sign == Some(b"-") { -d } else { d }, + sign == Some(b"+"), + ), u, - sign == Some(b"+"), - false))) | + ))) | variable | do_parse!(tag!("#") >> r: hexchar2 >> g: hexchar2 >> b: hexchar2 >> (Value::Color(from_hex(r), @@ -412,7 +416,10 @@ mod test { fn simple_number_pos() { check_expr( "+4;", - Numeric(Rational::new(4, 1), Unit::None, true, false), + Numeric( + Number::new(Rational::new(4, 1), true), + Unit::None, + ), ) } @@ -432,16 +439,17 @@ mod test { fn simple_number_onlydec_pos() { check_expr( "+.34;", - Numeric(Rational::new(34, 100), Unit::None, true, false), + Numeric( + Number::new(Rational::new(34, 100), true), + Unit::None, + ), ) } fn number(nom: isize, denom: isize) -> Value { Numeric( - Rational::new(nom, denom), + Number::new(Rational::new(nom, denom), false), Unit::None, - false, - false, ) } diff --git a/src/sass/value.rs b/src/sass/value.rs index cad723f6d..3fd079215 100644 --- a/src/sass/value.rs +++ b/src/sass/value.rs @@ -4,7 +4,7 @@ use num_rational::Rational; use num_traits::{One, Signed, Zero}; use ordermap::OrderMap; use sass::{CallArgs, SassString}; -use value::{ListSeparator, Operator, Quotes, Unit}; +use value::{ListSeparator, Number, Operator, Quotes, Unit}; use variablescope::Scope; /// A sass value. @@ -23,12 +23,7 @@ pub enum Value { List(Vec, ListSeparator, bool, bool), /// A Numeric value is a rational value with a Unit (which may be /// Unit::None) and flags. - /// - /// The first flag is true for values with an explicit + sign. - /// - /// The second flag is true for calculated values and false for - /// literal values. - Numeric(Rational, Unit, bool, bool), + Numeric(Number, Unit), /// "(a/b) and a/b differs semantically. Parens means the value /// should be evaluated numerically if possible, without parens / /// is not allways division. @@ -59,12 +54,7 @@ pub enum Value { impl Value { pub fn scalar(v: isize) -> Self { - Value::Numeric( - Rational::from_integer(v), - Unit::None, - false, - false, - ) + Value::Numeric(Number::from_integer(v), Unit::None) } pub fn bool(v: bool) -> Self { if v { @@ -110,14 +100,6 @@ impl Value { } } - pub fn is_calculated(&self) -> bool { - match *self { - Value::Numeric(_, _, _, calculated) => calculated, - Value::Color(_, _, _, _, None) => true, - _ => false, - } - } - /// All values other than `False` and `Null` should be considered true. pub fn is_true(&self) -> bool { match *self { @@ -188,9 +170,11 @@ impl Value { Value::Div(ref a, ref b, ref space1, ref space2) => { let (a, b) = { let aa = a.do_evaluate(scope, arithmetic); - let b = - b.do_evaluate(scope, arithmetic || a.is_calculated()); - if !arithmetic && b.is_calculated() && !a.is_calculated() + let b = b.do_evaluate( + scope, + arithmetic || aa.is_calculated(), + ); + if !arithmetic && b.is_calculated() && !aa.is_calculated() { (a.do_evaluate(scope, true), b) } else { @@ -201,8 +185,11 @@ impl Value { match (&a, &b) { ( &css::Value::Color(ref r, ref g, ref b, ref a, _), - &css::Value::Numeric(ref n, Unit::None, ..), - ) => css::Value::rgba(r / n, g / n, b / n, *a), + &css::Value::Numeric(ref n, Unit::None, _), + ) => { + let n = n.value; + css::Value::rgba(r / n, g / n, b / n, *a) + } ( &css::Value::Numeric(ref av, ref au, ..), &css::Value::Numeric(ref bv, ref bu, ..), @@ -215,19 +202,9 @@ impl Value { *space2, ) } else if bu == &Unit::None { - css::Value::Numeric( - av / bv, - au.clone(), - false, - true, - ) + css::Value::Numeric(av / bv, au.clone(), true) } else if au == bu { - css::Value::Numeric( - av / bv, - Unit::None, - false, - true, - ) + css::Value::Numeric(av / bv, Unit::None, true) } else { css::Value::Div( Box::new(a.clone()), @@ -253,8 +230,8 @@ impl Value { ) } } - Value::Numeric(ref v, ref u, ref sign, ref calc) => { - css::Value::Numeric(*v, u.clone(), *sign, arithmetic || *calc) + Value::Numeric(ref num, ref unit) => { + css::Value::Numeric(num.clone(), unit.clone(), arithmetic) } Value::Map(ref m) => css::Value::Map( m.iter() @@ -281,11 +258,15 @@ impl Value { } (Operator::Not, css::Value::True) => css::Value::False, (Operator::Not, css::Value::False) => css::Value::True, - (Operator::Minus, css::Value::Numeric(v, u, ..)) => { - css::Value::Numeric(-v, u, false, true) + (Operator::Minus, css::Value::Numeric(v, u, _)) => { + css::Value::Numeric(-&v, u, true) } - (Operator::Plus, css::Value::Numeric(v, u, ..)) => { - css::Value::Numeric(v, u, true, true) + (Operator::Plus, css::Value::Numeric(v, u, _)) => { + css::Value::Numeric( + Number::new(v.value, true), + u, + true, + ) } (op, v) => css::Value::UnaryOp(op, Box::new(v)), } diff --git a/src/value/mod.rs b/src/value/mod.rs index 256f08cd8..3d537d183 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,11 +1,13 @@ mod colors; mod list_separator; +mod number; mod operator; mod quotes; mod unit; pub use self::colors::{name_to_rgb, rgb_to_name}; pub use self::list_separator::ListSeparator; +pub use self::number::Number; pub use self::operator::Operator; pub use self::quotes::Quotes; pub use self::unit::Unit; diff --git a/src/value/number.rs b/src/value/number.rs new file mode 100644 index 000000000..b1d88dfcc --- /dev/null +++ b/src/value/number.rs @@ -0,0 +1,114 @@ +use num_rational::Rational; +use num_traits::{Signed, Zero}; +use std::fmt::{self, Write}; +use std::ops::{Add, Div, Mul, Neg, Sub}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Number { + pub value: Rational, + pub plus_sign: bool, +} + +impl Number { + pub fn new(value: Rational, plus_sign: bool) -> Self { + Number { + value, + plus_sign, + } + } + pub fn from_integer(n: isize) -> Self { + Number { + value: Rational::from_integer(n), + plus_sign: false, + } + } + pub fn abs(self) -> Self { + Number { + value: self.value.abs(), + plus_sign: self.plus_sign, + } + } + pub fn is_integer(&self) -> bool { + self.value.is_integer() + } + pub fn to_integer(&self) -> isize { + self.value.to_integer() + } +} + +impl Add for Number { + type Output = Number; + fn add(self, rhs: Self) -> Self::Output { + Number::new(self.value + rhs.value, false) + } +} +impl<'a> Div for &'a Number { + type Output = Number; + fn div(self, rhs: Self) -> Self::Output { + Number::new(self.value / rhs.value, false) + } +} +impl<'a> Mul for &'a Number { + type Output = Number; + fn mul(self, rhs: Self) -> Self::Output { + Number::new(self.value * rhs.value, false) + } +} +impl<'a> Neg for &'a Number { + type Output = Number; + fn neg(self) -> Number { + Number::new(-self.value, self.plus_sign) + } +} + +impl<'a> Sub for &'a Number { + type Output = Number; + fn sub(self, rhs: Self) -> Self::Output { + Number::new(self.value - rhs.value, false) + } +} +impl Zero for Number { + fn zero() -> Self { + Number::new(Rational::zero(), false) + } + fn is_zero(&self) -> bool { + self.value.is_zero() + } +} + +impl fmt::Display for Number { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + let t = self.value.to_integer(); + let skip_zero = out.alternate(); + if t == 0 { + if self.value.is_negative() { + out.write_str("-0")?; + } else if self.plus_sign { + out.write_str("+0")?; + } else if self.value.is_zero() || !skip_zero { + out.write_char('0')?; + } + } else { + if self.plus_sign && !t.is_negative() { + out.write_char('+')?; + } + write!(out, "{}", t)?; + } + let mut f = self.value.fract().abs(); + if !f.is_zero() { + out.write_char('.')?; + for _ in 0..4 { + f *= 10; + write!(out, "{}", f.to_integer())?; + f = f.fract(); + if f.is_zero() { + break; + } + } + if !f.is_zero() { + write!(out, "{}", (f * 10).round().to_integer())?; + } + } + Ok(()) + } +} diff --git a/src/value/operator.rs b/src/value/operator.rs index 986c8a5e5..838a6a635 100644 --- a/src/value/operator.rs +++ b/src/value/operator.rs @@ -40,8 +40,11 @@ impl Operator { match (a, b) { ( Value::Color(r, g, b, a, _), - Value::Numeric(n, Unit::None, ..), - ) => Value::rgba(r + n, g + n, b + n, a), + Value::Numeric(n, Unit::None, _), + ) => { + let n = n.value; + Value::rgba(r + n, g + n, b + n, a) + } ( Value::Color(ar, ag, ab, aa, _), Value::Color(br, bg, bb, ba, _), @@ -54,9 +57,9 @@ impl Operator { Value::Numeric(b, bu, ..), ) => { if au == bu || bu == Unit::None { - Value::Numeric(a + b, au, false, true) + Value::Numeric(a + b, au, true) } else if au == Unit::None { - Value::Numeric(a + b, bu, false, true) + Value::Numeric(a + b, bu, true) } else { Value::Literal( format!("{}{}", a, b), @@ -85,8 +88,11 @@ impl Operator { Operator::Minus => match (&a, &b) { ( &Value::Color(ref r, ref g, ref b, ref a, _), - &Value::Numeric(ref n, Unit::None, ..), - ) => Value::rgba(r - n, g - n, b - n, *a), + &Value::Numeric(ref n, Unit::None, _), + ) => { + let n = n.value; + Value::rgba(r - n, g - n, b - n, *a) + } ( &Value::Color(ref ar, ref ag, ref ab, ref aa, _), &Value::Color(ref br, ref bg, ref bb, ref ba, _), @@ -96,9 +102,9 @@ impl Operator { &Value::Numeric(ref bv, ref bu, ..), ) => { if au == bu || bu == &Unit::None { - Value::Numeric(av - bv, au.clone(), false, true) + Value::Numeric(av - bv, au.clone(), true) } else if au == &Unit::None { - Value::Numeric(av - bv, bu.clone(), false, true) + Value::Numeric(av - bv, bu.clone(), true) } else { Value::BinOp( Box::new(a.clone()), @@ -135,9 +141,9 @@ impl Operator { ) = (&a, &b) { if bu == &Unit::None { - Value::Numeric(a * b, au.clone(), false, true) + Value::Numeric(a * b, au.clone(), true) } else if au == &Unit::None { - Value::Numeric(a * b, bu.clone(), false, true) + Value::Numeric(a * b, bu.clone(), true) } else { Value::Literal(format!("{}*{}", a, b), Quotes::None) } diff --git a/tests/rust_functions.rs b/tests/rust_functions.rs index bdfedcf3d..b44d22b88 100644 --- a/tests/rust_functions.rs +++ b/tests/rust_functions.rs @@ -41,6 +41,12 @@ fn simple_function() { ); } +#[cfg(test)] +fn avg(a: Number, b: Number) -> Number { + let half = Rational::new(1, 2); + Number::new((a.value + b.value) * half, false) +} + #[test] fn function_with_args() { let mut scope = GlobalScope::new(); @@ -52,37 +58,22 @@ fn function_with_args() { ("b".into(), sass::Value::scalar(0)), ], false, - Arc::new(|s| { - let half = Rational::new(1, 2); - match (s.get("a"), s.get("b")) { - ( - css::Value::Numeric(a, au, ..), - css::Value::Numeric(b, bu, ..), - ) => { - if au == bu || bu == Unit::None { - Ok(css::Value::Numeric( - (a + b) * half, - au, - false, - true, - )) - } else if au == Unit::None { - Ok(css::Value::Numeric( - (a + b) * half, - bu, - false, - true, - )) - } else { - Err(Error::BadArguments( - "Incopatible args".into(), - )) - } - } - (a, b) => { - Err(Error::badargs(&["number", "number"], &[&a, &b])) + Arc::new(|s| match (s.get("a"), s.get("b")) { + ( + css::Value::Numeric(a, au, ..), + css::Value::Numeric(b, bu, ..), + ) => { + if au == bu || bu == Unit::None { + Ok(css::Value::Numeric(avg(a, b), au, true)) + } else if au == Unit::None { + Ok(css::Value::Numeric(avg(a, b), bu, true)) + } else { + Err(Error::BadArguments("Incopatible args".into())) } } + (a, b) => { + Err(Error::badargs(&["number", "number"], &[&a, &b])) + } }), ), ); From fa2698b3de0b9390e67c34ca4574a131fd67b907 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Tue, 1 May 2018 22:25:24 +0200 Subject: [PATCH 11/12] Keep track on if there was a leading zero. --- src/css/value.rs | 4 +-- src/functions/colors_hsl.rs | 8 ++--- src/functions/colors_other.rs | 8 ++--- src/functions/colors_rgb.rs | 2 +- src/functions/numbers.rs | 2 +- src/parser/value.rs | 60 ++++++++++++++++++++++++++--------- src/sass/value.rs | 6 +++- src/value/number.rs | 26 +++++++-------- tests/libsass.rs | 44 ++++++++++++++++++++++--- tests/rust_functions.rs | 2 +- 10 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/css/value.rs b/src/css/value.rs index 1e208ba67..efdaaa1fa 100644 --- a/src/css/value.rs +++ b/src/css/value.rs @@ -326,7 +326,7 @@ impl fmt::Display for Value { r.round().to_integer() as u8, g.round().to_integer() as u8, b.round().to_integer() as u8, - Number::new(a.clone(), false), + Number::new(a.clone()), ) } else { write!( @@ -335,7 +335,7 @@ impl fmt::Display for Value { r.round().to_integer() as u8, g.round().to_integer() as u8, b.round().to_integer() as u8, - Number::new(a.clone(), false), + Number::new(a.clone()), ) } } diff --git a/src/functions/colors_hsl.rs b/src/functions/colors_hsl.rs index eb4181798..4168463f0 100644 --- a/src/functions/colors_hsl.rs +++ b/src/functions/colors_hsl.rs @@ -131,11 +131,7 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { |args: &Scope| match &args.get("color") { &Value::Color(ref red, ref green, ref blue, ref _alpha, _) => { let (h, _s, _l) = rgb_to_hsl(red, green, blue); - Ok(Value::Numeric( - Number::new(h, false), - Unit::Deg, - true, - )) + Ok(Value::Numeric(Number::new(h), Unit::Deg, true)) } v => Err(Error::badarg("color", v)), } @@ -202,7 +198,7 @@ pub fn hsla_to_rgba( fn percentage(v: Rational) -> Value { Value::Numeric( - Number::new(v * Rational::from_integer(100), false), + Number::new(v * Rational::from_integer(100)), Unit::Percent, true, ) diff --git a/src/functions/colors_other.rs b/src/functions/colors_other.rs index 42137d835..36b66b7e1 100644 --- a/src/functions/colors_other.rs +++ b/src/functions/colors_other.rs @@ -73,11 +73,9 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { fn opacity(color: Value) -> Result { match color { - Value::Color(_r, _g, _b, a, _) => Ok(Value::Numeric( - Number::new(a, false), - Unit::None, - true, - )), + Value::Color(_r, _g, _b, a, _) => { + Ok(Value::Numeric(Number::new(a), Unit::None, true)) + } v => Err(Error::badarg("color", &v)), } } diff --git a/src/functions/colors_rgb.rs b/src/functions/colors_rgb.rs index 82524ce60..fccb41a5d 100644 --- a/src/functions/colors_rgb.rs +++ b/src/functions/colors_rgb.rs @@ -48,7 +48,7 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { }); fn num(v: &Rational) -> Result { Ok(Value::Numeric( - Number::new(*v, false), + Number::new(*v), Unit::None, true, )) diff --git a/src/functions/numbers.rs b/src/functions/numbers.rs index 04ac9fa8f..2c7e6d52c 100644 --- a/src/functions/numbers.rs +++ b/src/functions/numbers.rs @@ -61,7 +61,7 @@ pub fn register(f: &mut BTreeMap<&'static str, SassFunction>) { } fn number(v: Rational, unit: Unit) -> Value { - Value::Numeric(Number::new(v, false), unit, true) + Value::Numeric(Number::new(v), unit, true) } fn find_extreme(v: &[Value], pref: Ordering) -> &Value { diff --git a/src/parser/value.rs b/src/parser/value.rs index 444d68c15..7119184a8 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -218,23 +218,25 @@ named!( d: opt!(decimal_decimals) >> u: unit >> (Value::Numeric( - Number::new( - { - let d = r + d.unwrap_or_else(Rational::zero); - if sign == Some(b"-") { -d } else { d } + Number { + value: { + let d = r + d.unwrap_or_else(Rational::zero); + if sign == Some(b"-") { -d } else { d } }, - sign == Some(b"+"), - ), + plus_sign: sign == Some(b"+"), + lead_zero: true, // At least lead something? + }, u, ))) | do_parse!(sign: opt!(alt!(tag!("-") | tag!("+"))) >> d: decimal_decimals >> u: unit >> (Value::Numeric( - Number::new( - if sign == Some(b"-") { -d } else { d }, - sign == Some(b"+"), - ), + Number { + value: if sign == Some(b"-") { -d } else { d }, + plus_sign: sign == Some(b"+"), + lead_zero: false, + }, u, ))) | variable | @@ -417,7 +419,11 @@ mod test { check_expr( "+4;", Numeric( - Number::new(Rational::new(4, 1), true), + Number { + value: Rational::new(4, 1), + plus_sign: true, + lead_zero: true, + }, Unit::None, ), ) @@ -429,18 +435,42 @@ mod test { } #[test] fn simple_number_onlydec() { - check_expr(".34;", number(34, 100)) + check_expr( + ".34;", + Numeric( + Number { + value: Rational::new(34, 100), + plus_sign: false, + lead_zero: false, + }, + Unit::None, + ), + ) } #[test] fn simple_number_onlydec_neg() { - check_expr("-.34;", number(-34, 100)) + check_expr( + "-.34;", + Numeric( + Number { + value: Rational::new(-34, 100), + plus_sign: false, + lead_zero: false, + }, + Unit::None, + ), + ) } #[test] fn simple_number_onlydec_pos() { check_expr( "+.34;", Numeric( - Number::new(Rational::new(34, 100), true), + Number { + value: Rational::new(34, 100), // actually 17/50 + plus_sign: true, + lead_zero: false, + }, Unit::None, ), ) @@ -448,7 +478,7 @@ mod test { fn number(nom: isize, denom: isize) -> Value { Numeric( - Number::new(Rational::new(nom, denom), false), + Number::new(Rational::new(nom, denom)), Unit::None, ) } diff --git a/src/sass/value.rs b/src/sass/value.rs index 3fd079215..ef563c07f 100644 --- a/src/sass/value.rs +++ b/src/sass/value.rs @@ -263,7 +263,11 @@ impl Value { } (Operator::Plus, css::Value::Numeric(v, u, _)) => { css::Value::Numeric( - Number::new(v.value, true), + Number { + value: v.value, + plus_sign: true, + lead_zero: v.lead_zero, + }, u, true, ) diff --git a/src/value/number.rs b/src/value/number.rs index b1d88dfcc..ac2be9cef 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -7,25 +7,25 @@ use std::ops::{Add, Div, Mul, Neg, Sub}; pub struct Number { pub value: Rational, pub plus_sign: bool, + pub lead_zero: bool, } impl Number { - pub fn new(value: Rational, plus_sign: bool) -> Self { + pub fn new(value: Rational) -> Self { Number { value, - plus_sign, + plus_sign: false, + lead_zero: true, } } pub fn from_integer(n: isize) -> Self { - Number { - value: Rational::from_integer(n), - plus_sign: false, - } + Number::new(Rational::from_integer(n)) } pub fn abs(self) -> Self { Number { value: self.value.abs(), plus_sign: self.plus_sign, + lead_zero: self.lead_zero, } } pub fn is_integer(&self) -> bool { @@ -39,37 +39,37 @@ impl Number { impl Add for Number { type Output = Number; fn add(self, rhs: Self) -> Self::Output { - Number::new(self.value + rhs.value, false) + Number::new(self.value + rhs.value) } } impl<'a> Div for &'a Number { type Output = Number; fn div(self, rhs: Self) -> Self::Output { - Number::new(self.value / rhs.value, false) + Number::new(self.value / rhs.value) } } impl<'a> Mul for &'a Number { type Output = Number; fn mul(self, rhs: Self) -> Self::Output { - Number::new(self.value * rhs.value, false) + Number::new(self.value * rhs.value) } } impl<'a> Neg for &'a Number { type Output = Number; fn neg(self) -> Number { - Number::new(-self.value, self.plus_sign) + Number::new(-self.value) } } impl<'a> Sub for &'a Number { type Output = Number; fn sub(self, rhs: Self) -> Self::Output { - Number::new(self.value - rhs.value, false) + Number::new(self.value - rhs.value) } } impl Zero for Number { fn zero() -> Self { - Number::new(Rational::zero(), false) + Number::new(Rational::zero()) } fn is_zero(&self) -> bool { self.value.is_zero() @@ -79,7 +79,7 @@ impl Zero for Number { impl fmt::Display for Number { fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { let t = self.value.to_integer(); - let skip_zero = out.alternate(); + let skip_zero = out.alternate() || !self.lead_zero; if t == 0 { if self.value.is_negative() { out.write_str("-0")?; diff --git a/tests/libsass.rs b/tests/libsass.rs index 43aa2747b..2c8eb7269 100644 --- a/tests/libsass.rs +++ b/tests/libsass.rs @@ -4,7 +4,7 @@ use rsass::{compile_scss, OutputStyle}; #[test] fn t01_arg_eval() { check( - b" + " @function foo() { @return 1+2 3/4 5+6; } @@ -34,14 +34,50 @@ fn t01_arg_eval() { #[test] fn features_units_level_3() { check( - b"foo {\n foo: feature-exists('units-level-3');\n}\n", + "foo {\n foo: feature-exists('units-level-3');\n}\n", "foo {\n foo: true;\n}\n", ) } -fn check(input: &[u8], expected: &str) { +#[test] +fn keyframes() { + check( + "div {\n color: #181818;\n}\n\n\ + @-webkit-keyframes uiDelayedFadeIn {\n\t0% { opacity: 0; }\n\t\ + 50% { opacity: .5; }\n\t100% { opacity: 1; }\n}\n\n\ + @-webkit-keyframes bounce {\n\tfrom {\n\t\tleft: 0px;\n\t}\n\t\ + to {\n\t\tleft: 200px;\n\t}\n}\n\n\ + $name: bounce;\n\n\ + @-webkit-keyframes #{$name} {\n blah: blee;\n}\n\n\ + @mixin fudge() {\n @content;\n}\n\n\ + foo {\n @include fudge() {\n \ + div {\n color: red;\n }\n }\n}\n\n\ + @-moz-document url-prefix() {\n\t.fl {\n\t\tfloat:left;\n\t\t\ + margin:12px 4px 0 0;\n\t\tpadding:0;\n\t\tfont-size:65px;\n\t\t\ + line-height:62%;\n\t\tcolor:#ba1820;\n\t}\n\t\ + .fs {\n\t\tfloat:left;\n\t\tmargin:12px 4px 10px 0;\n\t\t\ + padding:0; font-size:65px;\n\t\tline-height:62%;\n\t\t\ + color:#ba1820;\n\t}\n}", + "div {\n color: #181818;\n}\n\n\ + @-webkit-keyframes uiDelayedFadeIn {\n \ + 0% {\n opacity: 0;\n }\n 50% {\n opacity: .5;\n }\n \ + 100% {\n opacity: 1;\n }\n}\n\ + @-webkit-keyframes bounce {\n \ + from {\n left: 0px;\n }\n to {\n left: 200px;\n }\n}\n\ + @-webkit-keyframes bounce {\n blah: blee;\n}\n\ + foo div {\n color: red;\n}\n\n\ + @-moz-document url-prefix() {\n .fl {\n float: left;\n \ + margin: 12px 4px 0 0;\n padding: 0;\n font-size: 65px;\n \ + line-height: 62%;\n color: #ba1820;\n }\n \ + .fs {\n float: left;\n margin: 12px 4px 10px 0;\n \ + padding: 0;\n font-size: 65px;\n line-height: 62%;\n \ + color: #ba1820;\n }\n}\n", + ) +} + +fn check(input: &str, expected: &str) { assert_eq!( - compile_scss(input, OutputStyle::Expanded) + compile_scss(input.as_ref(), OutputStyle::Expanded) .and_then(|s| Ok(String::from_utf8(s)?)) .unwrap(), expected diff --git a/tests/rust_functions.rs b/tests/rust_functions.rs index b44d22b88..9cf7e5080 100644 --- a/tests/rust_functions.rs +++ b/tests/rust_functions.rs @@ -44,7 +44,7 @@ fn simple_function() { #[cfg(test)] fn avg(a: Number, b: Number) -> Number { let half = Rational::new(1, 2); - Number::new((a.value + b.value) * half, false) + Number::new((a.value + b.value) * half) } #[test] From b75866ad682069c195af13b87fb95aef18fb799b Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Tue, 1 May 2018 22:51:08 +0200 Subject: [PATCH 12/12] Fix an initial-zero issue. --- src/output_style.rs | 4 ++-- src/sass/value.rs | 6 +++++- src/value/number.rs | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/output_style.rs b/src/output_style.rs index da256e9fe..e917273d8 100644 --- a/src/output_style.rs +++ b/src/output_style.rs @@ -106,7 +106,7 @@ impl OutputStyle { ref default, ref global, } => { - let val = val.evaluate(scope); + let val = val.do_evaluate(scope, true); if *default { scope.define_default(name, &val, *global); } else if *global { @@ -347,7 +347,7 @@ impl OutputStyle { default, global, } => { - let val = val.evaluate(scope); + let val = val.do_evaluate(scope, true); if default { scope.define_default(name, &val, global); } else if global { diff --git a/src/sass/value.rs b/src/sass/value.rs index ef563c07f..9ae8bf77b 100644 --- a/src/sass/value.rs +++ b/src/sass/value.rs @@ -231,7 +231,11 @@ impl Value { } } Value::Numeric(ref num, ref unit) => { - css::Value::Numeric(num.clone(), unit.clone(), arithmetic) + let mut num = num.clone(); + if arithmetic { + num.lead_zero = true; + } + css::Value::Numeric(num, unit.clone(), arithmetic) } Value::Map(ref m) => css::Value::Map( m.iter() diff --git a/src/value/number.rs b/src/value/number.rs index ac2be9cef..1b768a94e 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -82,7 +82,10 @@ impl fmt::Display for Number { let skip_zero = out.alternate() || !self.lead_zero; if t == 0 { if self.value.is_negative() { - out.write_str("-0")?; + out.write_str("-")?; + if !skip_zero { + out.write_str("0")?; + } } else if self.plus_sign { out.write_str("+0")?; } else if self.value.is_zero() || !skip_zero {