toString = function toString() {
  if (this.toStringCache != null) {
    return this.toStringCache;
  }

  var clean = function clean(obj) {
    if (obj == null) {
      return '';
    } else {
      return obj;
    }
  };

  var cleanVal = function cleanVal(val) {
    if (string(val)) {
      return '"' + val + '"';
    } else {
      return clean(val);
    }
  };

  var space = function space(val) {
    return ' ' + val + ' ';
  };

  var checkToString = function checkToString(check, subject) {
    var type = check.type,
        value = check.value;

    switch (type) {
      case Type.GROUP:
        {
          var group = clean(value);
          return group.substring(0, group.length - 1);
        }

      case Type.DATA_COMPARE:
        {
          var field = check.field,
              operator = check.operator;
          return '[' + field + space(clean(operator)) + cleanVal(value) + ']';
        }

      case Type.DATA_BOOL:
        {
          var _operator = check.operator,
              _field = check.field;
          return '[' + clean(_operator) + _field + ']';
        }

      case Type.DATA_EXIST:
        {
          var _field2 = check.field;
          return '[' + _field2 + ']';
        }

      case Type.META_COMPARE:
        {
          var _operator2 = check.operator,
              _field3 = check.field;
          return '[[' + _field3 + space(clean(_operator2)) + cleanVal(value) + ']]';
        }

      case Type.STATE:
        {
          return value;
        }

      case Type.ID:
        {
          return '#' + value;
        }

      case Type.CLASS:
        {
          return '.' + value;
        }

      case Type.PARENT:
      case Type.CHILD:
        {
          return queryToString(check.parent, subject) + space('>') + queryToString(check.child, subject);
        }

      case Type.ANCESTOR:
      case Type.DESCENDANT:
        {
          return queryToString(check.ancestor, subject) + ' ' + queryToString(check.descendant, subject);
        }

      case Type.COMPOUND_SPLIT:
        {
          var lhs = queryToString(check.left, subject);
          var sub = queryToString(check.subject, subject);
          var rhs = queryToString(check.right, subject);
          return lhs + (lhs.length > 0 ? ' ' : '') + sub + rhs;
        }

      case Type.TRUE:
        {
          return '';
        }
    }
  };

  var queryToString = function queryToString(query, subject) {
    return query.checks.reduce(function (str, chk, i) {
      return str + (subject === query && i === 0 ? '$' : '') + checkToString(chk, subject);
    }, '');
  };

  var str = '';

  for (var i = 0; i < this.length; i++) {
    var query = this[i];
    str += queryToString(query, query.subject);

    if (this.length > 1 && i < this.length - 1) {
      str += ', ';
    }
  }

  this.toStringCache = str;
  return str;
}
Get the selector represented as a string.  This value uses default formatting,
so things like spacing may differ from the input text passed to the constructor.
@returns {string} The selector string