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.
This commit is contained in:
Natalie Weizenbaum 2016-09-20 16:02:26 -07:00 committed by Natalie Weizenbaum
parent 5cc5bf1914
commit 227329f9c3
6 changed files with 84 additions and 25 deletions

View File

@ -2,13 +2,9 @@
// MIT-style license that can be found in the LICENSE file or at // MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'ast/sass.dart';
export 'callable/built_in.dart'; export 'callable/built_in.dart';
export 'callable/user_defined.dart'; export 'callable/user_defined.dart';
abstract class Callable { abstract class Callable {
String get name; String get name;
ArgumentDeclaration get arguments;
} }

View File

@ -9,12 +9,16 @@ import '../value.dart';
typedef Value _Callback(List<Value> arguments); typedef Value _Callback(List<Value> arguments);
class BuiltInCallable implements Callable { class BuiltInCallable implements Callable {
final _Callback callback;
final String name; final String name;
final ArgumentDeclaration arguments; final List<ArgumentDeclaration> overloads;
final List<_Callback> callbacks;
BuiltInCallable( BuiltInCallable(String name, ArgumentDeclaration arguments,
this.name, this.arguments, Value callback(List<Value> arguments)) Value callback(List<Value> arguments))
: callback = callback; : this.overloaded(name, [arguments], [callback]);
BuiltInCallable.overloaded(this.name, Iterable<ArgumentDeclaration> arguments,
Iterable<_Callback> callbacks)
: overloads = new List.unmodifiable(arguments),
callbacks = new List.unmodifiable(callbacks);
} }

View File

@ -19,18 +19,48 @@ void defineCoreFunctions(Environment environment) {
var blue = arguments[2].assertNumber("blue"); var blue = arguments[2].assertNumber("blue");
return new SassColor.rgb( return new SassColor.rgb(
_percentageOrUnitless(red, 255, "red"), _percentageOrUnitless(red, 255, "red").round(),
_percentageOrUnitless(green, 255, "green"), _percentageOrUnitless(green, 255, "green").round(),
_percentageOrUnitless(blue, 255, "blue")); _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( environment.setFunction(new BuiltInCallable(
"inspect", "inspect",
new ArgumentDeclaration([new Argument("value")]), new ArgumentDeclaration([new Argument("value")]),
(arguments) => new SassString(arguments.single.toString()))); (arguments) => new SassString(arguments.single.toString())));
} }
int _percentageOrUnitless(SassNumber number, int max, String name) { num _percentageOrUnitless(SassNumber number, num max, String name) {
num value; num value;
if (!number.hasUnits) { if (!number.hasUnits) {
value = number.value; value = number.value;
@ -41,5 +71,5 @@ int _percentageOrUnitless(SassNumber number, int max, String name) {
'\$$name: Expected $number to have no units or "%".'); '\$$name: Expected $number to have no units or "%".');
} }
return value.clamp(0, max).round(); return value.clamp(0, max);
} }

View File

@ -12,13 +12,19 @@ class SassColor extends Value {
final int red; final int red;
final int green; final int green;
final int blue; 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/*<T>*/(ValueVisitor/*<T>*/ visitor) => visitor.visitColor(this); /*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor) => visitor.visitColor(this);
SassColor assertColor([String name]) => 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) { Value plus(Value other) {
if (other is! SassNumber && other is! SassColor) return super.plus(other); if (other is! SassNumber && other is! SassColor) return super.plus(other);
throw new InternalException('Undefined operation "$this + $other".'); throw new InternalException('Undefined operation "$this + $other".');

View File

@ -766,9 +766,26 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
var named = triple.item2; var named = triple.item2;
var separator = triple.item3; 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++) { for (var i = positional.length; i < declaredArguments.length; i++) {
var argument = declaredArguments[i]; var argument = declaredArguments[i];
positional.add( positional.add(
@ -776,7 +793,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
} }
SassArgumentList argumentList; SassArgumentList argumentList;
if (callable.arguments.restArgument != null) { if (overload.restArgument != null) {
var rest = positional.length > declaredArguments.length var rest = positional.length > declaredArguments.length
? positional.sublist(declaredArguments.length) ? positional.sublist(declaredArguments.length)
: const <Value>[]; : const <Value>[];
@ -789,8 +806,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
positional.add(argumentList); positional.add(argumentList);
} }
var result = var result = _addExceptionSpan(() => callback(positional), invocation.span);
_addExceptionSpan(() => callable.callback(positional), invocation.span);
if (argumentList == null) return result; if (argumentList == null) return result;
if (named.isEmpty) return result; if (named.isEmpty) return result;

View File

@ -225,10 +225,17 @@ class _SerializeCssVisitor
void visitColor(SassColor value) { void visitColor(SassColor value) {
// TODO(nweiz): Use color names for named colors. // TODO(nweiz): Use color names for named colors.
if (value.alpha == 1) {
_buffer.writeCharCode($hash); _buffer.writeCharCode($hash);
_writeHexComponent(value.red); _writeHexComponent(value.red);
_writeHexComponent(value.green); _writeHexComponent(value.green);
_writeHexComponent(value.blue); _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) { void _writeHexComponent(int color) {