Commit 2ace445d by Andrew Nesbitt

added libsass

parent 80abeaf0
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.
CPP_FILES = \
context.cpp \
functions.cpp \
document.cpp \
document_parser.cpp \
eval_apply.cpp \
node.cpp \
node_comparisons.cpp \
values.cpp \
prelexer.cpp
libsass: libsass_objs
ar rvs libsass.a \
sass_interface.o \
context.o \
functions.o \
document.o \
document_parser.o \
eval_apply.o \
node.o \
node_comparisons.o \
values.o \
prelexer.o
libsass_objs: sass_interface.cpp $(CPP_FILES)
g++ -O2 -c -combine sass_interface.cpp $(CPP_FILES)
clean:
rm -rf *.o *.a
\ No newline at end of file
Libsass
=======
by Aaron Leung and Hampton Catlin (@hcatlin)
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.
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>
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>()),
source_refs(vector<char*>()),
registry(vector<vector<Node>*>()),
include_paths(vector<string>()),
ref_count(0)
{
register_functions();
collect_include_paths(paths_str);
}
Context::~Context()
{
int i;
for (i = 0; i < source_refs.size(); ++i) {
delete[] source_refs[i];
}
// cerr << "Deallocated " << i << " source string(s)." << endl;
}
inline void Context::register_function(Function_Descriptor d, Implementation 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, Implementation 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);
// 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);
}
}
\ No newline at end of file
#define SASS_CONTEXT_INCLUDED
#include <utility>
#include <map>
#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 {
string sass_path;
string css_path;
vector<string> include_paths;
Environment global_env;
map<pair<string, size_t>, Function> function_env;
vector<char*> source_refs; // all the source c-strings
vector<vector<Node>*> registry; // all the child vectors
size_t ref_count;
void collect_include_paths(const char* paths_str);
Context(const char* paths_str = 0);
~Context();
void register_function(Function_Descriptor d, Implementation ip);
void register_function(Function_Descriptor d, Implementation 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>
namespace Sass {
Document::Document(char* path_str, char* source_str, Context& ctx)
: path(string()),
source(source_str),
line_number(1),
context(ctx),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
{
if (source_str) {
own_source = false;
position = source;
end = position + std::strlen(source);
}
else if (path_str) {
path = string(path_str);
std::FILE *f;
// TO DO: CHECK f AGAINST NULL/0
f = std::fopen(path.c_str(), "rb");
std::fseek(f, 0, SEEK_END);
int len = std::ftell(f);
std::rewind(f);
// TO DO: WRAP THE new[] IN A TRY/CATCH BLOCK
source = new char[len + 1];
std::fread(source, sizeof(char), len, f);
source[len] = '\0';
end = source + len;
std::fclose(f);
own_source = true;
position = source;
context.source_refs.push_back(source);
}
else {
// report an error
}
++context.ref_count;
}
Document::Document(string path, char* source)
: path(path), source(source),
line_number(1), own_source(false),
context(*(new Context())),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
{
if (!source) {
std::FILE *f;
f = std::fopen(path.c_str(), "rb");
if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f);
if (len < 0) throw path;
std::rewind(f);
// TO DO: CATCH THE POTENTIAL badalloc EXCEPTION
source = new char[len + 1];
std::fread(source, sizeof(char), len, f);
if (std::ferror(f)) throw path;
source[len] = '\0';
end = source + len;
if (std::fclose(f)) throw path;
own_source = true;
}
position = source;
context.source_refs.push_back(source);
++context.ref_count;
}
Document::Document(string path, Context& context)
: path(path), source(0),
line_number(1), own_source(false),
context(context),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
{
std::FILE *f;
f = std::fopen(path.c_str(), "rb");
if (!f) throw path;
if (std::fseek(f, 0, SEEK_END)) throw path;
int len = std::ftell(f);
if (len < 0) throw path;
std::rewind(f);
// TO DO: CATCH THE POTENTIAL badalloc EXCEPTION
source = new char[len + 1];
std::fread(source, sizeof(char), len, f);
if (std::ferror(f)) throw path;
source[len] = '\0';
end = source + len;
if (std::fclose(f)) throw path;
position = source;
context.source_refs.push_back(source);
++context.ref_count;
}
Document::Document(const string& path, size_t line_number, Token t, Context& context)
: path(path),
source(const_cast<char*>(t.begin)),
position(t.begin),
end(t.end),
line_number(line_number),
own_source(false),
context(context),
root(Node(Node::root, context.registry, 1)),
lexed(Token::make())
{ }
Document::~Document() {
--context.ref_count;
// if (context.ref_count == 0) delete &context;
}
void Document::syntax_error(string message, size_t ln)
{ throw Error(Error::syntax, ln ? ln : line_number, path, message); }
void Document::read_error(string message, size_t ln)
{ throw Error(Error::read, ln ? ln : line_number, path, 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, vector<string>());
break;
case expanded:
root.emit_expanded_css(output, "");
break;
}
string retval(output.str());
if (!retval.empty()) retval.resize(retval.size()-1);
return retval;
}
}
\ No newline at end of file
#include <map>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
#include "context.hpp"
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_number;
bool own_source;
Context& context;
Node root;
Token lexed;
Document(char* path_str, char* source_str, Context& ctx);
Document(string path, char* source = 0);
Document(string path, Context& context);
Document(const string& path, size_t line_number, Token t, Context& context);
~Document();
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_number += 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_number += 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_mixin_parameters();
Node parse_parameter();
Node parse_mixin_call();
Node parse_arguments();
Node parse_argument();
Node parse_assignment();
Node parse_propset();
Node parse_ruleset(bool definition = false);
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(bool definition = false);
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_identifier();
Node parse_variable();
Node parse_function_call();
Node parse_string();
Node parse_value_schema();
const char* lookahead_for_selector(const char* start = 0);
const char* look_for_rule(const char* start = 0);
const char* look_for_values(const char* start = 0);
const char* look_for_selector_group(const char* start = 0);
const char* look_for_selector(const char* start = 0);
const char* look_for_simple_selector_sequence(const char* start = 0);
const char* look_for_simple_selector(const char* start = 0);
const char* look_for_pseudo(const char* start = 0);
const char* look_for_attrib(const char* start = 0);
void syntax_error(string message, size_t ln = 0);
void 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;
extern const char plus_equal[] = "+=";
void Document::parse_scss()
{
lex<optional_spaces>();
root << Node(Node::flags);
while(position < end) {
if (lex< block_comment >()) {
root << Node(Node::comment, line_number, lexed);
}
else if (peek< import >(position)) {
// TO DO: don't splice in place at parse-time -- use an expansion node
Node import(parse_import());
if (import.type == Node::css_import) {
root << import;
}
else {
root += import;
}
if (!lex< exactly<';'> >()) syntax_error("top-level @import directive must be terminated by ';'");
}
else if (peek< mixin >(position) || peek< exactly<'='> >(position)) {
root << parse_mixin_definition();
}
else if (peek< include >(position)) {
root << parse_mixin_call();
root[0].has_expansions = true;
if (!lex< exactly<';'> >()) syntax_error("top-level @include directive must be terminated by ';'");
}
else if (peek< variable >(position)) {
root << parse_assignment();
if (!lex< exactly<';'> >()) syntax_error("top-level variable binding must be terminated by ';'");
}
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
root << parse_propset();
}
else if (lookahead_for_selector(position)) {
root << parse_ruleset();
}
else if (peek< exactly<'+'> >()) {
root << parse_mixin_call();
root[0].has_expansions = true;
if (!lex< exactly<';'> >()) syntax_error("top-level @include directive must be terminated by ';'");
}
lex<optional_spaces>();
}
}
Node Document::parse_import()
{
lex< import >();
if (lex< uri_prefix >())
{
const char* beg = position;
const char* end = find_first< exactly<')'> >(position);
Node result(Node::css_import, line_number, Token::make(beg, end));
position = end;
lex< exactly<')'> >();
return result;
}
if (!lex< string_constant >()) syntax_error("@import directive requires a url or quoted path");
// TO DO: BETTER PATH HANDLING
// cerr << "Importing " << lexed.to_string() << endl;
string import_path(lexed.unquote());
const char* curr_path_start = path.c_str();
const char* curr_path_end = folders(curr_path_start);
string current_path(curr_path_start, curr_path_end - curr_path_start);
try {
Document importee(current_path + import_path, context);
importee.parse_scss();
// cerr << "Finished parsing import " << lexed.to_string() << endl;
return importee.root;
}
catch (string& path) {
read_error("error reading file \"" + path + "\"");
}
}
Node Document::parse_mixin_definition()
{
lex< mixin >() || lex< exactly<'='> >();
if (!lex< identifier >()) syntax_error("invalid name in @mixin directive");
Node name(Node::identifier, line_number, lexed);
Node params(parse_mixin_parameters());
if (!peek< exactly<'{'> >()) syntax_error("body for mixin " + name.content.token.to_string() + " must begin with a '{'");
Node body(parse_block(true));
Node mixin(Node::mixin, context.registry, line_number, 3);
mixin << name << params << body;
return mixin;
}
Node Document::parse_mixin_parameters()
{
Node params(Node::parameters, context.registry, line_number);
Token name(lexed);
if (lex< exactly<'('> >()) {
if (peek< variable >()) {
params << parse_parameter();
while (lex< exactly<','> >()) {
if (!peek< variable >()) syntax_error("expected a variable name (e.g. $x) for the parameter list for " + name.to_string());
params << parse_parameter();
}
if (!lex< exactly<')'> >()) syntax_error("parameter list for " + name.to_string() + " requires a ')'");
}
else if (!lex< exactly<')'> >()) syntax_error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name.to_string());
}
return params;
}
Node Document::parse_parameter() {
lex< variable >();
Node var(Node::variable, line_number, lexed);
if (lex< exactly<':'> >()) { // default value
Node val(parse_space_list());
Node par_and_val(Node::assignment, context.registry, line_number, 2);
par_and_val << var << val;
return par_and_val;
}
else {
return var;
}
}
Node Document::parse_mixin_call()
{
lex< include >() || lex< exactly<'+'> >();
if (!lex< identifier >()) syntax_error("invalid name in @include directive");
Node name(Node::identifier, line_number, lexed);
Node args(parse_arguments());
Node call(Node::expansion, context.registry, line_number, 3);
call << name << args;
return call;
}
Node Document::parse_arguments()
{
Token name(lexed);
Node args(Node::arguments, context.registry, line_number);
if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) {
args << parse_argument();
args.content.children->back().eval_me = true;
while (lex< exactly<','> >()) {
args << parse_argument();
args.content.children->back().eval_me = true;
}
}
if (!lex< exactly<')'> >()) 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(Node::variable, line_number, lexed);
lex< exactly<':'> >();
Node val(parse_space_list());
Node assn(Node::assignment, context.registry, line_number, 2);
assn << var << val;
return assn;
}
else {
return parse_space_list();
}
}
Node Document::parse_assignment()
{
lex< variable >();
Node var(Node::variable, line_number, lexed);
if (!lex< exactly<':'> >()) syntax_error("expected ':' after " + lexed.to_string() + " in assignment statement");
Node val(parse_list());
Node assn(Node::assignment, context.registry, line_number, 2);
assn << var << val;
return assn;
}
Node Document::parse_propset()
{
lex< identifier >();
Node property_segment(Node::identifier, line_number, lexed);
lex< exactly<':'> >();
lex< exactly<'{'> >();
Node block(Node::block, context.registry, line_number, 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.size() == 0) syntax_error("namespaced property cannot be empty");
Node propset(Node::propset, context.registry, line_number, 2);
propset << property_segment;
propset << block;
return propset;
}
Node Document::parse_ruleset(bool definition)
{
Node ruleset(Node::ruleset, context.registry, line_number, 2);
ruleset << parse_selector_group();
// if (ruleset[0].type == Node::selector) cerr << "ruleset starts with selector" << endl;
// if (ruleset[0].type == Node::selector_group) cerr << "ruleset starts with selector_group" << endl;
if (!peek< exactly<'{'> >()) syntax_error("expected a '{' after the selector");
ruleset << parse_block(definition);
return ruleset;
}
Node Document::parse_selector_group()
{
// Node group(Node::selector_group, line_number, 1);
// group << parse_selector();
// while (lex< exactly<','> >()) group << parse_selector();
// return group;
Node sel1(parse_selector());
if (!peek< exactly<','> >()) return sel1;
Node group(Node::selector_group, context.registry, line_number, 2);
group << sel1;
while (lex< exactly<','> >()) group << parse_selector();
return group;
}
Node Document::parse_selector()
{
// Node selector(Node::selector, line_number, 1);
// if (lex< exactly<'+'> >() ||
// lex< exactly<'~'> >() ||
// lex< exactly<'>'> >()) {
// selector << Node(Node::selector_combinator, line_number, lexed);
// }
// Node s(parse_simple_selector_sequence());
// if (s.has_backref) selector.has_backref = true;
// selector << s;
// while (lex< exactly<'+'> >() ||
// lex< exactly<'~'> >() ||
// lex< exactly<'>'> >() ||
// lex< ancestor_of >() /*||
// s.terminal_backref && lex< no_spaces >()*/) {
// selector << Node(Node::selector_combinator, line_number, lexed);
// s = parse_simple_selector_sequence();
// if (s.has_backref) selector.has_backref = true;
// selector << s;
// }
// return selector;
Node seq1(parse_simple_selector_sequence());
if (peek< exactly<','> >() ||
peek< exactly<')'> >() ||
peek< exactly<'{'> >()) return seq1;
Node selector(Node::selector, context.registry, line_number, 2);
if (seq1.has_backref) selector.has_backref = true;
selector << seq1;
while (!peek< exactly<'{'> >() && !peek< exactly<','> >()) {
Node seq(parse_simple_selector_sequence());
if (seq.has_backref) selector.has_backref = true;
selector << seq;
}
return selector;
}
Node Document::parse_simple_selector_sequence()
{
// check for initial and trailing combinators
if (lex< exactly<'+'> >() ||
lex< exactly<'~'> >() ||
lex< exactly<'>'> >())
{ return Node(Node::selector_combinator, line_number, lexed); }
// check for backref or type selector, which are only allowed at the front
Node simp1;
bool saw_backref = false;
if (lex< exactly<'&'> >()) {
simp1 = Node(Node::backref, line_number, lexed);
simp1.has_backref = true;
saw_backref = true;
}
else if (lex< alternatives< type_selector, universal > >()) {
simp1 = Node(Node::simple_selector, line_number, lexed);
}
else {
simp1 = parse_simple_selector();
}
// now we have one simple/atomic selector -- see if there are more
if (peek< spaces >() || peek< exactly<'>'> >() ||
peek< exactly<'+'> >() || peek< exactly<'~'> >() ||
peek< exactly<','> >() || peek< exactly<')'> >() ||
peek< exactly<'{'> >())
{ return simp1; }
// now we know we have a sequence of simple selectors
Node seq(Node::simple_selector_sequence, context.registry, line_number, 2);
seq << simp1;
seq.has_backref = saw_backref;
while (!peek< spaces >(position) &&
!(peek < exactly<'+'> >(position) ||
peek < exactly<'~'> >(position) ||
peek < exactly<'>'> >(position) ||
peek < exactly<','> >(position) ||
peek < exactly<')'> >(position) ||
peek < exactly<'{'> >(position))) {
seq << parse_simple_selector();
}
return seq;
//
// Node seq(Node::simple_selector_sequence, line_number, 1);
// if (lex< alternatives < type_selector, universal > >()) {
// seq << Node(Node::simple_selector, line_number, lexed);
// }
// else if (lex< exactly<'&'> >()) {
// seq << Node(Node::backref, line_number, lexed);
// seq.has_backref = true;
// // if (peek< sequence< no_spaces, alternatives< type_selector, universal > > >(position)) {
// // seq.terminal_backref = true;
// // return seq;
// // }
// }
// else {
// seq << parse_simple_selector();
// }
// while (!peek< spaces >(position) &&
// !(peek < exactly<'+'> >(position) ||
// peek < exactly<'~'> >(position) ||
// peek < exactly<'>'> >(position) ||
// peek < exactly<','> >(position) ||
// peek < exactly<')'> >(position) ||
// peek < exactly<'{'> >(position))) {
// seq << parse_simple_selector();
// }
// return seq;
}
Node Document::parse_selector_combinator()
{
lex< exactly<'+'> >() || lex< exactly<'~'> >() ||
lex< exactly<'>'> >() || lex< ancestor_of >();
return Node(Node::selector_combinator, line_number, lexed);
}
Node Document::parse_simple_selector()
{
if (lex< id_name >() || lex< class_name >()) {
return Node(Node::simple_selector, line_number, lexed);
}
else if (peek< exactly<':'> >(position)) {
return parse_pseudo();
}
else if (peek< exactly<'['> >(position)) {
return parse_attribute_selector();
}
else {
syntax_error("invalid selector after " + lexed.to_string());
}
}
Node Document::parse_pseudo() {
if (lex< pseudo_not >()) {
Node ps_not(Node::pseudo_negation, context.registry, line_number, 2);
ps_not << Node(Node::value, line_number, lexed);
ps_not << parse_selector_group();
lex< exactly<')'> >();
return ps_not;
}
else if (lex< sequence< pseudo_prefix, functional > >()) {
Node pseudo(Node::functional_pseudo, context.registry, line_number, 2);
Token name(lexed);
pseudo << Node(Node::value, line_number, name);
if (lex< alternatives< even, odd > >()) {
pseudo << Node(Node::value, line_number, lexed);
}
else if (peek< binomial >(position)) {
lex< coefficient >();
pseudo << Node(Node::value, line_number, lexed);
lex< exactly<'n'> >();
pseudo << Node(Node::value, line_number, lexed);
lex< sign >();
pseudo << Node(Node::value, line_number, lexed);
lex< digits >();
pseudo << Node(Node::value, line_number, lexed);
}
else if (lex< sequence< optional<sign>,
optional<digits>,
exactly<'n'> > >()) {
pseudo << Node(Node::value, line_number, lexed);
}
else if (lex< sequence< optional<sign>, digits > >()) {
pseudo << Node(Node::value, line_number, lexed);
}
else if (lex< string_constant >()) {
pseudo << Node(Node::string_constant, line_number, lexed);
}
else {
syntax_error("invalid argument to " + name.to_string() + "...)");
}
if (!lex< exactly<')'> >()) syntax_error("unterminated argument to " + name.to_string() + "...)");
return pseudo;
}
else if (lex < sequence< pseudo_prefix, identifier > >()) {
return Node(Node::pseudo, line_number, lexed);
}
else {
syntax_error("unrecognized pseudo-class or pseudo-element");
}
}
Node Document::parse_attribute_selector()
{
Node attr_sel(Node::attribute_selector, context.registry, line_number, 3);
lex< exactly<'['> >();
if (!lex< type_selector >()) syntax_error("invalid attribute name in attribute selector");
Token name(lexed);
attr_sel << Node(Node::value, line_number, name);
if (lex< exactly<']'> >()) return attr_sel;
if (!lex< alternatives< exact_match, class_match, dash_match,
prefix_match, suffix_match, substring_match > >()) {
syntax_error("invalid operator in attribute selector for " + name.to_string());
}
attr_sel << Node(Node::value, line_number, lexed);
if (!lex< string_constant >()) syntax_error("expected a quoted string constant in attribute selector for " + name.to_string());
attr_sel << Node(Node::value, line_number, lexed);
if (!lex< exactly<']'> >()) syntax_error("unterminated attribute selector for " + name.to_string());
return attr_sel;
}
Node Document::parse_block(bool definition)
{
lex< exactly<'{'> >();
bool semicolon = false;
Node block(Node::block, context.registry, line_number, 1);
block << Node(Node::flags);
while (!lex< exactly<'}'> >()) {
if (semicolon) {
if (!lex< exactly<';'> >()) syntax_error("non-terminal statement or declaration must end with ';'");
semicolon = false;
while (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed);
block[0].has_statements = true;
}
if (lex< exactly<'}'> >()) break;
}
if (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed);
block[0].has_statements = true;
//semicolon = true;
}
else if (peek< import >(position)) {
if (definition) {
lex< import >(); // to adjust the line number
syntax_error("@import directive not allowed inside mixin definition");
}
Node imported_tree(parse_import());
if (imported_tree.type == Node::css_import) {
// cerr << "css import inside block" << endl;
block << imported_tree;
block.has_statements = true;
}
else {
for (int i = 0; i < imported_tree.size(); ++i) {
if (imported_tree[i].type == Node::comment ||
imported_tree[i].type == Node::rule) {
block[0].has_statements = true;
}
else if (imported_tree[i].type == Node::ruleset) {
block[0].has_blocks = true;
}
block << imported_tree[i];
}
semicolon = true;
}
}
else if (peek< include >(position)) {
block << parse_mixin_call();
block[0].has_expansions = true;
semicolon = true;
}
else if (lex< variable >()) {
block << parse_assignment();
semicolon = true;
}
else if (peek< sequence< identifier, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {
block << parse_propset();
block[0].has_statements = true;
}
// else if (look_for_rule(position)) {
// block << parse_rule();
// block.has_statements = true;
// semicolon = true;
// }
// else if (!peek< exactly<';'> >()) {
// block << parse_ruleset();
// block.has_blocks = true;
// }
else if (const char* p = lookahead_for_selector(position)) {
block << parse_ruleset(definition);
block[0].has_blocks = true;
}
else if (peek< exactly<'+'> >()) {
block << parse_mixin_call();
block[0].has_expansions = true;
semicolon = true;
}
else if (!peek< exactly<';'> >()) {
block << parse_rule();
block[0].has_statements = true;
semicolon = true;
//lex< exactly<';'> >(); // TO DO: clean up the semicolon handling stuff
}
else lex< exactly<';'> >();
while (lex< block_comment >()) {
block << Node(Node::comment, line_number, lexed);
block[0].has_statements = true;
}
}
return block;
}
Node Document::parse_rule() {
Node rule(Node::rule, context.registry, line_number, 2);
if (!lex< sequence< optional< exactly<'*'> >, identifier > >()) syntax_error("invalid property name");
rule << Node(Node::property, line_number, lexed);
if (!lex< exactly<':'> >()) syntax_error("property \"" + lexed.to_string() + "\" must be followed by a ':'");
rule << 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 Node(Node::nil, context.registry, line_number); }
Node list1(parse_space_list());
// if it's a singleton, return it directly; don't wrap it
if (!peek< exactly<','> >(position)) return list1;
Node comma_list(Node::comma_list, context.registry, line_number, 2);
comma_list << list1;
comma_list.eval_me |= list1.eval_me;
while (lex< exactly<','> >())
{
Node list(parse_space_list());
comma_list << list;
comma_list.eval_me |= list.eval_me;
}
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))
{ return disj1; }
Node space_list(Node::space_list, context.registry, line_number, 2);
space_list << disj1;
space_list.eval_me |= disj1.eval_me;
while (!(peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) ||
peek< exactly<','> >(position)))
{
Node disj(parse_disjunction());
space_list << disj;
space_list.eval_me |= disj.eval_me;
}
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(Node::disjunction, context.registry, line_number, 2);
disjunction << conj1;
while (lex< sequence< or_kwd, negate< identifier > > >()) disjunction << parse_conjunction();
disjunction.eval_me = 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(Node::conjunction, context.registry, line_number, 2);
conjunction << rel1;
while (lex< sequence< and_kwd, negate< identifier > > >()) conjunction << parse_relation();
conjunction.eval_me = 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(Node::relation, context.registry, line_number, 3);
expr1.eval_me = true;
relation << expr1;
if (lex< eq_op >()) relation << Node(Node::eq, line_number, lexed);
else if (lex< neq_op >()) relation << Node(Node::neq, line_number, lexed);
else if (lex< gte_op >()) relation << Node(Node::gte, line_number, lexed);
else if (lex< lte_op >()) relation << Node(Node::lte, line_number, lexed);
else if (lex< gt_op >()) relation << Node(Node::gt, line_number, lexed);
else if (lex< lt_op >()) relation << Node(Node::lt, line_number, lexed);
Node expr2(parse_expression());
expr2.eval_me = true;
relation << expr2;
relation.eval_me = 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(Node::expression, context.registry, line_number, 3);
term1.eval_me = true;
expression << term1;
while (lex< exactly<'+'> >() || lex< sequence< negate< number >, exactly<'-'> > >()) {
if (lexed.begin[0] == '+') {
expression << Node(Node::add, line_number, lexed);
}
else {
expression << Node(Node::sub, line_number, lexed);
}
Node term(parse_term());
term.eval_me = true;
expression << term;
}
expression.eval_me = 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(Node::term, context.registry, line_number, 3);
term << fact1;
if (fact1.eval_me) term.eval_me = true;
while (lex< exactly<'*'> >() || lex< exactly<'/'> >()) {
if (lexed.begin[0] == '*') {
term << Node(Node::mul, line_number, lexed);
term.eval_me = true;
}
else {
term << Node(Node::div, line_number, lexed);
}
Node fact(parse_factor());
if (fact.eval_me) term.eval_me = true;
term << fact;
}
return term;
}
Node Document::parse_factor()
{
if (lex< exactly<'('> >()) {
Node value(parse_comma_list());
value.eval_me = true;
if (value.type == Node::comma_list || value.type == Node::space_list) {
value[0].eval_me = true;
}
if (!lex< exactly<')'> >()) syntax_error("unclosed parenthesis");
return value;
}
else if (lex< sequence< exactly<'+'>, negate< number > > >()) {
Node plus(Node::unary_plus, context.registry, line_number, 1);
plus << parse_factor();
plus.eval_me = true;
return plus;
}
else if (lex< sequence< exactly<'-'>, negate< number> > >()) {
Node minus(Node::unary_minus, context.registry, line_number, 1);
minus << parse_factor();
minus.eval_me = 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) syntax_error("URI is missing ')'");
Token contents(Token::make(value, rparen));
// lex< string_constant >();
Node result(Node::uri, line_number, contents);
position = rparen;
lex< exactly<')'> >();
return result;
}
if (lex< value_schema >())
{
// cerr << "parsing value schema: " << lexed.to_string() << endl;
Document schema_doc(path, line_number, lexed, context);
return schema_doc.parse_value_schema();
}
if (lex< sequence< true_kwd, negate< identifier > > >())
{
Node T(Node::boolean);
T.line_number = line_number;
T.content.boolean_value = true;
return T;
}
if (lex< sequence< false_kwd, negate< identifier > > >())
{
Node F(Node::boolean);
F.line_number = line_number;
F.content.boolean_value = false;
return F;
}
if (peek< functional >())
{ return parse_function_call(); }
if (lex< important >())
{ return Node(Node::important, line_number, lexed); }
if (lex< identifier >())
{ return Node(Node::identifier, line_number, lexed); }
if (lex< percentage >())
{ return Node(Node::textual_percentage, line_number, lexed); }
if (lex< dimension >())
{ return Node(Node::textual_dimension, line_number, lexed); }
if (lex< number >())
{ return Node(Node::textual_number, line_number, lexed); }
if (lex< hex >())
{ return Node(Node::textual_hex, line_number, lexed); }
if (peek< string_constant >())
// { return Node(Node::string_constant, line_number, lexed); }
{ return parse_string(); }
if (lex< variable >())
{
Node var(Node::variable, line_number, lexed);
var.eval_me = true;
return var;
}
syntax_error("error reading values after " + lexed.to_string());
}
extern const char hash_lbrace[] = "#{";
extern const char rbrace[] = "}";
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 Node(Node::string_constant, line_number, str);
}
Node schema(Node::string_schema, context.registry, line_number, 1);
while (i < str.end) {
if (p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly<hash_lbrace> > >(i, str.end)) {
if (i < p) schema << Node(Node::identifier, line_number, Token::make(i, p)); // accumulate the preceding segment if it's nonempty
const char* j = find_first_in_interval< exactly<rbrace> >(p, str.end); // find the closing brace
if (j) {
// parse the interpolant and accumulate it
Document interp_doc(path, line_number, Token::make(p+2,j-1), context);
Node interp_node(interp_doc.parse_list());
interp_node.eval_me = true;
schema << interp_node;
i = j + 1;
}
else {
// throw an error if the interpolant is unterminated
syntax_error("unterminated interpolant inside string constant " + str.to_string());
}
}
else { // no interpolants left; add the last segment if nonempty
if (i < str.end) schema << Node(Node::identifier, line_number, Token::make(i, str.end));
break;
}
}
return schema;
}
Node Document::parse_value_schema()
{
Node schema(Node::value_schema, context.registry, line_number, 1);
while (position < end) {
if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Document interp_doc(path, line_number, insides, context);
Node interp_node(interp_doc.parse_list());
schema << interp_node;
}
else if (lex< identifier >()) {
schema << Node(Node::identifier, line_number, lexed);
}
else if (lex< percentage >()) {
schema << Node(Node::textual_percentage, line_number, lexed);
}
else if (lex< dimension >()) {
schema << Node(Node::textual_dimension, line_number, lexed);
}
else if (lex< number >()) {
schema << Node(Node::textual_number, line_number, lexed);
}
else if (lex< hex >()) {
schema << Node(Node::textual_hex, line_number, lexed);
}
else if (lex< string_constant >()) {
schema << Node(Node::string_constant, line_number, lexed);
}
else if (lex< variable >()) {
schema << Node(Node::variable, line_number, lexed);
}
else {
syntax_error("error parsing interpolated value");
}
}
schema.eval_me = true;
return schema;
}
Node Document::parse_function_call()
{
lex< identifier >();
Node name(Node::identifier, line_number, lexed);
Node args(parse_arguments());
Node call(Node::function_call, context.registry, line_number, 2);
call << name << args;
call.eval_me = true;
return call;
}
Node Document::parse_identifier() {
lex< identifier >();
return Node(Node::identifier, line_number, lexed);
}
Node Document::parse_variable() {
lex< variable >();
return Node(Node::variable, line_number, lexed);
}
const char* Document::lookahead_for_selector(const char* start)
{
const char* p = start ? start : position;
const char* q;
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))) { p = q; }
if (peek< exactly<'{'> >(p)) return p;
else return 0;
}
}
// const char* Document::look_for_rule(const char* start)
// {
// const char* p = start ? start : position;
// (p = peek< identifier >(p)) &&
// (p = peek< exactly<':'> >(p)) &&
// (p = look_for_values(p)) &&
// (p = peek< alternatives< exactly<';'>, exactly<'}'> > >(p));
// return p;
// }
//
// const char* Document::look_for_values(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
// while ((q = peek< identifier >(p)) || (q = peek< dimension >(p)) ||
// (q = peek< percentage >(p)) || (q = peek< number >(p)) ||
// (q = peek< hex >(p)) || (q = peek< string_constant >(p)) ||
// (q = peek< variable >(p)))
// { p = q; }
// return p == start ? 0 : p;
// }
// // NEW LOOKAHEAD FUNCTIONS. THIS ESSENTIALLY IMPLEMENTS A BACKTRACKING
// // PARSER, BECAUSE SELECTORS AND VALUES ARE NOT EXPRESSIBLE IN A
// // REGULAR LANGUAGE.
// const char* Document::look_for_selector_group(const char* start)
// {
// const char* p = start ? start : position;
// const char* q = look_for_selector(p);
//
// if (!q) { return 0; }
// else { p = q; }
//
// while ((q = peek< exactly<','> >(p)) && (q = look_for_selector(q)))
// { p = q; }
//
// // return peek< exactly<'{'> >(p) ? p : 0;
// return peek< alternatives< exactly<'{'>, exactly<')'> > >(p) ? p : 0;
// }
//
// const char* Document::look_for_selector(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
//
// if ((q = peek< exactly<'+'> >(p)) ||
// (q = peek< exactly<'~'> >(p)) ||
// (q = peek< exactly<'>'> >(p)))
// { p = q; }
//
// p = look_for_simple_selector_sequence(p);
//
// if (!p) return 0;
//
// while (((q = peek< exactly<'+'> >(p)) ||
// (q = peek< exactly<'~'> >(p)) ||
// (q = peek< exactly<'>'> >(p)) ||
// (q = peek< ancestor_of > (p))) &&
// (q = look_for_simple_selector_sequence(q)))
// { p = q; }
//
// return p;
// }
//
// const char* Document::look_for_simple_selector_sequence(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
//
// if ((q = peek< type_selector >(p)) ||
// (q = peek< universal >(p)) ||
// (q = peek< exactly <'&'> >(p)) ||
// (q = look_for_simple_selector(p)))
// { p = q; }
// else
// { return 0; }
//
// while (!peek< spaces >(p) &&
// !(peek < exactly<'+'> >(p) ||
// peek < exactly<'~'> >(p) ||
// peek < exactly<'>'> >(p) ||
// peek < exactly<','> >(p) ||
// peek < exactly<')'> >(p) ||
// peek < exactly<'{'> >(p)) &&
// (q = look_for_simple_selector(p)))
// { p = q; }
//
// return p;
// }
//
// const char* Document::look_for_simple_selector(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
// (q = peek< id_name >(p)) || (q = peek< class_name >(p)) ||
// (q = look_for_pseudo(p)) || (q = look_for_attrib(p));
// // cerr << "looking for simple selector; found:" << endl;
// // cerr << (q ? string(Token::make(q,q+8)) : "nothing") << endl;
// return q;
// }
//
// const char* Document::look_for_pseudo(const char* start)
// {
// const char* p = start ? start : position;
// const char* q;
//
// if (q = peek< pseudo_not >(p)) {
// // (q = look_for_simple_selector(q)) && (q = peek< exactly<')'> >(q));
// (q = look_for_selector_group(q)) && (q = peek< exactly<')'> >(q));
// }
// else if (q = peek< sequence< pseudo_prefix, functional > >(p)) {
// p = q;
// (q = peek< alternatives< even, odd > >(p)) ||
// (q = peek< binomial >(p)) ||
// (q = peek< sequence< optional<sign>,
// optional<digits>,
// exactly<'n'> > >(p)) ||
// (q = peek< sequence< optional<sign>,
// digits > >(p));
// p = q;
// q = peek< exactly<')'> >(p);
// }
// else {
// q = peek< sequence< pseudo_prefix, identifier > >(p);
// }
// return q ? q : 0;
// }
//
// const char* Document::look_for_attrib(const char* start)
// {
// const char* p = start ? start : position;
//
// (p = peek< exactly<'['> >(p)) &&
// (p = peek< type_selector >(p)) &&
// (p = peek< alternatives<exact_match,
// class_match,
// dash_match,
// prefix_match,
// suffix_match,
// substring_match> >(p)) &&
// (p = peek< string_constant >(p)) &&
// (p = peek< exactly<']'> >(p));
//
// return p;
// }
// }
\ No newline at end of file
namespace Sass {
struct Error {
enum Type { read, write, syntax, evaluation };
Type type;
size_t line_number;
string file_name;
string message;
Error(Type type, size_t line_number, string file_name, string message)
: type(type), line_number(line_number), file_name(file_name), message(message)
{ }
};
}
\ No newline at end of file
#include "eval_apply.hpp"
#include "error.hpp"
#include <iostream>
#include <cstdlib>
namespace Sass {
using std::cerr; using std::endl;
static void eval_error(string message, size_t line_number, const char* file_name)
{
string fn;
if (file_name) {
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
}
Node eval(Node& expr, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
{
switch (expr.type)
{
case Node::mixin: {
env[expr[0].content.token] = expr;
return expr;
} break;
case Node::expansion: {
Token name(expr[0].content.token);
Node args(expr[1]);
if (!env.query(name)) eval_error("mixin " + name.to_string() + " is undefined", expr.line_number, expr.file_name);
Node mixin(env[name]);
Node expansion(apply_mixin(mixin, args, env, f_env, registry));
expr.content.children->pop_back();
expr.content.children->pop_back();
expr += expansion;
return expr;
} break;
case Node::ruleset: {
eval(expr[1], env, f_env, registry);
return expr;
} break;
case Node::root: {
for (int i = 0; i < expr.size(); ++i) {
eval(expr[i], env, f_env, registry);
}
return expr;
} break;
case Node::block: {
Environment current;
current.link(env);
for (int i = 0; i < expr.size(); ++i) {
eval(expr[i], current, f_env, registry);
}
return expr;
} break;
case Node::assignment: {
Node val(expr[1]);
if (val.type == Node::comma_list || val.type == Node::space_list) {
for (int i = 0; i < val.size(); ++i) {
if (val[i].eval_me) val[i] = eval(val[i], env, f_env, registry);
}
}
else {
val = eval(val, env, f_env, registry);
}
Node var(expr[0]);
if (env.query(var.content.token)) {
env[var.content.token] = val;
}
else {
env.current_frame[var.content.token] = val;
}
return expr;
} break;
case Node::rule: {
Node rhs(expr[1]);
if (rhs.type == Node::comma_list || rhs.type == Node::space_list) {
for (int i = 0; i < rhs.size(); ++i) {
if (rhs[i].eval_me) rhs[i] = eval(rhs[i], env, f_env, registry);
}
}
else if (rhs.type == Node::value_schema || rhs.type == Node::string_schema) {
eval(rhs, env, f_env, registry);
}
else {
if (rhs.eval_me) expr[1] = eval(rhs, env, f_env, registry);
}
return expr;
} break;
case Node::comma_list:
case Node::space_list: {
if (expr.eval_me) expr[0] = eval(expr[0], env, f_env, registry);
return expr;
} break;
case Node::disjunction: {
Node result;
for (int i = 0; i < expr.size(); ++i) {
// if (expr[i].type == Node::relation ||
// expr[i].type == Node::function_call && expr[0].content.token.to_string() == "not") {
result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) continue;
else return result;
}
return result;
} break;
case Node::conjunction: {
Node result;
for (int i = 0; i < expr.size(); ++i) {
result = eval(expr[i], env, f_env, registry);
if (result.type == Node::boolean && result.content.boolean_value == false) return result;
}
return result;
} break;
case Node::relation: {
Node lhs(eval(expr[0], env, f_env, registry));
Node op(expr[1]);
Node rhs(eval(expr[2], env, f_env, registry));
Node T(Node::boolean);
T.line_number = lhs.line_number;
T.content.boolean_value = true;
Node F(T);
F.content.boolean_value = false;
switch (op.type) {
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;
}
} break;
case Node::expression: {
Node acc(Node::expression, registry, expr.line_number, 1);
acc << eval(expr[0], env, f_env, registry);
Node rhs(eval(expr[2], env, f_env, registry));
accumulate(expr[1].type, acc, rhs, registry);
for (int i = 3; i < expr.size(); i += 2) {
Node rhs(eval(expr[i+1], env, f_env, registry));
accumulate(expr[i].type, acc, rhs, registry);
}
return acc.size() == 1 ? acc[0] : acc;
} break;
case Node::term: {
if (expr.eval_me) {
Node acc(Node::expression, registry, expr.line_number, 1);
acc << eval(expr[0], env, f_env, registry);
Node rhs(eval(expr[2], env, f_env, registry));
accumulate(expr[1].type, acc, rhs, registry);
for (int i = 3; i < expr.size(); i += 2) {
Node rhs(eval(expr[i+1], env, f_env, registry));
accumulate(expr[i].type, acc, rhs, registry);
}
return acc.size() == 1 ? acc[0] : acc;
}
else {
return expr;
}
} break;
case Node::textual_percentage: {
Node pct(expr.line_number, std::atof(expr.content.token.begin));
pct.type = Node::numeric_percentage;
return pct;
} break;
case Node::textual_dimension: {
return Node(expr.line_number,
std::atof(expr.content.token.begin),
Token::make(Prelexer::number(expr.content.token.begin),
expr.content.token.end));
} break;
case Node::textual_number: {
return Node(expr.line_number, std::atof(expr.content.token.begin));
} break;
case Node::textual_hex: {
Node triple(Node::numeric_color, registry, expr.line_number, 4);
Token hext(Token::make(expr.content.token.begin+1, expr.content.token.end));
if (hext.length() == 6) {
for (int i = 0; i < 6; i += 2) {
triple << Node(expr.line_number, static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
}
}
else {
for (int i = 0; i < 3; ++i) {
triple << Node(expr.line_number, static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
}
}
triple << Node(expr.line_number, 1.0);
return triple;
} break;
case Node::variable: {
if (!env.query(expr.content.token)) eval_error("reference to unbound variable " + expr.content.token.to_string(), expr.line_number, expr.file_name);
return env[expr.content.token];
} break;
case Node::function_call: {
// TO DO: default-constructed Function should be a generic callback
pair<string, size_t> sig(expr[0].content.token.to_string(), expr[1].size());
if (!f_env.count(sig)) {
// stringstream ss;
// ss << "no function named " << expr[0].content.token.to_string() << " taking " << expr[1].size() << " arguments has been defined";
// eval_error(ss.str(), expr.line_number, expr.file_name);
return expr;
}
return apply_function(f_env[sig], expr[1], env, f_env, registry);
} break;
case Node::unary_plus: {
Node arg(eval(expr[0], env, f_env, registry));
if (arg.is_numeric()) return arg;
else {
expr[0] = arg;
return expr;
}
} break;
case Node::unary_minus: {
Node arg(eval(expr[0], env, f_env, registry));
if (arg.is_numeric()) {
arg.set_numeric_value(-arg.numeric_value());
}
else {
expr[0] = arg;
return expr;
}
} break;
case Node::string_schema:
case Node::value_schema: {
// cerr << "evaluating schema of size " << expr.size() << endl;
for (int i = 0; i < expr.size(); ++i) {
expr[i] = eval(expr[i], env, f_env, registry);
}
return expr;
} break;
default: {
return expr;
}
}
}
Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry)
{
Node lhs(acc.content.children->back());
double lnum = lhs.numeric_value();
double rnum = rhs.numeric_value();
if (lhs.type == Node::number && rhs.type == Node::number) {
Node result(acc.line_number, operate(op, lnum, rnum));
acc.content.children->pop_back();
acc.content.children->push_back(result);
}
// TO DO: find a way to merge the following two clauses
else if (lhs.type == Node::number && rhs.type == Node::numeric_dimension) {
Node result(acc.line_number, operate(op, lnum, rnum), Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit)));
acc.content.children->pop_back();
acc.content.children->push_back(result);
}
else if (lhs.type == Node::numeric_dimension && rhs.type == Node::number) {
Node result(acc.line_number, operate(op, lnum, rnum), Token::make(lhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit)));
acc.content.children->pop_back();
acc.content.children->push_back(result);
}
else if (lhs.type == Node::numeric_dimension && rhs.type == Node::numeric_dimension) {
// TO DO: CHECK FOR MISMATCHED UNITS HERE
Node result;
if (op == Node::div)
{ result = Node(acc.line_number, operate(op, lnum, rnum)); }
else
{ result = Node(acc.line_number, operate(op, lnum, rnum), Token::make(lhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))); }
acc.content.children->pop_back();
acc.content.children->push_back(result);
}
// 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.content.numeric_value, rhs[0].content.numeric_value);
double g = operate(op, lhs.content.numeric_value, rhs[1].content.numeric_value);
double b = operate(op, lhs.content.numeric_value, rhs[2].content.numeric_value);
double a = rhs[3].content.numeric_value;
acc.content.children->pop_back();
acc << Node(registry, acc.line_number, r, g, b, a);
}
// trying to handle weird edge cases ... not sure if it's worth it
else if (op == Node::div) {
acc << Node(Node::div);
acc << rhs;
}
else if (op == Node::sub) {
acc << Node(Node::sub);
acc << rhs;
}
else {
acc << rhs;
}
}
else if (lhs.type == Node::numeric_color && rhs.type == Node::number) {
double r = operate(op, lhs[0].content.numeric_value, rhs.content.numeric_value);
double g = operate(op, lhs[1].content.numeric_value, rhs.content.numeric_value);
double b = operate(op, lhs[2].content.numeric_value, rhs.content.numeric_value);
double a = lhs[3].content.numeric_value;
acc.content.children->pop_back();
acc << Node(registry, acc.line_number, r, g, b, a);
}
else if (lhs.type == Node::numeric_color && rhs.type == Node::numeric_color) {
if (lhs[3].content.numeric_value != rhs[3].content.numeric_value) eval_error("alpha channels must be equal for " + lhs.to_string("") + " + " + rhs.to_string(""), lhs.line_number, lhs.file_name);
double r = operate(op, lhs[0].content.numeric_value, rhs[0].content.numeric_value);
double g = operate(op, lhs[1].content.numeric_value, rhs[1].content.numeric_value);
double b = operate(op, lhs[2].content.numeric_value, rhs[2].content.numeric_value);
double a = lhs[3].content.numeric_value;
acc.content.children->pop_back();
acc << Node(registry, acc.line_number, r, g, b, a);
}
// else if (lhs.type == Node::concatenation) {
// lhs << rhs;
// }
// else if (lhs.type == Node::string_constant || rhs.type == Node::string_constant) {
// acc.content.children->pop_back();
// Node cat(Node::concatenation, lhs.line_number, 2);
// cat << lhs << rhs;
// acc << cat;
// }
else {
// TO DO: disallow division and multiplication on lists
acc.content.children->push_back(rhs);
}
return acc;
}
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;
}
}
Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
{
Node params(mixin[1]);
Node body(mixin[2].clone(registry));
Environment bindings;
// bind arguments
for (int i = 0, j = 0; i < args.size(); ++i) {
if (args[i].type == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].content.token);
// check that the keyword arg actually names a formal parameter
bool valid_param = false;
for (int k = 0; k < params.size(); ++k) {
Node param_k = params[k];
if (param_k.type == Node::assignment) param_k = param_k[0];
if (arg[0] == param_k) {
valid_param = true;
break;
}
}
if (!valid_param) eval_error("mixin " + mixin[0].to_string("") + " has no parameter named " + name.to_string(), arg.line_number, arg.file_name);
if (!bindings.query(name)) {
bindings[name] = eval(arg[1], env, f_env, registry);
}
}
else {
// ensure that the number of ordinal args < params.size()
if (j >= params.size()) {
stringstream ss;
ss << "mixin " << mixin[0].to_string("") << " only takes " << params.size() << ((params.size() == 1) ? " argument" : " arguments");
eval_error(ss.str(), args[i].line_number, args[i].file_name);
}
Node param(params[j]);
Token name(param.type == Node::variable ? param.content.token : param[0].content.token);
bindings[name] = eval(args[i], env, f_env, registry);
++j;
}
}
// plug the holes with default arguments if any
for (int i = 0; i < params.size(); ++i) {
if (params[i].type == Node::assignment) {
Node param(params[i]);
Token name(param[0].content.token);
if (!bindings.query(name)) {
bindings[name] = eval(param[1], env, f_env, registry);
}
}
}
// lexically link the new environment and eval the mixin's body
bindings.link(env.global ? *env.global : env);
for (int i = 0; i < body.size(); ++i) {
body[i] = eval(body[i], bindings, f_env, registry);
}
return body;
}
Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry)
{
map<Token, Node> bindings;
// bind arguments
for (int i = 0, j = 0; i < args.size(); ++i) {
if (args[i].type == Node::assignment) {
Node arg(args[i]);
Token name(arg[0].content.token);
bindings[name] = eval(arg[1], env, f_env, registry);
}
else {
// TO DO: ensure (j < f.parameters.size())
bindings[f.parameters[j]] = eval(args[i], env, f_env, registry);
++j;
}
}
return f(bindings, registry);
}
}
\ No newline at end of file
#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, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node accumulate(Node::Type op, Node& acc, Node& rhs, vector<vector<Node>*>& registry);
double operate(Node::Type op, double lhs, double rhs);
Node apply_mixin(Node& mixin, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
Node apply_function(const Function& f, const Node& args, Environment& env, map<pair<string, size_t>, Function>& f_env, vector<vector<Node>*>& registry);
}
\ 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 "functions.hpp"
#include "error.hpp"
#include <iostream>
#include <cmath>
using std::cerr; using std::endl;
namespace Sass {
namespace Functions {
static void eval_error(string message, size_t line_number, const char* file_name)
{
string fn;
if (file_name) {
const char* end = Prelexer::string_constant(file_name);
if (end) fn = string(file_name, end - file_name);
else fn = string(file_name);
}
throw Error(Error::evaluation, line_number, fn, message);
}
// RGB Functions ///////////////////////////////////////////////////////
Function_Descriptor rgb_descriptor =
{ "rgb", "$red", "$green", "$blue", 0 };
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]);
if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number)) {
eval_error("arguments for rgb must be numbers", r.line_number, r.file_name);
}
Node color(Node::numeric_color, registry, 0, 4);
color << r << g << b << Node(0, 1.0);
return color;
}
Function_Descriptor rgba_4_descriptor =
{ "rgba", "$red", "$green", "$blue", "$alpha", 0 };
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node r(bindings[parameters[0]]);
Node g(bindings[parameters[1]]);
Node b(bindings[parameters[2]]);
Node a(bindings[parameters[3]]);
if (!(r.type == Node::number && g.type == Node::number && b.type == Node::number && a.type == Node::number)) {
eval_error("arguments for rgba must be numbers", r.line_number, r.file_name);
}
Node color(Node::numeric_color, registry, 0, 4);
color << r << g << b << a;
return color;
}
Function_Descriptor rgba_2_descriptor =
{ "rgba", "$color", "$alpha", 0 };
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]].clone(registry));
color[3] = bindings[parameters[1]];
return color;
}
Function_Descriptor red_descriptor =
{ "red", "$color", 0 };
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to red must be a color", color.line_number, color.file_name);
return color[0];
}
Function_Descriptor green_descriptor =
{ "green", "$color", 0 };
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to green must be a color", color.line_number, color.file_name);
return color[1];
}
Function_Descriptor blue_descriptor =
{ "blue", "$color", 0 };
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to blue must be a color", color.line_number, color.file_name);
return color[2];
}
Node mix_impl(Node color1, Node color2, double weight, vector<vector<Node>*>& registry) {
if (!(color1.type == Node::numeric_color && color2.type == Node::numeric_color)) {
eval_error("first two arguments to mix must be colors", color1.line_number, color1.file_name);
}
double p = weight/100;
double w = 2*p - 1;
double a = color1[3].content.numeric_value - color2[3].content.numeric_value;
double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0;
double w2 = 1 - w1;
Node mixed(Node::numeric_color, registry, color1.line_number, 4);
for (int i = 0; i < 3; ++i) {
mixed << Node(mixed.line_number, w1*color1[i].content.numeric_value +
w2*color2[i].content.numeric_value);
}
double alpha = color1[3].content.numeric_value*p + color2[3].content.numeric_value*(1-p);
mixed << Node(mixed.line_number, alpha);
return mixed;
}
Function_Descriptor mix_2_descriptor =
{ "mix", "$color1", "$color2", 0 };
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return mix_impl(bindings[parameters[0]], bindings[parameters[1]], 50, registry);
}
Function_Descriptor mix_3_descriptor =
{ "mix", "$color1", "$color2", "$weight", 0 };
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node percentage(bindings[parameters[2]]);
if (!(percentage.type == Node::number || percentage.type == Node::numeric_percentage || percentage.type == Node::numeric_dimension)) {
eval_error("third argument to mix must be numeric", percentage.line_number, percentage.file_name);
}
return mix_impl(bindings[parameters[0]],
bindings[parameters[1]],
percentage.numeric_value(),
registry);
}
// 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, vector<vector<Node>*>& registry) {
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 Node(registry, 0, r, g, b, a);
}
Function_Descriptor hsla_descriptor =
{ "hsla", "$hue", "$saturation", "$lightness", "$alpha", 0 };
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric() &&
bindings[parameters[3]].is_numeric())) {
eval_error("arguments to hsla must be numeric", bindings[parameters[0]].line_number, bindings[parameters[0]].file_name);
}
double h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value();
double a = bindings[parameters[3]].numeric_value();
Node color(hsla_impl(h, s, l, a, registry));
color.line_number = bindings[parameters[0]].line_number;
return color;
}
Function_Descriptor hsl_descriptor =
{ "hsl", "$hue", "$saturation", "$lightness", 0 };
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
if (!(bindings[parameters[0]].is_numeric() &&
bindings[parameters[1]].is_numeric() &&
bindings[parameters[2]].is_numeric())) {
eval_error("arguments to hsl must be numeric", bindings[parameters[0]].line_number, bindings[parameters[0]].file_name);
}
double h = bindings[parameters[0]].numeric_value();
double s = bindings[parameters[1]].numeric_value();
double l = bindings[parameters[2]].numeric_value();
Node color(hsla_impl(h, s, l, 1, registry));
color.line_number = bindings[parameters[0]].line_number;
return color;
}
Function_Descriptor invert_descriptor =
{ "invert", "$color", 0 };
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node orig(bindings[parameters[0]]);
if (orig.type != Node::numeric_color) eval_error("argument to invert must be a color", orig.line_number, orig.file_name);
return Node(registry, orig.line_number,
255 - orig[0].content.numeric_value,
255 - orig[1].content.numeric_value,
255 - orig[2].content.numeric_value,
orig[3].content.numeric_value);
}
// 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, vector<vector<Node>*>& registry) {
Node color(bindings[parameters[0]]);
if (color.type != Node::numeric_color) eval_error("argument to alpha must be a color", color.line_number, color.file_name);
return color[3];
}
Function_Descriptor opacify_descriptor =
{ "opacify", "$color", "$amount", 0 };
Function_Descriptor fade_in_descriptor =
{ "fade_in", "$color", "$amount", 0 };
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type != Node::numeric_color || !bindings[parameters[1]].is_numeric()) {
eval_error("arguments to opacify/fade_in must be a color and a numeric value", cpy.line_number, cpy.file_name);
}
Node delta(bindings[parameters[1]]);
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) eval_error("amount must be between 0 and 1 for opacify/fade-in", delta.line_number, delta.file_name);
cpy[3].content.numeric_value += delta.numeric_value();
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1;
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0;
return cpy;
}
Function_Descriptor transparentize_descriptor =
{ "transparentize", "$color", "$amount", 0 };
Function_Descriptor fade_out_descriptor =
{ "fade_out", "$color", "$amount", 0 };
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type != Node::numeric_color || !bindings[parameters[1]].is_numeric()) {
eval_error("arguments to transparentize/fade_out must be a color and a numeric value", cpy.line_number, cpy.file_name);
}
Node delta(bindings[parameters[1]]);
if (delta.numeric_value() < 0 || delta.numeric_value() > 1) eval_error("amount must be between 0 and 1 for transparentize/fade-out", delta.line_number, delta.file_name);
cpy[3].content.numeric_value -= delta.numeric_value();
if (cpy[3].numeric_value() > 1) cpy[3].content.numeric_value = 1;
if (cpy[3].numeric_value() < 0) cpy[3].content.numeric_value = 0;
return cpy;
}
// String Functions ////////////////////////////////////////////////////
Function_Descriptor unquote_descriptor =
{ "unquote", "$string", 0 };
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
// if (cpy.type != Node::string_constant /* && cpy.type != Node::concatenation */) {
// eval_error("argument to unquote must be a string", cpy.line_number, cpy.file_name);
// }
cpy.unquoted = true;
return cpy;
}
Function_Descriptor quote_descriptor =
{ "quote", "$string", 0 };
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type != Node::string_constant && cpy.type != Node::identifier) {
eval_error("argument to quote must be a string or identifier", cpy.line_number, cpy.file_name);
}
cpy.type = Node::string_constant;
cpy.unquoted = false;
return cpy;
}
// Number Functions ////////////////////////////////////////////////////
Function_Descriptor percentage_descriptor =
{ "percentage", "$value", 0 };
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
// TO DO: make sure it's not already a percentage
if (cpy.type != Node::number) eval_error("argument to percentage must be a unitless number", cpy.line_number, cpy.file_name);
cpy.content.numeric_value = cpy.content.numeric_value * 100;
cpy.type = Node::numeric_percentage;
return cpy;
}
Function_Descriptor round_descriptor =
{ "round", "$value", 0 };
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value + 0.5);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage) {
cpy.content.numeric_value = std::floor(cpy.content.numeric_value + 0.5);
}
else {
eval_error("argument to round must be numeric", cpy.line_number, cpy.file_name);
}
return cpy;
}
Function_Descriptor ceil_descriptor =
{ "ceil", "$value", 0 };
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::ceil(cpy.content.dimension.numeric_value);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::ceil(cpy.content.numeric_value);
}
else {
eval_error("argument to ceil must be numeric", cpy.line_number, cpy.file_name);
}
return cpy;
}
Function_Descriptor floor_descriptor =
{ "floor", "$value", 0 };
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::floor(cpy.content.dimension.numeric_value);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::floor(cpy.content.numeric_value);
}
else {
eval_error("argument to floor must be numeric", cpy.line_number, cpy.file_name);
}
return cpy;
}
Function_Descriptor abs_descriptor =
{ "abs", "$value", 0 };
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node cpy(bindings[parameters[0]].clone(registry));
if (cpy.type == Node::numeric_dimension) {
cpy.content.dimension.numeric_value = std::fabs(cpy.content.dimension.numeric_value);
}
else if (cpy.type == Node::number || cpy.type == Node::numeric_percentage){
cpy.content.numeric_value = std::abs(cpy.content.numeric_value);
}
else {
eval_error("argument to abs must be numeric", cpy.line_number, cpy.file_name);
}
return cpy;
}
// List Functions //////////////////////////////////////////////////////
Function_Descriptor length_descriptor =
{ "length", "$list", 0 };
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node arg(bindings[parameters[0]]);
if (arg.type == Node::space_list || arg.type == Node::comma_list) {
return Node(arg.line_number, arg.size());
}
else if (arg.type == Node::nil) {
return Node(arg.line_number, 0);
}
// single objects should be reported as lists of length 1
else {
return Node(arg.line_number, 1);
}
}
Function_Descriptor nth_descriptor =
{ "nth", "$list", "$n", 0 };
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node l(bindings[parameters[0]]);
// TO DO: check for empty list
if (l.type == Node::nil) eval_error("cannot index into an empty list", l.line_number, l.file_name);
if (l.type != Node::space_list && l.type != Node::comma_list) {
l = Node(Node::space_list, registry, l.line_number, 1) << l;
}
Node n(bindings[parameters[1]]);
if (n.type != Node::number) eval_error("second argument to nth must be a number", n.line_number, n.file_name);
if (n.numeric_value() < 1 || n.numeric_value() > l.size()) eval_error("out of range index for nth", n.line_number, n.file_name);
return l[bindings[parameters[1]].content.numeric_value - 1];
}
extern const char separator_kwd[] = "$separator";
Node join_impl(const vector<Token>& parameters, map<Token, Node>& bindings, bool has_sep, vector<vector<Node>*>& registry) {
// if the args aren't lists, turn them into singleton lists
Node l1(bindings[parameters[0]]);
if (l1.type != Node::space_list && l1.type != Node::comma_list && l1.type != Node::nil) {
l1 = Node(Node::space_list, registry, l1.line_number, 1) << l1;
}
Node l2(bindings[parameters[1]]);
if (l2.type != Node::space_list && l2.type != Node::comma_list && l2.type != Node::nil) {
l2 = Node(Node::space_list, registry, l2.line_number, 1) << l2;
}
// nil and nil is nil
if (l1.type == Node::nil && l2.type == Node::nil) return Node(Node::nil, registry, l1.line_number);
// figure out the combined size in advance
size_t size = 0;
if (l1.type != Node::nil) size += l1.size();
if (l2.type != Node::nil) size += l2.size();
// accumulate the result
Node lr(l1.type, registry, l1.line_number, size);
if (has_sep) {
string sep(bindings[parameters[2]].content.token.unquote());
if (sep == "comma") lr.type = Node::comma_list;
else if (sep == "space") lr.type = Node::space_list;
else if (sep == "auto") ; // leave it alone
else {
eval_error("third argument to join must be 'space', 'comma', or 'auto'", l2.line_number, l2.file_name);
}
}
else if (l1.type != Node::nil) lr.type = l1.type;
else if (l2.type != Node::nil) lr.type = l2.type;
if (l1.type != Node::nil) lr += l1;
if (l2.type != Node::nil) lr += l2;
return lr;
}
Function_Descriptor join_2_descriptor =
{ "join", "$list1", "$list2", 0 };
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return join_impl(parameters, bindings, false, registry);
}
Function_Descriptor join_3_descriptor =
{ "join", "$list1", "$list2", "$separator", 0 };
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
return join_impl(parameters, bindings, true, registry);
}
// 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, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]);
Node type(Node::string_constant, val.line_number, Token::make());
type.unquoted = true;
switch (val.type)
{
case Node::number:
case Node::numeric_dimension:
case Node::numeric_percentage:
type.content.token = Token::make(number_name);
break;
case Node::boolean:
type.content.token = Token::make(bool_name);
break;
case Node::string_constant:
case Node::value_schema:
type.content.token = Token::make(string_name);
break;
case Node::numeric_color:
type.content.token = Token::make(color_name);
break;
case Node::comma_list:
case Node::space_list:
case Node::nil:
type.content.token = Token::make(list_name);
break;
default:
type.content.token = Token::make(string_name);
}
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, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]);
Node u(Node::string_constant, val.line_number, Token::make());
switch (val.type)
{
case Node::number:
u.content.token = Token::make(empty_str);
break;
case Node::numeric_percentage:
u.content.token = Token::make(percent_str);
break;
case Node::numeric_dimension:
u.content.token = Token::make(val.content.dimension.unit, Prelexer::identifier(val.content.dimension.unit));
break;
default:
eval_error("argument to unit must be numeric", val.line_number, val.file_name);
break;
}
return u;
}
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, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]);
Node result(Node::string_constant, val.line_number, Token::make());
result.unquoted = true;
switch (val.type)
{
case Node::number: {
Node T(Node::boolean);
T.line_number = val.line_number;
T.content.boolean_value = true;
return T;
} break;
case Node::numeric_percentage:
case Node::numeric_dimension: {
Node F(Node::boolean);
F.line_number = val.line_number;
F.content.boolean_value = false;
return F;
} break;
default:
eval_error("argument to unitless must be numeric", val.line_number, val.file_name);
break;
}
return result;
}
Function_Descriptor comparable_descriptor =
{ "comparable", "$number_1", "$number_2", 0 };
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node n1(bindings[parameters[0]]);
Node n2(bindings[parameters[1]]);
Node::Type t1 = n1.type;
Node::Type t2 = n2.type;
if (t1 == Node::number && n2.is_numeric() ||
n1.is_numeric() && t2 == Node::number) {
Node T(Node::boolean);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
}
else if (t1 == Node::numeric_percentage && t2 == Node::numeric_percentage) {
Node T(Node::boolean);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
}
else if (t1 == Node::numeric_dimension && t2 == Node::numeric_dimension) {
string u1(Token::make(n1.content.dimension.unit, Prelexer::identifier(n1.content.dimension.unit)).to_string());
string u2(Token::make(n2.content.dimension.unit, Prelexer::identifier(n2.content.dimension.unit)).to_string());
if (u1 == "ex" && u2 == "ex" ||
u1 == "em" && u2 == "em" ||
(u1 == "in" || u1 == "cm" || u1 == "mm" || u1 == "pt" || u1 == "pc") &&
(u2 == "in" || u2 == "cm" || u2 == "mm" || u2 == "pt" || u2 == "pc")) {
Node T(Node::boolean);
T.line_number = n1.line_number;
T.content.boolean_value = true;
return T;
}
else {
Node F(Node::boolean);
F.line_number = n1.line_number;
F.content.boolean_value = false;
return F;
}
}
else if (!n1.is_numeric() && !n2.is_numeric()) {
eval_error("arguments to comparable must be numeric", n1.line_number, n1.file_name);
}
else {
Node F(Node::boolean);
F.line_number = n1.line_number;
F.content.boolean_value = false;
return F;
}
}
// Boolean Functions ///////////////////////////////////////////////////
Function_Descriptor not_descriptor =
{ "not", "value", 0 };
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry) {
Node val(bindings[parameters[0]]);
if (val.type == Node::boolean && val.content.boolean_value == false) {
Node T(Node::boolean);
T.line_number = val.line_number;
T.content.boolean_value = true;
return T;
}
else {
Node F(Node::boolean);
F.line_number = val.line_number;
F.content.boolean_value = false;
return F;
}
}
}
}
\ No newline at end of file
#include <cstring>
#include <map>
#ifndef SASS_NODE_INCLUDED
#include "node.hpp"
#endif
namespace Sass {
using std::map;
typedef Node (*Implementation)(const vector<Token>&, map<Token, Node>&, vector<vector<Node>*>& registry);
typedef const char* str;
typedef str Function_Descriptor[];
struct Function {
string name;
vector<Token> parameters;
Implementation implementation;
Function()
{ /* TO DO: set up the generic callback here */ }
Function(Function_Descriptor d, Implementation ip)
: name(d[0]),
parameters(vector<Token>()),
implementation(ip)
{
size_t len = 0;
while (d[len+1]) ++len;
parameters.reserve(len);
for (int 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, vector<vector<Node>*>& registry) const
{ return implementation(parameters, bindings, registry); }
};
namespace Functions {
// RGB Functions ///////////////////////////////////////////////////////
extern Function_Descriptor rgb_descriptor;
Node rgb(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor rgba_4_descriptor;
Node rgba_4(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor rgba_2_descriptor;
Node rgba_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor red_descriptor;
Node red(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor green_descriptor;
Node green(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor blue_descriptor;
Node blue(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor mix_2_descriptor;
Node mix_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor mix_3_descriptor;
Node mix_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// HSL Functions ///////////////////////////////////////////////////////
extern Function_Descriptor hsla_descriptor;
Node hsla(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor hsl_descriptor;
Node hsl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor invert_descriptor;
Node invert(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// Opacity Functions ///////////////////////////////////////////////////
extern Function_Descriptor alpha_descriptor;
extern Function_Descriptor opacity_descriptor;
Node alpha(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor opacify_descriptor;
extern Function_Descriptor fade_in_descriptor;
Node opacify(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor transparentize_descriptor;
extern Function_Descriptor fade_out_descriptor;
Node transparentize(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// String Functions ////////////////////////////////////////////////////
extern Function_Descriptor unquote_descriptor;
Node unquote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor quote_descriptor;
Node quote(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// Number Functions ////////////////////////////////////////////////////
extern Function_Descriptor percentage_descriptor;
Node percentage(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor round_descriptor;
Node round(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor ceil_descriptor;
Node ceil(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor floor_descriptor;
Node floor(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor abs_descriptor;
Node abs(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// List Functions //////////////////////////////////////////////////////
extern Function_Descriptor length_descriptor;
Node length(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor nth_descriptor;
Node nth(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor join_2_descriptor;
Node join_2(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor join_3_descriptor;
Node join_3(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// Introspection Functions /////////////////////////////////////////////
extern Function_Descriptor type_of_descriptor;
Node type_of(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor unit_descriptor;
Node unit(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor unitless_descriptor;
Node unitless(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
extern Function_Descriptor comparable_descriptor;
Node comparable(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
// Boolean Functions ///////////////////////////////////////////////////
extern Function_Descriptor not_descriptor;
Node not_impl(const vector<Token>& parameters, map<Token, Node>& bindings, vector<vector<Node>*>& registry);
}
}
#include <iostream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include "node.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
namespace Sass {
size_t Node::allocations = 0;
size_t Node::destructed = 0;
Node Node::clone(vector<vector<Node>*>& registry) const
{
Node n(*this);
if (has_children) {
n.content.children = new vector<Node>;
++allocations;
n.content.children->reserve(size());
for (int i = 0; i < size(); ++i) {
n << at(i).clone(registry);
}
registry.push_back(n.content.children);
}
return n;
}
string Node::to_string(const string& prefix) const
{
switch (type)
{
case selector_group: { // really only needed for arg to :not
string result(at(0).to_string(""));
for (int i = 1; i < size(); ++i) {
result += ", ";
result += at(i).to_string("");
}
return result;
} break;
case selector: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += ' ';
}
// if (at(0).type == selector_combinator) {
// result += at(0).content.token.to_string();
// result += ' ';
// }
// else {
// Node::Type t = at(0).type;
// result += at(0).to_string(t == backref ? prefix : "");
// }
Node::Type t = at(0).type;
result += at(0).to_string(at(0).has_backref ? prefix : "");
for (int i = 1; i < size(); ++i) {
Node::Type t = at(i).type;
result += " ";
result += at(i).to_string(at(i).has_backref ? prefix : "");
}
return result;
} break;
case selector_combinator: {
string result(prefix.empty() ? "" : prefix + " ");
result += content.token.to_string();
return result;
// return content.token.to_string();
// if (std::isspace(content.token.begin[0])) return string(" ");
// else return string(content.token);
} break;
case simple_selector_sequence: {
string result;
if (!has_backref && !prefix.empty()) {
result += prefix;
result += " ";
}
for (int i = 0; i < size(); ++i) {
Node::Type t = at(i).type;
result += at(i).to_string(t == backref ? prefix : "");
}
return result;
} break;
case pseudo:
case simple_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += content.token.to_string();
return result;
} break;
case pseudo_negation: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
result += at(1).to_string("");
result += ')';
return result;
} break;
case functional_pseudo: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += at(0).to_string("");
for (int i = 1; i < size(); ++i) {
result += at(i).to_string("");
}
result += ')';
return result;
} break;
case attribute_selector: {
string result(prefix);
if (!prefix.empty()) result += " ";
result += "[";
for (int i = 0; i < size(); ++i)
{ result += at(i).to_string(prefix); }
result += ']';
return result;
} break;
case backref: {
return prefix;
} break;
case comma_list: {
string result(at(0).to_string(prefix));
for (int i = 1; i < size(); ++i) {
if (at(i).type == nil) continue;
result += ", ";
result += at(i).to_string(prefix);
}
return result;
} break;
case space_list: {
string result(at(0).to_string(prefix));
for (int i = 1; i < size(); ++i) {
if (at(i).type == nil) continue;
result += " ";
result += at(i).to_string(prefix);
}
return result;
} break;
case expression:
case term: {
string result(at(0).to_string(prefix));
for (int i = 1; i < size(); ++i) {
if (!(at(i).type == add ||
// at(i).type == sub || // another edge case -- consider uncommenting
at(i).type == mul)) {
result += at(i).to_string(prefix);
}
}
return result;
} break;
//edge case
case sub: {
return "-";
} break;
case div: {
return "/";
} break;
case css_import: {
stringstream ss;
ss << "@import url(";
ss << content.token.to_string();
// cerr << content.token.to_string() << endl;
ss << ")";
return ss.str();
}
case function_call: {
stringstream ss;
ss << at(0).to_string("");
ss << "(";
ss << at(1).to_string("");
ss << ")";
return ss.str();
}
case arguments: {
stringstream ss;
if (size() > 0) {
ss << at(0).to_string("");
for (int i = 1; i < size(); ++i) {
ss << ", ";
ss << at(i).to_string("");
}
}
return ss.str();
}
case unary_plus: {
stringstream ss;
ss << "+";
ss << at(0).to_string("");
return ss.str();
}
case unary_minus: {
stringstream ss;
ss << "-";
ss << at(0).to_string("");
return ss.str();
}
case numeric_percentage: {
stringstream ss;
ss << content.dimension.numeric_value;
ss << '%';
return ss.str();
}
case numeric_dimension: {
stringstream ss;
ss << content.dimension.numeric_value;
ss << string(content.dimension.unit, 2);
// << string(content.dimension.unit, Prelexer::identifier(content.dimension.unit) - content.dimension.unit);
// cerr << Token::make(content.dimension.unit, content.dimension.unit + 2).to_string();
// << Token::make(content.dimension.unit, Prelexer::identifier(content.dimension.unit)).to_string();
return ss.str();
} break;
case number: {
stringstream ss;
ss << content.numeric_value;
return ss.str();
} break;
case numeric_color: {
if (at(3).content.numeric_value >= 1.0) {
double a = at(0).content.numeric_value;
double b = at(1).content.numeric_value;
double c = at(2).content.numeric_value;
if (a >= 0xff && b >= 0xff && c >= 0xff)
{ return "white"; }
else if (a >= 0xff && b >= 0xff && c == 0)
{ return "yellow"; }
else if (a == 0 && b >= 0xff && c >= 0xff)
{ return "aqua"; }
else if (a >= 0xff && b == 0 && c >= 0xff)
{ return "fuchsia"; }
else if (a >= 0xff && b == 0 && c == 0)
{ return "red"; }
else if (a == 0 && b >= 0xff && c == 0)
{ return "lime"; }
else if (a == 0 && b == 0 && c >= 0xff)
{ return "blue"; }
else if (a <= 0 && b <= 0 && c <= 0)
{ return "black"; }
else
{
stringstream ss;
ss << '#' << std::setw(2) << std::setfill('0') << std::hex;
for (int i = 0; i < 3; ++i) {
double x = at(i).content.numeric_value;
if (x > 0xff) x = 0xff;
else if (x < 0) x = 0;
ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(x+0.5));
}
return ss.str();
}
}
else {
stringstream ss;
ss << "rgba(" << static_cast<unsigned long>(at(0).content.numeric_value);
for (int i = 1; i < 3; ++i) {
ss << ", " << static_cast<unsigned long>(at(i).content.numeric_value);
}
ss << ", " << at(3).content.numeric_value << ')';
return ss.str();
}
} break;
case uri: {
string result("url(");
result += string(content.token);
result += ")";
return result;
} break;
// case expansion: {
// string result("MIXIN CALL: ");
// return result;
// } break;
case string_constant: {
if (unquoted) return content.token.unquote();
else {
string result(content.token.to_string());
if (result[0] != '"' && result[0] != '\'') return "\"" + result + "\"";
else return result;
}
} break;
case boolean: {
if (content.boolean_value) return "true";
else return "false";
} break;
case important: {
return "!important";
} break;
case value_schema: {
string result;
for (int i = 0; i < size(); ++i) result += at(i).to_string("");
return result;
} break;
case string_schema: {
string result;
for (int i = 0; i < size(); ++i) result += at(i).to_string("");
return result;
} break;
default: {
// return content.token.to_string();
if (!has_children && type != flags) return content.token.to_string();
else return "";
} break;
}
}
void Node::echo(stringstream& buf, size_t depth) {
string indentation(2*depth, ' ');
switch (type) {
case comment:
buf << indentation << string(content.token) << endl;
break;
case ruleset:
buf << indentation;
at(0).echo(buf, depth);
at(1).echo(buf, depth);
break;
case selector_group:
at(0).echo(buf, depth);
for (int i = 1; i < size(); ++i) {
buf << ", ";
at(i).echo(buf, depth);
}
break;
case selector:
for (int i = 0; i < size(); ++i) {
at(i).echo(buf, depth);
}
break;
case selector_combinator:
if (std::isspace(content.token.begin[0])) buf << ' ';
else buf << ' ' << string(content.token) << ' ';
break;
case simple_selector_sequence:
for (int i = 0; i < size(); ++i) {
buf << at(i).to_string(string());
}
break;
case simple_selector:
buf << string(content.token);
break;
case block:
buf << " {" << endl;
for (int i = 0; i < size(); at(i++).echo(buf, depth+1)) ;
buf << indentation << "}" << endl;
break;
case rule:
buf << indentation;
at(0).echo(buf, depth);
buf << ": ";
at(1).echo(buf, depth);
buf << ';' << endl;
break;
case property:
buf << string(content.token);
break;
case values:
for (int i = 0; i < size(); at(i++).echo(buf, depth)) ;
break;
case value:
buf << ' ' << string(content.token);
break;
}
}
void Node::emit_nested_css(stringstream& buf,
size_t depth,
const vector<string>& prefixes)
{
switch (type)
{
case root:
if (at(0).has_expansions) {
flatten();
}
for (int i = 0; i < size(); ++i) {
at(i).emit_nested_css(buf, depth, prefixes);
if (at(i).type == css_import) buf << endl;
}
break;
case ruleset: {
Node sel_group(at(0));
size_t sel_group_size = (sel_group.type == selector_group) ? sel_group.size() : 1; // parser ensures no singletons
Node block(at(1));
vector<string> new_prefixes;
if (prefixes.empty()) {
new_prefixes.reserve(sel_group_size);
for (int i = 0; i < sel_group_size; ++i) {
new_prefixes.push_back(sel_group_size > 1 ? sel_group[i].to_string(string()) : sel_group.to_string(string()));
}
}
else {
new_prefixes.reserve(prefixes.size() * sel_group_size);
for (int i = 0; i < prefixes.size(); ++i) {
for (int j = 0; j < sel_group_size; ++j) {
new_prefixes.push_back(sel_group_size > 1 ? sel_group[j].to_string(prefixes[i]) : sel_group.to_string(prefixes[i]));
}
}
}
if (block[0].has_expansions) block.flatten();
if (block[0].has_statements) {
buf << string(2*depth, ' ') << new_prefixes[0];
for (int i = 1; i < new_prefixes.size(); ++i) {
buf << ", " << new_prefixes[i];
}
buf << " {";
for (int i = 0; i < block.size(); ++i) {
Type stm_type = block[i].type;
if (stm_type == comment || stm_type == rule || stm_type == css_import || stm_type == propset) {
block[i].emit_nested_css(buf, depth+1); // NEED OVERLOADED VERSION FOR COMMENTS AND RULES
}
// else if (stm_type == propset) {
// block[i].emit_nested_css(buf, depth, new_prefixes);
// }
}
buf << " }" << endl;
++depth; // if we printed content at this level, we need to indent any nested rulesets
}
if (block[0].has_blocks) {
for (int i = 0; i < block.size(); ++i) {
if (block[i].type == ruleset) {
block[i].emit_nested_css(buf, depth, new_prefixes);
}
}
}
if (block[0].has_statements) --depth; // see previous comment
if (depth == 0 && prefixes.empty()) buf << endl;
} break;
default:
emit_nested_css(buf, depth); // pass it along to the simpler version
break;
}
}
void Node::emit_nested_css(stringstream& buf, size_t depth)
{
switch (type)
{
case propset: {
emit_propset(buf, depth, "");
// string prefix(string(2*depth, ' ') + at(0).content.token.to_string() + "-");
// Node rules(at(1));
// for (int i = 0; i < rules.size(); ++i) {
// buf << prefix;
// if (rules[i].type == propset) {
// rules[i].emit_nested_css(buf, depth+1);
// }
// else {
// rules[i][0].emit_nested_css(buf, depth);
// rules[i][1].emit_nested_css(buf, depth);
// buf << ';';
// }
// }
} break;
case rule:
buf << endl << string(2*depth, ' ');
at(0).emit_nested_css(buf, depth); // property
at(1).emit_nested_css(buf, depth); // values
buf << ";";
break;
case css_import:
buf << endl << string(2*depth, ' ');
buf << to_string("");
buf << ";";
break;
case property:
buf << string(content.token) << ": ";
break;
case values:
for (int i = 0; i < size(); ++i) {
buf << " " << string(at(i).content.token);
}
break;
case comment:
if (depth != 0) buf << endl;
buf << string(2*depth, ' ') << string(content.token);
if (depth == 0) buf << endl;
break;
default:
buf << to_string("");
break;
}
}
void Node::emit_propset(stringstream& buf, size_t depth, const string& prefix) {
string new_prefix(prefix);
if (new_prefix.empty()) new_prefix += "\n";
else new_prefix += "-";
new_prefix += at(0).content.token.to_string();
Node rules(at(1));
for (int i = 0; i < rules.size(); ++i) {
buf << new_prefix;
if (rules[i].type == propset) {
rules[i].emit_propset(buf, depth+1, new_prefix);
}
else {
rules[i][0].emit_nested_css(buf, depth);
rules[i][1].emit_nested_css(buf, depth);
buf << ';';
}
}
}
void Node::emit_expanded_css(stringstream& buf, const string& prefix) {
// switch (type) {
// case selector:
// if (!prefix.empty()) buf << " ";
// buf << string(token);
// break;
// case comment:
// if (!prefix.empty()) buf << " ";
// buf << string(token) << endl;
// break;
// case property:
// buf << string(token) << ":";
// break;
// case values:
// for (int i = 0; i < children.size(); ++i) {
// buf << " " << string(children[i].token);
// }
// break;
// case rule:
// buf << " ";
// children[0].emit_expanded_css(buf, prefix);
// children[1].emit_expanded_css(buf, prefix);
// buf << ";" << endl;
// break;
// case clauses:
// if (children.size() > 0) {
// buf << " {" << endl;
// for (int i = 0; i < children.size(); ++i)
// children[i].emit_expanded_css(buf, prefix);
// buf << "}" << endl;
// }
// for (int i = 0; i < opt_children.size(); ++i)
// opt_children[i].emit_expanded_css(buf, prefix);
// break;
// case ruleset:
// // buf << prefix;
// if (children[1].children.size() > 0) {
// buf << prefix << (prefix.empty() ? "" : " ");
// children[0].emit_expanded_css(buf, "");
// }
// string newprefix(prefix.empty() ? prefix : prefix + " ");
// children[1].emit_expanded_css(buf, newprefix + string(children[0].token));
// if (prefix.empty()) buf << endl;
// break;
// }
}
void Node::flatten()
{
if (type != block && type != expansion && type != root) return;
for (int i = 0; i < size(); ++i) {
if (at(i).type == expansion) {
Node expn = at(i);
if (expn[0].has_expansions) expn.flatten();
at(0).has_statements |= expn[0].has_statements;
at(0).has_blocks |= expn[0].has_blocks;
at(0).has_expansions |= expn[0].has_expansions;
at(i).type = none;
content.children->insert(content.children->begin() + i,
expn.content.children->begin(),
expn.content.children->end());
}
}
}
//
// void flatten_block(Node& block)
// {
//
// for (int i = 0; i < block.size(); ++i) {
//
// if (block[i].type == Node::expansion
//
// }
//
//
//
// }
}
\ No newline at end of file
#define SASS_NODE_INCLUDED
#include <vector>
#include <sstream>
#include "values.hpp"
#include <iostream>
namespace Sass {
using std::string;
using std::vector;
using std::stringstream;
using std::cerr; using std::endl;
struct Node {
enum Type {
root,
ruleset,
propset,
selector_group,
selector,
selector_combinator,
simple_selector_sequence,
backref,
simple_selector,
type_selector,
class_selector,
id_selector,
pseudo,
pseudo_negation,
functional_pseudo,
attribute_selector,
block,
rule,
property,
nil,
comma_list,
space_list,
disjunction,
conjunction,
relation,
eq,
neq,
gt,
gte,
lt,
lte,
expression,
add,
sub,
term,
mul,
div,
factor,
unary_plus,
unary_minus,
values,
value,
identifier,
uri,
textual_percentage,
textual_dimension,
textual_number,
textual_hex,
color_name,
string_constant,
numeric_percentage,
numeric_dimension,
number,
numeric_color,
boolean,
important,
value_schema,
string_schema,
css_import,
function_call,
mixin,
parameters,
expansion,
arguments,
variable,
assignment,
comment,
none,
flags
};
Type type;
unsigned int line_number;
bool has_children;
bool has_statements;
bool has_blocks;
bool has_expansions;
bool has_backref;
bool from_variable;
bool eval_me;
bool unquoted;
union {
Token token;
mutable vector<Node>* children;
Dimension dimension;
double numeric_value;
bool boolean_value;
} content;
const char* file_name;
static size_t allocations;
static size_t destructed;
void clear()
{
type = none; line_number = 0; file_name = 0;
has_children = false; has_statements = false; has_blocks = false;
has_expansions = false; has_backref = false; from_variable = false;
eval_me = false; unquoted = false;
}
size_t size() const
{ return content.children->size(); }
Node& operator[](const size_t i) const
{ return content.children->at(i); }
Node& at(const size_t i) const
{ return content.children->at(i); }
Node& operator<<(const Node& n)
{
content.children->push_back(n);
return *this;
}
bool is_numeric() const
{
switch (type)
{
case number:
case numeric_percentage:
case numeric_dimension:
return true;
break;
default:
return false;
}
}
double numeric_value() const
{
switch (type)
{
case number:
case numeric_percentage:
return content.numeric_value;
case numeric_dimension:
return content.dimension.numeric_value;
default:
break;
// throw an exception?
}
}
void set_numeric_value(double v)
{
switch (type)
{
case number:
case numeric_percentage:
content.numeric_value = v;
case numeric_dimension:
content.dimension.numeric_value = v;
default:
break;
// throw an exception?
}
}
Node& operator+=(const Node& n)
{
for (int i = 0; i < n.size(); ++i) {
content.children->push_back(n[i]);
}
return *this;
}
bool operator==(const Node& rhs) const;
bool operator!=(const Node& rhs) const;
bool operator<(const Node& rhs) const;
bool operator<=(const Node& rhs) const;
bool operator>(const Node& rhs) const;
bool operator>=(const Node& rhs) const;
string to_string(const string& prefix) const;
void echo(stringstream& buf, size_t depth = 0);
void emit_nested_css(stringstream& buf,
size_t depth,
const vector<string>& prefixes);
void emit_nested_css(stringstream& buf, size_t depth);
void emit_propset(stringstream& buf, size_t depth, const string& prefix);
void emit_expanded_css(stringstream& buf, const string& prefix);
Node clone(vector<vector<Node>*>& registry) const;
void flatten();
Node()
{ clear(); }
Node(Type t) // flags or booleans
{ clear(); type = t; }
Node(Type t, vector<vector<Node>*>& registry, unsigned int ln, size_t s = 0) // nodes with children
{
clear();
type = t;
line_number = ln;
content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(s);
has_children = true;
++allocations;
}
Node(Type t, unsigned int ln, const Token& tok) // nodes with a single token
{
clear();
type = t;
line_number = ln;
content.token = tok;
}
Node(unsigned int ln, double val) // numeric values
{
clear();
type = number;
line_number = ln;
content.numeric_value = val;
}
Node(unsigned int ln, double val, const Token& tok) // dimensions
{
clear();
type = numeric_dimension;
line_number = ln;
content.dimension = Dimension();
content.dimension.numeric_value = val;
content.dimension.unit = tok.begin;
}
Node(vector<vector<Node>*>& registry, unsigned int ln, double r, double g, double b, double a = 1.0) // colors
{
clear();
type = numeric_color;
line_number = ln;
content.children = new vector<Node>;
registry.push_back(content.children);
content.children->reserve(4);
content.children->push_back(Node(ln, r));
content.children->push_back(Node(ln, g));
content.children->push_back(Node(ln, b));
content.children->push_back(Node(ln, a));
has_children = true;
++allocations;
}
~Node() { ++destructed; }
};
}
\ No newline at end of file
#include <iostream>
#include <iomanip>
#include <string>
#include <cctype>
#include <cstdlib>
#include <cmath>
#include "node.hpp"
#include "error.hpp"
using std::string;
using std::stringstream;
using std::cout;
using std::cerr;
using std::endl;
namespace Sass {
bool Node::operator==(const Node& rhs) const
{
if (type != rhs.type) return false;
switch (type)
{
case comma_list:
case space_list:
case expression:
case term: {
for (int i = 0; i < size(); ++i) {
if (at(i) == rhs[i]) continue;
else return false;
}
return true;
} break;
case variable:
case identifier:
case uri:
case textual_percentage:
case textual_dimension:
case textual_number:
case textual_hex:
case string_constant: {
return content.token.unquote() == rhs.content.token.unquote();
} break;
case number:
case numeric_percentage: {
return numeric_value() == rhs.numeric_value();
} break;
case numeric_dimension: {
if (Token::make(content.dimension.unit, Prelexer::identifier(content.dimension.unit)) ==
Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))) {
return numeric_value() == rhs.numeric_value();
}
else {
return false;
}
} break;
case boolean: {
return content.boolean_value == rhs.content.boolean_value;
} break;
default: {
return true;
} break;
}
}
bool Node::operator!=(const Node& rhs) const
{ return !(*this == rhs); }
bool Node::operator<(const Node& rhs) const
{
if (type == number && rhs.type == number ||
type == numeric_percentage && rhs.type == numeric_percentage) {
return numeric_value() < rhs.numeric_value();
}
else if (type == numeric_dimension && rhs.type == numeric_dimension) {
if (Token::make(content.dimension.unit, Prelexer::identifier(content.dimension.unit)) ==
Token::make(rhs.content.dimension.unit, Prelexer::identifier(rhs.content.dimension.unit))) {
return numeric_value() < rhs.numeric_value();
}
else {
throw Error(Error::evaluation, line_number, file_name, "incompatible units");
}
}
else {
throw Error(Error::evaluation, line_number, file_name, "incomparable types");
}
}
bool Node::operator<=(const Node& rhs) const
{ return *this < rhs || *this == rhs; }
bool Node::operator>(const Node& rhs) const
{ return !(*this <= rhs); }
bool Node::operator>=(const Node& rhs) const
{ return !(*this < rhs); }
}
\ 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*)+
return one_plus< sequence< zero_plus< identifier >,
interpolant,
zero_plus< identifier > > >(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 mixin_kwd[] = "@mixin";
const char* mixin(const char* src) {
return exactly<mixin_kwd>(src);
}
extern const char include_kwd[] = "@include";
const char* include(const char* src) {
return exactly<include_kwd>(src);
}
const char* name(const char* src) {
return one_plus< alternatives< alnum,
exactly<'-'>,
exactly<'_'> > >(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 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* mixin(const char* src);
const char* include(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 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) {
const char* p;
if (p = mx(beg)) return p;
++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*) malloc(sizeof(sass_context)); }
void sass_free_context(sass_context* ctx)
{
free(ctx->output_string);
free(ctx);
}
sass_file_context* sass_new_file_context()
{ return (sass_file_context*) malloc(sizeof(sass_file_context)); }
void sass_free_file_context(sass_file_context* ctx)
{
free(ctx->output_string);
free(ctx);
}
sass_folder_context* sass_new_folder_context()
{ return (sass_folder_context*) malloc(sizeof(sass_folder_context)); }
static char* process_document(Sass::Document& doc, int style)
{
using namespace Sass;
doc.parse_scss();
// cerr << "PARSED" << endl;
eval(doc.root, doc.context.global_env, doc.context.function_env, doc.context.registry);
// cerr << "EVALUATED" << endl;
string output(doc.emit_css(static_cast<Document::CSS_Style>(style)));
// cerr << "EMITTED" << endl;
// cerr << "Allocations:\t" << Node::allocations << endl;
// cerr << "Destructions:\t" << Node::destructed << endl;
// cerr << "Registry size:\t" << doc.context.registry.size() << endl;
int i;
for (i = 0; i < doc.context.registry.size(); ++i) {
delete doc.context.registry[i];
}
// cerr << "Deallocations:\t" << i << endl;
char* c_output = (char*) malloc(output.size() + 1);
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);
c_ctx->output_string = process_document(doc, c_ctx->options.output_style);
c_ctx->error_message = 0;
c_ctx->error_status = 0;
}
catch (Error& e) {
stringstream msg_stream;
msg_stream << "ERROR -- " << e.file_name << ", line " << e.line_number << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
catch(bad_alloc& ba) {
stringstream msg_stream;
msg_stream << "ERROR -- unable to allocate memory: " << ba.what() << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
// TO DO: CATCH EVERYTHING ELSE
return 0;
}
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);
// 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.file_name << ", line " << e.line_number << ": " << e.message << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
catch(bad_alloc& ba) {
stringstream msg_stream;
msg_stream << "ERROR -- unable to allocate memory: " << ba.what() << endl;
string msg(msg_stream.str());
char* msg_str = (char*) malloc(msg.size() + 1);
strcpy(msg_str, msg.c_str());
c_ctx->error_status = 1;
c_ctx->output_string = 0;
c_ctx->error_message = msg_str;
}
// TO DO: CATCH EVERYTHING ELSE
return 0;
}
int sass_compile_folder(sass_folder_context* c_ctx)
{
return 1;
}
}
\ No newline at end of file
#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* input_string;
char* output_string;
struct sass_options options;
int error_status;
char* error_message;
};
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 ();
struct sass_file_context* sass_new_file_context ();
struct sass_folder_context* sass_new_folder_context ();
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 "values.hpp"
namespace Sass {
// Token::Token() : begin(0), end(0) { }
// Token::Token(const char* begin, const char* end)
// : begin(begin), end(end) { }
string Token::unquote() const {
string result;
const char* p = begin;
if (*begin == '\'' || *begin == '"') {
++p;
while (p < end) {
if (*p == '\\') {
switch (*(++p)) {
case 'n': result += '\n'; break;
case 't': result += '\t'; break;
case 'b': result += '\b'; break;
case 'r': result += '\r'; break;
case 'f': result += '\f'; break;
case 'v': result += '\v'; break;
case 'a': result += '\a'; break;
case '\\': result += '\\'; break;
default: result += *p; break;
}
}
else if (p == end - 1) {
return result;
}
else {
result += *p;
}
++p;
}
return result;
}
else {
while (p < end) {
result += *(p++);
}
return result;
}
}
void Token::unquote_to_stream(std::stringstream& buf) const {
const char* p = begin;
if (*begin == '\'' || *begin == '"') {
++p;
while (p < end) {
if (*p == '\\') {
switch (*(++p)) {
case 'n': buf << '\n'; break;
case 't': buf << '\t'; break;
case 'b': buf << '\b'; break;
case 'r': buf << '\r'; break;
case 'f': buf << '\f'; break;
case 'v': buf << '\v'; break;
case 'a': buf << '\a'; break;
case '\\': buf << '\\'; break;
default: buf << *p; break;
}
}
else if (p == end - 1) {
return;
}
else {
buf << *p;
}
++p;
}
return;
}
else {
while (p < end) {
buf << *(p++);
}
return;
}
}
bool Token::operator<(const Token& rhs) const
{
const char* first1 = begin;
const char* last1 = end;
const char* first2 = rhs.begin;
const char* last2 = rhs.end;
while (first1!=last1)
{
if (first2==last2 || *first2<*first1) return false;
else if (*first1<*first2) return true;
first1++; first2++;
}
return (first2!=last2);
}
bool Token::operator==(const Token& rhs) const
{
if (length() != rhs.length()) return false;
if ((begin[0] == '"' || begin[0] == '\'') &&
(rhs.begin[0] == '"' || rhs.begin[0] == '\''))
{ return unquote() == rhs.unquote(); }
const char* p = begin;
const char* q = rhs.begin;
for (; p < end; ++p, ++q) if (*p != *q) return false;
return true;
}
}
\ No newline at end of file
#include <string>
#include <sstream>
#include <cstring>
#ifndef SASS_PRELEXER_INCLUDED
#include "prelexer.hpp"
#endif
namespace Sass {
using std::string;
struct Token {
const char* begin;
const char* end;
// Token();
// Token(const char* begin, const char* end);
size_t length() const
{ return end - begin; }
inline operator string() const
{ return string(begin, end - begin); }
string to_string() const
{ return string(begin, end - begin); }
string unquote() const;
void unquote_to_stream(std::stringstream& buf) const;
bool operator<(const Token& rhs) const;
bool operator==(const Token& rhs) const;
operator bool()
{ return begin && end && begin >= end; }
static Token make()
{
Token t;
t.begin = 0;
t.end = 0;
return t;
}
static Token make(const char* s)
{
Token t;
t.begin = s;
t.end = s + std::strlen(s);
return t;
}
static Token make(const char* b, const char* e)
{
Token t;
t.begin = b;
t.end = e;
return t;
}
};
struct Dimension {
double numeric_value;
const char* unit;
};
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment