Blob Blame History Raw
#ifndef SASS_AST_H
#define SASS_AST_H

#include "sass.hpp"
#include <set>
#include <deque>
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <typeinfo>
#include <algorithm>
#include "sass/base.h"
#include "ast_fwd_decl.hpp"

#ifdef DEBUG_SHARED_PTR

#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \
  virtual klass##_Ptr copy(std::string, size_t) const = 0; \
  virtual klass##_Ptr clone(std::string, size_t) const = 0; \

#define ATTACH_AST_OPERATIONS(klass) \
  virtual klass##_Ptr copy(std::string, size_t) const; \
  virtual klass##_Ptr clone(std::string, size_t) const; \

#else

#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \
  virtual klass##_Ptr copy() const = 0; \
  virtual klass##_Ptr clone() const = 0; \

#define ATTACH_AST_OPERATIONS(klass) \
  virtual klass##_Ptr copy() const; \
  virtual klass##_Ptr clone() const; \

#endif

#ifdef __clang__

/*
 * There are some overloads used here that trigger the clang overload
 * hiding warning. Specifically:
 *
 * Type type() which hides string type() from Expression
 *
 */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Woverloaded-virtual"

#endif

#include "util.hpp"
#include "units.hpp"
#include "context.hpp"
#include "position.hpp"
#include "constants.hpp"
#include "operation.hpp"
#include "position.hpp"
#include "inspect.hpp"
#include "source_map.hpp"
#include "environment.hpp"
#include "error_handling.hpp"
#include "ast_def_macros.hpp"
#include "ast_fwd_decl.hpp"
#include "source_map.hpp"

#include "sass.h"

namespace Sass {

  // easier to search with name
  const bool DELAYED = true;

  // ToDo: should this really be hardcoded
  // Note: most methods follow precision option
  const double NUMBER_EPSILON = 0.00000000000001;

  // ToDo: where does this fit best?
  // We don't share this with C-API?
  class Operand {
    public:
      Operand(Sass_OP operand, bool ws_before = false, bool ws_after = false)
      : operand(operand), ws_before(ws_before), ws_after(ws_after)
      { }
    public:
      enum Sass_OP operand;
      bool ws_before;
      bool ws_after;
  };

  //////////////////////////////////////////////////////////
  // `hash_combine` comes from boost (functional/hash):
  // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html
  // Boost Software License - Version 1.0
  // http://www.boost.org/users/license.html
  template <typename T>
  void hash_combine (std::size_t& seed, const T& val)
  {
    seed ^= std::hash<T>()(val) + 0x9e3779b9
             + (seed<<6) + (seed>>2);
  }
  //////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////
  // Abstract base class for all abstract syntax tree nodes.
  //////////////////////////////////////////////////////////
  class AST_Node : public SharedObj {
    ADD_PROPERTY(ParserState, pstate)
  public:
    AST_Node(ParserState pstate)
    : pstate_(pstate)
    { }
    AST_Node(const AST_Node* ptr)
    : pstate_(ptr->pstate_)
    { }

    // AST_Node(AST_Node& ptr) = delete;

    virtual ~AST_Node() = 0;
    virtual size_t hash() { return 0; }
    ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node);
    virtual std::string inspect() const { return to_string({ INSPECT, 5 }); }
    virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); }
    virtual const std::string to_string(Sass_Inspect_Options opt) const;
    virtual const std::string to_string() const;
    virtual void cloneChildren() {};
    // generic find function (not fully implemented yet)
    // ToDo: add specific implementions to all children
    virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); };
  public:
    void update_pstate(const ParserState& pstate);
  public:
    Offset off() { return pstate(); }
    Position pos() { return pstate(); }
    ATTACH_OPERATIONS()
  };
  inline AST_Node::~AST_Node() { }

  //////////////////////////////////////////////////////////////////////
  // define cast template now (need complete type)
  //////////////////////////////////////////////////////////////////////

  template<class T>
  T* Cast(AST_Node* ptr) {
    return ptr && typeid(T) == typeid(*ptr) ?
           static_cast<T*>(ptr) : NULL;
  };

  template<class T>
  const T* Cast(const AST_Node* ptr) {
    return ptr && typeid(T) == typeid(*ptr) ?
           static_cast<const T*>(ptr) : NULL;
  };

  //////////////////////////////////////////////////////////////////////
  // Abstract base class for expressions. This side of the AST hierarchy
  // represents elements in value contexts, which exist primarily to be
  // evaluated and returned.
  //////////////////////////////////////////////////////////////////////
  class Expression : public AST_Node {
  public:
    enum Concrete_Type {
      NONE,
      BOOLEAN,
      NUMBER,
      COLOR,
      STRING,
      LIST,
      MAP,
      SELECTOR,
      NULL_VAL,
      C_WARNING,
      C_ERROR,
      FUNCTION,
      VARIABLE,
      NUM_TYPES
    };
    enum Simple_Type {
      SIMPLE,
      ATTR_SEL,
      PSEUDO_SEL,
      WRAPPED_SEL,
    };
  private:
    // expressions in some contexts shouldn't be evaluated
    ADD_PROPERTY(bool, is_delayed)
    ADD_PROPERTY(bool, is_expanded)
    ADD_PROPERTY(bool, is_interpolant)
    ADD_PROPERTY(Concrete_Type, concrete_type)
  public:
    Expression(ParserState pstate,
               bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE)
    : AST_Node(pstate),
      is_delayed_(d),
      is_expanded_(e),
      is_interpolant_(i),
      concrete_type_(ct)
    { }
    Expression(const Expression* ptr)
    : AST_Node(ptr),
      is_delayed_(ptr->is_delayed_),
      is_expanded_(ptr->is_expanded_),
      is_interpolant_(ptr->is_interpolant_),
      concrete_type_(ptr->concrete_type_)
    { }
    virtual operator bool() { return true; }
    virtual ~Expression() { }
    virtual std::string type() const { return ""; /* TODO: raise an error? */ }
    virtual bool is_invisible() const { return false; }
    static std::string type_name() { return ""; }
    virtual bool is_false() { return false; }
    // virtual bool is_true() { return !is_false(); }
    virtual bool operator== (const Expression& rhs) const { return false; }
    virtual bool eq(const Expression& rhs) const { return *this == rhs; };
    virtual void set_delayed(bool delayed) { is_delayed(delayed); }
    virtual bool has_interpolant() const { return is_interpolant(); }
    virtual bool is_left_interpolant() const { return is_interpolant(); }
    virtual bool is_right_interpolant() const { return is_interpolant(); }
    virtual std::string inspect() const { return to_string({ INSPECT, 5 }); }
    virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); }
    ATTACH_VIRTUAL_AST_OPERATIONS(Expression);
    virtual size_t hash() { return 0; }
  };

  //////////////////////////////////////////////////////////////////////
  // Still just an expression, but with a to_string method
  //////////////////////////////////////////////////////////////////////
  class PreValue : public Expression {
  public:
    PreValue(ParserState pstate,
               bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE)
    : Expression(pstate, d, e, i, ct)
    { }
    PreValue(const PreValue* ptr)
    : Expression(ptr)
    { }
    ATTACH_VIRTUAL_AST_OPERATIONS(PreValue);
    virtual ~PreValue() { }
  };

  //////////////////////////////////////////////////////////////////////
  // base class for values that support operations
  //////////////////////////////////////////////////////////////////////
  class Value : public Expression {
  public:
    Value(ParserState pstate,
          bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE)
    : Expression(pstate, d, e, i, ct)
    { }
    Value(const Value* ptr)
    : Expression(ptr)
    { }
    ATTACH_VIRTUAL_AST_OPERATIONS(Value);
    virtual bool operator== (const Expression& rhs) const = 0;
  };
}

/////////////////////////////////////////////////////////////////////////////////////
// Hash method specializations for std::unordered_map to work with Sass::Expression
/////////////////////////////////////////////////////////////////////////////////////

namespace std {
  template<>
  struct hash<Sass::Expression_Obj>
  {
    size_t operator()(Sass::Expression_Obj s) const
    {
      return s->hash();
    }
  };
  template<>
  struct equal_to<Sass::Expression_Obj>
  {
    bool operator()( Sass::Expression_Obj lhs,  Sass::Expression_Obj rhs) const
    {
      return lhs->hash() == rhs->hash();
    }
  };
}

namespace Sass {

  /////////////////////////////////////////////////////////////////////////////
  // Mixin class for AST nodes that should behave like vectors. Uses the
  // "Template Method" design pattern to allow subclasses to adjust their flags
  // when certain objects are pushed.
  /////////////////////////////////////////////////////////////////////////////
  template <typename T>
  class Vectorized {
    std::vector<T> elements_;
  protected:
    size_t hash_;
    void reset_hash() { hash_ = 0; }
    virtual void adjust_after_pushing(T element) { }
  public:
    Vectorized(size_t s = 0) : elements_(std::vector<T>()), hash_(0)
    { elements_.reserve(s); }
    virtual ~Vectorized() = 0;
    size_t length() const   { return elements_.size(); }
    bool empty() const      { return elements_.empty(); }
    void clear()            { return elements_.clear(); }
    T last() const          { return elements_.back(); }
    T first() const         { return elements_.front(); }
    T& operator[](size_t i) { return elements_[i]; }
    virtual const T& at(size_t i) const { return elements_.at(i); }
    virtual T& at(size_t i) { return elements_.at(i); }
    const T& operator[](size_t i) const { return elements_[i]; }
    virtual void append(T element)
    {
      if (element) {
        reset_hash();
        elements_.push_back(element);
        adjust_after_pushing(element);
      }
    }
    virtual void concat(Vectorized* v)
    {
      for (size_t i = 0, L = v->length(); i < L; ++i) this->append((*v)[i]);
    }
    Vectorized& unshift(T element)
    {
      elements_.insert(elements_.begin(), element);
      return *this;
    }
    std::vector<T>& elements() { return elements_; }
    const std::vector<T>& elements() const { return elements_; }
    std::vector<T>& elements(std::vector<T>& e) { elements_ = e; return elements_; }

    virtual size_t hash()
    {
      if (hash_ == 0) {
        for (T& el : elements_) {
          hash_combine(hash_, el->hash());
        }
      }
      return hash_;
    }

    typename std::vector<T>::iterator end() { return elements_.end(); }
    typename std::vector<T>::iterator begin() { return elements_.begin(); }
    typename std::vector<T>::const_iterator end() const { return elements_.end(); }
    typename std::vector<T>::const_iterator begin() const { return elements_.begin(); }
    typename std::vector<T>::iterator erase(typename std::vector<T>::iterator el) { return elements_.erase(el); }
    typename std::vector<T>::const_iterator erase(typename std::vector<T>::const_iterator el) { return elements_.erase(el); }

  };
  template <typename T>
  inline Vectorized<T>::~Vectorized() { }

  /////////////////////////////////////////////////////////////////////////////
  // Mixin class for AST nodes that should behave like a hash table. Uses an
  // extra <std::vector> internally to maintain insertion order for interation.
  /////////////////////////////////////////////////////////////////////////////
  class Hashed {
  private:
    ExpressionMap elements_;
    std::vector<Expression_Obj> list_;
  protected:
    size_t hash_;
    Expression_Obj duplicate_key_;
    void reset_hash() { hash_ = 0; }
    void reset_duplicate_key() { duplicate_key_ = 0; }
    virtual void adjust_after_pushing(std::pair<Expression_Obj, Expression_Obj> p) { }
  public:
    Hashed(size_t s = 0)
    : elements_(ExpressionMap(s)),
      list_(std::vector<Expression_Obj>()),
      hash_(0), duplicate_key_(NULL)
    { elements_.reserve(s); list_.reserve(s); }
    virtual ~Hashed();
    size_t length() const                  { return list_.size(); }
    bool empty() const                     { return list_.empty(); }
    bool has(Expression_Obj k) const          { return elements_.count(k) == 1; }
    Expression_Obj at(Expression_Obj k) const;
    bool has_duplicate_key() const         { return duplicate_key_ != 0; }
    Expression_Obj get_duplicate_key() const  { return duplicate_key_; }
    const ExpressionMap elements() { return elements_; }
    Hashed& operator<<(std::pair<Expression_Obj, Expression_Obj> p)
    {
      reset_hash();

      if (!has(p.first)) list_.push_back(p.first);
      else if (!duplicate_key_) duplicate_key_ = p.first;

      elements_[p.first] = p.second;

      adjust_after_pushing(p);
      return *this;
    }
    Hashed& operator+=(Hashed* h)
    {
      if (length() == 0) {
        this->elements_ = h->elements_;
        this->list_ = h->list_;
        return *this;
      }

      for (auto key : h->keys()) {
        *this << std::make_pair(key, h->at(key));
      }

      reset_duplicate_key();
      return *this;
    }
    const ExpressionMap& pairs() const { return elements_; }
    const std::vector<Expression_Obj>& keys() const { return list_; }

//    std::unordered_map<Expression_Obj, Expression_Obj>::iterator end() { return elements_.end(); }
//    std::unordered_map<Expression_Obj, Expression_Obj>::iterator begin() { return elements_.begin(); }
//    std::unordered_map<Expression_Obj, Expression_Obj>::const_iterator end() const { return elements_.end(); }
//    std::unordered_map<Expression_Obj, Expression_Obj>::const_iterator begin() const { return elements_.begin(); }

  };
  inline Hashed::~Hashed() { }


