skipnothing/Regex Engines

Literals & Character Classes

You wrote [0-9] to match a digit and it worked, so you wrote [cat] to match the word 'cat', and it matched a single letter instead. Brackets count characters differently than you expect.

  • ▸[cat] does not match 'cat'. It matches one character: c, a, or t.
  • ▸Widen the brackets from [a] to [a-z] to . and a match still eats exactly one character.
  • ▸\w quietly includes the underscore and the digits, not just letters.
01

One Character, Matched Exactly

The pattern 5 matches the character 5. The engine looks at one position in the input and asks a single question: is this character a 5? If yes, it consumes that one character and moves to the next position.

Run 5 against the phone number 555-0142. The engine reads left to right. Position 0 is 5, match, advance one character. Position 1 is 5, match again. Position 2 is 5, match. Position 3 is -, no match. Every successful match steps forward by exactly one character of input.

This is the floor everything else builds on: one piece of the pattern lines up against one character of the input. A pattern that spells cat is just three of these questions in a row, is it c, then is it a, then is it t. It gets interesting the moment a single position is allowed to accept more than one specific character. That is the next move.

A SINGLE CHARACTER WALKING THE INPUT
02

A Set at One Position

Put characters inside square brackets and that one position stops demanding a specific character. [abc] accepts any one of a, b, or c. Still one position, still one character consumed. The brackets widen which character is allowed there, not how many get matched.

Here is the trap: [cat] does not match the string cat. Run it against cat and it matches only c, the first character that satisfies 'a, or c, or t'. Order inside the brackets is irrelevant and repeats are ignored, so [cat], [tac], and [catt] all describe the same single-character choice.

When the allowed characters are contiguous, a hyphen writes them as a range. [0-9] is every digit from 0 to 9; [a-z] is every lowercase letter; [a-zA-Z0-9] stacks three spans into one position. A range still matches one character wide: [0-9] against 555 matches a single 5.

So the spectrum opens up. A literal accepts one fixed character; brackets accept one of a listed set; a range accepts one of a contiguous span. The number of characters consumed never changes. Only the width of what that one position will accept.

WIDEN THE SET, THE COUNT STAYS ONE
03

Everything Except This Set

Drop a caret just inside the opening bracket and the character class flips. [^0-9] matches one character that is not a digit. The one-position, one-character rule is unchanged; only the accepted set is inverted. Everything the plain class rejected, this version accepts, and the other way around.

Run [^0-9] against 555-0142. It skips every digit and matches the -, the single character that is not 0 through 9. The caret only flips the set when it sits first inside the brackets. [0-9^] is an ordinary class that simply includes a literal ^ among its members.

Negation is how a pattern says 'anything but'. [^aeiou] is any one non-vowel; [^ ] is any one character that is not a space. Same machine, same single character read at the position, with the accept and reject sides swapped.

FLIP THE ACCEPT AND REJECT SIDES
04

Shorthands for Common Sets

Some sets come up so often they get a one-token name. \d is the character class [0-9], every digit. \w is [A-Za-z0-9_], a 'word' character. \s is whitespace: a space, a tab, a newline, and a few cousins. Write \d\d\d and you have matched three digits without spelling out [0-9] three times.

Two of these surprise people. \w includes the digits and the underscore, not just letters, which is why user_42 is entirely word characters and [\w-] is the usual pattern for 'an identifier character or a hyphen'. And the breadth of \d and \w depends on the engine: by default some match only ASCII, while others count digits and letters from every script and narrow to [0-9] only when you ask for ASCII mode.

Each lowercase token has an uppercase twin that negates it. \D is any non-digit, \W any non-word character, \S any non-whitespace, exactly the negated character class you would otherwise write by hand.

EXPAND A SHORTHAND INTO ITS SET

Keep going, sign up to unlock the rest

1 more part in this topic, plus 11+ more topics in Regex Engines.

Sign up, it's freeSee the full Regex Engines
Anatomy of a Pattern0/3
#2 Anchors & Alternation