Skip to content

Commit

Permalink
Merge pull request #1202 from mgreter/feature/selector-functions-basic
Browse files Browse the repository at this point in the history
Implement `is_superselector` sass function
  • Loading branch information
mgreter committed May 13, 2015
2 parents 28ee088 + f8074a0 commit a6482aa
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 39 deletions.
138 changes: 102 additions & 36 deletions ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,29 @@ namespace Sass {
}

bool Complex_Selector::operator==(const Complex_Selector& rhs) const {
// TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version.
// TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version.

const Complex_Selector* pOne = this;
const Complex_Selector* pOne = this;
const Complex_Selector* pTwo = &rhs;

// Consume any empty references at the beginning of the Complex_Selector
if (pOne->combinator() == Complex_Selector::ANCESTOR_OF && pOne->head()->is_empty_reference()) {
pOne = pOne->tail_;
pOne = pOne->tail_;
}
if (pTwo->combinator() == Complex_Selector::ANCESTOR_OF && pTwo->head()->is_empty_reference()) {
pTwo = pTwo->tail_;
pTwo = pTwo->tail_;
}

while (pOne && pTwo) {
if (pOne->combinator() != pTwo->combinator()) {
return false;
if (pOne->combinator() != pTwo->combinator()) {
return false;
}

if (*(pOne->head()) != *(pTwo->head())) {
return false;
return false;
}

pOne = pOne->tail_;
pOne = pOne->tail_;
pTwo = pTwo->tail_;
}

Expand All @@ -68,21 +68,21 @@ namespace Sass {

bool Simple_Selector::operator==(const Simple_Selector& rhs) const
{
// Compare the string representations for equality.
// Compare the string representations for equality.

// Cast away const here. To_String should take a const object, but it doesn't.
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
// Cast away const here. To_String should take a const object, but it doesn't.
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
Simple_Selector* pRHS = const_cast<Simple_Selector*>(&rhs);

To_String to_string;
return pLHS->perform(&to_string) == pRHS->perform(&to_string);
}

bool Simple_Selector::operator<(const Simple_Selector& rhs) const {
// Use the string representation for ordering.
// Use the string representation for ordering.

// Cast away const here. To_String should take a const object, but it doesn't.
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
// Cast away const here. To_String should take a const object, but it doesn't.
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
Simple_Selector* pRHS = const_cast<Simple_Selector*>(&rhs);

To_String to_string;
Expand Down Expand Up @@ -217,32 +217,46 @@ namespace Sass {
set<string> lpsuedoset, rpsuedoset;
for (size_t i = 0, L = length(); i < L; ++i)
{
if ((*this)[i]->is_pseudo_element()) {
string pseudo((*this)[i]->perform(&to_string));
if ((*this)[i]->is_pseudo_element()) {
string pseudo((*this)[i]->perform(&to_string));
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
lpsuedoset.insert(pseudo);
lpsuedoset.insert(pseudo);
}
}
for (size_t i = 0, L = rhs->length(); i < L; ++i)
{
if ((*rhs)[i]->is_pseudo_element()) {
string pseudo((*rhs)[i]->perform(&to_string));
if ((*rhs)[i]->is_pseudo_element()) {
string pseudo((*rhs)[i]->perform(&to_string));
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
rpsuedoset.insert(pseudo);
rpsuedoset.insert(pseudo);
}
}
if (lpsuedoset != rpsuedoset) {
if (lpsuedoset != rpsuedoset) {
return false;
}

// Check the Simple_Selectors
// Check the Simple_Selectors

set<string> lset, rset;

if (!lbase) // no lbase; just see if the left-hand qualifiers are a subset of the right-hand selector
{
for (size_t i = 0, L = length(); i < L; ++i)
{ lset.insert((*this)[i]->perform(&to_string)); }
{
Selector* lhs = (*this)[i];
// very special case for wrapped matches selector
if (Wrapped_Selector* wrapped = dynamic_cast<Wrapped_Selector*>(lhs)) {
if (wrapped->name() == ":matches(" || wrapped->name() == ":-moz-any(") {
if (Selector_List* list = dynamic_cast<Selector_List*>(wrapped->selector())) {
if (Compound_Selector* comp = dynamic_cast<Compound_Selector*>(rhs)) {
if (list->is_superselector_of(comp)) return true;
}
}
}
}
// match from here on as strings
lset.insert(lhs->perform(&to_string));
}
for (size_t i = 0, L = rhs->length(); i < L; ++i)
{ rset.insert((*rhs)[i]->perform(&to_string)); }
return includes(rset.begin(), rset.end(), lset.begin(), lset.end());
Expand Down Expand Up @@ -274,33 +288,33 @@ namespace Sass {
set<string> lpsuedoset, rpsuedoset;
for (size_t i = 0, L = length(); i < L; ++i)
{
if ((*this)[i]->is_pseudo_element()) {
string pseudo((*this)[i]->perform(&to_string));
if ((*this)[i]->is_pseudo_element()) {
string pseudo((*this)[i]->perform(&to_string));
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
lpsuedoset.insert(pseudo);
lpsuedoset.insert(pseudo);
}
}
for (size_t i = 0, L = rhs.length(); i < L; ++i)
{
if (rhs[i]->is_pseudo_element()) {
string pseudo(rhs[i]->perform(&to_string));
if (rhs[i]->is_pseudo_element()) {
string pseudo(rhs[i]->perform(&to_string));
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
rpsuedoset.insert(pseudo);
rpsuedoset.insert(pseudo);
}
}
if (lpsuedoset != rpsuedoset) {
if (lpsuedoset != rpsuedoset) {
return false;
}

// Check the base
// Check the base

const Simple_Selector* const lbase = base();
const Simple_Selector* const rbase = rhs.base();

if ((lbase && !rbase) ||
(!lbase && rbase) ||
(!lbase && rbase) ||
((lbase && rbase) && (*lbase != *rbase))) {
return false;
return false;
}


Expand All @@ -326,8 +340,6 @@ namespace Sass {

bool Complex_Selector::is_superselector_of(Compound_Selector* rhs)
{
if (length() != 1)
{ return false; }
return base()->is_superselector_of(rhs);
}

Expand All @@ -352,6 +364,16 @@ namespace Sass {
if (l_len == 1)
{ return lhs->head()->is_superselector_of(rhs->base()); }

// we have to look one tail deeper, since we cary the
// combinator around for it (which is important here)
if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) {
Complex_Selector* lhs_tail = lhs->tail();
Complex_Selector* rhs_tail = rhs->tail();
if (lhs_tail->combinator() != rhs_tail->combinator()) return false;
if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false;
}


bool found = false;
Complex_Selector* marker = rhs;
for (size_t i = 0, L = rhs->length(); i < L; ++i) {
Expand Down Expand Up @@ -458,7 +480,7 @@ namespace Sass {
Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this);

if (head()) {
cpy->head(head()->clone(ctx));
cpy->head(head()->clone(ctx));
}

if (tail()) {
Expand Down Expand Up @@ -493,6 +515,50 @@ namespace Sass {
#endif
}

// it's a superselector if every selector of the right side
// list is a superselector of the given left side selector
bool Complex_Selector::is_superselector_of(Selector_List *sub)
{
// Check every rhs selector against left hand list
for(size_t i = 0, L = sub->length(); i < L; ++i) {
if (!is_superselector_of((*sub)[i])) return false;
}
return true;
}

// it's a superselector if every selector of the right side
// list is a superselector of the given left side selector
bool Selector_List::is_superselector_of(Selector_List *sub)
{
// Check every rhs selector against left hand list
for(size_t i = 0, L = sub->length(); i < L; ++i) {
if (!is_superselector_of((*sub)[i])) return false;
}
return true;
}

// it's a superselector if every selector on the right side
// is a superselector of any one of the left side selectors
bool Selector_List::is_superselector_of(Compound_Selector *sub)
{
// Check every lhs selector against right hand
for(size_t i = 0, L = length(); i < L; ++i) {
if ((*this)[i]->is_superselector_of(sub)) return true;
}
return false;
}

// it's a superselector if every selector on the right side
// is a superselector of any one of the left side selectors
bool Selector_List::is_superselector_of(Complex_Selector *sub)
{
// Check every lhs selector against right hand
for(size_t i = 0, L = length(); i < L; ++i) {
if ((*this)[i]->is_superselector_of(sub)) return true;
}
return false;
}

/* not used anymore - remove?
Selector_Placeholder* Selector_List::find_placeholder()
{
Expand Down
12 changes: 9 additions & 3 deletions ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1943,7 +1943,9 @@ namespace Sass {
return (*this)[0];
return 0;
}
bool is_superselector_of(Compound_Selector* rhs);
bool is_superselector_of(Compound_Selector* sub);
// bool is_superselector_of(Complex_Selector* sub);
// bool is_superselector_of(Selector_List* sub);
virtual unsigned long specificity()
{
int sum = 0;
Expand Down Expand Up @@ -2000,8 +2002,9 @@ namespace Sass {
Complex_Selector* context(Context&);
Complex_Selector* innermost();
size_t length();
bool is_superselector_of(Compound_Selector*);
bool is_superselector_of(Complex_Selector*);
bool is_superselector_of(Compound_Selector* sub);
bool is_superselector_of(Complex_Selector* sub);
bool is_superselector_of(Selector_List* sub);
// virtual Selector_Placeholder* find_placeholder();
Combinator clear_innermost();
void set_innermost(Complex_Selector*, Combinator);
Expand Down Expand Up @@ -2086,6 +2089,9 @@ namespace Sass {
: Selector(pstate), Vectorized<Complex_Selector*>(s), wspace_(0)
{ }
// virtual Selector_Placeholder* find_placeholder();
bool is_superselector_of(Compound_Selector* sub);
bool is_superselector_of(Complex_Selector* sub);
bool is_superselector_of(Selector_List* sub);
virtual unsigned long specificity()
{
unsigned long sum = 0;
Expand Down
2 changes: 2 additions & 0 deletions context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ namespace Sass {
// Misc Functions
register_function(ctx, inspect_sig, inspect, env);
register_function(ctx, unique_id_sig, unique_id, env);
// Selector functions
register_function(ctx, is_superselector_sig, is_superselector, env);
}

void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs)
Expand Down
14 changes: 14 additions & 0 deletions functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,20 @@ namespace Sass {
// return v;
}

Signature is_superselector_sig = "is-superselector($super, $sub)";
BUILT_IN(is_superselector)
{
To_String to_string(&ctx, false);
Expression* ex_sup = ARG("$super", Expression);
Expression* ex_sub = ARG("$sub", Expression);
string sup_src = ex_sup->perform(&to_string) + "{";
string sub_src = ex_sub->perform(&to_string) + "{";
Selector_List* sel_sup = Parser::parse_selector(sup_src.c_str(), ctx);
Selector_List* sel_sub = Parser::parse_selector(sub_src.c_str(), ctx);
bool result = sel_sup->is_superselector_of(sel_sub);
return new (ctx.mem) Boolean(pstate, result);
}

Signature unique_id_sig = "unique-id()";
BUILT_IN(unique_id)
{
Expand Down
2 changes: 2 additions & 0 deletions functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ namespace Sass {
extern Signature keywords_sig;
extern Signature set_nth_sig;
extern Signature unique_id_sig;
extern Signature is_superselector_sig;

BUILT_IN(rgb);
BUILT_IN(rgba_4);
Expand Down Expand Up @@ -175,6 +176,7 @@ namespace Sass {
BUILT_IN(keywords);
BUILT_IN(set_nth);
BUILT_IN(unique_id);
BUILT_IN(is_superselector);

}
}
Expand Down
8 changes: 8 additions & 0 deletions parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ namespace Sass {
return p;
}

Selector_List* Parser::parse_selector(const char* src, Context& ctx, ParserState pstate)
{
Parser p = Parser::from_c_str(src, ctx, pstate);
// ToDo: ruby sass errors on parent references
// ToDo: remap the source-map entries somehow
return p.parse_selector_group();
}

bool Parser::peek_newline(const char* start)
{
return peek_linefeed(start ? start : position);
Expand Down
2 changes: 2 additions & 0 deletions parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ namespace Sass {
static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"));
static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"));
static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"));
// special static parsers to convert strings into certain selectors
static Selector_List* parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"));

#ifdef __clang__

Expand Down

0 comments on commit a6482aa

Please sign in to comment.