Commit 25186694 by Andrew Nesbitt

Updated libsass

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