diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e56275af..55b217226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.32.7 + +* Improve the performance of unitless and single-unit numbers. + ## 1.32.6 ### Node JS API diff --git a/lib/src/value/external/number.dart b/lib/src/value/external/number.dart index cdcedacc3..628b9aea0 100644 --- a/lib/src/value/external/number.dart +++ b/lib/src/value/external/number.dart @@ -61,8 +61,8 @@ abstract class SassNumber extends Value { /// Creates a number with full [numeratorUnits] and [denominatorUnits]. factory SassNumber.withUnits(num value, - {Iterable numeratorUnits, - Iterable denominatorUnits}) = internal.SassNumber.withUnits; + {List numeratorUnits, + List denominatorUnits}) = internal.SassNumber.withUnits; /// Returns [value] as an [int], if it's an integer value according to /// [isInt]. diff --git a/lib/src/value/number.dart b/lib/src/value/number.dart index abbd4f272..dfb9acf9b 100644 --- a/lib/src/value/number.dart +++ b/lib/src/value/number.dart @@ -13,10 +13,13 @@ import '../utils.dart'; import '../value.dart'; import '../visitor/interface/value.dart'; import 'external/value.dart' as ext; +import 'number/complex.dart'; +import 'number/single_unit.dart'; +import 'number/unitless.dart'; /// A nested map containing unit conversion rates. /// -/// `1unit1 * _conversions[unit1][unit2] = 1unit2`. +/// `1unit1 * _conversions[unit2][unit1] = 1unit2`. const _conversions = { // Length "in": { @@ -157,25 +160,15 @@ final _typesByUnit = { for (var unit in entry.value) unit: entry.key }; -// TODO(nweiz): If it's faster, add subclasses specifically for unitless numbers -// and numbers with only a single numerator unit. These should be opaque to -// users of SassNumber. - -class SassNumber extends Value implements ext.SassNumber { +abstract class SassNumber extends Value implements ext.SassNumber { static const precision = ext.SassNumber.precision; final num value; - final List numeratorUnits; - - final List denominatorUnits; - /// The representation of this number as two slash-separated numbers, if it /// has one. final Tuple2 asSlash; - bool get hasUnits => numeratorUnits.isNotEmpty || denominatorUnits.isNotEmpty; - bool get isInt => fuzzyIsInt(value); int get asInt => fuzzyAsInt(value); @@ -184,35 +177,42 @@ class SassNumber extends Value implements ext.SassNumber { String get unitString => hasUnits ? _unitString(numeratorUnits, denominatorUnits) : ''; - SassNumber(num value, [String unit]) - : this.withUnits(value, numeratorUnits: unit == null ? null : [unit]); + factory SassNumber(num value, [String unit]) => unit == null + ? UnitlessSassNumber(value) + : SingleUnitSassNumber(value, unit); - SassNumber.withUnits(this.value, - {Iterable numeratorUnits, Iterable denominatorUnits}) - : numeratorUnits = numeratorUnits == null - ? const [] - : List.unmodifiable(numeratorUnits), - denominatorUnits = denominatorUnits == null - ? const [] - : List.unmodifiable(denominatorUnits), - asSlash = null; + factory SassNumber.withUnits(num value, + {List numeratorUnits, List denominatorUnits}) { + var emptyNumerator = numeratorUnits == null || numeratorUnits.isEmpty; + var emptyDenominator = denominatorUnits == null || denominatorUnits.isEmpty; + if (emptyNumerator && emptyDenominator) return UnitlessSassNumber(value); + + if (emptyDenominator && numeratorUnits.length == 1) { + return SingleUnitSassNumber(value, numeratorUnits[0]); + } else { + return ComplexSassNumber( + value, + emptyNumerator ? const [] : List.unmodifiable(numeratorUnits), + emptyDenominator ? const [] : List.unmodifiable(denominatorUnits)); + } + } - SassNumber._(this.value, this.numeratorUnits, this.denominatorUnits, - [this.asSlash]); + @protected + SassNumber.protected(this.value, this.asSlash); T accept(ValueVisitor visitor) => visitor.visitNumber(this); + /// Returns a number with the same units as [this] but with [value] as its + /// value. + @protected + SassNumber withValue(num value); + /// Returns a copy of [this] without [asSlash] set. - SassNumber withoutSlash() { - if (asSlash == null) return this; - return SassNumber._(value, numeratorUnits, denominatorUnits); - } + SassNumber withoutSlash() => asSlash == null ? this : withValue(value); /// Returns a copy of [this] with [this.asSlash] set to a tuple containing /// [numerator] and [denominator]. - SassNumber withSlash(SassNumber numerator, SassNumber denominator) => - SassNumber._(value, numeratorUnits, denominatorUnits, - Tuple2(numerator, denominator)); + SassNumber withSlash(SassNumber numerator, SassNumber denominator); SassNumber assertNumber([String name]) => this; @@ -230,18 +230,6 @@ class SassNumber extends Value implements ext.SassNumber { name); } - bool hasUnit(String unit) => - numeratorUnits.length == 1 && - denominatorUnits.isEmpty && - numeratorUnits.first == unit; - - bool compatibleWithUnit(String unit) { - if (denominatorUnits.isNotEmpty) return false; - if (numeratorUnits.isEmpty) return true; - return numeratorUnits.length == 1 && - _conversionFactor(numeratorUnits.first, unit) != null; - } - void assertUnit(String unit, [String name]) { if (hasUnit(unit)) return; throw _exception('Expected $this to have unit "$unit".', name); @@ -363,7 +351,7 @@ class SassNumber extends Value implements ext.SassNumber { var oldNumerators = numeratorUnits.toList(); for (var newNumerator in newNumerators) { removeFirstWhere(oldNumerators, (oldNumerator) { - var factor = _conversionFactor(newNumerator, oldNumerator); + var factor = conversionFactor(newNumerator, oldNumerator); if (factor == null) return false; value *= factor; return true; @@ -373,7 +361,7 @@ class SassNumber extends Value implements ext.SassNumber { var oldDenominators = denominatorUnits.toList(); for (var newDenominator in newDenominators) { removeFirstWhere(oldDenominators, (oldDenominator) { - var factor = _conversionFactor(newDenominator, oldDenominator); + var factor = conversionFactor(newDenominator, oldDenominator); if (factor == null) return false; value /= factor; return true; @@ -431,22 +419,26 @@ class SassNumber extends Value implements ext.SassNumber { Value modulo(Value other) { if (other is SassNumber) { - return _coerceNumber(other, (num1, num2) { - if (num2 > 0) return num1 % num2; - if (num2 == 0) return double.nan; - - // Dart has different mod-negative semantics than Ruby, and thus than - // Sass. - var result = num1 % num2; - return result == 0 ? 0 : result + num2; - }); + return withValue(_coerceUnits(other, moduloLikeSass)); } throw SassScriptException('Undefined operation "$this % $other".'); } + /// Return [num1] modulo [num2], using Sass's modulo semantics, which it + /// inherited from Ruby and which differ from Dart's. + num moduloLikeSass(num num1, num num2) { + if (num2 > 0) return num1 % num2; + if (num2 == 0) return double.nan; + + // Dart has different mod-negative semantics than Ruby, and thus than + // Sass. + var result = num1 % num2; + return result == 0 ? 0 : result + num2; + } + Value plus(Value other) { if (other is SassNumber) { - return _coerceNumber(other, (num1, num2) => num1 + num2); + return withValue(_coerceUnits(other, (num1, num2) => num1 + num2)); } if (other is! SassColor) return super.plus(other); throw SassScriptException('Undefined operation "$this + $other".'); @@ -454,7 +446,7 @@ class SassNumber extends Value implements ext.SassNumber { Value minus(Value other) { if (other is SassNumber) { - return _coerceNumber(other, (num1, num2) => num1 - num2); + return withValue(_coerceUnits(other, (num1, num2) => num1 - num2)); } if (other is! SassColor) return super.minus(other); throw SassScriptException('Undefined operation "$this - $other".'); @@ -462,98 +454,77 @@ class SassNumber extends Value implements ext.SassNumber { Value times(Value other) { if (other is SassNumber) { - return _multiplyUnits(value * other.value, numeratorUnits, - denominatorUnits, other.numeratorUnits, other.denominatorUnits); + if (!other.hasUnits) return withValue(value * other.value); + return multiplyUnits( + value * other.value, other.numeratorUnits, other.denominatorUnits); } throw SassScriptException('Undefined operation "$this * $other".'); } Value dividedBy(Value other) { if (other is SassNumber) { - return _multiplyUnits(value / other.value, numeratorUnits, - denominatorUnits, other.denominatorUnits, other.numeratorUnits); + if (!other.hasUnits) return withValue(value / other.value); + return multiplyUnits( + value / other.value, other.denominatorUnits, other.numeratorUnits); } return super.dividedBy(other); } Value unaryPlus() => this; - Value unaryMinus() => SassNumber.withUnits(-value, - numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits); - - /// Converts [other]'s value to be compatible with this number's, calls - /// [operation] with the resulting numbers, and wraps the result in a - /// [SassNumber]. - /// - /// Throws a [SassScriptException] if the two numbers' units are incompatible. - SassNumber _coerceNumber( - SassNumber other, num operation(num num1, num num2)) { - var result = _coerceUnits(other, operation); - return SassNumber.withUnits(result, - numeratorUnits: hasUnits ? numeratorUnits : other.numeratorUnits, - denominatorUnits: hasUnits ? denominatorUnits : other.denominatorUnits); - } - /// Converts [other]'s value to be compatible with this number's, and calls /// [operation] with the resulting numbers. /// /// Throws a [SassScriptException] if the two numbers' units are incompatible. + @protected T _coerceUnits(SassNumber other, T operation(num num1, num num2)) { - num num1; - num num2; - if (hasUnits) { - num1 = value; - try { - num2 = other.coerceValueToMatch(this); - } on SassScriptException { - // If the conversion fails, re-run it in the other direction. This will - // generate an error message that prints [this] before [other], which is - // more readable. - coerceValueToMatch(other); - rethrow; // This should be unreachable. - } - } else { - num1 = coerceValueToMatch(other); - num2 = other.value; + try { + return operation(value, other.coerceValueToMatch(this)); + } on SassScriptException { + // If the conversion fails, re-run it in the other direction. This will + // generate an error message that prints [this] before [other], which is + // more readable. + coerceValueToMatch(other); + rethrow; // This should be unreachable. } - - return operation(num1, num2); } - /// Returns a new number that's equivalent to `value numerators1/denominators1 - /// * 1 numerators2/denominators2`. - SassNumber _multiplyUnits( - num value, - List numerators1, - List denominators1, - List numerators2, - List denominators2) { + /// Returns a new number that's equivalent to `value + /// this.numeratorUnits/this.denominatorUnits * 1 + /// otherNumerators/otherDenominators`. + @protected + SassNumber multiplyUnits( + num value, List otherNumerators, List otherDenominators) { // Short-circuit without allocating any new unit lists if possible. - if (numerators1.isEmpty) { - if (denominators2.isEmpty && - !_areAnyConvertible(denominators1, numerators2)) { + if (numeratorUnits.isEmpty) { + if (otherDenominators.isEmpty && + !_areAnyConvertible(denominatorUnits, otherNumerators)) { return SassNumber.withUnits(value, - numeratorUnits: numerators2, denominatorUnits: denominators1); - } else if (denominators1.isEmpty) { + numeratorUnits: otherNumerators, + denominatorUnits: denominatorUnits); + } else if (denominatorUnits.isEmpty) { return SassNumber.withUnits(value, - numeratorUnits: numerators2, denominatorUnits: denominators2); + numeratorUnits: otherNumerators, + denominatorUnits: otherDenominators); } - } else if (numerators2.isEmpty) { - if (denominators2.isEmpty) { + } else if (otherNumerators.isEmpty) { + if (otherDenominators.isEmpty) { return SassNumber.withUnits(value, - numeratorUnits: numerators1, denominatorUnits: denominators2); - } else if (denominators1.isEmpty && - !_areAnyConvertible(numerators1, denominators2)) { + numeratorUnits: numeratorUnits, + denominatorUnits: otherDenominators); + } else if (denominatorUnits.isEmpty && + !_areAnyConvertible(numeratorUnits, otherDenominators)) { return SassNumber.withUnits(value, - numeratorUnits: numerators1, denominatorUnits: denominators2); + numeratorUnits: numeratorUnits, + denominatorUnits: otherDenominators); } } var newNumerators = []; - var mutableDenominators2 = denominators2.toList(); - for (var numerator in numerators1) { - removeFirstWhere(mutableDenominators2, (denominator) { - var factor = _conversionFactor(numerator, denominator); + var mutableOtherDenominators = otherDenominators.toList(); + for (var numerator in numeratorUnits) { + removeFirstWhere(mutableOtherDenominators, (denominator) { + var factor = conversionFactor(numerator, denominator); if (factor == null) return false; value /= factor; return true; @@ -563,10 +534,10 @@ class SassNumber extends Value implements ext.SassNumber { }); } - var mutableDenominators1 = denominators1.toList(); - for (var numerator in numerators2) { - removeFirstWhere(mutableDenominators1, (denominator) { - var factor = _conversionFactor(numerator, denominator); + var mutableDenominatorUnits = denominatorUnits.toList(); + for (var numerator in otherNumerators) { + removeFirstWhere(mutableDenominatorUnits, (denominator) { + var factor = conversionFactor(numerator, denominator); if (factor == null) return false; value /= factor; return true; @@ -578,7 +549,8 @@ class SassNumber extends Value implements ext.SassNumber { return SassNumber.withUnits(value, numeratorUnits: newNumerators, - denominatorUnits: mutableDenominators1..addAll(mutableDenominators2)); + denominatorUnits: mutableDenominatorUnits + ..addAll(mutableOtherDenominators)); } /// Returns whether there exists a unit in [units1] that can be converted to a @@ -596,8 +568,9 @@ class SassNumber extends Value implements ext.SassNumber { /// Returns the number of [unit1]s per [unit2]. /// - /// Equivalently, `1unit1 * _conversionFactor(unit1, unit2) = 1unit2`. - num _conversionFactor(String unit1, String unit2) { + /// Equivalently, `1unit2 * conversionFactor(unit1, unit2) = 1unit1`. + @protected + num conversionFactor(String unit1, String unit2) { if (unit1 == unit2) return 1; var innerMap = _conversions[unit1]; if (innerMap == null) return null; @@ -670,13 +643,17 @@ class SassNumber extends Value implements ext.SassNumber { /// /// That is, if `X units1 == Y units2`, `X * _canonicalMultiplier(units1) == Y /// * _canonicalMultiplier(units2)`. - num _canonicalMultiplier(List units) => - units.fold(1, (multiplier, unit) { - var innerMap = _conversions[unit]; - return innerMap == null - ? multiplier - : multiplier / innerMap.values.first; - }); + num _canonicalMultiplier(List units) => units.fold( + 1, (multiplier, unit) => multiplier * canonicalMultiplierForUnit(unit)); + + /// Returns a multiplier that encapsulates unit equivalence with [unit]. + /// + /// That is, if `X unit1 == Y unit2`, `X * canonicalMultiplierForUnit(unit1) + /// == Y * canonicalMultiplierForUnit(unit2)`. + num canonicalMultiplierForUnit(String unit) { + var innerMap = _conversions[unit]; + return innerMap == null ? 1 : 1 / innerMap.values.first; + } /// Throws a [SassScriptException] with the given [message]. SassScriptException _exception(String message, [String name]) => diff --git a/lib/src/value/number/complex.dart b/lib/src/value/number/complex.dart new file mode 100644 index 000000000..3b3014cd6 --- /dev/null +++ b/lib/src/value/number/complex.dart @@ -0,0 +1,42 @@ +// Copyright 2020 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:meta/meta.dart'; +import 'package:tuple/tuple.dart'; + +import '../../value.dart'; +import '../number.dart'; + +/// A specialized subclass of [SassNumber] for numbers that are not +/// [UnitlessSassNumber]s or [SingleUnitSassNumber]s. +@sealed +class ComplexSassNumber extends SassNumber { + final List numeratorUnits; + + final List denominatorUnits; + + bool get hasUnits => true; + + ComplexSassNumber(num value, Iterable numeratorUnits, + Iterable denominatorUnits) + : this._(value, List.unmodifiable(numeratorUnits), + List.unmodifiable(denominatorUnits)); + + ComplexSassNumber._(num value, this.numeratorUnits, this.denominatorUnits, + [Tuple2 asSlash]) + : super.protected(value, asSlash) { + assert(numeratorUnits.length > 1 || denominatorUnits.isNotEmpty); + } + + bool hasUnit(String unit) => false; + + bool compatibleWithUnit(String unit) => false; + + SassNumber withValue(num value) => + ComplexSassNumber._(value, numeratorUnits, denominatorUnits); + + SassNumber withSlash(SassNumber numerator, SassNumber denominator) => + ComplexSassNumber._(value, numeratorUnits, denominatorUnits, + Tuple2(numerator, denominator)); +} diff --git a/lib/src/value/number/single_unit.dart b/lib/src/value/number/single_unit.dart new file mode 100644 index 000000000..fa29d5d63 --- /dev/null +++ b/lib/src/value/number/single_unit.dart @@ -0,0 +1,130 @@ +// Copyright 2020 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:collection'; + +import 'package:meta/meta.dart'; +import 'package:tuple/tuple.dart'; + +import '../../util/number.dart'; +import '../../utils.dart'; +import '../../value.dart'; +import '../external/value.dart' as ext; +import '../number.dart'; + +/// A specialized subclass of [SassNumber] for numbers that have exactly one +/// numerator unit. +@sealed +class SingleUnitSassNumber extends SassNumber { + final String _unit; + + List get numeratorUnits => UnmodifiableListView([_unit]); + + List get denominatorUnits => const []; + + bool get hasUnits => true; + + SingleUnitSassNumber(num value, this._unit, + [Tuple2 asSlash]) + : super.protected(value, asSlash); + + SassNumber withValue(num value) => SingleUnitSassNumber(value, _unit); + + SassNumber withSlash(SassNumber numerator, SassNumber denominator) => + SingleUnitSassNumber(value, _unit, Tuple2(numerator, denominator)); + + bool hasUnit(String unit) => unit == _unit; + + bool compatibleWithUnit(String unit) => conversionFactor(_unit, unit) != null; + + SassNumber coerceToMatch(ext.SassNumber other, + [String name, String otherName]) => + convertToMatch(other, name, otherName); + + num coerceValueToMatch(ext.SassNumber other, + [String name, String otherName]) => + convertValueToMatch(other, name, otherName); + + SassNumber convertToMatch(ext.SassNumber other, + [String name, String otherName]) => + (other is SingleUnitSassNumber ? _coerceToUnit(other._unit) : null) ?? + // Call this to generate a consistent error message. + super.convertToMatch(other, name, otherName); + + num convertValueToMatch(ext.SassNumber other, + [String name, String otherName]) => + (other is SingleUnitSassNumber + ? _coerceValueToUnit(other._unit) + : null) ?? + // Call this to generate a consistent error message. + super.convertValueToMatch(other, name, otherName); + + SassNumber coerce(List newNumerators, List newDenominators, + [String name]) => + (newNumerators.length == 1 && newDenominators.isEmpty + ? _coerceToUnit(newNumerators[0]) + : null) ?? + // Call this to generate a consistent error message. + super.coerce(newNumerators, newDenominators, name); + + num coerceValue(List newNumerators, List newDenominators, + [String name]) => + (newNumerators.length == 1 && newDenominators.isEmpty + ? _coerceValueToUnit(newNumerators[0]) + : null) ?? + // Call this to generate a consistent error message. + super.coerceValue(newNumerators, newDenominators, name); + + num coerceValueToUnit(String unit, [String name]) => + _coerceValueToUnit(unit) ?? + // Call this to generate a consistent error message. + super.coerceValueToUnit(unit, name); + + /// A shorthand for [coerce] with only one numerator unit, except that it + /// returns `null` if coercion fails. + SassNumber _coerceToUnit(String unit) { + if (_unit == unit) return this; + + var factor = conversionFactor(unit, _unit); + return factor == null ? null : SingleUnitSassNumber(value * factor, unit); + } + + /// Like [coerceValueToUnit], except that it returns `null` if coercion fails. + num _coerceValueToUnit(String unit) { + var factor = conversionFactor(unit, _unit); + return factor == null ? null : value * factor; + } + + SassNumber multiplyUnits( + num value, List otherNumerators, List otherDenominators) { + var newNumerators = otherNumerators; + var mutableOtherDenominators = otherDenominators.toList(); + removeFirstWhere(mutableOtherDenominators, (denominator) { + var factor = conversionFactor(denominator, _unit); + if (factor == null) return false; + value *= factor; + return true; + }, orElse: () { + newNumerators = [_unit, ...newNumerators]; + return null; + }); + + return SassNumber.withUnits(value, + numeratorUnits: newNumerators, + denominatorUnits: mutableOtherDenominators); + } + + Value unaryMinus() => SingleUnitSassNumber(-value, _unit); + + bool operator ==(Object other) { + if (other is SingleUnitSassNumber) { + var factor = conversionFactor(other._unit, _unit); + return factor != null && fuzzyEquals(value * factor, other.value); + } else { + return false; + } + } + + int get hashCode => fuzzyHashCode(value * canonicalMultiplierForUnit(_unit)); +} diff --git a/lib/src/value/number/unitless.dart b/lib/src/value/number/unitless.dart new file mode 100644 index 000000000..b9fcdb5cb --- /dev/null +++ b/lib/src/value/number/unitless.dart @@ -0,0 +1,140 @@ +// Copyright 2020 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:meta/meta.dart'; +import 'package:tuple/tuple.dart'; + +import '../../util/number.dart'; +import '../../value.dart'; +import '../external/value.dart' as ext; +import '../number.dart'; + +/// A specialized subclass of [SassNumber] for numbers that have no units. +@sealed +class UnitlessSassNumber extends SassNumber { + List get numeratorUnits => const []; + + List get denominatorUnits => const []; + + bool get hasUnits => false; + + UnitlessSassNumber(num value, [Tuple2 asSlash]) + : super.protected(value, asSlash); + + SassNumber withValue(num value) => UnitlessSassNumber(value); + + SassNumber withSlash(SassNumber numerator, SassNumber denominator) => + UnitlessSassNumber(value, Tuple2(numerator, denominator)); + + bool hasUnit(String unit) => false; + + bool compatibleWithUnit(String unit) => true; + + SassNumber coerceToMatch(ext.SassNumber other, + [String name, String otherName]) => + (other as SassNumber).withValue(value); + + num coerceValueToMatch(ext.SassNumber other, + [String name, String otherName]) => + value; + + SassNumber convertToMatch(ext.SassNumber other, + [String name, String otherName]) => + other.hasUnits + // Call this to generate a consistent error message. + ? super.convertToMatch(other, name, otherName) + : this; + + num convertValueToMatch(ext.SassNumber other, + [String name, String otherName]) => + other.hasUnits + // Call this to generate a consistent error message. + ? super.convertValueToMatch(other, name, otherName) + : value; + + SassNumber coerce(List newNumerators, List newDenominators, + [String name]) => + SassNumber.withUnits(value, + numeratorUnits: newNumerators, denominatorUnits: newDenominators); + + num coerceValue(List newNumerators, List newDenominators, + [String name]) => + value; + + num coerceValueToUnit(String unit, [String name]) => value; + + SassBoolean greaterThan(Value other) { + if (other is SassNumber) { + return SassBoolean(fuzzyGreaterThan(value, other.value)); + } + return super.greaterThan(other); + } + + SassBoolean greaterThanOrEquals(Value other) { + if (other is SassNumber) { + return SassBoolean(fuzzyGreaterThanOrEquals(value, other.value)); + } + return super.greaterThanOrEquals(other); + } + + SassBoolean lessThan(Value other) { + if (other is SassNumber) { + return SassBoolean(fuzzyLessThan(value, other.value)); + } + return super.lessThan(other); + } + + SassBoolean lessThanOrEquals(Value other) { + if (other is SassNumber) { + return SassBoolean(fuzzyLessThanOrEquals(value, other.value)); + } + return super.lessThanOrEquals(other); + } + + Value modulo(Value other) { + if (other is SassNumber) { + return other.withValue(moduloLikeSass(value, other.value)); + } + return super.modulo(other); + } + + Value plus(Value other) { + if (other is SassNumber) { + return other.withValue(value + other.value); + } + return super.plus(other); + } + + Value minus(Value other) { + if (other is SassNumber) { + return other.withValue(value - other.value); + } + return super.minus(other); + } + + Value times(Value other) { + if (other is SassNumber) { + return other.withValue(value * other.value); + } + return super.times(other); + } + + Value dividedBy(Value other) { + if (other is SassNumber) { + return other.hasUnits + ? SassNumber.withUnits(value / other.value, + numeratorUnits: other.denominatorUnits, + denominatorUnits: other.numeratorUnits) + : UnitlessSassNumber(value / other.value); + } + return super.dividedBy(other); + } + + Value unaryMinus() => UnitlessSassNumber(-value); + + bool operator ==(Object other) => + other is UnitlessSassNumber && fuzzyEquals(value, other.value); + + int get hashCode => fuzzyHashCode(value); +} diff --git a/pubspec.yaml b/pubspec.yaml index 9c2c23b56..2d999191f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.32.6 +version: 1.32.7 description: A Sass implementation in Dart. author: Sass Team homepage: https://github.com/sass/dart-sass