Commit 4f8d92e5 by Aaron Leung

Merge branch 'node_pimpl'

Conflicts:
	eval_apply.cpp
	node.hpp
parents 40a251ea 61423540
...@@ -4,7 +4,7 @@ LDFLAGS= ...@@ -4,7 +4,7 @@ LDFLAGS=
SOURCES = \ SOURCES = \
context.cpp functions.cpp document.cpp \ context.cpp functions.cpp document.cpp \
document_parser.cpp eval_apply.cpp node.cpp \ document_parser.cpp eval_apply.cpp node.cpp \
node_comparisons.cpp values.cpp prelexer.cpp \ node_factory.cpp node_emitters.cpp prelexer.cpp \
sass_interface.cpp sass_interface.cpp
OBJECTS = $(SOURCES:.cpp=.o) OBJECTS = $(SOURCES:.cpp=.o)
......
#include "context.hpp" #include "context.hpp"
#include <iostream> #include <iostream>
#include <unistd.h>
#include "prelexer.hpp"
using std::cerr; using std::endl; using std::cerr; using std::endl;
namespace Sass { namespace Sass {
...@@ -44,6 +46,7 @@ namespace Sass { ...@@ -44,6 +46,7 @@ namespace Sass {
source_refs(vector<char*>()), source_refs(vector<char*>()),
registry(vector<vector<Node>*>()), registry(vector<vector<Node>*>()),
include_paths(vector<string>()), include_paths(vector<string>()),
new_Node(Node_Factory()),
ref_count(0) ref_count(0)
{ {
register_functions(); register_functions();
...@@ -55,6 +58,8 @@ namespace Sass { ...@@ -55,6 +58,8 @@ namespace Sass {
for (size_t i = 0; i < source_refs.size(); ++i) { for (size_t i = 0; i < source_refs.size(); ++i) {
delete[] source_refs[i]; delete[] source_refs[i];
} }
new_Node.free();
// cerr << "Deallocated " << i << " source string(s)." << endl; // cerr << "Deallocated " << i << " source string(s)." << endl;
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include <utility> #include <utility>
#include <map> #include <map>
#include "node_factory.hpp"
#include "functions.hpp" #include "functions.hpp"
namespace Sass { namespace Sass {
...@@ -45,10 +46,11 @@ namespace Sass { ...@@ -45,10 +46,11 @@ namespace Sass {
vector<char*> source_refs; // all the source c-strings vector<char*> source_refs; // all the source c-strings
vector<vector<Node>*> registry; // all the child vectors vector<vector<Node>*> registry; // all the child vectors
vector<string> include_paths; vector<string> include_paths;
Node_Factory new_Node;
size_t ref_count; size_t ref_count;
string sass_path; string sass_path;
string css_path; string css_path;
void collect_include_paths(const char* paths_str); void collect_include_paths(const char* paths_str);
Context(const char* paths_str = 0); Context(const char* paths_str = 0);
~Context(); ~Context();
......
...@@ -4,124 +4,97 @@ ...@@ -4,124 +4,97 @@
#include "eval_apply.hpp" #include "eval_apply.hpp"
#include "error.hpp" #include "error.hpp"
#include <iostream> #include <iostream>
#include <sstream>
namespace Sass { 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(string path, char* source) Document::Document(Context& ctx) : context(ctx)
: path(path), source(source), { ++context.ref_count; }
line_number(1), own_source(false),
context(*(new Context())), Document::Document(const Document& doc)
root(Node(Node::root, context.registry, 1)), : path(doc.path),
lexed(Token::make()) source(doc.source),
{ position(doc.position),
if (!source) { end(doc.end),
std::FILE *f; line(doc.line),
f = std::fopen(path.c_str(), "rb"); own_source(doc.own_source),
if (!f) throw path; context(doc.context),
if (std::fseek(f, 0, SEEK_END)) throw path; root(doc.root),
int len = std::ftell(f); lexed(doc.lexed)
if (len < 0) throw path; { ++doc.context.ref_count; }
std::rewind(f);
// TO DO: CATCH THE POTENTIAL badalloc EXCEPTION Document::~Document()
source = new char[len + 1]; { --context.ref_count; }
std::fread(source, sizeof(char), len, f);
if (std::ferror(f)) throw path; Document Document::make_from_file(Context& ctx, string path)
source[len] = '\0';
end = source + len;
if (std::fclose(f)) throw path;
own_source = true;
}
position = source;
context.source_refs.push_back(source);
++context.ref_count;
}
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())
{ {
std::FILE *f; std::FILE *f;
f = std::fopen(path.c_str(), "rb"); f = std::fopen(path.c_str(), "rb");
if (!f) throw path; if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path; if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f); int status = std::ftell(f);
if (len < 0) throw path; if (status < 0) throw path;
size_t len = status;
std::rewind(f); std::rewind(f);
// TO DO: CATCH THE POTENTIAL badalloc EXCEPTION char* source = new char[len + 1];
source = new char[len + 1]; size_t bytes_read = std::fread(source, sizeof(char), len, f);
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; if (std::ferror(f)) throw path;
source[len] = '\0'; source[len] = '\0';
end = source + len; char* end = source + len;
if (std::fclose(f)) throw path; if (std::fclose(f)) throw path;
position = source;
context.source_refs.push_back(source); Document doc(ctx);
++context.ref_count; 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(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() { Document Document::make_from_source_chars(Context& ctx, char* src, string path)
--context.ref_count; {
// if (context.ref_count == 0) delete &context; 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 = false;
doc.source = src;
doc.end = src + std::strlen(src);
doc.position = doc.end;
return doc;
}
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;
return doc;
} }
void Document::syntax_error(string message, size_t ln) void Document::throw_syntax_error(string message, size_t ln)
{ throw Error(Error::syntax, ln ? ln : line_number, path, message); } { throw Error(Error::syntax, path, ln ? ln : line, message); }
void Document::read_error(string message, size_t ln) void Document::throw_read_error(string message, size_t ln)
{ throw Error(Error::read, ln ? ln : line_number, path, message); } { throw Error(Error::read, path, ln ? ln : line, message); }
using std::string; using std::string;
using std::stringstream; using std::stringstream;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "node.hpp" #include "node.hpp"
#endif #endif
#include "prelexer.hpp"
#include "context.hpp" #include "context.hpp"
namespace Sass { namespace Sass {
...@@ -19,20 +20,25 @@ namespace Sass { ...@@ -19,20 +20,25 @@ namespace Sass {
char* source; char* source;
const char* position; const char* position;
const char* end; const char* end;
size_t line_number; size_t line;
bool own_source; bool own_source;
Context& context; Context& context;
Node root; Node root;
Token lexed; Token lexed;
Document(char* path_str, char* source_str, Context& ctx); private:
Document(string path, char* source = 0); // force the use of the "make_from_..." factory funtions
Document(string path, Context& context); Document(Context& ctx);
Document(const string& path, size_t line_number, Token t, Context& context); public:
Document(const Document& doc);
~Document(); ~Document();
static Document make_from_file(Context& ctx, string path);
static Document make_from_source_chars(Context& ctx, char* src, string path = "");
static Document make_from_token(Context& ctx, Token t, string path = "", size_t line_number = 1);
template <prelexer mx> template <prelexer mx>
const char* peek(const char* start = 0) const char* peek(const char* start = 0)
{ {
...@@ -83,7 +89,7 @@ namespace Sass { ...@@ -83,7 +89,7 @@ namespace Sass {
else if (mx == spaces) { else if (mx == spaces) {
after_whitespace = spaces(position); after_whitespace = spaces(position);
if (after_whitespace) { if (after_whitespace) {
line_number += count_interval<'\n'>(position, after_whitespace); line += count_interval<'\n'>(position, after_whitespace);
lexed = Token::make(position, after_whitespace); lexed = Token::make(position, after_whitespace);
return position = after_whitespace; return position = after_whitespace;
} }
...@@ -99,7 +105,7 @@ namespace Sass { ...@@ -99,7 +105,7 @@ namespace Sass {
} }
const char* after_token = mx(after_whitespace); const char* after_token = mx(after_whitespace);
if (after_token) { 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); lexed = Token::make(after_whitespace, after_token);
return position = after_token; return position = after_token;
} }
...@@ -140,26 +146,14 @@ namespace Sass { ...@@ -140,26 +146,14 @@ namespace Sass {
Node parse_term(); Node parse_term();
Node parse_factor(); Node parse_factor();
Node parse_value(); Node parse_value();
Node parse_identifier();
Node parse_variable();
Node parse_function_call(); Node parse_function_call();
Node parse_string(); Node parse_string();
Node parse_value_schema(); Node parse_value_schema();
const char* lookahead_for_selector(const char* start = 0); const char* lookahead_for_selector(const char* start = 0);
const char* look_for_rule(const char* start = 0); void throw_syntax_error(string message, size_t ln = 0);
const char* look_for_values(const char* start = 0); void throw_read_error(string message, size_t ln = 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);
void syntax_error(string message, size_t ln = 0);
void read_error(string message, size_t ln = 0);
string emit_css(CSS_Style style); string emit_css(CSS_Style style);
......
...@@ -5,38 +5,25 @@ ...@@ -5,38 +5,25 @@
namespace Sass { namespace Sass {
using namespace std; using namespace std;
extern const char plus_equal[] = "+=";
void Document::parse_scss() void Document::parse_scss()
{ {
lex<optional_spaces>(); lex< optional_spaces >();
root << Node(Node::flags); while (position < end) {
while(position < end) {
if (lex< block_comment >()) { if (lex< block_comment >()) {
root << Node(Node::comment, line_number, lexed); root << context.new_Node(Node::comment, path, line, lexed);
} }
else if (peek< import >(position)) { else if (peek< import >()) {
// TO DO: don't splice in place at parse-time -- use an expansion node Node importee(parse_import());
Node import(parse_import()); if (importee.type() == Node::css_import) root << importee;
if (import.type == Node::css_import) { else root += importee;
root << import; if (!lex< exactly<';'> >()) throw_syntax_error("top-level @import directive must be terminated by ';'");
}
else {
root += import;
}
if (!lex< exactly<';'> >()) 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(); root << parse_mixin_definition();
} }
else if (peek< include >(position)) { else if (peek< variable >()) {
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)) {
root << parse_assignment(); 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)) { else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
root << parse_propset(); root << parse_propset();
...@@ -44,16 +31,15 @@ namespace Sass { ...@@ -44,16 +31,15 @@ namespace Sass {
else if (lookahead_for_selector(position)) { else if (lookahead_for_selector(position)) {
root << parse_ruleset(); root << parse_ruleset();
} }
else if (peek< exactly<'+'> >()) { else if (peek< include >() || peek< exactly<'+'> >()) {
root << parse_mixin_call(); root << parse_mixin_call();
root[0].has_expansions = true; if (!lex< exactly<';'> >()) throw_syntax_error("top-level @include directive must be terminated by ';'");
if (!lex< exactly<';'> >()) syntax_error("top-level @include directive must be terminated by ';'");
} }
else { else {
lex< spaces_and_comments >(); lex< spaces_and_comments >();
syntax_error("invalid top-level expression"); throw_syntax_error("invalid top-level expression");
} }
lex<optional_spaces>(); lex< optional_spaces >();
} }
} }
...@@ -64,113 +50,115 @@ namespace Sass { ...@@ -64,113 +50,115 @@ namespace Sass {
{ {
if (peek< string_constant >()) { if (peek< string_constant >()) {
Node schema(parse_string()); Node schema(parse_string());
Node import(Node::css_import, context.registry, line_number, 1); Node importee(context.new_Node(Node::css_import, path, line, 1));
import << schema; importee << schema;
if (!lex< exactly<')'> >()) syntax_error("unterminated url in @import directive"); if (!lex< exactly<')'> >()) throw_syntax_error("unterminated url in @import directive");
return import; return importee;
} }
else { else {
const char* beg = position; const char* beg = position;
const char* end = find_first< exactly<')'> >(position); const char* end = find_first< exactly<')'> >(position);
if (!end) syntax_error("unterminated url in @import directive"); if (!end) throw_syntax_error("unterminated url in @import directive");
Node path(Node::identifier, line_number, Token::make(beg, end)); Node path_node(context.new_Node(Node::identifier, path, line, Token::make(beg, end)));
Node import(Node::css_import, context.registry, line_number, 1); Node importee(context.new_Node(Node::css_import, path, line, 1));
import << path; importee << path_node;
position = end; position = end;
lex< exactly<')'> >(); lex< exactly<')'> >();
return import; 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 // TO DO: BETTER PATH HANDLING
// cerr << "Importing " << lexed.to_string() << endl;
string import_path(lexed.unquote()); string import_path(lexed.unquote());
const char* curr_path_start = path.c_str(); const char* curr_path_start = path.c_str();
const char* curr_path_end = folders(curr_path_start); const char* curr_path_end = folders(curr_path_start);
string current_path(curr_path_start, curr_path_end - curr_path_start); string current_path(curr_path_start, curr_path_end - curr_path_start);
try { try {
Document importee(current_path + import_path, context); Document importee(Document::make_from_file(context, current_path + import_path));
importee.parse_scss(); importee.parse_scss();
// cerr << "Finished parsing import " << lexed.to_string() << endl;
return importee.root; return importee.root;
} }
catch (string& path) { catch (string& path) {
read_error("error reading file \"" + path + "\""); throw_read_error("error reading file \"" + path + "\"");
} }
// unreached statement // unreached statement
return Node(Node::none); return Node();
} }
Node Document::parse_mixin_definition() Node Document::parse_mixin_definition()
{ {
lex< mixin >() || lex< exactly<'='> >(); lex< mixin >() || lex< exactly<'='> >();
if (!lex< identifier >()) syntax_error("invalid name in @mixin directive"); if (!lex< identifier >()) throw_syntax_error("invalid name in @mixin directive");
Node name(Node::identifier, line_number, lexed); Node name(context.new_Node(Node::identifier, path, line, lexed));
Node params(parse_mixin_parameters()); Node params(parse_mixin_parameters());
if (!peek< exactly<'{'> >()) syntax_error("body for mixin " + name.content.token.to_string() + " must begin with a '{'"); if (!peek< exactly<'{'> >()) throw_syntax_error("body for mixin " + name.token().to_string() + " must begin with a '{'");
Node body(parse_block(true)); Node body(parse_block(true));
Node mixin(Node::mixin, context.registry, line_number, 3); Node the_mixin(context.new_Node(Node::mixin, path, line, 3));
mixin << name << params << body; the_mixin << name << params << body;
return mixin; return the_mixin;
} }
Node Document::parse_mixin_parameters() 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); Token name(lexed);
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
if (peek< variable >()) { if (peek< variable >()) {
params << parse_parameter(); params << parse_parameter();
while (lex< exactly<','> >()) { 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(); 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; return params;
} }
Node Document::parse_parameter() { Node Document::parse_parameter() {
lex< variable >(); lex< variable >();
Node var(Node::variable, line_number, lexed); Node var(context.new_Node(Node::variable, path, line, lexed));
if (lex< exactly<':'> >()) { // default value if (lex< exactly<':'> >()) { // default value
Node val(parse_space_list()); 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; par_and_val << var << val;
return par_and_val; return par_and_val;
} }
else { else {
return var; return var;
} }
// unreachable statement
return Node();
} }
Node Document::parse_mixin_call() Node Document::parse_mixin_call()
{ {
lex< include >() || lex< exactly<'+'> >(); lex< include >() || lex< exactly<'+'> >();
if (!lex< identifier >()) syntax_error("invalid name in @include directive"); if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive");
Node name(Node::identifier, line_number, lexed); Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments()); Node args(parse_arguments());
Node call(Node::expansion, context.registry, line_number, 3); Node the_call(context.new_Node(Node::expansion, path, line, 2));
call << name << args; the_call << name << args;
return call; return the_call;
} }
Node Document::parse_arguments() Node Document::parse_arguments()
{ {
Token name(lexed); 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 (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) { if (!peek< exactly<')'> >(position)) {
args << parse_argument(); Node arg(parse_argument());
args.content.children->back().eval_me = true; arg.should_eval() = true;
args << arg;
while (lex< exactly<','> >()) { while (lex< exactly<','> >()) {
args << parse_argument(); Node arg(parse_argument());
args.content.children->back().eval_me = true; 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; return args;
} }
...@@ -179,10 +167,10 @@ namespace Sass { ...@@ -179,10 +167,10 @@ namespace Sass {
{ {
if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) { if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
lex< variable >(); lex< variable >();
Node var(Node::variable, line_number, lexed); Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >(); lex< exactly<':'> >();
Node val(parse_space_list()); Node val(parse_space_list());
Node assn(Node::assignment, context.registry, line_number, 2); Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val; assn << var << val;
return assn; return assn;
} }
...@@ -194,10 +182,10 @@ namespace Sass { ...@@ -194,10 +182,10 @@ namespace Sass {
Node Document::parse_assignment() Node Document::parse_assignment()
{ {
lex< variable >(); lex< variable >();
Node var(Node::variable, line_number, lexed); Node var(context.new_Node(Node::variable, path, line, lexed));
if (!lex< exactly<':'> >()) syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement"); if (!lex< exactly<':'> >()) throw_syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement");
Node val(parse_list()); 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; assn << var << val;
return assn; return assn;
} }
...@@ -205,10 +193,10 @@ namespace Sass { ...@@ -205,10 +193,10 @@ namespace Sass {
Node Document::parse_propset() Node Document::parse_propset()
{ {
lex< identifier >(); 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<':'> >();
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<'}'> >()) { while (!lex< exactly<'}'> >()) {
if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) { if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset(); block << parse_propset();
...@@ -218,8 +206,8 @@ namespace Sass { ...@@ -218,8 +206,8 @@ namespace Sass {
lex< exactly<';'> >(); lex< exactly<';'> >();
} }
} }
if (block.size() == 0) syntax_error("namespaced property cannot be empty"); if (block.empty()) throw_syntax_error("namespaced property cannot be empty");
Node propset(Node::propset, context.registry, line_number, 2); Node propset(context.new_Node(Node::propset, path, line, 2));
propset << property_segment; propset << property_segment;
propset << block; propset << block;
return propset; return propset;
...@@ -227,26 +215,19 @@ namespace Sass { ...@@ -227,26 +215,19 @@ namespace Sass {
Node Document::parse_ruleset(bool definition) Node Document::parse_ruleset(bool definition)
{ {
Node ruleset(Node::ruleset, context.registry, line_number, 2); Node ruleset(context.new_Node(Node::ruleset, path, line, 2));
ruleset << parse_selector_group(); ruleset << parse_selector_group();
// if (ruleset[0].type == Node::selector) cerr << "ruleset starts with selector" << endl; if (!peek< exactly<'{'> >()) throw_syntax_error("expected a '{' after the selector");
// 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); ruleset << parse_block(definition);
return ruleset; return ruleset;
} }
Node Document::parse_selector_group() Node Document::parse_selector_group()
{ {
// Node group(Node::selector_group, line_number, 1);
// group << parse_selector();
// while (lex< exactly<','> >()) group << parse_selector();
// return group;
Node sel1(parse_selector()); Node sel1(parse_selector());
if (!peek< exactly<','> >()) return sel1; 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; group << sel1;
while (lex< exactly<','> >()) group << parse_selector(); while (lex< exactly<','> >()) group << parse_selector();
return group; return group;
...@@ -254,40 +235,16 @@ namespace Sass { ...@@ -254,40 +235,16 @@ namespace Sass {
Node Document::parse_selector() 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()); Node seq1(parse_simple_selector_sequence());
if (peek< exactly<','> >() || if (peek< exactly<','> >() ||
peek< exactly<')'> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >()) return seq1; peek< exactly<'{'> >()) return seq1;
Node selector(Node::selector, context.registry, line_number, 2); Node selector(context.new_Node(Node::selector, path, line, 2));
if (seq1.has_backref) selector.has_backref = true;
selector << seq1; selector << seq1;
while (!peek< exactly<'{'> >() && !peek< exactly<','> >()) { while (!peek< exactly<'{'> >() && !peek< exactly<','> >()) {
Node seq(parse_simple_selector_sequence()); selector << parse_simple_selector_sequence();
if (seq.has_backref) selector.has_backref = true;
selector << seq;
} }
return selector; return selector;
} }
...@@ -298,34 +255,30 @@ namespace Sass { ...@@ -298,34 +255,30 @@ namespace Sass {
if (lex< exactly<'+'> >() || if (lex< exactly<'+'> >() ||
lex< exactly<'~'> >() || 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 // check for backref or type selector, which are only allowed at the front
Node simp1; Node simp1;
bool saw_backref = false;
if (lex< exactly<'&'> >()) { if (lex< exactly<'&'> >()) {
simp1 = Node(Node::backref, line_number, lexed); simp1 = context.new_Node(Node::backref, path, line, lexed);
simp1.has_backref = true;
saw_backref = true;
} }
else if (lex< alternatives< type_selector, universal > >()) { 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 { else {
simp1 = parse_simple_selector(); 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<'>'> >() || if (peek< spaces >() || peek< exactly<'>'> >() ||
peek< exactly<'+'> >() || peek< exactly<'~'> >() || peek< exactly<'+'> >() || peek< exactly<'~'> >() ||
peek< exactly<','> >() || peek< exactly<')'> >() || peek< exactly<','> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >()) peek< exactly<'{'> >())
{ return simp1; } { return simp1; }
// now we know we have a sequence of simple selectors // otherwise, we have a sequence of simple selectors
Node seq(Node::simple_selector_sequence, context.registry, line_number, 2); Node seq(context.new_Node(Node::simple_selector_sequence, path, line, 2));
seq << simp1; seq << simp1;
seq.has_backref = saw_backref;
while (!peek< spaces >(position) && while (!peek< spaces >(position) &&
!(peek < exactly<'+'> >(position) || !(peek < exactly<'+'> >(position) ||
...@@ -337,46 +290,19 @@ namespace Sass { ...@@ -337,46 +290,19 @@ namespace Sass {
seq << parse_simple_selector(); seq << parse_simple_selector();
} }
return seq; 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() Node Document::parse_selector_combinator()
{ {
lex< exactly<'+'> >() || lex< exactly<'~'> >() || lex< exactly<'+'> >() || lex< exactly<'~'> >() ||
lex< exactly<'>'> >() || lex< ancestor_of >(); 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() Node Document::parse_simple_selector()
{ {
if (lex< id_name >() || lex< class_name >()) { 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)) { else if (peek< exactly<':'> >(position)) {
return parse_pseudo(); return parse_pseudo();
...@@ -384,76 +310,81 @@ namespace Sass { ...@@ -384,76 +310,81 @@ namespace Sass {
else if (peek< exactly<'['> >(position)) { else if (peek< exactly<'['> >(position)) {
return parse_attribute_selector(); return parse_attribute_selector();
} }
syntax_error("invalid selector after " + lexed.to_string()); else {
// unreached statement throw_syntax_error("invalid selector after " + lexed.to_string());
return Node(Node::none);} }
// unreachable statement
return Node();
}
Node Document::parse_pseudo() { Node Document::parse_pseudo() {
if (lex< pseudo_not >()) { if (lex< pseudo_not >()) {
Node ps_not(Node::pseudo_negation, context.registry, line_number, 2); Node ps_not(context.new_Node(Node::pseudo_negation, path, line, 2));
ps_not << Node(Node::value, line_number, lexed); ps_not << context.new_Node(Node::value, path, line, lexed);
ps_not << parse_selector_group(); ps_not << parse_selector_group();
lex< exactly<')'> >(); lex< exactly<')'> >();
return ps_not; return ps_not;
} }
else if (lex< sequence< pseudo_prefix, functional > >()) { 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); Token name(lexed);
pseudo << Node(Node::value, line_number, name); pseudo << context.new_Node(Node::value, path, line, name);
if (lex< alternatives< even, odd > >()) { 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)) { else if (peek< binomial >(position)) {
lex< coefficient >(); lex< coefficient >();
pseudo << Node(Node::value, line_number, lexed); pseudo << context.new_Node(Node::value, path, line, lexed);
lex< exactly<'n'> >(); lex< exactly<'n'> >();
pseudo << Node(Node::value, line_number, lexed); pseudo << context.new_Node(Node::value, path, line, lexed);
lex< sign >(); lex< sign >();
pseudo << Node(Node::value, line_number, lexed); pseudo << context.new_Node(Node::value, path, line, lexed);
lex< digits >(); lex< digits >();
pseudo << Node(Node::value, line_number, lexed); pseudo << context.new_Node(Node::value, path, line, lexed);
} }
else if (lex< sequence< optional<sign>, else if (lex< sequence< optional<sign>,
optional<digits>, optional<digits>,
exactly<'n'> > >()) { 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 > >()) { 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 >()) { else if (lex< string_constant >()) {
pseudo << Node(Node::string_constant, line_number, lexed); pseudo << context.new_Node(Node::string_constant, path, line, lexed);
} }
else { 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; return pseudo;
} }
else if (lex < sequence< pseudo_prefix, identifier > >()) { 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"); else {
// unreached statement throw_syntax_error("unrecognized pseudo-class or pseudo-element");
return Node(Node::none); }
// unreachable statement
return Node();
} }
Node Document::parse_attribute_selector() 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<'['> >(); 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); 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< exactly<']'> >()) return attr_sel;
if (!lex< alternatives< exact_match, class_match, dash_match, if (!lex< alternatives< exact_match, class_match, dash_match,
prefix_match, suffix_match, substring_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); attr_sel << context.new_Node(Node::value, path, line, lexed);
if (!lex< string_constant >()) syntax_error("expected a quoted string constant in attribute selector for " + name.to_string()); if (!lex< string_constant >()) throw_syntax_error("expected a quoted string constant in attribute selector for " + name.to_string());
attr_sel << Node(Node::value, line_number, lexed); attr_sel << context.new_Node(Node::value, path, line, lexed);
if (!lex< exactly<']'> >()) syntax_error("unterminated attribute selector for " + name.to_string()); if (!lex< exactly<']'> >()) throw_syntax_error("unterminated attribute selector for " + name.to_string());
return attr_sel; return attr_sel;
} }
...@@ -461,43 +392,30 @@ namespace Sass { ...@@ -461,43 +392,30 @@ namespace Sass {
{ {
lex< exactly<'{'> >(); lex< exactly<'{'> >();
bool semicolon = false; bool semicolon = false;
Node block(Node::block, context.registry, line_number, 1); Node block(context.new_Node(Node::block, path, line, 0));
block << Node(Node::flags);
while (!lex< exactly<'}'> >()) { while (!lex< exactly<'}'> >()) {
if (semicolon) { 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; semicolon = false;
while (lex< block_comment >()) { while (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed); block << context.new_Node(Node::comment, path, line, lexed);
block[0].has_statements = true;
} }
if (lex< exactly<'}'> >()) break; if (lex< exactly<'}'> >()) break;
} }
if (lex< block_comment >()) { if (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed); block << context.new_Node(Node::comment, path, line, lexed);
block[0].has_statements = true;
//semicolon = true;
} }
else if (peek< import >(position)) { else if (peek< import >(position)) {
if (definition) { if (definition) {
lex< import >(); // to adjust the line number 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()); Node imported_tree(parse_import());
if (imported_tree.type == Node::css_import) { if (imported_tree.type() == Node::css_import) {
// cerr << "css import inside block" << endl;
block << imported_tree; block << imported_tree;
block.has_statements = true;
} }
else { else {
for (size_t i = 0; i < imported_tree.size(); ++i) { for (size_t i = 0, S = imported_tree.size(); i < S; ++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;
}
block << imported_tree[i]; block << imported_tree[i];
} }
semicolon = true; semicolon = true;
...@@ -505,7 +423,6 @@ namespace Sass { ...@@ -505,7 +423,6 @@ namespace Sass {
} }
else if (peek< include >(position)) { else if (peek< include >(position)) {
block << parse_mixin_call(); block << parse_mixin_call();
block[0].has_expansions = true;
semicolon = true; semicolon = true;
} }
else if (lex< variable >()) { else if (lex< variable >()) {
...@@ -514,15 +431,12 @@ namespace Sass { ...@@ -514,15 +431,12 @@ namespace Sass {
} }
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) { else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset(); block << parse_propset();
block[0].has_statements = true;
} }
else if (lookahead_for_selector(position)) { else if (lookahead_for_selector(position)) {
block << parse_ruleset(definition); block << parse_ruleset(definition);
block[0].has_blocks = true;
} }
else if (peek< exactly<'+'> >()) { else if (peek< exactly<'+'> >()) {
block << parse_mixin_call(); block << parse_mixin_call();
block[0].has_expansions = true;
semicolon = true; semicolon = true;
} }
else if (!peek< exactly<';'> >()) { else if (!peek< exactly<';'> >()) {
...@@ -530,37 +444,34 @@ namespace Sass { ...@@ -530,37 +444,34 @@ namespace Sass {
// check for lbrace; if it's there, we have a namespace property with a value // check for lbrace; if it's there, we have a namespace property with a value
if (peek< exactly<'{'> >()) { if (peek< exactly<'{'> >()) {
Node inner(parse_block()); Node inner(parse_block());
Node propset(Node::propset, context.registry, line_number, 2); Node propset(context.new_Node(Node::propset, path, line, 2));
propset << rule[0]; propset << rule[0];
rule[0] = Node(Node::property, line_number, Token::make()); rule[0] = context.new_Node(Node::property, path, line, Token::make());
inner[0] = rule; inner.push_front(rule);
propset << inner; propset << inner;
block << propset; block << propset;
// cerr << block[block.size()-1][0].content.token.to_string() << endl;
} }
else { else {
block << rule; block << rule;
semicolon = true; semicolon = true;
} }
block[0].has_statements = true;
} }
else lex< exactly<';'> >(); else lex< exactly<';'> >();
while (lex< block_comment >()) { while (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed); block << context.new_Node(Node::comment, path, line, lexed);
block[0].has_statements = true;
} }
} }
return block; return block;
} }
Node Document::parse_rule() { 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 > >()) { if (!lex< sequence< optional< exactly<'*'> >, identifier > >()) {
lex< spaces_and_comments >(); // get the line number right 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); rule << context.new_Node(Node::property, path, line, lexed);
if (!lex< exactly<':'> >()) syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'"); if (!lex< exactly<':'> >()) throw_syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'");
rule << parse_list(); rule << parse_list();
return rule; return rule;
} }
...@@ -576,20 +487,20 @@ namespace Sass { ...@@ -576,20 +487,20 @@ namespace Sass {
peek< exactly<'}'> >(position) || peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) || 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()); Node list1(parse_space_list());
// if it's a singleton, return it directly; don't wrap it // if it's a singleton, return it directly; don't wrap it
if (!peek< exactly<','> >(position)) return list1; if (!peek< exactly<','> >(position)) return list1;
Node comma_list(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 << list1;
comma_list.eval_me |= list1.eval_me; comma_list.should_eval() |= list1.should_eval();
while (lex< exactly<','> >()) while (lex< exactly<','> >())
{ {
Node list(parse_space_list()); Node list(parse_space_list());
comma_list << list; comma_list << list;
comma_list.eval_me |= list.eval_me; comma_list.should_eval() |= list.should_eval();
} }
return comma_list; return comma_list;
...@@ -606,9 +517,9 @@ namespace Sass { ...@@ -606,9 +517,9 @@ namespace Sass {
peek< exactly<','> >(position)) peek< exactly<','> >(position))
{ return disj1; } { 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 << disj1;
space_list.eval_me |= disj1.eval_me; space_list.should_eval() |= disj1.should_eval();
while (!(peek< exactly<';'> >(position) || while (!(peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) || peek< exactly<'}'> >(position) ||
...@@ -618,7 +529,7 @@ namespace Sass { ...@@ -618,7 +529,7 @@ namespace Sass {
{ {
Node disj(parse_disjunction()); Node disj(parse_disjunction());
space_list << disj; space_list << disj;
space_list.eval_me |= disj.eval_me; space_list.should_eval() |= disj.should_eval();
} }
return space_list; return space_list;
...@@ -630,10 +541,10 @@ namespace Sass { ...@@ -630,10 +541,10 @@ namespace Sass {
// if it's a singleton, return it directly; don't wrap it // if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< or_kwd, negate< identifier > > >()) return conj1; if (!peek< sequence< or_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; disjunction << conj1;
while (lex< sequence< or_kwd, negate< identifier > > >()) disjunction << parse_conjunction(); while (lex< sequence< or_kwd, negate< identifier > > >()) disjunction << parse_conjunction();
disjunction.eval_me = true; disjunction.should_eval() = true;
return disjunction; return disjunction;
} }
...@@ -644,10 +555,10 @@ namespace Sass { ...@@ -644,10 +555,10 @@ namespace Sass {
// if it's a singleton, return it directly; don't wrap it // if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< and_kwd, negate< identifier > > >()) return rel1; if (!peek< sequence< and_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; conjunction << rel1;
while (lex< sequence< and_kwd, negate< identifier > > >()) conjunction << parse_relation(); while (lex< sequence< and_kwd, negate< identifier > > >()) conjunction << parse_relation();
conjunction.eval_me = true; conjunction.should_eval() = true;
return conjunction; return conjunction;
} }
...@@ -663,22 +574,22 @@ namespace Sass { ...@@ -663,22 +574,22 @@ namespace Sass {
peek< lte_op >(position))) peek< lte_op >(position)))
{ return expr1; } { return expr1; }
Node relation(Node::relation, context.registry, line_number, 3); Node relation(context.new_Node(Node::relation, path, line, 3));
expr1.eval_me = true; expr1.should_eval() = true;
relation << expr1; relation << expr1;
if (lex< eq_op >()) relation << Node(Node::eq, line_number, lexed); if (lex< eq_op >()) relation << context.new_Node(Node::eq, path, line, lexed);
else if (lex< neq_op >()) relation << Node(Node::neq, line_number, lexed); else if (lex< neq_op >()) relation << context.new_Node(Node::neq, path, line, lexed);
else if (lex< gte_op >()) relation << Node(Node::gte, line_number, lexed); else if (lex< gte_op >()) relation << context.new_Node(Node::gte, path, line, lexed);
else if (lex< lte_op >()) relation << Node(Node::lte, line_number, lexed); else if (lex< lte_op >()) relation << context.new_Node(Node::lte, path, line, lexed);
else if (lex< gt_op >()) relation << Node(Node::gt, line_number, lexed); else if (lex< gt_op >()) relation << context.new_Node(Node::gt, path, line, lexed);
else if (lex< lt_op >()) relation << Node(Node::lt, line_number, lexed); else if (lex< lt_op >()) relation << context.new_Node(Node::lt, path, line, lexed);
Node expr2(parse_expression()); Node expr2(parse_expression());
expr2.eval_me = true; expr2.should_eval() = true;
relation << expr2; relation << expr2;
relation.eval_me = true; relation.should_eval() = true;
return relation; return relation;
} }
...@@ -690,22 +601,22 @@ namespace Sass { ...@@ -690,22 +601,22 @@ namespace Sass {
peek< sequence< negate< number >, exactly<'-'> > >(position))) peek< sequence< negate< number >, exactly<'-'> > >(position)))
{ return term1; } { return term1; }
Node expression(Node::expression, context.registry, line_number, 3); Node expression(context.new_Node(Node::expression, path, line, 3));
term1.eval_me = true; term1.should_eval() = true;
expression << term1; expression << term1;
while (lex< exactly<'+'> >() || lex< sequence< negate< number >, exactly<'-'> > >()) { while (lex< exactly<'+'> >() || lex< sequence< negate< number >, exactly<'-'> > >()) {
if (lexed.begin[0] == '+') { if (lexed.begin[0] == '+') {
expression << Node(Node::add, line_number, lexed); expression << context.new_Node(Node::add, path, line, lexed);
} }
else { else {
expression << Node(Node::sub, line_number, lexed); expression << context.new_Node(Node::sub, path, line, lexed);
} }
Node term(parse_term()); Node term(parse_term());
term.eval_me = true; term.should_eval() = true;
expression << term; expression << term;
} }
expression.eval_me = true; expression.should_eval() = true;
return expression; return expression;
} }
...@@ -718,20 +629,20 @@ namespace Sass { ...@@ -718,20 +629,20 @@ namespace Sass {
peek< exactly<'/'> >(position))) peek< exactly<'/'> >(position)))
{ return fact1; } { return fact1; }
Node term(Node::term, context.registry, line_number, 3); Node term(context.new_Node(Node::term, path, line, 3));
term << fact1; term << fact1;
if (fact1.eval_me) term.eval_me = true; if (fact1.should_eval()) term.should_eval() = true;
while (lex< exactly<'*'> >() || lex< exactly<'/'> >()) { while (lex< exactly<'*'> >() || lex< exactly<'/'> >()) {
if (lexed.begin[0] == '*') { if (lexed.begin[0] == '*') {
term << Node(Node::mul, line_number, lexed); term << context.new_Node(Node::mul, path, line, lexed);
term.eval_me = true; term.should_eval() = true;
} }
else { else {
term << Node(Node::div, line_number, lexed); term << context.new_Node(Node::div, path, line, lexed);
} }
Node fact(parse_factor()); Node fact(parse_factor());
if (fact.eval_me) term.eval_me = true; term.should_eval() |= fact.should_eval();
term << fact; term << fact;
} }
...@@ -742,23 +653,23 @@ namespace Sass { ...@@ -742,23 +653,23 @@ namespace Sass {
{ {
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
Node value(parse_comma_list()); Node value(parse_comma_list());
value.eval_me = true; value.should_eval() = true;
if (value.type == Node::comma_list || value.type == Node::space_list) { if (value.type() == Node::comma_list || value.type() == Node::space_list) {
value[0].eval_me = true; value[0].should_eval() = true;
} }
if (!lex< exactly<')'> >()) syntax_error("unclosed parenthesis"); if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis");
return value; return value;
} }
else if (lex< sequence< exactly<'+'>, negate< number > > >()) { 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 << parse_factor();
plus.eval_me = true; plus.should_eval() = true;
return plus; return plus;
} }
else if (lex< sequence< exactly<'-'>, negate< number> > >()) { 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 << parse_factor();
minus.eval_me = true; minus.should_eval() = true;
return minus; return minus;
} }
else { else {
...@@ -772,74 +683,59 @@ namespace Sass { ...@@ -772,74 +683,59 @@ namespace Sass {
{ {
const char* value = position; const char* value = position;
const char* rparen = find_first< exactly<')'> >(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)); Token contents(Token::make(value, rparen));
// lex< string_constant >(); // lex< string_constant >();
Node result(Node::uri, line_number, contents); Node result(context.new_Node(Node::uri, path, line, contents));
position = rparen; position = rparen;
lex< exactly<')'> >(); lex< exactly<')'> >();
return result; return result;
} }
if (lex< value_schema >()) if (lex< value_schema >())
{ { return Document::make_from_token(context, lexed, path, line).parse_value_schema(); }
// cerr << "parsing value schema: " << lexed.to_string() << endl;
Document schema_doc(path, line_number, lexed, context);
return schema_doc.parse_value_schema();
}
if (lex< sequence< true_kwd, negate< identifier > > >()) if (lex< sequence< true_kwd, negate< identifier > > >())
{ { return context.new_Node(Node::boolean, path, line, true); }
Node T(Node::boolean);
T.line_number = line_number;
T.content.boolean_value = true;
return T;
}
if (lex< sequence< false_kwd, negate< identifier > > >()) if (lex< sequence< false_kwd, negate< identifier > > >())
{ { return context.new_Node(Node::boolean, path, line, false); }
Node F(Node::boolean);
F.line_number = line_number;
F.content.boolean_value = false;
return F;
}
if (peek< functional >()) if (peek< functional >())
{ return parse_function_call(); } { return parse_function_call(); }
if (lex< important >()) if (lex< important >())
{ return Node(Node::important, line_number, lexed); } { return context.new_Node(Node::important, path, line, lexed); }
if (lex< identifier >()) if (lex< identifier >())
{ return Node(Node::identifier, line_number, lexed); } { return context.new_Node(Node::identifier, path, line, lexed); }
if (lex< percentage >()) if (lex< percentage >())
{ return Node(Node::textual_percentage, line_number, lexed); } { return context.new_Node(Node::textual_percentage, path, line, lexed); }
if (lex< dimension >()) if (lex< dimension >())
{ return Node(Node::textual_dimension, line_number, lexed); } { return context.new_Node(Node::textual_dimension, path, line, lexed); }
if (lex< number >()) if (lex< number >())
{ return Node(Node::textual_number, line_number, lexed); } { return context.new_Node(Node::textual_number, path, line, lexed); }
if (lex< hex >()) 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 >()) if (peek< string_constant >())
// { return Node(Node::string_constant, line_number, lexed); }
{ return parse_string(); } { return parse_string(); }
if (lex< variable >()) if (lex< variable >())
{ {
Node var(Node::variable, line_number, lexed); Node var(context.new_Node(Node::variable, path, line, lexed));
var.eval_me = true; var.should_eval() = true;
return var; return var;
} }
syntax_error("error reading values after " + lexed.to_string()); throw_syntax_error("error reading values after " + lexed.to_string());
// unreached statement
return Node(Node::none); // unreachable statement
return Node();
} }
extern const char hash_lbrace[] = "#{"; extern const char hash_lbrace[] = "#{";
...@@ -853,34 +749,31 @@ namespace Sass { ...@@ -853,34 +749,31 @@ namespace Sass {
// see if there any interpolants // see if there any interpolants
const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(str.begin, str.end); const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(str.begin, str.end);
if (!p) { if (!p) {
return 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) { while (i < str.end) {
p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(i, str.end); p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(i, str.end);
if (p) { if (p) {
if (i < p) { if (i < p) {
schema << Node(Node::identifier, line_number, Token::make(i, p-2)); // accumulate the preceding segment if it's nonempty schema << context.new_Node(Node::identifier, path, line, Token::make(i, p-2)); // accumulate the preceding segment if it's nonempty
// cerr << '[' << Token::make(i,p-2).to_string() << ']' << endl;
} }
const char* j = find_first_in_interval< exactly<rbrace> >(p, str.end); // find the closing brace const char* j = find_first_in_interval< exactly<rbrace> >(p, str.end); // find the closing brace
if (j) { if (j) {
// parse the interpolant and accumulate it // parse the interpolant and accumulate it
// cerr << '[' << Token::make(p, j-1).to_string() << ']' << endl; Node interp_node(Document::make_from_token(context, Token::make(p, j-1), path, line).parse_list());
Document interp_doc(path, line_number, Token::make(p,j-1), context); interp_node.should_eval() = true;
Node interp_node(interp_doc.parse_list());
interp_node.eval_me = true;
schema << interp_node; schema << interp_node;
i = j; i = j;
} }
else { else {
// throw an error if the interpolant is unterminated // 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 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; break;
} }
} }
...@@ -889,65 +782,54 @@ namespace Sass { ...@@ -889,65 +782,54 @@ namespace Sass {
Node Document::parse_value_schema() 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) { while (position < end) {
if (lex< interpolant >()) { if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1)); Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Document interp_doc(path, line_number, insides, context); Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
Node interp_node(interp_doc.parse_list());
schema << interp_node; schema << interp_node;
} }
else if (lex< identifier >()) { else if (lex< identifier >()) {
schema << Node(Node::identifier, line_number, lexed); schema << context.new_Node(Node::identifier, path, line, lexed);
} }
else if (lex< percentage >()) { 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 >()) { 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 >()) { 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 >()) { 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 >()) { 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 >()) { else if (lex< variable >()) {
schema << Node(Node::variable, line_number, lexed); schema << context.new_Node(Node::variable, path, line, lexed);
} }
else { else {
syntax_error("error parsing interpolated value"); throw_syntax_error("error parsing interpolated value");
} }
} }
schema.eval_me = true; schema.should_eval() = true;
return schema; return schema;
} }
Node Document::parse_function_call() Node Document::parse_function_call()
{ {
lex< identifier >(); lex< identifier >();
Node name(Node::identifier, line_number, lexed); Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments()); 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 << name << args;
call.eval_me = true; call.should_eval() = true;
return call; return call;
} }
Node Document::parse_identifier() {
lex< identifier >();
return Node(Node::identifier, line_number, lexed);
}
Node Document::parse_variable() {
lex< variable >();
return Node(Node::variable, line_number, lexed);
}
const char* Document::lookahead_for_selector(const char* start) const char* Document::lookahead_for_selector(const char* start)
{ {
const char* p = start ? start : position; const char* p = start ? start : position;
...@@ -986,151 +868,4 @@ namespace Sass { ...@@ -986,151 +868,4 @@ namespace Sass {
else return 0; else return 0;
} }
} }
\ 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 { ...@@ -4,12 +4,12 @@ namespace Sass {
enum Type { read, write, syntax, evaluation }; enum Type { read, write, syntax, evaluation };
Type type; Type type;
size_t line_number; string path;
string file_name; size_t line;
string message; string message;
Error(Type type, size_t line_number, string file_name, string message) Error(Type type, string path, size_t line, string message)
: type(type), line_number(line_number), file_name(file_name), message(message) : type(type), path(path), line(line), message(message)
{ } { }
}; };
......
#include "prelexer.hpp"
#include "eval_apply.hpp" #include "eval_apply.hpp"
#include "error.hpp" #include "error.hpp"
#include <iostream> #include <iostream>
#include <sstream>
#include <cstdlib> #include <cstdlib>
namespace Sass { namespace Sass {
using std::cerr; using std::endl; 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 (!path.empty() && Prelexer::string_constant(path.c_str()))
if (file_name) { path = path.substr(1, path.size() - 1);
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name); throw Error(Error::evaluation, path, line, message);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
} }
Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry) Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node)
{ {
switch (expr.type) switch (expr.type())
{ {
case Node::mixin: { case Node::mixin: {
env[expr[0].content.token] = expr; env[expr[0].token()] = expr;
return expr; return expr;
} break; } break;
case Node::expansion: { case Node::expansion: {
Token name(expr[0].content.token); Token name(expr[0].token());
Node args(expr[1]); 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 mixin(env[name]);
Node expansion(apply_mixin(mixin, args, env, f_env, registry)); Node expansion(apply_mixin(mixin, args, env, f_env, new_Node));
expr.content.children->pop_back(); expr.pop_back();
expr.content.children->pop_back(); expr.pop_back();
expr += expansion; expr += expansion;
return expr; return expr;
} break; } break;
case Node::propset: case Node::propset:
case Node::ruleset: { case Node::ruleset: {
eval(expr[1], env, f_env, registry); eval(expr[1], env, f_env, new_Node);
return expr; return expr;
} break; } break;
case Node::root: { case Node::root: {
for (size_t i = 0; i < expr.size(); ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
eval(expr[i], env, f_env, registry); eval(expr[i], env, f_env, new_Node);
} }
return expr; return expr;
} break; } break;
case Node::block: { case Node::block: {
Environment current; Environment new_frame;
current.link(env); new_frame.link(env);
for (size_t i = 0; i < expr.size(); ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
eval(expr[i], current, f_env, registry); eval(expr[i], new_frame, f_env, new_Node);
} }
return expr; return expr;
} break; } break;
case Node::assignment: { case Node::assignment: {
Node val(expr[1]); Node val(expr[1]);
if (val.type == Node::comma_list || val.type == Node::space_list) { if (val.type() == Node::comma_list || val.type() == Node::space_list) {
for (size_t i = 0; i < val.size(); ++i) { for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].eval_me) val[i] = eval(val[i], env, f_env, registry); if (val[i].should_eval()) val[i] = eval(val[i], env, f_env, new_Node);
} }
} }
else { else {
val = eval(val, env, f_env, registry); val = eval(val, env, f_env, new_Node);
} }
Node var(expr[0]); Node var(expr[0]);
if (env.query(var.content.token)) { if (env.query(var.token())) {
env[var.content.token] = val; env[var.token()] = val;
} }
else { else {
env.current_frame[var.content.token] = val; env.current_frame[var.token()] = val;
} }
return expr; return expr;
} break; } break;
case Node::rule: { case Node::rule: {
Node rhs(expr[1]); Node rhs(expr[1]);
if (rhs.type == Node::comma_list || rhs.type == Node::space_list) { if (rhs.type() == Node::comma_list || rhs.type() == Node::space_list) {
for (size_t i = 0; i < rhs.size(); ++i) { for (size_t i = 0, S = rhs.size(); i < S; ++i) {
if (rhs[i].eval_me) rhs[i] = eval(rhs[i], env, f_env, registry); if (rhs[i].should_eval()) rhs[i] = eval(rhs[i], env, f_env, new_Node);
} }
} }
else if (rhs.type == Node::value_schema || rhs.type == Node::string_schema) { else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) {
eval(rhs, env, f_env, registry); eval(rhs, env, f_env, new_Node);
} }
else { else {
if (rhs.eval_me) expr[1] = eval(rhs, env, f_env, registry); if (rhs.should_eval()) expr[1] = eval(rhs, env, f_env, new_Node);
} }
return expr; return expr;
} break; } break;
case Node::comma_list: case Node::comma_list:
case Node::space_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], env, f_env, new_Node);
return expr; return expr;
} break; } break;
case Node::disjunction: { case Node::disjunction: {
Node result; Node result;
for (size_t i = 0; i < expr.size(); ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
// if (expr[i].type == Node::relation || result = eval(expr[i], env, f_env, new_Node);
// expr[i].type == Node::function_call && expr[0].content.token.to_string() == "not") { if (result.type() == Node::boolean && result.boolean_value() == false) continue;
result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) continue;
else return result; else return result;
} }
return result; return result;
...@@ -116,26 +113,23 @@ namespace Sass { ...@@ -116,26 +113,23 @@ namespace Sass {
case Node::conjunction: { case Node::conjunction: {
Node result; Node result;
for (size_t i = 0; i < expr.size(); ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], env, f_env, registry); result = eval(expr[i], env, f_env, new_Node);
if (result.type == Node::boolean && result.content.boolean_value == false) return result; if (result.type() == Node::boolean && result.boolean_value() == false) return result;
} }
return result; return result;
} break; } break;
case Node::relation: { case Node::relation: {
Node lhs(eval(expr[0], env, f_env, registry)); Node lhs(eval(expr[0], env, f_env, new_Node));
Node op(expr[1]); Node op(expr[1]);
Node rhs(eval(expr[2], env, f_env, registry)); Node rhs(eval(expr[2], env, f_env, new_Node));
Node T(Node::boolean); Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true));
T.line_number = lhs.line_number; Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false));
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::eq: return (lhs == rhs) ? T : F;
case Node::neq: return (lhs != rhs) ? T : F; case Node::neq: return (lhs != rhs) ? T : F;
case Node::gt: return (lhs > rhs) ? T : F; case Node::gt: return (lhs > rhs) ? T : F;
...@@ -143,32 +137,32 @@ namespace Sass { ...@@ -143,32 +137,32 @@ namespace Sass {
case Node::lt: return (lhs < rhs) ? T : F; case Node::lt: return (lhs < rhs) ? T : F;
case Node::lte: return (lhs <= rhs) ? T : F; case Node::lte: return (lhs <= rhs) ? T : F;
default: default:
eval_error("unknown comparison operator " + expr.content.token.to_string(), expr.line_number, expr.file_name); throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line());
return Node(Node::none); return Node();
} }
} break; } break;
case Node::expression: { case Node::expression: {
Node acc(Node::expression, registry, expr.line_number, 1); Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1));
acc << eval(expr[0], env, f_env, registry); acc << eval(expr[0], env, f_env, new_Node);
Node rhs(eval(expr[2], env, f_env, registry)); Node rhs(eval(expr[2], env, f_env, new_Node));
accumulate(expr[1].type, acc, rhs, registry); accumulate(expr[1].type(), acc, rhs, new_Node);
for (size_t i = 3; i < expr.size(); i += 2) { for (size_t i = 3, S = expr.size(); i < S; i += 2) {
Node rhs(eval(expr[i+1], env, f_env, registry)); Node rhs(eval(expr[i+1], env, f_env, new_Node));
accumulate(expr[i].type, acc, rhs, registry); accumulate(expr[i].type(), acc, rhs, new_Node);
} }
return acc.size() == 1 ? acc[0] : acc; return acc.size() == 1 ? acc[0] : acc;
} break; } break;
case Node::term: { case Node::term: {
if (expr.eval_me) { if (expr.should_eval()) {
Node acc(Node::expression, registry, expr.line_number, 1); Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1));
acc << eval(expr[0], env, f_env, registry); acc << eval(expr[0], env, f_env, new_Node);
Node rhs(eval(expr[2], env, f_env, registry)); Node rhs(eval(expr[2], env, f_env, new_Node));
accumulate(expr[1].type, acc, rhs, registry); accumulate(expr[1].type(), acc, rhs, new_Node);
for (size_t i = 3; i < expr.size(); i += 2) { for (size_t i = 3, S = expr.size(); i < S; i += 2) {
Node rhs(eval(expr[i+1], env, f_env, registry)); Node rhs(eval(expr[i+1], env, f_env, new_Node));
accumulate(expr[i].type, acc, rhs, registry); accumulate(expr[i].type(), acc, rhs, new_Node);
} }
return acc.size() == 1 ? acc[0] : acc; return acc.size() == 1 ? acc[0] : acc;
} }
...@@ -178,59 +172,54 @@ namespace Sass { ...@@ -178,59 +172,54 @@ namespace Sass {
} break; } break;
case Node::textual_percentage: { case Node::textual_percentage: {
Node pct(expr.line_number, std::atof(expr.content.token.begin)); return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage);
pct.type = Node::numeric_percentage;
return pct;
} break; } break;
case Node::textual_dimension: { case Node::textual_dimension: {
return Node(expr.line_number, return new_Node(expr.path(), expr.line(),
std::atof(expr.content.token.begin), std::atof(expr.token().begin),
Token::make(Prelexer::number(expr.content.token.begin), Token::make(Prelexer::number(expr.token().begin),
expr.content.token.end)); expr.token().end));
} break; } break;
case Node::textual_number: { 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; } break;
case Node::textual_hex: { case Node::textual_hex: {
Node triple(Node::numeric_color, registry, expr.line_number, 4); Node triple(new_Node(Node::numeric_color, expr.path(), expr.line(), 4));
Token hext(Token::make(expr.content.token.begin+1, expr.content.token.end)); Token hext(Token::make(expr.token().begin+1, expr.token().end));
if (hext.length() == 6) { if (hext.length() == 6) {
for (int i = 0; i < 6; i += 2) { for (int i = 0; i < 6; i += 2) {
triple << 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 { else {
for (int i = 0; i < 3; ++i) { 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; return triple;
} break; } break;
case Node::variable: { 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); if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line());
return env[expr.content.token]; return env[expr.token()];
} break; } break;
case Node::function_call: { case Node::function_call: {
// TO DO: default-constructed Function should be a generic callback // TO DO: default-constructed Function should be a generic callback (maybe)
pair<string, size_t> sig(expr[0].content.token.to_string(), expr[1].size()); pair<string, size_t> sig(expr[0].token().to_string(), expr[1].size());
if (!f_env.count(sig)) { if (!f_env.count(sig)) return expr;
// stringstream ss; return apply_function(f_env[sig], expr[1], env, f_env, new_Node);
// 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);
} break; } break;
case Node::unary_plus: { case Node::unary_plus: {
Node arg(eval(expr[0], env, f_env, registry)); Node arg(eval(expr[0], env, f_env, new_Node));
if (arg.is_numeric()) return arg; if (arg.is_numeric()) {
return arg;
}
else { else {
expr[0] = arg; expr[0] = arg;
return expr; return expr;
...@@ -238,9 +227,9 @@ namespace Sass { ...@@ -238,9 +227,9 @@ namespace Sass {
} break; } break;
case Node::unary_minus: { case Node::unary_minus: {
Node arg(eval(expr[0], env, f_env, registry)); Node arg(eval(expr[0], env, f_env, new_Node));
if (arg.is_numeric()) { if (arg.is_numeric()) {
arg.set_numeric_value(-arg.numeric_value()); return new_Node(expr.path(), expr.line(), -arg.numeric_value());
} }
else { else {
expr[0] = arg; expr[0] = arg;
...@@ -250,110 +239,100 @@ namespace Sass { ...@@ -250,110 +239,100 @@ namespace Sass {
case Node::string_schema: case Node::string_schema:
case Node::value_schema: { case Node::value_schema: {
// cerr << "evaluating schema of size " << expr.size() << endl; for (size_t i = 0, S = expr.size(); i < S; ++i) {
for (size_t i = 0; i < expr.size(); ++i) { expr[i] = eval(expr[i], env, f_env, new_Node);
expr[i] = eval(expr[i], env, f_env, registry);
} }
return expr; return expr;
} break; } break;
case Node::css_import: { case Node::css_import: {
expr[0] = eval(expr[0], env, f_env, registry); expr[0] = eval(expr[0], env, f_env, new_Node);
return expr; return expr;
} break; } break;
default: { default: {
return expr; return expr;
} } break;
} }
return expr; return expr;
} }
Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry) 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 lnum = lhs.numeric_value();
double rnum = rhs.numeric_value(); double rnum = rhs.numeric_value();
if (lhs.type == Node::number && rhs.type == Node::number) { if (lhs.type() == Node::number && rhs.type() == Node::number) {
Node result(acc.line_number, operate(op, lnum, rnum)); Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum)));
acc.content.children->pop_back(); acc.pop_back();
acc.content.children->push_back(result); acc.push_back(result);
} }
// TO DO: find a way to merge the following two clauses // TO DO: find a way to merge the following two clauses
else if (lhs.type == Node::number && rhs.type == Node::numeric_dimension) { 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))); Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), rhs.unit()));
acc.content.children->pop_back(); acc.pop_back();
acc.content.children->push_back(result); acc.push_back(result);
} }
else if (lhs.type == Node::numeric_dimension && rhs.type == Node::number) { 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(lhs.content.dimension.unit))); Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit()));
acc.content.children->pop_back(); acc.pop_back();
acc.content.children->push_back(result); 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 // TO DO: CHECK FOR MISMATCHED UNITS HERE
Node result; Node result;
if (op == Node::div) 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 else
{ result = Node(acc.line_number, operate(op, lnum, rnum), Token::make(lhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))); } { result = new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit()); }
acc.content.children->pop_back(); acc.pop_back();
acc.content.children->push_back(result); acc.push_back(result);
} }
// TO DO: find a way to merge the following two clauses // 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) { if (op != Node::sub && op != Node::div) {
double r = operate(op, lhs.content.numeric_value, rhs[0].content.numeric_value); double r = operate(op, lhs.numeric_value(), rhs[0].numeric_value());
double g = operate(op, lhs.content.numeric_value, rhs[1].content.numeric_value); double g = operate(op, lhs.numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs.content.numeric_value, rhs[2].content.numeric_value); double b = operate(op, lhs.numeric_value(), rhs[2].numeric_value());
double a = rhs[3].content.numeric_value; double a = rhs[3].numeric_value();
acc.content.children->pop_back(); acc.pop_back();
acc << Node(registry, acc.line_number, r, g, b, a); acc << new_Node(acc.path(), acc.line(), r, g, b, a);
} }
// trying to handle weird edge cases ... not sure if it's worth it // trying to handle weird edge cases ... not sure if it's worth it
else if (op == Node::div) { else if (op == Node::div) {
acc << Node(Node::div); acc << new_Node(Node::div, acc.path(), acc.line(), 0);
acc << rhs; acc << rhs;
} }
else if (op == Node::sub) { else if (op == Node::sub) {
acc << Node(Node::sub); acc << new_Node(Node::sub, acc.path(), acc.line(), 0);
acc << rhs; acc << rhs;
} }
else { else {
acc << rhs; acc << rhs;
} }
} }
else if (lhs.type == Node::numeric_color && rhs.type == Node::number) { 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 r = operate(op, lhs[0].numeric_value(), rhs.numeric_value());
double g = operate(op, lhs[1].content.numeric_value, rhs.content.numeric_value); double g = operate(op, lhs[1].numeric_value(), rhs.numeric_value());
double b = operate(op, lhs[2].content.numeric_value, rhs.content.numeric_value); double b = operate(op, lhs[2].numeric_value(), rhs.numeric_value());
double a = lhs[3].content.numeric_value; double a = lhs[3].numeric_value();
acc.content.children->pop_back(); acc.pop_back();
acc << Node(registry, acc.line_number, r, g, b, a); acc << new_Node(acc.path(), acc.line(), r, g, b, a);
} }
else if (lhs.type == Node::numeric_color && rhs.type == Node::numeric_color) { else if (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); 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].content.numeric_value, rhs[0].content.numeric_value); double r = operate(op, lhs[0].numeric_value(), rhs[0].numeric_value());
double g = operate(op, lhs[1].content.numeric_value, rhs[1].content.numeric_value); double g = operate(op, lhs[1].numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs[2].content.numeric_value, rhs[2].content.numeric_value); double b = operate(op, lhs[2].numeric_value(), rhs[2].numeric_value());
double a = lhs[3].content.numeric_value; double a = lhs[3].numeric_value();
acc.content.children->pop_back(); acc.pop_back();
acc << Node(registry, acc.line_number, r, g, b, a); acc << new_Node(acc.path(), acc.line(), 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 { else {
// TO DO: disallow division and multiplication on lists // TO DO: disallow division and multiplication on lists
acc.content.children->push_back(rhs); acc.push_back(rhs);
} }
return acc; return acc;
...@@ -371,29 +350,29 @@ namespace Sass { ...@@ -371,29 +350,29 @@ namespace Sass {
} }
} }
Node apply_mixin(Node& mixin, 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, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node)
{ {
Node params(mixin[1]); Node params(mixin[1]);
Node body(mixin[2].clone(registry)); Node body(new_Node(mixin[2])); // clone the body
Environment bindings; Environment bindings;
// bind arguments // bind arguments
for (size_t i = 0, j = 0; i < args.size(); ++i) { for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type == Node::assignment) { if (args[i].type() == Node::assignment) {
Node arg(args[i]); 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 // check that the keyword arg actually names a formal parameter
bool valid_param = false; 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]; 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) { if (arg[0] == param_k) {
valid_param = true; valid_param = true;
break; 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)) { if (!bindings.query(name)) {
bindings[name] = eval(arg[1], env, f_env, registry); bindings[name] = eval(arg[1], env, f_env, new_Node);
} }
} }
else { else {
...@@ -401,49 +380,49 @@ namespace Sass { ...@@ -401,49 +380,49 @@ namespace Sass {
if (j >= params.size()) { if (j >= params.size()) {
stringstream ss; stringstream ss;
ss << "mixin " << mixin[0].to_string("") << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments"); 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); throw_eval_error(ss.str(), args[i].path(), args[i].line());
} }
Node param(params[j]); Node param(params[j]);
Token name(param.type == Node::variable ? param.content.token : param[0].content.token); Token name(param.type() == Node::variable ? param.token() : param[0].token());
bindings[name] = eval(args[i], env, f_env, registry); bindings[name] = eval(args[i], env, f_env, new_Node);
++j; ++j;
} }
} }
// plug the holes with default arguments if any // plug the holes with default arguments if any
for (size_t i = 0; i < params.size(); ++i) { for (size_t i = 0, S = params.size(); i < S; ++i) {
if (params[i].type == Node::assignment) { if (params[i].type() == Node::assignment) {
Node param(params[i]); Node param(params[i]);
Token name(param[0].content.token); Token name(param[0].token());
if (!bindings.query(name)) { if (!bindings.query(name)) {
bindings[name] = eval(param[1], env, f_env, registry); bindings[name] = eval(param[1], env, f_env, new_Node);
} }
} }
} }
// lexically link the new environment and eval the mixin's body // lexically link the new environment and eval the mixin's body
bindings.link(env.global ? *env.global : env); bindings.link(env.global ? *env.global : env);
for (size_t i = 0; i < body.size(); ++i) { for (size_t i = 0, S = body.size(); i < S; ++i) {
body[i] = eval(body[i], bindings, f_env, registry); body[i] = eval(body[i], bindings, f_env, new_Node);
} }
return body; 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) Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node)
{ {
map<Token, Node> bindings; map<Token, Node> bindings;
// bind arguments // bind arguments
for (size_t i = 0, j = 0; i < args.size(); ++i) { for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type == Node::assignment) { if (args[i].type() == Node::assignment) {
Node arg(args[i]); Node arg(args[i]);
Token name(arg[0].content.token); Token name(arg[0].token());
bindings[name] = eval(arg[1], env, f_env, registry); bindings[name] = eval(arg[1], env, f_env, new_Node);
} }
else { else {
// TO DO: ensure (j < f.parameters.size()) // 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], env, f_env, new_Node);
++j; ++j;
} }
} }
return f(bindings, registry); return f(bindings, new_Node);
} }
} }
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
namespace Sass { namespace Sass {
using std::map; using std::map;
Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry); Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node);
Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry); Node accumulate(Node::Type op, Node& acc, Node& rhs, Node_Factory& new_Node);
double operate(Node::Type op, double lhs, double rhs); double operate(Node::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_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node);
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_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node);
} }
\ No newline at end of file
#ifndef SASS_PRELEXER_INCLUDED #ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp" #include "prelexer.hpp"
#endif #endif
#include "node_factory.hpp"
#include "functions.hpp" #include "functions.hpp"
#include "error.hpp" #include "error.hpp"
#include <iostream> #include <iostream>
...@@ -9,119 +10,116 @@ using std::cerr; using std::endl; ...@@ -9,119 +10,116 @@ using std::cerr; using std::endl;
namespace Sass { namespace Sass {
namespace Functions { 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 (!path.empty() && Prelexer::string_constant(path.c_str()))
if (file_name) { path = path.substr(1, path.length() - 1);
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name); throw Error(Error::evaluation, path, line, message);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
} }
// RGB Functions /////////////////////////////////////////////////////// // RGB Functions ///////////////////////////////////////////////////////
Function_Descriptor rgb_descriptor = Function_Descriptor rgb_descriptor =
{ "rgb", "$red", "$green", "$blue", 0 }; { "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 r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]); Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]); Node b(bindings[parameters[2]]);
if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number)) { 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); throw_eval_error("arguments for rgb must be numbers", r.path(), r.line());
} }
Node color(Node::numeric_color, registry, 0, 4); return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), 1.0);
color << r << g << b << Node(0, 1.0);
return color;
} }
Function_Descriptor rgba_4_descriptor = Function_Descriptor rgba_4_descriptor =
{ "rgba", "$red", "$green", "$blue", "$alpha", 0 }; { "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 r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]); Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]); Node b(bindings[parameters[2]]);
Node a(bindings[parameters[3]]); Node a(bindings[parameters[3]]);
if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number && a.type == Node::number)) { 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); throw_eval_error("arguments for rgba must be numbers", r.path(), r.line());
} }
Node color(Node::numeric_color, registry, 0, 4); return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value());
color << r << g << b << a;
return color;
} }
Function_Descriptor rgba_2_descriptor = Function_Descriptor rgba_2_descriptor =
{ "rgba", "$color", "$alpha", 0 }; { "rgba", "$color", "$alpha", 0 };
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) {
Node color(bindings[parameters[0]].clone(registry)); Node color(bindings[parameters[0]]);
color[3] = bindings[parameters[1]]; Node r(color[0]);
return color; 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 = Function_Descriptor red_descriptor =
{ "red", "$color", 0 }; { "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]]); 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]; return color[0];
} }
Function_Descriptor green_descriptor = Function_Descriptor green_descriptor =
{ "green", "$color", 0 }; { "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]]); 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]; return color[1];
} }
Function_Descriptor blue_descriptor = Function_Descriptor blue_descriptor =
{ "blue", "$color", 0 }; { "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]]); 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]; return color[2];
} }
Node mix_impl(Node color1, Node color2, double weight, vector<vector<Node>*>& registry) { Node mix_impl(Node color1, Node color2, double weight, Node_Factory& new_Node) {
if (!(color1.type == Node::numeric_color && color2.type == Node::numeric_color)) { 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); throw_eval_error("first two arguments to mix must be colors", color1.path(), color1.line());
} }
double p = weight/100; double p = weight/100;
double w = 2*p - 1; 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 w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0;
double w2 = 1 - w1; 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) { for (int i = 0; i < 3; ++i) {
mixed << Node(mixed.line_number, w1*color1[i].content.numeric_value + mixed << new_Node(mixed.path(), mixed.line(),
w2*color2[i].content.numeric_value); 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); double alpha = color1[3].numeric_value()*p + color2[3].numeric_value()*(1-p);
mixed << Node(mixed.line_number, alpha); mixed << new_Node(mixed.path(), mixed.line(), alpha);
return mixed; return mixed;
} }
Function_Descriptor mix_2_descriptor = Function_Descriptor mix_2_descriptor =
{ "mix", "$color1", "$color2", 0 }; { "mix", "$color1", "$color2", 0 };
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) {
return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, registry); return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, new_Node);
} }
Function_Descriptor mix_3_descriptor = Function_Descriptor mix_3_descriptor =
{ "mix", "$color1", "$color2", "$weight", 0 }; { "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]]); Node percentage(bindings[parameters[2]]);
if (!(percentage.type == Node::number || percentage.type == Node::numeric_percentage || percentage.type == Node::numeric_dimension)) { 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); throw_eval_error("third argument to mix must be numeric", percentage.path(), percentage.line());
} }
return mix_impl(bindings[parameters[0]], return mix_impl(bindings[parameters[0]],
bindings[parameters[1]], bindings[parameters[1]],
percentage.numeric_value(), percentage.numeric_value(),
registry); new_Node);
} }
// HSL Functions /////////////////////////////////////////////////////// // HSL Functions ///////////////////////////////////////////////////////
...@@ -135,7 +133,7 @@ namespace Sass { ...@@ -135,7 +133,7 @@ namespace Sass {
return m1; 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; h = static_cast<double>(((static_cast<int>(h) % 360) + 360) % 360) / 360.0;
s = s / 100.0; s = s / 100.0;
l = l / 100.0; l = l / 100.0;
...@@ -148,53 +146,53 @@ namespace Sass { ...@@ -148,53 +146,53 @@ namespace Sass {
double g = h_to_rgb(m1, m2, h) * 255.0; double g = h_to_rgb(m1, m2, h) * 255.0;
double b = h_to_rgb(m1, m2, h-1.0/3.0) * 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 = Function_Descriptor hsla_descriptor =
{ "hsla", "$hue", "$saturation", "$lightness", "$alpha", 0 }; { "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() && if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() && bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric() && bindings[parameters[2]].is_numeric() &&
bindings[parameters[3]].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 h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value(); double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value(); double l = bindings[parameters[2]].numeric_value();
double a = bindings[parameters[3]].numeric_value(); double a = bindings[parameters[3]].numeric_value();
Node color(hsla_impl(h, s, l, a, registry)); Node color(hsla_impl(h, s, l, a, new_Node));
color.line_number = bindings[parameters[0]].line_number; // color.line() = bindings[parameters[0]].line();
return color; return color;
} }
Function_Descriptor hsl_descriptor = Function_Descriptor hsl_descriptor =
{ "hsl", "$hue", "$saturation", "$lightness", 0 }; { "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() && if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() && bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].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 h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value(); double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value(); double l = bindings[parameters[2]].numeric_value();
Node color(hsla_impl(h, s, l, 1, registry)); Node color(hsla_impl(h, s, l, 1, new_Node));
color.line_number = bindings[parameters[0]].line_number; // color.line() = bindings[parameters[0]].line();
return color; return color;
} }
Function_Descriptor invert_descriptor = Function_Descriptor invert_descriptor =
{ "invert", "$color", 0 }; { "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]]); 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); if (orig.type() != Node::numeric_color) throw_eval_error("argument to invert must be a color", orig.path(), orig.line());
return Node(registry, orig.line_number, return new_Node(orig.path(), orig.line(),
255 - orig[0].content.numeric_value, 255 - orig[0].numeric_value(),
255 - orig[1].content.numeric_value, 255 - orig[1].numeric_value(),
255 - orig[2].content.numeric_value, 255 - orig[2].numeric_value(),
orig[3].content.numeric_value); orig[3].numeric_value());
} }
// Opacity Functions /////////////////////////////////////////////////// // Opacity Functions ///////////////////////////////////////////////////
...@@ -203,9 +201,9 @@ namespace Sass { ...@@ -203,9 +201,9 @@ namespace Sass {
{ "alpha", "$color", 0 }; { "alpha", "$color", 0 };
Function_Descriptor opacity_descriptor = Function_Descriptor opacity_descriptor =
{ "opacity", "$color", 0 }; { "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]]); 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]; return color[3];
} }
...@@ -213,58 +211,64 @@ namespace Sass { ...@@ -213,58 +211,64 @@ namespace Sass {
{ "opacify", "$color", "$amount", 0 }; { "opacify", "$color", "$amount", 0 };
Function_Descriptor fade_in_descriptor = Function_Descriptor fade_in_descriptor =
{ "fade_in", "$color", "$amount", 0 }; { "fade_in", "$color", "$amount", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node color(bindings[parameters[0]]);
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 delta(bindings[parameters[1]]); 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); if (color.type() != Node::numeric_color || !delta.is_numeric()) {
cpy[3].content.numeric_value += delta.numeric_value(); throw_eval_error("arguments to opacify/fade_in must be a color and a numeric value", color.path(), color.line());
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1; }
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0; if (delta.numeric_value() < 0 || delta.numeric_value() > 1) {
return cpy; 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 = Function_Descriptor transparentize_descriptor =
{ "transparentize", "$color", "$amount", 0 }; { "transparentize", "$color", "$amount", 0 };
Function_Descriptor fade_out_descriptor = Function_Descriptor fade_out_descriptor =
{ "fade_out", "$color", "$amount", 0 }; { "fade_out", "$color", "$amount", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node color(bindings[parameters[0]]);
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 delta(bindings[parameters[1]]); 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); if (color.type() != Node::numeric_color || !delta.is_numeric()) {
cpy[3].content.numeric_value -= delta.numeric_value(); throw_eval_error("arguments to transparentize/fade_out must be a color and a numeric value", color.path(), color.line());
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1; }
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0; if (delta.numeric_value() < 0 || delta.numeric_value() > 1) {
return cpy; 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 //////////////////////////////////////////////////// // String Functions ////////////////////////////////////////////////////
Function_Descriptor unquote_descriptor = Function_Descriptor unquote_descriptor =
{ "unquote", "$string", 0 }; { "unquote", "$string", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node cpy(new_Node(bindings[parameters[0]]));
// if (cpy.type != Node::string_constant /* && cpy.type != Node::concatenation */) { // 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); // throw_eval_error("argument to unquote must be a string", cpy.path(), cpy.line());
// } // }
cpy.unquoted = true; cpy.is_unquoted() = true;
return cpy; return cpy;
} }
Function_Descriptor quote_descriptor = Function_Descriptor quote_descriptor =
{ "quote", "$string", 0 }; { "quote", "$string", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node orig(bindings[parameters[0]]);
if (cpy.type != Node::string_constant && cpy.type != Node::identifier) { if (orig.type() != Node::string_constant && orig.type() != Node::identifier) {
eval_error("argument to quote must be a string or identifier", cpy.line_number, cpy.file_name); throw_eval_error("argument to quote must be a string or identifier", orig.path(), orig.line());
} }
cpy.type = Node::string_constant; Node cpy(new_Node(orig));
cpy.unquoted = false; cpy.is_unquoted() = false;
return cpy; return cpy;
} }
...@@ -272,159 +276,231 @@ namespace Sass { ...@@ -272,159 +276,231 @@ namespace Sass {
Function_Descriptor percentage_descriptor = Function_Descriptor percentage_descriptor =
{ "percentage", "$value", 0 }; { "percentage", "$value", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node orig(bindings[parameters[0]]);
// TO DO: make sure it's not already a percentage if (orig.type() != Node::number) {
if (cpy.type != Node::number) eval_error("argument to percentage must be a unitless number", cpy.line_number, cpy.file_name); throw_eval_error("argument to percentage must be a unitless number", orig.path(), orig.line());
cpy.content.numeric_value = cpy.content.numeric_value * 100; }
cpy.type = Node::numeric_percentage; return new_Node(orig.path(), orig.line(), orig.numeric_value() * 100, Node::numeric_percentage);
return cpy;
} }
Function_Descriptor round_descriptor = Function_Descriptor round_descriptor =
{ "round", "$value", 0 }; { "round", "$value", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node orig(bindings[parameters[0]]);
if (cpy.type == Node::numeric_dimension) { switch (orig.type())
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value + 0.5); {
} case Node::numeric_dimension: {
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage) { return new_Node(orig.path(), orig.line(),
cpy.content.numeric_value = std::floor(cpy.content.numeric_value + 0.5); std::floor(orig.numeric_value() + 0.5), orig.unit());
} } break;
else {
eval_error("argument to round must be numeric", cpy.line_number, cpy.file_name); 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 = Function_Descriptor ceil_descriptor =
{ "ceil", "$value", 0 }; { "ceil", "$value", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node orig(bindings[parameters[0]]);
if (cpy.type == Node::numeric_dimension) { switch (orig.type())
cpy.content.dimension.numeric_value = std::ceil(cpy.content.dimension.numeric_value); {
} case Node::numeric_dimension: {
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){ return new_Node(orig.path(), orig.line(),
cpy.content.numeric_value = std::ceil(cpy.content.numeric_value); std::ceil(orig.numeric_value()), orig.unit());
} } break;
else {
eval_error("argument to ceil must be numeric", cpy.line_number, cpy.file_name); 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 = Function_Descriptor floor_descriptor =
{ "floor", "$value", 0 }; { "floor", "$value", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node orig(bindings[parameters[0]]);
if (cpy.type == Node::numeric_dimension) { switch (orig.type())
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value); {
} case Node::numeric_dimension: {
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){ return new_Node(orig.path(), orig.line(),
cpy.content.numeric_value = std::floor(cpy.content.numeric_value); std::floor(orig.numeric_value()), orig.unit());
} } break;
else {
eval_error("argument to floor must be numeric", cpy.line_number, cpy.file_name); 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 = Function_Descriptor abs_descriptor =
{ "abs", "$value", 0 }; { "abs", "$value", 0 };
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) {
Node cpy(bindings[parameters[0]].clone(registry)); Node orig(bindings[parameters[0]]);
if (cpy.type == Node::numeric_dimension) { switch (orig.type())
cpy.content.dimension.numeric_value = std::fabs(cpy.content.dimension.numeric_value); {
} case Node::numeric_dimension: {
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){ return new_Node(orig.path(), orig.line(),
cpy.content.numeric_value = std::abs(cpy.content.numeric_value); std::abs(orig.numeric_value()), orig.unit());
} } break;
else {
eval_error("argument to abs must be numeric", cpy.line_number, cpy.file_name); 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 ////////////////////////////////////////////////////// // List Functions //////////////////////////////////////////////////////
Function_Descriptor length_descriptor = Function_Descriptor length_descriptor =
{ "length", "$list", 0 }; { "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]]); Node arg(bindings[parameters[0]]);
if (arg.type == Node::space_list || arg.type == Node::comma_list) { switch (arg.type())
return Node(arg.line_number, arg.size()); {
} case Node::space_list:
else if (arg.type == Node::nil) { case Node::comma_list: {
return Node(arg.line_number, 0); return new_Node(arg.path(), arg.line(), arg.size());
} } break;
// single objects should be reported as lists of length 1
else { case Node::nil: {
return Node(arg.line_number, 1); return new_Node(arg.path(), arg.line(), 0);
} break;
default: {
// single objects should be reported as lists of length 1
return new_Node(arg.path(), arg.line(), 1);
} break;
} }
// unreachable statement
return Node();
} }
Function_Descriptor nth_descriptor = Function_Descriptor nth_descriptor =
{ "nth", "$list", "$n", 0 }; { "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]]); 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]]); 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.type() != Node::number) {
if (n.numeric_value() < 1 || n.numeric_value() > l.size()) eval_error("out of range index for nth", n.line_number, n.file_name); throw_eval_error("second argument to nth must be a number", n.path(), n.line());
return l[bindings[parameters[1]].content.numeric_value - 1]; }
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"; 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 // if the args aren't lists, turn them into singleton lists
Node l1(bindings[parameters[0]]); Node l1(bindings[parameters[0]]);
if (l1.type != Node::space_list && l1.type != Node::comma_list && l1.type != Node::nil) { 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; l1 = new_Node(Node::space_list, l1.path(), l1.line(), 1) << l1;
} }
Node l2(bindings[parameters[1]]); Node l2(bindings[parameters[1]]);
if (l2.type != Node::space_list && l2.type != Node::comma_list && l2.type != Node::nil) { 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; 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 // figure out the combined size in advance
size_t size = 0; size_t size = 0;
if (l1.type != Node::nil) size += l1.size(); if (l1.type() != Node::nil) size += l1.size();
if (l2.type != Node::nil) size += l2.size(); if (l2.type() != Node::nil) size += l2.size();
// figure out the result type in advance
// accumulate the result Node::Type rtype = Node::space_list;
Node lr(l1.type, registry, l1.line_number, size);
if (has_sep) { if (has_sep) {
string sep(bindings[parameters[2]].content.token.unquote()); string sep(bindings[parameters[2]].token().unquote());
if (sep == "comma") lr.type = Node::comma_list; if (sep == "comma") rtype = Node::comma_list;
else if (sep == "space") lr.type = Node::space_list; else if (sep == "space") rtype = Node::space_list;
else if (sep == "auto") ; // leave it alone else if (sep == "auto") rtype = l1.type();
else { 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 (l1.type() != Node::nil) rtype = l1.type();
else if (l2.type != Node::nil) lr.type = l2.type; else if (l2.type() != Node::nil) rtype = l2.type();
// accumulate the result
if (l1.type != Node::nil) lr += l1; Node lr(new_Node(rtype, l1.path(), l1.line(), size));
if (l2.type != Node::nil) lr += l2; if (l1.type() != Node::nil) lr += l1;
if (l2.type() != Node::nil) lr += l2;
return lr; return lr;
} }
Function_Descriptor join_2_descriptor = Function_Descriptor join_2_descriptor =
{ "join", "$list1", "$list2", 0 }; { "join", "$list1", "$list2", 0 };
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) {
return join_impl(parameters, bindings, false, registry); return join_impl(parameters, bindings, false, new_Node);
} }
Function_Descriptor join_3_descriptor = Function_Descriptor join_3_descriptor =
{ "join", "$list1", "$list2", "$separator", 0 }; { "join", "$list1", "$list2", "$separator", 0 };
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) {
return join_impl(parameters, bindings, true, registry); return join_impl(parameters, bindings, true, new_Node);
} }
// Introspection Functions ///////////////////////////////////////////// // Introspection Functions /////////////////////////////////////////////
...@@ -437,35 +513,37 @@ namespace Sass { ...@@ -437,35 +513,37 @@ namespace Sass {
Function_Descriptor type_of_descriptor = Function_Descriptor type_of_descriptor =
{ "type-of", "$value", 0 }; { "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 val(bindings[parameters[0]]);
Node type(Node::string_constant, val.line_number, Token::make()); Token type_name;
type.unquoted = true; switch (val.type())
switch (val.type)
{ {
case Node::number: case Node::number:
case Node::numeric_dimension: case Node::numeric_dimension:
case Node::numeric_percentage: case Node::numeric_percentage: {
type.content.token = Token::make(number_name); type_name = Token::make(number_name);
break; } break;
case Node::boolean: case Node::boolean: {
type.content.token = Token::make(bool_name); type_name = Token::make(bool_name);
break; } break;
case Node::string_constant: case Node::string_constant:
case Node::value_schema: case Node::value_schema: {
type.content.token = Token::make(string_name); type_name = Token::make(string_name);
break; } break;
case Node::numeric_color: case Node::numeric_color: {
type.content.token = Token::make(color_name); type_name = Token::make(color_name);
break; } break;
case Node::comma_list: case Node::comma_list:
case Node::space_list: case Node::space_list:
case Node::nil: case Node::nil: {
type.content.token = Token::make(list_name); type_name = Token::make(list_name);
break; } break;
default: default: {
type.content.token = Token::make(string_name); 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; return type;
} }
...@@ -474,25 +552,25 @@ namespace Sass { ...@@ -474,25 +552,25 @@ namespace Sass {
Function_Descriptor unit_descriptor = Function_Descriptor unit_descriptor =
{ "unit", "$number", 0 }; { "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 val(bindings[parameters[0]]);
Node u(Node::string_constant, val.line_number, Token::make()); switch (val.type())
switch (val.type)
{ {
case Node::number: case Node::number: {
u.content.token = Token::make(empty_str); return new_Node(Node::string_constant, val.path(), val.line(), Token::make(empty_str));
break; } break;
case Node::numeric_percentage:
u.content.token = Token::make(percent_str);
break;
case Node::numeric_dimension: case Node::numeric_dimension:
u.content.token = Token::make(val.content.dimension.unit, Prelexer::identifier(val.content.dimension.unit)); case Node::numeric_percentage: {
break; return new_Node(Node::string_constant, val.path(), val.line(), val.unit());
default: } break;
eval_error("argument to unit must be numeric", val.line_number, val.file_name);
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"; extern const char true_str[] = "true";
...@@ -500,100 +578,73 @@ namespace Sass { ...@@ -500,100 +578,73 @@ namespace Sass {
Function_Descriptor unitless_descriptor = Function_Descriptor unitless_descriptor =
{ "unitless", "$number", 0 }; { "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 val(bindings[parameters[0]]);
Node result(Node::string_constant, val.line_number, Token::make()); switch (val.type())
result.unquoted = true;
switch (val.type)
{ {
case Node::number: { case Node::number: {
Node T(Node::boolean); return new_Node(Node::boolean, val.path(), val.line(), true);
T.line_number = val.line_number; } break;
T.content.boolean_value = true;
return T;
} break;
case Node::numeric_percentage: case Node::numeric_percentage:
case Node::numeric_dimension: { case Node::numeric_dimension: {
Node F(Node::boolean); return new_Node(Node::boolean, val.path(), val.line(), false);
F.line_number = val.line_number; } break;
F.content.boolean_value = false;
return F; default: {
} break; throw_eval_error("argument to unitless must be numeric", val.path(), val.line());
default: } break;
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 = Function_Descriptor comparable_descriptor =
{ "comparable", "$number_1", "$number_2", 0 }; { "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 n1(bindings[parameters[0]]);
Node n2(bindings[parameters[1]]); Node n2(bindings[parameters[1]]);
Node::Type t1 = n1.type; Node::Type t1 = n1.type();
Node::Type t2 = n2.type; Node::Type t2 = n2.type();
if (t1 == Node::number && n2.is_numeric() || if ((t1 == Node::number && n2.is_numeric()) ||
n1.is_numeric() && t2 == Node::number) { (n1.is_numeric() && t2 == Node::number)) {
Node T(Node::boolean); return new_Node(Node::boolean, n1.path(), n1.line(), true);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
} }
else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) { else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) {
Node T(Node::boolean); return new_Node(Node::boolean, n1.path(), n1.line(), true);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
} }
else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) { 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 u1(n1.unit().to_string());
string u2(Token::make(n2.content.dimension.unit, Prelexer::identifier(n2.content.dimension.unit)).to_string()); string u2(n2.unit().to_string());
if (u1 == "ex" && u2 == "ex" || if ((u1 == "ex" && u2 == "ex") ||
u1 == "em" && u2 == "em" || (u1 == "em" && u2 == "em") ||
(u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") && ((u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc")) { (u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc"))) {
Node T(Node::boolean); return new_Node(Node::boolean, n1.path(), n1.line(), true);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
} }
else { else {
Node F(Node::boolean); return new_Node(Node::boolean, n1.path(), n1.line(), false);
F.line_number = n1.line_number;
F.content.boolean_value = false;
return F;
} }
} }
else if (!n1.is_numeric() && !n2.is_numeric()) { 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());
} }
// default to false if we missed anything
Node F(Node::boolean); return new_Node(Node::boolean, n1.path(), n1.line(), false);
F.line_number = n1.line_number;
F.content.boolean_value = false;
return F;
} }
// Boolean Functions /////////////////////////////////////////////////// // Boolean Functions ///////////////////////////////////////////////////
Function_Descriptor not_descriptor = Function_Descriptor not_descriptor =
{ "not", "value", 0 }; { "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]]); Node val(bindings[parameters[0]]);
if (val.type == Node::boolean && val.content.boolean_value == false) { if (val.type() == Node::boolean && val.boolean_value() == false) {
Node T(Node::boolean); return new_Node(Node::boolean, val.path(), val.line(), true);
T.line_number = val.line_number;
T.content.boolean_value = true;
return T;
} }
else { else {
Node F(Node::boolean); return new_Node(Node::boolean, val.path(), val.line(), false);
F.line_number = val.line_number;
F.content.boolean_value = false;
return F;
} }
} }
} }
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
namespace Sass { namespace Sass {
using std::map; 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 const char* str;
typedef str Function_Descriptor[]; typedef str Function_Descriptor[];
...@@ -37,8 +37,8 @@ namespace Sass { ...@@ -37,8 +37,8 @@ namespace Sass {
} }
} }
Node operator()(map<Token, Node>& bindings, vector<vector<Node>*>& registry) const Node operator()(map<Token, Node>& bindings, Node_Factory& new_Node) const
{ return implementation(parameters, bindings, registry); } { return implementation(parameters, bindings, new_Node); }
}; };
...@@ -47,111 +47,111 @@ namespace Sass { ...@@ -47,111 +47,111 @@ namespace Sass {
// RGB Functions /////////////////////////////////////////////////////// // RGB Functions ///////////////////////////////////////////////////////
extern Function_Descriptor rgb_descriptor; 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; 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; 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; 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; 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; 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; 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; 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 /////////////////////////////////////////////////////// // HSL Functions ///////////////////////////////////////////////////////
extern Function_Descriptor hsla_descriptor; 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; 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; 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 /////////////////////////////////////////////////// // Opacity Functions ///////////////////////////////////////////////////
extern Function_Descriptor alpha_descriptor; extern Function_Descriptor alpha_descriptor;
extern Function_Descriptor opacity_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 opacify_descriptor;
extern Function_Descriptor fade_in_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 transparentize_descriptor;
extern Function_Descriptor fade_out_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 //////////////////////////////////////////////////// // String Functions ////////////////////////////////////////////////////
extern Function_Descriptor unquote_descriptor; 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; 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 //////////////////////////////////////////////////// // Number Functions ////////////////////////////////////////////////////
extern Function_Descriptor percentage_descriptor; 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; 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; 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; 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; 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 ////////////////////////////////////////////////////// // List Functions //////////////////////////////////////////////////////
extern Function_Descriptor length_descriptor; 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; 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; 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; 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 ///////////////////////////////////////////// // Introspection Functions /////////////////////////////////////////////
extern Function_Descriptor type_of_descriptor; 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; 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; 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; 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 /////////////////////////////////////////////////// // Boolean Functions ///////////////////////////////////////////////////
extern Function_Descriptor not_descriptor; 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 <sstream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include "node.hpp" #include "node.hpp"
#include "error.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
namespace Sass { namespace Sass {
size_t Node::allocations = 0; using namespace std;
size_t Node::destructed = 0;
// ------------------------------------------------------------------------
Node Node::clone(vector<vector<Node>*>& registry) const // Node method implementations
// ------------------------------------------------------------------------
void Node::flatten()
{ {
Node n(*this); if (type() != block && type() != expansion && type() != root) return;
if (has_children) { // size can change during flattening, so we need to call size() on each pass
n.content.children = new vector<Node>; for (size_t i = 0; i < size(); ++i) {
++allocations; if (at(i).type() == expansion) {
n.content.children->reserve(size()); Node expn(at(i));
for (size_t i = 0; i < size(); ++i) { if (expn.has_expansions()) expn.flatten();
n << at(i).clone(registry); 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());
} }
registry.push_back(n.content.children);
} }
return n;
} }
string Node::to_string(const string& prefix) const
{
switch (type)
{
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("");
}
return result;
} break;
case selector: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += ' ';
}
// if (at(0).type == selector_combinator) { bool Node::operator==(Node rhs) const
// result += at(0).content.token.to_string(); {
// result += ' '; Type t = type();
// } if (t != rhs.type()) return false;
// 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) { switch (t)
result += " "; {
result += at(i).to_string(at(i).has_backref ? prefix : ""); case comma_list:
} case space_list:
return result;
} 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);
} break;
case simple_selector_sequence: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += " ";
}
for (size_t i = 0; i < size(); ++i) {
Node::Type t = at(i).type;
result += at(i).to_string(t == backref ? prefix : "");
}
return result;
} break;
case pseudo:
case simple_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += content.token.to_string();
return result;
} break;
case pseudo_negation: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
result += at(1).to_string("");
result += ')';
return result;
} 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;
case backref: {
return prefix;
} break;
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;
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);
}
return result;
} break;
case expression: case expression:
case term: { case term:
string result(at(0).to_string(prefix)); case numeric_color: {
for (size_t i = 1; i < size(); ++i) { if (size() != rhs.size()) return false;
if (!(at(i).type == add || for (size_t i = 0, L = size(); i < L; ++i) {
// at(i).type == sub || // another edge case -- consider uncommenting if (at(i) == rhs[i]) continue;
at(i).type == mul)) { else return false;
result += at(i).to_string(prefix);
}
} }
return result; return true;
} break;
//edge case
case sub: {
return "-";
} break; } break;
case div: { case variable:
return "/"; 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; } break;
case css_import: { case number:
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;
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();
}
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: { case numeric_percentage: {
stringstream ss; return numeric_value() == rhs.numeric_value();
ss << content.dimension.numeric_value;
ss << '%';
return ss.str();
}
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();
} break;
case number: {
stringstream ss;
ss << content.numeric_value;
return ss.str();
} break; } break;
case numeric_color: { case numeric_dimension: {
if (at(3).content.numeric_value >= 1.0) { if (unit() == rhs.unit()) {
double a = at(0).content.numeric_value; return numeric_value() == rhs.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();
}
} }
else { else {
stringstream ss; return false;
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);
}
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;
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; } break;
case boolean: { case boolean: {
if (content.boolean_value) return "true"; return boolean_value() == rhs.boolean_value();
else return "false";
} break;
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;
case string_schema: {
string result;
for (size_t i = 0; i < size(); ++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; } break;
default: { default: {
// return content.token.to_string(); return true;
if (!has_children && type != flags) return content.token.to_string();
else return "";
} break; } break;
} }
return false;
} }
void Node::echo(stringstream& buf, size_t depth) { bool Node::operator!=(Node rhs) const
string indentation(2*depth, ' '); { return !(*this == rhs); }
switch (type) {
case comment: bool Node::operator<(Node rhs) const
buf << indentation << string(content.token) << endl; {
break; Type lhs_type = type();
case ruleset: Type rhs_type = rhs.type();
buf << indentation;
at(0).echo(buf, depth); if ((lhs_type == number && rhs_type == number) ||
at(1).echo(buf, depth); (lhs_type == numeric_percentage && rhs_type == numeric_percentage)) {
break; return numeric_value() < rhs.numeric_value();
case selector_group: }
at(0).echo(buf, depth); else if (lhs_type == numeric_dimension && rhs_type == numeric_dimension) {
for (size_t i = 1; i < size(); ++i) { if (unit() == rhs.unit()) {
buf << ", "; return numeric_value() < rhs.numeric_value();
at(i).echo(buf, depth);
}
break;
case selector:
for (size_t i = 0; i < size(); ++i) {
at(i).echo(buf, depth);
} }
break; else {
case selector_combinator: throw Error(Error::evaluation, path(), line(), "incompatible units");
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());
} }
break; }
case simple_selector: else {
buf << string(content.token); throw Error(Error::evaluation, path(), line(), "incomparable types");
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;
} }
} }
bool Node::operator<=(Node rhs) const
{ return *this < rhs || *this == rhs; }
bool Node::operator>(Node rhs) const
{ return !(*this <= rhs); }
bool Node::operator>=(Node rhs) const
{ return !(*this < rhs); }
void Node::emit_nested_css(stringstream& buf,
size_t depth,
const vector<string>& prefixes)
{
switch (type)
{
case root:
if (at(0).has_expansions) {
flatten();
}
for (size_t i = 0; i < size(); ++i) {
at(i).emit_nested_css(buf, depth, prefixes);
//if (at(i).type == css_import) buf << endl;
}
break;
case ruleset: { // ------------------------------------------------------------------------
Node sel_group(at(0)); // Token method implementations
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; string Token::unquote() const
if (prefixes.empty()) { {
new_prefixes.reserve(sel_group_size); string result;
for (size_t i = 0; i < sel_group_size; ++i) { const char* p = begin;
new_prefixes.push_back(sel_group_size > 1 ? sel_group[i].to_string(string()) : sel_group.to_string(string())); if (*begin == '\'' || *begin == '"') {
} ++p;
} while (p < end) {
else { if (*p == '\\') {
new_prefixes.reserve(prefixes.size() * sel_group_size); switch (*(++p)) {
for (size_t i = 0; i < prefixes.size(); ++i) { case 'n': result += '\n'; break;
for (size_t j = 0; j < sel_group_size; ++j) { case 't': result += '\t'; break;
new_prefixes.push_back(sel_group_size > 1 ? sel_group[j].to_string(prefixes[i]) : sel_group.to_string(prefixes[i])); 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;
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 << " {"; else {
for (size_t i = 0; i < block.size(); ++i) { result += *p;
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; ++p;
++depth; // if we printed content at this level, we need to indent any nested rulesets
} }
if (block[0].has_blocks) { return result;
for (size_t i = 0; i < block.size(); ++i) { }
if (block[i].type == ruleset) { else {
block[i].emit_nested_css(buf, depth, new_prefixes); 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;
} }
if (block[0].has_statements) --depth; // see previous comment return;
if (depth == 0 && prefixes.empty()) buf << endl; }
} break; else {
while (p < end) {
default: buf << *(p++);
emit_nested_css(buf, depth); // pass it along to the simpler version }
break; return;
} }
} }
void Node::emit_nested_css(stringstream& buf, size_t depth) bool Token::operator<(const Token& rhs) const
{ {
switch (type) const char* first1 = begin;
const char* last1 = end;
const char* first2 = rhs.begin;
const char* last2 = rhs.end;
while (first1!=last1)
{ {
case propset: { if (first2 == last2 || *first2 < *first1) return false;
emit_propset(buf, depth, ""); else if (*first1 < *first2) return true;
} break; ++first1; ++first2;
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 << 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;
} }
return (first2 != last2);
} }
void Node::emit_propset(stringstream& buf, size_t depth, const string& prefix) { bool Token::operator==(const Token& rhs) const
string new_prefix(prefix); {
bool has_prefix = false; if (length() != rhs.length()) return false;
if (new_prefix.empty()) {
new_prefix += "\n"; if ((begin[0] == '"' || begin[0] == '\'') &&
new_prefix += string(2*depth, ' '); (rhs.begin[0] == '"' || rhs.begin[0] == '\''))
new_prefix += at(0).content.token.to_string(); { return unquote() == rhs.unquote(); }
}
else {
new_prefix += "-";
new_prefix += at(0).content.token.to_string();
has_prefix = true;
}
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 << ';';
}
}
const char* p = begin;
const char* q = rhs.begin;
for (; p < end; ++p, ++q) if (*p != *q) return false;
return true;
} }
void Node::emit_expanded_css(stringstream& buf, const string& prefix) {
// switch (type) { // ------------------------------------------------------------------------
// case selector: // Node_Impl method implementations
// if (!prefix.empty()) buf << " "; // ------------------------------------------------------------------------
// buf << string(token);
// break; double Node_Impl::numeric_value()
// case comment: {
// if (!prefix.empty()) buf << " "; switch (type)
// buf << string(token) << endl; {
// break; case Node::number:
// case property: case Node::numeric_percentage:
// buf << string(token) << ":"; return value.numeric;
// break; case Node::numeric_dimension:
// case values: return value.dimension.numeric;
// for (size_t i = 0; i < children.size(); ++i) { default:
// buf << " " << string(children[i].token); break;
// } // throw an exception?
// break; }
// case rule: // if you reach this point, you've got a logic error somewhere
// buf << " "; return 0;
// 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;
// }
} }
void Node::flatten() extern const char percent_str[] = "%";
extern const char empty_str[] = "";
Token Node_Impl::unit()
{ {
if (type != block && type != expansion && type != root) return; switch (type)
for (size_t i = 0; i < size(); ++i) { {
if (at(i).type == expansion) { case Node::numeric_percentage: {
Node expn = at(i); return Token::make(percent_str);
if (expn[0].has_expansions) expn.flatten(); } break;
at(0).has_statements |= expn[0].has_statements;
at(0).has_blocks |= expn[0].has_blocks; case Node::numeric_dimension: {
at(0).has_expansions |= expn[0].has_expansions; return value.dimension.unit;
at(i).type = none; } break;
content.children->insert(content.children->begin() + i,
expn.content.children->begin(), default: break;
expn.content.children->end());
}
} }
return Token::make(empty_str);
} }
//
// 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 #define SASS_NODE_INCLUDED
#include <cstring>
#include <string>
#include <vector> #include <vector>
#include <sstream>
#include "values.hpp"
#include <iostream>
namespace Sass { namespace Sass {
using std::string; using namespace std;
using std::vector;
using std::stringstream; struct Token {
using std::cerr; using std::endl; 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,
struct Node {
enum Type {
root, root,
ruleset, ruleset,
propset, propset,
...@@ -38,10 +95,10 @@ namespace Sass { ...@@ -38,10 +95,10 @@ namespace Sass {
nil, nil,
comma_list, comma_list,
space_list, space_list,
disjunction, disjunction,
conjunction, conjunction,
relation, relation,
eq, eq,
neq, neq,
...@@ -71,16 +128,16 @@ namespace Sass { ...@@ -71,16 +128,16 @@ namespace Sass {
textual_hex, textual_hex,
color_name, color_name,
string_constant, string_constant,
number,
numeric_percentage, numeric_percentage,
numeric_dimension, numeric_dimension,
number,
numeric_color, numeric_color,
boolean, boolean,
important, important,
value_schema, value_schema,
string_schema, string_schema,
css_import, css_import,
function_call, function_call,
mixin, mixin,
...@@ -89,194 +146,213 @@ namespace Sass { ...@@ -89,194 +146,213 @@ namespace Sass {
arguments, arguments,
variable, variable,
assignment, assignment
comment,
none,
flags
}; };
Type type; Node(Node_Impl* ip = 0);
unsigned int line_number;
bool has_children; Type type() const;
bool has_statements;
bool has_blocks;
bool has_expansions;
bool has_backref;
bool from_variable;
bool eval_me;
bool unquoted;
union { bool has_children() const;
Token token; bool has_statements() const;
mutable vector<Node>* children; bool has_blocks() const;
Dimension dimension; bool has_expansions() const;
double numeric_value; bool has_backref() const;
bool boolean_value; bool from_variable() const;
} content; bool& should_eval() const;
bool& is_unquoted() const;
const char* file_name; bool is_numeric() const;
static size_t allocations; string& path() const;
static size_t destructed; size_t line() const;
size_t size() const;
void clear() bool empty() const;
{
type = none; line_number = 0; file_name = 0; Node& at(size_t i) const;
has_children = false; has_statements = false; has_blocks = false; Node& back() const;
has_expansions = false; has_backref = false; from_variable = false; Node& operator[](size_t i) const;
eval_me = false; unquoted = false; void pop_back();
} Node& push_back(Node n);
Node& push_front(Node n);
size_t size() const Node& operator<<(Node n);
{ return content.children->size(); } Node& operator+=(Node n);
Node& operator[](const size_t i) const vector<Node>::iterator begin();
{ return content.children->at(i); } vector<Node>::iterator end();
void insert(vector<Node>::iterator position,
Node& at(const size_t i) const vector<Node>::iterator first,
{ return content.children->at(i); } vector<Node>::iterator last);
Node& operator<<(const Node& n) bool boolean_value() const;
{ double numeric_value() const;
content.children->push_back(n); Token token() const;
return *this; Token unit() const;
}
void flatten();
bool is_numeric() const
{
switch (type)
{
case number:
case numeric_percentage:
case numeric_dimension:
return true;
break;
default:
return false;
}
}
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?
}
return 0;
}
void set_numeric_value(double v)
{
switch (type)
{
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)
{
for (size_t i = 0; i < n.size(); ++i) {
content.children->push_back(n[i]);
}
return *this;
}
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;
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 string& prefix) const; string to_string(const string& prefix) const;
void echo(stringstream& buf, size_t depth = 0);
void emit_nested_css(stringstream& buf, void emit_nested_css(stringstream& buf,
size_t depth, size_t depth,
const vector<string>& prefixes); const vector<string>& prefixes);
void emit_nested_css(stringstream& buf, size_t depth); void emit_nested_css(stringstream& buf, size_t depth);
void emit_propset(stringstream& buf, size_t depth, const string& prefix); 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); void emit_expanded_css(stringstream& buf, const string& prefix);
};
struct Node_Impl {
union {
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;
bool has_blocks;
bool has_expansions;
bool has_backref;
bool from_variable;
bool should_eval;
bool is_unquoted;
Node clone(vector<vector<Node>*>& registry) const; bool is_numeric()
void flatten(); { return type >= Node::number && type <= Node::numeric_dimension; }
size_t size()
{ return children.size(); }
Node() bool empty()
{ clear(); } { return children.empty(); }
Node& at(size_t i)
{ return children.at(i); }
Node(Type t) // flags or booleans Node& back()
{ clear(); type = t; } { return children.back(); }
Node(Type t, vector<vector<Node>*>& registry, unsigned int ln, size_t s = 0) // nodes with children void push_back(const Node& n)
{ {
clear(); children.push_back(n);
type = t;
line_number = ln;
content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(s);
has_children = true; has_children = true;
++allocations; switch (n.type())
} {
case Node::comment:
Node(Type t, unsigned int ln, const Token& tok) // nodes with a single token case Node::css_import:
{ case Node::rule:
clear(); case Node::propset: has_statements = true; break;
type = t; case Node::ruleset: has_blocks = true; break;
line_number = ln; case Node::expansion: has_expansions = true; break;
content.token = tok; case Node::backref: has_backref = true; break;
} default: break;
}
Node(unsigned int ln, double val) // numeric values if (n.has_backref()) has_backref = true;
{
clear();
type = number;
line_number = ln;
content.numeric_value = val;
}
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;
} }
Node(vector<vector<Node>*>& registry, unsigned int ln, double r, double g, double b, double a = 1.0) // colors void push_front(const Node& n)
{ {
clear(); children.insert(children.begin(), n);
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; has_children = true;
++allocations; switch (n.type())
{
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;
}
if (n.has_backref()) has_backref = true;
} }
void pop_back()
{ children.pop_back(); }
bool& boolean_value()
{ return value.boolean; }
~Node() { ++destructed; } double numeric_value();
Token unit();
}; };
// ------------------------------------------------------------------------
// 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)
{
ip_->push_back(n);
return *this;
}
inline Node& Node::push_front(Node n)
{
ip_->push_front(n);
return *this;
}
inline Node& Node::operator<<(Node n) { return push_back(n); }
inline Node& Node::operator+=(Node n)
{
for (size_t i = 0, L = n.size(); i < L; ++i) push_back(n[i]);
return *this;
}
inline vector<Node>::iterator Node::begin()
{ return ip_->children.begin(); }
inline vector<Node>::iterator Node::end()
{ 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); }
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(); }
} }
#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 string& prefix) 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;
if (!has_backref() && !prefix.empty()) {
result += prefix;
result += ' ';
}
result += at(0).to_string(at(0).has_backref() ? prefix : "");
for (size_t i = 1, S = size(); i < S; ++i) {
result += " ";
result += at(i).to_string(at(i).has_backref() ? prefix : "");
}
return result;
} break;
case selector_combinator: {
string result(prefix.empty() ? "" : prefix + " ");
result += token().to_string();
return result;
} break;
case simple_selector_sequence: {
string result;
if (!has_backref() && !prefix.empty()) {
result += prefix;
result += " ";
}
for (size_t i = 0, S = size(); i < S; ++i) {
Node::Type t = at(i).type();
result += at(i).to_string(t == backref ? prefix : "");
}
return result;
} break;
case pseudo:
case simple_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += token().to_string();
return result;
} break;
case pseudo_negation: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
result += at(1).to_string("");
result += ')';
return result;
} break;
case functional_pseudo: {
string result(prefix);
if (!prefix.empty()) 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(prefix);
if (!prefix.empty()) result += " ";
result += "[";
for (size_t i = 0, S = size(); i < S; ++i)
{ result += at(i).to_string(prefix); }
result += ']';
return result;
} break;
case backref: {
return prefix;
} break;
case comma_list: {
string result(at(0).to_string(prefix));
for (size_t i = 1, S = size(); i < S; ++i) {
if (at(i).type() == nil) continue;
result += ", ";
result += at(i).to_string(prefix);
}
return result;
} break;
case space_list: {
string result(at(0).to_string(prefix));
for (size_t i = 1, S = size(); i < S; ++i) {
if (at(i).type() == nil) continue;
result += " ";
result += at(i).to_string(prefix);
}
return result;
} break;
case expression:
case term: {
string result(at(0).to_string(prefix));
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(prefix);
}
}
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,
const vector<string>& prefixes)
{
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, prefixes);
}
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("") : sel_group.to_string(""));
}
}
else {
size_t PS = prefixes.size();
new_prefixes.reserve(PS * sel_group_size);
for (size_t i = 0; i < PS; ++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]));
}
}
}
if (block.has_expansions()) block.flatten();
if (block.has_statements()) {
buf << string(2*depth, ' ') << new_prefixes[0];
for (size_t i = 1, S = new_prefixes.size(); i < S; ++i) {
buf << ", " << new_prefixes[i];
}
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); // USE OVERLOADED VERSION FOR COMMENTS AND RULES
}
}
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, new_prefixes);
}
}
}
if (block.has_statements()) --depth; // see previous comment
if (depth == 0 && prefixes.empty()) buf << endl;
} break;
default:
emit_nested_css(buf, depth); // pass it along to the simpler version
break;
}
}
void Node::emit_nested_css(stringstream& buf, size_t depth)
{
switch (type())
{
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
#include <iostream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include "node.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
namespace Sass {
size_t Node::allocations = 0;
size_t Node::destructed = 0;
Node Node::clone(vector<vector<Node>*>& registry) const
{
Node n(*this);
if (has_children) {
n.content.children = new vector<Node>;
++allocations;
n.content.children->reserve(size());
for (size_t i = 0; i < size(); ++i) {
n << at(i).clone(registry);
}
registry.push_back(n.content.children);
}
return n;
}
string Node::to_string(const string& prefix) const
{
switch (type)
{
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("");
}
return result;
} 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;
} 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);
} break;
case simple_selector_sequence: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += " ";
}
for (size_t i = 0; i < size(); ++i) {
Node::Type t = at(i).type;
result += at(i).to_string(t == backref ? prefix : "");
}
return result;
} break;
case pseudo:
case simple_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += content.token.to_string();
return result;
} break;
case pseudo_negation: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
result += at(1).to_string("");
result += ')';
return result;
} 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;
case backref: {
return prefix;
} break;
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;
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);
}
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);
}
}
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;
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();
}
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 << content.dimension.numeric_value;
ss << '%';
return ss.str();
}
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();
} break;
case number: {
stringstream ss;
ss << content.numeric_value;
return ss.str();
} 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();
}
}
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);
}
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;
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;
case boolean: {
if (content.boolean_value) return "true";
else return "false";
} break;
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;
case string_schema: {
string result;
for (size_t i = 0; i < size(); ++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 && type != flags) return content.token.to_string();
else return "";
} break;
}
}
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);
}
break;
case selector:
for (size_t i = 0; i < size(); ++i) {
at(i).echo(buf, depth);
}
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());
}
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;
}
}
void Node::emit_nested_css(stringstream& buf,
size_t depth,
const vector<string>& prefixes)
{
switch (type)
{
case root:
if (at(0).has_expansions) {
flatten();
}
for (size_t i = 0; i < size(); ++i) {
at(i).emit_nested_css(buf, depth, prefixes);
//if (at(i).type == css_import) buf << endl;
}
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()));
}
}
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]));
}
}
}
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);
}
}
}
if (block[0].has_statements) --depth; // see previous comment
if (depth == 0 && prefixes.empty()) buf << endl;
} break;
default:
emit_nested_css(buf, depth); // pass it along to the simpler version
break;
}
}
void Node::emit_nested_css(stringstream& buf, size_t depth)
{
switch (type)
{
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 << 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;
}
}
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;
}
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;
// }
}
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
//
// }
//
//
//
// }
}
#define SASS_NODE_INCLUDED
#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;
struct Node {
enum Type {
root,
ruleset,
propset,
selector_group,
selector,
selector_combinator,
simple_selector_sequence,
backref,
simple_selector,
type_selector,
class_selector,
id_selector,
pseudo,
pseudo_negation,
functional_pseudo,
attribute_selector,
block,
rule,
property,
nil,
comma_list,
space_list,
disjunction,
conjunction,
relation,
eq,
neq,
gt,
gte,
lt,
lte,
expression,
add,
sub,
term,
mul,
div,
factor,
unary_plus,
unary_minus,
values,
value,
identifier,
uri,
textual_percentage,
textual_dimension,
textual_number,
textual_hex,
color_name,
string_constant,
numeric_percentage,
numeric_dimension,
number,
numeric_color,
boolean,
important,
value_schema,
string_schema,
css_import,
function_call,
mixin,
parameters,
expansion,
arguments,
variable,
assignment,
comment,
none,
flags
};
Type type;
unsigned int line_number;
bool has_children;
bool has_statements;
bool has_blocks;
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()
{
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); }
Node& at(const size_t i) const
{ return content.children->at(i); }
Node& operator<<(const Node& n)
{
content.children->push_back(n);
return *this;
}
bool is_numeric() const
{
switch (type)
{
case number:
case numeric_percentage:
case numeric_dimension:
return true;
break;
default:
return false;
}
}
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?
}
}
void set_numeric_value(double v)
{
switch (type)
{
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)
{
for (size_t i = 0; i < n.size(); ++i) {
content.children->push_back(n[i]);
}
return *this;
}
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 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);
Node clone(vector<vector<Node>*>& registry) const;
void flatten();
Node()
{ clear(); }
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
{
clear();
type = t;
line_number = ln;
content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(s);
has_children = true;
++allocations;
}
Node(Type t, unsigned int ln, const Token& tok) // nodes with a single token
{
clear();
type = t;
line_number = ln;
content.token = tok;
}
Node(unsigned int ln, double val) // numeric values
{
clear();
type = number;
line_number = ln;
content.numeric_value = val;
}
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;
}
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;
}
~Node() { ++destructed; }
};
}
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
#include "sass_interface.h" #include "sass_interface.h"
extern "C" { extern "C" {
using namespace std; using namespace std;
sass_context* sass_new_context() sass_context* sass_new_context()
{ return (sass_context*) calloc(1, sizeof(sass_context)); } { return (sass_context*) calloc(1, sizeof(sass_context)); }
...@@ -40,7 +40,7 @@ extern "C" { ...@@ -40,7 +40,7 @@ extern "C" {
using namespace Sass; using namespace Sass;
doc.parse_scss(); doc.parse_scss();
// cerr << "PARSED" << endl; // cerr << "PARSED" << endl;
eval(doc.root, doc.context.global_env, doc.context.function_env, doc.context.registry); eval(doc.root, doc.context.global_env, doc.context.function_env, doc.context.new_Node);
// cerr << "EVALUATED" << endl; // cerr << "EVALUATED" << endl;
string output(doc.emit_css(static_cast<Document::CSS_Style>(style))); string output(doc.emit_css(static_cast<Document::CSS_Style>(style)));
// cerr << "EMITTED" << endl; // cerr << "EMITTED" << endl;
...@@ -49,9 +49,9 @@ extern "C" { ...@@ -49,9 +49,9 @@ extern "C" {
// cerr << "Destructions:\t" << Node::destructed << endl; // cerr << "Destructions:\t" << Node::destructed << endl;
// cerr << "Registry size:\t" << doc.context.registry.size() << endl; // cerr << "Registry size:\t" << doc.context.registry.size() << endl;
for (size_t i = 0; i < doc.context.registry.size(); ++i) { // for (size_t i = 0; i < doc.context.registry.size(); ++i) {
delete doc.context.registry[i]; // delete doc.context.registry[i];
} // }
// cerr << "Deallocations:\t" << i << endl; // cerr << "Deallocations:\t" << i << endl;
char* c_output = (char*) malloc(output.size() + 1); char* c_output = (char*) malloc(output.size() + 1);
...@@ -64,14 +64,15 @@ extern "C" { ...@@ -64,14 +64,15 @@ extern "C" {
using namespace Sass; using namespace Sass;
try { try {
Context cpp_ctx(c_ctx->options.include_paths); 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->output_string = process_document(doc, c_ctx->options.output_style);
c_ctx->error_message = 0; c_ctx->error_message = 0;
c_ctx->error_status = 0; c_ctx->error_status = 0;
} }
catch (Error& e) { catch (Error& e) {
stringstream msg_stream; 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()); string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1); char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str()); strcpy(msg_str, msg.c_str());
...@@ -98,7 +99,8 @@ extern "C" { ...@@ -98,7 +99,8 @@ extern "C" {
using namespace Sass; using namespace Sass;
try { try {
Context cpp_ctx(c_ctx->options.include_paths); 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 << "MADE A DOC AND CONTEXT OBJ" << endl;
// cerr << "REGISTRY: " << doc.context.registry.size() << endl; // cerr << "REGISTRY: " << doc.context.registry.size() << endl;
c_ctx->output_string = process_document(doc, c_ctx->options.output_style); c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
...@@ -107,7 +109,7 @@ extern "C" { ...@@ -107,7 +109,7 @@ extern "C" {
} }
catch (Error& e) { catch (Error& e) {
stringstream msg_stream; 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()); string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1); char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str()); strcpy(msg_str, msg.c_str());
......
...@@ -13,7 +13,7 @@ struct sass_options { ...@@ -13,7 +13,7 @@ struct sass_options {
}; };
struct sass_context { struct sass_context {
char* input_string; char* source_string;
char* output_string; char* output_string;
struct sass_options options; struct sass_options options;
int error_status; int error_status;
......
#include <iostream>
#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;
new_Node.free();
return 0;
}
\ 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