UNPKG

parserator

Version:

An elegant parser combinators library for Typescript

1,367 lines (1,362 loc) 59.5 kB
/** * Represents a location span in source code with position and size information. * @example * ```typescript * const span: Span = { * offset: 10, * length: 5, * line: 2, * column: 3 * }; * ``` */ type Span = { /** Byte offset from the start of the source */ offset: number; /** Length of the span in bytes */ length: number; /** Line number (1-indexed) */ line: number; /** Column number (1-indexed) */ column: number; }; /** * Creates a Span from parser state and optional length. * @param state - Parser state containing position information * @param length - Length of the span (defaults to 0) * @returns A new Span object * @example * ```typescript * const state = { pos: { offset: 10, line: 2, column: 3 } }; * const span = Span(state, 5); * // Returns: { offset: 10, length: 5, line: 2, column: 3 } * ``` */ declare function Span(state: { pos: { offset: number; line: number; column: number; }; }, length?: number): Span; type ExpectedParseError = { tag: "Expected"; span: Span; items: string[]; context: string[]; found?: string; }; type UnexpectedParseError = { tag: "Unexpected"; span: Span; found: string; context: string[]; hints?: string[]; }; type CustomParseError = { tag: "Custom"; span: Span; message: string; hints?: string[]; context: string[]; }; type FatalParseError = { tag: "Fatal"; span: Span; message: string; context: string[]; }; /** * Union type representing all possible parsing errors. * Each error type has a discriminant tag for pattern matching. * @example * ```typescript * function handleError(error: ParseError) { * switch (error.tag) { * case "Expected": * console.log(`Expected ${error.items.join(" or ")}`); * break; * case "Unexpected": * console.log(`Unexpected ${error.found}`); * break; * // ... handle other cases * } * } * ``` */ type ParseError = CustomParseError | ExpectedParseError | UnexpectedParseError | FatalParseError; /** * Factory functions for creating different types of ParseError objects. * Provides a convenient API for constructing errors without manually setting tags. * @example * ```typescript * const span = Span(state); * * // Create an expected error * const expectedError = ParseError.expected({ * span, * items: ["identifier", "keyword"], * context: ["function declaration"], * found: "number" * }); * * // Create a custom error * const customError = ParseError.custom({ * span, * message: "Invalid syntax", * context: ["expression"], * hints: ["Try using parentheses"] * }); * ``` */ declare const ParseError: { /** Creates an ExpectedParseError for when specific tokens were expected */ expected: (params: Omit<ExpectedParseError, "tag">) => ExpectedParseError; /** Creates an UnexpectedParseError for when an unexpected token was found */ unexpected: (params: Omit<UnexpectedParseError, "tag">) => UnexpectedParseError; /** Creates a CustomParseError with a custom message */ custom: (params: Omit<CustomParseError, "tag">) => CustomParseError; /** Creates a FatalParseError that cannot be recovered from */ fatal: (params: Omit<FatalParseError, "tag">) => FatalParseError; }; /** * A collection of parsing errors with formatting and analysis capabilities. * Automatically determines the primary (furthest) error for reporting. * @example * ```typescript * const errors = [ * ParseError.expected({ span: spanAt10, items: ["("], context: [] }), * ParseError.unexpected({ span: spanAt15, found: ")", context: [] }) * ]; * const bundle = new ParseErrorBundle(errors, sourceCode); * * console.log(bundle.toString()); // Shows the furthest error * console.log(bundle.format("ansi")); // Formatted with colors * ``` */ declare class ParseErrorBundle { errors: ParseError[]; source: string; /** * Creates a new ParseErrorBundle. * @param errors - Array of parsing errors * @param source - The original source code being parsed * @returns {ParseErrorBundle} A new ParseErrorBundle instance containing the errors and source */ constructor(errors: ParseError[], source: string); /** * Gets the primary error (the one that occurred furthest in the input). * This is typically the most relevant error to show to the user. * @returns {ParseError} The error with the highest offset position */ get primary(): ParseError; /** * Gets all errors that occurred at the same position as the primary error. * Useful when multiple parse attempts failed at the same location. * @returns {ParseError[]} Array of errors at the furthest position */ get primaryErrors(): ParseError[]; /** * Converts the primary error to a simple string representation. * @returns {string} A human-readable error message */ toString(): string; /** * Formats the error bundle using the specified formatter. * @param format - The output format ("plain", "ansi", "html", or "json") * @returns {string} Formatted error message with context and highlighting */ format(format?: "plain" | "ansi" | "html" | "json"): string; } 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 = any> { 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>(onLeft: (left: L) => T, onRight: (right: R) => T): (either: Either<R, L>) => T; gen<R, L>(f: () => Generator<Either<any, L>, R, any>): Either<R, L>; }; type Spanned<T> = [value: T, span: Span]; /** * Represents the output of a parser operation, containing both the updated state * and the parsing result (either success or error). * @template T - The type of the successfully parsed value * @example * ```typescript * const output: ParserOutput<string> = { * state: newState, * result: Either.right("parsed value") * }; * ``` */ type ParserOutput<T> = { /** The parser state after the operation */ state: ParserState; /** Either a successful result of type T or a ParseErrorBundle */ result: Either<T, ParseErrorBundle>; }; /** * Factory function for creating ParserOutput objects. * @template T - The type of the successfully parsed value * @param state - The parser state after the operation * @param result - Either a successful result or error bundle * @returns A new ParserOutput object * @example * ```typescript * import { Either } from "./either"; * * const successOutput = ParserOutput(newState, Either.right("success")); * const errorOutput = ParserOutput(oldState, Either.left(errorBundle)); * ``` */ declare const ParserOutput: <T>(state: ParserState, result: Either<T, ParseErrorBundle>) => ParserOutput<T>; /** * Represents a position within source code with line, column, and byte offset. * All values are 1-indexed for human readability. * @example * ```typescript * const position: SourcePosition = { * line: 3, // Third line * column: 15, // 15th character on that line * offset: 42 // 42nd character from start of input * }; * ``` */ type SourcePosition = { /** Line number (1-indexed) */ line: number; /** Column number (1-indexed) */ column: number; /** Byte offset from start of input (0-indexed) */ offset: number; }; declare class SourcePosition_ { line: number; column: number; offset: number; constructor(line: number, column: number, offset: number); } /** * Represents the complete state of a parser at any point during parsing. * Contains the input being parsed, current position, and optional debugging/context information. * @example * ```typescript * const state: ParserState = { * remaining: "hello world", * pos: { line: 1, column: 1, offset: 0 }, * source: "hello world", * debug: true, * labelStack: ["expression", "identifier"], * committed: false * }; * ``` */ type ParserState = { /** The portion of input that hasn't been consumed yet */ remaining: string; /** Current position in the source code */ pos: SourcePosition; /** The complete original input string */ source: string; /** Whether debug mode is enabled for detailed error reporting */ debug?: boolean; /** Stack of parsing context labels for error reporting */ labelStack?: string[]; /** Whether the parser has committed to this parse path */ committed?: boolean; }; /** * 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(input: string): ParserState; /** * 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(state: ParserState, n: number): ParserState; /** * 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(state: ParserState, str: string): ParserState; /** * Creates a new state by moving to a specific offset position in the source. * Resets to the beginning and then consumes to the target position. * @param state - The current parser state * @param moveBy - Number of characters to move forward from current position * @returns A new state at the target position */ move(state: ParserState, moveBy: number): ParserState; /** * 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(state: ParserState, predicate: (char: string) => boolean): ParserState; /** * 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(state: ParserState, 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(state: ParserState): boolean; /** * Creates a human-readable string representation of the current parser position. * @param state - The current parser state * @returns A formatted string showing line, column, and offset * @example * ```typescript * const posStr = State.printPosition(state); * // Returns: "line 5, column 12, offset 89" * ``` */ printPosition(state: ParserState): string; }; /** * Parser is the core type 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 * - An error describing why the parse failed * * Parsers can be composed using various combinators to build complex parsers * from simple building blocks. * * @template T The type of value this parser produces when successful */ declare class Parser<T> { /** * @internal */ run: (state: ParserState) => ParserOutput<T>; /** * Creates a new Parser instance. * * @param run - The parsing function that takes a parser state and returns a parse result */ constructor( /** * @internal */ run: (state: ParserState) => ParserOutput<T>); /** * Creates a successful parser output with the given value and state. * * This is a low-level helper used internally to construct successful parse results. * It doesn't consume any input and returns the value with the current state unchanged. * * @param value - The value to wrap in a successful result * @param state - The current parser state * @returns {ParserOutput<T>} A successful parser output containing the value * @template T The type of the successful value * @internal */ static succeed<T>(value: T, state: ParserState): ParserOutput<T>; /** * Creates a parser that always succeeds with the given value without consuming any input. * * This is the basic way to inject a value into the parser context. The parser will * succeed immediately with the provided value and won't advance the parser state. * * @param a - The value to lift into the parser context * @returns {Parser<A>} A parser that always succeeds with the given value * @template A The type of the value being lifted * * @example * ```ts * 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 } * }) * ``` */ static lift: <A>(a: A) => Parser<A>; /** * Lifts a binary function into the parser context, applying it to the results of two parsers. * * This is the applicative functor's version of `map` for functions of two arguments. * It runs both parsers in sequence and applies the function to their results if both succeed. * * @param ma - The first parser * @param mb - The second parser * @param f - A function that takes the results of both parsers and produces a new value * @returns {Parser<C>} A parser that applies the function to the results of both input parsers * @template A The type of value produced by the first parser * @template B The type of value produced by the second parser * @template C The type of value produced by applying the function * * @example * ```ts * // 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 } * ``` */ static liftA2: <A, B, C>(ma: Parser<A>, mb: Parser<B>, f: (a: A, b: B) => C) => Parser<C>; /** * Applies a parser that produces a function to a parser that produces a value. * * This is the applicative functor's application operator. It allows you to apply * functions within the parser context, enabling powerful composition patterns. * * @param ma - A parser that produces a value * @param mf - A parser that produces a function from that value type to another type * @returns {Parser<B>} A parser that applies the parsed function to the parsed value * @template A The type of the input value * @template B The type of the output value after function application * * @example * ```ts * // 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 * * // Chain multiple applications * const add = (x: number) => (y: number) => x + y * const parseAdd = Parser.lift(add) * const addParser = Parser.ap( * number, * Parser.ap(number.trimLeft(plus), parseAdd) * ) * addParser.parse("3 + 4") // succeeds with 7 * ``` */ static ap: <A, B>(ma: Parser<A>, mf: Parser<(_: A) => B>) => Parser<B>; /** * Creates a failed parser output with the given error information. * * This is a low-level helper for constructing parse errors. It creates a custom * error with the provided message and optional expected/found information. * * @param error - Error details including message and optional expected/found values * @param state - The parser state where the error occurred * @returns {ParserOutput<never>} A failed parser output containing the error * @internal */ static fail(error: { message: string; expected?: string[]; found?: string; }, state: ParserState): ParserOutput<never>; /** * Creates a parser that always fails with a fatal error. * * Fatal errors are non-recoverable and prevent backtracking in choice combinators. * Use this when you've determined that the input is definitely malformed and trying * other alternatives would be meaningless. * * @param message - The error message to display * @returns {Parser<never>} A parser that always fails with a fatal error * * @example * ```ts * const number = regex(/-?[0-9]+/).map(Number); * const parsePositive = number.flatMap(n => * n > 0 ? Parser.lift(n) : Parser.fatal("Expected positive number") * ) * ``` */ static fatal: (message: string) => Parser<never>; /** * Runs the parser on the given input string and returns the full parser output. * * This method provides access to both the parse result and the final parser state, * which includes information about the remaining unparsed input and position. * * @param input - The string to parse * @returns {ParserOutput<T>} A parser output containing both the result (success or error) and final state * * @example * ```ts * 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 * ``` */ parse(input: string): ParserOutput<T>; /** * Runs the parser on the given input and returns either the parsed value or error bundle. * * This is a convenience method that unwraps the Either result, making it easier * to handle the common case where you just need the value or error without the * full parser state information. * * @param input - The string to parse * @returns {T | ParseErrorBundle} The successfully parsed value of type T, or a ParseErrorBundle on failure * * @example * ```ts * const parser = number(); * const result = parser.parseOrError("42"); * if (result instanceof ParseErrorBundle) { * console.error(result.format()); * } else { * console.log(result); // 42 * } * ``` */ parseOrError(input: string): T | ParseErrorBundle; /** * Runs the parser on the given input and returns the parsed value or throws an error. * * This method is useful when you're confident the parse will succeed or want to * handle parse errors as exceptions. The thrown error is a ParseErrorBundle which * contains detailed information about what went wrong. * * @param input - The string to parse * @returns {T} The successfully parsed value of type T * @throws {ParseErrorBundle} Thrown when parsing fails * * @example * ```ts * const parser = number(); * try { * const value = parser.parseOrThrow("42"); * console.log(value); // 42 * } catch (error) { * if (error instanceof ParseErrorBundle) { * console.error(error.format()); * } * } * ``` */ parseOrThrow(input: string): T; /** * Transforms the result of this parser by applying a function to the parsed value. * * This is the functor map operation. If the parser succeeds, the function is applied * to the result. If the parser fails, the error is propagated unchanged. The input * is not consumed if the transformation fails. * * @param f - A function that transforms the parsed value * @returns {Parser<B>} A new parser that produces the transformed value * @template B The type of the transformed value * * @example * ```ts * // 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" } * ``` */ map<B>(f: (a: T) => B): Parser<B>; /** * Chains this parser with another parser that depends on the result of this one. * * This is the monadic bind operation (also known as chain or andThen). It allows * you to create a parser whose behavior depends on the result of a previous parse. * This is essential for context-sensitive parsing where later parsing decisions * depend on earlier results. * * @param f - A function that takes the parsed value and returns a new parser * @returns {Parser<B>} A new parser that runs the second parser after the first succeeds * @template B The type of value produced by the resulting parser * * @example * ```ts * // 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" }) * ); * ``` */ flatMap<B>(f: (a: T) => Parser<B>): Parser<B>; /** * Creates a parser that always succeeds with the given value without consuming input. * * This is an alias for `Parser.lift` that follows the monadic naming convention. * It's the "return" or "pure" operation for the Parser monad, injecting a plain * value into the parser context. * * @param a - The value to wrap in a successful parser * @returns {Parser<A>} A parser that always succeeds with the given value * @template A The type of the value being lifted * * @example * ```ts * // Always succeed with a constant value * const always42 = Parser.pure(42); * always42.parse("any input") // succeeds with 42 * * // Use in flatMap to wrap values * const parser = number().flatMap(n => * n > 0 ? Parser.pure(n) : Parser.fail({ message: "Must be positive" }) * ); * ``` */ static pure: <A>(a: A) => Parser<A>; /** * 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 {Parser<T>} 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>; /** * Combines this parser with another parser, returning both results as a tuple. * * This is a fundamental sequencing operation that runs two parsers in order. * If either parser fails, the entire operation fails. The results are returned * as a tuple containing both parsed values. * * @param parserB - The second parser to run after this one * @returns {Parser<[T, B]>} A parser that produces a tuple of both results * @template B The type of value produced by the second parser * * @example * ```ts * // 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] * * // Combine multiple parsers * const triple = number() * .zip(number().trimLeft(comma)) * .zip(number().trimLeft(comma)) * .map(([[a, b], c]) => [a, b, c]); * triple.parse("1, 2, 3") // succeeds with [1, 2, 3] * ``` */ zip<B>(parserB: Parser<B>): Parser<[T, B]>; /** * Sequences this parser with another, keeping only the second result. * * This is useful when you need to parse something but only care about what * comes after it. The first parser must succeed for the second to run, but * its result is discarded. * * @param parserB - The parser whose result will be kept * @returns {Parser<B>} A parser that produces only the second result * @template B The type of value produced by the second parser * * @example * ```ts * // 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 * * // Parse the body after a keyword * const functionBody = keyword("function").then(identifier()).then(block()); * ``` */ then<B>(parserB: Parser<B>): Parser<B>; /** * Alias for `then` - sequences parsers and keeps the right result. * * This alias follows the naming convention from applicative functors where * "zipRight" means to combine two values but keep only the right one. * * @see {@link then} for details and examples */ zipRight: <B>(parserB: Parser<B>) => Parser<B>; /** * Sequences this parser with another, keeping only the first result. * * This is useful when you need to parse something that must be present but * whose value you don't need. Common uses include parsing required delimiters * or terminators. * * @param parserB - The parser to run but whose result will be discarded * @returns {Parser<T>} A parser that produces only the first result * @template B The type of value produced by the second parser (discarded) * * @example * ```ts * // Parse a statement and discard the semicolon * const statement = expression().thenDiscard(char(';')); * statement.parse("x + 1;") // succeeds with the expression, semicolon discarded * * // Parse a quoted string and discard the closing quote * const quotedContent = char('"').then(stringUntil('"')).thenDiscard(char('"')); * * // Parse array elements and discard separators * const element = number().thenDiscard(optional(char(','))); * ``` */ thenDiscard<B>(parserB: Parser<B>): Parser<T>; /** * Alias for `thenDiscard` - sequences parsers and keeps the left result. * * This alias follows the naming convention from applicative functors where * "zipLeft" means to combine two values but keep only the left one. * * @see {@link thenDiscard} for details and examples */ zipLeft: <B>(parserB: Parser<B>) => Parser<T>; /** * Makes this parser usable in generator syntax for cleaner sequential parsing. * * This iterator implementation allows parsers to be used with `yield*` in * generator functions, enabling a more imperative style of parser composition * that can be easier to read for complex sequential parsing. * * @returns {Generator<Parser<T>, T, any>} A generator that yields this parser and returns its result * @internal */ [Symbol.iterator](): Generator<Parser<T>, T, any>; /** * Adds a tap point to observe the current state and result during parsing. * Useful for debugging parser behavior. * * @example * ```ts * 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.pos}`); * }); * ``` * * @param callback - Function called with current state and result * @returns {Parser<T>} The same parser with the tap point added */ tap(callback: (args: { state: ParserState; result: ParserOutput<T>; }) => void): Parser<T>; static gen: <T_1>(f: () => Generator<Parser<any>, T_1, any>) => Parser<T_1>; trim(parser: Parser<any>): Parser<T>; trimLeft(parser: Parser<any>): Parser<T>; trimRight(parser: Parser<any>): Parser<T>; /** * Adds a label to this parser for better error messages * @param name - The label name to add to the context stack * @returns {Parser<T>} A new parser with the label added */ label(name: string): Parser<T>; /** * Helper for creating semantic expectations with both label and error message * @param description - The description for both the label and error message * @returns {Parser<T>} A new parser with both labeling and error message */ expect(description: string): Parser<T>; /** * Helper for creating semantic expectations with both label and error message * @param errorBundle - The error bundle containing the errors to be displayed * @param state - The current parser state * @returns {ParserOutput<never>} A parser output with the error bundle and the current state * @internal */ static failRich(errorBundle: { errors: ParseError[]; }, state: ParserState): ParserOutput<never>; /** * Commits to the current parsing path, preventing backtracking beyond this point. * * Once a parser is committed, if it fails later in the sequence, the error won't * backtrack to try other alternatives in a `choice` or `or` combinator. This leads * to more specific error messages instead of generic "expected one of" errors. * * @returns {Parser<T>} A new parser that sets the commit flag after successful parsing * * @example * ```ts * // 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 } * }) * * // In a choice, commit prevents backtracking * const statement = choice([ * ifStatement, * whileStatement, * assignment * ]) * * // Input: "if x > 5 {}" (missing parentheses) * // Without commit: "Expected if, while, or assignment" * // With commit: "Expected opening parenthesis after 'if'" * ``` * * @example * ```ts * // Commit can be chained with other methods * const jsonObject = char('{') * .commit() // Once we see '{', it must be an object * .then(whitespace) * .then(objectContent) * .expect("valid JSON object") * ``` * * @see {@link commit} - Standalone function version * @see {@link cut} - Alias with Prolog-style naming */ commit: () => Parser<T>; /** * Creates an atomic parser that either fully succeeds or resets to the original state. * * This is useful for "all-or-nothing" parsing where you want to try a complex * parser but not consume any input if it fails. The parser acts as a transaction - * if any part fails, the entire parse is rolled back. * * @returns {Parser<T>} A new parser that resets state on failure * * @example * ```ts * // 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() * ``` * * @example * ```ts * // Useful for trying complex alternatives * const value = or( * // Try to parse as a complex expression * expression.atomic(), * // If that fails completely, try as a simple literal * literal * ) * ``` * * @example * ```ts * // Lookahead parsing without consumption * const startsWithKeyword = or( * string("function").atomic(), * string("const").atomic(), * string("let").atomic() * ).map(() => true).or(Parser.succeed(false)) * ``` * * @see {@link atomic} - Standalone function version */ atomic(): Parser<T>; spanned(): Parser<Spanned<T>>; } declare const parser: <T>(f: () => Generator<Parser<any>, T, any>) => Parser<T>; /** * @fileoverview */ /** * 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 par - The parser to look ahead with * @returns {Parser<T | undefined>} 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 const lookahead: <T>(par: Parser<T>) => Parser<T | undefined>; /** * 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 par - The parser that should not match * @returns {Parser<boolean>} 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>(par: Parser<T>): Parser<boolean>; /** * Creates a parser that matches an exact string in the input. * * @param str - The string to match * @returns {Parser<string>} 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: (str: string) => Parser<string>; /** * 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 {Parser<T>} 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 const narrowedString: <const T extends string>(str: T) => Parser<T>; /** * Creates a parser that matches a single character. * * @param ch - The character to match * @returns {Parser<T>} 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>(ch: T) => Parser<T>; /** * 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 {Parser<T[]>} 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>(parser: Parser<T>, sepParser: Parser<S>): Parser<T[]>; /** * Parses one or more occurrences of a parser separated by another parser. * Requires at least one match of the main parser. * * @param par - The parser for the elements * @param sepParser - The parser for the separator * @returns {Parser<T[]>} A parser that produces a non-empty array of parsed elements * * @example * ```ts * const numbers = sepBy1(number, char(',')) * numbers.parse("1,2,3") // Success: [1, 2, 3] * numbers.parse("") // Error: Expected at least one element * ``` */ declare function sepBy1<S, T>(par: Parser<T>, sepParser: Parser<S>): Parser<T[]>; /** * Creates a parser that matches content between two string delimiters. * * @param start - The opening delimiter string * @param end - The closing delimiter string * @param par - The parser for the content between delimiters * @returns {Parser<T>} A parser that matches content between delimiters * * ```ts * const parser = between(char('('), char(')'), digit) * parser.run('(5)') // Right(['5', {...}]) * parser.run('5') // Left(error) * parser.run('(5') // Left(error: Expected closing delimiter) * ``` */ declare function between<T>(start: Parser<any>, end: Parser<any>, par: Parser<T>): Parser<T>; /** * A parser that matches any single character. * * @returns {Parser<string>} A parser that matches any single character */ declare function anyChar(): Parser<string>; /** * Creates a parser that matches zero or more occurrences of the input parser. * * @param parser - The parser to repeat * @returns {Parser<T[]>} A parser that produces an array of all matches */ declare const many0: <S, T>(parser: Parser<T>, separator?: Parser<S>) => Parser<T[]>; /** * Parses zero or more occurrences of a parser (alias for many0). * * @param parser - The parser to repeat * @returns {Parser<T[]>} A parser that produces an array of parsed elements */ declare const many: <T>(parser: Parser<T>) => Parser<T[]>; /** * Creates a parser that matches one or more occurrences of the input parser. * * @param parser - The parser to repeat * @returns {Parser<T[]>} A parser that produces an array of all matches (at least one) */ declare const many1: <S, T>(parser: Parser<T>, separator?: Parser<S>) => Parser<T[]>; /** * 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 {Parser<T[]>} A parser that produces an array of at least n matches */ declare const manyN: <S, T>(parser: Parser<T>, n: number, separator?: Parser<S>) => Parser<T[]>; /** * 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 {Parser<T[]>} A parser that produces an array of exactly n matches */ declare const manyNExact: <S, T>(par: Parser<T>, n: number, separator?: Parser<S>) => Parser<T[]>; /** * Creates a parser that skips zero or more occurrences of the input parser. * * @param parser - The parser to skip * @returns {Parser<undefined>} A parser that skips all matches */ declare const skipMany0: <T>(parser: Parser<T>) => Parser<undefined>; /** * Creates a parser that skips one or more occurrences of the input parser. * * @param parser - The parser to skip * @returns {Parser<undefined>} A parser that skips all matches (requires at least one) */ declare const skipMany1: <T>(parser: Parser<T>) => Parser<undefined>; /** * 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>(parser: Parser<T>, n: number) => Parser<undefined>; /** * 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>(parser: Parser<T>): Parser<undefined>; /** * 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>(parser: Parser<T>): Parser<string>; /** * 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(char: string): Parser<string>; /** * 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 */ /** * Creates a parser that tries each of the given parsers in order until one succeeds. * * This combinator is commit-aware: if any parser sets the `committed` flag during * parsing, no further alternatives will be tried. This enables better error messages * by preventing backtracking once we've identified the intended parse path. * * @param parsers - Array of parsers to try in order * @returns {Parser<Parsers[number] extends Parser<infer T> ? T : never>} A parser that succeeds with the first successful parser's result * * @example * ```ts * // Basic usage - tries each alternative * const value = or( * numberLiteral, * stringLiteral, * booleanLiteral * ) * ``` * * @example * ```ts * // 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 * ) * * // Input: "if x > 5" (missing parentheses) * // Without commit: "Expected if, while, or assignment" * // With commit: "Expected opening parenthesis" * ``` * * @example * ```ts * // Error accumulation without commit * const config = or( * jsonParser.label("JSON format"), * yamlParser.label("YAML format"), * tomlParser.label("TOML format") * ) * // Errors from all three parsers are accumulated * ``` */ declare function or<Parsers extends Parser<any>[]>(...parsers: Parsers): Parser<Parsers[number] extends Parser<infer T> ? T : never>; /** * 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 {Parser<T | undefined>} A parser that either succeeds with a value or undefined */ declare function optional<T>(parser: Parser<T>): Parser<T | undefined>; type SequenceOutput<T extends Parser<any>[], Acc extends any[] = []> = T["length"] extends 0 ? Acc : T extends [Parser<infer Head extends any>, ...infer Tail extends any[]] ? SequenceOutput<Tail, [...Acc, Head]> : never; /** * Creates a parser that runs multiple parsers in sequence and returns all results. * * @param parsers - Array of parsers to run in sequence * @returns {Parser<SequenceOutput<T>>} A parser that succeeds if all parsers succeed in order, returning a tuple of all results * * @example * ```ts * const parser = sequence([digit, char('-'), digit]) * parser.run('1-2') // Right([['1', '-', '2'], {...}]) * ``` */ declare const sequence: <const T extends any[]>(parsers: T) => Parser<SequenceOutput<T>>; /** * 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 {Parser<string>} A parser that matches the regex pattern */ declare const regex: (re: RegExp) => Parser<string>; 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 parser that commits to the current parsing path, preventing backtracking. * * After calling `commit()`, if parsing fails later in the sequence, the parser won't * backtrack to try alternatives in a `choice` or `or` combinator. This results in * more specific, helpful error messages instead of generic "expected one of" errors. * * @returns {Parser<void>} A parser that sets the commit flag in the parsing context * * @example * ```ts * // Use commit after identifying the type of construct * 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 } * }) * ``` * * @example * ```ts * // Commit in different parsing contexts * const jsonParser = parser(function* () { * const firstChar = yield* peekChar * * if (firstChar === '{') { * yield* char('{') * yield* commit() // Definitely parsing an object * return yield* jsonObject * } else if (firstChar === '[') { * yield* char('[') * yield* commit() // Definitely parsing an array * return yield* jsonArray * } * // ... * }) * ``` * * @example * ```ts * // Commit with error recovery * const statement = choice([ * ifStatement, // Has commit() after "if" * whileStatement, // Has commit() after "while" * forStatement, // Has commit() after "for" * expression // No commit, can always fall back to this * ]) * * // Input: "if (x > 5 { }" (missing closing paren) * // Result: "Expected closing parenthesis" (not "Expected if, while, for, or expression") * ``` * * @see {@link cut} - Alias with Prolog-style naming */ declare function commit(): Parser<void>; /** * Alias for {@link commit} using Prolog-style naming. * * The cut operator (!) in Prolog prevents backtracking, similar to how * this prevents the parser from trying other alternatives after this point. * * @example * ```ts * const prologStyleIf = parser(function* () { * yield* keyword("if") * yield* cut() // Using Prolog-style naming * yield* char('(') * // ... * }) * ``` */ declare const cut: typeof commit; /** * Creates an atomic parser that either fully succeeds or resets to the original state. * * This combinator wraps a parser in a transaction-like behavior. If the parser fails * at a