"Experienced" person here. I've done a parser generator grammar or two. I've implemented several parsers.
I mostly agree, but it depends on your use case.
Most people writing parsers are doing so for a bespoke language, usually for config of an internal-use-only program or something like that. Such parsers won't be great, but they will work for people that:
1. Know the language, or
2. Are expected to know the language.
In that case, I agree with Guy Steele.
Why these two caveats? Because as people have said, error reporting from the parser will be subpar. This includes hand-written recursive descent ones. So knowing the language is important.
(The second item is specifically for situations where new employees might not know the language yet, but it's part of their job, so they will learn it.)
This is, by far, the greatest amount of parsers in use. I would estimate 99.5% or more.
Of course, most people don't think about those parsers; they think about the hardened parsers for general-purpose languages in wide use, but while they get a lot of attention, they are few and far between.
Guy Steele's advice doesn't apply as well to such situations. For me, I wrote a parser for an existing language. I had to write the parser to that, and that language had a lot of baggage. It made no sense in that case.
For the parser I'm currently writing, it also makes no sense. This is for what I hope to become a widely-used general purpose language. Sure, I could try, but I don't think it would work.
To see why, look at perhaps the two newest big languages: Rust and Zig. Both are not that old (Rust 15 years-ish, Zig 8 years-ish?). And yet, I suspect both use handwritten parsers. I suspect both are recursive descent.
Why is this? These languages shouldn't have that much legacy baggage, like C or C++, right?
It doesn't matter. General-purpose languages have to be everything to all possible users. This means they will have to be able to do some crazy things. And to do crazy things with plain text, well, you need as much power as you can get.
This is why general-purpose languages basically always become context-sensitive. Context-sensitivity allows a recursive descent parser to bring the full power of Turing-completeness to bear on a problem.
Guy Steele also alludes to this in his classic "Growing a Language" talk. [1] He talks about how you need to create a language that users can grow. There are easy ways to do this (like having functions), but the easy ways are all limited. If there is something that must be a language feature, like format printing [2], it can't be added by users.
Adding those other features usually require context-sensitivity. Format printing definitely does when it allows you to use variable names in the format string.
But what if you make a language that any user can extend in any way, even if it's not easy? The result is my language, Yao. (Example at [3].) It's context-sensitive because it has to be.
So basically, Guy Steele is right for everybody but the creators of big general-purpose languages. But most people think he's talking about the big general-purpose languages. He might have been, but if he was, he's wrong.
That doesn't mean that I don't consider the ease of parsing when I add a feature. I definitely do. But I come at it from the human perspective, how readable it is to a human. This tends to give me much the same thing while giving me power.
[1]: https://www.youtube.com/watch?v=lw6TaiXzHAE
[2]: https://brevzin.github.io/c++/2023/01/02/rust-cpp-format/
[3]: https://git.yzena.com/Yzena/Yc/src/commit/66185f4ff4fb779e04...