A parser can be a row of small functions.
Florian Schindler's parser-combinator tutorial starts with a direct TypeScript type. A parser receives a string and returns a value with the rest of that string. The tutorial builds sequence, map, and repeat operations by combining parsers into new parsers. Its larger example turns ping output into named data.[1]
That design fits the gap between a dense regular expression and a full grammar tool. Each parser owns one rule. Sequence passes the remaining input to the next rule. Map turns an intermediate tuple into the data shape the caller needs.
The remaining input is part of the result, not cleanup.
Consumption changes the meaning of failure.
The original Parsec paper distinguishes failure before input consumption from failure after consumption. Its left-biased choice operation tries the next alternative only when the first parser has not consumed input. This restriction avoids retaining the original input for arbitrary backtracking. It also helps the parser report the position and legal grammar productions at the failure point.[2]
This is an operational rule, not a reason to make every parser commit early. It means parser authors must decide where alternatives belong and where lookahead is needed. The paper also states a known limit. A left-recursive parser calls itself before consuming input and loops unless the grammar is rewritten or a suitable combinator handles the pattern.
Keep state and errors inspectable.
Megaparsec exposes the remaining input, processed-token offset, source position, and delayed parse errors in its parser state. Its main interface also names operations for labels, lookahead, controlled retry, recovery, full-input checks, and custom failures.[3]
A useful development view should mirror those facts. Show the current offset. Show the unconsumed suffix. Name the rule that failed and what it expected. Require an end-of-input check when the format must consume the whole string. A green parsed object without that last check can accept a valid prefix and ignore a bad tail.
Test the grammar at its seams.
- Keep one valid fixture with the shortest legal input.
- Keep one valid fixture with the longest or most varied legal fields.
- Cut the input after every stage and check the reported expectation.
- Add a valid prefix followed by junk and require the full-input check to fail.
- Exercise every alternative at the first token where its route differs.
- If the parser retries, record whether the failed branch consumed input first.