Commit 0a31ae6d by litek
parents f0313c3d e9971023
CC=g++ CC = g++
CFLAGS=-c -Wall -O2 -fPIC CFLAGS = -Wall -O2 -fPIC
LDFLAGS= -fPIC LDFLAGS = -fPIC
SOURCES = \
context.cpp functions.cpp document.cpp \ PREFIX = /usr/local
document_parser.cpp eval_apply.cpp node.cpp \ LIBDIR = $(PREFIX)/lib
node_factory.cpp node_emitters.cpp prelexer.cpp \
sass_interface.cpp SOURCES = constants.cpp context.cpp functions.cpp document.cpp \
document_parser.cpp eval_apply.cpp node.cpp \
node_factory.cpp node_emitters.cpp prelexer.cpp \
selector.cpp sass_interface.cpp
OBJECTS = $(SOURCES:.cpp=.o) OBJECTS = $(SOURCES:.cpp=.o)
all: $(OBJECTS) static: libsass.a
ar rvs libsass.a $(OBJECTS) shared: libsass.so
libsass.a: $(OBJECTS)
ar rvs $@ $(OBJECTS)
shared: $(OBJECTS) libsass.so: $(OBJECTS)
$(CC) -shared -o libsass.so *.o $(CC) -shared $(LDFLAGS) -o $@ $(OBJECTS)
.cpp.o: .cpp.o:
$(CC) $(CFLAGS) $< -o $@ $(CC) $(CFLAGS) -c -o $@ $<
install: libsass.a
install -Dpm0755 $< $(DESTDIR)$(LIBDIR)/$<
install-shared: libsass.so
install -Dpm0755 $< $(DESTDIR)$(LIBDIR)/$<
clean: clean:
rm -rf *.o *.a *.so rm -f $(OBJECTS) *.a *.so
\ No newline at end of file
.PHONY: static shared install install-shared clean
...@@ -2,9 +2,9 @@ ACLOCAL_AMFLAGS = -I m4 ...@@ -2,9 +2,9 @@ ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libsass.la lib_LTLIBRARIES = libsass.la
libsass_la_SOURCES = context.cpp functions.cpp document.cpp \ libsass_la_SOURCES = context.cpp functions.cpp document.cpp \
document_parser.cpp eval_apply.cpp node.cpp \ constants.cpp document_parser.cpp eval_apply.cpp node.cpp \
node_factory.cpp node_emitters.cpp prelexer.cpp \ node_factory.cpp node_emitters.cpp prelexer.cpp \
sass_interface.cpp selector.cpp sass_interface.cpp
libsass_la_LDFLAGS = -no-undefined -version-info 0:0:0 libsass_la_LDFLAGS = -no-undefined -version-info 0:0:0
include_HEADERS = sass_interface.h include_HEADERS = sass_interface.h
...@@ -85,8 +85,8 @@ am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)" ...@@ -85,8 +85,8 @@ am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)"
LTLIBRARIES = $(lib_LTLIBRARIES) LTLIBRARIES = $(lib_LTLIBRARIES)
libsass_la_LIBADD = libsass_la_LIBADD =
am_libsass_la_OBJECTS = context.lo functions.lo document.lo \ am_libsass_la_OBJECTS = context.lo functions.lo document.lo \
document_parser.lo eval_apply.lo node.lo node_factory.lo \ constants.lo document_parser.lo eval_apply.lo node.lo node_factory.lo \
node_emitters.lo prelexer.lo sass_interface.lo node_emitters.lo prelexer.lo selector.lo sass_interface.lo
libsass_la_OBJECTS = $(am_libsass_la_OBJECTS) libsass_la_OBJECTS = $(am_libsass_la_OBJECTS)
libsass_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ libsass_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
...@@ -245,9 +245,9 @@ top_srcdir = @top_srcdir@ ...@@ -245,9 +245,9 @@ top_srcdir = @top_srcdir@
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4
lib_LTLIBRARIES = libsass.la lib_LTLIBRARIES = libsass.la
libsass_la_SOURCES = context.cpp functions.cpp document.cpp \ libsass_la_SOURCES = context.cpp functions.cpp document.cpp \
document_parser.cpp eval_apply.cpp node.cpp \ constants.cpp document_parser.cpp eval_apply.cpp node.cpp \
node_factory.cpp node_emitters.cpp prelexer.cpp \ node_factory.cpp node_emitters.cpp prelexer.cpp \
sass_interface.cpp selector.cpp sass_interface.cpp
libsass_la_LDFLAGS = -no-undefined -version-info 0:0:0 libsass_la_LDFLAGS = -no-undefined -version-info 0:0:0
include_HEADERS = sass_interface.h include_HEADERS = sass_interface.h
...@@ -347,6 +347,7 @@ distclean-compile: ...@@ -347,6 +347,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/context.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/context.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/document.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/document.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/constants.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/document_parser.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/document_parser.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eval_apply.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eval_apply.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/functions.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/functions.Plo@am__quote@
...@@ -354,6 +355,7 @@ distclean-compile: ...@@ -354,6 +355,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_emitters.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_emitters.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_factory.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node_factory.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prelexer.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prelexer.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/selector.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sass_interface.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sass_interface.Plo@am__quote@
.cpp.o: .cpp.o:
......
#define SASS_COLOR_NAMES_INCLUDED #define SASS_COLOR_NAMES
namespace Sass { namespace Sass {
......
#include "constants.hpp"
namespace Sass {
namespace Constants {
// hidden variable name for the image path (for the image-url built-in)
extern const char image_path_var[] = "$[image path]";
// sass keywords
extern const char import_kwd[] = "@import";
extern const char mixin_kwd[] = "@mixin";
extern const char function_kwd[] = "@function";
extern const char return_kwd[] = "@return";
extern const char include_kwd[] = "@include";
extern const char extend_kwd[] = "@extend";
extern const char if_kwd[] = "@if";
extern const char else_kwd[] = "@else";
extern const char if_after_else_kwd[] = "if";
extern const char for_kwd[] = "@for";
extern const char from_kwd[] = "from";
extern const char to_kwd[] = "to";
extern const char through_kwd[] = "through";
extern const char each_kwd[] = "@each";
extern const char in_kwd[] = "in";
extern const char while_kwd[] = "@while";
extern const char warn_kwd[] = "@warn";
extern const char default_kwd[] = "default";
// css standard units
extern const char em_kwd[] = "em";
extern const char ex_kwd[] = "ex";
extern const char px_kwd[] = "px";
extern const char cm_kwd[] = "cm";
extern const char mm_kwd[] = "mm";
extern const char pt_kwd[] = "pt";
extern const char pc_kwd[] = "pc";
extern const char deg_kwd[] = "deg";
extern const char rad_kwd[] = "rad";
extern const char grad_kwd[] = "grad";
extern const char ms_kwd[] = "ms";
extern const char s_kwd[] = "s";
extern const char Hz_kwd[] = "Hz";
extern const char kHz_kwd[] = "kHz";
// css functions and keywords
extern const char media_kwd[] = "@media";
extern const char only_kwd[] = "only";
extern const char rgb_kwd[] = "rgb(";
extern const char url_kwd[] = "url(";
extern const char image_url_kwd[] = "image-url(";
extern const char important_kwd[] = "important";
extern const char pseudo_not_kwd[] = ":not(";
extern const char even_kwd[] = "even";
extern const char odd_kwd[] = "odd";
// css attribute-matching operators
extern const char tilde_equal[] = "~=";
extern const char pipe_equal[] = "|=";
extern const char caret_equal[] = "^=";
extern const char dollar_equal[] = "$=";
extern const char star_equal[] = "*=";
// relational & logical operators and constants
extern const char and_kwd[] = "and";
extern const char or_kwd[] = "or";
extern const char not_kwd[] = "not";
extern const char gt[] = ">";
extern const char gte[] = ">=";
extern const char lt[] = "<";
extern const char lte[] = "<=";
extern const char eq[] = "==";
extern const char neq[] = "!=";
extern const char true_kwd[] = "true";
extern const char false_kwd[] = "false";
// miscellaneous punctuation and delimiters
extern const char percent_str[] = "%";
extern const char empty_str[] = "";
extern const char slash_slash[] = "//";
extern const char slash_star[] = "/*";
extern const char star_slash[] = "*/";
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
extern const char rparen[] = ")";
extern const char sign_chars[] = "-+";
// type names
extern const char numeric_name[] = "numeric value";
extern const char number_name[] = "number";
extern const char percentage_name[] = "percentage";
extern const char dimension_name[] = "numeric dimension";
extern const char string_name[] = "string";
extern const char bool_name[] = "bool";
extern const char color_name[] = "color";
extern const char list_name[] = "list";
}
}
\ No newline at end of file
#define SASS_CONSTANTS
namespace Sass {
namespace Constants {
// hidden variable name for the image path (for the image-url built-in)
extern const char image_path_var[];
// sass keywords
extern const char import_kwd[];
extern const char mixin_kwd[];
extern const char function_kwd[];
extern const char return_kwd[];
extern const char include_kwd[];
extern const char extend_kwd[];
extern const char if_kwd[];
extern const char else_kwd[];
extern const char if_after_else_kwd[];
extern const char for_kwd[];
extern const char from_kwd[];
extern const char to_kwd[];
extern const char through_kwd[];
extern const char each_kwd[];
extern const char in_kwd[];
extern const char while_kwd[];
extern const char warn_kwd[];
extern const char default_kwd[];
// css standard units
extern const char em_kwd[];
extern const char ex_kwd[];
extern const char px_kwd[];
extern const char cm_kwd[];
extern const char mm_kwd[];
extern const char pt_kwd[];
extern const char pc_kwd[];
extern const char deg_kwd[];
extern const char rad_kwd[];
extern const char grad_kwd[];
extern const char ms_kwd[];
extern const char s_kwd[];
extern const char Hz_kwd[];
extern const char kHz_kwd[];
// css functions and keywords
extern const char media_kwd[];
extern const char only_kwd[];
extern const char rgb_kwd[];
extern const char url_kwd[];
extern const char image_url_kwd[];
extern const char important_kwd[];
extern const char pseudo_not_kwd[];
extern const char even_kwd[];
extern const char odd_kwd[];
// css attribute-matching operators
extern const char tilde_equal[];
extern const char pipe_equal[];
extern const char caret_equal[];
extern const char dollar_equal[];
extern const char star_equal[];
// relational & logical operators and constants
extern const char and_kwd[];
extern const char or_kwd[];
extern const char not_kwd[];
extern const char gt[];
extern const char gte[];
extern const char lt[];
extern const char lte[];
extern const char eq[];
extern const char neq[];
extern const char true_kwd[];
extern const char false_kwd[];
// miscellaneous punctuation and delimiters
extern const char percent_str[];
extern const char empty_str[];
extern const char slash_slash[];
extern const char slash_star[];
extern const char star_slash[];
extern const char hash_lbrace[];
extern const char rbrace[];
extern const char rparen[];
extern const char sign_chars[];
// type names
extern const char numeric_name[];
extern const char number_name[];
extern const char percentage_name[];
extern const char dimension_name[];
extern const char string_name[];
extern const char bool_name[];
extern const char color_name[];
extern const char list_name[];
}
}
\ No newline at end of file
#include "context.hpp" #ifdef _WIN32
#include <direct.h>
#define getcwd _getcwd
#else
#include <unistd.h>
#endif
#include <cstring> #include <cstring>
#include <iostream> #include <iostream>
#include <unistd.h> #include <sstream>
#include "prelexer.hpp" #include "context.hpp"
#include "constants.hpp"
#include "color_names.hpp" #include "color_names.hpp"
using std::cerr; using std::endl; #include "prelexer.hpp"
namespace Sass { namespace Sass {
using std::pair; using namespace Constants;
using std::pair; using std::cerr; using std::endl;
void Context::collect_include_paths(const char* paths_str) void Context::collect_include_paths(const char* paths_str)
{ {
const size_t wd_len = 1024; const size_t wd_len = 1024;
...@@ -64,6 +73,9 @@ namespace Sass { ...@@ -64,6 +73,9 @@ namespace Sass {
path_string = "'" + path_string + "/'"; path_string = "'" + path_string + "/'";
image_path = new char[path_string.length() + 1]; image_path = new char[path_string.length() + 1];
std::strcpy(image_path, path_string.c_str()); std::strcpy(image_path, path_string.c_str());
// stash this hidden variable for the image-url built-in to use
global_env[Token::make(image_path_var)] = new_Node(Node::string_constant, "[IMAGE PATH]", 0, Token::make(image_path));
} }
Context::~Context() Context::~Context()
...@@ -73,21 +85,20 @@ namespace Sass { ...@@ -73,21 +85,20 @@ namespace Sass {
} }
delete[] image_path; delete[] image_path;
new_Node.free(); new_Node.free();
// cerr << "Deallocated " << i << " source string(s)." << endl;
} }
inline void Context::register_function(Function_Descriptor d, Primitive ip) inline void Context::register_function(Signature sig, Primitive ip)
{ {
Function f(d, ip, new_Node); Function f(const_cast<char*>(sig), ip, *this);
// function_env[pair<string, size_t>(f.name, f.parameters.size())] = f;
function_env[f.name] = f; function_env[f.name] = f;
} }
inline void Context::register_function(Function_Descriptor d, Primitive ip, size_t arity) inline void Context::register_function(Signature sig, Primitive ip, size_t arity)
{ {
Function f(d, ip, new_Node); Function f(const_cast<char*>(sig), ip, *this);
// function_env[pair<string, size_t>(f.name, arity)] = f; std::stringstream stub;
function_env[f.name] = f; stub << f.name << " " << arity;
function_env[stub.str()] = f;
} }
inline void Context::register_overload_stub(string name) inline void Context::register_overload_stub(string name)
...@@ -98,67 +109,80 @@ namespace Sass { ...@@ -98,67 +109,80 @@ namespace Sass {
void Context::register_functions() void Context::register_functions()
{ {
using namespace Functions; using namespace Functions;
// RGB Functions // RGB Functions
register_function(rgb_descriptor, rgb); register_function(rgb_sig, rgb);
register_overload_stub("rgba"); register_overload_stub("rgba");
register_function(rgba_4_descriptor, rgba_4); register_function(rgba_4_sig, rgba_4, 4);
register_function(rgba_2_descriptor, rgba_2); register_function(rgba_2_sig, rgba_2, 2);
register_function(red_descriptor, red); register_function(red_sig, red);
register_function(green_descriptor, green); register_function(green_sig, green);
register_function(blue_descriptor, blue); register_function(blue_sig, blue);
register_overload_stub("mix"); register_function(mix_sig, mix);
register_function(mix_2_descriptor, mix_2);
register_function(mix_3_descriptor, mix_3);
// HSL Functions // HSL Functions
register_function(hsla_descriptor, hsla); register_function(hsl_sig, hsl);
register_function(hsl_descriptor, hsl); register_function(hsla_sig, hsla);
register_function(invert_descriptor, invert); register_function(hue_sig, hue);
register_function(saturation_sig, saturation);
register_function(lightness_sig, lightness);
register_function(adjust_hue_sig, adjust_hue);
register_function(lighten_sig, lighten);
register_function(darken_sig, darken);
register_function(saturate_sig, saturate);
register_function(desaturate_sig, desaturate);
register_function(grayscale_sig, grayscale);
register_function(complement_sig, complement);
register_function(invert_sig, invert);
// Opacity Functions // Opacity Functions
register_function(alpha_descriptor, alpha); register_function(alpha_sig, alpha);
register_function(opacity_descriptor, alpha); register_function(opacity_sig, opacity);
register_function(opacify_descriptor, opacify); register_function(opacify_sig, opacify);
register_function(fade_in_descriptor, opacify); register_function(fade_in_sig, fade_in);
register_function(transparentize_descriptor, transparentize); register_function(transparentize_sig, transparentize);
register_function(fade_out_descriptor, transparentize); register_function(fade_out_sig, fade_out);
// Other Color Functions
register_function(adjust_color_sig, adjust_color);
register_function(scale_color_sig, scale_color);
register_function(change_color_sig, change_color);
// String Functions // String Functions
register_function(unquote_descriptor, unquote); register_function(unquote_sig, unquote);
register_function(quote_descriptor, quote); register_function(quote_sig, quote);
// Number Functions // Number Functions
register_function(percentage_descriptor, percentage); register_function(percentage_sig, percentage);
register_function(round_descriptor, round); register_function(round_sig, round);
register_function(ceil_descriptor, ceil); register_function(ceil_sig, ceil);
register_function(floor_descriptor, floor); register_function(floor_sig, floor);
register_function(abs_descriptor, abs); register_function(abs_sig, abs);
// List Functions // List Functions
register_function(length_descriptor, length); register_function(length_sig, length);
register_function(nth_descriptor, nth); register_function(nth_sig, nth);
register_overload_stub("join"); register_function(index_sig, index);
register_function(join_2_descriptor, join_2); register_function(join_sig, join);
register_function(join_3_descriptor, join_3); register_function(append_sig, append);
register_overload_stub("append");
register_function(append_2_descriptor, append_2);
register_function(append_3_descriptor, append_3);
register_overload_stub("compact"); register_overload_stub("compact");
register_function(compact_1_descriptor, compact); register_function(compact_1_sig, compact_1, 1);
register_function(compact_2_descriptor, compact); register_function(compact_n_sig, compact_n, 0);
register_function(compact_3_descriptor, compact); register_function(compact_n_sig, compact_n, 2);
register_function(compact_4_descriptor, compact); register_function(compact_n_sig, compact_n, 3);
register_function(compact_5_descriptor, compact); register_function(compact_n_sig, compact_n, 4);
register_function(compact_6_descriptor, compact); register_function(compact_n_sig, compact_n, 5);
register_function(compact_7_descriptor, compact); register_function(compact_n_sig, compact_n, 6);
register_function(compact_8_descriptor, compact); register_function(compact_n_sig, compact_n, 7);
register_function(compact_9_descriptor, compact); register_function(compact_n_sig, compact_n, 8);
register_function(compact_10_descriptor, compact); register_function(compact_n_sig, compact_n, 9);
register_function(compact_11_descriptor, compact); register_function(compact_n_sig, compact_n, 10);
register_function(compact_12_descriptor, compact); register_function(compact_n_sig, compact_n, 11);
register_function(compact_n_sig, compact_n, 12);
// Introspection Functions // Introspection Functions
register_function(type_of_descriptor, type_of); register_function(type_of_sig, type_of);
register_function(unit_descriptor, unit); register_function(unit_sig, unit);
register_function(unitless_descriptor, unitless); register_function(unitless_sig, unitless);
register_function(comparable_descriptor, comparable); register_function(comparable_sig, comparable);
// Boolean Functions // Boolean Functions
register_function(not_descriptor, not_impl); register_function(not_sig, not_impl);
register_function(if_descriptor, if_impl); register_function(if_sig, if_impl);
// Path Functions
register_function(image_url_sig, image_url);
} }
void Context::setup_color_map() void Context::setup_color_map()
......
#define SASS_CONTEXT_INCLUDED #define SASS_CONTEXT
//#ifndef SASS_ENVIRONMENT
#include "environment.hpp"
//#endif
#include <utility> #include <utility>
#include <map>
#ifndef SASS_NODE_FACTORY
#include "node_factory.hpp" #include "node_factory.hpp"
#endif
#ifndef SASS_FUNCTIONS
#include "functions.hpp" #include "functions.hpp"
#endif
namespace Sass { namespace Sass {
using std::pair; using std::pair;
using std::map; using std::map;
struct Environment {
map<Token, Node> current_frame;
Environment* parent;
Environment* global;
Environment()
: current_frame(map<Token, Node>()), parent(0), global(0)
{ }
void link(Environment& env)
{
parent = &env;
global = parent->global ? parent->global : parent;
}
bool query(const Token& key) const
{
if (current_frame.count(key)) return true;
else if (parent) return parent->query(key);
else return false;
}
Node& operator[](const Token& key)
{
if (current_frame.count(key)) return current_frame[key];
else if (parent) return (*parent)[key];
else return current_frame[key];
}
};
struct Context { struct Context {
Environment global_env; Environment global_env;
map<string, Function> function_env; map<string, Function> function_env;
...@@ -59,9 +37,9 @@ namespace Sass { ...@@ -59,9 +37,9 @@ namespace Sass {
void collect_include_paths(const char* paths_str); void collect_include_paths(const char* paths_str);
Context(const char* paths_str = 0, const char* img_path_str = 0); Context(const char* paths_str = 0, const char* img_path_str = 0);
~Context(); ~Context();
void register_function(Function_Descriptor d, Primitive ip); void register_function(Signature sig, Primitive ip);
void register_function(Function_Descriptor d, Primitive ip, size_t arity); void register_function(Signature sig, Primitive ip, size_t arity);
void register_overload_stub(string name); void register_overload_stub(string name);
void register_functions(); void register_functions();
void setup_color_map(); void setup_color_map();
......
#ifdef _WIN32
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#endif
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include "document.hpp" #include "document.hpp"
......
#define SASS_DOCUMENT
#include <map> #include <map>
#ifndef SASS_PRELEXER_INCLUDED #ifndef SASS_PRELEXER
#include "prelexer.hpp" #include "prelexer.hpp"
#endif #endif
#ifndef SASS_NODE_INCLUDED #ifndef SASS_NODE
#include "node.hpp" #include "node.hpp"
#endif #endif
#ifndef SASS_CONTEXT_INCLUDED #ifndef SASS_CONTEXT
#include "context.hpp" #include "context.hpp"
#endif #endif
...@@ -130,10 +132,10 @@ namespace Sass { ...@@ -130,10 +132,10 @@ namespace Sass {
Node parse_mixin_definition(); Node parse_mixin_definition();
Node parse_function_definition(); Node parse_function_definition();
Node parse_parameters(); Node parse_parameters();
Node parse_parameter(); Node parse_parameter(Node::Type);
Node parse_mixin_call(); Node parse_mixin_call();
Node parse_arguments(); Node parse_arguments();
Node parse_argument(); Node parse_argument(Node::Type);
Node parse_assignment(); Node parse_assignment();
Node parse_propset(); Node parse_propset();
Node parse_ruleset(Selector_Lookahead lookahead, Node::Type inside_of = Node::none); Node parse_ruleset(Selector_Lookahead lookahead, Node::Type inside_of = Node::none);
...@@ -162,6 +164,7 @@ namespace Sass { ...@@ -162,6 +164,7 @@ namespace Sass {
Node parse_string(); Node parse_string();
Node parse_value_schema(); Node parse_value_schema();
Node parse_identifier_schema(); Node parse_identifier_schema();
Node parse_url_schema();
Node parse_if_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none); Node parse_if_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_for_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none); Node parse_for_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_each_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none); Node parse_each_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
...@@ -172,7 +175,8 @@ namespace Sass { ...@@ -172,7 +175,8 @@ namespace Sass {
Node parse_warning(); Node parse_warning();
Selector_Lookahead lookahead_for_selector(const char* start = 0); Selector_Lookahead lookahead_for_selector(const char* start = 0);
Selector_Lookahead lookahead_for_extension_target(const char* start = 0);
void throw_syntax_error(string message, size_t ln = 0); void throw_syntax_error(string message, size_t ln = 0);
void throw_read_error(string message, size_t ln = 0); void throw_read_error(string message, size_t ln = 0);
......
#include <cstdlib>
#include <iostream>
#include "document.hpp" #include "document.hpp"
#include "constants.hpp"
#include "error.hpp" #include "error.hpp"
#include <iostream>
#ifndef SASS_PRELEXER
#include "prelexer.hpp"
#endif
namespace Sass { namespace Sass {
using namespace std; using namespace std;
using namespace Constants;
void Document::parse_scss() void Document::parse_scss()
{ {
...@@ -147,12 +154,17 @@ namespace Sass { ...@@ -147,12 +154,17 @@ namespace Sass {
{ {
Node params(context.new_Node(Node::parameters, path, line, 0)); Node params(context.new_Node(Node::parameters, path, line, 0));
Token name(lexed); Token name(lexed);
Node::Type param_type = Node::none;
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
if (peek< variable >()) { if (peek< variable >()) {
params << parse_parameter(); Node param(parse_parameter(param_type));
if (param.type() == Node::assignment) param_type = Node::assignment;
params << param;
while (lex< exactly<','> >()) { while (lex< exactly<','> >()) {
if (!peek< variable >()) throw_syntax_error("expected a variable name (e.g. $x) for the parameter list for " + name.to_string()); if (!peek< variable >()) throw_syntax_error("expected a variable name (e.g. $x) for the parameter list for " + name.to_string());
params << parse_parameter(); Node param(parse_parameter(param_type));
if (param.type() == Node::assignment) param_type = Node::assignment;
params << param;
} }
if (!lex< exactly<')'> >()) throw_syntax_error("parameter list for " + name.to_string() + " requires a ')'"); if (!lex< exactly<')'> >()) throw_syntax_error("parameter list for " + name.to_string() + " requires a ')'");
} }
...@@ -161,20 +173,27 @@ namespace Sass { ...@@ -161,20 +173,27 @@ namespace Sass {
return params; return params;
} }
Node Document::parse_parameter() { Node Document::parse_parameter(Node::Type param_type) {
lex< variable >(); lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed)); Node var(context.new_Node(Node::variable, path, line, lexed));
if (lex< exactly<':'> >()) { // default value if (param_type == Node::assignment) {
if (lex< exactly<':'> >()) { // default value
Node val(parse_space_list());
Node par_and_val(context.new_Node(Node::assignment, path, line, 2));
par_and_val << var << val;
return par_and_val;
}
else {
throw_syntax_error("required parameter " + var.token().to_string() + " must precede all optional parameters");
}
}
else if (lex< exactly<':'> >()) { // default value
Node val(parse_space_list()); Node val(parse_space_list());
Node par_and_val(context.new_Node(Node::assignment, path, line, 2)); Node par_and_val(context.new_Node(Node::assignment, path, line, 2));
par_and_val << var << val; par_and_val << var << val;
return par_and_val; return par_and_val;
} }
else { return var;
return var;
}
// unreachable statement
return Node();
} }
Node Document::parse_mixin_call() Node Document::parse_mixin_call()
...@@ -183,7 +202,7 @@ namespace Sass { ...@@ -183,7 +202,7 @@ namespace Sass {
if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive"); if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive");
Node name(context.new_Node(Node::identifier, path, line, lexed)); Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments()); Node args(parse_arguments());
Node the_call(context.new_Node(Node::expansion, path, line, 2)); Node the_call(context.new_Node(Node::mixin_call, path, line, 2));
the_call << name << args; the_call << name << args;
return the_call; return the_call;
} }
...@@ -192,15 +211,16 @@ namespace Sass { ...@@ -192,15 +211,16 @@ namespace Sass {
{ {
Token name(lexed); Token name(lexed);
Node args(context.new_Node(Node::arguments, path, line, 0)); Node args(context.new_Node(Node::arguments, path, line, 0));
Node::Type arg_type = Node::none;
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) { if (!peek< exactly<')'> >(position)) {
Node arg(parse_argument()); Node arg(parse_argument(Node::none));
arg.should_eval() = true;
args << arg; args << arg;
if (arg.type() == Node::assignment) arg_type = Node::assignment;
while (lex< exactly<','> >()) { while (lex< exactly<','> >()) {
Node arg(parse_argument()); Node arg(parse_argument(arg_type));
arg.should_eval() = true;
args << arg; args << arg;
if (arg.type() == Node::assignment) arg_type = Node::assignment;
} }
} }
if (!lex< exactly<')'> >()) throw_syntax_error("improperly terminated argument list for " + name.to_string()); if (!lex< exactly<')'> >()) throw_syntax_error("improperly terminated argument list for " + name.to_string());
...@@ -208,20 +228,39 @@ namespace Sass { ...@@ -208,20 +228,39 @@ namespace Sass {
return args; return args;
} }
Node Document::parse_argument() Node Document::parse_argument(Node::Type arg_type)
{ {
if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) { // if arg_type is assignment, only accept keyword args from here onwards
if (arg_type == Node::assignment) {
if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >();
Node val(parse_space_list());
val.should_eval() = true;
Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val;
return assn;
}
else {
throw_syntax_error("ordinal arguments must precede keyword arguments");
}
}
// otherwise accept either, and let the caller set the arg_type flag
if (arg_type == Node::none &&
peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
lex< variable >(); lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed)); Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >(); lex< exactly<':'> >();
Node val(parse_space_list()); Node val(parse_space_list());
val.should_eval() = true;
Node assn(context.new_Node(Node::assignment, path, line, 2)); Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val; assn << var << val;
return assn; return assn;
} }
else { Node val(parse_space_list());
return parse_space_list(); val.should_eval() = true;
} return val;
} }
Node Document::parse_assignment() Node Document::parse_assignment()
...@@ -273,8 +312,6 @@ namespace Sass { ...@@ -273,8 +312,6 @@ namespace Sass {
return ruleset; return ruleset;
} }
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
Node Document::parse_selector_schema(const char* end_of_selector) Node Document::parse_selector_schema(const char* end_of_selector)
{ {
const char* i = position; const char* i = position;
...@@ -318,12 +355,15 @@ namespace Sass { ...@@ -318,12 +355,15 @@ namespace Sass {
Node seq1(parse_simple_selector_sequence()); Node seq1(parse_simple_selector_sequence());
if (peek< exactly<','> >() || if (peek< exactly<','> >() ||
peek< exactly<')'> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >()) return seq1; peek< exactly<'{'> >() ||
peek< exactly<';'> >()) return seq1;
Node selector(context.new_Node(Node::selector, path, line, 2)); Node selector(context.new_Node(Node::selector, path, line, 2));
selector << seq1; selector << seq1;
while (!peek< exactly<'{'> >() && !peek< exactly<','> >()) { while (!peek< exactly<'{'> >() &&
!peek< exactly<','> >() &&
!peek< exactly<';'> >()) {
selector << parse_simple_selector_sequence(); selector << parse_simple_selector_sequence();
} }
return selector; return selector;
...@@ -431,6 +471,9 @@ namespace Sass { ...@@ -431,6 +471,9 @@ namespace Sass {
else if (lex< sequence< optional<sign>, digits > >()) { else if (lex< sequence< optional<sign>, digits > >()) {
pseudo << context.new_Node(Node::value, path, line, lexed); pseudo << context.new_Node(Node::value, path, line, lexed);
} }
else if (lex< identifier >()) {
pseudo << context.new_Node(Node::identifier, path, line, lexed);
}
else if (lex< string_constant >()) { else if (lex< string_constant >()) {
pseudo << context.new_Node(Node::string_constant, path, line, lexed); pseudo << context.new_Node(Node::string_constant, path, line, lexed);
} }
...@@ -548,11 +591,16 @@ namespace Sass { ...@@ -548,11 +591,16 @@ namespace Sass {
semicolon = true; semicolon = true;
} }
else if (lex< extend >()) { else if (lex< extend >()) {
if (surrounding_ruleset.is_null_ptr()) throw_syntax_error("@extend directive may only be used within rules"); Node request(context.new_Node(Node::extend_directive, path, line, 1));
Node extendee(parse_simple_selector_sequence()); Selector_Lookahead lookahead = lookahead_for_extension_target(position);
context.extensions.insert(pair<Node, Node>(extendee, surrounding_ruleset));
context.has_extensions = true; if (!lookahead.found) throw_syntax_error("invalid selector for @extend");
if (lookahead.has_interpolants) request << parse_selector_schema(lookahead.found);
else request << parse_selector_group();
semicolon = true; semicolon = true;
block << request;
} }
else if (peek< media >()) { else if (peek< media >()) {
block << parse_media_query(inside_of); block << parse_media_query(inside_of);
...@@ -614,12 +662,13 @@ namespace Sass { ...@@ -614,12 +662,13 @@ namespace Sass {
peek< exactly<'}'> >(position) || peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) || peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position)) peek< exactly<')'> >(position))
{ return context.new_Node(Node::nil, path, line, 0); } { return context.new_Node(Node::list, path, line, 0); }
Node list1(parse_space_list()); Node list1(parse_space_list());
// if it's a singleton, return it directly; don't wrap it // if it's a singleton, return it directly; don't wrap it
if (!peek< exactly<','> >(position)) return list1; if (!peek< exactly<','> >(position)) return list1;
Node comma_list(context.new_Node(Node::comma_list, path, line, 2)); Node comma_list(context.new_Node(Node::list, path, line, 2));
comma_list.is_comma_separated() = true;
comma_list << list1; comma_list << list1;
comma_list.should_eval() |= list1.should_eval(); comma_list.should_eval() |= list1.should_eval();
...@@ -645,7 +694,7 @@ namespace Sass { ...@@ -645,7 +694,7 @@ namespace Sass {
peek< default_flag >(position)) peek< default_flag >(position))
{ return disj1; } { return disj1; }
Node space_list(context.new_Node(Node::space_list, path, line, 2)); Node space_list(context.new_Node(Node::list, path, line, 2));
space_list << disj1; space_list << disj1;
space_list.should_eval() |= disj1.should_eval(); space_list.should_eval() |= disj1.should_eval();
...@@ -668,11 +717,11 @@ namespace Sass { ...@@ -668,11 +717,11 @@ namespace Sass {
{ {
Node conj1(parse_conjunction()); Node conj1(parse_conjunction());
// if it's a singleton, return it directly; don't wrap it // if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< or_kwd, negate< identifier > > >()) return conj1; if (!peek< sequence< or_op, negate< identifier > > >()) return conj1;
Node disjunction(context.new_Node(Node::disjunction, path, line, 2)); Node disjunction(context.new_Node(Node::disjunction, path, line, 2));
disjunction << conj1; disjunction << conj1;
while (lex< sequence< or_kwd, negate< identifier > > >()) disjunction << parse_conjunction(); while (lex< sequence< or_op, negate< identifier > > >()) disjunction << parse_conjunction();
disjunction.should_eval() = true; disjunction.should_eval() = true;
return disjunction; return disjunction;
...@@ -682,11 +731,11 @@ namespace Sass { ...@@ -682,11 +731,11 @@ namespace Sass {
{ {
Node rel1(parse_relation()); Node rel1(parse_relation());
// if it's a singleton, return it directly; don't wrap it // if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< and_kwd, negate< identifier > > >()) return rel1; if (!peek< sequence< and_op, negate< identifier > > >()) return rel1;
Node conjunction(context.new_Node(Node::conjunction, path, line, 2)); Node conjunction(context.new_Node(Node::conjunction, path, line, 2));
conjunction << rel1; conjunction << rel1;
while (lex< sequence< and_kwd, negate< identifier > > >()) conjunction << parse_relation(); while (lex< sequence< and_op, negate< identifier > > >()) conjunction << parse_relation();
conjunction.should_eval() = true; conjunction.should_eval() = true;
return conjunction; return conjunction;
} }
...@@ -783,7 +832,7 @@ namespace Sass { ...@@ -783,7 +832,7 @@ namespace Sass {
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
Node value(parse_comma_list()); Node value(parse_comma_list());
value.should_eval() = true; value.should_eval() = true;
if (value.type() == Node::comma_list || value.type() == Node::space_list) { if (value.type() == Node::list && value.size() > 0) {
value[0].should_eval() = true; value[0].should_eval() = true;
} }
if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis"); if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis");
...@@ -810,39 +859,46 @@ namespace Sass { ...@@ -810,39 +859,46 @@ namespace Sass {
{ {
if (lex< uri_prefix >()) if (lex< uri_prefix >())
{ {
const char* value = position;
const char* rparen = find_first< exactly<')'> >(position);
if (!rparen) throw_syntax_error("URI is missing ')'");
Token content_tok(Token::make(value, rparen));
Node content_node(context.new_Node(Node::string_constant, path, line, content_tok));
// lex< string_constant >();
Node result(context.new_Node(Node::uri, path, line, 1)); Node result(context.new_Node(Node::uri, path, line, 1));
result << content_node; if (lex< variable >()) {
position = rparen; result << context.new_Node(Node::variable, path, line, lexed);
lex< exactly<')'> >(); result.should_eval() = true;
}
else if (lex< string_constant >()) {
result << parse_string();
result.should_eval() = true;
}
else if (lex< url_schema >()) {
result << Document::make_from_token(context, lexed, path, line).parse_url_schema();
result.should_eval() = true;
}
else if (lex< url_value >()) {
result << context.new_Node(Node::identifier, path, line, lexed);
}
else {
const char* value = position;
const char* rparen = find_first< exactly<')'> >(position);
if (!rparen) throw_syntax_error("URI is missing ')'");
Token content_tok(Token::make(value, rparen));
Node content_node(context.new_Node(Node::identifier, path, line, content_tok));
// lex< string_constant >();
result << content_node;
position = rparen;
}
if (!lex< exactly<')'> >()) throw_syntax_error("URI is missing ')'");
return result; return result;
} }
if (lex< image_url_prefix >())
{
Node url(parse_value());
if (!lex< exactly<')'> >()) throw_syntax_error("call to image-url is missing ')'");
Node the_call(context.new_Node(Node::image_url, path, line, 1));
the_call << url;
the_call.should_eval() = true;
return the_call;
}
if (peek< functional >()) if (peek< functional >())
{ return parse_function_call(); } { return parse_function_call(); }
if (lex< value_schema >()) if (lex< value_schema >())
{ return Document::make_from_token(context, lexed, path, line).parse_value_schema(); } { return Document::make_from_token(context, lexed, path, line).parse_value_schema(); }
if (lex< sequence< true_kwd, negate< identifier > > >()) if (lex< sequence< true_val, negate< identifier > > >())
{ return context.new_Node(Node::boolean, path, line, true); } { return context.new_Node(Node::boolean, path, line, true); }
if (lex< sequence< false_kwd, negate< identifier > > >()) if (lex< sequence< false_val, negate< identifier > > >())
{ return context.new_Node(Node::boolean, path, line, false); } { return context.new_Node(Node::boolean, path, line, false); }
if (lex< important >()) if (lex< important >())
...@@ -863,6 +919,34 @@ namespace Sass { ...@@ -863,6 +919,34 @@ namespace Sass {
if (lex< hex >()) if (lex< hex >())
{ return context.new_Node(Node::textual_hex, path, line, lexed); } { return context.new_Node(Node::textual_hex, path, line, lexed); }
// if (lex< percentage >())
// { return context.new_Node(path, line, atof(lexed.begin), Node::numeric_percentage); }
// if (lex< dimension >()) {
// return context.new_Node(path, line, atof(lexed.begin),
// Token::make(Prelexer::number(lexed.begin), lexed.end));
// }
// if (lex< number >())
// { return context.new_Node(path, line, atof(lexed.begin)); }
// if (lex< hex >()) {
// Node triple(context.new_Node(Node::numeric_color, path, line, 4));
// Token hext(Token::make(lexed.begin+1, lexed.end));
// if (hext.length() == 6) {
// for (int i = 0; i < 6; i += 2) {
// triple << context.new_Node(path, line, static_cast<double>(strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
// }
// }
// else {
// for (int i = 0; i < 3; ++i) {
// triple << context.new_Node(path, line, static_cast<double>(strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
// }
// }
// triple << context.new_Node(path, line, 1.0);
// return triple;
// }
if (peek< string_constant >()) if (peek< string_constant >())
{ return parse_string(); } { return parse_string(); }
...@@ -887,7 +971,9 @@ namespace Sass { ...@@ -887,7 +971,9 @@ namespace Sass {
// see if there any interpolants // see if there any interpolants
const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(str.begin, str.end); const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(str.begin, str.end);
if (!p) { if (!p) {
return context.new_Node(Node::string_constant, path, line, str); Node result(context.new_Node(Node::string_constant, path, line, str));
result.is_quoted() = true;
return result;
} }
Node schema(context.new_Node(Node::string_schema, path, line, 1)); Node schema(context.new_Node(Node::string_schema, path, line, 1));
...@@ -915,6 +1001,7 @@ namespace Sass { ...@@ -915,6 +1001,7 @@ namespace Sass {
break; break;
} }
} }
schema.is_quoted() = true;
schema.should_eval() = true; schema.should_eval() = true;
return schema; return schema;
} }
...@@ -927,6 +1014,7 @@ namespace Sass { ...@@ -927,6 +1014,7 @@ namespace Sass {
if (lex< interpolant >()) { if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1)); Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Node interp_node(Document::make_from_token(context, insides, path, line).parse_list()); Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
interp_node.should_eval() = true;
schema << interp_node; schema << interp_node;
} }
else if (lex< identifier >()) { else if (lex< identifier >()) {
...@@ -934,18 +1022,38 @@ namespace Sass { ...@@ -934,18 +1022,38 @@ namespace Sass {
} }
else if (lex< percentage >()) { else if (lex< percentage >()) {
schema << context.new_Node(Node::textual_percentage, path, line, lexed); schema << context.new_Node(Node::textual_percentage, path, line, lexed);
// schema << context.new_Node(path, line, atof(lexed.begin), Node::numeric_percentage);
} }
else if (lex< dimension >()) { else if (lex< dimension >()) {
schema << context.new_Node(Node::textual_dimension, path, line, lexed); schema << context.new_Node(Node::textual_dimension, path, line, lexed);
// schema << context.new_Node(path, line, atof(lexed.begin),
// Token::make(Prelexer::number(lexed.begin), lexed.end));
} }
else if (lex< number >()) { else if (lex< number >()) {
schema << context.new_Node(Node::textual_number, path, line, lexed); schema << context.new_Node(Node::textual_number, path, line, lexed);
// schema << context.new_Node(path, line, atof(lexed.begin));
} }
else if (lex< hex >()) { else if (lex< hex >()) {
schema << context.new_Node(Node::textual_hex, path, line, lexed); schema << context.new_Node(Node::textual_hex, path, line, lexed);
// Node triple(context.new_Node(Node::numeric_color, path, line, 4));
// Token hext(Token::make(lexed.begin+1, lexed.end));
// if (hext.length() == 6) {
// for (int i = 0; i < 6; i += 2) {
// triple << context.new_Node(path, line, static_cast<double>(strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
// }
// }
// else {
// for (int i = 0; i < 3; ++i) {
// triple << context.new_Node(path, line, static_cast<double>(strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
// }
// }
// triple << context.new_Node(path, line, 1.0);
// schema << triple;
} }
else if (lex< string_constant >()) { else if (lex< string_constant >()) {
schema << context.new_Node(Node::string_constant, path, line, lexed); Node str(context.new_Node(Node::string_constant, path, line, lexed));
str.is_quoted() = true;
schema << str;
} }
else if (lex< variable >()) { else if (lex< variable >()) {
schema << context.new_Node(Node::variable, path, line, lexed); schema << context.new_Node(Node::variable, path, line, lexed);
...@@ -958,6 +1066,36 @@ namespace Sass { ...@@ -958,6 +1066,36 @@ namespace Sass {
return schema; return schema;
} }
Node Document::parse_url_schema()
{
Node schema(context.new_Node(Node::value_schema, path, line, 1));
while (position < end) {
if (position[0] == '/') {
lexed = Token::make(position, position+1);
schema << context.new_Node(Node::identifier, path, line, lexed);
++position;
}
else if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
interp_node.should_eval() = true;
schema << interp_node;
}
else if (lex< sequence< identifier, exactly<':'> > >()) {
schema << context.new_Node(Node::identifier, path, line, lexed);
}
else if (lex< filename >()) {
schema << context.new_Node(Node::identifier, path, line, lexed);
}
else {
throw_syntax_error("error parsing interpolated url");
}
}
schema.should_eval() = true;
return schema;
}
Node Document::parse_identifier_schema() Node Document::parse_identifier_schema()
{ {
lex< sequence< optional< exactly<'*'> >, identifier_schema > >(); lex< sequence< optional< exactly<'*'> >, identifier_schema > >();
...@@ -1010,7 +1148,7 @@ namespace Sass { ...@@ -1010,7 +1148,7 @@ namespace Sass {
} }
Node args(parse_arguments()); Node args(parse_arguments());
Node call(context.new_Node(Node::function_call, path, line, 2)); Node call(context.new_Node(Node::function_call, name.path(), name.line(), 2));
call << name << args; call << name << args;
call.should_eval() = true; call.should_eval() = true;
return call; return call;
...@@ -1117,13 +1255,11 @@ namespace Sass { ...@@ -1117,13 +1255,11 @@ namespace Sass {
return media_query; return media_query;
} }
// extern const char not_kwd[] = "not";
extern const char only_kwd[] = "only";
Node Document::parse_media_expression() Node Document::parse_media_expression()
{ {
Node media_expr(context.new_Node(Node::media_expression, path, line, 1)); Node media_expr(context.new_Node(Node::media_expression, path, line, 1));
// if the query begins with 'not' or 'only', then a media type is required // if the query begins with 'not' or 'only', then a media type is required
if (lex< not_kwd >() || lex< exactly<only_kwd> >()) { if (lex< not_op >() || lex< exactly<only_kwd> >()) {
media_expr << context.new_Node(Node::identifier, path, line, lexed); media_expr << context.new_Node(Node::identifier, path, line, lexed);
if (!lex< identifier >()) throw_syntax_error("media type expected in media query"); if (!lex< identifier >()) throw_syntax_error("media type expected in media query");
media_expr << context.new_Node(Node::identifier, path, line, lexed); media_expr << context.new_Node(Node::identifier, path, line, lexed);
...@@ -1140,7 +1276,7 @@ namespace Sass { ...@@ -1140,7 +1276,7 @@ namespace Sass {
} }
// parse the rest of the properties for this disjunct // parse the rest of the properties for this disjunct
while (!peek< exactly<','> >() && !peek< exactly<'{'> >()) { while (!peek< exactly<','> >() && !peek< exactly<'{'> >()) {
if (!lex< and_kwd >()) throw_syntax_error("invalid media query"); if (!lex< and_op >()) throw_syntax_error("invalid media query");
media_expr << context.new_Node(Node::identifier, path, line, lexed); media_expr << context.new_Node(Node::identifier, path, line, lexed);
if (!lex< exactly<'('> >()) throw_syntax_error("invalid media query"); if (!lex< exactly<'('> >()) throw_syntax_error("invalid media query");
media_expr << parse_rule(); media_expr << parse_rule();
...@@ -1207,5 +1343,56 @@ namespace Sass { ...@@ -1207,5 +1343,56 @@ namespace Sass {
return result; return result;
} }
Selector_Lookahead Document::lookahead_for_extension_target(const char* start)
{
const char* p = start ? start : position;
const char* q;
bool saw_interpolant = false;
while ((q = peek< identifier >(p)) ||
(q = peek< id_name >(p)) ||
(q = peek< class_name >(p)) ||
(q = peek< sequence< pseudo_prefix, identifier > >(p)) ||
(q = peek< string_constant >(p)) ||
(q = peek< exactly<'*'> >(p)) ||
(q = peek< exactly<'('> >(p)) ||
(q = peek< exactly<')'> >(p)) ||
(q = peek< exactly<'['> >(p)) ||
(q = peek< exactly<']'> >(p)) ||
(q = peek< exactly<'+'> >(p)) ||
(q = peek< exactly<'~'> >(p)) ||
(q = peek< exactly<'>'> >(p)) ||
(q = peek< exactly<','> >(p)) ||
(q = peek< binomial >(p)) ||
(q = peek< sequence< optional<sign>,
optional<digits>,
exactly<'n'> > >(p)) ||
(q = peek< sequence< optional<sign>,
digits > >(p)) ||
(q = peek< number >(p)) ||
(q = peek< exactly<'&'> >(p)) ||
(q = peek< alternatives<exact_match,
class_match,
dash_match,
prefix_match,
suffix_match,
substring_match> >(p)) ||
(q = peek< sequence< exactly<'.'>, interpolant > >(p)) ||
(q = peek< sequence< exactly<'#'>, interpolant > >(p)) ||
(q = peek< sequence< exactly<'-'>, interpolant > >(p)) ||
(q = peek< sequence< pseudo_prefix, interpolant > >(p)) ||
(q = peek< interpolant >(p))) {
p = q;
if (*(p - 1) == '}') saw_interpolant = true;
}
Selector_Lookahead result;
result.found = peek< alternatives< exactly<';'>, exactly<'}'> > >(p) ? p : 0;
result.has_interpolants = saw_interpolant;
return result;
}
} }
#define SASS_ENVIRONMENT
#include <map>
#ifndef SASS_NODE
#include "node.hpp"
#endif
namespace Sass {
using std::map;
struct Environment {
map<Token, Node> current_frame;
Environment* parent;
Environment* global;
Environment()
: current_frame(map<Token, Node>()), parent(0), global(0)
{ }
void link(Environment& env)
{
parent = &env;
global = parent->global ? parent->global : parent;
}
bool query(const Token& key) const
{
if (current_frame.count(key)) return true;
else if (parent) return parent->query(key);
else return false;
}
Node& operator[](const Token& key)
{
if (current_frame.count(key)) return current_frame[key];
else if (parent) return (*parent)[key];
else return current_frame[key];
}
void print()
{
for (map<Token, Node>::iterator i = current_frame.begin(); i != current_frame.end(); ++i) {
cerr << i->first.to_string() << ": " << i->second.to_string() << endl;
}
if (parent) {
cerr << "---" << endl;
parent->print();
}
}
};
}
\ No newline at end of file
#define SASS_ERROR
namespace Sass { namespace Sass {
struct Error { struct Error {
......
#include "prelexer.hpp"
#include "eval_apply.hpp" #include "eval_apply.hpp"
#include "selector.hpp"
#include "constants.hpp"
#include "prelexer.hpp"
#include "document.hpp" #include "document.hpp"
#include "error.hpp" #include "error.hpp"
#include <cctype> #include <cctype>
...@@ -8,6 +10,7 @@ ...@@ -8,6 +10,7 @@
#include <cstdlib> #include <cstdlib>
namespace Sass { namespace Sass {
using namespace Constants;
using std::cerr; using std::endl; using std::cerr; using std::endl;
static void throw_eval_error(string message, string path, size_t line) static void throw_eval_error(string message, string path, size_t line)
...@@ -18,125 +21,117 @@ namespace Sass { ...@@ -18,125 +21,117 @@ namespace Sass {
throw Error(Error::evaluation, path, line, message); throw Error(Error::evaluation, path, line, message);
} }
// Evaluate the parse tree in-place (mostly). Most nodes will be left alone. // Expansion function for nodes in an expansion context.
void expand(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name)
Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name)
{ {
switch (expr.type()) switch (expr.type())
{ {
case Node::mixin: { case Node::root: {
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expand(expr[i], prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::mixin: { // mixin definition
env[expr[0].token()] = expr; env[expr[0].token()] = expr;
return expr;
} break; } break;
case Node::function: { case Node::function: { // function definition
f_env[expr[0].to_string()] = Function(expr); f_env[expr[0].to_string()] = Function(expr);
return expr;
} break; } break;
case Node::expansion: { case Node::mixin_call: { // mixin invocation
Token name(expr[0].token()); Token name(expr[0].token());
Node args(expr[1]); Node args(expr[1]);
if (!env.query(name)) throw_eval_error("mixin " + name.to_string() + " is undefined", expr.path(), expr.line()); if (!env.query(name)) throw_eval_error("mixin " + name.to_string() + " is undefined", expr.path(), expr.line());
Node mixin(env[name]); Node mixin(env[name]);
Node expansion(apply_mixin(mixin, args, prefix, env, f_env, new_Node, ctx)); Node expansion(apply_mixin(mixin, args, prefix, env, f_env, new_Node, ctx));
expr.pop_back(); expr.pop_all(); // pop the mixin metadata
expr.pop_back(); expr += expansion; // push the expansion
expr += expansion;
return expr;
} break; } break;
case Node::propset: { case Node::propset: {
eval(expr[1], prefix, env, f_env, new_Node, ctx); // TO DO: perform the property expansion here, rather than in the emitter (also requires the parser to allow interpolants in the property names)
return expr; expand(expr[1], prefix, env, f_env, new_Node, ctx);
} break; } break;
case Node::ruleset: { case Node::ruleset: {
// if the selector contains interpolants, eval it and re-parse // if the selector contains interpolants, eval it and re-parse
if (expr[0].type() == Node::selector_schema) { if (expr[0].type() == Node::selector_schema) {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); Node schema(expr[0]);
} string expansion;
for (size_t i = 0, S = schema.size(); i < S; ++i) {
// expand the selector with the prefix and save it in expr[2] schema[i] = eval(schema[i], prefix, env, f_env, new_Node, ctx);
expr << expand_selector(expr[0], prefix, new_Node); if (schema[i].type() == Node::string_constant) {
expansion += schema[i].token().unquote();
// gather selector extensions into a pending queue
if (ctx.has_extensions) {
// check single selector
if (expr.back().type() != Node::selector_group) {
Node sel(selector_base(expr.back()));
if (ctx.extensions.count(sel)) {
for (multimap<Node, Node>::iterator i = ctx.extensions.lower_bound(sel); i != ctx.extensions.upper_bound(sel); ++i) {
ctx.pending_extensions.push_back(pair<Node, Node>(expr, i->second));
}
} }
} else {
// individually check each selector in a group expansion += schema[i].to_string();
else {
Node group(expr.back());
for (size_t i = 0, S = group.size(); i < S; ++i) {
Node sel(selector_base(group[i]));
if (ctx.extensions.count(sel)) {
for (multimap<Node, Node>::iterator j = ctx.extensions.lower_bound(sel); j != ctx.extensions.upper_bound(sel); ++j) {
ctx.pending_extensions.push_back(pair<Node, Node>(expr, j->second));
}
}
} }
} }
// need to re-parse the selector because its structure may have changed
expansion += " {"; // the parser looks for an lbrace to end a selector
char* expn_src = new char[expansion.size() + 1];
strcpy(expn_src, expansion.c_str());
Document needs_reparsing(Document::make_from_source_chars(ctx, expn_src, schema.path(), true));
needs_reparsing.line = schema.line(); // set the line number to the original node's line
expr[0] = needs_reparsing.parse_selector_group();
} }
// eval the body with the current selector as the prefix // expand the selector with the prefix and save it in expr[2]
eval(expr[1], expr.back(), env, f_env, new_Node, ctx); expr << expand_selector(expr[0], prefix, new_Node);
return expr;
// // gather selector extensions into a pending queue
// if (ctx.has_extensions) {
// // check single selector
// if (expr.back().type() != Node::selector_group) {
// Node sel(selector_base(expr.back()));
// if (ctx.extensions.count(sel)) {
// for (multimap<Node, Node>::iterator i = ctx.extensions.lower_bound(sel); i != ctx.extensions.upper_bound(sel); ++i) {
// ctx.pending_extensions.push_back(pair<Node, Node>(expr, i->second));
// }
// }
// }
// // individually check each selector in a group
// else {
// Node group(expr.back());
// for (size_t i = 0, S = group.size(); i < S; ++i) {
// Node sel(selector_base(group[i]));
// if (ctx.extensions.count(sel)) {
// for (multimap<Node, Node>::iterator j = ctx.extensions.lower_bound(sel); j != ctx.extensions.upper_bound(sel); ++j) {
// ctx.pending_extensions.push_back(pair<Node, Node>(expr, j->second));
// }
// }
// }
// }
// }
// expand the body with the newly expanded selector as the prefix
// cerr << "ORIGINAL SELECTOR:\t" << expr[2].to_string() << endl;
// cerr << "NORMALIZED SELECTOR:\t" << normalize_selector(expr[2], new_Node).to_string() << endl << endl;
expand(expr[1], expr.back(), env, f_env, new_Node, ctx);
} break; } break;
case Node::media_query: { case Node::media_query: {
Node block(expr[1]); Node block(expr[1]);
Node new_ruleset(new_Node(Node::ruleset, expr.path(), expr.line(), 3)); Node new_ruleset(new_Node(Node::ruleset, expr.path(), expr.line(), 3));
new_ruleset << prefix << block << prefix; expr[1] = new_ruleset << prefix << block << prefix;
expr[1] = eval(new_ruleset, new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx); expand(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
return expr;
} break; } break;
case Node::selector_schema: {
string expansion;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (expr[i].type() == Node::string_constant) {
expansion += expr[i].token().unquote();
}
else {
expansion += expr[i].to_string();
}
}
expansion += " {"; // the parser looks for an lbrace to end a selector
char* expn_src = new char[expansion.size() + 1];
strcpy(expn_src, expansion.c_str());
Document needs_reparsing(Document::make_from_source_chars(ctx, expn_src, expr.path(), true));
needs_reparsing.line = expr.line(); // set the line number to the original node's line
Node sel(needs_reparsing.parse_selector_group());
return sel;
} break;
case Node::root: {
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::block: { case Node::block: {
Environment new_frame; Environment new_frame;
new_frame.link(env); new_frame.link(env);
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, new_frame, f_env, new_Node, ctx); expand(expr[i], prefix, new_frame, f_env, new_Node, ctx);
} }
return expr;
} break; } break;
case Node::assignment: { case Node::assignment: {
Node var(expr[0]);
if (expr.is_guarded() && env.query(var.token())) return;
Node val(expr[1]); Node val(expr[1]);
if (val.type() == Node::comma_list || val.type() == Node::space_list) { if (val.type() == Node::list) {
for (size_t i = 0, S = val.size(); i < S; ++i) { for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].should_eval()) val[i] = eval(val[i], prefix, env, f_env, new_Node, ctx); if (val[i].should_eval()) val[i] = eval(val[i], prefix, env, f_env, new_Node, ctx);
} }
...@@ -144,9 +139,8 @@ namespace Sass { ...@@ -144,9 +139,8 @@ namespace Sass {
else { else {
val = eval(val, prefix, env, f_env, new_Node, ctx); val = eval(val, prefix, env, f_env, new_Node, ctx);
} }
Node var(expr[0]);
if (expr.is_guarded() && env.query(var.token())) return expr; // If a binding exists (possibly upframe), then update it.
// If a binding exists (possible upframe), then update it.
// Otherwise, make a new on in the current frame. // Otherwise, make a new on in the current frame.
if (env.query(var.token())) { if (env.query(var.token())) {
env[var.token()] = val; env[var.token()] = val;
...@@ -154,164 +148,334 @@ namespace Sass { ...@@ -154,164 +148,334 @@ namespace Sass {
else { else {
env.current_frame[var.token()] = val; env.current_frame[var.token()] = val;
} }
return expr;
} break; } break;
case Node::rule: { case Node::rule: {
Node lhs(expr[0]); Node lhs(expr[0]);
if (lhs.should_eval()) eval(lhs, prefix, env, f_env, new_Node, ctx); if (lhs.is_schema()) {
expr[0] = eval(lhs, prefix, env, f_env, new_Node, ctx);
}
Node rhs(expr[1]); Node rhs(expr[1]);
if (rhs.type() == Node::comma_list || rhs.type() == Node::space_list) { if (rhs.type() == Node::list) {
for (size_t i = 0, S = rhs.size(); i < S; ++i) { for (size_t i = 0, S = rhs.size(); i < S; ++i) {
if (rhs[i].should_eval()) rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx); if (rhs[i].should_eval()) {
rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx);
}
} }
} }
else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) { else if (rhs.is_schema() || rhs.should_eval()) {
eval(rhs, prefix, env, f_env, new_Node, ctx); expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
} }
} break;
case Node::extend_directive: {
if (prefix.is_null()) throw_eval_error("@extend directive may only be used within rules", expr.path(), expr.line());
// if the extendee contains interpolants, eval it and re-parse
if (expr[0].type() == Node::selector_schema) {
Node schema(expr[0]);
string expansion;
for (size_t i = 0, S = schema.size(); i < S; ++i) {
schema[i] = eval(schema[i], prefix, env, f_env, new_Node, ctx);
if (schema[i].type() == Node::string_constant) {
expansion += schema[i].token().unquote();
}
else {
expansion += schema[i].to_string();
}
}
// need to re-parse the selector because its structure may have changed
expansion += " {"; // the parser looks for an lbrace to end a selector
char* expn_src = new char[expansion.size() + 1];
strcpy(expn_src, expansion.c_str());
Document needs_reparsing(Document::make_from_source_chars(ctx, expn_src, schema.path(), true));
needs_reparsing.line = schema.line(); // set the line number to the original node's line
expr[0] = needs_reparsing.parse_selector_group();
}
// only simple selector sequences may be extended
switch (expr[0].type())
{
case Node::selector_group:
throw_eval_error("selector groups may not be extended", expr[0].path(), expr[0].line());
break;
case Node::selector:
throw_eval_error("nested selectors may not be extended", expr[0].path(), expr[0].line());
break;
default:
break;
}
// each extendee maps to a set of extenders: extendee -> { extenders }
// if it's a single selector, just add it to the set
if (prefix.type() != Node::selector_group) {
ctx.extensions.insert(pair<Node, Node>(expr[0], prefix));
}
// otherwise add each member of the selector group separately
else { else {
if (rhs.should_eval()) expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx); for (size_t i = 0, S = prefix.size(); i < S; ++i) {
ctx.extensions.insert(pair<Node, Node>(expr[0], prefix[i]));
}
}
ctx.has_extensions = true;
} break;
case Node::if_directive: {
Node expansion = Node();
for (size_t i = 0, S = expr.size(); i < S; i += 2) {
if (expr[i].type() != Node::block) {
Node predicate_val(eval(expr[i], prefix, env, f_env, new_Node, ctx));
if (!predicate_val.is_false()) {
expand(expansion = expr[i+1], prefix, env, f_env, new_Node, ctx);
break;
}
}
else {
expand(expansion = expr[i], prefix, env, f_env, new_Node, ctx);
break;
}
}
expr.pop_all();
if (!expansion.is_null()) expr += expansion;
} break;
case Node::for_through_directive:
case Node::for_to_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(for_kwd)) // stub name for debugging
<< (fake_param << expr[0]) // iteration variable
<< expr[3]; // body
Node lower_bound(eval(expr[1], prefix, env, f_env, new_Node, ctx));
Node upper_bound(eval(expr[2], prefix, env, f_env, new_Node, ctx));
if (!(lower_bound.is_numeric() && upper_bound.is_numeric())) {
throw_eval_error("bounds of @for directive must be numeric", expr.path(), expr.line());
}
expr.pop_all();
for (double i = lower_bound.numeric_value(),
U = upper_bound.numeric_value() + ((expr.type() == Node::for_to_directive) ? 0 : 1);
i < U;
++i) {
Node i_node(new_Node(expr.path(), expr.line(), i));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << i_node;
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::each_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(each_kwd)) // stub name for debugging
<< (fake_param << expr[0]) // iteration variable
<< expr[2]; // body
Node list(eval(expr[1], prefix, env, f_env, new_Node, ctx));
// If the list isn't really a list, make a singleton out of it.
if (list.type() != Node::list) {
list = (new_Node(Node::list, list.path(), list.line(), 1) << list);
}
expr.pop_all();
for (size_t i = 0, S = list.size(); i < S; ++i) {
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
list[i].should_eval() = true;
fake_arg << eval(list[i], prefix, env, f_env, new_Node, ctx);
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
} }
return expr;
} break; } break;
case Node::comma_list: case Node::while_directive: {
case Node::space_list: { Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
if (expr.should_eval()) expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 0));
return expr; Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 0));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(while_kwd)) // stub name for debugging
<< fake_param // no iteration variable
<< expr[1]; // body
Node pred(expr[0]);
expr.pop_back();
expr.pop_back();
Node ev_pred(eval(pred, prefix, env, f_env, new_Node, ctx));
while (!ev_pred.is_false()) {
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
ev_pred = eval(pred, prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::block_directive: {
// TO DO: eval the directive name for interpolants
expand(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
} break;
case Node::warning: {
Node contents(eval(expr[0], Node(), env, f_env, new_Node, ctx));
string prefix("WARNING: ");
string indent(" ");
string result(contents.to_string());
if (contents.type() == Node::string_constant || contents.type() == Node::string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string
}
// These cerrs aren't log lines! They're supposed to be here!
cerr << prefix << result << endl;
cerr << indent << "on line " << expr.line() << " of " << expr.path();
cerr << endl << endl;
} break;
default: {
// do nothing
} break;
}
}
void expand_list(Node list, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
for (size_t i = 0, S = list.size(); i < S; ++i) {
list[i].should_eval() = true;
list[i] = eval(list[i], prefix, env, f_env, new_Node, ctx);
}
}
// Evaluation function for nodes in a value context.
Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name)
{
Node result = Node();
switch (expr.type())
{
case Node::list: {
if (expr.should_eval() && expr.size() > 0) {
result = new_Node(Node::list, expr.path(), expr.line(), expr.size());
result.is_comma_separated() = expr.is_comma_separated();
result << eval(expr[0], prefix, env, f_env, new_Node, ctx);
for (size_t i = 1, S = expr.size(); i < S; ++i) result << expr[i];
}
} break; } break;
case Node::disjunction: { case Node::disjunction: {
Node result;
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx); result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) continue; if (result.is_false()) continue;
else return result; else break;
} }
return result;
} break; } break;
case Node::conjunction: { case Node::conjunction: {
Node result;
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx); result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) return result; if (result.is_false()) break;
} }
return result;
} break; } break;
case Node::relation: { case Node::relation: {
Node lhs(new_Node(Node::arguments, expr[0].path(), expr[0].line(), 1));
Node lhs(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node rhs(new_Node(Node::arguments, expr[2].path(), expr[2].line(), 1));
Node op(expr[1]); Node rel(expr[1]);
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
// TO DO: don't allocate both T and F lhs << expr[0];
rhs << expr[2];
lhs = eval_arguments(lhs, prefix, env, f_env, new_Node, ctx);
rhs = eval_arguments(rhs, prefix, env, f_env, new_Node, ctx);
lhs = lhs[0];
rhs = rhs[0];
if (lhs.type() == Node::list) expand_list(lhs, prefix, env, f_env, new_Node, ctx);
if (rhs.type() == Node::list) expand_list(rhs, prefix, env, f_env, new_Node, ctx);
Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true)); Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true));
Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false)); Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false));
switch (op.type()) switch (rel.type())
{ {
case Node::eq: return (lhs == rhs) ? T : F; case Node::eq: result = ((lhs == rhs) ? T : F); break;
case Node::neq: return (lhs != rhs) ? T : F; case Node::neq: result = ((lhs != rhs) ? T : F); break;
case Node::gt: return (lhs > rhs) ? T : F; case Node::gt: result = ((lhs > rhs) ? T : F); break;
case Node::gte: return (lhs >= rhs) ? T : F; case Node::gte: result = ((lhs >= rhs) ? T : F); break;
case Node::lt: return (lhs < rhs) ? T : F; case Node::lt: result = ((lhs < rhs) ? T : F); break;
case Node::lte: return (lhs <= rhs) ? T : F; case Node::lte: result = ((lhs <= rhs) ? T : F); break;
default: default:
throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line()); throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line());
return Node();
} }
} break; } break;
case Node::expression: { case Node::expression: {
Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1)); Node list(new_Node(Node::expression, expr.path(), expr.line(), expr.size()));
acc << eval(expr[0], prefix, env, f_env, new_Node, ctx); for (size_t i = 0, S = expr.size(); i < S; ++i) {
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx)); list << eval(expr[i], prefix, env, f_env, new_Node, ctx);
accumulate(expr[1].type(), acc, rhs, new_Node);
for (size_t i = 3, S = expr.size(); i < S; i += 2) {
Node rhs(eval(expr[i+1], prefix, env, f_env, new_Node, ctx));
accumulate(expr[i].type(), acc, rhs, new_Node);
} }
return acc.size() == 1 ? acc[0] : acc; result = reduce(list, 1, list[0], new_Node);
} break; } break;
case Node::term: { case Node::term: {
if (expr.should_eval()) { if (expr.should_eval()) {
Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1)); Node list(new_Node(Node::term, expr.path(), expr.line(), expr.size()));
acc << eval(expr[0], prefix, env, f_env, new_Node, ctx); for (size_t i = 0, S = expr.size(); i < S; ++i) {
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx)); list << eval(expr[i], prefix, env, f_env, new_Node, ctx);
accumulate(expr[1].type(), acc, rhs, new_Node);
for (size_t i = 3, S = expr.size(); i < S; i += 2) {
Node rhs(eval(expr[i+1], prefix, env, f_env, new_Node, ctx));
accumulate(expr[i].type(), acc, rhs, new_Node);
} }
return acc.size() == 1 ? acc[0] : acc; result = reduce(list, 1, list[0], new_Node);
}
else {
return expr;
} }
} break; } break;
case Node::textual_percentage: { case Node::textual_percentage: {
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage); result = new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage);
} break; } break;
case Node::textual_dimension: { case Node::textual_dimension: {
return new_Node(expr.path(), expr.line(), result = new_Node(expr.path(), expr.line(),
std::atof(expr.token().begin), std::atof(expr.token().begin),
Token::make(Prelexer::number(expr.token().begin), Token::make(Prelexer::number(expr.token().begin),
expr.token().end)); expr.token().end));
} break; } break;
case Node::textual_number: { case Node::textual_number: {
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin)); result = new_Node(expr.path(), expr.line(), std::atof(expr.token().begin));
} break; } break;
case Node::textual_hex: { case Node::textual_hex: {
Node triple(new_Node(Node::numeric_color, expr.path(), expr.line(), 4)); result = new_Node(Node::numeric_color, expr.path(), expr.line(), 4);
Token hext(Token::make(expr.token().begin+1, expr.token().end)); Token hext(Token::make(expr.token().begin+1, expr.token().end));
if (hext.length() == 6) { if (hext.length() == 6) {
for (int i = 0; i < 6; i += 2) { for (int i = 0; i < 6; i += 2) {
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16))); result << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
} }
} }
else { else {
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16))); result << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
} }
} }
triple << new_Node(expr.path(), expr.line(), 1.0); result << new_Node(expr.path(), expr.line(), 1.0);
return triple;
} break; } break;
case Node::variable: { case Node::variable: {
if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line()); if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line());
return env[expr.token()]; result = env[expr.token()];
} break; } break;
case Node::image_url: { case Node::uri: {
Node base(eval(expr[0], prefix, env, f_env, new_Node, ctx)); result = new_Node(Node::uri, expr.path(), expr.line(), 1);
Node prefix(new_Node(Node::identifier, base.path(), base.line(), Token::make(ctx.image_path))); result << eval(expr[0], prefix, env, f_env, new_Node, ctx);
Node fullpath(new_Node(Node::concatenation, base.path(), base.line(), 2));
Node url(new_Node(Node::uri, base.path(), base.line(), 1));
fullpath << prefix << base;
url << fullpath;
return url;
} break; } break;
case Node::function_call: { case Node::function_call: {
// TO DO: default-constructed Function should be a generic callback (maybe) // TO DO: default-constructed Function should be a generic callback (maybe)
// eval the function name in case it's interpolated // eval the function name in case it's interpolated
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx, true); Node name_node(eval(expr[0], prefix, env, f_env, new_Node, ctx, true));
string name(expr[0].to_string()); string name(name_node.to_string());
if (!f_env.count(name)) { if (!f_env.count(name)) {
// no definition available; just pass it through (with evaluated args) // no definition available; just pass it through (with evaluated args)
Node args(expr[1]); Node args(expr[1]);
Node evaluated_args(new_Node(Node::arguments, args.path(), args.line(), args.size()));
for (size_t i = 0, S = args.size(); i < S; ++i) { for (size_t i = 0, S = args.size(); i < S; ++i) {
args[i] = eval(args[i], prefix, env, f_env, new_Node, ctx); evaluated_args << eval(args[i], prefix, env, f_env, new_Node, ctx);
if (evaluated_args.back().type() == Node::list) {
Node arg_list(evaluated_args.back());
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
}
}
} }
return expr; result = new_Node(Node::function_call, expr.path(), expr.line(), 2);
result << name_node << evaluated_args;
} }
else { else {
// check to see if the function is primitive/built-in // check to see if the function is primitive/built-in
...@@ -323,29 +487,29 @@ namespace Sass { ...@@ -323,29 +487,29 @@ namespace Sass {
if (!f_env.count(resolved_name)) throw_eval_error("wrong number of arguments to " + name, expr.path(), expr.line()); if (!f_env.count(resolved_name)) throw_eval_error("wrong number of arguments to " + name, expr.path(), expr.line());
f = f_env[resolved_name]; f = f_env[resolved_name];
} }
return apply_function(f, expr[1], prefix, env, f_env, new_Node, ctx); result = apply_function(f, expr[1], prefix, env, f_env, new_Node, ctx, expr.path(), expr.line());
} }
} break; } break;
case Node::unary_plus: { case Node::unary_plus: {
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) { if (arg.is_numeric()) {
return arg; result = arg;
} }
else { else {
expr[0] = arg; result = new_Node(Node::unary_plus, expr.path(), expr.line(), 1);
return expr; result << arg;
} }
} break; } break;
case Node::unary_minus: { case Node::unary_minus: {
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) { if (arg.is_numeric()) {
return new_Node(expr.path(), expr.line(), -arg.numeric_value()); result = new_Node(expr.path(), expr.line(), -arg.numeric_value());
} }
else { else {
expr[0] = arg; result = new_Node(Node::unary_minus, expr.path(), expr.line(), 1);
return expr; result << arg;
} }
} break; } break;
...@@ -353,319 +517,288 @@ namespace Sass { ...@@ -353,319 +517,288 @@ namespace Sass {
string id_str(expr.to_string()); string id_str(expr.to_string());
to_lowercase(id_str); to_lowercase(id_str);
if (!function_name && ctx.color_names_to_values.count(id_str)) { if (!function_name && ctx.color_names_to_values.count(id_str)) {
return ctx.color_names_to_values[id_str]; Node color_orig(ctx.color_names_to_values[id_str]);
} Node r(color_orig[0]);
else { Node g(color_orig[1]);
return expr; Node b(color_orig[2]);
Node a(color_orig[3]);
result = new_Node(expr.path(), expr.line(),
r.numeric_value(),
g.numeric_value(),
b.numeric_value(),
a.numeric_value());
} }
} break; } break;
case Node::string_schema: case Node::string_schema:
case Node::value_schema: case Node::value_schema:
case Node::identifier_schema: { case Node::identifier_schema: {
result = new_Node(expr.type(), expr.path(), expr.line(), expr.size());
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx); result << eval(expr[i], prefix, env, f_env, new_Node, ctx);
} }
return expr; result.is_quoted() = expr.is_quoted();
} break; } break;
case Node::css_import: { case Node::css_import: {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); result = new_Node(Node::css_import, expr.path(), expr.line(), 1);
return expr; result << eval(expr[0], prefix, env, f_env, new_Node, ctx);
} break;
case Node::if_directive: {
for (size_t i = 0, S = expr.size(); i < S; i += 2) {
if (expr[i].type() != Node::block) {
// cerr << "EVALUATING PREDICATE " << (i/2+1) << endl;
Node predicate_val(eval(expr[i], prefix, env, f_env, new_Node, ctx));
if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) {
// cerr << "EVALUATING CONSEQUENT " << (i/2+1) << endl;
return eval(expr[i+1], prefix, env, f_env, new_Node, ctx);
}
}
else {
// cerr << "EVALUATING ALTERNATIVE" << endl;
return eval(expr[i], prefix, env, f_env, new_Node, ctx);
}
}
} break;
case Node::for_through_directive:
case Node::for_to_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::none, "", 0, 0) << (fake_param << expr[0]) << expr[3];
Node lower_bound(eval(expr[1], prefix, env, f_env, new_Node, ctx));
Node upper_bound(eval(expr[2], prefix, env, f_env, new_Node, ctx));
if (!(lower_bound.is_numeric() && upper_bound.is_numeric())) {
throw_eval_error("bounds of @for directive must be numeric", expr.path(), expr.line());
}
expr.pop_back();
expr.pop_back();
expr.pop_back();
expr.pop_back();
for (double i = lower_bound.numeric_value(),
U = upper_bound.numeric_value() + ((expr.type() == Node::for_to_directive) ? 0 : 1);
i < U;
++i) {
Node i_node(new_Node(expr.path(), expr.line(), i));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << i_node;
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::each_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::none, "", 0, 0) << (fake_param << expr[0]) << expr[2];
Node list(eval(expr[1], prefix, env, f_env, new_Node, ctx));
// If the list isn't really a list, make a singleton out of it.
if (list.type() != Node::space_list && list.type() != Node::comma_list) {
list = (new_Node(Node::space_list, list.path(), list.line(), 1) << list);
}
expr.pop_back();
expr.pop_back();
expr.pop_back();
for (size_t i = 0, S = list.size(); i < S; ++i) {
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << eval(list[i], prefix, env, f_env, new_Node, ctx);
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::while_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 0));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 0));
fake_mixin << new_Node(Node::none, "", 0, 0) << fake_param << expr[1];
Node pred(expr[0]);
expr.pop_back();
expr.pop_back();
Node ev_pred(eval(pred, prefix, env, f_env, new_Node, ctx));
while ((ev_pred.type() != Node::boolean) || ev_pred.boolean_value()) {
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
ev_pred = eval(pred, prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::block_directive: {
// TO DO: eval the directive name for interpolants
eval(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
return expr;
} break;
case Node::warning: {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
return expr;
} break; } break;
default: { default: {
return expr; result = expr;
} break; } break;
} }
if (result.is_null()) result = expr;
return expr; return result;
} }
// Accumulate arithmetic operations. It's done this way because arithmetic // Reduce arithmetic operations. Arithmetic expressions are stored as vectors
// expressions are stored as vectors of operands with operators interspersed, // of operands with operators interspersed, rather than as the usual binary
// rather than as the usual binary tree. // tree. (This function is essentially a left fold.)
Node reduce(Node list, size_t head, Node acc, Node_Factory& new_Node)
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node)
{ {
Node lhs(acc.back()); if (head >= list.size()) return acc;
double lnum = lhs.numeric_value(); Node op(list[head]);
double rnum = rhs.numeric_value(); Node rhs(list[head + 1]);
Node::Type optype = op.type();
if (lhs.type() == Node::number && rhs.type() == Node::number) { Node::Type ltype = acc.type();
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum))); Node::Type rtype = rhs.type();
acc.pop_back(); if (ltype == Node::number && rtype == Node::number) {
acc.push_back(result); acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value()));
}
// TO DO: find a way to merge the following two clauses
else if (lhs.type() == Node::number && rhs.type() == Node::numeric_dimension) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), rhs.unit()));
acc.pop_back();
acc.push_back(result);
} }
else if (lhs.type() == Node::numeric_dimension && rhs.type() == Node::number) { else if (ltype == Node::number && rtype == Node::numeric_dimension) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit())); acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value()), rhs.unit());
acc.pop_back();
acc.push_back(result);
} }
else if (lhs.type() == Node::numeric_dimension && rhs.type() == Node::numeric_dimension) { else if (ltype == Node::numeric_dimension && rtype == Node::number) {
// TO DO: CHECK FOR MISMATCHED UNITS HERE acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value()), acc.unit());
Node result;
if (op == Node::div)
{ result = new_Node(acc.path(), acc.line(), operate(op, lnum, rnum)); }
else
{ result = new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit()); }
acc.pop_back();
acc.push_back(result);
} }
// TO DO: find a way to merge the following two clauses else if (ltype == Node::numeric_dimension && rtype == Node::numeric_dimension) {
else if (lhs.type() == Node::number && rhs.type() == Node::numeric_color) { // TO DO: TRUE UNIT ARITHMETIC
if (op != Node::sub && op != Node::div) { if (optype == Node::div) {
double r = operate(op, lhs.numeric_value(), rhs[0].numeric_value()); acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value()));
double g = operate(op, lhs.numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs.numeric_value(), rhs[2].numeric_value());
double a = rhs[3].numeric_value();
acc.pop_back();
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
} }
// trying to handle weird edge cases ... not sure if it's worth it else {
else if (op == Node::div) { acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value()), acc.unit());
acc << new_Node(Node::div, acc.path(), acc.line(), 0);
acc << rhs;
} }
else if (op == Node::sub) { }
acc << new_Node(Node::sub, acc.path(), acc.line(), 0); else if (ltype == Node::number && rtype == Node::numeric_color) {
acc << rhs; if (optype == Node::add || optype == Node::mul) {
double r = operate(op, acc.numeric_value(), rhs[0].numeric_value());
double g = operate(op, acc.numeric_value(), rhs[1].numeric_value());
double b = operate(op, acc.numeric_value(), rhs[2].numeric_value());
double a = rhs[3].numeric_value();
acc = new_Node(list.path(), list.line(), r, g, b, a);
} }
else { else {
acc = (new_Node(Node::value_schema, list.path(), list.line(), 3) << acc);
acc << op;
acc << rhs; acc << rhs;
} }
} }
else if (lhs.type() == Node::numeric_color && rhs.type() == Node::number) { else if (ltype == Node::numeric_color && rtype == Node::number) {
double r = operate(op, lhs[0].numeric_value(), rhs.numeric_value()); double r = operate(op, acc[0].numeric_value(), rhs.numeric_value());
double g = operate(op, lhs[1].numeric_value(), rhs.numeric_value()); double g = operate(op, acc[1].numeric_value(), rhs.numeric_value());
double b = operate(op, lhs[2].numeric_value(), rhs.numeric_value()); double b = operate(op, acc[2].numeric_value(), rhs.numeric_value());
double a = lhs[3].numeric_value(); double a = acc[3].numeric_value();
acc.pop_back(); acc = new_Node(list.path(), list.line(), r, g, b, a);
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
} }
else if (lhs.type() == Node::numeric_color && rhs.type() == Node::numeric_color) { else if (ltype == Node::numeric_color && rtype == Node::numeric_color) {
if (lhs[3].numeric_value() != rhs[3].numeric_value()) throw_eval_error("alpha channels must be equal for " + lhs.to_string() + " + " + rhs.to_string(), lhs.path(), lhs.line()); if (acc[3].numeric_value() != rhs[3].numeric_value()) throw_eval_error("alpha channels must be equal for " + acc.to_string() + " + " + rhs.to_string(), acc.path(), acc.line());
double r = operate(op, lhs[0].numeric_value(), rhs[0].numeric_value()); double r = operate(op, acc[0].numeric_value(), rhs[0].numeric_value());
double g = operate(op, lhs[1].numeric_value(), rhs[1].numeric_value()); double g = operate(op, acc[1].numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs[2].numeric_value(), rhs[2].numeric_value()); double b = operate(op, acc[2].numeric_value(), rhs[2].numeric_value());
double a = lhs[3].numeric_value(); double a = acc[3].numeric_value();
acc.pop_back(); acc = new_Node(list.path(), list.line(), r, g, b, a);
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
} }
else if (lhs.type() == Node::concatenation && rhs.type() == Node::concatenation) { else if (ltype == Node::concatenation && rtype == Node::concatenation) {
if (op == Node::add) { if (optype != Node::add) acc << op;
lhs += rhs; acc += rhs;
} }
else { else if (ltype == Node::concatenation) {
acc << new_Node(op, acc.path(), acc.line(), Token::make()); if (optype != Node::add) acc << op;
acc << rhs; acc << rhs;
} }
else if (rtype == Node::concatenation) {
acc = (new_Node(Node::concatenation, list.path(), list.line(), 2) << acc);
if (optype != Node::add) acc << op;
acc += rhs;
acc.is_quoted() = acc[0].is_quoted();
} }
else if (lhs.type() == Node::concatenation && rhs.type() == Node::string_constant) { else if (acc.is_string() || rhs.is_string()) {
if (op == Node::add) { acc = (new_Node(Node::concatenation, list.path(), list.line(), 2) << acc);
lhs << rhs; if (optype != Node::add) acc << op;
acc << rhs;
if (acc[0].is_quoted() || (ltype == Node::number && rhs.is_quoted())) {
acc.is_quoted() = true;
} }
else { else {
acc << new_Node(op, acc.path(), acc.line(), Token::make()); acc.is_quoted() = false;
acc << rhs;
} }
} }
else if (lhs.type() == Node::string_constant && rhs.type() == Node::concatenation) { else { // lists or schemas
if (op == Node::add) { if (acc.is_schema() && rhs.is_schema()) {
Node new_cat(new_Node(Node::concatenation, lhs.path(), lhs.line(), 1 + rhs.size())); if (optype != Node::add) acc << op;
new_cat << lhs; acc += rhs;
new_cat += rhs;
acc.pop_back();
acc << new_cat;
} }
else { else if (acc.is_schema()) {
acc << new_Node(op, acc.path(), acc.line(), Token::make()); if (optype != Node::add) acc << op;
acc << rhs; acc << rhs;
} }
} else if (rhs.is_schema()) {
else if (lhs.type() == Node::string_constant && rhs.type() == Node::string_constant) { acc = (new_Node(Node::value_schema, list.path(), list.line(), 2) << acc);
if (op == Node::add) { if (optype != Node::add) acc << op;
Node new_cat(new_Node(Node::concatenation, lhs.path(), lhs.line(), 2)); acc += rhs;
new_cat << lhs << rhs;
acc.pop_back();
acc << new_cat;
} }
else { else {
acc << new_Node(op, acc.path(), acc.line(), Token::make()); acc = (new_Node(Node::value_schema, list.path(), list.line(), 2) << acc);
if (optype != Node::add) acc << op;
acc << rhs; acc << rhs;
} }
acc.is_quoted() = false;
} }
else { return reduce(list, head + 2, acc, new_Node);
// TO DO: disallow division and multiplication on lists
if (op == Node::sub) acc << new_Node(Node::sub, acc.path(), acc.line(), Token::make());
acc.push_back(rhs);
}
return acc;
} }
// Helper for doing the actual arithmetic. // Helper for doing the actual arithmetic.
double operate(Node op, double lhs, double rhs)
double operate(Node::Type op, double lhs, double rhs)
{ {
switch (op) switch (op.type())
{ {
case Node::add: return lhs + rhs; break; case Node::add: return lhs + rhs; break;
case Node::sub: return lhs - rhs; break; case Node::sub: return lhs - rhs; break;
case Node::mul: return lhs * rhs; break; case Node::mul: return lhs * rhs; break;
case Node::div: return lhs / rhs; break; case Node::div: {
if (rhs == 0) throw_eval_error("divide by zero", op.path(), op.line());
return lhs / rhs;
} break;
default: return 0; break; default: return 0; break;
} }
} }
// Apply a mixin -- bind the arguments in a new environment, link the new Node eval_arguments(Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx)
// environment to the current one, then copy the body and eval in the new
// environment.
Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope)
{ {
Node params(mixin[1]); Node evaluated_args(new_Node(Node::arguments, args.path(), args.line(), args.size()));
Node body(new_Node(mixin[2])); // clone the body for (size_t i = 0, S = args.size(); i < S; ++i) {
Environment bindings; if (args[i].type() != Node::assignment) {
// TO DO: REFACTOR THE ARG-BINDER evaluated_args << eval(args[i], prefix, env, f_env, new_Node, ctx);
// bind arguments if (evaluated_args.back().type() == Node::list) {
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) { Node arg_list(evaluated_args.back());
if (args[i].type() == Node::assignment) { Node new_arg_list(new_Node(Node::list, arg_list.path(), arg_list.line(), arg_list.size()));
Node arg(args[i]); for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
Token name(arg[0].token()); if (arg_list[j].should_eval()) new_arg_list << eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
// check that the keyword arg actually names a formal parameter else new_arg_list << arg_list[j];
bool valid_param = false;
for (size_t k = 0, S = params.size(); k < S; ++k) {
Node param_k = params[k];
if (param_k.type() == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
} }
} }
if (!valid_param) throw_eval_error("mixin " + mixin[0].to_string() + " has no parameter named " + name.to_string(), arg.path(), arg.line()); }
if (!bindings.query(name)) { else {
bindings[name] = eval(arg[1], prefix, env, f_env, new_Node, ctx); Node kwdarg(new_Node(Node::assignment, args[i].path(), args[i].line(), 2));
kwdarg << args[i][0];
kwdarg << eval(args[i][1], prefix, env, f_env, new_Node, ctx);
if (kwdarg.back().type() == Node::list) {
Node arg_list(kwdarg.back());
Node new_arg_list(new_Node(Node::list, arg_list.path(), arg_list.line(), arg_list.size()));
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) new_arg_list << eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
else new_arg_list << arg_list[j];
}
kwdarg[1] = new_arg_list;
}
evaluated_args << kwdarg;
}
}
// eval twice because args may be delayed
for (size_t i = 0, S = evaluated_args.size(); i < S; ++i) {
if (evaluated_args[i].type() != Node::assignment) {
evaluated_args[i].should_eval() = true;
evaluated_args[i] = eval(evaluated_args[i], prefix, env, f_env, new_Node, ctx);
if (evaluated_args[i].type() == Node::list) {
Node arg_list(evaluated_args[i]);
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
}
} }
} }
else { else {
// ensure that the number of ordinal args < params.size() Node kwdarg(evaluated_args[i]);
if (j >= params.size()) { kwdarg[1].should_eval() = true;
stringstream ss; kwdarg[1] = eval(kwdarg[1], prefix, env, f_env, new_Node, ctx);
ss << "mixin " << mixin[0].to_string() << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments"); if (kwdarg[1].type() == Node::list) {
throw_eval_error(ss.str(), args[i].path(), args[i].line()); Node arg_list(kwdarg[1]);
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
}
} }
Node param(params[j]); evaluated_args[i] = kwdarg;
Token name(param.type() == Node::variable ? param.token() : param[0].token()); }
bindings[name] = eval(args[i], prefix, env, f_env, new_Node, ctx); }
return evaluated_args;
}
// Helper function for binding arguments in function and mixin calls.
// Needs the environment containing the bindings to be passed in by the
// caller. Also expects the caller to have pre-evaluated the arguments.
void bind_arguments(string callee_name, const Node params, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
// populate the env with the names of the parameters so we can check for
// correctness further down
for (size_t i = 0, S = params.size(); i < S; ++i) {
Node param(params[i]);
env.current_frame[param.type() == Node::variable ? param.token() : param[0].token()] = Node();
}
// now do the actual binding
size_t args_bound = 0, num_params = params.size();
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (j >= num_params) {
stringstream msg;
msg << callee_name << " only takes " << num_params << " arguments";
throw_eval_error(msg.str(), args.path(), args.line());
}
Node arg(args[i]), param(params[j]);
// ordinal argument; just bind and keep going
if (arg.type() != Node::assignment) {
env[param.type() == Node::variable ? param.token() : param[0].token()] = arg;
++j; ++j;
} }
// keyword argument -- need to check for correctness
else {
Token arg_name(arg[0].token());
Node arg_value(arg[1]);
if (!env.query(arg_name)) {
throw_eval_error(callee_name + " has no parameter named " + arg_name.to_string(), arg.path(), arg.line());
}
if (!env[arg_name].is_null()) {
throw_eval_error(callee_name + " was passed argument " + arg_name.to_string() + " both by position and by name", arg.path(), arg.line());
}
env[arg_name] = arg_value;
++args_bound;
}
} }
// plug the holes with default arguments if any // now plug in the holes with default values, if any
for (size_t i = 0, S = params.size(); i < S; ++i) { for (size_t i = 0, S = params.size(); i < S; ++i) {
if (params[i].type() == Node::assignment) { Node param(params[i]);
Node param(params[i]); Token param_name((param.type() == Node::assignment ? param[0] : param).token());
Token name(param[0].token()); if (env[param_name].is_null()) {
if (!bindings.query(name)) { if (param.type() != Node::assignment) {
bindings[name] = eval(param[1], prefix, env, f_env, new_Node, ctx); throw_eval_error(callee_name + " is missing argument " + param_name.to_string(), args.path(), args.line());
} }
// eval default values in an environment where the previous vals have already been evaluated
env[param_name] = eval(param[1], prefix, env, f_env, new_Node, ctx);
} }
} }
// END ARG-BINDER }
// link the new environment and eval the mixin's body
// Apply a mixin -- bind the arguments in a new environment, link the new
// environment to the current one, then copy the body and eval in the new
// environment.
Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope)
{
Node params(mixin[1]);
Node body(new_Node(mixin[2])); // clone the body
Node evaluated_args(eval_arguments(args, prefix, env, f_env, new_Node, ctx));
// Create a new environment for the mixin and link it to the appropriate parent
Environment bindings;
if (dynamic_scope) { if (dynamic_scope) {
bindings.link(env); bindings.link(env);
} }
...@@ -674,85 +807,33 @@ namespace Sass { ...@@ -674,85 +807,33 @@ namespace Sass {
// to implement full lexical scope someday. // to implement full lexical scope someday.
bindings.link(env.global ? *env.global : env); bindings.link(env.global ? *env.global : env);
} }
for (size_t i = 0, S = body.size(); i < S; ++i) { // bind arguments in the extended environment
body[i] = eval(body[i], prefix, bindings, f_env, new_Node, ctx); stringstream mixin_name;
} mixin_name << "mixin";
if (!mixin[0].is_null()) mixin_name << " " << mixin[0].to_string();
bind_arguments(mixin_name.str(), params, evaluated_args, prefix, bindings, f_env, new_Node, ctx);
// evaluate the mixin's body
expand(body, prefix, bindings, f_env, new_Node, ctx);
return body; return body;
} }
// Apply a function -- bind the arguments and pass them to the underlying // Apply a function -- bind the arguments and pass them to the underlying
// primitive function implementation, then return its value. // primitive function implementation, then return its value.
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, string& path, size_t line)
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{ {
Node evaluated_args(eval_arguments(args, prefix, env, f_env, new_Node, ctx));
// bind arguments
Environment bindings;
Node params(f.primitive ? f.parameters : f.definition[1]);
bindings.link(env.global ? *env.global : env);
bind_arguments("function " + f.name, params, evaluated_args, prefix, bindings, f_env, new_Node, ctx);
if (f.primitive) { if (f.primitive) {
map<Token, Node> bindings; return f.primitive(f.parameter_names, bindings, new_Node, path, line);
// bind arguments
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type() == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].token());
bindings[name] = eval(arg[1], prefix, env, f_env, new_Node, ctx);
}
else {
// TO DO: ensure (j < f.parameters.size())
bindings[f.parameters[j].token()] = eval(args[i], prefix, env, f_env, new_Node, ctx);
++j;
}
}
return f(bindings, new_Node);
} }
else { else {
Node params(f.definition[1]); // TO DO: consider cloning the function body?
Node body(new_Node(f.definition[2])); return eval_function(f.name, f.definition[2], bindings, new_Node, ctx, true);
Environment bindings;
// TO DO: REFACTOR THE ARG-BINDER
// bind arguments
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type() == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].token());
// check that the keyword arg actually names a formal parameter
bool valid_param = false;
for (size_t k = 0, S = params.size(); k < S; ++k) {
Node param_k = params[k];
if (param_k.type() == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
}
}
if (!valid_param) throw_eval_error("mixin " + f.name + " has no parameter named " + name.to_string(), arg.path(), arg.line());
if (!bindings.query(name)) {
bindings[name] = eval(arg[1], prefix, env, f_env, new_Node, ctx);
}
}
else {
// ensure that the number of ordinal args < params.size()
if (j >= params.size()) {
stringstream ss;
ss << "mixin " << f.name << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments");
throw_eval_error(ss.str(), args[i].path(), args[i].line());
}
Node param(params[j]);
Token name(param.type() == Node::variable ? param.token() : param[0].token());
bindings[name] = eval(args[i], prefix, env, f_env, new_Node, ctx);
++j;
}
}
// plug the holes with default arguments if any
for (size_t i = 0, S = params.size(); i < S; ++i) {
if (params[i].type() == Node::assignment) {
Node param(params[i]);
Token name(param[0].token());
if (!bindings.query(name)) {
bindings[name] = eval(param[1], prefix, env, f_env, new_Node, ctx);
}
}
}
// END ARG-BINDER
bindings.link(env.global ? *env.global : env);
return function_eval(f.name, body, bindings, new_Node, ctx, true);
} }
} }
...@@ -760,8 +841,7 @@ namespace Sass { ...@@ -760,8 +841,7 @@ namespace Sass {
// algorithm is different in this case because the body needs to be // algorithm is different in this case because the body needs to be
// executed and a single value needs to be returned directly, rather than // executed and a single value needs to be returned directly, rather than
// styles being expanded and spliced in place. // styles being expanded and spliced in place.
Node eval_function(string name, Node body, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool at_toplevel)
Node function_eval(string name, Node body, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool at_toplevel)
{ {
for (size_t i = 0, S = body.size(); i < S; ++i) { for (size_t i = 0, S = body.size(); i < S; ++i) {
Node stm(body[i]); Node stm(body[i]);
...@@ -769,23 +849,27 @@ namespace Sass { ...@@ -769,23 +849,27 @@ namespace Sass {
{ {
case Node::assignment: { case Node::assignment: {
Node val(stm[1]); Node val(stm[1]);
if (val.type() == Node::comma_list || val.type() == Node::space_list) { Node newval;
if (val.type() == Node::list) {
newval = new_Node(Node::list, val.path(), val.line(), val.size());
newval.is_comma_separated() = val.is_comma_separated();
for (size_t i = 0, S = val.size(); i < S; ++i) { for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].should_eval()) val[i] = eval(val[i], Node(), bindings, ctx.function_env, new_Node, ctx); if (val[i].should_eval()) newval << eval(val[i], Node(), bindings, ctx.function_env, new_Node, ctx);
else newval << val[i];
} }
} }
else { else {
val = eval(val, Node(), bindings, ctx.function_env, new_Node, ctx); newval = eval(val, Node(), bindings, ctx.function_env, new_Node, ctx);
} }
Node var(stm[0]); Node var(stm[0]);
if (stm.is_guarded() && bindings.query(var.token())) continue; if (stm.is_guarded() && bindings.query(var.token())) continue;
// If a binding exists (possible upframe), then update it. // If a binding exists (possibly upframe), then update it.
// Otherwise, make a new on in the current frame. // Otherwise, make a new one in the current frame.
if (bindings.query(var.token())) { if (bindings.query(var.token())) {
bindings[var.token()] = val; bindings[var.token()] = newval;
} }
else { else {
bindings.current_frame[var.token()] = val; bindings.current_frame[var.token()] = newval;
} }
} break; } break;
...@@ -793,16 +877,16 @@ namespace Sass { ...@@ -793,16 +877,16 @@ namespace Sass {
for (size_t j = 0, S = stm.size(); j < S; j += 2) { for (size_t j = 0, S = stm.size(); j < S; j += 2) {
if (stm[j].type() != Node::block) { if (stm[j].type() != Node::block) {
Node predicate_val(eval(stm[j], Node(), bindings, ctx.function_env, new_Node, ctx)); Node predicate_val(eval(stm[j], Node(), bindings, ctx.function_env, new_Node, ctx));
if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) { if (!predicate_val.is_false()) {
Node v(function_eval(name, stm[j+1], bindings, new_Node, ctx)); Node v(eval_function(name, stm[j+1], bindings, new_Node, ctx));
if (v.is_null_ptr()) break; if (v.is_null()) break;
else return v; else return v;
} }
} }
else { else {
Node v(function_eval(name, stm[j], bindings, new_Node, ctx)); Node v(eval_function(name, stm[j], bindings, new_Node, ctx));
if (v.is_null_ptr()) break; if (v.is_null()) break;
else return v; else return v;
} }
} }
} break; } break;
...@@ -820,26 +904,27 @@ namespace Sass { ...@@ -820,26 +904,27 @@ namespace Sass {
j < T; j < T;
j += 1) { j += 1) {
for_env.current_frame[iter_var.token()] = new_Node(lower_bound.path(), lower_bound.line(), j); for_env.current_frame[iter_var.token()] = new_Node(lower_bound.path(), lower_bound.line(), j);
Node v(function_eval(name, for_body, for_env, new_Node, ctx)); Node v(eval_function(name, for_body, for_env, new_Node, ctx));
if (v.is_null_ptr()) continue; if (v.is_null()) continue;
else return v; else return v;
} }
} break; } break;
case Node::each_directive: { case Node::each_directive: {
Node iter_var(stm[0]); Node iter_var(stm[0]);
Node list(eval(stm[1], Node(), bindings, ctx.function_env, new_Node, ctx)); Node list(eval(stm[1], Node(), bindings, ctx.function_env, new_Node, ctx));
if (list.type() != Node::comma_list && list.type() != Node::space_list) { if (list.type() != Node::list) {
list = (new_Node(Node::space_list, list.path(), list.line(), 1) << list); list = (new_Node(Node::list, list.path(), list.line(), 1) << list);
} }
Node each_body(stm[2]); Node each_body(stm[2]);
Environment each_env; // re-use this env for each iteration Environment each_env; // re-use this env for each iteration
each_env.link(bindings); each_env.link(bindings);
for (size_t j = 0, T = list.size(); j < T; ++j) { for (size_t j = 0, T = list.size(); j < T; ++j) {
list[j].should_eval() = true;
each_env.current_frame[iter_var.token()] = eval(list[j], Node(), bindings, ctx.function_env, new_Node, ctx); each_env.current_frame[iter_var.token()] = eval(list[j], Node(), bindings, ctx.function_env, new_Node, ctx);
Node v(function_eval(name, each_body, each_env, new_Node, ctx)); Node v(eval_function(name, each_body, each_env, new_Node, ctx));
if (v.is_null_ptr()) continue; if (v.is_null()) continue;
else return v; else return v;
} }
} break; } break;
...@@ -849,9 +934,9 @@ namespace Sass { ...@@ -849,9 +934,9 @@ namespace Sass {
Environment while_env; // re-use this env for each iteration Environment while_env; // re-use this env for each iteration
while_env.link(bindings); while_env.link(bindings);
Node pred_val(eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx)); Node pred_val(eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx));
while ((pred_val.type() != Node::boolean) || pred_val.boolean_value()) { while (!pred_val.is_false()) {
Node v(function_eval(name, while_body, while_env, new_Node, ctx)); Node v(eval_function(name, while_body, while_env, new_Node, ctx));
if (v.is_null_ptr()) { if (v.is_null()) {
pred_val = eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx); pred_val = eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx);
continue; continue;
} }
...@@ -859,12 +944,29 @@ namespace Sass { ...@@ -859,12 +944,29 @@ namespace Sass {
} }
} break; } break;
case Node::warning: {
string prefix("WARNING: ");
string indent(" ");
Node contents(eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx));
string result(contents.to_string());
if (contents.type() == Node::string_constant || contents.type() == Node::string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string
}
// These cerrs aren't log lines! They're supposed to be here!
cerr << prefix << result << endl;
cerr << indent << "on line " << stm.line() << " of " << stm.path();
cerr << endl << endl;
} break;
case Node::return_directive: { case Node::return_directive: {
Node retval(eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx)); Node retval(eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx));
if (retval.type() == Node::comma_list || retval.type() == Node::space_list) { if (retval.type() == Node::list) {
Node new_list(new_Node(Node::list, retval.path(), retval.line(), retval.size()));
new_list.is_comma_separated() = retval.is_comma_separated();
for (size_t i = 0, S = retval.size(); i < S; ++i) { for (size_t i = 0, S = retval.size(); i < S; ++i) {
retval[i] = eval(retval[i], Node(), bindings, ctx.function_env, new_Node, ctx); new_list << eval(retval[i], Node(), bindings, ctx.function_env, new_Node, ctx);
} }
retval = new_list;
} }
return retval; return retval;
} break; } break;
...@@ -883,10 +985,9 @@ namespace Sass { ...@@ -883,10 +985,9 @@ namespace Sass {
// of a backref. When the selector doesn't have backrefs, just prepend the // of a backref. When the selector doesn't have backrefs, just prepend the
// prefix. This function needs multiple subsidiary cases in order to properly // prefix. This function needs multiple subsidiary cases in order to properly
// combine the various kinds of selectors. // combine the various kinds of selectors.
Node expand_selector(Node sel, Node pre, Node_Factory& new_Node) Node expand_selector(Node sel, Node pre, Node_Factory& new_Node)
{ {
if (pre.type() == Node::none) return sel; if (pre.is_null()) return sel;
if (sel.has_backref()) { if (sel.has_backref()) {
if ((pre.type() == Node::selector_group) && (sel.type() == Node::selector_group)) { if ((pre.type() == Node::selector_group) && (sel.type() == Node::selector_group)) {
...@@ -968,7 +1069,6 @@ namespace Sass { ...@@ -968,7 +1069,6 @@ namespace Sass {
} }
// Helper for expanding selectors with backrefs. // Helper for expanding selectors with backrefs.
Node expand_backref(Node sel, Node pre) Node expand_backref(Node sel, Node pre)
{ {
switch (sel.type()) switch (sel.type())
...@@ -993,7 +1093,90 @@ namespace Sass { ...@@ -993,7 +1093,90 @@ namespace Sass {
return Node(); return Node();
} }
// Resolve selector extensions. // Resolve selector extensions. Walk through the document tree and check each
// selector to see whether it's the base of an extension. Needs to be a
// separate pass after evaluation because extension requests may be located
// within mixins, and their targets may be interpolated.
void extend(Node expr, multimap<Node, Node>& extension_requests, Node_Factory& new_Node)
{
switch (expr.type())
{
case Node::ruleset: {
if (!expr[2].has_been_extended()) {
// check single selector
if (expr[2].type() != Node::selector_group) {
Node sel(selector_base(expr[2]));
// if this selector has extenders ...
size_t num_requests = extension_requests.count(sel);
if (num_requests) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), 1 + num_requests));
group << expr[2];
// for each of its extenders ...
for (multimap<Node, Node>::iterator request = extension_requests.lower_bound(sel);
request != extension_requests.upper_bound(sel);
++request) {
Node ext(generate_extension(expr[2], request->second, new_Node));
if (ext.type() == Node::selector_group) group += ext;
else group << ext;
}
group = remove_duplicate_selectors(group, new_Node);
group.has_been_extended() = true;
expr[2] = group;
}
}
// individually check each selector in a group
else {
Node group(expr[2]);
Node new_group(new_Node(Node::selector_group, group.path(), group.line(), group.size()));
// for each selector in the group ...
for (size_t i = 0, S = group.size(); i < S; ++i) {
new_group << group[i];
Node sel(selector_base(group[i]));
// if it has extenders ...
if (!group[i].has_been_extended() && extension_requests.count(sel)) {
// for each of its extenders ...
for (multimap<Node, Node>::iterator request = extension_requests.lower_bound(sel);
request != extension_requests.upper_bound(sel);
++request) {
Node ext(generate_extension(group[i], request->second, new_Node));
if (ext.type() == Node::selector_group) new_group += ext;
else new_group << ext;
}
group[i].has_been_extended() = true;
}
}
if (new_group.size() > 0) {
group.has_been_extended() = true;
new_group = remove_duplicate_selectors(new_group, new_Node);
new_group.has_been_extended() = true;
expr[2] = new_group;
}
}
}
extend(expr[1], extension_requests, new_Node);
} break;
case Node::root:
case Node::block:
case Node::mixin_call:
case Node::if_directive:
case Node::for_through_directive:
case Node::for_to_directive:
case Node::each_directive:
case Node::while_directive: {
// at this point, all directives have been expanded into style blocks,
// so just recursively process their children
for (size_t i = 0, S = expr.size(); i < S; ++i) {
extend(expr[i], extension_requests, new_Node);
}
} break;
default: {
// do nothing
} break;
}
return;
}
void extend_selectors(vector<pair<Node, Node> >& pending, multimap<Node, Node>& extension_table, Node_Factory& new_Node) void extend_selectors(vector<pair<Node, Node> >& pending, multimap<Node, Node>& extension_table, Node_Factory& new_Node)
{ {
...@@ -1009,6 +1192,7 @@ namespace Sass { ...@@ -1009,6 +1192,7 @@ namespace Sass {
for (multimap<Node, Node>::iterator i = extension_table.lower_bound(extendee_base), E = extension_table.upper_bound(extendee_base); for (multimap<Node, Node>::iterator i = extension_table.lower_bound(extendee_base), E = extension_table.upper_bound(extendee_base);
i != E; i != E;
++i) { ++i) {
if (i->second.size() <= 2) continue; // TODO: UN-HACKIFY THIS
if (i->second[2].type() == Node::selector_group) if (i->second[2].type() == Node::selector_group)
extender_group += i->second[2]; extender_group += i->second[2];
else else
...@@ -1033,6 +1217,7 @@ namespace Sass { ...@@ -1033,6 +1217,7 @@ namespace Sass {
for (multimap<Node, Node>::iterator i = extension_table.lower_bound(extendee_i_base), E = extension_table.upper_bound(extendee_i_base); for (multimap<Node, Node>::iterator i = extension_table.lower_bound(extendee_i_base), E = extension_table.upper_bound(extendee_i_base);
i != E; i != E;
++i) { ++i) {
if (i->second.size() <= 2) continue; // TODO: UN-HACKIFY THIS
if (i->second[2].type() == Node::selector_group) if (i->second[2].type() == Node::selector_group)
extender_group += i->second[2]; extender_group += i->second[2];
else else
...@@ -1046,128 +1231,11 @@ namespace Sass { ...@@ -1046,128 +1231,11 @@ namespace Sass {
} }
ruleset_to_extend[2] = extended_group; ruleset_to_extend[2] = extended_group;
} }
// if (extendee.type() != Node::selector_group && extender.type() != Node::selector_group) {
// Node ext(generate_extension(extendee, extender, new_Node));
// ext.push_front(extendee);
// ruleset_to_extend[2] = ext;
// }
// else if (extendee.type() == Node::selector_group && extender.type() != Node::selector_group) {
// cerr << "extending a group with a singleton!" << endl;
// Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), extendee.size()));
// for (size_t i = 0, S = extendee.size(); i < S; ++i) {
// new_group << extendee[i];
// if (extension_table.count(extendee[i])) {
// new_group << generate_extension(extendee[i], extender, new_Node);
// }
// }
// ruleset_to_extend[2] = new_group;
// }
// else if (extendee.type() != Node::selector_group && extender.type() == Node::selector_group) {
// cerr << "extending a singleton with a group!" << endl;
// }
// else {
// cerr << "skipping this for now!" << endl;
// }
// if (selector_to_extend.type() != Node::selector_group) {
// Node ext(generate_extension(selector_to_extend, extender, new_Node));
// ext.push_front(selector_to_extend);
// ruleset_to_extend[2] = ext;
// }
// else {
// cerr << "possibly extending a selector in a group: " << selector_to_extend.to_string() << endl;
// Node new_group(new_Node(Node::selector_group,
// selector_to_extend.path(),
// selector_to_extend.line(),
// selector_to_extend.size()));
// for (size_t i = 0, S = selector_to_extend.size(); i < S; ++i) {
// Node sel_i(selector_to_extend[i]);
// Node sel_ib(selector_base(sel_i));
// if (extension_table.count(sel_ib)) {
// for (multimap<Node, Node>::iterator i = extension_table.lower_bound(sel_ib); i != extension_table.upper_bound(sel_ib); ++i) {
// if (i->second.is(original_extender)) {
// new_group << sel_i;
// new_group += generate_extension(sel_i, extender, new_Node);
// }
// else {
// cerr << "not what you think is happening!" << endl;
// }
// }
// }
// }
// ruleset_to_extend[2] = new_group;
// }
// if (selector_to_extend.type() != Node::selector) {
// switch (extender.type())
// {
// case Node::simple_selector:
// case Node::attribute_selector:
// case Node::simple_selector_sequence:
// case Node::selector: {
// cerr << "EXTENDING " << selector_to_extend.to_string() << " WITH " << extender.to_string() << endl;
// if (selector_to_extend.type() == Node::selector_group) {
// selector_to_extend << extender;
// }
// else {
// Node new_group(new_Node(Node::selector_group, selector_to_extend.path(), selector_to_extend.line(), 2));
// new_group << selector_to_extend << extender;
// ruleset_to_extend[2] = new_group;
// }
// } break;
// default: {
// // handle the other cases later
// }
// }
// }
// else {
// switch (extender.type())
// {
// case Node::simple_selector:
// case Node::attribute_selector:
// case Node::simple_selector_sequence: {
// Node new_ext(new_Node(selector_to_extend));
// new_ext.back() = extender;
// if (selector_to_extend.type() == Node::selector_group) {
// selector_to_extend << new_ext;
// }
// else {
// Node new_group(new_Node(Node::selector_group, selector_to_extend.path(), selector_to_extend.line(), 2));
// new_group << selector_to_extend << new_ext;
// ruleset_to_extend[2] = new_group;
// }
// } break;
// case Node::selector: {
// Node new_ext1(new_Node(Node::selector, selector_to_extend.path(), selector_to_extend.line(), selector_to_extend.size() + extender.size() - 1));
// Node new_ext2(new_Node(Node::selector, selector_to_extend.path(), selector_to_extend.line(), selector_to_extend.size() + extender.size() - 1));
// new_ext1 += selector_prefix(selector_to_extend, new_Node);
// new_ext1 += extender;
// new_ext2 += selector_prefix(extender, new_Node);
// new_ext2 += selector_prefix(selector_to_extend, new_Node);
// new_ext2 << extender.back();
// if (selector_to_extend.type() == Node::selector_group) {
// selector_to_extend << new_ext1 << new_ext2;
// }
// else {
// Node new_group(new_Node(Node::selector_group, selector_to_extend.path(), selector_to_extend.line(), 2));
// new_group << selector_to_extend << new_ext1 << new_ext2;
// ruleset_to_extend[2] = new_group;
// }
// } break;
// default: {
// // something
// } break;
// }
// }
} }
} }
// Helper for generating selector extensions; called for each extendee and // Helper for generating selector extensions; called for each extendee and
// extender in a pair of selector groups. // extender in a pair of selector groups.
Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node) Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node)
{ {
Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1)); Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1));
...@@ -1223,7 +1291,6 @@ namespace Sass { ...@@ -1223,7 +1291,6 @@ namespace Sass {
} }
// Helpers for extracting subsets of selectors // Helpers for extracting subsets of selectors
Node selector_prefix(Node sel, Node_Factory& new_Node) Node selector_prefix(Node sel, Node_Factory& new_Node)
{ {
switch (sel.type()) switch (sel.type())
......
#define SASS_EVAL_APPLY
#include <map> #include <map>
#ifndef SASS_NODE_INCLUDED #ifndef SASS_NODE
#include "node.hpp" #include "node.hpp"
#endif #endif
#ifndef SASS_CONTEXT_INCLUDED #ifndef SASS_CONTEXT
#include "context.hpp" #include "context.hpp"
#endif #endif
namespace Sass { namespace Sass {
using std::map; using std::map;
void expand(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name = false);
Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name = false); Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name = false);
Node function_eval(string name, Node stm, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool toplevel = false); Node eval_arguments(Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx);
Node eval_function(string name, Node stm, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool toplevel = false);
Node reduce(Node list, size_t head, Node acc, Node_Factory& new_Node);
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node); Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node);
double operate(Node::Type op, double lhs, double rhs); double operate(Node op, double lhs, double rhs);
Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope = false); Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope = false);
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx); Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, string& path, size_t line);
Node expand_selector(Node sel, Node pre, Node_Factory& new_Node); Node expand_selector(Node sel, Node pre, Node_Factory& new_Node);
Node expand_backref(Node sel, Node pre); Node expand_backref(Node sel, Node pre);
void extend(Node expr, multimap<Node, Node>& extension_requests, Node_Factory& new_Node);
void extend_selectors(vector<pair<Node, Node> >&, multimap<Node, Node>&, Node_Factory&); void extend_selectors(vector<pair<Node, Node> >&, multimap<Node, Node>&, Node_Factory&);
Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node); Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node);
......
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
#include "node_factory.hpp"
#include "functions.hpp"
#include "error.hpp"
#include <iostream> #include <iostream>
#include <sstream>
#include <cmath> #include <cmath>
using std::cerr; using std::endl; #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 { 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 { namespace Functions {
static void throw_eval_error(string message, string path, size_t line) static void throw_eval_error(string message, string& path, size_t line)
{ {
if (!path.empty() && Prelexer::string_constant(path.c_str())) if (!path.empty() && Prelexer::string_constant(path.c_str()))
path = path.substr(1, path.length() - 1); path = path.substr(1, path.length() - 1);
...@@ -19,615 +55,967 @@ namespace Sass { ...@@ -19,615 +55,967 @@ namespace Sass {
throw Error(Error::evaluation, path, line, message); throw Error(Error::evaluation, path, line, message);
} }
// RGB Functions /////////////////////////////////////////////////////// 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;
Function_Descriptor rgb_descriptor = case Node::numeric_color: {
{ "rgb", "$red", "$green", "$blue", 0 }; return color_name;
Node rgb(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { } break;
Node r(bindings[parameters[0].token()]);
Node g(bindings[parameters[1].token()]); case Node::list: {
Node b(bindings[parameters[2].token()]); return list_name;
if (!(r.type() == Node::number && g.type() == Node::number && b.type() == Node::number)) { } break;
throw_eval_error("arguments for rgb must be numbers", r.path(), r.line());
default: {
return empty_str;
} break;
} }
return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), 1.0); // unreachable statement
return empty_str;
} }
Function_Descriptor rgba_4_descriptor = // Functions for fetching and checking arguments.
{ "rgba 4", "$red", "$green", "$blue", "$alpha", 0 }; 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 rgba_4(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node the_arg(bindings[parameter_names[param_num].token()]);
Node r(bindings[parameters[0].token()]); Node::Type arg_type = the_arg.type();
Node g(bindings[parameters[1].token()]); switch (param_type)
Node b(bindings[parameters[2].token()]); {
Node a(bindings[parameters[3].token()]); case Node::any: {
if (!(r.type() == Node::number && g.type() == Node::number && b.type() == Node::number && a.type() == Node::number)) { return the_arg;
throw_eval_error("arguments for rgba must be numbers", r.path(), r.line()); } 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;
} }
return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value()); 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();
} }
Function_Descriptor rgba_2_descriptor = 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) {
{ "rgba 2", "$color", "$alpha", 0 }; Node the_arg(arg(sig, path, line, parameter_names, bindings, param_num, t));
Node rgba_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { if (!the_arg.is_numeric()) {
Node color(bindings[parameters[0].token()]); stringstream msg;
Node r(color[0]); msg << "numeric value required for argument " << param_num+1 << " in call to '" << sig << "'";
Node g(color[1]); throw_eval_error(msg.str(), path, line);
Node b(color[2]); }
Node a(bindings[parameters[1].token()]); double val = the_arg.numeric_value();
if (color.type() != Node::numeric_color || a.type() != Node::number) throw_eval_error("arguments to rgba must be a color and a number", color.path(), color.line()); if (val < low || high < val) {
return new_Node(color.path(), color.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value()); 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;
} }
Function_Descriptor red_descriptor = ////////////////////////////////////////////////////////////////////////
{ "red", "$color", 0 }; // RGB Functions ///////////////////////////////////////////////////////
Node red(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { ////////////////////////////////////////////////////////////////////////
Node color(bindings[parameters[0].token()]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to red must be a color", color.path(), color.line()); extern Signature rgb_sig = "rgb($red, $green, $blue)";
return color[0]; 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);
} }
Function_Descriptor green_descriptor = extern Signature red_sig = "red($color)";
{ "green", "$color", 0 }; Node red(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node green(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node color(arg(red_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node color(bindings[parameters[0].token()]); return new_Node(path, line, color[0]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to green must be a color", color.path(), color.line());
return color[1];
} }
Function_Descriptor blue_descriptor = extern Signature green_sig = "green($color)";
{ "blue", "$color", 0 }; Node green(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node blue(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node color(arg(green_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node color(bindings[parameters[0].token()]); return new_Node(path, line, color[1]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to blue must be a color", color.path(), color.line());
return color[2];
} }
Node mix_impl(Node color1, Node color2, double weight, Node_Factory& new_Node) { extern Signature blue_sig = "blue($color)";
if (!(color1.type() == Node::numeric_color && color2.type() == Node::numeric_color)) { Node blue(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
throw_eval_error("first two arguments to mix must be colors", color1.path(), color1.line()); Node color(arg(blue_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
} return new_Node(path, line, color[2]);
double p = weight/100; }
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 w = 2*p - 1;
double a = color1[3].numeric_value() - color2[3].numeric_value(); 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 w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0;
double w2 = 1 - w1; double w2 = 1 - w1;
Node mixed(new_Node(Node::numeric_color, color1.path(), color1.line(), 4)); Node mixed(new_Node(Node::numeric_color, path, line, 4));
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
mixed << new_Node(mixed.path(), mixed.line(), mixed << new_Node(path, line, std::floor(w1*color1[i].numeric_value() + w2*color2[i].numeric_value()));
w1*color1[i].numeric_value() + w2*color2[i].numeric_value());
} }
double alpha = color1[3].numeric_value()*p + color2[3].numeric_value()*(1-p); double alpha = color1[3].numeric_value()*p + color2[3].numeric_value()*(1-p);
mixed << new_Node(mixed.path(), mixed.line(), alpha); mixed << new_Node(path, line, alpha);
return mixed; return mixed;
} }
Function_Descriptor mix_2_descriptor = ////////////////////////////////////////////////////////////////////////
{ "mix 2", "$color1", "$color2", 0 };
Node mix_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return mix_impl(bindings[parameters[0].token()], bindings[parameters[1].token()], 50, new_Node);
}
Function_Descriptor mix_3_descriptor =
{ "mix 3", "$color1", "$color2", "$weight", 0 };
Node mix_3(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node percentage(bindings[parameters[2].token()]);
if (!(percentage.type() == Node::number || percentage.type() == Node::numeric_percentage || percentage.type() == Node::numeric_dimension)) {
throw_eval_error("third argument to mix must be numeric", percentage.path(), percentage.line());
}
return mix_impl(bindings[parameters[0].token()],
bindings[parameters[1].token()],
percentage.numeric_value(),
new_Node);
}
// HSL Functions /////////////////////////////////////////////////////// // 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) { double h_to_rgb(double m1, double m2, double h) {
if (h < 0) ++h; if (h < 0) h += 1;
if (h > 1) --h; if (h > 1) h -= 1;
if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; if (h*6.0 < 1) return m1 + (m2 - m1)*h*6;
if (h*2.0 < 1) return m2; if (h*2.0 < 1) return m2;
if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6;
return m1; return m1;
} }
Node hsla_impl(double h, double s, double l, double a, Node_Factory& new_Node) { 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; h = static_cast<double>(((static_cast<int>(h) % 360) + 360) % 360) / 360.0;
s = s / 100.0; s = (s < 0) ? 0 :
l = l / 100.0; (s > 100) ? 100 :
s;
l = (l < 0) ? 0 :
(l > 100) ? 100 :
l;
s /= 100.0;
l /= 100.0;
double m2; double m2;
if (l <= 0.5) m2 = l*(s+1.0); if (l <= 0.5) m2 = l*(s+1.0);
else m2 = l+s-l*s; else m2 = l+s-l*s;
double m1 = l*2-m2; double m1 = l*2-m2;
double r = h_to_rgb(m1, m2, h+1.0/3.0) * 255.0; // round the results -- consider moving this into the Node constructors
double g = h_to_rgb(m1, m2, h) * 255.0; double r = std::floor(h_to_rgb(m1, m2, h+1.0/3.0) * 255.0 + 0.5);
double b = h_to_rgb(m1, m2, h-1.0/3.0) * 255.0; 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("", 0, r, g, b, a);
} return new_Node(path, line, r, g, b, a);
Function_Descriptor hsla_descriptor =
{ "hsla", "$hue", "$saturation", "$lightness", "$alpha", 0 };
Node hsla(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
if (!(bindings[parameters[0].token()].is_numeric() &&
bindings[parameters[1].token()].is_numeric() &&
bindings[parameters[2].token()].is_numeric() &&
bindings[parameters[3].token()].is_numeric())) {
throw_eval_error("arguments to hsla must be numeric", bindings[parameters[0].token()].path(), bindings[parameters[0].token()].line());
}
double h = bindings[parameters[0].token()].numeric_value();
double s = bindings[parameters[1].token()].numeric_value();
double l = bindings[parameters[2].token()].numeric_value();
double a = bindings[parameters[3].token()].numeric_value();
Node color(hsla_impl(h, s, l, a, new_Node));
// color.line() = bindings[parameters[0].token()].line();
return color;
} }
Function_Descriptor hsl_descriptor = extern Signature hsl_sig = "hsl($hue, $saturation, $lightness)";
{ "hsl", "$hue", "$saturation", "$lightness", 0 }; Node hsl(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node hsl(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { double h = arg(hsl_sig, path, line, parameter_names, bindings, 0, Node::numeric).numeric_value();
if (!(bindings[parameters[0].token()].is_numeric() && double s = arg(hsl_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100).numeric_value();
bindings[parameters[1].token()].is_numeric() && double l = arg(hsl_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 100).numeric_value();
bindings[parameters[2].token()].is_numeric())) { return hsla_impl(h, s, l, 1.0, new_Node, path, line);
throw_eval_error("arguments to hsl must be numeric", bindings[parameters[0].token()].path(), bindings[parameters[0].token()].line()); }
}
double h = bindings[parameters[0].token()].numeric_value(); extern Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)";
double s = bindings[parameters[1].token()].numeric_value(); Node hsla(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
double l = bindings[parameters[2].token()].numeric_value(); double h = arg(hsla_sig, path, line, parameter_names, bindings, 0, Node::numeric).numeric_value();
Node color(hsla_impl(h, s, l, 1, new_Node)); double s = arg(hsla_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 100).numeric_value();
// color.line() = bindings[parameters[0].token()].line(); double l = arg(hsla_sig, path, line, parameter_names, bindings, 2, Node::numeric, 0, 100).numeric_value();
return color; 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);
} }
Function_Descriptor invert_descriptor = extern Signature hue_sig = "hue($color)";
{ "invert", "$color", 0 }; Node hue(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node invert(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node rgb_color(arg(hue_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
Node orig(bindings[parameters[0].token()]); Node hsl_color(rgb_to_hsl(rgb_color[0].numeric_value(),
if (orig.type() != Node::numeric_color) throw_eval_error("argument to invert must be a color", orig.path(), orig.line()); rgb_color[1].numeric_value(),
return new_Node(orig.path(), orig.line(), rgb_color[2].numeric_value(),
255 - orig[0].numeric_value(), new_Node, path, line));
255 - orig[1].numeric_value(), return new_Node(path, line, hsl_color[0].numeric_value(), Token::make(deg_kwd));
255 - orig[2].numeric_value(), }
orig[3].numeric_value());
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 /////////////////////////////////////////////////// // Opacity Functions ///////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
Function_Descriptor alpha_descriptor = extern Signature alpha_sig = "alpha($color)";
{ "alpha", "$color", 0 }; Node alpha(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Function_Descriptor opacity_descriptor = Node color(arg(alpha_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
{ "opacity", "$color", 0 }; return new_Node(path, line, color[3]);
Node alpha(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { }
Node color(bindings[parameters[0].token()]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to alpha must be a color", color.path(), color.line()); extern Signature opacity_sig = "opacity($color)";
return color[3]; 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]);
} }
Function_Descriptor opacify_descriptor = extern Signature opacify_sig = "opacify($color, $amount)";
{ "opacify", "$color", "$amount", 0 }; Node opacify(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Function_Descriptor fade_in_descriptor = Node color(arg(opacify_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
{ "fade_in", "$color", "$amount", 0 }; double delta = arg(opacify_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
Node opacify(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { delta += color[3].numeric_value();
Node color(bindings[parameters[0].token()]); if (delta > 1) delta = 1;
Node delta(bindings[parameters[1].token()]); return new_Node(path, line,
if (color.type() != Node::numeric_color || !delta.is_numeric()) { color[0].numeric_value(),
throw_eval_error("arguments to opacify/fade_in must be a color and a numeric value", color.path(), color.line()); color[1].numeric_value(),
} color[2].numeric_value(),
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) { delta);
throw_eval_error("amount must be between 0 and 1 for opacify/fade-in", delta.path(), delta.line()); }
}
double alpha = color[3].numeric_value() + delta.numeric_value(); extern Signature fade_in_sig = "fade-in($color, $amount)";
if (alpha > 1) alpha = 1; Node fade_in(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
else if (alpha < 0) alpha = 0; Node color(arg(fade_in_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(color.path(), color.line(), double delta = arg(fade_in_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), alpha); 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);
} }
Function_Descriptor transparentize_descriptor = extern Signature transparentize_sig = "transparentize($color, $amount)";
{ "transparentize", "$color", "$amount", 0 }; Node transparentize(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Function_Descriptor fade_out_descriptor = Node color(arg(transparentize_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
{ "fade_out", "$color", "$amount", 0 }; double delta = arg(transparentize_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
Node transparentize(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { double alpha = color[3].numeric_value() - delta;
Node color(bindings[parameters[0].token()]); if (alpha < 0) alpha = 0;
Node delta(bindings[parameters[1].token()]); return new_Node(path, line,
if (color.type() != Node::numeric_color || !delta.is_numeric()) { color[0].numeric_value(),
throw_eval_error("arguments to transparentize/fade_out must be a color and a numeric value", color.path(), color.line()); color[1].numeric_value(),
} color[2].numeric_value(),
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) { alpha);
throw_eval_error("amount must be between 0 and 1 for transparentize/fade-out", delta.path(), delta.line()); }
}
double alpha = color[3].numeric_value() - delta.numeric_value(); extern Signature fade_out_sig = "fade-out($color, $amount)";
if (alpha > 1) alpha = 1; Node fade_out(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
else if (alpha < 0) alpha = 0; Node color(arg(fade_out_sig, path, line, parameter_names, bindings, 0, Node::numeric_color));
return new_Node(color.path(), color.line(), double delta = arg(fade_out_sig, path, line, parameter_names, bindings, 1, Node::numeric, 0, 1).numeric_value();
color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), alpha); 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 //////////////////////////////////////////////////// // String Functions ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
Function_Descriptor unquote_descriptor = extern Signature unquote_sig = "unquote($string)";
{ "unquote", "$string", 0 }; Node unquote(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node unquote(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node cpy(new_Node(path, line, bindings[parameter_names[0].token()]));
Node cpy(new_Node(bindings[parameters[0].token()]));
// if (cpy.type() != Node::string_constant /* && cpy.type() != Node::concatenation */) {
// throw_eval_error("argument to unquote must be a string", cpy.path(), cpy.line());
// }
cpy.is_unquoted() = true;
cpy.is_quoted() = false; cpy.is_quoted() = false;
return cpy; return cpy;
} }
Function_Descriptor quote_descriptor = extern Signature quote_sig = "quote($string)";
{ "quote", "$string", 0 }; Node quote(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node quote(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node orig(arg(quote_sig, path, line, parameter_names, bindings, 0, Node::string_t));
Node orig(bindings[parameters[0].token()]); Node copy(new_Node(path, line, orig));
switch (orig.type()) copy.is_quoted() = true;
{ return copy;
default: {
throw_eval_error("argument to quote must be a string or identifier", orig.path(), orig.line());
} break;
case Node::string_constant:
case Node::string_schema:
case Node::identifier:
case Node::identifier_schema:
case Node::concatenation: {
Node cpy(new_Node(orig));
cpy.is_unquoted() = false;
cpy.is_quoted() = true;
return cpy;
} break;
}
return orig;
} }
////////////////////////////////////////////////////////////////////////
// Number Functions //////////////////////////////////////////////////// // Number Functions ////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
Function_Descriptor percentage_descriptor = extern Signature percentage_sig = "percentage($value)";
{ "percentage", "$value", 0 }; Node percentage(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node percentage(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node orig(arg(percentage_sig, path, line, parameter_names, bindings, 0, Node::number));
Node orig(bindings[parameters[0].token()]); return new_Node(path, line, orig.numeric_value() * 100, Node::numeric_percentage);
if (orig.type() != Node::number) {
throw_eval_error("argument to percentage must be a unitless number", orig.path(), orig.line());
}
return new_Node(orig.path(), orig.line(), orig.numeric_value() * 100, Node::numeric_percentage);
} }
Function_Descriptor round_descriptor = extern Signature round_sig = "round($value)";
{ "round", "$value", 0 }; Node round(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node round(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node orig(arg(round_sig, path, line, parameter_names, bindings, 0, Node::numeric));
Node orig(bindings[parameters[0].token()]);
switch (orig.type()) switch (orig.type())
{ {
case Node::numeric_dimension: { case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::floor(orig.numeric_value() + 0.5), orig.unit()); std::floor(orig.numeric_value() + 0.5),
orig.unit());
} break; } break;
case Node::number: { case Node::number: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::floor(orig.numeric_value() + 0.5)); std::floor(orig.numeric_value() + 0.5));
} break; } break;
case Node::numeric_percentage: { case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::floor(orig.numeric_value() + 0.5), std::floor(orig.numeric_value() + 0.5),
Node::numeric_percentage); Node::numeric_percentage);
} break; } break;
default: { default: {
throw_eval_error("argument to round must be numeric", orig.path(), orig.line()); // unreachable
throw_eval_error("argument to round must be numeric", path, line);
} break; } break;
} }
// unreachable statement // unreachable statement
return Node(); return Node();
} }
Function_Descriptor ceil_descriptor = extern Signature ceil_sig = "ceil($value)";
{ "ceil", "$value", 0 }; Node ceil(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node ceil(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node orig(arg(ceil_sig, path, line, parameter_names, bindings, 0, Node::numeric));
Node orig(bindings[parameters[0].token()]);
switch (orig.type()) switch (orig.type())
{ {
case Node::numeric_dimension: { case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::ceil(orig.numeric_value()), orig.unit()); std::ceil(orig.numeric_value()),
orig.unit());
} break; } break;
case Node::number: { case Node::number: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::ceil(orig.numeric_value())); std::ceil(orig.numeric_value()));
} break; } break;
case Node::numeric_percentage: { case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::ceil(orig.numeric_value()), std::ceil(orig.numeric_value()),
Node::numeric_percentage); Node::numeric_percentage);
} break; } break;
default: { default: {
throw_eval_error("argument to ceil must be numeric", orig.path(), orig.line()); // unreachable
throw_eval_error("argument to ceil must be numeric", path, line);
} break; } break;
} }
// unreachable statement // unreachable statement
return Node(); return Node();
} }
Function_Descriptor floor_descriptor = extern Signature floor_sig = "floor($value)";
{ "floor", "$value", 0 }; Node floor(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node floor(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node orig(arg(floor_sig, path, line, parameter_names, bindings, 0, Node::numeric));
Node orig(bindings[parameters[0].token()]);
switch (orig.type()) switch (orig.type())
{ {
case Node::numeric_dimension: { case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::floor(orig.numeric_value()), orig.unit()); std::floor(orig.numeric_value()),
orig.unit());
} break; } break;
case Node::number: { case Node::number: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::floor(orig.numeric_value())); std::floor(orig.numeric_value()));
} break; } break;
case Node::numeric_percentage: { case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::floor(orig.numeric_value()), std::floor(orig.numeric_value()),
Node::numeric_percentage); Node::numeric_percentage);
} break; } break;
default: { default: {
throw_eval_error("argument to floor must be numeric", orig.path(), orig.line()); // unreachable
throw_eval_error("argument to floor must be numeric", path, line);
} break; } break;
} }
// unreachable statement // unreachable statement
return Node(); return Node();
} }
Function_Descriptor abs_descriptor = extern Signature abs_sig = "abs($value)";
{ "abs", "$value", 0 }; Node abs(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node abs(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node orig(arg(abs_sig, path, line, parameter_names, bindings, 0, Node::numeric));
Node orig(bindings[parameters[0].token()]);
switch (orig.type()) switch (orig.type())
{ {
case Node::numeric_dimension: { case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::abs(orig.numeric_value()), orig.unit()); std::abs(orig.numeric_value()),
orig.unit());
} break; } break;
case Node::number: { case Node::number: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::abs(orig.numeric_value())); std::abs(orig.numeric_value()));
} break; } break;
case Node::numeric_percentage: { case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(), return new_Node(path, line,
std::abs(orig.numeric_value()), std::abs(orig.numeric_value()),
Node::numeric_percentage); Node::numeric_percentage);
} break; } break;
default: { default: {
throw_eval_error("argument to abs must be numeric", orig.path(), orig.line()); // unreachable
throw_eval_error("argument to abs must be numeric", path, line);
} break; } break;
} }
// unreachable statement // unreachable statement
return Node(); return Node();
} }
////////////////////////////////////////////////////////////////////////
// List Functions ////////////////////////////////////////////////////// // List Functions //////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
Function_Descriptor length_descriptor = extern Signature length_sig = "length($list)";
{ "length", "$list", 0 }; Node length(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node length(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node arg(bindings[parameter_names[0].token()]);
Node arg(bindings[parameters[0].token()]); return new_Node(path, line, arg.type() == Node::list ? arg.size() : 1);
switch (arg.type())
{
case Node::space_list:
case Node::comma_list: {
return new_Node(arg.path(), arg.line(), arg.size());
} break;
case Node::nil: {
return new_Node(arg.path(), arg.line(), 0);
} break;
default: {
// single objects should be reported as lists of length 1
return new_Node(arg.path(), arg.line(), 1);
} break;
}
// unreachable statement
return Node();
} }
Function_Descriptor nth_descriptor = extern Signature nth_sig = "nth($list, $n)";
{ "nth", "$list", "$n", 0 }; Node nth(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node nth(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node l(bindings[parameter_names[0].token()]);
Node l(bindings[parameters[0].token()]);
Node n(bindings[parameters[1].token()]);
if (n.type() != Node::number) {
throw_eval_error("second argument to nth must be a number", n.path(), n.line());
}
if (l.type() == Node::nil) {
throw_eval_error("cannot index into an empty list", l.path(), l.line());
}
// wrap the first arg if it isn't a list // wrap the first arg if it isn't a list
if (l.type() != Node::space_list && l.type() != Node::comma_list) { if (l.type() != Node::list) {
l = new_Node(Node::space_list, l.path(), l.line(), 1) << l; l = new_Node(Node::list, path, line, 1) << l;
} }
double n_prim = n.numeric_value(); if (l.size() == 0) {
if (n_prim < 1 || n_prim > l.size()) { throw_eval_error("cannot index into an empty list", path, line);
throw_eval_error("out of range index for nth", n.path(), n.line());
} }
return l[n_prim - 1]; // 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 const char separator_kwd[] = "$separator"; extern Signature index_sig = "index($list, $value)";
Node join_impl(const Node parameters, map<Token, Node>& bindings, bool has_sep, Node_Factory& new_Node) { Node index(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 lst(bindings[parameter_names[0].token()]);
Node l1(bindings[parameters[0].token()]); Node val(bindings[parameter_names[1].token()]);
if (l1.type() != Node::space_list && l1.type() != Node::comma_list && l1.type() != Node::nil) { // if $list isn't a list, wrap it in a singleton list
l1 = new_Node(Node::space_list, l1.path(), l1.line(), 1) << l1; 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);
} }
Node l2(bindings[parameters[1].token()]);
if (l2.type() != Node::space_list && l2.type() != Node::comma_list && l2.type() != Node::nil) { return new_Node(Node::boolean, path, line, false);
l2 = new_Node(Node::space_list, l2.path(), l2.line(), 1) << l2; }
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);
} }
// nil + nil = nil Node l2(bindings[parameter_names[1].token()]);
if (l1.type() == Node::nil && l2.type() == Node::nil) { if (l2.type() != Node::list) {
return new_Node(Node::nil, l1.path(), l1.line(), 0); l2 = (new_Node(Node::list, path, line, 1) << l2);
} }
// figure out the combined size in advance // figure out the combined size in advance
size_t size = 0; size_t size = l1.size() + l2.size();
if (l1.type() != Node::nil) size += l1.size();
if (l2.type() != Node::nil) size += l2.size();
// figure out the result type in advance // figure out the result type in advance
Node::Type rtype = Node::space_list; bool comma_sep;
if (has_sep) { string sep(bindings[parameter_names[2].token()].token().unquote());
string sep(bindings[parameters[2].token()].token().unquote());
if (sep == "comma") rtype = Node::comma_list; if (sep == "comma") comma_sep = true;
else if (sep == "space") rtype = Node::space_list; else if (sep == "space") comma_sep = false;
else if (sep == "auto") rtype = l1.type(); else if (sep == "auto") comma_sep = l1.is_comma_separated();
else { else throw_eval_error("third argument to 'join' must be 'space', 'comma', or 'auto'", path, line);
throw_eval_error("third argument to join must be 'space', 'comma', or 'auto'", l2.path(), l2.line());
} if (l1.size() == 0) comma_sep = l2.is_comma_separated();
}
else if (l1.type() != Node::nil) rtype = l1.type();
else if (l2.type() != Node::nil) rtype = l2.type();
// accumulate the result // accumulate the result
Node lr(new_Node(rtype, l1.path(), l1.line(), size)); Node lr(new_Node(Node::list, path, line, size));
if (l1.type() != Node::nil) lr += l1; lr += l1;
if (l2.type() != Node::nil) lr += l2; lr += l2;
lr.is_comma_separated() = comma_sep;
return lr; return lr;
} }
Function_Descriptor join_2_descriptor =
{ "join 2", "$list1", "$list2", 0 };
Node join_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return join_impl(parameters, bindings, false, new_Node);
}
Function_Descriptor join_3_descriptor =
{ "join 3", "$list1", "$list2", "$separator", 0 };
Node join_3(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return join_impl(parameters, bindings, true, new_Node);
}
Node append_impl(const Node parameters, map<Token, Node>& bindings, bool has_sep, Node_Factory& new_Node) { extern Signature append_sig = "append($list1, $list2, $separator: auto)";
Node list(bindings[parameters[0].token()]); Node append(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
switch (list.type()) Node list(bindings[parameter_names[0].token()]);
{
case Node::space_list: // if the first arg isn't a list, wrap it in a singleton
case Node::comma_list: if (list.type() != Node::list) list = (new_Node(Node::list, path, line, 1) << list);
case Node::nil: {
// do nothing bool comma_sep;
} break; string sep(bindings[parameter_names[2].token()].token().unquote());
// if the first arg isn't a list, wrap it in a singleton
default: { if (sep == "comma") comma_sep = true;
list = (new_Node(Node::space_list, list.path(), list.line(), 1) << list); else if (sep == "space") comma_sep = false;
} break; 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::Type sep_type = list.type();
if (has_sep) { Node new_list(new_Node(Node::list, path, line, list.size() + 1));
string sep_string = bindings[parameters[2].token()].token().unquote();
if (sep_string == "comma") sep_type = Node::comma_list;
else if (sep_string == "space") sep_type = Node::space_list;
else if (sep_string == "auto") sep_type = list.type();
else throw_eval_error("third argument to append must be 'space', 'comma', or 'auto'", list.path(), list.line());
}
Node new_list(new_Node(sep_type, list.path(), list.line(), list.size() + 1));
new_list += list; new_list += list;
new_list << bindings[parameters[1].token()]; new_list << bindings[parameter_names[1].token()];
new_list.is_comma_separated() = comma_sep;
return new_list; return new_list;
} }
Function_Descriptor append_2_descriptor = extern Signature compact_1_sig = "compact($arg1)";
{ "append 2", "$list", "$val", 0 }; Node compact_1(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node append_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node the_arg(bindings[parameter_names[0].token()]);
return append_impl(parameters, bindings, false, new_Node);
}
Function_Descriptor append_3_descriptor =
{ "append 3", "$list", "$val", "$separator", 0 };
Node append_3(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return append_impl(parameters, bindings, true, new_Node);
}
Node compact(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { if (the_arg.type() == Node::list) {
size_t num_args = bindings.size(); Node non_nils(new_Node(Node::list, path, line, 0));
Node::Type sep_type = Node::comma_list; for (size_t i = 0, S = the_arg.size(); i < S; ++i) {
Node list; Node elt(the_arg[i]);
Node arg1(bindings[parameters[0].token()]); if (!elt.is_false()) non_nils << elt;
if (num_args == 1 && (arg1.type() == Node::space_list ||
arg1.type() == Node::comma_list ||
arg1.type() == Node::nil)) {
list = new_Node(arg1.type(), arg1.path(), arg1.line(), arg1.size());
list += arg1;
}
else {
list = new_Node(sep_type, arg1.path(), arg1.line(), num_args);
for (size_t i = 0; i < num_args; ++i) {
list << bindings[parameters[i].token()];
} }
return non_nils;
} }
Node new_list(new_Node(list.type(), list.path(), list.line(), 0));
for (size_t i = 0, S = list.size(); i < S; ++i) { return new_Node(Node::list, path, line, 1) << the_arg;
if ((list[i].type() != Node::boolean) || list[i].boolean_value()) { }
new_list << list[i];
} 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 new_list.size() ? new_list : new_Node(Node::nil, list.path(), list.line(), 0);
}
Function_Descriptor compact_1_descriptor =
{ "compact 1", "$arg1", 0 };
Function_Descriptor compact_2_descriptor =
{ "compact 2", "$arg1", "$arg2", 0 };
Function_Descriptor compact_3_descriptor =
{ "compact 3", "$arg1", "$arg2", "$arg3", 0 };
Function_Descriptor compact_4_descriptor =
{ "compact 4", "$arg1", "$arg2", "$arg3", "$arg4", 0 };
Function_Descriptor compact_5_descriptor =
{ "compact 5", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5", 0 };
Function_Descriptor compact_6_descriptor =
{ "compact 6", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", 0 };
Function_Descriptor compact_7_descriptor =
{ "compact 7", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", 0 };
Function_Descriptor compact_8_descriptor =
{ "compact 8", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", "$arg8", 0 };
Function_Descriptor compact_9_descriptor =
{ "compact 9", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", "$arg8", "$arg9", 0 };
Function_Descriptor compact_10_descriptor =
{ "compact 10", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", "$arg8", "$arg9", "$arg10", 0 };
Function_Descriptor compact_11_descriptor =
{ "compact 11", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5", "$arg6",
"$arg7", "$arg8", "$arg9", "$arg10", "$arg11", 0 };
Function_Descriptor compact_12_descriptor =
{ "compact 12", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5", "arg6",
"$arg7", "$arg8", "$arg9", "$arg10", "$arg11", "$arg12", 0 };
return non_nils;
}
////////////////////////////////////////////////////////////////////////
// Introspection Functions ///////////////////////////////////////////// // Introspection Functions /////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
extern const char number_name[] = "number"; extern Signature type_of_sig = "type-of($value)";
extern const char string_name[] = "string"; Node type_of(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
extern const char bool_name[] = "bool"; Node val(bindings[parameter_names[0].token()]);
extern const char color_name[] = "color";
extern const char list_name[] = "list";
Function_Descriptor type_of_descriptor =
{ "type-of", "$value", 0 };
Node type_of(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0].token()]);
Token type_name; Token type_name;
switch (val.type()) switch (val.type())
{ {
...@@ -646,129 +1034,134 @@ namespace Sass { ...@@ -646,129 +1034,134 @@ namespace Sass {
case Node::numeric_color: { case Node::numeric_color: {
type_name = Token::make(color_name); type_name = Token::make(color_name);
} break; } break;
case Node::comma_list: case Node::list: {
case Node::space_list:
case Node::nil: {
type_name = Token::make(list_name); type_name = Token::make(list_name);
} break; } break;
default: { default: {
type_name = Token::make(string_name); type_name = Token::make(string_name);
} break; } break;
} }
Node type(new_Node(Node::string_constant, val.path(), val.line(), type_name)); return new_Node(Node::identifier, path, line, type_name);
type.is_unquoted() = true;
return type;
} }
extern const char empty_str[] = ""; extern Signature unit_sig = "unit($number)";
extern const char percent_str[] = "%"; 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));
Function_Descriptor unit_descriptor =
{ "unit", "$number", 0 };
Node unit(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0].token()]);
switch (val.type()) switch (val.type())
{ {
case Node::number: { case Node::number: {
return new_Node(Node::string_constant, val.path(), val.line(), Token::make(empty_str)); Node u(new_Node(Node::string_constant, path, line, Token::make(empty_str)));
u.is_quoted() = true;
return u;
} break; } break;
case Node::numeric_dimension: case Node::numeric_dimension:
case Node::numeric_percentage: { case Node::numeric_percentage: {
return new_Node(Node::string_constant, val.path(), val.line(), val.unit()); Node u(new_Node(Node::string_constant, path, line, val.unit()));
u.is_quoted() = true;
return u;
} break; } break;
// unreachable
default: { default: {
throw_eval_error("argument to unit must be numeric", val.path(), val.line()); throw_eval_error("argument to 'unit' must be numeric", path, line);
} break; } break;
} }
// unreachable statement // unreachable statement
return Node(); return Node();
} }
extern const char true_str[] = "true"; extern Signature unitless_sig = "unitless($number)";
extern const char false_str[] = "false"; 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));
Function_Descriptor unitless_descriptor =
{ "unitless", "$number", 0 };
Node unitless(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0].token()]);
switch (val.type()) switch (val.type())
{ {
case Node::number: { case Node::number: {
return new_Node(Node::boolean, val.path(), val.line(), true); return new_Node(Node::boolean, path, line, true);
} break; } break;
case Node::numeric_percentage: case Node::numeric_percentage:
case Node::numeric_dimension: { case Node::numeric_dimension: {
return new_Node(Node::boolean, val.path(), val.line(), false); return new_Node(Node::boolean, path, line, false);
} break; } break;
// unreachable
default: { default: {
throw_eval_error("argument to unitless must be numeric", val.path(), val.line()); throw_eval_error("argument to 'unitless' must be numeric", path, line);
} break; } break;
} }
// unreachable statement // unreachable statement
return Node(); return Node();
} }
Function_Descriptor comparable_descriptor = extern Signature comparable_sig = "comparable($number-1, $number-2)";
{ "comparable", "$number_1", "$number_2", 0 }; Node comparable(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node comparable(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node n1(arg(comparable_sig, path, line, parameter_names, bindings, 0, Node::numeric));
Node n1(bindings[parameters[0].token()]); Node n2(arg(comparable_sig, path, line, parameter_names, bindings, 1, Node::numeric));
Node n2(bindings[parameters[1].token()]);
Node::Type t1 = n1.type(); Node::Type t1 = n1.type();
Node::Type t2 = n2.type(); Node::Type t2 = n2.type();
if ((t1 == Node::number && n2.is_numeric()) || if ((t1 == Node::number && n2.is_numeric()) ||
(n1.is_numeric() && t2 == Node::number)) { (n1.is_numeric() && t2 == Node::number)) {
return new_Node(Node::boolean, n1.path(), n1.line(), true); return new_Node(Node::boolean, path, line, true);
} }
else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) { else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) {
return new_Node(Node::boolean, n1.path(), n1.line(), true); return new_Node(Node::boolean, path, line, true);
} }
else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) { else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) {
string u1(n1.unit().to_string()); string u1(n1.unit().to_string());
string u2(n2.unit().to_string()); string u2(n2.unit().to_string());
if ((u1 == "ex" && u2 == "ex") || if ((u1 == "ex" && u2 == "ex") ||
(u1 == "em" && u2 == "em") || (u1 == "em" && u2 == "em") ||
((u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") && ((u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc" || u1 == "px") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc"))) { (u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc" || u2 == "px"))) {
return new_Node(Node::boolean, n1.path(), n1.line(), true); return new_Node(Node::boolean, path, line, true);
} }
else { else {
return new_Node(Node::boolean, n1.path(), n1.line(), false); return new_Node(Node::boolean, path, line, false);
} }
} }
else if (!n1.is_numeric() && !n2.is_numeric()) {
throw_eval_error("arguments to comparable must be numeric", n1.path(), n1.line());
}
// default to false if we missed anything // default to false if we missed anything
return new_Node(Node::boolean, n1.path(), n1.line(), false); return new_Node(Node::boolean, path, line, false);
} }
////////////////////////////////////////////////////////////////////////
// Boolean Functions /////////////////////////////////////////////////// // Boolean Functions ///////////////////////////////////////////////////
Function_Descriptor not_descriptor = ////////////////////////////////////////////////////////////////////////
{ "not", "$value", 0 };
Node not_impl(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { extern Signature not_sig = "not($value)";
Node val(bindings[parameters[0].token()]); Node not_impl(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
if (val.type() == Node::boolean && val.boolean_value() == false) { Node val(bindings[parameter_names[0].token()]);
return new_Node(Node::boolean, val.path(), val.line(), true); if (val.is_false()) return new_Node(Node::boolean, path, line, true);
} return new_Node(Node::boolean, path, line, false);
else {
return new_Node(Node::boolean, val.path(), val.line(), false);
}
} }
Function_Descriptor if_descriptor = extern Signature if_sig = "if($condition, $if-true, $if-false)";
{ "if", "$predicate", "$consequent", "$alternative", 0 }; Node if_impl(const Node parameter_names, Environment& bindings, Node_Factory& new_Node, string& path, size_t line) {
Node if_impl(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node) { Node predicate(bindings[parameter_names[0].token()]);
Node predicate(bindings[parameters[0].token()]); Node consequent(bindings[parameter_names[1].token()]);
Node consequent(bindings[parameters[1].token()]); Node alternative(bindings[parameter_names[2].token()]);
Node alternative(bindings[parameters[2].token()]);
if (predicate.type() == Node::boolean && predicate.boolean_value() == false) return alternative; if (predicate.is_false()) return alternative;
return consequent; 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;
}
} }
} }
#define SASS_FUNCTIONS
#include <cstring> #include <cstring>
#include <map> #include <map>
#ifndef SASS_NODE_INCLUDED #ifndef SASS_NODE
#include "node.hpp" #include "node.hpp"
#endif #endif
namespace Sass { namespace Sass {
struct Environment;
struct Context;
struct Node_Factory;
using std::map; using std::map;
typedef Node (*Primitive)(const Node, map<Token, Node>&, Node_Factory& new_Node); typedef Node (*Primitive)(const Node, Environment&, Node_Factory&, string&, size_t);
typedef const char* str; typedef const char Signature[];
typedef str Function_Descriptor[];
struct Function { struct Function {
string name; string name;
// vector<Token> parameters;
Node parameters; Node parameters;
Node parameter_names;
Node definition; Node definition;
Primitive primitive; Primitive primitive;
bool overloaded; bool overloaded;
...@@ -24,6 +29,7 @@ namespace Sass { ...@@ -24,6 +29,7 @@ namespace Sass {
Function() Function()
{ /* TO DO: set up the generic callback here */ } { /* TO DO: set up the generic callback here */ }
// for user-defined functions
Function(Node def) Function(Node def)
: name(def[0].to_string()), : name(def[0].to_string()),
parameters(def[1]), parameters(def[1]),
...@@ -40,26 +46,12 @@ namespace Sass { ...@@ -40,26 +46,12 @@ namespace Sass {
primitive(0), primitive(0),
overloaded(overloaded) overloaded(overloaded)
{ } { }
Function(Function_Descriptor d, Primitive ip, Node_Factory& new_Node) Function(char* signature, Primitive ip, Context& ctx);
: name(d[0]),
parameters(new_Node(Node::parameters, "[PRIMITIVE FUNCTIONS]", 0, 0)), Node operator()(Environment& bindings, Node_Factory& new_Node, string& path, size_t line) const
definition(Node()),
primitive(ip),
overloaded(false)
{
size_t len = 0;
while (d[len+1]) ++len;
for (size_t i = 0; i < len; ++i) {
const char* p = d[i+1];
parameters.push_back(new_Node(Node::variable, "[PRIMITIVE FUNCTIONS]", 0, Token::make(p, p + std::strlen(p))));
}
}
Node operator()(map<Token, Node>& bindings, Node_Factory& new_Node) const
{ {
if (primitive) return primitive(parameters, bindings, new_Node); if (primitive) return primitive(parameters, bindings, new_Node, path, line);
else return Node(); else return Node();
} }
...@@ -69,136 +61,172 @@ namespace Sass { ...@@ -69,136 +61,172 @@ namespace Sass {
// RGB Functions /////////////////////////////////////////////////////// // RGB Functions ///////////////////////////////////////////////////////
extern Function_Descriptor rgb_descriptor; extern Signature rgb_sig;
Node rgb(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node rgb(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor rgba_4_descriptor; extern Signature rgba_4_sig;
Node rgba_4(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node rgba_4(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor rgba_2_descriptor; extern Signature rgba_2_sig;
Node rgba_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node rgba_2(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor red_descriptor; extern Signature red_sig;
Node red(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node red(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor green_descriptor; extern Signature green_sig;
Node green(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node green(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor blue_descriptor; extern Signature blue_sig;
Node blue(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node blue(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor mix_2_descriptor; extern Signature mix_sig;
Node mix_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node mix(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor mix_3_descriptor;
Node mix_3(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// HSL Functions /////////////////////////////////////////////////////// // HSL Functions ///////////////////////////////////////////////////////
extern Function_Descriptor hsla_descriptor; extern Signature hsl_sig;
Node hsla(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node hsl(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature hsla_sig;
Node hsla(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor hsl_descriptor; extern Signature hue_sig;
Node hsl(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node hue(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature saturation_sig;
Node saturation(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature lightness_sig;
Node lightness(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature adjust_hue_sig;
Node adjust_hue(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature lighten_sig;
Node lighten(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor invert_descriptor; extern Signature darken_sig;
Node invert(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node darken(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature saturate_sig;
Node saturate(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature desaturate_sig;
Node desaturate(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature grayscale_sig;
Node grayscale(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature complement_sig;
Node complement(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature invert_sig;
Node invert(const Node, Environment&, Node_Factory&, string& path, size_t line);
// Opacity Functions /////////////////////////////////////////////////// // Opacity Functions ///////////////////////////////////////////////////
extern Function_Descriptor alpha_descriptor; extern Signature alpha_sig;
extern Function_Descriptor opacity_descriptor; Node alpha(const Node, Environment&, Node_Factory&, string& path, size_t line);
Node alpha(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Signature opacity_sig;
Node opacity(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor opacify_descriptor; extern Signature opacify_sig;
extern Function_Descriptor fade_in_descriptor; Node opacify(const Node, Environment&, Node_Factory&, string& path, size_t line);
Node opacify(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Signature fade_in_sig;
Node fade_in(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor transparentize_descriptor; extern Signature transparentize_sig;
extern Function_Descriptor fade_out_descriptor; Node transparentize(const Node, Environment&, Node_Factory&, string& path, size_t line);
Node transparentize(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Signature fade_out_sig;
Node fade_out(const Node, Environment&, Node_Factory&, string& path, size_t line);
// Other Color Functions ///////////////////////////////////////////////
extern Signature adjust_color_sig;
Node adjust_color(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature scale_color_sig;
Node scale_color(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature change_color_sig;
Node change_color(const Node, Environment&, Node_Factory&, string& path, size_t line);
// String Functions //////////////////////////////////////////////////// // String Functions ////////////////////////////////////////////////////
extern Function_Descriptor unquote_descriptor; extern Signature unquote_sig;
Node unquote(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node unquote(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor quote_descriptor; extern Signature quote_sig;
Node quote(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node quote(const Node, Environment&, Node_Factory&, string& path, size_t line);
// Number Functions //////////////////////////////////////////////////// // Number Functions ////////////////////////////////////////////////////
extern Function_Descriptor percentage_descriptor; extern Signature percentage_sig;
Node percentage(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node percentage(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor round_descriptor; extern Signature round_sig;
Node round(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node round(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor ceil_descriptor; extern Signature ceil_sig;
Node ceil(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node ceil(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor floor_descriptor; extern Signature floor_sig;
Node floor(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node floor(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor abs_descriptor; extern Signature abs_sig;
Node abs(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node abs(const Node, Environment&, Node_Factory&, string& path, size_t line);
// List Functions ////////////////////////////////////////////////////// // List Functions //////////////////////////////////////////////////////
extern Function_Descriptor length_descriptor; extern Signature length_sig;
Node length(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node length(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor nth_descriptor; extern Signature nth_sig;
Node nth(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node nth(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor join_2_descriptor; extern Signature index_sig;
Node join_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node index(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor join_3_descriptor;
Node join_3(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor append_2_descriptor; extern Signature join_sig;
Node append_2(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node join(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor append_3_descriptor; extern Signature append_sig;
Node append_3(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node append(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature compact_1_sig;
Node compact_1(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Signature compact_n_sig;
Node compact_n(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor compact_1_descriptor;
extern Function_Descriptor compact_2_descriptor;
extern Function_Descriptor compact_3_descriptor;
extern Function_Descriptor compact_4_descriptor;
extern Function_Descriptor compact_5_descriptor;
extern Function_Descriptor compact_6_descriptor;
extern Function_Descriptor compact_7_descriptor;
extern Function_Descriptor compact_8_descriptor;
extern Function_Descriptor compact_9_descriptor;
extern Function_Descriptor compact_10_descriptor;
extern Function_Descriptor compact_11_descriptor;
extern Function_Descriptor compact_12_descriptor;
Node compact(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Introspection Functions ///////////////////////////////////////////// // Introspection Functions /////////////////////////////////////////////
extern Function_Descriptor type_of_descriptor; extern Signature type_of_sig;
Node type_of(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node type_of(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor unit_descriptor; extern Signature unit_sig;
Node unit(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node unit(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor unitless_descriptor; extern Signature unitless_sig;
Node unitless(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node unitless(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor comparable_descriptor; extern Signature comparable_sig;
Node comparable(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node comparable(const Node, Environment&, Node_Factory&, string& path, size_t line);
// Boolean Functions /////////////////////////////////////////////////// // Boolean Functions ///////////////////////////////////////////////////
extern Function_Descriptor not_descriptor; extern Signature not_sig;
Node not_impl(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node not_impl(const Node, Environment&, Node_Factory&, string& path, size_t line);
extern Function_Descriptor if_descriptor; extern Signature if_sig;
Node if_impl(const Node parameters, map<Token, Node>& bindings, Node_Factory& new_Node); Node if_impl(const Node, Environment&, Node_Factory&, string& path, size_t line);
// Path Functions //////////////////////////////////////////////////////
extern Signature image_url_sig;
Node image_url(const Node, Environment&, Node_Factory&, string& path, size_t line);
} }
} }
#include <sstream> #include <sstream>
#include <algorithm> #include <algorithm>
#include <iostream>
#include "node.hpp" #include "node.hpp"
#include "constants.hpp"
#include "error.hpp" #include "error.hpp"
#include <iostream>
namespace Sass { namespace Sass {
using namespace std; using namespace std;
using namespace Constants;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Node method implementations // Node method implementations
...@@ -16,8 +18,9 @@ namespace Sass { ...@@ -16,8 +18,9 @@ namespace Sass {
switch (type()) switch (type())
{ {
case block: case block:
case expansion: case mixin_call:
case root: case root:
case if_directive:
case for_through_directive: case for_through_directive:
case for_to_directive: case for_to_directive:
case each_directive: case each_directive:
...@@ -31,8 +34,9 @@ namespace Sass { ...@@ -31,8 +34,9 @@ namespace Sass {
for (size_t i = 0; i < size(); ++i) { for (size_t i = 0; i < size(); ++i) {
switch (at(i).type()) switch (at(i).type())
{ {
case expansion: case mixin_call:
case block: case block:
case if_directive:
case for_through_directive: case for_through_directive:
case for_to_directive: case for_to_directive:
case each_directive: case each_directive:
...@@ -57,6 +61,18 @@ namespace Sass { ...@@ -57,6 +61,18 @@ namespace Sass {
string Node::unquote() const string Node::unquote() const
{ {
switch (type())
{
case string_constant:
case identifier: {
return token().unquote();
} break;
default: {
// do nothing; fall though to the rest
} break;
}
string intermediate(to_string()); string intermediate(to_string());
if (!intermediate.empty() && (intermediate[0] == '"' || intermediate[0] == '\'')) { if (!intermediate.empty() && (intermediate[0] == '"' || intermediate[0] == '\'')) {
return intermediate.substr(1, intermediate.length() - 2); return intermediate.substr(1, intermediate.length() - 2);
...@@ -70,8 +86,11 @@ namespace Sass { ...@@ -70,8 +86,11 @@ namespace Sass {
{ {
Type t = type(), u = rhs.type(); Type t = type(), u = rhs.type();
if ((t == identifier || t == string_constant || t == string_schema || t == concatenation) && // if ((t == identifier || t == string_constant || t == string_schema || t == concatenation) &&
(u == identifier || u == string_constant || u == string_schema || u == concatenation)) { // (u == identifier || u == string_constant || u == string_schema || u == concatenation)) {
// return unquote() == rhs.unquote();
// }
if (is_string() && rhs.is_string()) {
return unquote() == rhs.unquote(); return unquote() == rhs.unquote();
} }
else if (t != u) { else if (t != u) {
...@@ -80,12 +99,12 @@ namespace Sass { ...@@ -80,12 +99,12 @@ namespace Sass {
switch (t) switch (t)
{ {
case comma_list: case list:
case space_list:
case expression: case expression:
case term: case term:
case numeric_color: { case numeric_color: {
if (size() != rhs.size()) return false; if (size() != rhs.size()) return false;
if ((t == list) && (is_comma_separated() != rhs.is_comma_separated())) return false;
for (size_t i = 0, L = size(); i < L; ++i) { for (size_t i = 0, L = size(); i < L; ++i) {
if (at(i) == rhs[i]) continue; if (at(i) == rhs[i]) continue;
else return false; else return false;
...@@ -122,8 +141,25 @@ namespace Sass { ...@@ -122,8 +141,25 @@ namespace Sass {
return boolean_value() == rhs.boolean_value(); return boolean_value() == rhs.boolean_value();
} break; } break;
case selector: {
if (has_children() && rhs.has_children() && (size() == rhs.size())) {
for (size_t i = 0, S = size(); i < S; ++i) {
if (at(i) == rhs[i]) continue;
else return false;
}
return true;
}
else {
return false;
}
} break;
case simple_selector: {
if (token() == rhs.token()) return true;
} break;
default: { default: {
return true; return false;
} break; } break;
} }
return false; return false;
...@@ -159,12 +195,17 @@ namespace Sass { ...@@ -159,12 +195,17 @@ namespace Sass {
} }
// comparing identifiers and strings (treat them as comparable) // comparing identifiers and strings (treat them as comparable)
else if ((lhs_type == identifier || lhs_type == string_constant || lhs_type == value) && else if ((is_string() && rhs.is_string()) ||
(rhs_type == identifier || lhs_type == string_constant || rhs_type == value)) { (lhs_type == value && rhs_type == value)) {
return token().unquote() < rhs.token().unquote(); return unquote() < rhs.unquote();
} }
// COMPARING SELECTORS -- IMPORTANT FOR INHERITANCE // else if ((lhs_type == identifier || lhs_type == string_constant || lhs_type == value) &&
// (rhs_type == identifier || lhs_type == string_constant || rhs_type == value)) {
// return token().unquote() < rhs.token().unquote();
// }
// COMPARING SELECTORS -- IMPORTANT FOR ORDERING AND NORMALIZING
else if ((type() >= selector_group && type() <=selector_schema) && else if ((type() >= selector_group && type() <=selector_schema) &&
(rhs.type() >= selector_group && rhs.type() <=selector_schema)) { (rhs.type() >= selector_group && rhs.type() <=selector_schema)) {
...@@ -179,13 +220,15 @@ namespace Sass { ...@@ -179,13 +220,15 @@ namespace Sass {
return token() < rhs.token(); return token() < rhs.token();
} break; } break;
// assumes selectors are normalized by the time they're compared
case selector: case selector:
case attribute_selector: { case simple_selector_sequence:
case attribute_selector:
case functional_pseudo:
case pseudo_negation: {
return lexicographical_compare(begin(), end(), rhs.begin(), rhs.end()); return lexicographical_compare(begin(), end(), rhs.begin(), rhs.end());
} break; } break;
default: { default: {
return false; return false;
} break; } break;
...@@ -340,8 +383,6 @@ namespace Sass { ...@@ -340,8 +383,6 @@ namespace Sass {
return 0; return 0;
} }
extern const char percent_str[] = "%";
extern const char empty_str[] = "";
Token Node_Impl::unit() Token Node_Impl::unit()
{ {
switch (type) switch (type)
......
#define SASS_NODE_INCLUDED #define SASS_NODE
#include <cstring> #include <cstring>
#include <string> #include <string>
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace Sass { namespace Sass {
using namespace std; using namespace std;
// Token type for representing lexed chunks of text
struct Token { struct Token {
const char* begin; const char* begin;
const char* end; const char* end;
...@@ -61,6 +62,7 @@ namespace Sass { ...@@ -61,6 +62,7 @@ namespace Sass {
struct Node_Impl; struct Node_Impl;
// Node type for representing SCSS expression nodes. Really just a handle.
class Node { class Node {
private: private:
friend class Node_Factory; friend class Node_Factory;
...@@ -69,6 +71,9 @@ namespace Sass { ...@@ -69,6 +71,9 @@ namespace Sass {
public: public:
enum Type { enum Type {
none, none,
any,
numeric, // number, numeric_percentage, or numeric_dimension
string_t, // string_constant, identifier, concatenation, schemata
comment, comment,
root, root,
...@@ -98,9 +103,7 @@ namespace Sass { ...@@ -98,9 +103,7 @@ namespace Sass {
rule, rule,
property, property,
nil, list,
comma_list,
space_list,
disjunction, disjunction,
conjunction, conjunction,
...@@ -128,7 +131,6 @@ namespace Sass { ...@@ -128,7 +131,6 @@ namespace Sass {
value, value,
identifier, identifier,
uri, uri,
image_url,
textual_percentage, textual_percentage,
textual_dimension, textual_dimension,
textual_number, textual_number,
...@@ -148,13 +150,15 @@ namespace Sass { ...@@ -148,13 +150,15 @@ namespace Sass {
identifier_schema, identifier_schema,
css_import, css_import,
function,
function_call, function_call,
mixin, mixin,
function, mixin_call,
parameters, parameters,
expansion,
arguments, arguments,
extend_directive,
if_directive, if_directive,
for_through_directive, for_through_directive,
for_to_directive, for_to_directive,
...@@ -175,6 +179,7 @@ namespace Sass { ...@@ -175,6 +179,7 @@ namespace Sass {
Node(Node_Impl* ip = 0); Node(Node_Impl* ip = 0);
Type type() const; Type type() const;
Type type(Type);
bool has_children() const; bool has_children() const;
bool has_statements() const; bool has_statements() const;
...@@ -183,11 +188,14 @@ namespace Sass { ...@@ -183,11 +188,14 @@ namespace Sass {
bool has_backref() const; bool has_backref() const;
bool from_variable() const; bool from_variable() const;
bool& should_eval() const; bool& should_eval() const;
bool& is_unquoted() const; // for strings bool& is_quoted() const;
bool& is_quoted() const; // for identifiers
bool is_numeric() const; bool is_numeric() const;
bool is_string() const; // for all string-like types
bool is_schema() const; // for all interpolated data
bool is_guarded() const; bool is_guarded() const;
bool& has_been_extended() const; bool& has_been_extended() const;
bool is_false() const;
bool& is_comma_separated() const;
string& path() const; string& path() const;
size_t line() const; size_t line() const;
...@@ -198,6 +206,7 @@ namespace Sass { ...@@ -198,6 +206,7 @@ namespace Sass {
Node& back() const; Node& back() const;
Node& operator[](size_t i) const; Node& operator[](size_t i) const;
void pop_back(); void pop_back();
void pop_all();
Node& push_back(Node n); Node& push_back(Node n);
Node& push_front(Node n); Node& push_front(Node n);
Node& operator<<(Node n); Node& operator<<(Node n);
...@@ -214,7 +223,7 @@ namespace Sass { ...@@ -214,7 +223,7 @@ namespace Sass {
Token token() const; Token token() const;
Token unit() const; Token unit() const;
bool is_null_ptr() const { return !ip_; } bool is_null() const { return !ip_; }
bool is(Node n) const { return ip_ == n.ip_; } bool is(Node n) const { return ip_ == n.ip_; }
void flatten(); void flatten();
...@@ -236,6 +245,7 @@ namespace Sass { ...@@ -236,6 +245,7 @@ namespace Sass {
}; };
// The actual implementation object for Nodes; Node handles point at these.
struct Node_Impl { struct Node_Impl {
union value_t { union value_t {
bool boolean; bool boolean;
...@@ -259,9 +269,9 @@ namespace Sass { ...@@ -259,9 +269,9 @@ namespace Sass {
bool has_backref; bool has_backref;
bool from_variable; bool from_variable;
bool should_eval; bool should_eval;
bool is_unquoted;
bool is_quoted; bool is_quoted;
bool has_been_extended; bool has_been_extended;
bool is_comma_separated;
Node_Impl() Node_Impl()
: /* value(value_t()), : /* value(value_t()),
...@@ -276,14 +286,53 @@ namespace Sass { ...@@ -276,14 +286,53 @@ namespace Sass {
has_backref(false), has_backref(false),
from_variable(false), from_variable(false),
should_eval(false), should_eval(false),
is_unquoted(false), // for strings is_quoted(false),
is_quoted(false), // for identifiers -- yeah, it's hacky for now has_been_extended(false),
has_been_extended(false) is_comma_separated(false)
{ } { }
bool is_numeric() bool is_numeric()
{ return type >= Node::number && type <= Node::numeric_dimension; } { return type >= Node::number && type <= Node::numeric_dimension; }
bool is_string()
{
switch (type)
{
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 true;
} break;
default: {
return false;
} break;
}
return false;
}
bool is_schema()
{
switch (type)
{
case Node::selector_schema:
case Node::value_schema:
case Node::string_schema:
case Node::identifier_schema: {
return true;
} break;
default: {
return false;
} break;
}
return false;
}
size_t size() size_t size()
{ return children.size(); } { return children.size(); }
...@@ -323,7 +372,7 @@ namespace Sass { ...@@ -323,7 +372,7 @@ namespace Sass {
case Node::for_to_directive: case Node::for_to_directive:
case Node::each_directive: case Node::each_directive:
case Node::while_directive: case Node::while_directive:
case Node::expansion: { case Node::mixin_call: {
has_expansions = true; has_expansions = true;
} break; } break;
...@@ -355,7 +404,7 @@ namespace Sass { ...@@ -355,7 +404,7 @@ namespace Sass {
case Node::for_to_directive: case Node::for_to_directive:
case Node::each_directive: case Node::each_directive:
case Node::while_directive: case Node::while_directive:
case Node::expansion: has_expansions = true; break; case Node::mixin_call: has_expansions = true; break;
case Node::backref: has_backref = true; break; case Node::backref: has_backref = true; break;
...@@ -367,6 +416,9 @@ namespace Sass { ...@@ -367,6 +416,9 @@ namespace Sass {
void pop_back() void pop_back()
{ children.pop_back(); } { children.pop_back(); }
void pop_all()
{ for (size_t i = 0, S = size(); i < S; ++i) pop_back(); }
bool& boolean_value() bool& boolean_value()
{ return value.boolean; } { return value.boolean; }
...@@ -382,9 +434,10 @@ namespace Sass { ...@@ -382,9 +434,10 @@ namespace Sass {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
inline Node::Node(Node_Impl* ip) : ip_(ip) { } inline Node::Node(Node_Impl* ip) : ip_(ip) { }
inline Node::Type Node::type() const { return ip_->type; } inline Node::Type Node::type() const { return ip_->type; }
inline Node::Type Node::type(Type t) { return ip_->type = t; }
inline bool Node::has_children() const { return ip_->has_children; } inline bool Node::has_children() const { return ip_->has_children; }
inline bool Node::has_statements() const { return ip_->has_statements; } inline bool Node::has_statements() const { return ip_->has_statements; }
inline bool Node::has_blocks() const { return ip_->has_blocks; } inline bool Node::has_blocks() const { return ip_->has_blocks; }
...@@ -392,11 +445,14 @@ namespace Sass { ...@@ -392,11 +445,14 @@ namespace Sass {
inline bool Node::has_backref() const { return ip_->has_backref; } inline bool Node::has_backref() const { return ip_->has_backref; }
inline bool Node::from_variable() const { return ip_->from_variable; } inline bool Node::from_variable() const { return ip_->from_variable; }
inline bool& Node::should_eval() const { return ip_->should_eval; } inline bool& Node::should_eval() const { return ip_->should_eval; }
inline bool& Node::is_unquoted() const { return ip_->is_unquoted; }
inline bool& Node::is_quoted() const { return ip_->is_quoted; } inline bool& Node::is_quoted() const { return ip_->is_quoted; }
inline bool Node::is_numeric() const { return ip_->is_numeric(); } inline bool Node::is_numeric() const { return ip_->is_numeric(); }
inline bool Node::is_string() const { return ip_->is_string(); }
inline bool Node::is_schema() const { return ip_->is_schema(); }
inline bool Node::is_guarded() const { return (type() == assignment) && (size() == 3); } inline bool Node::is_guarded() const { return (type() == assignment) && (size() == 3); }
inline bool& Node::has_been_extended() const { return ip_->has_been_extended; } inline bool& Node::has_been_extended() const { return ip_->has_been_extended; }
inline bool Node::is_false() const { return (type() == boolean) && (boolean_value() == false); }
inline bool& Node::is_comma_separated() const { return ip_->is_comma_separated; }
inline string& Node::path() const { return ip_->path; } inline string& Node::path() const { return ip_->path; }
inline size_t Node::line() const { return ip_->line; } inline size_t Node::line() const { return ip_->line; }
...@@ -407,6 +463,7 @@ namespace Sass { ...@@ -407,6 +463,7 @@ namespace Sass {
inline Node& Node::back() const { return ip_->back(); } inline Node& Node::back() const { return ip_->back(); }
inline Node& Node::operator[](size_t i) const { return at(i); } inline Node& Node::operator[](size_t i) const { return at(i); }
inline void Node::pop_back() { ip_->pop_back(); } inline void Node::pop_back() { ip_->pop_back(); }
inline void Node::pop_all() { ip_->pop_all(); }
inline Node& Node::push_back(Node n) inline Node& Node::push_back(Node n)
{ {
ip_->push_back(n); ip_->push_back(n);
......
...@@ -19,6 +19,10 @@ namespace Sass { ...@@ -19,6 +19,10 @@ namespace Sass {
{ {
switch (type()) switch (type())
{ {
case none: {
return "";
} break;
case selector_group: case selector_group:
case media_expression_group: { // really only needed for arg to :not case media_expression_group: { // really only needed for arg to :not
string result(at(0).to_string()); string result(at(0).to_string());
...@@ -116,26 +120,19 @@ namespace Sass { ...@@ -116,26 +120,19 @@ namespace Sass {
return result; return result;
} break; } break;
case comma_list: { case list: {
if (size() == 0) return "";
string result(at(0).to_string()); string result(at(0).to_string());
for (size_t i = 1, S = size(); i < S; ++i) { for (size_t i = 1, S = size(); i < S; ++i) {
if (at(i).type() == nil) continue; if (at(i).is_null()) continue;
result += ", "; if (at(i).type() == list && at(i).size() == 0) continue;
result += at(i).to_string(); result += is_comma_separated() ? ", " : " ";
}
return result;
} break;
case space_list: {
string result(at(0).to_string());
for (size_t i = 1, S = size(); i < S; ++i) {
if (at(i).type() == nil) continue;
result += " ";
result += at(i).to_string(); result += at(i).to_string();
} }
return result; return result;
} break; } break;
// still necessary for unevaluated expressions
case expression: case expression:
case term: { case term: {
string result(at(0).to_string()); string result(at(0).to_string());
...@@ -274,13 +271,13 @@ namespace Sass { ...@@ -274,13 +271,13 @@ namespace Sass {
return result; return result;
} break; } break;
case expansion: { case mixin_call: {
// ignore it // ignore it
return ""; return "";
} break; } break;
case string_constant: { case string_constant: {
if (is_unquoted()) return token().unquote(); if (!is_quoted()) return token().unquote();
else { else {
string result(token().to_string()); string result(token().to_string());
if (result[0] != '"' && result[0] != '\'') return "\"" + result + "\""; if (result[0] != '"' && result[0] != '\'') return "\"" + result + "\"";
...@@ -329,35 +326,22 @@ namespace Sass { ...@@ -329,35 +326,22 @@ namespace Sass {
result += chunk; result += chunk;
} }
} }
if (is_unquoted()) result = result.substr(1, result.length() - 2); if (!is_quoted()) result = result.substr(1, result.length() - 2);
return result; return result;
} break; } break;
case concatenation: { case concatenation: {
string result; string result;
for (size_t i = 0, S = size(); i < S; ++i) { for (size_t i = 0, S = size(); i < S; ++i) {
result += at(i).to_string().substr(1, at(i).token().length()-2); result += at(i).unquote();
} }
// if (inside_of == identifier_schema || inside_of == property) return result; if (!(inside_of == identifier_schema || inside_of == property) && is_quoted()) {
// else return "\"" + result + "\"";
if (!(inside_of == identifier_schema || inside_of == property) && !is_unquoted()) {
result = "\"" + result + "\""; result = "\"" + result + "\"";
} }
return result; return result;
} break; } break;
case warning: { case warning: {
string prefix("WARNING: ");
string indent(" ");
Node contents(at(0));
string result(contents.to_string());
if (contents.type() == string_constant || contents.type() == string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string
}
// These cerrs aren't log lines! They're supposed to be here!
cerr << prefix << result << endl;
cerr << indent << "on line " << at(0).line() << " of " << at(0).path();
cerr << endl << endl;
return ""; return "";
} break; } break;
......
...@@ -27,6 +27,21 @@ namespace Sass { ...@@ -27,6 +27,21 @@ namespace Sass {
return ip_cpy; return ip_cpy;
} }
// returns a deep-copy of its argument, but uses the path and line that are passed in
Node_Impl* Node_Factory::alloc_Node_Impl(string& path, size_t line, Node_Impl* ip)
{
Node_Impl* ip_cpy = new Node_Impl(*ip);
pool_.push_back(ip_cpy);
if (ip_cpy->has_children) {
for (size_t i = 0, S = ip_cpy->size(); i < S; ++i) {
Node n(ip_cpy->at(i));
ip_cpy->at(i) = (*this)(path, line, n);
}
}
return ip_cpy;
}
// for cloning nodes // for cloning nodes
Node Node_Factory::operator()(const Node& n1) Node Node_Factory::operator()(const Node& n1)
{ {
...@@ -34,6 +49,15 @@ namespace Sass { ...@@ -34,6 +49,15 @@ namespace Sass {
return Node(ip_cpy); return Node(ip_cpy);
} }
// for cloning nodes and resetting their path and line-number fields
Node Node_Factory::operator()(string& path, size_t line, const Node& n1)
{
Node_Impl* ip_cpy = alloc_Node_Impl(path, line, n1.ip_); // deep-copy the implementation object
ip_cpy->path = path;
ip_cpy->line = line;
return Node(ip_cpy);
}
// for making leaf nodes out of terminals/tokens // for making leaf nodes out of terminals/tokens
Node Node_Factory::operator()(Node::Type type, string path, size_t line, Token t) Node Node_Factory::operator()(Node::Type type, string path, size_t line, Token t)
{ {
......
#define SASS_NODE_FACTORY
#include <vector> #include <vector>
#ifndef SASS_NODE_INCLUDED #ifndef SASS_NODE
#include "node.hpp" #include "node.hpp"
#endif #endif
...@@ -15,9 +17,11 @@ namespace Sass { ...@@ -15,9 +17,11 @@ namespace Sass {
Node_Impl* alloc_Node_Impl(Node::Type type, string file, size_t line); Node_Impl* alloc_Node_Impl(Node::Type type, string file, size_t line);
// returns a deep-copy of its argument // returns a deep-copy of its argument
Node_Impl* alloc_Node_Impl(Node_Impl* ip); Node_Impl* alloc_Node_Impl(Node_Impl* ip);
Node_Impl* alloc_Node_Impl(string& path, size_t line, Node_Impl* ip);
public: public:
// for cloning nodes // for cloning nodes
Node operator()(const Node& n1); Node operator()(const Node& n1);
Node operator()(string& path, size_t line, const Node& n1);
// for making leaf nodes out of terminals/tokens // for making leaf nodes out of terminals/tokens
Node operator()(Node::Type type, string file, size_t line, Token t); Node operator()(Node::Type type, string file, size_t line, Token t);
// for making boolean values or interior nodes that have children // for making boolean values or interior nodes that have children
......
#include <cctype> #include <cctype>
#include <iostream>
#include "constants.hpp"
#include "prelexer.hpp" #include "prelexer.hpp"
namespace Sass { namespace Sass {
using namespace Constants;
namespace Prelexer { namespace Prelexer {
using std::cerr; using std::endl;
// Matches zero characters (always succeeds without consuming input). // Matches zero characters (always succeeds without consuming input).
const char* epsilon(char *src) { const char* epsilon(char *src) {
return src; return src;
...@@ -32,11 +37,11 @@ namespace Sass { ...@@ -32,11 +37,11 @@ namespace Sass {
const char* puncts(const char* src) { return one_plus<punct>(src); } const char* puncts(const char* src) { return one_plus<punct>(src); }
// Match a line comment. // Match a line comment.
extern const char slash_slash[] = "//";
const char* line_comment(const char* src) { return to_endl<slash_slash>(src); } const char* line_comment(const char* src) { return to_endl<slash_slash>(src); }
// Match a block comment. // Match a block comment.
extern const char slash_star[] = "/*";
extern const char star_slash[] = "*/";
const char* block_comment(const char* src) { const char* block_comment(const char* src) {
return sequence< optional_spaces, delimited_by<slash_star, star_slash, false> >(src); return sequence< optional_spaces, delimited_by<slash_star, star_slash, false> >(src);
} }
...@@ -55,8 +60,8 @@ namespace Sass { ...@@ -55,8 +60,8 @@ namespace Sass {
return alternatives<double_quoted_string, single_quoted_string>(src); return alternatives<double_quoted_string, single_quoted_string>(src);
} }
// Match interpolants. // Match interpolants.
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
const char* interpolant(const char* src) { const char* interpolant(const char* src) {
return delimited_by<hash_lbrace, rbrace, false>(src); return delimited_by<hash_lbrace, rbrace, false>(src);
} }
...@@ -94,82 +99,87 @@ namespace Sass { ...@@ -94,82 +99,87 @@ namespace Sass {
interpolant, interpolant,
zero_plus< alternatives< identifier, percentage, dimension, hex, number, string_constant > > > >(src); zero_plus< alternatives< identifier, percentage, dimension, hex, number, string_constant > > > >(src);
} }
const char* filename_schema(const char* src) {
return one_plus< sequence< zero_plus< alternatives< identifier, number, exactly<'.'>, exactly<'/'> > >,
interpolant,
zero_plus< alternatives< identifier, number, exactly<'.'>, exactly<'/'> > > > >(src);
}
const char* filename(const char* src) {
return one_plus< alternatives< identifier, number, exactly<'.'> > >(src);
}
// Match CSS '@' keywords. // Match CSS '@' keywords.
const char* at_keyword(const char* src) { const char* at_keyword(const char* src) {
return sequence<exactly<'@'>, identifier>(src); return sequence<exactly<'@'>, identifier>(src);
} }
extern const char import_kwd[] = "@import";
const char* import(const char* src) { const char* import(const char* src) {
return exactly<import_kwd>(src); return exactly<import_kwd>(src);
} }
extern const char media_kwd[] = "@media";
const char* media(const char* src) { const char* media(const char* src) {
return exactly<media_kwd>(src); return exactly<media_kwd>(src);
} }
extern const char mixin_kwd[] = "@mixin";
const char* mixin(const char* src) { const char* mixin(const char* src) {
return exactly<mixin_kwd>(src); return exactly<mixin_kwd>(src);
} }
extern const char function_kwd[] = "@function";
const char* function(const char* src) { const char* function(const char* src) {
return exactly<function_kwd>(src); return exactly<function_kwd>(src);
} }
extern const char return_kwd[] = "@return";
const char* return_directive(const char* src) { const char* return_directive(const char* src) {
return exactly<return_kwd>(src); return exactly<return_kwd>(src);
} }
extern const char include_kwd[] = "@include";
const char* include(const char* src) { const char* include(const char* src) {
return exactly<include_kwd>(src); return exactly<include_kwd>(src);
} }
extern const char extend_kwd[] = "@extend";
const char* extend(const char* src) { const char* extend(const char* src) {
return exactly<extend_kwd>(src); return exactly<extend_kwd>(src);
} }
extern const char if_kwd[] = "@if";
extern const char if_chars[] = "if";
const char* if_directive(const char* src) { const char* if_directive(const char* src) {
return exactly<if_kwd>(src); return exactly<if_kwd>(src);
} }
extern const char else_kwd[] = "@else";
const char* else_directive(const char* src) { const char* else_directive(const char* src) {
return exactly<else_kwd>(src); return exactly<else_kwd>(src);
} }
const char* elseif_directive(const char* src) { const char* elseif_directive(const char* src) {
return sequence< else_directive, return sequence< else_directive,
spaces_and_comments, spaces_and_comments,
exactly< if_chars > >(src); exactly< if_after_else_kwd > >(src);
} }
extern const char for_kwd[] = "@for";
const char* for_directive(const char* src) { const char* for_directive(const char* src) {
return exactly<for_kwd>(src); return exactly<for_kwd>(src);
} }
extern const char from_kwd[] = "from";
const char* from(const char* src) { const char* from(const char* src) {
return exactly<from_kwd>(src); return exactly<from_kwd>(src);
} }
extern const char to_kwd[] = "to";
const char* to(const char* src) { const char* to(const char* src) {
return exactly<to_kwd>(src); return exactly<to_kwd>(src);
} }
extern const char through_kwd[] = "through";
const char* through(const char* src) { const char* through(const char* src) {
return exactly<through_kwd>(src); return exactly<through_kwd>(src);
} }
extern const char each_kwd[] = "@each";
const char* each_directive(const char* src) { const char* each_directive(const char* src) {
return exactly<each_kwd>(src); return exactly<each_kwd>(src);
} }
extern const char in_kwd[] = "in";
const char* in(const char* src) { const char* in(const char* src) {
return exactly<in_kwd>(src); return exactly<in_kwd>(src);
} }
extern const char while_kwd[] = "@while";
const char* while_directive(const char* src) { const char* while_directive(const char* src) {
return exactly<while_kwd>(src); return exactly<while_kwd>(src);
} }
...@@ -180,7 +190,6 @@ namespace Sass { ...@@ -180,7 +190,6 @@ namespace Sass {
exactly<'_'> > >(src); exactly<'_'> > >(src);
} }
extern const char warn_kwd[] = "@warn";
const char* warn(const char* src) { const char* warn(const char* src) {
return exactly<warn_kwd>(src); return exactly<warn_kwd>(src);
} }
...@@ -209,7 +218,7 @@ namespace Sass { ...@@ -209,7 +218,7 @@ namespace Sass {
return sequence<exactly<'.'>, identifier>(src); return sequence<exactly<'.'>, identifier>(src);
} }
// Match CSS numeric constants. // Match CSS numeric constants.
extern const char sign_chars[] = "-+";
const char* sign(const char* src) { const char* sign(const char* src) {
return class_char<sign_chars>(src); return class_char<sign_chars>(src);
} }
...@@ -236,21 +245,7 @@ namespace Sass { ...@@ -236,21 +245,7 @@ namespace Sass {
const char* percentage(const char* src) { const char* percentage(const char* src) {
return sequence< number, exactly<'%'> >(src); return sequence< number, exactly<'%'> >(src);
} }
extern const char em_kwd[] = "em";
extern const char ex_kwd[] = "ex";
extern const char px_kwd[] = "px";
extern const char cm_kwd[] = "cm";
extern const char mm_kwd[] = "mm";
// extern const char in_kwd[] = "in";
extern const char pt_kwd[] = "pt";
extern const char pc_kwd[] = "pc";
extern const char deg_kwd[] = "deg";
extern const char rad_kwd[] = "rad";
extern const char grad_kwd[] = "grad";
extern const char ms_kwd[] = "ms";
extern const char s_kwd[] = "s";
extern const char Hz_kwd[] = "Hz";
extern const char kHz_kwd[] = "kHz";
const char* em(const char* src) { const char* em(const char* src) {
return sequence< number, exactly<em_kwd> >(src); return sequence< number, exactly<em_kwd> >(src);
} }
...@@ -262,15 +257,16 @@ namespace Sass { ...@@ -262,15 +257,16 @@ namespace Sass {
int len = p - src; int len = p - src;
return (len != 4 && len != 7) ? 0 : p; return (len != 4 && len != 7) ? 0 : p;
} }
extern const char rgb_kwd[] = "rgb(";
const char* rgb_prefix(const char* src) { const char* rgb_prefix(const char* src) {
return exactly<rgb_kwd>(src); return exactly<rgb_kwd>(src);
} }
// Match CSS uri specifiers. // Match CSS uri specifiers.
extern const char url_kwd[] = "url(";
const char* uri_prefix(const char* src) { const char* uri_prefix(const char* src) {
return exactly<url_kwd>(src); return exactly<url_kwd>(src);
} }
// TODO: rename the following two functions
const char* uri(const char* src) { const char* uri(const char* src) {
return sequence< exactly<url_kwd>, return sequence< exactly<url_kwd>,
optional<spaces>, optional<spaces>,
...@@ -278,20 +274,22 @@ namespace Sass { ...@@ -278,20 +274,22 @@ namespace Sass {
optional<spaces>, optional<spaces>,
exactly<')'> >(src); exactly<')'> >(src);
} }
// Match SCSS image-url function const char* url_value(const char* src) {
extern const char image_url_kwd[] = "image-url("; return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol
const char* image_url_prefix(const char* src) { one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename
return exactly<image_url_kwd>(src); optional< exactly<'/'> > >(src);
}
const char* url_schema(const char* src) {
return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol
filename_schema >(src); // optional trailing slash
} }
// Match CSS "!important" keyword. // Match CSS "!important" keyword.
extern const char important_kwd[] = "important";
const char* important(const char* src) { const char* important(const char* src) {
return sequence< exactly<'!'>, return sequence< exactly<'!'>,
spaces_and_comments, spaces_and_comments,
exactly<important_kwd> >(src); exactly<important_kwd> >(src);
} }
// Match Sass "!default" keyword. // Match Sass "!default" keyword.
extern const char default_kwd[] = "default";
const char* default_flag(const char* src) { const char* default_flag(const char* src) {
return sequence< exactly<'!'>, return sequence< exactly<'!'>,
spaces_and_comments, spaces_and_comments,
...@@ -306,25 +304,17 @@ namespace Sass { ...@@ -306,25 +304,17 @@ namespace Sass {
return sequence< alternatives< identifier_schema, identifier >, exactly<'('> >(src); return sequence< alternatives< identifier_schema, identifier >, exactly<'('> >(src);
} }
// Match the CSS negation pseudo-class. // Match the CSS negation pseudo-class.
extern const char pseudo_not_chars[] = ":not(";
const char* pseudo_not(const char* src) { const char* pseudo_not(const char* src) {
return exactly< pseudo_not_chars >(src); return exactly< pseudo_not_kwd >(src);
} }
// Match CSS 'odd' and 'even' keywords for functional pseudo-classes. // Match CSS 'odd' and 'even' keywords for functional pseudo-classes.
extern const char even_chars[] = "even";
extern const char odd_chars[] = "odd";
const char* even(const char* src) { const char* even(const char* src) {
return exactly<even_chars>(src); return exactly<even_kwd>(src);
} }
const char* odd(const char* src) { const char* odd(const char* src) {
return exactly<odd_chars>(src); return exactly<odd_kwd>(src);
} }
// Match CSS attribute-matching operators. // Match CSS attribute-matching operators.
extern const char tilde_equal[] = "~=";
extern const char pipe_equal[] = "|=";
extern const char caret_equal[] = "^=";
extern const char dollar_equal[] = "$=";
extern const char star_equal[] = "*=";
const char* exact_match(const char* src) { return exactly<'='>(src); } const char* exact_match(const char* src) { return exactly<'='>(src); }
const char* class_match(const char* src) { return exactly<tilde_equal>(src); } const char* class_match(const char* src) { return exactly<tilde_equal>(src); }
const char* dash_match(const char* src) { return exactly<pipe_equal>(src); } const char* dash_match(const char* src) { return exactly<pipe_equal>(src); }
...@@ -351,50 +341,38 @@ namespace Sass { ...@@ -351,50 +341,38 @@ namespace Sass {
} }
// Match Sass boolean keywords. // Match Sass boolean keywords.
extern const char and_chars[] = "and"; const char* true_val(const char* src) {
extern const char or_chars[] = "or"; return exactly<true_kwd>(src);
extern const char not_chars[] = "not";
extern const char gt_chars[] = ">";
extern const char gte_chars[] = ">=";
extern const char lt_chars[] = "<";
extern const char lte_chars[] = "<=";
extern const char eq_chars[] = "==";
extern const char neq_chars[] = "!=";
extern const char true_chars[] = "true";
extern const char false_chars[] = "false";
const char* true_kwd(const char* src) {
return exactly<true_chars>(src);
} }
const char* false_kwd(const char* src) { const char* false_val(const char* src) {
return exactly<false_chars>(src); return exactly<false_kwd>(src);
} }
const char* and_kwd(const char* src) { const char* and_op(const char* src) {
return exactly<and_chars>(src); return exactly<and_kwd>(src);
} }
const char* or_kwd(const char* src) { const char* or_op(const char* src) {
return exactly<or_chars>(src); return exactly<or_kwd>(src);
} }
const char* not_kwd(const char* src) { const char* not_op(const char* src) {
return exactly<not_chars>(src); return exactly<not_kwd>(src);
} }
const char* eq_op(const char* src) { const char* eq_op(const char* src) {
return exactly<eq_chars>(src); return exactly<eq>(src);
} }
const char* neq_op(const char* src) { const char* neq_op(const char* src) {
return exactly<neq_chars>(src); return exactly<neq>(src);
} }
const char* gt_op(const char* src) { const char* gt_op(const char* src) {
return exactly<gt_chars>(src); return exactly<gt>(src);
} }
const char* gte_op(const char* src) { const char* gte_op(const char* src) {
return exactly<gte_chars>(src); return exactly<gte>(src);
} }
const char* lt_op(const char* src) { const char* lt_op(const char* src) {
return exactly<lt_chars>(src); return exactly<lt>(src);
} }
const char* lte_op(const char* src) { const char* lte_op(const char* src) {
return exactly<lte_chars>(src); return exactly<lte>(src);
} }
// Path matching functions. // Path matching functions.
......
#define SASS_PRELEXER_INCLUDED #define SASS_PRELEXER
namespace Sass { namespace Sass {
namespace Prelexer { namespace Prelexer {
...@@ -305,6 +305,10 @@ namespace Sass { ...@@ -305,6 +305,10 @@ namespace Sass {
// Match interpolant schemas // Match interpolant schemas
const char* identifier_schema(const char* src); const char* identifier_schema(const char* src);
const char* value_schema(const char* src); const char* value_schema(const char* src);
const char* filename(const char* src);
const char* filename_schema(const char* src);
const char* url_schema(const char* src);
const char* url_value(const char* src);
// Match CSS '@' keywords. // Match CSS '@' keywords.
const char* at_keyword(const char* src); const char* at_keyword(const char* src);
const char* import(const char* src); const char* import(const char* src);
...@@ -354,8 +358,7 @@ namespace Sass { ...@@ -354,8 +358,7 @@ namespace Sass {
// Match CSS uri specifiers. // Match CSS uri specifiers.
const char* uri_prefix(const char* src); const char* uri_prefix(const char* src);
const char* uri(const char* src); const char* uri(const char* src);
// Match SCSS image-url function. const char* url(const char* src);
const char* image_url_prefix(const char* src);
// Match CSS "!important" keyword. // Match CSS "!important" keyword.
const char* important(const char* src); const char* important(const char* src);
// Match Sass "!default" keyword. // Match Sass "!default" keyword.
...@@ -385,11 +388,11 @@ namespace Sass { ...@@ -385,11 +388,11 @@ namespace Sass {
const char* variable(const char* src); const char* variable(const char* src);
// Match Sass boolean keywords. // Match Sass boolean keywords.
const char* true_kwd(const char* src); const char* true_val(const char* src);
const char* false_kwd(const char* src); const char* false_val(const char* src);
const char* and_kwd(const char* src); const char* and_op(const char* src);
const char* or_kwd(const char* src); const char* or_op(const char* src);
const char* not_kwd(const char* src); const char* not_op(const char* src);
const char* eq_op(const char* src); const char* eq_op(const char* src);
const char* neq_op(const char* src); const char* neq_op(const char* src);
const char* gt_op(const char* src); const char* gt_op(const char* src);
...@@ -444,6 +447,5 @@ namespace Sass { ...@@ -444,6 +447,5 @@ namespace Sass {
} }
return counter; return counter;
} }
} }
} }
#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <cstdlib> #include <cstdlib>
#include <unistd.h>
#include <iostream> #include <iostream>
#include "document.hpp" #include "document.hpp"
#include "eval_apply.hpp" #include "eval_apply.hpp"
...@@ -41,13 +46,14 @@ extern "C" { ...@@ -41,13 +46,14 @@ extern "C" {
{ {
using namespace Sass; using namespace Sass;
doc.parse_scss(); doc.parse_scss();
eval(doc.root, expand(doc.root,
doc.context.new_Node(Node::none, doc.path, doc.line, 0), Node(),
doc.context.global_env, doc.context.global_env,
doc.context.function_env, doc.context.function_env,
doc.context.new_Node, doc.context.new_Node,
doc.context); doc.context);
extend_selectors(doc.context.pending_extensions, doc.context.extensions, doc.context.new_Node); // extend_selectors(doc.context.pending_extensions, doc.context.extensions, doc.context.new_Node);
if (doc.context.has_extensions) extend(doc.root, doc.context.extensions, doc.context.new_Node);
string output(doc.emit_css(static_cast<Document::CSS_Style>(style))); string output(doc.emit_css(static_cast<Document::CSS_Style>(style)));
char* c_output = (char*) malloc(output.size() + 1); char* c_output = (char*) malloc(output.size() + 1);
strcpy(c_output, output.c_str()); strcpy(c_output, output.c_str());
......
#include <set>
#include "selector.hpp"
namespace Sass {
using namespace std;
Node normalize_selector(Node s, Node_Factory& new_Node)
{
switch (s.type())
{
case Node::selector_group: {
Node normalized(new_Node(Node::selector_group, s.path(), s.line(), 1));
set<Node> normalizer;
for (size_t i = 0, S = s.size(); i < S; ++i)
normalizer.insert(normalize_selector(s[i], new_Node));
for (set<Node>::iterator i = normalizer.begin(); i != normalizer.end(); ++i)
normalized << *i;
return normalized;
} break;
case Node::selector: {
Node normalized(new_Node(Node::selector, s.path(), s.line(), s.size()));
for (size_t i = 0, S = s.size(); i < S; ++i)
normalized << normalize_selector(s[i], new_Node);
return normalized;
} break;
case Node::simple_selector_sequence: {
Node normalized(new_Node(Node::simple_selector_sequence, s.path(), s.line(), 1));
set<Node> normalizer;
size_t i = 0;
if (!selector_is_qualifier(s[0])) {
normalized << s[0];
i = 1;
}
for (size_t S = s.size(); i < S; ++i)
normalizer.insert(normalize_selector(s[i], new_Node));
for (set<Node>::iterator i = normalizer.begin(); i != normalizer.end(); ++i)
normalized << *i;
return normalized;
} break;
default: {
return s;
} break;
}
return s;
}
// Remove duplicate selectors from a selector group. Used when extending.
Node remove_duplicate_selectors(Node group, Node_Factory& new_Node)
{
if (group.type() != Node::selector_group) return group;
Node filtered(new_Node(Node::selector_group, group.path(), group.line(), 1));
for (size_t i = 0, S = group.size(); i < S; ++i) {
bool found_dup = false;
for (size_t j = 0; j < filtered.size(); ++j) {
if (group[i] == filtered[j]) {
found_dup = true;
break;
}
}
if (!found_dup) filtered << group[i];
}
return filtered;
}
bool selector_is_qualifier(Node s)
{
switch (s.type())
{
case Node::pseudo:
case Node::pseudo_negation:
case Node::functional_pseudo:
case Node::attribute_selector: {
return true;
} break;
case Node::simple_selector: {
if ((*s.token().begin == '.') || (*s.token().begin == '#')) return true;
} break;
default: {
return false;
} break;
}
return false;
}
// Node selector_is_specialization_of(Node s, Node t)
// {
// }
}
\ No newline at end of file
#ifndef SASS_NODE
#include "node.hpp"
#endif
#ifndef SASS_NODE_FACTORY
#include "node_factory.hpp"
#endif
namespace Sass {
Node normalize_selector(Node s, Node_Factory& new_Node);
Node remove_duplicate_selectors(Node group, Node_Factory& new_Node);
bool selector_is_qualifier(Node s);
}
\ No newline at end of file
#if _MSC_VER >= 1600
#include <unordered_map>
#else
#include <tr1/unordered_map>
#endif
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <tr1/unordered_map>
#include <map> #include <map>
#include <algorithm> #include <algorithm>
#ifndef SASS_NODE_INCLUDED #ifndef SASS_NODE
#include "node.hpp" #include "node.hpp"
#endif #endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment