Commit ab8fb913 by Dean Mao

make libsass as subtree, in the future do "git pull -s subtree libsass master"…

make libsass as subtree, in the future do "git pull -s subtree libsass master" to pull in new changes
parent 53f5dad6
src/Makefile
.DS_Store
tmp/
.sass-cache
sassc
build/*
*.o
*.a
a.out
bin/*
*.gem
Copyright (C) 2012 by Hampton Catlin
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The following files in the spec were taken from the original Ruby Sass project which
is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under
the same license.
CC=g++
CFLAGS=-c -Wall -O2 -fPIC
LDFLAGS= -fPIC
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)
all: $(OBJECTS)
ar rvs libsass.a $(OBJECTS)
shared: $(OBJECTS)
gcc -shared -o libsass.so *.o
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf *.o *.a *.so
\ No newline at end of file
Libsass
=======
by Aaron Leung and Hampton Catlin (@hcatlin)
[![Build Status](https://secure.travis-ci.org/hcatlin/sassc.png?branch=master)](http://travis-ci.org/hcatlin/sassc)
http://github.com/hcatlin/libsass
Libsass is just a library, but if you want to RUN libsass,
then go to http://github.com/hcatlin/sassc or
http://github.com/hcatlin/sassruby or find your local
implementer.
About
-----
Libsass is a C/C++ port of the Sass CSS precompiler. The original version was written in Ruby, but this version is meant for efficiency and portability.
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
-----
While libsass is primarily implemented in C++, it provides a simple
C interface that is defined in [sass_inteface.h]. Its usage is pretty
straight forward.
First, you create a sass context struct. We use these objects to define
different execution parameters for the library. There are three
different context types.
sass_context // string-in-string-out compilation
sass_file_context // file-based compilation
sass_folder_context // full-folder multi-file
Each of the context's have slightly different behavior and are
implemented seperately. This does add extra work to implementing
a wrapper library, but we felt that a mixed-use context object
provides for too much implicit logic. What if you set "input_string"
AND "input_file"... what do we do? This would introduce bugs into
wrapper libraries that would be difficult to debug.
We anticipate that most adapters in most languages will define
their own logic for how to separate these use cases based on the
language. For instance, the original Ruby interface has a combined
interface, but is backed by three different processes.
To generate a context, use one of the following methods.
new_sass_context()
new_sass_file_context()
new_sass_folder_context()
Again, please see the sass_interface.h for more information.
And, to get even more information, then please see the implementations
in SassC and SassC-Ruby.
About Sass
----------
Sass is a CSS pre-processor language to add on exciting, new,
awesome features to CSS. Sass was the first language of its kind
and by far the most mature and up to date codebase.
Sass was originally created by the co-creator of this library,
Hampton Catlin (@hcatlin). The extension and continuing evolution
of the language has all been the result of years of work by Nathan
Weizenbaum (@nex3) and Chris Eppstein (@chriseppstein).
For more information about Sass itself, please visit http://sass-lang.com
Contribution Agreement
----------------------
Any contribution to the project are seen as copyright assigned to Hampton Catlin. Your contribution warrants that you have the right to assign copyright on your work. This is to ensure that the project remains free and open -- similar to the Apache Foundation.
Our license is designed to be as simple as possible.
#include "context.hpp"
#include <iostream>
#include <unistd.h>
#include "prelexer.hpp"
using std::cerr; using std::endl;
namespace Sass {
using std::pair;
void Context::collect_include_paths(const char* paths_str)
{
const size_t wd_len = 1024;
char wd[wd_len];
include_paths.push_back(getcwd(wd, wd_len));
if (*include_paths.back().rbegin() != '/') include_paths.back() += '/';
if (paths_str) {
const char* beg = paths_str;
const char* end = Prelexer::find_first<':'>(beg);
while (end) {
string path(beg, end - beg);
if (!path.empty()) {
if (*path.rbegin() != '/') path += '/';
include_paths.push_back(path);
}
beg = end + 1;
end = Prelexer::find_first<':'>(beg);
}
string path(beg);
if (!path.empty()) {
if (*path.rbegin() != '/') path += '/';
include_paths.push_back(path);
}
}
// for (int i = 0; i < include_paths.size(); ++i) {
// cerr << include_paths[i] << endl;
// }
}
Context::Context(const char* paths_str)
: global_env(Environment()),
function_env(map<pair<string, size_t>, Function>()),
extensions(multimap<Node, Node>()),
pending_extensions(vector<pair<Node, Node> >()),
source_refs(vector<char*>()),
include_paths(vector<string>()),
new_Node(Node_Factory()),
ref_count(0),
has_extensions(false)
{
register_functions();
collect_include_paths(paths_str);
}
Context::~Context()
{
for (size_t i = 0; i < source_refs.size(); ++i) {
delete[] source_refs[i];
}
new_Node.free();
// cerr << "Deallocated " << i << " source string(s)." << endl;
}
inline void Context::register_function(Function_Descriptor d, Primitive ip)
{
Function f(d, ip);
function_env[pair<string, size_t>(f.name, f.parameters.size())] = f;
}
inline void Context::register_function(Function_Descriptor d, Primitive ip, size_t arity)
{
Function f(d, ip);
function_env[pair<string, size_t>(f.name, arity)] = f;
}
void Context::register_functions()
{
using namespace Functions;
// RGB Functions
register_function(rgb_descriptor, rgb);
register_function(rgba_4_descriptor, rgba_4);
register_function(rgba_2_descriptor, rgba_2);
register_function(red_descriptor, red);
register_function(green_descriptor, green);
register_function(blue_descriptor, blue);
register_function(mix_2_descriptor, mix_2);
register_function(mix_3_descriptor, mix_3);
// HSL Functions
register_function(hsla_descriptor, hsla);
register_function(hsl_descriptor, hsl);
register_function(invert_descriptor, invert);
// Opacity Functions
register_function(alpha_descriptor, alpha);
register_function(opacity_descriptor, alpha);
register_function(opacify_descriptor, opacify);
register_function(fade_in_descriptor, opacify);
register_function(transparentize_descriptor, transparentize);
register_function(fade_out_descriptor, transparentize);
// String Functions
register_function(unquote_descriptor, unquote);
register_function(quote_descriptor, quote);
// Number Functions
register_function(percentage_descriptor, percentage);
register_function(round_descriptor, round);
register_function(ceil_descriptor, ceil);
register_function(floor_descriptor, floor);
register_function(abs_descriptor, abs);
// List Functions
register_function(length_descriptor, length);
register_function(nth_descriptor, nth);
register_function(join_2_descriptor, join_2);
register_function(join_3_descriptor, join_3);
register_function(append_2_descriptor, append_2);
register_function(append_3_descriptor, append_3);
register_function(compact_1_descriptor, compact);
register_function(compact_2_descriptor, compact);
register_function(compact_3_descriptor, compact);
register_function(compact_4_descriptor, compact);
register_function(compact_5_descriptor, compact);
register_function(compact_6_descriptor, compact);
register_function(compact_7_descriptor, compact);
register_function(compact_8_descriptor, compact);
register_function(compact_9_descriptor, compact);
register_function(compact_10_descriptor, compact);
// Introspection Functions
register_function(type_of_descriptor, type_of);
register_function(unit_descriptor, unit);
register_function(unitless_descriptor, unitless);
register_function(comparable_descriptor, comparable);
// Boolean Functions
register_function(not_descriptor, not_impl);
}
}
#define SASS_CONTEXT_INCLUDED
#include <utility>
#include <map>
#include "node_factory.hpp"
#include "functions.hpp"
namespace Sass {
using std::pair;
using std::map;
struct Environment {
map<Token, Node> current_frame;
Environment* parent;
Environment* global;
Environment()
: current_frame(map<Token, Node>()), parent(0), global(0)
{ }
void link(Environment& env)
{
parent = &env;
global = parent->global ? parent->global : parent;
}
bool query(const Token& key) const
{
if (current_frame.count(key)) return true;
else if (parent) return parent->query(key);
else return false;
}
Node& operator[](const Token& key)
{
if (current_frame.count(key)) return current_frame[key];
else if (parent) return (*parent)[key];
else return current_frame[key];
}
};
struct Context {
Environment global_env;
map<pair<string, size_t>, Function> function_env;
multimap<Node, Node> extensions;
vector<pair<Node, Node> > pending_extensions;
vector<char*> source_refs; // all the source c-strings
vector<string> include_paths;
Node_Factory new_Node;
size_t ref_count;
string sass_path;
string css_path;
bool has_extensions;
void collect_include_paths(const char* paths_str);
Context(const char* paths_str = 0);
~Context();
void register_function(Function_Descriptor d, Primitive ip);
void register_function(Function_Descriptor d, Primitive ip, size_t arity);
void register_functions();
};
}
AliceBlue #F0F8FF
AntiqueWhite #FAEBD7
Aqua #00FFFF
Aquamarine #7FFFD4
Azure #F0FFFF
Beige #F5F5DC
Bisque #FFE4C4
Black #000000
BlanchedAlmond #FFEBCD
Blue #0000FF
BlueViolet #8A2BE2
Brown #A52A2A
BurlyWood #DEB887
CadetBlue #5F9EA0
Chartreuse #7FFF00
Chocolate #D2691E
Coral #FF7F50
CornflowerBlue #6495ED
Cornsilk #FFF8DC
Crimson #DC143C
Cyan #00FFFF
DarkBlue #00008B
DarkCyan #008B8B
DarkGoldenRod #B8860B
DarkGray #A9A9A9
DarkGrey #A9A9A9
DarkGreen #006400
DarkKhaki #BDB76B
DarkMagenta #8B008B
DarkOliveGreen #556B2F
Darkorange #FF8C00
DarkOrchid #9932CC
DarkRed #8B0000
DarkSalmon #E9967A
DarkSeaGreen #8FBC8F
DarkSlateBlue #483D8B
DarkSlateGray #2F4F4F
DarkSlateGrey #2F4F4F
DarkTurquoise #00CED1
DarkViolet #9400D3
DeepPink #FF1493
DeepSkyBlue #00BFFF
DimGray #696969
DimGrey #696969
DodgerBlue #1E90FF
FireBrick #B22222
FloralWhite #FFFAF0
ForestGreen #228B22
Fuchsia #FF00FF
Gainsboro #DCDCDC
GhostWhite #F8F8FF
Gold #FFD700
GoldenRod #DAA520
Gray #808080
Grey #808080
Green #008000
GreenYellow #ADFF2F
HoneyDew #F0FFF0
HotPink #FF69B4
IndianRed #CD5C5C
Indigo #4B0082
Ivory #FFFFF0
Khaki #F0E68C
Lavender #E6E6FA
LavenderBlush #FFF0F5
LawnGreen #7CFC00
LemonChiffon #FFFACD
LightBlue #ADD8E6
LightCoral #F08080
LightCyan #E0FFFF
LightGoldenRodYellow #FAFAD2
LightGray #D3D3D3
LightGrey #D3D3D3
LightGreen #90EE90
LightPink #FFB6C1
LightSalmon #FFA07A
LightSeaGreen #20B2AA
LightSkyBlue #87CEFA
LightSlateGray #778899
LightSlateGrey #778899
LightSteelBlue #B0C4DE
LightYellow #FFFFE0
Lime #00FF00
LimeGreen #32CD32
Linen #FAF0E6
Magenta #FF00FF
Maroon #800000
MediumAquaMarine #66CDAA
MediumBlue #0000CD
MediumOrchid #BA55D3
MediumPurple #9370D8
MediumSeaGreen #3CB371
MediumSlateBlue #7B68EE
MediumSpringGreen #00FA9A
MediumTurquoise #48D1CC
MediumVioletRed #C71585
MidnightBlue #191970
MintCream #F5FFFA
MistyRose #FFE4E1
Moccasin #FFE4B5
NavajoWhite #FFDEAD
Navy #000080
OldLace #FDF5E6
Olive #808000
OliveDrab #6B8E23
Orange #FFA500
OrangeRed #FF4500
Orchid #DA70D6
PaleGoldenRod #EEE8AA
PaleGreen #98FB98
PaleTurquoise #AFEEEE
PaleVioletRed #D87093
PapayaWhip #FFEFD5
PeachPuff #FFDAB9
Peru #CD853F
Pink #FFC0CB
Plum #DDA0DD
PowderBlue #B0E0E6
Purple #800080
Red #FF0000
RosyBrown #BC8F8F
RoyalBlue #4169E1
SaddleBrown #8B4513
Salmon #FA8072
SandyBrown #F4A460
SeaGreen #2E8B57
SeaShell #FFF5EE
Sienna #A0522D
Silver #C0C0C0
SkyBlue #87CEEB
SlateBlue #6A5ACD
SlateGray #708090
SlateGrey #708090
Snow #FFFAFA
SpringGreen #00FF7F
SteelBlue #4682B4
Tan #D2B48C
Teal #008080
Thistle #D8BFD8
Tomato #FF6347
Turquoise #40E0D0
Violet #EE82EE
Wheat #F5DEB3
White #FFFFFF
WhiteSmoke #F5F5F5
Yellow #FFFF00
YellowGreen #9ACD32
\ No newline at end of file
#include <cstdio>
#include <cstring>
#include "document.hpp"
#include "eval_apply.hpp"
#include "error.hpp"
#include <iostream>
#include <sstream>
namespace Sass {
Document::Document(Context& ctx) : context(ctx)
{ ++context.ref_count; }
Document::Document(const Document& doc)
: path(doc.path),
source(doc.source),
position(doc.position),
end(doc.end),
line(doc.line),
own_source(doc.own_source),
context(doc.context),
root(doc.root),
lexed(doc.lexed)
{ ++doc.context.ref_count; }
Document::~Document()
{ --context.ref_count; }
Document Document::make_from_file(Context& ctx, string path)
{
std::FILE *f;
f = std::fopen(path.c_str(), "rb");
if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int status = std::ftell(f);
if (status < 0) throw path;
size_t len = status;
std::rewind(f);
char* source = new char[len + 1];
size_t bytes_read = std::fread(source, sizeof(char), len, f);
if (bytes_read != len) {
std::cerr << "Warning: possible error reading from " << path << std::endl;
}
if (std::ferror(f)) throw path;
source[len] = '\0';
char* end = source + len;
if (std::fclose(f)) throw path;
Document doc(ctx);
doc.path = path;
doc.line = 1;
doc.root = ctx.new_Node(Node::root, path, 1, 0);
doc.lexed = Token::make();
doc.own_source = true;
doc.source = source;
doc.end = end;
doc.position = source;
doc.context.source_refs.push_back(source);
return doc;
}
Document Document::make_from_source_chars(Context& ctx, char* src, string path, bool own_source)
{
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::throw_syntax_error(string message, size_t ln)
{ throw Error(Error::syntax, path, ln ? ln : line, message); }
void Document::throw_read_error(string message, size_t ln)
{ throw Error(Error::read, path, ln ? ln : line, message); }
using std::string;
using std::stringstream;
using std::endl;
string Document::emit_css(CSS_Style style) {
stringstream output;
switch (style) {
case echo:
root.echo(output);
break;
case nested:
root.emit_nested_css(output, 0, true);
break;
case expanded:
root.emit_expanded_css(output, "");
break;
default:
break;
}
string retval(output.str());
if (!retval.empty()) retval.resize(retval.size()-1);
return retval;
}
}
#include <map>
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
#ifndef SASS_CONTEXT_INCLUDED
#include "context.hpp"
#endif
struct Selector_Lookahead {
const char* found;
bool has_interpolants;
};
namespace Sass {
using std::string;
using std::vector;
using std::map;
using namespace Prelexer;
struct Document {
enum CSS_Style { nested, expanded, compact, compressed, echo };
string path;
char* source;
const char* position;
const char* end;
size_t line;
bool own_source;
Context& context;
Node root;
Token lexed;
private:
// force the use of the "make_from_..." factory funtions
Document(Context& ctx);
public:
Document(const Document& doc);
~Document();
static Document make_from_file(Context& ctx, string path);
static Document make_from_source_chars(Context& ctx, char* src, string path = "", bool own_source = false);
static Document make_from_token(Context& ctx, Token t, string path = "", size_t line_number = 1);
template <prelexer mx>
const char* peek(const char* start = 0)
{
if (!start) start = position;
const char* after_whitespace;
if (mx == block_comment) {
after_whitespace = // start;
zero_plus< alternatives<spaces, line_comment> >(start);
}
else if (/*mx == ancestor_of ||*/ mx == no_spaces) {
after_whitespace = position;
}
else if (mx == spaces || mx == ancestor_of) {
after_whitespace = mx(start);
if (after_whitespace) {
return after_whitespace;
}
else {
return 0;
}
}
else if (mx == optional_spaces) {
after_whitespace = optional_spaces(start);
}
else {
after_whitespace = spaces_and_comments(start);
}
const char* after_token = mx(after_whitespace);
if (after_token) {
return after_token;
}
else {
return 0;
}
}
template <prelexer mx>
const char* lex()
{
const char* after_whitespace;
if (mx == block_comment) {
after_whitespace = // position;
zero_plus< alternatives<spaces, line_comment> >(position);
}
else if (mx == ancestor_of || mx == no_spaces) {
after_whitespace = position;
}
else if (mx == spaces) {
after_whitespace = spaces(position);
if (after_whitespace) {
line += count_interval<'\n'>(position, after_whitespace);
lexed = Token::make(position, after_whitespace);
return position = after_whitespace;
}
else {
return 0;
}
}
else if (mx == optional_spaces) {
after_whitespace = optional_spaces(position);
}
else {
after_whitespace = spaces_and_comments(position);
}
const char* after_token = mx(after_whitespace);
if (after_token) {
line += count_interval<'\n'>(position, after_token);
lexed = Token::make(after_whitespace, after_token);
return position = after_token;
}
else {
return 0;
}
}
void parse_scss();
Node parse_import();
Node parse_include();
Node parse_mixin_definition();
Node parse_function_definition();
Node parse_parameters();
Node parse_parameter();
Node parse_mixin_call();
Node parse_arguments();
Node parse_argument();
Node parse_assignment();
Node parse_propset();
Node parse_ruleset(Selector_Lookahead lookahead, Node::Type inside_of = Node::none);
Node parse_selector_schema(const char* end_of_selector);
Node parse_selector_group();
Node parse_selector();
Node parse_selector_combinator();
Node parse_simple_selector_sequence();
Node parse_simple_selector();
Node parse_pseudo();
Node parse_attribute_selector();
Node parse_block(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_rule();
Node parse_values();
Node parse_list();
Node parse_comma_list();
Node parse_space_list();
Node parse_disjunction();
Node parse_conjunction();
Node parse_relation();
Node parse_expression();
Node parse_term();
Node parse_factor();
Node parse_value();
Node parse_function_call();
Node parse_string();
Node parse_value_schema();
Node parse_identifier_schema();
Node parse_if_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_for_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_each_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_while_directive(Node surrounding_ruleset, Node::Type inside_of = Node::none);
Node parse_media_query(Node::Type inside_of = Node::none);
Node parse_media_expression();
Node parse_warning();
Selector_Lookahead lookahead_for_selector(const char* start = 0);
void throw_syntax_error(string message, size_t ln = 0);
void throw_read_error(string message, size_t ln = 0);
string emit_css(CSS_Style style);
};
}
\ No newline at end of file
#include "document.hpp"
#include "error.hpp"
#include <iostream>
namespace Sass {
using namespace std;
void Document::parse_scss()
{
lex< optional_spaces >();
Selector_Lookahead lookahead_result;
while (position < end) {
if (lex< block_comment >()) {
root << context.new_Node(Node::comment, path, line, lexed);
}
else if (peek< import >()) {
Node importee(parse_import());
if (importee.type() == Node::css_import) root << importee;
else root += importee;
if (!lex< exactly<';'> >()) throw_syntax_error("top-level @import directive must be terminated by ';'");
}
else if (peek< mixin >() || peek< exactly<'='> >()) {
root << parse_mixin_definition();
}
else if (peek< function >()) {
root << parse_function_definition();
}
else if (peek< variable >()) {
root << parse_assignment();
if (!lex< exactly<';'> >()) throw_syntax_error("top-level variable binding must be terminated by ';'");
}
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
root << parse_propset();
}
else if ((lookahead_result = lookahead_for_selector(position)).found) {
root << parse_ruleset(lookahead_result);
}
else if (peek< include >() || peek< exactly<'+'> >()) {
root << parse_mixin_call();
if (!lex< exactly<';'> >()) throw_syntax_error("top-level @include directive must be terminated by ';'");
}
else if (peek< if_directive >()) {
root << parse_if_directive(Node(), Node::none);
}
else if (peek< for_directive >()) {
root << parse_for_directive(Node(), Node::none);
}
else if (peek< each_directive >()) {
root << parse_each_directive(Node(), Node::none);
}
else if (peek< while_directive >()) {
root << parse_while_directive(Node(), Node::none);
}
else if (peek< media >()) {
root << parse_media_query(Node::none);
}
else if (peek< warn >()) {
root << parse_warning();
if (!lex< exactly<';'> >()) throw_syntax_error("top-level @warn directive must be terminated by ';'");
}
else {
lex< spaces_and_comments >();
throw_syntax_error("invalid top-level expression");
}
lex< optional_spaces >();
}
}
Node Document::parse_import()
{
lex< import >();
if (lex< uri_prefix >())
{
if (peek< string_constant >()) {
Node schema(parse_string());
Node importee(context.new_Node(Node::css_import, path, line, 1));
importee << schema;
if (!lex< exactly<')'> >()) throw_syntax_error("unterminated url in @import directive");
return importee;
}
else {
const char* beg = position;
const char* end = find_first< exactly<')'> >(position);
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
string import_path(lexed.unquote());
const char* curr_path_start = path.c_str();
const char* curr_path_end = folders(curr_path_start);
string current_path(curr_path_start, curr_path_end - curr_path_start);
try {
Document importee(Document::make_from_file(context, current_path + import_path));
importee.parse_scss();
return importee.root;
}
catch (string& path) {
throw_read_error("error reading file \"" + path + "\"");
}
// unreached statement
return Node();
}
Node Document::parse_mixin_definition()
{
lex< mixin >() || lex< exactly<'='> >();
if (!lex< identifier >()) throw_syntax_error("invalid name in @mixin directive");
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node params(parse_parameters());
if (!peek< exactly<'{'> >()) throw_syntax_error("body for mixin " + name.token().to_string() + " must begin with a '{'");
Node body(parse_block(Node(), Node::mixin));
Node the_mixin(context.new_Node(Node::mixin, path, line, 3));
the_mixin << name << params << body;
return the_mixin;
}
Node Document::parse_function_definition()
{
lex< function >();
size_t func_line = line;
if (!lex< identifier >()) throw_syntax_error("name required for function definition");
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node params(parse_parameters());
if (!peek< exactly<'{'> >()) throw_syntax_error("body for function " + name.to_string() + " must begin with a '{'");
Node body(parse_block(Node(), Node::function));
Node func(context.new_Node(Node::function, path, func_line, 3));
func << name << params << body;
return func;
}
Node Document::parse_parameters()
{
Node params(context.new_Node(Node::parameters, path, line, 0));
Token name(lexed);
if (lex< exactly<'('> >()) {
if (peek< variable >()) {
params << parse_parameter();
while (lex< exactly<','> >()) {
if (!peek< variable >()) throw_syntax_error("expected a variable name (e.g. $x) for the parameter list for " + name.to_string());
params << parse_parameter();
}
if (!lex< exactly<')'> >()) throw_syntax_error("parameter list for " + name.to_string() + " requires a ')'");
}
else if (!lex< exactly<')'> >()) throw_syntax_error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name.to_string());
}
return params;
}
Node Document::parse_parameter() {
lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed));
if (lex< exactly<':'> >()) { // default value
Node val(parse_space_list());
Node par_and_val(context.new_Node(Node::assignment, path, line, 2));
par_and_val << var << val;
return par_and_val;
}
else {
return var;
}
// unreachable statement
return Node();
}
Node Document::parse_mixin_call()
{
lex< include >() || lex< exactly<'+'> >();
if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive");
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments());
Node the_call(context.new_Node(Node::expansion, path, line, 2));
the_call << name << args;
return the_call;
}
Node Document::parse_arguments()
{
Token name(lexed);
Node args(context.new_Node(Node::arguments, path, line, 0));
if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) {
Node arg(parse_argument());
arg.should_eval() = true;
args << arg;
while (lex< exactly<','> >()) {
Node arg(parse_argument());
arg.should_eval() = true;
args << arg;
}
}
if (!lex< exactly<')'> >()) throw_syntax_error("improperly terminated argument list for " + name.to_string());
}
return args;
}
Node Document::parse_argument()
{
if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >();
Node val(parse_space_list());
Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val;
return assn;
}
else {
return parse_space_list();
}
}
Node Document::parse_assignment()
{
lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed));
if (!lex< exactly<':'> >()) throw_syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement");
Node val(parse_list());
Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val;
if (lex< default_flag >()) assn << context.new_Node(Node::none, path, line, 0);
return assn;
}
Node Document::parse_propset()
{
lex< identifier >();
Node property_segment(context.new_Node(Node::identifier, path, line, lexed));
lex< exactly<':'> >();
lex< exactly<'{'> >();
Node block(context.new_Node(Node::block, path, line, 1));
while (!lex< exactly<'}'> >()) {
if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset();
}
else {
block << parse_rule();
lex< exactly<';'> >();
}
}
if (block.empty()) throw_syntax_error("namespaced property cannot be empty");
Node propset(context.new_Node(Node::propset, path, line, 2));
propset << property_segment;
propset << block;
return propset;
}
Node Document::parse_ruleset(Selector_Lookahead lookahead, Node::Type inside_of)
{
Node ruleset(context.new_Node(Node::ruleset, path, line, 3));
if (lookahead.has_interpolants) {
ruleset << parse_selector_schema(lookahead.found);
}
else {
ruleset << parse_selector_group();
}
if (!peek< exactly<'{'> >()) throw_syntax_error("expected a '{' after the selector");
ruleset << parse_block(ruleset, inside_of);
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 sel1(parse_selector());
if (!peek< exactly<','> >()) return sel1;
Node group(context.new_Node(Node::selector_group, path, line, 2));
group << sel1;
while (lex< exactly<','> >()) group << parse_selector();
return group;
}
Node Document::parse_selector()
{
Node seq1(parse_simple_selector_sequence());
if (peek< exactly<','> >() ||
peek< exactly<')'> >() ||
peek< exactly<'{'> >()) return seq1;
Node selector(context.new_Node(Node::selector, path, line, 2));
selector << seq1;
while (!peek< exactly<'{'> >() && !peek< exactly<','> >()) {
selector << parse_simple_selector_sequence();
}
return selector;
}
Node Document::parse_simple_selector_sequence()
{
// check for initial and trailing combinators
if (lex< exactly<'+'> >() ||
lex< exactly<'~'> >() ||
lex< exactly<'>'> >())
{ return context.new_Node(Node::selector_combinator, path, line, lexed); }
// check for backref or type selector, which are only allowed at the front
Node simp1;
if (lex< exactly<'&'> >()) {
simp1 = context.new_Node(Node::backref, path, line, lexed);
}
else if (lex< alternatives< type_selector, universal > >()) {
simp1 = context.new_Node(Node::simple_selector, path, line, lexed);
}
else {
simp1 = parse_simple_selector();
}
// now we have one simple/atomic selector -- see if that's all
if (peek< spaces >() || peek< exactly<'>'> >() ||
peek< exactly<'+'> >() || peek< exactly<'~'> >() ||
peek< exactly<','> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >() || peek< exactly<';'> >())
{ return simp1; }
// otherwise, we have a sequence of simple selectors
Node seq(context.new_Node(Node::simple_selector_sequence, path, line, 2));
seq << simp1;
while (!peek< spaces >(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();
}
return seq;
}
Node Document::parse_selector_combinator()
{
lex< exactly<'+'> >() || lex< exactly<'~'> >() ||
lex< exactly<'>'> >() || lex< ancestor_of >();
return context.new_Node(Node::selector_combinator, path, line, lexed);
}
Node Document::parse_simple_selector()
{
if (lex< id_name >() || lex< class_name >()) {
return context.new_Node(Node::simple_selector, path, line, lexed);
}
else if (peek< exactly<':'> >(position)) {
return parse_pseudo();
}
else if (peek< exactly<'['> >(position)) {
return parse_attribute_selector();
}
else {
throw_syntax_error("invalid selector after " + lexed.to_string());
}
// unreachable statement
return Node();
}
Node Document::parse_pseudo() {
if (lex< pseudo_not >()) {
Node ps_not(context.new_Node(Node::pseudo_negation, path, line, 2));
ps_not << context.new_Node(Node::value, path, line, lexed);
ps_not << parse_selector_group();
lex< exactly<')'> >();
return ps_not;
}
else if (lex< sequence< pseudo_prefix, functional > >()) {
Node pseudo(context.new_Node(Node::functional_pseudo, path, line, 2));
Token name(lexed);
pseudo << context.new_Node(Node::value, path, line, name);
if (lex< alternatives< even, odd > >()) {
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (peek< binomial >(position)) {
lex< coefficient >();
pseudo << context.new_Node(Node::value, path, line, lexed);
lex< exactly<'n'> >();
pseudo << context.new_Node(Node::value, path, line, lexed);
lex< sign >();
pseudo << context.new_Node(Node::value, path, line, lexed);
lex< digits >();
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (lex< sequence< optional<sign>,
optional<digits>,
exactly<'n'> > >()) {
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (lex< sequence< optional<sign>, digits > >()) {
pseudo << context.new_Node(Node::value, path, line, lexed);
}
else if (lex< string_constant >()) {
pseudo << context.new_Node(Node::string_constant, path, line, lexed);
}
else {
throw_syntax_error("invalid argument to " + name.to_string() + "...)");
}
if (!lex< exactly<')'> >()) throw_syntax_error("unterminated argument to " + name.to_string() + "...)");
return pseudo;
}
else if (lex < sequence< pseudo_prefix, identifier > >()) {
return context.new_Node(Node::pseudo, path, line, lexed);
}
else {
throw_syntax_error("unrecognized pseudo-class or pseudo-element");
}
// unreachable statement
return Node();
}
Node Document::parse_attribute_selector()
{
Node attr_sel(context.new_Node(Node::attribute_selector, path, line, 3));
lex< exactly<'['> >();
if (!lex< type_selector >()) throw_syntax_error("invalid attribute name in attribute selector");
Token name(lexed);
attr_sel << context.new_Node(Node::value, path, line, name);
if (lex< exactly<']'> >()) return attr_sel;
if (!lex< alternatives< exact_match, class_match, dash_match,
prefix_match, suffix_match, substring_match > >()) {
throw_syntax_error("invalid operator in attribute selector for " + name.to_string());
}
attr_sel << context.new_Node(Node::value, path, line, lexed);
if (!lex< string_constant >() && !lex< identifier >()) throw_syntax_error("expected a string constant or identifier in attribute selector for " + name.to_string());
attr_sel << context.new_Node(Node::value, path, line, lexed);
if (!lex< exactly<']'> >()) throw_syntax_error("unterminated attribute selector for " + name.to_string());
return attr_sel;
}
Node Document::parse_block(Node surrounding_ruleset, Node::Type inside_of)
{
lex< exactly<'{'> >();
bool semicolon = false;
Selector_Lookahead lookahead_result;
Node block(context.new_Node(Node::block, path, line, 0));
while (!lex< exactly<'}'> >()) {
if (semicolon) {
if (!lex< exactly<';'> >()) throw_syntax_error("non-terminal statement or declaration must end with ';'");
semicolon = false;
while (lex< block_comment >()) {
block << context.new_Node(Node::comment, path, line, lexed);
}
if (lex< exactly<'}'> >()) break;
}
if (lex< block_comment >()) {
block << context.new_Node(Node::comment, path, line, lexed);
}
else if (peek< import >(position)) {
if (inside_of == Node::mixin || inside_of == Node::function) {
lex< import >(); // to adjust the line number
throw_syntax_error("@import directive not allowed inside definition of mixin or function");
}
Node imported_tree(parse_import());
if (imported_tree.type() == Node::css_import) {
block << imported_tree;
}
else {
for (size_t i = 0, S = imported_tree.size(); i < S; ++i) {
block << imported_tree[i];
}
semicolon = true;
}
}
else if (lex< variable >()) {
block << parse_assignment();
semicolon = true;
}
else if (peek< if_directive >()) {
block << parse_if_directive(surrounding_ruleset, inside_of);
}
else if (peek< for_directive >()) {
block << parse_for_directive(surrounding_ruleset, inside_of);
}
else if (peek< each_directive >()) {
block << parse_each_directive(surrounding_ruleset, inside_of);
}
else if (peek < while_directive >()) {
block << parse_while_directive(surrounding_ruleset, inside_of);
}
else if (lex < return_directive >()) {
Node ret_expr(context.new_Node(Node::return_directive, path, line, 1));
ret_expr << parse_list();
block << ret_expr;
semicolon = true;
}
else if (peek< warn >()) {
block << parse_warning();
semicolon = true;
}
else if (inside_of == Node::function) {
throw_syntax_error("only variable declarations and control directives are allowed inside functions");
}
else if (peek< include >(position)) {
block << parse_mixin_call();
semicolon = true;
}
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset();
}
else if ((lookahead_result = lookahead_for_selector(position)).found) {
block << parse_ruleset(lookahead_result, inside_of);
}
else if (peek< exactly<'+'> >()) {
block << parse_mixin_call();
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));
context.has_extensions = true;
semicolon = true;
}
else if (peek< media >()) {
block << parse_media_query(inside_of);
}
else if (!peek< exactly<';'> >()) {
Node rule(parse_rule());
// check for lbrace; if it's there, we have a namespace property with a value
if (peek< exactly<'{'> >()) {
Node inner(parse_block(Node()));
Node propset(context.new_Node(Node::propset, path, line, 2));
propset << rule[0];
rule[0] = context.new_Node(Node::property, path, line, Token::make());
inner.push_front(rule);
propset << inner;
block << propset;
}
else {
block << rule;
semicolon = true;
}
}
else lex< exactly<';'> >();
while (lex< block_comment >()) {
block << context.new_Node(Node::comment, path, line, lexed);
}
}
return block;
}
Node Document::parse_rule() {
Node rule(context.new_Node(Node::rule, path, line, 2));
if (peek< sequence< optional< exactly<'*'> >, identifier_schema > >()) {
rule << parse_identifier_schema();
}
else if (lex< sequence< optional< exactly<'*'> >, identifier > >()) {
rule << context.new_Node(Node::property, path, line, lexed);
}
else {
throw_syntax_error("invalid property name");
}
if (!lex< exactly<':'> >()) throw_syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'");
rule << parse_list();
return rule;
}
Node Document::parse_list()
{
return parse_comma_list();
}
Node Document::parse_comma_list()
{
if (peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position))
{ return context.new_Node(Node::nil, path, line, 0); }
Node list1(parse_space_list());
// if it's a singleton, return it directly; don't wrap it
if (!peek< exactly<','> >(position)) return list1;
Node comma_list(context.new_Node(Node::comma_list, path, line, 2));
comma_list << list1;
comma_list.should_eval() |= list1.should_eval();
while (lex< exactly<','> >())
{
Node list(parse_space_list());
comma_list << list;
comma_list.should_eval() |= list.should_eval();
}
return comma_list;
}
Node Document::parse_space_list()
{
Node disj1(parse_disjunction());
// if it's a singleton, return it directly; don't wrap it
if (peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) ||
peek< exactly<','> >(position) ||
peek< default_flag >(position))
{ return disj1; }
Node space_list(context.new_Node(Node::space_list, path, line, 2));
space_list << disj1;
space_list.should_eval() |= disj1.should_eval();
while (!(peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) ||
peek< exactly<','> >(position) ||
peek< default_flag >(position)))
{
Node disj(parse_disjunction());
space_list << disj;
space_list.should_eval() |= disj.should_eval();
}
return space_list;
}
Node Document::parse_disjunction()
{
Node conj1(parse_conjunction());
// if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< or_kwd, negate< identifier > > >()) return conj1;
Node disjunction(context.new_Node(Node::disjunction, path, line, 2));
disjunction << conj1;
while (lex< sequence< or_kwd, negate< identifier > > >()) disjunction << parse_conjunction();
disjunction.should_eval() = true;
return disjunction;
}
Node Document::parse_conjunction()
{
Node rel1(parse_relation());
// if it's a singleton, return it directly; don't wrap it
if (!peek< sequence< and_kwd, negate< identifier > > >()) return rel1;
Node conjunction(context.new_Node(Node::conjunction, path, line, 2));
conjunction << rel1;
while (lex< sequence< and_kwd, negate< identifier > > >()) conjunction << parse_relation();
conjunction.should_eval() = true;
return conjunction;
}
Node Document::parse_relation()
{
Node expr1(parse_expression());
// if it's a singleton, return it directly; don't wrap it
if (!(peek< eq_op >(position) ||
peek< neq_op >(position) ||
peek< gt_op >(position) ||
peek< gte_op >(position) ||
peek< lt_op >(position) ||
peek< lte_op >(position)))
{ return expr1; }
Node relation(context.new_Node(Node::relation, path, line, 3));
expr1.should_eval() = true;
relation << expr1;
if (lex< eq_op >()) relation << context.new_Node(Node::eq, path, line, lexed);
else if (lex< neq_op >()) relation << context.new_Node(Node::neq, path, line, lexed);
else if (lex< gte_op >()) relation << context.new_Node(Node::gte, path, line, lexed);
else if (lex< lte_op >()) relation << context.new_Node(Node::lte, path, line, lexed);
else if (lex< gt_op >()) relation << context.new_Node(Node::gt, path, line, lexed);
else if (lex< lt_op >()) relation << context.new_Node(Node::lt, path, line, lexed);
Node expr2(parse_expression());
expr2.should_eval() = true;
relation << expr2;
relation.should_eval() = true;
return relation;
}
Node Document::parse_expression()
{
Node term1(parse_term());
// if it's a singleton, return it directly; don't wrap it
if (!(peek< exactly<'+'> >(position) ||
peek< sequence< negate< number >, exactly<'-'> > >(position)))
{ return term1; }
Node expression(context.new_Node(Node::expression, path, line, 3));
term1.should_eval() = true;
expression << term1;
while (lex< exactly<'+'> >() || lex< sequence< negate< number >, exactly<'-'> > >()) {
if (lexed.begin[0] == '+') {
expression << context.new_Node(Node::add, path, line, lexed);
}
else {
expression << context.new_Node(Node::sub, path, line, lexed);
}
Node term(parse_term());
term.should_eval() = true;
expression << term;
}
expression.should_eval() = true;
return expression;
}
Node Document::parse_term()
{
Node fact1(parse_factor());
// if it's a singleton, return it directly; don't wrap it
if (!(peek< exactly<'*'> >(position) ||
peek< exactly<'/'> >(position)))
{ return fact1; }
Node term(context.new_Node(Node::term, path, line, 3));
term << fact1;
if (fact1.should_eval()) term.should_eval() = true;
while (lex< exactly<'*'> >() || lex< exactly<'/'> >()) {
if (lexed.begin[0] == '*') {
term << context.new_Node(Node::mul, path, line, lexed);
term.should_eval() = true;
}
else {
term << context.new_Node(Node::div, path, line, lexed);
}
Node fact(parse_factor());
term.should_eval() |= fact.should_eval();
term << fact;
}
return term;
}
Node Document::parse_factor()
{
if (lex< exactly<'('> >()) {
Node value(parse_comma_list());
value.should_eval() = true;
if (value.type() == Node::comma_list || value.type() == Node::space_list) {
value[0].should_eval() = true;
}
if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis");
return value;
}
else if (lex< sequence< exactly<'+'>, negate< number > > >()) {
Node plus(context.new_Node(Node::unary_plus, path, line, 1));
plus << parse_factor();
plus.should_eval() = true;
return plus;
}
else if (lex< sequence< exactly<'-'>, negate< number> > >()) {
Node minus(context.new_Node(Node::unary_minus, path, line, 1));
minus << parse_factor();
minus.should_eval() = true;
return minus;
}
else {
return parse_value();
}
}
Node Document::parse_value()
{
if (lex< uri_prefix >())
{
const char* value = position;
const char* rparen = find_first< exactly<')'> >(position);
if (!rparen) throw_syntax_error("URI is missing ')'");
Token contents(Token::make(value, rparen));
// lex< string_constant >();
Node result(context.new_Node(Node::uri, path, line, contents));
position = rparen;
lex< exactly<')'> >();
return result;
}
if (lex< value_schema >())
{ return Document::make_from_token(context, lexed, path, line).parse_value_schema(); }
if (lex< sequence< true_kwd, negate< identifier > > >())
{ return context.new_Node(Node::boolean, path, line, true); }
if (lex< sequence< false_kwd, negate< identifier > > >())
{ return context.new_Node(Node::boolean, path, line, false); }
if (peek< functional >())
{ return parse_function_call(); }
if (lex< important >())
{ return context.new_Node(Node::important, path, line, lexed); }
if (lex< identifier >())
{ return context.new_Node(Node::identifier, path, line, lexed); }
if (lex< percentage >())
{ return context.new_Node(Node::textual_percentage, path, line, lexed); }
if (lex< dimension >())
{ return context.new_Node(Node::textual_dimension, path, line, lexed); }
if (lex< number >())
{ return context.new_Node(Node::textual_number, path, line, lexed); }
if (lex< hex >())
{ return context.new_Node(Node::textual_hex, path, line, lexed); }
if (peek< string_constant >())
{ return parse_string(); }
if (lex< variable >())
{
Node var(context.new_Node(Node::variable, path, line, lexed));
var.should_eval() = true;
return var;
}
throw_syntax_error("error reading values after " + lexed.to_string());
// unreachable statement
return Node();
}
Node Document::parse_string()
{
lex< string_constant >();
Token str(lexed);
const char* i = str.begin;
// see if there any interpolants
const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(str.begin, str.end);
if (!p) {
return context.new_Node(Node::string_constant, path, line, str);
}
Node schema(context.new_Node(Node::string_schema, path, line, 1));
while (i < str.end) {
p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(i, str.end);
if (p) {
if (i < p) {
schema << context.new_Node(Node::identifier, path, line, Token::make(i, p)); // accumulate the preceding segment if it's nonempty
}
const char* j = find_first_in_interval< exactly<rbrace> >(p, str.end); // find the closing brace
if (j) {
// parse the interpolant and accumulate it
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 {
// throw an error if the interpolant is unterminated
throw_syntax_error("unterminated interpolant inside string constant " + str.to_string());
}
}
else { // no interpolants left; add the last segment if nonempty
if (i < str.end) schema << context.new_Node(Node::identifier, path, line, Token::make(i, str.end));
break;
}
}
schema.should_eval() = true;
return schema;
}
Node Document::parse_value_schema()
{
Node schema(context.new_Node(Node::value_schema, path, line, 1));
while (position < end) {
if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
schema << interp_node;
}
else if (lex< identifier >()) {
schema << context.new_Node(Node::identifier, path, line, lexed);
}
else if (lex< percentage >()) {
schema << context.new_Node(Node::textual_percentage, path, line, lexed);
}
else if (lex< dimension >()) {
schema << context.new_Node(Node::textual_dimension, path, line, lexed);
}
else if (lex< number >()) {
schema << context.new_Node(Node::textual_number, path, line, lexed);
}
else if (lex< hex >()) {
schema << context.new_Node(Node::textual_hex, path, line, lexed);
}
else if (lex< string_constant >()) {
schema << context.new_Node(Node::string_constant, path, line, lexed);
}
else if (lex< variable >()) {
schema << context.new_Node(Node::variable, path, line, lexed);
}
else {
throw_syntax_error("error parsing interpolated value");
}
}
schema.should_eval() = true;
return schema;
}
Node Document::parse_identifier_schema()
{
lex< sequence< optional< exactly<'*'> >, identifier_schema > >();
Token id(lexed);
const char* i = id.begin;
// see if there any interpolants
const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(id.begin, id.end);
if (!p) {
return context.new_Node(Node::string_constant, path, line, id);
}
Node schema(context.new_Node(Node::identifier_schema, path, line, 1));
while (i < id.end) {
p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(i, id.end);
if (p) {
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, id.end); // find the closing brace
if (j) {
// parse the interpolant and accumulate it
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 {
// throw an error if the interpolant is unterminated
throw_syntax_error("unterminated interpolant inside interpolated identifier " + id.to_string());
}
}
else { // no interpolants left; add the last segment if nonempty
if (i < id.end) schema << context.new_Node(Node::identifier, path, line, Token::make(i, id.end));
break;
}
}
schema.should_eval() = true;
return schema;
}
Node Document::parse_function_call()
{
lex< identifier >();
Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments());
Node call(context.new_Node(Node::function_call, path, line, 2));
call << name << args;
call.should_eval() = true;
return call;
}
Node Document::parse_if_directive(Node surrounding_ruleset, Node::Type inside_of)
{
lex< if_directive >();
Node conditional(context.new_Node(Node::if_directive, path, line, 2));
conditional << parse_list(); // the predicate
if (!lex< exactly<'{'> >()) throw_syntax_error("expected '{' after the predicate for @if");
conditional << parse_block(surrounding_ruleset, inside_of); // the consequent
// collect all "@else if"s
while (lex< elseif_directive >()) {
conditional << parse_list(); // the next predicate
if (!lex< exactly<'{'> >()) throw_syntax_error("expected '{' after the predicate for @else if");
conditional << parse_block(surrounding_ruleset, inside_of); // 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, inside_of); // the alternative
}
return conditional;
}
Node Document::parse_for_directive(Node surrounding_ruleset, Node::Type inside_of)
{
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, inside_of));
Node loop(context.new_Node(for_type, path, for_line, 4));
loop << var << lower_bound << upper_bound << body;
return loop;
}
Node Document::parse_each_directive(Node surrounding_ruleset, Node::Type inside_of)
{
lex < each_directive >();
size_t each_line = line;
if (!lex< variable >()) throw_syntax_error("@each directive requires an iteration variable");
Node var(context.new_Node(Node::variable, path, line, lexed));
if (!lex< in >()) throw_syntax_error("expected 'in' keyword in @each directive");
Node list(parse_list());
if (!peek< exactly<'{'> >()) throw_syntax_error("expected '{' after the upper bound in @each directive");
Node body(parse_block(surrounding_ruleset, inside_of));
Node each(context.new_Node(Node::each_directive, path, each_line, 3));
each << var << list << body;
return each;
}
Node Document::parse_while_directive(Node surrounding_ruleset, Node::Type inside_of)
{
lex< while_directive >();
size_t while_line = line;
Node predicate(parse_list());
Node body(parse_block(surrounding_ruleset, inside_of));
Node loop(context.new_Node(Node::while_directive, path, while_line, 2));
loop << predicate << body;
return loop;
}
Node Document::parse_media_query(Node::Type inside_of)
{
lex< media >();
Node media_query(context.new_Node(Node::media_query, path, line, 2));
Node media_expr(parse_media_expression());
if (peek< exactly<'{'> >()) {
media_query << media_expr;
}
else if (peek< exactly<','> >()) {
Node media_expr_group(context.new_Node(Node::media_expression_group, path, line, 2));
media_expr_group << media_expr;
while (lex< exactly<','> >()) {
media_expr_group << parse_media_expression();
}
media_query << media_expr_group;
}
else {
throw_syntax_error("expected '{' in media query");
}
media_query << parse_block(Node(), inside_of);
return media_query;
}
// extern const char not_kwd[] = "not";
extern const char only_kwd[] = "only";
Node Document::parse_media_expression()
{
Node media_expr(context.new_Node(Node::media_expression, path, line, 1));
// if the query begins with 'not' or 'only', then a media type is required
if (lex< not_kwd >() || lex< exactly<only_kwd> >()) {
media_expr << context.new_Node(Node::identifier, path, line, lexed);
if (!lex< identifier >()) throw_syntax_error("media type expected in media query");
media_expr << context.new_Node(Node::identifier, path, line, lexed);
}
// otherwise, the media type is optional
else if (lex< identifier >()) {
media_expr << context.new_Node(Node::identifier, path, line, lexed);
}
// if no media type was present, then require a parenthesized property
if (media_expr.empty()) {
if (!lex< exactly<'('> >()) throw_syntax_error("invalid media query");
media_expr << parse_rule();
if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis");
}
// parse the rest of the properties for this disjunct
while (!peek< exactly<','> >() && !peek< exactly<'{'> >()) {
if (!lex< and_kwd >()) throw_syntax_error("invalid media query");
media_expr << context.new_Node(Node::identifier, path, line, lexed);
if (!lex< exactly<'('> >()) throw_syntax_error("invalid media query");
media_expr << parse_rule();
if (!lex< exactly<')'> >()) throw_syntax_error("unclosed parenthesis");
}
return media_expr;
}
Node Document::parse_warning()
{
lex< warn >();
Node warning(context.new_Node(Node::warning, path, line, 1));
warning << parse_list();
warning[0].should_eval() = true;
return warning;
}
Selector_Lookahead Document::lookahead_for_selector(const char* start)
{
const char* p = start ? start : position;
const char* q;
bool saw_interpolant = false;
while ((q = peek< identifier >(p)) ||
(q = peek< id_name >(p)) ||
(q = peek< class_name >(p)) ||
(q = peek< sequence< pseudo_prefix, identifier > >(p)) ||
(q = peek< string_constant >(p)) ||
(q = peek< exactly<'*'> >(p)) ||
(q = peek< exactly<'('> >(p)) ||
(q = peek< exactly<')'> >(p)) ||
(q = peek< exactly<'['> >(p)) ||
(q = peek< exactly<']'> >(p)) ||
(q = peek< exactly<'+'> >(p)) ||
(q = peek< exactly<'~'> >(p)) ||
(q = peek< exactly<'>'> >(p)) ||
(q = peek< exactly<','> >(p)) ||
(q = peek< binomial >(p)) ||
(q = peek< sequence< optional<sign>,
optional<digits>,
exactly<'n'> > >(p)) ||
(q = peek< sequence< optional<sign>,
digits > >(p)) ||
(q = peek< number >(p)) ||
(q = peek< exactly<'&'> >(p)) ||
(q = peek< alternatives<exact_match,
class_match,
dash_match,
prefix_match,
suffix_match,
substring_match> >(p)) ||
(q = peek< sequence< exactly<'.'>, interpolant > >(p)) ||
(q = peek< sequence< exactly<'#'>, interpolant > >(p)) ||
(q = peek< sequence< exactly<'-'>, interpolant > >(p)) ||
(q = peek< sequence< pseudo_prefix, interpolant > >(p)) ||
(q = peek< interpolant >(p))) {
p = q;
if (*(p - 1) == '}') saw_interpolant = true;
}
Selector_Lookahead result;
result.found = peek< exactly<'{'> >(p) ? p : 0;
result.has_interpolants = saw_interpolant;
return result;
}
}
\ No newline at end of file
namespace Sass {
struct Error {
enum Type { read, write, syntax, evaluation };
Type type;
string path;
size_t line;
string message;
Error(Type type, string path, size_t line, string message)
: type(type), path(path), line(line), message(message)
{ }
};
}
\ No newline at end of file
#include "prelexer.hpp"
#include "eval_apply.hpp"
#include "document.hpp"
#include "error.hpp"
#include <iostream>
#include <sstream>
#include <cstdlib>
namespace Sass {
using std::cerr; using std::endl;
static void throw_eval_error(string message, string path, size_t line)
{
if (!path.empty() && Prelexer::string_constant(path.c_str()))
path = path.substr(1, path.size() - 1);
throw Error(Error::evaluation, path, line, message);
}
// 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())
{
case Node::mixin: {
env[expr[0].token()] = expr;
return expr;
} break;
case Node::function: {
f_env[pair<string, size_t>(expr[0].to_string(), expr[1].size())] = Function(expr);
return expr;
} break;
case Node::expansion: {
Token name(expr[0].token());
Node args(expr[1]);
if (!env.query(name)) throw_eval_error("mixin " + name.to_string() + " is undefined", expr.path(), expr.line());
Node mixin(env[name]);
Node expansion(apply_mixin(mixin, args, prefix, env, f_env, new_Node, ctx));
expr.pop_back();
expr.pop_back();
expr += expansion;
return expr;
} break;
case Node::propset: {
eval(expr[1], prefix, env, f_env, new_Node, ctx);
return expr;
} break;
case Node::ruleset: {
// 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) {
// check single selector
if (expr.back().type() != Node::selector_group) {
Node sel(selector_base(expr.back()));
if (ctx.extensions.count(sel)) {
for (multimap<Node, Node>::iterator i = ctx.extensions.lower_bound(sel); i != ctx.extensions.upper_bound(sel); ++i) {
ctx.pending_extensions.push_back(pair<Node, Node>(expr, i->second));
}
}
}
// individually check each selector in a group
else {
Node group(expr.back());
for (size_t i = 0, S = group.size(); i < S; ++i) {
Node sel(selector_base(group[i]));
if (ctx.extensions.count(sel)) {
for (multimap<Node, Node>::iterator j = ctx.extensions.lower_bound(sel); j != ctx.extensions.upper_bound(sel); ++j) {
ctx.pending_extensions.push_back(pair<Node, Node>(expr, j->second));
}
}
}
}
}
// eval the body with the current selector as the prefix
eval(expr[1], expr.back(), env, f_env, new_Node, ctx);
return expr;
} break;
case Node::media_query: {
Node block(expr[1]);
Node new_ruleset(new_Node(Node::ruleset, expr.path(), expr.line(), 3));
new_ruleset << prefix << block << prefix;
expr[1] = eval(new_ruleset, new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
return expr;
} break;
case Node::selector_schema: {
string expansion;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (expr[i].type() == Node::string_constant) {
expansion += expr[i].token().unquote();
}
else {
expansion += expr[i].to_string();
}
}
expansion += " {"; // the parser looks for an lbrace to end a selector
char* expn_src = new char[expansion.size() + 1];
strcpy(expn_src, expansion.c_str());
Document needs_reparsing(Document::make_from_source_chars(ctx, expn_src, expr.path(), true));
needs_reparsing.line = expr.line(); // set the line number to the original node's line
Node sel(needs_reparsing.parse_selector_group());
return sel;
} break;
case Node::root: {
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::block: {
Environment new_frame;
new_frame.link(env);
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, new_frame, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::assignment: {
Node val(expr[1]);
if (val.type() == Node::comma_list || val.type() == Node::space_list) {
for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].should_eval()) val[i] = eval(val[i], prefix, env, f_env, new_Node, ctx);
}
}
else {
val = eval(val, prefix, env, f_env, new_Node, ctx);
}
Node var(expr[0]);
if (expr.is_guarded() && env.query(var.token())) return expr;
// If a binding exists (possible upframe), then update it.
// Otherwise, make a new on in the current frame.
if (env.query(var.token())) {
env[var.token()] = val;
}
else {
env.current_frame[var.token()] = val;
}
return expr;
} break;
case Node::rule: {
Node lhs(expr[0]);
if (lhs.should_eval()) eval(lhs, prefix, env, f_env, new_Node, ctx);
Node rhs(expr[1]);
if (rhs.type() == Node::comma_list || rhs.type() == Node::space_list) {
for (size_t i = 0, S = rhs.size(); i < S; ++i) {
if (rhs[i].should_eval()) rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx);
}
}
else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) {
eval(rhs, prefix, env, f_env, new_Node, ctx);
}
else {
if (rhs.should_eval()) expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::comma_list:
case Node::space_list: {
if (expr.should_eval()) expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
return expr;
} break;
case Node::disjunction: {
Node result;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) continue;
else return result;
}
return result;
} break;
case Node::conjunction: {
Node result;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) return result;
}
return result;
} break;
case Node::relation: {
Node lhs(eval(expr[0], prefix, env, f_env, new_Node, ctx));
Node op(expr[1]);
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
// TO DO: don't allocate both T and F
Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true));
Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false));
switch (op.type())
{
case Node::eq: return (lhs == rhs) ? T : F;
case Node::neq: return (lhs != rhs) ? T : F;
case Node::gt: return (lhs > rhs) ? T : F;
case Node::gte: return (lhs >= rhs) ? T : F;
case Node::lt: return (lhs < rhs) ? T : F;
case Node::lte: return (lhs <= rhs) ? T : F;
default:
throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line());
return Node();
}
} break;
case Node::expression: {
Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1));
acc << eval(expr[0], prefix, env, f_env, new_Node, ctx);
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
accumulate(expr[1].type(), acc, rhs, new_Node);
for (size_t i = 3, S = expr.size(); i < S; i += 2) {
Node rhs(eval(expr[i+1], prefix, env, f_env, new_Node, ctx));
accumulate(expr[i].type(), acc, rhs, new_Node);
}
return acc.size() == 1 ? acc[0] : acc;
} break;
case Node::term: {
if (expr.should_eval()) {
Node acc(new_Node(Node::expression, expr.path(), expr.line(), 1));
acc << eval(expr[0], prefix, env, f_env, new_Node, ctx);
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
accumulate(expr[1].type(), acc, rhs, new_Node);
for (size_t i = 3, S = expr.size(); i < S; i += 2) {
Node rhs(eval(expr[i+1], prefix, env, f_env, new_Node, ctx));
accumulate(expr[i].type(), acc, rhs, new_Node);
}
return acc.size() == 1 ? acc[0] : acc;
}
else {
return expr;
}
} break;
case Node::textual_percentage: {
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage);
} break;
case Node::textual_dimension: {
return new_Node(expr.path(), expr.line(),
std::atof(expr.token().begin),
Token::make(Prelexer::number(expr.token().begin),
expr.token().end));
} break;
case Node::textual_number: {
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin));
} break;
case Node::textual_hex: {
Node triple(new_Node(Node::numeric_color, expr.path(), expr.line(), 4));
Token hext(Token::make(expr.token().begin+1, expr.token().end));
if (hext.length() == 6) {
for (int i = 0; i < 6; i += 2) {
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
}
}
else {
for (int i = 0; i < 3; ++i) {
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
}
}
triple << new_Node(expr.path(), expr.line(), 1.0);
return triple;
} break;
case Node::variable: {
if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line());
return env[expr.token()];
} break;
case Node::function_call: {
// TO DO: default-constructed Function should be a generic callback (maybe)
pair<string, size_t> sig(expr[0].token().to_string(), expr[1].size());
if (!f_env.count(sig)) return expr; // TO DO: EVAL THE ARGUMENTS
else return apply_function(f_env[sig], expr[1], prefix, env, f_env, new_Node, ctx);
} break;
case Node::unary_plus: {
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) {
return arg;
}
else {
expr[0] = arg;
return expr;
}
} break;
case Node::unary_minus: {
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) {
return new_Node(expr.path(), expr.line(), -arg.numeric_value());
}
else {
expr[0] = arg;
return expr;
}
} break;
case Node::string_schema:
case Node::value_schema:
case Node::identifier_schema: {
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::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, true);
}
} break;
case Node::each_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::none, "", 0, 0) << (fake_param << expr[0]) << expr[2];
Node list(eval(expr[1], prefix, env, f_env, new_Node, ctx));
// If the list isn't really a list, make a singleton out of it.
if (list.type() != Node::space_list && list.type() != Node::comma_list) {
list = (new_Node(Node::space_list, list.path(), list.line(), 1) << list);
}
expr.pop_back();
expr.pop_back();
expr.pop_back();
for (size_t i = 0, S = list.size(); i < S; ++i) {
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << eval(list[i], prefix, env, f_env, new_Node, ctx);
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::while_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 0));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 0));
fake_mixin << new_Node(Node::none, "", 0, 0) << fake_param << expr[1];
Node pred(expr[0]);
expr.pop_back();
expr.pop_back();
Node ev_pred(eval(pred, prefix, env, f_env, new_Node, ctx));
while ((ev_pred.type() != Node::boolean) || ev_pred.boolean_value()) {
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
ev_pred = eval(pred, prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::warning: {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
return expr;
} break;
default: {
return expr;
} break;
}
return expr;
}
// Accumulate arithmetic operations. It's done this way because arithmetic
// expressions are stored as vectors of operands with operators interspersed,
// rather than as the usual binary tree.
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node)
{
Node lhs(acc.back());
double lnum = lhs.numeric_value();
double rnum = rhs.numeric_value();
if (lhs.type() == Node::number && rhs.type() == Node::number) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum)));
acc.pop_back();
acc.push_back(result);
}
// TO DO: find a way to merge the following two clauses
else if (lhs.type() == Node::number && rhs.type() == Node::numeric_dimension) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), rhs.unit()));
acc.pop_back();
acc.push_back(result);
}
else if (lhs.type() == Node::numeric_dimension && rhs.type() == Node::number) {
Node result(new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit()));
acc.pop_back();
acc.push_back(result);
}
else if (lhs.type() == Node::numeric_dimension && rhs.type() == Node::numeric_dimension) {
// TO DO: CHECK FOR MISMATCHED UNITS HERE
Node result;
if (op == Node::div)
{ result = new_Node(acc.path(), acc.line(), operate(op, lnum, rnum)); }
else
{ result = new_Node(acc.path(), acc.line(), operate(op, lnum, rnum), lhs.unit()); }
acc.pop_back();
acc.push_back(result);
}
// TO DO: find a way to merge the following two clauses
else if (lhs.type() == Node::number && rhs.type() == Node::numeric_color) {
if (op != Node::sub && op != Node::div) {
double r = operate(op, lhs.numeric_value(), rhs[0].numeric_value());
double g = operate(op, lhs.numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs.numeric_value(), rhs[2].numeric_value());
double a = rhs[3].numeric_value();
acc.pop_back();
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
}
// trying to handle weird edge cases ... not sure if it's worth it
else if (op == Node::div) {
acc << new_Node(Node::div, acc.path(), acc.line(), 0);
acc << rhs;
}
else if (op == Node::sub) {
acc << new_Node(Node::sub, acc.path(), acc.line(), 0);
acc << rhs;
}
else {
acc << rhs;
}
}
else if (lhs.type() == Node::numeric_color && rhs.type() == Node::number) {
double r = operate(op, lhs[0].numeric_value(), rhs.numeric_value());
double g = operate(op, lhs[1].numeric_value(), rhs.numeric_value());
double b = operate(op, lhs[2].numeric_value(), rhs.numeric_value());
double a = lhs[3].numeric_value();
acc.pop_back();
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
}
else if (lhs.type() == Node::numeric_color && rhs.type() == Node::numeric_color) {
if (lhs[3].numeric_value() != rhs[3].numeric_value()) throw_eval_error("alpha channels must be equal for " + lhs.to_string() + " + " + rhs.to_string(), lhs.path(), lhs.line());
double r = operate(op, lhs[0].numeric_value(), rhs[0].numeric_value());
double g = operate(op, lhs[1].numeric_value(), rhs[1].numeric_value());
double b = operate(op, lhs[2].numeric_value(), rhs[2].numeric_value());
double a = lhs[3].numeric_value();
acc.pop_back();
acc << new_Node(acc.path(), acc.line(), r, g, b, a);
}
else if (lhs.type() == Node::concatenation && rhs.type() == Node::concatenation) {
if (op == Node::add) {
lhs += rhs;
}
else {
acc << new_Node(op, acc.path(), acc.line(), Token::make());
acc << rhs;
}
}
else if (lhs.type() == Node::concatenation && rhs.type() == Node::string_constant) {
if (op == Node::add) {
lhs << rhs;
}
else {
acc << new_Node(op, acc.path(), acc.line(), Token::make());
acc << rhs;
}
}
else if (lhs.type() == Node::string_constant && rhs.type() == Node::concatenation) {
if (op == Node::add) {
Node new_cat(new_Node(Node::concatenation, lhs.path(), lhs.line(), 1 + rhs.size()));
new_cat << lhs;
new_cat += rhs;
acc.pop_back();
acc << new_cat;
}
else {
acc << new_Node(op, acc.path(), acc.line(), Token::make());
acc << rhs;
}
}
else if (lhs.type() == Node::string_constant && rhs.type() == Node::string_constant) {
if (op == Node::add) {
Node new_cat(new_Node(Node::concatenation, lhs.path(), lhs.line(), 2));
new_cat << lhs << rhs;
acc.pop_back();
acc << new_cat;
}
else {
acc << new_Node(op, acc.path(), acc.line(), Token::make());
acc << rhs;
}
}
else {
// TO DO: disallow division and multiplication on lists
if (op == Node::sub) acc << new_Node(Node::sub, acc.path(), acc.line(), Token::make());
acc.push_back(rhs);
}
return acc;
}
// Helper for doing the actual arithmetic.
double operate(Node::Type op, double lhs, double rhs)
{
switch (op)
{
case Node::add: return lhs + rhs; break;
case Node::sub: return lhs - rhs; break;
case Node::mul: return lhs * rhs; break;
case Node::div: return lhs / rhs; 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, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope)
{
Node params(mixin[1]);
Node body(new_Node(mixin[2])); // clone the body
Environment bindings;
// TO DO: REFACTOR THE ARG-BINDER
// bind arguments
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type() == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].token());
// check that the keyword arg actually names a formal parameter
bool valid_param = false;
for (size_t k = 0, S = params.size(); k < S; ++k) {
Node param_k = params[k];
if (param_k.type() == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
}
}
if (!valid_param) throw_eval_error("mixin " + mixin[0].to_string() + " has no parameter named " + name.to_string(), arg.path(), arg.line());
if (!bindings.query(name)) {
bindings[name] = eval(arg[1], prefix, env, f_env, new_Node, ctx);
}
}
else {
// ensure that the number of ordinal args < params.size()
if (j >= params.size()) {
stringstream ss;
ss << "mixin " << mixin[0].to_string() << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments");
throw_eval_error(ss.str(), args[i].path(), args[i].line());
}
Node param(params[j]);
Token name(param.type() == Node::variable ? param.token() : param[0].token());
bindings[name] = eval(args[i], prefix, env, f_env, new_Node, ctx);
++j;
}
}
// plug the holes with default arguments if any
for (size_t i = 0, S = params.size(); i < S; ++i) {
if (params[i].type() == Node::assignment) {
Node param(params[i]);
Token name(param[0].token());
if (!bindings.query(name)) {
bindings[name] = eval(param[1], prefix, env, f_env, new_Node, ctx);
}
}
}
// END ARG-BINDER
// link the new environment and eval the mixin's body
if (dynamic_scope) {
bindings.link(env);
}
else {
// C-style scope for now (current/global, nothing in between). May need
// to implement full lexical scope someday.
bindings.link(env.global ? *env.global : env);
}
for (size_t i = 0, S = body.size(); i < S; ++i) {
body[i] = eval(body[i], prefix, bindings, f_env, new_Node, ctx);
}
return body;
}
// Apply a function -- bind the arguments and pass them to the underlying
// primitive function implementation, then return its value.
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
if (f.primitive) {
map<Token, Node> bindings;
// bind arguments
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type() == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].token());
bindings[name] = eval(arg[1], prefix, env, f_env, new_Node, ctx);
}
else {
// TO DO: ensure (j < f.parameters.size())
bindings[f.parameters[j]] = eval(args[i], prefix, env, f_env, new_Node, ctx);
++j;
}
}
return f(bindings, new_Node);
}
else {
Node params(f.definition[1]);
Node body(new_Node(f.definition[2]));
Environment bindings;
// TO DO: REFACTOR THE ARG-BINDER
// bind arguments
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (args[i].type() == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].token());
// check that the keyword arg actually names a formal parameter
bool valid_param = false;
for (size_t k = 0, S = params.size(); k < S; ++k) {
Node param_k = params[k];
if (param_k.type() == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
}
}
if (!valid_param) throw_eval_error("mixin " + f.name + " has no parameter named " + name.to_string(), arg.path(), arg.line());
if (!bindings.query(name)) {
bindings[name] = eval(arg[1], prefix, env, f_env, new_Node, ctx);
}
}
else {
// ensure that the number of ordinal args < params.size()
if (j >= params.size()) {
stringstream ss;
ss << "mixin " << f.name << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments");
throw_eval_error(ss.str(), args[i].path(), args[i].line());
}
Node param(params[j]);
Token name(param.type() == Node::variable ? param.token() : param[0].token());
bindings[name] = eval(args[i], prefix, env, f_env, new_Node, ctx);
++j;
}
}
// plug the holes with default arguments if any
for (size_t i = 0, S = params.size(); i < S; ++i) {
if (params[i].type() == Node::assignment) {
Node param(params[i]);
Token name(param[0].token());
if (!bindings.query(name)) {
bindings[name] = eval(param[1], prefix, env, f_env, new_Node, ctx);
}
}
}
// END ARG-BINDER
bindings.link(env.global ? *env.global : env);
return function_eval(f.name, body, bindings, new_Node, ctx, true);
}
}
// Special function for evaluating pure Sass functions. The evaluation
// algorithm is different in this case because the body needs to be
// executed and a single value needs to be returned directly, rather than
// styles being expanded and spliced in place.
Node function_eval(string name, Node body, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool at_toplevel)
{
for (size_t i = 0, S = body.size(); i < S; ++i) {
Node stm(body[i]);
switch (stm.type())
{
case Node::assignment: {
Node val(stm[1]);
if (val.type() == Node::comma_list || val.type() == Node::space_list) {
for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].should_eval()) val[i] = eval(val[i], Node(), bindings, ctx.function_env, new_Node, ctx);
}
}
else {
val = eval(val, Node(), bindings, ctx.function_env, new_Node, ctx);
}
Node var(stm[0]);
if (stm.is_guarded() && bindings.query(var.token())) continue;
// If a binding exists (possible upframe), then update it.
// Otherwise, make a new on in the current frame.
if (bindings.query(var.token())) {
bindings[var.token()] = val;
}
else {
bindings.current_frame[var.token()] = val;
}
} break;
case Node::if_directive: {
for (size_t j = 0, S = stm.size(); j < S; j += 2) {
if (stm[j].type() != Node::block) {
Node predicate_val(eval(stm[j], Node(), bindings, ctx.function_env, new_Node, ctx));
if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) {
Node v(function_eval(name, stm[j+1], bindings, new_Node, ctx));
if (v.is_null_ptr()) break;
else return v;
}
}
else {
Node v(function_eval(name, stm[j], bindings, new_Node, ctx));
if (v.is_null_ptr()) break;
else return v;
}
}
} break;
case Node::for_through_directive:
case Node::for_to_directive: {
Node::Type for_type = stm.type();
Node iter_var(stm[0]);
Node lower_bound(eval(stm[1], Node(), bindings, ctx.function_env, new_Node, ctx));
Node upper_bound(eval(stm[2], Node(), bindings, ctx.function_env, new_Node, ctx));
Node for_body(stm[3]);
Environment for_env; // re-use this env for each iteration
for_env.link(bindings);
for (double j = lower_bound.numeric_value(), T = upper_bound.numeric_value() + ((for_type == Node::for_to_directive) ? 0 : 1);
j < T;
j += 1) {
for_env.current_frame[iter_var.token()] = new_Node(lower_bound.path(), lower_bound.line(), j);
Node v(function_eval(name, for_body, for_env, new_Node, ctx));
if (v.is_null_ptr()) continue;
else return v;
}
} break;
case Node::each_directive: {
Node iter_var(stm[0]);
Node list(eval(stm[1], Node(), bindings, ctx.function_env, new_Node, ctx));
if (list.type() != Node::comma_list && list.type() != Node::space_list) {
list = (new_Node(Node::space_list, list.path(), list.line(), 1) << list);
}
Node each_body(stm[2]);
Environment each_env; // re-use this env for each iteration
each_env.link(bindings);
for (size_t j = 0, T = list.size(); j < T; ++j) {
each_env.current_frame[iter_var.token()] = eval(list[j], Node(), bindings, ctx.function_env, new_Node, ctx);
Node v(function_eval(name, each_body, each_env, new_Node, ctx));
if (v.is_null_ptr()) continue;
else return v;
}
} break;
case Node::while_directive: {
Node pred_expr(stm[0]);
Node while_body(stm[1]);
Environment while_env; // re-use this env for each iteration
while_env.link(bindings);
Node pred_val(eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx));
while ((pred_val.type() != Node::boolean) || pred_val.boolean_value()) {
Node v(function_eval(name, while_body, while_env, new_Node, ctx));
if (v.is_null_ptr()) {
pred_val = eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx);
continue;
}
else return v;
}
} break;
case Node::return_directive: {
return eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx);
} break;
default: {
} break;
}
}
if (at_toplevel) throw_eval_error("function finished without @return", body.path(), body.line());
return 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, multimap<Node, Node>& extension_table, Node_Factory& new_Node)
{
for (size_t i = 0, S = pending.size(); i < S; ++i) {
// Node extender(pending[i].second[2]);
// Node original_extender(pending[i].second[0]);
Node ruleset_to_extend(pending[i].first);
Node extendee(ruleset_to_extend[2]);
if (extendee.type() != Node::selector_group && !extendee.has_been_extended()) {
Node extendee_base(selector_base(extendee));
Node extender_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1));
for (multimap<Node, Node>::iterator i = extension_table.lower_bound(extendee_base), E = extension_table.upper_bound(extendee_base);
i != E;
++i) {
if (i->second[2].type() == Node::selector_group)
extender_group += i->second[2];
else
extender_group << i->second[2];
}
Node extended_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), extender_group.size() + 1));
extendee.has_been_extended() = true;
extended_group << extendee;
for (size_t i = 0, S = extender_group.size(); i < S; ++i) {
extended_group << generate_extension(extendee, extender_group[i], new_Node);
}
ruleset_to_extend[2] = extended_group;
}
else {
Node extended_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), extendee.size() + 1));
for (size_t i = 0, S = extendee.size(); i < S; ++i) {
Node extendee_i(extendee[i]);
Node extendee_i_base(selector_base(extendee_i));
extended_group << extendee_i;
if (!extendee_i.has_been_extended() && extension_table.count(extendee_i_base)) {
Node extender_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1));
for (multimap<Node, Node>::iterator i = extension_table.lower_bound(extendee_i_base), E = extension_table.upper_bound(extendee_i_base);
i != E;
++i) {
if (i->second[2].type() == Node::selector_group)
extender_group += i->second[2];
else
extender_group << i->second[2];
}
for (size_t j = 0, S = extender_group.size(); j < S; ++j) {
extended_group << generate_extension(extendee_i, extender_group[j], new_Node);
}
extendee_i.has_been_extended() = true;
}
}
ruleset_to_extend[2] = extended_group;
}
// if (extendee.type() != Node::selector_group && extender.type() != Node::selector_group) {
// Node ext(generate_extension(extendee, extender, new_Node));
// ext.push_front(extendee);
// ruleset_to_extend[2] = ext;
// }
// else if (extendee.type() == Node::selector_group && extender.type() != Node::selector_group) {
// cerr << "extending a group with a singleton!" << endl;
// Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), extendee.size()));
// for (size_t i = 0, S = extendee.size(); i < S; ++i) {
// new_group << extendee[i];
// if (extension_table.count(extendee[i])) {
// new_group << generate_extension(extendee[i], extender, new_Node);
// }
// }
// ruleset_to_extend[2] = new_group;
// }
// else if (extendee.type() != Node::selector_group && extender.type() == Node::selector_group) {
// cerr << "extending a singleton with a group!" << endl;
// }
// else {
// cerr << "skipping this for now!" << endl;
// }
// if (selector_to_extend.type() != Node::selector_group) {
// Node ext(generate_extension(selector_to_extend, extender, new_Node));
// ext.push_front(selector_to_extend);
// ruleset_to_extend[2] = ext;
// }
// else {
// cerr << "possibly extending a selector in a group: " << selector_to_extend.to_string() << endl;
// Node new_group(new_Node(Node::selector_group,
// selector_to_extend.path(),
// selector_to_extend.line(),
// selector_to_extend.size()));
// for (size_t i = 0, S = selector_to_extend.size(); i < S; ++i) {
// Node sel_i(selector_to_extend[i]);
// Node sel_ib(selector_base(sel_i));
// if (extension_table.count(sel_ib)) {
// for (multimap<Node, Node>::iterator i = extension_table.lower_bound(sel_ib); i != extension_table.upper_bound(sel_ib); ++i) {
// if (i->second.is(original_extender)) {
// new_group << sel_i;
// new_group += generate_extension(sel_i, extender, new_Node);
// }
// else {
// cerr << "not what you think is happening!" << endl;
// }
// }
// }
// }
// ruleset_to_extend[2] = new_group;
// }
// if (selector_to_extend.type() != Node::selector) {
// switch (extender.type())
// {
// case Node::simple_selector:
// case Node::attribute_selector:
// case Node::simple_selector_sequence:
// case Node::selector: {
// cerr << "EXTENDING " << selector_to_extend.to_string() << " WITH " << extender.to_string() << endl;
// if (selector_to_extend.type() == Node::selector_group) {
// selector_to_extend << extender;
// }
// else {
// Node new_group(new_Node(Node::selector_group, selector_to_extend.path(), selector_to_extend.line(), 2));
// new_group << selector_to_extend << extender;
// ruleset_to_extend[2] = new_group;
// }
// } break;
// default: {
// // handle the other cases later
// }
// }
// }
// else {
// switch (extender.type())
// {
// case Node::simple_selector:
// case Node::attribute_selector:
// case Node::simple_selector_sequence: {
// Node new_ext(new_Node(selector_to_extend));
// new_ext.back() = extender;
// if (selector_to_extend.type() == Node::selector_group) {
// selector_to_extend << new_ext;
// }
// else {
// Node new_group(new_Node(Node::selector_group, selector_to_extend.path(), selector_to_extend.line(), 2));
// new_group << selector_to_extend << new_ext;
// ruleset_to_extend[2] = new_group;
// }
// } break;
// case Node::selector: {
// Node new_ext1(new_Node(Node::selector, selector_to_extend.path(), selector_to_extend.line(), selector_to_extend.size() + extender.size() - 1));
// Node new_ext2(new_Node(Node::selector, selector_to_extend.path(), selector_to_extend.line(), selector_to_extend.size() + extender.size() - 1));
// new_ext1 += selector_prefix(selector_to_extend, new_Node);
// new_ext1 += extender;
// new_ext2 += selector_prefix(extender, new_Node);
// new_ext2 += selector_prefix(selector_to_extend, new_Node);
// new_ext2 << extender.back();
// if (selector_to_extend.type() == Node::selector_group) {
// selector_to_extend << new_ext1 << new_ext2;
// }
// else {
// Node new_group(new_Node(Node::selector_group, selector_to_extend.path(), selector_to_extend.line(), 2));
// new_group << selector_to_extend << new_ext1 << new_ext2;
// ruleset_to_extend[2] = new_group;
// }
// } break;
// default: {
// // something
// } break;
// }
// }
}
}
// Helper for generating selector extensions; called for each extendee and
// extender in a pair of selector groups.
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: {
new_group << extender;
return new_group;
} break;
default: {
// not sure why selectors sometimes get wrapped in a singleton group
return generate_extension(extendee, extender[0], new_Node);
} break;
}
}
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
cerr << "pux!" << endl;
} break;
}
}
cerr << "blux!" << endl;
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); }
}
#include <map>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
#ifndef SASS_CONTEXT_INCLUDED
#include "context.hpp"
#endif
namespace Sass {
using std::map;
Node eval(Node expr, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx);
Node function_eval(string name, Node stm, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool toplevel = false);
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node);
double operate(Node::Type op, double lhs, double rhs);
Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope = false);
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<pair<string, size_t>, Function>& f_env, Node_Factory& new_Node, Context& ctx);
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> >&, multimap<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
require 'mkmf'
# .. more stuff
#$LIBPATH.push(Config::CONFIG['libdir'])
$CFLAGS << " #{ENV["CFLAGS"]}"
$LIBS << " #{ENV["LIBS"]}"
create_makefile("libsass")
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
#include "node_factory.hpp"
#include "functions.hpp"
#include "error.hpp"
#include <iostream>
#include <cmath>
using std::cerr; using std::endl;
namespace Sass {
namespace Functions {
static void throw_eval_error(string message, string path, size_t line)
{
if (!path.empty() && Prelexer::string_constant(path.c_str()))
path = path.substr(1, path.length() - 1);
throw Error(Error::evaluation, path, line, message);
}
// RGB Functions ///////////////////////////////////////////////////////
Function_Descriptor rgb_descriptor =
{ "rgb", "$red", "$green", "$blue", 0 };
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]);
if (!(r.type() == Node::number && g.type() == Node::number && b.type() == Node::number)) {
throw_eval_error("arguments for rgb must be numbers", r.path(), r.line());
}
return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), 1.0);
}
Function_Descriptor rgba_4_descriptor =
{ "rgba", "$red", "$green", "$blue", "$alpha", 0 };
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]);
Node a(bindings[parameters[3]]);
if (!(r.type() == Node::number && g.type() == Node::number && b.type() == Node::number && a.type() == Node::number)) {
throw_eval_error("arguments for rgba must be numbers", r.path(), r.line());
}
return new_Node(r.path(), r.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value());
}
Function_Descriptor rgba_2_descriptor =
{ "rgba", "$color", "$alpha", 0 };
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
Node r(color[0]);
Node g(color[1]);
Node b(color[2]);
Node a(bindings[parameters[1]]);
if (color.type() != Node::numeric_color || a.type() != Node::number) throw_eval_error("arguments to rgba must be a color and a number", color.path(), color.line());
return new_Node(color.path(), color.line(), r.numeric_value(), g.numeric_value(), b.numeric_value(), a.numeric_value());
}
Function_Descriptor red_descriptor =
{ "red", "$color", 0 };
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to red must be a color", color.path(), color.line());
return color[0];
}
Function_Descriptor green_descriptor =
{ "green", "$color", 0 };
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to green must be a color", color.path(), color.line());
return color[1];
}
Function_Descriptor blue_descriptor =
{ "blue", "$color", 0 };
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to blue must be a color", color.path(), color.line());
return color[2];
}
Node mix_impl(Node color1, Node color2, double weight, Node_Factory& new_Node) {
if (!(color1.type() == Node::numeric_color && color2.type() == Node::numeric_color)) {
throw_eval_error("first two arguments to mix must be colors", color1.path(), color1.line());
}
double p = weight/100;
double w = 2*p - 1;
double a = color1[3].numeric_value() - color2[3].numeric_value();
double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0;
double w2 = 1 - w1;
Node mixed(new_Node(Node::numeric_color, color1.path(), color1.line(), 4));
for (int i = 0; i < 3; ++i) {
mixed << new_Node(mixed.path(), mixed.line(),
w1*color1[i].numeric_value() + w2*color2[i].numeric_value());
}
double alpha = color1[3].numeric_value()*p + color2[3].numeric_value()*(1-p);
mixed << new_Node(mixed.path(), mixed.line(), alpha);
return mixed;
}
Function_Descriptor mix_2_descriptor =
{ "mix", "$color1", "$color2", 0 };
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, new_Node);
}
Function_Descriptor mix_3_descriptor =
{ "mix", "$color1", "$color2", "$weight", 0 };
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node percentage(bindings[parameters[2]]);
if (!(percentage.type() == Node::number || percentage.type() == Node::numeric_percentage || percentage.type() == Node::numeric_dimension)) {
throw_eval_error("third argument to mix must be numeric", percentage.path(), percentage.line());
}
return mix_impl(bindings[parameters[0]],
bindings[parameters[1]],
percentage.numeric_value(),
new_Node);
}
// HSL Functions ///////////////////////////////////////////////////////
double h_to_rgb(double m1, double m2, double h) {
if (h < 0) ++h;
if (h > 1) --h;
if (h*6.0 < 1) return m1 + (m2 - m1)*h*6;
if (h*2.0 < 1) return m2;
if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6;
return m1;
}
Node hsla_impl(double h, double s, double l, double a, Node_Factory& new_Node) {
h = static_cast<double>(((static_cast<int>(h) % 360) + 360) % 360) / 360.0;
s = s / 100.0;
l = l / 100.0;
double m2;
if (l <= 0.5) m2 = l*(s+1.0);
else m2 = l+s-l*s;
double m1 = l*2-m2;
double r = h_to_rgb(m1, m2, h+1.0/3.0) * 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;
return new_Node("", 0, r, g, b, a);
}
Function_Descriptor hsla_descriptor =
{ "hsla", "$hue", "$saturation", "$lightness", "$alpha", 0 };
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric() &&
bindings[parameters[3]].is_numeric())) {
throw_eval_error("arguments to hsla must be numeric", bindings[parameters[0]].path(), bindings[parameters[0]].line());
}
double h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value();
double a = bindings[parameters[3]].numeric_value();
Node color(hsla_impl(h, s, l, a, new_Node));
// color.line() = bindings[parameters[0]].line();
return color;
}
Function_Descriptor hsl_descriptor =
{ "hsl", "$hue", "$saturation", "$lightness", 0 };
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric())) {
throw_eval_error("arguments to hsl must be numeric", bindings[parameters[0]].path(), bindings[parameters[0]].line());
}
double h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value();
Node color(hsla_impl(h, s, l, 1, new_Node));
// color.line() = bindings[parameters[0]].line();
return color;
}
Function_Descriptor invert_descriptor =
{ "invert", "$color", 0 };
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
if (orig.type() != Node::numeric_color) throw_eval_error("argument to invert must be a color", orig.path(), orig.line());
return new_Node(orig.path(), orig.line(),
255 - orig[0].numeric_value(),
255 - orig[1].numeric_value(),
255 - orig[2].numeric_value(),
orig[3].numeric_value());
}
// Opacity Functions ///////////////////////////////////////////////////
Function_Descriptor alpha_descriptor =
{ "alpha", "$color", 0 };
Function_Descriptor opacity_descriptor =
{ "opacity", "$color", 0 };
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
if (color.type() != Node::numeric_color) throw_eval_error("argument to alpha must be a color", color.path(), color.line());
return color[3];
}
Function_Descriptor opacify_descriptor =
{ "opacify", "$color", "$amount", 0 };
Function_Descriptor fade_in_descriptor =
{ "fade_in", "$color", "$amount", 0 };
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
Node delta(bindings[parameters[1]]);
if (color.type() != Node::numeric_color || !delta.is_numeric()) {
throw_eval_error("arguments to opacify/fade_in must be a color and a numeric value", color.path(), color.line());
}
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) {
throw_eval_error("amount must be between 0 and 1 for opacify/fade-in", delta.path(), delta.line());
}
double alpha = color[3].numeric_value() + delta.numeric_value();
if (alpha > 1) alpha = 1;
else if (alpha < 0) alpha = 0;
return new_Node(color.path(), color.line(),
color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), alpha);
}
Function_Descriptor transparentize_descriptor =
{ "transparentize", "$color", "$amount", 0 };
Function_Descriptor fade_out_descriptor =
{ "fade_out", "$color", "$amount", 0 };
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node color(bindings[parameters[0]]);
Node delta(bindings[parameters[1]]);
if (color.type() != Node::numeric_color || !delta.is_numeric()) {
throw_eval_error("arguments to transparentize/fade_out must be a color and a numeric value", color.path(), color.line());
}
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) {
throw_eval_error("amount must be between 0 and 1 for transparentize/fade-out", delta.path(), delta.line());
}
double alpha = color[3].numeric_value() - delta.numeric_value();
if (alpha > 1) alpha = 1;
else if (alpha < 0) alpha = 0;
return new_Node(color.path(), color.line(),
color[0].numeric_value(), color[1].numeric_value(), color[2].numeric_value(), alpha);
}
// String Functions ////////////////////////////////////////////////////
Function_Descriptor unquote_descriptor =
{ "unquote", "$string", 0 };
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node cpy(new_Node(bindings[parameters[0]]));
// if (cpy.type() != Node::string_constant /* && cpy.type() != Node::concatenation */) {
// throw_eval_error("argument to unquote must be a string", cpy.path(), cpy.line());
// }
cpy.is_unquoted() = true;
return cpy;
}
Function_Descriptor quote_descriptor =
{ "quote", "$string", 0 };
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
if (orig.type() != Node::string_constant && orig.type() != Node::identifier) {
throw_eval_error("argument to quote must be a string or identifier", orig.path(), orig.line());
}
Node cpy(new_Node(orig));
cpy.is_unquoted() = false;
return cpy;
}
// Number Functions ////////////////////////////////////////////////////
Function_Descriptor percentage_descriptor =
{ "percentage", "$value", 0 };
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
if (orig.type() != Node::number) {
throw_eval_error("argument to percentage must be a unitless number", orig.path(), orig.line());
}
return new_Node(orig.path(), orig.line(), orig.numeric_value() * 100, Node::numeric_percentage);
}
Function_Descriptor round_descriptor =
{ "round", "$value", 0 };
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value() + 0.5), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value() + 0.5));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value() + 0.5),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to round must be numeric", orig.path(), orig.line());
} break;
}
// unreachable statement
return Node();
}
Function_Descriptor ceil_descriptor =
{ "ceil", "$value", 0 };
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::ceil(orig.numeric_value()), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::ceil(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::ceil(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to ceil must be numeric", orig.path(), orig.line());
} break;
}
// unreachable statement
return Node();
}
Function_Descriptor floor_descriptor =
{ "floor", "$value", 0 };
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value()), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::floor(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to floor must be numeric", orig.path(), orig.line());
} break;
}
// unreachable statement
return Node();
}
Function_Descriptor abs_descriptor =
{ "abs", "$value", 0 };
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node orig(bindings[parameters[0]]);
switch (orig.type())
{
case Node::numeric_dimension: {
return new_Node(orig.path(), orig.line(),
std::abs(orig.numeric_value()), orig.unit());
} break;
case Node::number: {
return new_Node(orig.path(), orig.line(),
std::abs(orig.numeric_value()));
} break;
case Node::numeric_percentage: {
return new_Node(orig.path(), orig.line(),
std::abs(orig.numeric_value()),
Node::numeric_percentage);
} break;
default: {
throw_eval_error("argument to abs must be numeric", orig.path(), orig.line());
} break;
}
// unreachable statement
return Node();
}
// List Functions //////////////////////////////////////////////////////
Function_Descriptor length_descriptor =
{ "length", "$list", 0 };
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node arg(bindings[parameters[0]]);
switch (arg.type())
{
case Node::space_list:
case Node::comma_list: {
return new_Node(arg.path(), arg.line(), arg.size());
} break;
case Node::nil: {
return new_Node(arg.path(), arg.line(), 0);
} break;
default: {
// single objects should be reported as lists of length 1
return new_Node(arg.path(), arg.line(), 1);
} break;
}
// unreachable statement
return Node();
}
Function_Descriptor nth_descriptor =
{ "nth", "$list", "$n", 0 };
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node l(bindings[parameters[0]]);
Node n(bindings[parameters[1]]);
if (n.type() != Node::number) {
throw_eval_error("second argument to nth must be a number", n.path(), n.line());
}
if (l.type() == Node::nil) {
throw_eval_error("cannot index into an empty list", l.path(), l.line());
}
// wrap the first arg if it isn't a list
if (l.type() != Node::space_list && l.type() != Node::comma_list) {
l = new_Node(Node::space_list, l.path(), l.line(), 1) << l;
}
double n_prim = n.numeric_value();
if (n_prim < 1 || n_prim > l.size()) {
throw_eval_error("out of range index for nth", n.path(), n.line());
}
return l[n_prim - 1];
}
extern const char separator_kwd[] = "$separator";
Node join_impl(const vector<Token>& parameters, map<Token, Node>& bindings, bool has_sep, Node_Factory& new_Node) {
// if the args aren't lists, turn them into singleton lists
Node l1(bindings[parameters[0]]);
if (l1.type() != Node::space_list && l1.type() != Node::comma_list && l1.type() != Node::nil) {
l1 = new_Node(Node::space_list, l1.path(), l1.line(), 1) << l1;
}
Node l2(bindings[parameters[1]]);
if (l2.type() != Node::space_list && l2.type() != Node::comma_list && l2.type() != Node::nil) {
l2 = 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);
}
// figure out the combined size in advance
size_t size = 0;
if (l1.type() != Node::nil) size += l1.size();
if (l2.type() != Node::nil) size += l2.size();
// figure out the result type in advance
Node::Type rtype = Node::space_list;
if (has_sep) {
string sep(bindings[parameters[2]].token().unquote());
if (sep == "comma") rtype = Node::comma_list;
else if (sep == "space") rtype = Node::space_list;
else if (sep == "auto") rtype = l1.type();
else {
throw_eval_error("third argument to join must be 'space', 'comma', or 'auto'", l2.path(), l2.line());
}
}
else if (l1.type() != Node::nil) rtype = l1.type();
else if (l2.type() != Node::nil) rtype = l2.type();
// accumulate the result
Node lr(new_Node(rtype, l1.path(), l1.line(), size));
if (l1.type() != Node::nil) lr += l1;
if (l2.type() != Node::nil) lr += l2;
return lr;
}
Function_Descriptor join_2_descriptor =
{ "join", "$list1", "$list2", 0 };
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return join_impl(parameters, bindings, false, new_Node);
}
Function_Descriptor join_3_descriptor =
{ "join", "$list1", "$list2", "$separator", 0 };
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return join_impl(parameters, bindings, true, new_Node);
}
Node append_impl(const vector<Token>& parameters, map<Token, Node>& bindings, bool has_sep, Node_Factory& new_Node) {
Node list(bindings[parameters[0]]);
switch (list.type())
{
case Node::space_list:
case Node::comma_list:
case Node::nil: {
// do nothing
} break;
// if the first arg isn't a list, wrap it in a singleton
default: {
list = (new_Node(Node::space_list, list.path(), list.line(), 1) << list);
} break;
}
Node::Type sep_type = list.type();
if (has_sep) {
string sep_string = bindings[parameters[2]].token().unquote();
if (sep_string == "comma") sep_type = Node::comma_list;
else if (sep_string == "space") sep_type = Node::space_list;
else if (sep_string == "auto") sep_type = list.type();
else throw_eval_error("third argument to append must be 'space', 'comma', or 'auto'", list.path(), list.line());
}
Node new_list(new_Node(sep_type, list.path(), list.line(), list.size() + 1));
new_list += list;
new_list << bindings[parameters[1]];
return new_list;
}
Function_Descriptor append_2_descriptor =
{ "append", "$list", "$val", 0 };
Node append_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return append_impl(parameters, bindings, false, new_Node);
}
Function_Descriptor append_3_descriptor =
{ "append", "$list", "$val", "$separator", 0 };
Node append_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
return append_impl(parameters, bindings, true, new_Node);
}
Node compact(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
size_t num_args = bindings.size();
Node::Type sep_type = Node::comma_list;
Node list;
Node arg1(bindings[parameters[0]]);
if (num_args == 1 && (arg1.type() == Node::space_list ||
arg1.type() == Node::comma_list ||
arg1.type() == Node::nil)) {
list = new_Node(arg1.type(), arg1.path(), arg1.line(), arg1.size());
list += arg1;
}
else {
list = new_Node(sep_type, arg1.path(), arg1.line(), num_args);
for (size_t i = 0; i < num_args; ++i) {
list << bindings[parameters[i]];
}
}
Node new_list(new_Node(list.type(), list.path(), list.line(), 0));
for (size_t i = 0, S = list.size(); i < S; ++i) {
if ((list[i].type() != Node::boolean) || list[i].boolean_value()) {
new_list << list[i];
}
}
return new_list.size() ? new_list : new_Node(Node::nil, list.path(), list.line(), 0);
}
Function_Descriptor compact_1_descriptor =
{ "compact", "$arg1", 0 };
Function_Descriptor compact_2_descriptor =
{ "compact", "$arg1", "$arg2", 0 };
Function_Descriptor compact_3_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", 0 };
Function_Descriptor compact_4_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", 0 };
Function_Descriptor compact_5_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5", 0 };
Function_Descriptor compact_6_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", 0 };
Function_Descriptor compact_7_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", 0 };
Function_Descriptor compact_8_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", "$arg8", 0 };
Function_Descriptor compact_9_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", "$arg8", "$arg9", 0 };
Function_Descriptor compact_10_descriptor =
{ "compact", "$arg1", "$arg2", "$arg3", "$arg4", "$arg5",
"$arg6", "$arg7", "$arg8", "$arg9", "$arg10", 0 };
// Introspection Functions /////////////////////////////////////////////
extern const char number_name[] = "number";
extern const char string_name[] = "string";
extern const char bool_name[] = "bool";
extern const char color_name[] = "color";
extern const char list_name[] = "list";
Function_Descriptor type_of_descriptor =
{ "type-of", "$value", 0 };
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
Token type_name;
switch (val.type())
{
case Node::number:
case Node::numeric_dimension:
case Node::numeric_percentage: {
type_name = Token::make(number_name);
} break;
case Node::boolean: {
type_name = Token::make(bool_name);
} break;
case Node::string_constant:
case Node::value_schema: {
type_name = Token::make(string_name);
} break;
case Node::numeric_color: {
type_name = Token::make(color_name);
} break;
case Node::comma_list:
case Node::space_list:
case Node::nil: {
type_name = Token::make(list_name);
} break;
default: {
type_name = Token::make(string_name);
} break;
}
Node type(new_Node(Node::string_constant, val.path(), val.line(), type_name));
type.is_unquoted() = true;
return type;
}
extern const char empty_str[] = "";
extern const char percent_str[] = "%";
Function_Descriptor unit_descriptor =
{ "unit", "$number", 0 };
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
switch (val.type())
{
case Node::number: {
return new_Node(Node::string_constant, val.path(), val.line(), Token::make(empty_str));
} break;
case Node::numeric_dimension:
case Node::numeric_percentage: {
return new_Node(Node::string_constant, val.path(), val.line(), val.unit());
} break;
default: {
throw_eval_error("argument to unit must be numeric", val.path(), val.line());
} break;
}
// unreachable statement
return Node();
}
extern const char true_str[] = "true";
extern const char false_str[] = "false";
Function_Descriptor unitless_descriptor =
{ "unitless", "$number", 0 };
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
switch (val.type())
{
case Node::number: {
return new_Node(Node::boolean, val.path(), val.line(), true);
} break;
case Node::numeric_percentage:
case Node::numeric_dimension: {
return new_Node(Node::boolean, val.path(), val.line(), false);
} break;
default: {
throw_eval_error("argument to unitless must be numeric", val.path(), val.line());
} break;
}
// unreachable statement
return Node();
}
Function_Descriptor comparable_descriptor =
{ "comparable", "$number_1", "$number_2", 0 };
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node n1(bindings[parameters[0]]);
Node n2(bindings[parameters[1]]);
Node::Type t1 = n1.type();
Node::Type t2 = n2.type();
if ((t1 == Node::number && n2.is_numeric()) ||
(n1.is_numeric() && t2 == Node::number)) {
return new_Node(Node::boolean, n1.path(), n1.line(), true);
}
else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) {
return new_Node(Node::boolean, n1.path(), n1.line(), true);
}
else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) {
string u1(n1.unit().to_string());
string u2(n2.unit().to_string());
if ((u1 == "ex" && u2 == "ex") ||
(u1 == "em" && u2 == "em") ||
((u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc"))) {
return new_Node(Node::boolean, n1.path(), n1.line(), true);
}
else {
return new_Node(Node::boolean, n1.path(), n1.line(), false);
}
}
else if (!n1.is_numeric() && !n2.is_numeric()) {
throw_eval_error("arguments to comparable must be numeric", n1.path(), n1.line());
}
// default to false if we missed anything
return new_Node(Node::boolean, n1.path(), n1.line(), false);
}
// Boolean Functions ///////////////////////////////////////////////////
Function_Descriptor not_descriptor =
{ "not", "value", 0 };
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node) {
Node val(bindings[parameters[0]]);
if (val.type() == Node::boolean && val.boolean_value() == false) {
return new_Node(Node::boolean, val.path(), val.line(), true);
}
else {
return new_Node(Node::boolean, val.path(), val.line(), false);
}
}
}
}
#include <cstring>
#include <map>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
namespace Sass {
using std::map;
typedef Node (*Primitive)(const vector<Token>&, map<Token, Node>&, Node_Factory& new_Node);
typedef const char* str;
typedef str Function_Descriptor[];
struct Function {
string name;
vector<Token> parameters;
Node definition;
Primitive primitive;
Function()
{ /* TO DO: set up the generic callback here */ }
Function(Node def)
: name(def[0].to_string()),
parameters(vector<Token>()),
definition(def),
primitive(0)
{ }
Function(Function_Descriptor d, Primitive ip)
: name(d[0]),
parameters(vector<Token>()),
definition(Node()),
primitive(ip)
{
size_t len = 0;
while (d[len+1]) ++len;
parameters.reserve(len);
for (size_t i = 0; i < len; ++i) {
const char* p = d[i+1];
Token name(Token::make(p, p + std::strlen(p)));
parameters.push_back(name);
}
}
Node operator()(map<Token, Node>& bindings, Node_Factory& new_Node) const
{
if (primitive) return primitive(parameters, bindings, new_Node);
else return Node();
}
};
namespace Functions {
// RGB Functions ///////////////////////////////////////////////////////
extern Function_Descriptor rgb_descriptor;
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor rgba_4_descriptor;
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor rgba_2_descriptor;
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor red_descriptor;
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor green_descriptor;
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor blue_descriptor;
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor mix_2_descriptor;
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor mix_3_descriptor;
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// HSL Functions ///////////////////////////////////////////////////////
extern Function_Descriptor hsla_descriptor;
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor hsl_descriptor;
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor invert_descriptor;
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Opacity Functions ///////////////////////////////////////////////////
extern Function_Descriptor alpha_descriptor;
extern Function_Descriptor opacity_descriptor;
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor opacify_descriptor;
extern Function_Descriptor fade_in_descriptor;
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor transparentize_descriptor;
extern Function_Descriptor fade_out_descriptor;
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// String Functions ////////////////////////////////////////////////////
extern Function_Descriptor unquote_descriptor;
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor quote_descriptor;
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Number Functions ////////////////////////////////////////////////////
extern Function_Descriptor percentage_descriptor;
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor round_descriptor;
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor ceil_descriptor;
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor floor_descriptor;
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor abs_descriptor;
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// List Functions //////////////////////////////////////////////////////
extern Function_Descriptor length_descriptor;
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor nth_descriptor;
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor join_2_descriptor;
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor join_3_descriptor;
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor append_2_descriptor;
Node append_2(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor append_3_descriptor;
Node append_3(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor compact_1_descriptor;
extern Function_Descriptor compact_2_descriptor;
extern Function_Descriptor compact_3_descriptor;
extern Function_Descriptor compact_4_descriptor;
extern Function_Descriptor compact_5_descriptor;
extern Function_Descriptor compact_6_descriptor;
extern Function_Descriptor compact_7_descriptor;
extern Function_Descriptor compact_8_descriptor;
extern Function_Descriptor compact_9_descriptor;
extern Function_Descriptor compact_10_descriptor;
Node compact(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Introspection Functions /////////////////////////////////////////////
extern Function_Descriptor type_of_descriptor;
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor unit_descriptor;
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor unitless_descriptor;
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
extern Function_Descriptor comparable_descriptor;
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
// Boolean Functions ///////////////////////////////////////////////////
extern Function_Descriptor not_descriptor;
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, Node_Factory& new_Node);
}
}
#include <sstream>
#include <algorithm>
#include "node.hpp"
#include "error.hpp"
#include <iostream>
namespace Sass {
using namespace std;
// ------------------------------------------------------------------------
// Node method implementations
// ------------------------------------------------------------------------
void Node::flatten()
{
switch (type())
{
case block:
case expansion:
case root:
case for_through_directive:
case for_to_directive:
case each_directive:
case while_directive:
break;
default:
return;
}
// size can change during flattening, so we need to call size() on each pass
for (size_t i = 0; i < size(); ++i) {
switch (at(i).type())
{
case expansion:
case block:
case for_through_directive:
case for_to_directive:
case each_directive:
case while_directive: {
Node expn(at(i));
if (expn.has_expansions()) expn.flatten();
ip_->has_statements |= expn.has_statements();
ip_->has_blocks |= expn.has_blocks();
ip_->has_expansions |= expn.has_expansions();
// TO DO: make this more efficient -- replace with a dummy node instead of erasing
ip_->children.erase(begin() + i);
insert(begin() + i, expn.begin(), expn.end());
// skip over what we just spliced in
i += expn.size() - 1;
} break;
default: {
} break;
}
}
}
bool Node::operator==(Node rhs) const
{
Type t = type();
if (t != rhs.type()) return false;
switch (t)
{
case comma_list:
case space_list:
case expression:
case term:
case numeric_color: {
if (size() != rhs.size()) return false;
for (size_t i = 0, L = size(); i < L; ++i) {
if (at(i) == rhs[i]) continue;
else return false;
}
return true;
} break;
case variable:
case identifier:
case uri:
case textual_percentage:
case textual_dimension:
case textual_number:
case textual_hex:
case string_constant: {
return token().unquote() == rhs.token().unquote();
} break;
case number:
case numeric_percentage: {
return numeric_value() == rhs.numeric_value();
} break;
case numeric_dimension: {
if (unit() == rhs.unit()) {
return numeric_value() == rhs.numeric_value();
}
else {
return false;
}
} break;
case boolean: {
return boolean_value() == rhs.boolean_value();
} break;
default: {
return true;
} break;
}
return false;
}
bool Node::operator!=(Node rhs) const
{ return !(*this == rhs); }
bool Node::operator<(Node rhs) const
{
Type lhs_type = type();
Type rhs_type = rhs.type();
// comparing atomic numbers
if ((lhs_type == number && rhs_type == number) ||
(lhs_type == numeric_percentage && rhs_type == numeric_percentage)) {
return numeric_value() < rhs.numeric_value();
}
// comparing numbers with units
else if (lhs_type == numeric_dimension && rhs_type == numeric_dimension) {
if (unit() == rhs.unit()) {
return numeric_value() < rhs.numeric_value();
}
else {
throw Error(Error::evaluation, path(), line(), "incompatible units");
}
}
// comparing colors
else if (lhs_type == numeric_color && rhs_type == numeric_color) {
return lexicographical_compare(begin(), end(), rhs.begin(), rhs.end());
}
// 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;
}
}
// END OF SELECTOR COMPARISON
// catch-all
else {
throw Error(Error::evaluation, path(), line(), "incomparable types");
}
}
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); }
// ------------------------------------------------------------------------
// Token method implementations
// ------------------------------------------------------------------------
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;
}
// ------------------------------------------------------------------------
// Node_Impl method implementations
// ------------------------------------------------------------------------
double Node_Impl::numeric_value()
{
switch (type)
{
case Node::number:
case Node::numeric_percentage:
return value.numeric;
case Node::numeric_dimension:
return value.dimension.numeric;
default:
break;
// throw an exception?
}
// if you reach this point, you've got a logic error somewhere
return 0;
}
extern const char percent_str[] = "%";
extern const char empty_str[] = "";
Token Node_Impl::unit()
{
switch (type)
{
case Node::numeric_percentage: {
return Token::make(percent_str);
} break;
case Node::numeric_dimension: {
return value.dimension.unit;
} break;
default: break;
}
return Token::make(empty_str);
}
}
\ No newline at end of file
#define SASS_NODE_INCLUDED
#include <cstring>
#include <string>
#include <vector>
#include <iostream>
namespace Sass {
using namespace std;
struct Token {
const char* begin;
const char* end;
// Need Token::make(...) because tokens are union members, and hence they
// can't have non-trivial constructors.
static Token make()
{
Token t;
t.begin = 0;
t.end = 0;
return t;
}
static Token make(const char* s)
{
Token t;
t.begin = s;
t.end = s + std::strlen(s);
return t;
}
static Token make(const char* b, const char* e)
{
Token t;
t.begin = b;
t.end = e;
return t;
}
size_t length() const
{ return end - begin; }
string to_string() const
{ return string(begin, end - begin); }
string unquote() const;
void unquote_to_stream(std::stringstream& buf) const;
operator bool()
{ return begin && end && begin >= end; }
bool operator<(const Token& rhs) const;
bool operator==(const Token& rhs) const;
};
struct Dimension {
double numeric;
Token unit;
};
struct Node_Impl;
class Node {
private:
friend class Node_Factory;
Node_Impl* ip_;
public:
enum Type {
none,
comment,
root,
ruleset,
propset,
media_query,
selector_group,
selector,
selector_combinator,
simple_selector_sequence,
backref,
simple_selector,
type_selector,
class_selector,
id_selector,
pseudo,
pseudo_negation,
functional_pseudo,
attribute_selector,
selector_schema,
media_expression_group,
media_expression,
block,
rule,
property,
nil,
comma_list,
space_list,
disjunction,
conjunction,
relation,
eq,
neq,
gt,
gte,
lt,
lte,
expression,
add,
sub,
term,
mul,
div,
factor,
unary_plus,
unary_minus,
values,
value,
identifier,
uri,
textual_percentage,
textual_dimension,
textual_number,
textual_hex,
color_name,
string_constant,
concatenation,
number,
numeric_percentage,
numeric_dimension,
numeric_color,
boolean,
important,
value_schema,
string_schema,
identifier_schema,
css_import,
function_call,
mixin,
function,
parameters,
expansion,
arguments,
if_directive,
for_through_directive,
for_to_directive,
each_directive,
while_directive,
return_directive,
content_directive,
warning,
variable,
assignment
};
Node(Node_Impl* ip = 0);
Type type() const;
bool has_children() const;
bool has_statements() const;
bool has_blocks() const;
bool has_expansions() const;
bool has_backref() const;
bool from_variable() const;
bool& should_eval() const;
bool& is_unquoted() const;
bool is_numeric() const;
bool is_guarded() const;
bool& has_been_extended() 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 { return !ip_; }
bool is(Node n) const { return ip_ == n.ip_; }
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(Type inside_of = none) const;
void emit_nested_css(stringstream& buf, size_t depth, bool at_toplevel = false, bool in_media_query = 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_statements;
bool has_blocks;
bool has_expansions;
bool has_backref;
bool from_variable;
bool should_eval;
bool is_unquoted;
bool has_been_extended;
Node_Impl()
: /* value(value_t()),
children(vector<Node>()),
path(string()),
line(0),
type(Node::none), */
has_children(false),
has_statements(false),
has_blocks(false),
has_expansions(false),
has_backref(false),
from_variable(false),
should_eval(false),
is_unquoted(false),
has_been_extended(false)
{ }
bool is_numeric()
{ return type >= Node::number && type <= Node::numeric_dimension; }
size_t size()
{ return children.size(); }
bool empty()
{ return children.empty(); }
Node& at(size_t i)
{ return children.at(i); }
Node& back()
{ return children.back(); }
void push_back(const Node& n)
{
children.push_back(n);
has_children = true;
switch (n.type())
{
case Node::comment:
case Node::css_import:
case Node::rule:
case Node::propset:
case Node::warning: has_statements = true; break;
case Node::media_query:
case Node::ruleset: has_blocks = true; break;
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;
}
void push_front(const Node& n)
{
children.insert(children.begin(), n);
has_children = true;
switch (n.type())
{
case Node::comment:
case Node::css_import:
case Node::rule:
case Node::propset: has_statements = true; break;
case Node::media_query:
case Node::ruleset: has_blocks = true; break;
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;
}
void pop_back()
{ children.pop_back(); }
bool& boolean_value()
{ return value.boolean; }
double numeric_value();
Token unit();
};
// ------------------------------------------------------------------------
// Node method implementations
// -- in the header file so they can easily be declared inline
// -- outside of their class definition to get the right declaration order
// ------------------------------------------------------------------------
inline Node::Node(Node_Impl* ip) : ip_(ip) { }
inline Node::Type Node::type() const { return ip_->type; }
inline bool Node::has_children() const { return ip_->has_children; }
inline bool Node::has_statements() const { return ip_->has_statements; }
inline bool Node::has_blocks() const { return ip_->has_blocks; }
inline bool Node::has_expansions() const { return ip_->has_expansions; }
inline bool Node::has_backref() const { return ip_->has_backref; }
inline bool Node::from_variable() const { return ip_->from_variable; }
inline bool& Node::should_eval() const { return ip_->should_eval; }
inline bool& Node::is_unquoted() const { return ip_->is_unquoted; }
inline bool Node::is_numeric() const { return ip_->is_numeric(); }
inline bool Node::is_guarded() const { return (type() == assignment) && (size() == 3); }
inline bool& Node::has_been_extended() const { return ip_->has_been_extended; }
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(); }
}
#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(Type inside_of) const
{
switch (type())
{
case selector_group:
case media_expression_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 media_expression: {
string result;
if (at(0).type() == rule) {
result += "(";
result += at(0).to_string();
result += ")";
}
else {
result += at(0).to_string();
}
for (size_t i = 1, S = size(); i < S; ++i) {
result += " ";
if (at(i).type() == rule) {
result += "(";
result += at(i).to_string();
result += ")";
}
else {
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 rule: {
string result(at(0).to_string(property));
result += ": ";
result += at(1).to_string();
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() != 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();
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:
case identifier_schema: {
string result;
for (size_t i = 0, S = size(); i < S; ++i) {
if (at(i).type() == string_constant) {
result += at(i).token().unquote();
}
else {
result += at(i).to_string(identifier_schema);
}
}
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;
case concatenation: {
string result;
for (size_t i = 0, S = size(); i < S; ++i) {
result += at(i).to_string().substr(1, at(i).token().length()-2);
}
if (inside_of == identifier_schema || inside_of == property) return result;
else return "\"" + result + "\"";
} break;
case warning: {
string prefix("WARNING: ");
string indent(" ");
Node contents(at(0));
string result(contents.to_string());
if (contents.type() == string_constant || contents.type() == string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string
}
// These cerrs aren't log lines! They're supposed to be here!
cerr << prefix << result << endl;
cerr << indent << "on line " << at(0).line() << " of " << at(0).path();
cerr << endl << endl;
return "";
} 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, bool in_media_query)
{
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 || stm_type == warning) {
block[i].emit_nested_css(buf, depth+1);
}
}
buf << " }";
if (!in_media_query || (in_media_query && block.has_blocks())) 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].type() == media_query) {
block[i].emit_nested_css(buf, depth, false, in_media_query);
}
}
}
if (block.has_statements()) --depth; // see previous comment
if ((depth == 0) && at_toplevel && !in_media_query) buf << endl;
} break;
case media_query: {
buf << string(2*depth, ' ');
buf << "@media " << at(0).to_string() << " {" << endl;
at(1).emit_nested_css(buf, depth+1, false, true);
buf << " }" << endl;
} break;
case propset: {
emit_propset(buf, depth, "");
} break;
case rule: {
buf << endl << string(2*depth, ' ');
buf << to_string();
// at(0).emit_nested_css(buf, depth); // property
// at(1).emit_nested_css(buf, depth); // values
buf << ";";
} break;
case css_import: {
buf << string(2*depth, ' ');
buf << to_string();
buf << ";" << endl;
} break;
case property: {
buf << token().to_string() << ": ";
} break;
case values: {
for (size_t i = 0, S = size(); i < S; ++i) {
buf << " " << at(i).token().to_string();
}
} break;
case comment: {
if (depth != 0) buf << endl;
buf << string(2*depth, ' ') << token().to_string();
if (depth == 0) buf << endl;
} break;
default: {
buf << to_string();
} break;
}
}
void Node::emit_propset(stringstream& buf, size_t depth, const string& prefix)
{
string new_prefix(prefix);
// bool has_prefix = false;
if (new_prefix.empty()) {
new_prefix += "\n";
new_prefix += string(2*depth, ' ');
new_prefix += at(0).token().to_string();
}
else {
new_prefix += "-";
new_prefix += at(0).token().to_string();
// has_prefix = true;
}
Node rules(at(1));
for (size_t i = 0, S = rules.size(); i < S; ++i) {
if (rules[i].type() == propset) {
rules[i].emit_propset(buf, depth+1, new_prefix);
}
else {
buf << new_prefix;
if (rules[i][0].token().to_string() != "") buf << '-';
rules[i][0].emit_nested_css(buf, depth);
rules[i][1].emit_nested_css(buf, depth);
buf << ';';
}
}
}
void Node::echo(stringstream& buf, size_t depth) { }
void Node::emit_expanded_css(stringstream& buf, const string& prefix) { }
}
\ No newline at end of file
#include "node_factory.hpp"
namespace Sass {
Node_Impl* Node_Factory::alloc_Node_Impl(Node::Type type, string path, size_t line)
{
Node_Impl* ip = new Node_Impl();
ip->type = type;
if (type == Node::backref) ip->has_backref = true;
ip->path = path;
ip->line = line;
pool_.push_back(ip);
return ip;
}
// returns a deep-copy of its argument
Node_Impl* Node_Factory::alloc_Node_Impl(Node_Impl* ip)
{
Node_Impl* ip_cpy = new Node_Impl(*ip);
pool_.push_back(ip_cpy);
if (ip_cpy->has_children) {
for (size_t i = 0, S = ip_cpy->size(); i < S; ++i) {
Node n(ip_cpy->at(i));
ip_cpy->at(i) = (*this)(n);
}
}
return ip_cpy;
}
// for cloning nodes
Node Node_Factory::operator()(const Node& n1)
{
Node_Impl* ip_cpy = alloc_Node_Impl(n1.ip_); // deep-copy the implementation object
return Node(ip_cpy);
}
// for making leaf nodes out of terminals/tokens
Node Node_Factory::operator()(Node::Type type, string path, size_t line, Token t)
{
Node_Impl* ip = alloc_Node_Impl(type, path, line);
ip->value.token = t;
return Node(ip);
}
// for making boolean values or interior nodes that have children
Node Node_Factory::operator()(Node::Type type, string path, size_t line, size_t size)
{
Node_Impl* ip = alloc_Node_Impl(type, path, line);
if (type == Node::boolean) ip->value.boolean = size;
else ip->children.reserve(size);
return Node(ip);
}
// for making nodes representing numbers
Node Node_Factory::operator()(string path, size_t line, double v, Node::Type type)
{
Node_Impl* ip = alloc_Node_Impl(type, path, line);
ip->value.numeric = v;
return Node(ip);
}
// for making nodes representing numeric dimensions (e.g. 5px, 3em)
Node Node_Factory::operator()(string path, size_t line, double v, const Token& t)
{
Node_Impl* ip = alloc_Node_Impl(Node::numeric_dimension, path, line);
ip->value.dimension.numeric = v;
ip->value.dimension.unit = t;
return Node(ip);
}
// for making nodes representing rgba color quads
Node Node_Factory::operator()(string path, size_t line, double r, double g, double b, double a)
{
Node color((*this)(Node::numeric_color, path, line, 4));
color << (*this)(path, line, r)
<< (*this)(path, line, g)
<< (*this)(path, line, b)
<< (*this)(path, line, a);
return color;
}
void Node_Factory::free()
{ for (size_t i = 0, S = pool_.size(); i < S; ++i) delete pool_[i]; }
}
\ No newline at end of file
#include <vector>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
namespace Sass {
using namespace std;
struct Token;
struct Node_Impl;
class Node_Factory {
vector<Node_Impl*> pool_;
Node_Impl* alloc_Node_Impl(Node::Type type, string file, size_t line);
// returns a deep-copy of its argument
Node_Impl* alloc_Node_Impl(Node_Impl* ip);
public:
// for cloning nodes
Node operator()(const Node& n1);
// for making leaf nodes out of terminals/tokens
Node operator()(Node::Type type, string file, size_t line, Token t);
// for making boolean values or interior nodes that have children
Node operator()(Node::Type type, string file, size_t line, size_t size);
// // for making nodes representing boolean values
// Node operator()(Node::Type type, string file, size_t line, bool b);
// for making nodes representing numbers
Node operator()(string file, size_t line, double v, Node::Type type = Node::number);
// for making nodes representing numeric dimensions (e.g. 5px, 3em)
Node operator()(string file, size_t line, double v, const Token& t);
// for making nodes representing rgba color quads
Node operator()(string file, size_t line, double r, double g, double b, double a = 1.0);
void free();
};
}
\ No newline at end of file
#include <cctype>
#include "prelexer.hpp"
namespace Sass {
namespace Prelexer {
// Matches zero characters (always succeeds without consuming input).
const char* epsilon(char *src) {
return src;
}
// Matches the empty string.
const char* empty(char *src) {
return *src ? 0 : src;
}
// Match any single character.
const char* any_char(const char* src) { return *src ? src++ : src; }
// Match a single character satisfying the ctype predicates.
const char* space(const char* src) { return std::isspace(*src) ? src+1 : 0; }
const char* alpha(const char* src) { return std::isalpha(*src) ? src+1 : 0; }
const char* digit(const char* src) { return std::isdigit(*src) ? src+1 : 0; }
const char* xdigit(const char* src) { return std::isxdigit(*src) ? src+1 : 0; }
const char* alnum(const char* src) { return std::isalnum(*src) ? src+1 : 0; }
const char* punct(const char* src) { return std::ispunct(*src) ? src+1 : 0; }
// Match multiple ctype characters.
const char* spaces(const char* src) { return one_plus<space>(src); }
const char* alphas(const char* src) { return one_plus<alpha>(src); }
const char* digits(const char* src) { return one_plus<digit>(src); }
const char* xdigits(const char* src) { return one_plus<xdigit>(src); }
const char* alnums(const char* src) { return one_plus<alnum>(src); }
const char* puncts(const char* src) { return one_plus<punct>(src); }
// Match a line comment.
extern const char slash_slash[] = "//";
const char* line_comment(const char* src) { return to_endl<slash_slash>(src); }
// Match a block comment.
extern const char slash_star[] = "/*";
extern const char star_slash[] = "*/";
const char* block_comment(const char* src) {
return sequence< optional_spaces, delimited_by<slash_star, star_slash, false> >(src);
}
// Match either comment.
const char* comment(const char* src) {
return alternatives<block_comment, line_comment>(src);
}
// Match double- and single-quoted strings.
const char* double_quoted_string(const char* src) {
return delimited_by<'"', '"', true>(src);
}
const char* single_quoted_string(const char* src) {
return delimited_by<'\'', '\'', true>(src);
}
const char* string_constant(const char* src) {
return alternatives<double_quoted_string, single_quoted_string>(src);
}
// Match interpolants.
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
const char* interpolant(const char* src) {
return delimited_by<hash_lbrace, rbrace, false>(src);
}
// Whitespace handling.
const char* optional_spaces(const char* src) { return optional<spaces>(src); }
const char* optional_comment(const char* src) { return optional<comment>(src); }
const char* spaces_and_comments(const char* src) {
return zero_plus< alternatives<spaces, comment> >(src);
}
const char* no_spaces(const char* src) {
return negate< spaces >(src);
}
// Match CSS identifiers.
const char* identifier(const char* src) {
return sequence< optional< exactly<'-'> >,
alternatives< alpha, exactly<'_'> >,
zero_plus< alternatives< alnum,
exactly<'-'>,
exactly<'_'> > > >(src);
}
// Match interpolant schemas
const char* identifier_schema(const char* src) {
// follows this pattern: (x*ix*)+ ... well, not quite
return one_plus< sequence< zero_plus< alternatives< identifier, exactly<'-'> > >,
interpolant,
zero_plus< alternatives< identifier, number, exactly<'-'> > > > >(src);
}
const char* value_schema(const char* src) {
// follows this pattern: ([xyz]*i[xyz]*)+
return one_plus< sequence< zero_plus< alternatives< identifier, percentage, dimension, hex, number, string_constant > >,
interpolant,
zero_plus< alternatives< identifier, percentage, dimension, hex, number, string_constant > > > >(src);
}
// Match CSS '@' keywords.
const char* at_keyword(const char* src) {
return sequence<exactly<'@'>, identifier>(src);
}
extern const char import_kwd[] = "@import";
const char* import(const char* src) {
return exactly<import_kwd>(src);
}
extern const char media_kwd[] = "@media";
const char* media(const char* src) {
return exactly<media_kwd>(src);
}
extern const char mixin_kwd[] = "@mixin";
const char* mixin(const char* src) {
return exactly<mixin_kwd>(src);
}
extern const char function_kwd[] = "@function";
const char* function(const char* src) {
return exactly<function_kwd>(src);
}
extern const char return_kwd[] = "@return";
const char* return_directive(const char* src) {
return exactly<return_kwd>(src);
}
extern const char include_kwd[] = "@include";
const char* include(const char* src) {
return exactly<include_kwd>(src);
}
extern const char extend_kwd[] = "@extend";
const char* extend(const char* src) {
return exactly<extend_kwd>(src);
}
extern const char if_kwd[] = "@if";
extern const char if_chars[] = "if";
const char* if_directive(const char* src) {
return exactly<if_kwd>(src);
}
extern const char else_kwd[] = "@else";
const char* else_directive(const char* src) {
return exactly<else_kwd>(src);
}
const char* elseif_directive(const char* src) {
return sequence< else_directive,
spaces_and_comments,
exactly< if_chars > >(src);
}
extern const char for_kwd[] = "@for";
const char* for_directive(const char* src) {
return exactly<for_kwd>(src);
}
extern const char from_kwd[] = "from";
const char* from(const char* src) {
return exactly<from_kwd>(src);
}
extern const char to_kwd[] = "to";
const char* to(const char* src) {
return exactly<to_kwd>(src);
}
extern const char through_kwd[] = "through";
const char* through(const char* src) {
return exactly<through_kwd>(src);
}
extern const char each_kwd[] = "@each";
const char* each_directive(const char* src) {
return exactly<each_kwd>(src);
}
extern const char in_kwd[] = "in";
const char* in(const char* src) {
return exactly<in_kwd>(src);
}
extern const char while_kwd[] = "@while";
const char* while_directive(const char* src) {
return exactly<while_kwd>(src);
}
const char* name(const char* src) {
return one_plus< alternatives< alnum,
exactly<'-'>,
exactly<'_'> > >(src);
}
extern const char warn_kwd[] = "@warn";
const char* warn(const char* src) {
return exactly<warn_kwd>(src);
}
// Match CSS type selectors
const char* namespace_prefix(const char* src) {
return sequence< optional< alternatives< identifier, exactly<'*'> > >,
exactly<'|'> >(src);
}
const char* type_selector(const char* src) {
return sequence< optional<namespace_prefix>, identifier>(src);
}
const char* universal(const char* src) {
return sequence< optional<namespace_prefix>, exactly<'*'> >(src);
}
// Match CSS id names.
const char* id_name(const char* src) {
return sequence<exactly<'#'>, name>(src);
}
// Match CSS class names.
const char* class_name(const char* src) {
return sequence<exactly<'.'>, identifier>(src);
}
// Match CSS numeric constants.
extern const char sign_chars[] = "-+";
const char* sign(const char* src) {
return class_char<sign_chars>(src);
}
const char* unsigned_number(const char* src) {
return alternatives<sequence< zero_plus<digits>,
exactly<'.'>,
one_plus<digits> >,
digits>(src);
}
const char* number(const char* src) {
return sequence< optional<sign>, unsigned_number>(src);
}
const char* coefficient(const char* src) {
return alternatives< sequence< optional<sign>, digits >,
sign >(src);
}
const char* binomial(const char* src) {
return sequence< optional<sign>,
optional<digits>,
exactly<'n'>, optional_spaces,
sign, optional_spaces,
digits >(src);
}
const char* percentage(const char* src) {
return sequence< number, exactly<'%'> >(src);
}
extern const char em_kwd[] = "em";
extern const char ex_kwd[] = "ex";
extern const char px_kwd[] = "px";
extern const char cm_kwd[] = "cm";
extern const char mm_kwd[] = "mm";
// extern const char in_kwd[] = "in";
extern const char pt_kwd[] = "pt";
extern const char pc_kwd[] = "pc";
extern const char deg_kwd[] = "deg";
extern const char rad_kwd[] = "rad";
extern const char grad_kwd[] = "grad";
extern const char ms_kwd[] = "ms";
extern const char s_kwd[] = "s";
extern const char Hz_kwd[] = "Hz";
extern const char kHz_kwd[] = "kHz";
const char* em(const char* src) {
return sequence< number, exactly<em_kwd> >(src);
}
const char* dimension(const char* src) {
return sequence<number, identifier>(src);
}
const char* hex(const char* src) {
const char* p = sequence< exactly<'#'>, one_plus<xdigit> >(src);
int len = p - src;
return (len != 4 && len != 7) ? 0 : p;
}
extern const char rgb_kwd[] = "rgb(";
const char* rgb_prefix(const char* src) {
return exactly<rgb_kwd>(src);
}
// Match CSS uri specifiers.
extern const char url_kwd[] = "url(";
const char* uri_prefix(const char* src) {
return exactly<url_kwd>(src);
}
const char* uri(const char* src) {
return sequence< exactly<url_kwd>,
optional<spaces>,
string_constant,
optional<spaces>,
exactly<')'> >(src);
}
// Match CSS "!important" keyword.
extern const char important_kwd[] = "important";
const char* important(const char* src) {
return sequence< exactly<'!'>,
spaces_and_comments,
exactly<important_kwd> >(src);
}
// Match Sass "!default" keyword.
extern const char default_kwd[] = "default";
const char* default_flag(const char* src) {
return sequence< exactly<'!'>,
spaces_and_comments,
exactly<default_kwd> >(src);
}
// Match CSS pseudo-class/element prefixes.
const char* pseudo_prefix(const char* src) {
return sequence< exactly<':'>, optional< exactly<':'> > >(src);
}
// Match CSS function call openers.
const char* functional(const char* src) {
return sequence< identifier, exactly<'('> >(src);
}
// Match the CSS negation pseudo-class.
extern const char pseudo_not_chars[] = ":not(";
const char* pseudo_not(const char* src) {
return exactly< pseudo_not_chars >(src);
}
// Match CSS 'odd' and 'even' keywords for functional pseudo-classes.
extern const char even_chars[] = "even";
extern const char odd_chars[] = "odd";
const char* even(const char* src) {
return exactly<even_chars>(src);
}
const char* odd(const char* src) {
return exactly<odd_chars>(src);
}
// Match CSS attribute-matching operators.
extern const char tilde_equal[] = "~=";
extern const char pipe_equal[] = "|=";
extern const char caret_equal[] = "^=";
extern const char dollar_equal[] = "$=";
extern const char star_equal[] = "*=";
const char* exact_match(const char* src) { return exactly<'='>(src); }
const char* class_match(const char* src) { return exactly<tilde_equal>(src); }
const char* dash_match(const char* src) { return exactly<pipe_equal>(src); }
const char* prefix_match(const char* src) { return exactly<caret_equal>(src); }
const char* suffix_match(const char* src) { return exactly<dollar_equal>(src); }
const char* substring_match(const char* src) { return exactly<star_equal>(src); }
// Match CSS combinators.
const char* adjacent_to(const char* src) {
return sequence< optional_spaces, exactly<'+'> >(src);
}
const char* precedes(const char* src) {
return sequence< optional_spaces, exactly<'~'> >(src);
}
const char* parent_of(const char* src) {
return sequence< optional_spaces, exactly<'>'> >(src);
}
const char* ancestor_of(const char* src) {
return sequence< spaces, negate< exactly<'{'> > >(src);
}
// Match SCSS variable names.
const char* variable(const char* src) {
return sequence<exactly<'$'>, name>(src);
}
// Match Sass boolean keywords.
extern const char and_chars[] = "and";
extern const char or_chars[] = "or";
extern const char not_chars[] = "not";
extern const char gt_chars[] = ">";
extern const char gte_chars[] = ">=";
extern const char lt_chars[] = "<";
extern const char lte_chars[] = "<=";
extern const char eq_chars[] = "==";
extern const char neq_chars[] = "!=";
extern const char true_chars[] = "true";
extern const char false_chars[] = "false";
const char* true_kwd(const char* src) {
return exactly<true_chars>(src);
}
const char* false_kwd(const char* src) {
return exactly<false_chars>(src);
}
const char* and_kwd(const char* src) {
return exactly<and_chars>(src);
}
const char* or_kwd(const char* src) {
return exactly<or_chars>(src);
}
const char* not_kwd(const char* src) {
return exactly<not_chars>(src);
}
const char* eq_op(const char* src) {
return exactly<eq_chars>(src);
}
const char* neq_op(const char* src) {
return exactly<neq_chars>(src);
}
const char* gt_op(const char* src) {
return exactly<gt_chars>(src);
}
const char* gte_op(const char* src) {
return exactly<gte_chars>(src);
}
const char* lt_op(const char* src) {
return exactly<lt_chars>(src);
}
const char* lte_op(const char* src) {
return exactly<lte_chars>(src);
}
// Path matching functions.
const char* folder(const char* src) {
return sequence< zero_plus< any_char_except<'/'> >,
exactly<'/'> >(src);
}
const char* folders(const char* src) {
return zero_plus< folder >(src);
}
}
}
\ No newline at end of file
#define SASS_PRELEXER_INCLUDED
namespace Sass {
namespace Prelexer {
typedef int (*ctype_predicate)(int);
typedef const char* (*prelexer)(const char*);
// Match a single character literal.
template <char pre>
const char* exactly(const char* src) {
return *src == pre ? src + 1 : 0;
}
// Match a string constant.
template <const char* prefix>
const char* exactly(const char* src) {
const char* pre = prefix;
while (*pre && *src == *pre) ++src, ++pre;
return *pre ? 0 : src;
}
// Match a single character that satifies the supplied ctype predicate.
template <ctype_predicate pred>
const char* class_char(const char* src) {
return pred(*src) ? src + 1 : 0;
}
// Match a single character that is a member of the supplied class.
template <const char* char_class>
const char* class_char(const char* src) {
const char* cc = char_class;
while (*cc && *src != *cc) ++cc;
return *cc ? src + 1 : 0;
}
// Match a sequence of characters that all satisfy the supplied ctype predicate.
template <ctype_predicate pred>
const char* class_chars(const char* src) {
const char* p = src;
while (pred(*p)) ++p;
return p == src ? 0 : p;
}
// Match a sequence of characters that are all members of the supplied class.
template <const char* char_class>
const char* class_chars(const char* src) {
const char* p = src;
while (class_char<char_class>(p)) ++p;
return p == src ? 0 : p;
}
// Match a sequence of characters up to the next newline.
template <const char* prefix>
const char* to_endl(const char* src) {
if (!(src = exactly<prefix>(src))) return 0;
while (*src && *src != '\n') ++src;
return src;
}
// Match a sequence of characters delimited by the supplied chars.
template <char beg, char end, bool esc>
const char* delimited_by(const char* src) {
src = exactly<beg>(src);
if (!src) return 0;
const char* stop;
while (1) {
if (!*src) return 0;
stop = exactly<end>(src);
if (stop && (!esc || *(src - 1) != '\\')) return stop;
src = stop ? stop : src + 1;
}
}
// Match a sequence of characters delimited by the supplied strings.
template <const char* beg, const char* end, bool esc>
const char* delimited_by(const char* src) {
src = exactly<beg>(src);
if (!src) return 0;
const char* stop;
while (1) {
if (!*src) return 0;
stop = exactly<end>(src);
if (stop && (!esc || *(src - 1) != '\\')) return stop;
src = stop ? stop : src + 1;
}
}
// Match any single character.
const char* any_char(const char* src);
// Match any single character except the supplied one.
template <char c>
const char* any_char_except(const char* src) {
return (*src && *src != c) ? src+1 : 0;
}
// Matches zero characters (always succeeds without consuming input).
const char* epsilon(const char*);
// Matches the empty string.
const char* empty(const char*);
// Succeeds of the supplied matcher fails, and vice versa.
template <prelexer mx>
const char* negate(const char* src) {
return mx(src) ? 0 : src;
}
// Tries the matchers in sequence and returns the first match (or none)
template <prelexer mx1, prelexer mx2>
const char* alternatives(const char* src) {
const char* rslt;
(rslt = mx1(src)) || (rslt = mx2(src));
return rslt;
}
// Same as above, but with 3 arguments.
template <prelexer mx1, prelexer mx2, prelexer mx3>
const char* alternatives(const char* src) {
const char* rslt;
(rslt = mx1(src)) || (rslt = mx2(src)) || (rslt = mx3(src));
return rslt;
}
// Same as above, but with 4 arguments.
template <prelexer mx1, prelexer mx2, prelexer mx3, prelexer mx4>
const char* alternatives(const char* src) {
const char* rslt;
(rslt = mx1(src)) || (rslt = mx2(src)) ||
(rslt = mx3(src)) || (rslt = mx4(src));
return rslt;
}
// Same as above, but with 5 arguments.
template <prelexer mx1, prelexer mx2, prelexer mx3,
prelexer mx4, prelexer mx5>
const char* alternatives(const char* src) {
const char* rslt;
(rslt = mx1(src)) || (rslt = mx2(src)) || (rslt = mx3(src)) ||
(rslt = mx4(src)) || (rslt = mx5(src));
return rslt;
}
// Same as above, but with 6 arguments.
template <prelexer mx1, prelexer mx2, prelexer mx3,
prelexer mx4, prelexer mx5, prelexer mx6>
const char* alternatives(const char* src) {
const char* rslt;
(rslt = mx1(src)) || (rslt = mx2(src)) || (rslt = mx3(src)) ||
(rslt = mx4(src)) || (rslt = mx5(src)) || (rslt = mx6(src));
return rslt;
}
// Same as above, but with 7 arguments.
template <prelexer mx1, prelexer mx2,
prelexer mx3, prelexer mx4,
prelexer mx5, prelexer mx6,
prelexer mx7>
const char* alternatives(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) || (rslt = mx2(rslt)) ||
(rslt = mx3(rslt)) || (rslt = mx4(rslt)) ||
(rslt = mx5(rslt)) || (rslt = mx6(rslt)) ||
(rslt = mx7(rslt));
return rslt;
}
// Same as above, but with 8 arguments.
template <prelexer mx1, prelexer mx2,
prelexer mx3, prelexer mx4,
prelexer mx5, prelexer mx6,
prelexer mx7, prelexer mx8>
const char* alternatives(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) || (rslt = mx2(rslt)) ||
(rslt = mx3(rslt)) || (rslt = mx4(rslt)) ||
(rslt = mx5(rslt)) || (rslt = mx6(rslt)) ||
(rslt = mx7(rslt)) || (rslt = mx8(rslt));
return rslt;
}
// Tries the matchers in sequence and succeeds if they all succeed.
template <prelexer mx1, prelexer mx2>
const char* sequence(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) && (rslt = mx2(rslt));
return rslt;
}
// Same as above, but with 3 arguments.
template <prelexer mx1, prelexer mx2, prelexer mx3>
const char* sequence(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) && (rslt = mx2(rslt)) && (rslt = mx3(rslt));
return rslt;
}
// Same as above, but with 4 arguments.
template <prelexer mx1, prelexer mx2, prelexer mx3, prelexer mx4>
const char* sequence(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) && (rslt = mx2(rslt)) &&
(rslt = mx3(rslt)) && (rslt = mx4(rslt));
return rslt;
}
// Same as above, but with 5 arguments.
template <prelexer mx1, prelexer mx2,
prelexer mx3, prelexer mx4,
prelexer mx5>
const char* sequence(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) && (rslt = mx2(rslt)) &&
(rslt = mx3(rslt)) && (rslt = mx4(rslt)) &&
(rslt = mx5(rslt));
return rslt;
}
// Same as above, but with 6 arguments.
template <prelexer mx1, prelexer mx2,
prelexer mx3, prelexer mx4,
prelexer mx5, prelexer mx6>
const char* sequence(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) && (rslt = mx2(rslt)) &&
(rslt = mx3(rslt)) && (rslt = mx4(rslt)) &&
(rslt = mx5(rslt)) && (rslt = mx6(rslt));
return rslt;
}
// Same as above, but with 7 arguments.
template <prelexer mx1, prelexer mx2,
prelexer mx3, prelexer mx4,
prelexer mx5, prelexer mx6,
prelexer mx7>
const char* sequence(const char* src) {
const char* rslt = src;
(rslt = mx1(rslt)) && (rslt = mx2(rslt)) &&
(rslt = mx3(rslt)) && (rslt = mx4(rslt)) &&
(rslt = mx5(rslt)) && (rslt = mx6(rslt)) &&
(rslt = mx7(rslt));
return rslt;
}
// Match a pattern or not. Always succeeds.
template <prelexer mx>
const char* optional(const char* src) {
const char* p = mx(src);
return p ? p : src;
}
// Match zero or more of the supplied pattern
template <prelexer mx>
const char* zero_plus(const char* src) {
const char* p = mx(src);
while (p) src = p, p = mx(src);
return src;
}
// Match one or more of the supplied pattern
template <prelexer mx>
const char* one_plus(const char* src) {
const char* p = mx(src);
if (!p) return 0;
while (p) src = p, p = mx(src);
return src;
}
// Match a single character satisfying the ctype predicates.
const char* space(const char* src);
const char* alpha(const char* src);
const char* digit(const char* src);
const char* xdigit(const char* src);
const char* alnum(const char* src);
const char* punct(const char* src);
// Match multiple ctype characters.
const char* spaces(const char* src);
const char* alphas(const char* src);
const char* digits(const char* src);
const char* xdigits(const char* src);
const char* alnums(const char* src);
const char* puncts(const char* src);
// Match a line comment.
const char* line_comment(const char* src);
// Match a block comment.
const char* block_comment(const char* src);
// Match either.
const char* comment(const char* src);
// Match double- and single-quoted strings.
const char* double_quoted_string(const char* src);
const char* single_quoted_string(const char* src);
const char* string_constant(const char* src);
// Match interpolants.
const char* interpolant(const char* src);
// Whitespace handling.
const char* optional_spaces(const char* src);
const char* optional_comment(const char* src);
const char* spaces_and_comments(const char* src);
const char* no_spaces(const char* src);
// Match a CSS identifier.
const char* identifier(const char* src);
// Match interpolant schemas
const char* identifier_schema(const char* src);
const char* value_schema(const char* src);
// Match CSS '@' keywords.
const char* at_keyword(const char* src);
const char* import(const char* src);
const char* media(const char* src);
const char* mixin(const char* src);
const char* function(const char* src);
const char* return_directive(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* in(const char* src);
const char* while_directive(const char* src);
const char* warn(const char* src);
// Match CSS type selectors
const char* namespace_prefix(const char* src);
const char* type_selector(const char* src);
const char* universal(const char* src);
// Match CSS id names.
const char* id_name(const char* src);
// Match CSS class names.
const char* class_name(const char* src);
// Match CSS numeric constants.
const char* sign(const char* src);
const char* unsigned_number(const char* src);
const char* number(const char* src);
const char* coefficient(const char* src);
const char* binomial(const char* src);
const char* percentage(const char* src);
const char* dimension(const char* src);
const char* hex(const char* src);
const char* rgb_prefix(const char* src);
// Match CSS uri specifiers.
const char* uri_prefix(const char* src);
const char* uri(const char* src);
// Match CSS "!important" keyword.
const char* important(const char* src);
// Match Sass "!default" keyword.
const char* default_flag(const char* src);
// Match CSS pseudo-class/element prefixes
const char* pseudo_prefix(const char* src);
// Match CSS function call openers.
const char* functional(const char* src);
const char* pseudo_not(const char* src);
// Match CSS 'odd' and 'even' keywords for functional pseudo-classes.
const char* even(const char* src);
const char* odd(const char* src);
// Match CSS attribute-matching operators.
const char* exact_match(const char* src);
const char* class_match(const char* src);
const char* dash_match(const char* src);
const char* prefix_match(const char* src);
const char* suffix_match(const char* src);
const char* substring_match(const char* src);
// Match CSS combinators.
const char* adjacent_to(const char* src);
const char* precedes(const char* src);
const char* parent_of(const char* src);
const char* ancestor_of(const char* src);
// Match SCSS variable names.
const char* variable(const char* src);
// Match Sass boolean keywords.
const char* true_kwd(const char* src);
const char* false_kwd(const char* src);
const char* and_kwd(const char* src);
const char* or_kwd(const char* src);
const char* not_kwd(const char* src);
const char* eq_op(const char* src);
const char* neq_op(const char* src);
const char* gt_op(const char* src);
const char* gte_op(const char* src);
const char* lt_op(const char* src);
const char* lte_op(const char* src);
// Path matching functions.
const char* folder(const char* src);
const char* folders(const char* src);
// Utility functions for finding and counting characters in a string.
template<char c>
const char* find_first(const char* src) {
while (*src && *src != c) ++src;
return *src ? src : 0;
}
template<prelexer mx>
const char* find_first(const char* src) {
while (*src && !mx(src)) ++src;
return *src ? src : 0;
}
template<prelexer mx>
const char* find_first_in_interval(const char* beg, const char* end) {
while ((beg < end) && *beg) {
if (mx(beg)) return beg;
++beg;
}
return 0;
}
template <char c>
unsigned int count_interval(const char* beg, const char* end) {
unsigned int counter = 0;
while (beg < end && *beg) {
if (*beg == c) ++counter;
++beg;
}
return counter;
}
template <prelexer mx>
unsigned int count_interval(const char* beg, const char* end) {
unsigned int counter = 0;
while (beg < end && *beg) {
const char* p;
if (p = mx(beg)) {
++counter;
beg = p;
}
else {
++beg;
}
}
return counter;
}
}
}
#include <iostream>
#include <sstream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <iostream>
#include "document.hpp"
#include "eval_apply.hpp"
#include "error.hpp"
#include "sass_interface.h"
extern "C" {
using namespace std;
sass_context* sass_new_context()
{ return (sass_context*) calloc(1, sizeof(sass_context)); }
void sass_free_context(sass_context* ctx)
{
if (ctx->output_string)
free(ctx->output_string);
free(ctx);
}
sass_file_context* sass_new_file_context()
{ return (sass_file_context*) calloc(1, sizeof(sass_file_context)); }
void sass_free_file_context(sass_file_context* ctx)
{
if (ctx->output_string)
free(ctx->output_string);
free(ctx);
}
sass_folder_context* sass_new_folder_context()
{ return (sass_folder_context*) calloc(1, sizeof(sass_folder_context)); }
static char* process_document(Sass::Document& doc, int style)
{
using namespace Sass;
doc.parse_scss();
eval(doc.root,
doc.context.new_Node(Node::none, doc.path, doc.line, 0),
doc.context.global_env,
doc.context.function_env,
doc.context.new_Node,
doc.context);
extend_selectors(doc.context.pending_extensions, doc.context.extensions, doc.context.new_Node);
string output(doc.emit_css(static_cast<Document::CSS_Style>(style)));
char* c_output = (char*) malloc(output.size() + 1);
strcpy(c_output, output.c_str());
return c_output;
}
int sass_compile(sass_context* c_ctx)
{
using namespace Sass;
try {
Context cpp_ctx(c_ctx->options.include_paths);
// Document doc(0, c_ctx->input_string, cpp_ctx);
Document doc(Document::make_from_source_chars(cpp_ctx, c_ctx->source_string));
c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
c_ctx->error_message = 0;
c_ctx->error_status = 0;
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.path << ", line " << e.line << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
catch(bad_alloc& ba) {
stringstream msg_stream;
msg_stream << "ERROR -- unable to allocate memory: " << ba.what() << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
// TO DO: CATCH EVERYTHING ELSE
return 0;
}
int sass_compile_file(sass_file_context* c_ctx)
{
using namespace Sass;
try {
Context cpp_ctx(c_ctx->options.include_paths);
// Document doc(c_ctx->input_path, 0, cpp_ctx);
Document doc(Document::make_from_file(cpp_ctx, string(c_ctx->input_path)));
// cerr << "MADE A DOC AND CONTEXT OBJ" << endl;
// cerr << "REGISTRY: " << doc.context.registry.size() << endl;
c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
c_ctx->error_message = 0;
c_ctx->error_status = 0;
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.path << ", line " << e.line << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
catch(bad_alloc& ba) {
stringstream msg_stream;
msg_stream << "ERROR -- unable to allocate memory: " << ba.what() << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
// TO DO: CATCH EVERYTHING ELSE
return 0;
}
int sass_compile_folder(sass_folder_context* c_ctx)
{
return 1;
}
}
#include <node.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SASS_STYLE_NESTED 0
#define SASS_STYLE_EXPANDED 1
#define SASS_STYLE_COMPACT 2
#define SASS_STYLE_COMPRESSED 3
struct sass_options {
int output_style;
char* include_paths;
};
struct sass_context {
char* source_string;
char* output_string;
struct sass_options options;
int error_status;
char* error_message;
uv_work_t request;
v8::Persistent<v8::Function> callback;
};
struct sass_file_context {
char* input_path;
char* output_string;
struct sass_options options;
int error_status;
char* error_message;
};
struct sass_folder_context {
char* search_path;
char* output_path;
struct sass_options options;
int error_status;
char* error_message;
};
struct sass_context* sass_new_context (void);
struct sass_file_context* sass_new_file_context (void);
struct sass_folder_context* sass_new_folder_context (void);
void sass_free_context (struct sass_context* ctx);
void sass_free_file_context (struct sass_file_context* ctx);
void sass_free_folder_context (struct sass_folder_context* ctx);
int sass_compile (struct sass_context* ctx);
int sass_compile_file (struct sass_file_context* ctx);
int sass_compile_folder (struct sass_folder_context* ctx);
#ifdef __cplusplus
}
#endif
\ No newline at end of file
#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
...@@ -10,7 +10,7 @@ var scssStr = '#navbar {\ ...@@ -10,7 +10,7 @@ var scssStr = '#navbar {\
a {\ a {\
font-weight: bold; }}'; font-weight: bold; }}';
sass.render(scssStr, function(css){ sass.render(scssStr, function(err, css){
console.log(css) console.log(css)
}) })
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