# The Core Toolkit

Now that you know a regex *describes a shape*, you need a vocabulary for describing shapes more
richly than "these exact letters." This phase is that vocabulary, and the good news is it's small.
There are dozens of regex features in the world, but a handful do almost all the work.

We'll go piece by piece, each with a tiny example, then assemble them into something real at the end.

## Literals - characters that mean themselves

You already met these in Phase 1. Most characters in a regex are **literals**: they match exactly
themselves. `a` matches an `a`, `7` matches a `7`, `-` matches a dash.

```text
  pattern:  dog

  "good dog"   ►  MATCH   (the literal run d-o-g)
  "doge"       ►  MATCH   (d-o-g appears, then more)
  "cat"        ►  no match
```

*What just happened:* nothing fancy - each character stood for itself, in order. Literals are the
floor everything else builds on; the special power comes from the few characters that *don't* mean
themselves.

## Character classes - "any one of these"

Often you don't want one exact character; you want "any digit" or "any letter." That's a
**character class** - a description of a *set* of characters, any one of which matches a single
position.

The everyday shortcuts:

```text
  \d   any digit                 0 1 2 3 4 5 6 7 8 9
  \w   any "word" character      letters, digits, and underscore (a–z A–Z 0–9 _)
  \s   any whitespace            space, tab, newline
```

📝 **Terminology.** A **character class** matches *exactly one* character - one position in the
text. `\d` doesn't mean "a number," it means "one digit." To match several, you'll add a quantifier
(next section).

```text
  pattern:  \d

  "room 7"     ►  MATCH   (the 7)
  "no number"  ►  no match (not a single digit present)
```

*What just happened:* `\d` matched the single character `7`. It would also have matched the `3` in
`B3` or the `0` in `2026` - any one digit, wherever it sits.

You can also build your *own* class with square brackets. `[aeiou]` means "any one of these
vowels." A range with a dash, like `[a-f]`, means "any one character from a to f."

```text
  pattern:  [aeiou]

  "rhythm"     ►  no match (no a/e/i/o/u)
  "cat"        ►  MATCH   (the a)
```

*What just happened:* the brackets describe a custom set, and the engine matched the first character
that fell inside it - the `a` in `cat`. ⚠️ **Gotcha - a dash inside brackets is a range.** `[a-z]`
means "a through z," *not* "the letters a, dash, z." To match a literal dash, put it first or last:
`[-az]` or `[az-]`. This trips up everyone once.

## Quantifiers - "how many"

A character class matches one position. **Quantifiers** say how many times the thing right before
them may repeat. These four cover almost everything:

```text
  *      zero or more   (any amount, including none)
  +      one or more    (at least one)
  ?      zero or one     (optional)
  {n}    exactly n times
```

```text
  pattern:  \d+

  "room 7"       ►  MATCH on "7"      (one or more digits)
  "year 2026"    ►  MATCH on "2026"   (grabs all four - "one or more" keeps going)
  "no digits"    ►  no match
```

*What just happened:* `\d+` means "one or more digits in a row." On `year 2026` it didn't stop at
the first digit - it kept matching as long as the next character was also a digit, capturing the
whole `2026`. That "keep going" behavior is important, and it's the seed of the *greedy matching*
trap we'll meet in Phase 3.

A quick tour of the others:

```text
  pattern:  colou?r        the u is optional (? = zero or one)

  "color"        ►  MATCH   (zero u's)
  "colour"       ►  MATCH   (one u)
  "colouur"      ►  no match (two u's - ? allows at most one)
```

*What just happened:* `?` made the `u` optional, so one pattern matched both the US and British
spellings - a tiny taste of why regex is worth it: one description, several real-world variations.

## Anchors - "where in the text"

By default a pattern matches anywhere (Phase 1). **Anchors** pin a pattern to a position instead of
matching a character:

```text
  ^   the start of the line/text
  $   the end of the line/text
```

Anchors match a *position*, not a character - think of them as "right here at the edge."

```text
  pattern:  ^cat$         start, then c-a-t, then end - nothing else allowed

  "cat"          ►  MATCH   (the whole text is exactly "cat")
  "category"     ►  no match (there's more after "cat", so it's not the end)
  "the cat"      ►  no match (there's text before "cat", so it's not the start)
```

*What just happened:* wrapping `cat` in `^...$` turned "find cat anywhere" into "the text must be
*exactly* cat." This is the answer to the Phase 1 gotcha - anchors are how you demand a *whole*
match instead of a found-somewhere match. ⚠️ **Gotcha - `^` means two different things.** At the
*start* of a pattern, `^` is the start-anchor. But *inside square brackets*, `[^abc]` means "any
character *except* a, b, or c" - a negation. Same symbol, totally different job depending on where
it sits. Read carefully.

