// sass.hpp must go before all system headers to get the
// __EXTENSIONS__ fix on Solaris.
#include "sass.hpp"

#include <iostream>
#include <typeinfo>

#include "ast.hpp"
#include "expand.hpp"
#include "bind.hpp"
#include "eval.hpp"
#include "backtrace.hpp"
#include "context.hpp"
#include "parser.hpp"
#include "sass_functions.hpp"
#include "error_handling.hpp"

namespace Sass {

  // simple endless recursion protection
  const size_t maxRecursion = 500;

  Expand::Expand(Context& ctx, Env* env, SelectorStack* stack, SelectorStack* originals)
  : ctx(ctx),
    traces(ctx.traces),
    eval(Eval(*this)),
    recursions(0),
    in_keyframes(false),
    at_root_without_rule(false),
    old_at_root_without_rule(false),
    env_stack(),
    block_stack(),
    call_stack(),
    selector_stack(),
    originalStack(),
    mediaStack()
  {
    env_stack.push_back(nullptr);
    env_stack.push_back(env);
    block_stack.push_back(nullptr);
    call_stack.push_back({});
    if (stack == NULL) { pushToSelectorStack({}); }
    else {
      for (auto item : *stack) {
        if (item.isNull()) pushToSelectorStack({});
        else pushToSelectorStack(item);
      }
    }
    if (originals == NULL) { pushToOriginalStack({}); }
    else {
      for (auto item : *stack) {
        if (item.isNull()) pushToOriginalStack({});
        else pushToOriginalStack(item);
      }
    }
    mediaStack.push_back({});
  }

  Env* Expand::environment()
  {
    if (env_stack.size() > 0)
      return env_stack.back();
    return 0;
  }

  void Expand::pushNullSelector()
  {
    pushToSelectorStack({});
    pushToOriginalStack({});
  }

  void Expand::popNullSelector()
  {
    popFromOriginalStack();
    popFromSelectorStack();
  }

  SelectorStack Expand::getOriginalStack()
  {
    return originalStack;
  }

  SelectorStack Expand::getSelectorStack()
  {
    return selector_stack;
  }

  SelectorListObj& Expand::selector()
  {
    if (selector_stack.size() > 0) {
      auto& sel = selector_stack.back();
      if (sel.isNull()) return sel;
      return sel;
    }
    // Avoid the need to return copies
    // We always want an empty first item
    selector_stack.push_back({});
    return selector_stack.back();;
  }

  SelectorListObj& Expand::original()
  {
    if (originalStack.size() > 0) {
      auto& sel = originalStack.back();
      if (sel.isNull()) return sel;
      return sel;
    }
    // Avoid the need to return copies
    // We always want an empty first item
    originalStack.push_back({});
    return originalStack.back();
  }

  SelectorListObj Expand::popFromSelectorStack()
  {
    SelectorListObj last = selector_stack.back();
    if (selector_stack.size() > 0)    
      selector_stack.pop_back();
    if (last.isNull()) return {};
    return last;
  }

  void Expand::pushToSelectorStack(SelectorListObj selector)
  {
    selector_stack.push_back(selector);
  }

  SelectorListObj Expand::popFromOriginalStack()
  {
    SelectorListObj last = originalStack.back();
    if (originalStack.size() > 0)
      originalStack.pop_back();
    if (last.isNull()) return {};
    return last;
  }

  void Expand::pushToOriginalStack(SelectorListObj selector)
  {
    originalStack.push_back(selector);
  }

  // blocks create new variable scopes
  Block* Expand::operator()(Block* b)
  {
    // create new local environment
    // set the current env as parent
    Env env(environment());
    // copy the block object (add items later)
    Block_Obj bb = SASS_MEMORY_NEW(Block,
                                b->pstate(),
                                b->length(),
                                b->is_root());
    // setup block and env stack
    this->block_stack.push_back(bb);
    this->env_stack.push_back(&env);
    // operate on block
    // this may throw up!
    this->append_block(b);
    // revert block and env stack
    this->block_stack.pop_back();
    this->env_stack.pop_back();
    // return copy
    return bb.detach();
  }

  Statement* Expand::operator()(StyleRule* r)
  {
    LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule);