  /////////////////////////////////////////////////////////////////////////
  // Abstract base class for statements. This side of the AST hierarchy
  // represents elements in expansion contexts, which exist primarily to be
  // rewritten and macro-expanded.
  /////////////////////////////////////////////////////////////////////////
  class Statement : public AST_Node {
  public:
    enum Statement_Type {
      NONE,
      RULESET,
      MEDIA,
      DIRECTIVE,
      SUPPORTS,
      ATROOT,
      BUBBLE,
      CONTENT,
      KEYFRAMERULE,
      DECLARATION,
      ASSIGNMENT,
      IMPORT_STUB,
      IMPORT,
      COMMENT,
      WARNING,
      RETURN,
      EXTEND,
      ERROR,
      DEBUGSTMT,
      WHILE,
      EACH,
      FOR,
      IF
    };
  private:
    ADD_PROPERTY(Statement_Type, statement_type)
    ADD_PROPERTY(size_t, tabs)
    ADD_PROPERTY(bool, group_end)
  public:
    Statement(ParserState pstate, Statement_Type st = NONE, size_t t = 0)
    : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false)
     { }
    Statement(const Statement* ptr)
    : AST_Node(ptr),
      statement_type_(ptr->statement_type_),
      tabs_(ptr->tabs_),
      group_end_(ptr->group_end_)
     { }
    virtual ~Statement() = 0;
    // needed for rearranging nested rulesets during CSS emission
    virtual bool   is_invisible() const { return false; }
    virtual bool   bubbles() { return false; }
    virtual bool has_content()
    {
      return statement_type_ == CONTENT;
    }
  };
  inline Statement::~Statement() { }

  ////////////////////////
  // Blocks of statements.
  ////////////////////////
  class Block : public Statement, public Vectorized<Statement_Obj> {
    ADD_PROPERTY(bool, is_root)
    // needed for properly formatted CSS emission
  protected:
    void adjust_after_pushing(Statement_Obj s)
    {
    }
  public:
    Block(ParserState pstate, size_t s = 0, bool r = false)
    : Statement(pstate),
      Vectorized<Statement_Obj>(s),
      is_root_(r)
    { }
    Block(const Block* ptr)
    : Statement(ptr),
      Vectorized<Statement_Obj>(*ptr),
      is_root_(ptr->is_root_)
    { }
    virtual bool has_content()
    {
      for (size_t i = 0, L = elements().size(); i < L; ++i) {
        if (elements()[i]->has_content()) return true;
      }
      return Statement::has_content();
    }
    ATTACH_AST_OPERATIONS(Block)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////
  // Abstract base class for statements that contain blocks of statements.
  ////////////////////////////////////////////////////////////////////////
  class Has_Block : public Statement {
    ADD_PROPERTY(Block_Obj, block)
  public:
    Has_Block(ParserState pstate, Block_Obj b)
    : Statement(pstate), block_(b)
    { }
    Has_Block(const Has_Block* ptr)
    : Statement(ptr), block_(ptr->block_)
    { }
    virtual bool has_content()
    {
      return (block_ && block_->has_content()) || Statement::has_content();
    }
    virtual ~Has_Block() = 0;
  };
  inline Has_Block::~Has_Block() { }

  /////////////////////////////////////////////////////////////////////////////
  // Rulesets (i.e., sets of styles headed by a selector and containing a block
  // of style declarations.
  /////////////////////////////////////////////////////////////////////////////
  class Ruleset : public Has_Block {
    ADD_PROPERTY(Selector_List_Obj, selector)
    ADD_PROPERTY(bool, is_root);
  public:
    Ruleset(ParserState pstate, Selector_List_Obj s = 0, Block_Obj b = 0)
    : Has_Block(pstate, b), selector_(s), is_root_(false)
    { statement_type(RULESET); }
    Ruleset(const Ruleset* ptr)
    : Has_Block(ptr),
      selector_(ptr->selector_),
      is_root_(ptr->is_root_)
    { statement_type(RULESET); }
    bool is_invisible() const;
    ATTACH_AST_OPERATIONS(Ruleset)
    ATTACH_OPERATIONS()
  };

  /////////////////
  // Bubble.
  /////////////////
  class Bubble : public Statement {
    ADD_PROPERTY(Statement_Obj, node)
    ADD_PROPERTY(bool, group_end)
  public:
    Bubble(ParserState pstate, Statement_Obj n, Statement_Obj g = 0, size_t t = 0)
    : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == 0)
    { }
    Bubble(const Bubble* ptr)
    : Statement(ptr),
      node_(ptr->node_),
      group_end_(ptr->group_end_)
    { }
    bool bubbles() { return true; }
    ATTACH_AST_OPERATIONS(Bubble)
    ATTACH_OPERATIONS()
  };

  /////////////////
  // Trace.
  /////////////////
  class Trace : public Has_Block {
    ADD_CONSTREF(std::string, name)
  public:
    Trace(ParserState pstate, std::string n, Block_Obj b = 0)
    : Has_Block(pstate, b), name_(n)
    { }
    Trace(const Trace* ptr)
    : Has_Block(ptr),
      name_(ptr->name_)
    { }
    ATTACH_AST_OPERATIONS(Trace)
    ATTACH_OPERATIONS()
  };

  /////////////////
  // Media queries.
  /////////////////
  class Media_Block : public Has_Block {
    ADD_PROPERTY(List_Obj, media_queries)
  public:
    Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b)
    : Has_Block(pstate, b), media_queries_(mqs)
    { statement_type(MEDIA); }
    Media_Block(const Media_Block* ptr)
    : Has_Block(ptr), media_queries_(ptr->media_queries_)
    { statement_type(MEDIA); }
    bool bubbles() { return true; }
    bool is_invisible() const;
    ATTACH_AST_OPERATIONS(Media_Block)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////////////////////////////
  // At-rules -- arbitrary directives beginning with "@" that may have an
  // optional statement block.
  ///////////////////////////////////////////////////////////////////////
  class Directive : public Has_Block {
    ADD_CONSTREF(std::string, keyword)
    ADD_PROPERTY(Selector_List_Obj, selector)
    ADD_PROPERTY(Expression_Obj, value)
  public:
    Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0)
    : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed
    { statement_type(DIRECTIVE); }
    Directive(const Directive* ptr)
    : Has_Block(ptr),
      keyword_(ptr->keyword_),
      selector_(ptr->selector_),
      value_(ptr->value_) // set value manually if needed
    { statement_type(DIRECTIVE); }
    bool bubbles() { return is_keyframes() || is_media(); }
    bool is_media() {
      return keyword_.compare("@-webkit-media") == 0 ||
             keyword_.compare("@-moz-media") == 0 ||
             keyword_.compare("@-o-media") == 0 ||
             keyword_.compare("@media") == 0;
    }
    bool is_keyframes() {
      return keyword_.compare("@-webkit-keyframes") == 0 ||
             keyword_.compare("@-moz-keyframes") == 0 ||
             keyword_.compare("@-o-keyframes") == 0 ||
             keyword_.compare("@keyframes") == 0;
    }
    ATTACH_AST_OPERATIONS(Directive)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////////////////////////////
  // Keyframe-rules -- the child blocks of "@keyframes" nodes.
  ///////////////////////////////////////////////////////////////////////
  class Keyframe_Rule : public Has_Block {
    // according to css spec, this should be <keyframes-name>
    // <keyframes-name> = <custom-ident> | <string>
    ADD_PROPERTY(Selector_List_Obj, name)
  public:
    Keyframe_Rule(ParserState pstate, Block_Obj b)
    : Has_Block(pstate, b), name_()
    { statement_type(KEYFRAMERULE); }
    Keyframe_Rule(const Keyframe_Rule* ptr)
    : Has_Block(ptr), name_(ptr->name_)
    { statement_type(KEYFRAMERULE); }
    ATTACH_AST_OPERATIONS(Keyframe_Rule)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////
  // Declarations -- style rules consisting of a property name and values.
  ////////////////////////////////////////////////////////////////////////
  class Declaration : public Has_Block {
    ADD_PROPERTY(String_Obj, property)
    ADD_PROPERTY(Expression_Obj, value)
    ADD_PROPERTY(bool, is_important)
    ADD_PROPERTY(bool, is_indented)
  public:
    Declaration(ParserState pstate,
                String_Obj prop, Expression_Obj val, bool i = false, Block_Obj b = 0)
    : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_indented_(false)
    { statement_type(DECLARATION); }
    Declaration(const Declaration* ptr)
    : Has_Block(ptr),
      property_(ptr->property_),
      value_(ptr->value_),
      is_important_(ptr->is_important_),
      is_indented_(ptr->is_indented_)
    { statement_type(DECLARATION); }
    ATTACH_AST_OPERATIONS(Declaration)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////
  // Assignments -- variable and value.
  /////////////////////////////////////
  class Assignment : public Statement {
    ADD_CONSTREF(std::string, variable)
    ADD_PROPERTY(Expression_Obj, value)
    ADD_PROPERTY(bool, is_default)
    ADD_PROPERTY(bool, is_global)
  public:
    Assignment(ParserState pstate,
               std::string var, Expression_Obj val,
               bool is_default = false,
               bool is_global = false)
    : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global)
    { statement_type(ASSIGNMENT); }
    Assignment(const Assignment* ptr)
    : Statement(ptr),
      variable_(ptr->variable_),
      value_(ptr->value_),
      is_default_(ptr->is_default_),
      is_global_(ptr->is_global_)
    { statement_type(ASSIGNMENT); }
    ATTACH_AST_OPERATIONS(Assignment)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////////
  // Import directives. CSS and Sass import lists can be intermingled, so it's
  // necessary to store a list of each in an Import node.
  ////////////////////////////////////////////////////////////////////////////
  class Import : public Statement {
    std::vector<Expression_Obj> urls_;
    std::vector<Include>     incs_;
    ADD_PROPERTY(List_Obj,      import_queries);
  public:
    Import(ParserState pstate)
    : Statement(pstate),
      urls_(std::vector<Expression_Obj>()),
      incs_(std::vector<Include>()),
      import_queries_()
    { statement_type(IMPORT); }
    Import(const Import* ptr)
    : Statement(ptr),
      urls_(ptr->urls_),
      incs_(ptr->incs_),
      import_queries_(ptr->import_queries_)
    { statement_type(IMPORT); }
    std::vector<Expression_Obj>& urls() { return urls_; }
    std::vector<Include>& incs() { return incs_; }
    ATTACH_AST_OPERATIONS(Import)
    ATTACH_OPERATIONS()
  };

  // not yet resolved single import
  // so far we only know requested name
  class Import_Stub : public Statement {
    Include resource_;
  public:
    std::string abs_path() { return resource_.abs_path; };
    std::string imp_path() { return resource_.imp_path; };
    Include resource() { return resource_; };

    Import_Stub(ParserState pstate, Include res)
    : Statement(pstate), resource_(res)
    { statement_type(IMPORT_STUB); }
    Import_Stub(const Import_Stub* ptr)
    : Statement(ptr), resource_(ptr->resource_)
    { statement_type(IMPORT_STUB); }
    ATTACH_AST_OPERATIONS(Import_Stub)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////
  // The Sass `@warn` directive.
  //////////////////////////////
  class Warning : public Statement {
    ADD_PROPERTY(Expression_Obj, message)
  public:
    Warning(ParserState pstate, Expression_Obj msg)
    : Statement(pstate), message_(msg)
    { statement_type(WARNING); }
    Warning(const Warning* ptr)
    : Statement(ptr), message_(ptr->message_)
    { statement_type(WARNING); }
    ATTACH_AST_OPERATIONS(Warning)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////
  // The Sass `@error` directive.
  ///////////////////////////////
  class Error : public Statement {
    ADD_PROPERTY(Expression_Obj, message)
  public:
    Error(ParserState pstate, Expression_Obj msg)
    : Statement(pstate), message_(msg)
    { statement_type(ERROR); }
    Error(const Error* ptr)
    : Statement(ptr), message_(ptr->message_)
    { statement_type(ERROR); }
    ATTACH_AST_OPERATIONS(Error)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////
  // The Sass `@debug` directive.
  ///////////////////////////////
  class Debug : public Statement {
    ADD_PROPERTY(Expression_Obj, value)
  public:
    Debug(ParserState pstate, Expression_Obj val)
    : Statement(pstate), value_(val)
    { statement_type(DEBUGSTMT); }
    Debug(const Debug* ptr)
    : Statement(ptr), value_(ptr->value_)
    { statement_type(DEBUGSTMT); }
    ATTACH_AST_OPERATIONS(Debug)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////
  // CSS comments. These may be interpolated.
  ///////////////////////////////////////////
  class Comment : public Statement {
    ADD_PROPERTY(String_Obj, text)
    ADD_PROPERTY(bool, is_important)
  public:
    Comment(ParserState pstate, String_Obj txt, bool is_important)
    : Statement(pstate), text_(txt), is_important_(is_important)
    { statement_type(COMMENT); }
    Comment(const Comment* ptr)
    : Statement(ptr),
      text_(ptr->text_),
      is_important_(ptr->is_important_)
    { statement_type(COMMENT); }
    virtual bool is_invisible() const
    { return /* is_important() == */ false; }
    ATTACH_AST_OPERATIONS(Comment)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////
  // The Sass `@if` control directive.
  ////////////////////////////////////
  class If : public Has_Block {
    ADD_PROPERTY(Expression_Obj, predicate)
    ADD_PROPERTY(Block_Obj, alternative)
  public:
    If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0)
    : Has_Block(pstate, con), predicate_(pred), alternative_(alt)
    { statement_type(IF); }
    If(const If* ptr)
    : Has_Block(ptr),
      predicate_(ptr->predicate_),
      alternative_(ptr->alternative_)
    { statement_type(IF); }
    virtual bool has_content()
    {
      return Has_Block::has_content() || (alternative_ && alternative_->has_content());
    }
    ATTACH_AST_OPERATIONS(If)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////
  // The Sass `@for` control directive.
  /////////////////////////////////////
  class For : public Has_Block {
    ADD_CONSTREF(std::string, variable)
    ADD_PROPERTY(Expression_Obj, lower_bound)
    ADD_PROPERTY(Expression_Obj, upper_bound)
    ADD_PROPERTY(bool, is_inclusive)
  public:
    For(ParserState pstate,
        std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc)
    : Has_Block(pstate, b),
      variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc)
    { statement_type(FOR); }
    For(const For* ptr)
    : Has_Block(ptr),
      variable_(ptr->variable_),
      lower_bound_(ptr->lower_bound_),
      upper_bound_(ptr->upper_bound_),
      is_inclusive_(ptr->is_inclusive_)
    { statement_type(FOR); }
    ATTACH_AST_OPERATIONS(For)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////////////
  // The Sass `@each` control directive.
  //////////////////////////////////////
  class Each : public Has_Block {
    ADD_PROPERTY(std::vector<std::string>, variables)
    ADD_PROPERTY(Expression_Obj, list)
  public:
    Each(ParserState pstate, std::vector<std::string> vars, Expression_Obj lst, Block_Obj b)
    : Has_Block(pstate, b), variables_(vars), list_(lst)
    { statement_type(EACH); }
    Each(const Each* ptr)
    : Has_Block(ptr), variables_(ptr->variables_), list_(ptr->list_)
    { statement_type(EACH); }
    ATTACH_AST_OPERATIONS(Each)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////
  // The Sass `@while` control directive.
  ///////////////////////////////////////
  class While : public Has_Block {
    ADD_PROPERTY(Expression_Obj, predicate)
  public:
    While(ParserState pstate, Expression_Obj pred, Block_Obj b)
    : Has_Block(pstate, b), predicate_(pred)
    { statement_type(WHILE); }
    While(const While* ptr)
    : Has_Block(ptr), predicate_(ptr->predicate_)
    { statement_type(WHILE); }
    ATTACH_AST_OPERATIONS(While)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////////////////
  // The @return directive for use inside SassScript functions.
  /////////////////////////////////////////////////////////////
  class Return : public Statement {
    ADD_PROPERTY(Expression_Obj, value)
  public:
    Return(ParserState pstate, Expression_Obj val)
    : Statement(pstate), value_(val)
    { statement_type(RETURN); }
    Return(const Return* ptr)
    : Statement(ptr), value_(ptr->value_)
    { statement_type(RETURN); }
    ATTACH_AST_OPERATIONS(Return)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////
  // The Sass `@extend` directive.
  ////////////////////////////////
  class Extension : public Statement {
    ADD_PROPERTY(Selector_List_Obj, selector)
  public:
    Extension(ParserState pstate, Selector_List_Obj s)
    : Statement(pstate), selector_(s)
    { statement_type(EXTEND); }
    Extension(const Extension* ptr)
    : Statement(ptr), selector_(ptr->selector_)
    { statement_type(EXTEND); }
    ATTACH_AST_OPERATIONS(Extension)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////////////////////////////////
  // Definitions for both mixins and functions. The two cases are distinguished
  // by a type tag.
  /////////////////////////////////////////////////////////////////////////////
  struct Backtrace;
  typedef const char* Signature;
  typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector<Selector_List_Obj>);
  class Definition : public Has_Block {
  public:
    enum Type { MIXIN, FUNCTION };
    ADD_CONSTREF(std::string, name)
    ADD_PROPERTY(Parameters_Obj, parameters)
    ADD_PROPERTY(Env*, environment)
    ADD_PROPERTY(Type, type)
    ADD_PROPERTY(Native_Function, native_function)
    ADD_PROPERTY(Sass_Function_Entry, c_function)
    ADD_PROPERTY(void*, cookie)
    ADD_PROPERTY(bool, is_overload_stub)
    ADD_PROPERTY(Signature, signature)
  public:
    Definition(const Definition* ptr)
    : Has_Block(ptr),
      name_(ptr->name_),
      parameters_(ptr->parameters_),
      environment_(ptr->environment_),
      type_(ptr->type_),
      native_function_(ptr->native_function_),
      c_function_(ptr->c_function_),
      cookie_(ptr->cookie_),
      is_overload_stub_(ptr->is_overload_stub_),
      signature_(ptr->signature_)
    { }

    Definition(ParserState pstate,
               std::string n,
               Parameters_Obj params,
               Block_Obj b,
               Type t)
    : Has_Block(pstate, b),
      name_(n),
      parameters_(params),
      environment_(0),
      type_(t),
      native_function_(0),
      c_function_(0),
      cookie_(0),
      is_overload_stub_(false),
      signature_(0)
    { }
    Definition(ParserState pstate,
               Signature sig,
               std::string n,
               Parameters_Obj params,
               Native_Function func_ptr,
               bool overload_stub = false)
    : Has_Block(pstate, 0),
      name_(n),
      parameters_(params),
      environment_(0),
      type_(FUNCTION),
      native_function_(func_ptr),
      c_function_(0),
      cookie_(0),
      is_overload_stub_(overload_stub),
      signature_(sig)
    { }
    Definition(ParserState pstate,
               Signature sig,
               std::string n,
               Parameters_Obj params,
               Sass_Function_Entry c_func,
               bool whatever,
               bool whatever2)
    : Has_Block(pstate, 0),
      name_(n),
      parameters_(params),
      environment_(0),
      type_(FUNCTION),
      native_function_(0),
      c_function_(c_func),
      cookie_(sass_function_get_cookie(c_func)),
      is_overload_stub_(false),
      signature_(sig)
    { }
    ATTACH_AST_OPERATIONS(Definition)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////////////
  // Mixin calls (i.e., `@include ...`).
  //////////////////////////////////////
  class Mixin_Call : public Has_Block {
    ADD_CONSTREF(std::string, name)
    ADD_PROPERTY(Arguments_Obj, arguments)
  public:
    Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0)
    : Has_Block(pstate, b), name_(n), arguments_(args)
    { }
    Mixin_Call(const Mixin_Call* ptr)
    : Has_Block(ptr),
      name_(ptr->name_),
      arguments_(ptr->arguments_)
    { }
    ATTACH_AST_OPERATIONS(Mixin_Call)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////////
  // The @content directive for mixin content blocks.
  ///////////////////////////////////////////////////
  class Content : public Statement {
    ADD_PROPERTY(Media_Block_Ptr, media_block)
  public:
    Content(ParserState pstate)
    : Statement(pstate),
      media_block_(NULL)
    { statement_type(CONTENT); }
    Content(const Content* ptr)
    : Statement(ptr),
      media_block_(ptr->media_block_)
    { statement_type(CONTENT); }
    ATTACH_AST_OPERATIONS(Content)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////////////////////////////
  // Lists of values, both comma- and space-separated (distinguished by a
  // type-tag.) Also used to represent variable-length argument lists.
  ///////////////////////////////////////////////////////////////////////
  class List : public Value, public Vectorized<Expression_Obj> {
    void adjust_after_pushing(Expression_Obj e) { is_expanded(false); }
  private:
    ADD_PROPERTY(enum Sass_Separator, separator)
    ADD_PROPERTY(bool, is_arglist)
    ADD_PROPERTY(bool, from_selector)
  public:
    List(ParserState pstate,
         size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false)
    : Value(pstate),
      Vectorized<Expression_Obj>(size),
      separator_(sep),
      is_arglist_(argl),
      from_selector_(false)
    { concrete_type(LIST); }
    List(const List* ptr)
    : Value(ptr),
      Vectorized<Expression_Obj>(*ptr),
      separator_(ptr->separator_),
      is_arglist_(ptr->is_arglist_),
      from_selector_(ptr->from_selector_)
    { concrete_type(LIST); }
    std::string type() const { return is_arglist_ ? "arglist" : "list"; }
    static std::string type_name() { return "list"; }
    const char* sep_string(bool compressed = false) const {
      return separator() == SASS_SPACE ?
        " " : (compressed ? "," : ", ");
    }
    bool is_invisible() const { return empty(); }
    Expression_Obj value_at_index(size_t i);

    virtual size_t size() const;

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<std::string>()(sep_string());
        for (size_t i = 0, L = length(); i < L; ++i)
          hash_combine(hash_, (elements()[i])->hash());
      }
      return hash_;
    }

    virtual void set_delayed(bool delayed)
    {
      is_delayed(delayed);
      // don't set children
    }

    virtual bool operator== (const Expression& rhs) const;

    ATTACH_AST_OPERATIONS(List)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////////////////////////////
  // Key value paris.
  ///////////////////////////////////////////////////////////////////////
  class Map : public Value, public Hashed {
    void adjust_after_pushing(std::pair<Expression_Obj, Expression_Obj> p) { is_expanded(false); }
  public:
    Map(ParserState pstate,
         size_t size = 0)
    : Value(pstate),
      Hashed(size)
    { concrete_type(MAP); }
    Map(const Map* ptr)
    : Value(ptr),
      Hashed(*ptr)
    { concrete_type(MAP); }
    std::string type() const { return "map"; }
    static std::string type_name() { return "map"; }
    bool is_invisible() const { return empty(); }
    List_Obj to_list(ParserState& pstate);

    virtual size_t hash()
    {
      if (hash_ == 0) {
        for (auto key : keys()) {
          hash_combine(hash_, key->hash());
          hash_combine(hash_, at(key)->hash());
        }
      }

      return hash_;
    }

    virtual bool operator== (const Expression& rhs) const;

    ATTACH_AST_OPERATIONS(Map)
    ATTACH_OPERATIONS()
  };

  inline static const std::string sass_op_to_name(enum Sass_OP op) {
    switch (op) {
      case AND: return "and";
      case OR: return "or";
      case EQ: return "eq";
      case NEQ: return "neq";
      case GT: return "gt";
      case GTE: return "gte";
      case LT: return "lt";
      case LTE: return "lte";
      case ADD: return "plus";
      case SUB: return "sub";
      case MUL: return "times";
      case DIV: return "div";
      case MOD: return "mod";
      // this is only used internally!
      case NUM_OPS: return "[OPS]";
      default: return "invalid";
    }
  }

  //////////////////////////////////////////////////////////////////////////
  // Binary expressions. Represents logical, relational, and arithmetic
  // operations. Templatized to avoid large switch statements and repetitive
  // subclassing.
  //////////////////////////////////////////////////////////////////////////
  class Binary_Expression : public PreValue {
  private:
    HASH_PROPERTY(Operand, op)
    HASH_PROPERTY(Expression_Obj, left)
    HASH_PROPERTY(Expression_Obj, right)
    size_t hash_;
  public:
    Binary_Expression(ParserState pstate,
                      Operand op, Expression_Obj lhs, Expression_Obj rhs)
    : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0)
    { }
    Binary_Expression(const Binary_Expression* ptr)
    : PreValue(ptr),
      op_(ptr->op_),
      left_(ptr->left_),
      right_(ptr->right_),
      hash_(ptr->hash_)
    { }
    const std::string type_name() {
      switch (optype()) {
        case AND: return "and";
        case OR: return "or";
        case EQ: return "eq";
        case NEQ: return "neq";
        case GT: return "gt";
        case GTE: return "gte";
        case LT: return "lt";
        case LTE: return "lte";
        case ADD: return "add";
        case SUB: return "sub";
        case MUL: return "mul";
        case DIV: return "div";
        case MOD: return "mod";
        // this is only used internally!
        case NUM_OPS: return "[OPS]";
        default: return "invalid";
      }
    }
    const std::string separator() {
      switch (optype()) {
        case AND: return "&&";
        case OR: return "||";
        case EQ: return "==";
        case NEQ: return "!=";
        case GT: return ">";
        case GTE: return ">=";
        case LT: return "<";
        case LTE: return "<=";
        case ADD: return "+";
        case SUB: return "-";
        case MUL: return "*";
        case DIV: return "/";
        case MOD: return "%";
        // this is only used internally!
        case NUM_OPS: return "[OPS]";
        default: return "invalid";
      }
    }
    bool is_left_interpolant(void) const;
    bool is_right_interpolant(void) const;
    bool has_interpolant() const
    {
      return is_left_interpolant() ||
             is_right_interpolant();
    }
    virtual void set_delayed(bool delayed)
    {
      right()->set_delayed(delayed);
      left()->set_delayed(delayed);
      is_delayed(delayed);
    }
    virtual bool operator==(const Expression& rhs) const
    {
      try
      {
        Binary_Expression_Ptr_Const m = Cast<Binary_Expression>(&rhs);
        if (m == 0) return false;
        return type() == m->type() &&
               *left() == *m->left() &&
               *right() == *m->right();
      }
      catch (std::bad_cast&)
      {
        return false;
      }
      catch (...) { throw; }
    }
    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<size_t>()(optype());
        hash_combine(hash_, left()->hash());
        hash_combine(hash_, right()->hash());
      }
      return hash_;
    }
    enum Sass_OP optype() const { return op_.operand; }
    ATTACH_AST_OPERATIONS(Binary_Expression)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////////
  // Arithmetic negation (logical negation is just an ordinary function call).
  ////////////////////////////////////////////////////////////////////////////
  class Unary_Expression : public Expression {
  public:
    enum Type { PLUS, MINUS, NOT, SLASH };
  private:
    HASH_PROPERTY(Type, optype)
    HASH_PROPERTY(Expression_Obj, operand)
    size_t hash_;
  public:
    Unary_Expression(ParserState pstate, Type t, Expression_Obj o)
    : Expression(pstate), optype_(t), operand_(o), hash_(0)
    { }
    Unary_Expression(const Unary_Expression* ptr)
    : Expression(ptr),
      optype_(ptr->optype_),
      operand_(ptr->operand_),
      hash_(ptr->hash_)
    { }
    const std::string type_name() {
      switch (optype_) {
        case PLUS: return "plus";
        case MINUS: return "minus";
        case SLASH: return "slash";
        case NOT: return "not";
        default: return "invalid";
      }
    }
    virtual bool operator==(const Expression& rhs) const
    {
      try
      {
        Unary_Expression_Ptr_Const m = Cast<Unary_Expression>(&rhs);
        if (m == 0) return false;
        return type() == m->type() &&
               *operand() == *m->operand();
      }
      catch (std::bad_cast&)
      {
        return false;
      }
      catch (...) { throw; }
    }
    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<size_t>()(optype_);
        hash_combine(hash_, operand()->hash());
      };
      return hash_;
    }
    ATTACH_AST_OPERATIONS(Unary_Expression)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////
  // Individual argument objects for mixin and function calls.
  ////////////////////////////////////////////////////////////
  class Argument : public Expression {
    HASH_PROPERTY(Expression_Obj, value)
    HASH_CONSTREF(std::string, name)
    ADD_PROPERTY(bool, is_rest_argument)
    ADD_PROPERTY(bool, is_keyword_argument)
    size_t hash_;
  public:
    Argument(ParserState pstate, Expression_Obj val, std::string n = "", bool rest = false, bool keyword = false)
    : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0)
    {
      if (!name_.empty() && is_rest_argument_) {
        error("variable-length argument may not be passed by name", pstate_);
      }
    }
    Argument(const Argument* ptr)
    : Expression(ptr),
      value_(ptr->value_),
      name_(ptr->name_),
      is_rest_argument_(ptr->is_rest_argument_),
      is_keyword_argument_(ptr->is_keyword_argument_),
      hash_(ptr->hash_)
    {
      if (!name_.empty() && is_rest_argument_) {
        error("variable-length argument may not be passed by name", pstate_);
      }
    }

    virtual void set_delayed(bool delayed);
    virtual bool operator==(const Expression& rhs) const
    {
      try
      {
        Argument_Ptr_Const m = Cast<Argument>(&rhs);
        if (!(m && name() == m->name())) return false;
        return *value() == *m->value();
      }
      catch (std::bad_cast&)
      {
        return false;
      }
      catch (...) { throw; }
    }

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<std::string>()(name());
        hash_combine(hash_, value()->hash());
      }
      return hash_;
    }

    ATTACH_AST_OPERATIONS(Argument)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////
  // Argument lists -- in their own class to facilitate context-sensitive
  // error checking (e.g., ensuring that all ordinal arguments precede all
  // named arguments).
  ////////////////////////////////////////////////////////////////////////
  class Arguments : public Expression, public Vectorized<Argument_Obj> {
    ADD_PROPERTY(bool, has_named_arguments)
    ADD_PROPERTY(bool, has_rest_argument)
    ADD_PROPERTY(bool, has_keyword_argument)
  protected:
    void adjust_after_pushing(Argument_Obj a);
  public:
    Arguments(ParserState pstate)
    : Expression(pstate),
      Vectorized<Argument_Obj>(),
      has_named_arguments_(false),
      has_rest_argument_(false),
      has_keyword_argument_(false)
    { }
    Arguments(const Arguments* ptr)
    : Expression(ptr),
      Vectorized<Argument_Obj>(*ptr),
      has_named_arguments_(ptr->has_named_arguments_),
      has_rest_argument_(ptr->has_rest_argument_),
      has_keyword_argument_(ptr->has_keyword_argument_)
    { }

    virtual void set_delayed(bool delayed);

    Argument_Obj get_rest_argument();
    Argument_Obj get_keyword_argument();

    ATTACH_AST_OPERATIONS(Arguments)
    ATTACH_OPERATIONS()
  };

  //////////////////
  // Function calls.
  //////////////////
  class Function_Call : public PreValue {
    HASH_CONSTREF(std::string, name)
    HASH_PROPERTY(Arguments_Obj, arguments)
    ADD_PROPERTY(bool, via_call)
    ADD_PROPERTY(void*, cookie)
    size_t hash_;
  public:
    Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie)
    : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(cookie), hash_(0)
    { concrete_type(FUNCTION); }
    Function_Call(ParserState pstate, std::string n, Arguments_Obj args)
    : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0)
    { concrete_type(FUNCTION); }
    Function_Call(const Function_Call* ptr)
    : PreValue(ptr),
      name_(ptr->name_),
      arguments_(ptr->arguments_),
      via_call_(ptr->via_call_),
      cookie_(ptr->cookie_),
      hash_(ptr->hash_)
    { concrete_type(FUNCTION); }

    virtual bool operator==(const Expression& rhs) const
    {
      try
      {
        Function_Call_Ptr_Const m = Cast<Function_Call>(&rhs);
        if (!(m && name() == m->name())) return false;
        if (!(m && arguments()->length() == m->arguments()->length())) return false;
        for (size_t i =0, L = arguments()->length(); i < L; ++i)
          if (!(*(*arguments())[i] == *(*m->arguments())[i])) return false;
        return true;
      }
      catch (std::bad_cast&)
      {
        return false;
      }
      catch (...) { throw; }
    }

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<std::string>()(name());
        for (auto argument : arguments()->elements())
          hash_combine(hash_, argument->hash());
      }
      return hash_;
    }
    ATTACH_AST_OPERATIONS(Function_Call)
    ATTACH_OPERATIONS()
  };

  /////////////////////////
  // Function call schemas.
  /////////////////////////
  class Function_Call_Schema : public Expression {
    ADD_PROPERTY(String_Obj, name)
    ADD_PROPERTY(Arguments_Obj, arguments)
  public:
    Function_Call_Schema(ParserState pstate, String_Obj n, Arguments_Obj args)
    : Expression(pstate), name_(n), arguments_(args)
    { concrete_type(STRING); }
    Function_Call_Schema(const Function_Call_Schema* ptr)
    : Expression(ptr),
      name_(ptr->name_),
      arguments_(ptr->arguments_)
    { concrete_type(STRING); }
    ATTACH_AST_OPERATIONS(Function_Call_Schema)
    ATTACH_OPERATIONS()
  };

  ///////////////////////
  // Variable references.
  ///////////////////////
  class Variable : public PreValue {
    ADD_CONSTREF(std::string, name)
  public:
    Variable(ParserState pstate, std::string n)
    : PreValue(pstate), name_(n)
    { concrete_type(VARIABLE); }
    Variable(const Variable* ptr)
    : PreValue(ptr), name_(ptr->name_)
    { concrete_type(VARIABLE); }

    virtual bool operator==(const Expression& rhs) const
    {
      try
      {
        Variable_Ptr_Const e = Cast<Variable>(&rhs);
        return e && name() == e->name();
      }
      catch (std::bad_cast&)
      {
        return false;
      }
      catch (...) { throw; }
    }

    virtual size_t hash()
    {
      return std::hash<std::string>()(name());
    }

    ATTACH_AST_OPERATIONS(Variable)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////
  // Numbers, percentages, dimensions, and colors.
  ////////////////////////////////////////////////
  class Number : public Value {
    HASH_PROPERTY(double, value)
    ADD_PROPERTY(bool, zero)
    std::vector<std::string> numerator_units_;
    std::vector<std::string> denominator_units_;
    size_t hash_;
  public:
    Number(ParserState pstate, double val, std::string u = "", bool zero = true);

    Number(const Number* ptr)
    : Value(ptr),
      value_(ptr->value_), zero_(ptr->zero_),
      numerator_units_(ptr->numerator_units_),
      denominator_units_(ptr->denominator_units_),
      hash_(ptr->hash_)
    { concrete_type(NUMBER); }

    bool zero() { return zero_; }
    bool is_valid_css_unit() const;
    std::vector<std::string>& numerator_units()   { return numerator_units_; }
    std::vector<std::string>& denominator_units() { return denominator_units_; }
    const std::vector<std::string>& numerator_units() const   { return numerator_units_; }
    const std::vector<std::string>& denominator_units() const { return denominator_units_; }
    std::string type() const { return "number"; }
    static std::string type_name() { return "number"; }
    std::string unit() const;

    bool is_unitless() const;
    double convert_factor(const Number&) const;
    bool convert(const std::string& unit = "", bool strict = false);
    void normalize(const std::string& unit = "", bool strict = false);
    // useful for making one number compatible with another
    std::string find_convertible_unit() const;

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<double>()(value_);
        for (const auto numerator : numerator_units())
          hash_combine(hash_, std::hash<std::string>()(numerator));
        for (const auto denominator : denominator_units())
          hash_combine(hash_, std::hash<std::string>()(denominator));
      }
      return hash_;
    }

    virtual bool operator< (const Number& rhs) const;
    virtual bool operator== (const Expression& rhs) const;
    ATTACH_AST_OPERATIONS(Number)
    ATTACH_OPERATIONS()
  };

  //////////
  // Colors.
  //////////
  class Color : public Value {
    HASH_PROPERTY(double, r)
    HASH_PROPERTY(double, g)
    HASH_PROPERTY(double, b)
    HASH_PROPERTY(double, a)
    ADD_CONSTREF(std::string, disp)
    size_t hash_;
  public:
    Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "")
    : Value(pstate), r_(r), g_(g), b_(b), a_(a), disp_(disp),
      hash_(0)
    { concrete_type(COLOR); }
    Color(const Color* ptr)
    : Value(ptr),
      r_(ptr->r_),
      g_(ptr->g_),
      b_(ptr->b_),
      a_(ptr->a_),
      disp_(ptr->disp_),
      hash_(ptr->hash_)
    { concrete_type(COLOR); }
    std::string type() const { return "color"; }
    static std::string type_name() { return "color"; }

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<double>()(a_);
        hash_combine(hash_, std::hash<double>()(r_));
        hash_combine(hash_, std::hash<double>()(g_));
        hash_combine(hash_, std::hash<double>()(b_));
      }
      return hash_;
    }

    virtual bool operator== (const Expression& rhs) const;

    ATTACH_AST_OPERATIONS(Color)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////
  // Errors from Sass_Values.
  //////////////////////////////
  class Custom_Error : public Value {
    ADD_CONSTREF(std::string, message)
  public:
    Custom_Error(ParserState pstate, std::string msg)
    : Value(pstate), message_(msg)
    { concrete_type(C_ERROR); }
    Custom_Error(const Custom_Error* ptr)
    : Value(ptr), message_(ptr->message_)
    { concrete_type(C_ERROR); }
    virtual bool operator== (const Expression& rhs) const;
    ATTACH_AST_OPERATIONS(Custom_Error)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////
  // Warnings from Sass_Values.
  //////////////////////////////
  class Custom_Warning : public Value {
    ADD_CONSTREF(std::string, message)
  public:
    Custom_Warning(ParserState pstate, std::string msg)
    : Value(pstate), message_(msg)
    { concrete_type(C_WARNING); }
    Custom_Warning(const Custom_Warning* ptr)
    : Value(ptr), message_(ptr->message_)
    { concrete_type(C_WARNING); }
    virtual bool operator== (const Expression& rhs) const;
    ATTACH_AST_OPERATIONS(Custom_Warning)
    ATTACH_OPERATIONS()
  };

  ////////////
  // Booleans.
  ////////////
  class Boolean : public Value {
    HASH_PROPERTY(bool, value)
    size_t hash_;
  public:
    Boolean(ParserState pstate, bool val)
    : Value(pstate), value_(val),
      hash_(0)
    { concrete_type(BOOLEAN); }
    Boolean(const Boolean* ptr)
    : Value(ptr),
      value_(ptr->value_),
      hash_(ptr->hash_)
    { concrete_type(BOOLEAN); }
    virtual operator bool() { return value_; }
    std::string type() const { return "bool"; }
    static std::string type_name() { return "bool"; }
    virtual bool is_false() { return !value_; }

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<bool>()(value_);
      }
      return hash_;
    }

    virtual bool operator== (const Expression& rhs) const;

    ATTACH_AST_OPERATIONS(Boolean)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////
  // Abstract base class for Sass string values. Includes interpolated and
  // "flat" strings.
  ////////////////////////////////////////////////////////////////////////
  class String : public Value {
  public:
    String(ParserState pstate, bool delayed = false)
    : Value(pstate, delayed)
    { concrete_type(STRING); }
    String(const String* ptr)
    : Value(ptr)
    { concrete_type(STRING); }
    static std::string type_name() { return "string"; }
    virtual ~String() = 0;
    virtual void rtrim() = 0;
    virtual bool operator==(const Expression& rhs) const = 0;
    virtual bool operator<(const Expression& rhs) const {
      return this->to_string() < rhs.to_string();
    };
    ATTACH_VIRTUAL_AST_OPERATIONS(String);
    ATTACH_OPERATIONS()
  };
  inline String::~String() { };

  ///////////////////////////////////////////////////////////////////////
  // Interpolated strings. Meant to be reduced to flat strings during the
  // evaluation phase.
  ///////////////////////////////////////////////////////////////////////
  class String_Schema : public String, public Vectorized<Expression_Obj> {
    // ADD_PROPERTY(bool, has_interpolants)
    size_t hash_;
  public:
    String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false)
    : String(pstate), Vectorized<Expression_Obj>(size), hash_(0)
    { concrete_type(STRING); }
    String_Schema(const String_Schema* ptr)
    : String(ptr),
      Vectorized<Expression_Obj>(*ptr),
      hash_(ptr->hash_)
    { concrete_type(STRING); }

    std::string type() const { return "string"; }
    static std::string type_name() { return "string"; }

    bool is_left_interpolant(void) const;
    bool is_right_interpolant(void) const;
    // void has_interpolants(bool tc) { }
    bool has_interpolants() {
      for (auto el : elements()) {
        if (el->is_interpolant()) return true;
      }
      return false;
    }
    virtual void rtrim();

    virtual size_t hash()
    {
      if (hash_ == 0) {
        for (auto string : elements())
          hash_combine(hash_, string->hash());
      }
      return hash_;
    }

    virtual void set_delayed(bool delayed) {
      is_delayed(delayed);
    }

    virtual bool operator==(const Expression& rhs) const;
    ATTACH_AST_OPERATIONS(String_Schema)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////
  // Flat strings -- the lowest level of raw textual data.
  ////////////////////////////////////////////////////////
  class String_Constant : public String {
    ADD_PROPERTY(char, quote_mark)
    ADD_PROPERTY(bool, can_compress_whitespace)
    HASH_CONSTREF(std::string, value)
  protected:
    size_t hash_;
  public:
    String_Constant(const String_Constant* ptr)
    : String(ptr),
      quote_mark_(ptr->quote_mark_),
      can_compress_whitespace_(ptr->can_compress_whitespace_),
      value_(ptr->value_),
      hash_(ptr->hash_)
    { }
    String_Constant(ParserState pstate, std::string val)
    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val)), hash_(0)
    { }
    String_Constant(ParserState pstate, const char* beg)
    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg))), hash_(0)
    { }
    String_Constant(ParserState pstate, const char* beg, const char* end)
    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg))), hash_(0)
    { }
    String_Constant(ParserState pstate, const Token& tok)
    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end))), hash_(0)
    { }
    std::string type() const { return "string"; }
    static std::string type_name() { return "string"; }
    virtual bool is_invisible() const;
    virtual void rtrim();

    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_ = std::hash<std::string>()(value_);
      }
      return hash_;
    }

    virtual bool operator==(const Expression& rhs) const;
    virtual std::string inspect() const; // quotes are forced on inspection

    // static char auto_quote() { return '*'; }
    static char double_quote() { return '"'; }
    static char single_quote() { return '\''; }

    ATTACH_AST_OPERATIONS(String_Constant)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////
  // Possibly quoted string (unquote on instantiation)
  ////////////////////////////////////////////////////////
  class String_Quoted : public String_Constant {
  public:
    String_Quoted(ParserState pstate, std::string val, char q = 0,
      bool keep_utf8_escapes = false, bool skip_unquoting = false,
      bool strict_unquoting = true)
    : String_Constant(pstate, val)
    {
      if (skip_unquoting == false) {
        value_ = unquote(value_, &quote_mark_, keep_utf8_escapes, strict_unquoting);
      }
      if (q && quote_mark_) quote_mark_ = q;
    }
    String_Quoted(const String_Quoted* ptr)
    : String_Constant(ptr)
    { }
    virtual bool operator==(const Expression& rhs) const;
    virtual std::string inspect() const; // quotes are forced on inspection
    ATTACH_AST_OPERATIONS(String_Quoted)
    ATTACH_OPERATIONS()
  };

  /////////////////
  // Media queries.
  /////////////////
  class Media_Query : public Expression,
                      public Vectorized<Media_Query_Expression_Obj> {
    ADD_PROPERTY(String_Obj, media_type)
    ADD_PROPERTY(bool, is_negated)
    ADD_PROPERTY(bool, is_restricted)
  public:
    Media_Query(ParserState pstate,
                String_Obj t = 0, size_t s = 0, bool n = false, bool r = false)
    : Expression(pstate), Vectorized<Media_Query_Expression_Obj>(s),
      media_type_(t), is_negated_(n), is_restricted_(r)
    { }
    Media_Query(const Media_Query* ptr)
    : Expression(ptr),
      Vectorized<Media_Query_Expression_Obj>(*ptr),
      media_type_(ptr->media_type_),
      is_negated_(ptr->is_negated_),
      is_restricted_(ptr->is_restricted_)
    { }
    ATTACH_AST_OPERATIONS(Media_Query)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////
  // Media expressions (for use inside media queries).
  ////////////////////////////////////////////////////
  class Media_Query_Expression : public Expression {
    ADD_PROPERTY(Expression_Obj, feature)
    ADD_PROPERTY(Expression_Obj, value)
    ADD_PROPERTY(bool, is_interpolated)
  public:
    Media_Query_Expression(ParserState pstate,
                           Expression_Obj f, Expression_Obj v, bool i = false)
    : Expression(pstate), feature_(f), value_(v), is_interpolated_(i)
    { }
    Media_Query_Expression(const Media_Query_Expression* ptr)
    : Expression(ptr),
      feature_(ptr->feature_),
      value_(ptr->value_),
      is_interpolated_(ptr->is_interpolated_)
    { }
    ATTACH_AST_OPERATIONS(Media_Query_Expression)
    ATTACH_OPERATIONS()
  };

  ////////////////////
  // `@supports` rule.
  ////////////////////
  class Supports_Block : public Has_Block {
    ADD_PROPERTY(Supports_Condition_Obj, condition)
  public:
    Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block = 0)
    : Has_Block(pstate, block), condition_(condition)
    { statement_type(SUPPORTS); }
    Supports_Block(const Supports_Block* ptr)
    : Has_Block(ptr), condition_(ptr->condition_)
    { statement_type(SUPPORTS); }
    bool bubbles() { return true; }
    ATTACH_AST_OPERATIONS(Supports_Block)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////////////////////////////
  // The abstract superclass of all Supports conditions.
  //////////////////////////////////////////////////////
  class Supports_Condition : public Expression {
  public:
    Supports_Condition(ParserState pstate)
    : Expression(pstate)
    { }
    Supports_Condition(const Supports_Condition* ptr)
    : Expression(ptr)
    { }
    virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; }
    ATTACH_AST_OPERATIONS(Supports_Condition)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////
  // An operator condition (e.g. `CONDITION1 and CONDITION2`).
  ////////////////////////////////////////////////////////////
  class Supports_Operator : public Supports_Condition {
  public:
    enum Operand { AND, OR };
  private:
    ADD_PROPERTY(Supports_Condition_Obj, left);
    ADD_PROPERTY(Supports_Condition_Obj, right);
    ADD_PROPERTY(Operand, operand);
  public:
    Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o)
    : Supports_Condition(pstate), left_(l), right_(r), operand_(o)
    { }
    Supports_Operator(const Supports_Operator* ptr)
    : Supports_Condition(ptr),
      left_(ptr->left_),
      right_(ptr->right_),
      operand_(ptr->operand_)
    { }
    virtual bool needs_parens(Supports_Condition_Obj cond) const;
    ATTACH_AST_OPERATIONS(Supports_Operator)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////////////////
  // A negation condition (`not CONDITION`).
  //////////////////////////////////////////
  class Supports_Negation : public Supports_Condition {
  private:
    ADD_PROPERTY(Supports_Condition_Obj, condition);
  public:
    Supports_Negation(ParserState pstate, Supports_Condition_Obj c)
    : Supports_Condition(pstate), condition_(c)
    { }
    Supports_Negation(const Supports_Negation* ptr)
    : Supports_Condition(ptr), condition_(ptr->condition_)
    { }
    virtual bool needs_parens(Supports_Condition_Obj cond) const;
    ATTACH_AST_OPERATIONS(Supports_Negation)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////////
  // A declaration condition (e.g. `(feature: value)`).
  /////////////////////////////////////////////////////
  class Supports_Declaration : public Supports_Condition {
  private:
    ADD_PROPERTY(Expression_Obj, feature);
    ADD_PROPERTY(Expression_Obj, value);
  public:
    Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v)
    : Supports_Condition(pstate), feature_(f), value_(v)
    { }
    Supports_Declaration(const Supports_Declaration* ptr)
    : Supports_Condition(ptr),
      feature_(ptr->feature_),
      value_(ptr->value_)
    { }
    virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; }
    ATTACH_AST_OPERATIONS(Supports_Declaration)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////
  // An interpolation condition (e.g. `#{$var}`).
  ///////////////////////////////////////////////
  class Supports_Interpolation : public Supports_Condition {
  private:
    ADD_PROPERTY(Expression_Obj, value);
  public:
    Supports_Interpolation(ParserState pstate, Expression_Obj v)
    : Supports_Condition(pstate), value_(v)
    { }
    Supports_Interpolation(const Supports_Interpolation* ptr)
    : Supports_Condition(ptr),
      value_(ptr->value_)
    { }
    virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; }
    ATTACH_AST_OPERATIONS(Supports_Interpolation)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////
  // At root expressions (for use inside @at-root).
  /////////////////////////////////////////////////
  class At_Root_Query : public Expression {
  private:
    ADD_PROPERTY(Expression_Obj, feature)
    ADD_PROPERTY(Expression_Obj, value)
  public:
    At_Root_Query(ParserState pstate, Expression_Obj f = 0, Expression_Obj v = 0, bool i = false)
    : Expression(pstate), feature_(f), value_(v)
    { }
    At_Root_Query(const At_Root_Query* ptr)
    : Expression(ptr),
      feature_(ptr->feature_),
      value_(ptr->value_)
    { }
    bool exclude(std::string str);
    ATTACH_AST_OPERATIONS(At_Root_Query)
    ATTACH_OPERATIONS()
  };

  ///////////
  // At-root.
  ///////////
  class At_Root_Block : public Has_Block {
    ADD_PROPERTY(At_Root_Query_Obj, expression)
  public:
    At_Root_Block(ParserState pstate, Block_Obj b = 0, At_Root_Query_Obj e = 0)
    : Has_Block(pstate, b), expression_(e)
    { statement_type(ATROOT); }
    At_Root_Block(const At_Root_Block* ptr)
    : Has_Block(ptr), expression_(ptr->expression_)
    { statement_type(ATROOT); }
    bool bubbles() { return true; }
    bool exclude_node(Statement_Obj s) {
      if (expression() == 0)
      {
        return s->statement_type() == Statement::RULESET;
      }

      if (s->statement_type() == Statement::DIRECTIVE)
      {
        if (Directive_Obj dir = Cast<Directive>(s))
        {
          std::string keyword(dir->keyword());
          if (keyword.length() > 0) keyword.erase(0, 1);
          return expression()->exclude(keyword);
        }
      }
      if (s->statement_type() == Statement::MEDIA)
      {
        return expression()->exclude("media");
      }
      if (s->statement_type() == Statement::RULESET)
      {
        return expression()->exclude("rule");
      }
      if (s->statement_type() == Statement::SUPPORTS)
      {
        return expression()->exclude("supports");
      }
      if (Directive_Obj dir = Cast<Directive>(s))
      {
        if (dir->is_keyframes()) return expression()->exclude("keyframes");
      }
      return false;
    }
    ATTACH_AST_OPERATIONS(At_Root_Block)
    ATTACH_OPERATIONS()
  };

  //////////////////
  // The null value.
  //////////////////
  class Null : public Value {
  public:
    Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); }
    Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); }
    std::string type() const { return "null"; }
    static std::string type_name() { return "null"; }
    bool is_invisible() const { return true; }
    operator bool() { return false; }
    bool is_false() { return true; }

    virtual size_t hash()
    {
      return -1;
    }

    virtual bool operator== (const Expression& rhs) const;

    ATTACH_AST_OPERATIONS(Null)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////
  // Thunks for delayed evaluation.
  /////////////////////////////////
  class Thunk : public Expression {
    ADD_PROPERTY(Expression_Obj, expression)
    ADD_PROPERTY(Env*, environment)
  public:
    Thunk(ParserState pstate, Expression_Obj exp, Env* env = 0)
    : Expression(pstate), expression_(exp), environment_(env)
    { }
  };

  /////////////////////////////////////////////////////////
  // Individual parameter objects for mixins and functions.
  /////////////////////////////////////////////////////////
  class Parameter : public AST_Node {
    ADD_CONSTREF(std::string, name)
    ADD_PROPERTY(Expression_Obj, default_value)
    ADD_PROPERTY(bool, is_rest_parameter)
  public:
    Parameter(ParserState pstate,
              std::string n, Expression_Obj def = 0, bool rest = false)
    : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest)
    {
      // tried to come up with a spec test for this, but it does no longer
      // get  past the parser (it error out earlier). A spec test was added!
      // if (default_value_ && is_rest_parameter_) {
      //   error("variable-length parameter may not have a default value", pstate_);
      // }
    }
    Parameter(const Parameter* ptr)
    : AST_Node(ptr),
      name_(ptr->name_),
      default_value_(ptr->default_value_),
      is_rest_parameter_(ptr->is_rest_parameter_)
    {
      // tried to come up with a spec test for this, but it does no longer
      // get  past the parser (it error out earlier). A spec test was added!
      // if (default_value_ && is_rest_parameter_) {
      //   error("variable-length parameter may not have a default value", pstate_);
      // }
    }
    ATTACH_AST_OPERATIONS(Parameter)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////////////////////////////
  // Parameter lists -- in their own class to facilitate context-sensitive
  // error checking (e.g., ensuring that all optional parameters follow all
  // required parameters).
  /////////////////////////////////////////////////////////////////////////
  class Parameters : public AST_Node, public Vectorized<Parameter_Obj> {
    ADD_PROPERTY(bool, has_optional_parameters)
    ADD_PROPERTY(bool, has_rest_parameter)
  protected:
    void adjust_after_pushing(Parameter_Obj p)
    {
      if (p->default_value()) {
        if (has_rest_parameter()) {
          error("optional parameters may not be combined with variable-length parameters", p->pstate());
        }
        has_optional_parameters(true);
      }
      else if (p->is_rest_parameter()) {
        if (has_rest_parameter()) {
          error("functions and mixins cannot have more than one variable-length parameter", p->pstate());
        }
        has_rest_parameter(true);
      }
      else {
        if (has_rest_parameter()) {
          error("required parameters must precede variable-length parameters", p->pstate());
        }
        if (has_optional_parameters()) {
          error("required parameters must precede optional parameters", p->pstate());
        }
      }
    }
  public:
    Parameters(ParserState pstate)
    : AST_Node(pstate),
      Vectorized<Parameter_Obj>(),
      has_optional_parameters_(false),
      has_rest_parameter_(false)
    { }
    Parameters(const Parameters* ptr)
    : AST_Node(ptr),
      Vectorized<Parameter_Obj>(*ptr),
      has_optional_parameters_(ptr->has_optional_parameters_),
      has_rest_parameter_(ptr->has_rest_parameter_)
    { }
    ATTACH_AST_OPERATIONS(Parameters)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////
  // Abstract base class for CSS selectors.
  /////////////////////////////////////////
  class Selector : public Expression {
    // ADD_PROPERTY(bool, has_reference)
    // line break before list separator
    ADD_PROPERTY(bool, has_line_feed)
    // line break after list separator
    ADD_PROPERTY(bool, has_line_break)
    // maybe we have optional flag
    ADD_PROPERTY(bool, is_optional)
    // parent block pointers

    // must not be a reference counted object
    // otherwise we create circular references
    ADD_PROPERTY(Media_Block_Ptr, media_block)
  protected:
    size_t hash_;
  public:
    Selector(ParserState pstate)
    : Expression(pstate),
      has_line_feed_(false),
      has_line_break_(false),
      is_optional_(false),
      media_block_(0),
      hash_(0)
    { concrete_type(SELECTOR); }
    Selector(const Selector* ptr)
    : Expression(ptr),
      // has_reference_(ptr->has_reference_),
      has_line_feed_(ptr->has_line_feed_),
      has_line_break_(ptr->has_line_break_),
      is_optional_(ptr->is_optional_),
      media_block_(ptr->media_block_),
      hash_(ptr->hash_)
    { concrete_type(SELECTOR); }
    virtual ~Selector() = 0;
    virtual size_t hash() = 0;
    virtual unsigned long specificity() const = 0;
    virtual void set_media_block(Media_Block_Ptr mb) {
      media_block(mb);
    }
    virtual bool has_parent_ref() const {
      return false;
    }
    virtual bool has_real_parent_ref() const {
      return false;
    }
    // dispatch to correct handlers
    virtual bool operator<(const Selector& rhs) const = 0;
    virtual bool operator==(const Selector& rhs) const = 0;
    ATTACH_VIRTUAL_AST_OPERATIONS(Selector);
  };
  inline Selector::~Selector() { }

  /////////////////////////////////////////////////////////////////////////
  // Interpolated selectors -- the interpolated String will be expanded and
  // re-parsed into a normal selector class.
  /////////////////////////////////////////////////////////////////////////
  class Selector_Schema : public AST_Node {
    ADD_PROPERTY(String_Obj, contents)
    ADD_PROPERTY(bool, connect_parent);
    // must not be a reference counted object
    // otherwise we create circular references
    ADD_PROPERTY(Media_Block_Ptr, media_block)
    // store computed hash
    size_t hash_;
  public:
    Selector_Schema(ParserState pstate, String_Obj c)
    : AST_Node(pstate),
      contents_(c),
      connect_parent_(true),
      media_block_(NULL),
      hash_(0)
    { }
    Selector_Schema(const Selector_Schema* ptr)
    : AST_Node(ptr),
      contents_(ptr->contents_),
      connect_parent_(ptr->connect_parent_),
      media_block_(ptr->media_block_),
      hash_(ptr->hash_)
    { }
    virtual bool has_parent_ref() const;
    virtual bool has_real_parent_ref() const;
    virtual bool operator<(const Selector& rhs) const;
    virtual bool operator==(const Selector& rhs) const;
    // selector schema is not yet a final selector, so we do not
    // have a specificity for it yet. We need to
    virtual unsigned long specificity() const { return 0; }
    virtual size_t hash() {
      if (hash_ == 0) {
        hash_combine(hash_, contents_->hash());
      }
      return hash_;
    }
    ATTACH_AST_OPERATIONS(Selector_Schema)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////
  // Abstract base class for simple selectors.
  ////////////////////////////////////////////
  class Simple_Selector : public Selector {
    ADD_CONSTREF(std::string, ns)
    ADD_CONSTREF(std::string, name)
    ADD_PROPERTY(Simple_Type, simple_type)
    ADD_PROPERTY(bool, has_ns)
  public:
    Simple_Selector(ParserState pstate, std::string n = "")
    : Selector(pstate), ns_(""), name_(n), has_ns_(false)
    {
      simple_type(SIMPLE);
      size_t pos = n.find('|');
      // found some namespace
      if (pos != std::string::npos) {
        has_ns_ = true;
        ns_ = n.substr(0, pos);
        name_ = n.substr(pos + 1);
      }
    }
    Simple_Selector(const Simple_Selector* ptr)
    : Selector(ptr),
      ns_(ptr->ns_),
      name_(ptr->name_),
      has_ns_(ptr->has_ns_)
    { simple_type(SIMPLE); }
    virtual std::string ns_name() const
    {
      std::string name("");
      if (has_ns_)
        name += ns_ + "|";
      return name + name_;
    }
    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_combine(hash_, std::hash<int>()(SELECTOR));
        hash_combine(hash_, std::hash<std::string>()(ns()));
        hash_combine(hash_, std::hash<std::string>()(name()));
      }
      return hash_;
    }
    // namespace compare functions
    bool is_ns_eq(const Simple_Selector& r) const;
    // namespace query functions
    bool is_universal_ns() const
    {
      return has_ns_ && ns_ == "*";
    }
    bool has_universal_ns() const
    {
      return !has_ns_ || ns_ == "*";
    }
    bool is_empty_ns() const
    {
      return !has_ns_ || ns_ == "";
    }
    bool has_empty_ns() const
    {
      return has_ns_ && ns_ == "";
    }
    bool has_qualified_ns() const
    {
      return has_ns_ && ns_ != "" && ns_ != "*";
    }
    // name query functions
    bool is_universal() const
    {
      return name_ == "*";
    }

    virtual bool has_placeholder() {
      return false;
    }

    virtual ~Simple_Selector() = 0;
    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);
    virtual bool has_parent_ref() const { return false; };
    virtual bool has_real_parent_ref() const  { return false; };
    virtual bool is_pseudo_element() const { return false; }

    virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; }

    virtual bool operator==(const Selector& rhs) const;
    virtual bool operator==(const Simple_Selector& rhs) const;
    inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); }

    bool operator<(const Selector& rhs) const;
    bool operator<(const Simple_Selector& rhs) const;
    // default implementation should work for most of the simple selectors (otherwise overload)
    ATTACH_VIRTUAL_AST_OPERATIONS(Simple_Selector);
    ATTACH_OPERATIONS();
  };
  inline Simple_Selector::~Simple_Selector() { }


  //////////////////////////////////
  // The Parent Selector Expression.
  //////////////////////////////////
  // parent selectors can occur in selectors but also
  // inside strings in declarations (Compound_Selector).
  // only one simple parent selector means the first case.
  class Parent_Selector : public Simple_Selector {
    ADD_PROPERTY(bool, real)
  public:
    Parent_Selector(ParserState pstate, bool r = true)
    : Simple_Selector(pstate, "&"), real_(r)
    { /* has_reference(true); */ }
    Parent_Selector(const Parent_Selector* ptr)
    : Simple_Selector(ptr), real_(ptr->real_)
    { /* has_reference(true); */ }
    bool is_real_parent_ref() const { return real(); };
    virtual bool has_parent_ref() const { return true; };
    virtual bool has_real_parent_ref() const { return is_real_parent_ref(); };
    virtual unsigned long specificity() const
    {
      return 0;
    }
    std::string type() const { return "selector"; }
    static std::string type_name() { return "selector"; }
    ATTACH_AST_OPERATIONS(Parent_Selector)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////////////////////////////
  // Placeholder selectors (e.g., "%foo") for use in extend-only selectors.
  /////////////////////////////////////////////////////////////////////////
  class Placeholder_Selector : public Simple_Selector {
  public:
    Placeholder_Selector(ParserState pstate, std::string n)
    : Simple_Selector(pstate, n)
    { }
    Placeholder_Selector(const Placeholder_Selector* ptr)
    : Simple_Selector(ptr)
    { }
    virtual unsigned long specificity() const
    {
      return Constants::Specificity_Base;
    }
    virtual bool has_placeholder() {
      return true;
    }
    virtual ~Placeholder_Selector() {};
    ATTACH_AST_OPERATIONS(Placeholder_Selector)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////////////////////////
  // Element selectors (and the universal selector) -- e.g., div, span, *.
  /////////////////////////////////////////////////////////////////////
  class Element_Selector : public Simple_Selector {
  public:
    Element_Selector(ParserState pstate, std::string n)
    : Simple_Selector(pstate, n)
    { }
    Element_Selector(const Element_Selector* ptr)
    : Simple_Selector(ptr)
    { }
    virtual unsigned long specificity() const
    {
      if (name() == "*") return 0;
      else               return Constants::Specificity_Element;
    }
    virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr);
    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);
    virtual bool operator==(const Simple_Selector& rhs) const;
    virtual bool operator==(const Element_Selector& rhs) const;
    virtual bool operator<(const Simple_Selector& rhs) const;
    virtual bool operator<(const Element_Selector& rhs) const;
    ATTACH_AST_OPERATIONS(Element_Selector)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////
  // Class selectors  -- i.e., .foo.
  ////////////////////////////////////////////////
  class Class_Selector : public Simple_Selector {
  public:
    Class_Selector(ParserState pstate, std::string n)
    : Simple_Selector(pstate, n)
    { }
    Class_Selector(const Class_Selector* ptr)
    : Simple_Selector(ptr)
    { }
    virtual unsigned long specificity() const
    {
      return Constants::Specificity_Class;
    }
    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);
    ATTACH_AST_OPERATIONS(Class_Selector)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////
  // ID selectors -- i.e., #foo.
  ////////////////////////////////////////////////
  class Id_Selector : public Simple_Selector {
  public:
    Id_Selector(ParserState pstate, std::string n)
    : Simple_Selector(pstate, n)
    { }
    Id_Selector(const Id_Selector* ptr)
    : Simple_Selector(ptr)
    { }
    virtual unsigned long specificity() const
    {
      return Constants::Specificity_ID;
    }
    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);
    ATTACH_AST_OPERATIONS(Id_Selector)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////////////////////
  // Attribute selectors -- e.g., [src*=".jpg"], etc.
  ///////////////////////////////////////////////////
  class Attribute_Selector : public Simple_Selector {
    ADD_CONSTREF(std::string, matcher)
    // this cannot be changed to obj atm!!!!!!????!!!!!!!
    ADD_PROPERTY(String_Obj, value) // might be interpolated
  public:
    Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v)
    : Simple_Selector(pstate, n), matcher_(m), value_(v)
    { simple_type(ATTR_SEL); }
    Attribute_Selector(const Attribute_Selector* ptr)
    : Simple_Selector(ptr),
      matcher_(ptr->matcher_),
      value_(ptr->value_)
    { simple_type(ATTR_SEL); }
    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_combine(hash_, Simple_Selector::hash());
        hash_combine(hash_, std::hash<std::string>()(matcher()));
        if (value_) hash_combine(hash_, value_->hash());
      }
      return hash_;
    }
    virtual unsigned long specificity() const
    {
      return Constants::Specificity_Attr;
    }
    virtual bool operator==(const Simple_Selector& rhs) const;
    virtual bool operator==(const Attribute_Selector& rhs) const;
    virtual bool operator<(const Simple_Selector& rhs) const;
    virtual bool operator<(const Attribute_Selector& rhs) const;
    ATTACH_AST_OPERATIONS(Attribute_Selector)
    ATTACH_OPERATIONS()
  };

  //////////////////////////////////////////////////////////////////
  // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc.
  //////////////////////////////////////////////////////////////////
  /* '::' starts a pseudo-element, ':' a pseudo-class */
  /* Except :first-line, :first-letter, :before and :after */
  /* Note that pseudo-elements are restricted to one per selector */
  /* and occur only in the last simple_selector_sequence. */
  inline bool is_pseudo_class_element(const std::string& name)
  {
    return name == ":before"       ||
           name == ":after"        ||
           name == ":first-line"   ||
           name == ":first-letter";
  }

  // Pseudo Selector cannot have any namespace?
  class Pseudo_Selector : public Simple_Selector {
    ADD_PROPERTY(String_Obj, expression)
  public:
    Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr = 0)
    : Simple_Selector(pstate, n), expression_(expr)
    { simple_type(PSEUDO_SEL); }
    Pseudo_Selector(const Pseudo_Selector* ptr)
    : Simple_Selector(ptr), expression_(ptr->expression_)
    { simple_type(PSEUDO_SEL); }

    // A pseudo-element is made of two colons (::) followed by the name.
    // The `::` notation is introduced by the current document in order to
    // establish a discrimination between pseudo-classes and pseudo-elements.
    // For compatibility with existing style sheets, user agents must also
    // accept the previous one-colon notation for pseudo-elements introduced
    // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and
    // :after). This compatibility is not allowed for the new pseudo-elements
    // introduced in this specification.
    virtual bool is_pseudo_element() const
    {
      return (name_[0] == ':' && name_[1] == ':')
             || is_pseudo_class_element(name_);
    }
    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_combine(hash_, Simple_Selector::hash());
        if (expression_) hash_combine(hash_, expression_->hash());
      }
      return hash_;
    }
    virtual unsigned long specificity() const
    {
      if (is_pseudo_element())
        return Constants::Specificity_Element;
      return Constants::Specificity_Pseudo;
    }
    virtual bool operator==(const Simple_Selector& rhs) const;
    virtual bool operator==(const Pseudo_Selector& rhs) const;
    virtual bool operator<(const Simple_Selector& rhs) const;
    virtual bool operator<(const Pseudo_Selector& rhs) const;
    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);
    ATTACH_AST_OPERATIONS(Pseudo_Selector)
    ATTACH_OPERATIONS()
  };

  /////////////////////////////////////////////////
  // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir)
  /////////////////////////////////////////////////
  class Wrapped_Selector : public Simple_Selector {
    ADD_PROPERTY(Selector_List_Obj, selector)
  public:
    Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel)
    : Simple_Selector(pstate, n), selector_(sel)
    { simple_type(WRAPPED_SEL); }
    Wrapped_Selector(const Wrapped_Selector* ptr)
    : Simple_Selector(ptr), selector_(ptr->selector_)
    { simple_type(WRAPPED_SEL); }
    virtual bool is_superselector_of(Wrapped_Selector_Obj sub);
    // Selectors inside the negation pseudo-class are counted like any
    // other, but the negation itself does not count as a pseudo-class.
    virtual size_t hash();
    virtual bool has_parent_ref() const;
    virtual bool has_real_parent_ref() const;
    virtual unsigned long specificity() const;
    virtual bool find ( bool (*f)(AST_Node_Obj) );
    virtual bool operator==(const Simple_Selector& rhs) const;
    virtual bool operator==(const Wrapped_Selector& rhs) const;
    virtual bool operator<(const Simple_Selector& rhs) const;
    virtual bool operator<(const Wrapped_Selector& rhs) const;
    virtual void cloneChildren();
    ATTACH_AST_OPERATIONS(Wrapped_Selector)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////////
  // Simple selector sequences. Maintains flags indicating whether it contains
  // any parent references or placeholders, to simplify expansion.
  ////////////////////////////////////////////////////////////////////////////
  class Compound_Selector : public Selector, public Vectorized<Simple_Selector_Obj> {
  private:
    ComplexSelectorSet sources_;
    ADD_PROPERTY(bool, extended);
    ADD_PROPERTY(bool, has_parent_reference);
  protected:
    void adjust_after_pushing(Simple_Selector_Obj s)
    {
      // if (s->has_reference())   has_reference(true);
      // if (s->has_placeholder()) has_placeholder(true);
    }
  public:
    Compound_Selector(ParserState pstate, size_t s = 0)
    : Selector(pstate),
      Vectorized<Simple_Selector_Obj>(s),
      extended_(false),
      has_parent_reference_(false)
    { }
    Compound_Selector(const Compound_Selector* ptr)
    : Selector(ptr),
      Vectorized<Simple_Selector_Obj>(*ptr),
      extended_(ptr->extended_),
      has_parent_reference_(ptr->has_parent_reference_)
    { }
    bool contains_placeholder() {
      for (size_t i = 0, L = length(); i < L; ++i) {
        if ((*this)[i]->has_placeholder()) return true;
      }
      return false;
    };

    void append(Simple_Selector_Ptr element);

    bool is_universal() const
    {
      return length() == 1 && (*this)[0]->is_universal();
    }

    Complex_Selector_Obj to_complex();
    Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs);
    // virtual Placeholder_Selector_Ptr find_placeholder();
    virtual bool has_parent_ref() const;
    virtual bool has_real_parent_ref() const;
    Simple_Selector_Ptr base() const {
      if (length() == 0) return 0;
      // ToDo: why is this needed?
      if (Cast<Element_Selector>((*this)[0]))
        return (*this)[0];
      return 0;
    }
    virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = "");
    virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapped = "");
    virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapped = "");
    virtual size_t hash()
    {
      if (Selector::hash_ == 0) {
        hash_combine(Selector::hash_, std::hash<int>()(SELECTOR));
        if (length()) hash_combine(Selector::hash_, Vectorized::hash());
      }
      return Selector::hash_;
    }
    virtual unsigned long specificity() const
    {
      int sum = 0;
      for (size_t i = 0, L = length(); i < L; ++i)
      { sum += (*this)[i]->specificity(); }
      return sum;
    }

    virtual bool has_placeholder()
    {
      if (length() == 0) return false;
      if (Simple_Selector_Obj ss = elements().front()) {
        if (ss->has_placeholder()) return true;
      }
      return false;
    }

    bool is_empty_reference()
    {
      return length() == 1 &&
             Cast<Parent_Selector>((*this)[0]);
    }

    virtual bool find ( bool (*f)(AST_Node_Obj) );
    virtual bool operator<(const Selector& rhs) const;
    virtual bool operator==(const Selector& rhs) const;
    virtual bool operator<(const Compound_Selector& rhs) const;
    virtual bool operator==(const Compound_Selector& rhs) const;
    inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); }

    ComplexSelectorSet& sources() { return sources_; }
    void clearSources() { sources_.clear(); }
    void mergeSources(ComplexSelectorSet& sources);

    Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs);
    virtual void cloneChildren();
    ATTACH_AST_OPERATIONS(Compound_Selector)
    ATTACH_OPERATIONS()
  };

  ////////////////////////////////////////////////////////////////////////////
  // General selectors -- i.e., simple sequences combined with one of the four
  // CSS selector combinators (">", "+", "~", and whitespace). Essentially a
  // linked list.
  ////////////////////////////////////////////////////////////////////////////
  class Complex_Selector : public Selector {
  public:
    enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE };
  private:
    HASH_CONSTREF(Combinator, combinator)
    HASH_PROPERTY(Compound_Selector_Obj, head)
    HASH_PROPERTY(Complex_Selector_Obj, tail)
    HASH_PROPERTY(String_Obj, reference);
  public:
    bool contains_placeholder() {
      if (head() && head()->contains_placeholder()) return true;
      if (tail() && tail()->contains_placeholder()) return true;
      return false;
    };
    Complex_Selector(ParserState pstate,
                     Combinator c = ANCESTOR_OF,
                     Compound_Selector_Obj h = 0,
                     Complex_Selector_Obj t = 0,
                     String_Obj r = 0)
    : Selector(pstate),
      combinator_(c),
      head_(h), tail_(t),
      reference_(r)
    {}
    Complex_Selector(const Complex_Selector* ptr)
    : Selector(ptr),
      combinator_(ptr->combinator_),
      head_(ptr->head_), tail_(ptr->tail_),
      reference_(ptr->reference_)
    {};
    virtual bool has_parent_ref() const;
    virtual bool has_real_parent_ref() const;

    Complex_Selector_Obj skip_empty_reference()
    {
      if ((!head_ || !head_->length() || head_->is_empty_reference()) &&
          combinator() == Combinator::ANCESTOR_OF)
      {
        if (!tail_) return 0;
        tail_->has_line_feed_ = this->has_line_feed_;
        // tail_->has_line_break_ = this->has_line_break_;
        return tail_->skip_empty_reference();
      }
      return this;
    }

    // can still have a tail
    bool is_empty_ancestor() const
    {
      return (!head() || head()->length() == 0) &&
             combinator() == Combinator::ANCESTOR_OF;
    }

    Selector_List_Ptr tails(Selector_List_Ptr tails);

    // front returns the first real tail
    // skips over parent and empty ones
    Complex_Selector_Obj first();
    // last returns the last real tail
    Complex_Selector_Obj last();

    // some shortcuts that should be removed
    Complex_Selector_Obj innermost() { return last(); };

    size_t length() const;
    Selector_List_Ptr resolve_parent_refs(std::vector<Selector_List_Obj>& pstack, bool implicit_parent = true);
    virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = "");
    virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = "");
    virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = "");
    Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs);
    Combinator clear_innermost();
    void append(Complex_Selector_Obj);
    void set_innermost(Complex_Selector_Obj, Combinator);
    virtual size_t hash()
    {
      if (hash_ == 0) {
        hash_combine(hash_, std::hash<int>()(SELECTOR));
        hash_combine(hash_, std::hash<int>()(combinator_));
        if (head_) hash_combine(hash_, head_->hash());
        if (tail_) hash_combine(hash_, tail_->hash());
      }
      return hash_;
    }
    virtual unsigned long specificity() const
    {
      int sum = 0;
      if (head()) sum += head()->specificity();
      if (tail()) sum += tail()->specificity();
      return sum;
    }
    virtual void set_media_block(Media_Block_Ptr mb) {
      media_block(mb);
      if (tail_) tail_->set_media_block(mb);
      if (head_) head_->set_media_block(mb);
    }
    virtual bool has_placeholder() {
      if (head_ && head_->has_placeholder()) return true;
      if (tail_ && tail_->has_placeholder()) return true;
      return false;
    }
    virtual bool find ( bool (*f)(AST_Node_Obj) );
    virtual bool operator<(const Selector& rhs) const;
    virtual bool operator==(const Selector& rhs) const;
    virtual bool operator<(const Complex_Selector& rhs) const;
    virtual bool operator==(const Complex_Selector& rhs) const;
    inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); }
    const ComplexSelectorSet sources()
    {
      //s = Set.new
      //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)}
      //s

      ComplexSelectorSet srcs;

      Compound_Selector_Obj pHead = head();
      Complex_Selector_Obj  pTail = tail();

      if (pHead) {
        const ComplexSelectorSet& headSources = pHead->sources();
        srcs.insert(headSources.begin(), headSources.end());
      }

      if (pTail) {
        const ComplexSelectorSet& tailSources = pTail->sources();
        srcs.insert(tailSources.begin(), tailSources.end());
      }

      return srcs;
    }
    void addSources(ComplexSelectorSet& sources) {
      // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
      Complex_Selector_Ptr pIter = this;
      while (pIter) {
        Compound_Selector_Ptr pHead = pIter->head();

        if (pHead) {
          pHead->mergeSources(sources);
        }

        pIter = pIter->tail();
      }
    }
    void clearSources() {
      Complex_Selector_Ptr pIter = this;
      while (pIter) {
        Compound_Selector_Ptr pHead = pIter->head();

        if (pHead) {
          pHead->clearSources();
        }

        pIter = pIter->tail();
      }
    }

    virtual void cloneChildren();
    ATTACH_AST_OPERATIONS(Complex_Selector)
    ATTACH_OPERATIONS()
  };

  ///////////////////////////////////
  // Comma-separated selector groups.
  ///////////////////////////////////
  class Selector_List : public Selector, public Vectorized<Complex_Selector_Obj> {
    ADD_PROPERTY(Selector_Schema_Obj, schema)
    ADD_CONSTREF(std::vector<std::string>, wspace)
  protected:
    void adjust_after_pushing(Complex_Selector_Obj c);
  public:
    Selector_List(ParserState pstate, size_t s = 0)
    : Selector(pstate),
      Vectorized<Complex_Selector_Obj>(s),
      schema_(NULL),
      wspace_(0)
    { }
    Selector_List(const Selector_List* ptr)
    : Selector(ptr),
      Vectorized<Complex_Selector_Obj>(*ptr),
      schema_(ptr->schema_),
      wspace_(ptr->wspace_)
    { }
    std::string type() const { return "list"; }
    // remove parent selector references
    // basically unwraps parsed selectors
    virtual bool has_parent_ref() const;
    virtual bool has_real_parent_ref() const;
    void remove_parent_selectors();
    Selector_List_Ptr resolve_parent_refs(std::vector<Selector_List_Obj>& pstack, bool implicit_parent = true);
    virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = "");
    virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = "");
    virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = "");
    Selector_List_Ptr unify_with(Selector_List_Ptr);
    void populate_extends(Selector_List_Obj, Subset_Map&);
    Selector_List_Obj eval(Eval& eval);
    virtual size_t hash()
    {
      if (Selector::hash_ == 0) {
        hash_combine(Selector::hash_, std::hash<int>()(SELECTOR));
        hash_combine(Selector::hash_, Vectorized::hash());
      }
      return Selector::hash_;
    }
    virtual unsigned long specificity() const
    {
      unsigned long sum = 0;
      unsigned long specificity;
      for (size_t i = 0, L = length(); i < L; ++i)
      {
        specificity = (*this)[i]->specificity();
        if (sum < specificity) sum = specificity;
      }
      return sum;
    }
    virtual void set_media_block(Media_Block_Ptr mb) {
      media_block(mb);
      for (Complex_Selector_Obj cs : elements()) {
        cs->set_media_block(mb);
      }
    }
    virtual bool has_placeholder() {
      for (Complex_Selector_Obj cs : elements()) {
        if (cs->has_placeholder()) return true;
      }
      return false;
    }
    virtual bool find ( bool (*f)(AST_Node_Obj) );
    virtual bool operator<(const Selector& rhs) const;
    virtual bool operator==(const Selector& rhs) const;
    virtual bool operator<(const Selector_List& rhs) const;
    virtual bool operator==(const Selector_List& rhs) const;
    // Selector Lists can be compared to comma lists
    virtual bool operator==(const Expression& rhs) const;
    virtual void cloneChildren();
    ATTACH_AST_OPERATIONS(Selector_List)
    ATTACH_OPERATIONS()
  };

  // compare function for sorting and probably other other uses
  struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } };
  struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } };
  struct cmp_simple_selector { inline bool operator() (const Simple_Selector_Obj l, const Simple_Selector_Obj r) { return (*l < *r); } };

}

#ifdef __clang__

#pragma clang diagnostic pop

#endif

#endif