@@ -13,34 +13,60 @@ import '../module/built_in.dart';
13
13
import '../util/number.dart' ;
14
14
import '../value.dart' ;
15
15
16
- /// A random number generator.
17
- final _random = math.Random ();
18
-
19
16
/// The global definitions of Sass math functions.
20
17
final global = UnmodifiableListView ([
21
- _round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit, //
22
- _percentage,
18
+ _abs, _ceil, _floor, _max, _min, _percentage, _randomFunction, _round,
19
+ _unit, //
20
+ _compatible.withName ("comparable" ),
23
21
_isUnitless.withName ("unitless" ),
24
- _compatible.withName ("comparable" )
25
22
]);
26
23
27
24
/// The Sass math module.
28
25
final module = BuiltInModule ("math" , functions: [
29
- _round, _ceil, _floor, _abs, _max, _min, _randomFunction, _unit,
30
- _isUnitless, //
31
- _percentage, _compatible
26
+ _abs, _ceil, _clamp, _compatible, _floor, _hypot, _isUnitless, _max, _min, //
27
+ _percentage, _randomFunction, _round, _unit,
32
28
]);
33
29
34
- final _percentage = BuiltInCallable ("percentage" , r"$number" , (arguments) {
35
- var number = arguments[0 ].assertNumber ("number" );
36
- number.assertNoUnits ("number" );
37
- return SassNumber (number.value * 100 , '%' );
38
- });
30
+ /// Returns a [Callable] named [name] that transforms a number's value
31
+ /// using [transform] and preserves its units.
32
+ BuiltInCallable _numberFunction (String name, num transform (num value)) {
33
+ return BuiltInCallable (name, r"$number" , (arguments) {
34
+ var number = arguments[0 ].assertNumber ("number" );
35
+ return SassNumber .withUnits (transform (number.value),
36
+ numeratorUnits: number.numeratorUnits,
37
+ denominatorUnits: number.denominatorUnits);
38
+ });
39
+ }
40
+
41
+ ///
42
+ /// Bounding functions
43
+ ///
39
44
40
- final _round = _numberFunction ("round" , fuzzyRound);
41
45
final _ceil = _numberFunction ("ceil" , (value) => value.ceil ());
46
+
47
+ final _clamp = BuiltInCallable ("clamp" , r"$min, $number, $max" , (arguments) {
48
+ var min = arguments[0 ].assertNumber ("min" );
49
+ var number = arguments[1 ].assertNumber ("number" );
50
+ var max = arguments[2 ].assertNumber ("max" );
51
+
52
+ if (min.hasUnits == number.hasUnits && number.hasUnits == max.hasUnits) {
53
+ if (min.greaterThanOrEquals (max).isTruthy) return min;
54
+ if (min.greaterThanOrEquals (number).isTruthy) return min;
55
+ if (number.greaterThanOrEquals (max).isTruthy) return max;
56
+ return number;
57
+ }
58
+
59
+ var arg2 = min.hasUnits != number.hasUnits ? number : max;
60
+ var arg2Name = min.hasUnits != number.hasUnits ? "\$ number" : "\$ max" ;
61
+ var unit1 = min.hasUnits ? "has unit ${min .unitString }" : "is unitless" ;
62
+ var unit2 = arg2.hasUnits ? "has unit ${arg2 .unitString }" : "is unitless" ;
63
+
64
+ throw SassScriptException (
65
+ "\$ min $unit1 but $arg2Name $unit2 . Arguments must all have units or all "
66
+ "be unitless." );
67
+ });
68
+
42
69
final _floor = _numberFunction ("floor" , (value) => value.floor ());
43
- final _abs = _numberFunction ("abs" , (value) => value.abs ());
44
70
45
71
final _max = BuiltInCallable ("max" , r"$numbers..." , (arguments) {
46
72
SassNumber max;
@@ -62,39 +88,86 @@ final _min = BuiltInCallable("min", r"$numbers...", (arguments) {
62
88
throw SassScriptException ("At least one argument must be passed." );
63
89
});
64
90
65
- final _randomFunction = BuiltInCallable ("random" , r"$limit: null" , (arguments) {
66
- if (arguments[0 ] == sassNull) return SassNumber (_random.nextDouble ());
67
- var limit = arguments[0 ].assertNumber ("limit" ).assertInt ("limit" );
68
- if (limit < 1 ) {
69
- throw SassScriptException ("\$ limit: Must be greater than 0, was $limit ." );
91
+ final _round = _numberFunction ("round" , fuzzyRound);
92
+
93
+ ///
94
+ /// Distance functions
95
+ ///
96
+
97
+ final _abs = _numberFunction ("abs" , (value) => value.abs ());
98
+
99
+ final _hypot = BuiltInCallable ("hypot" , r"$numbers..." , (arguments) {
100
+ var numbers =
101
+ arguments[0 ].asList.map ((argument) => argument.assertNumber ()).toList ();
102
+
103
+ if (numbers.isEmpty) {
104
+ throw SassScriptException ("At least one argument must be passed." );
70
105
}
71
- return SassNumber (_random.nextInt (limit) + 1 );
72
- });
73
106
74
- final _unit = BuiltInCallable ("unit" , r"$number" , (arguments) {
75
- var number = arguments[0 ].assertNumber ("number" );
76
- return SassString (number.unitString, quotes: true );
77
- });
107
+ var numeratorUnits = numbers[0 ].numeratorUnits;
108
+ var denominatorUnits = numbers[0 ].denominatorUnits;
109
+ var subtotal = 0.0 ;
78
110
79
- final _isUnitless = BuiltInCallable ("is-unitless" , r"$number" , (arguments) {
80
- var number = arguments[0 ].assertNumber ("number" );
81
- return SassBoolean (! number.hasUnits);
111
+ for (var i = 0 ; i < numbers.length; i++ ) {
112
+ var number = numbers[i];
113
+
114
+ if (number.hasUnits != numbers[0 ].hasUnits) {
115
+ var unit1 = numbers[0 ].hasUnits
116
+ ? "has unit ${numbers [0 ].unitString }"
117
+ : "is unitless" ;
118
+ var unit2 =
119
+ number.hasUnits ? "has unit ${number .unitString }" : "is unitless" ;
120
+ throw SassScriptException (
121
+ "Argument 1 $unit1 but argument ${i + 1 } $unit2 . Arguments must all "
122
+ "have units or all be unitless." );
123
+ }
124
+
125
+ number = number.coerce (numeratorUnits, denominatorUnits);
126
+ subtotal += math.pow (number.value, 2 );
127
+ }
128
+
129
+ return SassNumber .withUnits (math.sqrt (subtotal),
130
+ numeratorUnits: numeratorUnits, denominatorUnits: denominatorUnits);
82
131
});
83
132
133
+ ///
134
+ /// Unit functions
135
+ ///
136
+
84
137
final _compatible =
85
138
BuiltInCallable ("compatible" , r"$number1, $number2" , (arguments) {
86
139
var number1 = arguments[0 ].assertNumber ("number1" );
87
140
var number2 = arguments[1 ].assertNumber ("number2" );
88
141
return SassBoolean (number1.isComparableTo (number2));
89
142
});
90
143
91
- /// Returns a [Callable] named [name] that transforms a number's value
92
- /// using [transform] and preserves its units.
93
- BuiltInCallable _numberFunction (String name, num transform (num value)) {
94
- return BuiltInCallable (name, r"$number" , (arguments) {
95
- var number = arguments[0 ].assertNumber ("number" );
96
- return SassNumber .withUnits (transform (number.value),
97
- numeratorUnits: number.numeratorUnits,
98
- denominatorUnits: number.denominatorUnits);
99
- });
100
- }
144
+ final _isUnitless = BuiltInCallable ("is-unitless" , r"$number" , (arguments) {
145
+ var number = arguments[0 ].assertNumber ("number" );
146
+ return SassBoolean (! number.hasUnits);
147
+ });
148
+
149
+ final _unit = BuiltInCallable ("unit" , r"$number" , (arguments) {
150
+ var number = arguments[0 ].assertNumber ("number" );
151
+ return SassString (number.unitString, quotes: true );
152
+ });
153
+
154
+ ///
155
+ /// Other functions
156
+ ///
157
+
158
+ final _percentage = BuiltInCallable ("percentage" , r"$number" , (arguments) {
159
+ var number = arguments[0 ].assertNumber ("number" );
160
+ number.assertNoUnits ("number" );
161
+ return SassNumber (number.value * 100 , '%' );
162
+ });
163
+
164
+ final _random = math.Random ();
165
+
166
+ final _randomFunction = BuiltInCallable ("random" , r"$limit: null" , (arguments) {
167
+ if (arguments[0 ] == sassNull) return SassNumber (_random.nextDouble ());
168
+ var limit = arguments[0 ].assertNumber ("limit" ).assertInt ("limit" );
169
+ if (limit < 1 ) {
170
+ throw SassScriptException ("\$ limit: Must be greater than 0, was $limit ." );
171
+ }
172
+ return SassNumber (_random.nextInt (limit) + 1 );
173
+ });
0 commit comments