## Groups - "treat this as one unit"

Parentheses **group** part of a pattern so a quantifier (or other operator) applies to the whole
group, not one character. They also "capture" what matched, so a tool can pull it out later.

```text
  pattern:  (ab)+         "ab" repeated one or more times

  "ababab"       ►  MATCH   (three ab's in a row)
  "abc"          ►  MATCH on "ab"  (one ab, then c stops it)
  "ba"           ►  no match
```

*What just happened:* without the parentheses, `ab+` would mean "an `a`, then one or more `b`s." The
group made `+` apply to the *pair* `ab`. Grouping describes repeated *structures*, not only repeated
single characters - and capturing is how search-and-replace knows which piece to keep.

## Building something real: a simple date

Now assemble the toolkit. A date like `2026-06-19` has a clear shape: four digits, a dash, two
digits, a dash, two digits - and we want the *whole* string to be that and nothing else.

```text
  pattern:  ^\d{4}-\d{2}-\d{2}$

  "2026-06-19"   ►  MATCH
  "26-6-19"      ►  no match (\d{4} needs four digits, \d{2} needs two)
  "2026-06-19x"  ►  no match ($ demands the end right after the last digit)
```

*What just happened:* read it left to right like a sentence. `^` (start), `\d{4}` (exactly four
digits), `-` (a literal dash), `\d{2}` (exactly two digits), `-`, `\d{2}`, `$` (end). That wall of
symbols from the guide's intro is now a plain English description you can read. That's the payoff of
the toolkit.

💡 **Key point.** This pattern checks the *shape*, not the *meaning*. It happily matches
`9999-99-99`, which is not a real date. Regex describes how text *looks*, not whether it's
*sensible*. Knowing where that line falls - and not asking regex to cross it - is half of using it
well.

## A straight word: the "perfect email regex" is a trap

A rite of passage is trying to write one regex that matches *every* valid email address and rejects
every invalid one. Don't. The rules for what's technically a valid email are genuinely strange and
sprawling, and the "complete" regex people pass around is hundreds of characters long, unreadable, and
*still* not fully correct.

What you actually want in real life is a *good-enough* shape check - "looks roughly like an email" -
and then to confirm it's real by sending a message to it. A practical, readable pattern:

```text
  pattern:  ^\S+@\S+\.\S+$

  "ada@example.com"   ►  MATCH
  "ada@localhost"     ►  no match (no dot after the @)
  "not an email"      ►  no match (no @)
```

*What just happened:* `\S` means "any non-whitespace character" (the uppercase counterpart of `\s`).
So this reads: start, some non-spaces, an `@`, some non-spaces, a literal dot (`\.` - escaped, because
a bare `.` is special; more in Phase 3), some non-spaces, end. It's not airtight, and it's not meant to
be - it catches obvious typos and stays readable, which is the right trade. Chasing perfection here is
how regex earns its scary reputation.

## Recap

1. **Literals** match themselves; they're the floor everything builds on.
2. **Character classes** match one character from a set: `\d` (digit), `\w` (word char), `\s`
   (whitespace), or your own `[...]`.
3. **Quantifiers** say how many: `*` (zero or more), `+` (one or more), `?` (optional), `{n}`
   (exactly n).
4. **Anchors** pin to a position: `^` (start), `$` (end) - this is how you demand a *whole* match.
5. **Groups** `(...)` apply a quantifier to a whole unit and capture what matched.
6. Regex checks **shape, not meaning** - and "the perfect email regex" is a trap; aim for
   good-enough and readable.

## Try it yourself

Edit the pattern or the sample text and watch the matches highlight live:

```playground-regex
\b[\w.]+@[\w.]+\.\w+\b
Email alice@example.com or bob@test.org - but "hello world" matches nothing.
```

## Practice

```exercise
[
  {
    "type": "regex",
    "task": "Write a regex that matches a date shaped like `2026-06-19` - four digits, a dash, two digits, a dash, two digits - and nothing else, not just a piece of a longer string.",
    "mustMatch": ["2026-06-19", "1999-01-01", "0000-12-31"],
    "mustNotMatch": ["26-6-19", "2026/06/19", "date: 2026-06-19", "2026-06-19 note"],
    "hint": "Anchor it with ^ and $ so the whole string has to match, not just part of it."
  }
]
```
