Commit 5bd4581b by Aaron Leung

Merge branch 'expand-eval-apply'

Conflicts:
	eval_apply.cpp
parents 580c318b e44e4923
...@@ -202,7 +202,7 @@ namespace Sass { ...@@ -202,7 +202,7 @@ namespace Sass {
if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive"); if (!lex< identifier >()) throw_syntax_error("invalid name in @include directive");
Node name(context.new_Node(Node::identifier, path, line, lexed)); Node name(context.new_Node(Node::identifier, path, line, lexed));
Node args(parse_arguments()); Node args(parse_arguments());
Node the_call(context.new_Node(Node::expansion, path, line, 2)); Node the_call(context.new_Node(Node::mixin_call, path, line, 2));
the_call << name << args; the_call << name << args;
return the_call; return the_call;
} }
...@@ -215,12 +215,10 @@ namespace Sass { ...@@ -215,12 +215,10 @@ namespace Sass {
if (lex< exactly<'('> >()) { if (lex< exactly<'('> >()) {
if (!peek< exactly<')'> >(position)) { if (!peek< exactly<')'> >(position)) {
Node arg(parse_argument(Node::none)); Node arg(parse_argument(Node::none));
arg.should_eval() = true;
args << arg; args << arg;
if (arg.type() == Node::assignment) arg_type = Node::assignment; if (arg.type() == Node::assignment) arg_type = Node::assignment;
while (lex< exactly<','> >()) { while (lex< exactly<','> >()) {
Node arg(parse_argument(arg_type)); Node arg(parse_argument(arg_type));
arg.should_eval() = true;
args << arg; args << arg;
if (arg.type() == Node::assignment) arg_type = Node::assignment; if (arg.type() == Node::assignment) arg_type = Node::assignment;
} }
...@@ -239,6 +237,7 @@ namespace Sass { ...@@ -239,6 +237,7 @@ namespace Sass {
Node var(context.new_Node(Node::variable, path, line, lexed)); Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >(); lex< exactly<':'> >();
Node val(parse_space_list()); Node val(parse_space_list());
val.should_eval() = true;
Node assn(context.new_Node(Node::assignment, path, line, 2)); Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val; assn << var << val;
return assn; return assn;
...@@ -254,23 +253,14 @@ namespace Sass { ...@@ -254,23 +253,14 @@ namespace Sass {
Node var(context.new_Node(Node::variable, path, line, lexed)); Node var(context.new_Node(Node::variable, path, line, lexed));
lex< exactly<':'> >(); lex< exactly<':'> >();
Node val(parse_space_list()); Node val(parse_space_list());
val.should_eval() = true;
Node assn(context.new_Node(Node::assignment, path, line, 2)); Node assn(context.new_Node(Node::assignment, path, line, 2));
assn << var << val; assn << var << val;
return assn; return assn;
} }
return parse_space_list(); Node val(parse_space_list());
// if (peek< sequence < variable, spaces_and_comments, exactly<':'> > >()) { val.should_eval() = true;
// lex< variable >(); return val;
// Node var(context.new_Node(Node::variable, path, line, lexed));
// lex< exactly<':'> >();
// Node val(parse_space_list());
// Node assn(context.new_Node(Node::assignment, path, line, 2));
// assn << var << val;
// return assn;
// }
// else {
// return parse_space_list();
// }
} }
Node Document::parse_assignment() Node Document::parse_assignment()
...@@ -598,7 +588,7 @@ namespace Sass { ...@@ -598,7 +588,7 @@ namespace Sass {
semicolon = true; semicolon = true;
} }
else if (lex< extend >()) { else if (lex< extend >()) {
if (surrounding_ruleset.is_null_ptr()) throw_syntax_error("@extend directive may only be used within rules"); if (surrounding_ruleset.is_null()) throw_syntax_error("@extend directive may only be used within rules");
Node extendee(parse_simple_selector_sequence()); Node extendee(parse_simple_selector_sequence());
context.extensions.insert(pair<Node, Node>(extendee, surrounding_ruleset)); context.extensions.insert(pair<Node, Node>(extendee, surrounding_ruleset));
context.has_extensions = true; context.has_extensions = true;
...@@ -1016,6 +1006,7 @@ namespace Sass { ...@@ -1016,6 +1006,7 @@ namespace Sass {
if (lex< interpolant >()) { if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1)); Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Node interp_node(Document::make_from_token(context, insides, path, line).parse_list()); Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
interp_node.should_eval() = true;
schema << interp_node; schema << interp_node;
} }
else if (lex< identifier >()) { else if (lex< identifier >()) {
...@@ -1080,6 +1071,7 @@ namespace Sass { ...@@ -1080,6 +1071,7 @@ namespace Sass {
else if (lex< interpolant >()) { else if (lex< interpolant >()) {
Token insides(Token::make(lexed.begin + 2, lexed.end - 1)); Token insides(Token::make(lexed.begin + 2, lexed.end - 1));
Node interp_node(Document::make_from_token(context, insides, path, line).parse_list()); Node interp_node(Document::make_from_token(context, insides, path, line).parse_list());
interp_node.should_eval() = true;
schema << interp_node; schema << interp_node;
} }
else if (lex< sequence< identifier, exactly<':'> > >()) { else if (lex< sequence< identifier, exactly<':'> > >()) {
......
...@@ -20,43 +20,60 @@ namespace Sass { ...@@ -20,43 +20,60 @@ namespace Sass {
throw Error(Error::evaluation, path, line, message); throw Error(Error::evaluation, path, line, message);
} }
// Evaluate the parse tree in-place (mostly). Most nodes will be left alone. // Expansion function for nodes in an expansion context.
void expand(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name)
Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name)
{ {
switch (expr.type()) switch (expr.type())
{ {
case Node::mixin: { case Node::root: {
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expand(expr[i], prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::mixin: { // mixin definition
env[expr[0].token()] = expr; env[expr[0].token()] = expr;
return expr;
} break; } break;
case Node::function: { case Node::function: { // function definition
f_env[expr[0].to_string()] = Function(expr); f_env[expr[0].to_string()] = Function(expr);
return expr;
} break; } break;
case Node::expansion: { case Node::mixin_call: { // mixin invocation
Token name(expr[0].token()); Token name(expr[0].token());
Node args(expr[1]); Node args(expr[1]);
if (!env.query(name)) throw_eval_error("mixin " + name.to_string() + " is undefined", expr.path(), expr.line()); if (!env.query(name)) throw_eval_error("mixin " + name.to_string() + " is undefined", expr.path(), expr.line());
Node mixin(env[name]); Node mixin(env[name]);
Node expansion(apply_mixin(mixin, args, prefix, env, f_env, new_Node, ctx)); Node expansion(apply_mixin(mixin, args, prefix, env, f_env, new_Node, ctx));
expr.pop_back(); expr.pop_all(); // pop the mixin metadata
expr.pop_back(); expr += expansion; // push the expansion
expr += expansion;
return expr;
} break; } break;
case Node::propset: { case Node::propset: {
eval(expr[1], prefix, env, f_env, new_Node, ctx); // TO DO: perform the property expansion here, rather than in the emitter (also requires the parser to allow interpolants in the property names)
return expr; expand(expr[1], prefix, env, f_env, new_Node, ctx);
} break; } break;
case Node::ruleset: { case Node::ruleset: {
// if the selector contains interpolants, eval it and re-parse // if the selector contains interpolants, eval it and re-parse
if (expr[0].type() == Node::selector_schema) { if (expr[0].type() == Node::selector_schema) {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); Node schema(expr[0]);
string expansion;
for (size_t i = 0, S = schema.size(); i < S; ++i) {
schema[i] = eval(schema[i], prefix, env, f_env, new_Node, ctx);
if (schema[i].type() == Node::string_constant) {
expansion += schema[i].token().unquote();
}
else {
expansion += schema[i].to_string();
}
}
expansion += " {"; // the parser looks for an lbrace to end a selector
char* expn_src = new char[expansion.size() + 1];
strcpy(expn_src, expansion.c_str());
Document needs_reparsing(Document::make_from_source_chars(ctx, expn_src, schema.path(), true));
needs_reparsing.line = schema.line(); // set the line number to the original node's line
expr[0] = needs_reparsing.parse_selector_group();
} }
// expand the selector with the prefix and save it in expr[2] // expand the selector with the prefix and save it in expr[2]
...@@ -87,56 +104,28 @@ namespace Sass { ...@@ -87,56 +104,28 @@ namespace Sass {
} }
} }
// eval the body with the current selector as the prefix // expand the body with the newly expanded selector as the prefix
eval(expr[1], expr.back(), env, f_env, new_Node, ctx); expand(expr[1], expr.back(), env, f_env, new_Node, ctx);
return expr;
} break; } break;
case Node::media_query: { case Node::media_query: {
Node block(expr[1]); Node block(expr[1]);
Node new_ruleset(new_Node(Node::ruleset, expr.path(), expr.line(), 3)); Node new_ruleset(new_Node(Node::ruleset, expr.path(), expr.line(), 3));
new_ruleset << prefix << block << prefix; expr[1] = new_ruleset << prefix << block << prefix;
expr[1] = eval(new_ruleset, new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx); expand(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
return expr;
} break; } break;
case Node::selector_schema: {
string expansion;
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (expr[i].type() == Node::string_constant) {
expansion += expr[i].token().unquote();
}
else {
expansion += expr[i].to_string();
}
}
expansion += " {"; // the parser looks for an lbrace to end a selector
char* expn_src = new char[expansion.size() + 1];
strcpy(expn_src, expansion.c_str());
Document needs_reparsing(Document::make_from_source_chars(ctx, expn_src, expr.path(), true));
needs_reparsing.line = expr.line(); // set the line number to the original node's line
Node sel(needs_reparsing.parse_selector_group());
return sel;
} break;
case Node::root: {
for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx);
}
return expr;
} break;
case Node::block: { case Node::block: {
Environment new_frame; Environment new_frame;
new_frame.link(env); new_frame.link(env);
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, new_frame, f_env, new_Node, ctx); expand(expr[i], prefix, new_frame, f_env, new_Node, ctx);
} }
return expr;
} break; } break;
case Node::assignment: { case Node::assignment: {
Node var(expr[0]);
if (expr.is_guarded() && env.query(var.token())) return;
Node val(expr[1]); Node val(expr[1]);
if (val.type() == Node::list) { if (val.type() == Node::list) {
for (size_t i = 0, S = val.size(); i < S; ++i) { for (size_t i = 0, S = val.size(); i < S; ++i) {
...@@ -146,9 +135,8 @@ namespace Sass { ...@@ -146,9 +135,8 @@ namespace Sass {
else { else {
val = eval(val, prefix, env, f_env, new_Node, ctx); val = eval(val, prefix, env, f_env, new_Node, ctx);
} }
Node var(expr[0]);
if (expr.is_guarded() && env.query(var.token())) return expr; // If a binding exists (possibly upframe), then update it.
// If a binding exists (possible upframe), then update it.
// Otherwise, make a new on in the current frame. // Otherwise, make a new on in the current frame.
if (env.query(var.token())) { if (env.query(var.token())) {
env[var.token()] = val; env[var.token()] = val;
...@@ -156,156 +144,280 @@ namespace Sass { ...@@ -156,156 +144,280 @@ namespace Sass {
else { else {
env.current_frame[var.token()] = val; env.current_frame[var.token()] = val;
} }
return expr;
} break; } break;
case Node::rule: { case Node::rule: {
Node lhs(expr[0]); Node lhs(expr[0]);
if (lhs.should_eval()) eval(lhs, prefix, env, f_env, new_Node, ctx); if (lhs.is_schema()) {
expr[0] = eval(lhs, prefix, env, f_env, new_Node, ctx);
}
Node rhs(expr[1]); Node rhs(expr[1]);
if (rhs.type() == Node::list) { if (rhs.type() == Node::list) {
for (size_t i = 0, S = rhs.size(); i < S; ++i) { for (size_t i = 0, S = rhs.size(); i < S; ++i) {
if (rhs[i].should_eval()) rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx); if (rhs[i].should_eval()) {
rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx);
}
} }
} }
else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) { else if (rhs.is_schema() || rhs.should_eval()) {
eval(rhs, prefix, env, f_env, new_Node, ctx); expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
} }
else { } break;
if (rhs.should_eval()) expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
case Node::if_directive: {
Node expansion = Node();
for (size_t i = 0, S = expr.size(); i < S; i += 2) {
if (expr[i].type() != Node::block) {
Node predicate_val(eval(expr[i], prefix, env, f_env, new_Node, ctx));
if (!predicate_val.is_false()) {
expand(expansion = expr[i+1], prefix, env, f_env, new_Node, ctx);
break;
}
}
else {
expand(expansion = expr[i], prefix, env, f_env, new_Node, ctx);
break;
}
} }
return expr; expr.pop_all();
if (!expansion.is_null()) expr += expansion;
} break; } break;
case Node::for_through_directive:
case Node::for_to_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(for_kwd)) // stub name for debugging
<< (fake_param << expr[0]) // iteration variable
<< expr[3]; // body
Node lower_bound(eval(expr[1], prefix, env, f_env, new_Node, ctx));
Node upper_bound(eval(expr[2], prefix, env, f_env, new_Node, ctx));
if (!(lower_bound.is_numeric() && upper_bound.is_numeric())) {
throw_eval_error("bounds of @for directive must be numeric", expr.path(), expr.line());
}
expr.pop_all();
for (double i = lower_bound.numeric_value(),
U = upper_bound.numeric_value() + ((expr.type() == Node::for_to_directive) ? 0 : 1);
i < U;
++i) {
Node i_node(new_Node(expr.path(), expr.line(), i));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << i_node;
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::each_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(each_kwd)) // stub name for debugging
<< (fake_param << expr[0]) // iteration variable
<< expr[2]; // body
Node list(eval(expr[1], prefix, env, f_env, new_Node, ctx));
// If the list isn't really a list, make a singleton out of it.
if (list.type() != Node::list) {
list = (new_Node(Node::list, list.path(), list.line(), 1) << list);
}
expr.pop_all();
for (size_t i = 0, S = list.size(); i < S; ++i) {
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
list[i].should_eval() = true;
fake_arg << eval(list[i], prefix, env, f_env, new_Node, ctx);
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::while_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 0));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 0));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(while_kwd)) // stub name for debugging
<< fake_param // no iteration variable
<< expr[1]; // body
Node pred(expr[0]);
expr.pop_back();
expr.pop_back();
Node ev_pred(eval(pred, prefix, env, f_env, new_Node, ctx));
while (!ev_pred.is_false()) {
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
ev_pred = eval(pred, prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::block_directive: {
// TO DO: eval the directive name for interpolants
expand(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
} break;
case Node::warning: {
Node contents(eval(expr[0], Node(), env, f_env, new_Node, ctx));
string prefix("WARNING: ");
string indent(" ");
string result(contents.to_string());
if (contents.type() == Node::string_constant || contents.type() == Node::string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string
}
// These cerrs aren't log lines! They're supposed to be here!
cerr << prefix << result << endl;
cerr << indent << "on line " << expr.line() << " of " << expr.path();
cerr << endl << endl;
} break;
default: {
// do nothing
} break;
}
}
void expand_list(Node list, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx) {
for (size_t i = 0, S = list.size(); i < S; ++i) {
list[i].should_eval() = true;
list[i] = eval(list[i], prefix, env, f_env, new_Node, ctx);
}
}
// Evaluation function for nodes in a value context.
Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name)
{
Node result = Node();
switch (expr.type())
{
case Node::list: { case Node::list: {
if (expr.should_eval() && expr.size() > 0) expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); if (expr.should_eval() && expr.size() > 0) {
return expr; 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);
for (size_t i = 1, S = expr.size(); i < S; ++i) result << expr[i];
}
} break; } break;
case Node::disjunction: { case Node::disjunction: {
Node result;
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx); result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) continue; if (result.is_false()) continue;
else return result; else break;
} }
return result;
} break; } break;
case Node::conjunction: { case Node::conjunction: {
Node result;
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
result = eval(expr[i], prefix, env, f_env, new_Node, ctx); result = eval(expr[i], prefix, env, f_env, new_Node, ctx);
if (result.type() == Node::boolean && result.boolean_value() == false) return result; if (result.is_false()) break;
} }
return result;
} break; } break;
case Node::relation: { case Node::relation: {
Node lhs(new_Node(Node::arguments, expr[0].path(), expr[0].line(), 1));
Node lhs(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node rhs(new_Node(Node::arguments, expr[2].path(), expr[2].line(), 1));
Node op(expr[1]); Node rel(expr[1]);
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
// TO DO: don't allocate both T and F lhs << expr[0];
rhs << expr[2];
lhs = eval_arguments(lhs, prefix, env, f_env, new_Node, ctx);
rhs = eval_arguments(rhs, prefix, env, f_env, new_Node, ctx);
lhs = lhs[0];
rhs = rhs[0];
if (lhs.type() == Node::list) expand_list(lhs, prefix, env, f_env, new_Node, ctx);
if (rhs.type() == Node::list) expand_list(rhs, prefix, env, f_env, new_Node, ctx);
Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true)); Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true));
Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false)); Node F(new_Node(Node::boolean, lhs.path(), lhs.line(), false));
switch (op.type()) switch (rel.type())
{ {
case Node::eq: return (lhs == rhs) ? T : F; case Node::eq: result = ((lhs == rhs) ? T : F); break;
case Node::neq: return (lhs != rhs) ? T : F; case Node::neq: result = ((lhs != rhs) ? T : F); break;
case Node::gt: return (lhs > rhs) ? T : F; case Node::gt: result = ((lhs > rhs) ? T : F); break;
case Node::gte: return (lhs >= rhs) ? T : F; case Node::gte: result = ((lhs >= rhs) ? T : F); break;
case Node::lt: return (lhs < rhs) ? T : F; case Node::lt: result = ((lhs < rhs) ? T : F); break;
case Node::lte: return (lhs <= rhs) ? T : F; case Node::lte: result = ((lhs <= rhs) ? T : F); break;
default: default:
throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line()); throw_eval_error("unknown comparison operator " + expr.token().to_string(), expr.path(), expr.line());
return Node();
} }
} break; } break;
case Node::expression: { case Node::expression: {
Node list(new_Node(Node::expression, expr.path(), expr.line(), expr.size()));
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx); list << eval(expr[i], prefix, env, f_env, new_Node, ctx);
} }
return reduce(expr, 1, expr[0], new_Node); result = reduce(list, 1, list[0], new_Node);
} break; } break;
case Node::term: { case Node::term: {
if (expr.should_eval()) { if (expr.should_eval()) {
Node list(new_Node(Node::term, expr.path(), expr.line(), expr.size()));
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx); list << eval(expr[i], prefix, env, f_env, new_Node, ctx);
} }
return reduce(expr, 1, expr[0], new_Node); result = reduce(list, 1, list[0], new_Node);
}
else {
return expr;
} }
} break; } break;
case Node::textual_percentage: { case Node::textual_percentage: {
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage); result = new_Node(expr.path(), expr.line(), std::atof(expr.token().begin), Node::numeric_percentage);
} break; } break;
case Node::textual_dimension: { case Node::textual_dimension: {
return new_Node(expr.path(), expr.line(), result = new_Node(expr.path(), expr.line(),
std::atof(expr.token().begin), std::atof(expr.token().begin),
Token::make(Prelexer::number(expr.token().begin), Token::make(Prelexer::number(expr.token().begin),
expr.token().end)); expr.token().end));
} break; } break;
case Node::textual_number: { case Node::textual_number: {
return new_Node(expr.path(), expr.line(), std::atof(expr.token().begin)); result = new_Node(expr.path(), expr.line(), std::atof(expr.token().begin));
} break; } break;
case Node::textual_hex: { case Node::textual_hex: {
Node triple(new_Node(Node::numeric_color, expr.path(), expr.line(), 4)); result = new_Node(Node::numeric_color, expr.path(), expr.line(), 4);
Token hext(Token::make(expr.token().begin+1, expr.token().end)); Token hext(Token::make(expr.token().begin+1, expr.token().end));
if (hext.length() == 6) { if (hext.length() == 6) {
for (int i = 0; i < 6; i += 2) { for (int i = 0; i < 6; i += 2) {
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16))); result << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(hext.begin+i, 2).c_str(), NULL, 16)));
} }
} }
else { else {
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
triple << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16))); result << new_Node(expr.path(), expr.line(), static_cast<double>(std::strtol(string(2, hext.begin[i]).c_str(), NULL, 16)));
} }
} }
triple << new_Node(expr.path(), expr.line(), 1.0); result << new_Node(expr.path(), expr.line(), 1.0);
return triple;
} break; } break;
case Node::variable: { case Node::variable: {
if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line()); if (!env.query(expr.token())) throw_eval_error("reference to unbound variable " + expr.token().to_string(), expr.path(), expr.line());
result = env[expr.token()];
// cerr << "ACCESSING VARIABLE " << expr.token().to_string() << endl;
// cerr << endl << "*** ENV DUMP ***" << endl;
// env.print();
// cerr << "*** END ENV ***" << endl << endl;
return env[expr.token()];
} break; } break;
case Node::uri: { case Node::uri: {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); result = new_Node(Node::uri, expr.path(), expr.line(), 1);
return expr; result << eval(expr[0], prefix, env, f_env, new_Node, ctx);
} break; } break;
case Node::function_call: { case Node::function_call: {
// TO DO: default-constructed Function should be a generic callback (maybe) // TO DO: default-constructed Function should be a generic callback (maybe)
// eval the function name in case it's interpolated // eval the function name in case it's interpolated
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx, true); Node name_node(eval(expr[0], prefix, env, f_env, new_Node, ctx, true));
string name(expr[0].to_string()); string name(name_node.to_string());
if (!f_env.count(name)) { if (!f_env.count(name)) {
// no definition available; just pass it through (with evaluated args) // no definition available; just pass it through (with evaluated args)
Node args(expr[1]); Node args(expr[1]);
Node evaluated_args(new_Node(Node::arguments, args.path(), args.line(), args.size()));
for (size_t i = 0, S = args.size(); i < S; ++i) { for (size_t i = 0, S = args.size(); i < S; ++i) {
args[i] = eval(args[i], prefix, env, f_env, new_Node, ctx); evaluated_args << eval(args[i], prefix, env, f_env, new_Node, ctx);
if (evaluated_args.back().type() == Node::list) {
Node arg_list(evaluated_args.back());
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
}
}
} }
return expr; result = new_Node(Node::function_call, expr.path(), expr.line(), 2);
result << name_node << evaluated_args;
} }
else { else {
// check to see if the function is primitive/built-in // check to see if the function is primitive/built-in
...@@ -317,29 +429,29 @@ namespace Sass { ...@@ -317,29 +429,29 @@ namespace Sass {
if (!f_env.count(resolved_name)) throw_eval_error("wrong number of arguments to " + name, expr.path(), expr.line()); if (!f_env.count(resolved_name)) throw_eval_error("wrong number of arguments to " + name, expr.path(), expr.line());
f = f_env[resolved_name]; f = f_env[resolved_name];
} }
return apply_function(f, expr[1], prefix, env, f_env, new_Node, ctx, expr.path(), expr.line()); result = apply_function(f, expr[1], prefix, env, f_env, new_Node, ctx, expr.path(), expr.line());
} }
} break; } break;
case Node::unary_plus: { case Node::unary_plus: {
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) { if (arg.is_numeric()) {
return arg; result = arg;
} }
else { else {
expr[0] = arg; result = new_Node(Node::unary_plus, expr.path(), expr.line(), 1);
return expr; result << arg;
} }
} break; } break;
case Node::unary_minus: { case Node::unary_minus: {
Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node arg(eval(expr[0], prefix, env, f_env, new_Node, ctx));
if (arg.is_numeric()) { if (arg.is_numeric()) {
return new_Node(expr.path(), expr.line(), -arg.numeric_value()); result = new_Node(expr.path(), expr.line(), -arg.numeric_value());
} }
else { else {
expr[0] = arg; result = new_Node(Node::unary_minus, expr.path(), expr.line(), 1);
return expr; result << arg;
} }
} break; } break;
...@@ -352,138 +464,35 @@ namespace Sass { ...@@ -352,138 +464,35 @@ namespace Sass {
Node g(color_orig[1]); Node g(color_orig[1]);
Node b(color_orig[2]); Node b(color_orig[2]);
Node a(color_orig[3]); Node a(color_orig[3]);
return new_Node(expr.path(), expr.line(), result = new_Node(expr.path(), expr.line(),
r.numeric_value(), r.numeric_value(),
g.numeric_value(), g.numeric_value(),
b.numeric_value(), b.numeric_value(),
a.numeric_value()); a.numeric_value());
}
else {
return expr;
} }
} break; } break;
case Node::string_schema: case Node::string_schema:
case Node::value_schema: case Node::value_schema:
case Node::identifier_schema: { case Node::identifier_schema: {
result = new_Node(expr.type(), expr.path(), expr.line(), expr.size());
for (size_t i = 0, S = expr.size(); i < S; ++i) { for (size_t i = 0, S = expr.size(); i < S; ++i) {
expr[i] = eval(expr[i], prefix, env, f_env, new_Node, ctx); result << eval(expr[i], prefix, env, f_env, new_Node, ctx);
} }
return expr; result.is_quoted() = expr.is_quoted();
} break; } break;
case Node::css_import: { case Node::css_import: {
expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); result = new_Node(Node::css_import, expr.path(), expr.line(), 1);
return expr; result << eval(expr[0], prefix, env, f_env, new_Node, ctx);
} break;
case Node::if_directive: {
for (size_t i = 0, S = expr.size(); i < S; i += 2) {
if (expr[i].type() != Node::block) {
// cerr << "EVALUATING PREDICATE " << (i/2+1) << endl;
Node predicate_val(eval(expr[i], prefix, env, f_env, new_Node, ctx));
if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) {
// cerr << "EVALUATING CONSEQUENT " << (i/2+1) << endl;
return eval(expr[i+1], prefix, env, f_env, new_Node, ctx);
}
}
else {
// cerr << "EVALUATING ALTERNATIVE" << endl;
return eval(expr[i], prefix, env, f_env, new_Node, ctx);
}
}
} break;
case Node::for_through_directive:
case Node::for_to_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(for_kwd)) << (fake_param << expr[0]) << expr[3];
Node lower_bound(eval(expr[1], prefix, env, f_env, new_Node, ctx));
Node upper_bound(eval(expr[2], prefix, env, f_env, new_Node, ctx));
if (!(lower_bound.is_numeric() && upper_bound.is_numeric())) {
throw_eval_error("bounds of @for directive must be numeric", expr.path(), expr.line());
}
expr.pop_back();
expr.pop_back();
expr.pop_back();
expr.pop_back();
for (double i = lower_bound.numeric_value(),
U = upper_bound.numeric_value() + ((expr.type() == Node::for_to_directive) ? 0 : 1);
i < U;
++i) {
Node i_node(new_Node(expr.path(), expr.line(), i));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << i_node;
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::each_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 1));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(each_kwd)) << (fake_param << expr[0]) << expr[2];
Node list(eval(expr[1], prefix, env, f_env, new_Node, ctx));
// If the list isn't really a list, make a singleton out of it.
if (list.type() != Node::list) {
list = (new_Node(Node::list, list.path(), list.line(), 1) << list);
}
expr.pop_back();
expr.pop_back();
expr.pop_back();
for (size_t i = 0, S = list.size(); i < S; ++i) {
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 1));
fake_arg << eval(list[i], prefix, env, f_env, new_Node, ctx);
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
}
} break;
case Node::while_directive: {
Node fake_mixin(new_Node(Node::mixin, expr.path(), expr.line(), 3));
Node fake_param(new_Node(Node::parameters, expr.path(), expr.line(), 0));
Node fake_arg(new_Node(Node::arguments, expr.path(), expr.line(), 0));
fake_mixin << new_Node(Node::identifier, "", 0, Token::make(while_kwd)) << fake_param << expr[1];
Node pred(expr[0]);
expr.pop_back();
expr.pop_back();
Node ev_pred(eval(pred, prefix, env, f_env, new_Node, ctx));
while ((ev_pred.type() != Node::boolean) || ev_pred.boolean_value()) {
expr += apply_mixin(fake_mixin, fake_arg, prefix, env, f_env, new_Node, ctx, true);
ev_pred = eval(pred, prefix, env, f_env, new_Node, ctx);
}
} break;
case Node::block_directive: {
// TO DO: eval the directive name for interpolants
eval(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
return expr;
} break;
case Node::warning: {
expr = new_Node(expr);
expr[0] = eval(expr[0], Node(), env, f_env, new_Node, ctx);
string prefix("WARNING: ");
string indent(" ");
Node contents(expr[0]);
string result(contents.to_string());
if (contents.type() == Node::string_constant || contents.type() == Node::string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string
}
// These cerrs aren't log lines! They're supposed to be here!
cerr << prefix << result << endl;
cerr << indent << "on line " << expr.line() << " of " << expr.path();
cerr << endl << endl;
return expr;
} break; } break;
default: { default: {
return expr; result = expr;
} break; } break;
} }
if (result.is_null()) result = expr;
return expr; return result;
} }
// Reduce arithmetic operations. Arithmetic expressions are stored as vectors // Reduce arithmetic operations. Arithmetic expressions are stored as vectors
...@@ -497,13 +506,6 @@ namespace Sass { ...@@ -497,13 +506,6 @@ namespace Sass {
Node::Type optype = op.type(); Node::Type optype = op.type();
Node::Type ltype = acc.type(); Node::Type ltype = acc.type();
Node::Type rtype = rhs.type(); Node::Type rtype = rhs.type();
// if (ltype == Node::number && rhs.is_string()) {
// acc = (new_Node(Node::concatenation, list.path(), list.line(), 2) << acc);
// if (optype != Node::add) acc << op;
// if (rtype == Node::concatenation) acc += rhs;
// else acc << rhs;
// acc.is_quoted() = rhs.is_quoted();
// }
if (ltype == Node::number && rtype == Node::number) { if (ltype == Node::number && rtype == Node::number) {
acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value())); acc = new_Node(list.path(), list.line(), operate(op, acc.numeric_value(), rhs.numeric_value()));
} }
...@@ -616,6 +618,65 @@ namespace Sass { ...@@ -616,6 +618,65 @@ namespace Sass {
} }
} }
Node eval_arguments(Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx)
{
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) {
evaluated_args << eval(args[i], prefix, env, f_env, new_Node, ctx);
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);
else new_arg_list << arg_list[j];
}
}
}
else {
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);
if (kwdarg.back().type() == Node::list) {
Node arg_list(kwdarg.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);
else new_arg_list << arg_list[j];
}
kwdarg[1] = new_arg_list;
}
evaluated_args << kwdarg;
}
}
// 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) {
evaluated_args[i].should_eval() = true;
evaluated_args[i] = eval(evaluated_args[i], prefix, env, f_env, new_Node, ctx);
if (evaluated_args[i].type() == Node::list) {
Node arg_list(evaluated_args[i]);
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
}
}
}
else {
Node kwdarg(evaluated_args[i]);
kwdarg[1].should_eval() = true;
kwdarg[1] = eval(kwdarg[1], prefix, env, f_env, new_Node, ctx);
if (kwdarg[1].type() == Node::list) {
Node arg_list(kwdarg[1]);
for (size_t j = 0, S = arg_list.size(); j < S; ++j) {
if (arg_list[j].should_eval()) arg_list[j] = eval(arg_list[j], prefix, env, f_env, new_Node, ctx);
}
}
evaluated_args[i] = kwdarg;
}
}
return evaluated_args;
}
// Helper function for binding arguments in function and mixin calls. // Helper function for binding arguments in function and mixin calls.
// Needs the environment containing the bindings to be passed in by the // Needs the environment containing the bindings to be passed in by the
// caller. Also expects the caller to have pre-evaluated the arguments. // caller. Also expects the caller to have pre-evaluated the arguments.
...@@ -649,7 +710,7 @@ namespace Sass { ...@@ -649,7 +710,7 @@ namespace Sass {
if (!env.query(arg_name)) { if (!env.query(arg_name)) {
throw_eval_error(callee_name + " has no parameter named " + arg_name.to_string(), arg.path(), arg.line()); throw_eval_error(callee_name + " has no parameter named " + arg_name.to_string(), arg.path(), arg.line());
} }
if (!env[arg_name].is_none()) { if (!env[arg_name].is_null()) {
throw_eval_error(callee_name + " was passed argument " + arg_name.to_string() + " both by position and by name", arg.path(), arg.line()); throw_eval_error(callee_name + " was passed argument " + arg_name.to_string() + " both by position and by name", arg.path(), arg.line());
} }
env[arg_name] = arg_value; env[arg_name] = arg_value;
...@@ -660,7 +721,7 @@ namespace Sass { ...@@ -660,7 +721,7 @@ namespace Sass {
for (size_t i = 0, S = params.size(); i < S; ++i) { for (size_t i = 0, S = params.size(); i < S; ++i) {
Node param(params[i]); Node param(params[i]);
Token param_name((param.type() == Node::assignment ? param[0] : param).token()); Token param_name((param.type() == Node::assignment ? param[0] : param).token());
if (env[param_name].is_none()) { if (env[param_name].is_null()) {
if (param.type() != Node::assignment) { if (param.type() != Node::assignment) {
throw_eval_error(callee_name + " is missing argument " + param_name.to_string(), args.path(), args.line()); throw_eval_error(callee_name + " is missing argument " + param_name.to_string(), args.path(), args.line());
} }
...@@ -673,32 +734,11 @@ namespace Sass { ...@@ -673,32 +734,11 @@ namespace Sass {
// Apply a mixin -- bind the arguments in a new environment, link the new // Apply a mixin -- bind the arguments in a new environment, link the new
// environment to the current one, then copy the body and eval in the new // environment to the current one, then copy the body and eval in the new
// environment. // environment.
Node apply_mixin(Node mixin, const Node aargs, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope) Node apply_mixin(Node mixin, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool dynamic_scope)
{ {
Node params(mixin[1]); Node params(mixin[1]);
Node body(new_Node(mixin[2])); // clone the body Node body(new_Node(mixin[2])); // clone the body
Node args = new_Node(aargs); Node evaluated_args(eval_arguments(args, prefix, env, f_env, new_Node, ctx));
// evaluate arguments in the current environment
for (size_t i = 0, S = args.size(); i < S; ++i) {
if (args[i].type() != Node::assignment) {
args[i] = eval(args[i], prefix, env, f_env, new_Node, ctx);
}
else {
args[i][1] = eval(args[i][1], prefix, env, f_env, new_Node, ctx);
}
}
// need to eval twice because some expressions get delayed
for (size_t i = 0, S = args.size(); i < S; ++i) {
if (args[i].type() != Node::assignment) {
args[i].should_eval() = true;
args[i] = eval(args[i], prefix, env, f_env, new_Node, ctx);
}
else {
args[i][1].should_eval() = true;
args[i][1] = eval(args[i][1], prefix, env, f_env, new_Node, ctx);
}
}
// Create a new environment for the mixin and link it to the appropriate parent // Create a new environment for the mixin and link it to the appropriate parent
Environment bindings; Environment bindings;
if (dynamic_scope) { if (dynamic_scope) {
...@@ -712,54 +752,30 @@ namespace Sass { ...@@ -712,54 +752,30 @@ namespace Sass {
// bind arguments in the extended environment // bind arguments in the extended environment
stringstream mixin_name; stringstream mixin_name;
mixin_name << "mixin"; mixin_name << "mixin";
if (!mixin[0].is_none()) mixin_name << " " << mixin[0].to_string(); if (!mixin[0].is_null()) mixin_name << " " << mixin[0].to_string();
bind_arguments(mixin_name.str(), params, args, prefix, bindings, f_env, new_Node, ctx); bind_arguments(mixin_name.str(), params, evaluated_args, prefix, bindings, f_env, new_Node, ctx);
// evaluate the mixin's body // evaluate the mixin's body
for (size_t i = 0, S = body.size(); i < S; ++i) { expand(body, prefix, bindings, f_env, new_Node, ctx);
body[i] = eval(body[i], prefix, bindings, f_env, new_Node, ctx);
}
// cerr << "expanded " << mixin_name.str() << endl;
return body; return body;
} }
// Apply a function -- bind the arguments and pass them to the underlying // Apply a function -- bind the arguments and pass them to the underlying
// primitive function implementation, then return its value. // primitive function implementation, then return its value.
Node apply_function(const Function& f, const Node aargs, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, string& path, size_t line) Node apply_function(const Function& f, const Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, string& path, size_t line)
{ {
Node args = new_Node(aargs); Node evaluated_args(eval_arguments(args, prefix, env, f_env, new_Node, ctx));
// evaluate arguments in the current environment
for (size_t i = 0, S = args.size(); i < S; ++i) {
if (args[i].type() != Node::assignment) {
args[i] = eval(args[i], prefix, env, f_env, new_Node, ctx);
}
else {
args[i][1] = eval(args[i][1], prefix, env, f_env, new_Node, ctx);
}
}
// need to eval twice because some expressions get delayed
for (size_t i = 0, S = args.size(); i < S; ++i) {
if (args[i].type() != Node::assignment) {
args[i].should_eval() = true;
args[i] = eval(args[i], prefix, env, f_env, new_Node, ctx);
}
else {
args[i][1].should_eval() = true;
args[i][1] = eval(args[i][1], prefix, env, f_env, new_Node, ctx);
}
}
// bind arguments // bind arguments
Environment bindings; Environment bindings;
Node params(f.primitive ? f.parameters : f.definition[1]); Node params(f.primitive ? f.parameters : f.definition[1]);
bindings.link(env.global ? *env.global : env); bindings.link(env.global ? *env.global : env);
bind_arguments("function " + f.name, params, args, prefix, bindings, f_env, new_Node, ctx); bind_arguments("function " + f.name, params, evaluated_args, prefix, bindings, f_env, new_Node, ctx);
if (f.primitive) { if (f.primitive) {
return f.primitive(f.parameter_names, bindings, new_Node, path, line); return f.primitive(f.parameter_names, bindings, new_Node, path, line);
} }
else { else {
// TO DO: consider cloning the function body? // TO DO: consider cloning the function body?
return function_eval(f.name, f.definition[2], bindings, new_Node, ctx, true); return eval_function(f.name, f.definition[2], bindings, new_Node, ctx, true);
} }
} }
...@@ -767,54 +783,52 @@ namespace Sass { ...@@ -767,54 +783,52 @@ namespace Sass {
// algorithm is different in this case because the body needs to be // algorithm is different in this case because the body needs to be
// executed and a single value needs to be returned directly, rather than // executed and a single value needs to be returned directly, rather than
// styles being expanded and spliced in place. // styles being expanded and spliced in place.
Node eval_function(string name, Node body, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool at_toplevel)
Node function_eval(string name, Node body, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool at_toplevel)
{ {
for (size_t i = 0, S = body.size(); i < S; ++i) { for (size_t i = 0, S = body.size(); i < S; ++i) {
Node stm(body[i]); Node stm(body[i]);
switch (stm.type()) switch (stm.type())
{ {
case Node::assignment: { case Node::assignment: {
Node val(new_Node(stm[1])); // clone the value because it might get mutated in place Node val(stm[1]);
Node newval;
if (val.type() == Node::list) { if (val.type() == Node::list) {
newval = new_Node(Node::list, val.path(), val.line(), val.size());
newval.is_comma_separated() = val.is_comma_separated();
for (size_t i = 0, S = val.size(); i < S; ++i) { for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].should_eval()) val[i] = eval(val[i], Node(), bindings, ctx.function_env, new_Node, ctx); if (val[i].should_eval()) newval << eval(val[i], Node(), bindings, ctx.function_env, new_Node, ctx);
else newval << val[i];
} }
} }
else { else {
val = eval(val, Node(), bindings, ctx.function_env, new_Node, ctx); newval = eval(val, Node(), bindings, ctx.function_env, new_Node, ctx);
} }
Node var(stm[0]); Node var(stm[0]);
// cerr << "ASSIGNMENT IN FUNCTION: " << var.to_string() << ": " << val.to_string() << endl;
if (stm.is_guarded() && bindings.query(var.token())) continue; if (stm.is_guarded() && bindings.query(var.token())) continue;
// If a binding exists (possibly upframe), then update it. // If a binding exists (possibly upframe), then update it.
// Otherwise, make a new one in the current frame. // Otherwise, make a new one in the current frame.
if (bindings.query(var.token())) { if (bindings.query(var.token())) {
// cerr << "MODIFYING EXISTING BINDING FOR " << var.token().to_string() << endl; bindings[var.token()] = newval;
// cerr << "CURRENT VALUE: " << bindings[var.token()].to_string() << endl;
// cerr << "NEW VALUE: " << val.to_string() << endl;
bindings[var.token()] = val;
} }
else { else {
bindings.current_frame[var.token()] = val; bindings.current_frame[var.token()] = newval;
} }
} break; } break;
case Node::if_directive: { case Node::if_directive: {
for (size_t j = 0, S = stm.size(); j < S; j += 2) { for (size_t j = 0, S = stm.size(); j < S; j += 2) {
if (stm[j].type() != Node::block) { if (stm[j].type() != Node::block) {
Node pred(new_Node(stm[j])); Node predicate_val(eval(stm[j], Node(), bindings, ctx.function_env, new_Node, ctx));
Node predicate_val(eval(pred, Node(), bindings, ctx.function_env, new_Node, ctx)); if (!predicate_val.is_false()) {
if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) { Node v(eval_function(name, stm[j+1], bindings, new_Node, ctx));
Node v(function_eval(name, stm[j+1], bindings, new_Node, ctx)); if (v.is_null()) break;
if (v.is_null_ptr()) break; else return v;
else return v;
} }
} }
else { else {
Node v(function_eval(name, stm[j], bindings, new_Node, ctx)); Node v(eval_function(name, stm[j], bindings, new_Node, ctx));
if (v.is_null_ptr()) break; if (v.is_null()) break;
else return v; else return v;
} }
} }
} break; } break;
...@@ -823,8 +837,8 @@ namespace Sass { ...@@ -823,8 +837,8 @@ namespace Sass {
case Node::for_to_directive: { case Node::for_to_directive: {
Node::Type for_type = stm.type(); Node::Type for_type = stm.type();
Node iter_var(stm[0]); Node iter_var(stm[0]);
Node lower_bound(eval(new_Node(stm[1]), Node(), bindings, ctx.function_env, new_Node, ctx)); Node lower_bound(eval(stm[1], Node(), bindings, ctx.function_env, new_Node, ctx));
Node upper_bound(eval(new_Node(stm[2]), Node(), bindings, ctx.function_env, new_Node, ctx)); Node upper_bound(eval(stm[2], Node(), bindings, ctx.function_env, new_Node, ctx));
Node for_body(stm[3]); Node for_body(stm[3]);
Environment for_env; // re-use this env for each iteration Environment for_env; // re-use this env for each iteration
for_env.link(bindings); for_env.link(bindings);
...@@ -832,15 +846,15 @@ namespace Sass { ...@@ -832,15 +846,15 @@ namespace Sass {
j < T; j < T;
j += 1) { j += 1) {
for_env.current_frame[iter_var.token()] = new_Node(lower_bound.path(), lower_bound.line(), j); for_env.current_frame[iter_var.token()] = new_Node(lower_bound.path(), lower_bound.line(), j);
Node v(function_eval(name, for_body, for_env, new_Node, ctx)); Node v(eval_function(name, for_body, for_env, new_Node, ctx));
if (v.is_null_ptr()) continue; if (v.is_null()) continue;
else return v; else return v;
} }
} break; } break;
case Node::each_directive: { case Node::each_directive: {
Node iter_var(stm[0]); Node iter_var(stm[0]);
Node list(eval(new_Node(stm[1]), Node(), bindings, ctx.function_env, new_Node, ctx)); Node list(eval(stm[1], Node(), bindings, ctx.function_env, new_Node, ctx));
if (list.type() != Node::list) { if (list.type() != Node::list) {
list = (new_Node(Node::list, list.path(), list.line(), 1) << list); list = (new_Node(Node::list, list.path(), list.line(), 1) << list);
} }
...@@ -848,27 +862,24 @@ namespace Sass { ...@@ -848,27 +862,24 @@ namespace Sass {
Environment each_env; // re-use this env for each iteration Environment each_env; // re-use this env for each iteration
each_env.link(bindings); each_env.link(bindings);
for (size_t j = 0, T = list.size(); j < T; ++j) { for (size_t j = 0, T = list.size(); j < T; ++j) {
list[j].should_eval() = true;
each_env.current_frame[iter_var.token()] = eval(list[j], Node(), bindings, ctx.function_env, new_Node, ctx); each_env.current_frame[iter_var.token()] = eval(list[j], Node(), bindings, ctx.function_env, new_Node, ctx);
// cerr << "EACH with " << iter_var.token().to_string() << ": " << each_env[iter_var.token()].to_string() << endl; Node v(eval_function(name, each_body, each_env, new_Node, ctx));
Node v(function_eval(name, each_body, each_env, new_Node, ctx)); if (v.is_null()) continue;
// cerr << endl << "*** ENV DUMP ***" << endl; else return v;
// each_env.print();
// cerr << "*** END ENV ***" << endl << endl;
if (v.is_null_ptr()) continue;
else return v;
} }
} break; } break;
case Node::while_directive: { case Node::while_directive: {
Node pred_expr(new_Node(stm[0])); Node pred_expr(stm[0]);
Node while_body(stm[1]); Node while_body(stm[1]);
Environment while_env; // re-use this env for each iteration Environment while_env; // re-use this env for each iteration
while_env.link(bindings); while_env.link(bindings);
Node pred_val(eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx)); Node pred_val(eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx));
while ((pred_val.type() != Node::boolean) || pred_val.boolean_value()) { while (!pred_val.is_false()) {
Node v(function_eval(name, while_body, while_env, new_Node, ctx)); Node v(eval_function(name, while_body, while_env, new_Node, ctx));
if (v.is_null_ptr()) { if (v.is_null()) {
pred_val = eval(new_Node(stm[0]), Node(), bindings, ctx.function_env, new_Node, ctx); pred_val = eval(pred_expr, Node(), bindings, ctx.function_env, new_Node, ctx);
continue; continue;
} }
else return v; else return v;
...@@ -876,12 +887,9 @@ namespace Sass { ...@@ -876,12 +887,9 @@ namespace Sass {
} break; } break;
case Node::warning: { case Node::warning: {
stm = new_Node(stm);
stm[0] = eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx);
string prefix("WARNING: "); string prefix("WARNING: ");
string indent(" "); string indent(" ");
Node contents(stm[0]); Node contents(eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx));
string result(contents.to_string()); string result(contents.to_string());
if (contents.type() == Node::string_constant || contents.type() == Node::string_schema) { if (contents.type() == Node::string_constant || contents.type() == Node::string_schema) {
result = result.substr(1, result.size()-2); // unquote if it's a single string result = result.substr(1, result.size()-2); // unquote if it's a single string
...@@ -893,11 +901,14 @@ namespace Sass { ...@@ -893,11 +901,14 @@ namespace Sass {
} break; } break;
case Node::return_directive: { case Node::return_directive: {
Node retval(eval(new_Node(stm[0]), Node(), bindings, ctx.function_env, new_Node, ctx)); Node retval(eval(stm[0], Node(), bindings, ctx.function_env, new_Node, ctx));
if (retval.type() == Node::list) { if (retval.type() == Node::list) {
Node new_list(new_Node(Node::list, retval.path(), retval.line(), retval.size()));
new_list.is_comma_separated() = retval.is_comma_separated();
for (size_t i = 0, S = retval.size(); i < S; ++i) { for (size_t i = 0, S = retval.size(); i < S; ++i) {
retval[i] = eval(retval[i], Node(), bindings, ctx.function_env, new_Node, ctx); new_list << eval(retval[i], Node(), bindings, ctx.function_env, new_Node, ctx);
} }
retval = new_list;
} }
return retval; return retval;
} break; } break;
...@@ -916,7 +927,6 @@ namespace Sass { ...@@ -916,7 +927,6 @@ namespace Sass {
// of a backref. When the selector doesn't have backrefs, just prepend the // of a backref. When the selector doesn't have backrefs, just prepend the
// prefix. This function needs multiple subsidiary cases in order to properly // prefix. This function needs multiple subsidiary cases in order to properly
// combine the various kinds of selectors. // combine the various kinds of selectors.
Node expand_selector(Node sel, Node pre, Node_Factory& new_Node) Node expand_selector(Node sel, Node pre, Node_Factory& new_Node)
{ {
if (pre.type() == Node::none) return sel; if (pre.type() == Node::none) return sel;
...@@ -1001,7 +1011,6 @@ namespace Sass { ...@@ -1001,7 +1011,6 @@ namespace Sass {
} }
// Helper for expanding selectors with backrefs. // Helper for expanding selectors with backrefs.
Node expand_backref(Node sel, Node pre) Node expand_backref(Node sel, Node pre)
{ {
switch (sel.type()) switch (sel.type())
...@@ -1027,7 +1036,6 @@ namespace Sass { ...@@ -1027,7 +1036,6 @@ namespace Sass {
} }
// Resolve selector extensions. // Resolve selector extensions.
void extend_selectors(vector<pair<Node, Node> >& pending, multimap<Node, Node>& extension_table, Node_Factory& new_Node) void extend_selectors(vector<pair<Node, Node> >& pending, multimap<Node, Node>& extension_table, Node_Factory& new_Node)
{ {
for (size_t i = 0, S = pending.size(); i < S; ++i) { for (size_t i = 0, S = pending.size(); i < S; ++i) {
...@@ -1202,7 +1210,6 @@ namespace Sass { ...@@ -1202,7 +1210,6 @@ namespace Sass {
// Helper for generating selector extensions; called for each extendee and // Helper for generating selector extensions; called for each extendee and
// extender in a pair of selector groups. // extender in a pair of selector groups.
Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node) Node generate_extension(Node extendee, Node extender, Node_Factory& new_Node)
{ {
Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1)); Node new_group(new_Node(Node::selector_group, extendee.path(), extendee.line(), 1));
...@@ -1258,7 +1265,6 @@ namespace Sass { ...@@ -1258,7 +1265,6 @@ namespace Sass {
} }
// Helpers for extracting subsets of selectors // Helpers for extracting subsets of selectors
Node selector_prefix(Node sel, Node_Factory& new_Node) Node selector_prefix(Node sel, Node_Factory& new_Node)
{ {
switch (sel.type()) switch (sel.type())
......
...@@ -12,9 +12,11 @@ ...@@ -12,9 +12,11 @@
namespace Sass { namespace Sass {
using std::map; using std::map;
void expand(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name = false);
Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name = false); Node eval(Node expr, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx, bool function_name = false);
Node function_eval(string name, Node stm, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool toplevel = false); Node eval_arguments(Node args, Node prefix, Environment& env, map<string, Function>& f_env, Node_Factory& new_Node, Context& ctx);
Node eval_function(string name, Node stm, Environment& bindings, Node_Factory& new_Node, Context& ctx, bool toplevel = false);
Node reduce(Node list, size_t head, Node acc, Node_Factory& new_Node); Node reduce(Node list, size_t head, Node acc, Node_Factory& new_Node);
Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node); Node accumulate(Node::Type op, Node acc, Node rhs, Node_Factory& new_Node);
double operate(Node op, double lhs, double rhs); double operate(Node op, double lhs, double rhs);
......
...@@ -18,8 +18,9 @@ namespace Sass { ...@@ -18,8 +18,9 @@ namespace Sass {
switch (type()) switch (type())
{ {
case block: case block:
case expansion: case mixin_call:
case root: case root:
case if_directive:
case for_through_directive: case for_through_directive:
case for_to_directive: case for_to_directive:
case each_directive: case each_directive:
...@@ -33,8 +34,9 @@ namespace Sass { ...@@ -33,8 +34,9 @@ namespace Sass {
for (size_t i = 0; i < size(); ++i) { for (size_t i = 0; i < size(); ++i) {
switch (at(i).type()) switch (at(i).type())
{ {
case expansion: case mixin_call:
case block: case block:
case if_directive:
case for_through_directive: case for_through_directive:
case for_to_directive: case for_to_directive:
case each_directive: case each_directive:
......
...@@ -150,11 +150,11 @@ namespace Sass { ...@@ -150,11 +150,11 @@ namespace Sass {
identifier_schema, identifier_schema,
css_import, css_import,
function,
function_call, function_call,
mixin, mixin,
function, mixin_call,
parameters, parameters,
expansion,
arguments, arguments,
if_directive, if_directive,
...@@ -177,8 +177,8 @@ namespace Sass { ...@@ -177,8 +177,8 @@ namespace Sass {
Node(Node_Impl* ip = 0); Node(Node_Impl* ip = 0);
Type type() const; Type type() const;
Type type(Type);
bool is_none() const;
bool has_children() const; bool has_children() const;
bool has_statements() const; bool has_statements() const;
bool has_blocks() const; bool has_blocks() const;
...@@ -204,6 +204,7 @@ namespace Sass { ...@@ -204,6 +204,7 @@ namespace Sass {
Node& back() const; Node& back() const;
Node& operator[](size_t i) const; Node& operator[](size_t i) const;
void pop_back(); void pop_back();
void pop_all();
Node& push_back(Node n); Node& push_back(Node n);
Node& push_front(Node n); Node& push_front(Node n);
Node& operator<<(Node n); Node& operator<<(Node n);
...@@ -220,7 +221,7 @@ namespace Sass { ...@@ -220,7 +221,7 @@ namespace Sass {
Token token() const; Token token() const;
Token unit() const; Token unit() const;
bool is_null_ptr() const { return !ip_; } bool is_null() const { return !ip_; }
bool is(Node n) const { return ip_ == n.ip_; } bool is(Node n) const { return ip_ == n.ip_; }
void flatten(); void flatten();
...@@ -369,7 +370,7 @@ namespace Sass { ...@@ -369,7 +370,7 @@ namespace Sass {
case Node::for_to_directive: case Node::for_to_directive:
case Node::each_directive: case Node::each_directive:
case Node::while_directive: case Node::while_directive:
case Node::expansion: { case Node::mixin_call: {
has_expansions = true; has_expansions = true;
} break; } break;
...@@ -401,7 +402,7 @@ namespace Sass { ...@@ -401,7 +402,7 @@ namespace Sass {
case Node::for_to_directive: case Node::for_to_directive:
case Node::each_directive: case Node::each_directive:
case Node::while_directive: case Node::while_directive:
case Node::expansion: has_expansions = true; break; case Node::mixin_call: has_expansions = true; break;
case Node::backref: has_backref = true; break; case Node::backref: has_backref = true; break;
...@@ -413,6 +414,9 @@ namespace Sass { ...@@ -413,6 +414,9 @@ namespace Sass {
void pop_back() void pop_back()
{ children.pop_back(); } { children.pop_back(); }
void pop_all()
{ for (size_t i = 0, S = size(); i < S; ++i) pop_back(); }
bool& boolean_value() bool& boolean_value()
{ return value.boolean; } { return value.boolean; }
...@@ -430,8 +434,8 @@ namespace Sass { ...@@ -430,8 +434,8 @@ namespace Sass {
inline Node::Node(Node_Impl* ip) : ip_(ip) { } inline Node::Node(Node_Impl* ip) : ip_(ip) { }
inline Node::Type Node::type() const { return ip_->type; } inline Node::Type Node::type() const { return ip_->type; }
inline Node::Type Node::type(Type t) { return ip_->type = t; }
inline bool Node::is_none() const { return !ip_; }
inline bool Node::has_children() const { return ip_->has_children; } inline bool Node::has_children() const { return ip_->has_children; }
inline bool Node::has_statements() const { return ip_->has_statements; } inline bool Node::has_statements() const { return ip_->has_statements; }
inline bool Node::has_blocks() const { return ip_->has_blocks; } inline bool Node::has_blocks() const { return ip_->has_blocks; }
...@@ -457,6 +461,7 @@ namespace Sass { ...@@ -457,6 +461,7 @@ namespace Sass {
inline Node& Node::back() const { return ip_->back(); } inline Node& Node::back() const { return ip_->back(); }
inline Node& Node::operator[](size_t i) const { return at(i); } inline Node& Node::operator[](size_t i) const { return at(i); }
inline void Node::pop_back() { ip_->pop_back(); } inline void Node::pop_back() { ip_->pop_back(); }
inline void Node::pop_all() { ip_->pop_all(); }
inline Node& Node::push_back(Node n) inline Node& Node::push_back(Node n)
{ {
ip_->push_back(n); ip_->push_back(n);
......
...@@ -124,6 +124,7 @@ namespace Sass { ...@@ -124,6 +124,7 @@ namespace Sass {
if (size() == 0) return ""; if (size() == 0) return "";
string result(at(0).to_string()); string result(at(0).to_string());
for (size_t i = 1, S = size(); i < S; ++i) { for (size_t i = 1, S = size(); i < S; ++i) {
if (at(i).is_null()) continue;
if (at(i).type() == list && at(i).size() == 0) continue; if (at(i).type() == list && at(i).size() == 0) continue;
result += is_comma_separated() ? ", " : " "; result += is_comma_separated() ? ", " : " ";
result += at(i).to_string(); result += at(i).to_string();
...@@ -270,7 +271,7 @@ namespace Sass { ...@@ -270,7 +271,7 @@ namespace Sass {
return result; return result;
} break; } break;
case expansion: { case mixin_call: {
// ignore it // ignore it
return ""; return "";
} break; } break;
......
...@@ -41,12 +41,12 @@ extern "C" { ...@@ -41,12 +41,12 @@ extern "C" {
{ {
using namespace Sass; using namespace Sass;
doc.parse_scss(); doc.parse_scss();
eval(doc.root, expand(doc.root,
doc.context.new_Node(Node::none, doc.path, doc.line, 0), doc.context.new_Node(Node::none, doc.path, doc.line, 0),
doc.context.global_env, doc.context.global_env,
doc.context.function_env, doc.context.function_env,
doc.context.new_Node, doc.context.new_Node,
doc.context); doc.context);
extend_selectors(doc.context.pending_extensions, doc.context.extensions, doc.context.new_Node); extend_selectors(doc.context.pending_extensions, doc.context.extensions, doc.context.new_Node);
string output(doc.emit_css(static_cast<Document::CSS_Style>(style))); string output(doc.emit_css(static_cast<Document::CSS_Style>(style)));
char* c_output = (char*) malloc(output.size() + 1); char* c_output = (char*) malloc(output.size() + 1);
......
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