01 · Problem
What is it for?
Parsing must preserve input position, errors and progress while supporting alternatives and repetition.
02 · Mental model
How should you think about it?
A parser maps a cursor state to either a new state with a result or an explicit error.
Composes byte-oriented parsers with slice-aware inputs and progress-checked repetition. The library has a broad combinator surface; the regression suite focuses on composition, view boundaries and termination.
03 · Control flow
What happens in which order?
- Create an input view with the correct offset and length.
- Run a primitive parser.
- Compose results or backtrack on failure.
- Require forward progress inside repetition.
- Return the final cursor and result.
04 · Building blocks
The important contracts
Parser / map / chain / runCompose and execute parser state transformations.str / char / regex / sequenceOf / choice / many / sepByBuild primitives, alternatives and progress-checked repetition.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Create an input view with the correct offset and length.
- Run a primitive parser.
- Compose results or backtrack on failure.
- Require forward progress inside repetition.
- Return the final cursor and result.
type State = { input: Uint8Array; offset: number };
type Result<T> =
| { ok: true; value: T; state: State }
| { ok: false; offset: number; expected: Set<string> };
class Parser<T> {
constructor(readonly parse: (state: State) => Result<T>) {}
map<U>(fn: (value: T) => U) {
return new Parser<U>(state => {
const result = this.parse(state);
return result.ok ? { ...result, value: fn(result.value) } : result;
});
}
}06 · Verify
What should you test?
- Reject repetition of a parser that consumes no input.
- Respect sliced typed-array boundaries.
- Check UTF-8 characters and final cursor positions.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
Reject repetition of a parser that consumes no input.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- Backtracking can become expensive.
- Character counts and byte counts differ.
- Errors should retain useful location and context.
The library has a broad combinator surface; the regression suite focuses on composition, view boundaries and termination.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/parser-combinator/examples/basic.ts