Commit a087ed82 by Andrew Nesbitt

Updated libsass, closes #52

parent 119743ae
......@@ -41,6 +41,17 @@ namespace Sass {
return ss.str();
}
size_t depth()
{
size_t d = 0;
Backtrace* p = parent;
while (p) {
++d;
p = p->parent;
}
return d-1;
}
};
}
\ No newline at end of file
......@@ -86,6 +86,7 @@ namespace Sass {
extern const char rparen[] = ")";
extern const char sign_chars[] = "-+";
extern const char hyphen[] = "-";
extern const char ellipsis[] = "...";
// type names
extern const char numeric_name[] = "numeric value";
......@@ -96,6 +97,7 @@ namespace Sass {
extern const char bool_name[] = "bool";
extern const char color_name[] = "color";
extern const char list_name[] = "list";
extern const char arglist_name[] = "arglist";
}
}
\ No newline at end of file
......@@ -86,6 +86,7 @@ namespace Sass {
extern const char rparen[];
extern const char sign_chars[];
extern const char hyphen[];
extern const char ellipsis[];
// type names
extern const char numeric_name[];
......@@ -96,5 +97,6 @@ namespace Sass {
extern const char bool_name[];
extern const char color_name[];
extern const char list_name[];
extern const char arglist_name[];
}
}
\ No newline at end of file
......@@ -177,23 +177,32 @@ namespace Sass {
Node::Type param_type = Node::none;
if (lex< exactly<'('> >()) {
if (peek< variable >()) {
Node param(parse_parameter(param_type));
if (param.type() == Node::assignment) param_type = Node::assignment;
params << param;
while (lex< exactly<','> >()) {
do {
if (!peek< variable >()) throw_syntax_error("expected a variable name (e.g. $x) for the parameter list for " + name.to_string());
Node param(parse_parameter(param_type));
if (param.type() == Node::assignment) param_type = Node::assignment;
params << param;
if (param.type() == Node::rest) {
param_type = Node::rest;
break;
}
} while (lex< exactly<','> >());
if (!lex< exactly<')'> >()) {
if (params.back().type() == Node::rest && peek< exactly<','> >()) {
throw_syntax_error("variable-length parameter must appear last in a parameter list");
}
else {
throw_syntax_error("parameter list for " + name.to_string() + " requires a ')'");
}
}
if (!lex< exactly<')'> >()) throw_syntax_error("parameter list for " + name.to_string() + " requires a ')'");
}
else if (!lex< exactly<')'> >()) throw_syntax_error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name.to_string());
}
return params;
}
Node Document::parse_parameter(Node::Type param_type) {
Node Document::parse_parameter(Node::Type param_type)
{
lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed));
if (param_type == Node::assignment) {
......@@ -213,6 +222,9 @@ namespace Sass {
par_and_val << var << val;
return par_and_val;
}
else if (lex< exactly< ellipsis > >()) {
return context.new_Node(Node::rest, var.path(), var.line(), var.token());
}
return var;
}
......@@ -241,16 +253,26 @@ namespace Sass {
Node::Type arg_type = Node::none;
if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) {
Node arg(parse_argument(Node::none));
args << arg;
if (arg.type() == Node::assignment) arg_type = Node::assignment;
while (lex< exactly<','> >()) {
do {
Node arg(parse_argument(arg_type));
args << arg;
if (arg.type() == Node::assignment) arg_type = Node::assignment;
if (arg.type() == Node::assignment) {
arg_type = Node::assignment;
}
else if (arg.type() == Node::rest) {
arg_type = Node::rest;
break;
}
} while (lex< exactly<','> >());
}
if (!lex< exactly<')'> >()) {
if (args.back().is_arglist() && peek< exactly<','> >()) {
throw_syntax_error("variable-length argument must appear last in an argument list");
}
else {
throw_syntax_error("improperly terminated argument list for " + name.to_string());
}
}
if (!lex< exactly<')'> >()) throw_syntax_error("improperly terminated argument list for " + name.to_string());
}
return args;
}
......@@ -274,8 +296,8 @@ namespace Sass {
}
}
// otherwise accept either, and let the caller set the arg_type flag
if (arg_type == Node::none &&
peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
else if (arg_type == Node::none &&
peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) {
lex< variable >();
Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >();
......@@ -285,8 +307,18 @@ namespace Sass {
assn << var << val;
return assn;
}
// else if (arg_type == Node::none &&
// peek< sequence < variable, spaces_and_comments, exactly< ellipsis > > >()) {
// lex< variable >();
// lex< exactly< ellipsis > >();
// return context.new_Node(Node::rest, path, line, lexed);
// }
Node val(parse_space_list());
val.should_eval() = true;
if (lex< exactly< ellipsis > >()) {
val.is_arglist() = true;
val.is_splat() = true;
}
return val;
}
......@@ -711,7 +743,8 @@ namespace Sass {
if (peek< exactly<';'> >(position) ||
peek< exactly<'}'> >(position) ||
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position))
peek< exactly<')'> >(position) ||
peek< exactly<ellipsis> >(position))
{ return context.new_Node(Node::list, path, line, 0); }
Node list1(parse_space_list());
// if it's a singleton, return it directly; don't wrap it
......@@ -741,6 +774,7 @@ namespace Sass {
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) ||
peek< exactly<','> >(position) ||
peek< exactly<ellipsis> >(position) ||
peek< default_flag >(position))
{ return disj1; }
......@@ -753,6 +787,7 @@ namespace Sass {
peek< exactly<'{'> >(position) ||
peek< exactly<')'> >(position) ||
peek< exactly<','> >(position) ||
peek< exactly<ellipsis> >(position) ||
peek< default_flag >(position)))
{
Node disj(parse_disjunction());
......
......@@ -57,9 +57,11 @@ namespace Sass {
} break;
case Node::mixin_content: {
expr += new_Node(content);
for (size_t i = 0, S = expr.size(); i < S; ++i) {
re_expand(expr[i], prefix, env, f_env, new_Node, ctx, bt, false, content);
if (!content.is_null()) {
expr += new_Node(content);
for (size_t i = 0, S = expr.size(); i < S; ++i) {
re_expand(expr[i], prefix, env, f_env, new_Node, ctx, bt, false, content);
}
}
} break;
......@@ -388,12 +390,19 @@ namespace Sass {
switch (expr.type())
{
case Node::list: {
// cerr << string(bt.depth(), '\t') << "about to eval a list: " << expr.to_string() << "::" << expr.is_arglist() << endl;
bool is_arglist = expr.is_arglist();
bool is_splat = expr.is_splat();
if (expr.should_eval() && expr.size() > 0) {
result = new_Node(Node::list, expr.path(), expr.line(), expr.size());
result.is_comma_separated() = expr.is_comma_separated();
result << eval(expr[0], prefix, env, f_env, new_Node, ctx, bt);
for (size_t i = 1, S = expr.size(); i < S; ++i) result << expr[i];
result.is_arglist() = is_arglist;
result.is_splat() = is_splat;
// cerr << string(bt.depth(), '\t') << "evaluated a list: " << result.to_string() << "::" << result.is_arglist() << endl;
}
// else cerr << string(bt.depth(), '\t') << "evaluated a list (sort of): " << expr.to_string() << "::" << expr.is_arglist() << endl;
} break;
case Node::disjunction: {
......@@ -493,6 +502,7 @@ namespace Sass {
case Node::variable: {
if (!env.query(expr.token())) throw_eval_error(bt, "reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line());
result = env[expr.token()];
// cerr << string(bt.depth(), '\t') << "evaluated a lookup: " << expr.to_string() << " -> " << result.to_string() << "::" << result.is_arglist() << endl;
} break;
case Node::uri: {
......@@ -756,20 +766,31 @@ namespace Sass {
Node eval_arguments(Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, Backtrace& bt)
{
// cerr << string(bt.depth(), '\t') << "evaluating arguments" << endl;
Node evaluated_args(new_Node(Node::arguments, args.path(), args.line(), args.size()));
for (size_t i = 0, S = args.size(); i < S; ++i) {
if (args[i].type() != Node::assignment) {
// bool is_arglist = args[i].is_arglist();
bool is_splat = args[i].is_splat();
evaluated_args << eval(args[i], prefix, env, f_env, new_Node, ctx, bt);
if (evaluated_args.back().type() == Node::list) {
Node arg_list(evaluated_args.back());
Node new_arg_list(new_Node(Node::list, arg_list.path(), arg_list.line(), arg_list.size()));
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) new_arg_list << eval(arg_list[j], prefix, env, f_env, new_Node, ctx, bt);
else new_arg_list << arg_list[j];
}
}
// cerr << string(bt.depth(), '\t') << "just pushed an evaluated arg: " << evaluated_args[i].to_string() << "::" << evaluated_args[i].is_arglist() << endl;
// if (evaluated_args[i].type() == Node::list) {
// Node arg_list(evaluated_args[i]);
// Node new_arg_list(new_Node(Node::list, arg_list.path(), arg_list.line(), arg_list.size()));
// new_arg_list.is_arglist() = arg_list.is_arglist();
// for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
// if (arg_list[j].should_eval()) new_arg_list << eval(arg_list[j], prefix, env, f_env, new_Node, ctx, bt);
// else new_arg_list << arg_list[j];
// }
// evaluated_args[i] = new_arg_list;
// }
// evaluated_args[i].is_arglist() = is_arglist;
// evaluated_args[i].is_arglist() = is_arglist;
evaluated_args[i].is_splat() = is_splat;
// cerr << string(bt.depth(), '\t') << "after first arg evaluation: " << evaluated_args[i].to_string() << "::" << evaluated_args[i].is_arglist() << endl;
}
else {
// cerr << "shouldn't be taking this branch!" << endl;
Node kwdarg(new_Node(Node::assignment, args[i].path(), args[i].line(), 2));
kwdarg << args[i][0];
kwdarg << eval(args[i][1], prefix, env, f_env, new_Node, ctx, bt);
......@@ -785,9 +806,12 @@ namespace Sass {
evaluated_args << kwdarg;
}
}
// cerr << string(bt.depth(), '\t') << "after first arg evaluation: " << evaluated_args.back().to_string() << "::" << evaluated_args.back().is_arglist() << endl;
// eval twice because args may be delayed
for (size_t i = 0, S = evaluated_args.size(); i < S; ++i) {
if (evaluated_args[i].type() != Node::assignment) {
// bool is_arglist = evaluated_args[i].is_arglist();
bool is_splat = evaluated_args[i].is_splat();
evaluated_args[i].should_eval() = true;
evaluated_args[i] = eval(evaluated_args[i], prefix, env, f_env, new_Node, ctx, bt);
if (evaluated_args[i].type() == Node::list) {
......@@ -796,6 +820,8 @@ namespace Sass {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx, bt);
}
}
// evaluated_args[i].is_arglist() = is_arglist;
evaluated_args[i].is_splat() = is_splat;
}
else {
Node kwdarg(evaluated_args[i]);
......@@ -810,6 +836,7 @@ namespace Sass {
evaluated_args[i] = kwdarg;
}
}
// cerr << string(bt.depth(), '\t') << "after second arg evaluation: " << evaluated_args.back().to_string() << "::" << evaluated_args.back().is_arglist() << endl;
return evaluated_args;
}
......@@ -820,27 +847,88 @@ namespace Sass {
{
// populate the env with the names of the parameters so we can check for
// correctness further down
bool has_rest_params = false;
for (size_t i = 0, S = params.size(); i < S; ++i) {
Node param(params[i]);
env.current_frame[param.type() == Node::variable ? param.token() : param[0].token()] = Node();
// env.current_frame[param.type() == Node::assignment ? param[0].token() : param.token()] = Node();
if (param.type() == Node::variable) {
env.current_frame[param.token()] = Node();
}
else if (param.type() == Node::assignment) {
env.current_frame[param[0].token()] = Node();
}
else {
Node arglist(new_Node(Node::list, args.path(), args.line(), 0));
arglist.is_arglist() = true;
arglist.is_comma_separated() = true;
env.current_frame[param.token()] = arglist;
has_rest_params = true;
}
}
// now do the actual binding
size_t args_bound = 0, num_params = params.size();
for (size_t i = 0, j = 0, S = args.size(); i < S; ++i) {
if (j >= num_params) {
if (j == num_params-1 && has_rest_params) {
Node arglist(env[params[j].token()]);
// collect rest-args
if (args[i].type() == Node::list && args[i].is_splat()) {
arglist += args[i];
arglist.is_comma_separated() = args[i].is_comma_separated();
}
else {
for (size_t k = i; k < S; ++k) {
arglist << args[k];
}
}
break;
}
else if (j >= num_params) {
stringstream msg;
msg << callee_name << " only takes " << num_params << " arguments";
throw_eval_error(bt, msg.str(), args.path(), args.line());
}
Node arg(args[i]), param(params[j]);
// ordinal argument; just bind and keep going
if (arg.type() != Node::assignment) {
env[param.type() == Node::variable ? param.token() : param[0].token()] = arg;
++j;
// ordinal argument; just bind it and keep going
else if (args[i].type() != Node::assignment) {
// if it's a splat
if (args[i].type() == Node::list && args[i].is_splat()) {
// loop through the splatted list and bind each element to the remaining parameters
for (size_t rest_i = 0, rest_length = args[i].size(); rest_i < rest_length; ++rest_i) {
// if we're binding the last parameter
if (j == num_params-1) {
Node leftovers;
if (has_rest_params) {
leftovers = env[params[j].token()];
leftovers.is_comma_separated() = args[i].is_comma_separated();
for (; rest_i < rest_length; ++rest_i) {
leftovers << args[i][rest_i];
}
}
else {
leftovers = args[i][rest_i];
}
Node param(params[j]);
env[param.type() != Node::assignment ? param.token() : param[0].token()] = leftovers;
break;
}
// otherwise keep going normally
else {
Node param(params[j]);
Token name = ((param.type() == Node::variable) ? param.token() : param[0].token());
env[name] = args[i][rest_i];
++j;
}
}
}
else {
Node arg(args[i]), param(params[j]);
env[param.type() == Node::variable ? param.token() : param[0].token()] = arg;
++j;
}
}
// keyword argument -- need to check for correctness
else {
Node arg(args[i]), param(params[j]);
Token arg_name(arg[0].token());
Node arg_value(arg[1]);
if (!env.query(arg_name)) {
......@@ -872,6 +960,10 @@ namespace Sass {
// environment.
Node apply_mixin(Node mixin, const Node args, const Node content, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, Backtrace& bt, bool dynamic_scope)
{
stringstream mixin_name;
mixin_name << "mixin";
if (!mixin[0].is_null()) mixin_name << " " << mixin[0].to_string();
// cerr << string(bt.depth(), '\t') << "applying " << mixin_name.str() << endl;
Node params(mixin[1]);
Node body(new_Node(mixin[2])); // clone the body
Node evaluated_args(eval_arguments(args, prefix, env, f_env, new_Node, ctx, bt));
......@@ -886,10 +978,11 @@ namespace Sass {
bindings.link(env.global ? *env.global : env);
}
// bind arguments in the extended environment
stringstream mixin_name;
mixin_name << "mixin";
if (!mixin[0].is_null()) mixin_name << " " << mixin[0].to_string();
// stringstream mixin_name;
// mixin_name << "mixin";
// if (!mixin[0].is_null()) mixin_name << " " << mixin[0].to_string();
bind_arguments(mixin_name.str(), params, evaluated_args, prefix, bindings, f_env, new_Node, ctx, bt);
// cerr << string(bt.depth(), '\t') << "last binding: " << params.back().to_string() << " -> " << bindings[params.back().token()].to_string() << "::" << bindings[params.back().token()].is_arglist() << endl;
// evaluate the mixin's body
expand(body, prefix, bindings, f_env, new_Node, ctx, bt, false, content);
return body;
......@@ -899,13 +992,18 @@ namespace Sass {
// primitive function implementation, then return its value.
Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, Backtrace& bt, string& path, size_t line)
{
// cerr << string(bt.depth(), '\t') << "applying " << f.name << endl;
// cerr << string(bt.depth(), '\t') << "last pre-evaluated arg: " << args.back().to_string() << endl;
Node evaluated_args(eval_arguments(args, prefix, env, f_env, new_Node, ctx, bt));
// cerr << string(bt.depth(), '\t') << "last evaluated arg: " << evaluated_args.back().to_string() << "::" << evaluated_args.back().is_arglist() << endl;
// bind arguments
Environment bindings;
Node params(f.primitive ? f.parameters : f.definition[1]);
bindings.link(env.global ? *env.global : env);
bind_arguments("function " + f.name, params, evaluated_args, prefix, bindings, f_env, new_Node, ctx, bt);
// cerr << string(bt.depth(), '\t') << "last binding: " << params.back().to_string() << " -> " << bindings[params.back().token()].to_string() << "::" << bindings[params.back().token()].is_arglist() << endl;
if (f.primitive) {
return f.primitive(f.parameter_names, bindings, new_Node, bt, path, line);
}
......
......@@ -1049,7 +1049,13 @@ namespace Sass {
type_name = Token::make(color_name);
} break;
case Node::list: {
type_name = Token::make(list_name);
// cerr << val.to_string() << endl;
// cerr << val.is_arglist() << endl;
// throw (42);
if (val.is_arglist())
type_name = Token::make(arglist_name);
else
type_name = Token::make(list_name);
} break;
default: {
type_name = Token::make(string_name);
......
......@@ -158,6 +158,7 @@ namespace Sass {
mixin_content,
parameters,
arguments,
rest,
extend_directive,
......@@ -199,6 +200,8 @@ namespace Sass {
bool& has_been_extended() const;
bool is_false() const;
bool& is_comma_separated() const;
bool& is_arglist() const;
bool& is_splat() const;
string& path() const;
size_t line() const;
......@@ -240,7 +243,7 @@ namespace Sass {
bool operator>(Node rhs) const;
bool operator>=(Node rhs) const;
string to_string(Type inside_of = none, const string space = " ") const;
string to_string(Type inside_of = none, const string space = " ", const bool in_media_feature = false) const;
void emit_nested_css(stringstream& buf, size_t depth, bool at_toplevel = false, bool in_media_query = false, bool source_comments = false);
void emit_propset(stringstream& buf, size_t depth, const string& prefix, const bool compressed = false);
void echo(stringstream& buf, size_t depth = 0);
......@@ -277,6 +280,8 @@ namespace Sass {
bool is_quoted;
bool has_been_extended;
bool is_comma_separated;
bool is_arglist;
bool is_splat;
Node_Impl()
: /* value(value_t()),
......@@ -294,7 +299,9 @@ namespace Sass {
should_eval(false),
is_quoted(false),
has_been_extended(false),
is_comma_separated(false)
is_comma_separated(false),
is_arglist(false),
is_splat(false)
{ }
bool is_numeric()
......@@ -467,6 +474,8 @@ namespace Sass {
inline bool& Node::has_been_extended() const { return ip_->has_been_extended; }
inline bool Node::is_false() const { return (type() == boolean) && (boolean_value() == false); }
inline bool& Node::is_comma_separated() const { return ip_->is_comma_separated; }
inline bool& Node::is_arglist() const { return ip_->is_arglist; }
inline bool& Node::is_splat() const { return ip_->is_splat; }
inline string& Node::path() const { return ip_->path; }
inline size_t Node::line() const { return ip_->line; }
......
......@@ -15,7 +15,7 @@ using std::endl;
namespace Sass {
string Node::to_string(Type inside_of, const string space) const
string Node::to_string(Type inside_of, const string space, const bool in_media_feature) const
{
if (is_null()) return "";
switch (type())
......@@ -39,22 +39,27 @@ namespace Sass {
string result;
if (at(0).type() == rule) {
result += "(";
result += at(0).to_string(none, space);
result += at(0).to_string(none, space, true);
result += ")";
}
else {
result += at(0).to_string(none, space);
string tmp = at(0).to_string(none, space);
result += tmp;
if (tmp == "and" && space == "") result += " ";
}
for (size_t i = 1, S = size(); i < S; ++i) {
if (at(i).type() == rule) {
result += space;
result += "(";
result += at(i).to_string(none, space);
result += at(i).to_string(none, space, true);
result += ")";
}
else {
result += " ";
result += at(i).to_string(none, space);
// result += at(i).to_string(none, space);
string tmp = at(i).to_string(none, space);
result += tmp;
if (tmp == "and" && space == "") result += " ";
}
}
return result;
......@@ -125,6 +130,7 @@ namespace Sass {
string result(at(0).to_string(property, space));
result += ":";
result += space;
if (space == "" && in_media_feature) result += " ";
result += at(1).to_string(none, space);
return result;
} break;
......
This diff was suppressed by a .gitattributes entry.
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