Commit 25186694 by Andrew Nesbitt

Updated libsass

parent 9e72e9c3
......@@ -10,7 +10,7 @@ Handle<Value> Render(const Arguments& args) {
String::AsciiValue astr(args[0]);
char * cs = *astr;
ctx->input_string = cs;
ctx->source_string = cs;
ctx->options.include_paths = 0;
ctx->options.output_style = SASS_STYLE_NESTED;
......
CC=g++
CFLAGS=-c -Wall -O2
LDFLAGS=
SOURCES = \
context.cpp functions.cpp document.cpp \
document_parser.cpp eval_apply.cpp node.cpp \
node_factory.cpp node_emitters.cpp prelexer.cpp \
sass_interface.cpp
OBJECTS = $(SOURCES:.cpp=.o)
CPP_FILES = \
context.cpp \
functions.cpp \
document.cpp \
document_parser.cpp \
eval_apply.cpp \
node.cpp \
node_comparisons.cpp \
values.cpp \
prelexer.cpp
all: $(OBJECTS)
ar rvs libsass.a $(OBJECTS)
libsass: libsass_objs
ar rvs libsass.a \
sass_interface.o \
context.o \
functions.o \
document.o \
document_parser.o \
eval_apply.o \
node.o \
node_comparisons.o \
values.o \
prelexer.o
libsass_objs: sass_interface.cpp $(CPP_FILES)
g++ -O2 -Wall -c -combine sass_interface.cpp $(CPP_FILES)
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf *.o *.a
\ No newline at end of file
......@@ -3,6 +3,8 @@ Libsass
by Aaron Leung and Hampton Catlin (@hcatlin)
[![Build Status](https://secure.travis-ci.org/hcatlin/sassc.png?branch=master)](http://travis-ci.org/hcatlin/sassc)
http://github.com/hcatlin/libsass
Libsass is just a library, but if you want to RUN libsass,
......@@ -17,6 +19,20 @@ Libsass is a C/C++ port of the Sass CSS precompiler. The original version was wr
This library strives to be light, simple, and easy to build and integrate with a variety of platforms and languages.
Developing
----------
As you may have noticed, the libsass repo itself has
no executables and no tests. Oh noes! How can you develop???
Well, luckily, SassC is the official binary wrapper for
libsass and is *always* kept in sync. SassC uses a git submodule
to include libsass. When developing libsass, its best to actually
check out SassC and develop in that directory with the SassC spec
and tests there.
We even run Travis tests for SassC!
Usage
-----
......@@ -28,9 +44,9 @@ First, you create a sass context struct. We use these objects to define
different execution parameters for the library. There are three
different context types.
sass_context //string-in-string-out compilation
sass_file_context //file-based compilation
sass_folder_context //full-folder multi-file
sass_context // string-in-string-out compilation
sass_file_context // file-based compilation
sass_folder_context // full-folder multi-file
Each of the context's have slightly different behavior and are
implemented seperately. This does add extra work to implementing
......
#include "context.hpp"
#include <iostream>
#include <unistd.h>
#include "prelexer.hpp"
using std::cerr; using std::endl;
namespace Sass {
......@@ -41,10 +43,13 @@ namespace Sass {
Context::Context(const char* paths_str)
: global_env(Environment()),
function_env(map<pair<string, size_t>, Function>()),
extensions(multimap<Node, Node>()),
pending_extensions(vector<pair<Node, Node> >()),
source_refs(vector<char*>()),
registry(vector<vector<Node>*>()),
include_paths(vector<string>()),
ref_count(0)
new_Node(Node_Factory()),
ref_count(0),
has_extensions(false)
{
register_functions();
collect_include_paths(paths_str);
......@@ -55,6 +60,8 @@ namespace Sass {
for (size_t i = 0; i < source_refs.size(); ++i) {
delete[] source_refs[i];
}
new_Node.free();
// cerr << "Deallocated " << i << " source string(s)." << endl;
}
......
......@@ -3,6 +3,7 @@
#include <utility>
#include <map>
#include "node_factory.hpp"
#include "functions.hpp"
namespace Sass {
......@@ -42,12 +43,15 @@ namespace Sass {
struct Context {
Environment global_env;
map<pair<string, size_t>, Function> function_env;
multimap<Node, Node> extensions;
vector<pair<Node, Node> > pending_extensions;
vector<char*> source_refs; // all the source c-strings
vector<vector<Node>*> registry; // all the child vectors
vector<string> include_paths;
Node_Factory new_Node;
size_t ref_count;
string sass_path;
string css_path;
bool has_extensions;
void collect_include_paths(const char* paths_str);
Context(const char* paths_str = 0);
......
......@@ -4,124 +4,98 @@
#include "eval_apply.hpp"
#include "error.hpp"
#include <iostream>
#include <sstream>
namespace Sass {
Document::Document(char* path_str, char* source_str, Context& ctx)
: path(string()),
source(source_str),
line_number(1),
context(ctx),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
{
if (source_str) {
own_source = false;
position = source;
end = position + std::strlen(source);
}
else if (path_str) {
path = string(path_str);
std::FILE *f;
// TO DO: CHECK f AGAINST NULL/0
f = std::fopen(path.c_str(), "rb");
std::fseek(f, 0, SEEK_END);
int len = std::ftell(f);
std::rewind(f);
// TO DO: WRAP THE new[] IN A TRY/CATCH BLOCK
source = new char[len + 1];
std::fread(source, sizeof(char), len, f);
source[len] = '\0';
end = source + len;
std::fclose(f);
own_source = true;
position = source;
context.source_refs.push_back(source);
}
else {
// report an error
}
++context.ref_count;
}
Document::Document(Context& ctx) : context(ctx)
{ ++context.ref_count; }
Document::Document(const Document& doc)
: path(doc.path),
source(doc.source),
position(doc.position),
end(doc.end),
line(doc.line),
own_source(doc.own_source),
context(doc.context),
root(doc.root),
lexed(doc.lexed)
{ ++doc.context.ref_count; }
Document::Document(string path, char* source)
: path(path), source(source),
line_number(1), own_source(false),
context(*(new Context())),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
Document::~Document()
{ --context.ref_count; }
Document Document::make_from_file(Context& ctx, string path)
{
if (!source) {
std::FILE *f;
f = std::fopen(path.c_str(), "rb");
if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f);
if (len < 0) throw path;
int status = std::ftell(f);
if (status < 0) throw path;
size_t len = status;
std::rewind(f);
// TO DO: CATCH THE POTENTIAL badalloc EXCEPTION
source = new char[len + 1];
std::fread(source, sizeof(char), len, f);
char* source = new char[len + 1];
size_t bytes_read = std::fread(source, sizeof(char), len, f);
if (bytes_read != len) {
std::cerr << "Warning: possible error reading from " << path << std::endl;
}
if (std::ferror(f)) throw path;
source[len] = '\0';
end = source + len;
char* end = source + len;
if (std::fclose(f)) throw path;
own_source = true;
}
position = source;
context.source_refs.push_back(source);
++context.ref_count;
Document doc(ctx);
doc.path = path;
doc.line = 1;
doc.root = ctx.new_Node(Node::root, path, 1, 0);
doc.lexed = Token::make();
doc.own_source = true;
doc.source = source;
doc.end = end;
doc.position = source;
doc.context.source_refs.push_back(source);
return doc;
}
Document::Document(string path, Context& context)
: path(path), source(0),
line_number(1), own_source(false),
context(context),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
Document Document::make_from_source_chars(Context& ctx, char* src, string path, bool own_source)
{
std::FILE *f;
f = std::fopen(path.c_str(), "rb");
if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f);
if (len < 0) throw path;
std::rewind(f);
// TO DO: CATCH THE POTENTIAL badalloc EXCEPTION
source = new char[len + 1];
std::fread(source, sizeof(char), len, f);
if (std::ferror(f)) throw path;
source[len] = '\0';
end = source + len;
if (std::fclose(f)) throw path;
position = source;
context.source_refs.push_back(source);
++context.ref_count;
Document doc(ctx);
doc.path = path;
doc.line = 1;
doc.root = ctx.new_Node(Node::root, path, 1, 0);
doc.lexed = Token::make();
doc.own_source = own_source;
doc.source = src;
doc.end = src + std::strlen(src);
doc.position = src;
if (own_source) doc.context.source_refs.push_back(src);
return doc;
}
Document::Document(const string& path, size_t line_number, Token t, Context& context)
: path(path),
source(const_cast<char*>(t.begin)),
position(t.begin),
end(t.end),
line_number(line_number),
own_source(false),
context(context),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
{ }
Document Document::make_from_token(Context& ctx, Token t, string path, size_t line_number)
{
Document doc(ctx);
doc.path = path;
doc.line = line_number;
doc.root = ctx.new_Node(Node::root, path, 1, 0);
doc.lexed = Token::make();
doc.own_source = false;
doc.source = const_cast<char*>(t.begin);
doc.end = t.end;
doc.position = doc.source;
Document::~Document() {
--context.ref_count;
// if (context.ref_count == 0) delete &context;
return doc;
}
void Document::syntax_error(string message, size_t ln)
{ throw Error(Error::syntax, ln ? ln : line_number, path, message); }
void Document::throw_syntax_error(string message, size_t ln)
{ throw Error(Error::syntax, path, ln ? ln : line, message); }
void Document::read_error(string message, size_t ln)
{ throw Error(Error::read, ln ? ln : line_number, path, message); }
void Document::throw_read_error(string message, size_t ln)
{ throw Error(Error::read, path, ln ? ln : line, message); }
using std::string;
using std::stringstream;
......@@ -134,7 +108,7 @@ namespace Sass {
root.echo(output);
break;
case nested:
root.emit_nested_css(output, 0, vector<string>());
root.emit_nested_css(output, 0, true);
break;
case expanded:
root.emit_expanded_css(output, "");
......
#include <map>
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
#ifndef SASS_CONTEXT_INCLUDED
#include "context.hpp"
#endif
struct Selector_Lookahead {
const char* found;
bool has_interpolants;
};
namespace Sass {
using std::string;
......@@ -19,7 +30,7 @@ namespace Sass {
char* source;
const char* position;
const char* end;
size_t line_number;
size_t line;
bool own_source;
Context& context;
......@@ -27,12 +38,17 @@ namespace Sass {
Node root;
Token lexed;
Document(char* path_str, char* source_str, Context& ctx);
Document(string path, char* source = 0);
Document(string path, Context& context);
Document(const string& path, size_t line_number, Token t, Context& context);
private:
// force the use of the "make_from_..." factory funtions
Document(Context& ctx);
public:
Document(const Document& doc);
~Document();
static Document make_from_file(Context& ctx, string path);
static Document make_from_source_chars(Context& ctx, char* src, string path = "", bool own_source = false);
static Document make_from_token(Context& ctx, Token t, string path = "", size_t line_number = 1);
template <prelexer mx>
const char* peek(const char* start = 0)
{
......@@ -83,7 +99,7 @@ namespace Sass {
else if (mx == spaces) {
after_whitespace = spaces(position);
if (after_whitespace) {
line_number += count_interval<'\n'>(position, after_whitespace);
line += count_interval<'\n'>(position, after_whitespace);
lexed = Token::make(position, after_whitespace);
return position = after_whitespace;
}
......@@ -99,7 +115,7 @@ namespace Sass {
}
const char* after_token = mx(after_whitespace);
if (after_token) {
line_number += count_interval<'\n'>(position, after_token);
line += count_interval<'\n'>(position, after_token);
lexed = Token::make(after_whitespace, after_token);
return position = after_token;
}
......@@ -119,7 +135,8 @@ namespace Sass {
Node parse_argument();
Node parse_assignment();
Node parse_propset();
Node parse_ruleset(bool definition = false);
Node parse_ruleset(Selector_Lookahead lookahead, bool in_definition = false);
Node parse_selector_schema(const char* end_of_selector);
Node parse_selector_group();
Node parse_selector();
Node parse_selector_combinator();
......@@ -127,7 +144,7 @@ namespace Sass {
Node parse_simple_selector();
Node parse_pseudo();
Node parse_attribute_selector();
Node parse_block(bool definition = false);
Node parse_block(Node surrounding_rulesetbool, bool in_definition = false);
Node parse_rule();
Node parse_values();
Node parse_list();
......@@ -140,26 +157,17 @@ namespace Sass {
Node parse_term();
Node parse_factor();
Node parse_value();
Node parse_identifier();
Node parse_variable();
Node parse_function_call();
Node parse_string();
Node parse_value_schema();
Node parse_if_directive(Node surrounding_ruleset);
Node parse_for_directive(Node surrounding_ruleset);
const char* lookahead_for_selector(const char* start = 0);
const char* look_for_rule(const char* start = 0);
const char* look_for_values(const char* start = 0);
const char* look_for_selector_group(const char* start = 0);
const char* look_for_selector(const char* start = 0);
const char* look_for_simple_selector_sequence(const char* start = 0);
const char* look_for_simple_selector(const char* start = 0);
const char* look_for_pseudo(const char* start = 0);
const char* look_for_attrib(const char* start = 0);
Selector_Lookahead lookahead_for_selector(const char* start = 0);
void syntax_error(string message, size_t ln = 0);
void read_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);
string emit_css(CSS_Style style);
......
......@@ -5,55 +5,48 @@
namespace Sass {
using namespace std;
extern const char plus_equal[] = "+=";
void Document::parse_scss()
{
lex<optional_spaces>();
root << Node(Node::flags);
while(position < end) {
lex< optional_spaces >();
Selector_Lookahead lookahead_result;
while (position < end) {
if (lex< block_comment >()) {
root << Node(Node::comment, line_number, lexed);
}
else if (peek< import >(position)) {
// TO DO: don't splice in place at parse-time -- use an expansion node
Node import(parse_import());
if (import.type == Node::css_import) {
root << import;
}
else {
root += import;
root << context.new_Node(Node::comment, path, line, lexed);
}
if (!lex< exactly<';'> >()) syntax_error("top-level @import directive must be terminated by ';'");
else if (peek< import >()) {
Node importee(parse_import());
if (importee.type() == Node::css_import) root << importee;
else root += importee;
if (!lex< exactly<';'> >()) throw_syntax_error("top-level @import directive must be terminated by ';'");
}
else if (peek< mixin >(position) || peek< exactly<'='> >(position)) {
else if (peek< mixin >() || peek< exactly<'='> >()) {
root << parse_mixin_definition();
}
else if (peek< include >(position)) {
root << parse_mixin_call();
root[0].has_expansions = true;
if (!lex< exactly<';'> >()) syntax_error("top-level @include directive must be terminated by ';'");
}
else if (peek< variable >(position)) {
else if (peek< variable >()) {
root << parse_assignment();
if (!lex< exactly<';'> >()) syntax_error("top-level variable binding must be terminated by ';'");
if (!lex< exactly<';'> >()) throw_syntax_error("top-level variable binding must be terminated by ';'");
}
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
root << parse_propset();
}
else if (lookahead_for_selector(position)) {
root << parse_ruleset();
else if ((lookahead_result = lookahead_for_selector(position)).found) {
root << parse_ruleset(lookahead_result);
}
else if (peek< exactly<'+'> >()) {
else if (peek< include >() || peek< exactly<'+'> >()) {
root << parse_mixin_call();
root[0].has_expansions = true;
if (!lex< exactly<';'> >()) syntax_error("top-level @include directive must be terminated by ';'");
if (!lex< exactly<';'> >()) throw_syntax_error("top-level @include directive must be terminated by ';'");
}
else if (peek< if_directive >()) {
root << parse_if_directive(Node());
}
else if (peek< for_directive >()) {
root << parse_for_directive(Node());
}
else {
lex< spaces_and_comments >();
syntax_error("invalid top-level expression");
throw_syntax_error("invalid top-level expression");
}
lex<optional_spaces>();
lex< optional_spaces >();
}
}
......@@ -62,103 +55,117 @@ namespace Sass {
lex< import >();
if (lex< uri_prefix >())
{
if (peek< string_constant >()) {
Node schema(parse_string());
Node importee(context.new_Node(Node::css_import, path, line, 1));
importee << schema;
if (!lex< exactly<')'> >()) throw_syntax_error("unterminated url in @import directive");
return importee;
}
else {
const char* beg = position;
const char* end = find_first< exactly<')'> >(position);
Node result(Node::css_import, line_number, Token::make(beg, end));
if (!end) throw_syntax_error("unterminated url in @import directive");
Node path_node(context.new_Node(Node::identifier, path, line, Token::make(beg, end)));
Node importee(context.new_Node(Node::css_import, path, line, 1));
importee << path_node;
position = end;
lex< exactly<')'> >();
return result;
return importee;
}
if (!lex< string_constant >()) syntax_error("@import directive requires a url or quoted path");
}
if (!lex< string_constant >()) throw_syntax_error("@import directive requires a url or quoted path");
// TO DO: BETTER PATH HANDLING
// cerr << "Importing " << lexed.to_string() << endl;
string import_path(lexed.unquote());
const char* curr_path_start = path.c_str();
const char* curr_path_end = folders(curr_path_start);
string current_path(curr_path_start, curr_path_end - curr_path_start);
try {
Document importee(current_path + import_path, context);
Document importee(Document::make_from_file(context, current_path + import_path));
importee.parse_scss();
// cerr << "Finished parsing import " << lexed.to_string() << endl;
return importee.root;
}
catch (string& path) {
read_error("error reading file \"" + path + "\"");
throw_read_error("error reading file \"" + path + "\"");
}
// unreached statement
return Node(Node::none);
return Node();
}
Node Document::parse_mixin_definition()
{
lex< mixin >() || lex< exactly<'='> >();
if (!lex< identifier >()) syntax_error("invalid name in @mixin directive");
Node name(Node::identifier, line_number, lexed);
if (!lex< identifier >()) throw_syntax_error("invalid name in @mixin directive");
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node params(parse_mixin_parameters());
if (!peek< exactly<'{'> >()) syntax_error("body for mixin " + name.content.token.to_string() + " must begin with a '{'");
Node body(parse_block(true));
Node mixin(Node::mixin, context.registry, line_number, 3);
mixin << name << params << body;
return mixin;
if (!peek< exactly<'{'> >()) throw_syntax_error("body for mixin " + name.token().to_string() + " must begin with a '{'");
Node body(parse_block(Node(), true));
Node the_mixin(context.new_Node(Node::mixin, path, line, 3));
the_mixin << name << params << body;
return the_mixin;
}
Node Document::parse_mixin_parameters()
{
Node params(Node::parameters, context.registry, line_number);
Node params(context.new_Node(Node::parameters, path, line, 0));
Token name(lexed);
if (lex< exactly<'('> >()) {
if (peek< variable >()) {
params << parse_parameter();
while (lex< exactly<','> >()) {
if (!peek< variable >()) 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();
}
if (!lex< exactly<')'> >()) syntax_error("parameter list for " + name.to_string() + " requires a ')'");
if (!lex< exactly<')'> >()) throw_syntax_error("parameter list for " + name.to_string() + " requires a ')'");
}
else if (!lex< exactly<')'> >()) syntax_error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name.to_string());
else if (!lex< exactly<')'> >()) throw_syntax_error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name.to_string());
}
return params;
}
Node Document::parse_parameter() {
lex< variable >();
Node var(Node::variable, line_number, lexed);
Node var(context.new_Node(Node::variable, path, line, lexed));
if (lex< exactly<':'> >()) { // default value
Node val(parse_space_list());
Node par_and_val(Node::assignment, context.registry, line_number, 2);
Node par_and_val(context.new_Node(Node::assignment, path, line, 2));
par_and_val << var << val;
return par_and_val;
}
else {
return var;
}
// unreachable statement
return Node();
}
Node Document::parse_mixin_call()
{
lex< include >() || lex< exactly<'+'> >();
if (!lex< identifier >()) syntax_error("invalid name in @include directive");
Node name(Node::identifier, line_number, lexed);
if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive");
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments());
Node call(Node::expansion, context.registry, line_number, 3);
call << name << args;
return call;
Node the_call(context.new_Node(Node::expansion, path, line, 2));
the_call << name << args;
return the_call;
}
Node Document::parse_arguments()
{
Token name(lexed);
Node args(Node::arguments, context.registry, line_number);
Node args(context.new_Node(Node::arguments, path, line, 0));
if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) {
args << parse_argument();
args.content.children->back().eval_me = true;
Node arg(parse_argument());
arg.should_eval() = true;
args << arg;
while (lex< exactly<','> >()) {
args << parse_argument();
args.content.children->back().eval_me = true;
Node arg(parse_argument());
arg.should_eval() = true;
args << arg;
}
}
if (!lex< exactly<')'> >()) syntax_error("improperly terminated argument list for " + name.to_string());
if (!lex< exactly<')'> >()) throw_syntax_error("improperly terminated argument list for " + name.to_string());
}
return args;
}
......@@ -167,10 +174,10 @@ namespace Sass {
{
if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
lex< variable >();
Node var(Node::variable, line_number, lexed);
Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >();
Node val(parse_space_list());
Node assn(Node::assignment, context.registry, line_number, 2);
Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val;
return assn;
}
......@@ -182,10 +189,10 @@ namespace Sass {
Node Document::parse_assignment()
{
lex< variable >();
Node var(Node::variable, line_number, lexed);
if (!lex< exactly<':'> >()) syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement");
Node var(context.new_Node(Node::variable, path, line, lexed));
if (!lex< exactly<':'> >()) throw_syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement");
Node val(parse_list());
Node assn(Node::assignment, context.registry, line_number, 2);
Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val;
return assn;
}
......@@ -193,10 +200,10 @@ namespace Sass {
Node Document::parse_propset()
{
lex< identifier >();
Node property_segment(Node::identifier, line_number, lexed);
Node property_segment(context.new_Node(Node::identifier, path, line, lexed));
lex< exactly<':'> >();
lex< exactly<'{'> >();
Node block(Node::block, context.registry, line_number, 1);
Node block(context.new_Node(Node::block, path, line, 1));
while (!lex< exactly<'}'> >()) {
if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset();
......@@ -206,35 +213,62 @@ namespace Sass {
lex< exactly<';'> >();
}
}
if (block.size() == 0) syntax_error("namespaced property cannot be empty");
Node propset(Node::propset, context.registry, line_number, 2);
if (block.empty()) throw_syntax_error("namespaced property cannot be empty");
Node propset(context.new_Node(Node::propset, path, line, 2));
propset << property_segment;
propset << block;
return propset;
}
Node Document::parse_ruleset(bool definition)
Node Document::parse_ruleset(Selector_Lookahead lookahead, bool in_definition)
{
Node ruleset(Node::ruleset, context.registry, line_number, 2);
Node ruleset(context.new_Node(Node::ruleset, path, line, 2));
if (lookahead.has_interpolants) {
ruleset << parse_selector_schema(lookahead.found);
}
else {
ruleset << parse_selector_group();
// if (ruleset[0].type == Node::selector) cerr << "ruleset starts with selector" << endl;
// if (ruleset[0].type == Node::selector_group) cerr << "ruleset starts with selector_group" << endl;
if (!peek< exactly<'{'> >()) syntax_error("expected a '{' after the selector");
ruleset << parse_block(definition);
}
if (!peek< exactly<'{'> >()) throw_syntax_error("expected a '{' after the selector");
ruleset << parse_block(ruleset, in_definition);
return ruleset;
}
Node Document::parse_selector_group()
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
Node Document::parse_selector_schema(const char* end_of_selector)
{
// Node group(Node::selector_group, line_number, 1);
// group << parse_selector();
// while (lex< exactly<','> >()) group << parse_selector();
// return group;
const char* i = position;
const char* p;
Node schema(context.new_Node(Node::selector_schema, path, line, 1));
while (i < end_of_selector) {
p = find_first_in_interval< exactly<hash_lbrace> >(i, end_of_selector);
if (p) {
// accumulate the preceding segment if there is one
if (i < p) schema << context.new_Node(Node::identifier, path, line, Token::make(i, p));
// find the end of the interpolant and parse it
const char* j = find_first_in_interval< exactly<rbrace> >(p, end_of_selector);
Node interp_node(Document::make_from_token(context, Token::make(p+2, j), path, line).parse_list());
interp_node.should_eval() = true;
schema << interp_node;
i = j + 1;
}
else { // no interpolants left; add the last segment if there is one
if (i < end_of_selector) schema << context.new_Node(Node::identifier, path, line, Token::make(i, end_of_selector));
break;
}
}
position = end_of_selector;
return schema;
}
Node Document::parse_selector_group()
{
Node sel1(parse_selector());
if (!peek< exactly<','> >()) return sel1;
Node group(Node::selector_group, context.registry, line_number, 2);
Node group(context.new_Node(Node::selector_group, path, line, 2));
group << sel1;
while (lex< exactly<','> >()) group << parse_selector();
return group;
......@@ -242,40 +276,16 @@ namespace Sass {
Node Document::parse_selector()
{
// Node selector(Node::selector, line_number, 1);
// if (lex< exactly<'+'> >() ||
// lex< exactly<'~'> >() ||
// lex< exactly<'>'> >()) {
// selector << Node(Node::selector_combinator, line_number, lexed);
// }
// Node s(parse_simple_selector_sequence());
// if (s.has_backref) selector.has_backref = true;
// selector << s;
// while (lex< exactly<'+'> >() ||
// lex< exactly<'~'> >() ||
// lex< exactly<'>'> >() ||
// lex< ancestor_of >() /*||
// s.terminal_backref && lex< no_spaces >()*/) {
// selector << Node(Node::selector_combinator, line_number, lexed);
// s = parse_simple_selector_sequence();
// if (s.has_backref) selector.has_backref = true;
// selector << s;
// }
// return selector;
Node seq1(parse_simple_selector_sequence());
if (peek< exactly<','> >() ||
peek< exactly<')'> >() ||
peek< exactly<'{'> >()) return seq1;
Node selector(Node::selector, context.registry, line_number, 2);
if (seq1.has_backref) selector.has_backref = true;
Node selector(context.new_Node(Node::selector, path, line, 2));
selector << seq1;
while (!peek< exactly<'{'> >() && !peek< exactly<','> >()) {
Node seq(parse_simple_selector_sequence());
if (seq.has_backref) selector.has_backref = true;
selector << seq;
selector << parse_simple_selector_sequence();
}
return selector;
}
......@@ -286,34 +296,30 @@ namespace Sass {
if (lex< exactly<'+'> >() ||
lex< exactly<'~'> >() ||
lex< exactly<'>'> >())
{ return Node(Node::selector_combinator, line_number, lexed); }
{ return context.new_Node(Node::selector_combinator, path, line, lexed); }
// check for backref or type selector, which are only allowed at the front
Node simp1;
bool saw_backref = false;
if (lex< exactly<'&'> >()) {
simp1 = Node(Node::backref, line_number, lexed);
simp1.has_backref = true;
saw_backref = true;
simp1 = context.new_Node(Node::backref, path, line, lexed);
}
else if (lex< alternatives< type_selector, universal > >()) {
simp1 = Node(Node::simple_selector, line_number, lexed);
simp1 = context.new_Node(Node::simple_selector, path, line, lexed);
}
else {
simp1 = parse_simple_selector();
}
// now we have one simple/atomic selector -- see if there are more
// now we have one simple/atomic selector -- see if that's all
if (peek< spaces >() || peek< exactly<'>'> >() ||
peek< exactly<'+'> >() || peek< exactly<'~'> >() ||
peek< exactly<','> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >())
peek< exactly<'{'> >() || peek< exactly<';'> >())
{ return simp1; }
// now we know we have a sequence of simple selectors
Node seq(Node::simple_selector_sequence, context.registry, line_number, 2);
// otherwise, we have a sequence of simple selectors
Node seq(context.new_Node(Node::simple_selector_sequence, path, line, 2));
seq << simp1;
seq.has_backref = saw_backref;
while (!peek< spaces >(position) &&
!(peek < exactly<'+'> >(position) ||
......@@ -321,50 +327,24 @@ namespace Sass {
peek < exactly<'>'> >(position) ||
peek < exactly<','> >(position) ||
peek < exactly<')'> >(position) ||
peek < exactly<'{'> >(position))) {
peek < exactly<'{'> >(position) ||
peek < exactly<';'> >(position))) {
seq << parse_simple_selector();
}
return seq;
//
// Node seq(Node::simple_selector_sequence, line_number, 1);
// if (lex< alternatives < type_selector, universal > >()) {
// seq << Node(Node::simple_selector, line_number, lexed);
// }
// else if (lex< exactly<'&'> >()) {
// seq << Node(Node::backref, line_number, lexed);
// seq.has_backref = true;
// // if (peek< sequence< no_spaces, alternatives< type_selector, universal > > >(position)) {
// // seq.terminal_backref = true;
// // return seq;
// // }
// }
// else {
// seq << parse_simple_selector();
// }
// while (!peek< spaces >(position) &&
// !(peek < exactly<'+'> >(position) ||
// peek < exactly<'~'> >(position) ||
// peek < exactly<'>'> >(position) ||
// peek < exactly<','> >(position) ||
// peek < exactly<')'> >(position) ||
// peek < exactly<'{'> >(position))) {
// seq << parse_simple_selector();
// }
// return seq;
}
Node Document::parse_selector_combinator()
{
lex< exactly<'+'> >() || lex< exactly<'~'> >() ||
lex< exactly<'>'> >() || lex< ancestor_of >();
return Node(Node::selector_combinator, line_number, lexed);
return context.new_Node(Node::selector_combinator, path, line, lexed);
}
Node Document::parse_simple_selector()
{
if (lex< id_name >() || lex< class_name >()) {
return Node(Node::simple_selector, line_number, lexed);
return context.new_Node(Node::simple_selector, path, line, lexed);
}
else if (peek< exactly<':'> >(position)) {
return parse_pseudo();
......@@ -372,120 +352,113 @@ namespace Sass {
else if (peek< exactly<'['> >(position)) {
return parse_attribute_selector();
}
syntax_error("invalid selector after " + lexed.to_string());
// unreached statement
return Node(Node::none);}
else {
throw_syntax_error("invalid selector after " + lexed.to_string());
}
// unreachable statement
return Node();
}
Node Document::parse_pseudo() {
if (lex< pseudo_not >()) {
Node ps_not(Node::pseudo_negation, context.registry, line_number, 2);
ps_not << Node(Node::value, line_number, lexed);
Node ps_not(context.new_Node(Node::pseudo_negation, path, line, 2));
ps_not << context.new_Node(Node::value, path, line, lexed);
ps_not << parse_selector_group();
lex< exactly<')'> >();
return ps_not;
}
else if (lex< sequence< pseudo_prefix, functional > >()) {
Node pseudo(Node::functional_pseudo, context.registry, line_number, 2);
Node pseudo(context.new_Node(Node::functional_pseudo, path, line, 2));
Token name(lexed);
pseudo << Node(Node::value, line_number, name);
pseudo << context.new_Node(Node::value, path, line, name);
if (lex< alternatives< even, odd > >()) {
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (peek< binomial >(position)) {
lex< coefficient >();
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
lex< exactly<'n'> >();
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
lex< sign >();
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
lex< digits >();
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (lex< sequence< optional<sign>,
optional<digits>,
exactly<'n'> > >()) {
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (lex< sequence< optional<sign>, digits > >()) {
pseudo << Node(Node::value, line_number, lexed);
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (lex< string_constant >()) {
pseudo << Node(Node::string_constant, line_number, lexed);
pseudo << context.new_Node(Node::string_constant, path, line, lexed);
}
else {
syntax_error("invalid argument to " + name.to_string() + "...)");
throw_syntax_error("invalid argument to " + name.to_string() + "...)");
}
if (!lex< exactly<')'> >()) syntax_error("unterminated argument to " + name.to_string() + "...)");
if (!lex< exactly<')'> >()) throw_syntax_error("unterminated argument to " + name.to_string() + "...)");
return pseudo;
}
else if (lex < sequence< pseudo_prefix, identifier > >()) {
return Node(Node::pseudo, line_number, lexed);
return context.new_Node(Node::pseudo, path, line, lexed);
}
syntax_error("unrecognized pseudo-class or pseudo-element");
// unreached statement
return Node(Node::none);
else {
throw_syntax_error("unrecognized pseudo-class or pseudo-element");
}
// unreachable statement
return Node();
}
Node Document::parse_attribute_selector()
{
Node attr_sel(Node::attribute_selector, context.registry, line_number, 3);
Node attr_sel(context.new_Node(Node::attribute_selector, path, line, 3));
lex< exactly<'['> >();
if (!lex< type_selector >()) syntax_error("invalid attribute name in attribute selector");
if (!lex< type_selector >()) throw_syntax_error("invalid attribute name in attribute selector");
Token name(lexed);
attr_sel << Node(Node::value, line_number, name);
attr_sel << context.new_Node(Node::value, path, line, name);
if (lex< exactly<']'> >()) return attr_sel;
if (!lex< alternatives< exact_match, class_match, dash_match,
prefix_match, suffix_match, substring_match > >()) {
syntax_error("invalid operator in attribute selector for " + name.to_string());
throw_syntax_error("invalid operator in attribute selector for " + name.to_string());
}
attr_sel << Node(Node::value, line_number, lexed);
if (!lex< string_constant >()) syntax_error("expected a quoted string constant in attribute selector for " + name.to_string());
attr_sel << Node(Node::value, line_number, lexed);
if (!lex< exactly<']'> >()) syntax_error("unterminated attribute selector for " + name.to_string());
attr_sel << context.new_Node(Node::value, path, line, lexed);
if (!lex< string_constant >() && !lex< identifier >()) throw_syntax_error("expected a string constant or identifier in attribute selector for " + name.to_string());
attr_sel << context.new_Node(Node::value, path, line, lexed);
if (!lex< exactly<']'> >()) throw_syntax_error("unterminated attribute selector for " + name.to_string());
return attr_sel;
}
Node Document::parse_block(bool definition)
Node Document::parse_block(Node surrounding_ruleset, bool in_definition)
{
lex< exactly<'{'> >();
bool semicolon = false;
Node block(Node::block, context.registry, line_number, 1);
block << Node(Node::flags);
Selector_Lookahead lookahead_result;
Node block(context.new_Node(Node::block, path, line, 0));
while (!lex< exactly<'}'> >()) {
if (semicolon) {
if (!lex< exactly<';'> >()) syntax_error("non-terminal statement or declaration must end with ';'");
if (!lex< exactly<';'> >()) throw_syntax_error("non-terminal statement or declaration must end with ';'");
semicolon = false;
while (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed);
block[0].has_statements = true;
block << context.new_Node(Node::comment, path, line, lexed);
}
if (lex< exactly<'}'> >()) break;
}
if (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed);
block[0].has_statements = true;
//semicolon = true;
block << context.new_Node(Node::comment, path, line, lexed);
}
else if (peek< import >(position)) {
if (definition) {
if (in_definition) {
lex< import >(); // to adjust the line number
syntax_error("@import directive not allowed inside mixin definition");
throw_syntax_error("@import directive not allowed inside mixin definition");
}
Node imported_tree(parse_import());
if (imported_tree.type == Node::css_import) {
// cerr << "css import inside block" << endl;
if (imported_tree.type() == Node::css_import) {
block << imported_tree;
block.has_statements = true;
}
else {
for (size_t i = 0; i < imported_tree.size(); ++i) {
if (imported_tree[i].type == Node::comment ||
imported_tree[i].type == Node::rule) {
block[0].has_statements = true;
}
else if (imported_tree[i].type == Node::ruleset) {
block[0].has_blocks = true;
}
for (size_t i = 0, S = imported_tree.size(); i < S; ++i) {
block << imported_tree[i];
}
semicolon = true;
......@@ -493,7 +466,6 @@ namespace Sass {
}
else if (peek< include >(position)) {
block << parse_mixin_call();
block[0].has_expansions = true;
semicolon = true;
}
else if (lex< variable >()) {
......@@ -502,53 +474,61 @@ namespace Sass {
}
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset();
block[0].has_statements = true;
}
else if (lookahead_for_selector(position)) {
block << parse_ruleset(definition);
block[0].has_blocks = true;
else if ((lookahead_result = lookahead_for_selector(position)).found) {
block << parse_ruleset(lookahead_result, in_definition);
}
else if (peek< exactly<'+'> >()) {
block << parse_mixin_call();
block[0].has_expansions = true;
semicolon = true;
}
else if (lex< extend >()) {
if (surrounding_ruleset.is_null_ptr()) throw_syntax_error("@extend directive may only be used within rules");
Node extendee(parse_simple_selector_sequence());
context.extensions.insert(pair<Node, Node>(extendee, surrounding_ruleset));
cerr << "PARSED EXTENSION REQUEST: " << surrounding_ruleset[0].to_string() << " EXTENDS " << extendee.to_string() << endl;
context.has_extensions = true;
semicolon = true;
}
else if (peek< if_directive >()) {
block << parse_if_directive(surrounding_ruleset);
}
else if (peek< for_directive >()) {
block << parse_for_directive(surrounding_ruleset);
}
else if (!peek< exactly<';'> >()) {
Node rule(parse_rule());
// check for lbrace; if it's there, we have a namespace property with a value
if (peek< exactly<'{'> >()) {
Node inner(parse_block());
Node propset(Node::propset, context.registry, line_number, 2);
Node inner(parse_block(Node()));
Node propset(context.new_Node(Node::propset, path, line, 2));
propset << rule[0];
rule[0] = Node(Node::property, line_number, Token::make());
inner[0] = rule;
rule[0] = context.new_Node(Node::property, path, line, Token::make());
inner.push_front(rule);
propset << inner;
block << propset;
// cerr << block[block.size()-1][0].content.token.to_string() << endl;
}
else {
block << rule;
semicolon = true;
}
block[0].has_statements = true;
}
else lex< exactly<';'> >();
while (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed);
block[0].has_statements = true;
block << context.new_Node(Node::comment, path, line, lexed);
}
}
return block;
}
Node Document::parse_rule() {
Node rule(Node::rule, context.registry, line_number, 2);
Node rule(context.new_Node(Node::rule, path, line, 2));
if (!lex< sequence< optional< exactly<'*'> >, identifier > >()) {
lex< spaces_and_comments >(); // get the line number right
syntax_error("invalid property name");
throw_syntax_error("invalid property name");
}
rule << Node(Node::property, line_number, lexed);
if (!lex< exactly<':'> >()) syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'");
rule << context.new_Node(Node::property, path, line, lexed);
if (!lex< exactly<':'> >()) throw_syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'");
rule << parse_list();
return rule;
}
......@@ -564,20 +544,20 @@ namespace Sass {
peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position))
{ return Node(Node::nil, context.registry, line_number); }
{ return context.new_Node(Node::nil, path, line, 0); }
Node list1(parse_space_list());
// if it's a singleton, return it directly; don't wrap it
if (!peek< exactly<','> >(position)) return list1;
Node comma_list(Node::comma_list, context.registry, line_number, 2);
Node comma_list(context.new_Node(Node::comma_list, path, line, 2));
comma_list << list1;
comma_list.eval_me |= list1.eval_me;
comma_list.should_eval() |= list1.should_eval();
while (lex< exactly<','> >())
{
Node list(parse_space_list());
comma_list << list;
comma_list.eval_me |= list.eval_me;
comma_list.should_eval() |= list.should_eval();
}
return comma_list;
......@@ -594,9 +574,9 @@ namespace Sass {
peek< exactly<','> >(position))
{ return disj1; }
Node space_list(Node::space_list, context.registry, line_number, 2);
Node space_list(context.new_Node(Node::space_list, path, line, 2));
space_list << disj1;
space_list.eval_me |= disj1.eval_me;
space_list.should_eval() |= disj1.should_eval();
while (!(peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) ||
......@@ -606,7 +586,7 @@ namespace Sass {
{
Node disj(parse_disjunction());
space_list << disj;
space_list.eval_me |= disj.eval_me;
space_list.should_eval() |= disj.should_eval();
}
return space_list;
......@@ -618,10 +598,10 @@ namespace Sass {
// if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< or_kwd, negate< identifier > > >()) return conj1;
Node disjunction(Node::disjunction, context.registry, line_number, 2);
Node disjunction(context.new_Node(Node::disjunction, path, line, 2));
disjunction << conj1;
while (lex< sequence< or_kwd, negate< identifier > > >()) disjunction << parse_conjunction();
disjunction.eval_me = true;
disjunction.should_eval() = true;
return disjunction;
}
......@@ -632,10 +612,10 @@ namespace Sass {
// if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< and_kwd, negate< identifier > > >()) return rel1;
Node conjunction(Node::conjunction, context.registry, line_number, 2);
Node conjunction(context.new_Node(Node::conjunction, path, line, 2));
conjunction << rel1;
while (lex< sequence< and_kwd, negate< identifier > > >()) conjunction << parse_relation();
conjunction.eval_me = true;
conjunction.should_eval() = true;
return conjunction;
}
......@@ -651,22 +631,22 @@ namespace Sass {
peek< lte_op >(position)))
{ return expr1; }
Node relation(Node::relation, context.registry, line_number, 3);
expr1.eval_me = true;
Node relation(context.new_Node(Node::relation, path, line, 3));
expr1.should_eval() = true;
relation << expr1;
if (lex< eq_op >()) relation << Node(Node::eq, line_number, lexed);
else if (lex< neq_op >()) relation << Node(Node::neq, line_number, lexed);
else if (lex< gte_op >()) relation << Node(Node::gte, line_number, lexed);
else if (lex< lte_op >()) relation << Node(Node::lte, line_number, lexed);
else if (lex< gt_op >()) relation << Node(Node::gt, line_number, lexed);
else if (lex< lt_op >()) relation << Node(Node::lt, line_number, lexed);
if (lex< eq_op >()) relation << context.new_Node(Node::eq, path, line, lexed);
else if (lex< neq_op >()) relation << context.new_Node(Node::neq, path, line, lexed);
else if (lex< gte_op >()) relation << context.new_Node(Node::gte, path, line, lexed);
else if (lex< lte_op >()) relation << context.new_Node(Node::lte, path, line, lexed);
else if (lex< gt_op >()) relation << context.new_Node(Node::gt, path, line, lexed);
else if (lex< lt_op >()) relation << context.new_Node(Node::lt, path, line, lexed);
Node expr2(parse_expression());
expr2.eval_me = true;
expr2.should_eval() = true;
relation << expr2;
relation.eval_me = true;
relation.should_eval() = true;
return relation;
}
......@@ -678,22 +658,22 @@ namespace Sass {
peek< sequence< negate< number >, exactly<'-'> > >(position)))
{ return term1; }
Node expression(Node::expression, context.registry, line_number, 3);
term1.eval_me = true;
Node expression(context.new_Node(Node::expression, path, line, 3));
term1.should_eval() = true;
expression << term1;
while (lex< exactly<'+'> >() || lex< sequence< negate< number >, exactly<'-'> > >()) {
if (lexed.begin[0] == '+') {
expression << Node(Node::add, line_number, lexed);
expression << context.new_Node(Node::add, path, line, lexed);
}
else {
expression << Node(Node::sub, line_number, lexed);
expression << context.new_Node(Node::sub, path, line, lexed);
}
Node term(parse_term());
term.eval_me = true;
term.should_eval() = true;
expression << term;
}
expression.eval_me = true;
expression.should_eval() = true;
return expression;
}
......@@ -706,20 +686,20 @@ namespace Sass {
peek< exactly<'/'> >(position)))
{ return fact1; }
Node term(Node::term, context.registry, line_number, 3);
Node term(context.new_Node(Node::term, path, line, 3));
term << fact1;
if (fact1.eval_me) term.eval_me = true;
if (fact1.should_eval()) term.should_eval() = true;
while (lex< exactly<'*'> >() || lex< exactly<'/'> >()) {
if (lexed.begin[0] == '*') {
term << Node(Node::mul, line_number, lexed);
term.eval_me = true;
term << context.new_Node(Node::mul, path, line, lexed);
term.should_eval() = true;
}
else {
term << Node(Node::div, line_number, lexed);
term << context.new_Node(Node::div, path, line, lexed);
}
Node fact(parse_factor());
if (fact.eval_me) term.eval_me = true;
term.should_eval() |= fact.should_eval();
term << fact;
}
......@@ -730,23 +710,23 @@ namespace Sass {
{
if (lex< exactly<'('> >()) {
Node value(parse_comma_list());
value.eval_me = true;
if (value.type == Node::comma_list || value.type == Node::space_list) {
value[0].eval_me = true;
value.should_eval() = true;
if (value.type() == Node::comma_list || value.type() == Node::space_list) {
value[0].should_eval() = true;
}
if (!lex< exactly<')'> >()) syntax_error("unclosed parenthesis");
if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis");
return value;
}
else if (lex< sequence< exactly<'+'>, negate< number > > >()) {
Node plus(Node::unary_plus, context.registry, line_number, 1);
Node plus(context.new_Node(Node::unary_plus, path, line, 1));
plus << parse_factor();
plus.eval_me = true;
plus.should_eval() = true;
return plus;
}
else if (lex< sequence< exactly<'-'>, negate< number> > >()) {
Node minus(Node::unary_minus, context.registry, line_number, 1);
Node minus(context.new_Node(Node::unary_minus, path, line, 1));
minus << parse_factor();
minus.eval_me = true;
minus.should_eval() = true;
return minus;
}
else {
......@@ -760,78 +740,60 @@ namespace Sass {
{
const char* value = position;
const char* rparen = find_first< exactly<')'> >(position);
if (!rparen) syntax_error("URI is missing ')'");
if (!rparen) throw_syntax_error("URI is missing ')'");
Token contents(Token::make(value, rparen));
// lex< string_constant >();
Node result(Node::uri, line_number, contents);
Node result(context.new_Node(Node::uri, path, line, contents));
position = rparen;
lex< exactly<')'> >();
return result;
}
if (lex< value_schema >())
{
// cerr << "parsing value schema: " << lexed.to_string() << endl;
Document schema_doc(path, line_number, lexed, context);
return schema_doc.parse_value_schema();
}
{ return Document::make_from_token(context, lexed, path, line).parse_value_schema(); }
if (lex< sequence< true_kwd, negate< identifier > > >())
{
Node T(Node::boolean);
T.line_number = line_number;
T.content.boolean_value = true;
return T;
}
{ return context.new_Node(Node::boolean, path, line, true); }
if (lex< sequence< false_kwd, negate< identifier > > >())
{
Node F(Node::boolean);
F.line_number = line_number;
F.content.boolean_value = false;
return F;
}
{ return context.new_Node(Node::boolean, path, line, false); }
if (peek< functional >())
{ return parse_function_call(); }
if (lex< important >())
{ return Node(Node::important, line_number, lexed); }
{ return context.new_Node(Node::important, path, line, lexed); }
if (lex< identifier >())
{ return Node(Node::identifier, line_number, lexed); }
{ return context.new_Node(Node::identifier, path, line, lexed); }
if (lex< percentage >())
{ return Node(Node::textual_percentage, line_number, lexed); }
{ return context.new_Node(Node::textual_percentage, path, line, lexed); }
if (lex< dimension >())
{ return Node(Node::textual_dimension, line_number, lexed); }
{ return context.new_Node(Node::textual_dimension, path, line, lexed); }
if (lex< number >())
{ return Node(Node::textual_number, line_number, lexed); }
{ return context.new_Node(Node::textual_number, path, line, lexed); }
if (lex< hex >())
{ return Node(Node::textual_hex, line_number, lexed); }
{ return context.new_Node(Node::textual_hex, path, line, lexed); }
if (peek< string_constant >())
// { return Node(Node::string_constant, line_number, lexed); }
{ return parse_string(); }
if (lex< variable >())
{
Node var(Node::variable, line_number, lexed);
var.eval_me = true;
Node var(context.new_Node(Node::variable, path, line, lexed));
var.should_eval() = true;
return var;
}
syntax_error("error reading values after " + lexed.to_string());
// unreached statement
return Node(Node::none);
}
throw_syntax_error("error reading values after " + lexed.to_string());
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
// unreachable statement
return Node();
}
Node Document::parse_string()
{
......@@ -841,30 +803,31 @@ namespace Sass {
// see if there any interpolants
const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(str.begin, str.end);
if (!p) {
return Node(Node::string_constant, line_number, str);
return context.new_Node(Node::string_constant, path, line, str);
}
Node schema(Node::string_schema, context.registry, line_number, 1);
Node schema(context.new_Node(Node::string_schema, path, line, 1));
while (i < str.end) {
p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(i, str.end);
if (p) {
if (i < p) schema << Node(Node::identifier, line_number, Token::make(i, p)); // accumulate the preceding segment if it's nonempty
if (i < p) {
schema << context.new_Node(Node::identifier, path, line, Token::make(i, p)); // accumulate the preceding segment if it's nonempty
}
const char* j = find_first_in_interval< exactly<rbrace> >(p, str.end); // find the closing brace
if (j) {
// parse the interpolant and accumulate it
Document interp_doc(path, line_number, Token::make(p+2,j-1), context);
Node interp_node(interp_doc.parse_list());
interp_node.eval_me = true;
Node interp_node(Document::make_from_token(context, Token::make(p+2, j), path, line).parse_list());
interp_node.should_eval() = true;
schema << interp_node;
i = j + 1;
i = j+1;
}
else {
// throw an error if the interpolant is unterminated
syntax_error("unterminated interpolant inside string constant " + str.to_string());
throw_syntax_error("unterminated interpolant inside string constant " + str.to_string());
}
}
else { // no interpolants left; add the last segment if nonempty
if (i < str.end) schema << Node(Node::identifier, line_number, Token::make(i, str.end));
if (i < str.end) schema << context.new_Node(Node::identifier, path, line, Token::make(i, str.end));
break;
}
}
......@@ -873,69 +836,100 @@ namespace Sass {
Node Document::parse_value_schema()
{
Node schema(Node::value_schema, context.registry, line_number, 1);
Node schema(context.new_Node(Node::value_schema, path, line, 1));
while (position < end) {
if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Document interp_doc(path, line_number, insides, context);
Node interp_node(interp_doc.parse_list());
Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
schema << interp_node;
}
else if (lex< identifier >()) {
schema << Node(Node::identifier, line_number, lexed);
schema << context.new_Node(Node::identifier, path, line, lexed);
}
else if (lex< percentage >()) {
schema << Node(Node::textual_percentage, line_number, lexed);
schema << context.new_Node(Node::textual_percentage, path, line, lexed);
}
else if (lex< dimension >()) {
schema << Node(Node::textual_dimension, line_number, lexed);
schema << context.new_Node(Node::textual_dimension, path, line, lexed);
}
else if (lex< number >()) {
schema << Node(Node::textual_number, line_number, lexed);
schema << context.new_Node(Node::textual_number, path, line, lexed);
}
else if (lex< hex >()) {
schema << Node(Node::textual_hex, line_number, lexed);
schema << context.new_Node(Node::textual_hex, path, line, lexed);
}
else if (lex< string_constant >()) {
schema << Node(Node::string_constant, line_number, lexed);
schema << context.new_Node(Node::string_constant, path, line, lexed);
}
else if (lex< variable >()) {
schema << Node(Node::variable, line_number, lexed);
schema << context.new_Node(Node::variable, path, line, lexed);
}
else {
syntax_error("error parsing interpolated value");
throw_syntax_error("error parsing interpolated value");
}
}
schema.eval_me = true;
schema.should_eval() = true;
return schema;
}
Node Document::parse_function_call()
{
lex< identifier >();
Node name(Node::identifier, line_number, lexed);
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments());
Node call(Node::function_call, context.registry, line_number, 2);
Node call(context.new_Node(Node::function_call, path, line, 2));
call << name << args;
call.eval_me = true;
call.should_eval() = true;
return call;
}
Node Document::parse_identifier() {
lex< identifier >();
return Node(Node::identifier, line_number, lexed);
Node Document::parse_if_directive(Node surrounding_ruleset)
{
lex< if_directive >();
Node conditional(context.new_Node(Node::if_directive, path, line, 2));
conditional << parse_list(); // the predicate
if (!lex< exactly<'{'> >()) throw_syntax_error("expected '{' after the predicate for @if");
conditional << parse_block(surrounding_ruleset); // the consequent
// collect all "@else if"s
while (lex< elseif_directive >()) {
conditional << parse_list(); // the next predicate
if (!lex< exactly<'{'> >()) throw_syntax_error("expected '{' after the predicate for @else if");
conditional << parse_block(surrounding_ruleset); // the next consequent
}
// parse the "@else" if present
if (lex< else_directive >()) {
if (!lex< exactly<'{'> >()) throw_syntax_error("expected '{' after @else");
conditional << parse_block(surrounding_ruleset); // the alternative
}
return conditional;
}
Node Document::parse_variable() {
lex< variable >();
return Node(Node::variable, line_number, lexed);
Node Document::parse_for_directive(Node surrounding_ruleset)
{
lex< for_directive >();
size_t for_line = line;
if (!lex< variable >()) throw_syntax_error("@for directive requires an iteration variable");
Node var(context.new_Node(Node::variable, path, line, lexed));
if (!lex< from >()) throw_syntax_error("expected 'from' keyword in @for directive");
Node lower_bound(parse_expression());
Node::Type for_type = Node::for_through_directive;
if (lex< through >()) for_type = Node::for_through_directive;
else if (lex< to >()) for_type = Node::for_to_directive;
else throw_syntax_error("expected 'through' or 'to' keywod in @for directive");
Node upper_bound(parse_expression());
if (!peek< exactly<'{'> >()) throw_syntax_error("expected '{' after the upper bound in @for directive");
Node body(parse_block(surrounding_ruleset));
Node loop(context.new_Node(for_type, path, for_line, 3));
loop << var << lower_bound << upper_bound << body;
return loop;
}
const char* Document::lookahead_for_selector(const char* start)
Selector_Lookahead Document::lookahead_for_selector(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)) ||
......@@ -964,157 +958,21 @@ namespace Sass {
dash_match,
prefix_match,
suffix_match,
substring_match> >(p))) { p = q; }
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< exactly<'{'> >(p) ? p : 0;
result.has_interpolants = saw_interpolant;
if (peek< exactly<'{'> >(p)) return p;
else return 0;
return result;
}
}
\ No newline at end of file
// const char* Document::look_for_rule(const char* start)
// {
// const char* p = start ? start : position;
// (p = peek< identifier >(p)) &&
// (p = peek< exactly<':'> >(p)) &&
// (p = look_for_values(p)) &&
// (p = peek< alternatives< exactly<';'>, exactly<'}'> > >(p));
// return p;
// }
//
// const char* Document::look_for_values(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
// while ((q = peek< identifier >(p)) || (q = peek< dimension >(p)) ||
// (q = peek< percentage >(p)) || (q = peek< number >(p)) ||
// (q = peek< hex >(p)) || (q = peek< string_constant >(p)) ||
// (q = peek< variable >(p)))
// { p = q; }
// return p == start ? 0 : p;
// }
// // NEW LOOKAHEAD FUNCTIONS. THIS ESSENTIALLY IMPLEMENTS A BACKTRACKING
// // PARSER, BECAUSE SELECTORS AND VALUES ARE NOT EXPRESSIBLE IN A
// // REGULAR LANGUAGE.
// const char* Document::look_for_selector_group(const char* start)
// {
// const char* p = start ? start : position;
// const char* q = look_for_selector(p);
//
// if (!q) { return 0; }
// else { p = q; }
//
// while ((q = peek< exactly<','> >(p)) && (q = look_for_selector(q)))
// { p = q; }
//
// // return peek< exactly<'{'> >(p) ? p : 0;
// return peek< alternatives< exactly<'{'>, exactly<')'> > >(p) ? p : 0;
// }
//
// const char* Document::look_for_selector(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
//
// if ((q = peek< exactly<'+'> >(p)) ||
// (q = peek< exactly<'~'> >(p)) ||
// (q = peek< exactly<'>'> >(p)))
// { p = q; }
//
// p = look_for_simple_selector_sequence(p);
//
// if (!p) return 0;
//
// while (((q = peek< exactly<'+'> >(p)) ||
// (q = peek< exactly<'~'> >(p)) ||
// (q = peek< exactly<'>'> >(p)) ||
// (q = peek< ancestor_of > (p))) &&
// (q = look_for_simple_selector_sequence(q)))
// { p = q; }
//
// return p;
// }
//
// const char* Document::look_for_simple_selector_sequence(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
//
// if ((q = peek< type_selector >(p)) ||
// (q = peek< universal >(p)) ||
// (q = peek< exactly <'&'> >(p)) ||
// (q = look_for_simple_selector(p)))
// { p = q; }
// else
// { return 0; }
//
// while (!peek< spaces >(p) &&
// !(peek < exactly<'+'> >(p) ||
// peek < exactly<'~'> >(p) ||
// peek < exactly<'>'> >(p) ||
// peek < exactly<','> >(p) ||
// peek < exactly<')'> >(p) ||
// peek < exactly<'{'> >(p)) &&
// (q = look_for_simple_selector(p)))
// { p = q; }
//
// return p;
// }
//
// const char* Document::look_for_simple_selector(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
// (q = peek< id_name >(p)) || (q = peek< class_name >(p)) ||
// (q = look_for_pseudo(p)) || (q = look_for_attrib(p));
// // cerr << "looking for simple selector; found:" << endl;
// // cerr << (q ? string(Token::make(q,q+8)) : "nothing") << endl;
// return q;
// }
//
// const char* Document::look_for_pseudo(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
//
// if (q = peek< pseudo_not >(p)) {
// // (q = look_for_simple_selector(q)) && (q = peek< exactly<')'> >(q));
// (q = look_for_selector_group(q)) && (q = peek< exactly<')'> >(q));
// }
// else if (q = peek< sequence< pseudo_prefix, functional > >(p)) {
// p = q;
// (q = peek< alternatives< even, odd > >(p)) ||
// (q = peek< binomial >(p)) ||
// (q = peek< sequence< optional<sign>,
// optional<digits>,
// exactly<'n'> > >(p)) ||
// (q = peek< sequence< optional<sign>,
// digits > >(p));
// p = q;
// q = peek< exactly<')'> >(p);
// }
// else {
// q = peek< sequence< pseudo_prefix, identifier > >(p);
// }
// return q ? q : 0;
// }
//
// const char* Document::look_for_attrib(const char* start)
// {
// const char* p = start ? start : position;
//
// (p = peek< exactly<'['> >(p)) &&
// (p = peek< type_selector >(p)) &&
// (p = peek< alternatives<exact_match,
// class_match,
// dash_match,
// prefix_match,
// suffix_match,
// substring_match> >(p)) &&
// (p = peek< string_constant >(p)) &&
// (p = peek< exactly<']'> >(p));
//
// return p;
// }
// }
......@@ -4,12 +4,12 @@ namespace Sass {
enum Type { read, write, syntax, evaluation };
Type type;
size_t line_number;
string file_name;
string path;
size_t line;
string message;
Error(Type type, size_t line_number, string file_name, string message)
: type(type), line_number(line_number), file_name(file_name), message(message)
Error(Type type, string path, size_t line, string message)
: type(type), path(path), line(line), message(message)
{ }
};
......
#include "prelexer.hpp"
#include "eval_apply.hpp"
#include "document.hpp"
#include "error.hpp"
#include <iostream>
#include <sstream>
#include <cstdlib>
namespace Sass {
using std::cerr; using std::endl;
static void eval_error(string message, size_t line_number, const char* file_name)
static void throw_eval_error(string message, string path, size_t line)
{
string fn;
if (file_name) {
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
if (!path.empty() && Prelexer::string_constant(path.c_str()))
path = path.substr(1, path.size() - 1);
throw Error(Error::evaluation, path, line, message);
}
Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
// Evaluate the parse tree in-place (mostly). Most nodes will be left alone.
Node eval(Node expr, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
switch (expr.type)
switch (expr.type())
{
case Node::mixin: {
env[expr[0].content.token] = expr;
env[expr[0].token()] = expr;
return expr;
} break;
case Node::expansion: {
Token name(expr[0].content.token);
Token name(expr[0].token());
Node args(expr[1]);
if (!env.query(name)) eval_error("mixin " + name.to_string() + " is undefined", expr.line_number, expr.file_name);
if (!env.query(name)) throw_eval_error("mixin " + name.to_string() + " is undefined", expr.path(), expr.line());
Node mixin(env[name]);
Node expansion(apply_mixin(mixin, args, env, f_env, registry));
expr.content.children->pop_back();
expr.content.children->pop_back();
Node expansion(apply_mixin(mixin, args, prefix, env, f_env, new_Node, ctx));
expr.pop_back();
expr.pop_back();
expr += expansion;
return expr;
} break;
case Node::propset:
case Node::propset: {
eval(expr[1], prefix, env, f_env, new_Node, ctx);
return expr;
} break;
case Node::ruleset: {
eval(expr[1], env, f_env, registry);
// if the selector contains interpolants, eval it and re-parse
if (expr[0].type() == Node::selector_schema) {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
}
// expand the selector with the prefix and save it in expr[2]
expr << expand_selector(expr[0], prefix, new_Node);
// gather selector extensions into a pending queue
if (ctx.has_extensions) {
Node sel(selector_base(expr.back()));
// if (sel.type() == Node::selector) sel = sel.back();
if (ctx.extensions.count(sel)) {
cerr << ctx.extensions.count(sel) << endl;
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));
}
}
}
// eval the body with the current selector as the prefix
eval(expr[1], expr.back(), env, f_env, new_Node, ctx);
return expr;
} 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; i < expr.size(); ++i) {
eval(expr[i], env, f_env, registry);
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: {
Environment current;
current.link(env);
for (size_t i = 0; i < expr.size(); ++i) {
eval(expr[i], current, f_env, registry);
Environment new_frame;
new_frame.link(env);
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, new_frame, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::assignment: {
Node val(expr[1]);
if (val.type == Node::comma_list || val.type == Node::space_list) {
for (size_t i = 0; i < val.size(); ++i) {
if (val[i].eval_me) val[i] = eval(val[i], env, f_env, registry);
if (val.type() == Node::comma_list || val.type() == Node::space_list) {
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);
}
}
else {
val = eval(val, env, f_env, registry);
val = eval(val, prefix, env, f_env, new_Node, ctx);
}
Node var(expr[0]);
if (env.query(var.content.token)) {
env[var.content.token] = val;
if (env.query(var.token())) {
env[var.token()] = val;
}
else {
env.current_frame[var.content.token] = val;
env.current_frame[var.token()] = val;
}
return expr;
} break;
case Node::rule: {
Node rhs(expr[1]);
if (rhs.type == Node::comma_list || rhs.type == Node::space_list) {
for (size_t i = 0; i < rhs.size(); ++i) {
if (rhs[i].eval_me) rhs[i] = eval(rhs[i], env, f_env, registry);
if (rhs.type() == Node::comma_list || rhs.type() == Node::space_list) {
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);
}
}
else if (rhs.type == Node::value_schema || rhs.type == Node::string_schema) {
eval(rhs, env, f_env, registry);
else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) {
eval(rhs, prefix, env, f_env, new_Node, ctx);
}
else {
if (rhs.eval_me) expr[1] = eval(rhs, env, f_env, registry);
if (rhs.should_eval()) expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::comma_list:
case Node::space_list: {
if (expr.eval_me) expr[0] = eval(expr[0], env, f_env, registry);
if (expr.should_eval()) expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
return expr;
} break;
case Node::disjunction: {
Node result;
for (size_t i = 0; i < expr.size(); ++i) {
// if (expr[i].type == Node::relation ||
// expr[i].type == Node::function_call && expr[0].content.token.to_string() == "not") {
result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) continue;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) continue;
else return result;
}
return result;
......@@ -116,26 +162,24 @@ namespace Sass {
case Node::conjunction: {
Node result;
for (size_t i = 0; i < expr.size(); ++i) {
result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) return result;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) return result;
}
return result;
} break;
case Node::relation: {
Node lhs(eval(expr[0], env, f_env, registry));
Node lhs(eval(expr[0], prefix, env, f_env, new_Node, ctx));
Node op(expr[1]);
Node rhs(eval(expr[2], env, f_env, registry));
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
// TO DO: don't allocate both T and F
Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true));
Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false));
Node T(Node::boolean);
T.line_number = lhs.line_number;
T.content.boolean_value = true;
Node F(T);
F.content.boolean_value = false;
switch (op.type) {
switch (op.type())
{
case Node::eq: return (lhs == rhs) ? T : F;
case Node::neq: return (lhs != rhs) ? T : F;
case Node::gt: return (lhs > rhs) ? T : F;
......@@ -143,32 +187,32 @@ namespace Sass {
case Node::lt: return (lhs < rhs) ? T : F;
case Node::lte: return (lhs <= rhs) ? T : F;
default:
eval_error("unknown comparison operator " + expr.content.token.to_string(), expr.line_number, expr.file_name);
return Node(Node::none);
throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line());
return Node();
}
} break;
case Node::expression: {
Node acc(Node::expression, registry, expr.line_number, 1);
acc << eval(expr[0], env, f_env, registry);
Node rhs(eval(expr[2], env, f_env, registry));
accumulate(expr[1].type, acc, rhs, registry);
for (size_t i = 3; i < expr.size(); i += 2) {
Node rhs(eval(expr[i+1], env, f_env, registry));
accumulate(expr[i].type, acc, rhs, registry);
Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1));
acc << eval(expr[0], prefix, env, f_env, new_Node, ctx);
Node rhs(eval(expr[2], 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;
} break;
case Node::term: {
if (expr.eval_me) {
Node acc(Node::expression, registry, expr.line_number, 1);
acc << eval(expr[0], env, f_env, registry);
Node rhs(eval(expr[2], env, f_env, registry));
accumulate(expr[1].type, acc, rhs, registry);
for (size_t i = 3; i < expr.size(); i += 2) {
Node rhs(eval(expr[i+1], env, f_env, registry));
accumulate(expr[i].type, acc, rhs, registry);
if (expr.should_eval()) {
Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1));
acc << eval(expr[0], prefix, env, f_env, new_Node, ctx);
Node rhs(eval(expr[2], 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;
}
......@@ -178,59 +222,54 @@ namespace Sass {
} break;
case Node::textual_percentage: {
Node pct(expr.line_number, std::atof(expr.content.token.begin));
pct.type = Node::numeric_percentage;
return pct;
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage);
} break;
case Node::textual_dimension: {
return Node(expr.line_number,
std::atof(expr.content.token.begin),
Token::make(Prelexer::number(expr.content.token.begin),
expr.content.token.end));
return new_Node(expr.path(), expr.line(),
std::atof(expr.token().begin),
Token::make(Prelexer::number(expr.token().begin),
expr.token().end));
} break;
case Node::textual_number: {
return Node(expr.line_number, std::atof(expr.content.token.begin));
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin));
} break;
case Node::textual_hex: {
Node triple(Node::numeric_color, registry, expr.line_number, 4);
Token hext(Token::make(expr.content.token.begin+1, expr.content.token.end));
Node triple(new_Node(Node::numeric_color, expr.path(), expr.line(), 4));
Token hext(Token::make(expr.token().begin+1, expr.token().end));
if (hext.length() == 6) {
for (int i = 0; i < 6; i += 2) {
triple << Node(expr.line_number, static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
}
}
else {
for (int i = 0; i < 3; ++i) {
triple << Node(expr.line_number, static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
}
}
triple << Node(expr.line_number, 1.0);
triple << new_Node(expr.path(), expr.line(), 1.0);
return triple;
} break;
case Node::variable: {
if (!env.query(expr.content.token)) eval_error("reference to unbound variable " + expr.content.token.to_string(), expr.line_number, expr.file_name);
return env[expr.content.token];
if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line());
return env[expr.token()];
} break;
case Node::function_call: {
// TO DO: default-constructed Function should be a generic callback
pair<string, size_t> sig(expr[0].content.token.to_string(), expr[1].size());
if (!f_env.count(sig)) {
// stringstream ss;
// ss << "no function named " << expr[0].content.token.to_string() << " taking " << expr[1].size() << " arguments has been defined";
// eval_error(ss.str(), expr.line_number, expr.file_name);
return expr;
}
return apply_function(f_env[sig], expr[1], env, f_env, registry);
// TO DO: default-constructed Function should be a generic callback (maybe)
pair<string, size_t> sig(expr[0].token().to_string(), expr[1].size());
if (!f_env.count(sig)) return expr;
return apply_function(f_env[sig], expr[1], prefix, env, f_env, new_Node, ctx);
} break;
case Node::unary_plus: {
Node arg(eval(expr[0], env, f_env, registry));
if (arg.is_numeric()) return arg;
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) {
return arg;
}
else {
expr[0] = arg;
return expr;
......@@ -238,9 +277,9 @@ namespace Sass {
} break;
case Node::unary_minus: {
Node arg(eval(expr[0], env, f_env, registry));
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) {
arg.set_numeric_value(-arg.numeric_value());
return new_Node(expr.path(), expr.line(), -arg.numeric_value());
}
else {
expr[0] = arg;
......@@ -250,110 +289,153 @@ namespace Sass {
case Node::string_schema:
case Node::value_schema: {
// cerr << "evaluating schema of size " << expr.size() << endl;
for (size_t i = 0; i < expr.size(); ++i) {
expr[i] = eval(expr[i], env, f_env, registry);
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;
default: {
case Node::css_import: {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
return expr;
} 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);
}
} break;
default: {
return expr;
} break;
}
Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry)
return expr;
}
// Accumulate arithmetic operations. It's done this way because arithmetic
// expressions are stored as vectors of operands with operators interspersed,
// rather than as the usual binary tree.
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node)
{
Node lhs(acc.content.children->back());
Node lhs(acc.back());
double lnum = lhs.numeric_value();
double rnum = rhs.numeric_value();
if (lhs.type == Node::number && rhs.type == Node::number) {
Node result(acc.line_number, operate(op, lnum, rnum));
acc.content.children->pop_back();
acc.content.children->push_back(result);
if (lhs.type() == Node::number && rhs.type() == Node::number) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum)));
acc.pop_back();
acc.push_back(result);
}
// TO DO: find a way to merge the following two clauses
else if (lhs.type == Node::number && rhs.type == Node::numeric_dimension) {
Node result(acc.line_number, operate(op, lnum, rnum), Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit)));
acc.content.children->pop_back();
acc.content.children->push_back(result);
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) {
Node result(acc.line_number, operate(op, lnum, rnum), Token::make(lhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit)));
acc.content.children->pop_back();
acc.content.children->push_back(result);
else if (lhs.type() == Node::numeric_dimension && rhs.type() == Node::number) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit()));
acc.pop_back();
acc.push_back(result);
}
else if (lhs.type == Node::numeric_dimension && rhs.type == Node::numeric_dimension) {
else if (lhs.type() == Node::numeric_dimension && rhs.type() == Node::numeric_dimension) {
// TO DO: CHECK FOR MISMATCHED UNITS HERE
Node result;
if (op == Node::div)
{ result = Node(acc.line_number, operate(op, lnum, rnum)); }
{ result = new_Node(acc.path(), acc.line(), operate(op, lnum, rnum)); }
else
{ result = Node(acc.line_number, operate(op, lnum, rnum), Token::make(lhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))); }
acc.content.children->pop_back();
acc.content.children->push_back(result);
{ 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 (lhs.type == Node::number && rhs.type == Node::numeric_color) {
else if (lhs.type() == Node::number && rhs.type() == Node::numeric_color) {
if (op != Node::sub && op != Node::div) {
double r = operate(op, lhs.content.numeric_value, rhs[0].content.numeric_value);
double g = operate(op, lhs.content.numeric_value, rhs[1].content.numeric_value);
double b = operate(op, lhs.content.numeric_value, rhs[2].content.numeric_value);
double a = rhs[3].content.numeric_value;
acc.content.children->pop_back();
acc << Node(registry, acc.line_number, r, g, b, a);
double r = operate(op, lhs.numeric_value(), rhs[0].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 if (op == Node::div) {
acc << Node(Node::div);
acc << new_Node(Node::div, acc.path(), acc.line(), 0);
acc << rhs;
}
else if (op == Node::sub) {
acc << Node(Node::sub);
acc << new_Node(Node::sub, acc.path(), acc.line(), 0);
acc << rhs;
}
else {
acc << rhs;
}
}
else if (lhs.type == Node::numeric_color && rhs.type == Node::number) {
double r = operate(op, lhs[0].content.numeric_value, rhs.content.numeric_value);
double g = operate(op, lhs[1].content.numeric_value, rhs.content.numeric_value);
double b = operate(op, lhs[2].content.numeric_value, rhs.content.numeric_value);
double a = lhs[3].content.numeric_value;
acc.content.children->pop_back();
acc << Node(registry, acc.line_number, r, g, b, a);
}
else if (lhs.type == Node::numeric_color && rhs.type == Node::numeric_color) {
if (lhs[3].content.numeric_value != rhs[3].content.numeric_value) eval_error("alpha channels must be equal for " + lhs.to_string("") + " + " + rhs.to_string(""), lhs.line_number, lhs.file_name);
double r = operate(op, lhs[0].content.numeric_value, rhs[0].content.numeric_value);
double g = operate(op, lhs[1].content.numeric_value, rhs[1].content.numeric_value);
double b = operate(op, lhs[2].content.numeric_value, rhs[2].content.numeric_value);
double a = lhs[3].content.numeric_value;
acc.content.children->pop_back();
acc << Node(registry, acc.line_number, r, g, b, a);
}
// else if (lhs.type == Node::concatenation) {
// lhs << rhs;
// }
// else if (lhs.type == Node::string_constant || rhs.type == Node::string_constant) {
// acc.content.children->pop_back();
// Node cat(Node::concatenation, lhs.line_number, 2);
// cat << lhs << rhs;
// acc << cat;
// }
else if (lhs.type() == Node::numeric_color && rhs.type() == Node::number) {
double r = operate(op, lhs[0].numeric_value(), rhs.numeric_value());
double g = operate(op, lhs[1].numeric_value(), rhs.numeric_value());
double b = operate(op, lhs[2].numeric_value(), rhs.numeric_value());
double a = lhs[3].numeric_value();
acc.pop_back();
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
}
else if (lhs.type() == Node::numeric_color && rhs.type() == 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());
double r = operate(op, lhs[0].numeric_value(), rhs[0].numeric_value());
double g = operate(op, lhs[1].numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs[2].numeric_value(), rhs[2].numeric_value());
double a = lhs[3].numeric_value();
acc.pop_back();
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
}
else {
// TO DO: disallow division and multiplication on lists
acc.content.children->push_back(rhs);
acc.push_back(rhs);
}
return acc;
}
// Helper for doing the actual arithmetic.
double operate(Node::Type op, double lhs, double rhs)
{
switch (op)
......@@ -366,79 +448,410 @@ namespace Sass {
}
}
Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
// 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<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
Node params(mixin[1]);
Node body(mixin[2].clone(registry));
Node body(new_Node(mixin[2])); // clone the body
Environment bindings;
// bind arguments
for (size_t i = 0, j = 0; i < args.size(); ++i) {
if (args[i].type == Node::assignment) {
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].content.token);
Token name(arg[0].token());
// check that the keyword arg actually names a formal parameter
bool valid_param = false;
for (size_t k = 0; k < params.size(); ++k) {
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 (param_k.type() == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
}
}
if (!valid_param) eval_error("mixin " + mixin[0].to_string("") + " has no parameter named " + name.to_string(), arg.line_number, arg.file_name);
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)) {
bindings[name] = eval(arg[1], env, f_env, registry);
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 " << mixin[0].to_string("") << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments");
eval_error(ss.str(), args[i].line_number, args[i].file_name);
ss << "mixin " << mixin[0].to_string() << " 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.content.token : param[0].content.token);
bindings[name] = eval(args[i], env, f_env, registry);
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; i < params.size(); ++i) {
if (params[i].type == Node::assignment) {
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].content.token);
Token name(param[0].token());
if (!bindings.query(name)) {
bindings[name] = eval(param[1], env, f_env, registry);
bindings[name] = eval(param[1], prefix, env, f_env, new_Node, ctx);
}
}
}
// lexically link the new environment and eval the mixin's body
bindings.link(env.global ? *env.global : env);
for (size_t i = 0; i < body.size(); ++i) {
body[i] = eval(body[i], bindings, f_env, registry);
for (size_t i = 0, S = body.size(); i < S; ++i) {
body[i] = eval(body[i], prefix, bindings, f_env, new_Node, ctx);
}
return body;
}
Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
// Apply a function -- bind the arguments and pass them to the underlying
// primitive function implementation, then return its value.
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
map<Token, Node> bindings;
// bind arguments
for (size_t i = 0, j = 0; i < args.size(); ++i) {
if (args[i].type == Node::assignment) {
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].content.token);
bindings[name] = eval(arg[1], env, f_env, registry);
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]] = eval(args[i], env, f_env, registry);
bindings[f.parameters[j]] = eval(args[i], prefix, env, f_env, new_Node, ctx);
++j;
}
}
return f(bindings, registry);
return f(bindings, new_Node);
}
// Expand a selector with respect to its prefix/context. Two separate cases:
// when the selector has backrefs, substitute the prefix for each occurrence
// of a backref. When the selector doesn't have backrefs, just prepend the
// prefix. This function needs multiple subsidiary cases in order to properly
// combine the various kinds of selectors.
Node expand_selector(Node sel, Node pre, Node_Factory& new_Node)
{
if (pre.type() == Node::none) return sel;
if (sel.has_backref()) {
if ((pre.type() == Node::selector_group) && (sel.type() == Node::selector_group)) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), pre.size() * sel.size()));
for (size_t i = 0, S = pre.size(); i < S; ++i) {
for (size_t j = 0, T = sel.size(); j < T; ++j) {
group << expand_backref(new_Node(sel[j]), pre[i]);
}
}
return group;
}
else if ((pre.type() == Node::selector_group) && (sel.type() != Node::selector_group)) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), pre.size()));
for (size_t i = 0, S = pre.size(); i < S; ++i) {
group << expand_backref(new_Node(sel), pre[i]);
}
return group;
}
else if ((pre.type() != Node::selector_group) && (sel.type() == Node::selector_group)) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), sel.size()));
for (size_t i = 0, S = sel.size(); i < S; ++i) {
group << expand_backref(new_Node(sel[i]), pre);
}
return group;
}
else {
return expand_backref(new_Node(sel), pre);
}
}
if ((pre.type() == Node::selector_group) && (sel.type() == Node::selector_group)) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), pre.size() * sel.size()));
for (size_t i = 0, S = pre.size(); i < S; ++i) {
for (size_t j = 0, T = sel.size(); j < T; ++j) {
Node new_sel(new_Node(Node::selector, sel.path(), sel.line(), 2));
if (pre[i].type() == Node::selector) new_sel += pre[i];
else new_sel << pre[i];
if (sel[j].type() == Node::selector) new_sel += sel[j];
else new_sel << sel[j];
group << new_sel;
}
}
return group;
}
else if ((pre.type() == Node::selector_group) && (sel.type() != Node::selector_group)) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), pre.size()));
for (size_t i = 0, S = pre.size(); i < S; ++i) {
Node new_sel(new_Node(Node::selector, sel.path(), sel.line(), 2));
if (pre[i].type() == Node::selector) new_sel += pre[i];
else new_sel << pre[i];
if (sel.type() == Node::selector) new_sel += sel;
else new_sel << sel;
group << new_sel;
}
return group;
}
else if ((pre.type() != Node::selector_group) && (sel.type() == Node::selector_group)) {
Node group(new_Node(Node::selector_group, sel.path(), sel.line(), sel.size()));
for (size_t i = 0, S = sel.size(); i < S; ++i) {
Node new_sel(new_Node(Node::selector, sel.path(), sel.line(), 2));
if (pre.type() == Node::selector) new_sel += pre;
else new_sel << pre;
if (sel[i].type() == Node::selector) new_sel += sel[i];
else new_sel << sel[i];
group << new_sel;
}
return group;
}
else {
Node new_sel(new_Node(Node::selector, sel.path(), sel.line(), 2));
if (pre.type() == Node::selector) new_sel += pre;
else new_sel << pre;
if (sel.type() == Node::selector) new_sel += sel;
else new_sel << sel;
return new_sel;
}
// unreachable statement
return Node();
}
// Helper for expanding selectors with backrefs.
Node expand_backref(Node sel, Node pre)
{
switch (sel.type())
{
case Node::backref: {
return pre;
} break;
case Node::simple_selector_sequence:
case Node::selector: {
for (size_t i = 0, S = sel.size(); i < S; ++i) {
sel[i] = expand_backref(sel[i], pre);
}
return sel;
} break;
default: {
return sel;
} break;
}
// unreachable statement
return Node();
}
// Resolve selector extensions.
void extend_selectors(vector<pair<Node, Node> >& pending, Node_Factory& new_Node)
{
for (size_t i = 0, S = pending.size(); i < S; ++i) {
Node extender(pending[i].second[2]);
Node ruleset_to_extend(pending[i].first);
Node selector_to_extend(ruleset_to_extend[2]);
// if (selectors_to_extend.type() != Node::selector_group) {
// Node ext(generate_extension(selectors_to_extend, extender, new_Node));
// ext.push_front(selectors_to_extend);
// ruleset_to_extend[2] = ext;
// }
// else {
// Node new_group(new_Node(Node::selector_group,
// selectors_to_extend.path(),
// selectors_to_extend.line(),
// selectors_to_extend.size()));
// for (size_t i = 0, S = selectors_to_extend.size(); i < S; ++i) {
// Node sel_i(selectors_to_extend[i]);
// if (extensions.count(sel_i)) {
// new_group << sel_i;
// Node ext(generate_extension(sel_i, extender, new_Node));
// new_group += ext;
// }
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 in a
// selector group.
Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node)
{
Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1));
if (extendee.type() != Node::selector) {
switch (extender.type())
{
case Node::simple_selector:
case Node::attribute_selector:
case Node::simple_selector_sequence:
case Node::selector: {
cerr << "EXTENDING " << extendee.to_string() << " WITH " << extender.to_string() << endl;
new_group << extender;
return 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(Node::selector, extendee.path(), extendee.line(), extendee.size()));
for (size_t i = 0, S = extendee.size() - 1; i < S; ++i) {
new_ext << extendee[i];
}
new_ext << extender;
new_group << new_ext;
return new_group;
} break;
case Node::selector: {
Node new_ext1(new_Node(Node::selector, extendee.path(), extendee.line(), extendee.size() + extender.size() - 1));
Node new_ext2(new_Node(Node::selector, extendee.path(), extendee.line(), extendee.size() + extender.size() - 1));
new_ext1 += selector_prefix(extendee, new_Node);
new_ext1 += extender;
new_ext2 += selector_prefix(extender, new_Node);
new_ext2 += selector_prefix(extendee, new_Node);
new_ext2 << extender.back();
new_group << new_ext1 << new_ext2;
return new_group;
} break;
default: {
// something
} break;
}
}
return Node();
}
// Helpers for extracting subsets of selectors
Node selector_prefix(Node sel, Node_Factory& new_Node)
{
switch (sel.type())
{
case Node::selector: {
Node pre(new_Node(Node::selector, sel.path(), sel.line(), sel.size() - 1));
for (size_t i = 0, S = sel.size() - 1; i < S; ++i) {
pre << sel[i];
}
return pre;
} break;
default: {
return new_Node(Node::selector, sel.path(), sel.line(), 0);
} break;
}
}
Node selector_base(Node sel)
{
switch (sel.type())
{
case Node::selector: {
return sel.back();
} break;
default: {
return sel;
} break;
}
}
static Node selector_but(Node sel, Node_Factory& new_Node, size_t start, size_t from_end)
{
switch (sel.type())
{
case Node::selector: {
Node bf(new_Node(Node::selector, sel.path(), sel.line(), sel.size() - 1));
for (size_t i = start, S = sel.size() - from_end; i < S; ++i) {
bf << sel[i];
}
return bf;
} break;
default: {
return new_Node(Node::selector, sel.path(), sel.line(), 0);
} break;
}
}
Node selector_butfirst(Node sel, Node_Factory& new_Node)
{ return selector_but(sel, new_Node, 1, 0); }
Node selector_butlast(Node sel, Node_Factory& new_Node)
{ return selector_but(sel, new_Node, 0, 1); }
}
......@@ -11,10 +11,21 @@
namespace Sass {
using std::map;
Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry);
Node eval(Node expr, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& src_refs);
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node);
double operate(Node::Type op, double lhs, double rhs);
Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& src_refs);
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& src_refs);
Node expand_selector(Node sel, Node pre, Node_Factory& new_Node);
Node expand_backref(Node sel, Node pre);
void extend_selectors(vector<pair<Node, Node> >&, Node_Factory&);
Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node);
Node selector_prefix(Node sel, Node_Factory& new_Node);
Node selector_base(Node sel);
Node selector_butfirst(Node sel, Node_Factory& new_Node);
Node selector_butlast(Node sel, Node_Factory& new_Node);
}
\ No newline at end of file
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
#include "node_factory.hpp"
#include "functions.hpp"
#include "error.hpp"
#include <iostream>
......@@ -10,118 +11,115 @@ using std::cerr; using std::endl;
namespace Sass {
namespace Functions {
static void eval_error(string message, size_t line_number, const char* file_name)
static void throw_eval_error(string message, string path, size_t line)
{
string fn;
if (file_name) {
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
if (!path.empty() && Prelexer::string_constant(path.c_str()))
path = path.substr(1, path.length() - 1);
throw Error(Error::evaluation, path, line, message);
}
// RGB Functions ///////////////////////////////////////////////////////
Function_Descriptor rgb_descriptor =
{ "rgb", "$red", "$green", "$blue", 0 };
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]);
if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number)) {
eval_error("arguments for rgb must be numbers", r.line_number, r.file_name);
if (!(r.type() == Node::number && g.type() == Node::number && b.type() == Node::number)) {
throw_eval_error("arguments for rgb must be numbers", r.path(), r.line());
}
Node color(Node::numeric_color, registry, 0, 4);
color << r << g << b << Node(0, 1.0);
return color;
return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), 1.0);
}
Function_Descriptor rgba_4_descriptor =
{ "rgba", "$red", "$green", "$blue", "$alpha", 0 };
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]);
Node a(bindings[parameters[3]]);
if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number && a.type == Node::number)) {
eval_error("arguments for rgba must be numbers", r.line_number, r.file_name);
if (!(r.type() == Node::number && g.type() == Node::number && b.type() == Node::number && a.type() == Node::number)) {
throw_eval_error("arguments for rgba must be numbers", r.path(), r.line());
}
Node color(Node::numeric_color, registry, 0, 4);
color << r << g << b << a;
return color;
return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value());
}
Function_Descriptor rgba_2_descriptor =
{ "rgba", "$color", "$alpha", 0 };
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]].clone(registry));
color[3] = bindings[parameters[1]];
return color;
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
Node r(color[0]);
Node g(color[1]);
Node b(color[2]);
Node a(bindings[parameters[1]]);
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());
return new_Node(color.path(), color.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value());
}
Function_Descriptor red_descriptor =
{ "red", "$color", 0 };
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to red must be a color", color.line_number, color.file_name);
if (color.type() != Node::numeric_color) throw_eval_error("argument to red must be a color", color.path(), color.line());
return color[0];
}
Function_Descriptor green_descriptor =
{ "green", "$color", 0 };
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to green must be a color", color.line_number, color.file_name);
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 =
{ "blue", "$color", 0 };
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to blue must be a color", color.line_number, color.file_name);
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, vector<vector<Node>*>& registry) {
if (!(color1.type == Node::numeric_color && color2.type == Node::numeric_color)) {
eval_error("first two arguments to mix must be colors", color1.line_number, color1.file_name);
Node mix_impl(Node color1, Node color2, double weight, Node_Factory& new_Node) {
if (!(color1.type() == Node::numeric_color && color2.type() == Node::numeric_color)) {
throw_eval_error("first two arguments to mix must be colors", color1.path(), color1.line());
}
double p = weight/100;
double w = 2*p - 1;
double a = color1[3].content.numeric_value - color2[3].content.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 w2 = 1 - w1;
Node mixed(Node::numeric_color, registry, color1.line_number, 4);
Node mixed(new_Node(Node::numeric_color, color1.path(), color1.line(), 4));
for (int i = 0; i < 3; ++i) {
mixed << Node(mixed.line_number, w1*color1[i].content.numeric_value +
w2*color2[i].content.numeric_value);
mixed << new_Node(mixed.path(), mixed.line(),
w1*color1[i].numeric_value() + w2*color2[i].numeric_value());
}
double alpha = color1[3].content.numeric_value*p + color2[3].content.numeric_value*(1-p);
mixed << Node(mixed.line_number, alpha);
double alpha = color1[3].numeric_value()*p + color2[3].numeric_value()*(1-p);
mixed << new_Node(mixed.path(), mixed.line(), alpha);
return mixed;
}
Function_Descriptor mix_2_descriptor =
{ "mix", "$color1", "$color2", 0 };
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, registry);
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, new_Node);
}
Function_Descriptor mix_3_descriptor =
{ "mix", "$color1", "$color2", "$weight", 0 };
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node percentage(bindings[parameters[2]]);
if (!(percentage.type == Node::number || percentage.type == Node::numeric_percentage || percentage.type == Node::numeric_dimension)) {
eval_error("third argument to mix must be numeric", percentage.line_number, percentage.file_name);
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]],
bindings[parameters[1]],
percentage.numeric_value(),
registry);
new_Node);
}
// HSL Functions ///////////////////////////////////////////////////////
......@@ -135,7 +133,7 @@ namespace Sass {
return m1;
}
Node hsla_impl(double h, double s, double l, double a, vector<vector<Node>*>& registry) {
Node hsla_impl(double h, double s, double l, double a, Node_Factory& new_Node) {
h = static_cast<double>(((static_cast<int>(h) % 360) + 360) % 360) / 360.0;
s = s / 100.0;
l = l / 100.0;
......@@ -148,53 +146,53 @@ namespace Sass {
double g = h_to_rgb(m1, m2, h) * 255.0;
double b = h_to_rgb(m1, m2, h-1.0/3.0) * 255.0;
return Node(registry, 0, r, g, b, a);
return new_Node("", 0, r, g, b, a);
}
Function_Descriptor hsla_descriptor =
{ "hsla", "$hue", "$saturation", "$lightness", "$alpha", 0 };
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric() &&
bindings[parameters[3]].is_numeric())) {
eval_error("arguments to hsla must be numeric", bindings[parameters[0]].line_number, bindings[parameters[0]].file_name);
throw_eval_error("arguments to hsla must be numeric", bindings[parameters[0]].path(), bindings[parameters[0]].line());
}
double h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value();
double a = bindings[parameters[3]].numeric_value();
Node color(hsla_impl(h, s, l, a, registry));
color.line_number = bindings[parameters[0]].line_number;
Node color(hsla_impl(h, s, l, a, new_Node));
// color.line() = bindings[parameters[0]].line();
return color;
}
Function_Descriptor hsl_descriptor =
{ "hsl", "$hue", "$saturation", "$lightness", 0 };
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric())) {
eval_error("arguments to hsl must be numeric", bindings[parameters[0]].line_number, bindings[parameters[0]].file_name);
throw_eval_error("arguments to hsl must be numeric", bindings[parameters[0]].path(), bindings[parameters[0]].line());
}
double h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value();
Node color(hsla_impl(h, s, l, 1, registry));
color.line_number = bindings[parameters[0]].line_number;
Node color(hsla_impl(h, s, l, 1, new_Node));
// color.line() = bindings[parameters[0]].line();
return color;
}
Function_Descriptor invert_descriptor =
{ "invert", "$color", 0 };
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
if (orig.type != Node::numeric_color) eval_error("argument to invert must be a color", orig.line_number, orig.file_name);
return Node(registry, orig.line_number,
255 - orig[0].content.numeric_value,
255 - orig[1].content.numeric_value,
255 - orig[2].content.numeric_value,
orig[3].content.numeric_value);
if (orig.type() != Node::numeric_color) throw_eval_error("argument to invert must be a color", orig.path(), orig.line());
return new_Node(orig.path(), orig.line(),
255 - orig[0].numeric_value(),
255 - orig[1].numeric_value(),
255 - orig[2].numeric_value(),
orig[3].numeric_value());
}
// Opacity Functions ///////////////////////////////////////////////////
......@@ -203,9 +201,9 @@ namespace Sass {
{ "alpha", "$color", 0 };
Function_Descriptor opacity_descriptor =
{ "opacity", "$color", 0 };
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to alpha must be a color", color.line_number, color.file_name);
if (color.type() != Node::numeric_color) throw_eval_error("argument to alpha must be a color", color.path(), color.line());
return color[3];
}
......@@ -213,58 +211,64 @@ namespace Sass {
{ "opacify", "$color", "$amount", 0 };
Function_Descriptor fade_in_descriptor =
{ "fade_in", "$color", "$amount", 0 };
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type != Node::numeric_color || !bindings[parameters[1]].is_numeric()) {
eval_error("arguments to opacify/fade_in must be a color and a numeric value", cpy.line_number, cpy.file_name);
}
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
Node delta(bindings[parameters[1]]);
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) eval_error("amount must be between 0 and 1 for opacify/fade-in", delta.line_number, delta.file_name);
cpy[3].content.numeric_value += delta.numeric_value();
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1;
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0;
return cpy;
if (color.type() != Node::numeric_color || !delta.is_numeric()) {
throw_eval_error("arguments to opacify/fade_in must be a color and a numeric value", color.path(), color.line());
}
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) {
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();
if (alpha > 1) alpha = 1;
else if (alpha < 0) alpha = 0;
return new_Node(color.path(), color.line(),
color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), alpha);
}
Function_Descriptor transparentize_descriptor =
{ "transparentize", "$color", "$amount", 0 };
Function_Descriptor fade_out_descriptor =
{ "fade_out", "$color", "$amount", 0 };
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type != Node::numeric_color || !bindings[parameters[1]].is_numeric()) {
eval_error("arguments to transparentize/fade_out must be a color and a numeric value", cpy.line_number, cpy.file_name);
}
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
Node delta(bindings[parameters[1]]);
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) eval_error("amount must be between 0 and 1 for transparentize/fade-out", delta.line_number, delta.file_name);
cpy[3].content.numeric_value -= delta.numeric_value();
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1;
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0;
return cpy;
if (color.type() != Node::numeric_color || !delta.is_numeric()) {
throw_eval_error("arguments to transparentize/fade_out must be a color and a numeric value", color.path(), color.line());
}
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) {
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();
if (alpha > 1) alpha = 1;
else if (alpha < 0) alpha = 0;
return new_Node(color.path(), color.line(),
color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), alpha);
}
// String Functions ////////////////////////////////////////////////////
Function_Descriptor unquote_descriptor =
{ "unquote", "$string", 0 };
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
// if (cpy.type != Node::string_constant /* && cpy.type != Node::concatenation */) {
// eval_error("argument to unquote must be a string", cpy.line_number, cpy.file_name);
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node cpy(new_Node(bindings[parameters[0]]));
// 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.unquoted = true;
cpy.is_unquoted() = true;
return cpy;
}
Function_Descriptor quote_descriptor =
{ "quote", "$string", 0 };
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type != Node::string_constant && cpy.type != Node::identifier) {
eval_error("argument to quote must be a string or identifier", cpy.line_number, cpy.file_name);
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
if (orig.type() != Node::string_constant && orig.type() != Node::identifier) {
throw_eval_error("argument to quote must be a string or identifier", orig.path(), orig.line());
}
cpy.type = Node::string_constant;
cpy.unquoted = false;
Node cpy(new_Node(orig));
cpy.is_unquoted() = false;
return cpy;
}
......@@ -272,159 +276,231 @@ namespace Sass {
Function_Descriptor percentage_descriptor =
{ "percentage", "$value", 0 };
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
// TO DO: make sure it's not already a percentage
if (cpy.type != Node::number) eval_error("argument to percentage must be a unitless number", cpy.line_number, cpy.file_name);
cpy.content.numeric_value = cpy.content.numeric_value * 100;
cpy.type = Node::numeric_percentage;
return cpy;
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
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 =
{ "round", "$value", 0 };
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value + 0.5);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage) {
cpy.content.numeric_value = std::floor(cpy.content.numeric_value + 0.5);
}
else {
eval_error("argument to round must be numeric", cpy.line_number, cpy.file_name);
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value() + 0.5), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value() + 0.5));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value() + 0.5),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to round must be numeric", orig.path(), orig.line());
} break;
}
return cpy;
// unreachable statement
return Node();
}
Function_Descriptor ceil_descriptor =
{ "ceil", "$value", 0 };
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::ceil(cpy.content.dimension.numeric_value);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::ceil(cpy.content.numeric_value);
}
else {
eval_error("argument to ceil must be numeric", cpy.line_number, cpy.file_name);
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::ceil(orig.numeric_value()), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::ceil(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::ceil(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to ceil must be numeric", orig.path(), orig.line());
} break;
}
return cpy;
// unreachable statement
return Node();
}
Function_Descriptor floor_descriptor =
{ "floor", "$value", 0 };
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::floor(cpy.content.numeric_value);
}
else {
eval_error("argument to floor must be numeric", cpy.line_number, cpy.file_name);
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value()), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to floor must be numeric", orig.path(), orig.line());
} break;
}
return cpy;
// unreachable statement
return Node();
}
Function_Descriptor abs_descriptor =
{ "abs", "$value", 0 };
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::fabs(cpy.content.dimension.numeric_value);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::abs(cpy.content.numeric_value);
}
else {
eval_error("argument to abs must be numeric", cpy.line_number, cpy.file_name);
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::abs(orig.numeric_value()), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::abs(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::abs(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to abs must be numeric", orig.path(), orig.line());
} break;
}
return cpy;
// unreachable statement
return Node();
}
// List Functions //////////////////////////////////////////////////////
Function_Descriptor length_descriptor =
{ "length", "$list", 0 };
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node arg(bindings[parameters[0]]);
if (arg.type == Node::space_list || arg.type == Node::comma_list) {
return Node(arg.line_number, arg.size());
}
else if (arg.type == Node::nil) {
return Node(arg.line_number, 0);
}
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
else {
return Node(arg.line_number, 1);
return new_Node(arg.path(), arg.line(), 1);
} break;
}
// unreachable statement
return Node();
}
Function_Descriptor nth_descriptor =
{ "nth", "$list", "$n", 0 };
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node l(bindings[parameters[0]]);
// TO DO: check for empty list
if (l.type == Node::nil) eval_error("cannot index into an empty list", l.line_number, l.file_name);
if (l.type != Node::space_list && l.type != Node::comma_list) {
l = Node(Node::space_list, registry, l.line_number, 1) << l;
}
Node n(bindings[parameters[1]]);
if (n.type != Node::number) eval_error("second argument to nth must be a number", n.line_number, n.file_name);
if (n.numeric_value() < 1 || n.numeric_value() > l.size()) eval_error("out of range index for nth", n.line_number, n.file_name);
return l[bindings[parameters[1]].content.numeric_value - 1];
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
if (l.type() != Node::space_list && l.type() != Node::comma_list) {
l = new_Node(Node::space_list, l.path(), l.line(), 1) << l;
}
double n_prim = n.numeric_value();
if (n_prim < 1 || n_prim > l.size()) {
throw_eval_error("out of range index for nth", n.path(), n.line());
}
return l[n_prim - 1];
}
extern const char separator_kwd[] = "$separator";
Node join_impl(const vector<Token>& parameters, map<Token, Node>& bindings, bool has_sep, vector<vector<Node>*>& registry) {
Node join_impl(const vector<Token>& parameters, map<Token, Node>& bindings, bool has_sep, Node_Factory& new_Node) {
// if the args aren't lists, turn them into singleton lists
Node l1(bindings[parameters[0]]);
if (l1.type != Node::space_list && l1.type != Node::comma_list && l1.type != Node::nil) {
l1 = Node(Node::space_list, registry, l1.line_number, 1) << l1;
if (l1.type() != Node::space_list && l1.type() != Node::comma_list && l1.type() != Node::nil) {
l1 = new_Node(Node::space_list, l1.path(), l1.line(), 1) << l1;
}
Node l2(bindings[parameters[1]]);
if (l2.type != Node::space_list && l2.type != Node::comma_list && l2.type != Node::nil) {
l2 = Node(Node::space_list, registry, l2.line_number, 1) << l2;
if (l2.type() != Node::space_list && l2.type() != Node::comma_list && l2.type() != Node::nil) {
l2 = new_Node(Node::space_list, l2.path(), l2.line(), 1) << l2;
}
// nil + nil = nil
if (l1.type() == Node::nil && l2.type() == Node::nil) {
return new_Node(Node::nil, l1.path(), l1.line(), 0);
}
// nil and nil is nil
if (l1.type == Node::nil && l2.type == Node::nil) return Node(Node::nil, registry, l1.line_number);
// figure out the combined size in advance
size_t size = 0;
if (l1.type != Node::nil) size += l1.size();
if (l2.type != Node::nil) size += l2.size();
// accumulate the result
Node lr(l1.type, registry, l1.line_number, size);
if (l1.type() != Node::nil) size += l1.size();
if (l2.type() != Node::nil) size += l2.size();
// figure out the result type in advance
Node::Type rtype = Node::space_list;
if (has_sep) {
string sep(bindings[parameters[2]].content.token.unquote());
if (sep == "comma") lr.type = Node::comma_list;
else if (sep == "space") lr.type = Node::space_list;
else if (sep == "auto") ; // leave it alone
string sep(bindings[parameters[2]].token().unquote());
if (sep == "comma") rtype = Node::comma_list;
else if (sep == "space") rtype = Node::space_list;
else if (sep == "auto") rtype = l1.type();
else {
eval_error("third argument to join must be 'space', 'comma', or 'auto'", l2.line_number, l2.file_name);
throw_eval_error("third argument to join must be 'space', 'comma', or 'auto'", l2.path(), l2.line());
}
}
else if (l1.type != Node::nil) lr.type = l1.type;
else if (l2.type != Node::nil) lr.type = l2.type;
if (l1.type != Node::nil) lr += l1;
if (l2.type != Node::nil) lr += l2;
else if (l1.type() != Node::nil) rtype = l1.type();
else if (l2.type() != Node::nil) rtype = l2.type();
// accumulate the result
Node lr(new_Node(rtype, l1.path(), l1.line(), size));
if (l1.type() != Node::nil) lr += l1;
if (l2.type() != Node::nil) lr += l2;
return lr;
}
Function_Descriptor join_2_descriptor =
{ "join", "$list1", "$list2", 0 };
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return join_impl(parameters, bindings, false, registry);
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return join_impl(parameters, bindings, false, new_Node);
}
Function_Descriptor join_3_descriptor =
{ "join", "$list1", "$list2", "$separator", 0 };
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return join_impl(parameters, bindings, true, registry);
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return join_impl(parameters, bindings, true, new_Node);
}
// Introspection Functions /////////////////////////////////////////////
......@@ -437,35 +513,37 @@ namespace Sass {
Function_Descriptor type_of_descriptor =
{ "type-of", "$value", 0 };
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
Node type(Node::string_constant, val.line_number, Token::make());
type.unquoted = true;
switch (val.type)
Token type_name;
switch (val.type())
{
case Node::number:
case Node::numeric_dimension:
case Node::numeric_percentage:
type.content.token = Token::make(number_name);
break;
case Node::boolean:
type.content.token = Token::make(bool_name);
break;
case Node::numeric_percentage: {
type_name = Token::make(number_name);
} break;
case Node::boolean: {
type_name = Token::make(bool_name);
} break;
case Node::string_constant:
case Node::value_schema:
type.content.token = Token::make(string_name);
break;
case Node::numeric_color:
type.content.token = Token::make(color_name);
break;
case Node::value_schema: {
type_name = Token::make(string_name);
} break;
case Node::numeric_color: {
type_name = Token::make(color_name);
} break;
case Node::comma_list:
case Node::space_list:
case Node::nil:
type.content.token = Token::make(list_name);
break;
default:
type.content.token = Token::make(string_name);
case Node::nil: {
type_name = Token::make(list_name);
} break;
default: {
type_name = Token::make(string_name);
} break;
}
Node type(new_Node(Node::string_constant, val.path(), val.line(), type_name));
type.is_unquoted() = true;
return type;
}
......@@ -474,25 +552,25 @@ namespace Sass {
Function_Descriptor unit_descriptor =
{ "unit", "$number", 0 };
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
Node u(Node::string_constant, val.line_number, Token::make());
switch (val.type)
switch (val.type())
{
case Node::number:
u.content.token = Token::make(empty_str);
break;
case Node::numeric_percentage:
u.content.token = Token::make(percent_str);
break;
case Node::number: {
return new_Node(Node::string_constant, val.path(), val.line(), Token::make(empty_str));
} break;
case Node::numeric_dimension:
u.content.token = Token::make(val.content.dimension.unit, Prelexer::identifier(val.content.dimension.unit));
break;
default:
eval_error("argument to unit must be numeric", val.line_number, val.file_name);
break;
case Node::numeric_percentage: {
return new_Node(Node::string_constant, val.path(), val.line(), val.unit());
} break;
default: {
throw_eval_error("argument to unit must be numeric", val.path(), val.line());
} break;
}
return u;
// unreachable statement
return Node();
}
extern const char true_str[] = "true";
......@@ -500,100 +578,73 @@ namespace Sass {
Function_Descriptor unitless_descriptor =
{ "unitless", "$number", 0 };
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
Node result(Node::string_constant, val.line_number, Token::make());
result.unquoted = true;
switch (val.type)
switch (val.type())
{
case Node::number: {
Node T(Node::boolean);
T.line_number = val.line_number;
T.content.boolean_value = true;
return T;
return new_Node(Node::boolean, val.path(), val.line(), true);
} break;
case Node::numeric_percentage:
case Node::numeric_dimension: {
Node F(Node::boolean);
F.line_number = val.line_number;
F.content.boolean_value = false;
return F;
return new_Node(Node::boolean, val.path(), val.line(), false);
} break;
default: {
throw_eval_error("argument to unitless must be numeric", val.path(), val.line());
} break;
default:
eval_error("argument to unitless must be numeric", val.line_number, val.file_name);
break;
}
return result;
// unreachable statement
return Node();
}
Function_Descriptor comparable_descriptor =
{ "comparable", "$number_1", "$number_2", 0 };
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node n1(bindings[parameters[0]]);
Node n2(bindings[parameters[1]]);
Node::Type t1 = n1.type;
Node::Type t2 = n2.type;
if (t1 == Node::number && n2.is_numeric() ||
n1.is_numeric() && t2 == Node::number) {
Node T(Node::boolean);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
Node::Type t1 = n1.type();
Node::Type t2 = n2.type();
if ((t1 == Node::number && n2.is_numeric()) ||
(n1.is_numeric() && t2 == Node::number)) {
return new_Node(Node::boolean, n1.path(), n1.line(), true);
}
else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) {
Node T(Node::boolean);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
return new_Node(Node::boolean, n1.path(), n1.line(), true);
}
else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) {
string u1(Token::make(n1.content.dimension.unit, Prelexer::identifier(n1.content.dimension.unit)).to_string());
string u2(Token::make(n2.content.dimension.unit, Prelexer::identifier(n2.content.dimension.unit)).to_string());
if (u1 == "ex" && u2 == "ex" ||
u1 == "em" && u2 == "em" ||
(u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc")) {
Node T(Node::boolean);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
string u1(n1.unit().to_string());
string u2(n2.unit().to_string());
if ((u1 == "ex" && u2 == "ex") ||
(u1 == "em" && u2 == "em") ||
((u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc"))) {
return new_Node(Node::boolean, n1.path(), n1.line(), true);
}
else {
Node F(Node::boolean);
F.line_number = n1.line_number;
F.content.boolean_value = false;
return F;
return new_Node(Node::boolean, n1.path(), n1.line(), false);
}
}
else if (!n1.is_numeric() && !n2.is_numeric()) {
eval_error("arguments to comparable must be numeric", n1.line_number, n1.file_name);
throw_eval_error("arguments to comparable must be numeric", n1.path(), n1.line());
}
Node F(Node::boolean);
F.line_number = n1.line_number;
F.content.boolean_value = false;
return F;
// default to false if we missed anything
return new_Node(Node::boolean, n1.path(), n1.line(), false);
}
// Boolean Functions ///////////////////////////////////////////////////
Function_Descriptor not_descriptor =
{ "not", "value", 0 };
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
if (val.type == Node::boolean && val.content.boolean_value == false) {
Node T(Node::boolean);
T.line_number = val.line_number;
T.content.boolean_value = true;
return T;
if (val.type() == Node::boolean && val.boolean_value() == false) {
return new_Node(Node::boolean, val.path(), val.line(), true);
}
else {
Node F(Node::boolean);
F.line_number = val.line_number;
F.content.boolean_value = false;
return F;
return new_Node(Node::boolean, val.path(), val.line(), false);
}
}
}
}
......@@ -8,7 +8,7 @@
namespace Sass {
using std::map;
typedef Node (*Implementation)(const vector<Token>&, map<Token, Node>&, vector<vector<Node>*>& registry);
typedef Node (*Implementation)(const vector<Token>&, map<Token, Node>&, Node_Factory& new_Node);
typedef const char* str;
typedef str Function_Descriptor[];
......@@ -37,8 +37,8 @@ namespace Sass {
}
}
Node operator()(map<Token, Node>& bindings, vector<vector<Node>*>& registry) const
{ return implementation(parameters, bindings, registry); }
Node operator()(map<Token, Node>& bindings, Node_Factory& new_Node) const
{ return implementation(parameters, bindings, new_Node); }
};
......@@ -47,111 +47,111 @@ namespace Sass {
// RGB Functions ///////////////////////////////////////////////////////
extern Function_Descriptor rgb_descriptor;
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor rgba_4_descriptor;
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor rgba_2_descriptor;
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor red_descriptor;
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor green_descriptor;
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor blue_descriptor;
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor mix_2_descriptor;
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor mix_3_descriptor;
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// HSL Functions ///////////////////////////////////////////////////////
extern Function_Descriptor hsla_descriptor;
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor hsl_descriptor;
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor invert_descriptor;
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Opacity Functions ///////////////////////////////////////////////////
extern Function_Descriptor alpha_descriptor;
extern Function_Descriptor opacity_descriptor;
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor opacify_descriptor;
extern Function_Descriptor fade_in_descriptor;
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor transparentize_descriptor;
extern Function_Descriptor fade_out_descriptor;
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// String Functions ////////////////////////////////////////////////////
extern Function_Descriptor unquote_descriptor;
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor quote_descriptor;
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Number Functions ////////////////////////////////////////////////////
extern Function_Descriptor percentage_descriptor;
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor round_descriptor;
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor ceil_descriptor;
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor floor_descriptor;
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor abs_descriptor;
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// List Functions //////////////////////////////////////////////////////
extern Function_Descriptor length_descriptor;
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor nth_descriptor;
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor join_2_descriptor;
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor join_3_descriptor;
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Introspection Functions /////////////////////////////////////////////
extern Function_Descriptor type_of_descriptor;
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor unit_descriptor;
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor unitless_descriptor;
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor comparable_descriptor;
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Boolean Functions ///////////////////////////////////////////////////
extern Function_Descriptor not_descriptor;
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
}
......
#include <iostream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <sstream>
#include <algorithm>
#include "node.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
#include "error.hpp"
#include <iostream>
namespace Sass {
size_t Node::allocations = 0;
size_t Node::destructed = 0;
using namespace std;
Node Node::clone(vector<vector<Node>*>& registry) const
// ------------------------------------------------------------------------
// Node method implementations
// ------------------------------------------------------------------------
void Node::flatten()
{
Node n(*this);
if (has_children) {
n.content.children = new vector<Node>;
++allocations;
n.content.children->reserve(size());
if (type() != block && type() != expansion && type() != root && type() != for_through_directive && type() != for_to_directive) return;
// size can change during flattening, so we need to call size() on each pass
for (size_t i = 0; i < size(); ++i) {
n << at(i).clone(registry);
Type i_type = at(i).type();
if ((i_type == expansion) || (i_type == block) || (i_type == for_through_directive) || (i_type == for_to_directive)) {
Node expn(at(i));
if (expn.has_expansions()) expn.flatten();
ip_->has_statements |= expn.has_statements();
ip_->has_blocks |= expn.has_blocks();
ip_->has_expansions |= expn.has_expansions();
// TO DO: make this more efficient -- replace with a dummy node instead of erasing
ip_->children.erase(begin() + i);
insert(begin() + i, expn.begin(), expn.end());
// skip over what we just spliced in
i += expn.size() - 1;
}
registry.push_back(n.content.children);
}
return n;
}
string Node::to_string(const string& prefix) const
bool Node::operator==(Node rhs) const
{
switch (type)
Type t = type();
if (t != rhs.type()) return false;
switch (t)
{
case selector_group: { // really only needed for arg to :not
string result(at(0).to_string(""));
for (size_t i = 1; i < size(); ++i) {
result += ", ";
result += at(i).to_string("");
case comma_list:
case space_list:
case expression:
case term:
case numeric_color: {
if (size() != rhs.size()) return false;
for (size_t i = 0, L = size(); i < L; ++i) {
if (at(i) == rhs[i]) continue;
else return false;
}
return result;
return true;
} break;
case selector: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += ' ';
}
// if (at(0).type == selector_combinator) {
// result += at(0).content.token.to_string();
// result += ' ';
// }
// else {
// Node::Type t = at(0).type;
// result += at(0).to_string(t == backref ? prefix : "");
// }
result += at(0).to_string(at(0).has_backref ? prefix : "");
for (size_t i = 1; i < size(); ++i) {
result += " ";
result += at(i).to_string(at(i).has_backref ? prefix : "");
}
return result;
case variable:
case identifier:
case uri:
case textual_percentage:
case textual_dimension:
case textual_number:
case textual_hex:
case string_constant: {
return token().unquote() == rhs.token().unquote();
} break;
case selector_combinator: {
string result(prefix.empty() ? "" : prefix + " ");
result += content.token.to_string();
return result;
// return content.token.to_string();
// if (std::isspace(content.token.begin[0])) return string(" ");
// else return string(content.token);
case number:
case numeric_percentage: {
return numeric_value() == rhs.numeric_value();
} break;
case simple_selector_sequence: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += " ";
case numeric_dimension: {
if (unit() == rhs.unit()) {
return numeric_value() == rhs.numeric_value();
}
for (size_t i = 0; i < size(); ++i) {
Node::Type t = at(i).type;
result += at(i).to_string(t == backref ? prefix : "");
else {
return false;
}
return result;
} break;
case pseudo:
case simple_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += content.token.to_string();
return result;
case boolean: {
return boolean_value() == rhs.boolean_value();
} break;
case pseudo_negation: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
result += at(1).to_string("");
result += ')';
return result;
default: {
return true;
} break;
case functional_pseudo: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
for (size_t i = 1; i < size(); ++i) {
result += at(i).to_string("");
}
result += ')';
return result;
} break;
case attribute_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += "[";
for (size_t i = 0; i < size(); ++i)
{ result += at(i).to_string(prefix); }
result += ']';
return result;
} break;
return false;
}
case backref: {
return prefix;
} break;
bool Node::operator!=(Node rhs) const
{ return !(*this == rhs); }
case comma_list: {
string result(at(0).to_string(prefix));
for (size_t i = 1; i < size(); ++i) {
if (at(i).type == nil) continue;
result += ", ";
result += at(i).to_string(prefix);
}
return result;
} break;
bool Node::operator<(Node rhs) const
{
Type lhs_type = type();
Type rhs_type = rhs.type();
case space_list: {
string result(at(0).to_string(prefix));
for (size_t i = 1; i < size(); ++i) {
if (at(i).type == nil) continue;
result += " ";
result += at(i).to_string(prefix);
// comparing atomic numbers
if ((lhs_type == number && rhs_type == number) ||
(lhs_type == numeric_percentage && rhs_type == numeric_percentage)) {
return numeric_value() < rhs.numeric_value();
}
return result;
} break;
case expression:
case term: {
string result(at(0).to_string(prefix));
for (size_t i = 1; i < size(); ++i) {
if (!(at(i).type == add ||
// at(i).type == sub || // another edge case -- consider uncommenting
at(i).type == mul)) {
result += at(i).to_string(prefix);
// comparing numbers with units
else if (lhs_type == numeric_dimension && rhs_type == numeric_dimension) {
if (unit() == rhs.unit()) {
return numeric_value() < rhs.numeric_value();
}
else {
throw Error(Error::evaluation, path(), line(), "incompatible units");
}
return result;
} break;
//edge case
case sub: {
return "-";
} break;
case div: {
return "/";
} break;
case css_import: {
stringstream ss;
ss << "@import url(";
ss << content.token.to_string();
// cerr << content.token.to_string() << endl;
ss << ")";
return ss.str();
}
case function_call: {
stringstream ss;
ss << at(0).to_string("");
ss << "(";
ss << at(1).to_string("");
ss << ")";
return ss.str();
// comparing colors
else if (lhs_type == numeric_color && rhs_type == numeric_color) {
return lexicographical_compare(begin(), end(), rhs.begin(), rhs.end());
}
case arguments: {
stringstream ss;
if (size() > 0) {
ss << at(0).to_string("");
for (size_t i = 1; i < size(); ++i) {
ss << ", ";
ss << at(i).to_string("");
}
}
return ss.str();
// comparing identifiers and strings (treat them as comparable)
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();
}
case unary_plus: {
stringstream ss;
ss << "+";
ss << at(0).to_string("");
return ss.str();
}
// COMPARING SELECTORS -- IMPORTANT FOR INHERITANCE
else if ((type() >= selector_group && type() <=selector_schema) &&
(rhs.type() >= selector_group && rhs.type() <=selector_schema)) {
case unary_minus: {
stringstream ss;
ss << "-";
ss << at(0).to_string("");
return ss.str();
}
// if they're not the same kind, just compare type tags
if (type() != rhs.type()) return type() < rhs.type();
case numeric_percentage: {
stringstream ss;
ss << content.dimension.numeric_value;
ss << '%';
return ss.str();
}
// otherwise we have to do more work
switch (type())
{
case simple_selector:
case pseudo: {
return token() < rhs.token();
} break;
case numeric_dimension: {
stringstream ss;
ss << content.dimension.numeric_value;
ss << string(content.dimension.unit, 2);
// << string(content.dimension.unit, Prelexer::identifier(content.dimension.unit) - content.dimension.unit);
// cerr << Token::make(content.dimension.unit, content.dimension.unit + 2).to_string();
// << Token::make(content.dimension.unit, Prelexer::identifier(content.dimension.unit)).to_string();
return ss.str();
case selector:
case attribute_selector: {
return lexicographical_compare(begin(), end(), rhs.begin(), rhs.end());
} break;
case number: {
stringstream ss;
ss << content.numeric_value;
return ss.str();
default: {
return false;
} break;
case numeric_color: {
if (at(3).content.numeric_value >= 1.0) {
double a = at(0).content.numeric_value;
double b = at(1).content.numeric_value;
double c = at(2).content.numeric_value;
if (a >= 0xff && b >= 0xff && c >= 0xff)
{ return "white"; }
else if (a >= 0xff && b >= 0xff && c == 0)
{ return "yellow"; }
else if (a == 0 && b >= 0xff && c >= 0xff)
{ return "aqua"; }
else if (a >= 0xff && b == 0 && c >= 0xff)
{ return "fuchsia"; }
else if (a >= 0xff && b == 0 && c == 0)
{ return "red"; }
else if (a == 0 && b >= 0xff && c == 0)
{ return "lime"; }
else if (a == 0 && b == 0 && c >= 0xff)
{ return "blue"; }
else if (a <= 0 && b <= 0 && c <= 0)
{ return "black"; }
else
{
stringstream ss;
ss << '#' << std::setw(2) << std::setfill('0') << std::hex;
for (size_t i = 0; i < 3; ++i) {
double x = at(i).content.numeric_value;
if (x > 0xff) x = 0xff;
else if (x < 0) x = 0;
ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(x+0.5));
}
return ss.str();
}
}
// END OF SELECTOR COMPARISON
// catch-all
else {
stringstream ss;
ss << "rgba(" << static_cast<unsigned long>(at(0).content.numeric_value);
for (size_t i = 1; i < 3; ++i) {
ss << ", " << static_cast<unsigned long>(at(i).content.numeric_value);
throw Error(Error::evaluation, path(), line(), "incomparable types");
}
ss << ", " << at(3).content.numeric_value << ')';
return ss.str();
}
} break;
case uri: {
string result("url(");
result += string(content.token);
result += ")";
return result;
} break;
// case expansion: {
// string result("MIXIN CALL: ");
// return result;
// } break;
bool Node::operator<=(Node rhs) const
{ return *this < rhs || *this == rhs; }
case string_constant: {
if (unquoted) return content.token.unquote();
else {
string result(content.token.to_string());
if (result[0] != '"' && result[0] != '\'') return "\"" + result + "\"";
else return result;
}
} break;
bool Node::operator>(Node rhs) const
{ return !(*this <= rhs); }
case boolean: {
if (content.boolean_value) return "true";
else return "false";
} break;
bool Node::operator>=(Node rhs) const
{ return !(*this < rhs); }
case important: {
return "!important";
} break;
case value_schema: {
string result;
for (size_t i = 0; i < size(); ++i) result += at(i).to_string("");
return result;
} break;
// ------------------------------------------------------------------------
// Token method implementations
// ------------------------------------------------------------------------
case string_schema: {
string Token::unquote() const
{
string result;
for (size_t i = 0; i < size(); ++i) result += at(i).to_string("");
const char* p = begin;
if (*begin == '\'' || *begin == '"') {
++p;
while (p < end) {
if (*p == '\\') {
switch (*(++p)) {
case 'n': result += '\n'; break;
case 't': result += '\t'; break;
case 'b': result += '\b'; break;
case 'r': result += '\r'; break;
case 'f': result += '\f'; break;
case 'v': result += '\v'; break;
case 'a': result += '\a'; break;
case '\\': result += '\\'; break;
default: result += *p; break;
}
}
else if (p == end - 1) {
return result;
} break;
default: {
// return content.token.to_string();
if (!has_children && type != flags) return content.token.to_string();
else return "";
} break;
}
else {
result += *p;
}
void Node::echo(stringstream& buf, size_t depth) {
string indentation(2*depth, ' ');
switch (type) {
case comment:
buf << indentation << string(content.token) << endl;
break;
case ruleset:
buf << indentation;
at(0).echo(buf, depth);
at(1).echo(buf, depth);
break;
case selector_group:
at(0).echo(buf, depth);
for (size_t i = 1; i < size(); ++i) {
buf << ", ";
at(i).echo(buf, depth);
++p;
}
break;
case selector:
for (size_t i = 0; i < size(); ++i) {
at(i).echo(buf, depth);
return result;
}
break;
case selector_combinator:
if (std::isspace(content.token.begin[0])) buf << ' ';
else buf << ' ' << string(content.token) << ' ';
break;
case simple_selector_sequence:
for (size_t i = 0; i < size(); ++i) {
buf << at(i).to_string(string());
else {
while (p < end) {
result += *(p++);
}
break;
case simple_selector:
buf << string(content.token);
break;
case block:
buf << " {" << endl;
for (size_t i = 0; i < size(); at(i++).echo(buf, depth+1)) ;
buf << indentation << "}" << endl;
break;
case rule:
buf << indentation;
at(0).echo(buf, depth);
buf << ": ";
at(1).echo(buf, depth);
buf << ';' << endl;
break;
case property:
buf << string(content.token);
break;
case values:
for (size_t i = 0; i < size(); at(i++).echo(buf, depth)) ;
break;
case value:
buf << ' ' << string(content.token);
break;
default:
break;
return result;
}
}
void Node::emit_nested_css(stringstream& buf,
size_t depth,
const vector<string>& prefixes)
void Token::unquote_to_stream(std::stringstream& buf) const
{
switch (type)
{
case root:
if (at(0).has_expansions) {
flatten();
const char* p = begin;
if (*begin == '\'' || *begin == '"') {
++p;
while (p < end) {
if (*p == '\\') {
switch (*(++p)) {
case 'n': buf << '\n'; break;
case 't': buf << '\t'; break;
case 'b': buf << '\b'; break;
case 'r': buf << '\r'; break;
case 'f': buf << '\f'; break;
case 'v': buf << '\v'; break;
case 'a': buf << '\a'; break;
case '\\': buf << '\\'; break;
default: buf << *p; break;
}
}
else if (p == end - 1) {
return;
}
for (size_t i = 0; i < size(); ++i) {
at(i).emit_nested_css(buf, depth, prefixes);
if (at(i).type == css_import) buf << endl;
else {
buf << *p;
}
break;
case ruleset: {
Node sel_group(at(0));
size_t sel_group_size = (sel_group.type == selector_group) ? sel_group.size() : 1; // parser ensures no singletons
Node block(at(1));
vector<string> new_prefixes;
if (prefixes.empty()) {
new_prefixes.reserve(sel_group_size);
for (size_t i = 0; i < sel_group_size; ++i) {
new_prefixes.push_back(sel_group_size > 1 ? sel_group[i].to_string(string()) : sel_group.to_string(string()));
++p;
}
return;
}
else {
new_prefixes.reserve(prefixes.size() * sel_group_size);
for (size_t i = 0; i < prefixes.size(); ++i) {
for (size_t j = 0; j < sel_group_size; ++j) {
new_prefixes.push_back(sel_group_size > 1 ? sel_group[j].to_string(prefixes[i]) : sel_group.to_string(prefixes[i]));
while (p < end) {
buf << *(p++);
}
return;
}
}
if (block[0].has_expansions) block.flatten();
if (block[0].has_statements) {
buf << string(2*depth, ' ') << new_prefixes[0];
for (size_t i = 1; i < new_prefixes.size(); ++i) {
buf << ", " << new_prefixes[i];
}
buf << " {";
for (size_t i = 0; i < block.size(); ++i) {
Type stm_type = block[i].type;
if (stm_type == comment || stm_type == rule || stm_type == css_import || stm_type == propset) {
block[i].emit_nested_css(buf, depth+1); // NEED OVERLOADED VERSION FOR COMMENTS AND RULES
}
// else if (stm_type == propset) {
// block[i].emit_nested_css(buf, depth, new_prefixes);
// }
}
buf << " }" << endl;
++depth; // if we printed content at this level, we need to indent any nested rulesets
}
if (block[0].has_blocks) {
for (size_t i = 0; i < block.size(); ++i) {
if (block[i].type == ruleset) {
block[i].emit_nested_css(buf, depth, new_prefixes);
bool Token::operator<(const Token& rhs) const
{
const char* first1 = begin;
const char* last1 = end;
const char* first2 = rhs.begin;
const char* last2 = rhs.end;
while (first1!=last1)
{
if (first2 == last2 || *first2 < *first1) return false;
else if (*first1 < *first2) return true;
++first1; ++first2;
}
return (first2 != last2);
}
bool Token::operator==(const Token& rhs) const
{
if (length() != rhs.length()) return false;
if ((begin[0] == '"' || begin[0] == '\'') &&
(rhs.begin[0] == '"' || rhs.begin[0] == '\''))
{ return unquote() == rhs.unquote(); }
const char* p = begin;
const char* q = rhs.begin;
for (; p < end; ++p, ++q) if (*p != *q) return false;
return true;
}
if (block[0].has_statements) --depth; // see previous comment
if (depth == 0 && prefixes.empty()) buf << endl;
} break;
// ------------------------------------------------------------------------
// Node_Impl method implementations
// ------------------------------------------------------------------------
double Node_Impl::numeric_value()
{
switch (type)
{
case Node::number:
case Node::numeric_percentage:
return value.numeric;
case Node::numeric_dimension:
return value.dimension.numeric;
default:
emit_nested_css(buf, depth); // pass it along to the simpler version
break;
// throw an exception?
}
// if you reach this point, you've got a logic error somewhere
return 0;
}
void Node::emit_nested_css(stringstream& buf, size_t depth)
extern const char percent_str[] = "%";
extern const char empty_str[] = "";
Token Node_Impl::unit()
{
switch (type)
{
case propset: {
emit_propset(buf, depth, "");
case Node::numeric_percentage: {
return Token::make(percent_str);
} break;
case rule:
buf << endl << string(2*depth, ' ');
at(0).emit_nested_css(buf, depth); // property
at(1).emit_nested_css(buf, depth); // values
buf << ";";
break;
case css_import:
buf << endl << string(2*depth, ' ');
buf << to_string("");
buf << ";";
break;
case property:
buf << string(content.token) << ": ";
break;
case values:
for (size_t i = 0; i < size(); ++i) {
buf << " " << string(at(i).content.token);
}
break;
case comment:
if (depth != 0) buf << endl;
buf << string(2*depth, ' ') << string(content.token);
if (depth == 0) buf << endl;
break;
default:
buf << to_string("");
break;
}
}
case Node::numeric_dimension: {
return value.dimension.unit;
} break;
void Node::emit_propset(stringstream& buf, size_t depth, const string& prefix) {
string new_prefix(prefix);
bool has_prefix = false;
if (new_prefix.empty()) {
new_prefix += "\n";
new_prefix += string(2*depth, ' ');
new_prefix += at(0).content.token.to_string();
}
else {
new_prefix += "-";
new_prefix += at(0).content.token.to_string();
has_prefix = true;
default: break;
}
Node rules(at(1));
for (size_t i = 0; i < rules.size(); ++i) {
if (rules[i].type == propset) {
rules[i].emit_propset(buf, depth+1, new_prefix);
}
else {
buf << new_prefix;
if (rules[i][0].content.token.to_string() != "") buf << '-';
rules[i][0].emit_nested_css(buf, depth);
rules[i][1].emit_nested_css(buf, depth);
buf << ';';
}
}
}
void Node::emit_expanded_css(stringstream& buf, const string& prefix) {
// switch (type) {
// case selector:
// if (!prefix.empty()) buf << " ";
// buf << string(token);
// break;
// case comment:
// if (!prefix.empty()) buf << " ";
// buf << string(token) << endl;
// break;
// case property:
// buf << string(token) << ":";
// break;
// case values:
// for (size_t i = 0; i < children.size(); ++i) {
// buf << " " << string(children[i].token);
// }
// break;
// case rule:
// buf << " ";
// children[0].emit_expanded_css(buf, prefix);
// children[1].emit_expanded_css(buf, prefix);
// buf << ";" << endl;
// break;
// case clauses:
// if (children.size() > 0) {
// buf << " {" << endl;
// for (size_t i = 0; i < children.size(); ++i)
// children[i].emit_expanded_css(buf, prefix);
// buf << "}" << endl;
// }
// for (size_t i = 0; i < opt_children.size(); ++i)
// opt_children[i].emit_expanded_css(buf, prefix);
// break;
// case ruleset:
// // buf << prefix;
// if (children[1].children.size() > 0) {
// buf << prefix << (prefix.empty() ? "" : " ");
// children[0].emit_expanded_css(buf, "");
// }
// string newprefix(prefix.empty() ? prefix : prefix + " ");
// children[1].emit_expanded_css(buf, newprefix + string(children[0].token));
// if (prefix.empty()) buf << endl;
// break;
// }
return Token::make(empty_str);
}
void Node::flatten()
{
if (type != block && type != expansion && type != root) return;
for (size_t i = 0; i < size(); ++i) {
if (at(i).type == expansion) {
Node expn = at(i);
if (expn[0].has_expansions) expn.flatten();
at(0).has_statements |= expn[0].has_statements;
at(0).has_blocks |= expn[0].has_blocks;
at(0).has_expansions |= expn[0].has_expansions;
at(i).type = none;
content.children->insert(content.children->begin() + i,
expn.content.children->begin(),
expn.content.children->end());
}
}
}
//
// void flatten_block(Node& block)
// {
//
// for (size_t i = 0; i < block.size(); ++i) {
//
// if (block[i].type == Node::expansion
//
// }
//
//
//
// }
}
\ No newline at end of file
#define SASS_NODE_INCLUDED
#include <cstring>
#include <string>
#include <vector>
#include <sstream>
#include "values.hpp"
#include <iostream>
namespace Sass {
using std::string;
using std::vector;
using std::stringstream;
using std::cerr; using std::endl;
using namespace std;
struct Node {
struct Token {
const char* begin;
const char* end;
// Need Token::make(...) because tokens are union members, and hence they
// can't have non-trivial constructors.
static Token make()
{
Token t;
t.begin = 0;
t.end = 0;
return t;
}
static Token make(const char* s)
{
Token t;
t.begin = s;
t.end = s + std::strlen(s);
return t;
}
static Token make(const char* b, const char* e)
{
Token t;
t.begin = b;
t.end = e;
return t;
}
size_t length() const
{ return end - begin; }
string to_string() const
{ return string(begin, end - begin); }
string unquote() const;
void unquote_to_stream(std::stringstream& buf) const;
operator bool()
{ return begin && end && begin >= end; }
bool operator<(const Token& rhs) const;
bool operator==(const Token& rhs) const;
};
struct Dimension {
double numeric;
Token unit;
};
struct Node_Impl;
class Node {
private:
friend class Node_Factory;
Node_Impl* ip_;
public:
enum Type {
none,
comment,
root,
ruleset,
propset,
......@@ -30,6 +88,7 @@ namespace Sass {
pseudo_negation,
functional_pseudo,
attribute_selector,
selector_schema,
block,
rule,
......@@ -71,9 +130,9 @@ namespace Sass {
textual_hex,
color_name,
string_constant,
number,
numeric_percentage,
numeric_dimension,
number,
numeric_color,
boolean,
important,
......@@ -88,16 +147,89 @@ namespace Sass {
expansion,
arguments,
if_directive,
for_through_directive,
for_to_directive,
each_directive,
while_directive,
variable,
assignment,
assignment
};
Node(Node_Impl* ip = 0);
Type type() const;
bool has_children() const;
bool has_statements() const;
bool has_blocks() const;
bool has_expansions() const;
bool has_backref() const;
bool from_variable() const;
bool& should_eval() const;
bool& is_unquoted() const;
bool is_numeric() const;
string& path() const;
size_t line() const;
size_t size() const;
bool empty() const;
Node& at(size_t i) const;
Node& back() const;
Node& operator[](size_t i) const;
void pop_back();
Node& push_back(Node n);
Node& push_front(Node n);
Node& operator<<(Node n);
Node& operator+=(Node n);
vector<Node>::iterator begin() const;
vector<Node>::iterator end() const;
void insert(vector<Node>::iterator position,
vector<Node>::iterator first,
vector<Node>::iterator last);
bool boolean_value() const;
double numeric_value() const;
Token token() const;
Token unit() const;
bool is_null_ptr() const;
void flatten();
bool operator==(Node rhs) const;
bool operator!=(Node rhs) const;
bool operator<(Node rhs) const;
bool operator<=(Node rhs) const;
bool operator>(Node rhs) const;
bool operator>=(Node rhs) const;
string to_string() const;
void emit_nested_css(stringstream& buf, size_t depth, bool at_toplevel = false);
void emit_propset(stringstream& buf, size_t depth, const string& prefix);
void echo(stringstream& buf, size_t depth = 0);
void emit_expanded_css(stringstream& buf, const string& prefix);
comment,
none,
flags
};
Type type;
unsigned int line_number;
struct Node_Impl {
union value_t {
bool boolean;
double numeric;
Token token;
Dimension dimension;
} value;
// TO DO: look into using a custom allocator in the Node_Factory class
vector<Node> children; // Can't be in the union because it has non-trivial constructors!
string path;
size_t line;
Node::Type type;
bool has_children;
bool has_statements;
......@@ -105,177 +237,156 @@ namespace Sass {
bool has_expansions;
bool has_backref;
bool from_variable;
bool eval_me;
bool unquoted;
union {
Token token;
mutable vector<Node>* children;
Dimension dimension;
double numeric_value;
bool boolean_value;
} content;
const char* file_name;
static size_t allocations;
static size_t destructed;
void clear()
bool should_eval;
bool is_unquoted;
Node_Impl()
: /* value(value_t()),
children(vector<Node>()),
path(string()),
line(0),
type(Node::none), */
has_children(false),
has_statements(false),
has_blocks(false),
has_expansions(false),
has_backref(false),
from_variable(false),
should_eval(false),
is_unquoted(false)
{ }
bool is_numeric()
{ return type >= Node::number && type <= Node::numeric_dimension; }
size_t size()
{ return children.size(); }
bool empty()
{ return children.empty(); }
Node& at(size_t i)
{ return children.at(i); }
Node& back()
{ return children.back(); }
void push_back(const Node& n)
{
type = none; line_number = 0; file_name = 0;
has_children = false; has_statements = false; has_blocks = false;
has_expansions = false; has_backref = false; from_variable = false;
eval_me = false; unquoted = false;
}
size_t size() const
{ return content.children->size(); }
Node& operator[](const size_t i) const
{ return content.children->at(i); }
children.push_back(n);
has_children = true;
switch (n.type())
{
case Node::comment:
case Node::css_import:
case Node::rule:
case Node::propset: has_statements = true; break;
Node& at(const size_t i) const
{ return content.children->at(i); }
case Node::ruleset: has_blocks = true; break;
Node& operator<<(const Node& n)
{
content.children->push_back(n);
return *this;
}
case Node::if_directive:
case Node::for_through_directive:
case Node::for_to_directive:
case Node::each_directive:
case Node::while_directive:
case Node::expansion: has_expansions = true; break;
bool is_numeric() const
{
switch (type)
{
case number:
case numeric_percentage:
case numeric_dimension:
return true;
break;
default:
return false;
}
}
case Node::backref: has_backref = true; break;
double numeric_value() const
{
switch (type)
{
case number:
case numeric_percentage:
return content.numeric_value;
case numeric_dimension:
return content.dimension.numeric_value;
default:
break;
// throw an exception?
default: break;
}
if (n.has_backref()) has_backref = true;
}
void set_numeric_value(double v)
{
switch (type)
void push_front(const Node& n)
{
case number:
case numeric_percentage:
content.numeric_value = v;
case numeric_dimension:
content.dimension.numeric_value = v;
default:
break;
// throw an exception?
}
}
Node& operator+=(const Node& n)
children.insert(children.begin(), n);
has_children = true;
switch (n.type())
{
for (size_t i = 0; i < n.size(); ++i) {
content.children->push_back(n[i]);
case Node::comment:
case Node::css_import:
case Node::rule:
case Node::propset: has_statements = true; break;
case Node::ruleset: has_blocks = true; break;
case Node::expansion: has_expansions = true; break;
case Node::backref: has_backref = true; break;
default: break;
}
return *this;
if (n.has_backref()) has_backref = true;
}
bool operator==(const Node& rhs) const;
bool operator!=(const Node& rhs) const;
bool operator<(const Node& rhs) const;
bool operator<=(const Node& rhs) const;
bool operator>(const Node& rhs) const;
bool operator>=(const Node& rhs) const;
string to_string(const string& prefix) const;
void pop_back()
{ children.pop_back(); }
void echo(stringstream& buf, size_t depth = 0);
void emit_nested_css(stringstream& buf,
size_t depth,
const vector<string>& prefixes);
void emit_nested_css(stringstream& buf, size_t depth);
void emit_propset(stringstream& buf, size_t depth, const string& prefix);
void emit_expanded_css(stringstream& buf, const string& prefix);
bool& boolean_value()
{ return value.boolean; }
Node clone(vector<vector<Node>*>& registry) const;
void flatten();
Node()
{ clear(); }
double numeric_value();
Token unit();
};
Node(Type t) // flags or booleans
{ clear(); type = t; }
Node(Type t, vector<vector<Node>*>& registry, unsigned int ln, size_t s = 0) // nodes with children
// ------------------------------------------------------------------------
// Node method implementations
// -- in the header file so they can easily be declared inline
// -- outside of their class definition to get the right declaration order
// ------------------------------------------------------------------------
inline Node::Node(Node_Impl* ip) : ip_(ip) { }
inline Node::Type Node::type() const { return ip_->type; }
inline bool Node::has_children() const { return ip_->has_children; }
inline bool Node::has_statements() const { return ip_->has_statements; }
inline bool Node::has_blocks() const { return ip_->has_blocks; }
inline bool Node::has_expansions() const { return ip_->has_expansions; }
inline bool Node::has_backref() const { return ip_->has_backref; }
inline bool Node::from_variable() const { return ip_->from_variable; }
inline bool& Node::should_eval() const { return ip_->should_eval; }
inline bool& Node::is_unquoted() const { return ip_->is_unquoted; }
inline bool Node::is_numeric() const { return ip_->is_numeric(); }
inline string& Node::path() const { return ip_->path; }
inline size_t Node::line() const { return ip_->line; }
inline size_t Node::size() const { return ip_->size(); }
inline bool Node::empty() const { return ip_->empty(); }
inline Node& Node::at(size_t i) const { return ip_->at(i); }
inline Node& Node::back() const { return ip_->back(); }
inline Node& Node::operator[](size_t i) const { return at(i); }
inline void Node::pop_back() { ip_->pop_back(); }
inline Node& Node::push_back(Node n)
{
clear();
type = t;
line_number = ln;
content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(s);
has_children = true;
++allocations;
ip_->push_back(n);
return *this;
}
Node(Type t, unsigned int ln, const Token& tok) // nodes with a single token
inline Node& Node::push_front(Node n)
{
clear();
type = t;
line_number = ln;
content.token = tok;
ip_->push_front(n);
return *this;
}
Node(unsigned int ln, double val) // numeric values
inline Node& Node::operator<<(Node n) { return push_back(n); }
inline Node& Node::operator+=(Node n)
{
clear();
type = number;
line_number = ln;
content.numeric_value = val;
for (size_t i = 0, L = n.size(); i < L; ++i) push_back(n[i]);
return *this;
}
Node(unsigned int ln, double val, const Token& tok) // dimensions
{
clear();
type = numeric_dimension;
line_number = ln;
content.dimension = Dimension();
content.dimension.numeric_value = val;
content.dimension.unit = tok.begin;
}
inline vector<Node>::iterator Node::begin() const
{ return ip_->children.begin(); }
inline vector<Node>::iterator Node::end() const
{ return ip_->children.end(); }
inline void Node::insert(vector<Node>::iterator position,
vector<Node>::iterator first,
vector<Node>::iterator last)
{ ip_->children.insert(position, first, last); }
Node(vector<vector<Node>*>& registry, unsigned int ln, double r, double g, double b, double a = 1.0) // colors
{
clear();
type = numeric_color;
line_number = ln;
content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(4);
content.children->push_back(Node(ln, r));
content.children->push_back(Node(ln, g));
content.children->push_back(Node(ln, b));
content.children->push_back(Node(ln, a));
has_children = true;
++allocations;
}
inline bool Node::boolean_value() const { return ip_->boolean_value(); }
inline double Node::numeric_value() const { return ip_->numeric_value(); }
inline Token Node::token() const { return ip_->value.token; }
inline Token Node::unit() const { return ip_->unit(); }
inline bool Node::is_null_ptr() const { return !ip_; }
~Node() { ++destructed; }
};
}
#include <iostream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include "node.hpp"
#include "error.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
namespace Sass {
bool Node::operator==(const Node& rhs) const
{
if (type != rhs.type) return false;
switch (type)
{
case comma_list:
case space_list:
case expression:
case term: {
for (size_t i = 0; i < size(); ++i) {
if (at(i) == rhs[i]) continue;
else return false;
}
return true;
} break;
case variable:
case identifier:
case uri:
case textual_percentage:
case textual_dimension:
case textual_number:
case textual_hex:
case string_constant: {
return content.token.unquote() == rhs.content.token.unquote();
} break;
case number:
case numeric_percentage: {
return numeric_value() == rhs.numeric_value();
} break;
case numeric_dimension: {
if (Token::make(content.dimension.unit, Prelexer::identifier(content.dimension.unit)) ==
Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))) {
return numeric_value() == rhs.numeric_value();
}
else {
return false;
}
} break;
case boolean: {
return content.boolean_value == rhs.content.boolean_value;
} break;
default: {
return true;
} break;
}
}
bool Node::operator!=(const Node& rhs) const
{ return !(*this == rhs); }
bool Node::operator<(const Node& rhs) const
{
if (type == number && rhs.type == number ||
type == numeric_percentage && rhs.type == numeric_percentage) {
return numeric_value() < rhs.numeric_value();
}
else if (type == numeric_dimension && rhs.type == numeric_dimension) {
if (Token::make(content.dimension.unit, Prelexer::identifier(content.dimension.unit)) ==
Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))) {
return numeric_value() < rhs.numeric_value();
}
else {
throw Error(Error::evaluation, line_number, file_name, "incompatible units");
}
}
else {
throw Error(Error::evaluation, line_number, file_name, "incomparable types");
}
}
bool Node::operator<=(const Node& rhs) const
{ return *this < rhs || *this == rhs; }
bool Node::operator>(const Node& rhs) const
{ return !(*this <= rhs); }
bool Node::operator>=(const Node& rhs) const
{ return !(*this < rhs); }
}
#include <iostream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include <sstream>
#include "node.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
namespace Sass {
string Node::to_string() const
{
switch (type())
{
case selector_group: { // really only needed for arg to :not
string result(at(0).to_string());
for (size_t i = 1, S = size(); i < S; ++i) {
result += ", ";
result += at(i).to_string();
}
return result;
} break;
case selector: {
string result;
result += at(0).to_string();
for (size_t i = 1, S = size(); i < S; ++i) {
result += " ";
result += at(i).to_string();
}
return result;
} break;
case selector_combinator: {
return token().to_string();
} break;
case simple_selector_sequence: {
string result;
for (size_t i = 0, S = size(); i < S; ++i) {
result += at(i).to_string();
}
return result;
} break;
case pseudo:
case simple_selector: {
return token().to_string();
} break;
case pseudo_negation: {
string result;
result += at(0).to_string();
result += at(1).to_string();
result += ')';
return result;
} break;
case functional_pseudo: {
string result;
result += at(0).to_string();
for (size_t i = 1, S = size(); i < S; ++i) {
result += at(i).to_string();
}
result += ')';
return result;
} break;
case attribute_selector: {
string result;
result += "[";
for (size_t i = 0, S = size(); i < S; ++i) {
result += at(i).to_string();
}
result += ']';
return result;
} break;
case comma_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();
}
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();
}
return result;
} break;
case expression:
case term: {
string result(at(0).to_string());
for (size_t i = 1, S = size(); i < S; ++i) {
if (!(at(i).type() == add ||
// at(i).type == sub || // another edge case -- consider uncommenting
at(i).type() == mul)) {
result += at(i).to_string();
}
}
return result;
} break;
//edge case
case sub: {
return "-";
} break;
case div: {
return "/";
} break;
case css_import: {
stringstream ss;
ss << "@import url(";
ss << at(0).to_string();
// cerr << content.token.to_string() << endl;
ss << ")";
return ss.str();
}
case function_call: {
stringstream ss;
ss << at(0).to_string();
ss << "(";
ss << at(1).to_string();
ss << ")";
return ss.str();
}
case arguments: {
stringstream ss;
size_t S = size();
if (S > 0) {
ss << at(0).to_string();
for (size_t i = 1; i < S; ++i) {
ss << ", ";
ss << at(i).to_string();
}
}
return ss.str();
}
case unary_plus: {
stringstream ss;
ss << "+";
ss << at(0).to_string();
return ss.str();
}
case unary_minus: {
stringstream ss;
ss << "-";
ss << at(0).to_string();
return ss.str();
}
case numeric_percentage: {
stringstream ss;
ss << numeric_value();
ss << '%';
return ss.str();
}
case numeric_dimension: {
stringstream ss;
ss << numeric_value() << unit().to_string();
return ss.str();
} break;
case number: {
stringstream ss;
ss << numeric_value();
return ss.str();
} break;
case numeric_color: {
if (at(3).numeric_value() >= 1.0)
{
double a = at(0).numeric_value();
double b = at(1).numeric_value();
double c = at(2).numeric_value();
if (a >= 0xff && b >= 0xff && c >= 0xff)
{ return "white"; }
else if (a >= 0xff && b >= 0xff && c == 0)
{ return "yellow"; }
else if (a == 0 && b >= 0xff && c >= 0xff)
{ return "aqua"; }
else if (a >= 0xff && b == 0 && c >= 0xff)
{ return "fuchsia"; }
else if (a >= 0xff && b == 0 && c == 0)
{ return "red"; }
else if (a == 0 && b >= 0xff && c == 0)
{ return "lime"; }
else if (a == 0 && b == 0 && c >= 0xff)
{ return "blue"; }
else if (a <= 0 && b <= 0 && c <= 0)
{ return "black"; }
else
{
stringstream ss;
ss << '#' << std::setw(2) << std::setfill('0') << std::hex;
for (size_t i = 0; i < 3; ++i) {
double x = at(i).numeric_value();
if (x > 0xff) x = 0xff;
else if (x < 0) x = 0;
ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(x+0.5));
}
return ss.str();
}
}
else
{
stringstream ss;
ss << "rgba(" << static_cast<unsigned long>(at(0).numeric_value());
for (size_t i = 1; i < 3; ++i) {
ss << ", " << static_cast<unsigned long>(at(i).numeric_value());
}
ss << ", " << at(3).numeric_value() << ')';
return ss.str();
}
} break;
case uri: {
string result("url(");
result += token().to_string();
result += ")";
return result;
} break;
case expansion: {
// ignore it
return "";
} break;
case string_constant: {
if (is_unquoted()) return token().unquote();
else {
string result(token().to_string());
if (result[0] != '"' && result[0] != '\'') return "\"" + result + "\"";
else return result;
}
} break;
case boolean: {
if (boolean_value()) return "true";
else return "false";
} break;
case important: {
return "!important";
} break;
case value_schema: {
string result;
for (size_t i = 0, S = size(); i < S; ++i) result += at(i).to_string();
return result;
} break;
case string_schema: {
string result;
for (size_t i = 0, S = size(); i < S; ++i) {
string chunk(at(i).to_string());
if (at(i).type() == string_constant) {
result += chunk.substr(1, chunk.size()-2);
}
else {
result += chunk;
}
}
return result;
} break;
default: {
// return content.token.to_string();
if (!has_children()) return token().to_string();
else return "";
} break;
}
}
void Node::emit_nested_css(stringstream& buf, size_t depth, bool at_toplevel)
{
switch (type())
{
case root:
if (has_expansions()) flatten();
for (size_t i = 0, S = size(); i < S; ++i) {
at(i).emit_nested_css(buf, depth, true);
}
break;
case ruleset: {
Node sel_group(at(2));
Node block(at(1));
if (block.has_expansions()) block.flatten();
if (block.has_statements()) {
buf << string(2*depth, ' ');
buf << sel_group.to_string();
buf << " {";
for (size_t i = 0, S = block.size(); i < S; ++i) {
Type stm_type = block[i].type();
if (stm_type == comment || stm_type == rule || stm_type == css_import || stm_type == propset) {
block[i].emit_nested_css(buf, depth+1);
}
}
buf << " }" << endl;
++depth; // if we printed content at this level, we need to indent any nested rulesets
}
if (block.has_blocks()) {
for (size_t i = 0, S = block.size(); i < S; ++i) {
if (block[i].type() == ruleset) {
block[i].emit_nested_css(buf, depth);
}
}
}
if (block.has_statements()) --depth; // see previous comment
if ((depth == 0) && at_toplevel) buf << endl;
} break;
case propset: {
emit_propset(buf, depth, "");
} break;
case rule: {
buf << endl << string(2*depth, ' ');
at(0).emit_nested_css(buf, depth); // property
at(1).emit_nested_css(buf, depth); // values
buf << ";";
} break;
case css_import: {
buf << string(2*depth, ' ');
buf << to_string();
buf << ";" << endl;
} break;
case property: {
buf << token().to_string() << ": ";
} break;
case values: {
for (size_t i = 0, S = size(); i < S; ++i) {
buf << " " << at(i).token().to_string();
}
} break;
case comment: {
if (depth != 0) buf << endl;
buf << string(2*depth, ' ') << token().to_string();
if (depth == 0) buf << endl;
} break;
default: {
buf << to_string();
} break;
}
}
void Node::emit_propset(stringstream& buf, size_t depth, const string& prefix)
{
string new_prefix(prefix);
// bool has_prefix = false;
if (new_prefix.empty()) {
new_prefix += "\n";
new_prefix += string(2*depth, ' ');
new_prefix += at(0).token().to_string();
}
else {
new_prefix += "-";
new_prefix += at(0).token().to_string();
// has_prefix = true;
}
Node rules(at(1));
for (size_t i = 0, S = rules.size(); i < S; ++i) {
if (rules[i].type() == propset) {
rules[i].emit_propset(buf, depth+1, new_prefix);
}
else {
buf << new_prefix;
if (rules[i][0].token().to_string() != "") buf << '-';
rules[i][0].emit_nested_css(buf, depth);
rules[i][1].emit_nested_css(buf, depth);
buf << ';';
}
}
}
void Node::echo(stringstream& buf, size_t depth) { }
void Node::emit_expanded_css(stringstream& buf, const string& prefix) { }
}
\ No newline at end of file
#include "node_factory.hpp"
namespace Sass {
Node_Impl* Node_Factory::alloc_Node_Impl(Node::Type type, string path, size_t line)
{
Node_Impl* ip = new Node_Impl();
ip->type = type;
if (type == Node::backref) ip->has_backref = true;
ip->path = path;
ip->line = line;
pool_.push_back(ip);
return ip;
}
// returns a deep-copy of its argument
Node_Impl* Node_Factory::alloc_Node_Impl(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)(n);
}
}
return ip_cpy;
}
// for cloning nodes
Node Node_Factory::operator()(const Node& n1)
{
Node_Impl* ip_cpy = alloc_Node_Impl(n1.ip_); // deep-copy the implementation object
return Node(ip_cpy);
}
// for making leaf nodes out of terminals/tokens
Node Node_Factory::operator()(Node::Type type, string path, size_t line, Token t)
{
Node_Impl* ip = alloc_Node_Impl(type, path, line);
ip->value.token = t;
return Node(ip);
}
// for making boolean values or interior nodes that have children
Node Node_Factory::operator()(Node::Type type, string path, size_t line, size_t size)
{
Node_Impl* ip = alloc_Node_Impl(type, path, line);
if (type == Node::boolean) ip->value.boolean = size;
else ip->children.reserve(size);
return Node(ip);
}
// for making nodes representing numbers
Node Node_Factory::operator()(string path, size_t line, double v, Node::Type type)
{
Node_Impl* ip = alloc_Node_Impl(type, path, line);
ip->value.numeric = v;
return Node(ip);
}
// for making nodes representing numeric dimensions (e.g. 5px, 3em)
Node Node_Factory::operator()(string path, size_t line, double v, const Token& t)
{
Node_Impl* ip = alloc_Node_Impl(Node::numeric_dimension, path, line);
ip->value.dimension.numeric = v;
ip->value.dimension.unit = t;
return Node(ip);
}
// for making nodes representing rgba color quads
Node Node_Factory::operator()(string path, size_t line, double r, double g, double b, double a)
{
Node color((*this)(Node::numeric_color, path, line, 4));
color << (*this)(path, line, r)
<< (*this)(path, line, g)
<< (*this)(path, line, b)
<< (*this)(path, line, a);
return color;
}
void Node_Factory::free()
{ for (size_t i = 0, S = pool_.size(); i < S; ++i) delete pool_[i]; }
}
\ No newline at end of file
#include <vector>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
namespace Sass {
using namespace std;
struct Token;
struct Node_Impl;
class Node_Factory {
vector<Node_Impl*> pool_;
Node_Impl* alloc_Node_Impl(Node::Type type, string file, size_t line);
// returns a deep-copy of its argument
Node_Impl* alloc_Node_Impl(Node_Impl* ip);
public:
// for cloning nodes
Node operator()(const Node& n1);
// for making leaf nodes out of terminals/tokens
Node operator()(Node::Type type, string file, size_t line, Token t);
// for making boolean values or interior nodes that have children
Node operator()(Node::Type type, string file, size_t line, size_t size);
// // for making nodes representing boolean values
// Node operator()(Node::Type type, string file, size_t line, bool b);
// for making nodes representing numbers
Node operator()(string file, size_t line, double v, Node::Type type = Node::number);
// for making nodes representing numeric dimensions (e.g. 5px, 3em)
Node operator()(string file, size_t line, double v, const Token& t);
// for making nodes representing rgba color quads
Node operator()(string file, size_t line, double r, double g, double b, double a = 1.0);
void free();
};
}
\ No newline at end of file
......@@ -111,6 +111,51 @@ namespace Sass {
const char* include(const char* src) {
return exactly<include_kwd>(src);
}
extern const char extend_kwd[] = "@extend";
const char* extend(const char* 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) {
return exactly<if_kwd>(src);
}
extern const char else_kwd[] = "@else";
const char* else_directive(const char* src) {
return exactly<else_kwd>(src);
}
const char* elseif_directive(const char* src) {
return sequence< else_directive,
spaces_and_comments,
exactly< if_chars > >(src);
}
extern const char for_kwd[] = "@for";
const char* for_directive(const char* src) {
return exactly<for_kwd>(src);
}
extern const char from_kwd[] = "from";
const char* from(const char* src) {
return exactly<from_kwd>(src);
}
extern const char to_kwd[] = "to";
const char* to(const char* src) {
return exactly<to_kwd>(src);
}
extern const char through_kwd[] = "through";
const char* through(const char* src) {
return exactly<through_kwd>(src);
}
extern const char each_kwd[] = "@each";
const char* each_directive(const char* src) {
return exactly<each_kwd>(src);
}
extern const char while_kwd[] = "@while";
const char* while_directive(const char* src) {
return exactly<while_kwd>(src);
}
const char* name(const char* src) {
return one_plus< alternatives< alnum,
......
......@@ -310,6 +310,21 @@ namespace Sass {
const char* import(const char* src);
const char* mixin(const char* src);
const char* include(const char* src);
const char* extend(const char* src);
const char* if_directive(const char* src);
const char* else_directive(const char* src);
const char* elseif_directive(const char* src);
const char* for_directive(const char* src);
const char* from(const char* src);
const char* to(const char* src);
const char* through(const char* src);
const char* each_directive(const char* src);
const char* while_directive(const char* src);
// Match CSS type selectors
const char* namespace_prefix(const char* src);
const char* type_selector(const char* src);
......@@ -388,10 +403,10 @@ namespace Sass {
template<prelexer mx>
const char* find_first_in_interval(const char* beg, const char* end) {
while ((beg < end) && *beg) {
const char* p = mx(beg);
if (p) return p;
if (mx(beg)) return beg;
++beg;
}
return 0;
}
template <char c>
......
......@@ -10,8 +10,8 @@
#include "sass_interface.h"
extern "C" {
using namespace std;
sass_context* sass_new_context()
{ return (sass_context*) calloc(1, sizeof(sass_context)); }
......@@ -39,21 +39,14 @@ extern "C" {
{
using namespace Sass;
doc.parse_scss();
// cerr << "PARSED" << endl;
eval(doc.root, doc.context.global_env, doc.context.function_env, doc.context.registry);
// cerr << "EVALUATED" << endl;
eval(doc.root,
doc.context.new_Node(Node::none, doc.path, doc.line, 0),
doc.context.global_env,
doc.context.function_env,
doc.context.new_Node,
doc.context);
extend_selectors(doc.context.pending_extensions, doc.context.new_Node);
string output(doc.emit_css(static_cast<Document::CSS_Style>(style)));
// cerr << "EMITTED" << endl;
// cerr << "Allocations:\t" << Node::allocations << endl;
// cerr << "Destructions:\t" << Node::destructed << endl;
// cerr << "Registry size:\t" << doc.context.registry.size() << endl;
for (size_t i = 0; i < doc.context.registry.size(); ++i) {
delete doc.context.registry[i];
}
// cerr << "Deallocations:\t" << i << endl;
char* c_output = (char*) malloc(output.size() + 1);
strcpy(c_output, output.c_str());
return c_output;
......@@ -64,14 +57,15 @@ extern "C" {
using namespace Sass;
try {
Context cpp_ctx(c_ctx->options.include_paths);
Document doc(0, c_ctx->input_string, cpp_ctx);
// Document doc(0, c_ctx->input_string, cpp_ctx);
Document doc(Document::make_from_source_chars(cpp_ctx, c_ctx->source_string));
c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
c_ctx->error_message = 0;
c_ctx->error_status = 0;
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.file_name << ", line " << e.line_number << ": " << e.message << endl;
msg_stream << "ERROR -- " << e.path << ", line " << e.line << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
......@@ -98,7 +92,8 @@ extern "C" {
using namespace Sass;
try {
Context cpp_ctx(c_ctx->options.include_paths);
Document doc(c_ctx->input_path, 0, cpp_ctx);
// Document doc(c_ctx->input_path, 0, cpp_ctx);
Document doc(Document::make_from_file(cpp_ctx, string(c_ctx->input_path)));
// cerr << "MADE A DOC AND CONTEXT OBJ" << endl;
// cerr << "REGISTRY: " << doc.context.registry.size() << endl;
c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
......@@ -107,7 +102,7 @@ extern "C" {
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.file_name << ", line " << e.line_number << ": " << e.message << endl;
msg_stream << "ERROR -- " << e.path << ", line " << e.line << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
......
......@@ -13,7 +13,7 @@ struct sass_options {
};
struct sass_context {
char* input_string;
char* source_string;
char* output_string;
struct sass_options options;
int error_status;
......
#include <iostream>
#include <string>
#include <tr1/unordered_map>
#include <map>
#include <algorithm>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
#include "node_factory.hpp"
int main()
{
using namespace Sass;
using namespace std;
cout << sizeof(Node_Impl*) << endl;
cout << sizeof(Node) << endl;
cout << sizeof(Node_Impl) << endl << endl;
Node_Factory new_Node = Node_Factory();
Node interior(new_Node(Node::block, "", 0, 3));
cout << interior.size() << endl;
cout << interior.has_children() << endl;
cout << interior.should_eval() << endl << endl;
Node num(new_Node("", 0, 255, 123, 32));
Node num2(new_Node("", 0, 255, 123, 32));
Node num3(new_Node("", 0, 255, 122, 20, .75));
cout << num.size() << endl;
cout << num.has_children() << endl;
cout << num.has_statements() << endl << endl;
cout << num[1].is_numeric() << endl;
cout << num[1].numeric_value() << endl << endl;
cout << (num == num2) << endl;
cout << (num == num3) << endl << endl;
cout << (num3[2] < num2[2]) << endl;
cout << (num2[3] < num3[3]) << endl << endl;
cout << (num2[2] >= num3[2]) << endl;
cout << (num2[3] != num3[3]) << endl << endl;
Node num4(new_Node(num3));
cout << num3[3].numeric_value() << endl;
cout << num4[3].numeric_value() << endl;
num4[3] = new_Node("", 0, 0.4567);
cout << num3[3].numeric_value() << endl;
cout << num4[3].numeric_value() << endl << endl;
Node block1(new_Node(Node::block, "block", 1, 2));
block1 << num2 << num4;
Node block2(new_Node(block1));
cout << (block1 == block2) << endl;
cout << block1[1][3].numeric_value() << endl;
cout << block2[1][3].numeric_value() << endl;
block2[1][3] = new_Node("", 0, .9876);
cout << block1[1][3].numeric_value() << endl;
cout << block2[1][3].numeric_value() << endl << endl;
map<Node, string> dict;
Node n(new_Node("", 0, 42));
Node m(new_Node("", 0, 41));
dict[n] = "hello";
dict[m] = "goodbye";
cout << dict[m] << " " << dict[n] << endl;
cout << "Lexicographical comparison: " << endl;
cout << lexicographical_compare(num2.begin(), num2.end(),
num3.begin(), num3.end())
<< endl;
cout << lexicographical_compare(num.begin(), num.end(),
num2.begin(), num2.end())
<< endl;
cout << lexicographical_compare(num3.begin(), num3.end(),
num.begin(), num.end())
<< endl << endl;
new_Node.free();
return 0;
}
\ No newline at end of file
#include "values.hpp"
namespace Sass {
// Token::Token() : begin(0), end(0) { }
// Token::Token(const char* begin, const char* end)
// : begin(begin), end(end) { }
string Token::unquote() const {
string result;
const char* p = begin;
if (*begin == '\'' || *begin == '"') {
++p;
while (p < end) {
if (*p == '\\') {
switch (*(++p)) {
case 'n': result += '\n'; break;
case 't': result += '\t'; break;
case 'b': result += '\b'; break;
case 'r': result += '\r'; break;
case 'f': result += '\f'; break;
case 'v': result += '\v'; break;
case 'a': result += '\a'; break;
case '\\': result += '\\'; break;
default: result += *p; break;
}
}
else if (p == end - 1) {
return result;
}
else {
result += *p;
}
++p;
}
return result;
}
else {
while (p < end) {
result += *(p++);
}
return result;
}
}
void Token::unquote_to_stream(std::stringstream& buf) const {
const char* p = begin;
if (*begin == '\'' || *begin == '"') {
++p;
while (p < end) {
if (*p == '\\') {
switch (*(++p)) {
case 'n': buf << '\n'; break;
case 't': buf << '\t'; break;
case 'b': buf << '\b'; break;
case 'r': buf << '\r'; break;
case 'f': buf << '\f'; break;
case 'v': buf << '\v'; break;
case 'a': buf << '\a'; break;
case '\\': buf << '\\'; break;
default: buf << *p; break;
}
}
else if (p == end - 1) {
return;
}
else {
buf << *p;
}
++p;
}
return;
}
else {
while (p < end) {
buf << *(p++);
}
return;
}
}
bool Token::operator<(const Token& rhs) const
{
const char* first1 = begin;
const char* last1 = end;
const char* first2 = rhs.begin;
const char* last2 = rhs.end;
while (first1!=last1)
{
if (first2==last2 || *first2<*first1) return false;
else if (*first1<*first2) return true;
first1++; first2++;
}
return (first2!=last2);
}
bool Token::operator==(const Token& rhs) const
{
if (length() != rhs.length()) return false;
if ((begin[0] == '"' || begin[0] == '\'') &&
(rhs.begin[0] == '"' || rhs.begin[0] == '\''))
{ return unquote() == rhs.unquote(); }
const char* p = begin;
const char* q = rhs.begin;
for (; p < end; ++p, ++q) if (*p != *q) return false;
return true;
}
}
\ No newline at end of file
#include <string>
#include <sstream>
#include <cstring>
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
namespace Sass {
using std::string;
struct Token {
const char* begin;
const char* end;
// Token();
// Token(const char* begin, const char* end);
size_t length() const
{ return end - begin; }
inline operator string() const
{ return string(begin, end - begin); }
string to_string() const
{ return string(begin, end - begin); }
string unquote() const;
void unquote_to_stream(std::stringstream& buf) const;
bool operator<(const Token& rhs) const;
bool operator==(const Token& rhs) const;
operator bool()
{ return begin && end && begin >= end; }
static Token make()
{
Token t;
t.begin = 0;
t.end = 0;
return t;
}
static Token make(const char* s)
{
Token t;
t.begin = s;
t.end = s + std::strlen(s);
return t;
}
static Token make(const char* b, const char* e)
{
Token t;
t.begin = b;
t.end = e;
return t;
}
};
struct Dimension {
double numeric_value;
const char* unit;
};
}
\ No newline at end of file
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