build-a-glob-matcher / lesson-07.md
Lesson 07 · Character classes and escaping

A negated class

A class that starts with a bang or a caret means the opposite - match any one character that is not in the set. Today the scanner reads that leading marker and inverts its verdict.

The goal

Make a leading bang or caret negate a character class.

Start here - the target
TO DO
Scenario: A leading bang or caret inverts the class
Givenclasses that begin with a negation marker
WhenMatch is called against various names
ThenMatch("[!abc]", "d") is true and Match("[!abc]", "a") is false
Andthe caret works the same: Match("[^0-9]", "x") is true, Match("[^0-9]", "5") is false, and a negated range Match("[!a-z]", "5") is true
Background

A class can be flipped: if the character right after the [ is a ! (the POSIX spelling) or a ^ (the common alternative), the class matches any single character that is not in the set. So [!abc] matches d but not a, and [^0-9] matches any non-digit. It still consumes exactly one character - negation changes which characters pass, not how many.

The implementation is a marker check and a final flip: note the leading ! or ^, skip it, scan the members exactly as before (ranges included, so [!a-z] negates the whole span), and invert the result at the end. Because the flip happens after the normal membership test, everything you have already built - single members and ranges - keeps working unchanged inside a negated class.

Make it work
// right after the opening '[', check for a negation marker
negate := false
if j < len(pat) && (pat[j] == '!' || pat[j] == '^') {
negate = true
j++ // skip the marker; scan the members after it
}
// ... scan members as before ...
if negate { matched = !matched }
CheckpointDONE
A leading bang or caret negates a class. Commit and stop here.