Exercise 1: A Gap-Tolerant Chained Descendant Selector — Possible Solution ==================================================================== THE TEST ------------------------------ Tree A: div > section > article > p (four levels deep) Tree B: div > p (p directly inside div, no section) sel = parse_selector("div section p") matches_selector(p_in_tree_A, sel) matches_selector(p_in_tree_B, sel) RESULT ------------------------------ Tree A -> True (matches, despite 'article' never appearing in the selector) Tree B -> False (correctly fails -- no 'section' ancestor at all) WHY TREE A MATCHES ------------------------------ matches_selector checks the selector's parts from the END backwards. It first confirms the target itself (p) matches the last part ('p'). Then, for each remaining part in reverse ('section', then 'div'), it walks UPWARD from the current position using a `while node is not None` loop that keeps climbing .parent links until it finds ANY ancestor that satisfies that part -- not necessarily the immediate parent. So when looking for 'section' above p, the loop first checks p's immediate parent (article) -- no match -- then keeps climbing to article's parent (section) -- match. The intervening 'article' is simply skipped over during the climb; it's never checked against any part of the selector at all, because the selector never mentioned it. WHY TREE B FAILS ------------------------------ Once matches_selector is looking for a 'section' ancestor above p, the upward walk in Tree B only has one node to check (div) before running out of ancestors (node becomes None). div does not match the SimpleSelector for 'section', so `found` stays False, and the function returns False immediately -- it never even gets to checking for 'div' as well, since the search already failed one part earlier in the chain. WHY THIS WORKS AS AN ANSWER ------------------------------ The specific mechanism responsible is the inner `while node is not None` loop in matches_selector -- it doesn't stop at the first parent it finds, it keeps climbing until either a match is found or the tree runs out. This is precisely how a real CSS descendant combinator (a plain space between two selector parts) behaves: "somewhere above," not "immediately above." A descendant combinator's gap-tolerance isn't a special case bolted on for this exercise -- it falls directly out of using a `while` loop instead of a single `.parent` lookup in the very first version of the function.