Commit 0259be64 by Aaron Leung

Trying a new expansion/evaluation process. Will hopefully fix some more little bugs.

parent f7c3321b
...@@ -598,7 +598,7 @@ namespace Sass { ...@@ -598,7 +598,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;
......
...@@ -20,43 +20,62 @@ namespace Sass { ...@@ -20,43 +20,62 @@ 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::expansion: { // 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_back(); // pop the mixin name
expr.pop_back(); expr.pop_back(); // pop the mixin args
expr += expansion; expr += expansion; // push the 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
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); // 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,67 +106,39 @@ namespace Sass { ...@@ -87,67 +106,39 @@ namespace Sass {
} }
} }
// eval the body with the current selector as the prefix // expand the body with the current 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 val(expr[1]);
if (val.type() == Node::list) {
for (size_t i = 0, S = val.size(); i < S; ++i) {
if (val[i].should_eval()) val[i] = eval(val[i], prefix, env, f_env, new_Node, ctx);
}
}
else {
val = eval(val, prefix, env, f_env, new_Node, ctx);
}
Node var(expr[0]); Node var(expr[0]);
if (expr.is_guarded() && env.query(var.token())) return expr; if (expr.is_guarded() && env.query(var.token())) return;
Node val(expr[1]);
val = eval(val, prefix, env, f_env, new_Node, ctx);
// if (val.type() == Node::list) {
// for (size_t i = 0, S = val.size(); i < S; ++i) {
// if (val[i].should_eval()) val[i] = eval(val[i], prefix, env, f_env, new_Node, ctx);
// }
// }
// else {
// val = eval(val, prefix, env, f_env, new_Node, ctx);
// }
// If a binding exists (possible 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())) {
...@@ -156,49 +147,185 @@ namespace Sass { ...@@ -156,49 +147,185 @@ 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.is_schema() || rhs.should_eval()) {
for (size_t i = 0, S = rhs.size(); i < S; ++i) { expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
if (rhs[i].should_eval()) rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx); }
// if (rhs.type() == Node::list) {
// for (size_t i = 0, S = rhs.size(); i < S; ++i) {
// if (rhs[i].should_eval()) rhs[i] = eval(rhs[i], prefix, env, f_env, new_Node, ctx);
// }
// }
// else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) {
// eval(rhs, prefix, env, f_env, new_Node, ctx);
// }
// else {
// if (rhs.should_eval()) expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx);
// }
} break;
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.type() != Node::boolean) || predicate_val.boolean_value()) {
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;
} }
} }
else if (rhs.type() == Node::value_schema || rhs.type() == Node::string_schema) { expr.pop_all();
eval(rhs, prefix, env, f_env, new_Node, ctx); if (!expansion.is_null()) expr += expansion;
} 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());
} }
else { expr.pop_all();
if (rhs.should_eval()) expr[1] = eval(rhs, prefix, env, f_env, new_Node, ctx); 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));
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.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
expand(expr[1], new_Node(Node::none, expr.path(), expr.line(), 0), env, f_env, new_Node, ctx);
} break;
case Node::warning: {
// expr = new_Node(expr);
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
} }
return expr; // 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; } break;
}
}
// 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;
switch (expr.type())
{
// 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::list: { case Node::list: {
if (expr.should_eval()) expr[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx); if (expr.should_eval()) {
return expr; result = new_Node(expr);
result[0] = eval(expr[0], prefix, env, f_env, new_Node, ctx);
}
} break; } break;
case Node::disjunction: { case Node::disjunction: {
Node result; 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; 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: {
...@@ -206,106 +333,97 @@ namespace Sass { ...@@ -206,106 +333,97 @@ namespace Sass {
Node lhs(eval(expr[0], prefix, env, f_env, new_Node, ctx)); Node lhs(eval(expr[0], prefix, env, f_env, new_Node, ctx));
Node op(expr[1]); Node op(expr[1]);
Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx)); Node rhs(eval(expr[2], prefix, env, f_env, new_Node, ctx));
// TO DO: don't allocate both T and F
Node T(new_Node(Node::boolean, lhs.path(), lhs.line(), true)); Node 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 (op.type())
{ {
case Node::eq: return (lhs == rhs) ? T : F; case Node::eq: result = ((lhs == rhs) ? T : F);
case Node::neq: return (lhs != rhs) ? T : F; case Node::neq: result = ((lhs != rhs) ? T : F);
case Node::gt: return (lhs > rhs) ? T : F; case Node::gt: result = ((lhs > rhs) ? T : F);
case Node::gte: return (lhs >= rhs) ? T : F; case Node::gte: result = ((lhs >= rhs) ? T : F);
case Node::lt: return (lhs < rhs) ? T : F; case Node::lt: result = ((lhs < rhs) ? T : F);
case Node::lte: return (lhs <= rhs) ? T : F; case Node::lte: result = ((lhs <= rhs) ? T : F);
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);
} }
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 +435,29 @@ namespace Sass { ...@@ -317,29 +435,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 +470,33 @@ namespace Sass { ...@@ -352,138 +470,33 @@ 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;
} 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;
} }
return result;
return expr;
} }
// Reduce arithmetic operations. Arithmetic expressions are stored as vectors // Reduce arithmetic operations. Arithmetic expressions are stored as vectors
...@@ -649,7 +662,7 @@ namespace Sass { ...@@ -649,7 +662,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 +673,7 @@ namespace Sass { ...@@ -660,7 +673,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());
} }
...@@ -712,7 +725,7 @@ namespace Sass { ...@@ -712,7 +725,7 @@ 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, 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) { for (size_t i = 0, S = body.size(); i < S; ++i) {
...@@ -807,13 +820,13 @@ namespace Sass { ...@@ -807,13 +820,13 @@ namespace Sass {
Node predicate_val(eval(pred, Node(), bindings, ctx.function_env, new_Node, ctx)); Node predicate_val(eval(pred, Node(), bindings, ctx.function_env, new_Node, ctx));
if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) { if ((predicate_val.type() != Node::boolean) || predicate_val.boolean_value()) {
Node v(function_eval(name, stm[j+1], bindings, new_Node, ctx)); Node v(function_eval(name, stm[j+1], bindings, new_Node, ctx));
if (v.is_null_ptr()) break; if (v.is_null()) break;
else return v; else return v;
} }
} }
else { else {
Node v(function_eval(name, stm[j], bindings, new_Node, ctx)); Node v(function_eval(name, stm[j], bindings, new_Node, ctx));
if (v.is_null_ptr()) break; if (v.is_null()) break;
else return v; else return v;
} }
} }
...@@ -833,7 +846,7 @@ namespace Sass { ...@@ -833,7 +846,7 @@ namespace Sass {
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(function_eval(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;
...@@ -854,7 +867,7 @@ namespace Sass { ...@@ -854,7 +867,7 @@ namespace Sass {
// cerr << endl << "*** ENV DUMP ***" << endl; // cerr << endl << "*** ENV DUMP ***" << endl;
// each_env.print(); // each_env.print();
// cerr << "*** END ENV ***" << endl << endl; // cerr << "*** END ENV ***" << endl << endl;
if (v.is_null_ptr()) continue; if (v.is_null()) continue;
else return v; else return v;
} }
} break; } break;
...@@ -867,7 +880,7 @@ namespace Sass { ...@@ -867,7 +880,7 @@ namespace Sass {
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.type() != Node::boolean) || pred_val.boolean_value()) {
Node v(function_eval(name, while_body, while_env, new_Node, ctx)); Node v(function_eval(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(new_Node(stm[0]), Node(), bindings, ctx.function_env, new_Node, ctx);
continue; continue;
} }
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
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 function_eval(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);
......
...@@ -20,6 +20,7 @@ namespace Sass { ...@@ -20,6 +20,7 @@ namespace Sass {
case block: case block:
case expansion: case expansion:
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:
...@@ -35,6 +36,7 @@ namespace Sass { ...@@ -35,6 +36,7 @@ namespace Sass {
{ {
case expansion: case expansion:
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:
......
...@@ -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();
...@@ -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);
......
...@@ -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