Take the parts you describe:
- First character must be a letter
It starts ^[A-Za-z]
- They can also use numbers but it isn't the first
[A-Za-z0-9]*
in the middle
- They can use fullstop/underscore once but not at the end of a username
Optionally, have an underscore followed by at least one of the other valid characters ([._][A-Za-z]+)?
Put it all together and you have: /^[A-Za-z][A-Za-z0-9]*([._][A-Za-z0-9]+)?$/
. Using the case '/i' insensitive flag lets you drop the 'A-Z': /^[a-z][a-z0-9]*([._][a-z0-9]+)?$/i
.
Regular expressions are equivalent to deterministic finite automatons (DFA). Studying them can help get a grasp on regular expressions. In particular, transitioning between states is directly applicable to your three points. The (reduced) state diagram for this language should be quite easy to understand:
The DFA:
- starts in state 0
- transitions to state 1 if a letter is encountered,
- stays in state 1 as long as letters and digits are encountered,
- switches to state 2 if a period or underscore are encountered,
- switches to state 3 if a letters and digit is encountered,
- stays in state 3 as long as letters and digits are encountered,
- accepts the string if it ends in state 1 or 3
I call it "reduced" because there's a fifth, non-accepting state and edges leading to it that aren't shown. Basically, if a character is encountered other than one listed, the DFA transitions to the fifth, non-accepting state and stays there.