Skip to content

Commit

Permalink
vaev-style: Separate selector matching from selector parsing.
Browse files Browse the repository at this point in the history
  • Loading branch information
Louciole committed Jan 30, 2025
1 parent e748915 commit f045bca
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 260 deletions.
4 changes: 2 additions & 2 deletions src/web/vaev-style/rules.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "rules.h"

#include "selector-matching.h"
#include "decls.h"

namespace Vaev::Style {
Expand All @@ -9,7 +9,7 @@ static bool DEBUG_RULE = false;
// MARK: StyleRule -------------------------------------------------------------

Opt<Spec> StyleRule::matchWithSpecificity(Markup::Element const& el) const {
return selector.matchWithSpecificity(el);
return matchSelectorWithSpecificity(selector,el);
}

void StyleRule::repr(Io::Emit& e) const {
Expand Down
253 changes: 0 additions & 253 deletions src/web/vaev-style/select.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "select.h"

#include "values.h"

namespace Vaev::Style {
Expand Down Expand Up @@ -51,258 +50,6 @@ Spec spec(Selector const& s) {
});
}

// MARK: Selector Matching -----------------------------------------------------

// https://www.w3.org/TR/selectors-4/#descendant-combinators
static bool _matchDescendant(Selector const& s, Markup::Element const& e) {
Cursor<Markup::Node> curr = &e;
while (curr->hasParent()) {
auto& parent = curr->parentNode();
if (auto el = parent.is<Markup::Element>())
if (s.match(*el))
return true;
curr = &parent;
}
return false;
}

// https://www.w3.org/TR/selectors-4/#child-combinators
static bool _matchChild(Selector const& s, Markup::Element const& e) {
if (not e.hasParent())
return false;

auto& parent = e.parentNode();
if (auto el = parent.is<Markup::Element>())
return s.match(*el);
return false;
}

// https://www.w3.org/TR/selectors-4/#adjacent-sibling-combinators
static bool _matchAdjacent(Selector const& s, Markup::Element const& e) {
if (not e.hasPreviousSibling())
return false;

auto prev = e.previousSibling();
if (auto el = prev.is<Markup::Element>())
return s.match(*el);
return false;
}

// https://www.w3.org/TR/selectors-4/#general-sibling-combinators
static bool _matchSubsequent(Selector const& s, Markup::Element const& e) {
Cursor<Markup::Node> curr = &e;
while (curr->hasPreviousSibling()) {
auto prev = curr->previousSibling();
if (auto el = prev.is<Markup::Element>())
if (s.match(*el))
return true;
curr = &prev.unwrap();
}
return false;
}

static bool _match(Infix const& s, Markup::Element const& e) {
if (not s.rhs->match(e))
return false;

switch (s.type) {
case Infix::DESCENDANT: // ' '
return _matchDescendant(*s.lhs, e);

case Infix::CHILD: // >
return _matchChild(*s.lhs, e);

case Infix::ADJACENT: // +
return _matchAdjacent(*s.lhs, e);

case Infix::SUBSEQUENT: // ~
return _matchSubsequent(*s.lhs, e);

default:
logWarnIf(DEBUG_SELECTORS, "unimplemented selector: {}", s);
return false;
}
}

static bool _match(Nfix const& s, Markup::Element const& el) {
switch (s.type) {
case Nfix::AND:
for (auto& inner : s.inners)
if (not inner.match(el))
return false;
return true;

// 4.1. Selector Lists
// https://www.w3.org/TR/selectors-4/#grouping
// and
// 4.2. The Matches-Any Pseudo-class: :is()
// https://www.w3.org/TR/selectors-4/#matchess
case Nfix::OR:
for (auto& inner : s.inners)
if (inner.match(el))
return true;
return false;

case Nfix::NOT:
return not s.inners[0].match(el);

case Nfix::WHERE:
return not s.inners[0].match(el);

default:
logWarnIf(DEBUG_SELECTORS, "unimplemented selector: {}", s);
return false;
}
}

// 5.1. Type (tag name) selector
// https://www.w3.org/TR/selectors-4/#type
static bool _match(TypeSelector const& s, Markup::Element const& el) {
return el.tagName == s.type;
}

static bool _match(IdSelector const& s, Markup::Element const& el) {
return el.id() == s.id;
}

static bool _match(ClassSelector const& s, Markup::Element const& el) {
return el.classList.contains(s.class_);
}

// 8.2. The Link History Pseudo-classes: :link and :visited
// https://www.w3.org/TR/selectors-4/#link

static bool _matchLink(Markup::Element const& el) {
return el.tagName == Html::A and el.hasAttribute(Html::HREF_ATTR);
}

// 14.3.3. :first-child pseudo-class
// https://www.w3.org/TR/selectors-4/#the-first-child-pseudo

static bool _matchFirstChild(Markup::Element const& e) {
Cursor<Markup::Node> curr = &e;
while (curr->hasPreviousSibling()) {
auto prev = curr->previousSibling();
if (auto el = prev.is<Markup::Element>())
return false;
curr = &prev.unwrap();
}
return true;
}

// 14.3.4. :last-child pseudo-class
// https://www.w3.org/TR/selectors-4/#the-last-child-pseudo

static bool _matchLastChild(Markup::Element const& e) {
Cursor<Markup::Node> curr = &e;
while (curr->hasNextSibling()) {
auto next = curr->nextSibling();
if (auto el = next.is<Markup::Element>())
return false;
curr = &next.unwrap();
}
return true;
}

// 14.4.3. :first-of-type pseudo-class
// https://www.w3.org/TR/selectors-4/#the-first-of-type-pseudo

static bool _matchFirstOfType(Markup::Element const& e) {
Cursor<Markup::Node> curr = &e;
auto tag = e.tagName;

while (curr->hasPreviousSibling()) {
auto prev = curr->previousSibling();
if (auto el = prev.is<Markup::Element>())
if (e.tagName == tag)
return false;
curr = &prev.unwrap();
}
return true;
}

// 14.4.4. :last-of-type pseudo-class
// https://www.w3.org/TR/selectors-4/#the-last-of-type-pseudo

static bool _matchLastOfType(Markup::Element const& e) {
Cursor<Markup::Node> curr = &e;
auto tag = e.tagName;

while (curr->hasNextSibling()) {
auto prev = curr->nextSibling();
if (auto el = prev.is<Markup::Element>())
if (e.tagName == tag)
return false;
curr = &prev.unwrap();
}
return true;
}

static bool _match(Pseudo const& s, Markup::Element const& el) {
switch (s.type) {
case Pseudo::LINK:
return _matchLink(el);

case Pseudo::ROOT:
return el.tagName == Html::HTML;

case Pseudo::FIRST_OF_TYPE:
return _matchFirstOfType(el);

case Pseudo::LAST_OF_TYPE:
return _matchLastOfType(el);

case Pseudo::FIRST_CHILD:
return _matchFirstChild(el);

case Pseudo::LAST_CHILD:
return _matchLastChild(el);

default:
logDebugIf(DEBUG_SELECTORS, "unimplemented pseudo class: {}", s);
return false;
}
}

// 5.2. Universal selector
// https://www.w3.org/TR/selectors-4/#the-universal-selector
static bool _match(UniversalSelector const&, Markup::Element const&) {
return true;
}

// MARK: Selector --------------------------------------------------------------

bool Selector::match(Markup::Element const& el) const {
return visit(
[&](auto const& s) {
if constexpr (requires { _match(s, el); })
return _match(s, el);

logWarnIf(DEBUG_SELECTORS, "unimplemented selector: {}", s);
return false;
}
);
}

Opt<Spec> Selector::matchWithSpecificity(Markup::Element const& el) const {
return visit(Visitor{
[&](Nfix const& n) -> Opt<Spec> {
if (n.type == Nfix::OR) {
Opt<Spec> specificity;
for (auto& inner : n.inners) {
if (inner.match(el))
specificity = max(specificity, spec(inner));
}
return specificity;
}
return match(el) ? Opt<Spec>{spec(n)} : NONE;
},
[&](auto const& s) -> Opt<Spec> {
return match(el) ? Opt<Spec>{spec(s)} : NONE;
}
});
}

// MARK: Parser ----------------------------------------------------------------

// enum order is the operator priority (the lesser the most important)
Expand Down
4 changes: 0 additions & 4 deletions src/web/vaev-style/select.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,6 @@ struct Selector : public _Selector {
});
}

bool match(Markup::Element const& el) const;

Opt<Spec> matchWithSpecificity(Markup::Element const& el) const;

bool operator==(Selector const&) const = default;

static Res<Selector> parse(Cursor<Css::Sst>& c);
Expand Down
Loading

0 comments on commit f045bca

Please sign in to comment.