I think the rules for infinite-lookahead would be:
- If the first token is an ident or function, then:
- If it's a dashed-ident (or function with a dashed-ident name), followed by any amount of whitespace, followed by a colon: it's a custom property.
- If it's followed by any amount of whitespace and a colon, then look forward until you see either a semicolon or EOF (it's a property) or a {}-block (it's a rule).
- Otherwise, it's a rule.
- Otherwise, it's a rule.
This gives us infinite lookahead, with arbitrary mixing of properties and selectors, with only three compromises:
- Non-custom properties can never contain top-level {}-blocks. (They can put them in strings, or in functions, or in parens, whatever. Just not at the top.)
- Selectors can never contain top-level semicolons. (Ditto.)
- Nested selectors can't start with a type selector that's a dashed-ident (that is, start with a --) followed by a pseudo-class. If you have a markup language that allows this (HTML doesn't), you still have to wrap it in an
:is()
or whatever. (But any other combo works ---foo.bar
is just fine.)
That's actually already implicit in the Syntax spec: you parse a full block from { to }, then parse its contents. (Real impls do indeed move the check inline, yeah.)
But, hm, yeah at least the EOF check does need to be in bullet 1.2, since "end of block" registers as EOF in the Syntax spec's model.