Parsers are composed using combinators - functions that create or combine parsers. Parserator provides a comprehensive set of combinators organized into logical categories.
Character Matching
char
Matches a single specific character.
const char: <T extends string>(ch: T) => Parser<T>
The character to match (must be a single character)
Example:
const parser = char('a')
parser.run('abc') // Right(['a', {...}])
parser.run('xyz') // Left(error)
string
Matches an exact string in the input.
const string: (str: string) => Parser<string>
Example:
const parser = string("hello")
parser.run("hello world") // Right(["hello", {...}])
parser.run("goodbye") // Left(error)
narrowedString
Matches an exact string literal type, preserving the literal type information.
const narrowedString: <const T extends string>(str: T) => Parser<T>
The string literal to match
Example:
const parser = narrowedString("hello") // Parser<"hello">
parser.run("hello world") // Right(["hello", {...}])
alphabet
Matches any single alphabetic character (a-z, A-Z).
const alphabet: Parser<string>
Example:
alphabet.run("abc") // Right(["a", {...}])
alphabet.run("123") // Left(error)
digit
Matches any single digit character (0-9).
const digit: Parser<string>
Example:
digit.run("123") // Right(["1", {...}])
digit.run("abc") // Left(error)
anyChar
Matches any single character.
const anyChar: () => Parser<string>
Example:
anyChar().run("hello") // Right(["h", {...}])
notChar
Matches any character except the specified one.
const notChar: (ch: string) => Parser<string>
The character to exclude (must be a single character)
Example:
const notQuote = notChar('"')
notQuote.parse('a') // Success: 'a'
notQuote.parse('"') // Error
regex
Matches input against a regular expression. The regex must match at the start of the input.
const regex: (re: RegExp) => Parser<string>
The regular expression to match against
Example:
const number = regex(/[0-9]+/)
number.run("123abc") // Right(["123", {...}])
Repetition
many
Matches zero or more occurrences of a parser.
const many: <T>(parser: Parser<T>) => Parser<T[]>
Example:
const digits = many(digit)
digits.run("123abc") // Right([["1", "2", "3"], {...}])
digits.run("abc") // Right([[], {...}]) - zero matches is OK
many0
Matches zero or more occurrences (alias for many).
const many0: <S, T>(parser: Parser<T>, separator?: Parser<S>) => Parser<T[]>
Optional separator parser between occurrences
many1
Matches one or more occurrences of a parser.
const many1: <S, T>(parser: Parser<T>, separator?: Parser<S>) => Parser<T[]>
Optional separator parser between occurrences
Example:
const digits = many1(digit)
digits.run("123abc") // Right([["1", "2", "3"], {...}])
digits.run("abc") // Left(error) - at least one required
manyN
Matches at least n occurrences of a parser.
const manyN: <S, T>(parser: Parser<T>, n: number, separator?: Parser<S>) => Parser<T[]>
Minimum number of required repetitions
Optional separator parser between occurrences
manyNExact
Matches exactly n occurrences of a parser.
const manyNExact: <S, T>(par: Parser<T>, n: number, separator?: Parser<S>) => Parser<T[]>
Exact number of required repetitions
Optional separator parser between occurrences
count
Parses exactly n occurrences of a parser.
const count: <T>(n: number, par: Parser<T>) => Parser<T[]>
The exact number of occurrences
Example:
const threeDigits = count(3, digit)
threeDigits.parse("123") // Success: ['1', '2', '3']
threeDigits.parse("12") // Error: not enough matches
skipMany0
Skips zero or more occurrences of a parser without collecting results.
const skipMany0: <T>(parser: Parser<T>) => Parser<undefined>
skipMany1
Skips one or more occurrences of a parser without collecting results.
const skipMany1: <T>(parser: Parser<T>) => Parser<undefined>
The parser to skip (requires at least one match)
skipManyN
Skips exactly n occurrences of a parser.
const skipManyN: <T>(parser: Parser<T>, n: number) => Parser<undefined>
Number of required repetitions to skip
Alternatives
Tries multiple parsers in order until one succeeds. Supports commit-aware parsing for better error messages.
const or: <Parsers extends Parser<any>[]>(
...parsers: Parsers
) => Parser<Parsers[number] extends Parser<infer T> ? T : never>
Array of parsers to try in order
Example:
const value = or(
numberLiteral,
stringLiteral,
booleanLiteral
)
With commit for better errors:
const statement = or(
parser(function* () {
yield* keyword("if")
yield* commit() // No backtracking after this
yield* char('(').expect("opening parenthesis")
// ...
}),
whileStatement,
assignment
)
optional
Makes a parser optional. Returns undefined if the parser fails.
const optional: <T>(parser: Parser<T>) => Parser<T | undefined>
The parser to make optional
Example:
const sign = optional(char('-'))
sign.run('-5') // Right(['-', {...}])
sign.run('5') // Right([undefined, {...}])
Sequences
sequence
Runs multiple parsers in sequence and returns all results as a tuple.
const sequence: <const T extends any[]>(
parsers: T
) => Parser<SequenceOutput<T>>
Array of parsers to run in sequence
Example:
const parser = sequence([digit, char('-'), digit])
parser.run('1-2') // Right([['1', '-', '2'], {...}])
sepBy
Matches zero or more occurrences of elements separated by a separator.
const sepBy: <S, T>(parser: Parser<T>, sepParser: Parser<S>) => Parser<T[]>
Parser for the separator between elements
Example:
const parser = sepBy(digit, char(','))
parser.run("1,2,3") // Right([["1", "2", "3"], {...}])
parser.run("") // Right([[], {...}])
sepBy1
Matches one or more occurrences separated by a separator. Requires at least one match.
const sepBy1: <S, T>(par: Parser<T>, sepParser: Parser<S>) => Parser<T[]>
Example:
const numbers = sepBy1(number, char(','))
numbers.parse("1,2,3") // Success: [1, 2, 3]
numbers.parse("") // Error: Expected at least one element
sepEndBy
Parses a list with optional trailing separator.
const sepEndBy: <S, T>(par: Parser<T>, sep: Parser<S>) => Parser<T[]>
Example:
const list = sepEndBy(number, char(','))
list.parse("1,2,3") // Success: [1, 2, 3]
list.parse("1,2,3,") // Success: [1, 2, 3] (trailing comma OK)
between
Matches content between two delimiters.
const between: <T>(
start: Parser<any>,
end: Parser<any>,
par: Parser<T>
) => Parser<T>
Parser for content between delimiters
Example:
const parser = between(char('('), char(')'), digit)
parser.run('(5)') // Right(['5', {...}])
parser.run('5') // Left(error)
zip
Runs two parsers in sequence and returns both results as a tuple.
const zip: <A, B>(parserA: Parser<A>, parserB: Parser<B>) => Parser<[A, B]>
then / zipRight
Runs two parsers in sequence, keeping only the second result.
const then: <A, B>(parserA: Parser<A>, parserB: Parser<B>) => Parser<B>
const zipRight: <A, B>(parserA: Parser<A>, parserB: Parser<B>) => Parser<B>
First parser (result discarded)
Second parser (result kept)
thenDiscard / zipLeft
Runs two parsers in sequence, keeping only the first result.
const thenDiscard: <A, B>(parserA: Parser<A>, parserB: Parser<B>) => Parser<A>
const zipLeft: <A, B>(parserA: Parser<A>, parserB: Parser<B>) => Parser<A>
First parser (result kept)
Second parser (result discarded)
Lookahead
lookahead
Looks ahead in the input without consuming any input.
const lookahead: <T>(par: Parser<T>) => Parser<T | undefined>
The parser to look ahead with
Example:
const parser = lookahead(char('a'))
parser.run('abc') // Right(['a', {...}])
// Input position remains at 'abc', 'a' is not consumed
notFollowedBy
Succeeds only if the given parser fails to match.
const notFollowedBy: <T>(par: Parser<T>) => Parser<boolean>
The parser that should not match
Example:
const notA = notFollowedBy(char('a'))
notA.run('bcd') // Right([true, {...}]) - Succeeds because 'a' is not found
notA.run('abc') // Left(error) - Fails because 'a' is found
Control Flow
commit / cut
Commits to the current parsing path, preventing backtracking. After calling commit(), if parsing fails, the parser won’t try alternatives in or combinators.
const commit: () => Parser<void>
const cut: () => Parser<void> // Alias using Prolog-style naming
Example:
const ifStatement = parser(function* () {
yield* keyword("if")
yield* commit() // No backtracking after this point
yield* char('(').expect("opening parenthesis after 'if'")
const condition = yield* expression
yield* char(')').expect("closing parenthesis")
const body = yield* block
return { type: "if", condition, body }
})
atomic
Creates an atomic parser that either fully succeeds or resets to the original state.
const atomic: <T>(parser: Parser<T>) => Parser<T>
The parser to make atomic
Example:
const functionCall = atomic(
parser(function* () {
const name = yield* identifier
yield* char('(')
const args = yield* sepBy(expression, char(','))
yield* char(')')
return { name, args }
})
)
Utilities
skipUntil
Skips input until the given parser succeeds.
const skipUntil: <T>(parser: Parser<T>) => Parser<undefined>
takeUntil
Takes input until the given parser succeeds.
const takeUntil: <T>(parser: Parser<T>) => Parser<string>
takeUpto
Takes input until the given parser would succeed, without consuming the parser.
const takeUpto: <T>(parser: Parser<T>) => Parser<string>
The parser to look for (not consumed)
parseUntilChar
Takes input until the given character is found.
const parseUntilChar: (char: string) => Parser<string>
The character to look for
Example:
const untilQuote = parseUntilChar('"')
untilQuote.run('hello"world') // Right(['hello', {...}])
skipSpaces
Skips any number of space characters.
const skipSpaces: Parser<undefined>
Example:
skipSpaces.run(' hello') // Right([undefined, {...}]) - positioned at 'hello'
eof
Succeeds only at the end of input.
Example:
const parser = string("hello").then(eof)
parser.parse("hello") // Success
parser.parse("hello world") // Error: Expected end of input
position
Returns the current source position.
const position: Parser<SourcePosition>
Example:
const pos = yield* position
// pos: { offset: number, line: number, column: number }