Quite likely, implementing your grammar as multiple interdependent parsers is only going to make your code more complicated. The data flow will become less obvious, and you will duplicate some behaviour. It is OK if a class is large.
However, many languages can easily be split into different levels, and handling these separately could be sensible. For example:
- you could extract tokenization from the main parser. C has a separate tokenization and preprocessor phase.
- You could do some post-processing in a separate phase that builds the final AST. This is particularly sensible if your parser also checks the semantics, e.g. resolving symbol definitions or doing type checks. Those should be separate from parsing.
- If your language has a strong statement–expression dichtomy, you could have separate parsers for each, with the statement parser calling into the expression parser as needed. Markdown is an example of a language with a line-based grammar (indentation) over a block-level grammar (paragraphs, headlines, lists) over an inline grammar (emphasis, links). Some parsers use a simple recursive descent approach for statement level syntax such as control flow constructs or top level definitions, but switch to an LR algorithm for expressions to properly handle precedence and associativity.
I have found it to be rather advantageous to extract low-level parsing operations into a separate class: handling the input buffer, checking lookaheads, extracting tokens, handling errors, is all better done by a custom class rather than relying on the facilities provided by the language (in particular,
std::istream is unsuitable for most problems). If you are using a parsing algorithm other than Recursive Descent, you should also handle these operations in a separate class.