Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1168 lines (1030 sloc) 53.2 KB
#include <iostream>
#include <sstream>
#include <cmath>
#include <algorithm>
#include "functions.hpp"
#include "constants.hpp"
#include "node_factory.hpp"
#include "context.hpp"
#include "document.hpp"
#include "eval_apply.hpp"
#include "error.hpp"
#ifndef SASS_PRELEXER
#include "prelexer.hpp"
#endif
using std::cerr; using std::endl; using std::stringstream;
namespace Sass {
using namespace Constants;
// this constructor needs context.hpp, so it can't be defined in functions.hpp
// because including context.hpp in functions.hpp would be circular
Function::Function(char* signature, Primitive ip, Context& ctx)
: definition(Node()),
primitive(ip),
overloaded(false)
{
Document sig_doc(Document::make_from_source_chars(ctx, signature));
sig_doc.lex<Prelexer::identifier>();
name = sig_doc.lexed.to_string();
parameters = sig_doc.parse_parameters();
parameter_names = ctx.new_Node(Node::parameters, "[PRIMITIVE FUNCTIONS]", 0, parameters.size());
for (size_t i = 0, S = parameters.size(); i < S; ++i) {
Node param(parameters[i]);
if (param.type() == Node::variable) {
parameter_names << param;
}
else {
parameter_names << param[0];
// assume it's safe to evaluate default args just once at initialization
param[1] = eval(param[1], Node(), ctx.global_env, ctx.function_env, ctx.new_Node, ctx);
}
}
}
namespace Functions {
static void throw_eval_error(string message, string& path, size_t line)
{
if (!path.empty() && Prelexer::string_constant(path.c_str()))
path = path.substr(1, path.length() - 1);
throw Error(Error::evaluation, path, line, message);
}
static const char* nameof(Node::Type t) {
switch (t)
{
case Node::numeric: {
return numeric_name;
} break;
case Node::number: {
return number_name;
} break;
case Node::numeric_percentage: {
return percentage_name;
} break;
case Node::numeric_dimension: {
return dimension_name;
} break;
case Node::string_t:
case Node::identifier:
case Node::value_schema:
case Node::identifier_schema:
case Node::string_constant:
case Node::string_schema:
case Node::concatenation: {
return string_name;
} break;
case Node::boolean: {
return bool_name;
} break;
case Node::numeric_color: {
return color_name;
} break;
case Node::list: {
return list_name;
} break;
default: {
return empty_str;
} break;
}
// unreachable statement
return empty_str;
}
// Functions for fetching and checking arguments.
static Node arg(Signature sig, string& path, size_t line, const Node parameter_names, Environment& bindings, size_t param_num, Node::Type param_type) {
Node the_arg(bindings[parameter_names[param_num].token()]);
Node::Type arg_type = the_arg.type();
switch (param_type)
{
case Node::any: {
return the_arg;
} break;
case Node::numeric: {
if (the_arg.is_numeric()) return the_arg;
} break;
case Node::string_t: {
switch (arg_type)
{
case Node::identifier:
case Node::value_schema:
case Node::identifier_schema:
case Node::string_constant:
case Node::string_schema:
case Node::concatenation: return the_arg;
default: break;
} break;
} break;
default: {
if (arg_type == param_type) return the_arg;
} break;
}
stringstream msg;
msg << nameof(param_type) << " required for argument " << param_num+1 << " in call to '" << sig << "'";
throw_eval_error(msg.str(), path, line);
// unreachable statement
return Node();
}
static Node arg(Signature sig, string& path, size_t line, const Node parameter_names, Environment& bindings, size_t param_num, Node::Type t, double low, double high) {
Node the_arg(arg(sig, path, line, parameter_names, bindings, param_num, t));
if (!the_arg.is_numeric()) {
stringstream msg;
msg << "numeric value required for argument " << param_num+1 << " in call to '" << sig << "'";
throw_eval_error(msg.str(), path, line);
}
double val = the_arg.numeric_value();
if (val < low || high < val) {
stringstream msg;
msg << "argument " << param_num+1 << " must be between " << low << " and " << high << " in call to '" << sig << "'";
throw_eval_error(msg.str(), path, line);
}
return the_arg;
}
////////////////////////////////////////////////////////////////////////
// RGB Functions ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature rgb_sig = "rgb($red, $green, $blue)";
Node rgb(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
double r = arg(rgb_sig, path, line, parameter_names, bindings, 0, Node::numeric, 0, 255).numeric_value();
double g = arg(rgb_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 255).numeric_value();
double b = arg(rgb_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 255).numeric_value();
return new_Node(path, line, std::floor(r), std::floor(g), std::floor(b), 1.0);
}
extern Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)";
Node rgba_4(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
double r = arg(rgba_4_sig, path, line, parameter_names, bindings, 0, Node::numeric, 0, 255).numeric_value();
double g = arg(rgba_4_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 255).numeric_value();
double b = arg(rgba_4_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 255).numeric_value();
double a = arg(rgba_4_sig, path, line, parameter_names, bindings, 3, Node::numeric, 0, 1).numeric_value();
return new_Node(path, line, std::floor(r), std::floor(g), std::floor(b), a);
}
extern Signature rgba_2_sig = "rgba($color, $alpha)";
Node rgba_2(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color_arg(arg(rgba_2_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node alpha_arg(arg(rgba_2_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1));
double r = color_arg[0].numeric_value();
double g = color_arg[1].numeric_value();
double b = color_arg[2].numeric_value();
double a = alpha_arg.numeric_value();
return new_Node(path, line, r, g, b, a);
}
extern Signature red_sig = "red($color)";
Node red(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(red_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(path, line, color[0]);
}
extern Signature green_sig = "green($color)";
Node green(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(green_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(path, line, color[1]);
}
extern Signature blue_sig = "blue($color)";
Node blue(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(blue_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(path, line, color[2]);
}
extern Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)";
Node mix(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color1(arg(mix_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node color2(arg(mix_sig, path, line, parameter_names, bindings, 1, Node::numeric_color));
Node weight(arg(mix_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 100));
double p = weight.numeric_value()/100;
double w = 2*p - 1;
double a = color1[3].numeric_value() - color2[3].numeric_value();
double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0;
double w2 = 1 - w1;
Node mixed(new_Node(Node::numeric_color, path, line, 4));
for (int i = 0; i < 3; ++i) {
mixed << new_Node(path, line, std::floor(w1*color1[i].numeric_value() + w2*color2[i].numeric_value()));
}
double alpha = color1[3].numeric_value()*p + color2[3].numeric_value()*(1-p);
mixed << new_Node(path, line, alpha);
return mixed;
}
////////////////////////////////////////////////////////////////////////
// HSL Functions ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// RGB to HSL helper function so we can do hsl operations.
// (taken from http://www.easyrgb.com)
Node rgb_to_hsl(double r, double g, double b, Node_Factory& new_Node, string& path, size_t line) {
r /= 255.0; g /= 255.0; b /= 255.0;
double max = std::max(r, std::max(g, b));
double min = std::min(r, std::min(g, b));
double del = max - min;
double h = 0, s = 0, l = (max + min)/2;
if (max == min) {
h = s = 0; // achromatic
}
else {
if (l < 0.5) s = del / (max + min);
else s = del / (2.0 - max - min);
double dr = (((max - r)/6.0) + (del/2.0))/del;
double dg = (((max - g)/6.0) + (del/2.0))/del;
double db = (((max - b)/6.0) + (del/2.0))/del;
if (r == max) h = db - dg;
else if (g == max) h = (1.0/3.0) + dr - db;
else if (b == max) h = (2.0/3.0) + dg - dr;
if (h < 0) h += 1;
else if (h > 1) h -= 1;
}
return new_Node(path, line, static_cast<int>(h*360)%360, s*100, l*100);
}
// Hue to RGB helper function
double h_to_rgb(double m1, double m2, double h) {
if (h < 0) h += 1;
if (h > 1) h -= 1;
if (h*6.0 < 1) return m1 + (m2 - m1)*h*6;
if (h*2.0 < 1) return m2;
if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6;
return m1;
}
Node hsla_impl(double h, double s, double l, double a, Node_Factory& new_Node, string& path, size_t line) {
h = static_cast<double>(((static_cast<int>(h) % 360) + 360) % 360) / 360.0;
s = (s < 0) ? 0 :
(s > 100) ? 100 :
s;
l = (l < 0) ? 0 :
(l > 100) ? 100 :
l;
s /= 100.0;
l /= 100.0;
double m2;
if (l <= 0.5) m2 = l*(s+1.0);
else m2 = l+s-l*s;
double m1 = l*2-m2;
// round the results -- consider moving this into the Node constructors
double r = std::floor(h_to_rgb(m1, m2, h+1.0/3.0) * 255.0 + 0.5);
double g = std::floor(h_to_rgb(m1, m2, h) * 255.0 + 0.5);
double b = std::floor(h_to_rgb(m1, m2, h-1.0/3.0) * 255.0 + 0.5);
return new_Node(path, line, r, g, b, a);
}
extern Signature hsl_sig = "hsl($hue, $saturation, $lightness)";
Node hsl(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
double h = arg(hsl_sig, path, line, parameter_names, bindings, 0, Node::numeric).numeric_value();
double s = arg(hsl_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100).numeric_value();
double l = arg(hsl_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 100).numeric_value();
return hsla_impl(h, s, l, 1.0, new_Node, path, line);
}
extern Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)";
Node hsla(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
double h = arg(hsla_sig, path, line, parameter_names, bindings, 0, Node::numeric).numeric_value();
double s = arg(hsla_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100).numeric_value();
double l = arg(hsla_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 100).numeric_value();
double a = arg(hsla_sig, path, line, parameter_names, bindings, 3, Node::numeric, 0, 1).numeric_value();
return hsla_impl(h, s, l, a, new_Node, path, line);
}
extern Signature hue_sig = "hue($color)";
Node hue(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_color(arg(hue_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node hsl_color(rgb_to_hsl(rgb_color[0].numeric_value(),
rgb_color[1].numeric_value(),
rgb_color[2].numeric_value(),
new_Node, path, line));
return new_Node(path, line, hsl_color[0].numeric_value(), Token::make(deg_kwd));
}
extern Signature saturation_sig = "saturation($color)";
Node saturation(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_color(arg(saturation_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node hsl_color(rgb_to_hsl(rgb_color[0].numeric_value(),
rgb_color[1].numeric_value(),
rgb_color[2].numeric_value(),
new_Node, path, line));
return new_Node(path, line, hsl_color[1].numeric_value(), Token::make(percent_str));
}
extern Signature lightness_sig = "lightness($color)";
Node lightness(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_color(arg(lightness_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node hsl_color(rgb_to_hsl(rgb_color[0].numeric_value(),
rgb_color[1].numeric_value(),
rgb_color[2].numeric_value(),
new_Node, path, line));
return new_Node(path, line, hsl_color[2].numeric_value(), Token::make(percent_str));
}
extern Signature adjust_hue_sig = "adjust-hue($color, $degrees)";
Node adjust_hue(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_col(arg(adjust_hue_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node degrees(arg(adjust_hue_sig, path, line, parameter_names, bindings, 1, Node::numeric));
Node hsl_col(rgb_to_hsl(rgb_col[0].numeric_value(),
rgb_col[1].numeric_value(),
rgb_col[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_col[0].numeric_value() + degrees.numeric_value(),
hsl_col[1].numeric_value(),
hsl_col[2].numeric_value(),
rgb_col[3].numeric_value(),
new_Node, path, line);
}
extern Signature lighten_sig = "lighten($color, $amount)";
Node lighten(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_col(arg(lighten_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node amount(arg(lighten_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100));
Node hsl_col(rgb_to_hsl(rgb_col[0].numeric_value(),
rgb_col[1].numeric_value(),
rgb_col[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_col[0].numeric_value(),
hsl_col[1].numeric_value(),
hsl_col[2].numeric_value() + amount.numeric_value(),
rgb_col[3].numeric_value(),
new_Node, path, line);
}
extern Signature darken_sig = "darken($color, $amount)";
Node darken(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_col(arg(darken_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node amount(arg(darken_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100));
Node hsl_col(rgb_to_hsl(rgb_col[0].numeric_value(),
rgb_col[1].numeric_value(),
rgb_col[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_col[0].numeric_value(),
hsl_col[1].numeric_value(),
hsl_col[2].numeric_value() - amount.numeric_value(),
rgb_col[3].numeric_value(),
new_Node, path, line);
}
extern Signature saturate_sig = "saturate($color, $amount)";
Node saturate(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_col(arg(saturate_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node amount(arg(saturate_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100));
Node hsl_col(rgb_to_hsl(rgb_col[0].numeric_value(),
rgb_col[1].numeric_value(),
rgb_col[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_col[0].numeric_value(),
hsl_col[1].numeric_value() + amount.numeric_value(),
hsl_col[2].numeric_value(),
rgb_col[3].numeric_value(),
new_Node, path, line);
}
extern Signature desaturate_sig = "desaturate($color, $amount)";
Node desaturate(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node rgb_col(arg(desaturate_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node amount(arg(desaturate_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100));
Node hsl_col(rgb_to_hsl(rgb_col[0].numeric_value(),
rgb_col[1].numeric_value(),
rgb_col[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_col[0].numeric_value(),
hsl_col[1].numeric_value() - amount.numeric_value(),
hsl_col[2].numeric_value(),
rgb_col[3].numeric_value(),
new_Node, path, line);
}
extern Signature grayscale_sig = "grayscale($color)";
Node grayscale(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(grayscale_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node hsl_color(rgb_to_hsl(color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_color[0].numeric_value(),
0.0, // desaturate completely
hsl_color[2].numeric_value(),
color[3].numeric_value(),
new_Node, path, line);
}
extern Signature complement_sig = "complement($color)";
Node complement(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(complement_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node hsl_color(rgb_to_hsl(color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
new_Node, path, line));
return hsla_impl(hsl_color[0].numeric_value() - 180, // other side of the color wheel
hsl_color[1].numeric_value(),
hsl_color[2].numeric_value(),
color[3].numeric_value(),
new_Node, path, line);
}
extern Signature invert_sig = "invert($color)";
Node invert(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(invert_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(path, line,
255 - color[0].numeric_value(),
255 - color[1].numeric_value(),
255 - color[2].numeric_value(),
color[3].numeric_value());
}
////////////////////////////////////////////////////////////////////////
// Opacity Functions ///////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature alpha_sig = "alpha($color)";
Node alpha(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(alpha_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(path, line, color[3]);
}
extern Signature opacity_sig = "opacity($color)";
Node opacity(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(opacity_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(path, line, color[3]);
}
extern Signature opacify_sig = "opacify($color, $amount)";
Node opacify(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(opacify_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
double delta = arg(opacify_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
delta += color[3].numeric_value();
if (delta > 1) delta = 1;
return new_Node(path, line,
color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
delta);
}
extern Signature fade_in_sig = "fade-in($color, $amount)";
Node fade_in(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(fade_in_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
double delta = arg(fade_in_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
delta += color[3].numeric_value();
if (delta > 1) delta = 1;
return new_Node(path, line,
color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
delta);
}
extern Signature transparentize_sig = "transparentize($color, $amount)";
Node transparentize(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(transparentize_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
double delta = arg(transparentize_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
double alpha = color[3].numeric_value() - delta;
if (alpha < 0) alpha = 0;
return new_Node(path, line,
color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
alpha);
}
extern Signature fade_out_sig = "fade-out($color, $amount)";
Node fade_out(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(arg(fade_out_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
double delta = arg(fade_out_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
double alpha = color[3].numeric_value() - delta;
if (alpha < 0) alpha = 0;
return new_Node(path, line,
color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
alpha);
}
////////////////////////////////////////////////////////////////////////
// Other Color Functions ///////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// not worth using the arg(...) functions
extern Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)";
Node adjust_color(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(bindings[parameter_names[0].token()]);
Node r(bindings[parameter_names[1].token()]);
Node g(bindings[parameter_names[2].token()]);
Node b(bindings[parameter_names[3].token()]);
Node h(bindings[parameter_names[4].token()]);
Node s(bindings[parameter_names[5].token()]);
Node l(bindings[parameter_names[6].token()]);
Node a(bindings[parameter_names[7].token()]);
bool no_rgb = r.is_false() && g.is_false() && b.is_false();
bool no_hsl = h.is_false() && s.is_false() && l.is_false();
if (color.type() != Node::numeric_color) {
throw_eval_error("first argument to 'adjust-color' must be a color", color.path(), color.line());
}
else if (!no_rgb && !no_hsl) {
throw_eval_error("cannot specify RGB and HSL values for a color at the same time for 'adjust-color'", r.path(), r.line());
}
else if (!no_rgb) {
if (!r.is_false() && !r.is_numeric()) throw_eval_error("argument $red of 'adjust-color' must be numeric", r.path(), r.line());
if (!g.is_false() && !g.is_numeric()) throw_eval_error("argument $green of 'adjust-color' must be numeric", g.path(), g.line());
if (!b.is_false() && !b.is_numeric()) throw_eval_error("argument $blue of 'adjust-color' must be numeric", b.path(), b.line());
if (!a.is_false() && !a.is_numeric()) throw_eval_error("argument $alpha of 'adjust-color' must be numeric", a.path(), a.line());
double new_r = color[0].numeric_value() + (r.is_false() ? 0 : r.numeric_value());
double new_g = color[1].numeric_value() + (g.is_false() ? 0 : g.numeric_value());
double new_b = color[2].numeric_value() + (b.is_false() ? 0 : b.numeric_value());
double new_a = color[3].numeric_value() + (a.is_false() ? 0 : a.numeric_value());
return new_Node(path, line, new_r, new_g, new_b, new_a);
}
else if (!no_hsl) {
Node hsl_node(rgb_to_hsl(color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
new_Node, path, line));
if (!h.is_false() && !h.is_numeric()) throw_eval_error("argument $hue of 'adjust-color' must be numeric", h.path(), h.line());
if (!s.is_false() && !s.is_numeric()) throw_eval_error("argument $saturation of 'adjust-color' must be numeric", s.path(), s.line());
if (!l.is_false() && !l.is_numeric()) throw_eval_error("argument $lightness of 'adjust-color' must be numeric", l.path(), l.line());
if (!a.is_false() && !a.is_numeric()) throw_eval_error("argument $alpha of 'adjust-color' must be numeric", a.path(), a.line());
double new_h = (h.is_false() ? 0 : h.numeric_value()) + hsl_node[0].numeric_value();
double new_s = (s.is_false() ? 0 : s.numeric_value()) + hsl_node[1].numeric_value();
double new_l = (l.is_false() ? 0 : l.numeric_value()) + hsl_node[2].numeric_value();
double new_a = (a.is_false() ? 0 : a.numeric_value()) + color[3].numeric_value();
return hsla_impl(new_h, new_s, new_l, new_a, new_Node, path, line);
}
else if (!a.is_false()) {
if (!a.is_numeric()) throw_eval_error("argument $alpha of 'adjust-color' must be numeric", a.path(), a.line());
return new_Node(path, line,
color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
color[3].numeric_value() + a.numeric_value());
}
else {
throw_eval_error("not enough arguments for 'adjust-color'", color.path(), color.line());
}
// unreachable statement
return Node();
}
extern Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)";
Node scale_color(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(bindings[parameter_names[0].token()]);
Node r(bindings[parameter_names[1].token()]);
Node g(bindings[parameter_names[2].token()]);
Node b(bindings[parameter_names[3].token()]);
Node h(bindings[parameter_names[4].token()]);
Node s(bindings[parameter_names[5].token()]);
Node l(bindings[parameter_names[6].token()]);
Node a(bindings[parameter_names[7].token()]);
bool no_rgb = r.is_false() && g.is_false() && b.is_false();
bool no_hsl = h.is_false() && s.is_false() && l.is_false();
size_t low, high;
bool is_hsl = false;
if (color.type() != Node::numeric_color) {
throw_eval_error("first argument to 'scale-color' must be a color", path, line);
}
if (!no_rgb && !no_hsl) {
throw_eval_error("cannot specify RGB and HSL values for a color at the same time for 'scale-color'", path, line);
}
else if (!no_rgb) {
low = 1; high = 4;
}
else if (!no_hsl) {
is_hsl = true;
low = 4; high = 7;
Node alpha(color[3]);
color = rgb_to_hsl(color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), new_Node, path, line);
color << alpha;
}
else if (!a.is_false()) {
Node result(new_Node(Node::numeric_color, path, line, 4));
for (size_t i = 0; i < 3; ++i) {
result << new_Node(path, line, color[i].numeric_value());
}
double current = color[3].numeric_value();
double scale = arg(scale_color_sig, path, line, parameter_names, bindings, 7, Node::numeric_percentage, -100, 100).numeric_value() / 100;
double diff = scale > 0 ? 1 - current : current;
result << new_Node(path, line, current + diff*scale);
return result;
}
else {
throw_eval_error("not enough arguments for 'scale-color'", color.path(), color.line());
}
Node result(new_Node(Node::numeric_color, path, line, 4));
for (size_t i = low, j = 0; i < high; ++i, ++j) {
double current = color[j].numeric_value();
if (!bindings[parameter_names[i].token()].is_false()) {
double scale = arg(scale_color_sig, path, line, parameter_names, bindings, i, Node::numeric_percentage, -100, 100).numeric_value() / 100;
double diff = scale > 0 ? (is_hsl ? 100 : 255) - current : current;
result << new_Node(path, line, current + diff*scale);
}
else {
result << new_Node(path, line, current);
}
}
if (!a.is_false()) {
double current = color[3].numeric_value();
double scale = arg(scale_color_sig, path, line, parameter_names, bindings, 7, Node::numeric_percentage, -100, 100).numeric_value() / 100;
double diff = scale > 0 ? 1 - current : current;
result << new_Node(path, line, current + diff*scale);
}
else {
result << new_Node(path, line, color[3].numeric_value());
}
if (is_hsl) {
result = hsla_impl(result[0].numeric_value(),
result[1].numeric_value(),
result[2].numeric_value(),
result[3].numeric_value(),
new_Node, path, line);
}
return result;
}
extern Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)";
Node change_color(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node color(bindings[parameter_names[0].token()]);
Node r(bindings[parameter_names[1].token()]);
Node g(bindings[parameter_names[2].token()]);
Node b(bindings[parameter_names[3].token()]);
Node h(bindings[parameter_names[4].token()]);
Node s(bindings[parameter_names[5].token()]);
Node l(bindings[parameter_names[6].token()]);
Node a(bindings[parameter_names[7].token()]);
bool no_rgb = r.is_false() && g.is_false() && b.is_false();
bool no_hsl = h.is_false() && s.is_false() && l.is_false();
if (color.type() != Node::numeric_color) {
throw_eval_error("first argument to 'change-color' must be a color", color.path(), color.line());
}
if (!no_rgb && !no_hsl) {
throw_eval_error("cannot specify RGB and HSL values for a color at the same time for 'change-color'", r.path(), r.line());
}
else if (!no_rgb) {
if (!r.is_false() && !r.is_numeric()) throw_eval_error("argument $red of 'change-color' must be numeric", r.path(), r.line());
if (!g.is_false() && !g.is_numeric()) throw_eval_error("argument $green of 'change-color' must be numeric", g.path(), g.line());
if (!b.is_false() && !b.is_numeric()) throw_eval_error("argument $blue of 'change-color' must be numeric", b.path(), b.line());
if (!a.is_false() && !a.is_numeric()) throw_eval_error("argument $alpha of 'change-color' must be numeric", a.path(), a.line());
double new_r = (r.is_false() ? color[0] : r).numeric_value();
double new_g = (g.is_false() ? color[1] : g).numeric_value();
double new_b = (b.is_false() ? color[2] : b).numeric_value();
double new_a = (a.is_false() ? color[3] : a).numeric_value();
return new_Node(path, line, new_r, new_g, new_b, new_a);
}
else if (!no_hsl) {
Node hsl_node(rgb_to_hsl(color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
new_Node, path, line));
if (!h.is_false() && !h.is_numeric()) throw_eval_error("argument $hue of 'change-color' must be numeric", h.path(), h.line());
if (!s.is_false() && !s.is_numeric()) throw_eval_error("argument $saturation of 'change-color' must be numeric", s.path(), s.line());
if (!l.is_false() && !l.is_numeric()) throw_eval_error("argument $lightness of 'change-color' must be numeric", l.path(), l.line());
if (!a.is_false() && !a.is_numeric()) throw_eval_error("argument $alpha of 'change-color' must be numeric", a.path(), a.line());
double new_h = (h.is_false() ? hsl_node[0].numeric_value() : h.numeric_value());
double new_s = (s.is_false() ? hsl_node[1].numeric_value() : s.numeric_value());
double new_l = (l.is_false() ? hsl_node[2].numeric_value() : l.numeric_value());
double new_a = (a.is_false() ? color[3].numeric_value() : a.numeric_value());
return hsla_impl(new_h, new_s, new_l, new_a, new_Node, path, line);
}
else if (!a.is_false()) {
if (!a.is_numeric()) throw_eval_error("argument $alpha of 'change-color' must be numeric", a.path(), a.line());
return new_Node(path, line,
color[0].numeric_value(),
color[1].numeric_value(),
color[2].numeric_value(),
a.numeric_value());
}
else {
throw_eval_error("not enough arguments for 'change-color'", color.path(), color.line());
}
// unreachable statement
return Node();
}
////////////////////////////////////////////////////////////////////////
// String Functions ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature unquote_sig = "unquote($string)";
Node unquote(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node cpy(new_Node(path, line, bindings[parameter_names[0].token()]));
cpy.is_quoted() = false;
return cpy;
}
extern Signature quote_sig = "quote($string)";
Node quote(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node orig(arg(quote_sig, path, line, parameter_names, bindings, 0, Node::string_t));
Node copy(new_Node(path, line, orig));
copy.is_quoted() = true;
return copy;
}
////////////////////////////////////////////////////////////////////////
// Number Functions ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature percentage_sig = "percentage($value)";
Node percentage(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node orig(arg(percentage_sig, path, line, parameter_names, bindings, 0, Node::number));
return new_Node(path, line, orig.numeric_value() * 100, Node::numeric_percentage);
}
extern Signature round_sig = "round($value)";
Node round(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node orig(arg(round_sig, path, line, parameter_names, bindings, 0, Node::numeric));
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(path, line,
std::floor(orig.numeric_value() + 0.5),
orig.unit());
} break;
case Node::number: {
return new_Node(path, line,
std::floor(orig.numeric_value() + 0.5));
} break;
case Node::numeric_percentage: {
return new_Node(path, line,
std::floor(orig.numeric_value() + 0.5),
Node::numeric_percentage);
} break;
default: {
// unreachable
throw_eval_error("argument to round must be numeric", path, line);
} break;
}
// unreachable statement
return Node();
}
extern Signature ceil_sig = "ceil($value)";
Node ceil(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node orig(arg(ceil_sig, path, line, parameter_names, bindings, 0, Node::numeric));
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(path, line,
std::ceil(orig.numeric_value()),
orig.unit());
} break;
case Node::number: {
return new_Node(path, line,
std::ceil(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(path, line,
std::ceil(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
// unreachable
throw_eval_error("argument to ceil must be numeric", path, line);
} break;
}
// unreachable statement
return Node();
}
extern Signature floor_sig = "floor($value)";
Node floor(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node orig(arg(floor_sig, path, line, parameter_names, bindings, 0, Node::numeric));
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(path, line,
std::floor(orig.numeric_value()),
orig.unit());
} break;
case Node::number: {
return new_Node(path, line,
std::floor(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(path, line,
std::floor(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
// unreachable
throw_eval_error("argument to floor must be numeric", path, line);
} break;
}
// unreachable statement
return Node();
}
extern Signature abs_sig = "abs($value)";
Node abs(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node orig(arg(abs_sig, path, line, parameter_names, bindings, 0, Node::numeric));
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(path, line,
std::abs(orig.numeric_value()),
orig.unit());
} break;
case Node::number: {
return new_Node(path, line,
std::abs(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(path, line,
std::abs(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
// unreachable
throw_eval_error("argument to abs must be numeric", path, line);
} break;
}
// unreachable statement
return Node();
}
////////////////////////////////////////////////////////////////////////
// List Functions //////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature length_sig = "length($list)";
Node length(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node arg(bindings[parameter_names[0].token()]);
return new_Node(path, line, arg.type() == Node::list ? arg.size() : 1);
}
extern Signature nth_sig = "nth($list, $n)";
Node nth(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node l(bindings[parameter_names[0].token()]);
// wrap the first arg if it isn't a list
if (l.type() != Node::list) {
l = new_Node(Node::list, path, line, 1) << l;
}
if (l.size() == 0) {
throw_eval_error("cannot index into an empty list", path, line);
}
// just truncate the index if it's not an integer ... more permissive than Ruby Sass
size_t n = std::floor(arg(nth_sig, path, line, parameter_names, bindings, 1, Node::numeric, 1, l.size()).numeric_value());
return l[n - 1];
}
extern Signature index_sig = "index($list, $value)";
Node index(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node lst(bindings[parameter_names[0].token()]);
Node val(bindings[parameter_names[1].token()]);
// if $list isn't a list, wrap it in a singleton list
if (lst.type() != Node::list) lst = (new_Node(Node::list, path, line, 1) << lst);
for (size_t i = 0, S = lst.size(); i < S; ++i) {
if (lst[i] == val) return new_Node(path, line, i + 1);
}
return new_Node(Node::boolean, path, line, false);
}
extern Signature join_sig = "join($list1, $list2, $separator: auto)";
Node join(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
// if the args aren't lists, turn them into singleton lists
Node l1(bindings[parameter_names[0].token()]);
if (l1.type() != Node::list) {
l1 = (new_Node(Node::list, path, line, 1) << l1);
}
Node l2(bindings[parameter_names[1].token()]);
if (l2.type() != Node::list) {
l2 = (new_Node(Node::list, path, line, 1) << l2);
}
// figure out the combined size in advance
size_t size = l1.size() + l2.size();
// figure out the result type in advance
bool comma_sep;
string sep(bindings[parameter_names[2].token()].token().unquote());
if (sep == "comma") comma_sep = true;
else if (sep == "space") comma_sep = false;
else if (sep == "auto") comma_sep = l1.is_comma_separated();
else throw_eval_error("third argument to 'join' must be 'space', 'comma', or 'auto'", path, line);
if (l1.size() == 0) comma_sep = l2.is_comma_separated();
// accumulate the result
Node lr(new_Node(Node::list, path, line, size));
lr += l1;
lr += l2;
lr.is_comma_separated() = comma_sep;
return lr;
}
extern Signature append_sig = "append($list1, $list2, $separator: auto)";
Node append(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node list(bindings[parameter_names[0].token()]);
// if the first arg isn't a list, wrap it in a singleton
if (list.type() != Node::list) list = (new_Node(Node::list, path, line, 1) << list);
bool comma_sep;
string sep(bindings[parameter_names[2].token()].token().unquote());
if (sep == "comma") comma_sep = true;
else if (sep == "space") comma_sep = false;
else if (sep == "auto") comma_sep = list.is_comma_separated();
else throw_eval_error("third argument to 'append' must be 'space', 'comma', or 'auto'", path, line);
Node new_list(new_Node(Node::list, path, line, list.size() + 1));
new_list += list;
new_list << bindings[parameter_names[1].token()];
new_list.is_comma_separated() = comma_sep;
return new_list;
}
extern Signature compact_1_sig = "compact($arg1)";
Node compact_1(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node the_arg(bindings[parameter_names[0].token()]);
if (the_arg.type() == Node::list) {
Node non_nils(new_Node(Node::list, path, line, 0));
for (size_t i = 0, S = the_arg.size(); i < S; ++i) {
Node elt(the_arg[i]);
if (!elt.is_false()) non_nils << elt;
}
return non_nils;
}
return new_Node(Node::list, path, line, 1) << the_arg;
}
extern Signature compact_n_sig = "compact($arg1: false, $arg2: false, $arg3: false, $arg4: false, $arg5: false, $arg6: false, $arg7: false, $arg8: false, $arg9: false, $arg10: false, $arg11: false, $arg12: false)";
Node compact_n(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node non_nils(new_Node(Node::list, path, line, 0));
non_nils.is_comma_separated() = true;
for (size_t i = 0, S = bindings.current_frame.size(); i < S; ++i) {
Node the_arg(bindings[parameter_names[i].token()]);
if (!the_arg.is_false()) non_nils << the_arg;
}
return non_nils;
}
////////////////////////////////////////////////////////////////////////
// Introspection Functions /////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature type_of_sig = "type-of($value)";
Node type_of(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node val(bindings[parameter_names[0].token()]);
Token type_name;
switch (val.type())
{
case Node::number:
case Node::numeric_dimension:
case Node::numeric_percentage: {
type_name = Token::make(number_name);
} break;
case Node::boolean: {
type_name = Token::make(bool_name);
} break;
case Node::string_constant:
case Node::value_schema: {
type_name = Token::make(string_name);
} break;
case Node::numeric_color: {
type_name = Token::make(color_name);
} break;
case Node::list: {
type_name = Token::make(list_name);
} break;
default: {
type_name = Token::make(string_name);
} break;
}
return new_Node(Node::identifier, path, line, type_name);
}
extern Signature unit_sig = "unit($number)";
Node unit(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node val(arg(unit_sig, path, line, parameter_names, bindings, 0, Node::numeric));
switch (val.type())
{
case Node::number: {
Node u(new_Node(Node::string_constant, path, line, Token::make(empty_str)));
u.is_quoted() = true;
return u;
} break;
case Node::numeric_dimension:
case Node::numeric_percentage: {
Node u(new_Node(Node::string_constant, path, line, val.unit()));
u.is_quoted() = true;
return u;
} break;
// unreachable
default: {
throw_eval_error("argument to 'unit' must be numeric", path, line);
} break;
}
// unreachable statement
return Node();
}
extern Signature unitless_sig = "unitless($number)";
Node unitless(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node val(arg(unitless_sig, path, line, parameter_names, bindings, 0, Node::numeric));
switch (val.type())
{
case Node::number: {
return new_Node(Node::boolean, path, line, true);
} break;
case Node::numeric_percentage:
case Node::numeric_dimension: {
return new_Node(Node::boolean, path, line, false);
} break;
// unreachable
default: {
throw_eval_error("argument to 'unitless' must be numeric", path, line);
} break;
}
// unreachable statement
return Node();
}
extern Signature comparable_sig = "comparable($number-1, $number-2)";
Node comparable(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node n1(arg(comparable_sig, path, line, parameter_names, bindings, 0, Node::numeric));
Node n2(arg(comparable_sig, path, line, parameter_names, bindings, 1, Node::numeric));
Node::Type t1 = n1.type();
Node::Type t2 = n2.type();
if ((t1 == Node::number && n2.is_numeric()) ||
(n1.is_numeric() && t2 == Node::number)) {
return new_Node(Node::boolean, path, line, true);
}
else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) {
return new_Node(Node::boolean, path, line, true);
}
else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) {
string u1(n1.unit().to_string());
string u2(n2.unit().to_string());
if ((u1 == "ex" && u2 == "ex") ||
(u1 == "em" && u2 == "em") ||
((u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc" || u1 == "px") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc" || u2 == "px"))) {
return new_Node(Node::boolean, path, line, true);
}
else {
return new_Node(Node::boolean, path, line, false);
}
}
// default to false if we missed anything
return new_Node(Node::boolean, path, line, false);
}
////////////////////////////////////////////////////////////////////////
// Boolean Functions ///////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature not_sig = "not($value)";
Node not_impl(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node val(bindings[parameter_names[0].token()]);
if (val.is_false()) return new_Node(Node::boolean, path, line, true);
return new_Node(Node::boolean, path, line, false);
}
extern Signature if_sig = "if($condition, $if-true, $if-false)";
Node if_impl(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node predicate(bindings[parameter_names[0].token()]);
Node consequent(bindings[parameter_names[1].token()]);
Node alternative(bindings[parameter_names[2].token()]);
if (predicate.is_false()) return alternative;
return consequent;
}
////////////////////////////////////////////////////////////////////////
// Path Functions //////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern Signature image_url_sig = "image-url($path, $only-path: false, $cache-buster: false)";
Node image_url(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node base_path(bindings[parameter_names[0].token()]);
bool only_path = !bindings[parameter_names[1].token()].is_false();
Node image_path_val(bindings[Token::make(image_path_var)]);
Node result(new_Node(Node::concatenation, path, line, 2));
result << image_path_val;
result << base_path;
result.is_quoted() = true;
if (!only_path) result = (new_Node(Node::uri, path, line, 1) << result);
return result;
}
}
}