Commit d58dae2b by Hampton Catlin

Merge branch 'master' of github.com:hcatlin/libsass

parents 19f9e84a b1580330
...@@ -104,6 +104,7 @@ div { ...@@ -104,6 +104,7 @@ div {
b: hey or ho or false or hoo; b: hey or ho or false or hoo;
c: false and hey and ho; c: false and hey and ho;
d: true and false and (1/0); d: true and false and (1/0);
e: (()()()()()()()()25()()()()()()()()()()()()()()()()()()()()()()());
/* e: 1 < 3 and 3 < 5; /* e: 1 < 3 and 3 < 5;
f: (1 2 3) and (4 5 6);*/ f: (1 2 3) and (4 5 6);*/
......
...@@ -33,15 +33,16 @@ namespace Sass { ...@@ -33,15 +33,16 @@ namespace Sass {
} }
} }
for (int i = 0; i < include_paths.size(); ++i) { // for (int i = 0; i < include_paths.size(); ++i) {
cerr << include_paths[i] << endl; // cerr << include_paths[i] << endl;
} // }
} }
Context::Context(const char* paths_str) Context::Context(const char* paths_str)
: global_env(Environment()), : global_env(Environment()),
function_env(map<pair<string, size_t>, Function>()), function_env(map<pair<string, size_t>, Function>()),
source_refs(vector<char*>()), source_refs(vector<char*>()),
registry(vector<vector<Node>*>()),
include_paths(vector<string>()), include_paths(vector<string>()),
ref_count(0) ref_count(0)
{ {
...@@ -51,9 +52,11 @@ namespace Sass { ...@@ -51,9 +52,11 @@ namespace Sass {
Context::~Context() Context::~Context()
{ {
for (int i = 0; i < source_refs.size(); ++i) { int i;
for (i = 0; i < source_refs.size(); ++i) {
delete[] source_refs[i]; delete[] source_refs[i];
} }
cerr << "Deallocated " << i << " source string(s)." << endl;
} }
inline void Context::register_function(Function_Descriptor d, Implementation ip) inline void Context::register_function(Function_Descriptor d, Implementation ip)
......
...@@ -45,6 +45,7 @@ namespace Sass { ...@@ -45,6 +45,7 @@ namespace Sass {
Environment global_env; Environment global_env;
map<pair<string, size_t>, Function> function_env; map<pair<string, size_t>, Function> function_env;
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
size_t ref_count; size_t ref_count;
void collect_include_paths(const char* paths_str); void collect_include_paths(const char* paths_str);
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#include <cstring> #include <cstring>
#include "document.hpp" #include "document.hpp"
#include "eval_apply.hpp" #include "eval_apply.hpp"
#include "error.hpp"
#include <iostream> #include <iostream>
namespace Sass { namespace Sass {
...@@ -11,7 +12,7 @@ namespace Sass { ...@@ -11,7 +12,7 @@ namespace Sass {
source(source_str), source(source_str),
line_number(1), line_number(1),
context(ctx), context(ctx),
root(Node(Node::root, 1)), root(Node(Node::root, context.registry, 1)),
lexed(Token::make()) lexed(Token::make())
{ {
if (source_str) { if (source_str) {
...@@ -48,22 +49,24 @@ namespace Sass { ...@@ -48,22 +49,24 @@ namespace Sass {
: path(path), source(source), : path(path), source(source),
line_number(1), own_source(false), line_number(1), own_source(false),
context(*(new Context())), context(*(new Context())),
root(Node(Node::root, 1)), root(Node(Node::root, context.registry, 1)),
lexed(Token::make()) lexed(Token::make())
{ {
if (!source) { if (!source) {
std::FILE *f; std::FILE *f;
// TO DO: CHECK f AGAINST NULL/0
f = std::fopen(path.c_str(), "rb"); f = std::fopen(path.c_str(), "rb");
std::fseek(f, 0, SEEK_END); if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f); int len = std::ftell(f);
if (len < 0) throw path;
std::rewind(f); std::rewind(f);
// TO DO: WRAP THE new[] IN A TRY/CATCH BLOCK // TO DO: CATCH THE POTENTIAL badalloc EXCEPTION
source = new char[len + 1]; source = new char[len + 1];
std::fread(source, sizeof(char), len, f); std::fread(source, sizeof(char), len, f);
if (std::ferror(f)) throw path;
source[len] = '\0'; source[len] = '\0';
end = source + len; end = source + len;
std::fclose(f); if (std::fclose(f)) throw path;
own_source = true; own_source = true;
} }
position = source; position = source;
...@@ -75,35 +78,37 @@ namespace Sass { ...@@ -75,35 +78,37 @@ namespace Sass {
: path(path), source(0), : path(path), source(0),
line_number(1), own_source(false), line_number(1), own_source(false),
context(context), context(context),
root(Node(Node::root, 1)), root(Node(Node::root, context.registry, 1)),
lexed(Token::make()) lexed(Token::make())
{ {
std::FILE *f; std::FILE *f;
// TO DO: CHECK f AGAINST NULL/0
f = std::fopen(path.c_str(), "rb"); f = std::fopen(path.c_str(), "rb");
std::fseek(f, 0, SEEK_END); if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f); int len = std::ftell(f);
if (len < 0) throw path;
std::rewind(f); std::rewind(f);
// TO DO: WRAP THE new[] IN A TRY/CATCH BLOCK // TO DO: CATCH THE POTENTIAL badalloc EXCEPTION
source = new char[len + 1]; source = new char[len + 1];
std::fread(source, sizeof(char), len, f); std::fread(source, sizeof(char), len, f);
if (std::ferror(f)) throw path;
source[len] = '\0'; source[len] = '\0';
end = source + len; end = source + len;
std::fclose(f); if (std::fclose(f)) throw path;
position = source; position = source;
context.source_refs.push_back(source); context.source_refs.push_back(source);
++context.ref_count; ++context.ref_count;
} }
Document::Document(size_t line_number, Token t, Context& context) Document::Document(const string& path, size_t line_number, Token t, Context& context)
: path(string()), : path(path),
source(const_cast<char*>(t.begin)), source(const_cast<char*>(t.begin)),
position(t.begin), position(t.begin),
end(t.end), end(t.end),
line_number(line_number), line_number(line_number),
own_source(false), own_source(false),
context(context), context(context),
root(Node(Node::root, 1)), root(Node(Node::root, context.registry, 1)),
lexed(Token::make()) lexed(Token::make())
{ } { }
...@@ -112,12 +117,11 @@ namespace Sass { ...@@ -112,12 +117,11 @@ namespace Sass {
// if (context.ref_count == 0) delete &context; // if (context.ref_count == 0) delete &context;
} }
// void Document::eval_pending() void Document::syntax_error(string message, size_t ln)
// { { throw Error(Error::syntax, ln ? ln : line_number, path, message); }
// for (int i = 0; i < context.pending.size(); ++i) {
// eval(context.pending[i], context.global_env); void Document::read_error(string message, size_t ln)
// } { throw Error(Error::read, ln ? ln : line_number, path, message); }
// }
using std::string; using std::string;
using std::stringstream; using std::stringstream;
......
...@@ -30,7 +30,7 @@ namespace Sass { ...@@ -30,7 +30,7 @@ namespace Sass {
Document(char* path_str, char* source_str, Context& ctx); Document(char* path_str, char* source_str, Context& ctx);
Document(string path, char* source = 0); Document(string path, char* source = 0);
Document(string path, Context& context); Document(string path, Context& context);
Document(size_t line_number, Token t, Context& context); Document(const string& path, size_t line_number, Token t, Context& context);
~Document(); ~Document();
template <prelexer mx> template <prelexer mx>
...@@ -142,6 +142,7 @@ namespace Sass { ...@@ -142,6 +142,7 @@ namespace Sass {
Node parse_identifier(); Node parse_identifier();
Node parse_variable(); Node parse_variable();
Node parse_function_call(); Node parse_function_call();
Node parse_value_schema();
const char* look_for_rule(const char* start = 0); const char* look_for_rule(const char* start = 0);
const char* look_for_values(const char* start = 0); const char* look_for_values(const char* start = 0);
...@@ -153,7 +154,9 @@ namespace Sass { ...@@ -153,7 +154,9 @@ namespace Sass {
const char* look_for_pseudo(const char* start = 0); const char* look_for_pseudo(const char* start = 0);
const char* look_for_attrib(const char* start = 0); const char* look_for_attrib(const char* start = 0);
void eval_pending(); 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);
}; };
......
#include "document.hpp" #include "document.hpp"
#include "error.hpp"
#include <iostream> #include <iostream>
namespace Sass { namespace Sass {
...@@ -15,7 +16,7 @@ namespace Sass { ...@@ -15,7 +16,7 @@ namespace Sass {
else if (peek< import >(position)) { else if (peek< import >(position)) {
// TO DO: don't splice in place at parse-time -- use an expansion node // TO DO: don't splice in place at parse-time -- use an expansion node
root += parse_import(); root += parse_import();
lex< exactly<';'> >(); if (!lex< exactly<';'> >()) syntax_error("top-level @import directive must be terminated by ';'");
} }
else if (peek< mixin >(position)) { else if (peek< mixin >(position)) {
root << parse_mixin_definition(); root << parse_mixin_definition();
...@@ -23,11 +24,11 @@ namespace Sass { ...@@ -23,11 +24,11 @@ namespace Sass {
else if (peek< include >(position)) { else if (peek< include >(position)) {
root << parse_mixin_call(); root << parse_mixin_call();
root[0].has_expansions = true; root[0].has_expansions = true;
lex< exactly<';'> >(); if (!lex< exactly<';'> >()) syntax_error("top-level @include directive must be terminated by ';'");
} }
else if (peek< variable >(position)) { else if (peek< variable >(position)) {
root << parse_assignment(); root << parse_assignment();
lex< exactly<';'> >(); if (!lex< exactly<';'> >()) syntax_error("top-level variable binding must be terminated by ';'");
} }
else { else {
root << parse_ruleset(); root << parse_ruleset();
...@@ -39,39 +40,50 @@ namespace Sass { ...@@ -39,39 +40,50 @@ namespace Sass {
Node Document::parse_import() Node Document::parse_import()
{ {
lex< import >(); lex< import >();
lex< string_constant >(); if (!lex< string_constant >()) syntax_error("@import directive requires a quoted path");
// TO DO: BETTER PATH HANDLING
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);
Document importee(current_path + import_path, context); try {
importee.parse_scss(); Document importee(current_path + import_path, context);
return importee.root; importee.parse_scss();
return importee.root;
}
catch (string& path) {
read_error("error reading file \"" + path + "\"");
}
} }
Node Document::parse_mixin_definition() Node Document::parse_mixin_definition()
{ {
lex< mixin >(); lex< mixin >();
lex< identifier >(); if (!lex< identifier >()) syntax_error("invalid name in @mixin directive");
Node name(Node::identifier, line_number, lexed); Node name(Node::identifier, line_number, 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 '{'");
Node body(parse_block(true)); Node body(parse_block(true));
Node mixin(Node::mixin, line_number, 3); Node mixin(Node::mixin, context.registry, line_number, 3);
mixin << name << params << body; mixin << name << params << body;
return mixin; return mixin;
} }
Node Document::parse_mixin_parameters() Node Document::parse_mixin_parameters()
{ {
Node params(Node::parameters, line_number); Node params(Node::parameters, context.registry, line_number);
lex< exactly<'('> >(); Token name(lexed);
if (peek< variable >()) { if (lex< exactly<'('> >()) {
params << parse_parameter(); if (peek< variable >()) {
while (lex< exactly<','> >()) {
params << parse_parameter(); params << parse_parameter();
while (lex< exactly<','> >()) {
if (!peek< variable >()) syntax_error("expected a variable name (e.g. $x) for the parameter list for " + name.to_string());
params << parse_parameter();
}
if (!lex< exactly<')'> >()) 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());
} }
lex< exactly<')'> >();
return params; return params;
} }
...@@ -80,7 +92,7 @@ namespace Sass { ...@@ -80,7 +92,7 @@ namespace Sass {
Node var(Node::variable, line_number, lexed); Node var(Node::variable, line_number, 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, line_number, 2); Node par_and_val(Node::assignment, context.registry, line_number, 2);
par_and_val << var << val; par_and_val << var << val;
return par_and_val; return par_and_val;
} }
...@@ -92,17 +104,18 @@ namespace Sass { ...@@ -92,17 +104,18 @@ namespace Sass {
Node Document::parse_mixin_call() Node Document::parse_mixin_call()
{ {
lex< include >(); lex< include >();
lex< identifier >(); if (!lex< identifier >()) syntax_error("invalid name in @include directive");
Node name(Node::identifier, line_number, lexed); Node name(Node::identifier, line_number, lexed);
Node args(parse_arguments()); Node args(parse_arguments());
Node call(Node::expansion, line_number, 3); Node call(Node::expansion, context.registry, line_number, 3);
call << name << args; call << name << args;
return call; return call;
} }
Node Document::parse_arguments() Node Document::parse_arguments()
{ {
Node args(Node::arguments, line_number); Token name(lexed);
Node args(Node::arguments, context.registry, line_number);
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) { if (!peek< exactly<')'> >(position)) {
args << parse_argument(); args << parse_argument();
...@@ -112,7 +125,7 @@ namespace Sass { ...@@ -112,7 +125,7 @@ namespace Sass {
args.content.children->back().eval_me = true; args.content.children->back().eval_me = true;
} }
} }
lex< exactly<')'> >(); if (!lex< exactly<')'> >()) syntax_error("improperly terminated argument list for " + name.to_string());
} }
return args; return args;
} }
...@@ -124,7 +137,7 @@ namespace Sass { ...@@ -124,7 +137,7 @@ namespace Sass {
Node var(Node::variable, line_number, lexed); Node var(Node::variable, line_number, lexed);
lex< exactly<':'> >(); lex< exactly<':'> >();
Node val(parse_space_list()); Node val(parse_space_list());
Node assn(Node::assignment, line_number, 2); Node assn(Node::assignment, context.registry, line_number, 2);
assn << var << val; assn << var << val;
return assn; return assn;
} }
...@@ -137,20 +150,20 @@ namespace Sass { ...@@ -137,20 +150,20 @@ namespace Sass {
{ {
lex< variable >(); lex< variable >();
Node var(Node::variable, line_number, lexed); Node var(Node::variable, line_number, lexed);
lex< exactly<':'> >(); if (!lex< exactly<':'> >()) syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement");
Node val(parse_list()); Node val(parse_list());
Node assn(Node::assignment, line_number, 2); Node assn(Node::assignment, context.registry, line_number, 2);
assn << var << val; assn << var << val;
return assn; return assn;
} }
Node Document::parse_ruleset(bool definition) Node Document::parse_ruleset(bool definition)
{ {
Node ruleset(Node::ruleset, line_number, 2); Node ruleset(Node::ruleset, context.registry, line_number, 2);
ruleset << parse_selector_group(); ruleset << parse_selector_group();
// if (ruleset[0].type == Node::selector) cerr << "ruleset starts with selector" << endl; // if (ruleset[0].type == Node::selector) cerr << "ruleset starts with selector" << endl;
// if (ruleset[0].type == Node::selector_group) cerr << "ruleset starts with selector_group" << endl; // if (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;
} }
...@@ -165,7 +178,7 @@ namespace Sass { ...@@ -165,7 +178,7 @@ namespace Sass {
Node sel1(parse_selector()); Node sel1(parse_selector());
if (!peek< exactly<','> >()) return sel1; if (!peek< exactly<','> >()) return sel1;
Node group(Node::selector_group, line_number, 2); Node group(Node::selector_group, context.registry, line_number, 2);
group << sel1; group << sel1;
while (lex< exactly<','> >()) group << parse_selector(); while (lex< exactly<','> >()) group << parse_selector();
return group; return group;
...@@ -199,7 +212,7 @@ namespace Sass { ...@@ -199,7 +212,7 @@ namespace Sass {
peek< exactly<')'> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >()) return seq1; peek< exactly<'{'> >()) return seq1;
Node selector(Node::selector, line_number, 2); Node selector(Node::selector, context.registry, line_number, 2);
if (seq1.has_backref) selector.has_backref = true; if (seq1.has_backref) selector.has_backref = true;
selector << seq1; selector << seq1;
...@@ -242,7 +255,7 @@ namespace Sass { ...@@ -242,7 +255,7 @@ namespace Sass {
{ return simp1; } { return simp1; }
// now we know we have a sequence of simple selectors // now we know we have a sequence of simple selectors
Node seq(Node::simple_selector_sequence, line_number, 2); Node seq(Node::simple_selector_sequence, context.registry, line_number, 2);
seq << simp1; seq << simp1;
seq.has_backref = saw_backref; seq.has_backref = saw_backref;
...@@ -303,19 +316,23 @@ namespace Sass { ...@@ -303,19 +316,23 @@ namespace Sass {
else if (peek< exactly<'['> >(position)) { else if (peek< exactly<'['> >(position)) {
return parse_attribute_selector(); return parse_attribute_selector();
} }
else {
syntax_error("invalid selector after " + lexed.to_string());
}
} }
Node Document::parse_pseudo() { Node Document::parse_pseudo() {
if (lex< pseudo_not >()) { if (lex< pseudo_not >()) {
Node ps_not(Node::pseudo_negation, line_number, 2); Node ps_not(Node::pseudo_negation, context.registry, line_number, 2);
ps_not << Node(Node::value, line_number, lexed); ps_not << Node(Node::value, line_number, 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, line_number, 2); Node pseudo(Node::functional_pseudo, context.registry, line_number, 2);
pseudo << Node(Node::value, line_number, lexed); Token name(lexed);
pseudo << Node(Node::value, line_number, name);
if (lex< alternatives< even, odd > >()) { if (lex< alternatives< even, odd > >()) {
pseudo << Node(Node::value, line_number, lexed); pseudo << Node(Node::value, line_number, lexed);
} }
...@@ -337,26 +354,35 @@ namespace Sass { ...@@ -337,26 +354,35 @@ namespace Sass {
else if (lex< sequence< optional<sign>, digits > >()) { else if (lex< sequence< optional<sign>, digits > >()) {
pseudo << Node(Node::value, line_number, lexed); pseudo << Node(Node::value, line_number, lexed);
} }
lex< exactly<')'> >(); else {
syntax_error("invalid argument to " + name.to_string() + "...)");
}
if (!lex< exactly<')'> >()) 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 Node(Node::pseudo, line_number, lexed);
} }
else {
syntax_error("unrecognized pseudo-class or pseudo-element");
}
} }
Node Document::parse_attribute_selector() Node Document::parse_attribute_selector()
{ {
Node attr_sel(Node::attribute_selector, line_number, 3); Node attr_sel(Node::attribute_selector, context.registry, line_number, 3);
lex< exactly<'['> >(); lex< exactly<'['> >();
lex< type_selector >(); if (!lex< type_selector >()) syntax_error("invalid attribute name in attribute selector");
Token name(lexed);
attr_sel << Node(Node::value, line_number, lexed); attr_sel << Node(Node::value, line_number, lexed);
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());
}
attr_sel << Node(Node::value, line_number, lexed); attr_sel << Node(Node::value, line_number, lexed);
lex< string_constant >(); if (!lex< string_constant >()) syntax_error("expected a quoted string constant in attribute selector for " + name.to_string());
attr_sel << Node(Node::value, line_number, lexed); attr_sel << Node(Node::value, line_number, lexed);
lex< exactly<']'> >(); if (!lex< exactly<']'> >()) syntax_error("unterminated attribute selector for " + name.to_string());
return attr_sel; return attr_sel;
} }
...@@ -364,21 +390,28 @@ namespace Sass { ...@@ -364,21 +390,28 @@ namespace Sass {
{ {
lex< exactly<'{'> >(); lex< exactly<'{'> >();
bool semicolon = false; bool semicolon = false;
Node block(Node::block, line_number, 1); Node block(Node::block, context.registry, line_number, 1);
block << Node(Node::flags); block << Node(Node::flags);
while (!lex< exactly<'}'> >()) { while (!lex< exactly<'}'> >()) {
if (semicolon) { if (semicolon) {
lex< exactly<';'> >(); // enforce terminal ';' here if (!lex< exactly<';'> >()) syntax_error("non-terminal statement or declaration must end with ';'");
semicolon = false; semicolon = false;
while (lex< block_comment >()) {
block << Node(Node::comment, line_number, 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 << Node(Node::comment, line_number, lexed);
block[0].has_statements = true; block[0].has_statements = true;
semicolon = true; //semicolon = true;
} }
else if (peek< import >(position)) { else if (peek< import >(position)) {
// TO DO: disallow imports inside of definitions if (definition) {
lex< import >(); // to adjust the line number
syntax_error("@import directive not allowed inside mixin definition");
}
Node imported_tree(parse_import()); Node imported_tree(parse_import());
for (int i = 0; i < imported_tree.size(); ++i) { for (int i = 0; i < imported_tree.size(); ++i) {
if (imported_tree[i].type == Node::comment || if (imported_tree[i].type == Node::comment ||
...@@ -395,6 +428,7 @@ namespace Sass { ...@@ -395,6 +428,7 @@ 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; block[0].has_expansions = true;
semicolon = true;
} }
else if (lex< variable >()) { else if (lex< variable >()) {
block << parse_assignment(); block << parse_assignment();
...@@ -417,7 +451,7 @@ namespace Sass { ...@@ -417,7 +451,7 @@ namespace Sass {
block << parse_rule(); block << parse_rule();
block[0].has_statements = true; block[0].has_statements = true;
semicolon = true; semicolon = true;
lex< exactly<';'> >(); // TO DO: clean up the semicolon handling stuff //lex< exactly<';'> >(); // TO DO: clean up the semicolon handling stuff
} }
else lex< exactly<';'> >(); else lex< exactly<';'> >();
while (lex< block_comment >()) { while (lex< block_comment >()) {
...@@ -429,11 +463,10 @@ namespace Sass { ...@@ -429,11 +463,10 @@ namespace Sass {
} }
Node Document::parse_rule() { Node Document::parse_rule() {
Node rule(Node::rule, line_number, 2); Node rule(Node::rule, context.registry, line_number, 2);
lex< identifier >(); if (!lex< identifier >()) syntax_error("invalid property name");
rule << Node(Node::property, line_number, lexed); rule << Node(Node::property, line_number, lexed);
lex< exactly<':'> >(); if (!lex< exactly<':'> >()) syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'");
// rule << parse_values();
rule << parse_list(); rule << parse_list();
return rule; return rule;
} }
...@@ -447,14 +480,14 @@ namespace Sass { ...@@ -447,14 +480,14 @@ namespace Sass {
{ {
if (peek< exactly<';'> >(position) || if (peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) || peek< exactly<'}'> >(position) ||
peek< exactly<')'> >(position)) { peek< exactly<'{'> >(position) ||
return Node(Node::nil, line_number); // TO DO: maybe use Node::none? peek< exactly<')'> >(position))
} { return Node(Node::nil, context.registry, line_number); }
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, line_number, 2); Node comma_list(Node::comma_list, context.registry, line_number, 2);
comma_list << list1; comma_list << list1;
comma_list.eval_me |= list1.eval_me; comma_list.eval_me |= list1.eval_me;
...@@ -474,16 +507,18 @@ namespace Sass { ...@@ -474,16 +507,18 @@ 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< exactly<';'> >(position) || if (peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) || peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) || peek< exactly<')'> >(position) ||
peek< exactly<','> >(position)) peek< exactly<','> >(position))
{ return disj1; } { return disj1; }
Node space_list(Node::space_list, line_number, 2); Node space_list(Node::space_list, context.registry, line_number, 2);
space_list << disj1; space_list << disj1;
space_list.eval_me |= disj1.eval_me; space_list.eval_me |= disj1.eval_me;
while (!(peek< exactly<';'> >(position) || while (!(peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) || peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) || peek< exactly<')'> >(position) ||
peek< exactly<','> >(position))) peek< exactly<','> >(position)))
{ {
...@@ -501,7 +536,7 @@ namespace Sass { ...@@ -501,7 +536,7 @@ 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, line_number, 2); Node disjunction(Node::disjunction, context.registry, line_number, 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.eval_me = true;
...@@ -515,7 +550,7 @@ namespace Sass { ...@@ -515,7 +550,7 @@ 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, line_number, 2); Node conjunction(Node::conjunction, context.registry, line_number, 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.eval_me = true;
...@@ -534,7 +569,7 @@ namespace Sass { ...@@ -534,7 +569,7 @@ namespace Sass {
peek< lte_op >(position))) peek< lte_op >(position)))
{ return expr1; } { return expr1; }
Node relation(Node::relation, line_number, 3); Node relation(Node::relation, context.registry, line_number, 3);
expr1.eval_me = true; expr1.eval_me = true;
relation << expr1; relation << expr1;
...@@ -561,7 +596,7 @@ namespace Sass { ...@@ -561,7 +596,7 @@ namespace Sass {
peek< sequence< negate< number >, exactly<'-'> > >(position))) peek< sequence< negate< number >, exactly<'-'> > >(position)))
{ return term1; } { return term1; }
Node expression(Node::expression, line_number, 3); Node expression(Node::expression, context.registry, line_number, 3);
term1.eval_me = true; term1.eval_me = true;
expression << term1; expression << term1;
...@@ -589,7 +624,7 @@ namespace Sass { ...@@ -589,7 +624,7 @@ namespace Sass {
peek< exactly<'/'> >(position))) peek< exactly<'/'> >(position)))
{ return fact1; } { return fact1; }
Node term(Node::term, line_number, 3); Node term(Node::term, context.registry, line_number, 3);
term << fact1; term << fact1;
if (fact1.eval_me) term.eval_me = true; if (fact1.eval_me) term.eval_me = true;
...@@ -617,7 +652,7 @@ namespace Sass { ...@@ -617,7 +652,7 @@ namespace Sass {
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].eval_me = true;
} }
lex< exactly<')'> >(); if (!lex< exactly<')'> >()) syntax_error("unclosed parenthesis");
return value; return value;
} }
else { else {
...@@ -635,7 +670,14 @@ namespace Sass { ...@@ -635,7 +670,14 @@ namespace Sass {
return result; return result;
} }
if (lex< true_kwd >()) if (peek< value_schema >())
{
// TO DO: handle value schemas!
return Node(Node::identifier, line_number, lexed);
return parse_value_schema();
}
if (lex< sequence< true_kwd, negate< identifier > > >())
{ {
Node T(Node::boolean); Node T(Node::boolean);
T.line_number = line_number; T.line_number = line_number;
...@@ -643,7 +685,7 @@ namespace Sass { ...@@ -643,7 +685,7 @@ namespace Sass {
return T; return T;
} }
if (lex< false_kwd >()) if (lex< sequence< false_kwd, negate< identifier > > >())
{ {
Node F(Node::boolean); Node F(Node::boolean);
F.line_number = line_number; F.line_number = line_number;
...@@ -681,6 +723,75 @@ namespace Sass { ...@@ -681,6 +723,75 @@ namespace Sass {
var.eval_me = true; var.eval_me = true;
return var; return var;
} }
if (lex< interpolant >())
{
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Document interp(path, line_number, insides, context);
return interp.parse_list();
}
syntax_error("error reading values after " + lexed.to_string());
}
Node Document::parse_value_schema()
{
Node schema;
// just an interpolant
if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Document interp_doc(path, line_number, insides, context);
Node interp_node(interp_doc.parse_list());
if (peek< alternatives< spaces, comment,
exactly<';'>, exactly<','>,
exactly<'('>, exactly<')'> > >()) {
return interp_node;
}
// interpolant with stuff immediately behind it
else {
schema = Node(Node::value_schema, context.registry, line_number, 2);
schema << interp_node;
}
}
while (true) {
if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Document interp_doc(path, line_number, insides, context);
Node interp_node(interp_doc.parse_list());
schema << interp_node;
}
else if (lex< identifier >()) {
schema << Node(Node::identifier, line_number, lexed);
}
else if (lex< percentage >()) {
schema << Node(Node::textual_percentage, line_number, lexed);
}
else if (lex< dimension >()) {
schema << Node(Node::textual_dimension, line_number, lexed);
}
else if (lex< number >()) {
schema << Node(Node::textual_number, line_number, lexed);
}
else if (lex< hex >()) {
schema << Node(Node::textual_hex, line_number, lexed);
}
else if (lex< string_constant >()) {
schema << Node(Node::string_constant, line_number, lexed);
}
else if (lex< variable >()) {
schema << Node(Node::variable, line_number, lexed);
}
else {
syntax_error("error parsing interpolated value");
}
if (peek< alternatives< spaces, comment,
exactly<';'>, exactly<','>,
exactly<'('>, exactly<')'> > >()) {
break;
}
}
return schema;
} }
Node Document::parse_function_call() Node Document::parse_function_call()
...@@ -688,7 +799,7 @@ namespace Sass { ...@@ -688,7 +799,7 @@ namespace Sass {
lex< identifier >(); lex< identifier >();
Node name(Node::identifier, line_number, lexed); Node name(Node::identifier, line_number, lexed);
Node args(parse_arguments()); Node args(parse_arguments());
Node call(Node::function_call, line_number, 2); Node call(Node::function_call, context.registry, line_number, 2);
call << name << args; call << name << args;
call.eval_me = true; call.eval_me = true;
return call; return call;
......
namespace Sass {
struct Error {
enum Type { read, write, syntax, evaluation };
Type type;
size_t line_number;
string file_name;
string message;
Error(Type type, size_t line_number, string file_name, string message)
: type(type), line_number(line_number), file_name(file_name), message(message)
{ }
};
}
\ No newline at end of file
#include "eval_apply.hpp" #include "eval_apply.hpp"
#include "error.hpp"
#include <iostream> #include <iostream>
#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)
{
string fn;
if (file_name) {
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
}
Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env) Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
{ {
switch (expr.type) switch (expr.type)
{ {
...@@ -17,8 +29,9 @@ namespace Sass { ...@@ -17,8 +29,9 @@ namespace Sass {
case Node::expansion: { case Node::expansion: {
Token name(expr[0].content.token); Token name(expr[0].content.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);
Node mixin(env[name]); Node mixin(env[name]);
Node expansion(apply_mixin(mixin, args, env, f_env)); Node expansion(apply_mixin(mixin, args, env, f_env, registry));
expr.content.children->pop_back(); expr.content.children->pop_back();
expr.content.children->pop_back(); expr.content.children->pop_back();
expr += expansion; expr += expansion;
...@@ -26,13 +39,13 @@ namespace Sass { ...@@ -26,13 +39,13 @@ namespace Sass {
} break; } break;
case Node::ruleset: { case Node::ruleset: {
eval(expr[1], env, f_env); eval(expr[1], env, f_env, registry);
return expr; return expr;
} break; } break;
case Node::root: { case Node::root: {
for (int i = 0; i < expr.size(); ++i) { for (int i = 0; i < expr.size(); ++i) {
eval(expr[i], env, f_env); eval(expr[i], env, f_env, registry);
} }
return expr; return expr;
} break; } break;
...@@ -41,7 +54,7 @@ namespace Sass { ...@@ -41,7 +54,7 @@ namespace Sass {
Environment current; Environment current;
current.link(env); current.link(env);
for (int i = 0; i < expr.size(); ++i) { for (int i = 0; i < expr.size(); ++i) {
eval(expr[i], current, f_env); eval(expr[i], current, f_env, registry);
} }
return expr; return expr;
} break; } break;
...@@ -50,11 +63,11 @@ namespace Sass { ...@@ -50,11 +63,11 @@ namespace Sass {
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 (int i = 0; i < val.size(); ++i) { for (int i = 0; i < val.size(); ++i) {
if (val[i].eval_me) val[i] = eval(val[i], env, f_env); if (val[i].eval_me) val[i] = eval(val[i], env, f_env, registry);
} }
} }
else { else {
val = eval(val, env, f_env); val = eval(val, env, f_env, registry);
} }
Node var(expr[0]); Node var(expr[0]);
if (env.query(var.content.token)) { if (env.query(var.content.token)) {
...@@ -70,18 +83,18 @@ namespace Sass { ...@@ -70,18 +83,18 @@ namespace Sass {
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 (int i = 0; i < rhs.size(); ++i) { for (int i = 0; i < rhs.size(); ++i) {
if (rhs[i].eval_me) rhs[i] = eval(rhs[i], env, f_env); if (rhs[i].eval_me) rhs[i] = eval(rhs[i], env, f_env, registry);
} }
} }
else { else {
if (rhs.eval_me) expr[1] = eval(rhs, env, f_env); if (rhs.eval_me) expr[1] = eval(rhs, env, f_env, registry);
} }
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); if (expr.eval_me) expr[0] = eval(expr[0], env, f_env, registry);
return expr; return expr;
} break; } break;
...@@ -90,7 +103,7 @@ namespace Sass { ...@@ -90,7 +103,7 @@ namespace Sass {
for (int i = 0; i < expr.size(); ++i) { for (int i = 0; i < expr.size(); ++i) {
// if (expr[i].type == Node::relation || // if (expr[i].type == Node::relation ||
// expr[i].type == Node::function_call && expr[0].content.token.to_string() == "not") { // expr[i].type == Node::function_call && expr[0].content.token.to_string() == "not") {
result = eval(expr[i], env, f_env); result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) continue; if (result.type == Node::boolean && result.content.boolean_value == false) continue;
else return result; else return result;
} }
...@@ -100,7 +113,7 @@ namespace Sass { ...@@ -100,7 +113,7 @@ namespace Sass {
case Node::conjunction: { case Node::conjunction: {
Node result; Node result;
for (int i = 0; i < expr.size(); ++i) { for (int i = 0; i < expr.size(); ++i) {
result = eval(expr[i], env, f_env); result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) return result; if (result.type == Node::boolean && result.content.boolean_value == false) return result;
} }
return result; return result;
...@@ -108,9 +121,9 @@ namespace Sass { ...@@ -108,9 +121,9 @@ namespace Sass {
case Node::relation: { case Node::relation: {
Node lhs(eval(expr[0], env, f_env)); Node lhs(eval(expr[0], env, f_env, registry));
Node op(expr[1]); Node op(expr[1]);
Node rhs(eval(expr[2], env, f_env)); Node rhs(eval(expr[2], env, f_env, registry));
Node T(Node::boolean); Node T(Node::boolean);
T.line_number = lhs.line_number; T.line_number = lhs.line_number;
...@@ -129,26 +142,26 @@ namespace Sass { ...@@ -129,26 +142,26 @@ namespace Sass {
} break; } break;
case Node::expression: { case Node::expression: {
Node acc(Node::expression, expr.line_number, 1); Node acc(Node::expression, registry, expr.line_number, 1);
acc << eval(expr[0], env, f_env); acc << eval(expr[0], env, f_env, registry);
Node rhs(eval(expr[2], env, f_env)); Node rhs(eval(expr[2], env, f_env, registry));
accumulate(expr[1].type, acc, rhs); accumulate(expr[1].type, acc, rhs, registry);
for (int i = 3; i < expr.size(); i += 2) { for (int i = 3; i < expr.size(); i += 2) {
Node rhs(eval(expr[i+1], env, f_env)); Node rhs(eval(expr[i+1], env, f_env, registry));
accumulate(expr[i].type, acc, rhs); accumulate(expr[i].type, acc, rhs, registry);
} }
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.eval_me) {
Node acc(Node::expression, expr.line_number, 1); Node acc(Node::expression, registry, expr.line_number, 1);
acc << eval(expr[0], env, f_env); acc << eval(expr[0], env, f_env, registry);
Node rhs(eval(expr[2], env, f_env)); Node rhs(eval(expr[2], env, f_env, registry));
accumulate(expr[1].type, acc, rhs); accumulate(expr[1].type, acc, rhs, registry);
for (int i = 3; i < expr.size(); i += 2) { for (int i = 3; i < expr.size(); i += 2) {
Node rhs(eval(expr[i+1], env, f_env)); Node rhs(eval(expr[i+1], env, f_env, registry));
accumulate(expr[i].type, acc, rhs); accumulate(expr[i].type, acc, rhs, registry);
} }
return acc.size() == 1 ? acc[0] : acc; return acc.size() == 1 ? acc[0] : acc;
} }
...@@ -175,7 +188,7 @@ namespace Sass { ...@@ -175,7 +188,7 @@ namespace Sass {
} break; } break;
case Node::textual_hex: { case Node::textual_hex: {
Node triple(Node::numeric_color, expr.line_number, 4); Node triple(Node::numeric_color, registry, expr.line_number, 4);
Token hext(Token::make(expr.content.token.begin+1, expr.content.token.end)); Token hext(Token::make(expr.content.token.begin+1, expr.content.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) {
...@@ -192,13 +205,26 @@ namespace Sass { ...@@ -192,13 +205,26 @@ namespace Sass {
} 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);
return env[expr.content.token]; return env[expr.content.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
pair<string, size_t> sig(expr[0].content.token.to_string(), expr[1].size()); pair<string, size_t> sig(expr[0].content.token.to_string(), expr[1].size());
return apply_function(f_env[sig], expr[1], env, f_env); if (!f_env.count(sig)) {
stringstream ss;
ss << "no function named " << expr[0].content.token.to_string() << " taking " << expr[1].size() << " arguments has been defined";
eval_error(ss.str(), expr.line_number, expr.file_name);
}
return apply_function(f_env[sig], expr[1], env, f_env, registry);
} break;
case Node::value_schema: {
for (int i = 0; i < expr.size(); ++i) {
expr[i] = eval(expr[i], env, f_env, registry);
}
return expr;
} break; } break;
default: { default: {
...@@ -207,7 +233,7 @@ namespace Sass { ...@@ -207,7 +233,7 @@ namespace Sass {
} }
} }
Node accumulate(Node::Type op, Node& acc, Node& rhs) Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry)
{ {
Node lhs(acc.content.children->back()); Node lhs(acc.content.children->back());
double lnum = lhs.content.numeric_value; double lnum = lhs.content.numeric_value;
...@@ -220,7 +246,6 @@ namespace Sass { ...@@ -220,7 +246,6 @@ namespace Sass {
} }
// 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) {
// TO DO: disallow division
Node result(acc.line_number, operate(op, lnum, rnum), Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))); Node result(acc.line_number, operate(op, lnum, rnum), Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit)));
acc.content.children->pop_back(); acc.content.children->pop_back();
acc.content.children->push_back(result); acc.content.children->push_back(result);
...@@ -243,12 +268,12 @@ namespace Sass { ...@@ -243,12 +268,12 @@ namespace Sass {
// 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) {
// TO DO: check that alphas match
double r = operate(op, lhs.content.numeric_value, rhs[0].content.numeric_value); double r = operate(op, lhs.content.numeric_value, rhs[0].content.numeric_value);
double g = operate(op, lhs.content.numeric_value, rhs[1].content.numeric_value); double g = operate(op, lhs.content.numeric_value, rhs[1].content.numeric_value);
double b = operate(op, lhs.content.numeric_value, rhs[2].content.numeric_value); double b = operate(op, lhs.content.numeric_value, rhs[2].content.numeric_value);
double a = rhs[3].content.numeric_value;
acc.content.children->pop_back(); acc.content.children->pop_back();
acc << Node(acc.line_number, r, g, b); acc << Node(registry, acc.line_number, 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) {
...@@ -267,16 +292,28 @@ namespace Sass { ...@@ -267,16 +292,28 @@ namespace Sass {
double r = operate(op, lhs[0].content.numeric_value, rhs.content.numeric_value); double r = operate(op, lhs[0].content.numeric_value, rhs.content.numeric_value);
double g = operate(op, lhs[1].content.numeric_value, rhs.content.numeric_value); double g = operate(op, lhs[1].content.numeric_value, rhs.content.numeric_value);
double b = operate(op, lhs[2].content.numeric_value, rhs.content.numeric_value); double b = operate(op, lhs[2].content.numeric_value, rhs.content.numeric_value);
double a = lhs[3].content.numeric_value;
acc.content.children->pop_back(); acc.content.children->pop_back();
acc << Node(acc.line_number, r, g, b); acc << Node(registry, acc.line_number, 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);
double r = operate(op, lhs[0].content.numeric_value, rhs[0].content.numeric_value); double r = operate(op, lhs[0].content.numeric_value, rhs[0].content.numeric_value);
double g = operate(op, lhs[1].content.numeric_value, rhs[1].content.numeric_value); double g = operate(op, lhs[1].content.numeric_value, rhs[1].content.numeric_value);
double b = operate(op, lhs[2].content.numeric_value, rhs[2].content.numeric_value); double b = operate(op, lhs[2].content.numeric_value, rhs[2].content.numeric_value);
double a = lhs[3].content.numeric_value;
acc.content.children->pop_back(); acc.content.children->pop_back();
acc << Node(acc.line_number, r, g, b); acc << Node(registry, acc.line_number, r, g, b, a);
} }
// else if (lhs.type == Node::concatenation) {
// lhs << rhs;
// }
// else if (lhs.type == Node::string_constant || rhs.type == Node::string_constant) {
// acc.content.children->pop_back();
// Node cat(Node::concatenation, lhs.line_number, 2);
// cat << lhs << rhs;
// acc << cat;
// }
else { else {
// TO DO: disallow division and multiplication on lists // TO DO: disallow division and multiplication on lists
acc.content.children->push_back(rhs); acc.content.children->push_back(rhs);
...@@ -287,7 +324,6 @@ namespace Sass { ...@@ -287,7 +324,6 @@ namespace Sass {
double operate(Node::Type op, double lhs, double rhs) double operate(Node::Type op, double lhs, double rhs)
{ {
// TO DO: check for division by zero
switch (op) switch (op)
{ {
case Node::add: return lhs + rhs; break; case Node::add: return lhs + rhs; break;
...@@ -298,25 +334,41 @@ namespace Sass { ...@@ -298,25 +334,41 @@ namespace Sass {
} }
} }
Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env) Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
{ {
Node params(mixin[1]); Node params(mixin[1]);
Node body(mixin[2].clone()); Node body(mixin[2].clone(registry));
Environment bindings; Environment bindings;
// bind arguments // bind arguments
for (int i = 0, j = 0; i < args.size(); ++i) { for (int i = 0, j = 0; i < args.size(); ++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].content.token);
// check that the keyword arg actually names a formal parameter
bool valid_param = false;
for (int k = 0; k < params.size(); ++k) {
Node param_k = params[k];
if (param_k.type == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
}
}
if (!valid_param) eval_error("mixin " + mixin[0].to_string("") + " has no parameter named " + name.to_string(), arg.line_number, arg.file_name);
if (!bindings.query(name)) { if (!bindings.query(name)) {
bindings[name] = eval(arg[1], env, f_env); bindings[name] = eval(arg[1], env, f_env, registry);
} }
} }
else { else {
// TO DO: ensure (j < params.size()) // ensure that the number of ordinal args < params.size()
if (j >= params.size()) {
stringstream ss;
ss << "mixin " << mixin[0].to_string("") << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments");
eval_error(ss.str(), args[i].line_number, args[i].file_name);
}
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.content.token : param[0].content.token);
bindings[name] = eval(args[i], env, f_env); bindings[name] = eval(args[i], env, f_env, registry);
++j; ++j;
} }
} }
...@@ -326,19 +378,19 @@ namespace Sass { ...@@ -326,19 +378,19 @@ namespace Sass {
Node param(params[i]); Node param(params[i]);
Token name(param[0].content.token); Token name(param[0].content.token);
if (!bindings.query(name)) { if (!bindings.query(name)) {
bindings[name] = eval(param[1], env, f_env); bindings[name] = eval(param[1], env, f_env, registry);
} }
} }
} }
// 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 (int i = 0; i < body.size(); ++i) { for (int i = 0; i < body.size(); ++i) {
body[i] = eval(body[i], bindings, f_env); body[i] = eval(body[i], bindings, f_env, registry);
} }
return body; return body;
} }
Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env) Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
{ {
map<Token, Node> bindings; map<Token, Node> bindings;
// bind arguments // bind arguments
...@@ -346,15 +398,15 @@ namespace Sass { ...@@ -346,15 +398,15 @@ namespace Sass {
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].content.token);
bindings[name] = eval(arg[1], env, f_env); bindings[name] = eval(arg[1], env, f_env, registry);
} }
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); bindings[f.parameters[j]] = eval(args[i], env, f_env, registry);
++j; ++j;
} }
} }
return f(bindings); return f(bindings, registry);
} }
} }
\ No newline at end of file
...@@ -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); Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node accumulate(Node::Type op, Node& acc, Node& rhs); Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry);
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); Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env); Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
} }
\ No newline at end of file
$x: 1 2 3;
bobo + mogo {
hey: hoo;
}
@mixin moogoo() {
hey: ho;
div {
blah: blah;
bloo: (1/0) + 5;
}
}
:nth-of-type(2) {
foo: bar;
}
div[hux ~= "hello"] {
hux: blux;
foo: boo;
dux: "hello" + "goodbye";
shux: 'hello' + 12;
frux: 12 + 'hello';
blah: blah;
hoo: blah == 12;
//moo: rgba(0,0,0,.5) + rgba(0,0,0,.6);
moo: rgba(0,0,0,.5) + 3;
}
$y: hey;
@mixin foo($x) {
foofoo: $x $y;
}
div {
@include foo();
hoo: rgba(1, 3, 3, .4);
blah: fade_out(#123, 1px);
flah: unquote("hello");
grah: quote(hello);
hrah: quote(mukluk);
mwah: join((), 2, "comma");
gwah: comparable(foo, bar);
}
\ No newline at end of file
...@@ -2,66 +2,92 @@ ...@@ -2,66 +2,92 @@
#include "prelexer.hpp" #include "prelexer.hpp"
#endif #endif
#include "functions.hpp" #include "functions.hpp"
#include "error.hpp"
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
using std::cerr; using std::endl; using std::cerr; using std::endl;
namespace Sass { namespace Sass {
namespace Functions { namespace Functions {
// TO DO: functions need to check the types of their arguments static void eval_error(string message, size_t line_number, const char* file_name)
{
string fn;
if (file_name) {
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
}
// 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) { Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(Node::numeric_color, 0, 4); Node r(bindings[parameters[0]]);
color << bindings[parameters[0]] Node g(bindings[parameters[1]]);
<< bindings[parameters[1]] Node b(bindings[parameters[2]]);
<< bindings[parameters[2]] if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number)) {
<< Node(0, 1.0); eval_error("arguments for rgb must be numbers", r.line_number, r.file_name);
}
Node color(Node::numeric_color, registry, 0, 4);
color << r << g << b << Node(0, 1.0);
return color; 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) { Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(Node::numeric_color, 0, 4); Node r(bindings[parameters[0]]);
color << bindings[parameters[0]] Node g(bindings[parameters[1]]);
<< bindings[parameters[1]] Node b(bindings[parameters[2]]);
<< bindings[parameters[2]] Node a(bindings[parameters[3]]);
<< bindings[parameters[3]]; if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number && a.type == Node::number)) {
eval_error("arguments for rgba must be numbers", r.line_number, r.file_name);
}
Node color(Node::numeric_color, registry, 0, 4);
color << r << g << b << a;
return color; 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) { Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]].clone()); Node color(bindings[parameters[0]].clone(registry));
color[3] = bindings[parameters[1]]; color[3] = bindings[parameters[1]];
return color; return color;
} }
Function_Descriptor red_descriptor = Function_Descriptor red_descriptor =
{ "red", "$color", 0 }; { "red", "$color", 0 };
Node red(const vector<Token>& parameters, map<Token, Node>& bindings) { Node red(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return bindings[parameters[0]][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);
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) { Node green(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return bindings[parameters[0]][1]; 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);
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) { Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return bindings[parameters[0]][2]; 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);
return color[2];
} }
Node mix_impl(Node color1, Node color2, double weight = 50) { Node mix_impl(Node color1, Node color2, double weight, vector<vector<Node>*>& registry) {
if (!(color1.type == Node::numeric_color && color2.type == Node::numeric_color)) {
eval_error("first two arguments to mix must be colors", color1.line_number, color1.file_name);
}
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].content.numeric_value - color2[3].content.numeric_value;
...@@ -69,7 +95,7 @@ namespace Sass { ...@@ -69,7 +95,7 @@ namespace Sass {
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, color1.line_number, 4); Node mixed(Node::numeric_color, registry, color1.line_number, 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 << Node(mixed.line_number, w1*color1[i].content.numeric_value +
w2*color2[i].content.numeric_value); w2*color2[i].content.numeric_value);
...@@ -81,16 +107,21 @@ namespace Sass { ...@@ -81,16 +107,21 @@ namespace Sass {
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) { Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return mix_impl(bindings[parameters[0]], bindings[parameters[1]]); return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, registry);
} }
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) { Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node percentage(bindings[parameters[2]]);
if (!(percentage.type == Node::number || percentage.type == Node::numeric_percentage || percentage.type == Node::numeric_dimension)) {
eval_error("third argument to mix must be numeric", percentage.line_number, percentage.file_name);
}
return mix_impl(bindings[parameters[0]], return mix_impl(bindings[parameters[0]],
bindings[parameters[1]], bindings[parameters[1]],
bindings[parameters[2]].content.numeric_value); percentage.numeric_value(),
registry);
} }
// HSL Functions /////////////////////////////////////////////////////// // HSL Functions ///////////////////////////////////////////////////////
...@@ -104,7 +135,7 @@ namespace Sass { ...@@ -104,7 +135,7 @@ namespace Sass {
return m1; return m1;
} }
Node hsla_impl(double h, double s, double l, double a = 1) { Node hsla_impl(double h, double s, double l, double a, vector<vector<Node>*>& registry) {
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;
...@@ -117,37 +148,49 @@ namespace Sass { ...@@ -117,37 +148,49 @@ 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(0, r, g, b, a); return Node(registry, 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) { Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric() &&
bindings[parameters[3]].is_numeric())) {
eval_error("arguments to hsla must be numeric", bindings[parameters[0]].line_number, bindings[parameters[0]].file_name);
}
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)); Node color(hsla_impl(h, s, l, a, registry));
color.line_number = bindings[parameters[0]].line_number; color.line_number = bindings[parameters[0]].line_number;
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) { Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric())) {
eval_error("arguments to hsl must be numeric", bindings[parameters[0]].line_number, bindings[parameters[0]].file_name);
}
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)); Node color(hsla_impl(h, s, l, 1, registry));
color.line_number = bindings[parameters[0]].line_number; color.line_number = bindings[parameters[0]].line_number;
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) { Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node orig(bindings[parameters[0]]); Node orig(bindings[parameters[0]]);
return Node(orig.line_number, if (orig.type != Node::numeric_color) eval_error("argument to invert must be a color", orig.line_number, orig.file_name);
return Node(registry, orig.line_number,
255 - orig[0].content.numeric_value, 255 - orig[0].content.numeric_value,
255 - orig[1].content.numeric_value, 255 - orig[1].content.numeric_value,
255 - orig[2].content.numeric_value, 255 - orig[2].content.numeric_value,
...@@ -160,18 +203,26 @@ namespace Sass { ...@@ -160,18 +203,26 @@ 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) { Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return bindings[parameters[0]][3]; 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);
return color[3];
} }
Function_Descriptor opacify_descriptor = Function_Descriptor opacify_descriptor =
{ "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) { Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
cpy[3].content.numeric_value += bindings[parameters[1]].content.numeric_value; if (cpy.type != Node::numeric_color || !bindings[parameters[1]].is_numeric()) {
if (cpy[3].content.numeric_value >= 1) cpy[3].content.numeric_value = 1; 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]]);
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) eval_error("amount must be between 0 and 1 for opacify/fade-in", delta.line_number, delta.file_name);
cpy[3].content.numeric_value += delta.numeric_value();
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1;
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0;
return cpy; return cpy;
} }
...@@ -179,10 +230,16 @@ namespace Sass { ...@@ -179,10 +230,16 @@ namespace Sass {
{ "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) { Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
cpy[3].content.numeric_value -= bindings[parameters[1]].content.numeric_value; if (cpy.type != Node::numeric_color || !bindings[parameters[1]].is_numeric()) {
if (cpy[3].content.numeric_value <= 0) cpy[3].content.numeric_value = 0; 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]]);
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) eval_error("amount must be between 0 and 1 for transparentize/fade-out", delta.line_number, delta.file_name);
cpy[3].content.numeric_value -= delta.numeric_value();
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1;
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0;
return cpy; return cpy;
} }
...@@ -190,17 +247,22 @@ namespace Sass { ...@@ -190,17 +247,22 @@ namespace Sass {
Function_Descriptor unquote_descriptor = Function_Descriptor unquote_descriptor =
{ "unquote", "$string", 0 }; { "unquote", "$string", 0 };
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings) { Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
// if (cpy.type != Node::string_constant /* && cpy.type != Node::concatenation */) {
// eval_error("argument to unquote must be a string", cpy.line_number, cpy.file_name);
// }
cpy.unquoted = true; cpy.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) { Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
// check the types -- will probably be an identifier if (cpy.type != Node::string_constant && cpy.type != Node::identifier) {
eval_error("argument to quote must be a string or identifier", cpy.line_number, cpy.file_name);
}
cpy.type = Node::string_constant; cpy.type = Node::string_constant;
cpy.unquoted = false; cpy.unquoted = false;
return cpy; return cpy;
...@@ -210,9 +272,10 @@ namespace Sass { ...@@ -210,9 +272,10 @@ 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) { Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
// TO DO: make sure it's not already a percentage // TO DO: make sure it's not already a percentage
if (cpy.type != Node::number) eval_error("argument to percentage must be a unitless number", cpy.line_number, cpy.file_name);
cpy.content.numeric_value = cpy.content.numeric_value * 100; cpy.content.numeric_value = cpy.content.numeric_value * 100;
cpy.type = Node::numeric_percentage; cpy.type = Node::numeric_percentage;
return cpy; return cpy;
...@@ -220,52 +283,64 @@ namespace Sass { ...@@ -220,52 +283,64 @@ namespace Sass {
Function_Descriptor round_descriptor = Function_Descriptor round_descriptor =
{ "round", "$value", 0 }; { "round", "$value", 0 };
Node round(const vector<Token>& parameters, map<Token, Node>& bindings) { Node round(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) { if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value + 0.5); cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value + 0.5);
} }
else { else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage) {
cpy.content.numeric_value = std::floor(cpy.content.numeric_value + 0.5); cpy.content.numeric_value = std::floor(cpy.content.numeric_value + 0.5);
} }
else {
eval_error("argument to round must be numeric", cpy.line_number, cpy.file_name);
}
return cpy; return cpy;
} }
Function_Descriptor ceil_descriptor = Function_Descriptor ceil_descriptor =
{ "ceil", "$value", 0 }; { "ceil", "$value", 0 };
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings) { Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) { if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::ceil(cpy.content.dimension.numeric_value); cpy.content.dimension.numeric_value = std::ceil(cpy.content.dimension.numeric_value);
} }
else { else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::ceil(cpy.content.numeric_value); cpy.content.numeric_value = std::ceil(cpy.content.numeric_value);
} }
else {
eval_error("argument to ceil must be numeric", cpy.line_number, cpy.file_name);
}
return cpy; return cpy;
} }
Function_Descriptor floor_descriptor = Function_Descriptor floor_descriptor =
{ "floor", "$value", 0 }; { "floor", "$value", 0 };
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings) { Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) { if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value); cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value);
} }
else { else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::floor(cpy.content.numeric_value); cpy.content.numeric_value = std::floor(cpy.content.numeric_value);
} }
else {
eval_error("argument to floor must be numeric", cpy.line_number, cpy.file_name);
}
return cpy; return cpy;
} }
Function_Descriptor abs_descriptor = Function_Descriptor abs_descriptor =
{ "abs", "$value", 0 }; { "abs", "$value", 0 };
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings) { Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone()); Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) { if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::fabs(cpy.content.dimension.numeric_value); cpy.content.dimension.numeric_value = std::fabs(cpy.content.dimension.numeric_value);
} }
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::abs(cpy.content.numeric_value);
}
else { else {
cpy.content.numeric_value = std::fabs(cpy.content.numeric_value); eval_error("argument to abs must be numeric", cpy.line_number, cpy.file_name);
} }
return cpy; return cpy;
} }
...@@ -274,7 +349,7 @@ namespace Sass { ...@@ -274,7 +349,7 @@ namespace Sass {
Function_Descriptor length_descriptor = Function_Descriptor length_descriptor =
{ "length", "$list", 0 }; { "length", "$list", 0 };
Node length(const vector<Token>& parameters, map<Token, Node>& bindings) { Node length(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node arg(bindings[parameters[0]]); Node arg(bindings[parameters[0]]);
if (arg.type == Node::space_list || arg.type == Node::comma_list) { if (arg.type == Node::space_list || arg.type == Node::comma_list) {
return Node(arg.line_number, arg.size()); return Node(arg.line_number, arg.size());
...@@ -282,6 +357,7 @@ namespace Sass { ...@@ -282,6 +357,7 @@ namespace Sass {
else if (arg.type == Node::nil) { else if (arg.type == Node::nil) {
return Node(arg.line_number, 0); return Node(arg.line_number, 0);
} }
// single objects should be reported as lists of length 1
else { else {
return Node(arg.line_number, 1); return Node(arg.line_number, 1);
} }
...@@ -289,39 +365,47 @@ namespace Sass { ...@@ -289,39 +365,47 @@ namespace Sass {
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) { Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node l(bindings[parameters[0]]); Node l(bindings[parameters[0]]);
// TO DO: check for empty list // 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) { if (l.type != Node::space_list && l.type != Node::comma_list) {
l = Node(Node::space_list, l.line_number, 1) << l; l = Node(Node::space_list, registry, l.line_number, 1) << l;
} }
Node n(bindings[parameters[1]]);
if (n.type != Node::number) eval_error("second argument to nth must be a number", n.line_number, n.file_name);
if (n.numeric_value() < 1 || n.numeric_value() > l.size()) eval_error("out of range index for nth", n.line_number, n.file_name);
return l[bindings[parameters[1]].content.numeric_value - 1]; return l[bindings[parameters[1]].content.numeric_value - 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 = false) { Node join_impl(const vector<Token>& parameters, map<Token, Node>& bindings, bool has_sep, vector<vector<Node>*>& registry) {
// 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, l1.line_number, 1) << l1; l1 = Node(Node::space_list, registry, l1.line_number, 1) << l1;
cerr << "listified singleton" << endl;
} }
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, l2.line_number, 1) << l2; l2 = Node(Node::space_list, registry, l2.line_number, 1) << l2;
cerr << "listified singleton" << endl;
} }
// nil and nil is nil
if (l1.type == Node::nil && l2.type == Node::nil) return Node(Node::nil, l1.line_number); if (l1.type == Node::nil && l2.type == Node::nil) return Node(Node::nil, registry, l1.line_number);
// figure out the combined size in advance
size_t size = 0; 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();
Node lr(Node::space_list, l1.line_number, size); // accumulate the result
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]].content.token.unquote());
if (sep == "comma") lr.type = Node::comma_list; if (sep == "comma") lr.type = Node::comma_list;
// TO DO: check for "space" or "auto" else if (sep == "space") lr.type = Node::space_list;
else if (sep == "auto") ; // leave it alone
else {
eval_error("third argument to join must be 'space', 'comma', or 'auto'", l2.line_number, l2.file_name);
}
} }
else if (l1.type != Node::nil) lr.type = l1.type; else if (l1.type != Node::nil) lr.type = l1.type;
else if (l2.type != Node::nil) lr.type = l2.type; else if (l2.type != Node::nil) lr.type = l2.type;
...@@ -333,14 +417,14 @@ namespace Sass { ...@@ -333,14 +417,14 @@ namespace Sass {
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) { Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return join_impl(parameters, bindings); return join_impl(parameters, bindings, false, registry);
} }
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) { Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return join_impl(parameters, bindings, true); return join_impl(parameters, bindings, true, registry);
} }
// Introspection Functions ///////////////////////////////////////////// // Introspection Functions /////////////////////////////////////////////
...@@ -353,7 +437,7 @@ namespace Sass { ...@@ -353,7 +437,7 @@ 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) { Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]); Node val(bindings[parameters[0]]);
Node type(Node::string_constant, val.line_number, Token::make()); Node type(Node::string_constant, val.line_number, Token::make());
type.unquoted = true; type.unquoted = true;
...@@ -389,7 +473,7 @@ namespace Sass { ...@@ -389,7 +473,7 @@ 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) { Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]); Node val(bindings[parameters[0]]);
Node u(Node::string_constant, val.line_number, Token::make()); Node u(Node::string_constant, val.line_number, Token::make());
switch (val.type) switch (val.type)
...@@ -403,8 +487,8 @@ namespace Sass { ...@@ -403,8 +487,8 @@ namespace Sass {
case Node::numeric_dimension: case Node::numeric_dimension:
u.content.token = Token::make(val.content.dimension.unit, Prelexer::identifier(val.content.dimension.unit)); u.content.token = Token::make(val.content.dimension.unit, Prelexer::identifier(val.content.dimension.unit));
break; break;
default: // TO DO: throw an exception default:
u.content.token = Token::make(empty_str); eval_error("argument to unit must be numeric", val.line_number, val.file_name);
break; break;
} }
return u; return u;
...@@ -415,7 +499,7 @@ namespace Sass { ...@@ -415,7 +499,7 @@ 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) { Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]); Node val(bindings[parameters[0]]);
Node result(Node::string_constant, val.line_number, Token::make()); Node result(Node::string_constant, val.line_number, Token::make());
result.unquoted = true; result.unquoted = true;
...@@ -435,7 +519,7 @@ namespace Sass { ...@@ -435,7 +519,7 @@ namespace Sass {
return F; return F;
} break; } break;
default: default:
// TO DO: throw an exception; eval_error("argument to unitless must be numeric", val.line_number, val.file_name);
break; break;
} }
return result; return result;
...@@ -443,12 +527,13 @@ namespace Sass { ...@@ -443,12 +527,13 @@ namespace Sass {
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) { Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
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 || t2 == Node::number) { if (t1 == Node::number && n2.is_numeric() ||
n1.is_numeric() && t2 == Node::number) {
Node T(Node::boolean); Node T(Node::boolean);
T.line_number = n1.line_number; T.line_number = n1.line_number;
T.content.boolean_value = true; T.content.boolean_value = true;
...@@ -479,18 +564,21 @@ namespace Sass { ...@@ -479,18 +564,21 @@ namespace Sass {
return F; return F;
} }
} }
else if (!n1.is_numeric() && !n2.is_numeric()) {
eval_error("arguments to comparable must be numeric", n1.line_number, n1.file_name);
}
else { else {
Node F(Node::boolean); Node F(Node::boolean);
F.line_number = n1.line_number; F.line_number = n1.line_number;
F.content.boolean_value = false; F.content.boolean_value = false;
return F; 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) { Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
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.content.boolean_value == false) {
Node T(Node::boolean); Node T(Node::boolean);
......
...@@ -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>&); typedef Node (*Implementation)(const vector<Token>&, map<Token, Node>&, vector<vector<Node>*>& registry);
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) const Node operator()(map<Token, Node>& bindings, vector<vector<Node>*>& registry) const
{ return implementation(parameters, bindings); } { return implementation(parameters, bindings, registry); }
}; };
...@@ -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); Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor rgba_4_descriptor; extern Function_Descriptor rgba_4_descriptor;
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings); Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor rgba_2_descriptor; extern Function_Descriptor rgba_2_descriptor;
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings); Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor red_descriptor; extern Function_Descriptor red_descriptor;
Node red(const vector<Token>& parameters, map<Token, Node>& bindings); Node red(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor green_descriptor; extern Function_Descriptor green_descriptor;
Node green(const vector<Token>& parameters, map<Token, Node>& bindings); Node green(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor blue_descriptor; extern Function_Descriptor blue_descriptor;
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings); Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor mix_2_descriptor; extern Function_Descriptor mix_2_descriptor;
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings); Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor mix_3_descriptor; extern Function_Descriptor mix_3_descriptor;
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings); Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// HSL Functions /////////////////////////////////////////////////////// // HSL Functions ///////////////////////////////////////////////////////
extern Function_Descriptor hsla_descriptor; extern Function_Descriptor hsla_descriptor;
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings); Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor hsl_descriptor; extern Function_Descriptor hsl_descriptor;
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings); Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor invert_descriptor; extern Function_Descriptor invert_descriptor;
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings); Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// 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); Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
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); Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
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); Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// String Functions //////////////////////////////////////////////////// // String Functions ////////////////////////////////////////////////////
extern Function_Descriptor unquote_descriptor; extern Function_Descriptor unquote_descriptor;
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings); Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor quote_descriptor; extern Function_Descriptor quote_descriptor;
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings); Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// Number Functions //////////////////////////////////////////////////// // Number Functions ////////////////////////////////////////////////////
extern Function_Descriptor percentage_descriptor; extern Function_Descriptor percentage_descriptor;
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings); Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor round_descriptor; extern Function_Descriptor round_descriptor;
Node round(const vector<Token>& parameters, map<Token, Node>& bindings); Node round(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor ceil_descriptor; extern Function_Descriptor ceil_descriptor;
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings); Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor floor_descriptor; extern Function_Descriptor floor_descriptor;
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings); Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor abs_descriptor; extern Function_Descriptor abs_descriptor;
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings); Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// List Functions ////////////////////////////////////////////////////// // List Functions //////////////////////////////////////////////////////
extern Function_Descriptor length_descriptor; extern Function_Descriptor length_descriptor;
Node length(const vector<Token>& parameters, map<Token, Node>& bindings); Node length(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor nth_descriptor; extern Function_Descriptor nth_descriptor;
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings); Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor join_2_descriptor; extern Function_Descriptor join_2_descriptor;
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings); Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor join_3_descriptor; extern Function_Descriptor join_3_descriptor;
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings); Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// 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); Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor unit_descriptor; extern Function_Descriptor unit_descriptor;
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings); Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor unitless_descriptor; extern Function_Descriptor unitless_descriptor;
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings); Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor comparable_descriptor; extern Function_Descriptor comparable_descriptor;
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings); Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// 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); Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
} }
......
$x: bar;
$blux: inside blux;
div {
a: blah foo#{$x};
b: (1#{2+3}) + 4;
b: (1 (2+3)) + 4;
c: 1#{2 + 3} + 4;
d: type-of(false);
e: type-of(fa#{lse});
e: 12fa#{lse}345;
f: 12+#{3}, 12(#{3});
g: (12)34, 12(34);
h: #fff#{f};
j: $bl#{ux};
}
\ No newline at end of file
#include <stdio.h>
#include "sass_interface.h"
#include <sys/resource.h>
int main(int argc, char** argv)
{
if (argc < 2) {
printf("Hey, I need an input file!\n");
return 0;
}
int who = RUSAGE_SELF;
struct rusage r;
while (1) {
getrusage(who, &r);
printf("Memory usage: %ld\n", r.ru_maxrss);
struct sass_file_context* ctx = sass_new_file_context();
ctx->options.include_paths = "::/blah/bloo/fuzz:/slub/flub/chub::/Users/Aaron/dev/libsass/::::/huzz/buzz:::";
ctx->options.output_style = SASS_STYLE_NESTED;
ctx->input_path = argv[1];
sass_compile_file(ctx);
if (ctx->error_status) {
if (ctx->error_message) printf("%s", ctx->error_message);
else printf("An error occured; no error message available.\n");
sass_free_file_context(ctx);
break;
}
else if (ctx->output_string) {
sass_free_file_context(ctx);
continue;
}
else {
printf("Unknown internal error.\n");
sass_free_file_context(ctx);
break;
}
}
return 0;
}
\ No newline at end of file
...@@ -14,8 +14,9 @@ using std::endl; ...@@ -14,8 +14,9 @@ using std::endl;
namespace Sass { namespace Sass {
size_t Node::allocations = 0; size_t Node::allocations = 0;
size_t Node::destructed = 0;
Node Node::clone() const Node Node::clone(vector<vector<Node>*>& registry) const
{ {
Node n(*this); Node n(*this);
if (has_children) { if (has_children) {
...@@ -23,8 +24,9 @@ namespace Sass { ...@@ -23,8 +24,9 @@ namespace Sass {
++allocations; ++allocations;
n.content.children->reserve(size()); n.content.children->reserve(size());
for (int i = 0; i < size(); ++i) { for (int i = 0; i < size(); ++i) {
n << at(i).clone(); n << at(i).clone(registry);
} }
registry.push_back(n.content.children);
} }
return n; return n;
} }
...@@ -270,6 +272,12 @@ namespace Sass { ...@@ -270,6 +272,12 @@ namespace Sass {
return "!important"; return "!important";
} break; } break;
case value_schema: {
string result;
for (int i = 0; i < size(); ++i) result += at(i).to_string("");
return result;
} break;
default: { default: {
// return content.token.to_string(); // return content.token.to_string();
if (!has_children && type != flags) return content.token.to_string(); if (!has_children && type != flags) return content.token.to_string();
......
...@@ -76,6 +76,8 @@ namespace Sass { ...@@ -76,6 +76,8 @@ namespace Sass {
boolean, boolean,
important, important,
value_schema,
function_call, function_call,
mixin, mixin,
parameters, parameters,
...@@ -110,14 +112,17 @@ namespace Sass { ...@@ -110,14 +112,17 @@ namespace Sass {
bool boolean_value; bool boolean_value;
} content; } content;
const char* file_name;
static size_t allocations; static size_t allocations;
static size_t destructed;
void clear() void clear()
{ {
type = none; line_number = 0; has_children = false; type = none; line_number = 0; file_name = 0;
has_statements = false; has_blocks = false; has_expansions = false; has_children = false; has_statements = false; has_blocks = false;
has_backref = false; from_variable = false; eval_me = false; has_expansions = false; has_backref = false; from_variable = false;
unquoted = false; eval_me = false; unquoted = false;
} }
size_t size() const size_t size() const
...@@ -135,6 +140,20 @@ namespace Sass { ...@@ -135,6 +140,20 @@ namespace Sass {
return *this; 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 double numeric_value() const
{ {
switch (type) switch (type)
...@@ -174,7 +193,7 @@ namespace Sass { ...@@ -174,7 +193,7 @@ namespace Sass {
void emit_nested_css(stringstream& buf, size_t depth); void emit_nested_css(stringstream& buf, size_t depth);
void emit_expanded_css(stringstream& buf, const string& prefix); void emit_expanded_css(stringstream& buf, const string& prefix);
Node clone() const; Node clone(vector<vector<Node>*>& registry) const;
void flatten(); void flatten();
Node() Node()
...@@ -183,12 +202,13 @@ namespace Sass { ...@@ -183,12 +202,13 @@ namespace Sass {
Node(Type t) // flags or booleans Node(Type t) // flags or booleans
{ clear(); type = t; } { clear(); type = t; }
Node(Type t, unsigned int ln, size_t s = 0) // nodes with children Node(Type t, vector<vector<Node>*>& registry, unsigned int ln, size_t s = 0) // nodes with children
{ {
clear(); clear();
type = t; type = t;
line_number = ln; line_number = ln;
content.children = new vector<Node>; content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(s); content.children->reserve(s);
has_children = true; has_children = true;
++allocations; ++allocations;
...@@ -220,12 +240,13 @@ namespace Sass { ...@@ -220,12 +240,13 @@ namespace Sass {
content.dimension.unit = tok.begin; content.dimension.unit = tok.begin;
} }
Node(unsigned int ln, double r, double g, double b, double a = 1.0) // colors Node(vector<vector<Node>*>& registry, unsigned int ln, double r, double g, double b, double a = 1.0) // colors
{ {
clear(); clear();
type = numeric_color; type = numeric_color;
line_number = ln; line_number = ln;
content.children = new vector<Node>; content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(4); content.children->reserve(4);
content.children->push_back(Node(ln, r)); content.children->push_back(Node(ln, r));
content.children->push_back(Node(ln, g)); content.children->push_back(Node(ln, g));
...@@ -234,5 +255,7 @@ namespace Sass { ...@@ -234,5 +255,7 @@ namespace Sass {
has_children = true; has_children = true;
++allocations; ++allocations;
} }
~Node() { ++destructed; }
}; };
} }
\ No newline at end of file
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <cstdlib> #include <cstdlib>
#include <cmath> #include <cmath>
#include "node.hpp" #include "node.hpp"
#include "error.hpp"
using std::string; using std::string;
using std::stringstream; using std::stringstream;
...@@ -31,6 +32,7 @@ namespace Sass { ...@@ -31,6 +32,7 @@ namespace Sass {
return true; return true;
} break; } break;
case variable:
case identifier: case identifier:
case uri: case uri:
case textual_percentage: case textual_percentage:
...@@ -82,11 +84,11 @@ namespace Sass { ...@@ -82,11 +84,11 @@ namespace Sass {
return numeric_value() < rhs.numeric_value(); return numeric_value() < rhs.numeric_value();
} }
else { else {
// TO DO: throw an exception ("units don't match") throw Error(Error::evaluation, line_number, file_name, "incompatible units");
} }
} }
else { else {
// TO DO: throw an exception ("incomparable types") throw Error(Error::evaluation, line_number, file_name, "incomparable types");
} }
} }
......
...@@ -79,6 +79,15 @@ namespace Sass { ...@@ -79,6 +79,15 @@ namespace Sass {
exactly<'-'>, exactly<'-'>,
exactly<'_'> > > >(src); exactly<'_'> > > >(src);
} }
// Match interpolant schemas
const char* value_schema(const char* src) {
// follows this pattern: ([xyz]*i[xyz]*)+
return one_plus< sequence< zero_plus< alternatives< identifier, number > >,
interpolant,
zero_plus< alternatives< identifier, number > > > >(src);
}
// Match CSS '@' keywords. // Match CSS '@' keywords.
const char* at_keyword(const char* src) { const char* at_keyword(const char* src) {
return sequence<exactly<'@'>, identifier>(src); return sequence<exactly<'@'>, identifier>(src);
......
...@@ -274,6 +274,8 @@ namespace Sass { ...@@ -274,6 +274,8 @@ namespace Sass {
// Match a CSS identifier. // Match a CSS identifier.
const char* identifier(const char* src); const char* identifier(const char* src);
// Match interpolant schemas
const char* value_schema(const char* src);
// Match CSS '@' keywords. // Match CSS '@' keywords.
const char* at_keyword(const char* src); const char* at_keyword(const char* src);
const char* import(const char* src); const char* import(const char* src);
......
#include <iostream> #include <iostream>
#include <sstream>
#include <string> #include <string>
#include <cstdlib> #include <cstdlib>
#include <unistd.h> #include <unistd.h>
#include <iostream>
#include "document.hpp" #include "document.hpp"
#include "eval_apply.hpp" #include "eval_apply.hpp"
#include "error.hpp"
#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*) malloc(sizeof(sass_context)); } { return (sass_context*) malloc(sizeof(sass_context)); }
void sass_free_context(sass_context* ctx) void sass_free_context(sass_context* ctx)
{ {
...@@ -20,7 +22,7 @@ extern "C" { ...@@ -20,7 +22,7 @@ extern "C" {
} }
sass_file_context* sass_new_file_context() sass_file_context* sass_new_file_context()
{ return (sass_file_context*) malloc(sizeof(sass_file_context)); } { return (sass_file_context*) malloc(sizeof(sass_file_context)); }
void sass_free_file_context(sass_file_context* ctx) void sass_free_file_context(sass_file_context* ctx)
{ {
...@@ -29,15 +31,27 @@ extern "C" { ...@@ -29,15 +31,27 @@ extern "C" {
} }
sass_folder_context* sass_new_folder_context() sass_folder_context* sass_new_folder_context()
{ return (sass_folder_context*) malloc(sizeof(sass_folder_context)); } { return (sass_folder_context*) malloc(sizeof(sass_folder_context)); }
static char* process_document(Sass::Document& doc, int style) static char* process_document(Sass::Document& doc, int style)
{ {
using namespace Sass; using namespace Sass;
doc.parse_scss(); doc.parse_scss();
eval(doc.root, doc.context.global_env, doc.context.function_env); cerr << "PARSED" << endl;
eval(doc.root, doc.context.global_env, doc.context.function_env, doc.context.registry);
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 << "Allocations:\t" << Node::allocations << endl;
cerr << "Destructions:\t" << Node::destructed << endl;
cerr << "Registry size:\t" << doc.context.registry.size() << endl;
int i;
for (i = 0; i < doc.context.registry.size(); ++i) {
delete doc.context.registry[i];
}
cerr << "Deallocations:\t" << i << endl;
char* c_output = (char*) malloc(output.size() + 1); char* c_output = (char*) malloc(output.size() + 1);
strcpy(c_output, output.c_str()); strcpy(c_output, output.c_str());
...@@ -47,22 +61,70 @@ extern "C" { ...@@ -47,22 +61,70 @@ extern "C" {
int sass_compile(sass_context* c_ctx) int sass_compile(sass_context* c_ctx)
{ {
using namespace Sass; using namespace Sass;
// TO DO: CATCH ALL EXCEPTIONS 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); 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_status = 0;
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.file_name << ", line " << e.line_number << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
catch(bad_alloc& ba) {
stringstream msg_stream;
msg_stream << "ERROR -- unable to allocate memory: " << ba.what() << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
// TO DO: CATCH EVERYTHING ELSE
return 0; return 0;
} }
int sass_compile_file(sass_file_context* c_ctx) int sass_compile_file(sass_file_context* c_ctx)
{ {
using namespace Sass; using namespace Sass;
// TO DO: CATCH ALL EXCEPTIONS 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); cerr << "MADE A DOC AND CONTEXT OBJ" << endl;
c_ctx->output_string = process_document(doc, c_ctx->options.output_style); cerr << "REGISTRY: " << doc.context.registry.size() << endl;
c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
c_ctx->error_message = 0;
c_ctx->error_status = 0;
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.file_name << ", line " << e.line_number << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
catch(bad_alloc& ba) {
stringstream msg_stream;
msg_stream << "ERROR -- unable to allocate memory: " << ba.what() << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
// TO DO: CATCH EVERYTHING ELSE
return 0; return 0;
} }
......
...@@ -16,27 +16,39 @@ struct sass_context { ...@@ -16,27 +16,39 @@ struct sass_context {
char* input_string; char* input_string;
char* output_string; char* output_string;
struct sass_options options; struct sass_options options;
int error_status;
char* error_message;
}; };
struct sass_folder_context { struct sass_folder_context {
char* search_path; char* search_path;
char* output_path; char* output_path;
struct sass_options options; struct sass_options options;
int error_status;
char* error_message;
}; };
struct sass_file_context { struct sass_file_context {
char* input_path; char* input_path;
char* output_string; char* output_string;
struct sass_options options; struct sass_options options;
int error_status;
char* error_message;
}; };
struct sass_context* sass_new_context (); struct sass_context* sass_new_context ();
struct sass_folder_context* sass_new_folder_context (); struct sass_folder_context* sass_new_folder_context ();
struct sass_file_context* sass_new_file_context (); struct sass_file_context* sass_new_file_context ();
int sass_compile (struct sass_context*); void sass_free_context (struct sass_context* ctx);
// int sass_folder_compile (struct sass_folder_context*); void sass_free_folder_context (struct sass_folder_context* ctx);
int sass_file_compile (struct sass_file_context*); void sass_free_file_context (struct sass_file_context* ctx);
int sass_compile (struct sass_context*);
//int sass_compile_folder (struct sass_folder_context*);
int sass_compile_file (struct sass_file_context*);
void sass_free_file_context (struct sass_file_context* ctx);
#ifdef __cplusplus #ifdef __cplusplus
} }
......
...@@ -34,7 +34,16 @@ int main(int argc, char** argv) ...@@ -34,7 +34,16 @@ int main(int argc, char** argv)
sass_compile_file(ctx); sass_compile_file(ctx);
printf("%s", ctx->output_string); if (ctx->error_status) {
if (ctx->error_message) printf("%s", ctx->error_message);
else printf("An error occured; no error message available.\n");
}
else if (ctx->output_string) {
printf("%s", ctx->output_string);
}
else {
printf("Unknown internal error.\n");
}
sass_free_file_context(ctx); sass_free_file_context(ctx);
return 0; return 0;
......
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