Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds built-in clamp() and hypot() #906

Merged
merged 4 commits into from Dec 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,11 @@
## 1.25.0

* Add functions to the built-in "sass:math" module.
Awjin marked this conversation as resolved.
Show resolved Hide resolved
* `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.0

* Add an optional `with` clause to the `@forward` rule. This works like the
Expand Down
153 changes: 113 additions & 40 deletions lib/src/functions/math.dart
Expand Up @@ -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 _round = _numberFunction("round", fuzzyRound);
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 _floor = _numberFunction("floor", (value) => value.floor());
final _abs = _numberFunction("abs", (value) => value.abs());

final _max = BuiltInCallable("max", r"$numbers...", (arguments) {
SassNumber max;
Expand All @@ -62,39 +88,86 @@ 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);
});

final _unit = BuiltInCallable("unit", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
return SassString(number.unitString, quotes: true);
});
var numeratorUnits = numbers[0].numeratorUnits;
var denominatorUnits = numbers[0].denominatorUnits;
var subtotal = 0.0;

final _isUnitless = BuiltInCallable("is-unitless", r"$number", (arguments) {
var number = arguments[0].assertNumber("number");
return SassBoolean(!number.hasUnits);
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);
});

///
/// Unit functions
///

final _compatible =
BuiltInCallable("compatible", r"$number1, $number2", (arguments) {
var number1 = arguments[0].assertNumber("number1");
var number2 = arguments[1].assertNumber("number2");
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);
});
2 changes: 1 addition & 1 deletion pubspec.yaml
@@ -1,5 +1,5 @@
name: sass
version: 1.24.0
version: 1.25.0-dev
description: A Sass implementation in Dart.
author: Sass Team
homepage: https://github.com/sass/dart-sass
Expand Down