mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-21 21:31:11 +01:00
Properly serialize numbers.
This commit is contained in:
parent
ddf6452f20
commit
c41f2bb023
@ -67,12 +67,28 @@ int asHex(int character) {
|
||||
return 10 + character - $a;
|
||||
}
|
||||
|
||||
/// Returns the hexadecimal digit for [character].
|
||||
/// Returns the hexadecimal digit for [number].
|
||||
///
|
||||
/// Assumes that [character] is less than 16.
|
||||
int hexCharFor(int character) {
|
||||
assert(character < 0x10);
|
||||
return character < 0xA ? $0 + character : $a - 0xA + character;
|
||||
/// Assumes that [number] is less than 16.
|
||||
int hexCharFor(int number) {
|
||||
assert(number < 0x10);
|
||||
return number < 0xA ? $0 + number : $a - 0xA + number;
|
||||
}
|
||||
|
||||
/// Returns the value of [character] as a decimal digit.
|
||||
///
|
||||
/// Assumes that [character] is a decimal digit.
|
||||
int asDecimal(int character) {
|
||||
assert(character >= $0 && character <= $9);
|
||||
return character - $0;
|
||||
}
|
||||
|
||||
/// Returns the decimal digit for [number].
|
||||
///
|
||||
/// Assumes that [number] is less than 10.
|
||||
int decimalCharFor(int number) {
|
||||
assert(number < 10);
|
||||
return $0 + number;
|
||||
}
|
||||
|
||||
/// Assumes that [character] is a left-hand brace-like character, and returns
|
||||
|
@ -2,11 +2,13 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../value.dart';
|
||||
|
||||
/// The maximum distance two Sass numbers are allowed to be from one another
|
||||
/// before they're considered different.
|
||||
const epsilon = 1 / (10 * SassNumber.precision);
|
||||
final epsilon = 1 / (math.pow(10, SassNumber.precision));
|
||||
|
||||
/// Returns whether [number1] and [number2] are equal within [epsilon].
|
||||
bool fuzzyEquals(num number1, num number2) =>
|
||||
@ -31,6 +33,21 @@ bool fuzzyGreaterThan(num number1, num number2) =>
|
||||
bool fuzzyGreaterThanOrEquals(num number1, num number2) =>
|
||||
number1 > number2 || fuzzyEquals(number1, number2);
|
||||
|
||||
/// Returns whether [number] is [fuzzyEquals] to an integer.
|
||||
bool fuzzyIsInt(num number) {
|
||||
if (number is int) return true;
|
||||
|
||||
// Check against 0.5 rather than 0.0 so that we catch numbers that are both
|
||||
// very slightly above an integer, and very slightly below.
|
||||
return fuzzyEquals((number - 0.5).abs() % 1, 0.5);
|
||||
}
|
||||
|
||||
/// If [number] is an integer according to [fuzzyIsInt], returns it as an
|
||||
/// [int].
|
||||
///
|
||||
/// Otherwise, returns `null`.
|
||||
int fuzzyAsInt(num number) => fuzzyIsInt(number) ? number.round() : null;
|
||||
|
||||
/// Rounds [number] to the nearest integer.
|
||||
///
|
||||
/// This rounds up numbers that are [fuzzyEquals] to `X.5`.
|
||||
|
@ -166,8 +166,16 @@ class SassNumber extends Value {
|
||||
|
||||
/// Whether [this] is an integer, according to [fuzzyEquals].
|
||||
///
|
||||
/// The [int] value can be accessed using [assertInt].
|
||||
bool get isInt => value is int || fuzzyEquals(value % 1, 0.0);
|
||||
/// The [int] value can be accessed using [asInt] or [assertInt]. Note that
|
||||
/// this may return `false` for very large doubles even though they may be
|
||||
/// mathematically integers, because not all platforms have a valid
|
||||
/// representation for integers that large.
|
||||
bool get isInt => fuzzyIsInt(value);
|
||||
|
||||
/// If [this] is an integer according to [isInt], returns [value] as an [int].
|
||||
///
|
||||
/// Otherwise, returns `null`.
|
||||
int get asInt => fuzzyAsInt(value);
|
||||
|
||||
/// Returns a human readable string representation of this number's units.
|
||||
String get unitString =>
|
||||
@ -196,13 +204,14 @@ class SassNumber extends Value {
|
||||
SassNumber assertNumber([String name]) => this;
|
||||
|
||||
/// Returns [value] as an [int], if it's an integer value according to
|
||||
/// [fuzzyEquals].
|
||||
/// [isInt].
|
||||
///
|
||||
/// Throws an [InternalException] if [value] isn't an integer. If this came
|
||||
/// from a function argument, [name] is the argument name (without the `$`).
|
||||
/// It's used for debugging.
|
||||
int assertInt([String name]) {
|
||||
if (isInt) return value.round();
|
||||
var integer = fuzzyAsInt(value);
|
||||
if (integer != null) return integer;
|
||||
throw _exception("$this is not an int.", name);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:charcode/charcode.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
@ -11,6 +12,7 @@ import '../ast/css.dart';
|
||||
import '../ast/selector.dart';
|
||||
import '../exception.dart';
|
||||
import '../util/character.dart';
|
||||
import '../util/number.dart';
|
||||
import '../value.dart';
|
||||
import 'interface/css.dart';
|
||||
import 'interface/selector.dart';
|
||||
@ -264,10 +266,9 @@ class _SerializeCssVisitor
|
||||
_writeHexComponent(value.green);
|
||||
_writeHexComponent(value.blue);
|
||||
} else {
|
||||
// TODO: support precision in alpha, make sure we don't write exponential
|
||||
// notation.
|
||||
_buffer.write(
|
||||
"rgba(${value.red}, ${value.green}, ${value.blue}, ${value.alpha})");
|
||||
_buffer.write("rgba(${value.red}, ${value.green}, ${value.blue}, ");
|
||||
_writeNumber(value.alpha);
|
||||
_buffer.writeCharCode($rparen);
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,9 +341,8 @@ class _SerializeCssVisitor
|
||||
_buffer.write("null");
|
||||
}
|
||||
|
||||
// TODO(nweiz): Support precision and don't support exponent notation.
|
||||
void visitNumber(SassNumber value) {
|
||||
_buffer.write(value.value);
|
||||
_writeNumber(value.value);
|
||||
|
||||
if (!_inspect) {
|
||||
if (value.numeratorUnits.length > 1 ||
|
||||
@ -358,6 +358,111 @@ class _SerializeCssVisitor
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes [number] without exponent notation and with at most
|
||||
/// [SassNumber.precision] digits after the decimal point.
|
||||
void _writeNumber(num number) {
|
||||
// Dart always converts integers to strings in the obvious way, so all we
|
||||
// have to do is clamp doubles that are close to being integers.
|
||||
var integer = fuzzyAsInt(number);
|
||||
if (integer != null) {
|
||||
_buffer.write(integer);
|
||||
return;
|
||||
}
|
||||
|
||||
var text = number.toString();
|
||||
if (text.contains("e")) text = _removeExponent(text);
|
||||
|
||||
// Any double that doesn't contain "e" and is less than
|
||||
// `SassNumber.precision + 2` digits long is guaranteed to be safe to emit
|
||||
// directly, since it'll contain at most `0.` followed by
|
||||
// [SassNumber.precision] digits.
|
||||
if (text.length < SassNumber.precision + 2) {
|
||||
_buffer.write(text);
|
||||
return;
|
||||
}
|
||||
|
||||
_writeDecimal(text);
|
||||
}
|
||||
|
||||
/// Assuming [text] is a double written in exponent notation, returns a string
|
||||
/// representation of that double without exponent notation.
|
||||
String _removeExponent(String text) {
|
||||
var buffer = new StringBuffer();
|
||||
int exponent;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
var codeUnit = text.codeUnitAt(i);
|
||||
if (codeUnit == $e) {
|
||||
exponent = int.parse(text.substring(i + 1, text.length));
|
||||
break;
|
||||
} else if (codeUnit != $dot) {
|
||||
buffer.writeCharCode(codeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
if (exponent > 0) {
|
||||
for (var i = 0; i < exponent; i++) {
|
||||
buffer.writeCharCode($0);
|
||||
}
|
||||
return buffer.toString();
|
||||
} else {
|
||||
var result = new StringBuffer();
|
||||
var negative = text.codeUnitAt(0) == $minus;
|
||||
if (negative) result.writeCharCode($minus);
|
||||
result.write("0.");
|
||||
for (var i = -1; i > exponent; i--) {
|
||||
result.writeCharCode($0);
|
||||
}
|
||||
result.write(negative ? buffer.toString().substring(1) : buffer);
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/// Assuming [text] is a double written without exponent notation, writes it
|
||||
/// to [_buffer] with at most [SassNumber.precision] digits after the decimal.
|
||||
void _writeDecimal(String text) {
|
||||
var textIndex = 0;
|
||||
for (; textIndex < text.length; textIndex++) {
|
||||
var codeUnit = text.codeUnitAt(textIndex);
|
||||
_buffer.writeCharCode(codeUnit);
|
||||
if (codeUnit == $dot) {
|
||||
textIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (textIndex == text.length) return;
|
||||
|
||||
// We need to ensure that we write at most [SassNumber.precision] digits
|
||||
// after the decimal point, and that we round appropriately if necessary. To
|
||||
// do this, we maintain an intermediate buffer of decimal digits, which we
|
||||
// then convert to text.
|
||||
var digits = new Uint8List(SassNumber.precision);
|
||||
var digitsIndex = 0;
|
||||
while (textIndex < text.length && digitsIndex < digits.length) {
|
||||
digits[digitsIndex++] = asDecimal(text.codeUnitAt(textIndex++));
|
||||
}
|
||||
|
||||
// Round the trailing digits in [digits] up if necessary. We can be
|
||||
// confident this won't cause us to need to round anything before the
|
||||
// decimal, because otherwise the number would be [fuzzyIsInt].
|
||||
if (textIndex != text.length &&
|
||||
asDecimal(text.codeUnitAt(textIndex)) >= 5) {
|
||||
while (digitsIndex >= 0) {
|
||||
var newDigit = ++digits[digitsIndex - 1];
|
||||
if (newDigit != 10) break;
|
||||
digitsIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing zeros.
|
||||
while (digitsIndex >= 0 && digits[digitsIndex - 1] == 0) {
|
||||
digitsIndex--;
|
||||
}
|
||||
|
||||
for (var i = 0; i < digitsIndex; i++) {
|
||||
_buffer.writeCharCode(decimalCharFor(digits[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void visitString(SassString string) {
|
||||
if (string.hasQuotes) {
|
||||
_visitString(string.text);
|
||||
|
Loading…
x
Reference in New Issue
Block a user