From 5f72acb14d518a2656fa694c4908a07ac927448d Mon Sep 17 00:00:00 2001 From: awjin Date: Tue, 14 Jan 2020 11:23:40 -0800 Subject: [PATCH] Revert "Revert "Adds built-in clamp() and hypot() (#906)"" This reverts commit 76280409bd1e3c6769ce3106a4e55e882d7ba3b4. --- CHANGELOG.md | 8 ++ lib/src/functions/math.dart | 155 ++++++++++++++++++++++++++---------- pubspec.yaml | 2 +- 3 files changed, 123 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f711504..de61d770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.25.0 + +* Add functions to the built-in "sass:math" module. + * `clamp()`: given a `$min`, $number`, and `$max` values, clamps the `$number` + in between `$min` and `$max`. + * `hypot()`: given *n* numbers, outputs the length of the *n*-dimensional + vector that has components equal to each of the inputs. + ## 1.24.4 ### JavaScript API diff --git a/lib/src/functions/math.dart b/lib/src/functions/math.dart index b0b52ec7..e0a10618 100644 --- a/lib/src/functions/math.dart +++ b/lib/src/functions/math.dart @@ -13,34 +13,60 @@ import '../module/built_in.dart'; import '../util/number.dart'; import '../value.dart'; -/// A random number generator. -final _random = math.Random(); - /// The global definitions of Sass math functions. final global = UnmodifiableListView([ - _round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit, // - _percentage, + _abs, _ceil, _floor, _max, _min, _percentage, _randomFunction, _round, + _unit, // + _compatible.withName("comparable"), _isUnitless.withName("unitless"), - _compatible.withName("comparable") ]); /// The Sass math module. final module = BuiltInModule("math", functions: [ - _round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit, - _isUnitless, // - _percentage, _compatible + _abs, _ceil, _clamp, _compatible, _floor, _hypot, _isUnitless, _max, _min, // + _percentage, _randomFunction, _round, _unit, ]); -final _percentage = BuiltInCallable("percentage", r"$number", (arguments) { - var number = arguments[0].assertNumber("number"); - number.assertNoUnits("number"); - return SassNumber(number.value * 100, '%'); +/// Returns a [Callable] named [name] that transforms a number's value +/// using [transform] and preserves its units. +BuiltInCallable _numberFunction(String name, num transform(num value)) { + return BuiltInCallable(name, r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + return SassNumber.withUnits(transform(number.value), + numeratorUnits: number.numeratorUnits, + denominatorUnits: number.denominatorUnits); + }); +} + +/// +/// Bounding functions +/// + +final _ceil = _numberFunction("ceil", (value) => value.ceil()); + +final _clamp = BuiltInCallable("clamp", r"$min, $number, $max", (arguments) { + var min = arguments[0].assertNumber("min"); + var number = arguments[1].assertNumber("number"); + var max = arguments[2].assertNumber("max"); + + if (min.hasUnits == number.hasUnits && number.hasUnits == max.hasUnits) { + if (min.greaterThanOrEquals(max).isTruthy) return min; + if (min.greaterThanOrEquals(number).isTruthy) return min; + if (number.greaterThanOrEquals(max).isTruthy) return max; + return number; + } + + var arg2 = min.hasUnits != number.hasUnits ? number : max; + var arg2Name = min.hasUnits != number.hasUnits ? "\$number" : "\$max"; + var unit1 = min.hasUnits ? "has unit ${min.unitString}" : "is unitless"; + var unit2 = arg2.hasUnits ? "has unit ${arg2.unitString}" : "is unitless"; + + throw SassScriptException( + "\$min $unit1 but $arg2Name $unit2. Arguments must all have units or all " + "be unitless."); }); -final _round = _numberFunction("round", fuzzyRound); -final _ceil = _numberFunction("ceil", (value) => value.ceil()); final _floor = _numberFunction("floor", (value) => value.floor()); -final _abs = _numberFunction("abs", (value) => value.abs()); final _max = BuiltInCallable("max", r"$numbers...", (arguments) { SassNumber max; @@ -62,24 +88,51 @@ final _min = BuiltInCallable("min", r"$numbers...", (arguments) { throw SassScriptException("At least one argument must be passed."); }); -final _randomFunction = BuiltInCallable("random", r"$limit: null", (arguments) { - if (arguments[0] == sassNull) return SassNumber(_random.nextDouble()); - var limit = arguments[0].assertNumber("limit").assertInt("limit"); - if (limit < 1) { - throw SassScriptException("\$limit: Must be greater than 0, was $limit."); +final _round = _numberFunction("round", fuzzyRound); + +/// +/// Distance functions +/// + +final _abs = _numberFunction("abs", (value) => value.abs()); + +final _hypot = BuiltInCallable("hypot", r"$numbers...", (arguments) { + var numbers = + arguments[0].asList.map((argument) => argument.assertNumber()).toList(); + + if (numbers.isEmpty) { + throw SassScriptException("At least one argument must be passed."); } - return SassNumber(_random.nextInt(limit) + 1); + + var numeratorUnits = numbers[0].numeratorUnits; + var denominatorUnits = numbers[0].denominatorUnits; + var subtotal = 0.0; + + for (var i = 0; i < numbers.length; i++) { + var number = numbers[i]; + + if (number.hasUnits != numbers[0].hasUnits) { + var unit1 = numbers[0].hasUnits + ? "has unit ${numbers[0].unitString}" + : "is unitless"; + var unit2 = + number.hasUnits ? "has unit ${number.unitString}" : "is unitless"; + throw SassScriptException( + "Argument 1 $unit1 but argument ${i + 1} $unit2. Arguments must all " + "have units or all be unitless."); + } + + number = number.coerce(numeratorUnits, denominatorUnits); + subtotal += math.pow(number.value, 2); + } + + return SassNumber.withUnits(math.sqrt(subtotal), + numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits); }); -final _unit = BuiltInCallable("unit", r"$number", (arguments) { - var number = arguments[0].assertNumber("number"); - return SassString(number.unitString, quotes: true); -}); - -final _isUnitless = BuiltInCallable("is-unitless", r"$number", (arguments) { - var number = arguments[0].assertNumber("number"); - return SassBoolean(!number.hasUnits); -}); +/// +/// Unit functions +/// final _compatible = BuiltInCallable("compatible", r"$number1, $number2", (arguments) { @@ -88,13 +141,33 @@ final _compatible = return SassBoolean(number1.isComparableTo(number2)); }); -/// Returns a [Callable] named [name] that transforms a number's value -/// using [transform] and preserves its units. -BuiltInCallable _numberFunction(String name, num transform(num value)) { - return BuiltInCallable(name, r"$number", (arguments) { - var number = arguments[0].assertNumber("number"); - return SassNumber.withUnits(transform(number.value), - numeratorUnits: number.numeratorUnits, - denominatorUnits: number.denominatorUnits); - }); -} +final _isUnitless = BuiltInCallable("is-unitless", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + return SassBoolean(!number.hasUnits); +}); + +final _unit = BuiltInCallable("unit", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + return SassString(number.unitString, quotes: true); +}); + +/// +/// Other functions +/// + +final _percentage = BuiltInCallable("percentage", r"$number", (arguments) { + var number = arguments[0].assertNumber("number"); + number.assertNoUnits("number"); + return SassNumber(number.value * 100, '%'); +}); + +final _random = math.Random(); + +final _randomFunction = BuiltInCallable("random", r"$limit: null", (arguments) { + if (arguments[0] == sassNull) return SassNumber(_random.nextDouble()); + var limit = arguments[0].assertNumber("limit").assertInt("limit"); + if (limit < 1) { + throw SassScriptException("\$limit: Must be greater than 0, was $limit."); + } + return SassNumber(_random.nextInt(limit) + 1); +}); diff --git a/pubspec.yaml b/pubspec.yaml index 7f4893bc..c693cebf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.24.4 +version: 1.25.0-dev description: A Sass implementation in Dart. author: Sass Team homepage: https://github.com/sass/dart-sass