Combinators are functions that create or combine parsers. Parserator provides a rich set of combinators organized by their purpose.
Character Parsers
char()
Matches a single specific character:
const char: <T extends string>(ch: T) => Parser<T>
const openParen = char('(');
openParen.parse("(abc)"); // succeeds with "("
openParen.parse("abc"); // fails
string()
Matches an exact string:
const string: (str: string) => Parser<string>
const hello = string("hello");
hello.parse("hello world"); // succeeds with "hello"
hello.parse("goodbye"); // fails
narrowedString()
Like string() but preserves the literal type:
const narrowedString: <const T extends string>(str: T) => Parser<T>
const hello = narrowedString("hello"); // Parser<"hello">
regex()
Matches input against a regular expression:
const regex: (re: RegExp) => Parser<string>
const identifier = regex(/[a-zA-Z_][a-zA-Z0-9_]*/);
identifier.parse("myVar123"); // succeeds with "myVar123"
const number = regex(/-?[0-9]+/).map(Number);
number.parse("-42"); // succeeds with -42
anyChar()
Matches any single character:
const anyChar: () => Parser<string>
anyChar().parse("a"); // succeeds with "a"
anyChar().parse(""); // fails at end of input
notChar()
Matches any character except the specified one:
const notChar: (ch: string) => Parser<string>
const notQuote = notChar('"');
notQuote.parse("a"); // succeeds with "a"
notQuote.parse('"'); // fails
alphabet
Matches any alphabetic character (a-z, A-Z):
const alphabet: Parser<string>
alphabet.parse("a"); // succeeds with "a"
alphabet.parse("1"); // fails
digit
Matches any digit character (0-9):
const digit: Parser<string>
digit.parse("5"); // succeeds with "5"
digit.parse("a"); // fails
Repetition Combinators
many()
Matches zero or more occurrences:
const many: <T>(parser: Parser<T>) => Parser<T[]>
const digits = many(digit);
digits.parse("123"); // succeeds with ["1", "2", "3"]
digits.parse(""); // succeeds with []
digits.parse("abc"); // succeeds with []
Alias: many0()
many1()
Matches one or more occurrences:
const many1: <T>(parser: Parser<T>) => Parser<T[]>
const digits = many1(digit);
digits.parse("123"); // succeeds with ["1", "2", "3"]
digits.parse(""); // fails (requires at least one)
manyN()
Matches at least n occurrences:
const manyN: <T>(parser: Parser<T>, n: number) => Parser<T[]>
const atLeast3 = manyN(digit, 3);
atLeast3.parse("1234"); // succeeds with ["1", "2", "3", "4"]
atLeast3.parse("12"); // fails
count()
Matches exactly n occurrences:
const count: <T>(n: number, parser: Parser<T>) => Parser<T[]>
const threeDigits = count(3, digit);
threeDigits.parse("123"); // succeeds with ["1", "2", "3"]
threeDigits.parse("12"); // fails
threeDigits.parse("1234"); // succeeds with ["1", "2", "3"], leaves "4"
skipMany0(), skipMany1()
Skips zero or more / one or more occurrences:
const skipMany0: <T>(parser: Parser<T>) => Parser<undefined>
const skipMany1: <T>(parser: Parser<T>) => Parser<undefined>
const skipWhitespace = skipMany0(regex(/\s/));
Alternative Combinators
or()
Tries parsers in order until one succeeds:
const or: <Parsers extends Parser<any>[]>(
...parsers: Parsers
) => Parser<Parsers[number] extends Parser<infer T> ? T : never>
const value = or(
string("true").map(() => true),
string("false").map(() => false),
number()
);
value.parse("true"); // succeeds with true
value.parse("false"); // succeeds with false
value.parse("42"); // succeeds with 42
The or() combinator is commit-aware. If any parser sets the committed flag during parsing, no further alternatives will be tried.
optional()
Makes a parser optional:
const optional: <T>(parser: Parser<T>) => Parser<T | undefined>
const sign = optional(or(char('+'), char('-')));
sign.parse("+"); // succeeds with "+"
sign.parse("123"); // succeeds with undefined
Sequence Combinators
sequence()
Runs multiple parsers in sequence and returns all results:
const sequence: <const T extends any[]>(
parsers: T
) => Parser<SequenceOutput<T>>
const date = sequence([digit, digit, char('/'), digit, digit]);
date.parse("12/25"); // succeeds with ["1", "2", "/", "2", "5"]
sepBy()
Parses zero or more occurrences separated by a separator:
const sepBy: <S, T>(
parser: Parser<T>,
sepParser: Parser<S>
) => Parser<T[]>
const numbers = sepBy(number(), char(','));
numbers.parse("1,2,3"); // succeeds with [1, 2, 3]
numbers.parse(""); // succeeds with []
sepBy1()
Parses one or more occurrences separated by a separator:
const sepBy1: <S, T>(
parser: Parser<T>,
sepParser: Parser<S>
) => Parser<T[]>
const numbers = sepBy1(number(), char(','));
numbers.parse("1,2,3"); // succeeds with [1, 2, 3]
numbers.parse(""); // fails (requires at least one)
sepEndBy()
Parses a list with optional trailing separator:
const sepEndBy: <S, T>(
parser: Parser<T>,
sep: Parser<S>
) => Parser<T[]>
const list = sepEndBy(number(), char(','));
list.parse("1,2,3"); // succeeds with [1, 2, 3]
list.parse("1,2,3,"); // succeeds with [1, 2, 3] (trailing comma OK)
between()
Parses content between two delimiters:
const between: <T>(
start: Parser<any>,
end: Parser<any>,
parser: Parser<T>
) => Parser<T>
const parens = between(char('('), char(')'), number());
parens.parse("(42)"); // succeeds with 42
const quoted = between(char('"'), char('"'), regex(/[^"]*/))
quoted.parse('"hello"'); // succeeds with "hello"
Lookahead Combinators
lookahead()
Looks ahead without consuming input:
const lookahead: <T>(parser: Parser<T>) => Parser<T | undefined>
const nextIsDigit = lookahead(digit);
// Succeeds if next char is a digit, but doesn't consume it
notFollowedBy()
Succeeds only if the given parser fails:
const notFollowedBy: <T>(parser: Parser<T>) => Parser<boolean>
const notA = notFollowedBy(char('a'));
notA.parse("bcd"); // succeeds
notA.parse("abc"); // fails
Utility Combinators
takeUntil()
Takes input until the given parser succeeds:
const takeUntil: <T>(parser: Parser<T>) => Parser<string>
const untilQuote = takeUntil(char('"'));
untilQuote.parse('hello"world'); // succeeds with "hello"
takeUpto()
Takes input until before the given parser would succeed:
const takeUpto: <T>(parser: Parser<T>) => Parser<string>
skipUntil()
Skips input until the given parser succeeds:
const skipUntil: <T>(parser: Parser<T>) => Parser<undefined>
eof
Matches only at the end of input:
const complete = identifier().thenDiscard(eof);
complete.parse("hello"); // succeeds
complete.parse("hello world"); // fails (not at end)
Control Flow Combinators
commit()
Prevents backtracking after this point:
const commit: () => Parser<void>
import { parser, commit, keyword, char } from 'parserator';
const ifStatement = parser(function* () {
yield* keyword("if");
yield* commit(); // No backtracking after this
yield* char('(').expect("opening parenthesis");
// ...
});
Alias: cut()
See the Error Handling guide for more details.
atomic()
Makes a parser all-or-nothing (resets state on failure):
const atomic: <T>(parser: Parser<T>) => Parser<T>
const keyword = atomic(regex(/[a-z]+/));
// If it fails, no input is consumed
Position Combinators
position
Gets the current source position:
const position: Parser<SourcePosition>
type SourcePosition = {
line: number;
column: number;
offset: number;
}
const withPosition = parser(function* () {
const start = yield* position;
const value = yield* identifier();
const end = yield* position;
return { value, start, end };
});
Whitespace Helpers
skipSpaces
Skips any number of space characters:
const skipSpaces: Parser<undefined>
const token = <T>(p: Parser<T>) => p.trimLeft(skipSpaces);