By "good theory," I mean being able to know properties of the parser as a result of the formalism.
For example, one can prove that any yacc-generated parser never infinite loops and runs in O(n) (at least for the actual parsing; you can write custom code in actions that runs when a construct has been parsed, and of course you can write whatever you want there).
If you hand-write a grammar like the following, you'll end up with an infinite loop without restructuring the grammar; such restructuring is of course much easier if you have written the grammar out, rather than having it implicitly exist in code.
Expr ::= Expr '[' Expr ']'
Expr ::= Name
Expr ::= Int
If you try to naively parse the following, you'll never parse the marked production either. Something like yacc will warn you about this, while an ordinary compiler compiling a hand-written parser won't.
Stmt ::= Expr ';'
Stmt ::= Name '=' Expr ';'
Stmt ::= Expr '[' Expr ']' '=' Expr ';'
Expr ::= Expr '[' Expr ']'
Expr ::= Name
Expr ::= Int
[0] is a good introduction to regex matching, [1] is a good introduction to the algorithm yacc uses, if you want to learn either.
[0]: https://swtch.com/~rsc/regexp/regexp1.html
[1]: https://web.archive.org/web/20210507215636/http://web.cs.dal...