Overview
The Parser<T> class is the fundamental type in Parserator that represents a parser combinator. A parser is a function that takes an input state and produces either a successful parse result with the remaining input state, or an error describing why the parse failed.
Parsers can be composed using various combinators to build complex parsers from simple building blocks.
import { Parser } from 'parserator';
Constructor
run
(state: ParserState) => ParserOutput<T>
required
The main parsing function that processes input state and returns a parser output
runFast
(ctx: MutableParserContext) => FastPathResult<T>
Optional optimized fast-path parsing function for better performance
Running Parsers
parse
Runs the parser on the given input string and returns the full parser output.
parse(input: string): ParserOutput<T>
A parser output containing both the result (success or error) and final state
const parser = string("hello");
const output = parser.parse("hello world");
// output.result contains Either.right("hello")
// output.state contains remaining input " world" and position info
parseOrError
Runs the parser on the given input and returns either the parsed value or error bundle.
parseOrError(input: string): T | ParseErrorBundle
The successfully parsed value of type T, or a ParseErrorBundle on failure
const parser = number();
const result = parser.parseOrError("42");
if (result instanceof ParseErrorBundle) {
console.error(result.format());
} else {
console.log(result); // 42
}
parseOrThrow
Runs the parser on the given input and returns the parsed value or throws an error.
parseOrThrow(input: string): T
The successfully parsed value of type T
const parser = number();
try {
const value = parser.parseOrThrow("42");
console.log(value); // 42
} catch (error) {
if (error instanceof ParseErrorBundle) {
console.error(error.format());
}
}
Static Methods
Parser.lift
Creates a parser that always succeeds with the given value without consuming any input.
static lift<A>(a: A): Parser<A>
The value to lift into the parser context
A parser that always succeeds with the given value
const always42 = Parser.lift(42);
always42.parse("any input"); // succeeds with 42
// Useful for providing default values
const parseNumberOrDefault = number.or(Parser.lift(0));
// Can be used to inject values in parser chains
const parser = parser(function* () {
const name = yield* identifier;
const separator = yield* Parser.lift(":");
const value = yield* number;
return { name, separator, value };
});
Parser.liftA2
Lifts a binary function into the parser context, applying it to the results of two parsers.
static liftA2<A, B, C>(
ma: Parser<A>,
mb: Parser<B>,
f: (a: A, b: B) => C
): Parser<C>
f
(a: A, b: B) => C
required
A function that takes the results of both parsers and produces a new value
A parser that applies the function to the results of both input parsers
// Combine two parsed values with a function
const parsePoint = Parser.liftA2(
number,
number.trimLeft(comma),
(x, y) => ({ x, y })
);
parsePoint.parse("10, 20"); // succeeds with { x: 10, y: 20 }
// Build a data structure from multiple parsers
const parsePerson = Parser.liftA2(
identifier,
number.trimLeft(colon),
(name, age) => ({ name, age })
);
parsePerson.parse("John:30"); // succeeds with { name: "John", age: 30 }
Parser.ap
Applies a parser that produces a function to a parser that produces a value.
static ap<A, B>(ma: Parser<A>, mf: Parser<(_: A) => B>): Parser<B>
A parser that produces a value
mf
Parser<(_: A) => B>
required
A parser that produces a function from that value type to another type
A parser that applies the parsed function to the parsed value
// Parse a function name and apply it
const parseFn = choice([
string("double").map(() => (x: number) => x * 2),
string("square").map(() => (x: number) => x * x)
]);
const result = Parser.ap(number, parseFn.trimLeft(space));
result.parse("5 double"); // succeeds with 10
result.parse("5 square"); // succeeds with 25
Parser.pure
Creates a parser that always succeeds with the given value without consuming input. This is an alias for Parser.lift.
static pure<A>(a: A): Parser<A>
Parser.fatal
Creates a parser that always fails with a fatal error.
static fatal(message: string): Parser<never>
The error message to display
A parser that always fails with a fatal error
Fatal errors are non-recoverable and prevent backtracking in choice combinators.
const number = regex(/-?[0-9]+/).map(Number);
const parsePositive = number.flatMap(n =>
n > 0 ? Parser.lift(n) : Parser.fatal("Expected positive number")
);
Parser.lazy
Creates a new parser that lazily evaluates the given function. This is useful for creating recursive parsers.
static lazy<T>(fn: () => Parser<T>): Parser<T>
A function that returns a parser
A new parser that evaluates the function when parsing
// Create a recursive parser for nested parentheses
const parens: Parser<string> = Parser.lazy(() =>
between(
char('('),
char(')'),
parens
)
);
Parser.gen
Creates a parser from a generator function, enabling imperative-style parser composition.
static gen<T>(f: () => Generator<Parser<any>, T, any>): Parser<T>
f
() => Generator<Parser<any>, T, any>
required
A generator function that yields parsers and returns the final value
A new parser that executes the generator
map
Transforms the result of this parser by applying a function to the parsed value.
map<B>(f: (a: T) => B): Parser<B>
A function that transforms the parsed value
A new parser that produces the transformed value
// Parse a number and double it
const doubled = number().map(n => n * 2);
doubled.parse("21"); // succeeds with 42
// Parse a string and get its length
const stringLength = quoted('"').map(s => s.length);
stringLength.parse('"hello"'); // succeeds with 5
// Chain multiple transformations
const parser = identifier()
.map(s => s.toUpperCase())
.map(s => ({ name: s }));
parser.parse("hello"); // succeeds with { name: "HELLO" }
flatMap
Chains this parser with another parser that depends on the result of this one.
flatMap<B>(f: (a: T) => Parser<B>): Parser<B>
f
(a: T) => Parser<B>
required
A function that takes the parsed value and returns a new parser
A new parser that runs the second parser after the first succeeds
// Parse a number and then that many 'a' characters
const parser = number().flatMap(n =>
string('a'.repeat(n))
);
parser.parse("3aaa"); // succeeds with "aaa"
// Parse a type annotation and return appropriate parser
const typeParser = identifier().flatMap(type => {
switch(type) {
case "int": return number();
case "string": return quoted('"');
default: return Parser.fail({ message: `Unknown type: ${type}` });
}
});
// Validate parsed values
const positiveNumber = number().flatMap(n =>
n > 0
? Parser.lift(n)
: Parser.fail({ message: "Expected positive number" })
);
Sequencing Methods
zip
Combines this parser with another parser, returning both results as a tuple.
zip<B>(parserB: Parser<B>): Parser<[T, B]>
The second parser to run after this one
A parser that produces a tuple of both results
// Parse a coordinate pair
const coordinate = number().zip(number().trimLeft(comma));
coordinate.parse("10, 20"); // succeeds with [10, 20]
// Parse a key-value pair
const keyValue = identifier().zip(number().trimLeft(colon));
keyValue.parse("age:30"); // succeeds with ["age", 30]
then / zipRight
Sequences this parser with another, keeping only the second result.
then<B>(parserB: Parser<B>): Parser<B>
zipRight<B>(parserB: Parser<B>): Parser<B> // alias
The parser whose result will be kept
A parser that produces only the second result
// Parse a value after a label
const labeledValue = string("value:").then(number());
labeledValue.parse("value:42"); // succeeds with 42
// Skip whitespace before parsing
const trimmedNumber = whitespace().then(number());
trimmedNumber.parse(" 123"); // succeeds with 123
thenDiscard / zipLeft
Sequences this parser with another, keeping only the first result.
thenDiscard<B>(parserB: Parser<B>): Parser<T>
zipLeft<B>(parserB: Parser<B>): Parser<T> // alias
The parser to run but whose result will be discarded
A parser that produces only the first result
// Parse a statement and discard the semicolon
const statement = expression().thenDiscard(char(';'));
statement.parse("x + 1;"); // succeeds with the expression, semicolon discarded
// Parse array elements and discard separators
const element = number().thenDiscard(optional(char(',')));
Whitespace Handling
trim
Parses the parser surrounded by the given parser (typically whitespace).
trim(parser: Parser<any>): Parser<T>
The parser to run before and after this parser (typically whitespace)
trimLeft
Parses this parser after the given parser.
trimLeft(parser: Parser<any>): Parser<T>
The parser to run before this parser
trimRight
Parses this parser before the given parser.
trimRight(parser: Parser<any>): Parser<T>
The parser to run after this parser
Error Handling
expect
Adds a semantic expectation message to the parser for better error reporting.
expect(description: string): Parser<T>
The description for the expectation (will be prefixed with “Expected ”)
A new parser with the expectation message added
const openParen = char('(').expect("opening parenthesis");
// On failure: "Expected opening parenthesis"
label
Adds a label to this parser for better error messages.
label(name: string): Parser<T>
The label name to add to the context stack
A new parser with the label added
Control Flow
commit
Commits to the current parsing path, preventing backtracking beyond this point.
A new parser that sets the commit flag after successful parsing
// Use commit after matching a keyword to ensure specific error messages
const ifStatement = parser(function* () {
yield* keyword("if");
yield* commit(); // After seeing "if", we know it's an if statement
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 };
});
// Input: "if x > 5 {}" (missing parentheses)
// Without commit: "Expected if, while, or assignment"
// With commit: "Expected opening parenthesis after 'if'"
atomic
Creates an atomic parser that either fully succeeds or resets to the original state.
A new parser that resets state on failure
// Without atomic - partial consumption on failure
const badParser = parser(function* () {
yield* string("foo");
yield* string("bar"); // If this fails, "foo" is already consumed
});
// With atomic - no consumption on failure
const goodParser = parser(function* () {
yield* string("foo");
yield* string("bar"); // If this fails, we reset to before "foo"
}).atomic();
Utility Methods
tap
Adds a tap point to observe the current state and result during parsing. Useful for debugging.
tap(
callback: (args: { state: ParserState; result: ParserOutput<T> }) => void
): Parser<T>
callback
(args: { state: ParserState; result: ParserOutput<T> }) => void
required
Function called with current state and result
The same parser with the tap point added
const parser = parser(function* () {
const name = yield* identifier();
yield* char(':');
const value = yield* number();
return { name, value };
});
parser.tap(({ state, result }) => {
console.log(`Parsed ${result} at position ${state.offset}`);
});
spanned
Wraps the parser result with span information.
spanned(): Parser<Spanned<T>>
A parser that produces a tuple of [value, span]
const parser = identifier().spanned();
const [value, span] = parser.parseOrThrow("hello");
// value = "hello"
// span = { offset: 0, line: 1, column: 1, length: 5, source: "hello" }