@@ -16,7 +16,6 @@ import '../ast/node.dart';
16
16
import '../ast/selector.dart' ;
17
17
import '../color_names.dart' ;
18
18
import '../exception.dart' ;
19
- import '../io.dart' ;
20
19
import '../parse/parser.dart' ;
21
20
import '../utils.dart' ;
22
21
import '../util/character.dart' ;
@@ -682,31 +681,26 @@ class _SerializeVisitor
682
681
// have to do is clamp doubles that are close to being integers.
683
682
var integer = fuzzyAsInt (number);
684
683
if (integer != null ) {
685
- // Node.js prints integers at least 1e21 using exponential notation.
686
- _buffer.write (isNode && integer >= 1e21
687
- ? _removeExponent (integer.toString ())
688
- : integer.toString ());
684
+ // Node.js still uses exponential notation for integers, so we have to
685
+ // handle it here.
686
+ _buffer.write (_removeExponent (integer.toString ()));
689
687
return ;
690
688
}
691
689
692
- // Dart and Node both print doubles at least 1e21 using exponential
693
- // notation.
694
- var text =
695
- number >= 1e21 ? _removeExponent (number.toString ()) : number.toString ();
690
+ var text = _removeExponent (number.toString ());
696
691
697
692
// Any double that's less than `SassNumber.precision + 2` digits long is
698
693
// guaranteed to be safe to emit directly, since it'll contain at most `0.`
699
694
// followed by [SassNumber.precision] digits.
700
695
var canWriteDirectly = text.length < SassNumber .precision + 2 ;
701
696
702
- if (_isCompressed && text.codeUnitAt (0 ) == $0) text = text.substring (1 );
703
-
704
697
if (canWriteDirectly) {
698
+ if (_isCompressed && text.codeUnitAt (0 ) == $0) text = text.substring (1 );
705
699
_buffer.write (text);
706
700
return ;
707
701
}
708
702
709
- _writeDecimal (text);
703
+ _writeRounded (text);
710
704
}
711
705
712
706
/// If [text] is written in exponent notation, returns a string representation
@@ -716,6 +710,8 @@ class _SerializeVisitor
716
710
String _removeExponent (String text) {
717
711
// Don't allocate this until we know [text] contains exponent notation.
718
712
StringBuffer ? buffer;
713
+ var negative = text.codeUnitAt (0 ) == $minus;
714
+
719
715
late int exponent;
720
716
for (var i = 0 ; i < text.length; i++ ) {
721
717
var codeUnit = text.codeUnitAt (i);
@@ -727,7 +723,12 @@ class _SerializeVisitor
727
723
// If the number has more than one significant digit, the second
728
724
// character will be a decimal point that we don't want to include in
729
725
// the generated number.
730
- if (i > 2 ) buffer.write (text.substring (2 , i));
726
+ if (negative) {
727
+ buffer.writeCharCode (text.codeUnitAt (1 ));
728
+ if (i > 3 ) buffer.write (text.substring (3 , i));
729
+ } else {
730
+ if (i > 2 ) buffer.write (text.substring (2 , i));
731
+ }
731
732
732
733
exponent = int .parse (text.substring (i + 1 , text.length));
733
734
break ;
@@ -737,8 +738,11 @@ class _SerializeVisitor
737
738
if (exponent > 0 ) {
738
739
// Write an additional zero for each exponent digits other than those
739
740
// already written to the buffer. We subtract 1 from `buffer.length`
740
- // because the first digit doesn't count towards the exponent.
741
- var additionalZeroes = exponent - (buffer.length - 1 );
741
+ // because the first digit doesn't count towards the exponent. Subtract 1
742
+ // more for negative numbers because of the `-` written to the buffer.
743
+ var additionalZeroes =
744
+ exponent - (buffer.length - 1 - (negative ? 1 : 0 ));
745
+
742
746
for (var i = 0 ; i < additionalZeroes; i++ ) {
743
747
buffer.writeCharCode ($0);
744
748
}
@@ -756,62 +760,114 @@ class _SerializeVisitor
756
760
}
757
761
}
758
762
759
- /// Assuming [text] is a double written without exponent notation, writes it
760
- /// to [_buffer] with at most [SassNumber.precision] digits after the decimal.
761
- void _writeDecimal (String text) {
762
- // Write up until the decimal point, or to the end of [text] if it has no
763
- // decimal point.
764
- var textIndex = 0 ;
765
- for (; textIndex < text.length; textIndex++ ) {
766
- var codeUnit = text.codeUnitAt (textIndex);
767
- if (codeUnit == $dot) {
768
- // Most integer-value doubles will have been converted to ints using
769
- // [fuzzyAsInt] in [_writeNumber]. However, that logic isn't rock-solid
770
- // for very large doubles due to floating-point imprecision, so we
771
- // handle that case here as well.
772
- if (textIndex == text.length - 2 &&
773
- text.codeUnitAt (text.length - 1 ) == $0) {
774
- return ;
775
- }
763
+ /// Assuming [text] is a number written without exponent notation, rounds it
764
+ /// to [SassNumber.precision] digits after the decimal and writes the result
765
+ /// to [_buffer] .
766
+ void _writeRounded (String text) {
767
+ assert (RegExp (r"^-?\d+(\.\d+)?$" ).hasMatch (text),
768
+ '"$text " should be a number written without exponent notation.' );
769
+
770
+ // Dart serializes all doubles with a trailing `.0`, even if they have
771
+ // integer values. In that case we definitely don't need to adjust for
772
+ // precision, so we can just write the number as-is without the `.0`.
773
+ if (text.endsWith (".0" )) {
774
+ _buffer.write (text.substring (0 , text.length - 2 ));
775
+ return ;
776
+ }
776
777
777
- _buffer.writeCharCode (codeUnit);
778
- textIndex++ ;
779
- break ;
778
+ // We need to ensure that we write at most [SassNumber.precision] digits
779
+ // after the decimal point, and that we round appropriately if necessary. To
780
+ // do this, we maintain an intermediate buffer of digits (both before and
781
+ // after the decimal point), which we then write to [_buffer] as text. We
782
+ // start writing after the first digit to give us room to round up to a
783
+ // higher decimal place than was represented in the original number.
784
+ var digits = Uint8List (text.length + 1 );
785
+ var digitsIndex = 1 ;
786
+
787
+ // Write the digits before the decimal to [digits].
788
+ var textIndex = 0 ;
789
+ var negative = text.codeUnitAt (0 ) == $minus;
790
+ if (negative) textIndex++ ;
791
+ while (true ) {
792
+ if (textIndex == text.length) {
793
+ // If we get here, [text] has no decmial point. It definitely doesn't
794
+ // need to be rounded; we can write it as-is.
795
+ _buffer.write (text);
796
+ return ;
780
797
}
781
798
782
- _buffer.writeCharCode (codeUnit);
799
+ var codeUnit = text.codeUnitAt (textIndex++ );
800
+ if (codeUnit == $dot) break ;
801
+ digits[digitsIndex++ ] = asDecimal (codeUnit);
783
802
}
784
- if (textIndex == text.length) return ;
803
+ var firstFractionalDigit = digitsIndex ;
785
804
786
- // We need to ensure that we write at most [SassNumber.precision] digits
787
- // after the decimal point, and that we round appropriately if necessary. To
788
- // do this, we maintain an intermediate buffer of decimal digits, which we
789
- // then convert to text.
790
- var digits = Uint8List (SassNumber .precision);
791
- var digitsIndex = 0 ;
792
- while (textIndex < text.length && digitsIndex < digits.length) {
805
+ // Only write at most [precision] digits after the decimal. If there aren't
806
+ // that many digits left in the number, write it as-is since no rounding or
807
+ // truncation is needed.
808
+ var indexAfterPrecision = textIndex + SassNumber .precision;
809
+ if (indexAfterPrecision >= text.length) {
810
+ _buffer.write (text);
811
+ return ;
812
+ }
813
+
814
+ // Write the digits after the decimal to [digits].
815
+ while (textIndex < indexAfterPrecision) {
793
816
digits[digitsIndex++ ] = asDecimal (text.codeUnitAt (textIndex++ ));
794
817
}
795
818
796
- // Round the trailing digits in [digits] up if necessary. We can be
797
- // confident this won't cause us to need to round anything before the
798
- // decimal, because otherwise the number would be [fuzzyIsInt].
799
- if (textIndex != text.length &&
800
- asDecimal (text. codeUnitAt (textIndex)) >= 5 ) {
801
- while (digitsIndex >= 0 ) {
819
+ // Round the trailing digits in [digits] up if necessary.
820
+ if ( asDecimal (text. codeUnitAt (textIndex)) >= 5 ) {
821
+ while ( true ) {
822
+ // [digitsIndex] is guaranteed to be >0 here because we added a leading
823
+ // 0 to [digits] when we constructed it, so even if we round everything
824
+ // up [newDigit] will always be 1 when digitsIndex is 1.
802
825
var newDigit = ++ digits[digitsIndex - 1 ];
803
826
if (newDigit != 10 ) break ;
804
827
digitsIndex-- ;
805
828
}
806
829
}
807
830
808
- // Remove trailing zeros.
809
- while (digitsIndex > 0 && digits[digitsIndex - 1 ] == 0 ) {
831
+ // At most one of the following loops will actually execute. If we rounded
832
+ // digits up before the decimal point, the first loop will set those digits
833
+ // to 0 (rather than 10, which is not a valid decimal digit). On the other
834
+ // hand, if we have trailing zeros left after the decimal point, the second
835
+ // loop will move [digitsIndex] before them and cause them not to be
836
+ // written. Either way, [digitsIndex] will end up >= [firstFractionalDigit].
837
+ for (; digitsIndex < firstFractionalDigit; digitsIndex++ ) {
838
+ digits[digitsIndex] = 0 ;
839
+ }
840
+ while (digitsIndex > firstFractionalDigit && digits[digitsIndex - 1 ] == 0 ) {
810
841
digitsIndex-- ;
811
842
}
812
843
813
- for (var i = 0 ; i < digitsIndex; i++ ) {
814
- _buffer.writeCharCode (decimalCharFor (digits[i]));
844
+ // Omit the minus sign if the number ended up being rounded to exactly zero,
845
+ // write "0" explicit to avoid adding a minus sign or omitting the number
846
+ // entirely in compressed mode.
847
+ if (digitsIndex == 2 && digits[0 ] == 0 && digits[1 ] == 0 ) {
848
+ _buffer.writeCharCode ($0);
849
+ return ;
850
+ }
851
+
852
+ if (negative) _buffer.writeCharCode ($minus);
853
+
854
+ // Write the digits before the decimal point to [_buffer]. Omit the leading
855
+ // 0 that's added to [digits] to accommodate rounding, and in compressed
856
+ // mode omit the 0 before the decimal point as well.
857
+ var writtenIndex = 0 ;
858
+ if (digits[0 ] == 0 ) {
859
+ writtenIndex++ ;
860
+ if (_isCompressed && digits[1 ] == 0 ) writtenIndex++ ;
861
+ }
862
+ for (; writtenIndex < firstFractionalDigit; writtenIndex++ ) {
863
+ _buffer.writeCharCode (decimalCharFor (digits[writtenIndex]));
864
+ }
865
+
866
+ if (digitsIndex > firstFractionalDigit) {
867
+ _buffer.writeCharCode ($dot);
868
+ for (; writtenIndex < digitsIndex; writtenIndex++ ) {
869
+ _buffer.writeCharCode (decimalCharFor (digits[writtenIndex]));
870
+ }
815
871
}
816
872
}
817
873
0 commit comments