From 227329f9c30625ef720693567646e3d9134653ba Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 20 Sep 2016 16:02:26 -0700 Subject: [PATCH] Add rgba(). This also adds built-in function overloading. This adds some extra layers to the default function-call logic which could be avoided by handling overloading purely in the callbacks of functions that require it, but it's not clear how to share logic gracefully between the perform visitor and function helpers. --- lib/src/callable.dart | 4 ---- lib/src/callable/built_in.dart | 16 +++++++++----- lib/src/functions.dart | 40 +++++++++++++++++++++++++++++----- lib/src/value/color.dart | 8 ++++++- lib/src/visitor/perform.dart | 26 +++++++++++++++++----- lib/src/visitor/serialize.dart | 15 +++++++++---- 6 files changed, 84 insertions(+), 25 deletions(-) diff --git a/lib/src/callable.dart b/lib/src/callable.dart index 1046e6a4..284209fe 100644 --- a/lib/src/callable.dart +++ b/lib/src/callable.dart @@ -2,13 +2,9 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import 'ast/sass.dart'; - export 'callable/built_in.dart'; export 'callable/user_defined.dart'; abstract class Callable { String get name; - - ArgumentDeclaration get arguments; } diff --git a/lib/src/callable/built_in.dart b/lib/src/callable/built_in.dart index 8c8cd445..5d5be10e 100644 --- a/lib/src/callable/built_in.dart +++ b/lib/src/callable/built_in.dart @@ -9,12 +9,16 @@ import '../value.dart'; typedef Value _Callback(List arguments); class BuiltInCallable implements Callable { - final _Callback callback; - final String name; - final ArgumentDeclaration arguments; + final List overloads; + final List<_Callback> callbacks; - BuiltInCallable( - this.name, this.arguments, Value callback(List arguments)) - : callback = callback; + BuiltInCallable(String name, ArgumentDeclaration arguments, + Value callback(List arguments)) + : this.overloaded(name, [arguments], [callback]); + + BuiltInCallable.overloaded(this.name, Iterable arguments, + Iterable<_Callback> callbacks) + : overloads = new List.unmodifiable(arguments), + callbacks = new List.unmodifiable(callbacks); } diff --git a/lib/src/functions.dart b/lib/src/functions.dart index 45c2fe64..f01030a7 100644 --- a/lib/src/functions.dart +++ b/lib/src/functions.dart @@ -19,18 +19,48 @@ void defineCoreFunctions(Environment environment) { var blue = arguments[2].assertNumber("blue"); return new SassColor.rgb( - _percentageOrUnitless(red, 255, "red"), - _percentageOrUnitless(green, 255, "green"), - _percentageOrUnitless(blue, 255, "blue")); + _percentageOrUnitless(red, 255, "red").round(), + _percentageOrUnitless(green, 255, "green").round(), + _percentageOrUnitless(blue, 255, "blue").round()); })); + environment.setFunction(new BuiltInCallable.overloaded("rgba", [ + new ArgumentDeclaration([ + new Argument("red"), + new Argument("green"), + new Argument("blue"), + new Argument("alpha") + ]), + new ArgumentDeclaration([new Argument("color"), new Argument("alpha")]), + ], [ + (arguments) { + // TODO: support calc strings + var red = arguments[0].assertNumber("red"); + var green = arguments[1].assertNumber("green"); + var blue = arguments[2].assertNumber("blue"); + var alpha = arguments[3].assertNumber("alpha"); + + return new SassColor.rgb( + _percentageOrUnitless(red, 255, "red").round(), + _percentageOrUnitless(green, 255, "green").round(), + _percentageOrUnitless(blue, 255, "blue").round(), + _percentageOrUnitless(alpha, 1, "alpha")); + }, + (arguments) { + var color = arguments[0].assertColor("color"); + var alpha = arguments[0].assertNumber("alpha"); + + return color.change(alpha: _percentageOrUnitless(alpha, 1, "alpha")); + } + ])); + environment.setFunction(new BuiltInCallable( "inspect", new ArgumentDeclaration([new Argument("value")]), (arguments) => new SassString(arguments.single.toString()))); } -int _percentageOrUnitless(SassNumber number, int max, String name) { +num _percentageOrUnitless(SassNumber number, num max, String name) { num value; if (!number.hasUnits) { value = number.value; @@ -41,5 +71,5 @@ int _percentageOrUnitless(SassNumber number, int max, String name) { '\$$name: Expected $number to have no units or "%".'); } - return value.clamp(0, max).round(); + return value.clamp(0, max); } diff --git a/lib/src/value/color.dart b/lib/src/value/color.dart index a257f164..dcb1f8fe 100644 --- a/lib/src/value/color.dart +++ b/lib/src/value/color.dart @@ -12,13 +12,19 @@ class SassColor extends Value { final int red; final int green; final int blue; + final double alpha; - SassColor.rgb(this.red, this.green, this.blue); + SassColor.rgb(this.red, this.green, this.blue, [double alpha]) + : alpha = alpha ?? 1.0; /*=T*/ accept/**/(ValueVisitor/**/ visitor) => visitor.visitColor(this); SassColor assertColor([String name]) => this; + SassColor change({int red, int green, int blue, double alpha}) => + new SassColor.rgb(red ?? this.red, green ?? this.green, blue ?? this.blue, + alpha ?? this.alpha); + Value plus(Value other) { if (other is! SassNumber && other is! SassColor) return super.plus(other); throw new InternalException('Undefined operation "$this + $other".'); diff --git a/lib/src/visitor/perform.dart b/lib/src/visitor/perform.dart index 3f38fb4b..6f5e01c4 100644 --- a/lib/src/visitor/perform.dart +++ b/lib/src/visitor/perform.dart @@ -766,9 +766,26 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor { var named = triple.item2; var separator = triple.item3; - _verifyArguments(positional, named, callable.arguments, invocation.span); + int overloadIndex; + for (var i = 0; i < callable.overloads.length - 1; i++) { + try { + _verifyArguments( + positional, named, callable.overloads[i], invocation.span); + overloadIndex = i; + break; + } on SassRuntimeException catch (_) { + continue; + } + } + if (overloadIndex == null) { + _verifyArguments( + positional, named, callable.overloads.last, invocation.span); + overloadIndex = callable.overloads.length - 1; + } - var declaredArguments = callable.arguments.arguments; + var overload = callable.overloads[overloadIndex]; + var callback = callable.callbacks[overloadIndex]; + var declaredArguments = overload.arguments; for (var i = positional.length; i < declaredArguments.length; i++) { var argument = declaredArguments[i]; positional.add( @@ -776,7 +793,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor { } SassArgumentList argumentList; - if (callable.arguments.restArgument != null) { + if (overload.restArgument != null) { var rest = positional.length > declaredArguments.length ? positional.sublist(declaredArguments.length) : const []; @@ -789,8 +806,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor { positional.add(argumentList); } - var result = - _addExceptionSpan(() => callable.callback(positional), invocation.span); + var result = _addExceptionSpan(() => callback(positional), invocation.span); if (argumentList == null) return result; if (named.isEmpty) return result; diff --git a/lib/src/visitor/serialize.dart b/lib/src/visitor/serialize.dart index 43b8d9e2..1ebab4d5 100644 --- a/lib/src/visitor/serialize.dart +++ b/lib/src/visitor/serialize.dart @@ -225,10 +225,17 @@ class _SerializeCssVisitor void visitColor(SassColor value) { // TODO(nweiz): Use color names for named colors. - _buffer.writeCharCode($hash); - _writeHexComponent(value.red); - _writeHexComponent(value.green); - _writeHexComponent(value.blue); + if (value.alpha == 1) { + _buffer.writeCharCode($hash); + _writeHexComponent(value.red); + _writeHexComponent(value.green); + _writeHexComponent(value.blue); + } else { + // TODO: support precision in alpha, make sure we don't write exponential + // notation. + _buffer.write( + "rgb(${value.red}, ${value.green}, ${value.blue}, ${value.alpha})"); + } } void _writeHexComponent(int color) {