parserator
Version:
An elegant parser combinators library for Typescript
486 lines (476 loc) • 21.5 kB
text/typescript
declare class Left<L, R = never> {
readonly left: L;
readonly _tag = "Left";
constructor(left: L);
[Symbol.iterator](): Generator<Either<R, L>, R, any>;
}
declare class Right<R, L> {
readonly right: R;
readonly _tag = "Right";
constructor(right: R);
[Symbol.iterator](): Generator<Either<R, L>, R, any>;
}
type Either<R, L> = Left<L, R> | Right<R, L>;
declare const Either: {
left<L, R = never>(l: L): Either<R, L>;
right<R, L = never>(r: R): Either<R, L>;
isLeft<R, L>(either: Either<R, L>): either is Left<L, R>;
isRight<R, L>(either: Either<R, L>): either is Right<R, L>;
match<R, L, T>(either: Either<R, L>, patterns: {
onLeft: (left: L) => T;
onRight: (right: R) => T;
}): T;
gen<R, L>(f: () => Generator<Either<any, L>, R, any>): Either<R, L>;
};
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
type Last<T> = T extends [...any[], infer L] ? L : never;
type ParserContext<Ctx = {}> = Prettify<Ctx & {
debug?: boolean;
source: string;
}>;
type ParserOptions = {
name?: string;
};
declare class ParserError {
message: string;
expected: string[];
found?: string | undefined;
constructor(message: string, expected: string[], found?: string | undefined);
}
type ParserOutput<T, Ctx = {}> = {
state: ParserState<Ctx>;
result: Either<T, ParserError>;
};
type SourcePosition = {
line: number;
column: number;
offset: number;
};
type ParserState<Ctx = {}> = {
remaining: string;
pos: SourcePosition;
context: ParserContext<Ctx>;
};
/**
* Utility object containing static methods for creating and manipulating parser state.
*/
declare const State: {
/**
* Creates a new parser state from an input string.
*
* @param input - The input string to parse
* @returns A new parser state initialized at the start of the input
*/
fromInput<Ctx = {}>(input: string, context: ParserContext<Ctx>): ParserState<Ctx>;
/**
* Creates a new state by consuming n characters from the current state.
*
* @param state - The current parser state
* @param n - Number of characters to consume
* @returns A new state with n characters consumed and position updated
* @throws Error if attempting to consume more characters than remaining
*/
consume<Ctx = {}>(state: ParserState<Ctx>, n: number): ParserState<Ctx>;
/**
* Creates a new state by consuming a specific string from the current state.
*
* @param state - The current parser state
* @param str - The string to consume
* @returns A new state with the string consumed and position updated
* @throws Error if the input doesn't start with the specified string
*/
consumeString<Ctx = {}>(state: ParserState<Ctx>, str: string): ParserState<Ctx>;
move<Ctx = {}>(state: ParserState<Ctx>, moveBy: number): ParserState<Ctx>;
/**
* Creates a new state by consuming characters while a predicate is true.
*
* @param state - The current parser state
* @param predicate - Function that tests each character
* @returns A new state with matching characters consumed
*/
consumeWhile<Ctx = {}>(state: ParserState<Ctx>, predicate: (char: string) => boolean): ParserState<Ctx>;
/**
* Gets the next n characters from the input without consuming them.
*
* @param state - The current parser state
* @param n - Number of characters to peek (default: 1)
* @returns The next n characters as a string
*/
peek<Ctx = {}>(state: ParserState<Ctx>, n?: number): string;
/**
* Checks if the parser has reached the end of input.
*
* @param state - The current parser state
* @returns True if at end of input, false otherwise
*/
isAtEnd<Ctx = {}>(state: ParserState<Ctx>): boolean;
printPosition<Ctx = {}>(state: ParserState<Ctx>): string;
};
type BindResult<T, K extends string, B> = Prettify<T & {
[k in K]: B;
}>;
declare class Parser<T, Ctx = {}> {
/**
* @internal
*/
run: (state: ParserState<Ctx>) => ParserOutput<T, Ctx>;
options?: ParserOptions | undefined;
constructor(
/**
* @internal
*/
run: (state: ParserState<Ctx>) => ParserOutput<T, Ctx>, options?: ParserOptions | undefined);
name(name: string): this;
static succeed<T, Ctx = {}>(value: T, state: ParserState<Ctx>): ParserOutput<T, Ctx>;
static fail<Ctx = {}>(error: {
message: string;
expected?: string[];
found?: string;
}, state: ParserState<Ctx>): ParserOutput<never, Ctx>;
static error<Ctx = {}>(message: string, expected?: string[], stateCallback?: (state: ParserState<Ctx>) => ParserState<Ctx>): Parser<never, Ctx>;
/**
* Adds an error message to the parser
* @param makeMessage - A function that returns an error message
* @returns A new parser with the error message added
*/
withError(makeMessage: (errorCtx: {
error: ParserError;
state: ParserState<Ctx>;
}) => string): Parser<T, Ctx>;
parse(input: string, context?: ParserContext<Ctx>): ParserOutput<T, Ctx>;
withTrace(label: string): Parser<T, Ctx>;
parseOrError(input: string, context?: ParserContext<Ctx>): ParserError | T;
parseOrThrow(input: string, context?: ParserContext<Ctx>): T;
map<B>(f: (a: T) => B): Parser<B, Ctx>;
flatMap<B>(f: (a: T) => Parser<B, Ctx>): Parser<B, Ctx>;
static pure: <A>(a: A) => Parser<A>;
static Do: Parser<{}, {}>;
/**
* Creates a new parser that lazily evaluates the given function.
* This is useful for creating recursive parsers.
*
* @param fn - A function that returns a parser
* @returns A new parser that evaluates the function when parsing
* @template T The type of value produced by the parser
*
* @example
* ```ts
* // Create a recursive parser for nested parentheses
* const parens: Parser<string> = Parser.lazy(() =>
* between(
* char('('),
* char(')'),
* parens
* )
* )
* ```
*/
static lazy<T>(fn: () => Parser<T>): Parser<T>;
zip<B>(parserB: Parser<B, Ctx>): Parser<[T, B], Ctx>;
then<B>(parserB: Parser<B, Ctx>): Parser<B, Ctx>;
zipRight: <B>(parserB: Parser<B, Ctx>) => Parser<B, Ctx>;
thenDiscard<B>(parserB: Parser<B, Ctx>): Parser<T, Ctx>;
zipLeft: <B>(parserB: Parser<B, Ctx>) => Parser<T, Ctx>;
bind<K extends string, B>(k: K, other: Parser<B, Ctx> | ((a: T) => Parser<B, Ctx>)): Parser<BindResult<T, K, B>, Ctx>;
[Symbol.iterator](): Generator<Parser<T, Ctx>, T, any>;
/**
* Adds a tap point to observe the current state and result during parsing.
* Useful for debugging parser behavior.
*
* @param callback - Function called with current state and result
* @returns The same parser with the tap point added
*/
tap(callback: (args: {
state: ParserState<Ctx>;
result: ParserOutput<T, Ctx>;
}) => void): Parser<T, Ctx>;
static gen<T, Ctx = unknown>(f: () => Generator<Parser<any, Ctx>, T, any>): Parser<T, Ctx>;
trim(parser: Parser<any, Ctx>): Parser<T, Ctx>;
trimLeft(parser: Parser<any, Ctx>): Parser<T, Ctx>;
trimRight(parser: Parser<any, Ctx>): Parser<T, Ctx>;
}
declare function parser<T, Ctx = unknown>(f: () => Generator<Parser<any, Ctx>, T, any>): Parser<T, Ctx>;
type Chain<Ctx = {}> = {
<T, U>(parser: Parser<T, Ctx>, fn1: (value: T) => Parser<U>): Parser<U>;
<T1, T2, T3>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>): Parser<T3>;
<T1, T2, T3, T4>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>): Parser<T4>;
<T1, T2, T3, T4, T5>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>, fn4: (value: T4) => Parser<T5, Ctx>): Parser<T5>;
<T1, T2, T3, T4, T5, T6>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>, fn4: (value: T4) => Parser<T5, Ctx>, fn5: (value: T5) => Parser<T6, Ctx>): Parser<T6>;
<T1, T2, T3, T4, T5, T6, T7>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>, fn4: (value: T4) => Parser<T5, Ctx>, fn5: (value: T5) => Parser<T6, Ctx>, fn6: (value: T6) => Parser<T7, Ctx>): Parser<T7>;
<T1, T2, T3, T4, T5, T6, T7, T8>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>, fn4: (value: T4) => Parser<T5, Ctx>, fn5: (value: T5) => Parser<T6, Ctx>, fn6: (value: T6) => Parser<T7, Ctx>, fn7: (value: T7) => Parser<T8, Ctx>): Parser<T8>;
<T1, T2, T3, T4, T5, T6, T7, T8, T9>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>, fn4: (value: T4) => Parser<T5, Ctx>, fn5: (value: T5) => Parser<T6, Ctx>, fn6: (value: T6) => Parser<T7, Ctx>, fn7: (value: T7) => Parser<T8, Ctx>, fn8: (value: T8) => Parser<T9, Ctx>): Parser<T9>;
<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(parser: Parser<T1, Ctx>, fn1: (value: T1) => Parser<T2, Ctx>, fn2: (value: T2) => Parser<T3, Ctx>, fn3: (value: T3) => Parser<T4, Ctx>, fn4: (value: T4) => Parser<T5, Ctx>, fn5: (value: T5) => Parser<T6, Ctx>, fn6: (value: T6) => Parser<T7, Ctx>, fn7: (value: T7) => Parser<T8, Ctx>, fn8: (value: T8) => Parser<T9, Ctx>, fn9: (value: T9) => Parser<T10, Ctx>): Parser<T10>;
};
declare const chain: <Ctx = {}>(parser: Parser<any, Ctx>, ...fns: Array<(value: any) => Parser<any, Ctx>>) => Chain<Ctx>;
/**
* Creates a parser that looks ahead in the input stream without consuming any input.
* The parser will succeed with the result of the given parser but won't advance the input position.
*
* @param parser - The parser to look ahead with
* @returns A new parser that peeks at the input without consuming it
* ```ts
* const parser = lookAhead(char('a'))
* parser.run('abc') // Right(['a', {...}])
* // Input position remains at 'abc', 'a' is not consumed
* ```
*/
declare function lookAhead<T, Ctx = {}>(parser: Parser<T, Ctx>): Parser<T | undefined, Ctx>;
/**
* Creates a parser that succeeds only if the given parser fails to match.
* If the parser succeeds, this parser fails with an error message.
*
* @param parser - The parser that should not match
* @returns A new parser that succeeds only if the input parser fails
* ```ts
* 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
* ```
*/
declare function notFollowedBy<T, Ctx = {}>(parser: Parser<T, Ctx>): Parser<boolean, Ctx>;
/**
* Creates a parser that matches an exact string in the input.
*
* @param str - The string to match
* @returns A parser that matches and consumes the exact string
* ```ts
* const parser = string("hello")
* parser.run("hello world") // Right(["hello", {...}])
* parser.run("goodbye") // Left(error)
* ```
*/
declare const string: <Ctx = {}>(str: string) => Parser<string, Ctx>;
/**
* Creates a parser that matches an exact string literal type.
* Similar to string parser but preserves the literal type information.
*
* @param str - The string literal to match
* @returns A parser that matches and consumes the exact string with preserved type
* ```ts
* const parser = narrowedString("hello") // Parser<"hello">
* parser.run("hello world") // Right(["hello", {...}])
* parser.run("goodbye") // Left(error)
* ```
*/
declare function narrowedString<const T extends string, Ctx>(str: T): Parser<T, Ctx>;
/**
* Creates a parser that matches a single character.
*
* @param ch - The character to match
* @returns A parser that matches and consumes a single character
* ```ts
* const parser = char("a")
* parser.run("abc") // Right(["a", {...}])
* parser.run("xyz") // Left(error)
* ```
*/
declare const char: <T extends string, Ctx = {}>(ch: T) => Parser<T, Ctx>;
/**
* A parser that matches any single alphabetic character (a-z, A-Z).
*
* ```ts
* const parser = alphabet
* parser.run("abc") // Right(["a", {...}])
* parser.run("123") // Left(error)
* ```
*/
declare const alphabet: Parser<string, {}>;
/**
* A parser that matches any single digit character (0-9).
*
* ```ts
* const parser = digit
* parser.run("123") // Right(["1", {...}])
* parser.run("abc") // Left(error)
* ```
*/
declare const digit: Parser<string, {}>;
/**
* Creates a parser that matches zero or more occurrences of elements separated by a separator.
*
* @param sepParser - Parser for the separator between elements
* @param parser - Parser for the elements
* @returns A parser that produces an array of matched elements
*
* ```ts
* const parser = sepBy(char(','), digit)
* parser.run("1,2,3") // Right([["1", "2", "3"], {...}])
* parser.run("") // Right([[], {...}])
* ```
*/
declare function sepBy<S, T, Ctx>(sepParser: Parser<S, Ctx>, parser: Parser<T, Ctx>): Parser<T[], Ctx>;
/**
* Creates a parser that matches content between two string delimiters.
*
* @param start - The opening delimiter string
* @param end - The closing delimiter string
* @param parser - The parser for the content between delimiters
* @returns A parser that matches content between delimiters
*
* ```ts
* const parser = between('(', ')', digit)
* parser.run('(5)') // Right(['5', {...}])
* parser.run('5') // Left(error)
* ```
*/
declare function between<T, Ctx = {}>(start: Parser<any, Ctx>, end: Parser<any, Ctx>, parser: Parser<T, Ctx>): Parser<any, Ctx>;
declare function anyChar<Ctx = {}>(): Parser<string, Ctx>;
/**
* Creates a parser that matches zero or more occurrences of the input parser.
*
* @param parser - The parser to repeat
* @returns A parser that produces an array of all matches
*/
declare const many0: <S, T, Ctx = {}>(parser: Parser<T, Ctx>, separator?: Parser<S, Ctx>) => Parser<T[], Ctx>;
/**
* Creates a parser that matches one or more occurrences of the input parser.
*
* @param parser - The parser to repeat
* @returns A parser that produces an array of all matches (at least one)
*/
declare const many1: <S, T, Ctx>(parser: Parser<T, Ctx>, separator?: Parser<S, Ctx>) => Parser<T[], Ctx>;
/**
* Creates a parser that matches at least n occurrences of the input parser.
*
* @param parser - The parser to repeat
* @param n - Number of required repetitions
* @returns A parser that produces an array of at least n matches
*/
declare const manyN: <S, T, Ctx>(parser: Parser<T, Ctx>, n: number, separator?: Parser<S, Ctx>) => Parser<T[], Ctx>;
/**
* Creates a parser that matches exactly n occurrences of the input parser.
*
* @param parser - The parser to repeat
* @param n - Number of required repetitions
* @param separator - Optional parser to match between occurrences
* @returns A parser that produces an array of exactly n matches
*/
declare const manyNExact: <S, T, Ctx>(parser: Parser<T, Ctx>, n: number, separator?: Parser<S, Ctx>) => Parser<T[], Ctx>;
/**
* Creates a parser that skips zero or more occurrences of the input parser.
*
* @param parser - The parser to skip
* @returns A parser that skips all matches
*/
declare const skipMany0: <T, Ctx = {}>(parser: Parser<T, Ctx>) => Parser<undefined, Ctx>;
/**
* Creates a parser that skips one or more occurrences of the input parser.
*
* @param parser - The parser to skip
* @returns A parser that skips all matches (requires at least one)
*/
declare const skipMany1: <T, Ctx>(parser: Parser<T, Ctx>) => Parser<undefined, Ctx>;
/**
* Creates a parser that skips exactly n occurrences of the input parser.
*
* @param parser - The parser to skip
* @param n - Number of required repetitions to skip
* @returns A parser that skips exactly n matches
*/
declare const skipManyN: <T, Ctx>(parser: Parser<T, Ctx>, n: number) => Parser<undefined, Ctx>;
/**
* Creates a parser that skips input until the given parser succeeds.
*
* @param parser - The parser to look for
* @returns A parser that skips input until a match is found
*/
declare function skipUntil<T, Ctx = {}>(parser: Parser<T, Ctx>): Parser<undefined, Ctx>;
/**
* Creates a parser that takes input until the given parser succeeds.
*
* @param parser - The parser to look for
* @returns A parser that takes input until a match is found
*/
declare function takeUntil<T, Ctx = {}>(parser: Parser<T, Ctx>): Parser<string, Ctx>;
/**
* Creates a parser that takes input until the given character is found.
*
* @param char - The character to look for
* @returns A parser that takes input until the character is found
*/
declare function parseUntilChar<Ctx = {}>(char: string): Parser<string, Ctx>;
/**
* A parser that skips any number of space characters.
*/
declare const skipSpaces: Parser<undefined, {}>;
/**
* Creates a parser that tries multiple parsers in order until one succeeds.
*
* @param parsers - Array of parsers to try
* @returns A parser that succeeds if any of the input parsers succeed
*/
declare function or<Parsers extends Parser<any, any>[], Ctx = {}>(...parsers: Parsers): Parser<Parsers[number] extends Parser<infer T, Ctx> ? T : never, Ctx>;
/**
* Creates a parser that optionally matches the input parser.
* If the parser fails, returns undefined without consuming input.
*
* @param parser - The parser to make optional
* @returns A parser that either succeeds with a value or undefined
*/
declare function optional<T, Ctx = {}>(parser: Parser<T, Ctx>): Parser<T | undefined, Ctx>;
type LastParser<T, Ctx = {}> = T extends [...any[], Parser<infer L, Ctx>] ? L : never;
/**
* Creates a parser that runs multiple parsers in sequence.
* Returns the result of the last parser in the sequence.
*
* @param parsers - Array of parsers to run in sequence
* @returns A parser that succeeds if all parsers succeed in sequence
*/
declare function sequence<Parsers extends Parser<any>[], Ctx = {}>(parsers: [...Parsers]): Parser<LastParser<Parsers, Ctx>, Ctx>;
/**
* Creates a parser that matches input against a regular expression.
* The regex must match at the start of the input.
*
* @param re - The regular expression to match against
* @returns A parser that matches the regex pattern
*/
declare const regex: <Ctx = {}>(re: RegExp) => Parser<string, Ctx>;
declare function zip<A, B>(parserA: Parser<A>, parserB: Parser<B>): Parser<[A, B]>;
declare function then<A, B>(parserA: Parser<A>, parserB: Parser<B>): Parser<B>;
declare const zipRight: typeof then;
declare function thenDiscard<A, B>(parserA: Parser<A>, parserB: Parser<B>): Parser<A>;
declare const zipLeft: typeof thenDiscard;
/**
* Creates a parser that takes input until the given parser would succeed, without consuming the parser.
*
* @param parser - The parser to look for
* @returns A parser that takes input until before a match would be found
*/
declare function takeUpto<T>(parser: Parser<T>): Parser<string>;
/**
* Creates a debug output for a parser's current state and result
*/
declare function debugState<Ctx = {}>(label: string, state: ParserState<Ctx>, result: ParserOutput<any, Ctx>, options?: {
inputPreviewLength?: number;
separator?: string;
}): void;
/**
* Adds debug output to a parser
*/
declare function debug<T, Ctx = {}>(parser: Parser<T, Ctx>, label: string): Parser<T, Ctx>;
/**
* Creates a parser that logs its input state and continues
*/
declare function trace<Ctx = {}>(label: string): Parser<void, Ctx>;
/**
* Adds breakpoints to a parser for step-by-step debugging
*/
declare function breakpoint<T, Ctx = {}>(parser: Parser<T, Ctx>, label: string): Parser<T, Ctx>;
/**
* Times how long a parser takes to run
*/
declare function benchmark<T, Ctx = {}>(parser: Parser<T, Ctx>, label: string): Parser<T, Ctx>;
declare function printPosition(position: SourcePosition): string;
declare function printArrow(position: SourcePosition): string;
declare function printErrorContext<Ctx = {}>(state: ParserState<Ctx>, message?: string): string;
declare function printErrorLine<Ctx = {}>(state: ParserState<Ctx>): string;
declare function printPositionWithOffset(position: SourcePosition): string;
declare function getErrorLine<Ctx = {}>(error: ParserError, state: ParserState<Ctx>): string;
declare const peekState: Parser<ParserState<{}>, {}>;
declare const peekRemaining: Parser<string, {}>;
declare const peekAhead: (n: number) => Parser<string, {}>;
declare const peekLine: Parser<string, {}>;
declare const peekUntil: (ch: string) => Parser<string, {}>;
export { type Chain, Either, type Last, Left, Parser, type ParserContext, ParserError, type ParserOptions, type ParserOutput, type ParserState, type Prettify, Right, type SourcePosition, State, alphabet, anyChar, benchmark, between, breakpoint, chain, char, debug, debugState, digit, getErrorLine, lookAhead, many0, many1, manyN, manyNExact, narrowedString, notFollowedBy, optional, or, parseUntilChar, parser, peekAhead, peekLine, peekRemaining, peekState, peekUntil, printArrow, printErrorContext, printErrorLine, printPosition, printPositionWithOffset, regex, sepBy, sequence, skipMany0, skipMany1, skipManyN, skipSpaces, skipUntil, string, takeUntil, takeUpto, then, thenDiscard, trace, zip, zipLeft, zipRight };