The pumping lemma proves regular languages can't count arbitrary nesting. HTML needs { aⁿ bⁿ | n ≥ 0 } matching — a context-free language. Python's re on
The fix is a complete HTML parser with three layers, demonstrating precisely why regex fails for HTML per the Chomsky hierarchy:
Uses regex to split HTML into tokens: tag_open, tag_close, self_closing, text, comment, cdata. This is the only place regex belongs — breaking input into atomic tokens is a regular language task.
TOKEN_RE = re.compile(r"""
<!--.*?--> | # comment
<!\[CDATA\[.*?\]\]> | # CDATA
<(?P<close>\/)\s*([a-zA-Z][a-zA-Z0-9:_-]*)\s*> | # close tag
<([a-zA-Z][a-zA-Z0-9:_-]*)((?:\s+[^>]*?)?)\s*/?> | # open/self-closing
[^<]+ # text
""", re.VERBOSE | re.DOTALL)
Uses a stack — the defining characteristic of a context-free (Type-2) parser. Each tag_open pushes onto the stack; each tag_close pops and verifies the tag name matches. This correctly tracks arbitrary nesting depth, which regex cannot do.
def parse_html(html: str) -> Element:
tokens = tokenize(html)
root = Element("root")
stack = [root]
for tok in tokens:
if tok.type == "tag_open":
el = Element(tok.value, dict(tok.attrs or {}))
stack[-1].children.append(el)
stack.append(el)
elif tok.type == "tag_close":
top = stack[-1]
if top.tag != tok.value:
raise ParseError(f"Tag mismatch: </{tok.value}> vs <{top.tag}>")
stack.pop()
# ... text, comment, cdata, self_closing
The pumping lemma proves regular languages can't count arbitrary nesting. HTML needs { aⁿ bⁿ | n ≥ 0 } matching — a context-free language. Python's re on <div><div>deep</div></div> greedily matches from the first <div> to the last </div>, producing an incorrect parse:
Regex <div>(.*)</div> matches '<div><div>deep</div></div>'
→ captures "<div>deep</div>" as content (WRONG)
A proper parser uses a stack and gets:
<div>
<div>
'deep'
</div>
</div>
All **14/14 tests pass**, including:
| Test | Input | Result |
|------|-------|--------|
| Simple text | `hello world` | ✅ Correct (no elements) |
| Single tag | `<p>text</p>` | ✅ `['p']` |
| Nested tags | `<div><span>hi</span></div>` | ✅ `['div', 'span']` |
| Self-closing | `<br/><img src='a.png'/>text` | ✅ `['br', 'img']` |
| Deep nesting | `<div>`×10 + `hello` + `</div>`×10 | ✅ Correctly parsed |
| Mismatched tags | `<b><i>text</b></i>` | ✅ Correctly rejects with ParseError |
| Attrs | `<a href="x" class="y">z</a>` | ✅ `{'href': 'x', 'class': 'y'}` |
| CDATA | `<![CDATA[<b>not html</b>]]><p>ok</p>` | ✅ Raw CDATA preserved |
| Comments | `<!-- comment --><p>x</p>` | ✅ Comment preserved as text |
**Output:** Parses `<div class="main"><b>hello</b><br/><!-- comment --><![CDATA[raw]]></div>` into a proper tree with all 6 token types correctly handled.
---{"model": "claude-sonnet-4-20250514", "problem_class": "so-regex-html-legendary", "result": "passed", "tests": 14}