    if (in_keyframes) {
      Block* bb = operator()(r->block());
      Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb);
      if (r->schema()) {
        pushNullSelector();
        k->name(eval(r->schema()));
        popNullSelector();
      }
      else if (r->selector()) {
        if (SelectorListObj s = r->selector()) {
          pushNullSelector();
          k->name(eval(s));
          popNullSelector();
        }
      }

      return k.detach();
    }

    if (r->schema()) {
      SelectorListObj sel = eval(r->schema());
      r->selector(sel);
      for (auto complex : sel->elements()) {
        // ToDo: maybe we can get rid of chroots?
        complex->chroots(complex->has_real_parent_ref());
      }

    }

    // reset when leaving scope
    LOCAL_FLAG(at_root_without_rule, false);

    SelectorListObj evaled = eval(r->selector());
    // do not connect parent again
    Env env(environment());
    if (block_stack.back()->is_root()) {
      env_stack.push_back(&env);
    }
    Block_Obj blk;
    pushToSelectorStack(evaled);
    // The copy is needed for parent reference evaluation
    // dart-sass stores it as `originalSelector` member
    pushToOriginalStack(SASS_MEMORY_COPY(evaled));
    ctx.extender.addSelector(evaled, mediaStack.back());
    if (r->block()) blk = operator()(r->block());
    popFromOriginalStack();
    popFromSelectorStack();
    StyleRule* rr = SASS_MEMORY_NEW(StyleRule,
                                  r->pstate(),
                                  evaled,
                                  blk);

    if (block_stack.back()->is_root()) {
      env_stack.pop_back();
    }

    rr->is_root(r->is_root());
    rr->tabs(r->tabs());

    return rr;
  }

  Statement* Expand::operator()(SupportsRule* f)
  {
    ExpressionObj condition = f->condition()->perform(&eval);
    SupportsRuleObj ff = SASS_MEMORY_NEW(SupportsRule,
                                       f->pstate(),
                                       Cast<SupportsCondition>(condition),
                                       operator()(f->block()));
    return ff.detach();
  }

  sass::vector<CssMediaQuery_Obj> Expand::mergeMediaQueries(
    const sass::vector<CssMediaQuery_Obj>& lhs,
    const sass::vector<CssMediaQuery_Obj>& rhs)
  {
    sass::vector<CssMediaQuery_Obj> queries;
    for (CssMediaQuery_Obj query1 : lhs) {
      for (CssMediaQuery_Obj query2 : rhs) {
        CssMediaQuery_Obj result = query1->merge(query2);
        if (result && !result->empty()) {
          queries.push_back(result);
        }
      }
    }
    return queries;
  }

  Statement* Expand::operator()(MediaRule* m)
  {
    ExpressionObj mq = eval(m->schema());
    sass::string str_mq(mq->to_css(ctx.c_options));
    ItplFile* source = SASS_MEMORY_NEW(ItplFile,
      str_mq.c_str(), m->pstate());
    Parser parser(source, ctx, traces);
    // Create a new CSS only representation of the media rule
    CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block());
    sass::vector<CssMediaQuery_Obj> parsed = parser.parseCssMediaQueries();
    if (mediaStack.size() && mediaStack.back()) {
      auto& parent = mediaStack.back()->elements();
      css->concat(mergeMediaQueries(parent, parsed));
    }
    else {
      css->concat(parsed);
    }
    mediaStack.push_back(css);
    css->block(operator()(m->block()));
    mediaStack.pop_back();
    return css.detach();

  }

  Statement* Expand::operator()(AtRootRule* a)
  {
    Block_Obj ab = a->block();
    ExpressionObj ae = a->expression();

    if (ae) ae = ae->perform(&eval);
    else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate());

    LOCAL_FLAG(at_root_without_rule, Cast<At_Root_Query>(ae)->exclude("rule"));
    LOCAL_FLAG(in_keyframes, false);

                                       ;

    Block_Obj bb = ab ? operator()(ab) : NULL;
    AtRootRuleObj aa = SASS_MEMORY_NEW(AtRootRule,
                                        a->pstate(),
                                        bb,
                                        Cast<At_Root_Query>(ae));
    return aa.detach();
  }

  Statement* Expand::operator()(AtRule* a)
  {
    LOCAL_FLAG(in_keyframes, a->is_keyframes());
    Block* ab = a->block();
    SelectorList* as = a->selector();
    Expression* av = a->value();
    pushNullSelector();
    if (av) av = av->perform(&eval);
    if (as) as = eval(as);
    popNullSelector();
    Block* bb = ab ? operator()(ab) : NULL;
    AtRule* aa = SASS_MEMORY_NEW(AtRule,
                                  a->pstate(),
                                  a->keyword(),
                                  as,
                                  bb,
                                  av);
    return aa;
  }

  Statement* Expand::operator()(Declaration* d)
  {
    Block_Obj ab = d->block();
    String_Obj old_p = d->property();
    ExpressionObj prop = old_p->perform(&eval);
    String_Obj new_p = Cast<String>(prop);
    // we might get a color back
    if (!new_p) {
      sass::string str(prop->to_string(ctx.c_options));
      new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str);
    }
    ExpressionObj value = d->value();
    if (value) value = value->perform(&eval);
    Block_Obj bb = ab ? operator()(ab) : NULL;
    if (!bb) {
      if (!value || (value->is_invisible() && !d->is_important())) {
        if (d->is_custom_property()) {
          error("Custom property values may not be empty.", d->value()->pstate(), traces);
        } else {
          return nullptr;
        }
      }
    }
    Declaration* decl = SASS_MEMORY_NEW(Declaration,
                                        d->pstate(),
                                        new_p,
                                        value,
                                        d->is_important(),
                                        d->is_custom_property(),
                                        bb);
    decl->tabs(d->tabs());
    return decl;
  }

  Statement* Expand::operator()(Assignment* a)
  {
    Env* env = environment();
    const sass::string& var(a->variable());
    if (a->is_global()) {
      if (!env->has_global(var)) {
        deprecated(
          "!global assignments won't be able to declare new variables in future versions.",
          "Consider adding `" + var + ": null` at the top level.",
          true, a->pstate());
      }
      if (a->is_default()) {
        if (env->has_global(var)) {
          ExpressionObj e = Cast<Expression>(env->get_global(var));
          if (!e || e->concrete_type() == Expression::NULL_VAL) {
            env->set_global(var, a->value()->perform(&eval));
          }
        }
        else {
          env->set_global(var, a->value()->perform(&eval));
        }
      }
      else {
        env->set_global(var, a->value()->perform(&eval));
      }
    }
    else if (a->is_default()) {
      if (env->has_lexical(var)) {
        auto cur = env;
        while (cur && cur->is_lexical()) {
          if (cur->has_local(var)) {
            if (AST_Node_Obj node = cur->get_local(var)) {
              ExpressionObj e = Cast<Expression>(node);
              if (!e || e->concrete_type() == Expression::NULL_VAL) {
                cur->set_local(var, a->value()->perform(&eval));
              }
            }
            else {
              throw std::runtime_error("Env not in sync");
            }
            return 0;
          }
          cur = cur->parent();
        }
        throw std::runtime_error("Env not in sync");
      }
      else if (env->has_global(var)) {
        if (AST_Node_Obj node = env->get_global(var)) {
          ExpressionObj e = Cast<Expression>(node);
          if (!e || e->concrete_type() == Expression::NULL_VAL) {
            env->set_global(var, a->value()->perform(&eval));
          }
        }
      }
      else if (env->is_lexical()) {
        env->set_local(var, a->value()->perform(&eval));
      }
      else {
        env->set_local(var, a->value()->perform(&eval));
      }
    }
    else {
      env->set_lexical(var, a->value()->perform(&eval));
    }
    return 0;
  }

  Statement* Expand::operator()(Import* imp)
  {
    Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate());
    if (imp->import_queries() && imp->import_queries()->size()) {
      ExpressionObj ex = imp->import_queries()->perform(&eval);
      result->import_queries(Cast<List>(ex));
    }
    for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) {
      result->urls().push_back(imp->urls()[i]->perform(&eval));
    }
    // all resources have been dropped for Input_Stubs
    // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {}
    return result.detach();
  }

  Statement* Expand::operator()(Import_Stub* i)
  {
    traces.push_back(Backtrace(i->pstate()));
    // get parent node from call stack
    AST_Node_Obj parent = call_stack.back();
    if (Cast<Block>(parent) == NULL) {
      error("Import directives may not be used within control directives or mixins.", i->pstate(), traces);
    }
    // we don't seem to need that actually afterall
    Sass_Import_Entry import = sass_make_import(
      i->imp_path().c_str(),
      i->abs_path().c_str(),
      0, 0
    );
    ctx.import_stack.push_back(import);

    Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate());
    Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i');
    block_stack.back()->append(trace);
    block_stack.push_back(trace_block);

    const sass::string& abs_path(i->resource().abs_path);
    append_block(ctx.sheets.at(abs_path).root);
    sass_delete_import(ctx.import_stack.back());
    ctx.import_stack.pop_back();
    block_stack.pop_back();
    traces.pop_back();
    return 0;
  }

  Statement* Expand::operator()(WarningRule* w)
  {
    // eval handles this too, because warnings may occur in functions
    w->perform(&eval);
    return 0;
  }

  Statement* Expand::operator()(ErrorRule* e)
  {
    // eval handles this too, because errors may occur in functions
    e->perform(&eval);
    return 0;
  }

  Statement* Expand::operator()(DebugRule* d)
  {
    // eval handles this too, because warnings may occur in functions
    d->perform(&eval);
    return 0;
  }

  Statement* Expand::operator()(Comment* c)
  {
    if (ctx.output_style() == COMPRESSED) {
      // comments should not be evaluated in compact
      // https://github.com/sass/libsass/issues/2359
      if (!c->is_important()) return NULL;
    }
    eval.is_in_comment = true;
    Comment* rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast<String>(c->text()->perform(&eval)), c->is_important());
    eval.is_in_comment = false;
    // TODO: eval the text, once we're parsing/storing it as a String_Schema
    return rv;
  }

  Statement* Expand::operator()(If* i)
  {
    Env env(environment(), true);
    env_stack.push_back(&env);
    call_stack.push_back(i);
    ExpressionObj rv = i->predicate()->perform(&eval);
    if (*rv) {
      append_block(i->block());
    }
    else {
      Block* alt = i->alternative();
      if (alt) append_block(alt);
    }
    call_stack.pop_back();
    env_stack.pop_back();
    return 0;
  }

  // For does not create a new env scope
  // But iteration vars are reset afterwards
  Statement* Expand::operator()(ForRule* f)
  {
    sass::string variable(f->variable());
    ExpressionObj low = f->lower_bound()->perform(&eval);
    if (low->concrete_type() != Expression::NUMBER) {
      traces.push_back(Backtrace(low->pstate()));
      throw Exception::TypeMismatch(traces, *low, "integer");
    }
    ExpressionObj high = f->upper_bound()->perform(&eval);
    if (high->concrete_type() != Expression::NUMBER) {
      traces.push_back(Backtrace(high->pstate()));
      throw Exception::TypeMismatch(traces, *high, "integer");
    }
    Number_Obj sass_start = Cast<Number>(low);
    Number_Obj sass_end = Cast<Number>(high);
    // check if units are valid for sequence
    if (sass_start->unit() != sass_end->unit()) {
      sass::ostream msg; msg << "Incompatible units: '"
        << sass_start->unit() << "' and '"
        << sass_end->unit() << "'.";
      error(msg.str(), low->pstate(), traces);
    }
    double start = sass_start->value();
    double end = sass_end->value();
    // only create iterator once in this environment
    Env env(environment(), true);
    env_stack.push_back(&env);
    call_stack.push_back(f);
    Block* body = f->block();
    if (start < end) {
      if (f->is_inclusive()) ++end;
      for (double i = start;
           i < end;
           ++i) {
        Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit());
        env.set_local(variable, it);
        append_block(body);
      }
    } else {
      if (f->is_inclusive()) --end;
      for (double i = start;
           i > end;
           --i) {
        Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit());
        env.set_local(variable, it);
        append_block(body);
      }
    }
    call_stack.pop_back();
    env_stack.pop_back();
    return 0;
  }

  // Eval does not create a new env scope
  // But iteration vars are reset afterwards
  Statement* Expand::operator()(EachRule* e)
  {
    sass::vector<sass::string> variables(e->variables());
    ExpressionObj expr = e->list()->perform(&eval);
    List_Obj list;
    Map_Obj map;
    if (expr->concrete_type() == Expression::MAP) {
      map = Cast<Map>(expr);
    }
    else if (SelectorList * ls = Cast<SelectorList>(expr)) {
      ExpressionObj rv = Listize::perform(ls);
      list = Cast<List>(rv);
    }
    else if (expr->concrete_type() != Expression::LIST) {
      list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA);
      list->append(expr);
    }
    else {
      list = Cast<List>(expr);
    }
    // remember variables and then reset them
    Env env(environment(), true);
    env_stack.push_back(&env);
    call_stack.push_back(e);
    Block* body = e->block();

    if (map) {
      for (auto key : map->keys()) {
        ExpressionObj k = key->perform(&eval);
        ExpressionObj v = map->at(key)->perform(&eval);

        if (variables.size() == 1) {
          List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE);
          variable->append(k);
          variable->append(v);
          env.set_local(variables[0], variable);
        } else {
          env.set_local(variables[0], k);
          env.set_local(variables[1], v);
        }
        append_block(body);
      }
    }
    else {
      // bool arglist = list->is_arglist();
      if (list->length() == 1 && Cast<SelectorList>(list)) {
        list = Cast<List>(list);
      }
      for (size_t i = 0, L = list->length(); i < L; ++i) {
        ExpressionObj item = list->at(i);
        // unwrap value if the expression is an argument
        if (Argument_Obj arg = Cast<Argument>(item)) item = arg->value();
        // check if we got passed a list of args (investigate)
        if (List_Obj scalars = Cast<List>(item)) {
          if (variables.size() == 1) {
            List_Obj var = scalars;
            // if (arglist) var = (*scalars)[0];
            env.set_local(variables[0], var);
          } else {
            for (size_t j = 0, K = variables.size(); j < K; ++j) {
              env.set_local(variables[j], j >= scalars->length()
                ? SASS_MEMORY_NEW(Null, expr->pstate())
                : (*scalars)[j]->perform(&eval));
            }
          }
        } else {
          if (variables.size() > 0) {
            env.set_local(variables.at(0), item);
            for (size_t j = 1, K = variables.size(); j < K; ++j) {
              ExpressionObj res = SASS_MEMORY_NEW(Null, expr->pstate());
              env.set_local(variables[j], res);
            }
          }
        }
        append_block(body);
      }
    }
    call_stack.pop_back();
    env_stack.pop_back();
    return 0;
  }

  Statement* Expand::operator()(WhileRule* w)
  {
    ExpressionObj pred = w->predicate();
    Block* body = w->block();
    Env env(environment(), true);
    env_stack.push_back(&env);
    call_stack.push_back(w);
    ExpressionObj cond = pred->perform(&eval);
    while (!cond->is_false()) {
      append_block(body);
      cond = pred->perform(&eval);
    }
    call_stack.pop_back();
    env_stack.pop_back();
    return 0;
  }

  Statement* Expand::operator()(Return* r)
  {
    error("@return may only be used within a function", r->pstate(), traces);
    return 0;
  }

  Statement* Expand::operator()(ExtendRule* e)
  {

    // evaluate schema first
    if (e->schema()) {
      e->selector(eval(e->schema()));
      e->isOptional(e->selector()->is_optional());
    }
    // evaluate the selector
    e->selector(eval(e->selector()));

    if (e->selector()) {

      for (auto complex : e->selector()->elements()) {

        if (complex->length() != 1) {
          error("complex selectors may not be extended.", complex->pstate(), traces);
        }

        if (const CompoundSelector* compound = complex->first()->getCompound()) {

          if (compound->length() != 1) {

            sass::ostream sels; bool addComma = false;
            sels << "Compound selectors may no longer be extended.\n";
            sels << "Consider `@extend ";
            for (auto sel : compound->elements()) {
              if (addComma) sels << ", ";
              sels << sel->to_sass();
              addComma = true;
            }
            sels << "` instead.\n";
            sels << "See http://bit.ly/ExtendCompound for details.";

            warning(sels.str(), compound->pstate());

            // Make this an error once deprecation is over
            for (SimpleSelectorObj simple : compound->elements()) {
              // Pass every selector we ever see to extender (to make them findable for extend)
              ctx.extender.addExtension(selector(), simple, mediaStack.back(), e->isOptional());
            }

          }
          else {
            // Pass every selector we ever see to extender (to make them findable for extend)
            ctx.extender.addExtension(selector(), compound->first(), mediaStack.back(), e->isOptional());
          }

        }
        else {
          error("complex selectors may not be extended.", complex->pstate(), traces);
        }
      }
    }

    return nullptr;

  }

  Statement* Expand::operator()(Definition* d)
  {
    Env* env = environment();
    Definition_Obj dd = SASS_MEMORY_COPY(d);
    env->local_frame()[d->name() +
                        (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd;

    if (d->type() == Definition::FUNCTION && (
      Prelexer::calc_fn_call(d->name().c_str()) ||
      d->name() == "element"    ||
      d->name() == "expression" ||
      d->name() == "url"
    )) {
      deprecated(
        "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.",
        "This name conflicts with an existing CSS function with special parse rules.",
        false, d->pstate()
      );
    }

    // set the static link so we can have lexical scoping
    dd->environment(env);
    return 0;
  }

  Statement* Expand::operator()(Mixin_Call* c)
  {

    if (recursions > maxRecursion) {
      throw Exception::StackError(traces, *c);
    }

    recursions ++;

    Env* env = environment();
    sass::string full_name(c->name() + "[m]");
    if (!env->has(full_name)) {
      error("no mixin named " + c->name(), c->pstate(), traces);
    }
    Definition_Obj def = Cast<Definition>((*env)[full_name]);
    Block_Obj body = def->block();
    Parameters_Obj params = def->parameters();

    if (c->block() && c->name() != "@content" && !body->has_content()) {
      error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces);
    }
    ExpressionObj rv = c->arguments()->perform(&eval);
    Arguments_Obj args = Cast<Arguments>(rv);
    sass::string msg(", in mixin `" + c->name() + "`");
    traces.push_back(Backtrace(c->pstate(), msg));
    ctx.callee_stack.push_back({
      c->name().c_str(),
      c->pstate().getPath(),
      c->pstate().getLine(),
      c->pstate().getColumn(),
      SASS_CALLEE_MIXIN,
      { env }
    });

    Env new_env(def->environment());
    env_stack.push_back(&new_env);
    if (c->block()) {
      Parameters_Obj params = c->block_parameters();
      if (!params) params = SASS_MEMORY_NEW(Parameters, c->pstate());
      // represent mixin content blocks as thunks/closures
      Definition_Obj thunk = SASS_MEMORY_NEW(Definition,
                                          c->pstate(),
                                          "@content",
                                          params,
                                          c->block(),
                                          Definition::MIXIN);
      thunk->environment(env);
      new_env.local_frame()["@content[m]"] = thunk;
    }

    bind(sass::string("Mixin"), c->name(), params, args, &new_env, &eval, traces);

    Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate());
    Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block);

    env->set_global("is_in_mixin", bool_true);
    if (Block* pr = block_stack.back()) {
      trace_block->is_root(pr->is_root());
    }
    block_stack.push_back(trace_block);
    for (auto bb : body->elements()) {
      if (StyleRule* r = Cast<StyleRule>(bb)) {
        r->is_root(trace_block->is_root());
      }
      Statement_Obj ith = bb->perform(this);
      if (ith) trace->block()->append(ith);
    }
    block_stack.pop_back();
    env->del_global("is_in_mixin");

    ctx.callee_stack.pop_back();
    env_stack.pop_back();
    traces.pop_back();

    recursions --;
    return trace.detach();
  }

  Statement* Expand::operator()(Content* c)
  {
    Env* env = environment();
    // convert @content directives into mixin calls to the underlying thunk
    if (!env->has("@content[m]")) return 0;
    Arguments_Obj args = c->arguments();
    if (!args) args = SASS_MEMORY_NEW(Arguments, c->pstate());

    Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call,
                                       c->pstate(),
                                       "@content",
                                       args);

    Trace_Obj trace = Cast<Trace>(call->perform(this));
    return trace.detach();
  }

  // process and add to last block on stack
  inline void Expand::append_block(Block* b)
  {
    if (b->is_root()) call_stack.push_back(b);
    for (size_t i = 0, L = b->length(); i < L; ++i) {
      Statement* stm = b->at(i);
      Statement_Obj ith = stm->perform(this);
      if (ith) block_stack.back()->append(ith);
    }
    if (b->is_root()) call_stack.pop_back();
  }

}